11# CI Workflow — Build, Test, Lint
22# Non-negotiable: This must pass before any deploy.
33# Per AGENTS.md: GitHub Actions is the only deploy path.
4+ # Runtime: Deno only (per AGENTS.md policy)
45
56name : CI
67
@@ -16,124 +17,67 @@ concurrency:
1617 cancel-in-progress : true
1718
1819env :
19- DOTNET_VERSION : " 8.0.x"
20- BUN_VERSION : " 1.1"
21- NODE_VERSION : " 20"
20+ DENO_VERSION : " 2.x"
2221
2322jobs :
2423 # ============================================================================
25- # .NET Build & Test (F# primary, C# secondary )
24+ # Deno Build & Test (primary runtime per AGENTS.md )
2625 # ============================================================================
27- dotnet :
28- name : .NET Build & Test
26+ deno :
27+ name : Deno Build & Test
2928 runs-on : ubuntu-latest
3029 steps :
3130 - name : Checkout
3231 uses : actions/checkout@v4
3332
34- - name : Skip if no .NET projects
35- if : ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') == '' }}
36- run : echo "No .NET project files found, skipping."
37-
38- - name : Setup .NET
39- if : ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
40- uses : actions/setup-dotnet@v4
33+ - name : Setup Deno
34+ uses : denoland/setup-deno@v2
4135 with :
42- dotnet -version : ${{ env.DOTNET_VERSION }}
36+ deno -version : ${{ env.DENO_VERSION }}
4337
44- - name : Restore dependencies
45- if : ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
46- run : dotnet restore
47-
48- - name : Build
49- if : ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
50- run : dotnet build --configuration Release --no-restore
51-
52- - name : Test
53- if : ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
54- run : dotnet test --configuration Release --no-build --verbosity normal --logger trx --results-directory ./TestResults
55-
56- - name : Upload test results
57- uses : actions/upload-artifact@v4
58- if : ${{ always() && hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
38+ - name : Cache Deno dependencies
39+ uses : actions/cache@v4
5940 with :
60- name : dotnet-test-results
61- path : ./TestResults
62- retention-days : 7
41+ path : |
42+ ~/.cache/deno
43+ ~/.deno
44+ key : deno-${{ runner.os }}-${{ hashFiles('deno.lock', 'deno.json') }}
45+ restore-keys : deno-${{ runner.os }}-
6346
64- # ============================================================================
65- # Bun Build & Test (primary JS/TS runtime)
66- # ============================================================================
67- bun :
68- name : Bun Build & Test
69- runs-on : ubuntu-latest
70- steps :
71- - name : Checkout
72- uses : actions/checkout@v4
73-
74- - name : Setup Bun
75- uses : oven-sh/setup-bun@v2
76- with :
77- bun-version : ${{ env.BUN_VERSION }}
78-
79- - name : Detect UI package
80- id : ui
47+ - name : Install UI dependencies
8148 run : |
8249 if [ -f "src/ui/package.json" ]; then
83- echo "dir=src/ui" >> $GITHUB_OUTPUT
84- elif [ -f "package.json" ]; then
85- echo "dir=." >> $GITHUB_OUTPUT
86- else
87- echo "dir=" >> $GITHUB_OUTPUT
50+ cd src/ui && deno install --node-modules-dir --allow-scripts
8851 fi
8952
90- - name : Skip if no package.json
91- if : ${{ steps.ui.outputs.dir == '' }}
92- run : echo "No package.json found, skipping."
93-
94- - name : Install dependencies
95- if : ${{ steps.ui.outputs.dir != '' }}
96- working-directory : ${{ steps.ui.outputs.dir }}
97- run : bun install --frozen-lockfile
98-
9953 - name : Type check
100- if : ${{ steps.ui.outputs.dir != '' }}
101- working-directory : ${{ steps.ui.outputs.dir }}
10254 run : |
103- if grep -q '"typecheck"' package .json; then
104- bun run typecheck
55+ if grep -q '"typecheck"' deno .json 2>/dev/null ; then
56+ deno task typecheck
10557 else
106- echo "No typecheck script configured"
58+ echo "No typecheck task configured"
10759 fi
10860
10961 - name : Lint
110- if : ${{ steps.ui.outputs.dir != '' }}
111- working-directory : ${{ steps.ui.outputs.dir }}
112- run : |
113- if grep -q '"lint"' package.json; then
114- bun run lint
115- else
116- echo "No lint script configured"
117- fi
62+ run : deno lint
63+
64+ - name : Format check
65+ run : deno fmt --check
11866
11967 - name : Test
120- if : ${{ steps.ui.outputs.dir != '' }}
121- working-directory : ${{ steps.ui.outputs.dir }}
12268 run : |
123- if grep -q '"test"' package .json; then
124- bun run test
69+ if grep -q '"test"' deno .json 2>/dev/null ; then
70+ deno task test
12571 else
126- echo "No test script configured"
72+ deno test --allow-all
12773 fi
12874
129- - name : Build
130- if : ${{ steps.ui.outputs.dir != '' }}
131- working-directory : ${{ steps.ui.outputs.dir }}
75+ - name : Build UI
13276 run : |
133- if grep -q '"build"' package .json; then
134- bun run build
77+ if grep -q '"ui: build"' deno .json 2>/dev/null ; then
78+ deno task ui: build
13579 else
136- echo "No build script configured"
80+ echo "No ui: build task configured"
13781 fi
13882
13983 # ============================================================================
@@ -146,15 +90,15 @@ jobs:
14690 - name : Checkout
14791 uses : actions/checkout@v4
14892
149- - name : Setup Bun
150- uses : oven-sh /setup-bun @v2
93+ - name : Setup Deno
94+ uses : denoland /setup-deno @v2
15195 with :
152- bun -version : ${{ env.BUN_VERSION }}
96+ deno -version : ${{ env.DENO_VERSION }}
15397
15498 - name : Run FPF Doctor
15599 run : |
156- if command -v bunx &> /dev/null && [ -f "fpf.config.ts" ]; then
157- bunx --bun @venikman/fpf doctor
100+ if [ -f "fpf.config.ts" ]; then
101+ deno run -A npm: @venikman/fpf doctor
158102 else
159103 echo "FPF not configured, skipping doctor checks"
160104 fi
@@ -166,22 +110,31 @@ jobs:
166110 playwright :
167111 name : Playwright Tests
168112 runs-on : ubuntu-latest
169- needs : [bun] # Only run if Bun build passes
113+ needs : [deno]
170114 steps :
171115 - name : Checkout
172116 uses : actions/checkout@v4
173117
174118 - name : Setup Deno
175119 uses : denoland/setup-deno@v2
176120 with :
177- deno-version : v2.x
121+ deno-version : ${{ env.DENO_VERSION }}
122+
123+ - name : Install UI dependencies
124+ run : |
125+ if [ -f "src/ui/package.json" ]; then
126+ cd src/ui && deno install --node-modules-dir --allow-scripts
127+ fi
128+
129+ - name : Build UI
130+ run : deno task ui:build
178131
179132 - name : Install Playwright browsers
180133 run : deno task test:e2e:install
181134
182135 - name : Run Playwright tests
183136 run : deno task test:e2e
184- continue-on-error : true # May not exist in all projects
137+ continue-on-error : true
185138
186139 - name : Upload Playwright report
187140 uses : actions/upload-artifact@v4
@@ -216,64 +169,19 @@ jobs:
216169 with :
217170 sarif_file : " trivy-results.sarif"
218171
219- # ============================================================================
220- # PR Release Prep Checklist
221- # ============================================================================
222- release-prep :
223- name : PR Release Prep
224- if : github.event_name == 'pull_request'
225- runs-on : ubuntu-latest
226- steps :
227- - name : Validate release checklist
228- uses : actions/github-script@v7
229- with :
230- script : |
231- const body = context.payload.pull_request?.body ?? "";
232- const lines = body.split(/\r?\n/);
233- const labels = [
234- "Release notes summary",
235- "Rollout plan",
236- "Rollback plan",
237- "Flags/migrations",
238- ];
239-
240- const missing = [];
241- for (const label of labels) {
242- const line = lines.find((entry) =>
243- entry.toLowerCase().includes(label.toLowerCase())
244- );
245- if (!line) {
246- missing.push(`${label} (missing line)`);
247- continue;
248- }
249- const isChecked = /- \[[xX]\]/.test(line);
250- const isNa = /\bN\/A\b|\bNA\b/i.test(line);
251- if (!isChecked && !isNa) {
252- missing.push(label);
253- }
254- }
255-
256- if (missing.length) {
257- core.setFailed(
258- `Release prep incomplete. Fix: ${missing.join(", ")}.`,
259- );
260- }
261-
262172 # ============================================================================
263173 # Gate Check — All must pass
264174 # ============================================================================
265175 ci-gate :
266176 name : CI Gate
267177 runs-on : ubuntu-latest
268- needs : [dotnet, bun, fpf-doctor, playwright, security, release-prep ]
178+ needs : [deno, fpf-doctor, playwright, security]
269179 if : always()
270180 steps :
271181 - name : Check all jobs passed
272182 run : |
273- if [[ "${{ needs.dotnet.result }}" == "failure" ]] || \
274- [[ "${{ needs.bun.result }}" == "failure" ]] || \
275- [[ "${{ needs.security.result }}" == "failure" ]] || \
276- ([[ "${{ github.event_name }}" == "pull_request" ]] && [[ "${{ needs.release-prep.result }}" == "failure" ]]); then
183+ if [[ "${{ needs.deno.result }}" == "failure" ]] || \
184+ [[ "${{ needs.security.result }}" == "failure" ]]; then
277185 echo "One or more required jobs failed"
278186 exit 1
279187 fi
0 commit comments