mike/.github/workflows/ci.yml
QA Runner e25c2637ec ci: guard against silently merge-corrupted lockfiles
PR #233's CI failed with "npm ci can only install with an existing
package-lock.json" even though the file existed: a "Merge branch 'main'"
commit had auto-merged backend/package.json and package-lock.json into
invalid JSON with no conflict raised, and npm reports an unparseable
lockfile as if it were missing.

Two guards: .gitattributes marks package-lock.json/bun.lock merge=binary so
concurrent lockfile changes surface as explicit conflicts (resolve by
regenerating, never hand-merging), and CI parse-checks package.json and the
lockfile before npm ci so any corruption that still lands fails with the
real reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 10:23:15 -07:00

112 lines
4 KiB
YAML

# CI: build and test.
#
# Adapted from the amal66/mike fork's .github/workflows/ci.yml (monorepo
# layout) to this repository's backend/ + frontend/ layout. Test steps use
# `npm test --if-present`, and the eval job checks for evals/run.mjs, so this
# workflow is safe to merge before or after the test-harness and evals PRs:
# on a tree without those pieces the test steps no-op and the build still
# gates the merge. Beyond the fork version this also builds the frontend
# (placeholder NEXT_PUBLIC_* env — verified sufficient for `next build`) and
# runs eslint as a blocking gate (the error backlog is at zero).
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
backend:
name: Backend build and tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: backend/package-lock.json
# Git's line-level merge can splice both sides of package(-lock).json
# into invalid JSON without a conflict (bit PR #233). npm's own error
# for an unparseable lockfile is the misleading "npm ci can only
# install with an existing package-lock.json" — fail fast with the real
# reason instead.
- name: Validate package.json and lockfile parse
run: node -e "for (const f of ['package.json','package-lock.json']) JSON.parse(require('fs').readFileSync(f, 'utf8'))"
- run: npm ci
# No-ops on a tree without a "test" script (e.g. before the vitest
# harness PR merges); runs the suite once it exists.
- run: npm test --if-present
- run: npm run build
frontend:
name: Frontend build and tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: frontend/package-lock.json
# Same silent-merge-corruption guard as the backend job.
- name: Validate package.json and lockfile parse
run: node -e "for (const f of ['package.json','package-lock.json']) JSON.parse(require('fs').readFileSync(f, 'utf8'))"
- run: npm ci
# No-ops on a tree without a "test" script (e.g. before the vitest
# harness PR merges); runs the suite once it exists.
- run: npm test --if-present
# Blocking gate: the eslint error backlog was burned down in this PR
# (0 errors; warnings do not fail the step), so any new error fails CI.
- run: npm run lint
# Production build. NEXT_PUBLIC_* values are inlined at build time and
# only need to be well-formed here — nothing is contacted during build.
# This catches type errors (next build runs tsc) and broken routes/imports.
- run: npm run build
env:
NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY: sb_publishable_placeholder
NEXT_PUBLIC_API_BASE_URL: http://localhost:3001
# -----------------------------------------------------------------------
# Offline eval harness: deterministic scorecard for citation accuracy,
# prompt-injection resistance, and privilege/PII leakage. Runs against
# committed fixtures (no network, no LLM calls, no secrets), so it is cheap
# enough to gate every PR. --threshold 1.0 = every case must pass.
# Skips gracefully until the evals PR merges.
# -----------------------------------------------------------------------
evals:
name: Eval harness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Run eval harness (skips if evals/ not present)
run: |
if [ -f evals/run.mjs ]; then
node evals/run.mjs --threshold 1.0
else
echo "evals/run.mjs not present on this tree; skipping"
fi