← back to York Yardgood Exec

nuovo-revert.mjs

36 lines

#!/usr/bin/env node
// Revert the 15 Nuovo products wrongly relabeled Sold-Per-Yard by york32 — they are 27.55" A-Street ROLL goods.
// Restores: uom -> old (Priced Per Single Roll), min/units -> 2, variant option -> old title. Price kept.
// DRY-RUN default; --apply writes. Uses restore-map-32.jsonl (old uom/title) + york32-target (Nuovo ids).
import { readFileSync } from 'node:fs';
const APPLY=process.argv.includes('--apply');
const TOKEN=readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=')[1].trim();
const SHOP='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
const DIR=new URL('.',import.meta.url).pathname;
const T='single_line_text_field';
const nuovoIds=new Set(JSON.parse(readFileSync(DIR+'data/york32-target.json')).filter(r=>r.group==='Nuovo').map(r=>String(r.product_id)));
const rest=readFileSync(DIR+'data/restore-map-32.jsonl','utf8').trim().split('\n').map(l=>JSON.parse(l)).filter(r=>nuovoIds.has(String(r.pid)));
const gql=(q,v)=>fetch(SHOP,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})}).then(r=>r.json());
console.log(`Nuovo revert — ${rest.length} products (expect 15)`);
let ok=0,fail=0;
for(const r of rest){
  const pid=`gid://shopify/Product/${r.pid}`;
  const oldUom=r.old.uom||'Priced Per Single Roll'; const oldTitle=r.old.title||'Sold Per Single Roll';
  if(!APPLY){console.log(`  DRY ${r.pid} -> uom='${oldUom}' option='${oldTitle}' min=2 units=2 (price kept)`);ok++;continue;}
  const mfs=[
    {ownerId:pid,namespace:'global',key:'unit_of_measure',type:T,value:oldUom},
    {ownerId:pid,namespace:'global',key:'v_prods_quantity_order_min',type:T,value:'2'},
    {ownerId:pid,namespace:'global',key:'v_prods_quantity_order_units',type:T,value:'2'},
    {ownerId:r.sellVid,namespace:'custom',key:'v_prod_quantity_order_min',type:T,value:'2'},
    {ownerId:r.sellVid,namespace:'custom',key:'v_prods_quantity_order_units',type:T,value:'2'},
  ];
  const m=await gql(`mutation($m:[MetafieldsSetInput!]!){metafieldsSet(metafields:$m){userErrors{message}}}`,{m:mfs});
  if(m.data.metafieldsSet.userErrors.length){console.log('  MF-ERR',r.pid,JSON.stringify(m.data.metafieldsSet.userErrors));fail++;continue;}
  const vu=await gql(`mutation($pid:ID!,$v:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$pid,variants:$v){userErrors{message}}}`,
    {pid,v:[{id:r.sellVid,optionValues:[{optionName:'Size',name:oldTitle}]}]});
  if(vu.data.productVariantsBulkUpdate.userErrors.length){console.log('  VAR-ERR',r.pid,JSON.stringify(vu.data.productVariantsBulkUpdate.userErrors));fail++;continue;}
  console.log(`  OK ${r.pid} reverted to '${oldTitle}' / ${oldUom} / min2`);ok++;
  await new Promise(x=>setTimeout(x,400));
}
console.log(`\n${APPLY?'REVERTED':'DRY-RUN'} — ok=${ok} fail=${fail}`);