← back to Dw Image Dup Audit

rename-run.js

93 lines

#!/usr/bin/env node
// Private-label rename program 2026-07-22. Steve-approved name map; title-only renames
// (handles/URLs unchanged) + body_html scrub of old series names, plus dedup drafts of
// the "Natural Silhouettes" sample generation and the 5 nameless bare "Sisal" products.
// Ledger: fix-ledger.jsonl. Reversible: ledger stores old title per rename.
const https = require("https");
const fs = require("fs");
const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
const HOST = "designer-laboratory-sandbox.myshopify.com";
const API = "/admin/api/2024-10";
const LEDGER = __dirname + "/fix-ledger.jsonl";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function req(method, path, body) {
  return new Promise((res, rej) => {
    const data = body ? JSON.stringify(body) : null;
    const r = https.request({ host: HOST, path, method, headers: { "X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json", ...(data ? { "Content-Length": Buffer.byteLength(data) } : {}) } },
      (resp) => { let d = ""; resp.on("data", (c) => (d += c)); resp.on("end", () => res({ status: resp.statusCode, body: d, link: resp.headers.link || "" })); });
    r.on("error", rej); if (data) r.write(data); r.end();
  });
}
async function reqRetry(method, path, body) {
  for (let i = 0; i < 5; i++) { const r = await req(method, path, body); if (r.status === 429) { await sleep(2000); continue; } return r; }
  throw new Error("429 persisted " + path);
}
const ledger = (e) => fs.appendFileSync(LEDGER, JSON.stringify({ ts: new Date().toISOString(), ...e }) + "\n");

// series rename map: WQ-real name -> new Phillipe Romano private-label name
const RENAMES = [
  [/^Palais\b/, "Palermo"],
  [/^Shantung\b/, "Portofino"],
  [/^Bergamo\b/, "Bellano"],
  [/^Chess\b/, "Cremona"],
  [/^Manila\b/, "Messina"],
  [/^Cebu\b/, "Cefalu"],
  [/^Pure Elements Sisal\b/, "Salina Sisal"],
  [/^Specialty Grasscloths & Veneers\s+/, ""], // strip the WQ book prefix; inner pattern rename = follow-up batch
  [/^Navy Grey & White\b/, "Riviera Neutrals"],
];

(async () => {
  // 1) full active PR fetch
  let path = `${API}/products.json?vendor=${encodeURIComponent("Phillipe Romano")}&fields=id,title,handle,status,body_html&limit=250`;
  const all = [];
  while (path) {
    const { body, link } = await reqRetry("GET", path);
    all.push(...JSON.parse(body).products);
    const m = link.match(/<https:\/\/[^>]+(\/admin[^>]+)>; rel="next"/);
    path = m ? m[1] : null;
    await sleep(550);
  }
  const active = all.filter((p) => p.status === "active");
  console.log("active PR products:", active.length);

  // 2) dedup drafts: Natural Silhouettes sample generation + nameless bare Sisal
  let drafted = 0;
  const draftTargets = active.filter((p) =>
    /^Natural Silhouettes /.test(p.title) ||
    /^Sisal \| Phillipe Romano$/.test(p.title.trim())
  );
  console.log("dedup-draft targets:", draftTargets.length);
  for (const p of draftTargets) {
    const r = await reqRetry("PUT", `${API}/products/${p.id}.json`, { product: { id: p.id, status: "draft" } });
    await sleep(550);
    const ok = r.status === 200;
    ledger({ act: "ns-dedup-draft", handle: p.handle, id: p.id, title: p.title, ok });
    if (ok) { drafted++; console.log("drafted", p.handle); } else console.log("FAIL draft", p.handle, r.status);
  }

  // 3) renames (title + body_html scrub); skip anything we just drafted
  let renamed = 0;
  const draftedIds = new Set(draftTargets.map((p) => p.id));
  for (const p of active) {
    if (draftedIds.has(p.id)) continue;
    let newTitle = p.title;
    let oldSeries = null, newSeries = null;
    for (const [re, repl] of RENAMES) {
      if (re.test(newTitle)) { oldSeries = newTitle.match(re)[0].trim(); newTitle = newTitle.replace(re, repl).replace(/\s+/g, " ").trim(); newSeries = repl || "(book prefix stripped)"; break; }
    }
    if (newTitle === p.title) continue;
    const upd = { id: p.id, title: newTitle };
    if (oldSeries && newSeries && p.body_html && p.body_html.includes(oldSeries) && !newSeries.startsWith("(")) {
      upd.body_html = p.body_html.split(oldSeries).join(newSeries);
    }
    const r = await reqRetry("PUT", `${API}/products/${p.id}.json`, { product: upd });
    await sleep(550);
    const ok = r.status === 200;
    ledger({ act: "pl-rename", handle: p.handle, id: p.id, oldTitle: p.title, newTitle, bodyScrubbed: !!upd.body_html, ok });
    if (ok) { renamed++; console.log("renamed:", p.title.replace(" | Phillipe Romano", ""), "->", newTitle.replace(" | Phillipe Romano", "")); }
    else console.log("FAIL rename", p.handle, r.status);
  }
  console.log(`\nRENAME RUN DONE. dedup-drafted=${drafted} renamed=${renamed}`);
})().catch((e) => { console.error("FATAL", e); process.exit(1); });