← back to Dw Sku Integrity
TK-11002: fix live write — productVariantUpdate removed in API 2024-10; use productVariantsBulkUpdate(productId,variants) with inventoryItem.sku, grouped per product; rollback path too. Schema-validated + dry-run clean.
233fb7fa6e7ddc6977288e7283200cb108034df2 · 2026-08-31 09:53:11 -0700 · steve
Files touched
M tk11002-harlequin-prefix-fix.mjs
Diff
commit 233fb7fa6e7ddc6977288e7283200cb108034df2
Author: steve <steve@designerwallcoverings.com>
Date: Mon Aug 31 09:53:11 2026 -0700
TK-11002: fix live write — productVariantUpdate removed in API 2024-10; use productVariantsBulkUpdate(productId,variants) with inventoryItem.sku, grouped per product; rollback path too. Schema-validated + dry-run clean.
---
tk11002-harlequin-prefix-fix.mjs | 52 +++++++++++++++++++++++++---------------
1 file changed, 33 insertions(+), 19 deletions(-)
diff --git a/tk11002-harlequin-prefix-fix.mjs b/tk11002-harlequin-prefix-fix.mjs
index 647701b..5a11173 100644
--- a/tk11002-harlequin-prefix-fix.mjs
+++ b/tk11002-harlequin-prefix-fix.mjs
@@ -86,24 +86,33 @@ async function runForward() {
if (!APPLY) { console.log('\nDRY-RUN — no writes. Re-run with --apply (Steve `!`) to execute.'); return; }
- console.log('\n--apply: writing LIVE (Shopify first, then mirror, CAS-guarded)...');
+ console.log('\n--apply: writing LIVE (Shopify productVariantsBulkUpdate per product, then mirror, CAS-guarded)...');
let ok = 0, skip = 0;
- for (const p of plan) {
- // CAS: re-read this variant live; abort this variant if it drifted off DWHQ-
- const chk = await gql(`query($id:ID!){ productVariant(id:$id){ id sku } }`, { id: p.variant });
- if (!chk.productVariant || chk.productVariant.sku !== p.old_sku) { console.log(` CAS-drift SKIP ${p.old_sku}`); skip++; continue; }
+ const byProduct = new Map();
+ for (const p of plan) { if (!byProduct.has(p.product)) byProduct.set(p.product, []); byProduct.get(p.product).push(p); }
+ for (const [productId, items] of byProduct) {
+ // CAS: re-read each variant live; only include those still on DWHQ- (SKU set via inventoryItem.sku in 2024-10)
+ const variants = [];
+ for (const p of items) {
+ const chk = await gql(`query($id:ID!){ productVariant(id:$id){ id sku } }`, { id: p.variant });
+ if (!chk.productVariant || chk.productVariant.sku !== p.old_sku) { console.log(` CAS-drift SKIP ${p.old_sku}`); skip++; continue; }
+ variants.push({ id: p.variant, inventoryItem: { sku: p.new_sku } });
+ }
+ if (!variants.length) continue;
const res = await gql(
- `mutation($input:ProductVariantInput!){ productVariantUpdate(input:$input){ productVariant{ id sku } userErrors{ field message } } }`,
- { input: { id: p.variant, sku: p.new_sku } }
+ `mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){ productVariantsBulkUpdate(productId:$productId,variants:$variants){ productVariants{ id sku } userErrors{ field message } } }`,
+ { productId, variants }
);
- const ue = res.productVariantUpdate.userErrors;
- if (ue.length) { console.log(` ERROR ${p.old_sku}: ${JSON.stringify(ue)}`); skip++; continue; }
- // mirror follows (CAS on old value)
- psql(`update shopify_products set sku=case when sku=${q(p.mirror_sku)} then ${q(restamp(p.mirror_sku))} else sku end, ` +
- `variant_sku=case when variant_sku like 'DWHQ-%' then replace(variant_sku,'DWHQ-','DWHF-') else variant_sku end, ` +
- `dw_sku=case when dw_sku like 'DWHQ-%' then replace(dw_sku,'DWHQ-','DWHF-') else dw_sku end ` +
- `where id=${p.mirror_id}`);
- ok++;
+ const ue = res.productVariantsBulkUpdate.userErrors;
+ if (ue.length) { console.log(` ERROR product ${productId}: ${JSON.stringify(ue)}`); skip += variants.length; continue; }
+ // mirror follows once per product row (idempotent DWHQ->DWHF replace)
+ for (const mid of new Set(items.map(x => x.mirror_id))) {
+ psql(`update shopify_products set sku=case when sku like 'DWHQ-%' then replace(sku,'DWHQ-','DWHF-') else sku end, ` +
+ `variant_sku=case when variant_sku like 'DWHQ-%' then replace(variant_sku,'DWHQ-','DWHF-') else variant_sku end, ` +
+ `dw_sku=case when dw_sku like 'DWHQ-%' then replace(dw_sku,'DWHQ-','DWHF-') else dw_sku end ` +
+ `where id=${mid}`);
+ }
+ ok += variants.length;
}
console.log(`\nDone: ${ok} written, ${skip} skipped.`);
console.log('\nPost-check — re-running prefix-integrity canary:');
@@ -117,10 +126,15 @@ async function runRollback(mapPath) {
const plan = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
console.log(`ROLLBACK ${plan.length} variants from ${mapPath}`);
if (!APPLY) { console.log('DRY-RUN rollback — re-run with --apply to revert.'); return; }
- for (const p of plan) {
- await gql(`mutation($input:ProductVariantInput!){ productVariantUpdate(input:$input){ userErrors{ message } } }`,
- { input: { id: p.variant, sku: p.old_sku } });
- psql(`update shopify_products set sku=${q(p.mirror_sku)}, variant_sku=${q(p.mirror_variant_sku)}, dw_sku=${q(p.mirror_dw_sku)} where id=${p.mirror_id}`);
+ const byP = new Map();
+ for (const p of plan) { if (!byP.has(p.product)) byP.set(p.product, []); byP.get(p.product).push(p); }
+ for (const [productId, items] of byP) {
+ await gql(`mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){ productVariantsBulkUpdate(productId:$productId,variants:$variants){ userErrors{ message } } }`,
+ { productId, variants: items.map(p => ({ id: p.variant, inventoryItem: { sku: p.old_sku } })) });
+ for (const mid of new Set(items.map(p => p.mirror_id))) {
+ const p = items.find(x => x.mirror_id === mid);
+ psql(`update shopify_products set sku=${q(p.mirror_sku)}, variant_sku=${q(p.mirror_variant_sku)}, dw_sku=${q(p.mirror_dw_sku)} where id=${mid}`);
+ }
}
console.log('Rollback complete.');
}
← 289793b TK-11002: Harlequin DWHQ-->DWHF- prefix re-stamp (Option A)
·
back to Dw Sku Integrity
·
auto-data-snapshot: 2026-08-31T10:06:07 (4 data files) — dat 0b977b0 →