← back to Dw Image Shrink
Pivot to purge-first strategy: add gated purge-candidates analyzer (archived imgs + spin-GIFs)
f84c7bc59d12f6c71555afb410d9f17a740c2f2d · 2026-07-29 07:53:49 -0700 · steve
Files touched
Diff
commit f84c7bc59d12f6c71555afb410d9f17a740c2f2d
Author: steve <steve@designerwallcoverings.com>
Date: Wed Jul 29 07:53:49 2026 -0700
Pivot to purge-first strategy: add gated purge-candidates analyzer (archived imgs + spin-GIFs)
---
purge-candidates.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/purge-candidates.js b/purge-candidates.js
new file mode 100644
index 0000000..764e2b3
--- /dev/null
+++ b/purge-candidates.js
@@ -0,0 +1,55 @@
+// Build a GATED purge manifest of disposable image bytes, sized in GB via CDN
+// HEAD. Categories (least-regret first):
+// A archived_img — images on ARCHIVED products (hidden from storefront)
+// B gif_archived — animated GIFs (fat spin-gifs) on archived products
+// C gif_active — animated GIFs on ACTIVE products (product decision: deleting
+// removes the "spin" viewer — FLAGGED, not recommended)
+// READ-ONLY (HEAD only). Writes purge-manifest.jsonl (candidates) + purge-summary.md.
+// NOTHING is deleted here — deletion is a separate Steve-gated step.
+import fs from 'fs';
+import https from 'https';
+const DIR='/Users/macstudio3/Projects/dw-image-shrink';
+const rows=fs.readFileSync(DIR+'/inventory.jsonl','utf8').split('\n').filter(Boolean).map(l=>JSON.parse(l));
+const isGif=s=>/\.gif(\?|$)/i.test(s||'');
+function head(url){return new Promise(res=>{try{const u=new URL(url);const rq=https.request({method:'HEAD',host:u.host,path:u.pathname+u.search},r=>{r.resume();res(+(r.headers['content-length']||0));});rq.on('error',()=>res(0));rq.setTimeout(15000,()=>{rq.destroy();res(0);});rq.end();}catch(e){res(0);}});}
+async function pool(items,fn,conc=24){let i=0,out=new Array(items.length);await Promise.all(Array.from({length:conc},async()=>{while(i<items.length){const k=i++;out[k]=await fn(items[k]);}}));return out;}
+const sample=(a,n)=>{if(a.length<=n)return a.slice();const b=a.slice();for(let i=b.length-1;i>0;i--){const j=(i*2654435761>>>0)%(i+1);[b[i],b[j]]=[b[j],b[i]];}return b.slice(0,n);};
+const GB=b=>(b/1073741824);
+
+(async()=>{
+ const archived = rows.filter(r=>r.status==='archived');
+ const archImg = archived.filter(r=>!isGif(r.src));
+ const gifArch = archived.filter(r=>isGif(r.src));
+ const gifActive = rows.filter(r=>r.status==='active' && isGif(r.src));
+
+ // A: archived non-gif — HEAD sample -> extrapolate
+ const sA=sample(archImg,800); const bA=(await pool(sA,r=>head(r.src))).filter(x=>x>0);
+ const meanA=bA.reduce((a,b)=>a+b,0)/(bA.length||1); const totA=meanA*archImg.length;
+ // B: archived gifs — HEAD ALL (fat, few)
+ const bB=(await pool(gifArch,r=>head(r.src))).filter(x=>x>0); const totB=bB.reduce((a,b)=>a+b,0);
+ // C: active gifs — HEAD ALL (flag only)
+ const bC=(await pool(gifActive,r=>head(r.src))).filter(x=>x>0); const totC=bC.reduce((a,b)=>a+b,0);
+
+ // write manifest (A + B are the recommended-deletable set; C flagged)
+ const man=fs.createWriteStream(DIR+'/purge-manifest.jsonl');
+ for(const r of archImg) man.write(JSON.stringify({cat:'A_archived_img',product_id:r.product_id,image_id:r.image_id,src:r.src})+'\n');
+ for(const r of gifArch) man.write(JSON.stringify({cat:'B_gif_archived',product_id:r.product_id,image_id:r.image_id,src:r.src})+'\n');
+ for(const r of gifActive) man.write(JSON.stringify({cat:'C_gif_active_FLAG',product_id:r.product_id,image_id:r.image_id,src:r.src})+'\n');
+ man.end();
+
+ const md=`# Purge Candidates (GATED — nothing deleted yet)
+_Sized via CDN HEAD. Deletion frees storage only after Shopify async-GC._
+
+| cat | what | # images | est. size | recommend |
+|---|---|--:|--:|---|
+| A | images on ARCHIVED products | ${archImg.length.toLocaleString()} | ~${GB(totA).toFixed(1)} GB | DELETE (hidden from storefront) |
+| B | spin-GIFs on ARCHIVED products | ${gifArch.length.toLocaleString()} | ${GB(totB).toFixed(2)} GB (exact) | DELETE |
+| C | spin-GIFs on ACTIVE products | ${gifActive.length.toLocaleString()} | ${GB(totC).toFixed(2)} GB (exact) | FLAG — removes the on-page "spin" viewer; your call |
+| — | **A+B recommended reclaim** | | **~${GB(totA+totB).toFixed(1)} GB** | |
+
+Manifest: purge-manifest.jsonl (one row per candidate image; delete only the categories you approve).
+NOT measured here (need your manual check / separate step): the **Files section** (Settings → Files) and **videos** — potentially the biggest non-image consumers.
+`;
+ fs.writeFileSync(DIR+'/purge-summary.md',md);
+ console.log(md);
+})();
← d25c69d HALT: delete-first fails at cap (async GC); hold 197 rows; a
·
back to Dw Image Shrink
·
auto-save: 2026-07-29T08:07:14 (2 files) — purge-deleted.jso f3dc93b →