Appearance
CI/CD
Forgejo Actions workflow at .forgejo/workflows/ci.yml. Runs on push to main and PRs to main.
Caching strategy
Every job that runs pnpm/turbo restores two caches via actions/cache@v4:
- pnpm store —
~/.local/share/pnpm/store/v3, keyed onpnpm-lock.yamlhash. Skips re-downloading unchanged deps. Note this only skips the network download; the per-projectnode_modules/symlink tree still has to be materialized on every install (~18s). Thebuildjob's artifact is how we skip that too. - Turbo outputs —
.turbo/, keyed on commit SHA with abase_ref/mainfallback. Replays per-package task outputs when inputs haven't changed — the biggest single win for build-heavy jobs.
Combined with actions/checkout@v4 using fetch-depth: 0 so Turbo can diff against main, this lets the check and test jobs run with --affected and skip packages whose source didn't change (e.g. a docs-only PR doesn't retypecheck api/web).
Shared build artifact
The build job runs once, produces apps/web/dist/ and services/api-hono/dist/, and tars up the fully-materialized node_modules/ with all its .pnpm/ virtual store links. It publishes the tarball as build-output-<sha>. Downstream e2e shards download + extract the artifact and skip both pnpm install and pnpm run build — ~80s saved per shard.
E2E sharding
e2e-pr and e2e-main are matrix-sharded (shard: ["1/2", "2/2"]) so the full Playwright suite runs in parallel across two runners. fail-fast: false — a failure in one shard doesn't cancel the other. With two runners registered (runner1 and runner2), both shards run simultaneously.
Jobs
1. check
Code quality gate — runs on every trigger.
- Set up Vite+ via
voidzero-dev/setup-vp@v1(Node + pnpm + cache) - Install dependencies (
vp install) - Run
vp run --cache -r check— format + lint + typecheck across all packages, plus a VitePress build of the docs workspace. A broken docs build (missing pages, invalid frontmatter, bad links) fails CI. Vite Task replays cached steps for unchanged packages.
2. test
Unit tests — runs on every trigger.
- Set up Vite+ and restore caches
- Run monorepo tests (
vp run --cache -r test) — Vite Task fingerprints inputs per package, so unchanged packages replay from cache.
3. build
Shared prerequisite for all e2e jobs — runs once after check passes. Builds every workspace project (api + web + docs) and publishes a build-output-<sha> artifact containing:
- Root
node_modules/(fully materialized with pnpm's.pnpm/virtual store) apps/web/node_modules/andapps/web/dist/services/api-hono/node_modules/andservices/api-hono/dist/tests/e2e/node_modules/(Playwright workspace)
Artifact retention is 1 day (intermediate build artifact, not a release asset).
4. e2e-pr
Smoke E2E tests — runs only on PRs, depends on build (which itself depends on check).
Container: mcr.microsoft.com/playwright:v1.58.2-jammy — Microsoft's official Playwright image with browsers + system deps pre-installed. The image tag is pinned to the @playwright/test version in tests/e2e/package.json and must be bumped in lockstep when upgrading Playwright.
Services: PostgreSQL 17 + Redis 7 (inline service containers).
Tests run: @smoke tag only (fast feedback). Sharded across two matrix shards that run in parallel.
Artifacts:
- Playwright HTML report (always uploaded)
- Test results — screenshots, videos, traces (uploaded on failure)
PR integration:
- Test results posted as PR comment via Forgejo API (creates or updates existing comment)
5. e2e-main
Full E2E suite — runs only on push to main, depends on build (which itself depends on check).
Container: same mcr.microsoft.com/playwright:v1.58.2-jammy image as e2e-pr.
Services: PostgreSQL 17 + Redis 7 (inline service containers).
Tests run: Full Playwright suite (no tag filter). Sharded across two matrix shards that run in parallel.
Artifacts:
- Playwright HTML report (always uploaded)
- Test results — screenshots, videos, traces (uploaded on failure)
6. deploy-docs
Deploys VitePress documentation to Cloudflare Pages — runs on push to main only, depends on check passing.
- Gate step:
git diff --name-only HEAD^ HEAD— skip the rest of the job when the push doesn't touchdocs/,pnpm-lock.yaml,vite.config.ts, or any rootpackage.json. - Set up Vite+ and restore caches.
- Build docs (
vp run --cache -F @libris/docs build, which also runs@libris/api-hono#build:specfor the OpenAPI spec via dependsOn) — replayed from Vite Task cache when unchanged. - Deploy
docs/.vitepress/distviawrangler pages deploy.
Secrets required: CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN
Environment Variables (CI)
Set on the e2e-pr and e2e-main jobs:
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_USER=libris_test
POSTGRES_PASSWORD=libris_test
POSTGRES_DB=libris_test
REDIS_HOST=redis
REDIS_PORT=6379
LIBRIS_INBOX_PATH=/tmp/e2e-inbox
LIBRIS_LIBRARY_PATH=/tmp/e2e-library
API_SECRET_KEY=ci-e2e-test-secret-key-minimum-32-chars!
COOKIE_DOMAIN=""
MIGRATIONS_PATH=./services/api-hono/migrationsPublish Images
Manual workflow at .forgejo/workflows/publish-images.yml. Builds and pushes Docker images to the Forgejo container registry.
Trigger: workflow_dispatch (run from Forgejo UI: Actions > Publish Images > Run workflow)
Jobs:
- version — Runs
changeset versionto bump package versions and update changelogs. Creates git tags (api-hono/v<version>,web/v<version>) and Forgejo releases with changelogs. - publish — Builds and pushes the unified Docker image (API + SPA).
Image produced:
registry.example.com/user/libris:v<api-version>-web<web-version>+latest
See docs/deployment.md for production usage.
Docker Test Services
docker-compose.test.yml for local testing:
| Service | Port | Config |
|---|---|---|
| PostgreSQL 17 | 5433 | DB: libris_test, tmpfs storage |
| Redis 7 | 6380 | Default storage |
PostgreSQL uses tmpfs for fast ephemeral storage. Redis uses its default in-memory store.