feat(env): four env targets — localhost, dev, uat, prod#33
Merged
Conversation
Adds a third Vite mode (`uat`) backed by `.env.uat`, plus `dev:uat` and `build:uat` scripts, so the local dev server and local builds can target the UAT backend. Local-only: no CI deploy, no infra, no source changes. Documents the CORS constraint — api.ts uses an absolute baseURL, bypassing the Vite proxy, so the UAT backend must allowlist http://localhost:3304. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two tasks: (1) .env.uat + dev:uat/build:uat scripts, verified by grepping the built bundle for the inlined UAT host; (2) document the mode in .env.example and CLAUDE.md, which are the only committed record since .env.uat is ignored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a third Vite mode targeting the UAT backend. vite.config.ts is already mode-generic via loadEnv(mode, ...), so no config or source change is needed — only the scripts and a gitignored .env.uat file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.env.uat is gitignored, so these two files are the only committed record of how to recreate it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.env.example documented only UAT's base URL, so copying it paired UAT with the DEV app id hardcoded in the file body — requests would be rejected by AppIdGuard. Document UAT's own app id and REACT_APP_ENV. App ids are public identifiers: the DEV one is already committed here and api.ts ships it as a plain header. docs/DEVELOPMENT.md was missed by the spec and still enumerated exactly two modes; mirror the CLAUDE.md edits into it. Also correct the "(proxied in dev)" claim: api.ts uses an absolute baseURL, so the vite.config.ts proxies never fire for service calls in any mode, and every mode depends on backend CORS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renaming .env.development/.env.production means renaming the Vite modes, since Vite derives the filename from the mode. Verified against Vite 8.1.0 before designing: bare vite/build/preview work today only because the filenames match Vite's default mode names, so 8 scripts must gain an explicit --mode. The suspected NODE_ENV flip was refuted by build probe — NODE_ENV comes from the command, not the mode name. Adds a fail-fast guard for the two required REACT_APP_ vars, replacing the silent localhost fallback. loadEnv merges process.env, so CI is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three tasks: (1) fail-fast guard, landing first so the rename never has a silent-failure window — RED proves the current localhost fallback hides a missing env file, GREEN proves the guard throws; (2) rename the files on disk and pin --mode on all 9 scripts; (3) docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vite.config.ts fell back to http://localhost:4000 when REACT_APP_API_BASE_URL was absent, so a missing .env file silently produced a bundle pointed at nothing. Require both REACT_APP_API_BASE_URL and REACT_APP_API_APP_ID (both are mandatory — api.ts sends the app id as x-app-id and the backend enforces it) and throw naming the mode and expected file. loadEnv merges prefixed process.env vars, so CI is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vite derives the env filename from the mode, so renaming .env.development -> .env.dev and .env.production -> .env.prod is a mode rename. The old filenames matched Vite's default mode names, which is the only reason a bare `vite`, `vite build`, or `vite preview` picked up an env file; every script now passes --mode explicitly. uat is unchanged. CI is unaffected: loadEnv merges prefixed process.env vars, so the workflows never depended on an .env file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.env.dev and .env.prod are gitignored, so these three files are the only committed record of the modes. Also documents that every script must pass --mode explicitly, since Vite's default modes no longer match any file. Also corrects two errors in the spec/plan for this branch: the vite.config.ts apiTarget fallback only ever fed the dev-server proxy (never the client bundle), so removing it doesn't fix a build-time silent failure — the real one is src/services/api.ts:4 inlining REACT_APP_API_BASE_URL as undefined; and .env.dev/.env.prod carry no REACT_APP_ENV key at all (only .env.uat sets it), so the Login/Landing badge only ever renders under uat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The header line was padded to satisfy an alignment check that accidentally scoped over it along with the five data lines it was meant to align. The check was wrong; the ~40-char gap read as a typo. Data lines 2-6 are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vite loads a bare .env in EVERY mode — like the .env.local the docs already warn against — and it satisfies the new required-env guard. So `cp .env.example .env` gave a fresh clone all three modes silently pointed at the deployed DEV backend, with no signal distinguishing dev:local from dev:prod and no uat badge: the exact silent misconfiguration this branch exists to prevent. Document per-mode copies instead, retarget the .env references at the mode file, warn against bare .env alongside .env.local, and annotate preview's mode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Troubleshooting still told readers to check REACT_APP_API_BASE_URL "in .env" — the one reference the setup cleanup missed, and one that contradicts the same file's warning never to create a bare .env. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supersedes the two-mode scheme on this branch. Four honestly-named targets, each reachable by dev:X and build:X. Mode `local` is illegal in Vite (verified: it throws, conflicting with the .local postfix), so the local-backend mode is named `localhost`; script names are free-form so dev:local still works. Fixes an inherited misnaming: mode `prod` held the DEV backend. prod now has its own slot, pointing at DEV as a documented placeholder until production exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two tasks: (1) mv .env.local -> .env.localhost, add REACT_APP_ENV to the existing .env.prod, 12 scripts, guard hint — RED shows dev:local currently resolving to the DEV backend and dev:dev missing; (2) docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds dev:dev/build:dev and splits the local backend onto its own mode, fixing an inherited misnaming: mode `prod` held the DEV backend, and `dev:local` therefore ran against dev.blueledgers.com rather than localhost. The local mode is named `localhost` because Vite throws on a mode named `local` (it conflicts with the .local postfix). Script names are free-form, so dev:local still works. prod points at DEV as a documented placeholder until production is stood up; it keeps REACT_APP_ENV=production because .env.prod is production's config slot — the URL is the placeholder, not the label. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The four env files are gitignored, so these three files are the only committed record of them. Includes the loud PLACEHOLDER note on .env.prod: it points at the DEV backend while badging `· production`, and .env.example is the only place that can warn anyone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Troubleshooting entry for a failing local dev server still named .env.dev, which is now the deployed DEV backend's file. Local development reads .env.localhost. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three superseding waves on one branch left two kinds of residue. The superseded plans still read as live agentic instructions — unchecked boxes, "REQUIRED SUB-SKILL: implement this plan task-by-task", no supersession marker — and their script blocks mandate `dev:local` -> `--mode dev`, precisely the misnaming the final wave exists to fix. Mark all four as historical records. DEVELOPMENT.md's Troubleshooting still credited the dev proxy with handling self-signed certs, and CLAUDE.md's Service Layer still said "(proxied in dev)" — both refuted by this branch's own correction: api.ts uses an absolute baseURL, so the proxy never fires. Also note in .env.example that REACT_APP_ENV is optional for localhost/dev, since the documented `cp` procedure otherwise yields a badge the header doesn't imply. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The banner exists to stop someone executing a stale plan, so its first sentence should read cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
samutpra
added a commit
that referenced
this pull request
Jul 16, 2026
commit 79843fa Merge: 203c0cc 00a2a88 Author: Thammanoon Semapru <samutpra@users.noreply.github.com> Date: Thu Jul 16 15:34:20 2026 +0700 Merge pull request #37 from CarmenSoftware-organization/fix/deploy-env-prod-filename fix(ci): write .env.prod (not .env.production) so the prod build finds its env commit 00a2a88 Author: Thammanoon Semapru <samutpra@users.noreply.github.com> Date: Thu Jul 16 15:32:45 2026 +0700 fix(ci): write .env.prod (not .env.production) so the prod build finds its env `build:prod` runs `vite build --mode prod`, which loads `.env.prod`. The deploy workflow wrote `.env.production` — a name Vite never loads for mode "prod" — so the file was ignored and the build failed the env guard: Error: [env] Missing REACT_APP_API_BASE_URL, REACT_APP_API_APP_ID for mode "prod". Left over from the env refactor (#33) that renamed .env.production -> .env.prod and switched to explicit `--mode prod`; the workflow and the deploy-gcs.sh comment still referenced the old name. Rename the written file to .env.prod and fix the stale comments. Verified locally: `bun run build:prod` succeeds with a .env.prod present. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> commit 203c0cc Author: Thammanoon Semapru <samutpra@users.noreply.github.com> Date: Thu Jul 16 15:27:36 2026 +0700 chore: rename .env.uat to .env.production in GCS deployment workflow commit 84b711e Author: Thammanoon Semapru <samutpra@users.noreply.github.com> Date: Thu Jul 16 15:24:47 2026 +0700 chore: rename generated .env.production to .env.uat in deployment workflow commit c267613 Author: Thammanoon Semapru <samutpra@users.noreply.github.com> Date: Thu Jul 16 15:23:03 2026 +0700 chore: remove GCP deployment workflow file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restructures the Vite env-mode setup into four honestly-named targets. No
src/changes — the whole branch ispackage.json,vite.config.ts, and docs.The four targets
bun start,dev,dev:locallocalhost.env.localhosthttp://localhost:4000dev:devdev.env.devhttps://dev.blueledgers.com:4001dev:uatuat.env.uathttps://api-carmen-web.pncsb-app.comdev:prodprod.env.prodhttps://dev.blueledgers.com:4001build:*mirrorsdev:*—dev:Xandbuild:Xread the same file. All 12 scripts pin an explicit--mode.What this fixes
An inherited misnaming. Mode
prodheld the DEV backend (the original.env.productionwas documented as "deployed dev backend"). Sodev:localwas running againstdev.blueledgers.com, anddev:prodlied about where it pointed. Now each target has its own honest slot.A silent-failure class.
vite.config.tsnow throws ifREACT_APP_API_BASE_URLorREACT_APP_API_APP_IDis missing, replacing a|| 'http://localhost:4000'fallback. The real bug was worse than that fallback suggested:api.tsreadsimport.meta.env.REACT_APP_API_BASE_URLdirectly, so a missing env file produced a complete, exit-0 bundle runningaxios.create({ baseURL: undefined })— every request silently resolving against whatever origin served the SPA.Adds the UAT target (
.env.uat,dev:uat/build:uat). CORS verified live: the UAT backend answers preflight fromhttp://localhost:3304with204+access-control-allow-origin: *. No backend change needed.Why the mode is
localhostand notlocalVite hard-throws on a mode named
local— it conflicts with the.localenv-file postfix. That's why this repo originally used the awkwarddevelopment/productionnames. Script names are free-form, sodev:localstill works; it maps to--mode localhost.Related, and the reason the docs warn about it: a bare
.envor.env.localis not a mode file. Vite loads both in every mode, so either silently leaks across all four targets and satisfies the new guard.prodis a placeholderProduction isn't stood up yet, so
.env.prodpoints at DEV. It still setsREACT_APP_ENV=productiondeliberately:.env.prodis production's config slot, soproductionis the correct steady-state label — the URL is the placeholder, not the label. The alternative would need a future flip that gets forgotten, leaving real production badgeddevelopment.Accepted risk: during this window the badge reads
· productionwhile the app talks to DEV..env.examplecarries the warning, since the badge can't. When production exists: swap the URL and app id, delete the note. Nothing structural moves.Blast radius is local only — CI and Vercel supply
REACT_APP_*via process env, whichloadEnvmerges at higher precedence, so the placeholder can never reach a real deploy.Before merging
vercel.jsonrunsnpm run build→--mode prod..env.prodis gitignored, so the guard needs bothREACT_APP_vars set as Vercel project env vars. If they're missing the first deploy after merge fails loudly — the guard working as designed, but a surprise. (If Vercel deploys currently succeed, they're already set.) GitHub workflows are verified fine.Testing
453/453 Vitest, no
src/changes. Each of the four modes verified by grepping the built bundle for the inlined backend URL; barenpx vite buildverified to throw and emit zero assets;loadEnv('development')andloadEnv('production')— Vite's bare defaults — both verified to resolve to nothing, so there's no leak path around the guard.Known flake, pre-existing and unrelated:
NewsManagement.test.tsxcan fail under parallel load (15/15 isolated).🤖 Generated with Claude Code