← back to Dw Contact Us Pages
scripts/rollback-theme.mjs
48 lines
#!/usr/bin/env node
// rollback-theme.mjs — restore a theme from the preimage push-theme.mjs captured.
// node scripts/rollback-theme.mjs --theme <id> # dry-run
// node scripts/rollback-theme.mjs --theme <id> --apply
// Assets that had NO preimage (created:true) are DELETED; the rest are restored byte-for-byte.
import { readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { ROOT, parseArgs, banner, rest, logReversible } from './lib.mjs';
const a = parseArgs();
const themeId = String(a.theme || '');
if (!/^\d+$/.test(themeId)) { console.error('usage: node scripts/rollback-theme.mjs --theme <id> [--apply]'); process.exit(1); }
banner('rollback-theme', a.apply);
const preDir = join(ROOT, 'data', 'theme-preimage', themeId);
const mf = join(preDir, 'manifest.json');
if (!existsSync(mf)) { console.error(`FATAL: no preimage manifest at ${mf} — nothing to roll back from.`); process.exit(1); }
const manifest = JSON.parse(readFileSync(mf, 'utf8'));
const restores = manifest.files.filter((f) => !f.created);
const deletes = manifest.files.filter((f) => f.created);
console.log(`preimage from ${manifest.captured_at} · theme "${manifest.name}" role=${manifest.role}`);
console.log(`plan: restore ${restores.length}, delete ${deletes.length}`);
for (const f of restores) console.log(` restore ${f.key} (${f.remote_bytes}B)`);
for (const f of deletes) console.log(` DELETE ${f.key} (was not present before the push)`);
if (!a.apply) { console.log('\nDRY-RUN: nothing was written.'); process.exit(0); }
let ok = 0, fail = 0;
for (const f of restores) {
const value = readFileSync(join(preDir, f.key), 'utf8');
const r = await rest(`themes/${themeId}/assets.json`, { method: 'PUT', body: { asset: { key: f.key, value } } });
r.ok ? (ok++, console.log(` restored ${f.key}`)) : (fail++, console.error(` FAIL ${f.key}: ${r.status}`));
await new Promise((x) => setTimeout(x, 250));
}
for (const f of deletes) {
const r = await rest(`themes/${themeId}/assets.json?asset[key]=${encodeURIComponent(f.key)}`, { method: 'DELETE' });
r.ok ? (ok++, console.log(` deleted ${f.key}`)) : (fail++, console.error(` FAIL delete ${f.key}: ${r.status}`));
await new Promise((x) => setTimeout(x, 250));
}
console.log(`\nrollback: ${ok} ok, ${fail} failed`);
logReversible({
action: `TK-11925 ROLLBACK theme ${themeId} from preimage (${restores.length} restored, ${deletes.length} deleted)`,
blast: ok,
undo: `cd ~/Projects/dw-contact-us-pages && node scripts/push-theme.mjs --theme ${themeId} --apply`,
verify: 'cd ~/Projects/dw-contact-us-pages && node scripts/verify.mjs',
});
process.exit(fail ? 1 : 0);