← back to Fentucci Naturals

scripts/reconcile-width-backfill.js

43 lines

// TK-34 width reconcile: set global.width="36 inches" + drop the Needs-Width tag
// on the 504 Fentucci/Tokiwa DRAFTs, so TK-00058's Needs-Width guard stops
// reverting them and go-live.py can trickle-activate. DRAFT products only —
// nothing customer-facing flips here. Idempotent. --commit to write (dry by default).
const fs = require('fs'), { execSync } = require('child_process');
const TOK = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8')
  .split('\n').find(l => l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=')[1];
const STORE = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
const DRY = !process.argv.includes('--commit');
const WIDTH = '36 inches';
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function gql(q, v) { for (let a = 0; a < 4; a++) { try {
  const r = await fetch(`https://${STORE}/admin/api/${API}/graphql.json`, { method: 'POST',
    headers: { 'X-Shopify-Access-Token': TOK, 'Content-Type': 'application/json' },
    body: JSON.stringify({ query: q, variables: v }) });
  const j = await r.json(); if (j.errors && a < 3) { await sleep(1500); continue; } return j;
} catch { await sleep(1500); } } return {}; }

// product IDs of the exact batch (DRAFT · image · quote-tag · fentucci)
const rows = execSync(`psql "host=/tmp dbname=dw_unified" -tAc "SELECT shopify_product_id FROM tokiwa_catalog WHERE shopify_product_id IS NOT NULL"`)
  .toString().trim().split('\n').map(s => s.trim()).filter(Boolean);

(async () => {
  console.log(`mode=${DRY ? 'DRY' : 'COMMIT'}  candidates=${rows.length}  width="${WIDTH}"`);
  let set = 0, untagged = 0, skip = 0, err = 0;
  for (const pid of rows) {
    const gid = `gid://shopify/Product/${pid}`;
    if (DRY) { set++; continue; }
    // set width metafield
    const mr = await gql(`mutation($mfs:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mfs){userErrors{message}}}`,
      { mfs: [{ ownerId: gid, namespace: 'global', key: 'width', type: 'single_line_text_field', value: WIDTH }] });
    const me = mr.data?.metafieldsSet?.userErrors || [];
    if (me.length) { err++; console.log(`  ERR width ${pid}: ${JSON.stringify(me)}`); } else set++;
    // drop Needs-Width tag
    const tr = await gql(`mutation($id:ID!,$t:[String!]!){tagsRemove(id:$id,tags:$t){userErrors{message}}}`,
      { id: gid, t: ['Needs-Width'] });
    if (!(tr.data?.tagsRemove?.userErrors || []).length) untagged++;
    if (set % 50 === 0) console.log(`  ...${set}/${rows.length}`);
    await sleep(250);
  }
  console.log(`DONE ${DRY ? '(DRY)' : ''}: width_set=${set} untagged=${untagged} skip=${skip} err=${err}`);
})();