Skip to content

CI/CD

All GitHub Actions workflows live in .github/workflows/. There are three:

File Trigger Purpose
ci.yml Pull requests + pushes to main, demo Lint, unit tests, build, bundle budgets, Playwright e2e
release.yml workflow_run when CI completes on main Run semantic-release to tag and changelog
supabase-backup.yml Daily cron at 02:00 UTC + workflow_dispatch Dump Postgres schema + data (see backup-restore.md)

CI (ci.yml)

Runs two jobs: ci (lint + test + build + bundle-size) and e2e (Playwright, skipped for Dependabot because it can't see repo secrets).

Steps in the ci job

  1. Checkout
  2. Setup Node.js 24 with npm cache
  3. npm ci
  4. npm run lint
  5. npm run test:run (Vitest, non-watch mode)
  6. npm run build — production Vite build, needs all VITE_* secrets in the environment
  7. node scripts/check-bundle-budgets.mjs — see bundle-budgets.md
  8. Upload dist/ as an artifact (3-day retention)

A failure at any step fails the PR check.

Steps in the e2e job

Runs after ci succeeds. Installs Chromium, runs npm run e2e (Playwright), uploads the HTML report on failure.

Tip

If you are a Dependabot dep-bump PR author, the e2e job is deliberately skipped (the bot doesn't have access to VITE_SUPABASE_URL etc.). The ci job alone is authoritative for dep bumps.

Reproducing a failing check locally

# match CI exactly
nvm use 24                        # or fnm use 24
npm ci
npm run lint
npm run test:run
npm run build
node scripts/check-bundle-budgets.mjs

For the e2e job:

npx playwright install --with-deps chromium
npm run e2e
# open the last run's report:
npm run e2e:report

Common gotchas:

  • Build fails locally with "missing env": copy env.example to .env.local and fill in the VITE_* values. These must be present at build time (Vite inlines them).
  • Bundle-budget failure locally but green on CI (or vice versa): you forgot npm run build before running the budget script. The script reads dist/assets/ and exits early if the directory is missing.
  • Typecheck drift: CI doesn't run a standalone tsc --noEmit, but npm run build invokes the TypeScript compiler through Vite. Run npx tsc --noEmit locally if you want the faster, type-only check (CLAUDE.md requires this to be clean before PR review).

Release (release.yml)

Fires only after a successful CI run on main (on: workflow_run: workflows: ["CI"], branches: [main]). If CI fails, no release happens.

The job checks out full history (for tag detection), installs deps, and runs:

npx semantic-release

Configuration lives in .releaserc.json. Summary of behaviour:

  • Branches: only main produces releases.
  • Tag format: v${version}.
  • Version bumps (Angular commit-message convention):
Commit type Bump
feat: minor
fix: patch
perf: patch
revert: patch
refactor: patch
Footer BREAKING CHANGE: anywhere major
docs:, style:, test:, chore:, ci: no release
  • Changelog: CHANGELOG.md is regenerated and committed back to main with the commit message chore(release): <version> [skip ci] so the release commit does not re-trigger CI.
  • GitHub Release: a release is published with the generated notes.
  • npm: npmPublish: false — this is a private app, no package is pushed.

Warning

Only the commit subject line is parsed for the release type. A commit subject of chore: small tweak that quietly includes a breaking refactor will not trigger a major bump. Put BREAKING CHANGE: <explanation> in the commit footer (or use feat!: / fix!: shorthand) if the behaviour actually changed.

What happens when release fails

  1. Check Actions → Release for the failed run.
  2. Common cause: no releasable commits since the last tag (every commit on main was docs:, chore:, etc.). This is not an error — semantic-release logs "There are no relevant changes, so no new version is released" and exits 0.
  3. If the job genuinely errored (auth, tag conflict), re-trigger with the Actions → Release → Run workflow button, or push an empty commit to main (git commit --allow-empty -m "chore: retry release") to drive another CI → Release cycle.

Supabase Backup (supabase-backup.yml)

Covered in backup-restore.md. Summary: daily cron dumps schema-only and data-only SQL, gzips, checksums, uploads as a 30-day artifact, optionally rotates to S3.

Adding a new workflow

  1. Place the YAML in .github/workflows/.
  2. Pin action versions by major tag (actions/checkout@v6, actions/setup-node@v6) — matches the convention in existing workflows.
  3. Secrets go through ${{ secrets.NAME }}. Never hardcode or echo them into logs.
  4. If the workflow needs Node, use Node 24 with cache: 'npm'.
  5. Add a row to the table at the top of this doc.