← back to Hollywood Import

push-g1-shopify.mjs

52 lines

// Push Group-1 retail prices to the 62 live Shopify products. For each product, resolve the
// NON-sample ("Sold Per Yard") variant live and set its price = newhw. NEVER touch the $4.25
// Sample variant. DRY-RUN by default (no writes); pass --apply to PUT. Read token from canonical .env.
import fs from 'node:fs';
import { execSync } from 'node:child_process';

const APPLY = process.argv.includes('--apply');
const envTxt = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
const TOKEN = (envTxt.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1].trim();
const STORE = (envTxt.match(/^SHOPIFY_STORE=(.+)$/m) || [])[1].trim();
const API = `https://${STORE}/admin/api/2024-01`;
const H = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' };

const rows = fs.readFileSync('/tmp/g1_push.tsv', 'utf8').trim().split('\n').map(l => l.split('\t'));
const sleep = ms => new Promise(r => setTimeout(r, ms));
const log = [];
let changed = 0, noop = 0, skipped = 0, errors = 0;

for (const [pid, mfr, newhw, unit] of rows) {
  try {
    const r = await fetch(`${API}/products/${pid}.json?fields=id,title,variants`, { headers: H });
    if (!r.ok) { errors++; log.push({ pid, mfr, err: 'GET ' + r.status }); continue; }
    const p = (await r.json()).product;
    // yard variant = the one that is NOT the sample
    const sample = p.variants.find(v => /sample/i.test(v.sku || '') || /sample/i.test(v.title || ''));
    const yard = p.variants.find(v => v !== sample);
    if (!yard) { skipped++; log.push({ pid, title: p.title, note: 'no non-sample variant (sample-only)' }); continue; }
    const cur = parseFloat(yard.price);
    const next = parseFloat(newhw);
    const rec = { pid, title: p.title, mfr, yard_sku: yard.sku, yard_vid: yard.id,
      cur_price: yard.price, new_price: next.toFixed(2), unit,
      sample_price: sample ? sample.price : '(none)' };
    if (Math.abs(cur - next) < 0.005) { noop++; rec.action = 'noop'; log.push(rec); continue; }
    rec.action = APPLY ? 'UPDATED' : 'would-update';
    if (APPLY) {
      const u = await fetch(`${API}/variants/${yard.id}.json`, { method: 'PUT', headers: H,
        body: JSON.stringify({ variant: { id: yard.id, price: next.toFixed(2) } }) });
      if (!u.ok) { errors++; rec.action = 'ERR ' + u.status; log.push(rec); await sleep(600); continue; }
      const got = (await u.json()).variant?.price;
      rec.confirmed = got;
      if (parseFloat(got) !== next) { errors++; rec.action = 'MISMATCH'; }
      await sleep(550); // ~2/s, safe under Shopify REST bucket
    }
    changed++; log.push(rec);
  } catch (e) { errors++; log.push({ pid, mfr, err: String(e).slice(0, 80) }); }
}

fs.writeFileSync(new URL('push-g1-result.json', import.meta.url), JSON.stringify(log, null, 2));
console.log(`mode=${APPLY ? 'APPLY' : 'DRY-RUN'} | total=${rows.length} change=${changed} noop=${noop} skip=${skipped} err=${errors}`);
console.log('--- first 8 ---');
log.slice(0, 8).forEach(r => console.log('  ' + JSON.stringify(r)));