Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 51 additions & 143 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# CI Workflow — Build, Test, Lint
# Non-negotiable: This must pass before any deploy.
# Per AGENTS.md: GitHub Actions is the only deploy path.
# Runtime: Deno only (per AGENTS.md policy)

name: CI

Expand All @@ -16,124 +17,67 @@ concurrency:
cancel-in-progress: true

env:
DOTNET_VERSION: "8.0.x"
BUN_VERSION: "1.1"
NODE_VERSION: "20"
DENO_VERSION: "2.x"

jobs:
# ============================================================================
# .NET Build & Test (F# primary, C# secondary)
# Deno Build & Test (primary runtime per AGENTS.md)
# ============================================================================
dotnet:
name: .NET Build & Test
deno:
name: Deno Build & Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Skip if no .NET projects
if: ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') == '' }}
run: echo "No .NET project files found, skipping."

- name: Setup .NET
if: ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
uses: actions/setup-dotnet@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
deno-version: ${{ env.DENO_VERSION }}

- name: Restore dependencies
if: ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
run: dotnet restore

- name: Build
if: ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
run: dotnet build --configuration Release --no-restore

- name: Test
if: ${{ hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
run: dotnet test --configuration Release --no-build --verbosity normal --logger trx --results-directory ./TestResults

- name: Upload test results
uses: actions/upload-artifact@v4
if: ${{ always() && hashFiles('**/*.sln', '**/*.csproj', '**/*.fsproj') != '' }}
- name: Cache Deno dependencies
uses: actions/cache@v4
with:
name: dotnet-test-results
path: ./TestResults
retention-days: 7
path: |
~/.cache/deno
~/.deno
key: deno-${{ runner.os }}-${{ hashFiles('deno.lock', 'deno.json') }}
restore-keys: deno-${{ runner.os }}-

# ============================================================================
# Bun Build & Test (primary JS/TS runtime)
# ============================================================================
bun:
name: Bun Build & Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}

- name: Detect UI package
id: ui
- name: Install UI dependencies
run: |
if [ -f "src/ui/package.json" ]; then
echo "dir=src/ui" >> $GITHUB_OUTPUT
elif [ -f "package.json" ]; then
echo "dir=." >> $GITHUB_OUTPUT
else
echo "dir=" >> $GITHUB_OUTPUT
cd src/ui && deno install --node-modules-dir --allow-scripts
fi

- name: Skip if no package.json
if: ${{ steps.ui.outputs.dir == '' }}
run: echo "No package.json found, skipping."

- name: Install dependencies
if: ${{ steps.ui.outputs.dir != '' }}
working-directory: ${{ steps.ui.outputs.dir }}
run: bun install --frozen-lockfile

- name: Type check
if: ${{ steps.ui.outputs.dir != '' }}
working-directory: ${{ steps.ui.outputs.dir }}
run: |
if grep -q '"typecheck"' package.json; then
bun run typecheck
if grep -q '"typecheck"' deno.json 2>/dev/null; then
deno task typecheck
else
echo "No typecheck script configured"
echo "No typecheck task configured"
fi

- name: Lint
if: ${{ steps.ui.outputs.dir != '' }}
working-directory: ${{ steps.ui.outputs.dir }}
run: |
if grep -q '"lint"' package.json; then
bun run lint
else
echo "No lint script configured"
fi
run: deno lint

- name: Format check
run: deno fmt --check

- name: Test
if: ${{ steps.ui.outputs.dir != '' }}
working-directory: ${{ steps.ui.outputs.dir }}
run: |
if grep -q '"test"' package.json; then
bun run test
if grep -q '"test"' deno.json 2>/dev/null; then
deno task test
else
echo "No test script configured"
deno test --allow-all
fi

- name: Build
if: ${{ steps.ui.outputs.dir != '' }}
working-directory: ${{ steps.ui.outputs.dir }}
- name: Build UI
run: |
if grep -q '"build"' package.json; then
bun run build
if grep -q '"ui:build"' deno.json 2>/dev/null; then
deno task ui:build
else
echo "No build script configured"
echo "No ui:build task configured"
fi

# ============================================================================
Expand All @@ -146,15 +90,15 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
bun-version: ${{ env.BUN_VERSION }}
deno-version: ${{ env.DENO_VERSION }}

- name: Run FPF Doctor
run: |
if command -v bunx &> /dev/null && [ -f "fpf.config.ts" ]; then
bunx --bun @venikman/fpf doctor
if [ -f "fpf.config.ts" ]; then
deno run -A npm:@venikman/fpf doctor
else
echo "FPF not configured, skipping doctor checks"
fi
Expand All @@ -166,22 +110,31 @@ jobs:
playwright:
name: Playwright Tests
runs-on: ubuntu-latest
needs: [bun] # Only run if Bun build passes
needs: [deno]
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
deno-version: ${{ env.DENO_VERSION }}

- name: Install UI dependencies
run: |
if [ -f "src/ui/package.json" ]; then
cd src/ui && deno install --node-modules-dir --allow-scripts
fi

- name: Build UI
run: deno task ui:build

- name: Install Playwright browsers
run: deno task test:e2e:install

- name: Run Playwright tests
run: deno task test:e2e
continue-on-error: true # May not exist in all projects
continue-on-error: true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Playwright test failures are silently ignored in CI gate.

With continue-on-error: true, Playwright test failures won't fail the job. However, the CI gate (line 226-228) doesn't check needs.playwright.result, so test failures are effectively silent.

If Playwright failures should block merges, either:

  1. Remove continue-on-error: true, or
  2. Add needs.playwright.result check to the gate

If test flakiness is the concern, consider using retry logic instead.

🔧 Option A: Make Playwright failures block CI
      - name: Run Playwright tests
        run: deno task test:e2e
-        continue-on-error: true
🔧 Option B: Check Playwright in CI gate
      - name: Check all jobs passed
        run: |
          if [[ "${{ needs.deno.result }}" == "failure" ]] || \
+            [[ "${{ needs.playwright.result }}" == "failure" ]] || \
             [[ "${{ needs.security.result }}" == "failure" ]] || \
             ([[ "${{ github.event_name }}" == "pull_request" ]] && [[ "${{ needs.release-prep.result }}" == "failure" ]]); then

Based on learnings, Playwright tests are required for UI changes per AGENTS.md policy.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
continue-on-error: true
- name: Run Playwright tests
run: deno task test:e2e
Suggested change
continue-on-error: true
- name: Check all jobs passed
run: |
if [[ "${{ needs.deno.result }}" == "failure" ]] || \
[[ "${{ needs.playwright.result }}" == "failure" ]] || \
[[ "${{ needs.security.result }}" == "failure" ]] || \
([[ "${{ github.event_name }}" == "pull_request" ]] && [[ "${{ needs.release-prep.result }}" == "failure" ]]); then
🤖 Prompt for AI Agents
In @.github/workflows/ci.yaml at line 137, The CI workflow currently sets
continue-on-error: true for the Playwright job (playwright), which lets failures
pass silently; either remove continue-on-error: true from the playwright job to
make failures fail the workflow, or keep it but add a gate check that inspects
needs.playwright.result (e.g., update the gate condition that references
needs.playwright to require needs.playwright.result == 'success') so Playwright
failures will block merges as intended.


- name: Upload Playwright report
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -216,64 +169,19 @@ jobs:
with:
sarif_file: "trivy-results.sarif"

# ============================================================================
# PR Release Prep Checklist
# ============================================================================
release-prep:
name: PR Release Prep
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Validate release checklist
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request?.body ?? "";
const lines = body.split(/\r?\n/);
const labels = [
"Release notes summary",
"Rollout plan",
"Rollback plan",
"Flags/migrations",
];

const missing = [];
for (const label of labels) {
const line = lines.find((entry) =>
entry.toLowerCase().includes(label.toLowerCase())
);
if (!line) {
missing.push(`${label} (missing line)`);
continue;
}
const isChecked = /- \[[xX]\]/.test(line);
const isNa = /\bN\/A\b|\bNA\b/i.test(line);
if (!isChecked && !isNa) {
missing.push(label);
}
}

if (missing.length) {
core.setFailed(
`Release prep incomplete. Fix: ${missing.join(", ")}.`,
);
}

# ============================================================================
# Gate Check — All must pass
# ============================================================================
ci-gate:
name: CI Gate
runs-on: ubuntu-latest
needs: [dotnet, bun, fpf-doctor, playwright, security, release-prep]
needs: [deno, fpf-doctor, playwright, security]
if: always()
steps:
- name: Check all jobs passed
run: |
if [[ "${{ needs.dotnet.result }}" == "failure" ]] || \
[[ "${{ needs.bun.result }}" == "failure" ]] || \
[[ "${{ needs.security.result }}" == "failure" ]] || \
([[ "${{ github.event_name }}" == "pull_request" ]] && [[ "${{ needs.release-prep.result }}" == "failure" ]]); then
if [[ "${{ needs.deno.result }}" == "failure" ]] || \
[[ "${{ needs.security.result }}" == "failure" ]]; then
echo "One or more required jobs failed"
exit 1
fi
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/deploy-deno.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ jobs:
with:
deno-version: v2.x

- name: Install UI dependencies
run: |
if [ -f "src/ui/package.json" ]; then
cd src/ui && deno install --node-modules-dir --allow-scripts
fi

- name: Install deployctl
run: deno install -A --global jsr:@deno/deployctl

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ jobs:
with:
deno-version: v2.x

- name: Install UI dependencies
run: |
if [ -f "src/ui/package.json" ]; then
cd src/ui && deno install --node-modules-dir --allow-scripts
fi

- name: Install deployctl
run: deno install -A --global jsr:@deno/deployctl

Expand Down
Loading
Loading