#!/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.`);