fix(api): treat blank optional env values as unset - #185
Conversation
Reproduction detailVerified on Node v22.23.2 with zod 4.4.3, from 1. dotenv keeps blank keys. Parsing the committed 2. The old schema rejected that. Comparing the previous and new definitions against the three possible inputs: 3. It was fatal, not cosmetic. Because All four keys were reported together, so the failure is not specific to one of them. One reviewer question: I kept |
99c674b to
0f275ac
Compare
`.env.example` ships `VERCEL_TOKEN`, `VERCEL_OIDC_TOKEN`, `VERCEL_TEAM_ID` and `VERCEL_PROJECT_ID` with no value, and dotenv loads a bare `KEY=` as an empty string rather than omitting it. The schema marked those fields optional but validated them with `min(1)`, so an empty string counted as present and failed. `readConfig` parses instead of safe-parsing, which means the API crashed at import time with a `ZodError` naming all four keys. Copying the example file, the documented first step of the quickstart, therefore produced an instance that could not boot. Normalize blank strings to `undefined` before validation so an unset credential stays unset, while a value that is only whitespace is still rejected rather than silently accepted. Co-authored-by: Cursor <cursoragent@cursor.com>
0f275ac to
3683d9a
Compare
Summary
Fixes the API crashing at startup after copying
.env.exampleto.env, which is the documented first step of the self-host quickstart..env.exampleshipsVERCEL_TOKEN,VERCEL_OIDC_TOKEN,VERCEL_TEAM_IDandVERCEL_PROJECT_IDwith no value. dotenv loads a bareKEY=as an empty string rather than omitting the key, and the schema marked those fields optional while validating them withmin(1)— so""counted as present and failed. SincereadConfigusesEnvSchema.parserather thansafeParse, the result was fatal: the API exited during import with aZodErrornaming all four keys.OptionalUrlin the same file already normalized blank strings toundefined, so the intended behavior was established and these fields were simply missing it. This extracts that normalization asblankToUndefined, reuses it forOptionalUrl, and addsOptionalNonEmptyStringfor the affected optional credentials, includingFACILITY_AWS_CODEBUILD_PROJECT,FACILITY_AWS_CODEBUILD_CACHE_BASE_LOCATIONandPACKAGE_REGISTRY_TOKEN.A whitespace-only value is still rejected rather than silently accepted, so a genuinely malformed credential does not slip through as "unset".
Closes #183
Test plan
pnpm --filter @facility/api exec vitest run test/config.test.ts— 15 passed, including a new case asserting that blank Vercel credentials from a copied.env.exampleresolve toundefined""(too_small) while acceptingundefinedand a real value, and that the new schema accepts""as unsetpresent=true value=""when parsing the committed.env.example.env.exampleand reports{"ok":true,"version":"0.3.0","db":"ok"}on/healthVERCEL_*value being a validation errorScope note
This deliberately does not change
.env.example. Removing the blank keys would hide the same class of failure for anyone who writesKEY=by hand, and the schema is the correct place to decide that "present but empty" means "unset". Happy to also drop the blank lines from the example file if you would rather have both.