← back to Dw Image Shrink
orphan-diff.js
53 lines
// Compute PROVABLY-UNREFERENCED orphan images = Files MediaImages that are
// (a) not attached to any product (gid not in attached-gids.txt) AND
// (b) whose normalized filename is in NO referenced URL (product descriptions,
// articles, pages, collections, theme). Writes orphans.jsonl + orphan-summary.md.
// Conservative: an image with a missing url, or any uncertainty, is KEPT.
import fs from 'fs';
const DIR='/Users/macstudio3/Projects/dw-image-shrink';
const norm=u=>{if(!u)return'';let s=u.split('?')[0].split('/').pop();s=s.replace(/_\d+x\d+(?=\.[a-z0-9]+$)/i,'');return s.toLowerCase();};
const load=f=>fs.existsSync(f)?new Set(fs.readFileSync(f,'utf8').split('\n').filter(Boolean)):new Set();
const attached=new Set([...load(DIR+'/attached-gids.txt'),...load(DIR+'/attached-gids-rest.txt'),...load(DIR+'/referenced-file-gids.txt')]); // UNION: product-attached (REST∪GraphQL) + metaobject-referenced (color-pattern swatches)
const refUrls=new Set([...load(DIR+'/referenced-urls.txt'),...load(DIR+'/referenced-urls-extra.txt')]);
console.log(`attached gids: ${attached.size} | referenced urls: ${refUrls.size}`);
const out=fs.createWriteStream(DIR+'/orphans.jsonl');
let img=0, orphan=0, orphanBytes=0, attachedN=0, refN=0, nourl=0, vids=0, generic=0;
const distr={'>10MB':0,'1-10MB':0,'100KB-1MB':0,'<100KB':0};
for(const line of fs.readFileSync(DIR+'/files-inventory.jsonl','utf8').split('\n')){
if(!line)continue; const f=JSON.parse(line);
if(f.type==='Video'){ vids++; continue; }
if(f.type==='GenericFile'){ generic++; continue; }
if(f.type!=='MediaImage') continue;
img++;
const gid=String(f.id).split('/').pop();
if(attached.has(gid)){ attachedN++; continue; }
if(!f.url){ nourl++; continue; } // conservative: keep unknown
if(refUrls.has(norm(f.url))){ refN++; continue; }
// ORPHAN
orphan++; orphanBytes+=f.size||0;
const mb=(f.size||0)/1048576;
distr[mb>10?'>10MB':mb>1?'1-10MB':mb>0.1?'100KB-1MB':'<100KB']++;
out.write(JSON.stringify({id:f.id,size:f.size,url:f.url})+'\n');
}
out.end();
const GB=b=>(b/1073741824).toFixed(2);
const md=`# Orphan Image Candidates (PROVABLY UNREFERENCED)
_A candidate is a Files MediaImage attached to NO product and referenced in NO description/article/page/collection/theme._
- MediaImages scanned: **${img.toLocaleString()}**
- attached to a product: ${attachedN.toLocaleString()}
- referenced in content/theme: ${refN.toLocaleString()}
- no URL (kept, conservative): ${nourl.toLocaleString()}
- **ORPHANS: ${orphan.toLocaleString()} images = ${GB(orphanBytes)} GB reclaimable**
- size mix: ${JSON.stringify(distr)}
- (Videos ${vids} + GenericFiles ${generic} handled separately, not in this list)
⚠️ RESIDUAL RISK not yet checked: file_reference METAFIELDS (a product/metaobject metafield pointing at a file by gid). Recommend a canary-batched delete + storefront watch before the full run.
Delete list: orphans.jsonl (gids). Gated — run files-delete.js --apply only after approval.
`;
fs.writeFileSync(DIR+'/orphan-summary.md',md);
console.log('\n'+md);