← back to Dw Sku Integrity

chunk-content-match-plan.mjs

45 lines

#!/usr/bin/env node
// Split one reviewed content-match restore map into independently preflightable,
// reversible <=N-row chunks. This is local-only plan generation; it never
// connects to or writes a database. TK-10900.

import { readFileSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
import { join } from 'node:path';

const sqlEscape = (v) => String(v).replace(/'/g, "''");

export function splitPlan(entries, size) {
  if (!Number.isInteger(size) || size < 1 || size > 500) throw new Error('chunk size must be an integer from 1 to 500');
  const out = [];
  for (let i = 0; i < entries.length; i += size) out.push(entries.slice(i, i + size));
  return out;
}

export function writeChunks({ sourceDir, outDir, size = 500 }) {
  const entries = JSON.parse(readFileSync(join(sourceDir, 'restore-map.json'), 'utf8'));
  const chunks = splitPlan(entries, size);
  rmSync(outDir, { recursive: true, force: true });
  mkdirSync(outDir, { recursive: true });
  const header = '-- GATED -- TK-10900 chunked canonical recovery; existing codes only, no mint.\n';
  chunks.forEach((rows, index) => {
    const name = `chunk-${String(index + 1).padStart(3, '0')}`;
    const dir = join(outDir, name);
    mkdirSync(dir, { recursive: true });
    writeFileSync(join(dir, 'restore-map.json'), JSON.stringify(rows, null, 2) + '\n');
    writeFileSync(join(dir, 'apply.sql'), header + rows.map((r) =>
      `UPDATE shopify_products SET dw_sku='${sqlEscape(r.new)}' WHERE shopify_id='${sqlEscape(r.shopify_id)}' AND (dw_sku IS NULL OR btrim(dw_sku)='');`).join('\n') + '\n');
    writeFileSync(join(dir, 'undo.sql'), header + rows.map((r) =>
      `UPDATE shopify_products SET dw_sku=NULL WHERE shopify_id='${sqlEscape(r.shopify_id)}' AND dw_sku='${sqlEscape(r.new)}';`).join('\n') + '\n');
  });
  return chunks.map((rows, index) => ({ name: `chunk-${String(index + 1).padStart(3, '0')}`, rows: rows.length }));
}

if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
  const arg = (name, fallback = null) => { const i = process.argv.indexOf(name); return i >= 0 ? process.argv[i + 1] : fallback; };
  const sourceDir = arg('--source');
  const outDir = arg('--out');
  const size = Number(arg('--size', '500'));
  if (!sourceDir || !outDir) throw new Error('usage: chunk-content-match-plan.mjs --source <vendor-plan-dir> --out <chunk-root> [--size 500]');
  console.log(JSON.stringify(writeChunks({ sourceDir, outDir, size }), null, 2));
}