98 lines
4.9 KiB
JavaScript
98 lines
4.9 KiB
JavaScript
/**
|
|
* Guard: no raw SiteConversion queries without countedConversionWhere
|
|
* ────────────────────────────────────────────────────────────────────
|
|
* Fails the build when a file contains a direct prisma.siteConversion
|
|
* count/findMany/groupBy call that neither:
|
|
* (a) imports countedConversionWhere, nor
|
|
* (b) carries an explicit "// intentionally unfiltered:" comment on
|
|
* the same or immediately preceding line.
|
|
*
|
|
* Admin/debug/backfill endpoints that legitimately need raw rows MUST
|
|
* add: // intentionally unfiltered: <reason>
|
|
* to suppress the check.
|
|
*
|
|
* Run: node scripts/check-raw-siteconversion.mjs
|
|
*/
|
|
|
|
import { readFileSync, readdirSync, statSync } from "fs";
|
|
import { join, relative } from "path";
|
|
|
|
const ROOT = join(new URL(".", import.meta.url).pathname, "..");
|
|
const SRC = join(ROOT, "src");
|
|
|
|
const RAW_CALL_RE = /prisma\.siteConversion\.(count|findMany|groupBy|aggregate)\s*\(/g;
|
|
// Lines that are part of a comment block should not be flagged
|
|
const COMMENT_LINE_RE = /^\s*(\*|\/\/|\/\*)/;
|
|
const UNFILTERED_COMMENT_RE = /\/\/\s*intentionally unfiltered:/i;
|
|
const IMPORT_HELPER_RE = /import\s+[^;]*countedConversionWhere[^;]*from/;
|
|
|
|
function walkFiles(dir, results = []) {
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
const stat = statSync(full);
|
|
if (stat.isDirectory()) {
|
|
// Skip node_modules and .next
|
|
if (entry === "node_modules" || entry === ".next") continue;
|
|
walkFiles(full, results);
|
|
} else if (full.endsWith(".ts") || full.endsWith(".tsx")) {
|
|
results.push(full);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
const files = walkFiles(SRC);
|
|
const violations = [];
|
|
|
|
for (const filePath of files) {
|
|
const content = readFileSync(filePath, "utf8");
|
|
const hasHelper = IMPORT_HELPER_RE.test(content);
|
|
const lines = content.split("\n");
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
if (!RAW_CALL_RE.test(line) || COMMENT_LINE_RE.test(line)) {
|
|
RAW_CALL_RE.lastIndex = 0;
|
|
continue;
|
|
}
|
|
RAW_CALL_RE.lastIndex = 0;
|
|
|
|
// If the file imports countedConversionWhere, it's opted in — trust the author.
|
|
if (hasHelper) continue;
|
|
|
|
// Check for an intentionally-unfiltered comment on this line or the line above.
|
|
const prevLine = i > 0 ? lines[i - 1] : "";
|
|
if (UNFILTERED_COMMENT_RE.test(line) || UNFILTERED_COMMENT_RE.test(prevLine)) continue;
|
|
|
|
violations.push(` ${relative(ROOT, filePath)}:${i + 1} → ${line.trim().slice(0, 80)}`);
|
|
}
|
|
}
|
|
|
|
if (violations.length > 0) {
|
|
console.error("");
|
|
console.error("╔══════════════════════════════════════════════════════════════════════╗");
|
|
console.error("║ RAW SITECONVERSION CHECK FAILED ║");
|
|
console.error("╠══════════════════════════════════════════════════════════════════════╣");
|
|
console.error("║ The following files query SiteConversion without using ║");
|
|
console.error("║ countedConversionWhere() or an explicit unfiltered comment. ║");
|
|
console.error("║ ║");
|
|
console.error("║ User-facing conversion numbers MUST route through the helper so ║");
|
|
console.error("║ isDuplicateOf / invalidatedReason / EXCLUDED_CONVERSION_TYPES are ║");
|
|
console.error("║ applied consistently. ║");
|
|
console.error("║ ║");
|
|
console.error("║ Fix options: ║");
|
|
console.error("║ 1. import { countedConversionWhere } from ║");
|
|
console.error('║ "@/lib/conversions/counted-where" ║');
|
|
console.error("║ and use it in the where clause. ║");
|
|
console.error("║ 2. Add: // intentionally unfiltered: <reason> ║");
|
|
console.error("║ on the line before the query for debug/admin/backfill paths. ║");
|
|
console.error("╚══════════════════════════════════════════════════════════════════════╝");
|
|
console.error("");
|
|
violations.forEach((v) => console.error(v));
|
|
console.error("");
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`[check-raw-siteconversion] OK — ${files.length} files checked, no violations.`);
|
|
process.exit(0);
|