← 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
D shopify/scripts/artmura-title-fix.jsD shopify/scripts/romo-drilldown.jsD shopify/scripts/romo-lookup.js
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 →