← back to Dw Yolo Loop
artmura-site/showroom.js
107 lines
/**
* Showroom-only guard for the DW vendor-landing generator (TK-11200).
*
* Steve's standing rule (showroom-line-steward): a SHOWROOM-ONLY line — currently
* Phillip Jeffries — must stay "addressable but not discoverable". It may be reached
* by a direct /products/<handle> URL or on-site search, but must NOT appear on any
* push/browse/discovery surface. A dedicated public vendor microsite (a full browsable
* grid on its own subdomain) IS a discovery surface, so the generator must refuse to
* mint one for a showroom-only vendor.
*
* Self-contained by design: this file is rsync'd with the app on a BUNDLE=1 deploy, so
* the deployed microsite carries its own copy of the rule (no repo-relative ../ reads).
* config/showroom-vendors.json is kept in sync with the canonical list at
* ~/Projects/fix-live-board/config/showroom-vendors.json.
*
* Mirrors dw-domain-fleet/shared/catalog.js so both fleets speak the same rule.
*/
'use strict';
const fs = require('fs');
const path = require('path');
const SHOWROOM_VENDORS = (() => {
try {
const raw = JSON.parse(fs.readFileSync(path.join(__dirname, 'config', 'showroom-vendors.json'), 'utf8'));
return (Array.isArray(raw) ? raw : []).map(v => norm(v)).filter(Boolean);
} catch { return []; }
})();
// squashed forms, so a SLUG or a SUBDOMAIN label ("philipjeffries", "phillip-jeffries")
// matches the spaced vendor name ("Phillip Jeffries"). deploy-vendor.sh passes slugs.
const SHOWROOM_SQUASHED = SHOWROOM_VENDORS.map(v => v.replace(/ /g, ''));
// Normalize for comparison. Also collapses the well-known "Philip"/"Phillip" spelling
// split — the live catalog carries BOTH ("Phillip Jeffries" vendor, "philip-jeffries"
// handles), and a one-letter typo must not be a way past the guard.
function norm(v) {
return String(v || '')
.trim().toLowerCase()
.replace(/[^a-z0-9]+/g, ' ')
.replace(/philip/g, 'phillip') // single-L typo -> canonical; 'phillip' can't re-match
.replace(/\s+/g, ' ')
.trim();
}
// same normalization with ALL separators removed — matches slugs/subdomain labels.
function squash(v) { return norm(v).replace(/ /g, ''); }
/** true if a vendor NAME (string) is a showroom-only line */
function isShowroomVendorName(name) {
const n = norm(name), q = squash(name);
if (!n) return false;
return SHOWROOM_VENDORS.includes(n) || SHOWROOM_SQUASHED.includes(q);
}
/** true if a PRODUCT's vendor is a showroom-only line */
function isShowroomVendor(p) {
return isShowroomVendorName(p && p.vendor);
}
/**
* Product-level showroom TAG (TK-11307). A shared-vendor showroom line (e.g. MDC under
* the shared "Phillipe Romano" label, which also carries SELLABLE lines) is hidden by its
* "Showroom" tag WITHOUT hiding the vendor's sellable products. Exact tag, not a token.
*/
function isShowroomTagged(p) {
const tags = p && p.tags;
const arr = Array.isArray(tags) ? tags : (typeof tags === 'string' ? tags.split(',') : []);
return arr.some(t => String(t).trim().toLowerCase() === 'showroom');
}
/** showroom-only = vendor on the list OR carries the Showroom tag */
function isShowroomProduct(p) {
return isShowroomVendor(p) || isShowroomTagged(p);
}
/**
* The escape hatch. Building or serving a showroom line as a microsite is a deliberate,
* Steve-gated act — never an accident. ALLOW_SHOWROOM=1 opts in and always logs loudly.
*/
function showroomOverride() {
return process.env.ALLOW_SHOWROOM === '1';
}
/**
* Hard gate used by build-line.js / server.js / deploy-vendor.sh.
* Returns true when the caller may proceed; otherwise prints why and returns false.
*/
function assertNotShowroom(vendorName, what) {
if (!isShowroomVendorName(vendorName)) return true;
if (showroomOverride()) {
console.warn(`[showroom] ⚠ ALLOW_SHOWROOM=1 — proceeding with ${what} for SHOWROOM-ONLY vendor "${vendorName}". This publishes a discovery surface for a showroom line.`);
return true;
}
console.error(
`\n[showroom] REFUSED: "${vendorName}" is a SHOWROOM-ONLY line.\n` +
` ${what} would create a public, browsable discovery surface for it, which breaks the\n` +
` "addressable but not discoverable" rule (showroom-line-steward).\n` +
` Showroom-only vendors: ${SHOWROOM_VENDORS.join(', ') || '(none configured)'}\n` +
` If this is deliberate and Steve-approved, re-run with ALLOW_SHOWROOM=1.\n`
);
return false;
}
module.exports = {
SHOWROOM_VENDORS, SHOWROOM_SQUASHED, norm, squash,
isShowroomVendorName, isShowroomVendor, isShowroomTagged, isShowroomProduct,
showroomOverride, assertNotShowroom,
};