← back to Goldleafwallpaper
scripts/build-products.js
89 lines
#!/usr/bin/env node
/**
* build-products.js — regenerate data/products.json for goldleafwallpaper.com
*
* The site is a metal-leaf micro-storefront: it must show ONLY the Renaissance
* Metal Leaf line (Phillipe Romano, dw_sku prefix "RML-") — the gold & silver
* leaf wallcoverings — and nothing else. It previously carried a 1,957-product
* catalog dump from a dozen unrelated vendors, which was wrong.
*
* Source of truth: the local dw_unified mirror (host=/tmp socket). We read the
* ACTIVE RML- products only (ARCHIVED are excluded — never resurrect archived
* SKUs), then shape each row into the flat object the front end consumes.
*
* Every RML- product ships the same DW title ("Phillipe Romano - Renaissance
* Metal Leaf"), so we derive a distinct, honest per-card title from the real
* catalog tags: "<finish> Leaf — <colorway>" (e.g. "Gold Leaf — Brass").
* finish comes from the Gold/Silver/Bronze tag; colorway from the "color:X" tag.
* The linked DW product page and handle are left untouched.
*
* Usage: node scripts/build-products.js
*/
const { execFileSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const OUT = path.join(__dirname, '..', 'data', 'products.json');
// Pull the raw ACTIVE RML- rows as one JSON array straight from Postgres.
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, retail_price, price, 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());
// tags arrive as the Postgres text form: {"Architectural","color:Brass",...}
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']; // Silver first so silver-tagged win over a stray Gold
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 on sister sites.
return colorway ? `${finishLabel} · ${colorway}` : finishLabel;
}
const products = rows.map((r) => {
const tags = parseTags(r.tags);
return {
handle: r.handle,
title: deriveTitle(tags),
vendor: r.vendor, // "Phillipe Romano"
product_type: r.product_type || 'Wallcovering',
image_url: r.image_url || '',
retail_price: r.retail_price || '', // quote-only metal-leaf line
mfr_sku: r.mfr_sku || '',
dw_sku: r.dw_sku,
tags: tags.join(', '), // front end searches/filters a lowercased string
material: 'Metal Leaf',
};
});
fs.writeFileSync(OUT, JSON.stringify(products));
const finishCount = (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: ${finishCount('Gold Leaf')} Silver Leaf: ${finishCount('Silver Leaf')} Bronze Leaf: ${finishCount('Bronze Leaf')}`);