← back to Designer Wallcoverings
Scrub residual Newwall leak from metafields: global.Brand->real brand + delete junk custom.collection_name letter (74 products)
c03397290df083b0f07078adce83c11b3bdbe366 · 2026-06-26 10:24:50 -0700 · Steve
Files touched
A shopify/scripts/scrub-newwall-metafields.js
Diff
commit c03397290df083b0f07078adce83c11b3bdbe366
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 26 10:24:50 2026 -0700
Scrub residual Newwall leak from metafields: global.Brand->real brand + delete junk custom.collection_name letter (74 products)
---
shopify/scripts/scrub-newwall-metafields.js | 57 +++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/shopify/scripts/scrub-newwall-metafields.js b/shopify/scripts/scrub-newwall-metafields.js
new file mode 100644
index 00000000..34da4f14
--- /dev/null
+++ b/shopify/scripts/scrub-newwall-metafields.js
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+/**
+ * Scrub residual "Newwall" leak from metafields on the 74 remapped products:
+ * - global.Brand "Newwall" -> real per-SKU brand (Coordonné / Tres Tintas)
+ * - custom.collection_name single-letter junk (A/C/D/F = newwall.com browse nav) -> delete
+ * Targets the 74 by handle (vendor already fixed, so can't query vendor:Newwall).
+ * APPLY=1 to write. Brand map: /tmp/newwall_brand_map.tsv ; handles: /tmp/newwall_live.tsv
+ */
+const fs = require('path') && require('fs');
+const path = require('path');
+const ROOT = path.resolve(__dirname, '..', '..');
+const env = Object.fromEntries(fs.readFileSync(path.join(ROOT, '.env'), 'utf8').split('\n')
+ .filter(l => l.includes('=') && !l.trim().startsWith('#'))
+ .map(l => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()]; }));
+const S = (env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com').replace(/^https?:\/\//, '');
+const T = env.SHOPIFY_ADMIN_TOKEN;
+const APPLY = process.env.APPLY === '1';
+
+const brand = Object.fromEntries(fs.readFileSync('/tmp/newwall_brand_map.tsv', 'utf8').trim().split('\n')
+ .map(l => l.split('\t')).map(([k, v]) => [k.toUpperCase(), v]));
+const handles = fs.readFileSync('/tmp/newwall_live.tsv', 'utf8').trim().split('\n')
+ .map(l => l.split('\t')).map(([key, status, handle]) => ({ key: key.toUpperCase(), handle }));
+
+const gql = (q, v) => fetch(`https://${S}/admin/api/2024-01/graphql.json`,
+ { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': T }, body: JSON.stringify({ query: q, variables: v }) }).then(r => r.json());
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+(async () => {
+ let brandFixed = 0, collDeleted = 0, missing = 0;
+ for (const { key, handle } of handles) {
+ const b = brand[key];
+ if (!b) { missing++; console.log('SKIP no brand', handle); continue; }
+ const d = await gql(`query($h:String!){productByHandle(handle:$h){id metafields(first:60){edges{node{id namespace key value}}}}}`, { h: handle });
+ const p = d.data?.productByHandle;
+ if (!p) { console.log('SKIP not found', handle); continue; }
+ const mf = (p.metafields?.edges || []).map(e => e.node);
+ const brandMf = mf.find(m => m.namespace === 'global' && m.key === 'Brand');
+ const collMf = mf.find(m => m.namespace === 'custom' && m.key === 'collection_name');
+ const needBrand = brandMf && /newwall/i.test(brandMf.value);
+ const needColl = collMf && /^[A-Za-z]$/.test((collMf.value || '').trim());
+ if (!needBrand && !needColl) continue;
+ console.log(`${handle}: ${needBrand ? `Brand "${brandMf.value}"->"${b}" ` : ''}${needColl ? `del collection_name "${collMf.value}"` : ''}`);
+ if (APPLY) {
+ if (needBrand) {
+ const r = await gql(`mutation($m:[MetafieldsSetInput!]!){metafieldsSet(metafields:$m){userErrors{field message}}}`,
+ { m: [{ ownerId: p.id, namespace: 'global', key: 'Brand', type: 'single_line_text_field', value: b }] });
+ if ((r.data?.metafieldsSet?.userErrors || []).length) console.log(' !!brand', JSON.stringify(r.data.metafieldsSet.userErrors)); else brandFixed++;
+ }
+ if (needColl) {
+ const r = await gql(`mutation($id:ID!){metafieldDelete(input:{id:$id}){deletedId userErrors{field message}}}`, { id: collMf.id });
+ if ((r.data?.metafieldDelete?.userErrors || []).length) console.log(' !!coll', JSON.stringify(r.data.metafieldDelete.userErrors)); else collDeleted++;
+ }
+ await sleep(300);
+ }
+ }
+ console.log(`\n${APPLY ? 'APPLIED' : 'DRY RUN'} — Brand fixed: ${brandFixed}, collection_name deleted: ${collDeleted}, missing-brand: ${missing}`);
+})();
← 277d0d98 auto-save: 2026-06-26T10:14:48 (4 files) — pending-approval/
·
back to Designer Wallcoverings
·
Designtex: Sold Per Yard variant relabel + Bolt size (spec-t b1cc8522 →