mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
Adds three automated drift-detection scripts under scripts/, plus npm helpers to run them, and applies small follow-ups to TERMUX, I18N, and CODEBASE docs flagged by the docs audit. New scripts (scripts/): - check-env-doc-sync.mjs — cross-checks process.env.X in code vs .env.example vs docs/ENVIRONMENT.md. Soft-fails by default; --strict exits 1 on drift. - check-docs-counts-sync.mjs — validates counts (executors, routing strategies, OAuth providers, A2A skills, cloud agents) match between code and docs. - check-deprecated-versions.mjs — flags hardcoded stale versions and "Last updated" dates older than 60 days. Uses hardcoded regexes to satisfy semgrep ReDoS guidance. package.json: - New scripts: check:env-doc-sync, check:docs-counts, check:deprecated-versions, check:docs-all (umbrella). Doc refinements: - TERMUX_GUIDE: spell out the Node version range required by package.json. - I18N: note that docs/i18n/in/ tree is an orphan duplicate of hi/ that the generator no longer writes to; replace hard-coded UNTRANSLATABLE_KEYS count with "varies per release". - CODEBASE_DOCUMENTATION: minor wording. Pre-commit hook is unchanged — the new checks are heuristic and ship as on-demand npm scripts to avoid false-positive blocks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
4.1 KiB
JavaScript
132 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// Validates that env vars referenced in code appear in .env.example AND in docs/ENVIRONMENT.md.
|
|
// Exits 0 on success, 1 on missing entries. Designed for use in pre-commit / CI.
|
|
//
|
|
// Run: node scripts/check-env-doc-sync.mjs
|
|
// Strict mode: node scripts/check-env-doc-sync.mjs --strict
|
|
// In strict mode, missing entries cause failure. In default mode, only summary is printed.
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { execSync } from "node:child_process";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = path.resolve(__dirname, "..");
|
|
const ENV_EXAMPLE = path.join(ROOT, ".env.example");
|
|
const ENV_DOC = path.join(ROOT, "docs", "ENVIRONMENT.md");
|
|
|
|
const STRICT = process.argv.includes("--strict");
|
|
|
|
// Vars that are intentionally not documented or detected in code via dynamic patterns.
|
|
const IGNORE = new Set([
|
|
"NODE_ENV",
|
|
"PATH",
|
|
"HOME",
|
|
"USER",
|
|
"PWD",
|
|
"SHELL",
|
|
"TERM",
|
|
"TZ",
|
|
"LANG",
|
|
"LC_ALL",
|
|
"CI",
|
|
"GITHUB_ACTIONS",
|
|
"RUNNER_OS",
|
|
// Add false positives here as discovered.
|
|
]);
|
|
|
|
function readEnvExampleVars() {
|
|
if (!fs.existsSync(ENV_EXAMPLE)) {
|
|
console.error(`✗ ${ENV_EXAMPLE} not found`);
|
|
process.exit(2);
|
|
}
|
|
const txt = fs.readFileSync(ENV_EXAMPLE, "utf8");
|
|
const vars = new Set();
|
|
for (const line of txt.split("\n")) {
|
|
// Match both "VAR=value" and "# VAR=value" (commented-out examples are still documented)
|
|
const m = line.match(/^#?\s*([A-Z][A-Z0-9_]+)\s*=/);
|
|
if (m) vars.add(m[1]);
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
function readEnvDocVars() {
|
|
if (!fs.existsSync(ENV_DOC)) {
|
|
console.error(`✗ ${ENV_DOC} not found`);
|
|
process.exit(2);
|
|
}
|
|
const txt = fs.readFileSync(ENV_DOC, "utf8");
|
|
const vars = new Set();
|
|
// Match `VAR_NAME` in inline code or table cells.
|
|
for (const m of txt.matchAll(/`([A-Z][A-Z0-9_]{2,})`/g)) {
|
|
vars.add(m[1]);
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
function readCodeVars() {
|
|
const vars = new Set();
|
|
let stdout;
|
|
try {
|
|
stdout = execSync(
|
|
"grep -rhoE 'process\\.env\\.[A-Z][A-Z0-9_]+' src/ open-sse/ bin/ scripts/ 2>/dev/null || true",
|
|
{ cwd: ROOT, encoding: "utf8", maxBuffer: 20 * 1024 * 1024 }
|
|
);
|
|
} catch (e) {
|
|
console.error(`✗ grep failed: ${e.message}`);
|
|
process.exit(2);
|
|
}
|
|
for (const line of stdout.split("\n")) {
|
|
const m = line.match(/^process\.env\.([A-Z][A-Z0-9_]+)$/);
|
|
if (m && !IGNORE.has(m[1])) vars.add(m[1]);
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
function diff(set, against) {
|
|
return [...set].filter((v) => !against.has(v)).sort();
|
|
}
|
|
|
|
function main() {
|
|
const codeVars = readCodeVars();
|
|
const exampleVars = readEnvExampleVars();
|
|
const docVars = readEnvDocVars();
|
|
|
|
const inCodeMissingExample = diff(codeVars, exampleVars);
|
|
const inCodeMissingDoc = diff(codeVars, docVars);
|
|
const inExampleMissingDoc = diff(exampleVars, docVars);
|
|
const inExampleMissingCode = diff(exampleVars, codeVars);
|
|
|
|
console.log("Env var sync report");
|
|
console.log("===================");
|
|
console.log(`Code references: ${codeVars.size} unique vars`);
|
|
console.log(`In .env.example: ${exampleVars.size} unique vars`);
|
|
console.log(`In docs/ENVIRONMENT.md: ${docVars.size} unique vars (heuristic)`);
|
|
console.log();
|
|
|
|
function printList(label, list) {
|
|
if (list.length === 0) {
|
|
console.log(` ✓ ${label}: none`);
|
|
} else {
|
|
console.log(` ⚠ ${label}: ${list.length}`);
|
|
for (const v of list.slice(0, 30)) console.log(` - ${v}`);
|
|
if (list.length > 30) console.log(` ... and ${list.length - 30} more`);
|
|
}
|
|
}
|
|
|
|
printList("In code but missing from .env.example", inCodeMissingExample);
|
|
printList("In code but missing from ENVIRONMENT.md", inCodeMissingDoc);
|
|
printList("In .env.example but missing from ENVIRONMENT.md", inExampleMissingDoc);
|
|
printList("In .env.example but not referenced in code (dead?)", inExampleMissingCode);
|
|
|
|
const errors = inCodeMissingExample.length + inExampleMissingDoc.length;
|
|
if (STRICT && errors > 0) {
|
|
console.error(`\n✗ ${errors} drift(s) detected (strict mode)`);
|
|
process.exit(1);
|
|
}
|
|
console.log(`\n${errors === 0 ? "✓" : "⚠"} Done.`);
|
|
}
|
|
|
|
main();
|