← back to Designer Wallcoverings
Kravet-under-PR re-vendor tool (dry-run default; --apply/--archive gated)
080ee6528037066f774b198796ae726c91aa8b1b · 2026-07-06 14:29:28 -0700 · Steve
Files touched
A scripts/kravet_revendor.js
Diff
commit 080ee6528037066f774b198796ae726c91aa8b1b
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jul 6 14:29:28 2026 -0700
Kravet-under-PR re-vendor tool (dry-run default; --apply/--archive gated)
---
scripts/kravet_revendor.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/scripts/kravet_revendor.js b/scripts/kravet_revendor.js
new file mode 100644
index 00000000..dbf812a5
--- /dev/null
+++ b/scripts/kravet_revendor.js
@@ -0,0 +1,72 @@
+#!/usr/bin/env node
+/**
+ * Fix Kravet products mis-vendored under "Phillipe Romano".
+ * Target = vendor:'Phillipe Romano' AND title contains 'Kravet'.
+ * Proposed change per product: vendor → 'Kravet', title → stripped of ' | Phillipe Romano...' cruft.
+ * DRY-RUN by default (enumerate only, NO writes). Pass --apply to write. --archive to archive instead of re-vendor.
+ */
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const SHOP = process.env.SHOPIFY_STORE_DOMAIN;
+const TOKEN = process.env.SHOPIFY_ADMIN_ACCESS_TOKEN || process.env.SHOPIFY_ADMIN_TOKEN;
+const VER = process.env.SHOPIFY_ADMIN_API_VERSION || '2024-01';
+const GQL = `https://${SHOP}/admin/api/${VER}/graphql.json`;
+const REST = `https://${SHOP}/admin/api/${VER}`;
+const argv = process.argv.slice(2);
+const APPLY = argv.includes('--apply');
+const ARCHIVE = argv.includes('--archive');
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+const cleanTitle = (t) => t.replace(/\s*\|\s*Phillipe Romano.*$/i, '').trim();
+
+async function gql(query) {
+ const res = await fetch(GQL, { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ query }) });
+ const j = await res.json();
+ if (j.errors) throw new Error(JSON.stringify(j.errors));
+ return j.data;
+}
+async function rest(method, path, body) {
+ const res = await fetch(`${REST}${path}`, { method, headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined });
+ if (!res.ok) throw new Error(`${res.status}: ${(await res.text()).slice(0, 200)}`);
+ return res.json();
+}
+
+(async () => {
+ // Paginate all vendor=Phillipe Romano products whose title contains Kravet.
+ const items = []; let cursor = null;
+ for (let page = 0; page < 20; page++) {
+ const after = cursor ? `, after:"${cursor}"` : '';
+ const q = `{ products(first:100${after}, query:"vendor:'Phillipe Romano' AND title:*Kravet*"){ pageInfo{hasNextPage endCursor} edges{ node{ id title status vendor variants(first:1){edges{node{sku price}}} } } } }`;
+ const d = await gql(q);
+ for (const e of d.products.edges) {
+ const n = e.node; const v = n.variants.edges[0]?.node || {};
+ items.push({ gid: n.id, id: n.id.split('/').pop(), title: n.title, status: n.status, sku: v.sku || '', price: v.price || '0' });
+ }
+ if (!d.products.pageInfo.hasNextPage) break;
+ cursor = d.products.pageInfo.endCursor; await sleep(200);
+ }
+ const nonSample = items.filter(i => !i.sku.endsWith('-Sample'));
+ const samples = items.filter(i => i.sku.endsWith('-Sample'));
+
+ const mode = ARCHIVE ? 'ARCHIVE' : 'RE-VENDOR→Kravet + strip title';
+ console.log(`${APPLY ? '' : '[DRY-RUN] '}Kravet-under-PR fix — mode: ${mode}`);
+ console.log(`Matched: ${items.length} (products ${nonSample.length}, samples ${samples.length})\n`);
+ console.log('STATUS SKU $PRICE CURRENT TITLE → PROPOSED');
+ for (const it of nonSample) {
+ const proposed = ARCHIVE ? '(archive)' : `vendor=Kravet | "${cleanTitle(it.title)}"`;
+ console.log(`${it.status.padEnd(8)} ${it.sku.padEnd(14)} $${String(it.price).padStart(8)} ${it.title.slice(0, 44)} → ${proposed}`);
+ }
+ if (samples.length) console.log(`\n(+ ${samples.length} memo-sample products would follow the same vendor/title change)`);
+
+ if (!APPLY) { console.log('\nNO WRITES PERFORMED. Re-run with --apply (or --apply --archive) to execute.'); return; }
+
+ console.log(`\nAPPLYING to ${items.length} products…`);
+ let ok = 0;
+ for (const it of items) {
+ const body = ARCHIVE ? { product: { id: Number(it.id), status: 'archived' } }
+ : { product: { id: Number(it.id), vendor: 'Kravet', title: cleanTitle(it.title) } };
+ try { await rest('PUT', `/products/${it.id}.json`, body); ok++; console.log(` ✓ ${it.sku}`); }
+ catch (e) { console.log(` ✗ ${it.sku}: ${e.message.slice(0, 120)}`); }
+ await sleep(350);
+ }
+ console.log(`Done ${ok}/${items.length}`);
+})().catch(e => { console.error('FATAL', e); process.exit(1); });
← a109ccd5 Carl Robinson increments: add global.Sold-Per/Minimum to ful
·
back to Designer Wallcoverings
·
Daisy Bennett: room-setting images (62 generated on Kamatera ee576edf →