← back to Dw Kravet Hires
scripts/verify-tk11740.mjs
67 lines
#!/usr/bin/env node
// READ-ONLY live verification for TK-11740: for each of the 84 applied products,
// query the current featured (position-0) IMAGE media and classify:
// hi-res-held = featured media id == new_media_id (the swap landed and is live)
// reverted = featured media id == old_media_id (self-healed / rolled back to 400px)
// other = featured is some third media (manual edit / new upload since)
// Zero writes. Uses the same secrets token + endpoint as apply-hires.mjs.
import fs from 'fs';
import os from 'os';
import path from 'path';
const PROJ = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
const LEDGER = path.join(PROJ, 'data/rollback-tk11740.jsonl');
function shopTok() {
const env = fs.readFileSync(path.join(os.homedir(), 'Projects/secrets-manager/.env'), 'utf8');
const admin = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1];
const full = (env.match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m) || [])[1];
const tok = admin || full;
if (!tok) { console.error('FATAL: no shopify token'); process.exit(2); }
return tok.trim();
}
const SHOP = 'designer-laboratory-sandbox.myshopify.com';
const TOK = shopTok();
async function shopify(query, variables) {
const r = await fetch(`https://${SHOP}/admin/api/2024-10/graphql.json`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': TOK },
body: JSON.stringify({ query, variables }),
});
const j = await r.json();
if (j.errors) throw new Error(JSON.stringify(j.errors).slice(0, 200));
return j.data;
}
const rows = fs.readFileSync(LEDGER, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
const tally = { held: 0, reverted: 0, other: 0, missing: 0, err: 0 };
const problems = [];
for (const r of rows) {
const gid = String(r.shopify_id).startsWith('gid://') ? r.shopify_id : `gid://shopify/Product/${r.shopify_id}`;
try {
const d = await shopify(
`query($id:ID!){ product(id:$id){ title media(first:25){ nodes{ id mediaContentType ... on MediaImage { image { width height } } } } } }`,
{ id: gid });
const p = d.product;
if (!p) { tally.missing++; problems.push(`${r.mfr_sku} MISSING product`); continue; }
const imgs = p.media.nodes.filter((n) => n.mediaContentType === 'IMAGE');
const feat = imgs[0];
if (!feat) { tally.missing++; problems.push(`${r.mfr_sku} no image media`); continue; }
const w = feat.image?.width || 0;
if (feat.id === r.new_media_id) tally.held++;
else if (feat.id === r.old_media_id) { tally.reverted++; problems.push(`${r.mfr_sku} REVERTED->400px (w=${w})`); }
else { tally.other++; problems.push(`${r.mfr_sku} OTHER featured id=${feat.id} w=${w}`); }
} catch (e) { tally.err++; problems.push(`${r.mfr_sku} ERR ${String(e).slice(0, 80)}`); }
}
console.log('=== TK-11740 live featured-image verification (84 products) ===');
console.log(`hi-res HELD : ${tally.held}/84`);
console.log(`reverted 400 : ${tally.reverted}/84`);
console.log(`other : ${tally.other}/84`);
console.log(`missing : ${tally.missing}/84`);
console.log(`errors : ${tally.err}/84`);
if (problems.length) { console.log('--- exceptions ---'); problems.forEach((p) => console.log(' ' + p)); }
const verdict = tally.held === rows.length ? 'PASS' : (tally.err || tally.missing ? 'WARN(unmeasured)' : 'FAIL');
console.log(`VERDICT: ${verdict}`);