← back to Hollywood Import
legacy-real-vendor/apply-real-vendor.mjs
54 lines
// apply-real-vendor.mjs — legacy Hollywood real_vendor correction (Steve-approved 2026-07-15).
// Reads apply-targets.csv (shopify_id,sku,new_vendor — generated per-product from the
// FileMaker vid map + HWC/newwall_catalog matches; see PROPOSAL.md) and sets
// dwc.real_vendor on each product. Same batched metafieldsSet pattern as ../fix-real-vendor.mjs.
// UNKNOWN-BM / DWX-FRS / long-tail rows are NOT in the targets file. $0 — Admin API only.
import fs from 'node:fs';
const env = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
const STORE = (env.match(/^SHOPIFY_STORE=(.+)$/m) || [])[1].trim();
const TOKEN = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1].trim();
const GQL = `https://${STORE}/admin/api/2024-10/graphql.json`;
const H = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' };
const APPLY = process.argv.includes('--apply');
const sleep = ms => new Promise(r => setTimeout(r, ms));
const M_SET = `mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){userErrors{field message}}}`;
const fileArg = (process.argv.find(a => a.startsWith('--file=')) || '').split('=')[1] || 'apply-targets.csv';
const csv = fs.readFileSync(new URL(fileArg, import.meta.url).pathname, 'utf8').trim().split('\n').slice(1);
const items = csv.map(line => {
const [gid, sku, ...rest] = line.split(',');
return { pid: Number(gid.match(/\d+$/)[0]), sku, vendor: rest.join(',') };
});
const byVendor = {};
for (const it of items) byVendor[it.vendor] = (byVendor[it.vendor] || 0) + 1;
console.log(`apply-real-vendor: ${items.length} products · ${APPLY ? 'APPLY' : 'DRY-RUN'}`);
console.log(Object.entries(byVendor).map(([v, n]) => ` ${v}: ${n}`).join('\n'));
if (!APPLY) { console.log('sample:', JSON.stringify(items.slice(0, 3))); process.exit(0); }
const audit = fs.createWriteStream(new URL('apply-audit.jsonl', import.meta.url).pathname, { flags: 'a' });
let done = 0, err = 0;
for (let i = 0; i < items.length; i += 25) {
const slice = items.slice(i, i + 25);
const batch = slice.map(it => ({
ownerId: `gid://shopify/Product/${it.pid}`,
namespace: 'dwc', key: 'real_vendor',
type: 'single_line_text_field', value: it.vendor,
}));
try {
const r = await fetch(GQL, { method: 'POST', headers: H, body: JSON.stringify({ query: M_SET, variables: { mf: batch } }) });
const j = await r.json();
if (j.errors) throw new Error(JSON.stringify(j.errors).slice(0, 200));
const ue = j.data.metafieldsSet.userErrors;
if (ue.length) { console.error(` batch@${i} userErrors: ${JSON.stringify(ue).slice(0, 200)}`); err += ue.length; }
done += batch.length - ue.length;
for (const it of slice) audit.write(JSON.stringify({ t: new Date().toISOString(), pid: it.pid, sku: it.sku, value: it.vendor }) + '\n');
if ((i / 25) % 10 === 0) console.log(` … ${Math.min(i + 25, items.length)}/${items.length}`);
await sleep(800);
} catch (e) {
console.error(` batch@${i} ERR: ${e.message.slice(0, 160)}`); err += batch.length; await sleep(1500);
}
}
audit.end();
console.log(`DONE: set=${done} err=${err}`);