[object Object]

← back to Sanderson Onboard

chore: lint (1 dead-var removed) + refactor (named consts, toRestoreRows helper, revert-map guards — behavior-preserving), v1.1.2 → v1.2.0 (session close)

494134f98e03b9fdabb28f4085c6e6aebabb36b8 · 2026-08-31 09:53:08 -0700 · Steve Abrams

Files touched

Diff

commit 494134f98e03b9fdabb28f4085c6e6aebabb36b8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 31 09:53:08 2026 -0700

    chore: lint (1 dead-var removed) + refactor (named consts, toRestoreRows helper, revert-map guards — behavior-preserving), v1.1.2 → v1.2.0 (session close)
---
 tk10873/VERSION            |  2 +-
 tk10873/apply_labeling.mjs | 18 +++++++++++++-----
 tk10873/retire_dupes.mjs   | 22 ++++++++++++++++------
 3 files changed, 30 insertions(+), 12 deletions(-)

diff --git a/tk10873/VERSION b/tk10873/VERSION
index 45a1b3f..26aaba0 100644
--- a/tk10873/VERSION
+++ b/tk10873/VERSION
@@ -1 +1 @@
-1.1.2
+1.2.0
diff --git a/tk10873/apply_labeling.mjs b/tk10873/apply_labeling.mjs
index 28e5c69..48de9d1 100644
--- a/tk10873/apply_labeling.mjs
+++ b/tk10873/apply_labeling.mjs
@@ -50,7 +50,6 @@ import { execFileSync } from 'node:child_process';
 
 const DIR = new URL('.', import.meta.url).pathname;
 const SPEC_IN = process.env.LABELING_SPEC ? `${DIR}${process.env.LABELING_SPEC}` : `${DIR}zoffany_widewidth_labeling_spec.json`;
-const BASE = SPEC_IN.replace(/\.json$/, '').split('/').pop();
 const PLAN_OUT = `${DIR}apply_labeling.live_plan.json`;
 const PREVIEW_OUT = `${DIR}apply_labeling.restore_map.preview.json`;
 const RESTORE_OUT = `${DIR}apply_labeling.restore_map.live.json`; // populated ONLY by --apply, before writes
@@ -63,6 +62,14 @@ const SAMPLE_PRICE = 4.25;
 const NEW_VALUE = 'Per Yard';
 const OLD_VALUE = 'Default Title';
 
+// GraphQL query width limits (must cover all real products; bump if Shopify returns truncation warnings)
+const OPTIONS_FIRST = 5;
+const VARIANTS_FIRST = 20;
+
+// Throttle pacing: pause when cost bucket is low
+const THROTTLE_MIN_AVAILABLE = 200;
+const THROTTLE_PAUSE_MS = 1200;
+
 const argv = process.argv.slice(2);
 const APPLY = argv.includes('--apply');
 const LIVE_OK = argv.includes('--i-understand-this-is-live');
@@ -113,7 +120,7 @@ async function gql(query, variables) {
   if (j.errors) throw new Error('GraphQL errors: ' + JSON.stringify(j.errors));
   // pace against the throttle: if we're low on the cost bucket, breathe.
   const throttle = j.extensions?.cost?.throttleStatus;
-  if (throttle && throttle.currentlyAvailable < 200) await new Promise(r => setTimeout(r, 1200));
+  if (throttle && throttle.currentlyAvailable < THROTTLE_MIN_AVAILABLE) await new Promise(r => setTimeout(r, THROTTLE_PAUSE_MS));
   return j.data;
 }
 
