← back to Dw Add Sellable Variant Tk10902
reprice-existing-variant.mjs
81 lines
// TK-10456 companion to add-variant.mjs — for cohort products that ALREADY have a
// sellable (non -sample) variant sitting at the $4.25 leak price. add-variant.mjs
// skips these (variant_count != 1); this reprices the existing sellable variant to
// the real DW price instead of adding a new one.
//
// Usage: node reprice-existing-variant.mjs <candidates.json> [--limit N] [--live]
// default = DRY-RUN (no writes). --live performs the writes.
// Candidate shape (same file as add-variant.mjs): { product_id, vendor, final_price, ... }
//
// Guards (fail-safe — can only reprice a real leak, never mint/activate):
// - product fetched OK, has a NON-sample sellable variant
// - that sellable variant's price is <= 4.25 (i.e. the $4.25 leak) — never touch a
// variant that already carries a real price
// - final_price > 4.25 (sane) — else skip
// Each repriced variant is ledgered with a per-variant undo (restore old price).
import fs from 'fs';
import { execSync } from 'child_process';
const TOKEN = execSync(`grep -E '^SHOPIFY_ADMIN_TOKEN=' ${process.env.HOME}/Projects/secrets-manager/.env | cut -d= -f2-`).toString().trim();
const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
const LEDGER = `${process.env.HOME}/.claude/yolo-queue/executed-reversible/ledger.jsonl`;
const RUNLOG = `${process.cwd()}/reprice-run-log.jsonl`;
const args = process.argv.slice(2);
const file = args.find(a => !a.startsWith('--'));
const LIVE = args.includes('--live');
const limArg = args.find(a => a.startsWith('--limit'));
const LIMIT = limArg ? parseInt(args[args.indexOf(limArg) + 1], 10) : Infinity;
async function shopify(path, method = 'GET', body = null) {
for (let i = 0; i < 4; i++) {
try {
const res = await fetch(`https://${DOMAIN}/admin/api/2024-10${path}`, {
method, headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
return { status: res.status, json: await res.json().catch(() => ({})) };
} catch (e) { await new Promise(r => setTimeout(r, 2500)); }
}
return { status: 0, json: {} };
}
const appendJsonl = (p, o) => fs.appendFileSync(p, JSON.stringify(o) + '\n');
const candidates = JSON.parse(fs.readFileSync(file, 'utf8'));
const batch = candidates.slice(0, LIMIT);
let repriced = 0, skipped = 0;
const skips = [];
console.log(`\n=== ${LIVE ? 'LIVE' : 'DRY-RUN'} reprice — ${batch.length} candidates ===\n`);
for (const c of batch) {
const pid = c.product_id;
const { status, json } = await shopify(`/products/${pid}.json`);
if (status !== 200 || !json.product) { skipped++; skips.push({ pid, reason: `fetch_failed_${status}`, vendor: c.vendor }); continue; }
const p = json.product;
const variants = p.variants || [];
const sellables = variants.filter(v => !/-sample$/i.test((v.sku || '').trim()));
if (!sellables.length) { skipped++; skips.push({ pid, reason: 'no_sellable_variant', vendor: c.vendor, title: p.title }); continue; }
// pick the sellable variant currently at the $4.25 leak (or <= 4.25)
const leak = sellables.find(v => parseFloat(v.price) <= 4.25);
if (!leak) { skipped++; skips.push({ pid, reason: `sellable_already_priced ${sellables.map(v => v.price).join('/')}`, vendor: c.vendor, title: p.title }); continue; }
const newPrice = Number(c.final_price).toFixed(2);
if (!(parseFloat(newPrice) > 4.25)) { skipped++; skips.push({ pid, reason: `bad_price ${newPrice}`, vendor: c.vendor }); continue; }
const plan = { pid, vendor: c.vendor, title: p.title, variant_id: leak.id, sku: leak.sku, old_price: leak.price, new_price: newPrice, basis: c.price_basis };
if (!LIVE) {
console.log(`WOULD REPRICE ${pid} (${c.vendor}) ${leak.sku} $${leak.price} -> $${newPrice}`);
repriced++; appendJsonl(RUNLOG, { ts: new Date().toISOString(), mode: 'dry', action: 'would_reprice', ...plan });
continue;
}
const r = await shopify(`/variants/${leak.id}.json`, 'PUT', { variant: { id: leak.id, price: newPrice } });
if (r.status === 200) {
repriced++;
appendJsonl(RUNLOG, { ts: new Date().toISOString(), mode: 'live', action: 'repriced', ...plan });
appendJsonl(LEDGER, { ts: new Date().toISOString(), agent: 'claude-run-10456', ticket: 'TK-10456',
action: `reprice ${c.vendor} ${leak.sku} $${leak.price}->$${newPrice} (real sourced price; existing -Roll variant)`,
blast_radius: 1, undo_cmd: `PUT variants/${leak.id} price=${leak.price}`, verify: `GET product ${pid} sellable variant $${newPrice}` });
console.log(`REPRICED ${pid} (${c.vendor}) ${leak.sku} -> $${newPrice}`);
} else { skipped++; skips.push({ pid, reason: `put_failed_${r.status}`, vendor: c.vendor, title: p.title }); }
}
fs.writeFileSync(`${process.cwd()}/reprice-skips.json`, JSON.stringify(skips, null, 1));
console.log(`\n=== ${LIVE ? 'REPRICED' : 'WOULD REPRICE'}: ${repriced} SKIPPED: ${skipped} ===`);