← back to Koroseal Goodrich Audit
tk10347/apply_swaps.mjs
90 lines
#!/usr/bin/env node
// TK-10347 — apply 6 approved Koroseal cross-link image swaps on LIVE store.
// Steve APPROVED + UNGATED (2026-08-08). Idempotent-ish: skips upload if same source already present.
import fs from 'node:fs';
const ENV = fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env','utf8');
const TOKEN = (ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1]?.trim().replace(/^["']|["']$/g,'');
const STORE = 'designer-laboratory-sandbox.myshopify.com', API='2024-10';
const URL = `https://${STORE}/admin/api/${API}/graphql.json`;
const AK='https://koroseal.asset.akeneo.cloud';
const KM='https://koroseal.com';
const APPLY = process.argv.includes('--apply');
const JOBS = [
{sku:'TAK-EA02-04', pid:'gid://shopify/Product/7519950110771', mode:'upload',
src:`${AK}/images/media/Kilika_ROOMSET_TAK_EA02_04.jpg`, alt:'Kilika Deep Taupe TAK-EA02-04',
wrong:'gid://shopify/MediaImage/24773828247603'},
{sku:'TAK-EA01-01', pid:'gid://shopify/Product/7519949357107', mode:'upload',
src:`${AK}/images/media/TAKUMI_ROOMSET_TAK_EA01_01__WEAVES_.jpg`, alt:'Cyrus Weave Linen Natural TAK-EA01-01',
wrong:'gid://shopify/MediaImage/24773827788851'},
{sku:'TAK-CA05-01', pid:'gid://shopify/Product/7519948603443', mode:'upload',
src:`${AK}/images/media/TAKUMI_ROOMSET_TAK_CA05_01__CORK_.jpg`, alt:'Aura Cork Silver TAK-CA05-01',
wrong:'gid://shopify/MediaImage/24773827297331'},
{sku:'SRD-CH1-02', pid:'gid://shopify/Product/7634018566195', mode:'upload',
src:`${AK}/thumbnails/media/chess_1_gray_pink_srd_ch1_02__sarah_rowland_for_koroseal.jpg`, alt:'Chess 1 Gray Pink SRD-CH1-02',
wrong:'gid://shopify/MediaImage/26514726125619'},
{sku:'8221-08', pid:'gid://shopify/Product/7584922009651', mode:'upload',
src:`${AK}/thumbnails/media/ramie_woven_8221_08_.jpg`, alt:'Ramie Woven 8221-08',
wrong:'gid://shopify/MediaImage/26434620620851'},
{sku:'SRD-AUR-02', pid:'gid://shopify/Product/7634018074675', mode:'reorder',
keep:'gid://shopify/MediaImage/26514716131379', // existing AURORA_SMOKE_SRD-AUR-02
wrong:'gid://shopify/MediaImage/26514747752499'},
];
async function gql(query, variables){
const r = await fetch(URL, {method:'POST', headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},
body: JSON.stringify({query, variables})});
const j = await r.json();
if(j.errors) throw new Error('GraphQL errors: '+JSON.stringify(j.errors));
return j.data;
}
const sleep = ms => new Promise(r=>setTimeout(r,ms));
async function firstMediaId(pid){
const d = await gql(`query($id:ID!){ product(id:$id){ media(first:1){ edges{ node{ id } } } } }`, {id:pid});
return d.product.media.edges[0]?.node?.id;
}
async function run(){
console.log(`APPLY=${APPLY} token=${TOKEN? 'present':'MISSING'}`);
for(const j of JOBS){
console.log(`\n=== ${j.sku} (${j.mode}) ===`);
let newId = j.keep;
if(j.mode==='upload'){
if(!APPLY){ console.log(' DRY: would createMedia', j.src); }
else {
const d = await gql(`mutation($pid:ID!,$m:[CreateMediaInput!]!){ productCreateMedia(productId:$pid, media:$m){ media{ ... on MediaImage{ id status } } mediaUserErrors{ field message } } }`,
{pid:j.pid, m:[{originalSource:j.src, mediaContentType:'IMAGE', alt:j.alt}]});
const ue=d.productCreateMedia.mediaUserErrors; if(ue.length) throw new Error(j.sku+' createMedia: '+JSON.stringify(ue));
newId = d.productCreateMedia.media[0].id;
console.log(' created', newId, 'status', d.productCreateMedia.media[0].status);
// poll READY
for(let i=0;i<30;i++){
const p = await gql(`query($id:ID!){ node(id:$id){ ... on MediaImage{ status image{ url } } } }`, {id:newId});
const st=p.node.status;
if(st==='READY'){ console.log(' READY', (p.node.image?.url||'').slice(-70)); break; }
if(st==='FAILED') throw new Error(j.sku+' media FAILED');
await sleep(2000);
}
}
}
// reorder new/keep media to position 0 (featured)
if(!APPLY){ console.log(' DRY: would reorder', newId||'<newId>', '->0 and delete', j.wrong); continue; }
const rd = await gql(`mutation($id:ID!,$moves:[MoveInput!]!){ productReorderMedia(id:$id, moves:$moves){ mediaUserErrors{ message } job{ id done } } }`,
{id:j.pid, moves:[{id:newId, newPosition:'0'}]});
const rue=rd.productReorderMedia.mediaUserErrors; if(rue.length) throw new Error(j.sku+' reorder: '+JSON.stringify(rue));
console.log(' reordered ->0');
await sleep(2500);
// delete the wrong sibling
const dd = await gql(`mutation($pid:ID!,$ids:[ID!]!){ productDeleteMedia(productId:$pid, mediaIds:$ids){ deletedMediaIds mediaUserErrors{ message } } }`,
{pid:j.pid, ids:[j.wrong]});
const due=dd.productDeleteMedia.mediaUserErrors; if(due.length) throw new Error(j.sku+' delete: '+JSON.stringify(due));
console.log(' deleted wrong', dd.productDeleteMedia.deletedMediaIds);
// verify featured
const fm = await firstMediaId(j.pid);
console.log(' NOW featured media =', fm, fm===newId?'✓':'(check)');
}
console.log('\nDONE');
}
run().catch(e=>{ console.error('ERROR:', e.message); process.exit(1); });