← back to Dw Yolo Loop
scripts/kravet-cost/revert-map-push.mjs
53 lines
#!/usr/bin/env node
/**
* revert-map-push.mjs — undo the MAP push: restore every product whose roll price
* was changed to MAP back to its ORIGINAL price (status-quo-ante).
*
* Why: DTD verdict (3/3, 2026-06-15) = the MAP table has a units/granularity defect
* (~3x gap, split across the 2.5x guard), so the 998 push cannot be trusted. This
* restores the prior live prices. Re-detects what was actually written by reading the
* live roll price (== MAP => was written; == original => skip). Reversible, restorative.
*
* DRY-RUN default; LIVE requires --apply --i-am-steve.
*/
import fs from 'node:fs';
const SHOP = 'designer-laboratory-sandbox.myshopify.com', VER = '2024-10';
const TOKEN = (fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
const URL = `https://${SHOP}/admin/api/${VER}/graphql.json`;
const args = Object.fromEntries(process.argv.slice(2).map(a => { const [k, v] = a.replace(/^--/, '').split('='); return [k, v === undefined ? true : v]; }));
const APPLY = args.apply === true && args['i-am-steve'] === true;
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function gql(q, v) { for (let a = 0; a < 8; a++) { let j; try { const r = await fetch(URL, { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ query: q, variables: v }) }); j = await r.json(); } catch (e) { await sleep(1500 * (a + 1)); continue; } if (j.errors) { if (JSON.stringify(j.errors).includes('THROTTLED')) { await sleep(2000 * (a + 1)); continue; } throw new Error(JSON.stringify(j.errors)); } const t = j.extensions?.cost?.throttleStatus; if (t && t.currentlyAvailable < 400) await sleep(1200); return j.data; } throw new Error('retries'); }
const plan = JSON.parse(fs.readFileSync('data/kravet-cost/price-push-plan.json', 'utf8'));
const items = plan.toWrite; // {pid, vid, mfr, cur, map}
console.log(`revert-map-push — mode: ${APPLY ? '⚠️ LIVE REVERT' : 'DRY-RUN'} | candidates: ${items.length}`);
const Q = `query($ids:[ID!]!){ nodes(ids:$ids){ ... on ProductVariant { id price } } }`;
const M = `mutation($pid:ID!,$variants:[ProductVariantsBulkInput!]!){ productVariantsBulkUpdate(productId:$pid, variants:$variants){ userErrors{ field message } } }`;
// detect which were written (live price == map)
const written = [];
for (let i = 0; i < items.length; i += 100) {
const batch = items.slice(i, i + 100);
const d = await gql(Q, { ids: batch.map(b => b.vid) });
const live = new Map(d.nodes.filter(Boolean).map(n => [n.id, parseFloat(n.price)]));
for (const it of batch) {
const lp = live.get(it.vid);
if (lp != null && Math.abs(lp - it.map) < 0.01) written.push(it); // currently at MAP => was written
}
}
console.log(`detected WRITTEN (live price == MAP): ${written.length} / ${items.length}`);
console.log('sample reverts:'); for (const w of written.slice(0, 8)) console.log(` ${w.mfr}: $${w.map} -> restore $${w.cur}`);
if (!APPLY) { console.log('\nDRY-RUN. To execute: node revert-map-push.mjs --apply --i-am-steve'); process.exit(0); }
console.log('\n⚠️ LIVE REVERT — restoring original prices…');
let ok = 0, err = 0;
for (const w of written) {
try { const d = await gql(M, { pid: w.pid, variants: [{ id: w.vid, price: w.cur.toFixed(2) }] });
const ue = d.productVariantsBulkUpdate?.userErrors || []; if (ue.length) { err++; if (err <= 10) console.log(' err', w.mfr, JSON.stringify(ue)); } else ok++;
} catch (e) { err++; if (err <= 10) console.log(' EX', w.mfr, e.message); }
if ((ok + err) % 200 === 0) console.log(` progress ${ok} reverted / ${err} err`);
}
console.log(`\nDONE — ${ok} prices restored to original, ${err} errors.`);