← back to Dwla Rebrand

scripts/build_dryrun.js

192 lines

#!/usr/bin/env node
// Dry-run builder for DWLA rebrand.
// Phase 1: 17 PI-PAMPAS-* registry rows + their 17 dup `-1` Shopify records.
// Output: preview/phase1.html + preview/phase1.csv + preview/phase1.json
// Reads dw_unified PG. Does NOT touch Shopify. Does NOT mutate registry.
//
// Run:   node scripts/build_dryrun.js [--phase 1]

const { Client } = require('pg');
const fs = require('fs');
const path = require('path');

const PHASE = parseInt((process.argv.find(a => a.startsWith('--phase=')) || '--phase=1').split('=')[1], 10);
const ROOT = path.join(__dirname, '..');
const PREVIEW_DIR = path.join(ROOT, 'preview');
fs.mkdirSync(PREVIEW_DIR, { recursive: true });

const NAME_MAP = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'la_name_map.json'), 'utf8')).map;

const NEXT_DWLA_FILE = path.join(ROOT, 'data', 'dwla_sequence.txt');
let nextDwla = 100001;
if (fs.existsSync(NEXT_DWLA_FILE)) nextDwla = parseInt(fs.readFileSync(NEXT_DWLA_FILE, 'utf8').trim(), 10);

function allocateDwla() {
  const sku = `DWLA-${String(nextDwla).padStart(6, '0')}`;
  nextDwla++;
  return sku;
}

