Skip to content

CI/CD

PrintStudio uses GitHub Actions for CI. The pipeline is designed to fail fast — cheap checks (lint, types) run first, expensive checks (database tests) only run when upstream jobs pass.

push / PR
├── changes (detect changed packages)
├── workflow-lint (actionlint)
├── quality (lint + types + unit tests) ← depends on: changes
├── core-db-tests (integration tests with Postgres + Redis) ← depends on: quality
└── ci-ok (required status check — all jobs must pass)

Detects which packages changed in the PR/push using dorny/paths-filter. Downstream jobs use these outputs to skip work when irrelevant code changes.

outputs:
core: ${{ steps.filter.outputs.core }}
integrations: ${{ steps.filter.outputs.integrations }}
api: ${{ steps.filter.outputs.api }}
web: ${{ steps.filter.outputs.web }}

Runs actionlint to validate all GitHub Actions workflow files in .github/workflows/. Catches YAML syntax errors and type issues in workflow expressions before they cause mysterious CI failures.

Terminal window
# Run locally
brew install actionlint
actionlint

The main unit test and static analysis job. Runs on every push:

  1. Bun install — installs workspace dependencies
  2. Build packagesbun run build with Turbo cache
  3. Typechecktsc --noEmit across all packages
  4. Unit testsbun test with TEST_INFRA_SKIP=true (no Docker required)
  5. Lint — ESLint + Prettier check

This job runs without Docker services, keeping it fast (<2 min on typical PRs).

Integration tests that need a real database and Redis. Only runs when packages/core or packages/integrations change (using the changes job outputs).

Services started by the job:

services:
postgres:
image: postgres:16
env:
POSTGRES_DB: printstudio_test
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s

Steps:

  1. Bun install
  2. Run migrations against the test DB
  3. bun test (with real Postgres and Redis)

A synthetic job that depends on all other jobs. GitHub branch protection rules require this job to pass before merging. This pattern allows the required check list to be stable even as individual jobs are added or removed.

ci-ok:
needs: [quality, core-db-tests]
runs-on: ubuntu-latest
steps:
- run: echo "All checks passed"

Run the same checks locally before pushing:

Terminal window
# Quality checks (no Docker)
TEST_INFRA_SKIP=true bun run test
bun run typecheck
# Integration tests (needs Docker)
bun run test:infra:up
bun run test
bun run test:infra:down

The main branch requires:

  • ci-ok to pass (covers all jobs)
  • At least 1 approving review
  • No unresolved review comments

A minimal deploy job pattern:

deploy:
needs: [ci-ok]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy API
run: fly deploy --app printstudio-api
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}