CI / CD graph
Five GitHub Actions workflows on Blacksmith runners (2-vCPU Ubuntu 24.04).
All run on Blacksmith (blacksmith-2vcpu-ubuntu-2404).
flowchart TD
PR([Pull request opened / updated])
PUSH([Push to main])
CRON4H([Cron: every 4 hours])
CRON30M([Cron: every 30 minutes])
MANUAL([Manual workflow_dispatch])
PR --> CI
PUSH --> CI
PUSH --> DEPLOY
PUSH --> PULUMI_APPLY
PR --> PULUMI_PREVIEW
CRON4H --> E2E_PROD
CRON30M --> SMOKE
MANUAL --> SMOKE
subgraph CI["ci.yml — Lint · Test · Build"]
LTB["Job 1: lint-typecheck-test-build
──────────────────────────────
• pnpm install (cached)
• oxlint
• oxfmt --check
• Drizzle journal sync check
• tsc (Turbo affected)
• build workspace packages
• vitest unit tests
• build affected apps"]
E2E["Job 2: e2e-sync
──────────────────────────────
• Docker: 3× Postgres + Redis + MinIO
• Bootstrap Directus (migrations + seed)
• Bootstrap Vendure (migrations + seed)
• Start scheduler-api (tsx)
• Run e2e test suite (vitest)
• Cleanup Stripe test accounts"]
LTB -->|must pass| E2E
end
subgraph DEPLOY["deploy-production.yml — Build Docker images"]
DC["detect-changes
Turbo affected analysis"]
DV["deploy-vendure-server
+ vendure-worker"]
DD["deploy-directus"]
DS["deploy-scheduler-api"]
DSI["deploy-sites
site-main · site-shop · site-hypno"]
DP["deploy-portal"]
DPB["deploy-portal-business
+ portal-business-web"]
DDOC["deploy-docs"]
DC --> DV & DD & DS & DSI & DP & DPB & DDOC
end
subgraph PULUMI_PREVIEW["pulumi.yml (PR preview)"]
PP["pulumi preview --stack stage
Posts plan as PR comment"]
end
subgraph PULUMI_APPLY["pulumi.yml (push apply)"]
PA["pulumi up --stack stage
Auto-approves resource updates"]
end
subgraph E2E_PROD["e2e-prod.yml — Production health checks"]
EP["Full-flow tests against Railway
• Real DB writes + cleanup
• Tenant provisioning + onboarding
On failure: open/update GitHub issue
label: production-alert-e2e"]
end
subgraph SMOKE["smoke-prod.yml — Quick smoke"]
SP["Read-only health checks
No writes to production data"]
end
E2E sync test suite
Section titled “E2E sync test suite”Tests that run in the e2e-sync job (write to local Docker DBs, cleaned up after):
| Test file | What it covers |
|---|---|
business-cascade.e2e.test.ts |
Tenant provisioning → full convergence sync |
convergence-drift-reconcile.e2e.test.ts |
Mirror sync after Directus/Vendure drift |
booking-reminders.e2e.test.ts |
SMS/email reminders via BullMQ |
link-orphans.e2e.test.ts |
Orphan cleanup + reconciliation |
public-payments-smoke.test.ts |
Public API booking + Stripe flow |
workos-oidc-wiring.e2e.test.ts |
Directus OIDC (skipped unless WORKOS_E2E_ENABLED) |
Deploy job per service
Section titled “Deploy job per service”Each deploy-* job:
- Checks out code
- Runs
turbo prune --scope <app>to isolate the sub-tree - Builds a multi-stage Docker image (pruner → installer → builder → runner)
- Pushes image to GHCR tagged with
${{ github.sha }} - Triggers Railway redeploy via GitHub → Railway integration
Known constraints
Section titled “Known constraints”scheduler-apitsc requiresNODE_OPTIONS=--max-old-space-size=6144on the 2-vCPU runner or it OOMs.- E2E sync builds a hardcoded subset of workspace packages before starting
scheduler-api; adding a new@nexus/*runtime dep requires a matching entry in.github/workflows/ci.yml. - Pulumi auto-runs
pulumi upon merge; never run it manually. e2e-prodcron was trimmed from*/10 * * * *to0 */4 * * *to reduce Railway API load.