← back to Dwjs Consolidation 2026 04 23

dwjs_mfr_dryrun.js

130 lines

// Dry-run: for each of the 893 newly-resolved DWJS products, fetch current custom.manufacturer_sku
// from Shopify, diff against the planned new value, and write dwjs_changes.json.
// No mutations.
const fs = require('fs');
const path = require('path');

const TOKEN=(process.env.SHOPIFY_ADMIN_TOKEN || '');
const SHOP='designer-laboratory-sandbox.myshopify.com';
const API=`https://${SHOP}/admin/api/2024-10/graphql.json`;

const RESOLVED_CSV = path.join(__dirname, 'dwjs_mfr_resolved_v2_2026-04-29.csv');
const OUT_CHANGES  = path.join(__dirname, 'dwjs_changes.json');

function parseCsv(file) {
  const text = fs.readFileSync(file, 'utf8').replace(/\r\n/g,'\n');
  const lines = text.split('\n').filter(Boolean);
  const headers = lines.shift().split(',');
  return lines.map(line => {
    // simple split — our CSV has no commas in quoted fields except titles
    // handle quoted fields properly
    const out = []; let cur=''; let inQ=false;
    for (let i=0;i<line.length;i++) {
      const c = line[i];
      if (c === '"') { inQ = !inQ; continue; }
      if (c === ',' && !inQ) { out.push(cur); cur=''; continue; }
      cur += c;
    }
    out.push(cur);
    return Object.fromEntries(headers.map((h,i) => [h, out[i] ?? '']));
  });
}

async function gql(q, vars, retries=3) {
  for (let i=0;i<retries;i++) {
    try {
      const r = await fetch(API, {
        method:'POST',
        headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},
        body: JSON.stringify({ query: q, variables: vars }),
      });
      const j = await r.json();
      if (j.errors && j.errors.some(e => /THROTTLED|throttled/.test(JSON.stringify(e)))) {
        await new Promise(r=>setTimeout(r, 2000*(i+1))); continue;
      }
      return j;
    } catch (e) {
      if (i === retries-1) throw e;
      await new Promise(r=>setTimeout(r, 1000*(i+1)));
    }
  }
}

(async () => {
  const rows = parseCsv(RESOLVED_CSV);
  console.log(`Loaded ${rows.length} resolved rows from ${path.basename(RESOLVED_CSV)}`);
  // Build planned map: product_id -> {sku,title,planned_mfr}
  const planned = new Map();
  for (const r of rows) {
    planned.set(r.product_id, { sku: r.current_sku, title: r.title, planned: r.fmpro_mfr });
  }
  const ids = [...planned.keys()];

  // Batch-fetch current custom.manufacturer_sku
  const BATCH = 50;
  const current = new Map(); // pid -> string|null
  const missing = [];
  for (let i=0; i<ids.length; i+=BATCH) {
    const batch = ids.slice(i, i+BATCH);
    const q = `query($ids:[ID!]!){
      nodes(ids: $ids) {
        ... on Product {
          id status vendor
          mfr: metafield(namespace:"custom", key:"manufacturer_sku") { value }
        }
      }
    }`;
    const j = await gql(q, { ids: batch });
    const nodes = j.data?.nodes || [];
    if (j.errors) console.error('GQL errors:', JSON.stringify(j.errors).slice(0,400));
    for (let k=0;k<batch.length;k++) {
      const n = nodes[k];
      if (!n) { missing.push(batch[k]); continue; }
      current.set(batch[k], { value: n.mfr?.value || '', status: n.status, vendor: n.vendor });
    }
    process.stdout.write(`  fetched ${Math.min(i+BATCH, ids.length)}/${ids.length}\r`);
  }
  console.log('');

  // Build changes
  let alreadyCorrect=0, willUpdate=0, missingProduct=0, statusArchived=0, vendorMismatch=0;
  const changes = [];
  for (const pid of ids) {
    const p = planned.get(pid);
    const c = current.get(pid);
    if (!c) { missingProduct++; continue; }
    if (c.status === 'ARCHIVED') statusArchived++;            // count, but still update
    if (c.vendor !== 'Jeffrey Stevens') vendorMismatch++;     // count, but still update (sanity)
    if ((c.value || '') === p.planned) { alreadyCorrect++; continue; }
    willUpdate++;
    changes.push({
      id: pid,
      title: p.title,
      status: c.status,
      vendor: c.vendor,
      sku: p.sku,
      currentMfr: c.value || '',
      newMfr: p.planned,
    });
  }

  console.log('\nDry-run results:');
  console.log(`  total resolved rows:       ${ids.length}`);
  console.log(`  product not found in shop: ${missingProduct}`);
  console.log(`  already correct:           ${alreadyCorrect}`);
  console.log(`  WILL UPDATE:               ${willUpdate}`);
  console.log(`  status=ARCHIVED (info):    ${statusArchived}`);
  console.log(`  vendor != JS  (info):      ${vendorMismatch}`);
  if (missing.length) console.log(`  product_id missing from nodes() response: ${missing.length}`);

  if (changes.length) {
    console.log(`\nFirst 10 planned changes:`);
    changes.slice(0,10).forEach(c => {
      console.log(` - ${c.sku.padEnd(22)} cur=${(c.currentMfr||'(none)').padEnd(18)} → new=${c.newMfr.padEnd(12)}  '${c.title.slice(0,55)}'`);
    });
  }
  fs.writeFileSync(OUT_CHANGES, JSON.stringify(changes, null, 2));
  console.log(`\nWrote ${changes.length} change rows to ${path.basename(OUT_CHANGES)}`);
  console.log(`(No Shopify mutations were performed.)`);
})();