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