From d6602a3e5118203488f9e2d793d4a2af33821e93 Mon Sep 17 00:00:00 2001 From: Semih Yesilyurt Date: Mon, 1 Jun 2026 19:05:34 +0300 Subject: [PATCH] fix(config): preprocess empty env strings to undefined for .url() fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PCAT_SOURCE_DB_URL and EMEX_SOURCE_DB_URL are declared as `z.string().url().optional()` (and `.optional()` resp). docker-compose's `${VAR:-}` substitution ALWAYS sets the env var, even to "", so when the Coolify env is unset the container receives PCAT_SOURCE_DB_URL="". zod's `.optional()` only accepts undefined, so `.url()` then rejects "" and the api crashes on boot with "Invalid url". This is exactly what took prod down on commit 939e4dc — the dev→main merge brought in the catalog-source env schema without the empty-string preprocess. Hotfixed by setting the env to a dummy URL via Coolify DB; this patch makes the schema resilient permanently so future env edits that clear the value won't recrash boot. Apply a preprocess that maps empty/whitespace strings to undefined before the URL check fires. Mirror the same treatment on EMEX so it can also be unset without surprises. Co-Authored-By: Claude Opus 4.7 --- packages/config/src/index.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 6838140..d64e29a 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -118,8 +118,17 @@ export const envSchema = z.object({ .string() .transform((v) => v === "true") .default("false"), - PCAT_SOURCE_DB_URL: z.string().url().optional(), - EMEX_SOURCE_DB_URL: z.string().optional(), // mysql://... — not a strict URL per WHATWG + // docker-compose's `${VAR:-}` substitution always sets the env, even if to + // an empty string. zod's `.optional()` only accepts undefined, so a chained + // `.url()` would reject "" and crash boot — preprocess "" → undefined first. + PCAT_SOURCE_DB_URL: z.preprocess( + (v) => (typeof v === "string" && v.trim() === "" ? undefined : v), + z.string().url().optional(), + ), + EMEX_SOURCE_DB_URL: z.preprocess( + (v) => (typeof v === "string" && v.trim() === "" ? undefined : v), + z.string().optional(), + ), // mysql://... — not a strict URL per WHATWG // Per-source kill switches under the master CATALOG_SOURCE_DB_ENABLED. // EMEX_SOURCE_DB_ENABLED keeps the connection pool alive but, per the // 2026-06-01 safety audit, fetchCategoryParts ALWAYS returns null unless the