← back to Designer Wallcoverings
WallQuest onboard: gated Phillipe Romano draft-push (per-yard, settlement-filtered)
d4b939ce4e84cd56bd0dd0bfa5524542ae4ac5af · 2026-07-09 23:31:37 -0700 · Steve
push-drafts-book.cjs creates DRAFT products (status:draft): beach-city title, per-yard
pricing (min/step 8, 8-yd bolt metafields), Sample+Per-Yard variants, leak-safe LOCAL
swatch+patch-quilt-room images, mfr/private-label/spec metafields. SKIPS settlement-flagged
rows (Daisy 7 florals stay DRAFT). Idempotent (shopify_product_id), --dry-run validated:
LA 135 + Daisy 45 = 180 clean, 0 leaks. GATED live write — Steve runs via !.
Files touched
A scripts/wallquest-refresh/push-drafts-book.cjs
Diff
commit d4b939ce4e84cd56bd0dd0bfa5524542ae4ac5af
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 9 23:31:37 2026 -0700
WallQuest onboard: gated Phillipe Romano draft-push (per-yard, settlement-filtered)
push-drafts-book.cjs creates DRAFT products (status:draft): beach-city title, per-yard
pricing (min/step 8, 8-yd bolt metafields), Sample+Per-Yard variants, leak-safe LOCAL
swatch+patch-quilt-room images, mfr/private-label/spec metafields. SKIPS settlement-flagged
rows (Daisy 7 florals stay DRAFT). Idempotent (shopify_product_id), --dry-run validated:
LA 135 + Daisy 45 = 180 clean, 0 leaks. GATED live write — Steve runs via !.
---
scripts/wallquest-refresh/push-drafts-book.cjs | 88 ++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
diff --git a/scripts/wallquest-refresh/push-drafts-book.cjs b/scripts/wallquest-refresh/push-drafts-book.cjs
new file mode 100644
index 00000000..975210a5
--- /dev/null
+++ b/scripts/wallquest-refresh/push-drafts-book.cjs
@@ -0,0 +1,88 @@
+// Create Phillipe Romano DRAFT products for a WallQuest real-textile book. Per-yard pricing,
+// sample variant, beach-city title, leak-safe LOCAL images (swatch + patch-quilt room), metafields.
+// Settlement-flagged rows are SKIPPED. Idempotent (skips rows with shopify_product_id). Status=draft.
+// GATED live write — Steve runs via `!`. Use --dry-run first (no token, no network).
+// TABLE=lillian_august_catalog VIEW=<dir> node push-drafts-book.cjs --dry-run
+const fs = require('fs'), https = require('https'), { execSync } = require('child_process');
+const DRY = process.argv.includes('--dry-run');
+const STORE = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const TABLE = process.env.TABLE, VIEW = process.env.VIEW;
+const CONN = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+if (!TABLE || !VIEW) { console.error('need TABLE + VIEW'); process.exit(1); }
+if (!DRY && !TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN (use --dry-run)'); process.exit(1); }
+const psql = sql => execSync(`psql "${CONN}" -tAF'\u00a7' -c "${sql.replace(/"/g,'\\"')}"`, { encoding: 'utf8', maxBuffer: 1e9 });
+const clean = s => (s||'').replace(/\bWallpapers?\b/gi,'Wallcovering');
+const FORBID = /wallquest|lillian\s*august|carl\s*robinson|daisy\s*bennett|chesapeake|brewster/i;
+function rest(method, path, body) {
+ return new Promise((res, rej) => {
+ const data = body ? JSON.stringify(body) : null;
+ const req = https.request({ hostname: STORE, path: `/admin/api/${API}${path}`, method,
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}) }, timeout: 60000 },
+ r => { let d = ''; r.on('data', c => d += c); r.on('end', () => { try { res({ status: r.statusCode, j: d ? JSON.parse(d) : {} }); } catch { res({ status: r.statusCode, raw: d }); } }); });
+ req.on('error', rej); req.on('timeout', () => { req.destroy(); rej(new Error('timeout')); });
+ if (data) req.write(data); req.end();
+ });
+}
+const b64 = p => { try { return fs.readFileSync(p).toString('base64'); } catch { return null; } };
+
+const rows = psql(`SELECT dw_sku, pattern_name, color_name, material, our_price, COALESCE(ai_description,''),
+ COALESCE((local_image_path::jsonb)->>0,''), COALESCE((room_setting_images::jsonb)->>0,''),
+ mfr_sku, COALESCE(width,''), COALESCE(repeat_v,''), COALESCE(match_type,''),
+ COALESCE(ai_styles::text,'[]'), COALESCE(color_hex,''), COALESCE(shopify_product_id::text,''), COALESCE(settlement_flag,'')
+ FROM ${TABLE} ORDER BY dw_sku`).trim().split('\n').filter(Boolean).map(l => {
+ const [dw_sku,pattern,color,material,price,desc,img,room,mfr,width,repeat,match,styles,hex,pid,flag] = l.split('\u00a7');
+ return { dw_sku,pattern,color,material,price,desc,img,room,mfr,width,repeat,match,styles,hex,pid,flag };
+ });
+
+(async () => {
+ let created = 0, skip = 0, flagged = 0, already = 0, err = 0, leak = 0;
+ console.log(`${DRY?'[DRY-RUN] ':''}${TABLE}: ${rows.length} rows`);
+ for (const r of rows) {
+ if (r.flag) { flagged++; continue; } // settlement — DRAFT, don't push
+ if (r.pid) { already++; continue; } // idempotent
+ const title = clean(`${r.pattern}${r.color?' '+r.color:''} Wallcovering | Phillipe Romano`);
+ if (FORBID.test(`${title} ${r.desc}`)) { leak++; console.log(' LEAK', r.dw_sku); continue; }
+ const swatch = r.img ? b64(`${VIEW}/${r.img}`) : null;
+ const roomImg = r.room ? b64(`${VIEW}/${r.room}`) : null;
+ if (!swatch) { skip++; console.log(' no swatch', r.dw_sku); continue; }
+ let styles = []; try { styles = JSON.parse(r.styles); } catch {}
+ const product = {
+ title, body_html: clean(r.desc), vendor: 'Phillipe Romano', product_type: 'Wallcovering', status: 'draft',
+ tags: [r.pattern.split(' ')[0], r.material, 'Natural Texture', 'Grasscloth & Naturals', 'Phillipe Romano', ...styles].filter(Boolean).join(', '),
+ options: [{ name: 'Size' }],
+ variants: [
+ { option1: 'Sample', sku: `${r.dw_sku}-Sample`, price: '4.25', inventory_management: null, inventory_policy: 'deny' },
+ { option1: 'Per Yard', sku: r.dw_sku, price: String(r.price), inventory_quantity: 999, inventory_policy: 'continue' },
+ ],
+ images: [ { attachment: swatch, alt: title }, ...(roomImg ? [{ attachment: roomImg, alt: `${title} room setting`, position: 2 }] : []) ],
+ metafields: [
+ { namespace: 'custom', key: 'manufacturer_sku', value: r.mfr, type: 'single_line_text_field' },
+ { namespace: 'dwc', key: 'manufacturer_sku', value: r.mfr, type: 'single_line_text_field' },
+ { namespace: 'private_label', key: 'real_vendor_name', value: 'WallQuest', type: 'single_line_text_field' },
+ { namespace: 'global', key: 'width', value: r.width, type: 'single_line_text_field' },
+ { namespace: 'global', key: 'unit_of_measure', value: 'Priced Per Yard', type: 'single_line_text_field' },
+ { namespace: 'global', key: 'v_prods_quantity_order_min', value: '8', type: 'single_line_text_field' },
+ { namespace: 'global', key: 'v_prods_quantity_order_units', value: '8', type: 'single_line_text_field' },
+ { namespace: 'global', key: 'packaged', value: '8-Yard Bolt (sold per yard)', type: 'single_line_text_field' },
+ { namespace: 'specs', key: 'material', value: r.material, type: 'single_line_text_field' },
+ { namespace: 'specs', key: 'repeat_v', value: r.repeat, type: 'single_line_text_field' },
+ { namespace: 'custom', key: 'color', value: r.color, type: 'single_line_text_field' },
+ { namespace: 'custom', key: 'color_hex', value: r.hex, type: 'single_line_text_field' },
+ ].filter(m => m.value),
+ };
+ if (DRY) { created++; if (created <= 3) console.log(` would create: ${title} | $${r.price}/yd | ${product.images.length} imgs | ${product.metafields.length} mf`); continue; }
+ try {
+ const u = await rest('POST', '/products.json', { product });
+ if (u.status >= 200 && u.status < 300 && u.j.product) {
+ const pid = u.j.product.id;
+ psql(`UPDATE ${TABLE} SET shopify_product_id=${pid} WHERE dw_sku='${r.dw_sku}'`);
+ created++; console.log(` ✓ ${r.dw_sku} ${title}`);
+ } else { err++; console.log(` ✗ ${r.dw_sku} HTTP ${u.status} ${(u.raw||JSON.stringify(u.j)).slice(0,120)}`); }
+ await sleep(600);
+ } catch (e) { err++; console.log(` ✗ ${r.dw_sku} ${String(e).slice(0,70)}`); }
+ }
+ console.log(`\n${DRY?'[DRY-RUN] ':''}${TABLE} DONE: created=${created} already=${already} settlement-skipped=${flagged} no-swatch=${skip} leaks=${leak} errors=${err}`);
+ if (DRY) console.log('live: SHOPIFY_ADMIN_TOKEN=… TABLE=… VIEW=… node push-drafts-book.cjs (Steve via !)');
+})();
← 3252281c catalog-push: HARDEN sync bucket to missing/empty tables ONL
·
back to Designer Wallcoverings
·
auto-save: 2026-07-09T23:42:48 (4 files) — pending-approval/ a4a39262 →