From bdc3dc405f8a231cf23d6b9d2b251d2e8b0d709e Mon Sep 17 00:00:00 2001 From: shenlan Date: Sun, 2 Nov 2025 11:00:56 +0800 Subject: [PATCH] Fix runtime env detection for SIT domains (#634) --- dashboard/config/runtime-loader.ts | 131 ++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 3 deletions(-) diff --git a/dashboard/config/runtime-loader.ts b/dashboard/config/runtime-loader.ts index 858ca55..7646dbc 100644 --- a/dashboard/config/runtime-loader.ts +++ b/dashboard/config/runtime-loader.ts @@ -33,6 +33,8 @@ const YAML_SOURCES: Record = { const parsedYamlCache: Partial>> = {} const runtimeConfigCache = new Map() +const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]']) + function isPlainRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) } @@ -159,6 +161,15 @@ function detectHostname(hostnameOverride?: string): { hostname?: string; detecte for (const candidate of envCandidates) { const hostname = sanitizeHostname(candidate.value) + if (!hostname) { + continue + } + + const likelyMachineHostname = !hostname.includes('.') && !LOCAL_HOSTNAMES.has(hostname) + if (likelyMachineHostname) { + continue + } + if (hostname) { return { hostname, detectedBy: candidate.source } } @@ -167,15 +178,129 @@ function detectHostname(hostnameOverride?: string): { hostname?: string; detecte return { hostname: undefined, detectedBy: 'default' } } +function normalizeEnvironmentKey(value?: string): RuntimeEnvironment | undefined { + if (!value) { + return undefined + } + + const normalized = value + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, '') + + const mapping: Record = { + prod: 'prod', + production: 'prod', + release: 'prod', + main: 'prod', + live: 'prod', + sit: 'sit', + staging: 'sit', + test: 'sit', + qa: 'sit', + uat: 'sit', + dev: 'sit', + development: 'sit', + preview: 'sit', + preprod: 'sit', + } + + return mapping[normalized] +} + +function detectEnvironmentFromEnvVars(): { environment: RuntimeEnvironment; matchedRule: string } | undefined { + const envCandidates: Array<{ source: string; value?: string; allowProd?: boolean }> = [ + { source: 'RUNTIME_ENV', value: process.env.RUNTIME_ENV }, + { source: 'NEXT_RUNTIME_ENV', value: process.env.NEXT_RUNTIME_ENV }, + { source: 'APP_ENV', value: process.env.APP_ENV }, + { source: 'ENVIRONMENT', value: process.env.ENVIRONMENT }, + { source: 'STAGE', value: process.env.STAGE }, + { source: 'NODE_ENV', value: process.env.NODE_ENV, allowProd: false }, + ] + + for (const candidate of envCandidates) { + const normalized = normalizeEnvironmentKey(candidate.value) + if (!normalized) { + continue + } + + if (!candidate.allowProd && normalized === 'prod') { + continue + } + + return { environment: normalized, matchedRule: `env:${candidate.source}` } + } + + return undefined +} + +function detectEnvironmentFromHostnamePattern(hostname: string): + | { environment: RuntimeEnvironment; matchedRule: string } + | undefined { + const normalized = hostname.toLowerCase() + + if (normalized.startsWith('dev.') || normalized.startsWith('dev-') || normalized.includes('.dev.')) { + return { environment: 'sit', matchedRule: 'dev-subdomain' } + } + + return undefined +} + +function detectEnvironmentFromUrlHints(): { environment: RuntimeEnvironment; matchedRule: string } | undefined { + const urlCandidates = [ + { source: 'RUNTIME_HOSTNAME', value: process.env.RUNTIME_HOSTNAME }, + { source: 'NEXT_RUNTIME_HOSTNAME', value: process.env.NEXT_RUNTIME_HOSTNAME }, + { source: 'RUNTIME_DASHBOARD_URL', value: process.env.RUNTIME_DASHBOARD_URL }, + { source: 'NEXT_PUBLIC_DASHBOARD_URL', value: process.env.NEXT_PUBLIC_DASHBOARD_URL }, + { source: 'DASHBOARD_URL', value: process.env.DASHBOARD_URL }, + { source: 'ACCOUNT_SERVICE_URL', value: process.env.ACCOUNT_SERVICE_URL }, + { source: 'NEXT_PUBLIC_ACCOUNT_SERVICE_URL', value: process.env.NEXT_PUBLIC_ACCOUNT_SERVICE_URL }, + { source: 'SERVER_SERVICE_URL', value: process.env.SERVER_SERVICE_URL }, + { source: 'NEXT_PUBLIC_SERVER_SERVICE_URL', value: process.env.NEXT_PUBLIC_SERVER_SERVICE_URL }, + { source: 'NEXT_PUBLIC_API_BASE_URL', value: process.env.NEXT_PUBLIC_API_BASE_URL }, + { source: 'API_BASE_URL', value: process.env.API_BASE_URL }, + { source: 'AUTH_URL', value: process.env.AUTH_URL }, + { source: 'NEXT_PUBLIC_AUTH_URL', value: process.env.NEXT_PUBLIC_AUTH_URL }, + ] + + for (const candidate of urlCandidates) { + const hostname = sanitizeHostname(candidate.value) + if (!hostname) { + continue + } + + const detected = detectEnvironmentFromHostnamePattern(hostname) + if (detected) { + return { ...detected, matchedRule: `env-url:${candidate.source}` } + } + } + + return undefined +} + function detectEnvironment(hostname?: string): { environment: RuntimeEnvironment; region: RuntimeRegion; matchedRule: string } { const normalized = hostname?.toLowerCase() ?? '' let environment: RuntimeEnvironment = 'prod' let matchedRule = 'default' - if (normalized.startsWith('dev.') || normalized.startsWith('dev-') || normalized.includes('.dev.')) { - environment = 'sit' - matchedRule = 'dev-subdomain' + const envOverride = detectEnvironmentFromEnvVars() + if (envOverride) { + environment = envOverride.environment + matchedRule = envOverride.matchedRule + } else { + const hostnameMatch = normalized ? detectEnvironmentFromHostnamePattern(normalized) : undefined + if (hostnameMatch) { + environment = hostnameMatch.environment + matchedRule = hostnameMatch.matchedRule + } else { + const hintMatch = detectEnvironmentFromUrlHints() + if (hintMatch) { + environment = hintMatch.environment + matchedRule = hintMatch.matchedRule + } + } } let region: RuntimeRegion = 'default'