import path from "node:path"; import process from "node:process"; import type { AdminDefaultUser, RuntimeConfig, } from "../types/runtime-config.js"; import { parseBoolean, parseInteger, parseJsonArray } from "./runtime-env.js"; import { deepMerge } from "./runtime-merge.js"; type RuntimeConfigPath = readonly [string, ...string[]]; type RuntimeConfigValue = string | number | boolean | AdminDefaultUser[]; type RuntimeEnv = Record; type EnvOverride = { env: string; path: RuntimeConfigPath; read: (rawValue: unknown) => RuntimeConfigValue | null; }; export const ENV_OVERRIDES: readonly EnvOverride[] = [ integerEnv("PORT", ["server", "port"]), stringEnv("CHROME_PATH", ["browser", "chromePath"]), booleanEnv("TENCENT_BROWSER_HEADLESS", ["browser", "headless"]), booleanEnv("TENCENT_BROWSER_DEVTOOLS", ["browser", "devtools"]), booleanEnv("TENCENT_BROWSER_KEEP_ALIVE", ["browser", "keepAlive"]), booleanEnv("TENCENT_BROWSER_PREWARM", ["browser", "prewarm"]), integerEnv("TENCENT_BROWSER_SLOW_MO", ["browser", "slowMoMs"]), stringEnv("TENCENT_BROWSER_VNC_RESOLUTION", ["browser", "vncResolution"]), booleanEnv("TENCENT_SESSION_DEBUG", ["session", "debug"]), stringEnv("OCR_BASE_URL", ["ocr", "baseUrl"]), stringEnv("DATA_ROOT", ["data", "root"], path.resolve), stringEnv("LOG_LEVEL", ["logging", "level"]), stringEnv("DATABASE_URL", ["database", "url"]), booleanEnv("DATABASE_SSL", ["database", "ssl"]), integerEnv("DATABASE_MAX_CONNECTIONS", ["database", "maxConnections"]), integerEnv("CLAIM_TOKEN_TTL_HOURS", ["orders", "tokenTtlHours"]), stringEnv("ADMIN_SESSION_SECRET", ["admin", "sessionSecret"]), integerEnv("ADMIN_SESSION_TTL_HOURS", ["admin", "sessionTtlHours"]), adminUsersJsonEnv("ADMIN_DEFAULT_USERS_JSON", ["admin", "defaultUsers"]), stringEnv("AGISO_APP_SECRET", ["platforms", "agiso", "appSecret"]), stringEnv("AGISO_TRADE_DETAIL_ENDPOINT", [ "platforms", "agiso", "tradeDetail", "endpoint", ]), stringEnv("AGISO_TRADE_DETAIL_API_VERSION", [ "platforms", "agiso", "tradeDetail", "apiVersion", ]), integerEnv("AGISO_TRADE_DETAIL_TIMEOUT_MS", [ "platforms", "agiso", "tradeDetail", "timeoutMs", ]), booleanEnv("AGISO_AUTO_DELIVERY_ENABLED", [ "platforms", "agiso", "autoDelivery", "enabled", ]), stringEnv("AGISO_AUTO_DELIVERY_ENDPOINT", [ "platforms", "agiso", "autoDelivery", "endpoint", ]), stringEnv("AGISO_AUTO_DELIVERY_API_VERSION", [ "platforms", "agiso", "autoDelivery", "apiVersion", ]), stringEnv("AGISO_MESSAGE_API_VERSION", [ "platforms", "agiso", "messaging", "apiVersion", ]), stringEnv("AGISO_SEND_MESSAGE_ENDPOINT", [ "platforms", "agiso", "messaging", "sendMessageEndpoint", ]), booleanEnv("AGISO_MESSAGING_ENABLED", [ "platforms", "agiso", "messaging", "enabled", ]), stringEnv("NINETYONE_USER_ID", ["platforms", "ninetyone", "userId"]), stringEnv("NINETYONE_SECRET", ["platforms", "ninetyone", "secret"]), stringEnv("NINETYONE_VERSION", ["platforms", "ninetyone", "version"]), stringEnv("NINETYONE_SHOP_ID", ["platforms", "ninetyone", "shopId"]), stringEnv("NINETYONE_SHOP_NAME", ["platforms", "ninetyone", "shopName"]), integerEnv("NINETYONE_TIMESTAMP_TOLERANCE_SECONDS", [ "platforms", "ninetyone", "timestampToleranceSeconds", ]), stringEnv("NINETYONE_CARDS_ENCODING", [ "platforms", "ninetyone", "cardsEncoding", ]), stringEnv("CLOUDTENTACLES_BASE_URL", [ "platforms", "cloudtentacles", "baseUrl", ]), integerEnv("CLOUDTENTACLES_TIMEOUT_MS", [ "platforms", "cloudtentacles", "timeoutMs", ]), stringEnv("CLOUDTENTACLES_SEND_SMS_PATH", [ "platforms", "cloudtentacles", "sendSmsPath", ]), stringEnv("CLOUDTENTACLES_LOGIN_PATH", [ "platforms", "cloudtentacles", "loginPath", ]), stringEnv("CLOUDTENTACLES_USER_INFO_PATH", [ "platforms", "cloudtentacles", "userInfoPath", ]), stringEnv("CLOUDTENTACLES_ASSET_PATH", [ "platforms", "cloudtentacles", "assetPath", ]), stringEnv("CLOUDTENTACLES_PERMISSION_PATH", [ "platforms", "cloudtentacles", "permissionPath", ]), stringEnv("CLOUDTENTACLES_CATEGORIES_PATH", [ "platforms", "cloudtentacles", "categoriesPath", ]), stringEnv("CLOUDTENTACLES_SKU_LIST_PATH", [ "platforms", "cloudtentacles", "skuListPath", ]), stringEnv("CLOUDTENTACLES_SKU_BUY_PATH", [ "platforms", "cloudtentacles", "skuBuyPath", ]), stringEnv("CLOUDTENTACLES_KNAPSACK_PATH", [ "platforms", "cloudtentacles", "knapsackPath", ]), stringEnv("CLOUDTENTACLES_VN_LIST_PATH", [ "platforms", "cloudtentacles", "vnListPath", ]), stringEnv("CLOUDTENTACLES_VN_APPOINT_PATH", [ "platforms", "cloudtentacles", "vnAppointPath", ]), stringEnv("CLOUDTENTACLES_VN_GENERATE_LOGIN_CODE_PATH", [ "platforms", "cloudtentacles", "vnGenerateLoginCodePath", ]), stringEnv("CLOUDTENTACLES_VN_VERIF_CODE_PATH", [ "platforms", "cloudtentacles", "vnVerifCodePath", ]), stringEnv("CLOUDTENTACLES_VN_VERIFY_LOGIN_CODE_PATH", [ "platforms", "cloudtentacles", "vnVerifyLoginCodePath", ]), stringEnv("CLOUDTENTACLES_VN_BIND_URL_PATH", [ "platforms", "cloudtentacles", "vnBindUrlPath", ]), stringEnv("CLOUDTENTACLES_VN_BIND_INFO_PATH", [ "platforms", "cloudtentacles", "vnBindInfoPath", ]), stringEnv("CLOUDTENTACLES_VN_BACK_PATH", [ "platforms", "cloudtentacles", "vnBackPath", ]), integerEnv("CLOUDTENTACLES_BIND_URL_TTL_SECONDS", [ "platforms", "cloudtentacles", "bindUrlTtlSeconds", ]), integerEnv("CLOUDTENTACLES_BIND_URL_PROBE_INTERVAL_SECONDS", [ "platforms", "cloudtentacles", "bindUrlProbeIntervalSeconds", ]), integerEnv("CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS", [ "platforms", "cloudtentacles", "bindUrlProbeTimeoutMs", ]), stringEnv("CLOUDTENTACLES_BIND_URL_PROBE_USER_AGENT", [ "platforms", "cloudtentacles", "bindUrlProbeUserAgent", ]), stringEnv("CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT", [ "platforms", "cloudtentacles", "bindUrlProbeEndpoint", ]), stringEnv("CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID", [ "platforms", "cloudtentacles", "bindUrlProbeChartId", ]), stringEnv("CLOUDTENTACLES_BIND_URL_PROBE_SUB_CHART_ID", [ "platforms", "cloudtentacles", "bindUrlProbeSubChartId", ]), stringEnv("CLOUDTENTACLES_BIND_URL_PROBE_IDE_TOKEN", [ "platforms", "cloudtentacles", "bindUrlProbeIdeToken", ]), stringEnv("CLOUDTENTACLES_BIND_URL_PROBE_ACTIVITY_URL", [ "platforms", "cloudtentacles", "bindUrlProbeActivityUrl", ]), stringEnv("CLOUDTENTACLES_BIND_URL_PROBE_REFERER", [ "platforms", "cloudtentacles", "bindUrlProbeReferer", ]), stringEnv("CLOUDTENTACLES_BIND_URL_PROBE_EXTRA_COOKIE", [ "platforms", "cloudtentacles", "bindUrlProbeExtraCookie", ]), stringEnv("CLOUDTENTACLES_PUBLIC_KEY_PEM", [ "platforms", "cloudtentacles", "publicKeyPem", ]), stringEnv("CLOUDTENTACLES_CLIENT_SOURCE", [ "platforms", "cloudtentacles", "clientSource", ]), stringEnv("CLOUDTENTACLES_DEVICE_ID", [ "platforms", "cloudtentacles", "deviceId", ]), integerEnv("CLOUDTENTACLES_DEVICE_TYPE", [ "platforms", "cloudtentacles", "deviceType", ]), stringEnv("TENCENT_REDEEM_PROOF_MODE", ["redeem", "proofMode"]), corsOriginsEnv("CORS_ALLOWED_ORIGINS", ["cors", "allowedOrigins"]), ]; export function applyEnvOverrides( baseConfig: RuntimeConfig, env: RuntimeEnv = process.env ): RuntimeConfig { const nextConfig = deepMerge(baseConfig, {}); applyClaimBaseUrlOverride(nextConfig, env); for (const override of ENV_OVERRIDES) { const value = override.read(env[override.env]); if (value !== null) { setConfigValue(nextConfig, override.path, value); } } return nextConfig; } function applyClaimBaseUrlOverride( config: RuntimeConfig, env: RuntimeEnv ): void { const claimBaseUrl = parseString(env.CLAIM_BASE_URL); if (claimBaseUrl !== null) { config.orders.claimBaseUrl = claimBaseUrl; return; } const appBaseUrl = parseString(env.APP_BASE_URL); if (appBaseUrl !== null) { config.orders.claimBaseUrl = `${appBaseUrl.replace(/\/+$/, "")}/#/claim`; } } function stringEnv( env: string, configPath: RuntimeConfigPath, transform: (value: string) => string = (value) => value ): EnvOverride { return { env, path: configPath, read(rawValue) { const value = parseString(rawValue); return value === null ? null : transform(value); }, }; } function integerEnv(env: string, configPath: RuntimeConfigPath): EnvOverride { return { env, path: configPath, read: parseInteger, }; } function booleanEnv(env: string, configPath: RuntimeConfigPath): EnvOverride { return { env, path: configPath, read: parseBoolean, }; } function adminUsersJsonEnv(env: string, configPath: RuntimeConfigPath): EnvOverride { return { env, path: configPath, read(rawValue) { return parseJsonArray(rawValue) }, } } function corsOriginsEnv(env: string, configPath: RuntimeConfigPath): EnvOverride { return { env, path: configPath, read(rawValue) { const value = parseString(rawValue) if (value === null) { return null } if (value.trim() === '*') { return ['*'] } return value.split(',').map((s) => s.trim()).filter(Boolean) }, } } function parseString(rawValue: unknown): string | null { const value = String(rawValue || "").trim(); return value ? value : null; } function setConfigValue( config: RuntimeConfig, configPath: RuntimeConfigPath, value: RuntimeConfigValue ): void { let current: Record = config as unknown as Record< string, unknown >; for (const key of configPath.slice(0, -1)) { const nextValue = current[key]; if ( !nextValue || typeof nextValue !== "object" || Array.isArray(nextValue) ) { current[key] = {}; } current = current[key] as Record; } current[configPath[configPath.length - 1]] = value; }