/** * Sanity-check `DATABASE_URL` early in bootstrap so a malformed value * fails fast with a clear, actionable message instead of an opaque * Prisma error deep into the request lifecycle. * * The most common malformation is a literal special character in * POSTGRES_PASSWORD that the contributor forgot to URL-encode (`@`, * `#`, `:`, `/`, `?`, `%`, `&`, `=`, `+`, `;` all require it). This * is the same family of bug that bit pgweb in #63 — pgweb's compose * was switched to discrete CLI flags to bypass URL parsing entirely, * but Prisma requires a URL string, so we have no equivalent escape * hatch here. Documentation + early validation are the v1 fix. */ const URL_ENCODE_HINT = 'If POSTGRES_PASSWORD contains special characters (@ # : / ? % & = + ;), ' + 'they must be URL-encoded in DATABASE_URL — e.g., "@" → "%40", "#" → "%23". ' + 'See apps/portal-bff/.env.example for the canonical form.'; export function assertDatabaseUrl(): void { const raw = process.env['DATABASE_URL']; if (!raw) { throw new Error( 'DATABASE_URL is not set. Copy apps/portal-bff/.env.example to ' + 'apps/portal-bff/.env and adjust.', ); } let parsed: URL; try { parsed = new URL(raw); } catch { throw new Error(`DATABASE_URL is not a valid URL. ${URL_ENCODE_HINT}`); } if (parsed.protocol !== 'postgresql:' && parsed.protocol !== 'postgres:') { throw new Error( `DATABASE_URL must use the "postgresql://" scheme; got "${parsed.protocol}".`, ); } // Heuristic — multiple "@" before the path almost always means the // password contains a literal "@" that should have been encoded. // Splitting on the parsed pathname isolates the userinfo+host // segment without depending on scheme-specific quirks. const beforePath = raw.split(parsed.pathname || '/')[0] ?? raw; const atCount = (beforePath.match(/@/g) ?? []).length; if (atCount > 1) { throw new Error( `DATABASE_URL has ${atCount} "@" before the database path — your password almost ` + `certainly needs URL-encoding. ${URL_ENCODE_HINT}`, ); } }