← back to York Reprice 2026 08
clear-cadence-4.mjs
27 lines
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 APPLY=process.argv.includes('--apply');
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const gql=async(q,v)=>{for(let a=0;a<6;a++){const r=await fetch(URL,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});const j=await r.json();if(j.errors&&JSON.stringify(j.errors).includes('THROTTLED')){await sleep(1500);continue;}return j.data;}};
const norm=s=>String(s||'').toUpperCase().trim();
const j=JSON.parse(fs.readFileSync('cadence/data/latest.json','utf8'));
const reprice=j.reprice_sample, disco=j.disco_sample;
// resolve a product by its exact manufacturer_sku metafield (scan JS vendor once, index by mfr)
console.log('resolving targets by manufacturer_sku metafield…');
const idx=new Map(); let cur=null;
for(let pg=0;pg<90;pg++){const d=(await gql(`query($c:String){products(first:80,after:$c,query:"vendor:'Jeffrey Stevens'"){pageInfo{hasNextPage endCursor} nodes{id title status dwc:metafield(namespace:"dwc",key:"manufacturer_sku"){value} cust:metafield(namespace:"custom",key:"manufacturer_sku"){value} variants(first:20){nodes{id sku price title}}}}}`,{c:cur}))?.products;if(!d)break;for(const p of d.nodes){const m=norm(p.dwc?.value||p.cust?.value);if(m)idx.set(m,p);}if(!d.pageInfo.hasNextPage)break;cur=d.pageInfo.endCursor;}
console.log(`indexed ${idx.size} JS products | mode: ${APPLY?'LIVE APPLY':'DRY-RUN'}`);
const Mp=`mutation($pid:ID!,$variants:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$pid,variants:$variants){userErrors{message}}}`;
const Ma=`mutation($input:ProductInput!){productUpdate(input:$input){product{status} userErrors{message}}}`;
let done=0;
for(const r of reprice){const p=idx.get(norm(r.mfr));if(!p){console.log(' reprice MISS',r.mfr);continue;}
const rolls=p.variants.nodes.filter(v=>!/sample|-sample$/i.test(v.sku||''));const roll=rolls.reduce((a,b)=>parseFloat(b.price)>parseFloat(a.price)?b:a);
console.log(` REPRICE ${r.mfr} "${p.title.slice(0,34)}" $${roll.price} -> $${r.map.toFixed(2)}`);
if(APPLY){const d=await gql(Mp,{pid:p.id,variants:[{id:roll.id,price:r.map.toFixed(2)}]});const ue=d.productVariantsBulkUpdate?.userErrors||[];if(!ue.length)done++;else console.log(' err',JSON.stringify(ue));}}
for(const r of disco){const p=idx.get(norm(r.mfr));if(!p){console.log(' disco MISS',r.mfr);continue;}
console.log(` ARCHIVE ${r.mfr} "${p.title.slice(0,34)}" (was ${p.status})`);
if(APPLY){const d=await gql(Ma,{input:{id:p.id,status:'ARCHIVED'}});const ue=d.productUpdate?.userErrors||[];if(!ue.length)done++;else console.log(' err',JSON.stringify(ue));}}
console.log(APPLY?`\nDONE — ${done} writes applied.`:'\nDRY-RUN. add --apply');