← back to Dw Yolo Loop
scripts/collection-orphan-sweep/low-membership-probe.mjs
44 lines
// low-membership-probe — READ-ONLY, $0. Cycle 86 follow-up to collection-orphan-sweep.
// The main sweep found 0 true orphans (100% in >=1 collection). Two skeptical caveats to close:
// (1) the 47 products in <=2 collections — is their membership a CUSTOMER-FACING collection, or
// ONLY the internal 'best-seller-internal-use' (which would make them effectively browse-dark)?
// (2) is 'best-seller-internal-use' (90% membership) PUBLISHED to the Online Store channel?
// An "internal use" collection visible to shoppers is a leak/oddity worth flagging.
import fs from 'fs';
const ENV=fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8');
const TOK=((ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1]||'').replace(/['"\r]/g,'').trim();
const GQL='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
const PJ=/phillip[- ]?jeffries/i;
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
async function gql(q,v){for(let a=0;a<6;a++){const r=await fetch(GQL,{method:'POST',headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});if(r.status===429||r.status>=500){await sleep(2000*(a+1));continue;}const j=await r.json();if(j.errors&&JSON.stringify(j.errors).includes('Throttled')){await sleep(2500*(a+1));continue;}return j;}return null;}
// (2) FIRST: best-seller-internal-use publication status + a couple of customer collections for contrast
const CQ=`{ collections(first:60, query:"title:best-seller OR handle:best-seller-internal-use OR handle:patterns OR handle:contemporary"){ edges{ node{ handle title productsCount{count} publishedOnCurrentPublication } } } }`;
const cj=await gql(CQ,{});
console.log(`=== (2) collection publication status (publishedOnCurrentPublication = visible on current channel) ===`);
for(const e of (cj?.data?.collections?.edges||[])){ const n=e.node; console.log(` ${n.handle} | "${n.title}" | products=${n.productsCount?.count} | publishedOnCurrentPublication=${n.publishedOnCurrentPublication}`); }
// (1) SECOND: re-scan active products, record handles for those with <=2 collections (the low-membership tail)
const Q=`query($cursor:String){ products(first:80, after:$cursor, query:"status:active"){ pageInfo{hasNextPage endCursor}
edges{node{ title vendor collections(first:11){ edges{ node{ handle } } } } } } }`;
let cursor=null,pages=0,scanned=0,complete=true;
const lowMembers=[]; const onlyInternal=[];
const INTERNAL=/internal/i;
while(true){ const j=await gql(Q,{cursor}); if(!j?.data){complete=false;break;} const c=j.data.products;
for(const e of c.edges){ const n=e.node; if(PJ.test(n.vendor||''))continue; scanned++;
const handles=(n.collections?.edges||[]).map(x=>x.node.handle);
if(handles.length<=2){ lowMembers.push({t:(n.title||'').slice(0,42),vendor:n.vendor,handles}); }
// effectively-orphaned-from-customer-browse = every collection it is in is an 'internal' one
if(handles.length>0 && handles.every(h=>INTERNAL.test(h))){ onlyInternal.push({t:(n.title||'').slice(0,42),vendor:n.vendor,handles}); }
}
pages++; if(!c.pageInfo.hasNextPage)break; cursor=c.pageInfo.endCursor;
const cost=j.extensions?.cost?.throttleStatus; if(cost&&cost.currentlyAvailable<500) await sleep(1500); else await sleep(250);
}
console.log(`\n=== (1) low-membership tail (<=2 collections) — active scanned ${scanned} (complete=${complete}) ===`);
console.log(`products in <=2 collections: ${lowMembers.length}`);
lowMembers.slice(0,50).forEach(x=>console.log(` "${x.t}" [${x.vendor}] -> [${x.handles.join(', ')}]`));
console.log(`\n=== effectively browse-dark (ALL their collections are 'internal') : ${onlyInternal.length} ===`);
onlyInternal.slice(0,50).forEach(x=>console.log(` "${x.t}" [${x.vendor}] -> [${x.handles.join(', ')}]`));
fs.writeFileSync('/tmp/low-membership-probe.json',JSON.stringify({ts:new Date().toISOString(),scanned,complete,lowMembers,onlyInternal,collPub:(cj?.data?.collections?.edges||[]).map(e=>e.node)},null,2));
console.log(`\nwrote /tmp/low-membership-probe.json`);