@plainconceptsplatform/workflows 0.19.2 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog-installation.js +12 -1
- package/dist/stack-defaults.js +16 -16
- package/dist/worker-env.js +14 -0
- package/loops/actions/add-issue-labels/action.yml +50 -50
- package/loops/actions/agent-output.cjs +17 -17
- package/loops/actions/apply-agent-bundle/action.yml +24 -24
- package/loops/actions/apply-agent-comments/action.yml +42 -42
- package/loops/actions/apply-agent-labels/action.yml +55 -55
- package/loops/actions/apply-agent-output/action.yml +108 -108
- package/loops/actions/assess-blast-radius/action.yml +148 -0
- package/loops/actions/assess-blast-radius/assess-blast-radius.sh +138 -0
- package/loops/actions/classify-route/action.yml +100 -100
- package/loops/actions/cleanup-artifacts/action.yml +91 -91
- package/loops/actions/close-agent-issues/action.yml +43 -43
- package/loops/actions/create-agent-issues/action.yml +52 -52
- package/loops/actions/create-issue-comment/action.yml +29 -29
- package/loops/actions/download-agent-output/action.yml +53 -53
- package/loops/actions/housekeeping/action.yml +55 -2
- package/loops/actions/link-pr-to-issue/action.yml +40 -40
- package/loops/actions/list-open-issues/action.yml +33 -33
- package/loops/actions/load-issue-context/action.yml +45 -45
- package/loops/actions/merge-agent-pr/action.yml +49 -49
- package/loops/actions/push-agent-branch/action.yml +45 -45
- package/loops/actions/remove-issue-labels/action.yml +37 -37
- package/loops/actions/update-agent-issues/action.yml +58 -58
- package/loops/actions/validate-merge-gate-output/action.yml +62 -40
- package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +147 -31
- package/loops/actions/validate-refine-output/action.yml +48 -44
- package/loops/actions/validate-refine-output/validate-refine-output.sh +15 -4
- package/loops/actions/validate-review-output/action.yml +35 -35
- package/loops/actions/validate-triage-output/action.yml +36 -36
- package/loops/actions/verify-composite-actions/action.yml +9 -9
- package/loops/actions/verify-refine-output/action.yml +9 -9
- package/loops/actions/verify-refine-output/verify-refine-output.sh +6 -1
- package/loops/actions/verify-route-matrix/action.yml +9 -9
- package/loops/actions/verify-route-matrix/verify-gate-metrics.mjs +51 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +329 -29
- package/loops/scripts/compile-agent-workflows.mjs +331 -331
- package/loops/templates/agentics/agentics-maintenance.yml +121 -121
- package/loops/templates/ci/app-ci-dotnet-next.yml +330 -330
- package/loops/templates/ci/app-ci-node-monorepo.yml +260 -260
- package/loops/templates/issues/bug_report.yml +109 -109
- package/loops/templates/issues/feature_request.yml +75 -75
- package/loops/templates/opencode/opencode.ci.json +55 -49
- package/loops/templates/opencode/opencode.ci.json.md +59 -49
- package/loops/templates/release/github-release.yml +30 -30
- package/loops/workflows/agent-merge-gate.md +367 -148
- package/loops/workflows/agent-refine.md +60 -16
- package/loops/workflows/authorize-bot-work.yml +105 -105
- package/loops/workflows/shared/opencode-ci.md +206 -206
- package/loops/workflows/shared/platform-defaults.md +19 -19
- package/package.json +1 -1
|
@@ -1,260 +1,260 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/templates/ci/app-ci-node-monorepo.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
-
name: "App: CI (Node monorepo)"
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
pull_request:
|
|
6
|
-
schedule:
|
|
7
|
-
- cron: "0 6 * * 1"
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
env:
|
|
11
|
-
NODE_VERSION: "22"
|
|
12
|
-
PNPM_VERSION: "10"
|
|
13
|
-
WEB_PACKAGE: "@app/web"
|
|
14
|
-
DESKTOP_PACKAGE: "@app/desktop"
|
|
15
|
-
MOBILE_PACKAGE: "@app/mobile"
|
|
16
|
-
WEB_EXPORT_PATH: apps/web/out
|
|
17
|
-
PLAYWRIGHT_REPORT_PATH: apps/web/playwright-report
|
|
18
|
-
DESKTOP_MAIN_PATH: apps/desktop/out/main/index.js
|
|
19
|
-
DESKTOP_PRELOAD_PATH: apps/desktop/out/preload/index.cjs
|
|
20
|
-
DESKTOP_PRELOAD_BRIDGE_PATTERN: exposeInMainWorld
|
|
21
|
-
MOBILE_IOS_PATH: apps/mobile/ios
|
|
22
|
-
MOBILE_ANDROID_PATH: apps/mobile/android
|
|
23
|
-
WEB_ARTIFACT_NAME: web-export
|
|
24
|
-
PLAYWRIGHT_ARTIFACT_NAME: playwright-report
|
|
25
|
-
TRUFFLEHOG_IMAGE: "trufflesecurity/trufflehog:latest"
|
|
26
|
-
SEMGREP_IMAGE: "semgrep/semgrep:1.168.0"
|
|
27
|
-
SBOM_ARTIFACT_NAME: app-sbom.cyclonedx.json
|
|
28
|
-
MUTATION_THRESHOLD: "60"
|
|
29
|
-
|
|
30
|
-
permissions:
|
|
31
|
-
contents: read
|
|
32
|
-
|
|
33
|
-
concurrency:
|
|
34
|
-
group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
|
35
|
-
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
36
|
-
|
|
37
|
-
jobs:
|
|
38
|
-
lint:
|
|
39
|
-
name: Lint (Biome)
|
|
40
|
-
if: github.event_name != 'schedule'
|
|
41
|
-
runs-on: agents-arc
|
|
42
|
-
steps:
|
|
43
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
|
-
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
45
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
46
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
47
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
48
|
-
- run: pnpm install --frozen-lockfile
|
|
49
|
-
- run: pnpm exec biome check .
|
|
50
|
-
|
|
51
|
-
web:
|
|
52
|
-
name: Web (Next.js static export)
|
|
53
|
-
if: github.event_name != 'schedule'
|
|
54
|
-
runs-on: agents-arc
|
|
55
|
-
steps:
|
|
56
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
57
|
-
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
58
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
59
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
60
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
61
|
-
- run: pnpm install --frozen-lockfile
|
|
62
|
-
- run: pnpm --filter "$WEB_PACKAGE" run typecheck
|
|
63
|
-
- run: pnpm --filter "$WEB_PACKAGE" run test
|
|
64
|
-
- run: pnpm --filter "$WEB_PACKAGE" run lint:fsd
|
|
65
|
-
- run: pnpm --filter "$WEB_PACKAGE" run build
|
|
66
|
-
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
67
|
-
with:
|
|
68
|
-
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
69
|
-
path: ${{ env.WEB_EXPORT_PATH }}
|
|
70
|
-
retention-days: 3
|
|
71
|
-
if-no-files-found: error
|
|
72
|
-
|
|
73
|
-
mutation-web:
|
|
74
|
-
name: Mutation (web, diff)
|
|
75
|
-
if: github.event_name == 'pull_request'
|
|
76
|
-
runs-on: agents-arc
|
|
77
|
-
timeout-minutes: 25
|
|
78
|
-
steps:
|
|
79
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
80
|
-
with:
|
|
81
|
-
fetch-depth: 0
|
|
82
|
-
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
83
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
84
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
85
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
86
|
-
- name: Detect web changes
|
|
87
|
-
id: scope
|
|
88
|
-
run: |
|
|
89
|
-
git fetch origin "$GITHUB_BASE_REF" --depth=1
|
|
90
|
-
if git diff --name-only "origin/$GITHUB_BASE_REF" HEAD | grep -qE "^apps/web/"; then
|
|
91
|
-
echo "run=true" >> "$GITHUB_OUTPUT"
|
|
92
|
-
else
|
|
93
|
-
echo "run=false" >> "$GITHUB_OUTPUT"
|
|
94
|
-
echo "No apps/web changes in this PR; skipping web mutation."
|
|
95
|
-
fi
|
|
96
|
-
- run: pnpm install --frozen-lockfile
|
|
97
|
-
if: steps.scope.outputs.run == 'true'
|
|
98
|
-
- name: StrykerJS (changed files only)
|
|
99
|
-
if: steps.scope.outputs.run == 'true'
|
|
100
|
-
run: pnpm --filter "$WEB_PACKAGE" exec stryker run --since="origin/$GITHUB_BASE_REF"
|
|
101
|
-
|
|
102
|
-
e2e:
|
|
103
|
-
name: E2E (Playwright)
|
|
104
|
-
if: github.event_name != 'schedule'
|
|
105
|
-
needs: web
|
|
106
|
-
runs-on: agents-arc
|
|
107
|
-
steps:
|
|
108
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
109
|
-
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
110
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
111
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
112
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
113
|
-
- run: pnpm install --frozen-lockfile
|
|
114
|
-
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
115
|
-
with:
|
|
116
|
-
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
117
|
-
path: ${{ env.WEB_EXPORT_PATH }}
|
|
118
|
-
- run: pnpm --filter "$WEB_PACKAGE" exec playwright install --with-deps chromium webkit
|
|
119
|
-
- run: pnpm --filter "$WEB_PACKAGE" run e2e
|
|
120
|
-
- if: failure()
|
|
121
|
-
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
122
|
-
with:
|
|
123
|
-
name: ${{ env.PLAYWRIGHT_ARTIFACT_NAME }}
|
|
124
|
-
path: ${{ env.PLAYWRIGHT_REPORT_PATH }}
|
|
125
|
-
retention-days: 7
|
|
126
|
-
|
|
127
|
-
desktop:
|
|
128
|
-
name: Desktop (Electron)
|
|
129
|
-
if: github.event_name != 'schedule'
|
|
130
|
-
needs: web
|
|
131
|
-
runs-on: agents-arc
|
|
132
|
-
steps:
|
|
133
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
134
|
-
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
135
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
136
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
137
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
138
|
-
- run: pnpm install --frozen-lockfile
|
|
139
|
-
- run: pnpm --filter "$DESKTOP_PACKAGE" run typecheck
|
|
140
|
-
- run: pnpm --filter "$DESKTOP_PACKAGE" run build
|
|
141
|
-
- run: |
|
|
142
|
-
test -f "$DESKTOP_MAIN_PATH"
|
|
143
|
-
test -f "$DESKTOP_PRELOAD_PATH"
|
|
144
|
-
grep -q "$DESKTOP_PRELOAD_BRIDGE_PATTERN" "$DESKTOP_PRELOAD_PATH"
|
|
145
|
-
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
146
|
-
with:
|
|
147
|
-
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
148
|
-
path: ${{ env.WEB_EXPORT_PATH }}
|
|
149
|
-
- run: pnpm --filter "$DESKTOP_PACKAGE" exec playwright install-deps chromium
|
|
150
|
-
- run: xvfb-run --auto-servernum pnpm --filter "$DESKTOP_PACKAGE" run e2e
|
|
151
|
-
|
|
152
|
-
mobile:
|
|
153
|
-
name: Mobile (Capacitor)
|
|
154
|
-
if: github.event_name != 'schedule'
|
|
155
|
-
runs-on: agents-arc
|
|
156
|
-
steps:
|
|
157
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
158
|
-
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
159
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
160
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
161
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
162
|
-
- run: pnpm install --frozen-lockfile
|
|
163
|
-
- run: pnpm --filter "$MOBILE_PACKAGE" run typecheck
|
|
164
|
-
- run: |
|
|
165
|
-
if [ -d "$MOBILE_IOS_PATH" ] || [ -d "$MOBILE_ANDROID_PATH" ]; then
|
|
166
|
-
pnpm --filter "$WEB_PACKAGE" run build
|
|
167
|
-
pnpm --filter "$MOBILE_PACKAGE" run sync
|
|
168
|
-
else
|
|
169
|
-
echo "::notice::Native projects absent; skipping Capacitor sync."
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
secret-scan:
|
|
173
|
-
name: Secret scan (TruffleHog)
|
|
174
|
-
runs-on: agents-arc
|
|
175
|
-
steps:
|
|
176
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
177
|
-
- run: docker run --rm -v "${{ github.workspace }}:/repo" "$TRUFFLEHOG_IMAGE" filesystem /repo --only-verified --fail --no-update
|
|
178
|
-
|
|
179
|
-
deps-and-iac-scan:
|
|
180
|
-
name: Dependencies and IaC (Trivy)
|
|
181
|
-
runs-on: agents-arc
|
|
182
|
-
steps:
|
|
183
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
184
|
-
- uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
185
|
-
with:
|
|
186
|
-
scan-type: fs
|
|
187
|
-
scanners: vuln,misconfig,secret
|
|
188
|
-
severity: CRITICAL,HIGH
|
|
189
|
-
ignore-unfixed: true
|
|
190
|
-
exit-code: "1"
|
|
191
|
-
format: table
|
|
192
|
-
|
|
193
|
-
sast-scan:
|
|
194
|
-
name: SAST (Semgrep)
|
|
195
|
-
runs-on: agents-arc
|
|
196
|
-
steps:
|
|
197
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
198
|
-
- run: docker run --rm -v "${{ github.workspace }}:/src" -w /src "$SEMGREP_IMAGE" semgrep scan --error --metrics=off --oss-only --disable-version-check --config p/default --config p/security-audit --config p/secrets --config p/typescript --config p/react --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
199
|
-
|
|
200
|
-
sbom:
|
|
201
|
-
name: SBOM (CycloneDX)
|
|
202
|
-
runs-on: agents-arc
|
|
203
|
-
steps:
|
|
204
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
205
|
-
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
|
|
206
|
-
with:
|
|
207
|
-
format: cyclonedx-json
|
|
208
|
-
artifact-name: ${{ env.SBOM_ARTIFACT_NAME }}
|
|
209
|
-
|
|
210
|
-
# A bot pull request is opened with GITHUB_TOKEN, and GitHub delivers no workflow_run event
|
|
211
|
-
# for the CI runs that follow (the run's actor is the bot). The router's CI-completion route
|
|
212
|
-
# never sees them, so this job hands the verdict over from inside CI. workflow_dispatch is
|
|
213
|
-
# one of the two events GITHUB_TOKEN may raise, so no App token is needed here, and the
|
|
214
|
-
# router treats a dispatch as trusted. Human pull requests are left to the workflow_run
|
|
215
|
-
# route, which does fire for them. The reconcile belt remains the hourly fallback.
|
|
216
|
-
#
|
|
217
|
-
# If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
|
|
218
|
-
# `actions: write`. A called workflow may not request a scope its caller withheld, and the
|
|
219
|
-
# whole run fails to start with zero jobs and no annotation when it does.
|
|
220
|
-
dispatch-merge-gate:
|
|
221
|
-
name: Dispatch merge gate
|
|
222
|
-
needs: [lint, web, mutation-web, e2e, desktop, mobile]
|
|
223
|
-
if: >
|
|
224
|
-
always() &&
|
|
225
|
-
github.event_name == 'pull_request' &&
|
|
226
|
-
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
227
|
-
github.event.pull_request.draft == false &&
|
|
228
|
-
github.event.pull_request.user.type == 'Bot'
|
|
229
|
-
runs-on: agents-arc
|
|
230
|
-
timeout-minutes: 5
|
|
231
|
-
permissions:
|
|
232
|
-
contents: read
|
|
233
|
-
actions: write
|
|
234
|
-
steps:
|
|
235
|
-
- name: Dispatch the merge gate with this run's verdict
|
|
236
|
-
env:
|
|
237
|
-
GH_TOKEN: ${{ github.token }}
|
|
238
|
-
REPO: ${{ github.repository }}
|
|
239
|
-
REF: ${{ github.event.repository.default_branch }}
|
|
240
|
-
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
241
|
-
RUN_ID: ${{ github.run_id }}
|
|
242
|
-
RESULTS: ${{ join(needs.*.result, ' ') }}
|
|
243
|
-
run: |
|
|
244
|
-
set -euo pipefail
|
|
245
|
-
conclusion=success
|
|
246
|
-
for result in $RESULTS; do
|
|
247
|
-
case "$result" in
|
|
248
|
-
failure) conclusion=failure; break ;;
|
|
249
|
-
cancelled | timed_out) conclusion=cancelled ;;
|
|
250
|
-
esac
|
|
251
|
-
done
|
|
252
|
-
# A cancelled run was superseded by a newer push, whose own run dispatches.
|
|
253
|
-
if [ "$conclusion" = cancelled ]; then
|
|
254
|
-
echo "CI was cancelled; not dispatching the merge gate."
|
|
255
|
-
exit 0
|
|
256
|
-
fi
|
|
257
|
-
echo "Dispatching the merge gate for PR #$PR_NUMBER with CI $conclusion (run $RUN_ID)."
|
|
258
|
-
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
|
|
259
|
-
-f operation=merge-gate -f pr-number="$PR_NUMBER" \
|
|
260
|
-
-f ci-conclusion="$conclusion" -f ci-run-id="$RUN_ID"
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/templates/ci/app-ci-node-monorepo.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
+
name: "App: CI (Node monorepo)"
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "0 6 * * 1"
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
env:
|
|
11
|
+
NODE_VERSION: "22"
|
|
12
|
+
PNPM_VERSION: "10"
|
|
13
|
+
WEB_PACKAGE: "@app/web"
|
|
14
|
+
DESKTOP_PACKAGE: "@app/desktop"
|
|
15
|
+
MOBILE_PACKAGE: "@app/mobile"
|
|
16
|
+
WEB_EXPORT_PATH: apps/web/out
|
|
17
|
+
PLAYWRIGHT_REPORT_PATH: apps/web/playwright-report
|
|
18
|
+
DESKTOP_MAIN_PATH: apps/desktop/out/main/index.js
|
|
19
|
+
DESKTOP_PRELOAD_PATH: apps/desktop/out/preload/index.cjs
|
|
20
|
+
DESKTOP_PRELOAD_BRIDGE_PATTERN: exposeInMainWorld
|
|
21
|
+
MOBILE_IOS_PATH: apps/mobile/ios
|
|
22
|
+
MOBILE_ANDROID_PATH: apps/mobile/android
|
|
23
|
+
WEB_ARTIFACT_NAME: web-export
|
|
24
|
+
PLAYWRIGHT_ARTIFACT_NAME: playwright-report
|
|
25
|
+
TRUFFLEHOG_IMAGE: "trufflesecurity/trufflehog:latest"
|
|
26
|
+
SEMGREP_IMAGE: "semgrep/semgrep:1.168.0"
|
|
27
|
+
SBOM_ARTIFACT_NAME: app-sbom.cyclonedx.json
|
|
28
|
+
MUTATION_THRESHOLD: "60"
|
|
29
|
+
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
|
|
33
|
+
concurrency:
|
|
34
|
+
group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
|
35
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
36
|
+
|
|
37
|
+
jobs:
|
|
38
|
+
lint:
|
|
39
|
+
name: Lint (Biome)
|
|
40
|
+
if: github.event_name != 'schedule'
|
|
41
|
+
runs-on: agents-arc
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
|
+
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
45
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
46
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
47
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
48
|
+
- run: pnpm install --frozen-lockfile
|
|
49
|
+
- run: pnpm exec biome check .
|
|
50
|
+
|
|
51
|
+
web:
|
|
52
|
+
name: Web (Next.js static export)
|
|
53
|
+
if: github.event_name != 'schedule'
|
|
54
|
+
runs-on: agents-arc
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
57
|
+
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
58
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
59
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
60
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
61
|
+
- run: pnpm install --frozen-lockfile
|
|
62
|
+
- run: pnpm --filter "$WEB_PACKAGE" run typecheck
|
|
63
|
+
- run: pnpm --filter "$WEB_PACKAGE" run test
|
|
64
|
+
- run: pnpm --filter "$WEB_PACKAGE" run lint:fsd
|
|
65
|
+
- run: pnpm --filter "$WEB_PACKAGE" run build
|
|
66
|
+
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
67
|
+
with:
|
|
68
|
+
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
69
|
+
path: ${{ env.WEB_EXPORT_PATH }}
|
|
70
|
+
retention-days: 3
|
|
71
|
+
if-no-files-found: error
|
|
72
|
+
|
|
73
|
+
mutation-web:
|
|
74
|
+
name: Mutation (web, diff)
|
|
75
|
+
if: github.event_name == 'pull_request'
|
|
76
|
+
runs-on: agents-arc
|
|
77
|
+
timeout-minutes: 25
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
80
|
+
with:
|
|
81
|
+
fetch-depth: 0
|
|
82
|
+
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
83
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
84
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
85
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
86
|
+
- name: Detect web changes
|
|
87
|
+
id: scope
|
|
88
|
+
run: |
|
|
89
|
+
git fetch origin "$GITHUB_BASE_REF" --depth=1
|
|
90
|
+
if git diff --name-only "origin/$GITHUB_BASE_REF" HEAD | grep -qE "^apps/web/"; then
|
|
91
|
+
echo "run=true" >> "$GITHUB_OUTPUT"
|
|
92
|
+
else
|
|
93
|
+
echo "run=false" >> "$GITHUB_OUTPUT"
|
|
94
|
+
echo "No apps/web changes in this PR; skipping web mutation."
|
|
95
|
+
fi
|
|
96
|
+
- run: pnpm install --frozen-lockfile
|
|
97
|
+
if: steps.scope.outputs.run == 'true'
|
|
98
|
+
- name: StrykerJS (changed files only)
|
|
99
|
+
if: steps.scope.outputs.run == 'true'
|
|
100
|
+
run: pnpm --filter "$WEB_PACKAGE" exec stryker run --since="origin/$GITHUB_BASE_REF"
|
|
101
|
+
|
|
102
|
+
e2e:
|
|
103
|
+
name: E2E (Playwright)
|
|
104
|
+
if: github.event_name != 'schedule'
|
|
105
|
+
needs: web
|
|
106
|
+
runs-on: agents-arc
|
|
107
|
+
steps:
|
|
108
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
109
|
+
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
110
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
111
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
112
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
113
|
+
- run: pnpm install --frozen-lockfile
|
|
114
|
+
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
115
|
+
with:
|
|
116
|
+
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
117
|
+
path: ${{ env.WEB_EXPORT_PATH }}
|
|
118
|
+
- run: pnpm --filter "$WEB_PACKAGE" exec playwright install --with-deps chromium webkit
|
|
119
|
+
- run: pnpm --filter "$WEB_PACKAGE" run e2e
|
|
120
|
+
- if: failure()
|
|
121
|
+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
122
|
+
with:
|
|
123
|
+
name: ${{ env.PLAYWRIGHT_ARTIFACT_NAME }}
|
|
124
|
+
path: ${{ env.PLAYWRIGHT_REPORT_PATH }}
|
|
125
|
+
retention-days: 7
|
|
126
|
+
|
|
127
|
+
desktop:
|
|
128
|
+
name: Desktop (Electron)
|
|
129
|
+
if: github.event_name != 'schedule'
|
|
130
|
+
needs: web
|
|
131
|
+
runs-on: agents-arc
|
|
132
|
+
steps:
|
|
133
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
134
|
+
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
135
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
136
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
137
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
138
|
+
- run: pnpm install --frozen-lockfile
|
|
139
|
+
- run: pnpm --filter "$DESKTOP_PACKAGE" run typecheck
|
|
140
|
+
- run: pnpm --filter "$DESKTOP_PACKAGE" run build
|
|
141
|
+
- run: |
|
|
142
|
+
test -f "$DESKTOP_MAIN_PATH"
|
|
143
|
+
test -f "$DESKTOP_PRELOAD_PATH"
|
|
144
|
+
grep -q "$DESKTOP_PRELOAD_BRIDGE_PATTERN" "$DESKTOP_PRELOAD_PATH"
|
|
145
|
+
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
146
|
+
with:
|
|
147
|
+
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
148
|
+
path: ${{ env.WEB_EXPORT_PATH }}
|
|
149
|
+
- run: pnpm --filter "$DESKTOP_PACKAGE" exec playwright install-deps chromium
|
|
150
|
+
- run: xvfb-run --auto-servernum pnpm --filter "$DESKTOP_PACKAGE" run e2e
|
|
151
|
+
|
|
152
|
+
mobile:
|
|
153
|
+
name: Mobile (Capacitor)
|
|
154
|
+
if: github.event_name != 'schedule'
|
|
155
|
+
runs-on: agents-arc
|
|
156
|
+
steps:
|
|
157
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
158
|
+
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
|
159
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
160
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
161
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
162
|
+
- run: pnpm install --frozen-lockfile
|
|
163
|
+
- run: pnpm --filter "$MOBILE_PACKAGE" run typecheck
|
|
164
|
+
- run: |
|
|
165
|
+
if [ -d "$MOBILE_IOS_PATH" ] || [ -d "$MOBILE_ANDROID_PATH" ]; then
|
|
166
|
+
pnpm --filter "$WEB_PACKAGE" run build
|
|
167
|
+
pnpm --filter "$MOBILE_PACKAGE" run sync
|
|
168
|
+
else
|
|
169
|
+
echo "::notice::Native projects absent; skipping Capacitor sync."
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
secret-scan:
|
|
173
|
+
name: Secret scan (TruffleHog)
|
|
174
|
+
runs-on: agents-arc
|
|
175
|
+
steps:
|
|
176
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
177
|
+
- run: docker run --rm -v "${{ github.workspace }}:/repo" "$TRUFFLEHOG_IMAGE" filesystem /repo --only-verified --fail --no-update
|
|
178
|
+
|
|
179
|
+
deps-and-iac-scan:
|
|
180
|
+
name: Dependencies and IaC (Trivy)
|
|
181
|
+
runs-on: agents-arc
|
|
182
|
+
steps:
|
|
183
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
184
|
+
- uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
185
|
+
with:
|
|
186
|
+
scan-type: fs
|
|
187
|
+
scanners: vuln,misconfig,secret
|
|
188
|
+
severity: CRITICAL,HIGH
|
|
189
|
+
ignore-unfixed: true
|
|
190
|
+
exit-code: "1"
|
|
191
|
+
format: table
|
|
192
|
+
|
|
193
|
+
sast-scan:
|
|
194
|
+
name: SAST (Semgrep)
|
|
195
|
+
runs-on: agents-arc
|
|
196
|
+
steps:
|
|
197
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
198
|
+
- run: docker run --rm -v "${{ github.workspace }}:/src" -w /src "$SEMGREP_IMAGE" semgrep scan --error --metrics=off --oss-only --disable-version-check --config p/default --config p/security-audit --config p/secrets --config p/typescript --config p/react --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
199
|
+
|
|
200
|
+
sbom:
|
|
201
|
+
name: SBOM (CycloneDX)
|
|
202
|
+
runs-on: agents-arc
|
|
203
|
+
steps:
|
|
204
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
205
|
+
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
|
|
206
|
+
with:
|
|
207
|
+
format: cyclonedx-json
|
|
208
|
+
artifact-name: ${{ env.SBOM_ARTIFACT_NAME }}
|
|
209
|
+
|
|
210
|
+
# A bot pull request is opened with GITHUB_TOKEN, and GitHub delivers no workflow_run event
|
|
211
|
+
# for the CI runs that follow (the run's actor is the bot). The router's CI-completion route
|
|
212
|
+
# never sees them, so this job hands the verdict over from inside CI. workflow_dispatch is
|
|
213
|
+
# one of the two events GITHUB_TOKEN may raise, so no App token is needed here, and the
|
|
214
|
+
# router treats a dispatch as trusted. Human pull requests are left to the workflow_run
|
|
215
|
+
# route, which does fire for them. The reconcile belt remains the hourly fallback.
|
|
216
|
+
#
|
|
217
|
+
# If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
|
|
218
|
+
# `actions: write`. A called workflow may not request a scope its caller withheld, and the
|
|
219
|
+
# whole run fails to start with zero jobs and no annotation when it does.
|
|
220
|
+
dispatch-merge-gate:
|
|
221
|
+
name: Dispatch merge gate
|
|
222
|
+
needs: [lint, web, mutation-web, e2e, desktop, mobile]
|
|
223
|
+
if: >
|
|
224
|
+
always() &&
|
|
225
|
+
github.event_name == 'pull_request' &&
|
|
226
|
+
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
227
|
+
github.event.pull_request.draft == false &&
|
|
228
|
+
github.event.pull_request.user.type == 'Bot'
|
|
229
|
+
runs-on: agents-arc
|
|
230
|
+
timeout-minutes: 5
|
|
231
|
+
permissions:
|
|
232
|
+
contents: read
|
|
233
|
+
actions: write
|
|
234
|
+
steps:
|
|
235
|
+
- name: Dispatch the merge gate with this run's verdict
|
|
236
|
+
env:
|
|
237
|
+
GH_TOKEN: ${{ github.token }}
|
|
238
|
+
REPO: ${{ github.repository }}
|
|
239
|
+
REF: ${{ github.event.repository.default_branch }}
|
|
240
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
241
|
+
RUN_ID: ${{ github.run_id }}
|
|
242
|
+
RESULTS: ${{ join(needs.*.result, ' ') }}
|
|
243
|
+
run: |
|
|
244
|
+
set -euo pipefail
|
|
245
|
+
conclusion=success
|
|
246
|
+
for result in $RESULTS; do
|
|
247
|
+
case "$result" in
|
|
248
|
+
failure) conclusion=failure; break ;;
|
|
249
|
+
cancelled | timed_out) conclusion=cancelled ;;
|
|
250
|
+
esac
|
|
251
|
+
done
|
|
252
|
+
# A cancelled run was superseded by a newer push, whose own run dispatches.
|
|
253
|
+
if [ "$conclusion" = cancelled ]; then
|
|
254
|
+
echo "CI was cancelled; not dispatching the merge gate."
|
|
255
|
+
exit 0
|
|
256
|
+
fi
|
|
257
|
+
echo "Dispatching the merge gate for PR #$PR_NUMBER with CI $conclusion (run $RUN_ID)."
|
|
258
|
+
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
|
|
259
|
+
-f operation=merge-gate -f pr-number="$PR_NUMBER" \
|
|
260
|
+
-f ci-conclusion="$conclusion" -f ci-run-id="$RUN_ID"
|