[object Object]

← back to Dw Image Shrink

HALT: delete-first fails at cap (async GC); hold 197 rows; add auto-restore for 3 imageless products

d25c69daa5d06f06dd3b30d37c6f1ca684f3793e · 2026-07-29 07:49:15 -0700 · steve

Files touched

Diff

commit d25c69daa5d06f06dd3b30d37c6f1ca684f3793e
Author: steve <steve@designerwallcoverings.com>
Date:   Wed Jul 29 07:49:15 2026 -0700

    HALT: delete-first fails at cap (async GC); hold 197 rows; add auto-restore for 3 imageless products
---
 package.json         |  4 ++--
 restore-imageless.js | 41 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index e2f5e3c..d6a4fdf 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,8 @@
   "keywords": [],
   "author": "",
   "license": "ISC",
-  "type": "commonjs",
+  "type": "module",
   "dependencies": {
     "pg": "^8.22.0"
   }
-}
+}
\ No newline at end of file
diff --git a/restore-imageless.js b/restore-imageless.js
new file mode 100644
index 0000000..ef0a04c
--- /dev/null
+++ b/restore-imageless.js
@@ -0,0 +1,41 @@
+// Auto-restore the products left imageless by the canary. Their ORIGINAL images
+// still resolve on Shopify's CDN (orig_src → 200), so we re-POST them by src.
+// While AT the cap this 413s harmlessly; it retries (smallest-first) so the
+// instant Shopify GC frees space, the originals are restored. Marks the ledger
+// row 'restored' on success.  node restore-imageless.js [--loop] [--interval 120]
+import fs from 'fs';
+import https from 'https';
+import pg from 'pg';
+const SHOP='designer-laboratory-sandbox.myshopify.com';
+const ATOK=fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8').split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=').slice(1).join('=').replace(/["' ]/g,'');
+const LOOP=process.argv.includes('--loop');
+const arg=(k,d)=>{const a=process.argv.find(x=>x.startsWith('--'+k+'='));return a?a.split('=').slice(1).join('='):d;};
+const INTERVAL=(+arg('interval','120'))*1000;
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+function raw(m,p,b){return new Promise((res,rej)=>{const data=b?JSON.stringify(b):null;const req=https.request({method:m,host:SHOP,path:p,headers:{'X-Shopify-Access-Token':ATOK,'Content-Type':'application/json',...(data?{'Content-Length':Buffer.byteLength(data)}:{})}},r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>res({status:r.statusCode,json:(()=>{try{return JSON.parse(d)}catch(e){return d}})()}));});req.on('error',rej);if(data)req.write(data);req.end();});}
+function headLen(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.end();}catch(e){res(0);}});}
+const db=new pg.Client({host:'/tmp',database:'dw_unified'}); await db.connect();
+
+async function pass(){
+  const {rows}=await db.query("SELECT * FROM img_shrink_ledger WHERE state='imageless_alert' ORDER BY id");
+  if(!rows.length){ console.log('no imageless rows remaining'); return 0; }
+  // sort smallest-first by CDN size so partial free restores the most
+  for(const r of rows){ r._len=await headLen(r.orig_src); }
+  rows.sort((a,b)=>a._len-b._len);
+  let restored=0;
+  for(const r of rows){
+    const alt=r.orig_alt||null;
+    const up=await raw('POST',`/admin/api/2024-10/products/${r.product_id}/images.json`,{image:{src:r.orig_src,position:r.orig_position||1,...(alt?{alt}:{})}});
+    if(up.status<300 && up.json.image){
+      await db.query("UPDATE img_shrink_ledger SET state='restored', new_image_id=$2, new_src=$3, updated_at=now() WHERE id=$1",[r.id,up.json.image.id,up.json.image.src]);
+      console.log(`RESTORED product ${r.product_id} (${(r._len/1048576).toFixed(1)}MB)`);
+      restored++;
+    } else {
+      console.log(`still blocked product ${r.product_id}: ${up.status} ${JSON.stringify(up.json).slice(0,60)}`);
+    }
+  }
+  return restored;
+}
+if(LOOP){ while(true){ const {rows:[c]}=await db.query("SELECT count(*) n FROM img_shrink_ledger WHERE state='imageless_alert'"); if(+c.n===0){console.log('all restored — exiting loop');break;} await pass(); await sleep(INTERVAL); } }
+else { await pass(); }
+await db.end();

← ef8a15e Add reaper + phase4 verify; encoder validated (3290px 1.2MB→  ·  back to Dw Image Shrink  ·  Pivot to purge-first strategy: add gated purge-candidates an f84c7bc →