use env slack_channel_id #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Module Container Tests | |
| env: | |
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | |
| SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }} | |
| permissions: | |
| contents: read | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - "src/modules/**" | |
| - "src/server/core/**" | |
| - ".github/workflows/test-module-container.yml" | |
| workflow_dispatch: | |
| jobs: | |
| slack-start: | |
| name: Slack start message | |
| runs-on: ubuntu-latest | |
| outputs: | |
| ts: ${{ steps.slack-start.outputs.ts }} | |
| steps: | |
| - name: checkout repository | |
| uses: actions/checkout@v6 | |
| - name: send slack start message | |
| id: slack-start | |
| uses: ./.github/actions/slack-run-start | |
| with: | |
| message_prefix: Workflow running | |
| discover-modules: | |
| name: Discover modules to test | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.discover.outputs.matrix }} | |
| has_modules: ${{ steps.discover.outputs.has_modules }} | |
| selected_modules: ${{ steps.discover.outputs.selected_modules }} | |
| module_count: ${{ steps.discover.outputs.module_count }} | |
| steps: | |
| - name: checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: set commit range | |
| id: commit-range | |
| shell: bash | |
| run: | | |
| base_sha="${{ github.event.before }}" | |
| head_sha="${{ github.sha }}" | |
| if [[ "$base_sha" =~ ^0+$ ]]; then | |
| base_sha="$(git rev-parse "$head_sha^")" | |
| fi | |
| echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT" | |
| echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" | |
| - name: discover changed and testable modules | |
| id: discover | |
| env: | |
| BASE_SHA: ${{ steps.commit-range.outputs.base_sha }} | |
| HEAD_SHA: ${{ steps.commit-range.outputs.head_sha }} | |
| GITHUB_EVENT_NAME: ${{ github.event_name }} | |
| run: | | |
| node <<'NODE' | |
| const fs = require("fs"); | |
| const path = require("path"); | |
| const { execSync } = require("child_process"); | |
| const baseSha = process.env.BASE_SHA; | |
| const headSha = process.env.HEAD_SHA; | |
| const eventName = process.env.GITHUB_EVENT_NAME; | |
| let changedFiles = []; | |
| try { | |
| const diff = execSync(`git diff --name-only ${baseSha} ${headSha}`, { encoding: "utf8" }); | |
| changedFiles = diff.split("\n").map((s) => s.trim()).filter(Boolean); | |
| } catch { | |
| changedFiles = []; | |
| } | |
| const changedModules = Array.from( | |
| new Set( | |
| changedFiles | |
| .map((file) => { | |
| const match = file.match(/^src\/modules\/([^/]+)\//); | |
| return match ? match[1] : null; | |
| }) | |
| .filter(Boolean) | |
| ) | |
| ); | |
| const coreChanged = | |
| eventName === "workflow_dispatch" || | |
| changedFiles.some((file) => file.startsWith("src/server/core/")); | |
| const modulesRoot = path.join("src", "modules"); | |
| const testableModules = []; | |
| function hasTestFiles(dirPath) { | |
| const stack = [dirPath]; | |
| while (stack.length > 0) { | |
| const current = stack.pop(); | |
| const entries = fs.readdirSync(current, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| if (entry.name === "node_modules" || entry.name.startsWith(".")) { | |
| continue; | |
| } | |
| const fullPath = path.join(current, entry.name); | |
| if (entry.isDirectory()) { | |
| stack.push(fullPath); | |
| continue; | |
| } | |
| if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| for (const moduleName of fs.readdirSync(modulesRoot)) { | |
| const containerPath = path.join(modulesRoot, moduleName, "container"); | |
| const packagePath = path.join(modulesRoot, moduleName, "container", "package.json"); | |
| if (!fs.existsSync(packagePath)) { | |
| continue; | |
| } | |
| try { | |
| const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8")); | |
| if (pkg?.scripts?.test && hasTestFiles(containerPath)) { | |
| testableModules.push(moduleName); | |
| } | |
| } catch { | |
| // Skip invalid package.json files. | |
| } | |
| } | |
| const selected = coreChanged | |
| ? testableModules | |
| : testableModules.filter((moduleName) => changedModules.includes(moduleName)); | |
| const matrix = selected.map((moduleName) => ({ | |
| module: moduleName, | |
| })); | |
| const out = process.env.GITHUB_OUTPUT; | |
| fs.appendFileSync(out, `matrix=${JSON.stringify(matrix)}\n`); | |
| fs.appendFileSync(out, `has_modules=${matrix.length > 0}\n`); | |
| fs.appendFileSync(out, `selected_modules=${selected.join(",")}\n`); | |
| fs.appendFileSync(out, `module_count=${matrix.length}\n`); | |
| NODE | |
| module-tests: | |
| name: Module Tests | |
| needs: discover-modules | |
| if: needs.discover-modules.outputs.has_modules == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.discover-modules.outputs.matrix) }} | |
| steps: | |
| - name: checkout repository | |
| uses: actions/checkout@v6 | |
| - name: module under test | |
| run: | | |
| echo "Testing module: ${{ matrix.module }}" | |
| - name: setup node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| - name: ensure module test Dockerfile exists | |
| run: | | |
| dockerfile_path="src/modules/${{ matrix.module }}/container/Dockerfile.test" | |
| if [[ -f "$dockerfile_path" ]]; then | |
| echo "Using existing test Dockerfile at $dockerfile_path" | |
| exit 0 | |
| fi | |
| echo "No test Dockerfile found at $dockerfile_path, creating CI test Dockerfile" | |
| cat > "$dockerfile_path" <<EOF_DOCKER | |
| FROM node:22-alpine | |
| WORKDIR /home/node/module | |
| COPY src/server/core ./core | |
| COPY src/modules/${{ matrix.module }}/container ./ | |
| RUN npm install | |
| CMD ["npm", "run", "development"] | |
| EOF_DOCKER | |
| - name: run tests for module | |
| run: | | |
| cd "src/modules/${{ matrix.module }}/container" | |
| npm ci | |
| npm run test | |
| no-modules-changed: | |
| name: No module tests required | |
| needs: discover-modules | |
| if: needs.discover-modules.outputs.has_modules != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: print skip reason | |
| run: | | |
| echo "No changed modules with both a test script and test files were detected. Module count: ${{ needs.discover-modules.outputs.module_count }}" | |
| slack-finish: | |
| name: Slack final status | |
| if: ${{ always() && needs.slack-start.result == 'success' && needs.slack-start.outputs.ts != '' }} | |
| needs: | |
| - slack-start | |
| - discover-modules | |
| - module-tests | |
| - no-modules-changed | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: checkout repository | |
| uses: actions/checkout@v6 | |
| - name: set end timestamp | |
| id: end-time | |
| run: | | |
| echo "value=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" | |
| - name: determine workflow status | |
| id: workflow-status | |
| run: | | |
| workflow_status="success" | |
| if [[ "${{ needs.discover-modules.result }}" == "failure" || "${{ needs.discover-modules.result }}" == "cancelled" || "${{ needs.module-tests.result }}" == "failure" || "${{ needs.module-tests.result }}" == "cancelled" || "${{ needs.no-modules-changed.result }}" == "failure" || "${{ needs.no-modules-changed.result }}" == "cancelled" ]]; then | |
| workflow_status="failure" | |
| fi | |
| echo "status=$workflow_status" >> "$GITHUB_OUTPUT" | |
| - name: update slack message | |
| uses: ./.github/actions/slack-run-finish | |
| with: | |
| ts: ${{ needs.slack-start.outputs.ts }} | |
| status: ${{ steps.workflow-status.outputs.status }} | |
| message_prefix: Workflow | |
| extra_text: ". Modules selected: ${{ needs.discover-modules.outputs.module_count }}" |