Files
order_site/apps/backend/src/config/runtime.js
T
yml e812ea2cc4 refactor(ocr): remove subprocess mode — HTTP only
- ocr.js: Removed all subprocess/spawn logic (spawn, fs, path, process
  imports, resolveWorkerCommand, buildPythonPath, buildWorkerError,
  callOcrViaSubprocess, resolveOcrMode, runOcrCommand, etc.)
  HTTP is now the only mode. Added validation when OCR_BASE_URL is not
  configured. ~281 lines → ~80 lines.
- default.cjs: Removed projectRoot from ocr config (kept path import
  for data.root)
- runtime.js: Removed OCR_PROJECT_ROOT env var parsing
- runtime-config.js: Removed projectRoot from ocr type definition

All 5 exports preserved: recognizeTencentCaptcha,
recognizeImageCaptcha, batchRecognizeTencentCaptcha,
warmupLocalOcrWorker, closeLocalOcrWorker
2026-05-19 23:29:42 +08:00

682 lines
19 KiB
JavaScript

// @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);
}