ai-chatbot/scripts/check-env-example.mjs
2026-05-27 12:08:44 +08:00

161 lines
4.6 KiB
JavaScript

#!/usr/bin/env node
import { readFileSync } from "node:fs";
const envExamplePath = ".env.example";
const source = readFileSync(envExamplePath, "utf8");
const lines = source.split(/\r?\n/);
const requiredKeys = [
"AUTH_SECRET",
"AUTH_URL",
"NEXTAUTH_URL",
"NEXT_PUBLIC_APP_URL",
"AUTH_TRUST_HOST",
"GOOGLE_CLIENT_ID",
"GOOGLE_CLIENT_SECRET",
"ADMIN_EMAIL",
"ADMIN_PASSWORD",
"RESEND_API_KEY",
"RESEND_FROM_EMAIL",
"EMAIL_VERIFICATION_SECRET",
"CRON_SECRET",
"POSTGRES_URL",
"POSTGRES_MAX_CONNECTIONS",
"POSTGRES_IDLE_TIMEOUT_SECONDS",
"POSTGRES_CONNECT_TIMEOUT_SECONDS",
"BLOB_READ_WRITE_TOKEN",
"S3_ENDPOINT",
"S3_REGION",
"S3_BUCKET",
"S3_ACCESS_KEY_ID",
"S3_SECRET_ACCESS_KEY",
"S3_PUBLIC_URL",
"AI_GATEWAY_API_KEY",
"NEXT_PUBLIC_DEFAULT_CHAT_MODEL",
"DASHSCOPE_BASE_URL",
"DASHSCOPE_API_KEY",
"GTP5_API_URL",
"GTP5_API_KEY",
"GTP5_MODEL",
"FAL_KEY",
"FAL_API_KEY",
"FAL_BASE_URL",
"FAL_MODEL",
"FAL_REALTIME_MODEL",
"REPLICATE_API_TOKEN",
"BFL_API_KEY",
"BFL_KEY",
"BFL_BASE_URL",
"BFL_MODEL",
"RUNWARE_MODEL",
"TRIPO_API_KEY",
"TRIPO_BASE_URL",
"WORLDLABS_API_KEY",
"WORLDLABS_BASE_URL",
"WORLDLABS_IMPORT_TOKEN",
"WORLDLABS_KEY_POOL_IMPORT_TOKEN",
"KITTYCAD_API_TOKEN",
"KITTYCAD_API_KEY",
"KITTYCAD_BASE_URL",
"ZOO_API_TOKEN",
"ZOO_BASE_URL",
"RUXA_CALLBACK_URL",
"RUXA_VIDEO_CALLBACK_URL",
"PRODUCT_PLANNING_DETAIL_PLUS_BASE_URL",
"PRODUCT_PLANNING_DETAIL_PLUS_INTERNAL_BASE_URL",
"PRODUCT_PLANNING_DETAIL_PLUS_API_KEY",
"PRODUCT_PLANNING_QA_TOKEN",
"TAOBAO_API_KEY",
"TAOBAO_API_SECRET",
"TAOBAO_SALE_API_TOKEN",
"PRODUCT_PLANNING_DB_HOST",
"PRODUCT_PLANNING_DB_PORT",
"PRODUCT_PLANNING_DB_DATABASE",
"PRODUCT_PLANNING_DB_NAME",
"PRODUCT_PLANNING_DB_USER",
"PRODUCT_PLANNING_DB_PASSWORD",
"PRODUCT_PLANNING_DB_CHARSET",
"PRODUCT_PLANNING_DB_SSL",
"CANVAS_IMAGE_TASK_CONCURRENCY",
"CANVAS_IMAGE_TASK_BATCH_DISPATCH_INTERVAL_MS",
"EXTERNAL_IMAGE_API_MAX_CONCURRENT",
"EXTERNAL_IMAGE_API_MAX_QUEUE",
"EXTERNAL_IMAGE_API_QUEUE_TIMEOUT_MS",
"AGENT_FORCE_LLM_PLANNER",
"AGENT_FORCE_QWEN3VL_FALLBACK",
"AGENT_IMAGE_GEN_LLM_OPTIMIZER_ENABLED",
"AGENT_LLM_PROMPT_OPTIMIZER_ENABLED",
"AGENT_REPLACEMENT_PARALLEL_WORKER_COUNT",
"AGENT_PSD_LOCAL_CACHE_DIR",
"AGENT_PSD_REMOTE_ASSET_CACHE_DISABLED",
"AGENT_PSD_REMOTE_ASSET_CACHE_MAX_BYTES",
"AGENT_PSD_REMOTE_ASSET_CACHE_MAX_ENTRY_BYTES",
"AGENT_PSD_REMOTE_ASSET_CACHE_TTL_MS",
"AGENT_PSD_RENDER_PNG_COMPRESSION_LEVEL",
"AGENT_PSD_RENDER_TASK_CONCURRENCY",
"NEXT_PUBLIC_AGENT_STYLE_PERF_MONITOR",
"NEXT_PUBLIC_AGENT_STYLE_PERF_SAMPLE_RATE",
"NEXT_PUBLIC_AGENT_STYLE_OPTIMIZED_RENDER",
"LOG_LEVEL",
"LOG_INCLUDE_STACK",
"NEXT_PUBLIC_ENABLE_CLIENT_LOGS",
"VIDEO_ARCHIVE_DELAY_HOURS",
"PORT",
"PLAYWRIGHT",
"CI_PLAYWRIGHT",
"PLAYWRIGHT_TEST_BASE_URL",
"SMOKE_BASE_URL",
"SMOKE_TIMEOUT_MS",
"DISABLE_PERF_GUARD_AUTO_INSTALL",
];
const secretLikeKey = /(SECRET|TOKEN|PASSWORD|API_KEY|ACCESS_KEY|CLIENT_SECRET|FAL_KEY|BFL_KEY|ZOO_API_TOKEN|KITTYCAD_API_TOKEN)$/;
const suspiciousSecretValue =
/(sk-[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|AKIA[0-9A-Z]{16}|eyJ[A-Za-z0-9_-]{20,})/;
const allowedSecretPlaceholders = /^(|replace-with-[a-z0-9-]+|change-me[a-z0-9-]*|placeholder|example|your-[a-z0-9-]+)$/i;
const parsed = new Map();
const violations = [];
lines.forEach((line, index) => {
if (!line || line.trim().startsWith("#")) {
return;
}
const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
if (!match) {
violations.push(`${envExamplePath}:${index + 1}: invalid env assignment: ${line}`);
return;
}
const [, key, rawValue] = match;
const value = rawValue.trim();
if (parsed.has(key)) {
violations.push(`${envExamplePath}:${index + 1}: duplicate key: ${key}`);
}
parsed.set(key, { value, line: index + 1 });
if (secretLikeKey.test(key) && value && !allowedSecretPlaceholders.test(value)) {
violations.push(`${envExamplePath}:${index + 1}: secret-like key must use an empty value or placeholder: ${key}`);
}
if (suspiciousSecretValue.test(value)) {
violations.push(`${envExamplePath}:${index + 1}: suspicious real secret value detected: ${key}`);
}
});
for (const key of requiredKeys) {
if (!parsed.has(key)) {
violations.push(`${envExamplePath}: missing documented env key: ${key}`);
}
}
if (violations.length > 0) {
console.error("Environment example hygiene check failed.");
console.error(violations.join("\n"));
process.exit(1);
}
console.log(`Environment example hygiene check passed for ${parsed.size} keys.`);