// @ts-check import fs from "node:fs"; import path from "node:path"; import process from "node:process"; import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; /** @typedef {import('../types/runtime-config.js').RuntimeConfig} RuntimeConfig */ const require = createRequire(import.meta.url); const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url)); export const PROJECT_ROOT = path.resolve(CURRENT_DIR, "../.."); const WORKSPACE_ROOT = path.resolve(PROJECT_ROOT, "../.."); const CONFIG_ROOT = path.join(PROJECT_ROOT, "config"); loadEnvFiles([ path.join(WORKSPACE_ROOT, ".env"), path.join(PROJECT_ROOT, ".env"), ]); const defaultConfig = /** @type {RuntimeConfig} */ ( loadConfig(path.join(CONFIG_ROOT, "default.cjs")) ); export const runtimeConfig = /** @type {RuntimeConfig} */ ( applyEnvOverrides(defaultConfig) ); function loadConfig(configPath) { if (!fs.existsSync(configPath)) { return {}; } const loaded = require(configPath); return isPlainObject(loaded) ? loaded : {}; } function loadEnvFiles(filePaths) { for (const filePath of filePaths) { loadEnvFile(filePath); } } function loadEnvFile(filePath) { if (!fs.existsSync(filePath)) { return; } const rawText = fs.readFileSync(filePath, "utf8"); const lines = rawText.split(/\r?\n/); for (const rawLine of lines) { const line = rawLine.trim(); if (!line || line.startsWith("#")) { continue; } const separatorIndex = line.indexOf("="); if (separatorIndex <= 0) { continue; } const key = line.slice(0, separatorIndex).trim(); if (!key || key in process.env) { continue; } process.env[key] = parseEnvValue(line.slice(separatorIndex + 1)); } } function parseEnvValue(rawValue) { const value = String(rawValue || "").trim(); if (!value) { return ""; } const quote = value[0]; if ((quote === '"' || quote === "'") && value.endsWith(quote)) { return value.slice(1, -1); } return value; } function applyEnvOverrides(baseConfig) { const nextConfig = deepMerge(baseConfig, {}); const port = parseInteger(process.env.PORT); if (port !== null) { nextConfig.server.port = port; } const chromePath = String(process.env.CHROME_PATH || "").trim(); if (chromePath) { nextConfig.browser.chromePath = chromePath; } const browserHeadless = parseBoolean(process.env.TENCENT_BROWSER_HEADLESS); if (browserHeadless !== null) { nextConfig.browser.headless = browserHeadless; } const browserDevtools = parseBoolean(process.env.TENCENT_BROWSER_DEVTOOLS); if (browserDevtools !== null) { nextConfig.browser.devtools = browserDevtools; } const browserKeepAlive = parseBoolean(process.env.TENCENT_BROWSER_KEEP_ALIVE); if (browserKeepAlive !== null) { nextConfig.browser.keepAlive = browserKeepAlive; } const browserPrewarm = parseBoolean(process.env.TENCENT_BROWSER_PREWARM); if (browserPrewarm !== null) { nextConfig.browser.prewarm = browserPrewarm; } const browserSlowMoMs = parseInteger(process.env.TENCENT_BROWSER_SLOW_MO); if (browserSlowMoMs !== null) { nextConfig.browser.slowMoMs = browserSlowMoMs; } const sessionDebug = parseBoolean(process.env.TENCENT_SESSION_DEBUG); if (sessionDebug !== null) { nextConfig.session.debug = sessionDebug; } const ocrBaseUrl = String(process.env.OCR_BASE_URL || "").trim(); if (ocrBaseUrl) { nextConfig.ocr.baseUrl = ocrBaseUrl; } const dataRoot = String(process.env.DATA_ROOT || "").trim(); if (dataRoot) { nextConfig.data.root = path.resolve(dataRoot); } const logLevel = String(process.env.LOG_LEVEL || "").trim(); if (logLevel) { nextConfig.logging.level = logLevel; } const databaseUrl = String(process.env.DATABASE_URL || "").trim(); if (databaseUrl) { nextConfig.database.url = databaseUrl; } const databaseSsl = parseBoolean(process.env.DATABASE_SSL); if (databaseSsl !== null) { nextConfig.database.ssl = databaseSsl; } const databaseMaxConnections = parseInteger( process.env.DATABASE_MAX_CONNECTIONS ); if (databaseMaxConnections !== null) { nextConfig.database.maxConnections = databaseMaxConnections; } const claimBaseUrl = String(process.env.CLAIM_BASE_URL || "").trim(); if (claimBaseUrl) { nextConfig.orders.claimBaseUrl = claimBaseUrl; } else { const appBaseUrl = String(process.env.APP_BASE_URL || "").trim(); if (appBaseUrl) { nextConfig.orders.claimBaseUrl = `${appBaseUrl.replace( /\/+$/, "" )}/#/claim`; } } const tokenTtlHours = parseInteger(process.env.CLAIM_TOKEN_TTL_HOURS); if (tokenTtlHours !== null) { nextConfig.orders.tokenTtlHours = tokenTtlHours; } const adminSessionSecret = String( process.env.ADMIN_SESSION_SECRET || "" ).trim(); if (adminSessionSecret) { nextConfig.admin.sessionSecret = adminSessionSecret; } const adminSessionTtlHours = parseInteger( process.env.ADMIN_SESSION_TTL_HOURS ); if (adminSessionTtlHours !== null) { nextConfig.admin.sessionTtlHours = adminSessionTtlHours; } const adminDefaultUsers = parseJsonArray( process.env.ADMIN_DEFAULT_USERS_JSON ); if (adminDefaultUsers) { nextConfig.admin.defaultUsers = adminDefaultUsers; } const agisoAppSecret = String(process.env.AGISO_APP_SECRET || "").trim(); if (agisoAppSecret) { nextConfig.platforms.agiso.appSecret = agisoAppSecret; } const agisoTradeDetailEndpoint = String( process.env.AGISO_TRADE_DETAIL_ENDPOINT || "" ).trim(); if (agisoTradeDetailEndpoint) { nextConfig.platforms.agiso.tradeDetail.endpoint = agisoTradeDetailEndpoint; } const agisoTradeDetailApiVersion = String( process.env.AGISO_TRADE_DETAIL_API_VERSION || "" ).trim(); if (agisoTradeDetailApiVersion) { nextConfig.platforms.agiso.tradeDetail.apiVersion = agisoTradeDetailApiVersion; } const agisoTradeDetailTimeoutMs = parseInteger( process.env.AGISO_TRADE_DETAIL_TIMEOUT_MS ); if (agisoTradeDetailTimeoutMs !== null) { nextConfig.platforms.agiso.tradeDetail.timeoutMs = agisoTradeDetailTimeoutMs; } const agisoAutoDeliveryEnabled = parseBoolean( process.env.AGISO_AUTO_DELIVERY_ENABLED ); if (agisoAutoDeliveryEnabled !== null) { nextConfig.platforms.agiso.autoDelivery.enabled = agisoAutoDeliveryEnabled; } const agisoAutoDeliveryEndpoint = String( process.env.AGISO_AUTO_DELIVERY_ENDPOINT || "" ).trim(); if (agisoAutoDeliveryEndpoint) { nextConfig.platforms.agiso.autoDelivery.endpoint = agisoAutoDeliveryEndpoint; } const agisoAutoDeliveryApiVersion = String( process.env.AGISO_AUTO_DELIVERY_API_VERSION || "" ).trim(); if (agisoAutoDeliveryApiVersion) { nextConfig.platforms.agiso.autoDelivery.apiVersion = agisoAutoDeliveryApiVersion; } const agisoMessageApiVersion = String( process.env.AGISO_MESSAGE_API_VERSION || "" ).trim(); if (agisoMessageApiVersion) { nextConfig.platforms.agiso.messaging.apiVersion = agisoMessageApiVersion; } const agisoSendMessageEndpoint = String( process.env.AGISO_SEND_MESSAGE_ENDPOINT || "" ).trim(); if (agisoSendMessageEndpoint) { nextConfig.platforms.agiso.messaging.sendMessageEndpoint = agisoSendMessageEndpoint; } const agisoMessagingEnabled = parseBoolean( process.env.AGISO_MESSAGING_ENABLED ); if (agisoMessagingEnabled !== null) { nextConfig.platforms.agiso.messaging.enabled = agisoMessagingEnabled; } const ninetyoneUserId = String(process.env.NINETYONE_USER_ID || "").trim(); if (ninetyoneUserId) { nextConfig.platforms.ninetyone.userId = ninetyoneUserId; } const ninetyoneSecret = String(process.env.NINETYONE_SECRET || "").trim(); if (ninetyoneSecret) { nextConfig.platforms.ninetyone.secret = ninetyoneSecret; } const ninetyoneVersion = String(process.env.NINETYONE_VERSION || "").trim(); if (ninetyoneVersion) { nextConfig.platforms.ninetyone.version = ninetyoneVersion; } const ninetyoneShopId = String(process.env.NINETYONE_SHOP_ID || "").trim(); if (ninetyoneShopId) { nextConfig.platforms.ninetyone.shopId = ninetyoneShopId; } const ninetyoneShopName = String( process.env.NINETYONE_SHOP_NAME || "" ).trim(); if (ninetyoneShopName) { nextConfig.platforms.ninetyone.shopName = ninetyoneShopName; } const ninetyoneTimestampToleranceSeconds = parseInteger( process.env.NINETYONE_TIMESTAMP_TOLERANCE_SECONDS ); if (ninetyoneTimestampToleranceSeconds !== null) { nextConfig.platforms.ninetyone.timestampToleranceSeconds = ninetyoneTimestampToleranceSeconds; } const ninetyoneCardsEncoding = String( process.env.NINETYONE_CARDS_ENCODING || "" ).trim(); if (ninetyoneCardsEncoding) { nextConfig.platforms.ninetyone.cardsEncoding = ninetyoneCardsEncoding; } const cloudtentaclesBaseUrl = String( process.env.CLOUDTENTACLES_BASE_URL || "" ).trim(); if (cloudtentaclesBaseUrl) { nextConfig.platforms.cloudtentacles.baseUrl = cloudtentaclesBaseUrl; } const cloudtentaclesTimeoutMs = parseInteger( process.env.CLOUDTENTACLES_TIMEOUT_MS ); if (cloudtentaclesTimeoutMs !== null) { nextConfig.platforms.cloudtentacles.timeoutMs = cloudtentaclesTimeoutMs; } const cloudtentaclesSendSmsPath = String( process.env.CLOUDTENTACLES_SEND_SMS_PATH || "" ).trim(); if (cloudtentaclesSendSmsPath) { nextConfig.platforms.cloudtentacles.sendSmsPath = cloudtentaclesSendSmsPath; } const cloudtentaclesLoginPath = String( process.env.CLOUDTENTACLES_LOGIN_PATH || "" ).trim(); if (cloudtentaclesLoginPath) { nextConfig.platforms.cloudtentacles.loginPath = cloudtentaclesLoginPath; } const cloudtentaclesUserInfoPath = String( process.env.CLOUDTENTACLES_USER_INFO_PATH || "" ).trim(); if (cloudtentaclesUserInfoPath) { nextConfig.platforms.cloudtentacles.userInfoPath = cloudtentaclesUserInfoPath; } const cloudtentaclesAssetPath = String( process.env.CLOUDTENTACLES_ASSET_PATH || "" ).trim(); if (cloudtentaclesAssetPath) { nextConfig.platforms.cloudtentacles.assetPath = cloudtentaclesAssetPath; } const cloudtentaclesPermissionPath = String( process.env.CLOUDTENTACLES_PERMISSION_PATH || "" ).trim(); if (cloudtentaclesPermissionPath) { nextConfig.platforms.cloudtentacles.permissionPath = cloudtentaclesPermissionPath; } const cloudtentaclesCategoriesPath = String( process.env.CLOUDTENTACLES_CATEGORIES_PATH || "" ).trim(); if (cloudtentaclesCategoriesPath) { nextConfig.platforms.cloudtentacles.categoriesPath = cloudtentaclesCategoriesPath; } const cloudtentaclesSkuListPath = String( process.env.CLOUDTENTACLES_SKU_LIST_PATH || "" ).trim(); if (cloudtentaclesSkuListPath) { nextConfig.platforms.cloudtentacles.skuListPath = cloudtentaclesSkuListPath; } const cloudtentaclesSkuBuyPath = String( process.env.CLOUDTENTACLES_SKU_BUY_PATH || "" ).trim(); if (cloudtentaclesSkuBuyPath) { nextConfig.platforms.cloudtentacles.skuBuyPath = cloudtentaclesSkuBuyPath; } const cloudtentaclesKnapsackPath = String( process.env.CLOUDTENTACLES_KNAPSACK_PATH || "" ).trim(); if (cloudtentaclesKnapsackPath) { nextConfig.platforms.cloudtentacles.knapsackPath = cloudtentaclesKnapsackPath; } const cloudtentaclesVnListPath = String( process.env.CLOUDTENTACLES_VN_LIST_PATH || "" ).trim(); if (cloudtentaclesVnListPath) { nextConfig.platforms.cloudtentacles.vnListPath = cloudtentaclesVnListPath; } const cloudtentaclesVnAppointPath = String( process.env.CLOUDTENTACLES_VN_APPOINT_PATH || "" ).trim(); if (cloudtentaclesVnAppointPath) { nextConfig.platforms.cloudtentacles.vnAppointPath = cloudtentaclesVnAppointPath; } const cloudtentaclesVnGenerateLoginCodePath = String( process.env.CLOUDTENTACLES_VN_GENERATE_LOGIN_CODE_PATH || "" ).trim(); if (cloudtentaclesVnGenerateLoginCodePath) { nextConfig.platforms.cloudtentacles.vnGenerateLoginCodePath = cloudtentaclesVnGenerateLoginCodePath; } const cloudtentaclesVnVerifCodePath = String( process.env.CLOUDTENTACLES_VN_VERIF_CODE_PATH || "" ).trim(); if (cloudtentaclesVnVerifCodePath) { nextConfig.platforms.cloudtentacles.vnVerifCodePath = cloudtentaclesVnVerifCodePath; } const cloudtentaclesVnVerifyLoginCodePath = String( process.env.CLOUDTENTACLES_VN_VERIFY_LOGIN_CODE_PATH || "" ).trim(); if (cloudtentaclesVnVerifyLoginCodePath) { nextConfig.platforms.cloudtentacles.vnVerifyLoginCodePath = cloudtentaclesVnVerifyLoginCodePath; } const cloudtentaclesVnBindUrlPath = String( process.env.CLOUDTENTACLES_VN_BIND_URL_PATH || "" ).trim(); if (cloudtentaclesVnBindUrlPath) { nextConfig.platforms.cloudtentacles.vnBindUrlPath = cloudtentaclesVnBindUrlPath; } const cloudtentaclesVnBindInfoPath = String( process.env.CLOUDTENTACLES_VN_BIND_INFO_PATH || "" ).trim(); if (cloudtentaclesVnBindInfoPath) { nextConfig.platforms.cloudtentacles.vnBindInfoPath = cloudtentaclesVnBindInfoPath; } const cloudtentaclesVnBackPath = String( process.env.CLOUDTENTACLES_VN_BACK_PATH || "" ).trim(); if (cloudtentaclesVnBackPath) { nextConfig.platforms.cloudtentacles.vnBackPath = cloudtentaclesVnBackPath; } const cloudtentaclesBindUrlTtlSeconds = parseInteger( process.env.CLOUDTENTACLES_BIND_URL_TTL_SECONDS ); if (cloudtentaclesBindUrlTtlSeconds !== null) { nextConfig.platforms.cloudtentacles.bindUrlTtlSeconds = cloudtentaclesBindUrlTtlSeconds; } const cloudtentaclesBindUrlProbeIntervalSeconds = parseInteger( process.env.CLOUDTENTACLES_BIND_URL_PROBE_INTERVAL_SECONDS ); if (cloudtentaclesBindUrlProbeIntervalSeconds !== null) { nextConfig.platforms.cloudtentacles.bindUrlProbeIntervalSeconds = cloudtentaclesBindUrlProbeIntervalSeconds; } const cloudtentaclesBindUrlProbeTimeoutMs = parseInteger( process.env.CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS ); if (cloudtentaclesBindUrlProbeTimeoutMs !== null) { nextConfig.platforms.cloudtentacles.bindUrlProbeTimeoutMs = cloudtentaclesBindUrlProbeTimeoutMs; } const cloudtentaclesBindUrlProbeUserAgent = String( process.env.CLOUDTENTACLES_BIND_URL_PROBE_USER_AGENT || "" ).trim(); if (cloudtentaclesBindUrlProbeUserAgent) { nextConfig.platforms.cloudtentacles.bindUrlProbeUserAgent = cloudtentaclesBindUrlProbeUserAgent; } const cloudtentaclesBindUrlProbeEndpoint = String( process.env.CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT || "" ).trim(); if (cloudtentaclesBindUrlProbeEndpoint) { nextConfig.platforms.cloudtentacles.bindUrlProbeEndpoint = cloudtentaclesBindUrlProbeEndpoint; } const cloudtentaclesBindUrlProbeChartId = String( process.env.CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID || "" ).trim(); if (cloudtentaclesBindUrlProbeChartId) { nextConfig.platforms.cloudtentacles.bindUrlProbeChartId = cloudtentaclesBindUrlProbeChartId; } const cloudtentaclesBindUrlProbeSubChartId = String( process.env.CLOUDTENTACLES_BIND_URL_PROBE_SUB_CHART_ID || "" ).trim(); if (cloudtentaclesBindUrlProbeSubChartId) { nextConfig.platforms.cloudtentacles.bindUrlProbeSubChartId = cloudtentaclesBindUrlProbeSubChartId; } const cloudtentaclesBindUrlProbeIdeToken = String( process.env.CLOUDTENTACLES_BIND_URL_PROBE_IDE_TOKEN || "" ).trim(); if (cloudtentaclesBindUrlProbeIdeToken) { nextConfig.platforms.cloudtentacles.bindUrlProbeIdeToken = cloudtentaclesBindUrlProbeIdeToken; } const cloudtentaclesBindUrlProbeActivityUrl = String( process.env.CLOUDTENTACLES_BIND_URL_PROBE_ACTIVITY_URL || "" ).trim(); if (cloudtentaclesBindUrlProbeActivityUrl) { nextConfig.platforms.cloudtentacles.bindUrlProbeActivityUrl = cloudtentaclesBindUrlProbeActivityUrl; } const cloudtentaclesBindUrlProbeReferer = String( process.env.CLOUDTENTACLES_BIND_URL_PROBE_REFERER || "" ).trim(); if (cloudtentaclesBindUrlProbeReferer) { nextConfig.platforms.cloudtentacles.bindUrlProbeReferer = cloudtentaclesBindUrlProbeReferer; } const cloudtentaclesBindUrlProbeExtraCookie = String( process.env.CLOUDTENTACLES_BIND_URL_PROBE_EXTRA_COOKIE || "" ).trim(); if (cloudtentaclesBindUrlProbeExtraCookie) { nextConfig.platforms.cloudtentacles.bindUrlProbeExtraCookie = cloudtentaclesBindUrlProbeExtraCookie; } const cloudtentaclesPublicKeyPem = String( process.env.CLOUDTENTACLES_PUBLIC_KEY_PEM || "" ).trim(); if (cloudtentaclesPublicKeyPem) { nextConfig.platforms.cloudtentacles.publicKeyPem = cloudtentaclesPublicKeyPem; } const cloudtentaclesClientSource = String( process.env.CLOUDTENTACLES_CLIENT_SOURCE || "" ).trim(); if (cloudtentaclesClientSource) { nextConfig.platforms.cloudtentacles.clientSource = cloudtentaclesClientSource; } const cloudtentaclesDeviceId = String( process.env.CLOUDTENTACLES_DEVICE_ID || "" ).trim(); if (cloudtentaclesDeviceId) { nextConfig.platforms.cloudtentacles.deviceId = cloudtentaclesDeviceId; } const cloudtentaclesDeviceType = parseInteger( process.env.CLOUDTENTACLES_DEVICE_TYPE ); if (cloudtentaclesDeviceType !== null) { nextConfig.platforms.cloudtentacles.deviceType = cloudtentaclesDeviceType; } const proofMode = String(process.env.TENCENT_REDEEM_PROOF_MODE || "").trim(); if (proofMode) { nextConfig.redeem.proofMode = proofMode; } return nextConfig; } function deepMerge(baseValue, overrideValue) { if (!isPlainObject(baseValue)) { return cloneValue(overrideValue); } const result = cloneValue(baseValue); if (!isPlainObject(overrideValue)) { return result; } for (const [key, value] of Object.entries(overrideValue)) { if (isPlainObject(value) && isPlainObject(result[key])) { result[key] = deepMerge(result[key], value); continue; } result[key] = cloneValue(value); } return result; } function cloneValue(value) { if (Array.isArray(value)) { return value.map((item) => cloneValue(item)); } if (isPlainObject(value)) { const output = {}; for (const [key, item] of Object.entries(value)) { output[key] = cloneValue(item); } return output; } return value; } function isPlainObject(value) { return Object.prototype.toString.call(value) === "[object Object]"; } function parseBoolean(rawValue) { const normalized = String(rawValue || "") .trim() .toLowerCase(); if (!normalized) { return null; } if (["1", "true", "yes", "on"].includes(normalized)) { return true; } if (["0", "false", "no", "off"].includes(normalized)) { return false; } return null; } function parseInteger(rawValue) { const normalized = String(rawValue || "").trim(); if (!normalized) { return null; } const parsed = Number(normalized); return Number.isFinite(parsed) ? parsed : null; } function parseJsonArray(rawValue) { const normalized = String(rawValue || "").trim(); if (!normalized) { return null; } try { const parsed = JSON.parse(normalized); return Array.isArray(parsed) ? parsed : null; } catch { return null; } } function normalizeBooleanLike(value) { if (typeof value === "boolean") { return value; } return parseBoolean(value); }