#!/usr/bin/env node // tools/db-schema/scripts/check-source-comments.mjs // Source: Phase 3 D-22 + plan 03-06 WARNING 7 — per-column SOURCE-comment // enforcement. For each Drizzle column declaration line in src/tables.ts, // asserts the SAME line OR the IMMEDIATELY-FOLLOWING non-blank line contains // `// SOURCE:`. Replaces heuristic `grep -c // SOURCE: ... >= N` checks. // // Usage: node scripts/check-source-comments.mjs // Exit: 0 success, 1 missing-source on at least one column, 2 invariant // broken (zero columns detected — regex stale). import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; const tablesPath = fileURLToPath(new URL('../src/tables.ts', import.meta.url)); const txt = readFileSync(tablesPath, 'utf-8'); const lines = txt.split('\n'); // Drizzle column declaration heuristic: a line that calls one of the column // constructors (text|integer|blob|real) AND is NOT itself a comment line. // We anchor on `: (` so this won't match `import { text, ... }` // or method-chained `.references(() => ...)` calls. const COL_RE = /^\s*(\w+):\s*(text|integer|blob|real)\s*\(/; let errors = 0; let columnCount = 0; let inTable = false; // Tracks paren depth inside the current sqliteTable(...) call so we close the // scope correctly even when the columns object spans `},` -> `(t) => ({ ... })`. let parenDepth = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Skip pure-comment lines from column-detection altogether. const isComment = /^\s*\/\//.test(line); if (!inTable && /sqliteTable\s*\(/.test(line) && !isComment) { inTable = true; // Count parens after the sqliteTable( opener on this line. parenDepth = 0; const sliceFromCall = line.slice(line.indexOf('sqliteTable(')); for (const ch of sliceFromCall) { if (ch === '(') parenDepth++; else if (ch === ')') parenDepth--; } continue; } if (inTable) { if (!isComment) { for (const ch of line) { if (ch === '(') parenDepth++; else if (ch === ')') parenDepth--; } if (parenDepth <= 0) { inTable = false; continue; } } if (isComment) continue; if (!COL_RE.test(line)) continue; columnCount++; const sameLineHasSource = /\/\/\s*SOURCE:/.test(line); let nextLineHasSource = false; for (let j = i + 1; j < Math.min(lines.length, i + 5); j++) { const peek = lines[j]; if (peek.trim() === '') continue; nextLineHasSource = /^\s*\/\/\s*SOURCE:/.test(peek); break; } if (!sameLineHasSource && !nextLineHasSource) { process.stderr.write( `check-source-comments: tables.ts:${i + 1} column '${line.trim().slice(0, 80)}' missing // SOURCE: comment\n` ); errors++; } } } if (columnCount === 0) { process.stderr.write('check-source-comments: zero columns detected — heuristic regex stale or src/tables.ts empty\n'); process.exit(2); } if (errors > 0) { process.stderr.write(`check-source-comments: ${errors} of ${columnCount} columns missing SOURCE comments\n`); process.exit(1); } process.stdout.write(`check-source-comments: OK (${columnCount} columns, all SOURCE-cited)\n`); process.exit(0);