← Files Vocabloot Deck KitARCHIVED FILE

scripts/check.mjs

2.63 KB · Oct 2, 2026 · 00:34 UTC

↓ Download file

#!/usr/bin/env node
// /deck-check: every gate the packer enforces, plus review/preview.html.
//
//   node scripts/check.mjs <deck-folder>
import { writeFileSync, mkdirSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

import { loadFolder, stickerExists } from "./lib/folder.mjs";
import { loadLanguageFacts } from "./lib/registry.mjs";
import { deferredKeys, validateCards, validateDeck, validateLocalization, validateReview } from "./lib/schema.mjs";
import { renderPreview } from "./lib/preview.mjs";

/** All problems for a loaded folder; [] means ready to pack. */
export function checkFolder(folder, { facts = loadLanguageFacts() } = {}) {
  const p = [];
  p.push(...validateDeck(folder.deck));
  if (!folder.cards) return [...p, "cards.json is missing; run /deck-text"];
  const deferred = deferredKeys(folder.review);
  p.push(...validateCards(folder.cards, { facts, learningLanguage: folder.deck.learningLanguage, deferred }));
  if (!folder.localization) p.push(`localizations/${folder.deck.knownLanguage}.json is missing; run /deck-text`);
  else p.push(...validateLocalization(folder.localization, folder.cards));
  for (const c of folder.cards) {
    if (c.sticker?.mode !== "text-first" && c.sticker?.file && !stickerExists(folder.dir, c))
      p.push(`${c.key}: sticker file ${c.sticker.file} is missing`);
    if (c.review?.safe === false) p.push(`${c.key}: marked unsafe`);
    if (c.review?.flags?.length) p.push(`${c.key}: unresolved flags: ${c.review.flags.join(", ")}`);
  }
  p.push(...validateReview(folder.review, folder.cards));
  if (folder.deck.wordCount && folder.cards.length !== folder.deck.wordCount)
    p.push(`deck.wordCount is ${folder.deck.wordCount} but there are ${folder.cards.length} cards`);
  return p;
}

export function writePreview(folder) {
  const dir = path.join(folder.dir, "review");
  mkdirSync(dir, { recursive: true });
  const file = path.join(dir, "preview.html");
  writeFileSync(file, renderPreview(folder));
  return file;
}

if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
  const dir = process.argv[2];
  if (!dir) {
    console.error("usage: node scripts/check.mjs <deck-folder>");
    process.exit(2);
  }
  const folder = loadFolder(dir);
  const problems = checkFolder(folder);
  if (folder.cards) console.log(`preview: ${writePreview(folder)}`);
  if (problems.length) {
    console.error(`${problems.length} problem${problems.length === 1 ? "" : "s"}:`);
    for (const m of problems) console.error(`  ${m}`);
    process.exit(1);
  }
  console.log(`${folder.deck.id}: ${folder.cards.length} cards, ready to pack`);
}

SHA-256: 22882cf270da6397b7007084d0c70ae2163bef03fadba0b75723fa9daf4a7370