[object Object]

← 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

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 →