@@ -121,8 +128,8 @@ async function gql(query, variables) {
 const PRODUCT_Q = `query($id: ID!) {
   product(id: $id) {
     id status title descriptionHtml
-    options(first: 5) { id name position optionValues { id name } }
-    variants(first: 20) { nodes { id sku title price selectedOptions { name value } } }
+    options(first: ${OPTIONS_FIRST}) { id name position optionValues { id name } }
+    variants(first: ${VARIANTS_FIRST}) { nodes { id sku title price selectedOptions { name value } } }
   }
 }`;
 
@@ -172,7 +179,7 @@ function appendPdpLine(oldHtml, pdpLine) {
 
 const OPTION_UPDATE_M = `mutation($productId: ID!, $option: OptionUpdateInput!, $optionValuesToUpdate: [OptionValueUpdateInput!]) {
   productOptionUpdate(productId: $productId, option: $option, optionValuesToUpdate: $optionValuesToUpdate) {
-    product { id options(first: 5) { id name optionValues { id name } } }
+    product { id options(first: ${OPTIONS_FIRST}) { id name optionValues { id name } } }
     userErrors { field message }
   }
 }`;
@@ -393,6 +400,7 @@ async function runRevert() {
 
 // ── dispatch ──────────────────────────────────────────────────────────────────
 (async () => {
+  if (REVERT_IDX >= 0 && !REVERT_MAP) throw new Error('--revert requires a restore-map path argument');
   if (REVERT_MAP) return runRevert();
   if (APPLY) return runApply();
   return runPlan();
diff --git a/tk10873/retire_dupes.mjs b/tk10873/retire_dupes.mjs
index 87cf6ef..3613f44 100644
--- a/tk10873/retire_dupes.mjs
+++ b/tk10873/retire_dupes.mjs
@@ -24,9 +24,12 @@ const ORPHANS = [
 ];
 const RETIRE_TO = 'DRAFT'; // reversible; restore sets back to ACTIVE
 
+const MAX_RETRIES = 5;
+const RETRY_BACKOFF_MS = 1200;
+
 const args = process.argv.slice(2);
 const APPLY = args.includes('--apply');
-const REVERT_I = args.indexOf('--revert');
+const REVERT_IDX = args.indexOf('--revert');
 const LIVE_OK = args.includes('--i-understand-this-is-live');
 
 function token() {
@@ -42,7 +45,7 @@ async function gql(query, variables) {
       headers: { 'X-Shopify-Access-Token': token(), 'Content-Type': 'application/json' },
       body: JSON.stringify({ query, variables }),
     });
-    if (res.status === 429 && attempt++ < 5) { await new Promise(r => setTimeout(r, 1200 * attempt)); continue; }
+    if (res.status === 429 && attempt++ < MAX_RETRIES) { await new Promise(r => setTimeout(r, RETRY_BACKOFF_MS * attempt)); continue; }
     const j = await res.json();
     if (j.errors) throw new Error('GraphQL: ' + JSON.stringify(j.errors));
     return j.data;
@@ -57,6 +60,9 @@ async function resolveOne(sku) {
   if (nodes.length !== 1) throw new Error(`FAIL-CLOSED: sku ${sku} resolved to ${nodes.length} products (need exactly 1)`);
   return nodes[0];
 }
+function toRestoreRows(resolved) {
+  return resolved.map(r => ({ sku: r.sku, product_id: r.product_id, old_status: r.old_status, new_status: RETIRE_TO }));
+}
 function persist(path, obj) {
   const fd = fs.openSync(path, 'w');
   fs.writeSync(fd, JSON.stringify(obj, null, 2));
@@ -65,11 +71,15 @@ function persist(path, obj) {
 }
 
 // ── REVERT ────────────────────────────────────────────────────────────────────
-if (REVERT_I !== -1) {
-  const mapPath = args[REVERT_I + 1];
+if (REVERT_IDX !== -1) {
+  const mapPath = args[REVERT_IDX + 1];
   if (!mapPath) throw new Error('--revert needs a restore-map path');
   if (!LIVE_OK) throw new Error('revert is LIVE — pass --i-understand-this-is-live');
   const map = JSON.parse(fs.readFileSync(mapPath, 'utf8')).rows;
+  if (!Array.isArray(map) || !map.length) throw new Error(`restore map has no rows: ${mapPath}`);
+  for (const r of map) {
+    if (!r.product_id || !r.sku || !r.old_status) throw new Error(`restore map row missing required field (product_id/sku/old_status): ${JSON.stringify(r)}`);
+  }
   let ok = 0, errs = [];
   for (const r of map) {
     try {
@@ -97,7 +107,7 @@ if (!APPLY) {
   console.log('=== retire_dupes — PLAN (DRY-RUN, no writes) ===');
   console.log(`orphans resolved: ${resolved.length}/${ORPHANS.length}`);
   for (const r of resolved) console.log(`  ${r.sku}  "${r.title}"  ${r.old_status} → ${RETIRE_TO}   (${r.label})`);
-  persist(PREVIEW_OUT, { generated: 'preview', retire_to: RETIRE_TO, rows: resolved.map(r => ({ sku: r.sku, product_id: r.product_id, old_status: r.old_status, new_status: RETIRE_TO })) });
+  persist(PREVIEW_OUT, { generated: 'preview', retire_to: RETIRE_TO, rows: toRestoreRows(resolved) });
   console.log(`\npreview restore map → ${PREVIEW_OUT}`);
   console.log('LIVE run: node retire_dupes.mjs --apply --i-understand-this-is-live  (Steve, token sourced)');
   process.exit(0);
@@ -105,7 +115,7 @@ if (!APPLY) {
 
 // ── APPLY (LIVE — restore-map FIRST) ───────────────────────────────────────────
 if (!LIVE_OK) throw new Error('--apply is LIVE — pass --i-understand-this-is-live');
-const restore = { generated: 'live', retire_to: RETIRE_TO, rows: resolved.map(r => ({ sku: r.sku, product_id: r.product_id, old_status: r.old_status, new_status: RETIRE_TO })) };
+const restore = { generated: 'live', retire_to: RETIRE_TO, rows: toRestoreRows(resolved) };
 persist(RESTORE_OUT, restore); // COMPLETE restore map + fsync BEFORE any write — this IS the undo
 console.log(`[apply] restore map persisted BEFORE any write → ${RESTORE_OUT}`);
 console.log(`[apply] undo: node retire_dupes.mjs --revert ${RESTORE_OUT} --i-understand-this-is-live`);

← 2d6ad38 TK-10873: record live labeling restore map (44 rows) — the s  ·  back to Sanderson Onboard  ·  auto-data-snapshot: 2026-08-31T10:06:07 (3 data files) — tk1 79bbeca →