← back to Dw Yolo Loop
scripts/fence-strip/fence-strip-apply.mjs
36 lines
// fence-strip-apply (c53) — APPLY the officer-CONFIRMED code-fence-strip to the 60
// leading-fence-leak products (Steve-authorized 2026-06-16). Per product: re-fetch
// fresh body, re-run guards (leading fence present, unwrap changes it, post-strip
// non-empty + no banned word), SAVE old body to rollback, then
// productUpdate(descriptionHtml=cleaned, seo.description=regen from cleaned).
import fs from 'fs';
const T=(fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1];
const API='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
async function gql(q,v){for(let a=0;a<6;a++){const r=await fetch(API,{method:'POST',headers:{'X-Shopify-Access-Token':T,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v||{}})});if(r.status===429){await sleep(2500);continue;}const j=await r.json();if(j.errors&&JSON.stringify(j.errors).match(/THROTTLED/)){await sleep(2500);continue;}return j;}throw new Error('throttled');}
function unwrap(b){let body=b;body=body.replace(/^\s*```[a-z]*\s*\n?/i,'');body=body.replace(/\n?\s*```\s*$/,'');body=body.replace(/```[a-z]*\s*([\s\S]*?)\s*```/gi,'$1');body=body.replace(/^\s*<code>([\s\S]*?)<\/code>\s*$/i,'$1');return body.trim();}
const txt=h=>(h||'').replace(/<[^>]+>/g,'').replace(/&[a-z]+;/g,' ').replace(/\s+/g,' ').trim();
const work=JSON.parse(fs.readFileSync(process.env.HOME+'/.claude/yolo-queue/fence-strip-dryrun-worklist.json','utf8')).filter(i=>i.changed);
console.log(`apply targets (changed, leading-fence): ${work.length}`);
const M=`mutation($id:ID!,$html:String!,$seo:String!){productUpdate(input:{id:$id,descriptionHtml:$html,seo:{description:$seo}}){userErrors{message}}}`;
const rollback=[]; let ok=0,skip=0,fail=0;
for(const it of work){
try{
const j=await gql(`{products(first:1,query:"handle:${it.handle}"){nodes{id descriptionHtml}}}`);
const p=j.data?.products?.nodes?.[0]; if(!p){skip++;console.log(` skip(not found): ${it.handle}`);continue;}
const old=p.descriptionHtml||'';
if(!/^\s*(```|<code>)/i.test(old)){skip++;console.log(` skip(no leading fence now): ${it.handle}`);continue;}
const cleaned=unwrap(old);
if(cleaned===old || txt(cleaned).length<3){skip++;console.log(` skip(no-op/empty): ${it.handle}`);continue;}
if(/\bwallpaper\b/i.test(txt(cleaned))){skip++;console.log(` skip(banned-word surfaced): ${it.handle}`);continue;}
const seo=txt(cleaned).slice(0,300);
rollback.push({id:p.id,handle:it.handle,old_descriptionHtml:old});
const r=await gql(M,{id:p.id,html:cleaned,seo}); const ue=r.data?.productUpdate?.userErrors;
if(ue&&ue.length)throw new Error(ue[0].message);
ok++; if(ok%15===0)console.log(` ${ok}...`);
}catch(e){fail++;console.log(` ✗ ${it.handle}: ${e.message.slice(0,70)}`);}
await sleep(300);
}
fs.writeFileSync(process.env.HOME+'/.claude/yolo-queue/fence-strip-ROLLBACK-2026-06-16.json',JSON.stringify(rollback,null,2));
console.log(`\nDONE: ${ok} stripped, ${skip} skipped(guard), ${fail} failed. Rollback saved: ${rollback.length}.`);