← 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); });