← 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
A shopify/scripts/artmura-title-fix.jsA shopify/scripts/draft-1838.js
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 →