Continuous Integration
Refract ships with three GitHub Actions workflows. Together they make sure every PR is safe to merge and thatmain always has a clean coverage baseline.
Overview
All three workflows run onubuntu-latest and use Node 24 with pnpm 10.
integration.yml — runs on every PR
Trigger: any PR opened, updated, or reopened against main.
This is the main gate. It runs four parallel jobs, and all four must pass before a PR can merge.
tests job
The most involved job. It:
- Spins up Postgres and Redis as service containers.
- Installs deps and builds all workspace packages (the backend test suite needs the compiled tool packages in
dist/). - Runs the shared/tool package tests (
pnpm test:modules). - Runs the frontend Vitest suite.
- Downloads the coverage baseline from
main— first from themain-coverageworkflow artifact, then from a previous integration run as a fallback. If neither exists, it checks outmain, generates coverage itself, then switches back to the PR branch. - Runs the backend Jest suite and posts a coverage diff comment on the PR via
jest-coverage-report-action.
builds job
Runs pnpm -r run build across every workspace package. Catches TypeScript compile errors that don’t show up at runtime.
lints job
Runs pnpm -r run lint across every workspace package. Fails on any ESLint error.
codegen-verification job
Runs make gql-codegen (backend schema dump + frontend TypeScript hooks generation) and then checks whether any files changed. If the generated code in apps/portal/src/gql/hooks.ts or the schema file doesn’t match what’s committed, this job fails and tells you to run make gql-codegen and commit the result. make local-verification runs the same dirty-tree check after its codegen step.
This means you can never accidentally ship a frontend that’s out of sync with the backend’s GraphQL schema.
production-browser-static-verify job
Builds Dockerfile.production and smokes marketing routes (/, /pricing, /about, /blog → 301, /blog/ → 200, /admin). make local-verification runs the same make verify-production-browser-static target as its last step.
main-coverage.yml — runs on push to main
Trigger: any push to main (which in practice means every merged PR).
This workflow has one job: run the full backend Jest suite and upload the results as a main-jest-coverage artifact. That artifact is what integration.yml’s tests job downloads for coverage comparison.
The artifact is retained for 90 days. Keeping main’s coverage up to date means every new PR gets an accurate baseline to compare against rather than a stale one.
mintlify-docs-sync.yml — runs on PR merge or manual dispatch
Trigger: PRs merged into main, or manually via workflow_dispatch.
This workflow converts the source Markdown in apps/documentation/ to MDX, rewrites internal links to Mintlify-compatible routes, validates the output, and commits it back to the repo. Mintlify picks up the commit automatically.
See Documentation for a full breakdown of the sync script.
Running it manually
Go to Actions → mintlify-docs-sync → Run workflow. You can optionally set:
The workflow now hard-locks both source and target docs roots to
apps/documentation so no manual dispatch can accidentally write docs to another path (for example /documentation).
The sync report is uploaded as an artifact on every run (even dry runs) so you can inspect what changed.
Running CI locally
You can’t run the full CI pipeline locally (it needs GitHub’s service containers and parallel jobs), but you can approximate the quality gate.Preferred: make local-verification
Same migrate → lint → build → test → codegen → dirty-tree → production browser-static checklist as agents use before merge. Default is Docker Compose. On a runner with Postgres and Redis on localhost (cloud VM, or laptop without relying on docker compose exec for lint/test):
/shared, skip-install): Local verification.
What CI still does that this gate does not: coverage PR comments, parallel job speed, and other specialized jobs (dev-proxy Caddyfile, Dockerfile glob sync, Lighthouse). Dirty-tree codegen and production browser-static smoke run in both places.
Piecewise commands
💡 Tip: if a CI job fails on something that passes locally, the most common cause is a staledist/— the CI always builds from scratch. Trymake clean-dockerandmake fresh-startto get a clean slate.
What’s next?
- Local verification — compose vs native exec backend
- Testing — how tests are structured, coverage thresholds, and mocking patterns.
- Linter — ESLint and Prettier configuration.
- Documentation — how the docs sync workflow fits together.
