Skip to main content

Continuous Integration

Refract ships with three GitHub Actions workflows. Together they make sure every PR is safe to merge and that main always has a clean coverage baseline.

Overview

All three workflows run on ubuntu-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:
  1. Spins up Postgres and Redis as service containers.
  2. Installs deps and builds all workspace packages (the backend test suite needs the compiled tool packages in dist/).
  3. Runs the shared/tool package tests (pnpm test:modules).
  4. Runs the frontend Vitest suite.
  5. Downloads the coverage baseline from main — first from the main-coverage workflow artifact, then from a previous integration run as a fallback. If neither exists, it checks out main, generates coverage itself, then switches back to the PR branch.
  6. Runs the backend Jest suite and posts a coverage diff comment on the PR via jest-coverage-report-action.
The coverage comparison tells you at a glance whether your PR is increasing or decreasing test coverage.

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/frontend/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. This means you can never accidentally ship a frontend that’s out of sync with the backend’s GraphQL schema.

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:
InputDefaultWhat it does
dry_runfalseRuns the sync without committing — useful for debugging
source_rootapps/documentationWhere to read source docs from
target_rootapps/documentationWhere to write converted docs to
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), but you can run each piece:
# Run backend tests
make test module=backend

# Run frontend tests
make test module=frontend

# Run a single spec file
make test module=backend path=src/tools/queue/__tests__/loader.spec.ts

# Lint all packages
make lint-all

# Build all packages
make build-all

# Run codegen
make gql-codegen
💡 Tip: if a CI job fails on something that passes locally, the most common cause is a stale dist/ — the CI always builds from scratch. Try make clean-docker and make fresh-start to get a clean slate.

What’s next?

  • Testing — how tests are structured, coverage thresholds, and mocking patterns.
  • Linter — ESLint and Prettier configuration.
  • Documentation — how the docs sync workflow fits together.