← back to Dwjs Consolidation 2026 04 23

hollywood_mfr_apply.js

78 lines

// Apply: write real mfr SKU from fmpro into custom.manufacturer_sku for 2,786 Hollywood products.
const fs = require('fs');
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 STATE = __dirname + '/hollywood_apply_state.json';
const NDJSON = __dirname + '/hollywood_apply_execution.ndjson';

const changes = JSON.parse(fs.readFileSync(__dirname + '/hollywood_changes.json','utf8'));
console.log(`Loaded ${changes.length} changes to apply`);

const state = fs.existsSync(STATE) ? JSON.parse(fs.readFileSync(STATE,'utf8')) : { done: [] };
const done = new Set(state.done);
const todo = changes.filter(c => !done.has(c.id));
console.log(`Already done: ${done.size}, Remaining: ${todo.length}\n`);

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

const ndStream = fs.createWriteStream(NDJSON, { flags: 'a' });
const BATCH = 25;

(async () => {
  let totalOk=0, totalErr=0, batchNum=0;
  for (let i=0; i<todo.length; i+=BATCH) {
    batchNum++;
    const chunk = todo.slice(i, i+BATCH);
    const metafields = chunk.map(c => ({
      ownerId: c.id,
      namespace: 'custom',
      key: 'manufacturer_sku',
      type: 'single_line_text_field',
      value: c.newMfr,
    }));
    const q = `mutation set($mfs:[MetafieldsSetInput!]!) {
      metafieldsSet(metafields: $mfs) {
        metafields { id key }
        userErrors { field message }
      }
    }`;
    const j = await gql(q, { mfs: metafields });
    const ue = j.data?.metafieldsSet?.userErrors || [];
    const top = j.errors || [];
    if (ue.length || top.length) {
      totalErr += chunk.length;
      ndStream.write(JSON.stringify({ ts:new Date().toISOString(), batch:batchNum, error:true, ue, top })+'\n');
    } else {
      totalOk += chunk.length;
      chunk.forEach(c => done.add(c.id));
      chunk.forEach(c => ndStream.write(JSON.stringify({ ts:new Date().toISOString(), id:c.id, sku:c.sku, oldMfr:c.currentMfr, newMfr:c.newMfr })+'\n'));
    }
    fs.writeFileSync(STATE, JSON.stringify({ done:[...done] }));
    if (batchNum % 5 === 0 || i+BATCH >= todo.length) {
      console.log(`  batch ${batchNum}: ok=${totalOk} err=${totalErr} (${(((i+chunk.length)/todo.length)*100).toFixed(1)}%)`);
    }
  }
  ndStream.end();
  console.log(`\nDONE. ok=${totalOk} err=${totalErr}`);
})();