[object Object]

← back to Designer Wallcoverings

Guard cadence-import against {type,value} blob: unwrap row.material at parse so live importer can't leak JSON into custom.material + product description

ef5c99cb7c274af2ad3bcaffe947c8294c7f26bf · 2026-06-24 16:31:59 -0700 · Steve Abrams

Files touched

Diff

commit ef5c99cb7c274af2ad3bcaffe947c8294c7f26bf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jun 24 16:31:59 2026 -0700

    Guard cadence-import against {type,value} blob: unwrap row.material at parse so live importer can't leak JSON into custom.material + product description
---
 shopify/scripts/cadence/cadence-import.js | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/shopify/scripts/cadence/cadence-import.js b/shopify/scripts/cadence/cadence-import.js
index f505b4a8..4ca61306 100644
--- a/shopify/scripts/cadence/cadence-import.js
+++ b/shopify/scripts/cadence/cadence-import.js
@@ -114,6 +114,19 @@ function titleCase(s) {
   return ws.map((w,i) => tcWord(w, i===0, i===ws.length-1)).join(' ');
 }
 const esc = s => String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+// Defensive unwrap for double-encoded metafield values. If a catalog spec column
+// (e.g. material) holds a whole Shopify metafield object — {"type":...,"value":"X"} —
+// stored as its JSON string, writing it raw leaks the blob into BOTH custom.material
+// and the product description spec list on every new product. Return the inner value.
+// (DWQW/Malibu blob-leak root cause, 2026-06-24.)
+const mfVal = v => {
+  if (typeof v !== 'string') return v;
+  const s = v.trim();
+  if (s.startsWith('{') && s.includes('"value"')) {
+    try { const o = JSON.parse(s); if (o && typeof o.value === 'string') return o.value; } catch (_) {}
+  }
+  return v;
+};
 
 // ---- image-size guard (Steve 2026-06-12) ----
 // Shopify rejects images >20MB (INVALID_IMAGE_FILE_SIZE) → the product lands
@@ -294,7 +307,7 @@ function selectSkus(cfg, limit) {
     const out = psql(sql);
     if (!out) return [];
     const keys = [...STD, 'vendor_desc', ...(hasAllImg ? ['all_images'] : [])];
-    return out.split('\n').map(line => { const v=line.split('\t'); const o={}; keys.forEach((k,i)=>o[k]=v[i]); o.cost=0; o.sampleOnly=true; return o; });
+    return out.split('\n').map(line => { const v=line.split('\t'); const o={}; keys.forEach((k,i)=>o[k]=v[i]); o.material=mfVal(o.material); o.cost=0; o.sampleOnly=true; return o; });
   }
   const sql = `SELECT ${cols}, (${cfg.costExpr})::numeric AS cost${sellSel}${allImgSel}
     FROM ${cfg.table}
@@ -303,7 +316,7 @@ function selectSkus(cfg, limit) {
   const out = psql(sql);
   if (!out) return [];
   const keys = [...STD, 'cost', ...(cfg.sellExpr ? ['sell'] : []), ...(hasAllImg ? ['all_images'] : [])];
-  return out.split('\n').map(line => { const v=line.split('\t'); const o={}; keys.forEach((k,i)=>o[k]=v[i]); o.cost=parseFloat(o.cost); if(cfg.sellExpr) o.sell=parseFloat(o.sell); return o; });
+  return out.split('\n').map(line => { const v=line.split('\t'); const o={}; keys.forEach((k,i)=>o[k]=v[i]); o.material=mfVal(o.material); o.cost=parseFloat(o.cost); if(cfg.sellExpr) o.sell=parseFloat(o.sell); return o; });
 }
 
 // ---- SAMPLE-ONLY product input (Quadrille-house, no cost — Steve AUTHORIZED 2026-06-23) ----

← 98eea4a6 Harden backfill-to-catalog extractFromMetafields: unwrap {ty  ·  back to Designer Wallcoverings  ·  Repair tool: collapse newlines for single-line metafield typ e4ee0772 →