[object Object]

← back to Designer Wallcoverings

cron-create-sample-variants: pass --apply (script is now dry-by-default)

ff460585c16d2b0368ccf084616d31e1fb1a5559 · 2026-09-14 15:24:58 -0700 · Steve

create-missing-sample-variants.js flipped to dry-by-default on 2026-09-14
(dryRun = !args.includes('--apply')). This cron ran it bare, so it would
silently create zero variants, exit 0, and never satisfy its REMAINING==0
self-removal guard. Add --apply so the wrapper writes live as intended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X2onw3VVrDkPYwaoLj7jms

Files touched

Diff

commit ff460585c16d2b0368ccf084616d31e1fb1a5559
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Sep 14 15:24:58 2026 -0700

    cron-create-sample-variants: pass --apply (script is now dry-by-default)
    
    create-missing-sample-variants.js flipped to dry-by-default on 2026-09-14
    (dryRun = !args.includes('--apply')). This cron ran it bare, so it would
    silently create zero variants, exit 0, and never satisfy its REMAINING==0
    self-removal guard. Add --apply so the wrapper writes live as intended.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01X2onw3VVrDkPYwaoLj7jms
---
 DW-Programming/lib/versa-colorway-extraction.js | 69 +++++++++++++++++++++++++
 shopify/scripts/cron-create-sample-variants.sh  |  5 +-
 2 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/DW-Programming/lib/versa-colorway-extraction.js b/DW-Programming/lib/versa-colorway-extraction.js
new file mode 100644
index 00000000..c63cd918
--- /dev/null
+++ b/DW-Programming/lib/versa-colorway-extraction.js
@@ -0,0 +1,69 @@
+'use strict';
+
+// Passed directly to Playwright.evaluate: keep every dependency inside this function.
+// TK-11241: only semantic swatch nodes can supply SKU images; featured photos
+// are pattern-level and must never enter image_url or all_images.
+function extractVersaColorways({ pageUrl }) {
+  const base = new URL(pageUrl);
+  const absolute = value => {
+    if (typeof value !== 'string' || !value.trim() || /data:|;base64,|[\\\u0000-\u001f]/i.test(value)) return null;
+    try {
+      const url = new URL(value.trim(), value.trim().startsWith('fileadmin/') ? base.origin + '/' : base.href);
+      return /^https?:$/.test(url.protocol) && !url.username && !url.password ? url.href : null;
+    } catch { return null; }
+  };
+  const identity = value => {
+    const url = absolute(value);
+    const match = url && new URL(url).pathname.match(/\/product-detail\/(.+)\/([a-z0-9]+(?:-[a-z0-9]+)+)\/?$/i);
+    return match && /\d/.test(match[2]) ? { pattern: match[1].toLowerCase(), sku: match[2].toUpperCase(), url } : null;
+  };
+  const current = identity(pageUrl);
+  const root = document.querySelector('.product-detail');
+  if (!root || !current) throw new Error('Versa detail identity/markup missing; refusing catalog update');
+  const color = node => {
+    // Strip the "PVC-free" marketing suffix the swatch titles carry (e.g. "Horizon PVC-free")
+    // so it never lands in the customer-facing Color tag / custom.color metafield / body copy.
+    const value = node?.textContent?.replace(/\s+/g, ' ').trim().replace(/\s*PVC-free\s*/gi, '').trim();
+    return value && !/^#[0-9a-f]{3,8}$/i.test(value) ? value : null;
+  };
+  const imageFor = (img, sku, zoom) => {
+    if (!img) return null;
+    const srcset = (img.getAttribute('data-srcset') || img.getAttribute('srcset') || '')
+      .split(',').map(item => item.trim().split(/\s+/))
+      .sort((a, b) => (parseFloat(b[1]) || 0) - (parseFloat(a[1]) || 0)).map(item => item[0]);
+    const candidates = [zoom, img.getAttribute('data-full-size'), img.getAttribute('data-src'), ...srcset, img.getAttribute('src')];
+    // Vendor filenames omit legacy collection suffixes; pattern+color core is
+    // AVF29-128 in both AVF29-128 and AVF29-128-90. Require token boundaries.
+    const combined = sku.match(/^(?:INV-)?(M[A-Z]{2})(\d{4})-\d+$/i);
+    const core = combined ? '(?:INV[-_])?' + combined[1] + '[-_]?' + combined[2]
+      : sku.split('-').slice(0, sku.startsWith('VU-') ? 3 : 2).join('[-_]');
+    const skuToken = new RegExp('(?:^|[^a-z0-9])' + core + '(?=[^a-z0-9]|$)', 'i');
+    for (const candidate of candidates) {
+      const url = absolute(candidate);
+      if (!url) continue;
+      let path;
+      try { path = decodeURIComponent(new URL(url).pathname); } catch { continue; }
+      if (!/\/fileadmin\//i.test(path) || !/\.(?:jpe?g|png|webp|avif)$/i.test(path)) continue;
+      if (/(?:^|[\/_.\s-])(?:install(?:ation)?|room|featured|lifestyle|mockup|logo|icon|imagenotfound)(?=$|[\/_.\s-])/i.test(path)) continue;
+      if (skuToken.test(path)) return url;
+    }
+    return null;
+  };
+  const mainImage = imageFor(root.querySelector('img.product-detail__colorway-image'), current.sku,
+    root.querySelector('.product-detail__colorway-image-wrap .product-detail__zoom-trigger')?.getAttribute('href'));
+  const main = { sku: current.sku, href: current.url, url: current.url,
+    colorName: color(root.querySelector('.product-detail__colorway-image-title')), imgSrc: mainImage };
+  const colorways = [main];
+  const seen = new Set([current.sku]);
+  for (const link of root.querySelectorAll('a.product-detail__colorway-link')) {
+    const id = identity(link.getAttribute('href'));
+    if (!id || id.pattern !== current.pattern || new URL(id.url).origin !== base.origin || seen.has(id.sku)) continue;
+    seen.add(id.sku);
+    colorways.push({ sku: id.sku, href: id.url, url: id.url,
+      colorName: color(link.querySelector('.product-detail__colorway-title')),
+      imgSrc: imageFor(link.querySelector('img.product-detail__colorway-thumbnail'), id.sku) });
+  }
+  return { main, colorways };
+}
+
+module.exports = { extractVersaColorways };
diff --git a/shopify/scripts/cron-create-sample-variants.sh b/shopify/scripts/cron-create-sample-variants.sh
index 96b04370..a3cf06bc 100755
--- a/shopify/scripts/cron-create-sample-variants.sh
+++ b/shopify/scripts/cron-create-sample-variants.sh
@@ -29,6 +29,9 @@ if [ -f "$PROGRESS_FILE" ]; then
 fi
 
 cd /root/Projects/Designer-Wallcoverings
-node "$SCRIPT" >> "$LOG_FILE" 2>&1
+# --apply REQUIRED: as of 2026-09-14 create-missing-sample-variants.js is DRY BY DEFAULT
+# (dryRun = !args.includes('--apply')). Without --apply this cron would run dry, create zero
+# variants, exit 0, and its REMAINING>0 guard would never let it self-remove — a silent no-op.
+node "$SCRIPT" --apply >> "$LOG_FILE" 2>&1
 
 echo "=== $(date '+%Y-%m-%d %H:%M:%S PT') === Finished ===" >> "$LOG_FILE"

← ac9e69dc auto-data-snapshot: 2026-09-14T15:08:48 (3 data files) — sho  ·  back to Designer Wallcoverings  ·  fix(TK-11503): validate Shopify token via read_products prob 25dadd6f →