← back to Dw Signup Fulfillment

verification/tk10836/verify-app-install-order.js

41 lines

#!/usr/bin/env node
// TK-11366: AppInstallation has no createdAt. BUT Shopify resource IDs are broadly monotonic in
// creation time, so sorting installations by numeric ID recovers INSTALL ORDER for free.
// Calibrate against known-age anchors, then look at what was installed most recently.
const fs=require('fs'),os=require('os');
for (const line of fs.readFileSync(os.homedir()+'/Projects/secrets-manager/.env','utf8').split('\n')) {
  const m=line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)$/); if(!m) continue;
  let v=m[2].trim().replace(/^['"]|['"]$/g,''); if(!process.env[m[1]]) process.env[m[1]]=v;
}
const STORE=process.env.SHOPIFY_STORE_DOMAIN, TOKEN=process.env.SHOPIFY_FULL_ACCESS_TOKEN, API='2024-10';
async function gql(q,v={}){const r=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',
  headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});
  const j=await r.json(); if(j.errors)console.error('ERR',JSON.stringify(j.errors).slice(0,300)); return j.data;}
(async()=>{
  let c=null, apps=[];
  do{
    const r=await gql(`query($c:String){ appInstallations(first:50, after:$c){
      pageInfo{hasNextPage endCursor}
      nodes{ id app{ title handle developerName } accessScopes{ handle } } } }`,{c});
    if(!r?.appInstallations) break;
    apps.push(...r.appInstallations.nodes);
    c=r.appInstallations.pageInfo.hasNextPage?r.appInstallations.pageInfo.endCursor:null;
  }while(c);
  const SHIP=/shipping|delivery|checkout|cart_transform|rate/i;
  const rows=apps.map(a=>({
    id: Number(a.id.split('/').pop()),
    title: a.app?.title||'?', dev: a.app?.developerName||'?',
    ship: (a.accessScopes||[]).map(s=>s.handle).filter(s=>SHIP.test(s))
  })).sort((x,y)=>y.id-x.id);
  console.log(`### ${rows.length} installations, sorted NEWEST-FIRST by installation id ###\n`);
  console.log('--- 20 MOST RECENTLY INSTALLED ---');
  for(const r of rows.slice(0,20))
    console.log(`  ${r.id}  ${r.ship.length?'*SHIP*':'      '}  ${r.title}  (${r.dev})${r.ship.length?'\n            scopes: '+r.ship.join(', '):''}`);
  console.log('\n--- ALL apps holding shipping/delivery/checkout/cart-transform scopes, newest first ---');
  for(const r of rows.filter(r=>r.ship.length))
    console.log(`  ${r.id}  ${r.title} (${r.dev})\n            ${r.ship.join(', ')}`);
  console.log('\n--- 5 OLDEST (calibration anchors) ---');
  for(const r of rows.slice(-5)) console.log(`  ${r.id}  ${r.title}`);
  fs.writeFileSync('verification/tk10836/app-install-order.json',JSON.stringify(rows,null,2));
})();