diff --git a/apps/backend/src/config/env-overrides.ts b/apps/backend/src/config/env-overrides.ts index 555b4e7a..e47e5222 100644 --- a/apps/backend/src/config/env-overrides.ts +++ b/apps/backend/src/config/env-overrides.ts @@ -5,11 +5,21 @@ import type { AdminDefaultUser, RuntimeConfig, } from "../types/runtime-config.js"; -import { parseBoolean, parseInteger, parseJsonArray } from "./runtime-env.js"; +import { + parseBoolean, + parseInteger, + parseJsonArray, + parseCommaSeparatedStrings, +} from "./runtime-env.js"; import { deepMerge } from "./runtime-merge.js"; type RuntimeConfigPath = readonly [string, ...string[]]; -type RuntimeConfigValue = string | number | boolean | AdminDefaultUser[]; +type RuntimeConfigValue = + | string + | number + | boolean + | AdminDefaultUser[] + | string[]; type RuntimeEnv = Record; type EnvOverride = { @@ -347,31 +357,51 @@ function booleanEnv(env: string, configPath: RuntimeConfigPath): EnvOverride { }; } -function adminUsersJsonEnv(env: string, configPath: RuntimeConfigPath): EnvOverride { +function adminUsersJsonEnv( + env: string, + configPath: RuntimeConfigPath +): EnvOverride { return { env, path: configPath, read(rawValue) { - return parseJsonArray(rawValue) + return parseJsonArray(rawValue); }, - } + }; } -function corsOriginsEnv(env: string, configPath: RuntimeConfigPath): EnvOverride { +function stringArrayEnv( + env: string, + configPath: RuntimeConfigPath +): EnvOverride { + return { + env, + path: configPath, + read: parseCommaSeparatedStrings, + }; +} + +function corsOriginsEnv( + env: string, + configPath: RuntimeConfigPath +): EnvOverride { return { env, path: configPath, read(rawValue) { - const value = parseString(rawValue) + const value = parseString(rawValue); if (value === null) { - return null + return null; } - if (value.trim() === '*') { - return ['*'] + if (value.trim() === "*") { + return ["*"]; } - return value.split(',').map((s) => s.trim()).filter(Boolean) + return value + .split(",") + .map((s) => s.trim()) + .filter(Boolean); }, - } + }; } function parseString(rawValue: unknown): string | null { diff --git a/apps/backend/src/config/runtime-env.ts b/apps/backend/src/config/runtime-env.ts index 0b052246..699fa5ff 100644 --- a/apps/backend/src/config/runtime-env.ts +++ b/apps/backend/src/config/runtime-env.ts @@ -1,98 +1,113 @@ -import fs from 'node:fs' -import process from 'node:process' +import fs from "node:fs"; +import process from "node:process"; export function loadEnvFiles(filePaths: string[]): void { for (const filePath of filePaths) { - loadEnvFile(filePath) + loadEnvFile(filePath); } } function loadEnvFile(filePath: string): void { if (!fs.existsSync(filePath)) { - return + return; } - const rawText = fs.readFileSync(filePath, 'utf8') - const lines = rawText.split(/\r?\n/) + const rawText = fs.readFileSync(filePath, "utf8"); + const lines = rawText.split(/\r?\n/); for (const rawLine of lines) { - const line = rawLine.trim() + const line = rawLine.trim(); - if (!line || line.startsWith('#')) { - continue + if (!line || line.startsWith("#")) { + continue; } - const separatorIndex = line.indexOf('=') + const separatorIndex = line.indexOf("="); if (separatorIndex <= 0) { - continue + continue; } - const key = line.slice(0, separatorIndex).trim() + const key = line.slice(0, separatorIndex).trim(); if (!key || key in process.env) { - continue + continue; } - process.env[key] = parseEnvValue(line.slice(separatorIndex + 1)) + process.env[key] = parseEnvValue(line.slice(separatorIndex + 1)); } } function parseEnvValue(rawValue: string): string { - const value = String(rawValue || '').trim() + const value = String(rawValue || "").trim(); if (!value) { - return '' + return ""; } - const quote = value[0] + const quote = value[0]; if ((quote === '"' || quote === "'") && value.endsWith(quote)) { - return value.slice(1, -1) + return value.slice(1, -1); } - return value + return value; } export function parseBoolean(rawValue: unknown): boolean | null { - const normalized = String(rawValue || '') + const normalized = String(rawValue || "") .trim() - .toLowerCase() + .toLowerCase(); if (!normalized) { - return null + return null; } - if (['1', 'true', 'yes', 'on'].includes(normalized)) { - return true + if (["1", "true", "yes", "on"].includes(normalized)) { + return true; } - if (['0', 'false', 'no', 'off'].includes(normalized)) { - return false + if (["0", "false", "no", "off"].includes(normalized)) { + return false; } - return null + return null; } export function parseInteger(rawValue: unknown): number | null { - const normalized = String(rawValue || '').trim() + const normalized = String(rawValue || "").trim(); if (!normalized) { - return null + return null; } - const parsed = Number(normalized) - return Number.isFinite(parsed) ? parsed : null + const parsed = Number(normalized); + return Number.isFinite(parsed) ? parsed : null; } export function parseJsonArray(rawValue: unknown): T[] | null { - const normalized = String(rawValue || '').trim() + const normalized = String(rawValue || "").trim(); if (!normalized) { - return null + return null; } try { - const parsed = JSON.parse(normalized) - return Array.isArray(parsed) ? parsed as T[] : null + const parsed = JSON.parse(normalized); + return Array.isArray(parsed) ? (parsed as T[]) : null; } catch { - return null + return null; } } + +export function parseCommaSeparatedStrings(rawValue: unknown): string[] | null { + const normalized = String(rawValue || "").trim(); + + if (!normalized) { + return null; + } + + const items = normalized + .split(",") + .map((item) => item.trim()) + .filter((item) => item.length > 0); + + return items.length > 0 ? items : null; +}