← 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
A scripts/gmc-feed-guard/export-leak-list.mjs
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 →