Local Development
Prerequisites
- Node.js —
20.xor>= 22.14(matchespackage.json#engines). - Yarn 4 — comes via Corepack:
corepack enable. - Wrangler — installed as a workspace devDependency. Authenticate once:
Terminal window npx wrangler login - OpenSSL — for generating
TOKEN_ENCRYPTION_KEY(preinstalled on macOS/Linux).
First-time setup
-
Clone and install
Terminal window git clone git@github.com:alokai/alokai-cms.gitcd alokai-cmsyarn install -
Create
.dev.varswrangler devreadsapps/cms/.dev.vars(gitignored) for local secrets.Terminal window cp apps/cms/.dev.vars.example apps/cms/.dev.vars# generate the AES-256-GCM key and paste it into TOKEN_ENCRYPTION_KEY=openssl rand -hex 32The
.examplefile documents every variable. The defaultAUTH_JWT_SECRETis fine for local-only dev. -
Initialize the local D1 database
Terminal window yarn workspace cms db:goThis runs
db:reset→db:migrate→db:seedin sequence. After it completes you have:- A fresh local D1 in
apps/cms/.wrangler/state/. - All schema migrations from
apps/cms/migrations/applied viawrangler d1 migrations apply --local. - One seeded super-admin:
rick@alokai.com/123qwe.
- A fresh local D1 in
-
Start the dev servers
Terminal window yarn devServer URL Purpose Wrangler (Worker + API) http://localhost:8787Hot-reloads server code Vite (React SPA) http://localhost:5173HMR for frontend Open
http://localhost:5173and log in with the seeded user.
Project structure
apps/cms/├── src/│ ├── client/ # React SPA (Vite)│ ├── server/ # Hono Worker│ ├── shared/ # Types + schemas shared by client + server│ └── cli/ # CLI tool source├── migrations/ # D1 SQL migrations (wrangler-tracked)├── scripts/ # One-off DB scripts (e.g. backfill-d1-migrations.sql)├── .dev.vars.example # Template for local secrets├── package.json├── vite.config.ts└── wrangler.jsonc # Cloudflare Worker config (prod + staging envs)Database commands
All scripts run in the cms workspace (yarn workspace cms <script>).
| Command | Description |
|---|---|
db:migrate | Apply pending migrations to local D1 (wrangler d1 migrations apply --local). |
db:migrate:list | Show local applied/pending migrations. |
db:migrate:staging | Apply pending migrations to staging D1 (remote). |
db:migrate:prod | Apply pending migrations to production D1 (remote). |
db:migrate:list:staging | Show staging applied/pending migrations. |
db:migrate:list:prod | Show production applied/pending migrations. |
db:migrate:backfill:staging | One-time backfill — mark all current migrations as applied (see below). |
db:migrate:backfill:prod | Same, for production. |
db:seed | POST /api/auth/seed to create the super-admin user. |
db:reset | Delete .wrangler/state (local DB). |
db:go | db:reset → db:migrate → db:seed. |
Adding a new migration
- Create the next file in order:
apps/cms/migrations/035_your_change.sql. - Use idempotent statements where SQLite supports it:
CREATE TABLE IF NOT EXISTS,CREATE INDEX IF NOT EXISTS,INSERT OR IGNORE. Wrangler’sd1_migrationstracker is the idempotency layer for everything else (ALTER ADD COLUMN, RENAME, DROP). - Apply locally:
yarn workspace cms db:migrate. - If the change must also be visible on first cold-start of an empty DB
(the runtime self-heal in
apps/cms/src/server/migrate.ts), updaterunIncrementalMigrationsto include the same DDL wrapped intry { ... } catch { /* already exists */ }.
Running tests
yarn workspace cms testTests use Vitest with @cloudflare/vitest-pool-workers for Worker-runtime
fidelity.
Building
yarn workspace cms buildOutputs:
apps/cms/dist/— React SPA, served as static assets by the Worker.- Wrangler bundles
apps/cms/src/server/index.tsat deploy time.
Reproducing CI test failures locally
Some test failures only show up on the GitHub Actions ubuntu-latest runner — usually because of Linux-vs-macOS differences in miniflare’s native bindings, glibc, or async scheduling. To debug those without push-and-wait, run the suite inside a Linux container that mirrors the CI runner:
# Full suite, same command CI runs.scripts/test-ci-local.sh
# A single test file (forwarded to vitest).scripts/test-ci-local.sh tests/ab-impressions.test.tsRequirements: Docker. The script auto-detects your Verdaccio token from
.yarnrc.yml, mirrors the workspace into a node:22-bookworm container,
runs yarn install --immutable, and executes the same turbo test
target CI runs. Yarn cache is persisted across runs in a named Docker
volume so reruns finish in seconds.
Troubleshooting
- Port 8787 or 5173 already in use — kill the stray process or change the port (
PORT=5174 yarn dev:cms,wrangler dev --port 8788). wrangler devcomplains about an unauthenticated account — runnpx wrangler login.db:gofails ondb:seed— the Worker isn’t up yet. Startyarn dev:worker, wait for the “Ready on http://localhost:8787” line, then runyarn workspace cms db:seedseparately.- Schema drift / “no such column” —
yarn workspace cms db:gofully resets local state. AUTH_PROVIDER=cloudflare-accessrejects every request locally — Cloudflare Access can’t sign tokens forlocalhost. LeaveAUTH_PROVIDER=nonefor local dev unless you have a tunnelled hostname.TOKEN_ENCRYPTION_KEYerrors on API key reveal — the key must be exactly 64 hex chars. Re-runopenssl rand -hex 32.