From 5a43802a8e591a3d09970daef07a93b7a468edbd Mon Sep 17 00:00:00 2001 From: oonyeje Date: Wed, 8 Jul 2026 00:45:50 +0000 Subject: [PATCH] Apply the same env-value normalization to per-route JSON fragments in ROUTE_MAP --- src/server.ts | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/server.ts b/src/server.ts index a413cf6..b9c9330 100644 --- a/src/server.ts +++ b/src/server.ts @@ -40,6 +40,26 @@ type RouteMap = Record; // ----- Helpers -------------------------------------------------------------- +/** + * Try to parse a string as JSON. If parsing fails, run the string through + * normalizeEnvValue (in case the host shell escaped/quoted it) and retry. + * Returns the parsed JSON value, or null if both attempts fail. + */ +function safeJsonParse(raw: string): T | null { + const trimmed = raw.trim(); + try { + return JSON.parse(trimmed) as T; + } catch { + // fall through + } + const normalized = normalizeEnvValue(trimmed); + try { + return JSON.parse(normalized) as T; + } catch { + return null; + } +} + /** * Normalize a raw env-var value. Different hosting platforms apply different * (and surprising) transformations when you paste a JSON value into an @@ -61,11 +81,8 @@ function normalizeEnvValue(raw: string): string { const candidates: string[] = [ value, - // Strip a single surrounding layer of `"` or `'` stripWrappingQuotes(value), - // Unescape every \" -> " (handles Coolify double-escape) value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'), - // Combined: strip wrapping quotes AND unescape inner escapes stripWrappingQuotes(value).replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'), ]; @@ -103,11 +120,11 @@ const APPWRITE_BASE_URL = (process.env.APPWRITE_BASE_URL ?? 'https://appwrite.bs let PROJECT_CREDENTIALS: CredentialsMap = {}; try { - const normalized = normalizeEnvValue(process.env.PROJECT_CREDENTIALS ?? ''); - if (!normalized) { - throw new Error('PROJECT_CREDENTIALS is empty'); + const parsed = safeJsonParse(process.env.PROJECT_CREDENTIALS ?? ''); + if (!parsed) { + throw new Error('PROJECT_CREDENTIALS is empty or unparseable'); } - PROJECT_CREDENTIALS = JSON.parse(normalized) as CredentialsMap; + PROJECT_CREDENTIALS = parsed; } catch (err) { const message = err instanceof Error ? err.message : String(err); const raw = (process.env.PROJECT_CREDENTIALS ?? '').slice(0, 300); @@ -118,6 +135,9 @@ try { } // Parse ROUTE_MAP. Format: "/path=>{json};/path2=>{json2}" +// The inner {json} fragment can have the same escaping problems as top-level +// env vars, so we normalize the full route map as a raw string first, then +// also try safeJsonParse on each per-route fragment as a defensive fallback. const ROUTE_MAP: RouteMap = {}; if (process.env.ROUTE_MAP) { const normalizedRouteMap = normalizeEnvValue(process.env.ROUTE_MAP); @@ -128,10 +148,15 @@ if (process.env.ROUTE_MAP) { if (arrowIdx === -1) continue; const path = trimmed.slice(0, arrowIdx).trim(); const cfgStr = trimmed.slice(arrowIdx + 2).trim(); - try { - ROUTE_MAP[path] = JSON.parse(cfgStr) as RouteEntry; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); + + // Try direct parse first, then fall back to normalized parse (handles + // cases where the outer wrapper was stripped but inner quotes are still + // escaped). + const parsed = safeJsonParse(cfgStr); + if (parsed) { + ROUTE_MAP[path] = parsed; + } else { + const message = `expected valid JSON, got "${cfgStr.slice(0, 80)}"`; console.error(`[startup] Invalid ROUTE_MAP entry for "${path}":`, message); } }