Spaces:
Runtime error
Runtime error
| // Detects hardcoded old versions / stale dates in docs that should follow the current release. | |
| // Uses hardcoded regexes to avoid dynamic RegExp() (ReDoS concern flagged by semgrep). | |
| // Exits 0 if clean, 1 (in --strict mode) if drift detected. | |
| // | |
| // Run: node scripts/check/check-deprecated-versions.mjs | |
| // Strict: node scripts/check/check-deprecated-versions.mjs --strict | |
| import fs from "node:fs"; | |
| import path from "node:path"; | |
| import { fileURLToPath } from "node:url"; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
| const ROOT = path.resolve(__dirname, "..", ".."); | |
| const DOCS_DIR = path.join(ROOT, "docs"); | |
| const PKG_JSON = path.join(ROOT, "package.json"); | |
| const STRICT = process.argv.includes("--strict"); | |
| function currentVersion() { | |
| try { | |
| return JSON.parse(fs.readFileSync(PKG_JSON, "utf8")).version || "0.0.0"; | |
| } catch { | |
| return "0.0.0"; | |
| } | |
| } | |
| // Hardcoded set of obviously-stale version patterns. Update when bumping major/minor. | |
| // These match any version <= v3.6.x or any pre-3 major. | |
| const STALE_VERSION_PATTERNS = [ | |
| /\bv?[12]\.\d+\.\d+\b/, // 1.x.x / 2.x.x | |
| /\bv?3\.[0-6]\.\d+\b/, // 3.0.x..3.6.x | |
| ]; | |
| const SAFE_CONTEXTS = | |
| /(historical|archive|legacy|previously|deprecated|since|introduced|was|originally|fix|fixed)/i; | |
| // Dates older than 60 days are considered stale for "Last updated" / "Last consolidated". | |
| const STALE_DATE_DAYS = 60; | |
| const today = new Date(); | |
| const LAST_UPDATED_RE = /Last (?:updated|consolidated|generated)[^\d]{0,30}(\d{4}-\d{2}-\d{2})/i; | |
| function isStaleDate(yyyy_mm_dd) { | |
| const d = new Date(yyyy_mm_dd); | |
| if (Number.isNaN(d.getTime())) return false; | |
| const ageDays = (today - d) / (1000 * 60 * 60 * 24); | |
| return ageDays > STALE_DATE_DAYS; | |
| } | |
| const IGNORED_DIRS = new Set(["archive", "i18n", "superpowers"]); | |
| const IGNORED_BASENAMES = new Set(["CHANGELOG.md", "RFC-AUTO-ASSESSMENT-DRAFT.md"]); | |
| function walkDocs(dir) { | |
| const out = []; | |
| for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { | |
| if (IGNORED_DIRS.has(entry.name)) continue; | |
| const full = path.join(dir, entry.name); | |
| if (entry.isDirectory()) { | |
| out.push(...walkDocs(full)); | |
| } else if (entry.isFile() && entry.name.endsWith(".md")) { | |
| if (IGNORED_BASENAMES.has(entry.name)) continue; | |
| out.push(full); | |
| } | |
| } | |
| return out; | |
| } | |
| function main() { | |
| const cur = currentVersion(); | |
| console.log(`Version drift report (current: v${cur})`); | |
| console.log("=".repeat(40)); | |
| const files = walkDocs(DOCS_DIR); | |
| let drift = 0; | |
| for (const file of files) { | |
| const rel = path.relative(ROOT, file); | |
| const txt = fs.readFileSync(file, "utf8"); | |
| const lines = txt.split("\n"); | |
| const issues = []; | |
| for (let i = 0; i < lines.length; i++) { | |
| const line = lines[i]; | |
| if (line.length > 500) continue; // skip very long lines (likely code blocks / data) | |
| // 1. Stale version refs | |
| for (const re of STALE_VERSION_PATTERNS) { | |
| const m = re.exec(line); | |
| if (m && !SAFE_CONTEXTS.test(line)) { | |
| issues.push({ | |
| line: i + 1, | |
| type: "stale-version", | |
| match: m[0], | |
| text: line.trim().slice(0, 100), | |
| }); | |
| break; | |
| } | |
| } | |
| // 2. Stale "Last updated/consolidated/generated" dates | |
| const dateMatch = LAST_UPDATED_RE.exec(line); | |
| if (dateMatch && isStaleDate(dateMatch[1])) { | |
| issues.push({ | |
| line: i + 1, | |
| type: "stale-date", | |
| match: dateMatch[1], | |
| text: line.trim().slice(0, 100), | |
| }); | |
| } | |
| } | |
| if (issues.length > 0) { | |
| console.log(`\n ${rel}`); | |
| for (const iss of issues.slice(0, 5)) { | |
| console.log(` L${iss.line} [${iss.type}] ${iss.match}: ${iss.text}`); | |
| } | |
| if (issues.length > 5) console.log(` ... and ${issues.length - 5} more`); | |
| drift += issues.length; | |
| } | |
| } | |
| console.log(); | |
| if (drift > 0) { | |
| console.warn(`⚠ ${drift} potential drift(s) detected across ${files.length} doc files.`); | |
| if (STRICT) process.exit(1); | |
| } else { | |
| console.log(`✓ No drift detected across ${files.length} doc files.`); | |
| } | |
| } | |
| main(); | |