← back to Silverleafwallpaper
scripts/build-products.js
84 lines
#!/usr/bin/env node
/**
* build-products.js — regenerate data/products.json for silverleafwallpaper.com
*
* Like its gold sibling, this micro-storefront must show ONLY the Renaissance
* Metal Leaf line (Phillipe Romano, dw_sku prefix "RML-") — the gold & silver
* leaf wallcoverings. It previously carried 464 loosely-"metallic" products
* from unrelated vendors (Hollywood, LA Walls, generic DW), which was wrong.
*
* Source of truth: the local dw_unified mirror (host=/tmp socket). ACTIVE RML-
* rows only (ARCHIVED excluded). Output is shaped for BOTH consumers:
* • migrate-microsite-products.js (upserts into dw_unified.microsite_products,
* the live read path) — needs sku, title, vendor, product_type, handle,
* image_url, tags[] (array), max_price, product_url.
* • the static data/products.json fallback the server reads when PG is down.
*
* Titles are derived from real catalog tags ("Gold Leaf — Brass", etc.) because
* every RML- product ships the identical DW title.
*
* Usage: node scripts/build-products.js (then run migrate-microsite-products.js)
*/
const { execFileSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const OUT = path.join(__dirname, '..', 'data', 'products.json');
const SQL = `
select coalesce(json_agg(row_to_json(t) order by t.dw_sku), '[]'::json)
from (
select dw_sku, mfr_sku, title, vendor, handle, product_type, image_url, tags
from shopify_products
where dw_sku ilike 'RML-%' and status = 'ACTIVE'
) t;
`;
const raw = execFileSync('psql', ['-h', '/tmp', 'dw_unified', '-tAc', SQL], {
encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
});
const rows = JSON.parse(raw.trim());
function parseTags(tagStr) {
if (!tagStr) return [];
return String(tagStr)
.replace(/^\{|\}$/g, '')
.split(',')
.map((s) => s.trim().replace(/^"|"$/g, ''))
.filter(Boolean);
}
const FINISH_ORDER = ['Silver', 'Gold', 'Bronze'];
function deriveTitle(tags) {
const finish = FINISH_ORDER.find((f) => tags.includes(f));
const colorTag = tags.find((t) => /^color:/i.test(t));
const colorway = colorTag ? colorTag.replace(/^color:/i, '').trim() : '';
const finishLabel = finish ? `${finish} Leaf` : 'Renaissance Metal Leaf';
// Separator is a middot, NOT an em-dash: the fleet's api-vendor-redact
// scrubTitle() splits titles on | – — and drops trailing segments (it strips
// "Vendor — Pattern" leakage), which would eat the colorway. Middot survives.
return colorway ? `${finishLabel} · ${colorway}` : finishLabel;
}
const products = rows.map((r) => {
const tags = parseTags(r.tags);
return {
sku: r.dw_sku,
handle: r.handle,
title: deriveTitle(tags),
vendor: r.vendor,
product_type: r.product_type || 'Wallcovering',
image_url: r.image_url || '',
tags, // array — migrate + niche/color logic expect an array here
max_price: null, // quote-only metal-leaf line
product_url: `https://designerwallcoverings.com/products/${r.handle}#sample`,
mfr_sku: r.mfr_sku || '',
material: 'Metal Leaf',
};
});
fs.writeFileSync(OUT, JSON.stringify(products));
const n = (needle) => products.filter((p) => p.title.startsWith(needle)).length;
console.log(`Wrote ${products.length} RML- products → ${path.relative(process.cwd(), OUT)}`);
console.log(` Gold Leaf: ${n('Gold Leaf')} Silver Leaf: ${n('Silver Leaf')} Other: ${products.length - n('Gold Leaf') - n('Silver Leaf')}`);