406 lines
10 KiB
TypeScript
406 lines
10 KiB
TypeScript
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<string, string | undefined>;
|
|
|
|
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<AdminDefaultUser>(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<string, unknown> = 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<string, unknown>;
|
|
}
|
|
|
|
current[configPath[configPath.length - 1]] = value;
|
|
}
|