← back to Council 0809 Builds
rolls-ar-deeplink/scan-metafield-coverage.js
46 lines
// Live Shopify Admin-API coverage scan for Council #5 (read-only).
// Tallies, across ACTIVE products, how many carry a PARSEABLE custom.width
// and what repeat metafields exist. $0 (Admin API is unmetered).
const fs = require('fs');
const env = fs.readFileSync(require('os').homedir()+'/Projects/secrets-manager/.env','utf8');
const g = k => (env.match(new RegExp('^'+k+'=(.*)$','m'))||[])[1];
const TOKEN = g('SHOPIFY_ADMIN_TOKEN'), STORE = g('SHOPIFY_STORE');
const URL = `https://${STORE}/admin/api/2024-10/graphql.json`;
const numish = v => { if(v==null) return false; const n=parseFloat(String(v).replace(/[^0-9.]/g,'')); return Number.isFinite(n)&&n>0; };
const Q = `query($c:String){ products(first:200, after:$c, query:"status:active"){
pageInfo{hasNextPage endCursor}
nodes{ id productType
width: metafield(namespace:"custom", key:"width"){value}
prep: metafield(namespace:"custom", key:"pattern_repeat"){value}
rep: metafield(namespace:"custom", key:"repeat"){value}
prnum: metafield(namespace:"custom", key:"pattern_repeat_number"){value}
}}}`;
(async()=>{
let cur=null, more=true, page=0;
const t={total:0, width_ok:0, width_any:0, prep_any:0, prep_numish:0, rep_numish:0, prnum_any:0, width_and_repeatnum:0, byType:{}};
const CAP = parseInt(process.env.CAP||'99',10);
while(more && page<CAP){
const r=await fetch(URL,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:Q,variables:{c:cur}})});
const j=await r.json();
if(j.errors){console.error(JSON.stringify(j.errors).slice(0,300)); break;}
const d=j.data.products;
for(const n of d.nodes){
t.total++;
const pt=(n.productType||'(none)');
t.byType[pt]=(t.byType[pt]||0)+1;
if(n.width){t.width_any++; if(numish(n.width.value)) t.width_ok++;}
if(n.prep){t.prep_any++; if(numish(n.prep.value)) t.prep_numish++;}
if(n.rep && numish(n.rep.value)) t.rep_numish++;
if(n.prnum){t.prnum_any++;}
const hasWidth = n.width && numish(n.width.value);
const hasRepeatNum = (n.prep&&numish(n.prep.value))||(n.rep&&numish(n.rep.value))||(n.prnum&&numish(n.prnum.value));
if(hasWidth && hasRepeatNum) t.width_and_repeatnum++;
}
cur=d.pageInfo.endCursor; more=d.pageInfo.hasNextPage; page++;
if(page%10===0) console.error(`...page ${page}, scanned ${t.total}`);
await new Promise(x=>setTimeout(x,120));
}
t.pages=page; t.completed=!more;
console.log(JSON.stringify(t,null,2));
})().catch(e=>{console.error(e.message);process.exit(1);});