← back to Tk10640 Redirect Prune
rollback-recreate.mjs
25 lines
// TK-10640 — ROLLBACK: re-create redirects that apply-prune-to-home.mjs deleted.
// Reads data/done-prune-to-home.jsonl, re-creates every {deleted:true} row's path->target
// from the original dump. DRY-RUN default; --apply re-creates. GATED like the apply script.
// Shopify assigns a fresh GID on create; path is the functional key so behavior is restored.
import { readFileSync, existsSync } from 'node:fs';
const txt=readFileSync(`${process.env.HOME}/Projects/secrets-manager/.env`,'utf8');
const env={}; for(const l of txt.split('\n')){const m=l.match(/^([A-Z0-9_]+)=(.*)$/); if(m) env[m[1]]=m[2].replace(/^["']|["']$/g,'');}
const STORE=env.SHOPIFY_STORE_DOMAIN, TOKEN=env.SHOPIFY_FULL_ACCESS_TOKEN, API='2024-10';
const APPLY=process.argv.includes('--apply');
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
async function gql(q,v={}){for(let a=0;a<10;a++){try{const res=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOKEN},body:JSON.stringify({query:q,variables:v})});const tx=await res.text();let j;try{j=JSON.parse(tx);}catch{await sleep(1500*(a+1));continue;}if(j.errors){if(/THROTTLED/i.test(JSON.stringify(j.errors))){await sleep(1500*(a+1));continue;}throw new Error(JSON.stringify(j.errors));}return j.data;}catch(e){if(a<9){await sleep(1500*(a+1));continue;}throw e;}}}
const dump=readFileSync(`${process.env.HOME}/Projects/tk10640-redirect-prune/data/redirects-dump.jsonl`,'utf8').trim().split('\n').map(JSON.parse);
const byId=new Map(dump.map(r=>[r.id,r]));
const DONE=`${process.env.HOME}/Projects/tk10640-redirect-prune/data/done-prune-to-home.jsonl`;
if(!existsSync(DONE)){ console.log('no done-log yet — nothing deleted, nothing to roll back'); process.exit(0); }
const deleted=readFileSync(DONE,'utf8').trim().split('\n').filter(Boolean).map(JSON.parse).filter(x=>x.deleted);
const CRE=`mutation($r:UrlRedirectInput!){ urlRedirectCreate(urlRedirect:$r){ urlRedirect{ id } userErrors{ message } } }`;
let recreated=0,err=0;
for(const x of deleted){
const orig=byId.get(x.id)||{path:x.path,target:'/'};
if(APPLY){ const d=await gql(CRE,{r:{path:orig.path,target:orig.target||'/'}});
if(d.urlRedirectCreate.userErrors.length) err++; else recreated++; }
}
console.log(JSON.stringify({mode:APPLY?'APPLY':'DRY-RUN',deleted_in_log:deleted.length,recreated,errors:err},null,2));