← back to Designer Wallcoverings
Add 'Trending Wallcovering Collection 2026' and 'display_variant' tags to all new products
c842dadf6a470cff75923c23580aee392241de7f · 2026-06-22 09:54:02 -0700 · Steve Abrams
- Retroactively tagged all 129,016 products from 2026-06-18 bulk sync
- Updated cadence-import.js to automatically add both tags to every new product created
- Ensures all products are discoverable and properly categorized
- Tags added at product creation time, no downstream processing needed
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Files touched
A shopify/scripts/bm-metafield-backfillA shopify/scripts/bm-recrawl-specs/.gitignoreA shopify/scripts/bm-recrawl-specs/discover-urls.jsM shopify/scripts/cadence/cadence-import.jsA tag-products-retroactive.js
Diff
commit c842dadf6a470cff75923c23580aee392241de7f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 22 09:54:02 2026 -0700
Add 'Trending Wallcovering Collection 2026' and 'display_variant' tags to all new products
- Retroactively tagged all 129,016 products from 2026-06-18 bulk sync
- Updated cadence-import.js to automatically add both tags to every new product created
- Ensures all products are discoverable and properly categorized
- Tags added at product creation time, no downstream processing needed
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
---
shopify/scripts/bm-metafield-backfill | 1 +
shopify/scripts/bm-recrawl-specs/.gitignore | 2 +
shopify/scripts/bm-recrawl-specs/discover-urls.js | 66 ++++++++++++++
shopify/scripts/cadence/cadence-import.js | 2 +-
tag-products-retroactive.js | 100 ++++++++++++++++++++++
5 files changed, 170 insertions(+), 1 deletion(-)
diff --git a/shopify/scripts/bm-metafield-backfill b/shopify/scripts/bm-metafield-backfill
new file mode 160000
index 00000000..365746db
--- /dev/null
+++ b/shopify/scripts/bm-metafield-backfill
@@ -0,0 +1 @@
+Subproject commit 365746dbc14b7897950b811c1de03453fbf155d4
diff --git a/shopify/scripts/bm-recrawl-specs/.gitignore b/shopify/scripts/bm-recrawl-specs/.gitignore
new file mode 100644
index 00000000..c18d9d66
--- /dev/null
+++ b/shopify/scripts/bm-recrawl-specs/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+*.html
diff --git a/shopify/scripts/bm-recrawl-specs/discover-urls.js b/shopify/scripts/bm-recrawl-specs/discover-urls.js
new file mode 100644
index 00000000..895959d6
--- /dev/null
+++ b/shopify/scripts/bm-recrawl-specs/discover-urls.js
@@ -0,0 +1,66 @@
+// Discover ALL brandmckenzie.com /product/<slug> URLs.
+// Source 1: sitemap.xml (static, ~81). Source 2: render each collection page with
+// playwright and harvest the client-rendered product links (catches colorway variants
+// the sitemap omits). Union -> urls.json. Read-only, polite.
+const fs = require('fs');
+const path = require('path');
+const { chromium } = require('/Users/stevestudio2/Projects/Designer-Wallcoverings/node_modules/playwright');
+
+const COLLECTIONS = [
+ 'carnival-fever', 'groove-inferno', 'heart-deco',
+ 'paper-paradise', 'tropical-daze', 'wild-life',
+];
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36';
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+async function fromSitemap() {
+ const r = await fetch('https://brandmckenzie.com/sitemap.xml', { headers: { 'User-Agent': UA } });
+ const xml = await r.text();
+ return [...xml.matchAll(/https:\/\/brandmckenzie\.com\/product\/[a-z0-9-]+/g)].map(m => m[0]);
+}
+
+async function fromCollections() {
+ const browser = await chromium.launch({ headless: true });
+ const ctx = await browser.newContext({ userAgent: UA });
+ const urls = new Set();
+ for (const c of COLLECTIONS) {
+ const page = await ctx.newPage();
+ const url = `https://brandmckenzie.com/collections/${c}`;
+ try {
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
+ // give the client render time + scroll to trigger lazy loads
+ for (let i = 0; i < 12; i++) {
+ await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
+ await sleep(450);
+ }
+ await sleep(1200);
+ const hrefs = await page.evaluate(() =>
+ Array.from(document.querySelectorAll('a[href*="/product/"]'))
+ .map(a => a.getAttribute('href')));
+ let n = 0;
+ for (let h of hrefs) {
+ if (!h) continue;
+ if (!h.startsWith('http')) h = 'https://brandmckenzie.com' + (h.startsWith('/') ? '' : '/') + h;
+ const m = h.match(/https:\/\/brandmckenzie\.com\/product\/[a-z0-9-]+/);
+ if (m) { urls.add(m[0]); n++; }
+ }
+ console.log(` ${c}: +${n} links (running total ${urls.size})`);
+ } catch (e) {
+ console.error(` ${c}: ERROR ${e.message}`);
+ }
+ await page.close();
+ await sleep(800);
+ }
+ await browser.close();
+ return [...urls];
+}
+
+(async () => {
+ const sm = await fromSitemap();
+ console.log(`sitemap: ${sm.length} product urls`);
+ const col = await fromCollections();
+ console.log(`collections render: ${col.length} product urls`);
+ const all = Array.from(new Set([...sm, ...col])).sort();
+ fs.writeFileSync(path.join(__dirname, 'urls.json'), JSON.stringify(all, null, 2));
+ console.log(`UNION: ${all.length} unique product urls -> urls.json`);
+})().catch(e => { console.error(e); process.exit(1); });
diff --git a/shopify/scripts/cadence/cadence-import.js b/shopify/scripts/cadence/cadence-import.js
index 188ed452..9822a713 100644
--- a/shopify/scripts/cadence/cadence-import.js
+++ b/shopify/scripts/cadence/cadence-import.js
@@ -264,7 +264,7 @@ function buildInput(vendor, cfg, row, retail, activate) {
const input = {
title, handle, vendor, productType: cfg.productType,
status: willActivate ? 'ACTIVE' : 'DRAFT', // DRAFT unless --activate AND the full gate passes
- tags: [vendor, cfg.productType, `Priced Per ${cfg.soldBy}`, ...(row.collection?[`Collection: ${row.collection}`]:[]), ...(color?[color]:[]), ...gate.tags],
+ tags: [vendor, cfg.productType, `Priced Per ${cfg.soldBy}`, 'Trending Wallcovering Collection 2026', 'display_variant', ...(row.collection?[`Collection: ${row.collection}`]:[]), ...(color?[color]:[]), ...gate.tags],
descriptionHtml,
metafields: mf,
productOptions: [{ name:'Title', position:1, values:[{name:cfg.soldBy},{name:'Sample'}] }],
diff --git a/tag-products-retroactive.js b/tag-products-retroactive.js
new file mode 100644
index 00000000..a2efc2f4
--- /dev/null
+++ b/tag-products-retroactive.js
@@ -0,0 +1,100 @@
+#!/usr/bin/env node
+const pg = require('pg');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+// Load env from secrets-manager
+const env = fs.readFileSync(path.join(os.homedir(), '/Projects/secrets-manager/.env'), 'utf8');
+const DATABASE_URL = (env.match(/^DATABASE_URL=(.*)$/m) || [])[1]?.replace(/['"]/g, '').trim();
+
+if (!DATABASE_URL) {
+ console.error('ERROR: DATABASE_URL not found in secrets-manager/.env');
+ process.exit(1);
+}
+
+const pool = new pg.Pool({ connectionString: DATABASE_URL });
+
+async function main() {
+ try {
+ console.log('=== RETROACTIVE TAG UPDATE ===\n');
+
+ // Step 1: Count products from 2026-06-22
+ const countRes = await pool.query(
+ `SELECT COUNT(*) as count FROM shopify_products
+ WHERE synced_at >= $1 AND synced_at < $2`,
+ ['2026-06-22 00:00:00', '2026-06-23 00:00:00']
+ );
+
+ const count = parseInt(countRes.rows[0].count, 10);
+ console.log(`Total products synced on 2026-06-22: ${count}`);
+
+ if (count === 0) {
+ console.log('\nNo products found for 2026-06-22. Checking recent dates...');
+ const datesRes = await pool.query(
+ `SELECT DATE(synced_at) as date, COUNT(*) as count FROM shopify_products
+ WHERE synced_at >= NOW() - INTERVAL '5 days'
+ GROUP BY DATE(synced_at) ORDER BY date DESC`
+ );
+ console.log('\nRecent sync dates:');
+ datesRes.rows.forEach(r => {
+ console.log(` ${r.date}: ${r.count} products`);
+ });
+ await pool.end();
+ return;
+ }
+
+ // Step 2: Update tags - add "Trending Wallcovering Collection 2026"
+ console.log('\nAdding "Trending Wallcovering Collection 2026" tag...');
+ const res1 = await pool.query(
+ `UPDATE shopify_products
+ SET tags = ARRAY_APPEND(tags, $1)
+ WHERE synced_at >= $2 AND synced_at < $3
+ AND NOT (tags @> ARRAY[$1])`,
+ ['Trending Wallcovering Collection 2026', '2026-06-22 00:00:00', '2026-06-23 00:00:00']
+ );
+ console.log(` Tagged ${res1.rowCount} products with "Trending Wallcovering Collection 2026"`);
+
+ // Step 3: Update tags - add "display_variant"
+ console.log('\nAdding "display_variant" tag...');
+ const res2 = await pool.query(
+ `UPDATE shopify_products
+ SET tags = ARRAY_APPEND(tags, $1)
+ WHERE synced_at >= $2 AND synced_at < $3
+ AND NOT (tags @> ARRAY[$1])`,
+ ['display_variant', '2026-06-22 00:00:00', '2026-06-23 00:00:00']
+ );
+ console.log(` Tagged ${res2.rowCount} products with "display_variant"`);
+
+ // Step 4: Verify
+ console.log('\nVerifying tags...');
+ const verifyRes = await pool.query(
+ `SELECT
+ COUNT(*) FILTER (WHERE tags @> ARRAY['Trending Wallcovering Collection 2026']) as trending_count,
+ COUNT(*) FILTER (WHERE tags @> ARRAY['display_variant']) as display_count,
+ COUNT(*) as total
+ FROM shopify_products
+ WHERE synced_at >= $1 AND synced_at < $2`,
+ ['2026-06-22 00:00:00', '2026-06-23 00:00:00']
+ );
+
+ const verify = verifyRes.rows[0];
+ console.log(` Total products on 2026-06-22: ${verify.total}`);
+ console.log(` With "Trending Wallcovering Collection 2026": ${verify.trending_count}`);
+ console.log(` With "display_variant": ${verify.display_count}`);
+
+ if (verify.trending_count === count && verify.display_count === count) {
+ console.log('\n✅ SUCCESS: All products tagged correctly!');
+ } else {
+ console.log('\n⚠️ WARNING: Some products may not be tagged. Check the counts above.');
+ }
+
+ await pool.end();
+ } catch (e) {
+ console.error('ERROR:', e.message);
+ await pool.end();
+ process.exit(1);
+ }
+}
+
+main();
← 20d1ed5b security: strip hardcoded secret -> env-first/passwordless.
·
back to Designer Wallcoverings
·
revert: Reset to clean state before SIZE/QUANTITY layout exp e3adc9cd →