[object Object]

← back to York Reprice 2026 08

Onboard-set publisher + weekly cadence (batches)

572076f875a0224178e312d96fd639f2f606920c · 2026-07-14 13:05:41 -0700 · Steve

publish-onboard.mjs: DRAFT->ACTIVE for the onboard-payloads generation (735 eligible),
nets its OWN exclusion lists (settlement+image+colorfail=66, verified 0 overlap =
pre-excluded, defensive no-op). DRY-RUN default, --apply --i-am-steve gate, --limit=N
batch cap from the start, idempotent + miss-tolerant (only flips already-created DRAFTs,
so it tracks the nightly onboard creation drip). Distinct set from publish-632 (phase1) —
never cross the exclusion lists.

Weekly LaunchAgent (Tue 07:00, --limit=100, RunAtLoad=false, launchd-safe abs node path +
WorkingDirectory). Mirrors the York phase1 publish cadence, offset one day.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit 572076f875a0224178e312d96fd639f2f606920c
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jul 14 13:05:41 2026 -0700

    Onboard-set publisher + weekly cadence (batches)
    
    publish-onboard.mjs: DRAFT->ACTIVE for the onboard-payloads generation (735 eligible),
    nets its OWN exclusion lists (settlement+image+colorfail=66, verified 0 overlap =
    pre-excluded, defensive no-op). DRY-RUN default, --apply --i-am-steve gate, --limit=N
    batch cap from the start, idempotent + miss-tolerant (only flips already-created DRAFTs,
    so it tracks the nightly onboard creation drip). Distinct set from publish-632 (phase1) —
    never cross the exclusion lists.
    
    Weekly LaunchAgent (Tue 07:00, --limit=100, RunAtLoad=false, launchd-safe abs node path +
    WorkingDirectory). Mirrors the York phase1 publish cadence, offset one day.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 .../com.steve.york-onboard-publish-cadence.plist   | 26 ++++++++++++++
 publish-onboard.mjs                                | 42 ++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/cadence/com.steve.york-onboard-publish-cadence.plist b/cadence/com.steve.york-onboard-publish-cadence.plist
