← back to Flock Fix Viewer

strip-mistag.mjs

41 lines

// (a) Reversibly strip 'Flock Velvet' tag from mis-tagged NON-flock products.
// Non-flock = title/handle/productType does NOT contain flock/flocked/velvet. Preserve all other tags.
// Snapshot tags before, ledger each with re-add undo. --apply to write.
import fs from 'fs'; import os from 'os'; import path from 'path';
const SHOP='designer-laboratory-sandbox.myshopify.com';
const TOKEN=fs.readFileSync(new URL('./.token',import.meta.url),'utf8').trim();
const LEDGER=path.join(os.homedir(),'.claude/yolo-queue/executed-reversible/ledger.jsonl');
const SNAP=new URL('./strip-mistag-snapshot.json',import.meta.url);
const DRY=!process.argv.includes('--apply');
const API=`https://${SHOP}/admin/api/2024-10`;
const H={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const gql=async(q,v={})=>(await (await fetch(`${API}/graphql.json`,{method:'POST',headers:H,body:JSON.stringify({query:q,variables:v})})).json());
const Q=`query($c:String){ products(first:100, query:"tag:'Flock Velvet' -vendor:'Phillipe Romano'", after:$c){
  pageInfo{hasNextPage endCursor}
  edges{node{ id handle title vendor productType status tags }}}}`;
let cur=null,all=[];
do{const r=await gql(Q,{c:cur});const p=r?.data?.products;if(!p){console.error(JSON.stringify(r));break;}all=all.concat(p.edges);cur=p.pageInfo.hasNextPage?p.pageInfo.endCursor:null;}while(cur);
// genuine flock signal: title/handle/type mentions flock OR velvet. Only strip when NEITHER present.
const flockish=n=>/flock|velvet/i.test(n.title||'')||/flock|velvet/i.test(n.handle||'')||/flock|velvet/i.test(n.productType||'');
const norm=t=>String(t).replace(/["{}]/g,'').trim();
const FLOCKTAG=t=>{const s=norm(t).toLowerCase();return s==='flock velvet'||s==='material-group-flock-velvet';};
const targets=all.map(e=>e.node).filter(n=>!flockish(n) && (n.tags||[]).some(FLOCKTAG));
console.log(`mis-tagged non-flock (no flock/velvet in title/handle/type): ${targets.length}`);
const snap=[];
let done=0;
for(const n of targets){
  const tags=(n.tags||[]).map(norm);
  const kept=tags.filter(t=>!FLOCKTAG(t));
  if(DRY){ console.log(`  ${n.vendor.padEnd(24)} ${n.status.padEnd(8)} ${n.title.slice(0,44)}`); done++; continue; }
  const r=await gql(`mutation($id:ID!,$tags:[String!]!){productUpdate(input:{id:$id,tags:$tags}){userErrors{message}}}`,{id:n.id,tags:kept});
  const errs=r?.data?.productUpdate?.userErrors;
  if(errs&&errs.length){console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
  snap.push({id:n.id,handle:n.handle,vendor:n.vendor,oldTags:tags});
  fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'vp-dw-commerce:strip-mistag-flock',ticket:'TK-11128',action:`strip 'Flock Velvet' tag from ${n.handle} [${n.vendor}] (non-flock)`,blast_radius:1,undo_cmd:`productUpdate ${n.id} re-add 'Flock Velvet' tag`,verify:'product no longer tagged Flock Velvet'})+'\n');
  done++; if(done%15===0)console.log(`  …${done}/${targets.length}`);
  await sleep(200);
}
if(!DRY) fs.writeFileSync(SNAP,JSON.stringify(snap,null,1));
console.log(`${DRY?'DRY':'DONE'} · ${done}/${targets.length}`);