← back to Trending Dw
trending: scripts/push-to-shopify.js — creates DW products from DWPV queue as DRAFT (safe: refuses w/o creds, never auto-publishes); ready for token
bd1056c3eec0d954a7e1b4c9f9d62d86dad891a3 · 2026-07-03 09:34:08 -0700 · Steve
Files touched
A scripts/push-to-shopify.js
Diff
commit bd1056c3eec0d954a7e1b4c9f9d62d86dad891a3
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jul 3 09:34:08 2026 -0700
trending: scripts/push-to-shopify.js — creates DW products from DWPV queue as DRAFT (safe: refuses w/o creds, never auto-publishes); ready for token
---
scripts/push-to-shopify.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/scripts/push-to-shopify.js b/scripts/push-to-shopify.js
new file mode 100644
index 0000000..ed1be68
--- /dev/null
+++ b/scripts/push-to-shopify.js
@@ -0,0 +1,81 @@
+#!/usr/bin/env node
+// push-to-shopify.js — create real DW Shopify products from the staged DWPV queue.
+//
+// SAFE BY DEFAULT: refuses to run unless SHOPIFY_STORE + SHOPIFY_ADMIN_TOKEN are set,
+// and creates every product as status:'draft' (NOT published) so nothing goes
+// customer-facing without an explicit publish. Live publish stays a Steve-gated step.
+//
+// env SHOPIFY_STORE=xxxx.myshopify.com SHOPIFY_ADMIN_TOKEN=shpat_… \
+// node scripts/push-to-shopify.js # push all 'staged'
+// ... --dry # print payloads, no API calls
+//
+// Idempotent: only pushes queue items with status==='staged'; on success flips them
+// to 'pushed' + records shopifyId + handle, so re-runs never double-create.
+
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+
+const QUEUE = path.join(__dirname, '..', 'data', 'shopify-queue.json');
+const STORE = process.env.SHOPIFY_STORE; // xxxx.myshopify.com
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN; // shpat_…
+const API_VER = process.env.SHOPIFY_API_VERSION || '2024-01';
+const DRY = process.argv.includes('--dry');
+
+if (!DRY && (!STORE || !TOKEN)){
+ console.error('REFUSING TO RUN: set SHOPIFY_STORE + SHOPIFY_ADMIN_TOKEN (route via /secrets). Use --dry to preview payloads.');
+ process.exit(2);
+}
+
+function priceFromTier(tier){ // "Commercial — $495" -> "495.00"
+ const m = String(tier || '').match(/\$\s*([\d,]+)/);
+ return m ? m[1].replace(/,/g, '') + '.00' : '149.00';
+}
+
+function buildProduct(p){
+ return { product: {
+ title: p.title,
+ body_html: `<p>${p.style} · ${p.color}. Original Designer Wallcoverings pattern (Pattern Vault). Trend signal: ${p.trendSignal || ''} (rank ${p.signalRank || ''}).</p>`,
+ vendor: p.vendor || 'Designer Wallcoverings',
+ product_type: 'Wallcovering',
+ status: 'draft', // never auto-publish
+ tags: [p.series, 'Pattern Vault', 'AI-Original', p.style, p.color].filter(Boolean).join(', '),
+ variants: [{ sku: p.sku, price: priceFromTier(p.priceTier), inventory_management: null }],
+ images: p.image ? [{ src: p.image }] : []
+ }};
+}
+
+function apiPost(bodyObj){
+ return new Promise((resolve, reject) => {
+ const body = JSON.stringify(bodyObj);
+ const req = https.request({
+ hostname: STORE, path: `/admin/api/${API_VER}/products.json`, method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': TOKEN, 'Content-Length': Buffer.byteLength(body) }
+ }, res => {
+ let s = ''; res.on('data', d => s += d); res.on('end', () => {
+ if (res.statusCode >= 200 && res.statusCode < 300) resolve(JSON.parse(s));
+ else reject(new Error(`HTTP ${res.statusCode}: ${s.slice(0, 300)}`));
+ });
+ });
+ req.on('error', reject); req.write(body); req.end();
+ });
+}
+
+async function main(){
+ const q = JSON.parse(fs.readFileSync(QUEUE, 'utf8'));
+ const todo = q.filter(p => p.status === 'staged');
+ console.log(`${todo.length} staged product(s) to push${DRY ? ' (DRY RUN)' : ` to ${STORE} as DRAFT`}`);
+ let ok = 0;
+ for (const p of todo){
+ const payload = buildProduct(p);
+ if (DRY){ console.log(` ${p.sku} ${p.title} $${payload.product.variants[0].price}`); continue; }
+ try {
+ const r = await apiPost(payload);
+ p.status = 'pushed'; p.shopifyId = r.product.id; p.handle = r.product.handle; p.pushedAt = new Date(Date.now()).toISOString();
+ ok++; console.log(` ✓ ${p.sku} -> product ${r.product.id} (draft)`);
+ } catch (e){ console.error(` ✗ ${p.sku}: ${e.message}`); }
+ }
+ if (!DRY){ fs.writeFileSync(QUEUE, JSON.stringify(q, null, 2)); console.log(`pushed ${ok}/${todo.length} as DRAFT — review + publish in Shopify admin.`); }
+}
+
+main().catch(e => { console.error(e); process.exit(1); });
← 732d40d trending: 'Put on Shopify' button — stages our-own designs a
·
back to Trending Dw
·
gap-lane generator: 3 seam-PASS settlement-OK tiles (TR-013/ bf548c9 →