Files
Genarrative/scripts/dev-utils.mjs
高物 d1adfa3406 Improve local auth env handling and fallbacks
Allow local env files to reliably override authentication feature flags (SMS/WeChat) by whitelisting keys in scripts/dev-utils.mjs and adding a unit test. Add SMS checks to scripts/check-api-server-env.mjs. Make server config.parse_bool tolerant of shell-wrapped quoted values (e.g. '"true"') and add tests so SMS_AUTH_ENABLED is parsed correctly when shells supply quotes. Update docs to clarify SMS env behaviour, restart requirements, and add guidance + a CSS fallback for old mobile browsers (QQ/X5) so public cover images render even when aspect-ratio is unsupported. Also include related frontend test and component adjustments and add puzzle onboarding handlers/endpoints in server-rs/crates/api-server/src/puzzle.rs.
2026-05-18 23:13:49 +08:00

116 lines
2.8 KiB
JavaScript

import {existsSync, mkdirSync, readFileSync} from 'node:fs';
import {dirname, isAbsolute, resolve} from 'node:path';
export const LOCAL_ENV_FILES = ['.env', '.env.local', '.env.secrets.local'];
const LOCAL_ENV_OVERRIDE_KEYS = new Set([
'SMS_AUTH_ENABLED',
'SMS_AUTH_PROVIDER',
'SMS_AUTH_MOCK_VERIFY_CODE',
'WECHAT_AUTH_ENABLED',
'WECHAT_AUTH_PROVIDER',
]);
export function buildProtectedEnvKeys(baseEnv) {
return new Set(
Object.entries(baseEnv)
.filter(([, value]) => String(value ?? '').trim())
.map(([key]) => key),
);
}
export function loadEnvFile(path, target, protectedKeys) {
if (!existsSync(path)) {
return;
}
const rawText = readFileSync(path, 'utf8');
for (const rawLine of rawText.split(/\r?\n/u)) {
const line = rawLine.trim();
if (!line || line.startsWith('#')) {
continue;
}
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/u);
if (!match) {
continue;
}
const [, key, rawValue] = match;
if (protectedKeys.has(key) && !LOCAL_ENV_OVERRIDE_KEYS.has(key)) {
continue;
}
target[key] = rawValue.replace(/^['"]|['"]$/gu, '');
}
}
export function loadApiServerEnv(repoRootPath, target, protectedKeys) {
const resolvedProtectedKeys =
protectedKeys ?? buildProtectedEnvKeys(process.env);
// shell > .env > .env.local > .env.secrets.local
for (const fileName of LOCAL_ENV_FILES) {
loadEnvFile(resolve(repoRootPath, fileName), target, resolvedProtectedKeys);
}
}
export function mergeApiServerEnv(repoRootPath, baseEnv = process.env) {
const mergedEnv = {...baseEnv};
loadApiServerEnv(repoRootPath, mergedEnv, buildProtectedEnvKeys(baseEnv));
return mergedEnv;
}
export function formatApiServerLogTimestamp(date = new Date()) {
const pad = (value) => String(value).padStart(2, '0');
return [
date.getFullYear(),
pad(date.getMonth() + 1),
pad(date.getDate()),
'-',
pad(date.getHours()),
pad(date.getMinutes()),
pad(date.getSeconds()),
].join('');
}
export function resolveApiServerLogFile(
repoRootPath,
env = process.env,
now = new Date(),
) {
const explicitLogFile = String(
env.GENARRATIVE_API_SERVER_LOG_FILE ?? '',
).trim();
if (explicitLogFile) {
return isAbsolute(explicitLogFile)
? explicitLogFile
: resolve(repoRootPath, explicitLogFile);
}
const logDir =
String(env.GENARRATIVE_API_SERVER_LOG_DIR ?? '').trim() ||
'logs/api-server';
const resolvedLogDir = isAbsolute(logDir)
? logDir
: resolve(repoRootPath, logDir);
return resolve(
resolvedLogDir,
`api-server-${formatApiServerLogTimestamp(now)}.log`,
);
}
export function ensureParentDir(filePath) {
mkdirSync(dirname(filePath), {recursive: true});
}
export function resolveClientHost(hostName) {
if (hostName === '0.0.0.0' || hostName === '::') {
return '127.0.0.1';
}
return hostName;
}