← back to Designer Wallcoverings
Fleet room-image push: idempotent paced daily cadence job (top-3 credited room shots/SKU, <=900 adds/day)
b75de15fdbf80941ed5ce3aa63a18a919d8e26f9 · 2026-06-26 10:14:13 -0700 · Steve Abrams
Files touched
A scripts/room-image-push/com.steve.dw-room-image-push.plistA scripts/room-image-push/fleet-room-push.cjsA scripts/room-image-push/run.sh
Diff
commit b75de15fdbf80941ed5ce3aa63a18a919d8e26f9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jun 26 10:14:13 2026 -0700
Fleet room-image push: idempotent paced daily cadence job (top-3 credited room shots/SKU, <=900 adds/day)
---
.../com.steve.dw-room-image-push.plist | 14 ++++++
scripts/room-image-push/fleet-room-push.cjs | 50 ++++++++++++++++++++++
scripts/room-image-push/run.sh | 6 +++
3 files changed, 70 insertions(+)
diff --git a/scripts/room-image-push/com.steve.dw-room-image-push.plist b/scripts/room-image-push/com.steve.dw-room-image-push.plist
new file mode 100644
index 00000000..7a8564ad
--- /dev/null
+++ b/scripts/room-image-push/com.steve.dw-room-image-push.plist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key><string>com.steve.dw-room-image-push</string>
+ <key>ProgramArguments</key>
+ <array><string>/bin/zsh</string><string>/Users/stevestudio2/Projects/Designer-Wallcoverings/scripts/room-image-push/run.sh</string></array>
+ <key>StartCalendarInterval</key>
+ <dict><key>Hour</key><integer>2</integer><key>Minute</key><integer>15</integer></dict>
+ <key>StandardOutPath</key><string>/Users/stevestudio2/Projects/Designer-Wallcoverings/scripts/room-image-push/launchd.out.log</string>
+ <key>StandardErrorPath</key><string>/Users/stevestudio2/Projects/Designer-Wallcoverings/scripts/room-image-push/launchd.err.log</string>
+ <key>RunAtLoad</key><false/>
+</dict>
+</plist>
diff --git a/scripts/room-image-push/fleet-room-push.cjs b/scripts/room-image-push/fleet-room-push.cjs
new file mode 100644
index 00000000..a8310698
--- /dev/null
+++ b/scripts/room-image-push/fleet-room-push.cjs
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+/* Fleet room-image push — idempotent, paced, resumable.
+ * Adds top-3 credited editorial room images as ADDITIONAL images (primary untouched, credit in alt-text)
+ * to every live Quadrille/China-Seas SKU. Caps adds per run (DAILY_CAP) so a daily launchd job
+ * walks the fleet in ~4 runs without breaching Shopify's rate limit. Exits 0 when fully done.
+ * DRYRUN=1 -> no writes, just report what it would add.
+ */
+const https=require('https'); const fs=require('fs'); const path=require('path');
+const DIR=__dirname;
+const SHOP='designer-laboratory-sandbox.myshopify.com';
+const DAILY_CAP=parseInt(process.env.DAILY_CAP||'900',10); // safely under Shopify's 1k/day guideline
+const DRY=process.env.DRYRUN==='1';
+let TOKEN=''; try{TOKEN=fs.readFileSync(process.env.HOME+'/Projects/Designer-Wallcoverings/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)[1].trim().replace(/^"|"$/g,'');}catch(e){}
+const workset=JSON.parse(fs.readFileSync(path.join(DIR,'workset.json'),'utf8'));
+const LEDGER=path.join(DIR,'ledger.json');
+const ledger=fs.existsSync(LEDGER)?JSON.parse(fs.readFileSync(LEDGER,'utf8')):{done:[],added:0,runs:0,startedAt:null};
+const doneSet=new Set(ledger.done);
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+function req(method,p,body){return new Promise((res,rej)=>{const data=body?JSON.stringify(body):null;
+ const r=https.request({host:SHOP,path:`/admin/api/2024-10${p}`,method,headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json',...(data?{'Content-Length':Buffer.byteLength(data)}:{})}},rs=>{let d='';rs.on('data',c=>d+=c);rs.on('end',()=>res({status:rs.statusCode,body:(()=>{try{return JSON.parse(d)}catch(e){return d}})()}));});r.on('error',rej);if(data)r.write(data);r.end();});}
+(async()=>{
+ let addedThisRun=0, skipped=0, failed=0, skusTouched=0;
+ for(const p of workset){
+ if(doneSet.has(p.mfr_sku)) continue;
+ if(addedThisRun>=DAILY_CAP){ break; }
+ const imgs=(p.imgs||[]).filter(i=>i.credit);
+ if(!imgs.length){ doneSet.add(p.mfr_sku); continue; }
+ const cur=await req('GET',`/products/${p.pid}/images.json`);
+ if(cur.status>=300){ failed++; continue; } // leave for next run, don't mark done
+ const haveAlts=new Set((cur.body.images||[]).map(i=>(i.alt||'')));
+ let allPresent=true;
+ for(const im of imgs){
+ if(addedThisRun>=DAILY_CAP){ allPresent=false; break; }
+ const alt=`Room setting — ${im.credit}`;
+ if(haveAlts.has(alt)){ skipped++; continue; }
+ if(DRY){ addedThisRun++; continue; }
+ const add=await req('POST',`/products/${p.pid}/images.json`,{image:{src:im.url,alt}});
+ if(add.status>=300||!add.body.image){ failed++; allPresent=false; }
+ else{ addedThisRun++; ledger.added=(ledger.added||0)+1; }
+ await sleep(900);
+ }
+ skusTouched++;
+ if(allPresent && addedThisRun<DAILY_CAP) doneSet.add(p.mfr_sku);
+ }
+ ledger.done=[...doneSet]; ledger.runs=(ledger.runs||0)+1; ledger.startedAt=ledger.startedAt||new Date().toISOString();
+ if(!DRY) fs.writeFileSync(LEDGER,JSON.stringify(ledger,null,2));
+ const remaining=workset.filter(p=>!doneSet.has(p.mfr_sku)).length;
+ console.log(JSON.stringify({dry:DRY, addedThisRun, skipped, failed, skusTouched, skusDone:doneSet.size, skusRemaining:remaining, totalAddedAllRuns:ledger.added||0},null,2));
+ if(remaining===0) console.log('FLEET COMPLETE — safe to unload the launchd job.');
+})();
diff --git a/scripts/room-image-push/run.sh b/scripts/room-image-push/run.sh
new file mode 100755
index 00000000..0b84e3e6
--- /dev/null
+++ b/scripts/room-image-push/run.sh
@@ -0,0 +1,6 @@
+#!/bin/zsh
+# Daily fleet room-image push — paced batch (<=900 adds), idempotent, resumable.
+export PATH="/usr/local/bin:$PATH"
+cd "/Users/stevestudio2/Projects/Designer-Wallcoverings/scripts/room-image-push"
+DAILY_CAP=900 "/usr/local/bin/node" fleet-room-push.cjs >> "/Users/stevestudio2/Projects/Designer-Wallcoverings/scripts/room-image-push/run.log" 2>&1
+echo "[$(date)] exit $?" >> "/Users/stevestudio2/Projects/Designer-Wallcoverings/scripts/room-image-push/run.log"
← 14755f27 auto-save: 2026-06-26T09:44:31 (5 files) — pending-approval/
·
back to Designer Wallcoverings
·
auto-save: 2026-06-26T10:14:48 (4 files) — pending-approval/ 277d0d98 →