← back to Dw Rotation Activator
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));