diff --git a/src/server.ts b/src/server.ts index 802bf44..a413cf6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -41,27 +41,59 @@ type RouteMap = Record; // ----- Helpers -------------------------------------------------------------- /** - * Normalize a raw env-var value. Some hosting platforms (Coolify included) - * wrap pasted JSON values in extra quotes when the user doesn't quote-escape - * properly, producing strings like: - * "{\"square\":{...}}" - * which JSON.parse cannot read at position 1. + * Normalize a raw env-var value. Different hosting platforms apply different + * (and surprising) transformations when you paste a JSON value into an + * env-var field. We've observed at least: * - * This helper: - * 1. Trims whitespace - * 2. Strips a single surrounding layer of `"` or `'` - * 3. Unescapes `\"` -> `"`, `\'` -> `'`, and `\\` -> `\` + * 1. Raw JSON pasted in: {"square":{"project":"x"}} + * 2. Wrapped in a single layer of quotes: "{"square":{"project":"x"}}" + * 3. Wrapped AND every inner quote escaped: "{\"square\":{\"project\":\"x\"}}" + * 4. Inner quotes escaped, no outer wrapping: {\"square\":{\"project\":\"x\"}} + * + * This helper walks through each plausible unescaping and returns the first + * one that produces a syntactically valid JSON string. If none succeed, the + * raw value is returned and JSON.parse will throw a clear error. */ function normalizeEnvValue(raw: string): string { + if (!raw) return raw; + let value = raw.trim(); - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); - value = value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'); + + 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, '\\'), + ]; + + // Dedupe and try each + const seen = new Set(); + for (const candidate of candidates) { + const trimmed = candidate.trim(); + if (seen.has(trimmed)) continue; + seen.add(trimmed); + try { + JSON.parse(trimmed); + return trimmed; + } catch { + // try next + } } - return value.trim(); + + return value; +} + +function stripWrappingQuotes(s: string): string { + if ( + (s.startsWith('"') && s.endsWith('"')) || + (s.startsWith("'") && s.endsWith("'")) + ) { + return s.slice(1, -1); + } + return s; } // ----- Config --------------------------------------------------------------- @@ -81,7 +113,7 @@ try { const raw = (process.env.PROJECT_CREDENTIALS ?? '').slice(0, 300); console.error('[startup] PROJECT_CREDENTIALS is not valid JSON:', message); console.error('[startup] Received value (first 300 chars):', raw); - console.error('[startup] Hint: in Coolify, paste the JSON without surrounding quotes. If the platform wraps it, set the env var to a single-line value with no leading/trailing whitespace.'); + console.error('[startup] Tried stripping wrapping quotes and unescaping \\"->". If you still see this, paste the JSON without any escaping.'); process.exit(1); }