fix(docker): drop the postgres image's Go privilege-drop binary (#1714) #4630
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: CI/CD | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: ["main"] | |
| pull_request: | |
| branches: ["**"] | |
| merge_group: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: {} | |
| jobs: | |
| detect-changes: | |
| name: "Detect changes" | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: read | |
| contents: read | |
| outputs: | |
| webapp: ${{ steps.filter.outputs.webapp }} | |
| webapp-image: ${{ steps.filter.outputs.webapp-image == 'true' || steps.webapp_image_source.outputs.webapp-image-source == 'true' }} | |
| application-server: ${{ steps.filter.outputs.application-server }} | |
| tooling: ${{ steps.filter.outputs.tooling }} | |
| application-server-image: ${{ steps.filter.outputs.application-server-image }} | |
| e2e: ${{ steps.filter.outputs.e2e }} | |
| agent-images: ${{ steps.filter.outputs.agent-images }} | |
| postgres-image: ${{ steps.filter.outputs.postgres-image }} | |
| docs: ${{ steps.filter.outputs.docs }} | |
| ci-config: ${{ steps.filter.outputs.ci-config }} | |
| docker-config: ${{ steps.filter.outputs.docker-config }} | |
| pmd-canary: ${{ steps.filter.outputs.pmd-canary }} | |
| version-bump: ${{ steps.version_bump.outputs.changed }} | |
| any-code: ${{ steps.filter.outputs.webapp == 'true' || steps.filter.outputs.application-server == 'true' || steps.filter.outputs.tooling == 'true' || steps.filter.outputs.agent-images == 'true' || steps.filter.outputs.postgres-image == 'true' }} | |
| should_skip: ${{ steps.skip_check.outputs.should_skip }} | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - id: skip_check | |
| uses: fkirc/skip-duplicate-actions@b974a9395958c231af965b70070979a577efa578 # v5.3.2 | |
| with: | |
| do_not_skip: '["workflow_dispatch", "push", "merge_group"]' | |
| - name: Detect version bump | |
| id: version_bump | |
| if: github.event_name == 'push' | |
| run: | | |
| current=$(jq -r .version package.json) | |
| previous=$(git show HEAD^:package.json | jq -r .version) | |
| changed=false | |
| [ "$current" = "$previous" ] || changed=true | |
| echo "changed=$changed" >> "$GITHUB_OUTPUT" | |
| - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 | |
| id: filter | |
| with: | |
| filters: | | |
| webapp: | |
| - 'webapp/**' | |
| - 'docs/images/readme/**' | |
| # check:docs-tokens compares this stylesheet with webapp/src/styles.css, so both | |
| # halves of that pair have to reach the leg that runs it. | |
| - 'docs/src/css/custom.css' | |
| - '.oxlintrc.json' | |
| - '.oxfmtrc.json' | |
| - 'scripts/check-docs-tokens.ts' | |
| - 'scripts/check-docs-tokens.test.ts' | |
| - 'scripts/check-presentational-components.ts' | |
| - 'scripts/check-story-prose.ts' | |
| - 'scripts/check-story-sort.ts' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'pnpm-workspace.yaml' | |
| webapp-image: | |
| - '.oxfmtrc.json' | |
| - 'patches/**' | |
| - 'webapp/public/**' | |
| - 'webapp/docker/**' | |
| - 'webapp/Dockerfile' | |
| - 'webapp/index.html' | |
| - 'webapp/package.json' | |
| - 'webapp/tsconfig.json' | |
| - 'webapp/vite.config.ts' | |
| - 'webapp/vite.shared.ts' | |
| - 'docs/package.json' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'pnpm-workspace.yaml' | |
| application-server: | |
| - 'server/**' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'scripts/run-mvnw.ts' | |
| # check:env validates these deployment inputs against application.yml. | |
| - 'docker/compose.app.yaml' | |
| - 'docker/compose.core.yaml' | |
| - '!server/**/AGENTS.md' | |
| - '!server/**/CLAUDE.md' | |
| tooling: | |
| - 'scripts/**' | |
| - 'docker/agents/**' | |
| # format:load:check and test:load:syntax run on this leg. | |
| - 'load-tests/**' | |
| - 'docs/**' | |
| - 'tsconfig.agents.json' | |
| - 'tsconfig.json' | |
| - '.oxlintrc.json' | |
| - '.oxfmtrc.json' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'pnpm-workspace.yaml' | |
| - '.changeset/**' | |
| - 'commitlint.config.ts' | |
| - '.github/scripts/**' | |
| - '**/AGENTS.md' | |
| - '**/CLAUDE.md' | |
| - '.claude/**' | |
| - '.opencode/**' | |
| - '.agents/**' | |
| - 'opencode.json' | |
| - 'renovate.json' | |
| - 'openapitools.json' | |
| - 'jean.json' | |
| - 'project.code-workspace' | |
| - '.vscode/settings.json' | |
| application-server-image: | |
| - 'server/application/src/main/**' | |
| - 'server/application/pom.xml' | |
| - 'server/generated-clients/**' | |
| - 'server/pom.xml' | |
| - 'server/mvnw' | |
| - 'server/.mvn/**' | |
| e2e: | |
| - 'webapp/e2e/**' | |
| - 'webapp/src/**' | |
| - 'webapp/public/**' | |
| - 'webapp/index.html' | |
| - 'webapp/package.json' | |
| - 'webapp/playwright.config.ts' | |
| - 'server/application/src/main/**' | |
| - 'server/application/pom.xml' | |
| - 'server/generated-clients/**' | |
| - 'server/pom.xml' | |
| - 'server/compose.yaml' | |
| - 'docker/postgres/**' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'pnpm-workspace.yaml' | |
| - '.github/workflows/cicd.yml' | |
| - '.github/workflows/ci-tests.yml' | |
| - '.github/actions/setup-caches/**' | |
| - '.github/actions/setup-node-pnpm/**' | |
| agent-images: | |
| - 'docker/agents/**' | |
| postgres-image: | |
| - 'docker/postgres/**' | |
| docs: | |
| - 'docs/**' | |
| ci-config: | |
| - '.github/workflows/**' | |
| - '.github/actions/**' | |
| - '.github/actionlint.yaml' | |
| docker-config: | |
| - '.github/workflows/ci-docker-build.yml' | |
| - '.github/workflows/reusable-docker-build.yml' | |
| pmd-canary: | |
| - 'server/pmd-ruleset.xml' | |
| - 'server/pom.xml' | |
| - 'package.json' | |
| - 'scripts/run-mvnw.ts' | |
| - '.github/workflows/ci-quality-gates.yml' | |
| - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 | |
| id: webapp_image_source | |
| with: | |
| filters: | | |
| webapp-image-source: | |
| - 'webapp/src/**' | |
| - '!webapp/src/**/*.test.*' | |
| - '!webapp/src/**/*.stories.*' | |
| - '!webapp/src/test/**' | |
| workflow-lint: | |
| name: "Actionlint" | |
| timeout-minutes: 10 | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes] | |
| if: | | |
| (github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') && | |
| needs.detect-changes.outputs.should_skip != 'true' && ( | |
| needs.detect-changes.outputs.ci-config == 'true' || | |
| github.event_name != 'pull_request' | |
| ) | |
| permissions: | |
| checks: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: reviewdog/action-actionlint@dbe5299849118fd6f099ba563d263d770955a64a # v1.73.2 | |
| env: | |
| SHELLCHECK_OPTS: --severity=warning | |
| with: | |
| fail_level: error | |
| filter_mode: nofilter | |
| reporter: github-check | |
| zizmor: | |
| name: "Zizmor" | |
| timeout-minutes: 10 | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes] | |
| if: github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true' | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - id: zizmor | |
| uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 | |
| with: | |
| min-confidence: medium | |
| # renovate: datasource=github-releases depName=zizmorcore/zizmor | |
| version: 1.29.0 | |
| - name: Enforce Zizmor findings | |
| env: | |
| ZIZMOR_SARIF: ${{ steps.zizmor.outputs.output-file }} | |
| run: jq -e '[.runs[]?.results[]?] | length == 0' "$ZIZMOR_SARIF" | |
| Quality: | |
| uses: ./.github/workflows/ci-quality-gates.yml | |
| needs: [detect-changes] | |
| if: | | |
| (github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') && | |
| needs.detect-changes.outputs.should_skip != 'true' && ( | |
| needs.detect-changes.outputs.any-code == 'true' || | |
| needs.detect-changes.outputs.ci-config == 'true' || | |
| github.event_name != 'pull_request' | |
| ) | |
| permissions: | |
| checks: write | |
| contents: read | |
| with: | |
| should_skip: ${{ needs.detect-changes.outputs.should_skip }} | |
| webapp_changed: ${{ (needs.detect-changes.outputs.webapp == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| application_server_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| tooling_changed: ${{ (needs.detect-changes.outputs.tooling == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| postgres_image_changed: ${{ (needs.detect-changes.outputs.postgres-image == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| contracts_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.postgres-image == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| pmd_canary: ${{ (needs.detect-changes.outputs.pmd-canary == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| Security: | |
| uses: ./.github/workflows/ci-security-scan.yml | |
| needs: [detect-changes] | |
| if: | | |
| (github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') && | |
| needs.detect-changes.outputs.should_skip != 'true' && ( | |
| needs.detect-changes.outputs.any-code == 'true' || | |
| needs.detect-changes.outputs.ci-config == 'true' || | |
| github.event_name != 'pull_request' | |
| ) | |
| permissions: | |
| contents: read | |
| security-events: write | |
| with: | |
| should_skip: ${{ needs.detect-changes.outputs.should_skip }} | |
| Test: | |
| uses: ./.github/workflows/ci-tests.yml | |
| needs: [detect-changes] | |
| if: | | |
| (github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') && | |
| needs.detect-changes.outputs.should_skip != 'true' && ( | |
| needs.detect-changes.outputs.any-code == 'true' || | |
| needs.detect-changes.outputs.ci-config == 'true' || | |
| github.event_name != 'pull_request' | |
| ) | |
| secrets: | |
| CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} | |
| SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} | |
| permissions: | |
| contents: read | |
| checks: write | |
| pull-requests: write | |
| statuses: write | |
| with: | |
| should_skip: ${{ needs.detect-changes.outputs.should_skip }} | |
| webapp_changed: ${{ (needs.detect-changes.outputs.webapp == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| application_server_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| e2e_changed: ${{ (needs.detect-changes.outputs.e2e == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| postgres_image_changed: ${{ (needs.detect-changes.outputs.postgres-image == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| Changesets: | |
| if: github.event_name == 'pull_request' | |
| uses: ./.github/workflows/verify-changesets.yml | |
| permissions: | |
| contents: read | |
| Compose: | |
| needs: [detect-changes] | |
| if: github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true' | |
| uses: ./.github/workflows/ci-compose-validate.yml | |
| permissions: | |
| contents: read | |
| Docker: | |
| uses: ./.github/workflows/ci-docker-build.yml | |
| secrets: | |
| SENTRY_AUTH_TOKEN: ${{ github.event_name == 'push' && secrets.SENTRY_AUTH_TOKEN || '' }} | |
| SENTRY_ORG: ${{ github.event_name == 'push' && secrets.SENTRY_ORG || '' }} | |
| SENTRY_PROJECT: ${{ github.event_name == 'push' && secrets.SENTRY_PROJECT || '' }} | |
| needs: [detect-changes] | |
| # Same-repository previews require every image at the PR head tag; unchanged images are aliased. | |
| # Forks cannot preview, so their image builds remain path-filtered. | |
| if: | | |
| needs.detect-changes.outputs.should_skip != 'true' && ( | |
| github.event_name != 'pull_request' || | |
| github.event.pull_request.head.repo.full_name == github.repository || | |
| needs.detect-changes.outputs.any-code == 'true' || | |
| needs.detect-changes.outputs.ci-config == 'true' | |
| ) | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| with: | |
| should_skip: ${{ needs.detect-changes.outputs.should_skip }} | |
| webapp_changed: ${{ (needs.detect-changes.outputs.webapp-image == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| application_server_changed: ${{ (needs.detect-changes.outputs.application-server-image == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| agent_images_changed: ${{ (needs.detect-changes.outputs.agent-images == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| postgres_image_changed: ${{ (needs.detect-changes.outputs.postgres-image == 'true' || needs.detect-changes.outputs.docker-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }} | |
| all-ci-passed: | |
| name: "CI Status Gate" | |
| timeout-minutes: 10 | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| statuses: write | |
| needs: [detect-changes, workflow-lint, zizmor, Quality, Security, Test, Changesets, Compose, Docker] | |
| if: always() | |
| steps: | |
| - name: Generate workflow timeline | |
| uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0 | |
| with: | |
| show-waiting-runner: true | |
| - name: Summarize workflow performance | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const run = await github.rest.actions.getWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: context.runId, | |
| }); | |
| const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: context.runId, | |
| per_page: 100, | |
| filter: 'latest', | |
| }); | |
| const finished = jobs.filter((job) => job.started_at && job.completed_at); | |
| const seconds = (start, end) => (Date.parse(end) - Date.parse(start)) / 1000; | |
| const elapsed = seconds(run.data.created_at, new Date().toISOString()); | |
| const runnerSeconds = finished.reduce( | |
| (total, job) => total + seconds(job.started_at, job.completed_at), | |
| 0, | |
| ); | |
| const startDelays = finished.map((job) => seconds(job.created_at, job.started_at)); | |
| const format = (value) => `${Math.floor(value / 60)}m ${Math.round(value % 60)}s`; | |
| await core.summary | |
| .addHeading('Workflow performance', 3) | |
| .addTable([ | |
| [{ data: 'Metric', header: true }, { data: 'Value', header: true }], | |
| ['Workflow-to-gate sample', format(elapsed)], | |
| ['Completed runner time', format(runnerSeconds)], | |
| ['Longest job creation-to-start delay', format(Math.max(0, ...startDelays))], | |
| ]) | |
| .addRaw( | |
| 'The creation-to-start delay includes dependency waiting; use the timeline above to distinguish dependency time from runner waiting. Cache hits and misses remain visible in each native cache action log.', | |
| ) | |
| .write(); | |
| - name: Evaluate CI results | |
| id: evaluate | |
| run: | | |
| echo "detect-changes: ${{ needs.detect-changes.result }}" | |
| echo "workflow-lint: ${{ needs.workflow-lint.result }}" | |
| echo "zizmor: ${{ needs.zizmor.result }}" | |
| echo "Quality: ${{ needs.Quality.result }}" | |
| echo "Security: ${{ needs.Security.result }}" | |
| echo "Test: ${{ needs.Test.result }}" | |
| echo "Changesets: ${{ needs.Changesets.result }}" | |
| echo "Compose: ${{ needs.Compose.result }}" | |
| echo "Docker: ${{ needs.Docker.result }}" | |
| if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then | |
| echo "status=failure" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| if [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then | |
| echo "status=cancelled" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| echo "status=success" >> $GITHUB_OUTPUT | |
| - name: Generate CI Summary | |
| if: always() | |
| run: | | |
| echo "## 🔍 CI Pipeline Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ steps.evaluate.outputs.status }}" == "success" ]]; then | |
| echo "✅ **All checks passed!**" >> $GITHUB_STEP_SUMMARY | |
| elif [[ "${{ steps.evaluate.outputs.status }}" == "failure" ]]; then | |
| echo "❌ **Some checks failed.** See details below." >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "⚠️ **CI was cancelled or encountered an issue.**" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Workflow Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Workflow | Status |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY | |
| result_to_emoji() { | |
| case "$1" in | |
| success) echo "✅ Passed" ;; | |
| failure) echo "❌ Failed" ;; | |
| skipped) echo "⏭️ Skipped" ;; | |
| cancelled) echo "🚫 Cancelled" ;; | |
| *) echo "❓ Unknown" ;; | |
| esac | |
| } | |
| echo "| Actionlint | $(result_to_emoji '${{ needs.workflow-lint.result }}') |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Zizmor | $(result_to_emoji '${{ needs.zizmor.result }}') |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Quality | $(result_to_emoji '${{ needs.Quality.result }}') |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Test | $(result_to_emoji '${{ needs.Test.result }}') |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Changesets | $(result_to_emoji '${{ needs.Changesets.result }}') |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Compose | $(result_to_emoji '${{ needs.Compose.result }}') |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Security | $(result_to_emoji '${{ needs.Security.result }}') |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Docker | $(result_to_emoji '${{ needs.Docker.result }}') |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 📁 Components Changed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Component | Changed |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-----------|---------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Webapp | ${{ needs.detect-changes.outputs.webapp == 'true' && '✅ Yes' || '➖ No' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Application Server | ${{ needs.detect-changes.outputs.application-server == 'true' && '✅ Yes' || '➖ No' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Agent Images | ${{ needs.detect-changes.outputs.agent-images == 'true' && '✅ Yes' || '➖ No' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| CI Config | ${{ needs.detect-changes.outputs.ci-config == 'true' && '⚙️ Yes' || '➖ No' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ steps.evaluate.outputs.status }}" == "failure" ]]; then | |
| echo "### 💡 Troubleshooting Guide" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ needs.Quality.result }}" == "failure" ]]; then | |
| echo "<details><summary><b>❌ Quality Failed</b></summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Issue | Fix Command |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-------|-------------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Formatting errors | \`pnpm run format\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| TypeScript errors | \`pnpm run typecheck\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Lint errors (webapp) | \`pnpm run check:webapp:fix\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Lint errors (outside webapp) | \`pnpm run check:agents:fix\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Java formatting | \`pnpm run format:java\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Java lint (PMD) | \`pnpm run lint:java:report\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Storybook sidebar order | \`pnpm run check:story-sort\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Mermaid diagram parse errors | \`pnpm run check:diagrams\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Stale \`routeTree.gen.ts\` | \`cd webapp && pnpm run build\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| OpenAPI out of sync | \`pnpm run generate:api\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Database schema drift | \`pnpm run db:draft-changelog\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ needs.Test.result }}" == "failure" ]]; then | |
| echo "<details><summary><b>❌ Tests Failed</b></summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Test Suite | Run Locally |" >> $GITHUB_STEP_SUMMARY | |
| echo "|------------|-------------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Webapp unit | \`pnpm run test:webapp\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Webapp Storybook | \`pnpm --filter webapp run test:storybook\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Application server | \`cd server && ./mvnw test\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tip:** Check the **Test Results** tab above for specific failures." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ needs.Changesets.result }}" == "failure" ]]; then | |
| echo "<details><summary><b>❌ Changesets Failed</b></summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Run \`pnpm run check:changesets\` for policy-test failures; otherwise correct the changeset reported by the job." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ needs.Docker.result }}" == "failure" ]]; then | |
| echo "<details><summary><b>❌ Docker Failed</b></summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Common causes:" >> $GITHUB_STEP_SUMMARY | |
| echo "- Build errors in the application code" >> $GITHUB_STEP_SUMMARY | |
| echo "- Missing dependencies" >> $GITHUB_STEP_SUMMARY | |
| echo "- Dockerfile syntax errors" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Test locally: \`docker build -f <component>/Dockerfile .\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ needs.Security.result }}" == "failure" ]]; then | |
| echo "<details><summary><b>❌ Security Failed</b></summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Check the **Security** tab for details on vulnerabilities." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "---" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Quick fix:** Run \`pnpm run format && pnpm run check\` before pushing." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ steps.evaluate.outputs.status }}" == "success" ]]; then | |
| echo "### ⚡ Performance" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| SKIPPED_COUNT=0 | |
| [[ "${{ needs.Quality.result }}" == "skipped" ]] && SKIPPED_COUNT=$((SKIPPED_COUNT + 1)) || true | |
| [[ "${{ needs.Test.result }}" == "skipped" ]] && SKIPPED_COUNT=$((SKIPPED_COUNT + 1)) || true | |
| [[ "${{ needs.Security.result }}" == "skipped" ]] && SKIPPED_COUNT=$((SKIPPED_COUNT + 1)) || true | |
| [[ "${{ needs.Docker.result }}" == "skipped" ]] && SKIPPED_COUNT=$((SKIPPED_COUNT + 1)) || true | |
| if [[ $SKIPPED_COUNT -gt 0 ]]; then | |
| echo "🚀 **Path-based filtering saved time!** $SKIPPED_COUNT workflow(s) skipped because no relevant files changed." >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "All workflows ran (CI config or main branch push)." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "---" >> $GITHUB_STEP_SUMMARY | |
| echo "*Generated by CI Status Gate • [View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})*" >> $GITHUB_STEP_SUMMARY | |
| - name: Create commit status | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const sha = context.payload.pull_request?.head?.sha || context.sha; | |
| const outcome = '${{ steps.evaluate.outputs.status }}' || 'failure'; | |
| // createCommitStatus accepts error, failure, pending or success and 422s on anything | |
| // else, so a cancelled run has to be reported as one of them rather than by its name. | |
| const REPORTED = { | |
| success: ['success', 'All CI checks passed'], | |
| failure: ['failure', 'One or more CI checks failed'], | |
| cancelled: ['error', 'CI was cancelled before it finished'], | |
| }; | |
| const [state, description] = REPORTED[outcome] ?? REPORTED.failure; | |
| await github.rest.repos.createCommitStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| sha: sha, | |
| state: state, | |
| description: description, | |
| context: 'All CI Passed' | |
| }); |