← back to Crazy News Channel Shadowman
scripts/verify-tags.mjs
72 lines
#!/usr/bin/env node
// scripts/verify-tags.mjs — TK-12165 proof script.
// Loads stories-data.js, real-news-data.js, and cartoons/manifest.js in a
// node vm Context (same "plain <script src> globals" shape the pages use,
// no DOM/browser needed) and asserts every post carries a non-empty `tags`
// array. Prints tag-vocabulary stats (distinct tags, top 15 by count,
// singleton count) so drift is visible at a glance.
//
// Usage: node scripts/verify-tags.mjs
import fs from "node:fs";
import path from "node:path";
import vm from "node:vm";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, "..");
const ctx = { window: {}, console };
vm.createContext(ctx);
for (const f of ["stories-data.js", "real-news-data.js", "cartoons/manifest.js"]) {
vm.runInContext(fs.readFileSync(path.join(ROOT, f), "utf8"), ctx, { filename: f });
}
const buckets = [
["stories-data.js (window.P24_EXTRA_STORIES)", ctx.window.P24_EXTRA_STORIES || []],
["real-news-data.js (window.P24_REAL_STORIES)", ctx.window.P24_REAL_STORIES || []],
["cartoons/manifest.js (window.P24_CARTOONS)", ctx.window.P24_CARTOONS || []],
];
let totalPosts = 0;
let missing = [];
const freq = new Map();
for (const [label, arr] of buckets) {
console.log(`${label}: ${arr.length} entries`);
for (const post of arr) {
totalPosts++;
const tags = post.tags;
if (!Array.isArray(tags) || tags.length === 0) {
missing.push(`${label} :: ${post.id}`);
continue;
}
for (const t of tags) {
const k = String(t).toLowerCase();
freq.set(k, (freq.get(k) || 0) + 1);
}
}
}
console.log("");
console.log(`Total posts checked: ${totalPosts}`);
console.log(`Posts with missing/empty tags: ${missing.length}`);
if (missing.length) {
console.log("MISSING:");
missing.forEach((m) => console.log(" - " + m));
}
const entries = [...freq.entries()].sort((a, b) => b[1] - a[1]);
const singletons = entries.filter(([, c]) => c === 1);
console.log("");
console.log(`Distinct tags: ${entries.length}`);
console.log(`Singleton tags (used exactly once): ${singletons.length}`);
console.log("");
console.log("Top 15 tags by count:");
for (const [t, c] of entries.slice(0, 15)) console.log(` ${String(c).padStart(3)} ${t}`);
if (missing.length > 0) {
console.error("\nFAIL: one or more posts have no tags.");
process.exit(1);
}
console.log("\nPASS: every post has a non-empty tags array.");