← back to Dw Yolo Loop

scripts/fix-schumacher-double-wallcovering.js

53 lines

#!/usr/bin/env node
/* Fix Schumacher product titles with a redundant doubled "Wallcoverings Wallcovering(s)".
   "{name} Wallcoverings Wallcovering | Schumacher" → "{name} Wallcovering | Schumacher".
   DRY RUN by default; --apply writes live (PUT title only). Read-modify is title-only, idempotent. */
const STORE = 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = process.env.T;
const APPLY = process.argv.includes('--apply');
const sleep = ms => new Promise(r => setTimeout(r, ms));

// collapse any run of Wallcovering(s) Wallcovering(s)[ ...] → single "Wallcovering"
function fixTitle(t) {
  let f = t.replace(/\bWallcoverings?\s+Wallcoverings?\b/gi, 'Wallcovering');
  f = f.replace(/\s{2,}/g, ' ').replace(/\s+\|/g, ' |').trim();
  return f;
}

async function api(p, opts = {}, tries = 5) {
  for (let i = 0; i < tries; i++) {
    const r = await fetch(`https://${STORE}/admin/api/2024-10${p}`, { ...opts, headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', ...(opts.headers || {}) } });
    if (r.status === 429 || r.status >= 500) { await sleep(1500 * (i + 1)); continue; }
    await sleep(90); return r;
  }
  throw new Error('api fail ' + p);
}
async function getAll() {
  let url = `/products.json?vendor=Schumacher&limit=250&fields=id,title,status`;
  const out = [];
  while (url) {
    const r = await api(url); const link = r.headers.get('Link') || '';
    out.push(...((await r.json()).products || []));
    const m = link.split(',').find(s => s.includes('rel="next"'));
    url = m ? m.slice(m.indexOf('<') + 1, m.indexOf('>')).replace(/^https:\/\/[^/]+\/admin\/api\/[^/]+/, '') : null;
  }
  return out;
}

(async () => {
  const all = await getAll();
  const todo = all.map(p => ({ ...p, fixed: fixTitle(p.title) })).filter(p => p.fixed !== p.title);
  console.log(`Schumacher: ${all.length} | titles to fix: ${todo.length} | mode: ${APPLY ? 'APPLY' : 'DRY RUN'}\n`);
  todo.slice(0, 10).forEach(p => console.log(`  ${p.title}\n   → ${p.fixed}\n`));
  if (!APPLY) { console.log(`DRY RUN — re-run with --apply to write ${todo.length} titles.`); return; }
  let ok = 0, fail = 0;
  for (const p of todo) {
    try {
      const r = await api(`/products/${p.id}.json`, { method: 'PUT', body: JSON.stringify({ product: { id: p.id, title: p.fixed } }) });
      if (r.ok) { ok++; if (ok % 50 === 0) console.log(`  …${ok}/${todo.length}`); }
      else { fail++; console.log(`  FAIL ${p.id} ${r.status}`); }
    } catch (e) { fail++; console.log(`  ERR ${p.id} ${e.message}`); }
  }
  console.log(`\nDONE — ${ok} fixed, ${fail} failed.`);
})().catch(e => { console.error(e); process.exit(1); });