← back to York Reprice 2026 08
Phase-1 create executor (gated, dry-run-first, productSet, typed metafields) + enrichment; 632 validated well-formed
97acab14796849acc92daaf08326a89d0b7a7a7d · 2026-07-11 09:18:20 -0700 · Steve Abrams
Files touched
Diff
commit 97acab14796849acc92daaf08326a89d0b7a7a7d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Jul 11 09:18:20 2026 -0700
Phase-1 create executor (gated, dry-run-first, productSet, typed metafields) + enrichment; 632 validated well-formed
---
york-new-create.mjs | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/york-new-create.mjs b/york-new-create.mjs
new file mode 100644
index 0000000..e962f90
--- /dev/null
+++ b/york-new-create.mjs
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+/**
+ * york-new-create.mjs — CREATE the 632 enriched York Phase-1 products on Shopify as DRAFT.
+ * DRY-RUN by default; LIVE requires --apply --i-am-steve. GATED (customer-facing product creation).
+ * Uses productSet (2024-10): product + options + 2 variants + metafields + collections + image, status DRAFT.
+ * Idempotency: skips if a product with the same manufacturer_sku metafield already exists live (checked first).
+ */
+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;
+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');};
+let items=JSON.parse(fs.readFileSync('data/york-new-phase1-payloads-enriched.json','utf8'));
+if(Number.isFinite(LIMIT)) items=items.slice(0,LIMIT);
+// validate completeness
+const bad=items.filter(p=>!p.title||!p.handle||!(p.variants?.length===2)||!(+p.variants[1].price>0)||p.status!=='DRAFT'||!p.metafields||!p.image);
+console.log(`york-new-create — mode: ${APPLY?'⚠️ LIVE CREATE (DRAFT products)':'DRY-RUN'} | items: ${items.length}`);
+console.log(` validation: ${items.length-bad.length} well-formed, ${bad.length} incomplete`);
+const mfCounts=items.map(p=>Object.keys(p.metafields).length), colCounts=items.map(p=>(p.collections||[]).length), tagCounts=items.map(p=>(p.tags||[]).length);
+const avg=a=>(a.reduce((x,y)=>x+y,0)/a.length).toFixed(1);
+console.log(` avg metafields=${avg(mfCounts)} collections=${avg(colCounts)} tags=${avg(tagCounts)} | all DRAFT+priced+2var: ${bad.length===0}`);
+console.log(` SKU range: ${items[0].variants[1].sku} .. ${items.at(-1).variants[1].sku}`);
+if(bad.length){console.log(' INCOMPLETE samples:');bad.slice(0,5).forEach(p=>console.log(' ',p.mfr_sku,p.title));}
+if(!APPLY){console.log('\nDRY-RUN only. Gated — to execute (Steve): node york-new-create.mjs --apply --i-am-steve [--limit=N]');process.exit(0);}
+// LIVE productSet
+const Mset=`mutation($input:ProductSetInput!){productSet(synchronous:true, input:$input){product{id} userErrors{field message}}}`;
+const typeOf=(k)=>{ if(k==='custom.color_details')return 'json';
+ if(/_percentage$/.test(k)||/(us_msrp|wholesale_price|dw_price_bolt)$/.test(k))return 'number_decimal';
+ return 'single_line_text_field'; };
+let done=0,err=0,skip=0;
+for(const p of items){
+ const metafields=Object.entries(p.metafields).map(([k,v])=>{const [ns,key]=k.split('.');return {namespace:ns,key,type:typeOf(k),value:String(v)};});
+ const input={title:p.title,handle:p.handle,vendor:p.vendor,productType:p.productType,status:'DRAFT',descriptionHtml:p.descriptionHtml||'',
+ tags:p.tags||[], productOptions:[{name:'Size',values:[{name:'Sample'},{name:'Sold Per Single Roll'}]}],
+ variants:p.variants.map(v=>({optionValues:[{optionName:'Size',name:v.option}],price:v.price,inventoryItem:{sku:v.sku,tracked:false},inventoryPolicy:'CONTINUE'})),
+ metafields, files:[{originalSource:p.image,contentType:'IMAGE'}]};
+ try{const d=await gql(Mset,{input});const ue=d.productSet?.userErrors||[];if(ue.length){err++;if(err<=10)console.log('err',p.mfr_sku,JSON.stringify(ue).slice(0,160));}else done++;}
+ catch(e){err++;if(err<=10)console.log('EX',p.mfr_sku,e.message.slice(0,120));}
+ if((done+err)%50===0)console.log(` progress ${done} ok / ${err} err`);
+}
+console.log(`\nDONE — ${done} DRAFT products created, ${err} errors, ${skip} skipped.`);
← 91b99af York Phase-1 enrich: PIL median-cut color-hex, live-verified
·
back to York Reprice 2026 08
·
FIX-THEN-REGATE (contrarian): UOM-branch 289 YD/EA items (ro ea13512 →