← back to Harlequin Pilot Publish
04-untrack-samples.mjs
87 lines
// PART A: Flip the 23 pilot Sample variants to inventory-UNTRACKED (DW rule: samples untracked).
// IDEMPOTENT (skip any already untracked). REVERSIBLE — records prior tracked state into a restore-map.
// Uses productVariantsBulkUpdate on inventoryItem.tracked=false (the 2024-10 way to untrack).
import fs from 'node:fs';
import path from 'node:path';
import { gql, getProductVariants } from './lib.mjs';
const APPLY = process.argv.includes('--apply');
const verify = JSON.parse(fs.readFileSync(new URL('./data/verify.json', import.meta.url)));
const located = JSON.parse(fs.readFileSync(new URL('./data/located.json', import.meta.url)));
const byDw = Object.fromEntries(located.map(l => [l.dw_sku, l]));
const RESTORE = process.env.HOME + '/.claude/yolo-queue/executed-reversible/harlequin-sample-untrack-restore-map.json';
const LEDGER = process.env.HOME + '/.claude/yolo-queue/executed-reversible/ledger.jsonl';
let restore = { ticket: 'TK-10876', agent: 'vp-dw-commerce', created_at: new Date().toISOString(),
action: 'Part A: untrack 23 Harlequin pilot Sample variants (inventory_management shopify -> null)', entries: [] };
if (fs.existsSync(RESTORE)) { try { restore = JSON.parse(fs.readFileSync(RESTORE)); if (!restore.entries) restore.entries = []; } catch {} }
function save() { fs.mkdirSync(path.dirname(RESTORE), { recursive: true }); fs.writeFileSync(RESTORE, JSON.stringify(restore, null, 2)); }
// GraphQL: read current tracked state fresh, then flip via productVariantsBulkUpdate
const BULK = `mutation($pid:ID!,$variants:[ProductVariantsBulkInput!]!){
productVariantsBulkUpdate(productId:$pid, variants:$variants){
productVariants { id inventoryItem { id tracked } }
userErrors { field message }
}
}`;
const results = [];
for (const row of verify) {
const dw = row.dw_sku;
const loc = byDw[dw];
const prod = await getProductVariants(loc.productGid); // fresh
const vs = prod.variants.edges.map(e => e.node);
const sample = vs.find(v => (v.sku || '').toUpperCase() === (dw + '-SAMPLE').toUpperCase());
if (!sample) { results.push({ dw_sku: dw, action: 'SKIP-NO-SAMPLE' }); process.stderr.write(`${dw}\tSKIP-NO-SAMPLE\n`); continue; }
const priorTracked = sample.inventoryItem?.tracked; // true = currently tracked
if (priorTracked === false) {
results.push({ dw_sku: dw, action: 'SKIP-ALREADY-UNTRACKED', variant_id: sample.id });
process.stderr.write(`${dw}\tSKIP-ALREADY-UNTRACKED\n`); continue;
}
if (!APPLY) {
results.push({ dw_sku: dw, action: 'WOULD-UNTRACK', variant_id: sample.id, prior_tracked: priorTracked });
process.stderr.write(`${dw}\tWOULD-UNTRACK (prior tracked=${priorTracked})\n`); continue;
}
const d = await gql(BULK, { pid: loc.productGid, variants: [{ id: sample.id, inventoryItem: { tracked: false } }] });
const errs = d.productVariantsBulkUpdate.userErrors;
if (errs && errs.length) {
results.push({ dw_sku: dw, action: 'ERROR', errors: errs });
process.stderr.write(`${dw}\tERROR ${JSON.stringify(errs)}\n`); continue;
}
const upd = d.productVariantsBulkUpdate.productVariants[0];
restore.entries.push({
dw_sku: dw, product_gid: loc.productGid, handle: loc.handle,
variant_id: sample.id, inventory_item_id: sample.inventoryItem?.id,
change: 'UNTRACKED_SAMPLE', field: 'inventoryItem.tracked',
prior_value: true, new_value: false,
undo: `productVariantsBulkUpdate ${loc.productGid} [{id:${sample.id}, inventoryItem:{tracked:true}}]`,
now_tracked: upd?.inventoryItem?.tracked, at: new Date().toISOString(),
});
save();
results.push({ dw_sku: dw, action: 'UNTRACKED', variant_id: sample.id, now_tracked: upd?.inventoryItem?.tracked });
process.stderr.write(`${dw}\tUNTRACKED now_tracked=${upd?.inventoryItem?.tracked}\n`);
await new Promise(r => setTimeout(r, 400));
}
fs.writeFileSync(new URL('./data/untrack-results.json', import.meta.url), JSON.stringify(results, null, 2));
save();
if (APPLY && restore.entries.length) {
fs.mkdirSync(path.dirname(LEDGER), { recursive: true });
fs.appendFileSync(LEDGER, JSON.stringify({
ts: new Date().toISOString(), agent: 'vp-dw-commerce', ticket: 'TK-10876',
action: 'Part A: untrack Harlequin pilot Sample variants',
blast_radius: restore.entries.length,
undo_cmd: `node ~/Projects/harlequin-pilot-publish/rollback-untrack.mjs --apply`,
verify: 'GET each sample variant; inventoryItem.tracked must be false, price 4.25',
restore_map: RESTORE,
}) + '\n');
}
const u = results.filter(r => r.action === 'UNTRACKED').length;
const s = results.filter(r => r.action === 'SKIP-ALREADY-UNTRACKED').length;
const e = results.filter(r => r.action === 'ERROR').length;
process.stderr.write(`\n${APPLY ? 'APPLY' : 'DRY-RUN'} DONE: untracked=${u} already=${s} errors=${e}\nrestore-map: ${RESTORE}\n`);