[object Object]

← back to Designer Wallcoverings

1838 Wallcoverings: set all 464 live products to DRAFT (Steve directive); add Artmura title-fix tool (no-op — all 161 already correct)

2d09d654734b1a93c4044155c7ed8afa58b11142 · 2026-06-12 10:11:22 -0700 · SteveStudio2

Files touched

Diff

commit 2d09d654734b1a93c4044155c7ed8afa58b11142
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Fri Jun 12 10:11:22 2026 -0700

    1838 Wallcoverings: set all 464 live products to DRAFT (Steve directive); add Artmura title-fix tool (no-op — all 161 already correct)
---
 shopify/scripts/artmura-title-fix.js | 55 ++++++++++++++++++++++++++++++++++++
 shopify/scripts/draft-1838.js        | 50 ++++++++++++++++++++++++++++++++
 2 files changed, 105 insertions(+)

diff --git a/shopify/scripts/artmura-title-fix.js b/shopify/scripts/artmura-title-fix.js
new file mode 100644
index 00000000..00a9d0f1
--- /dev/null
+++ b/shopify/scripts/artmura-title-fix.js
@@ -0,0 +1,55 @@
+#!/usr/bin/env node
+/**
+ * artmura-title-fix.js — Steve 2026-06-12: ensure every Artmura title reads
+ * "<Pattern Color> Wallcovering | Artmura". Some are missing the word "Wallcovering"
+ * before "| Artmura"; insert it. Skip ones that already have Wallcovering/Wallcoverings.
+ * Vendor:Artmura on live Shopify. Dry-run unless --go.
+ */
+const https = require('https');
+const fs = require('fs');
+const os = require('os');
+const env = fs.readFileSync(os.homedir() + '/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m) || [])[1].replace(/['"]/g, '').trim();
+const STORE = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
+const GO = process.argv.includes('--go');
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+function gql(q, v) { const d = JSON.stringify({ query: q, variables: v });
+  return new Promise(res => { const r = https.request({ host: STORE, path: `/admin/api/${API}/graphql.json`, method: 'POST',
+    headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(d) } },
+    x => { let b=''; x.on('data',c=>b+=c); x.on('end',()=>{ try{res(JSON.parse(b))}catch{res({})} }); }); r.on('error',()=>res({})); r.write(d); r.end(); }); }
+
+const SUFFIX = /\s*\|\s*Artmura\s*$/i;
+// returns new title if it needs the word, else null
+function fix(title) {
+  if (!SUFFIX.test(title)) return null;
+  const prefix = title.replace(SUFFIX, '').trim();
+  if (/wallcoverings?$/i.test(prefix)) return null;          // already has Wallcovering(s)
+  return `${prefix} Wallcovering | Artmura`;
+}
+
+(async () => {
+  // page through all vendor:Artmura products
+  const items = []; let after = null, page = 0;
+  do {
+    const r = await gql(`query($q:String!,$after:String){products(first:100,query:$q,after:$after){pageInfo{hasNextPage endCursor} edges{node{id title}}}}`,
+      { q: 'vendor:Artmura', after });
+    const p = r.data && r.data.products; if (!p) break;
+    p.edges.forEach(e => items.push(e.node));
+    after = p.pageInfo.hasNextPage ? p.pageInfo.endCursor : null; page++;
+  } while (after && page < 50);
+
+  const todo = items.map(n => ({ ...n, next: fix(n.title) })).filter(x => x.next);
+  console.log(`Artmura products: ${items.length} total · ${todo.length} need "Wallcovering" added`);
+  todo.slice(0, 12).forEach(x => console.log(`   "${x.title}"  ->  "${x.next}"`));
+  if (!todo.length) { console.log('nothing to change'); return; }
+  if (!GO) { console.log(`\nDRY RUN — re-run with --go to apply to ${todo.length} products.`); return; }
+
+  let ok = 0, fail = 0;
+  for (const x of todo) {
+    const r = await gql(`mutation($id:ID!,$t:String!){productUpdate(input:{id:$id,title:$t}){product{id title} userErrors{message}}}`, { id: x.id, t: x.next });
+    if (r?.data?.productUpdate?.product?.title === x.next) { ok++; if (ok % 25 === 0) process.stdout.write(`\r  updated ${ok}…`); }
+    else { fail++; console.log(`\n  ✗ ${x.id} ${JSON.stringify(r?.data?.productUpdate?.userErrors || r).slice(0,140)}`); }
+    await sleep(250);
+  }
+  console.log(`\n✓ retitled ${ok}/${todo.length} (failed ${fail})`);
+})().catch(e => { console.error('ERR', e.message); process.exit(1); });
diff --git a/shopify/scripts/draft-1838.js b/shopify/scripts/draft-1838.js
new file mode 100644
index 00000000..e8e07e5c
--- /dev/null
+++ b/shopify/scripts/draft-1838.js
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+/**
+ * draft-1838.js — set all live "1838 Wallcoverings" products to DRAFT (Steve 2026-06-12).
+ * 464 ACTIVE → DRAFT via Shopify Admin GraphQL. Reversible. Mirrors into shopify_products.
+ * Scoped: vendor ILIKE '%1838%' AND status='ACTIVE'. Dry-run unless --go.
+ */
+const https = require('https');
+const fs = require('fs');
+const os = require('os');
+const { execFileSync } = require('child_process');
+
+const GO = process.argv.includes('--go');
+const env = fs.readFileSync(os.homedir() + '/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m) || [])[1].replace(/['"]/g, '').trim();
+const STORE = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const psql = sql => execFileSync('psql', ['-d', 'dw_unified', '-At', '-F', '\t', '-c', sql], { encoding: 'utf8', maxBuffer: 1 << 28 }).trim();
+
+function gql(query, variables) {
+  const d = JSON.stringify({ query, variables });
+  return new Promise(res => {
+    const r = https.request({ host: STORE, path: `/admin/api/${API}/graphql.json`, method: 'POST',
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(d) } },
+      x => { let b = ''; x.on('data', c => b += c); x.on('end', () => { try { res(JSON.parse(b)); } catch { res({}); } }); });
+    r.on('error', () => res({})); r.write(d); r.end();
+  });
+}
+const MUT = `mutation($id:ID!){ productUpdate(input:{id:$id, status:DRAFT}){ product{id status} userErrors{field message} } }`;
+
+(async () => {
+  const rows = psql(`SELECT DISTINCT shopify_id FROM shopify_products
+                     WHERE vendor ILIKE '%1838%' AND status='ACTIVE' AND shopify_id IS NOT NULL`);
+  const ids = rows ? rows.split('\n').filter(Boolean) : [];
+  console.log(`1838 Wallcoverings ACTIVE → DRAFT: ${ids.length}`);
+  if (!ids.length) { console.log('nothing to do'); return; }
+  if (!GO) { console.log('DRY RUN — re-run with --go. Sample:', ids.slice(0, 3).join(', ')); return; }
+
+  let ok = 0, fail = 0;
+  for (const id of ids) {
+    const gid = id.startsWith('gid://') ? id : `gid://shopify/Product/${id}`;
+    const r = await gql(MUT, { id: gid });
+    if (r?.data?.productUpdate?.product?.status === 'DRAFT') {
+      ok++; psql(`UPDATE shopify_products SET status='DRAFT' WHERE shopify_id='${id.replace(/'/g, "''")}'`);
+      if (ok % 25 === 0) process.stdout.write(`\r  drafted ${ok}…`);
+    } else { fail++; console.log(`\n  ✗ ${gid} ${JSON.stringify(r?.data?.productUpdate?.userErrors || r).slice(0, 160)}`); }
+    await sleep(250);
+  }
+  console.log(`\n✓ drafted ${ok}/${ids.length} (failed ${fail})`);
+  console.log(`1838 ACTIVE remaining: ${psql(`SELECT count(DISTINCT shopify_id) FROM shopify_products WHERE vendor ILIKE '%1838%' AND status='ACTIVE'`)}`);
+})().catch(e => { console.error('ERR', e.message); process.exit(1); });

← 00a91125 Tier 3 cost-sourcing matrix + complete campaign roadmap: big  ·  back to Designer Wallcoverings  ·  scan-active-dups: filter to exact PREFIX- bases (loose Shopi 139f7c9b →