[object Object]

← back to Designer Wallcoverings

TK-11786: remove superseded one-off scripts (romo-drilldown, romo-lookup, artmura-title-fix)

83214c102716587a6c7700b03dd62a0441e49c3c · 2026-09-22 13:35:27 -0700 · Steve

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

Files touched

Diff

commit 83214c102716587a6c7700b03dd62a0441e49c3c
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Sep 22 13:35:27 2026 -0700

    TK-11786: remove superseded one-off scripts (romo-drilldown, romo-lookup, artmura-title-fix)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01G3ChReG53fwpNgUESv4SY7
---
 shopify/scripts/artmura-title-fix.js |  55 ------
 shopify/scripts/romo-drilldown.js    | 317 ------------------------------
 shopify/scripts/romo-lookup.js       | 363 -----------------------------------
 3 files changed, 735 deletions(-)

diff --git a/shopify/scripts/artmura-title-fix.js b/shopify/scripts/artmura-title-fix.js
deleted file mode 100644
index 00a9d0f1..00000000
--- a/shopify/scripts/artmura-title-fix.js
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/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/romo-drilldown.js b/shopify/scripts/romo-drilldown.js
deleted file mode 100644
index f562e865..00000000
--- a/shopify/scripts/romo-drilldown.js
+++ /dev/null
@@ -1,317 +0,0 @@
-const path = require('path');
-const { chromium } = require(path.join('/root/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/node_modules/playwright'));
-const { Pool } = require(path.join('/root/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/node_modules/pg'));
-
-const pool = new Pool({
-  connectionString: (process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified')
-});
-
-const BATCH_SIZE = 50;
-const PAGE_TIMEOUT = 15000;
-const DELAY_BETWEEN = 800;
-
-async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
-
-async function scrapeProductPage(page, url) {
-  try {
-    await page.goto(url, { waitUntil: 'domcontentloaded', timeout: PAGE_TIMEOUT });
-    await sleep(2500);
-
-    const data = await page.evaluate(() => {
-      const bodyText = document.body.innerText || '';
-
-      // Extract specs from body text
-      const specPatterns = [
-        { key: 'roll_width', patterns: ['Roll Width'] },
-        { key: 'roll_length', patterns: ['Roll Length'] },
-        { key: 'pattern_repeat', patterns: ['Pattern Repeat', 'Vertical Repeat'] },
-        { key: 'application', patterns: ['Application'] },
-        { key: 'design_style', patterns: ['Design Style'] },
-        { key: 'fire_rating', patterns: ['Fire Ratings', 'Fire Rating'] },
-        { key: 'care', patterns: ['After Care', 'Care Instructions'] },
-        { key: 'colour', patterns: ['Colour'] },
-        { key: 'product_type', patterns: ['Product Type'] },
-        { key: 'environmental', patterns: ['Environmental'] },
-        { key: 'material', patterns: ['Contents', 'Content', 'Composition'] },
-      ];
-
-      const specs = {};
-      const lines = bodyText.split('\n').map(l => l.trim()).filter(Boolean);
-
-      for (const { key, patterns } of specPatterns) {
-        for (const pat of patterns) {
-          // Try "Label\nValue" pattern (label on one line, value on next)
-          for (let i = 0; i < lines.length - 1; i++) {
-            if (lines[i] === pat || lines[i].startsWith(pat + ':')) {
-              let val = lines[i].includes(':') ? lines[i].split(':').slice(1).join(':').trim() : lines[i+1];
-              if (val && val.length < 500 && val !== pat) {
-                specs[key] = val;
-                break;
-              }
-            }
-          }
-          if (specs[key]) break;
-
-          // Try "Label: Value" on same line
-          const regex = new RegExp(pat + '\\s*[:\\-]\\s*(.+)', 'i');
-          const match = bodyText.match(regex);
-          if (match) {
-            specs[key] = match[1].trim().substring(0, 500);
-            break;
-          }
-        }
-      }
-
-      // Collection from breadcrumbs
-      let collection = '';
-      const breadcrumbs = document.querySelectorAll('.breadcrumb a, [class*="breadcrumb"] a, nav a');
-      breadcrumbs.forEach(a => {
-        const text = (a.textContent || '').trim();
-        if (text.toLowerCase().includes('wallcovering') && !text.toLowerCase().includes('all')) {
-          collection = text;
-        }
-      });
-      // Also check for collection in page heading areas
-      if (!collection) {
-        const h2s = document.querySelectorAll('h2, h3, .collection-name, [class*="collection"]');
-        h2s.forEach(el => {
-          const text = (el.textContent || '').trim();
-          if (text.toLowerCase().includes('wallcovering') && text.length < 100) {
-            collection = text;
-          }
-        });
-      }
-
-      // High-res product images (splide gallery)
-      const productImages = [];
-      document.querySelectorAll('.splide__slide img, .product-image img, .product-gallery img, [class*="product"] img').forEach(img => {
-        const src = img.src || img.getAttribute('data-src') || img.getAttribute('data-lazy') || '';
-        if (src && src.includes('catalog/product') && !productImages.includes(src)) {
-          productImages.push(src);
-        }
-      });
-
-      // Room setting images (slideimg class)
-      const roomImages = [];
-      document.querySelectorAll('.slideimg, .room-setting img, [class*="room"] img, [class*="setting"] img, [class*="ambiance"] img, [class*="lifestyle"] img').forEach(el => {
-        let src = '';
-        if (el.tagName === 'IMG') {
-          src = el.src || el.getAttribute('data-src') || '';
-        } else {
-          // Background image
-          const style = el.getAttribute('style') || '';
-          const match = style.match(/url\(['"]?([^'")\s]+)/);
-          if (match) src = match[1];
-          // Also check child images
-          const childImg = el.querySelector('img');
-          if (childImg) src = childImg.src || childImg.getAttribute('data-src') || '';
-        }
-        if (src && !roomImages.includes(src)) {
-          roomImages.push(src);
-        }
-      });
-
-      // Gallery/carousel images
-      document.querySelectorAll('.gallery img, .carousel img, .slider img, [class*="gallery"] img, [class*="slider"] img').forEach(img => {
-        const src = img.src || img.getAttribute('data-src') || '';
-        if (src && src.includes('catalog') && !roomImages.includes(src) && !productImages.includes(src)) {
-          roomImages.push(src);
-        }
-      });
-
-      // Background images that could be room settings
-      document.querySelectorAll('[style*="background-image"]').forEach(el => {
-        const style = el.getAttribute('style') || '';
-        const match = style.match(/url\(['"]?([^'")\s]+)/);
-        if (match && match[1].includes('catalog') && !roomImages.includes(match[1])) {
-          roomImages.push(match[1]);
-        }
-      });
-
-      // Description - first paragraph or product description
-      let description = '';
-      const descEl = document.querySelector('.product-description, [class*="description"], .product-detail p, article p');
-      if (descEl) {
-        description = (descEl.textContent || '').trim().substring(0, 1000);
-      }
-
-      // Color variants on page
-      const colorVariants = [];
-      document.querySelectorAll('.colour-option, [class*="colour"] a, [class*="color-option"], [class*="swatch"] a').forEach(el => {
-        const href = el.href || '';
-        const text = (el.textContent || '').trim();
-        const img = el.querySelector('img');
-        const imgSrc = img ? (img.src || '') : '';
-        if (href || text) {
-          colorVariants.push({ text: text.substring(0, 100), href, image: imgSrc });
-        }
-      });
-
-      return {
-        specs,
-        collection,
-        productImages: productImages.slice(0, 10),
-        roomImages: roomImages.slice(0, 10),
-        description,
-        colorVariants: colorVariants.slice(0, 20),
-        bodySnippet: bodyText.substring(0, 2000)
-      };
-    });
-
-    return data;
-  } catch (err) {
-    return { error: err.message, specs: {}, productImages: [], roomImages: [] };
-  }
-}
-
-async function main() {
-  console.log('=== Romo Drill-Down Scraper ===');
-  console.log(`Started: ${new Date().toISOString()}`);
-
-  // Get all products that need drill-down (no width = not yet scraped)
-  const { rows: products } = await pool.query(`
-    SELECT id, mfr_sku, pattern_name, color_name, brand, product_url, image_url
-    FROM romo_catalog
-    WHERE width IS NULL OR width = ''
-    ORDER BY id
-  `);
-
-  console.log(`Products to scrape: ${products.length}`);
-
-  const browser = await chromium.launch({
-    headless: true,
-    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled']
-  });
-  const context = await browser.newContext({
-    userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
-    viewport: { width: 1920, height: 1080 },
-    ignoreHTTPSErrors: true
-  });
-  await context.addInitScript(() => { Object.defineProperty(navigator, 'webdriver', { get: () => false }); });
-  const page = await context.newPage();
-
-  let updated = 0, errors = 0, skipped = 0;
-  const startTime = Date.now();
-
-  for (let i = 0; i < products.length; i++) {
-    const product = products[i];
-
-    if (!product.product_url) {
-      skipped++;
-      continue;
-    }
-
-    if (i % 50 === 0) {
-      const elapsed = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
-      const rate = updated > 0 ? (elapsed / updated).toFixed(2) : '?';
-      console.log(`\n--- Progress: ${i}/${products.length} | Updated: ${updated} | Errors: ${errors} | ${elapsed}min | ${rate}min/item ---`);
-    }
-
-    try {
-      const data = await scrapeProductPage(page, product.product_url);
-
-      if (data.error) {
-        console.log(`  ✗ [${i}] ${product.mfr_sku}: ${data.error}`);
-        errors++;
-        await sleep(DELAY_BETWEEN);
-        continue;
-      }
-
-      const specs = data.specs || {};
-
-      // Get best product image (prefer high-res from gallery)
-      let bestImage = product.image_url;
-      if (data.productImages.length > 0) {
-        // Get highest res version
-        bestImage = data.productImages[0].replace(/\/\d+x\d+\//, '/720x720/');
-      }
-
-      // Build room setting images array
-      const roomSettingImages = data.roomImages.map(url => {
-        // Upgrade to larger size
-        return url.replace(/\/\d+x\d+\//, '/900x550/');
-      });
-
-      // Update PostgreSQL
-      await pool.query(`
-        UPDATE romo_catalog SET
-          width = $1,
-          roll_length = $2,
-          pattern_repeat = $3,
-          material = $4,
-          fire_rating = $5,
-          care_instructions = $6,
-          description = $7,
-          collection = $8,
-          image_url = $9,
-          room_setting_images = $10,
-          gallery_images = $11,
-          specs = $12,
-          room_setting_url = $13,
-          updated_at = NOW()
-        WHERE id = $14
-      `, [
-        specs.roll_width || null,
-        specs.roll_length || null,
-        specs.pattern_repeat || null,
-        specs.material || null,
-        specs.fire_rating || null,
-        specs.care || null,
-        data.description || null,
-        data.collection || null,
-        bestImage,
-        JSON.stringify(roomSettingImages),
-        JSON.stringify(data.productImages),
-        JSON.stringify(specs),
-        roomSettingImages.length > 0 ? roomSettingImages[0] : null,
-        product.id
-      ]);
-
-      const specCount = Object.keys(specs).length;
-      const imgCount = data.productImages.length;
-      const roomCount = roomSettingImages.length;
-
-      if (specCount > 0 || imgCount > 0 || roomCount > 0) {
-        console.log(`  ✓ [${i}] ${product.mfr_sku} (${product.brand}): ${specCount} specs, ${imgCount} imgs, ${roomCount} rooms`);
-        updated++;
-      } else {
-        console.log(`  ~ [${i}] ${product.mfr_sku}: no data found on page`);
-        skipped++;
-      }
-
-    } catch (err) {
-      console.log(`  ✗ [${i}] ${product.mfr_sku}: ${err.message}`);
-      errors++;
-    }
-
-    await sleep(DELAY_BETWEEN);
-  }
-
-  const totalTime = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
-
-  console.log(`\n=== COMPLETE ===`);
-  console.log(`Total: ${products.length} | Updated: ${updated} | Errors: ${errors} | Skipped: ${skipped}`);
-  console.log(`Time: ${totalTime} minutes`);
-
-  // Print summary stats
-  const stats = await pool.query(`
-    SELECT
-      COUNT(*) as total,
-      COUNT(width) as has_width,
-      COUNT(roll_length) as has_length,
-      COUNT(pattern_repeat) as has_repeat,
-      COUNT(collection) as has_collection,
-      COUNT(room_setting_url) as has_room,
-      COUNT(description) as has_desc
-    FROM romo_catalog
-  `);
-  console.log('\nDatabase Stats:', JSON.stringify(stats.rows[0], null, 2));
-
-  await browser.close();
-  await pool.end();
-}
-
-main().catch(err => {
-  console.error('Fatal:', err);
-  process.exit(1);
-});
diff --git a/shopify/scripts/romo-lookup.js b/shopify/scripts/romo-lookup.js
deleted file mode 100644
index 9cefd8d8..00000000
--- a/shopify/scripts/romo-lookup.js
+++ /dev/null
@@ -1,363 +0,0 @@
-#!/usr/bin/env node
-/**
- * Look up unmatched ROP- products on Romo website to find MFR numbers.
- * Uses Playwright stealth mode (no proxy needed).
- * Searches by pattern name, extracts W-codes, updates Shopify metafields.
- */
-
-const path = require('path');
-// Use playwright from ImportNewSkufromURL
-const playwrightPath = path.join('/root/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/node_modules/playwright');
-const { chromium } = require(playwrightPath);
-const fs = require('fs');
-const https = require('https');
-
-const SHOPIFY_DOMAIN = "designer-laboratory-sandbox.myshopify.com";
-const SHOPIFY_TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
-const LOG_FILE = "/tmp/romo-lookup.log";
-const RESULTS_FILE = "/tmp/romo-lookup-results.json";
-const SLACK_WEBHOOK = "${SLACK_WEBHOOK_URL}";
-
-const UNMATCHED = {
-  "Santiago Metal Stripe": ["73500"],
-  "Kimora Floral": ["73501"],
-  "Marekeshet Floral": ["73502"],
-  "Dufrey Stripe": ["73503","73504","73505","73506","73507","73509","73511"],
-  "Gingko": ["73508","73516"],
-  "Lisette Leaves": ["73512"],
-  "Pussy Willow": ["73513"],
-  "Fallig Flower": ["73514"],
-  "Bonsai": ["73515"],
-  "Sally Floral": ["73523"],
-  "Tatiana Trellis": ["73530","73531","73532","73533","73534","73535"],
-  "Falling Willows": ["73539","73540"],
-  "Orvieta": ["73543"],
-  "Salamanca": ["73545"],
-  "Regal Chevron": ["73547","73548","73549","73550","73552","73554"],
-  "Parrots": ["73555"],
-  "Tokohama": ["73560","73561","73562","73563"],
-  "Hirohama": ["73566","73567","73568","73569","73570","73571","73572","73574"],
-  "Willow Branches": ["73580","73581","73582","73583"],
-  "Gingko Elegante": ["73593","73594","73595"],
-  "Hiyacinth": ["73597","73598"],
-  "Piza": ["73607","73608","73609","73610","73611","73612"],
-  "Cynthia Lanterns": ["73613","73614","73615","73616"],
-  "Samantha Stripe": ["73625","73626","73627","73628","73629","73630","73631","73632","73633","73634"],
-  "Infatuation Flowers": ["73635","73636","73637","73638"],
-  "Scalamanca": ["73661","73662","73663","73664","73665","73667","73668"],
-  "Bulgara": ["73670","73672"],
-  "Byzantium Flock": ["73675"],
-};
-
-function log(msg) {
-  const ts = new Date().toISOString().substr(11, 8);
-  const line = `[${ts}] ${msg}`;
-  console.log(line);
-  fs.appendFileSync(LOG_FILE, line + '\n');
-}
-
-function shopifyRest(method, endpoint, data) {
-  return new Promise((resolve, reject) => {
-    const payload = data ? JSON.stringify(data) : null;
-    const opts = {
-      hostname: SHOPIFY_DOMAIN,
-      path: `/admin/api/2024-01/${endpoint}`,
-      method,
-      headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN, 'Content-Type': 'application/json' }
-    };
-    if (payload) opts.headers['Content-Length'] = Buffer.byteLength(payload);
-    const req = https.request(opts, res => {
-      let body = '';
-      res.on('data', c => body += c);
-      res.on('end', () => {
-        if (res.statusCode === 429) {
-          setTimeout(() => shopifyRest(method, endpoint, data).then(resolve).catch(reject), 2500);
-          return;
-        }
-        try { resolve(JSON.parse(body)); } catch { resolve(body); }
-      });
-    });
-    req.on('error', reject);
-    if (payload) req.write(payload);
-    req.end();
-  });
-}
-
-function shopifyGql(query) {
-  return new Promise((resolve, reject) => {
-    const payload = JSON.stringify({ query });
-    const opts = {
-      hostname: SHOPIFY_DOMAIN,
-      path: '/admin/api/2024-01/graphql.json',
-      method: 'POST',
-      headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
-    };
-    const req = https.request(opts, res => {
-      let body = '';
-      res.on('data', c => body += c);
-      res.on('end', () => {
-        try { resolve(JSON.parse(body)); } catch { resolve(body); }
-      });
-    });
-    req.on('error', reject);
-    req.write(payload);
-    req.end();
-  });
-}
-
-async function searchRomo(page, term) {
-  try {
-    await page.goto('https://www.romo.com/search/advanced', { waitUntil: 'domcontentloaded', timeout: 20000 });
-    await page.waitForTimeout(2000);
-
-    // Fill search and submit
-    const searchInput = page.locator('input[name="search-query"]').first();
-    await searchInput.fill(term);
-    await searchInput.press('Enter');
-    await page.waitForTimeout(4000);
-
-    // Extract results
-    const data = await page.evaluate(() => {
-      const text = document.body.innerText;
-      // W-codes like W304, W380/06, W364-01
-      const wCodes = [...new Set((text.match(/\bW\d{3,5}[\/\-]?\d{0,2}\b/gi) || []))];
-
-      // Product items - look for links with product info
-      const productLinks = [];
-      document.querySelectorAll('a').forEach(a => {
-        const href = a.href || '';
-        const txt = (a.textContent || '').trim();
-        if (href.includes('/product/') || href.includes('/wallcovering') || (href.includes('/w') && /\/w\d{3}/i.test(href))) {
-          if (txt.length > 2 && txt.length < 150) {
-            productLinks.push({ text: txt, href });
-          }
-        }
-      });
-
-      // Try to find product cards/items
-      const items = [];
-      document.querySelectorAll('.product-item, .search-results-item, .product, [class*="product"], [class*="result"]').forEach(el => {
-        const txt = (el.textContent || '').trim();
-        if (txt.length > 5 && txt.length < 500) {
-          items.push(txt.substring(0, 200));
-        }
-      });
-
-      // Also look for reference codes in specific elements
-      const refs = [];
-      document.querySelectorAll('[class*="ref"], [class*="code"], [class*="sku"], .product-code').forEach(el => {
-        refs.push((el.textContent || '').trim());
-      });
-
-      return {
-        wCodes,
-        productLinks: productLinks.slice(0, 20),
-        items: items.slice(0, 10),
-        refs,
-        title: document.title,
-        resultCount: text.match(/(\d+)\s+items?\s+(were\s+)?found/i)?.[1] || '0',
-        bodySnippet: text.substring(0, 1500)
-      };
-    });
-
-    return data;
-  } catch (err) {
-    log(`  Search error: ${err.message.substring(0, 80)}`);
-    return null;
-  }
-}
-
-async function visitProductPage(page, url) {
-  try {
-    await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
-    await page.waitForTimeout(2000);
-
-    return await page.evaluate(() => {
-      const text = document.body.innerText;
-      const wCodes = [...new Set((text.match(/\bW\d{3,5}[\/\-]?\d{0,2}\b/gi) || []))];
-      const refs = [];
-      document.querySelectorAll('[class*="ref"], [class*="code"], [class*="sku"]').forEach(el => {
-        refs.push((el.textContent || '').trim());
-      });
-      return { wCodes, refs, title: document.title, bodySnippet: text.substring(0, 1000) };
-    });
-  } catch {
-    return null;
-  }
-}
-
-async function main() {
-  fs.writeFileSync(LOG_FILE, '');
-  const startTime = Date.now();
-
-  log('========================================');
-  log('ROMO WEBSITE LOOKUP (Direct - No Proxy)');
-  log('========================================');
-  log(`Patterns to search: ${Object.keys(UNMATCHED).length}`);
-  log(`Total SKUs: ${Object.values(UNMATCHED).flat().length}`);
-
-  const browser = await chromium.launch({
-    headless: true,
-    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled']
-  });
-  const context = await browser.newContext({
-    userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
-    viewport: { width: 1920, height: 1080 },
-    locale: 'en-US',
-    ignoreHTTPSErrors: true
-  });
-  await context.addInitScript(() => {
-    Object.defineProperty(navigator, 'webdriver', { get: () => false });
-  });
-  const page = await context.newPage();
-
-  const allResults = {};
-  let foundCount = 0;
-  let notFoundCount = 0;
-
-  for (const [pattern, skus] of Object.entries(UNMATCHED)) {
-    log(`\n--- "${pattern}" (${skus.length} SKUs) ---`);
-
-    const result = await searchRomo(page, pattern);
-    if (!result) {
-      log('  FAILED to search');
-      allResults[pattern] = { skus, status: 'error' };
-      notFoundCount++;
-      continue;
-    }
-
-    log(`  Results: ${result.resultCount} items, W-codes: [${result.wCodes.join(', ')}], Links: ${result.productLinks.length}`);
-
-    if (result.wCodes.length > 0) {
-      log(`  FOUND: ${result.wCodes.join(', ')}`);
-      allResults[pattern] = { skus, wCodes: result.wCodes, status: 'found' };
-      foundCount++;
-    } else if (result.productLinks.length > 0) {
-      // Visit first product page to get codes
-      log(`  Checking ${result.productLinks.length} product links...`);
-      let pageWCodes = [];
-      for (const link of result.productLinks.slice(0, 3)) {
-        log(`    -> ${link.text.substring(0, 60)} : ${link.href}`);
-        const pageData = await visitProductPage(page, link.href);
-        if (pageData && pageData.wCodes.length > 0) {
-          pageWCodes.push(...pageData.wCodes);
-          log(`       W-codes: ${pageData.wCodes.join(', ')}`);
-        }
-        await page.waitForTimeout(1500);
-      }
-      pageWCodes = [...new Set(pageWCodes)];
-      if (pageWCodes.length > 0) {
-        log(`  FOUND from pages: ${pageWCodes.join(', ')}`);
-        allResults[pattern] = { skus, wCodes: pageWCodes, links: result.productLinks.map(l => l.href), status: 'found' };
-        foundCount++;
-      } else {
-        log(`  Links found but no W-codes extracted`);
-        allResults[pattern] = { skus, links: result.productLinks.map(l => ({t: l.text, h: l.href})), status: 'links_only' };
-        notFoundCount++;
-      }
-    } else {
-      log(`  NOT FOUND on Romo`);
-      allResults[pattern] = { skus, status: 'not_found' };
-      notFoundCount++;
-    }
-
-    await page.waitForTimeout(2000);
-  }
-
-  await browser.close();
-
-  // Save results
-  fs.writeFileSync(RESULTS_FILE, JSON.stringify(allResults, null, 2));
-
-  // Now update Shopify for products where we found W-codes
-  log('\n========================================');
-  log('UPDATING SHOPIFY WITH FOUND MFR NUMBERS');
-  log('========================================');
-
-  let updated = 0;
-  let errors = 0;
-
-  for (const [pattern, data] of Object.entries(allResults)) {
-    if (data.status !== 'found' || !data.wCodes || data.wCodes.length === 0) continue;
-
-    // For each SKU in this pattern, assign the W-code(s)
-    // If multiple W-codes found, we need to figure out which goes to which SKU
-    // For now, if only 1 W-code, assign to all. If multiple, log for review.
-    const mfr = data.wCodes.length === 1 ? data.wCodes[0] : data.wCodes.join(', ');
-
-    for (const skuNum of data.skus) {
-      const sku = `ROP-${skuNum}`;
-
-      // Find product ID by SKU via GraphQL
-      const gqlResult = await shopifyGql(`{ products(first:1, query:"sku:${sku}") { edges { node { id variants(first:5) { edges { node { id sku } } } } } } }`);
-
-      const edges = gqlResult?.data?.products?.edges;
-      if (!edges || edges.length === 0) {
-        log(`  ${sku}: Product not found in Shopify`);
-        continue;
-      }
-
-      const productId = edges[0].node.id.replace('gid://shopify/Product/', '');
-
-      // If multiple W-codes and multiple SKUs, try to match by position
-      let thisMfr = mfr;
-      if (data.wCodes.length > 1 && data.skus.length > 1) {
-        const idx = data.skus.indexOf(skuNum);
-        if (idx < data.wCodes.length) {
-          thisMfr = data.wCodes[idx];
-        }
-      }
-
-      log(`  ${sku} -> MFR: ${thisMfr}`);
-
-      const updateResult = await shopifyRest('PUT', `products/${productId}.json`, {
-        product: {
-          id: parseInt(productId),
-          metafields: [
-            { namespace: 'custom', key: 'manufacturer_sku', value: thisMfr, type: 'single_line_text_field' },
-            { namespace: 'dwc', key: 'manufacturer_sku', value: thisMfr, type: 'single_line_text_field' }
-          ]
-        }
-      });
-
-      if (updateResult?.product) {
-        updated++;
-      } else {
-        errors++;
-        log(`    ERROR updating ${sku}`);
-      }
-
-      await new Promise(r => setTimeout(r, 600));
-    }
-  }
-
-  const duration = ((Date.now() - startTime) / 1000).toFixed(1);
-
-  log('\n========================================');
-  log('FINAL SUMMARY');
-  log('========================================');
-  log(`Patterns searched: ${Object.keys(UNMATCHED).length}`);
-  log(`Found MFR codes: ${foundCount}`);
-  log(`Not found: ${notFoundCount}`);
-  log(`Shopify updated: ${updated}`);
-  log(`Errors: ${errors}`);
-  log(`Duration: ${duration}s`);
-
-  // Slack
-  const slackMsg = {
-    text: `${errors === 0 ? '✅' : '⚠️'} *Romo MFR Lookup Complete*\n• Patterns searched: ${Object.keys(UNMATCHED).length}\n• Found: ${foundCount}\n• Not found: ${notFoundCount}\n• Shopify updated: ${updated}\n• Errors: ${errors}\n• Duration: ${duration}s`
-  };
-  try {
-    const req = https.request(SLACK_WEBHOOK, { method: 'POST', headers: { 'Content-Type': 'application/json' } });
-    req.write(JSON.stringify(slackMsg));
-    req.end();
-    log('Slack notification sent');
-  } catch {}
-
-  log('Done!');
-}
-
-main().catch(err => {
-  log(`FATAL: ${err.message}`);
-  process.exit(1);
-});

← 008592bf TK-11786: scraper fixes — anna-french null-byte sentinel, sl  ·  back to Designer Wallcoverings  ·  TK-11786 #4: HMAC-sign DW Central SSO session (close base64 733bbfd2 →