← back to Designerwallcoverings
chore: lint, refactor, v0.1.2 (session close) — DRY build-payloads (SAMPLE_PRICE/RE_BRAND/patLabel), visible dbWriteBack + pid/handle safety belts, fail-loud drip.sh
8edf5abf73ca7399dcfbb5680595e43c52738636 · 2026-07-26 20:39:03 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M package.jsonM scripts/maharam-onboard/build-payloads.mjsM scripts/maharam-onboard/create-drafts.mjsM scripts/maharam-onboard/go-live.mjsM scripts/maharam-onboard/maharam-daily-drip.sh
Diff
commit 8edf5abf73ca7399dcfbb5680595e43c52738636
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 26 20:39:03 2026 -0700
chore: lint, refactor, v0.1.2 (session close) — DRY build-payloads (SAMPLE_PRICE/RE_BRAND/patLabel), visible dbWriteBack + pid/handle safety belts, fail-loud drip.sh
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
package.json | 2 +-
scripts/maharam-onboard/build-payloads.mjs | 17 ++++++++++-------
scripts/maharam-onboard/create-drafts.mjs | 5 +++--
scripts/maharam-onboard/go-live.mjs | 9 ++++++---
scripts/maharam-onboard/maharam-daily-drip.sh | 9 +++++----
5 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/package.json b/package.json
index 64c9e07..b2de913 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "designerwallcoverings-ai",
- "version": "0.1.1",
+ "version": "0.1.2",
"private": true,
"description": "designerwallcoverings.ai — AI room-render landing. Upload a room photo, get matching wallcoverings.",
"main": "server.js",
diff --git a/scripts/maharam-onboard/build-payloads.mjs b/scripts/maharam-onboard/build-payloads.mjs
index 3863ba0..239932a 100644
--- a/scripts/maharam-onboard/build-payloads.mjs
+++ b/scripts/maharam-onboard/build-payloads.mjs
@@ -28,10 +28,15 @@ const OUT = path.join(HERE, 'out');
const DB = 'postgresql:///dw_unified?host=/tmp';
const BRAND = 'Maharam';
+const SAMPLE_PRICE = '4.25'; // DW standard memo sample
+const RE_BRAND = /Maharam/i;
+
const titleCase = s => (s || '').replace(/\w\S*/g, w => w[0].toUpperCase() + w.slice(1).toLowerCase()).trim();
// "Wallpaper" is banned in customer-facing text → always "Wallcovering"
const clean = s => (s || '').replace(/wall\s*paper/ig, 'Wallcovering').replace(/\s+/g, ' ').trim();
const isBadWord = s => !s || /unknown/i.test(s);
+// Resolved display name for the pattern (never "Unknown"/empty); shared by titleFor + bodyHtml.
+const patLabel = r => { let p = titleCase(clean(r.pattern_name)); return isBadWord(p) ? titleCase(clean(r.mfr_sku)) : p; };
function rows() {
const json = execSync(`psql "${DB}" -tAc "select coalesce(json_agg(row_to_json(t))::text,'[]') from (
@@ -46,8 +51,7 @@ function rows() {
}
function titleFor(r) {
- let pat = titleCase(clean(r.pattern_name));
- if (isBadWord(pat)) pat = titleCase(clean(r.mfr_sku)); // never "Unknown"/empty
+ const pat = patLabel(r);
const rawCol = clean(r.color_name);
const col = rawCol && !isBadWord(rawCol) && rawCol.toLowerCase() !== (r.pattern_name || '').toLowerCase()
? titleCase(rawCol) : '';
@@ -55,7 +59,7 @@ function titleFor(r) {
}
function tags(r) {
const t = new Set([BRAND, 'Wallcovering', 'Commercial', 'quotes']);
- if (r.collection && !new RegExp(BRAND, 'i').test(r.collection)) t.add(titleCase(r.collection));
+ if (r.collection && !RE_BRAND.test(r.collection)) t.add(titleCase(r.collection));
if (r.material) t.add(titleCase(r.material.split(',')[0]));
return [...t].filter(Boolean).join(', ');
}
@@ -70,10 +74,9 @@ function bodyHtml(r) {
['Pattern', clean(r.pattern_name)], ['Color', r.color_name], ['Material', r.material],
['Width', r.width], ['Length', r.length], ['Repeat', repeatStr(r)],
['Finish', r.finish], ['Fire Rating', r.fire_rating], ['Application', r.application],
- ['Manufacturer SKU', r.mfr_sku], ['Collection', new RegExp(BRAND, 'i').test(r.collection || '') ? '' : r.collection],
+ ['Manufacturer SKU', r.mfr_sku], ['Collection', RE_BRAND.test(r.collection || '') ? '' : r.collection],
].filter(([, v]) => v != null && String(v).trim() !== '' && !isBadWord(String(v)));
- let pat = titleCase(clean(r.pattern_name)); if (isBadWord(pat)) pat = titleCase(clean(r.mfr_sku));
- return `<p>${pat} from ${BRAND} — a high-performance commercial wallcovering. Request a $4.25 memo sample to see the material in hand; <strong>call or email for a trade quote</strong> on full rolls.</p>`
+ return `<p>${patLabel(r)} from ${BRAND} — a high-performance commercial wallcovering. Request a $${SAMPLE_PRICE} memo sample to see the material in hand; <strong>call or email for a trade quote</strong> on full rolls.</p>`
+ `<table>${rowsHtml.map(([k, v]) => `<tr><td><strong>${k}</strong></td><td>${clean(String(v))}</td></tr>`).join('')}</table>`;
}
function metafields(r) {
@@ -112,7 +115,7 @@ function main() {
const gallery = galleryUrls(r);
if (gallery.length === 0) { heldNoImg++; continue; } // ≥1 image required
const sku = r.dw_sku; // DWAX-* already assigned in staging
- const samplePrice = (r.sample_price != null ? Number(r.sample_price) : 4.25).toFixed(2);
+ const samplePrice = (r.sample_price != null ? Number(r.sample_price) : Number(SAMPLE_PRICE)).toFixed(2);
const o = {
mfr_sku: r.mfr_sku, sku, dw_sku: r.dw_sku, quote: true,
image_url: gallery[0], gallery,
diff --git a/scripts/maharam-onboard/create-drafts.mjs b/scripts/maharam-onboard/create-drafts.mjs
index b03b578..906eae1 100644
--- a/scripts/maharam-onboard/create-drafts.mjs
+++ b/scripts/maharam-onboard/create-drafts.mjs
@@ -37,11 +37,12 @@ const loadJsonl = f => fs.existsSync(path.join(OUT, f))
// PG-FIRST write-back: stamp the real product_id onto the staging row immediately.
function dbWriteBack(sku, pid) {
+ const safePid = String(parseInt(pid, 10)); // numeric-only (Shopify id) — safety belt
try {
execFileSync(PSQL, ['-d', 'dw_unified', '-c',
- `UPDATE maharam_catalog SET shopify_product_id='${pid}', updated_at=NOW() WHERE dw_sku='${sku.replace(/'/g, "''")}';`],
+ `UPDATE maharam_catalog SET shopify_product_id='${safePid}', updated_at=NOW() WHERE dw_sku='${sku.replace(/'/g, "''")}';`],
{ env: PGENV, stdio: 'ignore' });
- } catch (e) { /* non-fatal — created.jsonl is the durable record; go-live re-writes it too */ }
+ } catch (e) { console.error(` ⚠ dbWriteBack ${sku}: ${String(e.message || e).slice(0, 120)}`); } // non-fatal but VISIBLE — created.jsonl is durable, go-live re-writes
}
async function attachImage(pid, url, filename) {
diff --git a/scripts/maharam-onboard/go-live.mjs b/scripts/maharam-onboard/go-live.mjs
index bcbaef9..2c0a71a 100644
--- a/scripts/maharam-onboard/go-live.mjs
+++ b/scripts/maharam-onboard/go-live.mjs
@@ -46,12 +46,15 @@ const M_PUBLISH = `mutation($id:ID!,$pubs:[PublicationInput!]!){ publishablePubl
const M_ACTIVE = `mutation($id:ID!){ productUpdate(input:{id:$id, status:ACTIVE}){ product{status} userErrors{message} } }`;
function dbWriteBack(sku, pid, handle) {
+ const safePid = String(parseInt(pid, 10)); // numeric-only (Shopify id)
+ const safeHandle = /^[a-z0-9-]+$/.test(handle || '') ? handle : null; // slug-safe or NULL
+ const s = sku.replace(/'/g, "''");
try {
execFileSync(PSQL, ['-d', 'dw_unified', '-c',
- `UPDATE maharam_catalog SET shopify_product_id='${pid}', updated_at=NOW() WHERE dw_sku='${sku.replace(/'/g, "''")}';
- UPDATE dw_sku_registry SET shopify_product_id='${pid}', shopify_handle=${handle ? `'${handle.replace(/'/g, "''")}'` : 'NULL'}, status='active', updated_at=NOW() WHERE dw_sku='${sku.replace(/'/g, "''")}';`],
+ `UPDATE maharam_catalog SET shopify_product_id='${safePid}', updated_at=NOW() WHERE dw_sku='${s}';
+ UPDATE dw_sku_registry SET shopify_product_id='${safePid}', shopify_handle=${safeHandle ? `'${safeHandle}'` : 'NULL'}, status='active', updated_at=NOW() WHERE dw_sku='${s}';`],
{ env: PGENV, stdio: 'ignore' });
- } catch (e) { /* non-fatal — Shopify write is authoritative; mirror re-syncs */ }
+ } catch (e) { console.error(` ⚠ dbWriteBack ${sku}: ${String(e.message || e).slice(0, 120)}`); } // non-fatal but VISIBLE — Shopify write is authoritative; mirror re-syncs
}
async function goLive(pid, sku) {
diff --git a/scripts/maharam-onboard/maharam-daily-drip.sh b/scripts/maharam-onboard/maharam-daily-drip.sh
index 5f13238..99df316 100755
--- a/scripts/maharam-onboard/maharam-daily-drip.sh
+++ b/scripts/maharam-onboard/maharam-daily-drip.sh
@@ -6,7 +6,7 @@
# the maharam-wallcoverings smart collection. Resumable + idempotent (skips live/created SKUs).
# Metered by the shared 'backlog' variant budget (budget.cjs) — never bursts, cooperates with the
# 5-field backlog drain. Held OUT of the shared hourly cadence via vendors.js mahDrip:true.
-set -u
+set -uo pipefail
# launchd runs with a minimal PATH — node (/usr/local/bin) + psql (homebrew) aren't on it.
export PATH="/usr/local/bin:/opt/homebrew/bin:/opt/homebrew/opt/postgresql@14/bin:$PATH"
DIR="$HOME/Projects/designerwallcoverings/scripts/maharam-onboard"
@@ -21,10 +21,11 @@ cd "$DIR" || { echo "FATAL: no $DIR"; exit 1; }
# refresh payloads from staging (cheap, idempotent — reflects any staging edits)
node build-payloads.mjs || { echo "build-payloads failed"; exit 1; }
-# 1) create drafts (budget-metered via 'backlog'; PG-first write-back inside)
-node create-drafts.mjs --apply --limit="$DAILY_CAP"
+# 1) create drafts (budget-metered via 'backlog'; PG-first write-back inside).
+# Budget-exhausted is a clean exit 0 (normal stop); a real failure exits non-zero → stop before go-live.
+node create-drafts.mjs --apply --limit="$DAILY_CAP" || { echo "create-drafts failed (rc=$?)"; exit 1; }
# 2) activate today's (and any leftover) drafts → auto-join the smart collection
-node go-live.mjs --apply --limit="$DAILY_CAP"
+node go-live.mjs --apply --limit="$DAILY_CAP" || { echo "go-live failed (rc=$?)"; exit 1; }
# quick tally
REMAIN=$(psql "postgresql:///dw_unified?host=/tmp" -tAc \
← 3dbd602 auto-save: 2026-07-26T20:15:38 (2 files) — package-lock.json
·
back to Designerwallcoverings
·
auto-save: 2026-07-26T20:45:50 (2 files) — package-lock.json b786056 →