Skip to content

Deploy Environments (Dev / Staging / Production)

Neemias uses three deployment environments controlled by the VITE_DEPLOY_ENV build-time variable.

Git hooks (Husky)

The project uses Husky for versioned hooks (installed automatically via pnpm install):

HookFileWhat it does
pre-commit.husky/pre-commit6 gates: lint-staged, as any regression, Biome check, typecheck, doc lint, unit tests, E2E smoke
pre-push.husky/pre-pushDoc drift detection (route count vs README, permissions, config files)

If any gate fails, the commit or push is blocked with the error message.

Note: Full tests run on pre-commit, not pre-push. Pre-push focuses on detecting drift between code and documentation. See docs/quality/quality-gates.md for details.

Deploy script

scripts/deploy.sh automates the full flow:

bash
./scripts/deploy.sh          # dev (default)
./scripts/deploy.sh staging  # staging
./scripts/deploy.sh prod     # production

The script runs:

  1. pnpm -r test — all monorepo tests
  2. DEPLOY_ENV=$ENV pnpm build:app — frontend build with the correct environment
  3. npx wrangler deploy — Worker deploy
  4. npx wrangler pages deploy app/dist --project-name neemias — frontend deploy

Note: The ENV argument is used for the build (DEPLOY_ENV) but not for wrangler deploy, since wrangler.toml has no [env.staging] or [env.production] sections. For isolated environments, configure branches or separate Pages projects.

Environment matrix

EnvironmentVITE_DEPLOY_ENVSeed runs?Example URL
Dev (remote)dev (default)✅ Full seedneemias.app
Dev (local)dev✅ Full seedlocalhost:5173
Stagingstaging✅ Full seedstaging.neemias.app
Productionprod❌ Skippedapp.neemias.app

How it works

Frontend (app/vite.config.ts)

ts
define: {
  __DEPLOY_ENV__: JSON.stringify(process.env.DEPLOY_ENV ?? "dev"),
}

When DEPLOY_ENV is not set (local dev, or Pages secret not configured), the default is "dev" — the seed runs normally.

Seed guard (app/src/db/seed.ts)

ts
const deployEnv = typeof __DEPLOY_ENV__ !== "undefined" ? __DEPLOY_ENV__ : "dev";
if (deployEnv === "prod") {
  return; // no demo data in production
}

Backend seed (workers/src/routes/seed.ts)

The worker seed (POST /api/v1/_seed) is environment-agnostic — it runs whenever called, but only if the users table is empty (ALREADY_SEEDED guard). It now creates 500 enriched students with photos, guardians, birth dates, phones, addresses, class assignments, nuclei, allergies, and special needs.

Configuring Cloudflare Pages

Dev (default — neemias.app)

No secret needed — default is dev. The seed runs automatically.

Staging

bash
echo 'staging' | wrangler pages secret put DEPLOY_ENV --project-name neemias-staging

Production

bash
echo 'prod' | wrangler pages secret put DEPLOY_ENV --project-name neemias-prod

Important: The secret value must be ONLY the environment name (dev, staging, or prod), NOT the full KEY=VALUE line. See secret-value-url-only for context.

DEPLOY_ENV is injected at build time via Vite define as __DEPLOY_ENV__. Unlike VITE_* env vars, it does NOT use the VITE_ prefix — this avoids conflicts with Vite's built-in import.meta.env handling which can cause issues in production builds.

Data flow per environment

text
┌──────────┐     ┌─────────────────────┐     ┌──────────────────────┐
│  Dev/Stg │────▶│ Frontend IndexedDB  │◀────│ Worker D1 (seed)     │
│  (seed)  │     │ 500 students + 7 cls │     │ 500 students + 7 cls  │
└──────────┘     └─────────────────────┘     └──────────────────────┘

┌──────────┐     ┌─────────────────────┐     ┌──────────────────────┐
│   Prod   │────▶│ Frontend IndexedDB  │◀────│ Worker D1 (empty)    │
│ (no seed)│     │ Real data only      │     │ Real data only       │
└──────────┘     └─────────────────────┘     └──────────────────────┘

In dev and staging, both the frontend IndexedDB and the backend D1 are populated with demo data. The frontend seed populates IndexedDB directly; the backend seed populates D1 when POST /api/v1/_seed is called.

In production, neither seed runs. All data comes from real usage — users created by admins, students registered through the app, and attendance captured in the field.

Switching environments

  1. Set/update the VITE_DEPLOY_ENV secret on Cloudflare Pages
  2. Re-deploy the frontend (pnpm build:app && wrangler pages deploy)
  3. For the backend, call POST /api/v1/_seed if moving to dev/staging (after clearing existing users in D1)

Distributed under MIT License.