[object Object]

← back to Designer Wallcoverings

Daisy Bennett Vol 2 live-store fixes: strip related-carousel images (355 removed), drop-DW SKU rename (GRS-/SIS-/etc, unique), 8yd-bolt packaged metafield

3b2621b4c6b847c1a01c114dbc2ea8ad89e6cd5a · 2026-07-06 13:14:19 -0700 · Steve

Files touched

Diff

commit 3b2621b4c6b847c1a01c114dbc2ea8ad89e6cd5a
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 6 13:14:19 2026 -0700

    Daisy Bennett Vol 2 live-store fixes: strip related-carousel images (355 removed), drop-DW SKU rename (GRS-/SIS-/etc, unique), 8yd-bolt packaged metafield
---
 .../wallquest-refresh/add-packaged-metafield.cjs   | 48 ++++++++++++++++
 scripts/wallquest-refresh/fix-images-v2.cjs        | 67 ++++++++++++++++++++++
 scripts/wallquest-refresh/rename-drop-dw-v2.cjs    | 55 ++++++++++++++++++
 3 files changed, 170 insertions(+)

diff --git a/scripts/wallquest-refresh/add-packaged-metafield.cjs b/scripts/wallquest-refresh/add-packaged-metafield.cjs
new file mode 100644
index 00000000..4f4e0c6a
--- /dev/null
+++ b/scripts/wallquest-refresh/add-packaged-metafield.cjs
@@ -0,0 +1,48 @@
+// Steve directive: add "Packaged in 8 yard bolts only" to the metafields of the new WallQuest
+// real-textile products from today. Sets global.packaged. Additive/non-destructive.
+// SCOPE env: 'v2' (mfr ~ ^DB[0-9], default) | 'v1' (mfr ~ ^BX) | 'cr' (dw_sku ~ ^(GRS|SIS|RAF|CORK|JUT)- with CL mfr).
+const https = require('https');
+const { Client } = require('pg');
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+const CONN = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
+const SCOPE = process.env.SCOPE || 'v2';
+const VALUE = 'Packaged in 8 yard bolts only';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+const WHERE = {
+  v2: "mfr_sku ~ '^DB[0-9]' AND shopify_product_id IS NOT NULL",
+  v1: "mfr_sku ~ '^BX' AND shopify_product_id IS NOT NULL",
+  cr: "mfr_sku ~ '^CL[0-9]' AND shopify_product_id IS NOT NULL",
+};
+
+function rest(method, path, body) {
+  return new Promise((resolve, reject) => {
+    const data = body ? JSON.stringify(body) : null;
+    const req = https.request({ hostname: STORE, path: `/admin/api/${API}/${path}`, method,
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}) }, timeout: 30000 },
+      res => { let d = ''; res.on('data', c => d += c); res.on('end', () => { let j; try { j = d ? JSON.parse(d) : {}; } catch { j = {}; }
+        (res.statusCode >= 200 && res.statusCode < 300) ? resolve(j) : reject(new Error(`HTTP ${res.statusCode} ${JSON.stringify(j).slice(0,160)}`)); }); });
+    req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
+    if (data) req.write(data); req.end();
+  });
+}
+
+(async () => {
+  const db = new Client({ connectionString: CONN }); await db.connect();
+  const { rows } = await db.query(`SELECT dw_sku, shopify_product_id FROM dw_sku_registry WHERE ${WHERE[SCOPE]} ORDER BY dw_sku`);
+  console.log(`Adding global.packaged="${VALUE}" to ${rows.length} products (scope=${SCOPE})…\n`);
+  let ok = 0, err = 0;
+  for (const r of rows) {
+    try {
+      await rest('POST', `products/${r.shopify_product_id}/metafields.json`,
+        { metafield: { namespace: 'global', key: 'packaged', value: VALUE, type: 'single_line_text_field' } });
+      ok++;
+      await sleep(150);
+    } catch (e) { console.log(`  ❌ ${r.dw_sku}: ${String(e).slice(0,120)}`); err++; }
+  }
+  console.log(`DONE packaged metafield: ok=${ok} err=${err} (scope=${SCOPE})`);
+  await db.end();
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });
diff --git a/scripts/wallquest-refresh/fix-images-v2.cjs b/scripts/wallquest-refresh/fix-images-v2.cjs
new file mode 100644
index 00000000..72b194d5
--- /dev/null
+++ b/scripts/wallquest-refresh/fix-images-v2.cjs
@@ -0,0 +1,67 @@
+// FIX: the V2 scrape swept in WallQuest "related products" carousel thumbnails (_400.jpg) so each
+// live product shows other colorways/patterns. Keep ONLY images whose filename encodes THIS
+// product's own design + colorway; delete the rest. Scope: V2 (mfr DB%). Idempotent. Keeps >=1.
+const fs = require('fs');
+const https = require('https');
+const { Client } = require('pg');
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+const CONN = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
+const LIMIT = parseInt(process.env.LIMIT || '999', 10);
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const slug = s => (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '');
+
+const norm = JSON.parse(fs.readFileSync('/tmp/wq-daisy-bennett-v2-normalized.json', 'utf8'));
+const byMfr = {}; norm.forEach(r => { byMfr[r.mfr_sku] = r; });
+
+function rest(method, path) {
+  return new Promise((resolve, reject) => {
+    const req = https.request({ hostname: STORE, path: `/admin/api/${API}/${path}`, method,
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, timeout: 30000 },
+      res => { let d = ''; res.on('data', c => d += c); res.on('end', () => { let j; try { j = d ? JSON.parse(d) : {}; } catch { j = {}; }
+        (res.statusCode >= 200 && res.statusCode < 300) ? resolve(j) : reject(new Error(`HTTP ${res.statusCode}`)); }); });
+    req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
+    req.end();
+  });
+}
+// keep = filename contains colorway slug AND (design slug OR every design word)
+function isOwn(fname, p) {
+  const f = slug(fname);
+  const c = slug(p.color), d = slug(p.pattern);
+  const words = (p.pattern || '').toLowerCase().split(/\s+/).map(slug).filter(Boolean);
+  const colorOk = c && f.includes(c);
+  const designOk = (d && f.includes(d)) || (words.length && words.every(w => f.includes(w)));
+  return colorOk && designOk;
+}
+
+(async () => {
+  const db = new Client({ connectionString: CONN }); await db.connect();
+  const ONLY = process.env.ONLY || null; // restrict to a single mfr_sku for scoped fixes
+  const { rows } = await db.query(
+    ONLY
+      ? "SELECT dw_sku, mfr_sku, shopify_product_id FROM dw_sku_registry WHERE mfr_sku=$1 AND shopify_product_id IS NOT NULL"
+      : "SELECT dw_sku, mfr_sku, shopify_product_id FROM dw_sku_registry WHERE mfr_sku LIKE 'DB%' AND shopify_product_id IS NOT NULL ORDER BY dw_sku",
+    ONLY ? [ONLY] : []);
+  const todo = rows.slice(0, LIMIT);
+  console.log(`Cleaning images on ${todo.length} V2 products…\n`);
+  let fixed = 0, del = 0, err = 0, clean = 0;
+  for (const r of todo) {
+    const p = byMfr[r.mfr_sku];
+    if (!p) { console.log(`  ? ${r.dw_sku} no scrape row`); continue; }
+    try {
+      const ij = await rest('GET', `products/${r.shopify_product_id}/images.json`);
+      const imgs = ij.images || [];
+      const keep = imgs.filter(im => isOwn(im.src.split('/').pop().split('?')[0], p));
+      const kill = imgs.filter(im => !keep.includes(im));
+      if (!keep.length) { console.log(`  ! ${r.dw_sku} (${p.pattern}/${p.color}) 0 own-images matched — LEAVING as-is for review`); continue; }
+      if (!kill.length) { clean++; continue; }
+      for (const im of kill) { try { await rest('DELETE', `products/${r.shopify_product_id}/images/${im.id}.json`); del++; } catch {} await sleep(120); }
+      console.log(`  ✅ ${r.dw_sku} ${p.pattern} ${p.color}: kept ${keep.length}, removed ${kill.length}`);
+      fixed++; await sleep(200);
+    } catch (e) { console.log(`  ❌ ${r.dw_sku}: ${String(e).slice(0,120)}`); err++; }
+  }
+  console.log(`\nIMAGE FIX DONE: fixed=${fixed} already-clean=${clean} images-removed=${del} err=${err}`);
+  await db.end();
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });
diff --git a/scripts/wallquest-refresh/rename-drop-dw-v2.cjs b/scripts/wallquest-refresh/rename-drop-dw-v2.cjs
new file mode 100644
index 00000000..3e003a06
--- /dev/null
+++ b/scripts/wallquest-refresh/rename-drop-dw-v2.cjs
@@ -0,0 +1,55 @@
+// Steve directive: drop the "DW" from Daisy Bennett Vol 2 SKUs (DWGRS-40001 -> GRS-40001),
+// matching the Carl Robinson convention. Renames BOTH Standard + Sample variant SKUs on the
+// live products AND dw_sku_registry. Verified: no collisions, all remain unique.
+// Scope: V2 only via mfr ~ '^DB[0-9]' (excludes the Scalamandre DBLC... false match). Idempotent.
+const https = require('https');
+const { Client } = require('pg');
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+const CONN = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+function rest(method, path, body) {
+  return new Promise((resolve, reject) => {
+    const data = body ? JSON.stringify(body) : null;
+    const req = https.request({ hostname: STORE, path: `/admin/api/${API}/${path}`, method,
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}) }, timeout: 30000 },
+      res => { let d = ''; res.on('data', c => d += c); res.on('end', () => { let j; try { j = d ? JSON.parse(d) : {}; } catch { j = {}; }
+        (res.statusCode >= 200 && res.statusCode < 300) ? resolve(j) : reject(new Error(`HTTP ${res.statusCode} ${JSON.stringify(j).slice(0,160)}`)); }); });
+    req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
+    if (data) req.write(data); req.end();
+  });
+}
+
+(async () => {
+  const db = new Client({ connectionString: CONN }); await db.connect();
+  const { rows } = await db.query("SELECT dw_sku, mfr_sku, shopify_product_id FROM dw_sku_registry WHERE mfr_sku ~ '^DB[0-9]' AND shopify_product_id IS NOT NULL ORDER BY dw_sku");
+  console.log(`Renaming ${rows.length} V2 SKUs (drop DW)…\n`);
+  // pre-flight uniqueness check
+  const news = rows.map(r => r.dw_sku.replace(/^DW/, ''));
+  if (new Set(news).size !== news.length) { console.error('ABORT: dropped SKUs not unique among V2'); process.exit(1); }
+  let ok = 0, err = 0;
+  for (const r of rows) {
+    const oldSku = r.dw_sku, newSku = oldSku.replace(/^DW/, '');
+    if (newSku === oldSku) { continue; }
+    try {
+      const pj = await rest('GET', `products/${r.shopify_product_id}.json?fields=id,variants`);
+      for (const v of pj.product.variants) {
+        let ns = null;
+        if (v.sku === oldSku) ns = newSku;                      // Standard
+        else if (v.sku === `${oldSku}-Sample`) ns = `${newSku}-Sample`; // Sample
+        if (ns && ns !== v.sku) { await rest('PUT', `variants/${v.id}.json`, { variant: { id: v.id, sku: ns } }); await sleep(120); }
+      }
+      await db.query("UPDATE dw_sku_registry SET dw_sku=$1, vendor_prefix=$2, updated_at=now() WHERE dw_sku=$3 AND mfr_sku=$4",
+        [newSku, newSku.split('-')[0], oldSku, r.mfr_sku]);
+      console.log(`  ✅ ${oldSku} -> ${newSku}`);
+      ok++; await sleep(180);
+    } catch (e) { console.log(`  ❌ ${oldSku}: ${String(e).slice(0,140)}`); err++; }
+  }
+  // verify final uniqueness in registry for V2
+  const dup = await db.query("SELECT dw_sku, count(*) FROM dw_sku_registry WHERE mfr_sku ~ '^DB[0-9]' GROUP BY 1 HAVING count(*)>1");
+  console.log(`\nDONE drop-DW: renamed=${ok} err=${err}. duplicate SKUs in V2 registry: ${dup.rows.length}`);
+  await db.end();
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });

← bd1c4cee Localize Carl Robinson viewer (:64761): local images + real-  ·  back to Designer Wallcoverings  ·  5x sweep 1: suppress favicon 404 (inline empty favicon) in b c16dd080 →