[object Object]

← back to Dw Yolo Loop

feat: read-only GMC full-leak-list exporter (TK-10862 session)

25ac4732e3b805081aeea0760de61dcc9d3ca67f · 2026-08-26 09:19:48 -0700 · Steve

Files touched

Diff

commit 25ac4732e3b805081aeea0760de61dcc9d3ca67f
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 26 09:19:48 2026 -0700

    feat: read-only GMC full-leak-list exporter (TK-10862 session)
---
 scripts/gmc-feed-guard/export-leak-list.mjs | 43 +++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/scripts/gmc-feed-guard/export-leak-list.mjs b/scripts/gmc-feed-guard/export-leak-list.mjs
new file mode 100644
index 0000000..4a8a474
--- /dev/null
+++ b/scripts/gmc-feed-guard/export-leak-list.mjs
@@ -0,0 +1,43 @@
+// export-leak-list.mjs — READ-ONLY. Same scan as gmc-feed-guard but captures the
+// FULL offender list (not capped at 20) to a CSV for the feed-exclusion remediation.
+// Emits: handle, vendor, productId, minPrice, maxPrice, variantCount. $0, no writes to Shopify.
+import fs from 'fs';
+const T=(fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1];
+const API='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
+const GOOG='gid://shopify/Publication/29646651457';
+const OUT=process.env.HOME+'/Projects/dw-yolo-loop/scripts/gmc-feed-guard/data/leak-list.csv';
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+async function gql(q,v){
+  for(let a=0;a<7;a++){
+    try{ const r=await fetch(API,{method:'POST',headers:{'X-Shopify-Access-Token':T,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v||{}}),signal:AbortSignal.timeout(20000)});
+      if(r.status===429||r.status>=500){await sleep(2500*(a+1));continue;}
+      const txt=await r.text(); let j; try{j=JSON.parse(txt);}catch(e){await sleep(2500*(a+1));continue;}
+      if(j.errors&&JSON.stringify(j.errors).match(/THROTTLED/)){await sleep(2500*(a+1));continue;}
+      return j;
+    }catch(e){ await sleep(2000*(a+1)); }
+  }
+  throw new Error('exhausted retries');
+}
+let cur=null,scanned=0,leaks=0; const rows=[['handle','vendor','productId','minPrice','maxPrice','variantCount']]; const byVendor={}; const t0=Date.now();
+do{
+  const d=await gql(`query($c:String){products(first:150,after:$c,query:"status:active"){pageInfo{hasNextPage endCursor} nodes{id handle vendor onGoogle:publishedOnPublication(publicationId:"${GOOG}") variants(first:60){nodes{price}}}}}`,{c:cur});
+  const pg=d.data.products;
+  for(const p of pg.nodes){ scanned++;
+    const prices=p.variants.nodes.map(v=>parseFloat(v.price)).filter(x=>!isNaN(x));
+    if(!prices.length) continue;
+    const mn=Math.min(...prices), mx=Math.max(...prices);
+    if(mn<=4.25 && p.onGoogle && mx>4.25){
+      leaks++;
+      rows.push([p.handle, (p.vendor||'').replace(/,/g,' '), p.id, mn, mx, prices.length]);
+      byVendor[p.vendor||'(none)']=(byVendor[p.vendor||'(none)']||0)+1;
+    }
+  }
+  process.stderr.write(`\r  scanned=${scanned} leaks=${leaks}`);
+  cur=pg.pageInfo.hasNextPage?pg.pageInfo.endCursor:null;
+}while(cur);
+fs.writeFileSync(OUT, rows.map(r=>r.join(',')).join('\n'));
+process.stderr.write('\n');
+console.log(`[export-leak-list] scanned=${scanned} leaks=${leaks} elapsed_s=${((Date.now()-t0)/1000)|0}`);
+console.log('CSV:',OUT);
+console.log('top leaking vendors:');
+Object.entries(byVendor).sort((a,b)=>b[1]-a[1]).slice(0,15).forEach(([v,n])=>console.log(`  ${n.toString().padStart(5)}  ${v}`));

← 28e22d5 gmc: staged GID-scoped exclude for the 9 true $4.25 leaks (T  ·  back to Dw Yolo Loop  ·  TK-11055: lib/shopify.mjs prefer SHOPIFY_FULL_ACCESS_TOKEN f 9270cd2 →