← back to Dig Concept Finish
DIG concept-sample finish: rename 4 quarantine samples + price+rename 1 single-variant sample to 2ft x 1ft Sample/$12; rolls out of scope, B sku left flagged
ff6afc98d031c9aacff5bc15e574d6b4b6b72a58 · 2026-06-24 13:19:48 -0700 · Steve
Files touched
A .gitignoreA finish.mjsA finish_result.jsonA identify.mjsA identify_result.jsonA verify_rolls.mjs
Diff
commit ff6afc98d031c9aacff5bc15e574d6b4b6b72a58
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jun 24 13:19:48 2026 -0700
DIG concept-sample finish: rename 4 quarantine samples + price+rename 1 single-variant sample to 2ft x 1ft Sample/$12; rolls out of scope, B sku left flagged
---
.gitignore | 5 ++
finish.mjs | 105 +++++++++++++++++++++++++++++++++++++++
finish_result.json | 44 ++++++++++++++++
identify.mjs | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++
identify_result.json | 128 +++++++++++++++++++++++++++++++++++++++++++++++
verify_rolls.mjs | 15 ++++++
6 files changed, 435 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff2422c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
diff --git a/finish.mjs b/finish.mjs
new file mode 100644
index 0000000..ea48aba
--- /dev/null
+++ b/finish.mjs
@@ -0,0 +1,105 @@
+#!/usr/bin/env node
+// Finisher: apply real-sample treatment to the remaining DIG- CONCEPT-SAMPLE variants ONLY.
+// (A) 4 quarantine sample variants on product 7664622272563 — Size rename -> "2ft x 1ft Sample" (already $12).
+// (B) 1 single-variant sample on product (handle ..._wallpapers), vid 43321302351923 —
+// price -> $12.00 AND Size value -> "2ft x 1ft Sample". SKU LEFT UNTOUCHED (corrupt @gpt, non-derivable -> flagged).
+// ZERO roll writes. ZERO archived writes. ZERO SKU writes.
+// Requires --apply to write; otherwise DRY RUN.
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+const __dir = path.dirname(fileURLToPath(import.meta.url));
+const APPLY = process.argv.includes('--apply');
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const URL = `https://${DOMAIN}/admin/api/${API}/graphql.json`;
+const env = fs.readFileSync('/Users/stevestudio2/Projects/Designer-Wallcoverings/.env', 'utf8');
+const TOK = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
+if (!TOK) { console.error('No SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+
+const LABEL = '2ft x 1ft Sample';
+const PRICE = '12.00';
+
+async function gql(query, variables) {
+ const r = await fetch(URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': TOK },
+ body: JSON.stringify({ query, variables }),
+ });
+ const j = await r.json();
+ if (j.errors) { console.error(JSON.stringify(j.errors, null, 2)); throw new Error('gql err'); }
+ return j.data;
+}
+
+const MUT = `mutation($productId:ID!, $variants:[ProductVariantsBulkInput!]!){
+ productVariantsBulkUpdate(productId:$productId, variants:$variants){
+ productVariants{ id title price sku selectedOptions{name value} }
+ userErrors{ field message }
+ }
+}`;
+
+// Each group = one product's bulk update.
+// Pocket A: rename Size for the 4 quarantine variants (price already $12, do NOT include price).
+const GROUP_A = {
+ label: 'POCKET A (product 7664622272563) - Size rename only',
+ productId: 'gid://shopify/Product/7664622272563',
+ variants: [
+ { id: 'gid://shopify/ProductVariant/44019704660019', optionValues: [{ name: LABEL, optionName: 'Size' }] },
+ { id: 'gid://shopify/ProductVariant/44019704692787', optionValues: [{ name: LABEL, optionName: 'Size' }] },
+ { id: 'gid://shopify/ProductVariant/44035167813683', optionValues: [{ name: LABEL, optionName: 'Size' }] },
+ { id: 'gid://shopify/ProductVariant/44035167846451', optionValues: [{ name: LABEL, optionName: 'Size' }] },
+ ],
+};
+
+// Pocket B: price -> $12 AND Size rename. SKU untouched. productId resolved at runtime from the variant.
+const B_VID = 'gid://shopify/ProductVariant/43321302351923';
+
+(async () => {
+ // Resolve B product id + safety re-check (single variant, ACTIVE, is a sample, NOT a roll).
+ const bd = await gql(`query($id:ID!){ productVariant(id:$id){ id title price sku
+ selectedOptions{name value} product{ id handle status variants(first:50){nodes{id}} } } }`, { id: B_VID });
+ const bv = bd.productVariant;
+ if (!bv) { console.error('B variant not found — abort'); process.exit(1); }
+ const bp = bv.product;
+ const bSize = (bv.selectedOptions.find(o => o.name === 'Size') || {}).value;
+ // Safety gates for B:
+ if (bp.status === 'ARCHIVED') { console.error('B product ARCHIVED — abort (no archived writes)'); process.exit(1); }
+ if (bp.variants.nodes.length !== 1) { console.error(`B product has ${bp.variants.nodes.length} variants — expected single-variant sample. ABORT to avoid touching a roll.`); process.exit(1); }
+ if (bSize === LABEL) console.error('B already at target label (idempotent, will still set).');
+
+ const GROUP_B = {
+ label: `POCKET B (product ${bp.id.split('/').pop()}, handle ${bp.handle}) - price + Size rename, SKU untouched`,
+ productId: bp.id,
+ variants: [
+ { id: B_VID, price: PRICE, optionValues: [{ name: LABEL, optionName: 'Size' }] }, // NO sku field => SKU unchanged
+ ],
+ bMeta: { currentSize: bSize, currentPrice: bv.price, skuRedacted: bv.sku && bv.sku.includes('@gpt') ? '@gpt(REDACTED)' : bv.sku },
+ };
+
+ const groups = [GROUP_A, GROUP_B];
+
+ console.log(`MODE: ${APPLY ? 'APPLY (LIVE WRITE)' : 'DRY RUN'} | label="${LABEL}" | price=$${PRICE}`);
+ console.log(`Pocket A: 4 Size renames (no price change). Pocket B: 1 price+rename (vid ${B_VID.split('/').pop()}, Size "${GROUP_B.bMeta.currentSize}" -> "${LABEL}", $${GROUP_B.bMeta.currentPrice} -> $${PRICE}, SKU=${GROUP_B.bMeta.skuRedacted} UNCHANGED).`);
+
+ if (!APPLY) {
+ console.log('\nDRY RUN — no writes. Re-run with --apply.');
+ fs.writeFileSync(path.join(__dir, 'finish_plan.json'), JSON.stringify(groups.map(g => ({ ...g, bMeta: g.bMeta || undefined })), null, 2));
+ return;
+ }
+
+ const results = [];
+ for (const g of groups) {
+ const d = await gql(MUT, { productId: g.productId, variants: g.variants });
+ const res = d.productVariantsBulkUpdate;
+ const errs = res.userErrors || [];
+ console.log(`\n[${g.label}] userErrors: ${errs.length ? JSON.stringify(errs) : 'NONE'}`);
+ for (const v of (res.productVariants || [])) {
+ const size = (v.selectedOptions.find(o => o.name === 'Size') || {}).value;
+ console.log(` -> ${v.id.split('/').pop()} | "${v.title}" | $${v.price} | Size="${size}"`);
+ }
+ results.push({ label: g.label, userErrors: errs, productVariants: (res.productVariants||[]).map(v => ({ id: v.id, title: v.title, price: v.price, size: (v.selectedOptions.find(o=>o.name==='Size')||{}).value })) });
+ await new Promise(r => setTimeout(r, 600)); // ~2 req/s
+ }
+ fs.writeFileSync(path.join(__dir, 'finish_result.json'), JSON.stringify(results, null, 2));
+ console.log('\nDone. Result written to finish_result.json');
+})();
diff --git a/finish_result.json b/finish_result.json
new file mode 100644
index 0000000..c61da09
--- /dev/null
+++ b/finish_result.json
@@ -0,0 +1,44 @@
+[
+ {
+ "label": "POCKET A (product 7664622272563) - Size rename only",
+ "userErrors": [],
+ "productVariants": [
+ {
+ "id": "gid://shopify/ProductVariant/44019704660019",
+ "title": "Gold Textured Metallic / 2ft x 1ft Sample",
+ "price": "12.00",
+ "size": "2ft x 1ft Sample"
+ },
+ {
+ "id": "gid://shopify/ProductVariant/44019704692787",
+ "title": "Peel and Stick / 2ft x 1ft Sample",
+ "price": "12.00",
+ "size": "2ft x 1ft Sample"
+ },
+ {
+ "id": "gid://shopify/ProductVariant/44035167813683",
+ "title": "Silver Textured Metallic / 2ft x 1ft Sample",
+ "price": "12.00",
+ "size": "2ft x 1ft Sample"
+ },
+ {
+ "id": "gid://shopify/ProductVariant/44035167846451",
+ "title": "Type 2 PVC Free Paper / 2ft x 1ft Sample",
+ "price": "12.00",
+ "size": "2ft x 1ft Sample"
+ }
+ ]
+ },
+ {
+ "label": "POCKET B (product 7582548066355, handle dig_74754_vintage_wallpaper_designer_wallpapers) - price + Size rename, SKU untouched",
+ "userErrors": [],
+ "productVariants": [
+ {
+ "id": "gid://shopify/ProductVariant/43321302351923",
+ "title": "2ft x 1ft Sample",
+ "price": "12.00",
+ "size": "2ft x 1ft Sample"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/identify.mjs b/identify.mjs
new file mode 100644
index 0000000..8ce00bb
--- /dev/null
+++ b/identify.mjs
@@ -0,0 +1,138 @@
+#!/usr/bin/env node
+// READ-ONLY. Identify the remaining DIG- CONCEPT-SAMPLE variants in pockets (A) and (B).
+// (A) the 4 quarantine sample variants on product 7664622272563 (already $12, SKU repaired to Dig-25-25-*-sample,
+// but Size option value still "Concept Sample").
+// (B) the single-variant $4.25 CONCEPT-SAMPLE on handle dig_74754_..._wallpapers (vid 43321302351923).
+// Also store-wide scan: any DIG- variant whose Size/sample option is still "Concept Sample"/"Concept Samples".
+// NO WRITES.
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+const __dir = path.dirname(fileURLToPath(import.meta.url));
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const URL = `https://${DOMAIN}/admin/api/${API}/graphql.json`;
+const env = fs.readFileSync('/Users/stevestudio2/Projects/Designer-Wallcoverings/.env', 'utf8');
+const TOK = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
+if (!TOK) { console.error('No SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+
+async function gql(query, variables) {
+ const r = await fetch(URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': TOK },
+ body: JSON.stringify({ query, variables }),
+ });
+ const j = await r.json();
+ if (j.errors) { console.error(JSON.stringify(j.errors, null, 2)); throw new Error('gql err'); }
+ return j.data;
+}
+
+const CONCEPT = new Set(['Concept Sample', 'Concept Samples']);
+
+// --- Pocket A: product 7664622272563, dump all variants ---
+async function dumpProduct(pid) {
+ const q = `query($id:ID!){ product(id:$id){ id title handle status options{name values}
+ variants(first:50){ nodes{ id title price sku selectedOptions{name value} } } } }`;
+ const d = await gql(q, { id: `gid://shopify/Product/${pid}` });
+ return d.product;
+}
+
+// --- Pocket B: find single-variant ..._wallpapers sample by handle ---
+async function byHandle(handle) {
+ const q = `query($h:String!){ products(first:5, query:$h){ nodes{ id title handle status
+ options{name values}
+ variants(first:50){ nodes{ id title price sku selectedOptions{name value} } } } } }`;
+ const d = await gql(q, { h: `handle:${handle}` });
+ return d.products.nodes;
+}
+
+// --- Store-wide: count remaining DIG- variants still labeled Concept Sample (ACTIVE/DRAFT) ---
+async function storeWideConceptCount() {
+ let cursor = null, hasNext = true;
+ const hits = [];
+ while (hasNext) {
+ const q = `query($cursor:String){ products(first:100, query:"sku:DIG-* OR sku:Dig-* OR sku:dig-*", after:$cursor){
+ pageInfo{hasNextPage endCursor}
+ nodes{ id title handle status
+ variants(first:30){ nodes{ id title price sku selectedOptions{name value} } } } } }`;
+ const d = await gql(q, { cursor });
+ for (const p of d.products.nodes) {
+ if (p.status === 'ARCHIVED') continue; // active+draft only
+ for (const v of p.variants.nodes) {
+ for (const o of v.selectedOptions) {
+ if (CONCEPT.has(o.value)) {
+ hits.push({ product: p.id, handle: p.handle, status: p.status, vid: v.id, vtitle: v.title, price: v.price, sku: v.sku, optName: o.name, optValue: o.value });
+ }
+ }
+ }
+ }
+ hasNext = d.products.pageInfo.hasNextPage;
+ cursor = d.products.pageInfo.endCursor;
+ process.stderr.write('.');
+ }
+ process.stderr.write('\n');
+ return hits;
+}
+
+const QUAR_IDS = new Set([
+ 'gid://shopify/ProductVariant/44019704660019',
+ 'gid://shopify/ProductVariant/44019704692787',
+ 'gid://shopify/ProductVariant/44035167813683',
+ 'gid://shopify/ProductVariant/44035167846451',
+]);
+const B_VID = 'gid://shopify/ProductVariant/43321302351923';
+
+const redactSku = (s) => (s && s.includes('@gpt')) ? '@gpt(REDACTED)' : s;
+
+(async () => {
+ // A
+ const pA = await dumpProduct('7664622272563');
+ const aVariants = pA.variants.nodes.filter(v => QUAR_IDS.has(v.id)).map(v => ({
+ vid: v.id, title: v.title, price: v.price, sku: redactSku(v.sku),
+ size: (v.selectedOptions.find(o => o.name === 'Size') || {}).value,
+ selectedOptions: v.selectedOptions.map(o => ({ name: o.name, value: o.value })),
+ }));
+
+ // B
+ const bNodes = await byHandle('dig_74754_vintage_wallpaper_designer_wallcoverings');
+ // also try the explicit _wallpapers handle pattern
+ let bProduct = null, bVar = null;
+ for (const p of bNodes) {
+ const hit = p.variants.nodes.find(v => v.id === B_VID);
+ if (hit) { bProduct = p; bVar = hit; break; }
+ }
+ // fallback: direct variant lookup
+ if (!bVar) {
+ const d = await gql(`query($id:ID!){ productVariant(id:$id){ id title price sku
+ selectedOptions{name value} product{ id title handle status options{name values} } } }`, { id: B_VID });
+ if (d.productVariant) { bVar = d.productVariant; bProduct = d.productVariant.product; }
+ }
+ const bOut = bVar ? {
+ product: bProduct.id, handle: bProduct.handle, title: bProduct.title, status: bProduct.status,
+ vid: bVar.id, vtitle: bVar.title, price: bVar.price, sku: redactSku(bVar.sku),
+ options: bProduct.options,
+ size: (bVar.selectedOptions.find(o => o.name === 'Size') || {}).value,
+ selectedOptions: bVar.selectedOptions.map(o => ({ name: o.name, value: o.value })),
+ } : null;
+
+ // Store-wide
+ const allHits = await storeWideConceptCount();
+ const sanitized = allHits.map(h => ({ ...h, sku: redactSku(h.sku) }));
+
+ const out = {
+ pocketA: { product: pA.id, handle: pA.handle, title: pA.title, status: pA.status, options: pA.options, variants: aVariants },
+ pocketB: bOut,
+ storeWideConceptRemaining: { count: sanitized.length, hits: sanitized },
+ };
+ fs.writeFileSync(path.join(__dir, 'identify_result.json'), JSON.stringify(out, null, 2));
+
+ console.log('=== POCKET A (product 7664622272563) ===');
+ console.log('status:', pA.status, '| handle:', pA.handle);
+ for (const v of aVariants) console.log(` ${v.vid.split('/').pop()} | "${v.title}" | $${v.price} | Size="${v.size}" | sku=${v.sku}`);
+ console.log('\n=== POCKET B ===');
+ if (bOut) console.log(` ${bOut.vid.split('/').pop()} | handle=${bOut.handle} | status=${bOut.status} | "${bOut.vtitle}" | $${bOut.price} | Size="${bOut.size}" | sku=${bOut.sku}\n options=${JSON.stringify(bOut.options)}`);
+ else console.log(' B variant NOT FOUND');
+ console.log('\n=== STORE-WIDE remaining "Concept Sample" (active+draft DIG) ===');
+ console.log(' count:', sanitized.length);
+ for (const h of sanitized) console.log(` ${h.vid.split('/').pop()} | ${h.status} | ${h.handle} | "${h.vtitle}" | $${h.price} | ${h.optName}="${h.optValue}" | sku=${h.sku}`);
+})();
diff --git a/identify_result.json b/identify_result.json
new file mode 100644
index 0000000..dce66bf
--- /dev/null
+++ b/identify_result.json
@@ -0,0 +1,128 @@
+{
+ "pocketA": {
+ "product": "gid://shopify/Product/7664622272563",
+ "handle": "dig_74754_vintage_wallpaper_designer_wallcoverings-1",
+ "title": "Hideo Bamboo Vintage 1970's Mylar Retro | Architectural Wallcoverings",
+ "status": "ACTIVE",
+ "options": [
+ {
+ "name": "Wallpaper Type",
+ "values": [
+ "Type 2 Textured Vinyl",
+ "Gold Textured Metallic",
+ "Peel and Stick",
+ "Prepasted",
+ "Silver Textured Metallic",
+ "Type 2 PVC Free Paper"
+ ]
+ },
+ {
+ "name": "Size",
+ "values": [
+ "2ft x 1ft Sample",
+ "2ft x 12ft",
+ "2ft x 27ft"
+ ]
+ }
+ ],
+ "variants": [
+ {
+ "vid": "gid://shopify/ProductVariant/44019704660019",
+ "title": "Gold Textured Metallic / 2ft x 1ft Sample",
+ "price": "12.00",
+ "sku": "Dig-25-25-gold-metallic-sample",
+ "size": "2ft x 1ft Sample",
+ "selectedOptions": [
+ {
+ "name": "Wallpaper Type",
+ "value": "Gold Textured Metallic"
+ },
+ {
+ "name": "Size",
+ "value": "2ft x 1ft Sample"
+ }
+ ]
+ },
+ {
+ "vid": "gid://shopify/ProductVariant/44019704692787",
+ "title": "Peel and Stick / 2ft x 1ft Sample",
+ "price": "12.00",
+ "sku": "Dig-25-25-peel-stick-sample",
+ "size": "2ft x 1ft Sample",
+ "selectedOptions": [
+ {
+ "name": "Wallpaper Type",
+ "value": "Peel and Stick"
+ },
+ {
+ "name": "Size",
+ "value": "2ft x 1ft Sample"
+ }
+ ]
+ },
+ {
+ "vid": "gid://shopify/ProductVariant/44035167813683",
+ "title": "Silver Textured Metallic / 2ft x 1ft Sample",
+ "price": "12.00",
+ "sku": "Dig-25-25-silver-metallic-sample",
+ "size": "2ft x 1ft Sample",
+ "selectedOptions": [
+ {
+ "name": "Wallpaper Type",
+ "value": "Silver Textured Metallic"
+ },
+ {
+ "name": "Size",
+ "value": "2ft x 1ft Sample"
+ }
+ ]
+ },
+ {
+ "vid": "gid://shopify/ProductVariant/44035167846451",
+ "title": "Type 2 PVC Free Paper / 2ft x 1ft Sample",
+ "price": "12.00",
+ "sku": "Dig-25-25-type-2-pvc-sample",
+ "size": "2ft x 1ft Sample",
+ "selectedOptions": [
+ {
+ "name": "Wallpaper Type",
+ "value": "Type 2 PVC Free Paper"
+ },
+ {
+ "name": "Size",
+ "value": "2ft x 1ft Sample"
+ }
+ ]
+ }
+ ]
+ },
+ "pocketB": {
+ "product": "gid://shopify/Product/7582548066355",
+ "handle": "dig_74754_vintage_wallpaper_designer_wallpapers",
+ "title": "Hideo Bamboo Vintage 1970'S Mylar Chinoiserie | Architectural Wallcoverings",
+ "status": "ACTIVE",
+ "vid": "gid://shopify/ProductVariant/43321302351923",
+ "vtitle": "2ft x 1ft Sample",
+ "price": "12.00",
+ "sku": "@gpt(REDACTED)",
+ "options": [
+ {
+ "name": "Size",
+ "values": [
+ "2ft x 1ft Sample"
+ ]
+ }
+ ],
+ "size": "2ft x 1ft Sample",
+ "selectedOptions": [
+ {
+ "name": "Size",
+ "value": "2ft x 1ft Sample"
+ }
+ ]
+ },
+ "storeWideConceptRemaining": {
+ "count": 0,
+ "hits": []
+ }
+}
\ No newline at end of file
diff --git a/verify_rolls.mjs b/verify_rolls.mjs
new file mode 100644
index 0000000..ab11668
--- /dev/null
+++ b/verify_rolls.mjs
@@ -0,0 +1,15 @@
+import fs from 'fs';
+const DOMAIN='designer-laboratory-sandbox.myshopify.com';
+const URL=`https://${DOMAIN}/admin/api/2024-10/graphql.json`;
+const env=fs.readFileSync('/Users/stevestudio2/Projects/Designer-Wallcoverings/.env','utf8');
+const TOK=(env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1].trim();
+const g=async(q,v)=>{const r=await fetch(URL,{method:'POST',headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOK},body:JSON.stringify({query:q,variables:v})});return (await r.json()).data;};
+const red=s=>s&&s.includes('@gpt')?'@gpt(REDACTED)':s;
+// B product single-variant?
+const b=await g(`query($id:ID!){product(id:$id){handle status variants(first:50){nodes{id price sku selectedOptions{name value}}}}}`,{id:'gid://shopify/Product/7582548066355'});
+console.log('B product variants:',b.product.variants.nodes.length,'| status',b.product.status);
+b.product.variants.nodes.forEach(v=>console.log(' ',v.id.split('/').pop(),'$'+v.price,'sku='+red(v.sku),JSON.stringify(v.selectedOptions.map(o=>o.value))));
+// A-sibling roll variants (the wallcoverings-1 product) - confirm rolls untouched (still their $179-$668 + @gpt skus)
+const a=await g(`query($id:ID!){product(id:$id){handle status variants(first:60){nodes{id price sku selectedOptions{name value}}}}}`,{id:'gid://shopify/Product/7664622272563'});
+console.log('\nA product (wallcoverings-1) ALL variants — rolls must be unchanged:');
+a.product.variants.nodes.forEach(v=>{const size=(v.selectedOptions.find(o=>o.name==='Size')||{}).value;console.log(' ',v.id.split('/').pop(),'$'+v.price,'Size="'+size+'"','sku='+red(v.sku));});
(oldest)
·
back to Dig Concept Finish
·
chore: macstudio3 migration — reconcile from mac2 + repoint d1e63c3 →