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 --------------------------------------------------------------
/**
* 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
* (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<CredentialsMap>(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<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);
}
}