← back to Designer Wallcoverings
auto-save: 2026-07-29T21:42:29 (1 files) — scripts/tk10002-null-sample/fix-missoni-sample.js
dd4a842f705156897f2a88e2534a691beac450f5 · 2026-07-29 21:42:35 -0700 · Steve Abrams
Files touched
A scripts/tk10002-null-sample/fix-missoni-sample.js
Diff
commit dd4a842f705156897f2a88e2534a691beac450f5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 29 21:42:35 2026 -0700
auto-save: 2026-07-29T21:42:29 (1 files) — scripts/tk10002-null-sample/fix-missoni-sample.js
---
scripts/tk10002-null-sample/fix-missoni-sample.js | 63 +++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/scripts/tk10002-null-sample/fix-missoni-sample.js b/scripts/tk10002-null-sample/fix-missoni-sample.js
new file mode 100644
index 00000000..857475f8
--- /dev/null
+++ b/scripts/tk10002-null-sample/fix-missoni-sample.js
@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+/**
+ * TK-10002 — fix the 2 Missoni -sample collisions BATCH-1 missed (Cody caught, live-verified).
+ * apply-residual renamed only the loser's BASE variant; the loser's -sample sibling still
+ * shares MIS-183xx-sample with the keeper. Rename loser's sample to match its new base.
+ * REASSIGN only, 0 archives. In-scope of the residual-collision-cleanup artifact.
+ * DRY-RUN by default; APPLY=1 + artifact required. Includes the live-uniqueness guard.
+ */
+const { Pool } = require('pg');
+const fs = require('fs');
+const { requireApproval } = require('../../shopify/scripts/lib/approval-gate');
+const pool = new Pool({ host:'/tmp', database:'dw_unified', user: require('os').userInfo().username });
+const TOKEN = fs.readFileSync(require('path').join(process.env.HOME,'Projects/Designer-Wallcoverings/shopify/.env'),'utf8')
+ .split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=').slice(1).join('=').replace(/['"]/g,'').trim();
+const STORE='designer-laboratory-sandbox.myshopify.com', API='2024-10';
+const APPLY = process.env.APPLY === '1';
+const sleep = ms => new Promise(r=>setTimeout(r,ms));
+const TARGETS = [
+ { pid:'6751727386675', label:'Magic Garden Cream', old:'MIS-18313-sample', neu:'MIS-18313-W2-sample' },
+ { pid:'6753081589811', label:'Striped Sunset', old:'MIS-18398-sample', neu:'MIS-18398-W2-sample' },
+];
+async function gql(query,variables){
+ for(let a=0;a<5;a++){
+ const r=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query,variables})});
+ if(r.status===429){await sleep(2000);continue;}
+ const j=await r.json();
+ if((j.errors||[]).some(e=>/throttl/i.test(e.message||''))){await sleep(2000);continue;}
+ if(j.errors) throw new Error('GQL '+JSON.stringify(j.errors));
+ return j.data;
+ }
+ throw new Error('gql retries exhausted');
+}
+const MUT=`mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){ productVariantsBulkUpdate(productId:$productId,variants:$variants){ productVariants{ id inventoryItem{ sku } } userErrors{ field message } } }`;
+async function skuFreeLive(sku){
+ const d=await gql(`{ productVariants(first:10, query:"sku:${sku}"){ nodes{ product{ status } inventoryItem{ sku } } } }`);
+ return !(d.productVariants.nodes||[]).some(n=>String(n.product.status).toUpperCase()!=='ARCHIVED' && n.inventoryItem?.sku===sku);
+}
+async function main(){
+ const gate=requireApproval({ ticket:'TK-10002', scope:'residual-collision-cleanup', apply:APPLY });
+ console.log(`MODE: ${APPLY?'APPLY (LIVE WRITE)':'DRY-RUN'} gate=${gate.mode}\n`);
+ const results={applied:[],skipped:[],failed:[]};
+ for(const t of TARGETS){
+ const gid=`gid://shopify/Product/${t.pid}`;
+ if(!(await skuFreeLive(t.neu))){ results.skipped.push({...t,reason:'new sku not free'}); console.log('⚠',t.label,t.neu,'NOT free — SKIP'); continue; }
+ const d=await gql(`{ product(id:"${gid}"){ variants(first:20){ nodes{ id inventoryItem{ sku } } } } }`);
+ const v=(d.product.variants.nodes||[]).find(n=>n.inventoryItem?.sku===t.old);
+ if(!v){ results.skipped.push({...t,reason:'old sku absent (already fixed?)'}); console.log('=',t.label,t.old,'absent — SKIP'); continue; }
+ console.log(`${APPLY?'':'[dry] '}${t.label}: '${t.old}' -> '${t.neu}'`);
+ if(!APPLY) continue;
+ try{
+ const r=await gql(MUT,{productId:gid,variants:[{id:v.id,inventoryItem:{sku:t.neu}}]});
+ const ue=r.productVariantsBulkUpdate.userErrors;
+ if(ue.length){ results.failed.push({...t,ue}); console.log(' ✗',JSON.stringify(ue)); }
+ else { results.applied.push({...t,got:r.productVariantsBulkUpdate.productVariants[0].inventoryItem.sku}); console.log(' ✓ ->',results.applied.at(-1).got); }
+ }catch(e){ results.failed.push({...t,err:e.message}); console.log(' ✗',e.message); }
+ await sleep(300);
+ }
+ fs.writeFileSync('/tmp/tk10002_missoni_sample_results.json',JSON.stringify(results,null,2));
+ console.log(`\nDONE: applied=${results.applied.length} skipped=${results.skipped.length} failed=${results.failed.length}`);
+ if(!APPLY) console.log('(DRY-RUN)');
+ await pool.end();
+}
+main().catch(e=>{console.error(e);process.exit(1);});
← f9d5538b TK-10002: PR base-collision regression fix (dry-run tooling,
·
back to Designer Wallcoverings
·
TK-10002 /yolo FIX-THEN-SHIP: fix 2 Missoni -sample collisio 1376340c →