[object Object]

← back to Designer Wallcoverings

Surgical scrub of Chesapeake vendor-leak tag from 26 live Shopify products (Versa 'Chesapeake' + Fentucci 'Borders By Chesapeake'); preserve Et Cie 'Chesapeake Bay' colorway

08f38d9855043487973c951fb81e5f426200bc80 · 2026-06-16 13:32:08 -0700 · SteveStudio2

Files touched

Diff

commit 08f38d9855043487973c951fb81e5f426200bc80
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Tue Jun 16 13:32:08 2026 -0700

    Surgical scrub of Chesapeake vendor-leak tag from 26 live Shopify products (Versa 'Chesapeake' + Fentucci 'Borders By Chesapeake'); preserve Et Cie 'Chesapeake Bay' colorway
---
 scripts/scrub-chesapeake-tag.js | 95 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 95 insertions(+)

diff --git a/scripts/scrub-chesapeake-tag.js b/scripts/scrub-chesapeake-tag.js
new file mode 100644
index 00000000..380cbfc5
--- /dev/null
+++ b/scripts/scrub-chesapeake-tag.js
@@ -0,0 +1,95 @@
+#!/usr/bin/env node
+/**
+ * scrub-chesapeake-tag.js — surgical removal of the upstream vendor-leak tag
+ * "Chesapeake" from LIVE Shopify products (Versa Wallcovering line is resold;
+ * "Chesapeake" is the manufacturer and must not leak on the storefront).
+ *
+ * Token-level only: removes EXACTLY the tag(s) whose lowercased value contains
+ * "chesapeake"; never touches any other tag, title, or body. Idempotent.
+ *
+ * Dry-run by default (enumerate + sample). Pass --apply to write.
+ *   node scrub-chesapeake-tag.js            # dry run
+ *   node scrub-chesapeake-tag.js --apply    # remove the tag live
+ */
+const https = require('https');
+
+const SHOPIFY_STORE = 'designer-laboratory-sandbox.myshopify.com';
+const SHOPIFY_TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const APPLY = process.argv.includes('--apply');
+const NEEDLE = 'chesapeake';
+const DELAY_MS = 350;
+
+if (!SHOPIFY_TOKEN) { console.error('FATAL: SHOPIFY_ADMIN_TOKEN not set'); process.exit(1); }
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+function gql(query, variables = {}) {
+  return new Promise((resolve, reject) => {
+    const body = JSON.stringify({ query, variables });
+    const req = https.request({
+      hostname: SHOPIFY_STORE, path: '/admin/api/2024-10/graphql.json', method: 'POST',
+      headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
+    }, res => {
+      let d = ''; res.on('data', c => d += c);
+      res.on('end', () => {
+        try {
+          const p = JSON.parse(d);
+          if (p.errors && JSON.stringify(p.errors).includes('Throttled')) return resolve({ throttled: true });
+          if (p.errors) return reject(new Error(JSON.stringify(p.errors)));
+          resolve(p);
+        } catch (e) { reject(new Error('parse: ' + d.slice(0, 200))); }
+      });
+    });
+    req.on('error', reject); req.write(body); req.end();
+  });
+}
+
+async function gqlRetry(q, v) { for (let i = 0; i < 6; i++) { const r = await gql(q, v); if (r.throttled) { await sleep(2000); continue; } return r; } throw new Error('throttled out'); }
+
+const SEARCH = `query($cursor:String){ products(first:100, after:$cursor, query:"tag:Chesapeake"){ pageInfo{hasNextPage endCursor} edges{ node{ id title status tags vendor } } } }`;
+const MUT = `mutation($id:ID!,$tags:[String!]!){ tagsRemove(id:$id, tags:$tags){ userErrors{field message} } }`;
+
+// SURGICAL leak rule (NOT a blind substring): "Chesapeake Bay" is a legitimate
+// colorway/place name on Et Cie designer murals — KEEP. Only these are leaks:
+//   • exact tag "Chesapeake" on vendor "Versa Designed Surfaces" (upstream mfr leak)
+//   • exact tag "Borders By Chesapeake" (any vendor — explicitly names the mfr)
+function leakTags(node) {
+  const out = [];
+  for (const t of (node.tags || [])) {
+    const tl = t.trim().toLowerCase();
+    if (tl === 'borders by chesapeake') out.push(t);
+    else if (tl === 'chesapeake' && node.vendor === 'Versa Designed Surfaces') out.push(t);
+  }
+  return out;
+}
+
+(async () => {
+  console.log(`scrub-chesapeake-tag — ${APPLY ? 'APPLY (live writes)' : 'DRY RUN'} — needle="${NEEDLE}"`);
+  let cursor = null, hasNext = true, all = [];
+  while (hasNext) {
+    const r = await gqlRetry(SEARCH, { cursor });
+    const conn = r.data.products;
+    for (const e of conn.edges) {
+      const hit = leakTags(e.node);
+      if (hit.length) all.push({ id: e.node.id, title: e.node.title, status: e.node.status, vendor: e.node.vendor, hitTags: hit });
+    }
+    hasNext = conn.pageInfo.hasNextPage; cursor = conn.pageInfo.endCursor;
+  }
+  console.log(`Found ${all.length} products carrying a Chesapeake tag.`);
+  const byStatus = all.reduce((a, p) => { a[p.status] = (a[p.status] || 0) + 1; return a; }, {});
+  console.log('  by status:', JSON.stringify(byStatus));
+  console.log('  sample:', all.slice(0, 8).map(p => `${p.title} [${p.hitTags.join('|')}]`).join('\n          '));
+
+  if (!APPLY) { console.log('\nDRY RUN — no writes. Re-run with --apply to remove.'); return; }
+
+  let ok = 0, fail = 0;
+  for (const p of all) {
+    try {
+      const r = await gqlRetry(MUT, { id: p.id, tags: p.hitTags });
+      const ue = r.data.tagsRemove.userErrors;
+      if (ue && ue.length) { fail++; console.log(`  ✗ ${p.title}: ${JSON.stringify(ue)}`); }
+      else { ok++; if (ok % 25 === 0) console.log(`  …${ok}/${all.length}`); }
+    } catch (e) { fail++; console.log(`  ✗ ${p.title}: ${e.message}`); }
+    await sleep(DELAY_MS);
+  }
+  console.log(`\nDONE — removed Chesapeake tag from ${ok} products, ${fail} failed.`);
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });

← 144c9df6 China Seas de-header: COMPLETE — 1810 products pushed (band  ·  back to Designer Wallcoverings  ·  gitignore scraper output (vendor-scrapers 4.5G) + large data 16b2e388 →