new file mode 100644
index 0000000..5be6ed1
--- /dev/null
+++ b/cadence/com.steve.york-onboard-publish-cadence.plist
@@ -0,0 +1,26 @@
+<?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.york-onboard-publish-cadence</string>
+  <key>ProgramArguments</key><array>
+    <string>/opt/homebrew/bin/node</string>
+    <string>/Users/macstudio3/Projects/york-reprice-2026-08/publish-onboard.mjs</string>
+    <string>--apply</string>
+    <string>--i-am-steve</string>
+    <string>--limit=100</string>
+  </array>
+  <key>WorkingDirectory</key><string>/Users/macstudio3/Projects/york-reprice-2026-08</string>
+  <!-- Weekly: Tuesday 07:00 local (offset from York phase1 Monday). Publishes up to 100 onboard-set
+       DRAFT->ACTIVE per run, holds settlement+image+colorfail (66), idempotent, miss-tolerant
+       (only flips SKUs already created as DRAFT — tracks the nightly onboard creation drip). -->
+  <key>StartCalendarInterval</key><dict>
+    <key>Weekday</key><integer>2</integer>
+    <key>Hour</key><integer>7</integer>
+    <key>Minute</key><integer>0</integer>
+  </dict>
+  <key>RunAtLoad</key><false/>
+  <key>StandardOutPath</key><string>/Users/macstudio3/Projects/york-reprice-2026-08/cadence/data/onboard-publish-cadence.log</string>
+  <key>StandardErrorPath</key><string>/Users/macstudio3/Projects/york-reprice-2026-08/cadence/data/onboard-publish-cadence.err</string>
+</dict>
+</plist>
diff --git a/publish-onboard.mjs b/publish-onboard.mjs
new file mode 100644
index 0000000..50e152c
--- /dev/null
+++ b/publish-onboard.mjs
@@ -0,0 +1,42 @@
+#!/usr/bin/env node
+/** publish-onboard.mjs — flip the ONBOARD-SET York DRAFTs to ACTIVE, holding this generation's own
+ *  exclusion lists (settlement-held + image-held + color-fail). DRY-RUN default; --apply --i-am-steve.
+ *  --limit=N caps NEW publishes per run so it can drip on a weekly cadence (like publish-632). GATED (go-live).
+ *
+ *  Distinct from publish-632.mjs: that one is the phase1 generation (phase1 payloads + phase1 settlement
+ *  flags). THIS is the onboard generation (onboard-payloads-enriched.json), which nets its OWN held-lists.
+ *  Never point one at the other's set — the exclusion lists don't transfer. */
+import fs from 'node:fs';
+const SHOP='designer-laboratory-sandbox.myshopify.com',VER='2024-10';
+const TOKEN=(fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1]?.trim();
+const URL=`https://${SHOP}/admin/api/${VER}/graphql.json`;
+const args=Object.fromEntries(process.argv.slice(2).map(a=>{const[k,v]=a.replace(/^--/,'').split('=');return[k,v===undefined?true:v];}));
+const APPLY=args.apply===true&&args['i-am-steve']===true;
+const LIMIT=args.limit?parseInt(args.limit,10):Infinity; // cap NEW publishes per run (cadence drip); default = all
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const gql=async(q,v)=>{for(let a=0;a<8;a++){let j;try{const r=await fetch(URL,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});j=await r.json();}catch(e){await sleep(1500*(a+1));continue;}if(j.errors){if(JSON.stringify(j.errors).includes('THROTTLED')){await sleep(2000*(a+1));continue;}throw new Error(JSON.stringify(j.errors));}const t=j.extensions?.cost?.throttleStatus;if(t&&t.currentlyAvailable<300)await sleep(1500);return j.data;}throw new Error('retries');};
+const norm=s=>String(s||'').toUpperCase().trim();
+const readList=f=>{try{return JSON.parse(fs.readFileSync(f,'utf8'));}catch(e){return [];}};
+const pay=JSON.parse(fs.readFileSync('data/onboard-payloads-enriched.json','utf8'));
+// Union this generation's OWN exclusion lists → never flip these to ACTIVE.
+const hold=new Set([
+  ...readList('data/onboard-settlement-held.json'),
+  ...readList('data/onboard-image-held.json'),
+  ...readList('data/onboard-color-fail-list.json'),
+].map(x=>norm(x.mfr_sku)));
+const toPublish=pay.filter(p=>!hold.has(norm(p.mfr_sku)));
+console.log(`publish-onboard — ${APPLY?'⚠️ LIVE PUBLISH (DRAFT→ACTIVE)':'DRY-RUN'}`);
+console.log(`  total ${pay.length} | HELD (settlement+image+colorfail): ${pay.length-toPublish.length} | eligible: ${toPublish.length} | cap this run: ${LIMIT===Infinity?'ALL':LIMIT}`);
+if(!APPLY){console.log('\nDRY-RUN. Apply: node publish-onboard.mjs --apply --i-am-steve [--limit=N]');process.exit(0);}
+const Sku=`query($q:String!){productVariants(first:1,query:$q){nodes{product{id status}}}}`;
+const Mup=`mutation($input:ProductInput!){productUpdate(input:$input){product{status} userErrors{message}}}`;
+let done=0,skip=0,miss=0,err=0;
+for(const p of toPublish){
+  const sku=p.variants[1].sku; // variants[0]=-Sample, variants[1]=real product SKU
+  const d=await gql(Sku,{q:`sku:${sku}`}); const prod=d.productVariants.nodes[0]?.product;
+  if(!prod){miss++;continue;} if(prod.status==='ACTIVE'){skip++;continue;}
+  try{const u=await gql(Mup,{input:{id:prod.id,status:'ACTIVE'}});(u.productUpdate?.userErrors||[]).length?err++:done++;}catch(e){err++;}
+  if((done+skip+err)%50===0)console.log(`  progress published=${done} skip=${skip} err=${err}`);
+  if(done>=LIMIT){console.log(`  cap reached: ${done} published this run (--limit=${LIMIT})`);break;}
+}
+console.log(`\nDONE — published ${done} ACTIVE, ${skip} already-active, ${miss} not-found, ${err} err. (${pay.length-toPublish.length} held DRAFT: settlement+image+colorfail)`);

← a3b8a4f York publish on a weekly cadence (100/wk) instead of big-ban  ·  back to York Reprice 2026 08  ·  Remove onboard-set publish cadence plist (Steve's call) 367e82b →