← back to Greenland Onboard

scripts/reactivate-archived-cork.mjs

48 lines

import { execSync } from 'node:child_process';
import { readFileSync, appendFileSync, existsSync } from 'node:fs';
const TOKEN = execSync(`grep -E '^SHOPIFY_ADMIN_TOKEN=' ~/Projects/secrets-manager/.env | head -1 | cut -d= -f2-`, {encoding:'utf8'}).trim().replace(/^["']|["']$/g,'');
const SHOP='designer-laboratory-sandbox.myshopify.com';
const GQL=`https://${SHOP}/admin/api/2024-10/graphql.json`;
const PUB='gid://shopify/Publication/22208643184';
const IMGDIR='/Users/macstudio3/Projects/greenland-onboard/public/img';
const RUNFILE='/Users/macstudio3/Projects/greenland-onboard/backups/runs/reactivate-500210-500350.jsonl';
async function gql(q,v={}){for(let i=0;i<6;i++){const r=await fetch(GQL,{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){if(JSON.stringify(j.errors).includes('Throttled')){await new Promise(s=>setTimeout(s,2500));continue;}throw new Error(JSON.stringify(j.errors));}return j.data;}}
async function rest(path,opts={}){for(let i=0;i<6;i++){const r=await fetch(`https://${SHOP}/admin/api/2024-10${path}`,{method:opts.method||'GET',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:opts.body?JSON.stringify(opts.body):undefined});if(r.status===429){await new Promise(s=>setTimeout(s,2500));continue;}return {status:r.status, json: await r.json().catch(()=>({}))};}return {status:0,json:{}};}

const MODE=process.argv[2]||'canary';
const state=JSON.parse(readFileSync('/tmp/gl_shopify_state.json','utf8'));
const archived=JSON.parse(readFileSync('/tmp/gl_archived.json','utf8')).sort((a,b)=>a.sku>b.sku?1:-1);
const idBySku={}; for(const r of state.rows) if(r.sku) idBySku[r.sku]=r.id;
const canary='Cork-500210';
let targets = MODE==='canary'?archived.filter(a=>a.sku===canary): MODE==='rest'?archived.filter(a=>a.sku!==canary):archived;

for(const a of targets){
  const id=idBySku[a.sku]; const pid=id.split('/').pop();
  const before=await gql(`query{ product(id:"${id}"){ status vendor featuredImage{url} publishedOnPublication(publicationId:"${PUB}") } }`);
  const b=before.product;
  const title=a.title; const alt=`${title} — ${a.sku}`;
  // 1) attach local image (if none)
  let imgResult='had-image';
  if(!b.featuredImage){
    const f=`${IMGDIR}/${a.sku}.jpg`;
    if(!existsSync(f)){ console.log(`SKIP ${a.sku}: local image missing`); continue; }
    const b64=readFileSync(f).toString('base64');
    const r=await rest(`/products/${pid}/images.json`,{method:'POST',body:{image:{attachment:b64, filename:`${a.sku}.jpg`, alt}}});
    imgResult=(r.status===200||r.status===201)?'attached':`img-err-${r.status}`;
  }
  // 2) status ACTIVE
  const upd=await gql(`mutation($id:ID!){ productUpdate(input:{id:$id,status:ACTIVE}){ product{status} userErrors{message} } }`,{id});
  // 3) publish online
  const pub=await gql(`mutation($id:ID!,$pub:ID!){ publishablePublish(id:$id,input:{publicationId:$pub}){ userErrors{message} } }`,{id,pub:PUB});
  const after=await gql(`query{ product(id:"${id}"){ status vendor title featuredImage{url altText} publishedOnPublication(publicationId:"${PUB}") } }`);
  const af=after.product;
  const rec={ts:new Date().toISOString(),step:'image+activate',sku:a.sku,id,mfr:a.mfr,
    before:{status:b.status,online:b.publishedOnPublication,hadImage:!!b.featuredImage},
    after:{status:af.status,online:af.publishedOnPublication,hasImage:!!af.featuredImage,altText:af.featuredImage?.altText},
    imgResult, vendor:af.vendor, leak:/greenland/i.test(af.title)||/greenland/i.test(af.vendor||'')};
  appendFileSync(RUNFILE,JSON.stringify(rec)+'\n');
  console.log(`${a.sku}: img=${imgResult} ${b.status}/${b.publishedOnPublication?'on':'off'}->${af.status}/${af.publishedOnPublication?'on':'off'} hasImg=${!!af.featuredImage} vendor=${af.vendor} leak=${rec.leak}`);
  await new Promise(s=>setTimeout(s,400));
}
console.log(`\nDONE (${MODE}) -> ${RUNFILE}`);