[object Object]

← 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 →