← back to Dw Silver Mylar Rename
apply.mjs
55 lines
// Apply the title/descriptionHtml swaps to LIVE Shopify. No option changes (scan found 0).
// Usage: node apply.mjs --ids gid1,gid2 (canary subset)
// node apply.mjs --all (everything not already done)
// node apply.mjs --all --exclude gid1,gid2
import { readFileSync, appendFileSync, existsSync } from 'node:fs';
import { gql, has } from './lib.mjs';
const plan = JSON.parse(readFileSync(new URL('./data/plan.json', import.meta.url)));
const args = process.argv.slice(2);
const idsArg = args.includes('--ids') ? args[args.indexOf('--ids') + 1].split(',') : null;
const exclArg = args.includes('--exclude') ? new Set(args[args.indexOf('--exclude') + 1].split(',')) : new Set();
const all = args.includes('--all');
if (!idsArg && !all) { console.error('pass --ids <gid,gid> or --all'); process.exit(1); }
const LOG = new URL('./data/apply-results.jsonl', import.meta.url);
// resume-safe: skip gids already successfully applied
const done = new Set();
if (existsSync(LOG)) for (const l of readFileSync(LOG, 'utf8').split('\n').filter(Boolean)) {
try { const r = JSON.parse(l); if (r.ok) done.add(r.gid); } catch {}
}
let targets = plan;
if (idsArg) targets = plan.filter(p => idsArg.includes(p.gid));
targets = targets.filter(p => !exclArg.has(p.gid) && !done.has(p.gid));
const M = `mutation($input: ProductInput!){
productUpdate(input:$input){ product{ id title descriptionHtml } userErrors{ field message } }
}`;
let ok = 0, fail = 0; const failures = [];
console.log(`applying to ${targets.length} products...`);
for (const p of targets) {
const input = { id: p.gid };
if (p.changes.title) input.title = p.changes.title.to;
if (p.changes.descriptionHtml) input.descriptionHtml = p.changes.descriptionHtml.to;
try {
const d = await gql(M, { input });
const ue = d.productUpdate.userErrors;
if (ue && ue.length) throw new Error('userErrors: ' + JSON.stringify(ue));
const prod = d.productUpdate.product;
// verify no residual token in returned fields
const residual = has(prod.title) || has(prod.descriptionHtml);
const rec = { ts: new Date().toISOString(), gid: p.gid, status: p.status, ok: !residual,
title_changed: !!p.changes.title, body_changed: !!p.changes.descriptionHtml, residual };
appendFileSync(LOG, JSON.stringify(rec) + '\n');
if (residual) { fail++; failures.push({ gid: p.gid, reason: 'residual token after write' }); }
else ok++;
} catch (e) {
fail++; failures.push({ gid: p.gid, reason: String(e.message || e) });
appendFileSync(LOG, JSON.stringify({ ts: new Date().toISOString(), gid: p.gid, ok: false, error: String(e.message || e) }) + '\n');
}
}
console.log(`DONE ok=${ok} fail=${fail}`);
if (failures.length) { console.log('FAILURES:'); for (const f of failures) console.log(' ', f.gid, f.reason); }