function rebrandTitle(t) {
  if (!t) return t;
  let out = t;
  // Replace "| Phillipe Romano" suffix with "| Los Angeles Fabrics"
  out = out.replace(/\|\s*Phillipe Romano\s*$/i, '| Los Angeles Fabrics');
  out = out.replace(/\bPhillipe Romano\b/g, 'Los Angeles Fabrics');
  // Apply East-Coast → LA name map (longest-first match to avoid "Bar Harbor" matching "Bar")
  const keys = Object.keys(NAME_MAP).sort((a, b) => b.length - a.length);
  for (const k of keys) {
    if (NAME_MAP[k] === k) continue;
    const re = new RegExp(`\\b${k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g');
    out = out.replace(re, NAME_MAP[k]);
  }
  return out;
}

// Option (b) — handle is derived from the new title, NOT from the old handle.
// New handle = slug(pattern) + "-" + slug(colorway) + "-los-angeles-fabrics"
// Pattern + colorway come from the title structure: "Pampas - Aqua Sustainable Bio-Based | Phillipe Romano"
function buildHandleFromTitle(title, colorway, isDup) {
  if (!title) return '';
  // Pattern is whatever comes before the first " - "
  const pattern = (title.split(' - ')[0] || '').trim();
  const slug = (s) => s.toLowerCase()
    .replace(/['']/g, '')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
  const base = `${slug(pattern)}-${slug(colorway)}-los-angeles-fabrics`;
  return isDup ? `${base}-1` : base;
}

async function main() {
  const c = new Client({ host: '/tmp', database: 'dw_unified' });
  await c.connect();

  // Phase 1 = the 17 PI-PAMPAS-* rows + their corresponding dup Shopify products
  const registryRows = (await c.query(`
    SELECT id, dw_sku, vendor_prefix, vendor_name, mfr_sku, shopify_product_id
    FROM dw_sku_registry
    WHERE mfr_sku LIKE 'PI-PAMPAS-%'
    ORDER BY mfr_sku
  `)).rows;

  // For each registry row, find ALL Shopify products whose title matches the colorway
  const records = [];
  for (const r of registryRows) {
    const colorway = r.mfr_sku.replace(/^PI-PAMPAS-/, '').toLowerCase();
    const colorwayPretty = colorway.charAt(0).toUpperCase() + colorway.slice(1);
    const shopify = (await c.query(`
      SELECT id, handle, title, sku, vendor
      FROM shopify_products
      WHERE vendor = 'Phillipe Romano'
        AND title ILIKE '%pampas%spring%' = false  -- placeholder; replaced below
        AND (title ILIKE $1 OR title ILIKE $2)
      ORDER BY id
    `.replace("title ILIKE '%pampas%spring%' = false  -- placeholder; replaced below\n        AND ", ""),
      [`%pampas%${colorwayPretty}%`, `%pampas%${colorway}%`])).rows;

    const dwlaSku = allocateDwla();
    records.push({
      registry: r,
      colorway: colorwayPretty,
      proposed_dwla_sku: dwlaSku,
      shopify_products: shopify.map(s => {
        const isDup = s.handle.endsWith('-1');
        const newTitle = rebrandTitle(s.title);
        return {
          old_id: s.id,
          old_handle: s.handle,
          old_sku: s.sku,
          old_vendor: s.vendor,
          old_title: s.title,
          new_handle: buildHandleFromTitle(newTitle, colorwayPretty, isDup),
          new_sku: dwlaSku,
          new_vendor: 'Los Angeles Fabrics',
          new_title: newTitle,
          action: isDup ? 'archive_duplicate' : 'rename_canonical'
        };
      })
    });
  }

  await c.end();

  // Persist allocated sequence
  fs.writeFileSync(NEXT_DWLA_FILE, String(nextDwla));

  // Write JSON
  const outJson = path.join(PREVIEW_DIR, `phase${PHASE}.json`);
  fs.writeFileSync(outJson, JSON.stringify(records, null, 2));

  // Write CSV
  const outCsv = path.join(PREVIEW_DIR, `phase${PHASE}.csv`);
  const csvLines = ['action,registry_dw_sku,registry_mfr_sku,proposed_dwla_sku,shopify_id,old_handle,new_handle,old_sku,new_sku,old_vendor,new_vendor,old_title,new_title'];
  for (const rec of records) {
    for (const sp of rec.shopify_products) {
      csvLines.push([
        sp.action, rec.registry.dw_sku, rec.registry.mfr_sku, rec.proposed_dwla_sku,
        sp.old_id, sp.old_handle, sp.new_handle,
        sp.old_sku, sp.new_sku, sp.old_vendor, sp.new_vendor,
        JSON.stringify(sp.old_title), JSON.stringify(sp.new_title)
      ].join(','));
    }
  }
  fs.writeFileSync(outCsv, csvLines.join('\n'));

  // Write HTML
  const outHtml = path.join(PREVIEW_DIR, `phase${PHASE}.html`);
  let html = `<!doctype html>
<html><head><meta charset="utf-8"><title>DWLA Rebrand · Phase ${PHASE} preview</title>
<style>
body{font:13px/1.45 -apple-system,Segoe UI,Helvetica,Arial,sans-serif;margin:24px;background:#fafaf7;color:#222}
h1{font-weight:600;font-size:22px}
.meta{color:#666;margin-bottom:24px}
table{border-collapse:collapse;width:100%;margin:14px 0;font-size:12px}
th,td{padding:6px 8px;border-bottom:1px solid #eee;vertical-align:top;text-align:left}
th{background:#f0ede4;font-weight:600;color:#444}
.action-archive{color:#a44;font-weight:500}
.action-canonical{color:#262;font-weight:500}
.old{color:#888;text-decoration:line-through;font-family:ui-monospace,monospace;font-size:11px}
.new{color:#262;font-family:ui-monospace,monospace;font-size:11px}
.sku{font-family:ui-monospace,monospace}
.warn{background:#fffbe7;border:1px solid #f0e1a8;padding:10px 12px;margin:12px 0;border-radius:6px}
.ok{background:#e9f6ec;border:1px solid #b3dfb8;padding:10px 12px;margin:12px 0;border-radius:6px}
</style></head><body>
<h1>DWLA Rebrand · Phase ${PHASE} dry-run preview</h1>
<div class="meta">Generated ${new Date().toISOString()} · ${records.length} registry rows · ${records.reduce((a,r)=>a+r.shopify_products.length,0)} Shopify products affected</div>
<div class="warn">
  <strong>NO Shopify writes have happened.</strong> This is a dry-run. Approve below to generate <code>push_phase${PHASE}.js</code>; review that before execution.
</div>`;

  for (const rec of records) {
    html += `<h3>${rec.registry.dw_sku} · ${rec.registry.mfr_sku} · → <span class="sku">${rec.proposed_dwla_sku}</span></h3>`;
    if (rec.shopify_products.length === 0) {
      html += `<p style="color:#a44">No Shopify products matched.</p>`;
      continue;
    }
    html += `<table><tr><th>Action</th><th>Shopify ID</th><th>Old → New handle</th><th>Old → New SKU</th><th>Title</th></tr>`;
    for (const sp of rec.shopify_products) {
      const cls = sp.action === 'archive_duplicate' ? 'action-archive' : 'action-canonical';
      html += `<tr>
<td class="${cls}">${sp.action}</td>
<td>${sp.old_id}</td>
<td><div class="old">${sp.old_handle}</div><div class="new">${sp.new_handle}</div></td>
<td><div class="old">${sp.old_sku}</div><div class="new">${sp.new_sku}</div></td>
<td><div class="old">${sp.old_title}</div><div class="new">${sp.new_title}</div></td>
</tr>`;
    }
    html += `</table>`;
  }
  html += `<div class="ok"><strong>Next step:</strong> review the diffs above. If all looks correct, reply "GO phase 1" and I'll write <code>push_phase1.js</code> for your final approval before any Shopify call.</div></body></html>`;
  fs.writeFileSync(outHtml, html);

  console.log(`✓ phase ${PHASE} preview written`);
  console.log(`  ${outJson}`);
  console.log(`  ${outCsv}`);
  console.log(`  ${outHtml}`);
  console.log(`  ${records.length} registry rows · ${records.reduce((a,r)=>a+r.shopify_products.length,0)} Shopify products`);
  console.log(`  next DWLA seq: ${nextDwla}`);
}

main().catch(e => { console.error(e); process.exit(1); });