Skip to content

Local Development

Prerequisites

  • Node.js20.x or >= 22.14 (matches package.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

  1. Clone and install

    Terminal window
    git clone git@github.com:alokai/alokai-cms.git
    cd alokai-cms
    yarn install
  2. Create .dev.vars

    wrangler dev reads apps/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 32

    The .example file documents every variable. The default AUTH_JWT_SECRET is fine for local-only dev.

  3. Initialize the local D1 database

    Terminal window
    yarn workspace cms db:go

    This runs db:resetdb:migratedb:seed in sequence. After it completes you have:

    • A fresh local D1 in apps/cms/.wrangler/state/.
    • All schema migrations from apps/cms/migrations/ applied via wrangler d1 migrations apply --local.
    • One seeded super-admin: rick@alokai.com / 123qwe.
  4. Start the dev servers

    Terminal window
    yarn dev
    ServerURLPurpose
    Wrangler (Worker + API)http://localhost:8787Hot-reloads server code
    Vite (React SPA)http://localhost:5173HMR for frontend

    Open http://localhost:5173 and 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>).

CommandDescription
db:migrateApply pending migrations to local D1 (wrangler d1 migrations apply --local).
db:migrate:listShow local applied/pending migrations.
db:migrate:stagingApply pending migrations to staging D1 (remote).
db:migrate:prodApply pending migrations to production D1 (remote).
db:migrate:list:stagingShow staging applied/pending migrations.
db:migrate:list:prodShow production applied/pending migrations.
db:migrate:backfill:stagingOne-time backfill — mark all current migrations as applied (see below).
db:migrate:backfill:prodSame, for production.
db:seedPOST /api/auth/seed to create the super-admin user.
db:resetDelete .wrangler/state (local DB).
db:godb:resetdb:migratedb:seed.

Adding a new migration

  1. Create the next file in order: apps/cms/migrations/035_your_change.sql.
  2. Use idempotent statements where SQLite supports it: CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, INSERT OR IGNORE. Wrangler’s d1_migrations tracker is the idempotency layer for everything else (ALTER ADD COLUMN, RENAME, DROP).
  3. Apply locally: yarn workspace cms db:migrate.
  4. 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), update runIncrementalMigrations to include the same DDL wrapped in try { ... } catch { /* already exists */ }.

Running tests

Terminal window
yarn workspace cms test

Tests use Vitest with @cloudflare/vitest-pool-workers for Worker-runtime fidelity.

Building

Terminal window
yarn workspace cms build

Outputs:

  • apps/cms/dist/ — React SPA, served as static assets by the Worker.
  • Wrangler bundles apps/cms/src/server/index.ts at 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:

Terminal window
# 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.ts

Requirements: 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 dev complains about an unauthenticated account — run npx wrangler login.
  • db:go fails on db:seed — the Worker isn’t up yet. Start yarn dev:worker, wait for the “Ready on http://localhost:8787” line, then run yarn workspace cms db:seed separately.
  • Schema drift / “no such column”yarn workspace cms db:go fully resets local state.
  • AUTH_PROVIDER=cloudflare-access rejects every request locally — Cloudflare Access can’t sign tokens for localhost. Leave AUTH_PROVIDER=none for local dev unless you have a tunnelled hostname.
  • TOKEN_ENCRYPTION_KEY errors on API key reveal — the key must be exactly 64 hex chars. Re-run openssl rand -hex 32.