← back to Tk10640 Redirect Prune
target-liveness-probe.mjs
28 lines
// TK-10653 — random target-liveness probe: of unique /products/ redirect TARGETS, how many
// are a live ACTIVE+published landing vs a dead/archived/draft 404. A dead target = the
// redirect never usefully lands anyone (broken if it fires, wasteful if inert) = prune cand.
import { readFileSync, writeFileSync } 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<10;a++){try{const res=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 tx=await res.text();let j;try{j=JSON.parse(tx);}catch{await sleep(1500*(a+1));continue;}if(j.errors){if(/THROTTLED/i.test(JSON.stringify(j.errors))){await sleep(1500*(a+1));continue;}throw new Error(JSON.stringify(j.errors));}return j.data;}catch(e){if(a<9){await sleep(1500*(a+1));continue;}throw e;}}}
const N=Number((process.argv.find(a=>a.startsWith('--n='))||'').split('=')[1]||1000);
const targets=readFileSync('data/unique-product-targets.txt','utf8').trim().split('\n');
for(let i=targets.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[targets[i],targets[j]]=[targets[j],targets[i]];}
let A=0,AR=0,DR=0,DE=0; const deadT=[];
for(let i=0;i<N&&i<targets.length;i++){ const h=targets[i].replace(/^\/products\//,'').split('?')[0].split('#')[0];
const d=await gql(`query($h:String!){productByHandle(handle:$h){status onlineStoreUrl}}`,{h});
const p=d.productByHandle; const st=p?p.status:'DEAD';
if(st==='ACTIVE'&&p.onlineStoreUrl)A++; else { if(st==='ARCHIVED')AR++;else if(st==='DRAFT')DR++;else DE++; deadT.push({target:targets[i],status:st}); }
if(i%250===249) process.stderr.write(` probed ${i+1}/${N}: live=${A} dead-landing=${deadT.length}\n`);
}
const n=Math.min(N,targets.length);
const deadRate=(AR+DR+DE)/n;
writeFileSync('data/dead-targets-sample.json', JSON.stringify(deadT.slice(0,200),null,2));
console.log(JSON.stringify({unique_product_targets:targets.length, probed:n,
live_active_published:A, archived_target:AR, draft_target:DR, dead404_target:DE,
dead_landing_rate:+deadRate.toFixed(3),
est_dead_unique_targets:Math.round(deadRate*targets.length),
note:'redirects to a dead target never usefully land anyone; broken if source also dead (fires->404)'},null,2));