← 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);