[object Object]

← back to Designerwallcoverings

TK-10209: publish 435 O&L products missing from Google/YouTube channel

c9a590113974cee5755868aeb9f33e3e74f2af76 · 2026-09-04 12:42:39 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STupyqBoLfTPHaTr1Gi42b

Files touched

Diff

commit c9a590113974cee5755868aeb9f33e3e74f2af76
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Sep 4 12:42:39 2026 -0700

    TK-10209: publish 435 O&L products missing from Google/YouTube channel
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01STupyqBoLfTPHaTr1Gi42b
---
 scripts/osborne-publish/publish-435.mjs | 52 +++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/scripts/osborne-publish/publish-435.mjs b/scripts/osborne-publish/publish-435.mjs
new file mode 100644
index 0000000..73911e1
--- /dev/null
+++ b/scripts/osborne-publish/publish-435.mjs
@@ -0,0 +1,52 @@
+#!/usr/bin/env node
+// TK-10209 Part 2 — publish the 435 O&L products that are NOT on the Google & YouTube
+// channel (pub 29646651457). Same safety model as publish.mjs:
+// Reversibility-FIRST: appends {id,ts,undo} to data/publish-reversibility-435.jsonl BEFORE each write.
+// Re-verifies price >$5 per product IMMEDIATELY before publishing (STOP if <=5).
+// Flags: --only=<id>  (canary) | --apply (default dry-run)
+import { readFileSync, appendFileSync, mkdirSync } from 'node:fs';
+const ENV = readFileSync(new URL('../../../secrets-manager/.env', import.meta.url).pathname,'utf8');
+const TOKEN=(ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1]?.trim();
+const SHOP='designer-laboratory-sandbox.myshopify.com', VER='2024-10';
+const PUB='gid://shopify/Publication/29646651457';
+const APPLY=process.argv.includes('--apply');
+const ONLY=(process.argv.find(a=>a.startsWith('--only='))||'').split('=')[1];
+const REV=new URL('./data/publish-reversibility-435.jsonl',import.meta.url).pathname;
+mkdirSync(new URL('./data',import.meta.url).pathname,{recursive:true});
+let ids=readFileSync('/Users/macstudio3/.claude/yolo-queue/gmc-osborne-notongoogle-ids-2026-09-02.txt','utf8').trim().split('\n').filter(Boolean);
+if(ONLY) ids=ids.filter(id=>String(id)===String(ONLY));
+
+async function gql(query,variables){
+  const r=await fetch(`https://${SHOP}/admin/api/${VER}/graphql.json`,{method:'POST',
+    headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query,variables})});
+  const j=await r.json(); if(j.errors) throw new Error(JSON.stringify(j.errors)); return j.data;
+}
+const RECHECK=`query($id:ID!){product(id:$id){id title variants(first:10){edges{node{title sku price}}}
+  resourcePublications(first:50){edges{node{publication{id} isPublished}}}}}`;
+const PUBLISH=`mutation($id:ID!,$pubs:[PublicationInput!]!){publishablePublish(id:$id,input:$pubs){
+  publishable{ ... on Product{id}} userErrors{field message}}}`;
+
+let done=0, skip=0, stop=0, errCount=0;
+for(const idNum of ids){
+  const id=`gid://shopify/Product/${idNum}`;
+  let d; try{ d=await gql(RECHECK,{id}); }catch(e){ console.log('ERROR-READ',idNum,e.message); errCount++; continue; }
+  const vars=d.product.variants.edges.map(e=>e.node);
+  const sample=vars.find(v=>/sample/i.test(v.title));
+  const roll=vars.find(v=>v!==sample);
+  const rollP=parseFloat(roll?.price??'0');
+  const alreadyPub=d.product.resourcePublications.edges.some(e=>e.node.publication.id===PUB&&e.node.isPublished);
+  if(rollP<=5){ console.log('STOP',idNum,'roll price',rollP,'<=5 — would leak $4.25 to GMC. NOT publishing.'); stop++; continue; }
+  if(alreadyPub){ console.log('skip',idNum,'already on google'); skip++; continue; }
+  if(!APPLY){ console.log('DRY would publish',idNum,d.product.title,'roll=$'+rollP); continue; }
+  // reversibility FIRST
+  appendFileSync(REV, JSON.stringify({id:idNum,pub:PUB,ts:new Date().toISOString(),
+    undo:`publishableUnpublish ${id} from ${PUB}`})+'\n');
+  let res; try{
+    for(let a=0;a<4;a++){ try{ res=await gql(PUBLISH,{id,pubs:[{publicationId:PUB}]}); break;}catch(e){ if(a===3)throw e; await new Promise(r=>setTimeout(r,2000*(a+1)));}}
+  }catch(e){ console.log('ERROR-PUBLISH',idNum,e.message); errCount++; continue; }
+  const ue=res.publishablePublish.userErrors;
+  if(ue&&ue.length){ console.log('ERROR',idNum,JSON.stringify(ue)); errCount++; continue; }
+  done++; console.log('published',idNum,d.product.title,'roll=$'+rollP);
+  await new Promise(r=>setTimeout(r,350)); // gentle pacing
+}
+console.log(`\n${APPLY?'APPLIED':'DRY'} — total ${ids.length}, published ${done}, skipped(already-on-google) ${skip}, STOPPED(price<=5) ${stop}, errors ${errCount}`);

← 86ca8d4 TK-11076: Hollywood rollback artifact (no-op — batch judged  ·  back to Designerwallcoverings  ·  TK-11046: Sanderson Dalmatians activate + dup-draft cleanup f32a9c1 →