← 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));
}