← back to Dw Image Shrink
cap-watcher.js
42 lines
// Poll the file-storage cap by attempting a real (tiny) upload to a draft probe
// product every INTERVAL. While 413 → keep waiting. On the FIRST 200 → log
// "CAP CLEARED", delete the test image, and EXIT 0 (its completion notifies the
// session to start the recovery cascade). READ-mostly: only writes a throwaway
// probe image that it immediately deletes. node cap-watcher.js [--interval 180] [--maxhours 24]
import fs from 'fs';
import os from 'os';
import https from 'https';
import { execFileSync } from 'child_process';
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 arg=(k,d)=>{const a=process.argv.find(x=>x.startsWith('--'+k+'='));return a?a.split('=').slice(1).join('='):d;};
const INTERVAL=(+arg('interval','180'))*1000;
const MAX=(+arg('maxhours','24'))*3600*1000;
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
function api(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',()=>res({status:0,json:{}}));if(data)req.write(data);req.end();});}
const fp=`${os.tmpdir()}/cap_probe.jpg`;
execFileSync('python3',['-c',`from PIL import Image;import random;im=Image.new('RGB',(600,600),(120,120,120));[im.putpixel((random.randint(0,599),random.randint(0,599)),(255,0,0)) for _ in range(3000)];im.save('${fp}','JPEG',quality=82)`]);
const b64=fs.readFileSync(fp).toString('base64');
// find/create probe product once
let probe;
const s=await api('GET','/admin/api/2024-10/products.json?limit=1&title=ZZ%20Storage%20Probe&fields=id,title');
probe=(s.json.products&&s.json.products[0]&&s.json.products[0].id)|| (await api('POST','/admin/api/2024-10/products.json',{product:{title:'ZZ Storage Probe',status:'draft',published:false}})).json.product?.id;
const t0=Date.now(); let n=0;
while(Date.now()-t0<MAX){
n++;
const up=await api('POST',`/admin/api/2024-10/products/${probe}/images.json`,{image:{attachment:b64,filename:'cap-probe.jpg'}});
const ts=new Date().toISOString();
if(up.status<300&&up.json.image){
console.log(`${ts} ✅ CAP CLEARED (attempt ${n}) — uploads work again`);
await api('DELETE',`/admin/api/2024-10/products/${probe}/images/${up.json.image.id}.json`);
process.exit(0);
}
const capped=/exceed the file storage/i.test(JSON.stringify(up.json));
console.log(`${ts} still capped (attempt ${n}, http ${up.status}${capped?' 413-storage':''})`);
await sleep(INTERVAL);
}
console.log('cap-watcher: max wait elapsed, still capped — giving up (rerun to resume)');
process.exit(2);