← back to Dw Image Dup Audit

twin-draft.js

70 lines

#!/usr/bin/env node
// Phase 2 per DTD verdict A (4/5, 2026-07-21): in each old/new twin pair, DRAFT the NEW
// WallQuest-real-named import; the OLD private-label listing stays ACTIVE.
// Reversible status writes only; ledgered to fix-ledger.jsonl.
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 })); }
    );
    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");

// WQ-real mill-name prefixes = the NEW-generation side of each pair (the drafted side).
const MILL_PREFIXES = [
  "Enya Sisal", "Meilin Grasscloth", "Meilian Raffia", "Anhe Abaca",
  "Huanzhu Leaf Stripe", "Pei Ling Lattice", "Yujin Cork", "Zinzhu Jute",
  "Malia ", "Playa Jute", "Roark Flax", "Cebu Hemp", "Bergamo Wheat",
  "Shantung Taupe", "Manila Viridian",
];

(async () => {
  const audit = JSON.parse(fs.readFileSync(__dirname + "/pr-dup-images-2026-07-21.json"));
  const targets = new Map(); // handle -> title
  for (const g of audit.crossline) {
    const titles = g.items.map(([t]) => t);
    // only act on groups that actually contain an old-generation twin (a non-mill-named partner)
    const hasOldTwin = g.items.some(([t]) => !MILL_PREFIXES.some((p) => t.startsWith(p)));
    if (!hasOldTwin) continue;
    for (const [t, h] of g.items) {
      if (MILL_PREFIXES.some((p) => t.startsWith(p))) targets.set(h, t);
    }
  }
  console.log("twin-draft targets:", targets.size);
  let drafted = 0, skipped = 0, failed = 0;
  for (const [handle, title] of targets) {
    const g = await reqRetry("GET", `${API}/products.json?handle=${handle}&fields=id,status,title`);
    await sleep(550);
    const p = (JSON.parse(g.body).products || [])[0];
    if (!p) { ledger({ act: "twin-draft", handle, ok: false, err: "not found" }); failed++; continue; }
    if (p.status !== "active") { ledger({ act: "twin-draft", handle, ok: true, skip: "not active" }); skipped++; continue; }
    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: "twin-draft", handle, id: p.id, title, ok });
    if (ok) { drafted++; console.log("twin-drafted", handle); } else { failed++; console.log("FAIL", handle, r.status); }
  }
  console.log(`\nTWIN-DRAFT DONE. drafted=${drafted} skipped=${skipped} failed=${failed}`);
})().catch((e) => { console.error("FATAL", e); process.exit(1); });