Skip to content

fix(server): align agent runtime with Pi 0.84.3 #3950

fix(server): align agent runtime with Pi 0.84.3

fix(server): align agent runtime with Pi 0.84.3 #3950

Workflow file for this run

name: CI/CD
on:
workflow_dispatch:
push:
branches: ["main"]
pull_request:
branches: ["**"]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
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 }}
application-server-image: ${{ steps.filter.outputs.application-server-image }}
e2e: ${{ steps.filter.outputs.e2e }}
agent-images: ${{ steps.filter.outputs.agent-images }}
release-pin-fetcher: ${{ steps.filter.outputs.release-pin-fetcher }}
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 }}
any-code: ${{ steps.filter.outputs.webapp == 'true' || steps.filter.outputs.application-server == 'true' || steps.filter.outputs.agent-images == '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"]'
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: filter
with:
filters: |
webapp:
- 'webapp/**'
- 'docs/images/readme/**'
# webapp/.oxlintrc.json carries no `options` block and webapp/biome.jsonc declares
# `root: false`, so `typeAware`, `reportUnusedDisableDirectives` and the Biome graph
# root all come from these two files — they decide the webapp's verdict from outside
# webapp/.
- '.oxlintrc.json'
- 'biome.jsonc'
# check:components, check:stories and check:story-sort live here, run on the Webapp leg.
- 'scripts/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.npmrc'
- '.node-version'
webapp-image:
- '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'
- '.npmrc'
application-server:
- 'server/**'
# check:env, check:contracts, check:diagrams and db-utils.sh all live here and run on
# the App Server or Database leg.
- 'scripts/**'
# test:agents and typecheck:agents cover the precompute tree and are configured here;
# the Bun they run on is read from the agent image.
- 'docker/agents/**'
- 'tsconfig.agents.json'
# The lint/format gate for every non-webapp TypeScript tree is assembled from all five:
# oxlint's rules, Biome's rules, the `:agents` scripts that invoke them, the pinned
# binaries, and the root tsconfig — which exists only to give the Bun trees, configured
# by tsconfig.agents.json, the project that oxlint's type-aware rules require.
- '.oxlintrc.json'
- 'biome.jsonc'
- 'tsconfig.json'
- 'package.json'
- 'pnpm-lock.yaml'
# Linted by the same leg (docs/.oxlintrc.json decides that verdict), markdown-linted by
# docs:lint, and parsed by check:diagrams. The `docs` filter below feeds no job, so this
# entry is the only thing that puts a docs-only PR into `any-code`.
- 'docs/**'
- '.changeset/*.cjs'
- 'commitlint.config.ts'
- '.github/scripts/**'
# check:biome-pin compares this file's $schema against the pinned version.
- 'webapp/biome.jsonc'
# check:env compares application.yml against the deployment that has to deliver it.
- 'docker/compose.app.yaml'
- 'docker/compose.core.yaml'
# check:instructions runs on this leg and reads every one of these. The two markdown
# globs are named even though webapp/** and server/** already carry them, because
# webapp/** is behind the *webapp* filter — a PR touching only webapp/CLAUDE.md would
# otherwise run the leg that does not hold this gate.
- '**/AGENTS.md'
- '**/CLAUDE.md'
- '.claude/**'
- '.opencode/**'
- '.agents/**'
- 'opencode.json'
application-server-image:
- 'server/src/main/**'
- '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/src/main/**'
- 'server/pom.xml'
- 'server/compose.yaml'
- 'docker/postgres/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.npmrc'
- '.node-version'
- '.github/workflows/cicd.yml'
- '.github/workflows/ci-tests.yml'
- '.github/actions/setup-caches/**'
- '.github/actions/setup-pnpm-node/**'
agent-images:
- 'docker/agents/**'
release-pin-fetcher:
- 'docker/release-pin-fetcher/**'
postgres-image:
- 'docker/postgres/**'
docs:
- 'docs/**'
ci-config:
- '.github/workflows/**'
- '.github/actions/**'
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/**'
# Coolify creates previews for trusted same-repository pull requests. This job waits for the
# immutable application-server image, then updates the preview to the exact head commit. Coolify's
# deploy API only accepts a pull request that already has a preview, so a missing preview remains a
# safe no-op. Instance identifiers come from repository variables; forks never receive credentials.
preview:
name: "Preview / Coolify"
runs-on: ubuntu-latest
# Wait for Docker so SOURCE_COMMIT always names an image that already exists in GHCR. `always`
# preserves the link/no-op behavior when the Docker workflow is legitimately skipped.
needs: [detect-changes, Docker]
if: >-
always() &&
github.event_name == 'pull_request' &&
vars.COOLIFY_URL != '' &&
vars.COOLIFY_APP_UUID != '' &&
(needs.Docker.result == 'success' || needs.Docker.result == 'skipped')
permissions:
statuses: write
timeout-minutes: 2
env:
COOLIFY_URL: ${{ vars.COOLIFY_URL }}
COOLIFY_APP_UUID: ${{ vars.COOLIFY_APP_UUID }}
COOLIFY_PROJECT_UUID: ${{ vars.COOLIFY_PROJECT_UUID }}
COOLIFY_ENVIRONMENT_UUID: ${{ vars.COOLIFY_ENVIRONMENT_UUID }}
steps:
- name: Link the preview deployments page on the PR
if: vars.COOLIFY_PROJECT_UUID != '' && vars.COOLIFY_ENVIRONMENT_UUID != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { COOLIFY_URL, COOLIFY_PROJECT_UUID, COOLIFY_ENVIRONMENT_UUID, COOLIFY_APP_UUID } = process.env;
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.payload.pull_request.head.sha,
state: 'success',
target_url: `${COOLIFY_URL}/project/${COOLIFY_PROJECT_UUID}/environment/${COOLIFY_ENVIRONMENT_UUID}/application/${COOLIFY_APP_UUID}/preview-deployments`,
description: 'Click Details to view Coolify preview deployments',
context: 'Preview / Coolify',
});
# Forks are skipped deliberately: a preview runs with the instance's real credentials.
- name: Update this PR's preview deployment, if it has one
if: >-
(github.event.action == 'opened' ||
github.event.action == 'reopened' ||
github.event.action == 'synchronize') &&
github.event.pull_request.head.repo.full_name == github.repository
env:
COOLIFY_TOKEN: ${{ secrets.COOLIFY_API_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
APP_SERVER_PUBLISHED: ${{ needs.Docker.outputs.application-server-published }}
run: |
set -euo pipefail
if [ -z "${COOLIFY_TOKEN}" ]; then
echo "::notice::COOLIFY_API_TOKEN is not configured; skipping preview update."
exit 0
fi
# Coolify pins the preview to SOURCE_COMMIT, so without that tag the deployment can only
# fail on `manifest unknown` — under a green check, since queueing one always succeeds.
if [ "${APP_SERVER_PUBLISHED}" != 'true' ]; then
echo "::notice::No application-server image for this commit; skipping preview update."
exit 0
fi
body=$(mktemp)
status=$(curl -sS -o "${body}" -w '%{http_code}' -X POST \
-H "Authorization: Bearer ${COOLIFY_TOKEN}" \
-H 'Accept: application/json' \
--retry 3 --retry-connrefused --max-time 30 \
"${COOLIFY_URL}/api/v1/deploy?uuid=${COOLIFY_APP_UUID}&pr=${PR_NUMBER}")
if [ "${status}" -ge 400 ]; then
echo "::error::Coolify returned HTTP ${status}: $(cat "${body}")"
exit 1
fi
deployment=$(jq -r '.deployments[0].deployment_uuid // empty' "${body}")
if [ -z "${deployment}" ]; then
echo "::notice::PR #${PR_NUMBER} has no preview deployment ($(jq -r '.deployments[0].message // "no deployment queued"' "${body}"))."
exit 0
fi
echo "::notice::Queued Coolify deployment ${deployment} for PR #${PR_NUMBER}."
Quality:
uses: ./.github/workflows/ci-quality-gates.yml
needs: [detect-changes]
if: |
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: inherit
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' }}
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: |
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: inherit
with:
should_skip: ${{ needs.detect-changes.outputs.should_skip }}
Test:
uses: ./.github/workflows/ci-tests.yml
needs: [detect-changes]
if: |
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: inherit
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' }}
Docker:
uses: ./.github/workflows/ci-docker-build.yml
# Image builds consume the detected source tree, not Quality outputs. Running both branches at
# once keeps image and preview confidence without adding Docker as a second CI stage.
needs: [detect-changes]
# Every same-repository pull request gets a Coolify preview pinned to SOURCE_COMMIT, so it needs
# an application-server tag at its head commit even when it touches only docs or the preview
# stack. The path filters below therefore gate fork pull requests only — forks get no preview.
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'
)
secrets: inherit
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' }}
release_pin_fetcher_changed: ${{ (needs.detect-changes.outputs.release-pin-fetcher == '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"
runs-on: ubuntu-latest
permissions:
actions: read
statuses: write
needs: [detect-changes, Quality, Security, Test, Docker]
if: always()
steps:
- name: Generate workflow timeline
uses: Kesin11/actions-timeline@7c7e0821d38f27460f4a71ef874a1c8cf23602e4 # v2
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 "Quality: ${{ needs.Quality.result }}"
echo "Security: ${{ needs.Security.result }}"
echo "Test: ${{ needs.Test.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 "| Quality | $(result_to_emoji '${{ needs.Quality.result }}') |" >> $GITHUB_STEP_SUMMARY
echo "| Test | $(result_to_emoji '${{ needs.Test.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.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'
});