← back to Dw Activation Debug TK11314

out/cleanup/verify-tier1.js

57 lines

'use strict';
/* TK-10446 Tier-1 live verification (READ-ONLY).
 * For each of the 943 shopify_ids the rotation published in the last 7d, query
 * LIVE Shopify variants and classify:
 *   A = true orphan (ACTIVE, only sample variant, no variant priced > $4.25) → safe to revert to DRAFT
 *   B = has a sellable roll variant (real product) → do NOT revert
 *   inactive = not currently ACTIVE (already pruned/reverted)
 *   gone = product not found
 * Uses curl via child_process (node fetch is blocked in this sandbox; curl works).
 * Writes out/cleanup/tier1-verified.json. NO writes to Shopify.
 */
const fs = require('fs');
const { execFileSync } = require('child_process');
const SAMPLE = 4.25;
const ENV = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
const TOKEN = (ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1];
const URL = 'https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
const ids = fs.readFileSync(process.argv[2], 'utf8').trim().split('\n').filter(Boolean);

function q(gid) {
  const body = JSON.stringify({ query: `{ product(id:"${gid}"){ status title vendor variants(first:40){ nodes{ sku price title } } } }` });
  for (let a = 0; a < 5; a++) {
    try {
      const out = execFileSync('curl', ['-s', '--max-time', '25', '-X', 'POST', URL,
        '-H', `X-Shopify-Access-Token: ${TOKEN}`, '-H', 'Content-Type: application/json', '--data', body],
        { encoding: 'utf8', maxBuffer: 4e6 });
      const j = JSON.parse(out);
      if (j.errors && JSON.stringify(j.errors).includes('THROTTLED')) { sleep(1500); continue; }
      return j.data && j.data.product;
    } catch (e) { sleep(1000 * (a + 1)); }
  }
  return undefined;
}
function sleep(ms){ try{ execFileSync('sleep', [String(ms/1000)]); }catch{} }

const res = { A: [], B: [], inactive: [], gone: [] };
let n = 0;
for (const gid of ids) {
  const p = q(gid);
  n++;
  if (p === undefined) { res.gone.push({ gid }); }
  else if (!p) { res.gone.push({ gid }); }
  else if (String(p.status).toUpperCase() !== 'ACTIVE') { res.inactive.push({ gid, status: p.status }); }
  else {
    const vs = p.variants.nodes;
    const hasRoll = vs.some(v => parseFloat(v.price) > SAMPLE);
    const rec = { gid, title: p.title, vendor: p.vendor, variants: vs.length };
    if (hasRoll) res.B.push(rec); else res.A.push(rec);
  }
  if (n % 100 === 0) process.stderr.write(`  ...${n}/${ids.length}  A=${res.A.length} B=${res.B.length} inactive=${res.inactive.length} gone=${res.gone.length}\n`);
}
const summary = { generated_at: new Date().toISOString(), total: ids.length,
  A_true_orphan_revert: res.A.length, B_has_roll_leave: res.B.length,
  inactive: res.inactive.length, gone: res.gone.length };
fs.writeFileSync(__dirname + '/tier1-verified.json', JSON.stringify({ summary, ...res }, null, 2));
console.log(JSON.stringify(summary, null, 2));