← 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}`);