← back to Dw Image Shrink
files-unused.js
27 lines
// Enumerate files Shopify itself marks UNREFERENCED (query: "used_in:none") — its
// own reference tracking across products, content, themes, metaobjects, checkout,
// email, everything. This is the AUTHORITATIVE safe-to-delete set. Writes
// unused-files.jsonl + prints total GB. READ-ONLY. Uses THEME token (read access).
import fs from 'fs';
import https from 'https';
const SHOP='designer-laboratory-sandbox.myshopify.com';
const TOK=fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8').split('\n').find(l=>l.startsWith('SHOPIFY_THEME_TOKEN=')).split('=').slice(1).join('=').replace(/["' ]/g,'');
const DIR='/Users/macstudio3/Projects/dw-image-shrink';
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
function gql(query){return new Promise((res,rej)=>{const body=JSON.stringify({query});const req=https.request({method:'POST',host:SHOP,path:'/admin/api/2024-10/graphql.json',headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json','Content-Length':Buffer.byteLength(body)}},r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>{try{res(JSON.parse(d))}catch(e){res({errors:[{message:d.slice(0,120)}]})}});});req.on('error',rej);req.write(body);req.end();});}
const Q=(c)=>`{ files(first: 250, query: "used_in:none"${c?`, after: "${c}"`:''}) { pageInfo{ hasNextPage endCursor } edges{ node{ __typename id ... on MediaImage{ originalSource{ fileSize } image{ url } } ... on GenericFile{ originalFileSize url } ... on Video{ originalSource{ fileSize } filename } } } } }`;
const out=fs.createWriteStream(DIR+'/unused-files.jsonl');
let cursor=null,page=0,n=0,bytes=0; const byType={};
while(true){
let r; for(let a=0;a<6;a++){ r=await gql(Q(cursor)); if(r.errors&&/throttl/i.test(JSON.stringify(r.errors))){await sleep(2000);continue;} break; }
if(r.errors){ console.error('ERR',JSON.stringify(r.errors).slice(0,160)); break; }
const conn=r.data.files;
for(const e of conn.edges){ const nd=e.node; const size=nd.__typename==='GenericFile'?(nd.originalFileSize||0):(nd.originalSource&&nd.originalSource.fileSize)||0; const url=nd.__typename==='GenericFile'?nd.url:(nd.image&&nd.image.url)||nd.filename||''; out.write(JSON.stringify({id:nd.id,type:nd.__typename,size:+size,url})+'\n'); n++; bytes+=+size; byType[nd.__typename]=(byType[nd.__typename]||0)+1; }
page++; if(page%20===0) console.log(` page ${page} | unused ${n} | ${(bytes/1073741824).toFixed(1)}GB`);
if(!conn.pageInfo.hasNextPage) break; cursor=conn.pageInfo.endCursor;
const c=r.extensions&&r.extensions.cost&&r.extensions.cost.throttleStatus; await sleep(c&&c.currentlyAvailable<400?800:150);
}
out.end();
console.log(`DONE: ${n} UNUSED files = ${(bytes/1073741824).toFixed(2)} GB | by type: ${JSON.stringify(byType)}`);
console.log('→ unused-files.jsonl (Shopify-authoritative safe-to-delete set)');