← back to Tk10661 Product Seo

exact-scan.mjs

32 lines

// TK-10661 EXACT-SCAN (read-only): paginate ALL products, record those where the FIRST
// variant (position 1) is "Sample" = the leaking set. Capture full variant id+position list
// per target for rollback. Writes data/targets.jsonl (one product/line) + count summary.
import {readFileSync, writeFileSync, appendFileSync} from 'node:fs';
const txt=readFileSync(`${process.env.HOME}/Projects/secrets-manager/.env`,'utf8');
const env={}; for(const l of txt.split('\n')){const m=l.match(/^([A-Z0-9_]+)=(.*)$/); if(m) env[m[1]]=m[2].replace(/^["']|["']$/g,'');}
const STORE=env.SHOPIFY_STORE_DOMAIN, TOKEN=env.SHOPIFY_FULL_ACCESS_TOKEN, API='2024-10';
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
async function gql(q,v={}){for(let a=0;a<8;a++){try{const r=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOKEN},body:JSON.stringify({query:q,variables:v})});const j=await r.json();if(j.errors){if(/THROTTLED/i.test(JSON.stringify(j.errors))){await sleep(2000*(a+1));continue;}throw new Error(JSON.stringify(j.errors));}return j.data;}catch(e){if(a<7){await sleep(1500*(a+1));continue;}throw e;}}}
const OUT='data/targets.jsonl'; writeFileSync(OUT,'');
let cursor=null, scanned=0, targets=0, statusCount={};
while(true){
  const d=await gql(`query($c:String){ products(first:100, after:$c){ pageInfo{hasNextPage endCursor} nodes{ id status variants(first:15){ nodes{ id title position } } } } }`,{c:cursor});
  const buf=[];
  for(const p of d.products.nodes){ scanned++;
    const vs=p.variants.nodes.slice().sort((a,b)=>a.position-b.position);
    if(vs.length && /^sample$/i.test((vs[0].title||'').trim())){
      // target: first variant is Sample. Only reorder if there IS a non-sample variant to promote.
      const hasReal = vs.some(v=>!/^sample$/i.test((v.title||'').trim()));
      if(hasReal){ targets++; statusCount[p.status]=(statusCount[p.status]||0)+1;
        buf.push(JSON.stringify({id:p.id,status:p.status,variants:vs.map(v=>({id:v.id,title:v.title,position:v.position}))}));
      }
    }
  }
  if(buf.length) appendFileSync(OUT,buf.join('\n')+'\n');
  if(scanned%5000===0) process.stderr.write(`  scanned ${scanned}, targets ${targets}\n`);
  if(!d.products.pageInfo.hasNextPage) break;
  cursor=d.products.pageInfo.endCursor;
}
writeFileSync('data/scan-summary.json',JSON.stringify({scanned,targets,by_status:statusCount},null,2));
console.log(JSON.stringify({scanned,targets,by_status:statusCount},null,2));