Apply the same env-value normalization to per-route JSON fragments in ROUTE_MAP

This commit is contained in:
oonyeje 2026-07-08 00:45:50 +00:00
parent bc09e3a0cf
commit 5a43802a8e

View File

@ -40,6 +40,26 @@ type RouteMap = Record<string, RouteEntry>;
// ----- Helpers -------------------------------------------------------------- // ----- 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<T>(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 * Normalize a raw env-var value. Different hosting platforms apply different
* (and surprising) transformations when you paste a JSON value into an * (and surprising) transformations when you paste a JSON value into an
@ -61,11 +81,8 @@ function normalizeEnvValue(raw: string): string {
const candidates: string[] = [ const candidates: string[] = [
value, value,
// Strip a single surrounding layer of `"` or `'`
stripWrappingQuotes(value), stripWrappingQuotes(value),
// Unescape every \" -> " (handles Coolify double-escape)
value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'), value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'),
// Combined: strip wrapping quotes AND unescape inner escapes
stripWrappingQuotes(value).replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'), 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 = {}; let PROJECT_CREDENTIALS: CredentialsMap = {};
try { try {
const normalized = normalizeEnvValue(process.env.PROJECT_CREDENTIALS ?? ''); const parsed = safeJsonParse<CredentialsMap>(process.env.PROJECT_CREDENTIALS ?? '');
if (!normalized) { if (!parsed) {
throw new Error('PROJECT_CREDENTIALS is empty'); throw new Error('PROJECT_CREDENTIALS is empty or unparseable');
} }
PROJECT_CREDENTIALS = JSON.parse(normalized) as CredentialsMap; PROJECT_CREDENTIALS = parsed;
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : String(err); const message = err instanceof Error ? err.message : String(err);
const raw = (process.env.PROJECT_CREDENTIALS ?? '').slice(0, 300); const raw = (process.env.PROJECT_CREDENTIALS ?? '').slice(0, 300);
@ -118,6 +135,9 @@ try {
} }
// Parse ROUTE_MAP. Format: "/path=>{json};/path2=>{json2}" // 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 = {}; const ROUTE_MAP: RouteMap = {};
if (process.env.ROUTE_MAP) { if (process.env.ROUTE_MAP) {
const normalizedRouteMap = normalizeEnvValue(process.env.ROUTE_MAP); const normalizedRouteMap = normalizeEnvValue(process.env.ROUTE_MAP);
@ -128,10 +148,15 @@ if (process.env.ROUTE_MAP) {
if (arrowIdx === -1) continue; if (arrowIdx === -1) continue;
const path = trimmed.slice(0, arrowIdx).trim(); const path = trimmed.slice(0, arrowIdx).trim();
const cfgStr = trimmed.slice(arrowIdx + 2).trim(); const cfgStr = trimmed.slice(arrowIdx + 2).trim();
try {
ROUTE_MAP[path] = JSON.parse(cfgStr) as RouteEntry; // Try direct parse first, then fall back to normalized parse (handles
} catch (err) { // cases where the outer wrapper was stripped but inner quotes are still
const message = err instanceof Error ? err.message : String(err); // escaped).
const parsed = safeJsonParse<RouteEntry>(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); console.error(`[startup] Invalid ROUTE_MAP entry for "${path}":`, message);
} }
} }