fix(config): select environment.prod.ts in the compiled @gauzy/config for production builds
Problem
Every production image of the API and the worker runs with the development environment: environment.production === false, envName: 'dev'.
Two things combined:
apps/api/project.jsonandapps/worker/project.jsonask webpack to replacepackages/config/src/environments/environment.tswithenvironment.prod.ts— a directory that does not exist (the files are underpackages/config/src/lib/environments/), so the replacement never matched anything.- Even with the right path it could not work: the API and worker bundles externalize
@gauzy/*.@gauzy/configis compiled on its own bynx build config(@nx/js:tsc, which has no file replacement) anddist/packages/configis copied intodist/apps/<app>/node_modules/@gauzy/configby the apps' webpack configs. AfileReplacementsentry in the consumer's build never reaches that copy. The package's ownlib:build:prodscript (scripts/replace-env-files.ts) also pointed at the missingsrc/environmentsdirectory, so it would have thrown had anything called it.
Evidence: every local --configuration=production build in this workspace has production: false, envName: 'dev' in dist/packages/config/src/lib/environments/environment.js and in the API's node_modules/@gauzy/config copy; the production API created gauzydevsess: session keys in Redis during the #10976 benchmarks.
Fix
The selection happens where the file actually lives — on the compiled package — and per application, at copy time, so the shared dist/packages/config is never rewritten by an app build (review finding by Greptile: two concurrent builds with different configurations must not affect each other).
packages/config/src/lib/environments/select-compiled-environment.ts— compiled with the package, used fromdist/with plainnode.compiledEnvironmentContent(dir, environment)returns whatenvironment.jsmust contain for the requested environment (production:environment.prod.jsbehind a marker line; development: the compiledenvironment.ts, or the preserved copy of it if the dist was switched to production), reading only.selectCompiledEnvironmentwrites it for a standalone production dist.apps/api/config/webpack-package-copy-patterns.js(shared by the worker's webpack config):getCopyPatterns(..., { environment })adds a CopyWebpackPlugintransformfor the@gauzy/configpackage only, which writesenvironment.jsin the app's ownnode_modulesascompiledEnvironmentContent(..., environment). Both webpack configs passctx.context.configurationName === 'production' ? 'production' : 'development'and log@gauzy/config environment for this build: ….config:select-environmenttarget (nx:run-commands, not cached,dependsOn: build,development/productionconfigurations) rewritesdist/packages/configitself — for a standalone production dist of the package;lib:build:prodnow runs it. App builds do not depend on it.- The
fileReplacementspaths are corrected too (they apply if a workspace package is ever bundled from source instead of externalized), README documents the mechanism.
config:build itself is untouched (renaming the tsc target broke dependency resolution: @nx/js:tsc finds a dependency's compiled output by a target of the same name).
Verification
nx build worker -c production --skip-nx-cache(full webpack build): logs@gauzy/config environment for this build: production;dist/apps/worker/node_modules/@gauzy/config/src/lib/environments/environment.jsstarts with the marker and hasproduction: true, whiledist/packages/config/.../environment.jsis unchanged (development).- Direct call of the copy transform against the real dist:
production→ marker +production: true;development→ the compiled development file; other files of the package pass through untouched; withoutenvironmentno pattern has a transform. nx run config:select-environment -c production --skip-nx-cache→ distenvironment.jsstarts with the marker and loads under node (four secrets set) asproduction=true envName=prod THROTTLE_ENABLED=true isDevelopment()=false; without secrets it throwsProduction startup aborted: insecure JWT secrets detectedasenvironment.prod.tsintends.nx run config:select-environment(development) →production: false; the same with a cachedconfig:buildafter a production selection → restored.select-compiled-environment.spec.ts: 14 tests (content for either environment in either dist state, substitution, restore, idempotency, tsc re-emit between selections, missing-file errors, argument parsing) — pass.git statusafter the builds: only the intended files changed; noenvironment.tsedits.
What changes at runtime in production images (stage first)
Everything environment.prod.ts differs in from environment.ts becomes effective for the API and the worker:
validateProductionSecrets()at module load:JWT_SECRET,JWT_REFRESH_TOKEN_SECRET,JWT_VERIFICATION_TOKEN_SECRET,EXPRESS_SESSION_SECRETmust be set and not well-known defaults, or the process refuses to start. The stage and prod manifests set all four from secrets;bootstrap/validate-secrets.tsalready enforced the same underNODE_ENV=production.THROTTLE_ENABLEDdefaults to on (THROTTLE_TTL60 s,THROTTLE_LIMIT60000 unless set).- Express session keys use the
gauzyprodsess:prefix (one-time re-login for sessions stored under the old prefix). - i18n
watch: false(app.module.ts,seeder.module.ts);isDevelopment()is false; seeding and job-search take their production paths (no random fake job posts when the Gauzy AI integration is down);baseUrl/clientBaseUrldefaults are the gauzy.co URLs (env vars override as before). - Demo is built with
NODE_ENV=developmentand is unaffected. Desktop/Electron server builds have their own webpack configs and keep the development environment as today.
Per the owner's rule for Gauzy API/UI: develop → stage only; watch stage for the points above before any production promotion.
scripts/replace-env-files.ts is left in place (unused, and it edits source files); it can go in a later cleanup if wanted.
🤖 Generated with Claude Code