[object Object]

← back to Designer Wallcoverings

Add gated Trending-collection backfill apply script (1040 net-new, DTD 3/3 Option C)

ffb5c80a8669ee67d645eedbcdba3a95645a2082 · 2026-06-19 15:42:32 -0700 · SteveStudio2

Files touched

Diff

commit ffb5c80a8669ee67d645eedbcdba3a95645a2082
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Fri Jun 19 15:42:32 2026 -0700

    Add gated Trending-collection backfill apply script (1040 net-new, DTD 3/3 Option C)
---
 shopify/scripts/cadence/trending-backfill-apply.js | 86 ++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/shopify/scripts/cadence/trending-backfill-apply.js b/shopify/scripts/cadence/trending-backfill-apply.js
new file mode 100644
index 00000000..ea4b4ffb
--- /dev/null
+++ b/shopify/scripts/cadence/trending-backfill-apply.js
@@ -0,0 +1,86 @@
+#!/usr/bin/env node
+/**
+ * GATED — Trending Wallcovering Collection 2026 backfill (DTD 3/3 Option C, 2026-06-19).
+ *
+ * Adds the 1040 ACTIVE+published net-new products (created since 2026-06-12, NOT
+ * already members) to the live custom collection 298339205171, newest-first, then
+ * flips the collection sort_order to created-desc and DROPS the legacy 250-cap.
+ *
+ * This is a LIVE storefront write (gated). DRY-RUN by default; pass --commit to write.
+ * Reads the frozen id list assembled by the audit (newest-first):
+ *   ~/.claude/yolo-queue/pending-approval/trending-backfill-ids-1040.json
+ *
+ * Usage:
+ *   node trending-backfill-apply.js                 # DRY-RUN (default): prints plan, writes nothing
+ *   node trending-backfill-apply.js --commit        # LIVE: add members + set sort_order=created-desc
+ *   node trending-backfill-apply.js --commit --no-sort   # add members only, leave sort_order as-is
+ */
+const fs = require('fs'), os = require('os'), https = require('https');
+const args = process.argv.slice(2);
+const COMMIT = args.includes('--commit');
+const NO_SORT = args.includes('--no-sort');
+const COLL_ID = 298339205171;
+const COLL_GID = `gid://shopify/Collection/${COLL_ID}`;
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const IDS_FILE = os.homedir() + '/.claude/yolo-queue/pending-approval/trending-backfill-ids-1040.json';
+
+const env = fs.readFileSync(os.homedir() + '/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m) || [])[1].replace(/['"]/g, '').trim();
+
+function gql(query, variables) {
+  return new Promise((res, rej) => {
+    const body = JSON.stringify({ query, variables });
+    const req = https.request({ host: SHOP, path: '/admin/api/2024-10/graphql.json', method: 'POST',
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } },
+      r => { let d = ''; r.on('data', c => d += c); r.on('end', () => { try { const j = JSON.parse(d); j.errors ? rej(JSON.stringify(j.errors)) : res(j.data); } catch (e) { rej(d.slice(0, 400)); } }); });
+    req.on('error', rej); req.write(body); req.end();
+  });
+}
+function rest(method, path, payload) {
+  return new Promise((res, rej) => {
+    const body = payload ? JSON.stringify(payload) : null;
+    const req = https.request({ host: SHOP, path, method,
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', ...(body ? { 'Content-Length': Buffer.byteLength(body) } : {}) } },
+      r => { let d = ''; r.on('data', c => d += c); r.on('end', () => { try { res(JSON.parse(d)); } catch (e) { rej(d.slice(0, 400)); } }); });
+    req.on('error', rej); if (body) req.write(body); req.end();
+  });
+}
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+(async () => {
+  const ids = JSON.parse(fs.readFileSync(IDS_FILE, 'utf8'));   // newest-first
+  console.log(`Loaded ${ids.length} product ids (newest-first) from ${IDS_FILE}`);
+  console.log(`Mode: ${COMMIT ? 'LIVE COMMIT' : 'DRY-RUN'} | collection ${COLL_ID} | set sort_order=created-desc: ${COMMIT && !NO_SORT}`);
+
+  if (!COMMIT) {
+    console.log('DRY-RUN — would add these in', Math.ceil(ids.length / 50), 'batches of 50 via collectionAddProducts.');
+    console.log('  first:', ids.slice(0, 2).map(p => p.id).join(', '), '… last:', ids.slice(-2).map(p => p.id).join(', '));
+    console.log('Pass --commit to execute.');
+    return;
+  }
+
+  // 1. add members, batched 50/call, newest-first (so created-desc order is consistent)
+  const gids = ids.map(p => p.gid || `gid://shopify/Product/${p.id}`);
+  let added = 0;
+  for (let i = 0; i < gids.length; i += 50) {
+    const batch = gids.slice(i, i + 50);
+    const d = await gql(`mutation($id:ID!,$pids:[ID!]!){collectionAddProducts(id:$id,productIds:$pids){userErrors{field message}}}`,
+      { id: COLL_GID, pids: batch });
+    const errs = d.collectionAddProducts.userErrors;
+    if (errs && errs.length) console.error('  batch', i / 50, 'errors:', JSON.stringify(errs));
+    added += batch.length;
+    console.log(`  added batch ${i / 50 + 1}/${Math.ceil(gids.length / 50)} (${added}/${gids.length})`);
+    await sleep(500);
+  }
+
+  // 2. flip sort_order to created-desc + drop the legacy 250-cap (no trim performed here)
+  if (!NO_SORT) {
+    const r = await rest('PUT', `/admin/api/2024-10/custom_collections/${COLL_ID}.json`,
+      { custom_collection: { id: COLL_ID, sort_order: 'created-desc' } });
+    console.log('  sort_order ->', r.custom_collection ? r.custom_collection.sort_order : JSON.stringify(r).slice(0, 200));
+  }
+
+  // 3. verify
+  const v = await gql(`query($id:ID!){collection(id:$id){productsCount{count} sortOrder}}`, { id: COLL_GID });
+  console.log(`DONE. Collection now ${v.collection.productsCount.count} members, sortOrder=${v.collection.sortOrder}`);
+})().catch(e => { console.error('ERR', e); process.exit(1); });

← 2e31cf35 cadence-import: auto-add newly-published products to Trendin  ·  back to Designer Wallcoverings  ·  cadence: add description to activation gate (all new must ha 8dcb9177 →