← back to Designerwallcoverings
data/hollywood-resku-20260813/resku-hollywood.mjs
101 lines
#!/usr/bin/env node
// Hollywood SKU leak fix — TK-10518 (2026-08-13)
// Migrates 287 HWC-* and XWJ-* products to opaque DWHD-100001+ SKUs.
// GATED: Shopify variant SKU write + dw_sku_registry insert + alias creation
// DRY-RUN by default; pass --apply to execute.
//
// Usage:
// node resku-hollywood.mjs # dry run
// node resku-hollywood.mjs --apply --yes-i-am-steve # live run
import fs from 'node:fs';
import { execFileSync } from 'node:child_process';
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
process.env.PATH = `/opt/homebrew/bin:/opt/homebrew/opt/postgresql@14/bin:/usr/local/bin:${process.env.PATH || ''}`;
const APPLY = process.argv.includes('--apply') && process.argv.includes('--yes-i-am-steve');
const CSV = new URL('./allocation.csv', import.meta.url).pathname;
const ENDPOINT = 'https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
const ENV = fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8');
const TOKEN = (ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
const sleep = ms => new Promise(r => setTimeout(r, ms));
const rows = fs.readFileSync(CSV, 'utf8').trim().split('\n').map(line => {
const [shopify_id, old_sku, new_sku, mfr_sku, handle] = line.split(',');
return { shopify_id, old_sku, new_sku, mfr_sku, handle };
});
console.log(`[hollywood-resku] ${rows.length} products to migrate. APPLY=${APPLY}`);
if (!APPLY) console.log(' DRY RUN — pass --apply --yes-i-am-steve to execute live Shopify writes.\n');
async function gql(q, v) {
for (let i = 0; i < 5; i++) {
const res = await fetch(ENDPOINT, {
method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
body: JSON.stringify({ query: q, variables: v })
});
if (res.status === 429 || res.status >= 500) { await sleep(2000 * (i + 1)); continue; }
const j = await res.json();
if (j.errors?.some(e => e.extensions?.code === 'THROTTLED')) { await sleep(2000 * (i + 1)); continue; }
return j;
}
throw new Error('gql exhausted');
}
// Step 1: Get variant IDs for each product (need variant.id to update variant SKU)
const GET_VARIANTS = `query($id:ID!){ product(id:$id){ variants(first:10){ nodes { id sku } } } }`;
// Step 2: Update variant SKU
const UPDATE_VARIANT = `mutation($id:ID!,$sku:String!){ productVariantUpdate(input:{id:$id,sku:$sku}){ productVariant{ id sku } userErrors{ message } } }`;
const ledger = fs.createWriteStream(new URL('./resku-ledger.jsonl', import.meta.url).pathname, { flags: 'a' });
let ok = 0, skip = 0, dryTotal = 0;
for (const row of rows) {
if (!APPLY) {
dryTotal++;
console.log(` [DRY] ${row.old_sku} → ${row.new_sku} (${row.handle})`);
if (dryTotal > 10) { console.log(` … and ${rows.length - 10} more`); break; }
continue;
}
// 1. Get variant ID
const pRes = await gql(GET_VARIANTS, { id: row.shopify_id });
const variants = pRes.data?.product?.variants?.nodes || [];
const targetVariant = variants.find(v => v.sku === row.old_sku) || variants[0];
if (!targetVariant) {
console.log(` SKIP ${row.old_sku}: no variant found`);
ledger.write(JSON.stringify({ ts: new Date().toISOString(), ...row, result: 'SKIP_NO_VARIANT' }) + '\n');
skip++; continue;
}
// 2. Update variant SKU
const vRes = await gql(UPDATE_VARIANT, { id: targetVariant.id, sku: row.new_sku });
const ue = vRes.data?.productVariantUpdate?.userErrors || [];
if (ue.length) {
console.log(` ERROR ${row.old_sku}: ${JSON.stringify(ue)}`);
ledger.write(JSON.stringify({ ts: new Date().toISOString(), ...row, result: 'ERROR', errors: ue }) + '\n');
skip++; continue;
}
// 3. Register in dw_sku_registry
execFileSync('psql', ['host=/tmp dbname=dw_unified', '-c',
`INSERT INTO dw_sku_registry(dw_sku,vendor_prefix,vendor_name,mfr_sku,shopify_product_id,shopify_handle)
VALUES('${row.new_sku}','DWHD','Hollywood Wallcoverings','${row.mfr_sku}','${row.shopify_id}','${row.handle}')
ON CONFLICT(dw_sku) DO NOTHING`
]);
// 4. Update dw_unified mirror
execFileSync('psql', ['host=/tmp dbname=dw_unified', '-c',
`UPDATE shopify_products SET dw_sku='${row.new_sku}', synced_at=now() WHERE shopify_id='${row.shopify_id}'`
]);
ledger.write(JSON.stringify({ ts: new Date().toISOString(), ...row, result: 'OK', variant_id: targetVariant.id }) + '\n');
console.log(` [OK] ${row.old_sku} → ${row.new_sku}`);
ok++; await sleep(150);
}
ledger.end();
if (APPLY) console.log(`\n[hollywood-resku] DONE: ok=${ok} skip=${skip}`);
else console.log(`\n[hollywood-resku] DRY RUN complete. ${rows.length} products would be migrated.`);