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):
| Hook | File | What it does |
|---|---|---|
pre-commit | .husky/pre-commit | 6 gates: lint-staged, as any regression, Biome check, typecheck, doc lint, unit tests, E2E smoke |
pre-push | .husky/pre-push | Doc 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:
./scripts/deploy.sh # dev (default)
./scripts/deploy.sh staging # staging
./scripts/deploy.sh prod # productionThe script runs:
pnpm -r test— all monorepo testsDEPLOY_ENV=$ENV pnpm build:app— frontend build with the correct environmentnpx wrangler deploy— Worker deploynpx wrangler pages deploy app/dist --project-name neemias— frontend deploy
Note: The
ENVargument is used for the build (DEPLOY_ENV) but not forwrangler deploy, sincewrangler.tomlhas no[env.staging]or[env.production]sections. For isolated environments, configure branches or separate Pages projects.
Environment matrix
| Environment | VITE_DEPLOY_ENV | Seed runs? | Example URL |
|---|---|---|---|
| Dev (remote) | dev (default) | ✅ Full seed | neemias.app |
| Dev (local) | dev | ✅ Full seed | localhost:5173 |
| Staging | staging | ✅ Full seed | staging.neemias.app |
| Production | prod | ❌ Skipped | app.neemias.app |
How it works
Frontend (app/vite.config.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)
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
echo 'staging' | wrangler pages secret put DEPLOY_ENV --project-name neemias-stagingProduction
echo 'prod' | wrangler pages secret put DEPLOY_ENV --project-name neemias-prodImportant: The secret value must be ONLY the environment name (
dev,staging, orprod), NOT the fullKEY=VALUEline. See secret-value-url-only for context.
DEPLOY_ENVis injected at build time via Vitedefineas__DEPLOY_ENV__. UnlikeVITE_*env vars, it does NOT use theVITE_prefix — this avoids conflicts with Vite's built-inimport.meta.envhandling which can cause issues in production builds.
Data flow per environment
┌──────────┐ ┌─────────────────────┐ ┌──────────────────────┐
│ 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
- Set/update the
VITE_DEPLOY_ENVsecret on Cloudflare Pages - Re-deploy the frontend (
pnpm build:app && wrangler pages deploy) - For the backend, call
POST /api/v1/_seedif moving to dev/staging (after clearing existing users in D1)