← back to Shopify Sample Shipping
restore-band.mjs
64 lines
#!/usr/bin/env node
// TK-11333 — UNDO for remove-band.mjs (and general "put the free band back"). Snapshot-driven
// TRUE inverse with two paths:
// • method PRESENT (was deactivated) -> reactivate + restore exact prior conditions.
// • method ABSENT (was deleted, or never present) -> RECREATE it from the snapshot
// (DeliveryRateDefinition $0 + the snapshot's price conditions) in the Domestic zone.
// Snapshot source: verification/remove-band-snapshot.json, else raise-band-snapshot.json.
// DRY-RUN BY DEFAULT; --apply to write.
import { query } from './query.mjs';
import fs from 'node:fs';
const APPLY = process.argv.includes('--apply');
const PROFILE = 'gid://shopify/DeliveryProfile/29033627699';
const METHOD_NAME = 'Free Shipping (No Tracking)';
const CANDIDATES = ['./verification/remove-band-snapshot.json', './verification/raise-band-snapshot.json'];
let snap = null, snapPath = null;
for (const p of CANDIDATES) { const u = new URL(p, import.meta.url); if (fs.existsSync(u)) { snap = JSON.parse(fs.readFileSync(u, 'utf8')); snapPath = p; break; } }
if (!snap) { console.error('no band snapshot found — expected verification/remove-band-snapshot.json'); process.exit(1); }
const snapConds = (snap.method?.methodConditions || []).filter(c => c.conditionCriteria?.amount !== undefined)
.map(c => ({ operator: c.operator, amount: String(parseFloat(c.conditionCriteria.amount)) }));
const snapPrice = snap.method?.rateProvider?.price?.amount != null ? String(parseFloat(snap.method.rateProvider.price.amount)) : '0';
console.log('=== restore-band (' + (APPLY ? 'APPLY' : 'DRY-RUN') + ') using ' + snapPath + ' ===');
console.log('band to restore: price $' + snapPrice + ' conditions:', snapConds.map(c => `${c.operator} $${c.amount}`).join(' & ') || '(none)');
// same working query as remove-band (includes rateProvider so brace count is correct)
const rq = `query($id:ID!){deliveryProfile(id:$id){profileLocationGroups{locationGroup{id} locationGroupZones(first:20){nodes{zone{id name} methodDefinitions(first:40){nodes{id name active rateProvider{__typename ... on DeliveryRateDefinition{price{amount currencyCode}}} methodConditions{id operator conditionCriteria{__typename ... on MoneyV2{amount currencyCode}}}}}}}}}}`;
const d = await query(rq, { id: PROFILE });
let m = null, lg = null, zone = null;
for (const g of d.deliveryProfile.profileLocationGroups) for (const z of g.locationGroupZones.nodes) {
if (z.zone.name !== 'Domestic') continue;
lg = g.locationGroup.id; zone = z.zone.id;
for (const md of z.methodDefinitions.nodes) if (md.name === METHOD_NAME) m = md;
}
if (!lg || !zone) { console.error('ABORT: Domestic zone not found'); process.exit(1); }
console.log(m ? `live method present (active=${m.active})` : 'live method ABSENT — will RECREATE');
if (!APPLY) { console.log('\nDry-run only. Re-run with --apply.'); process.exit(0); }
const mut = `mutation($id:ID!,$profile:DeliveryProfileInput!){deliveryProfileUpdate(id:$id,profile:$profile){userErrors{field message}}}`;
if (m) {
// reactivate
const act = (await query(mut, { id: PROFILE, profile: { locationGroupsToUpdate: [{ id: lg, zonesToUpdate: [{ id: zone, methodDefinitionsToUpdate: [{ id: m.id, active: true }] }] }] } })).deliveryProfileUpdate;
if (act.userErrors?.length) { console.error('reactivate ERR', JSON.stringify(act.userErrors)); process.exit(1); }
const live = m.methodConditions.map(c => `${c.operator}:${parseFloat(c.conditionCriteria?.amount)}`).sort().join('|');
const want = snapConds.map(c => `${c.operator}:${parseFloat(c.amount)}`).sort().join('|');
if (snapConds.length && live !== want) {
if (m.methodConditions.length) await query(mut, { id: PROFILE, profile: { conditionsToDelete: m.methodConditions.map(c => c.id) } });
const add = (await query(mut, { id: PROFILE, profile: { locationGroupsToUpdate: [{ id: lg, zonesToUpdate: [{ id: zone, methodDefinitionsToUpdate: [{ id: m.id, priceConditionsToCreate: snapConds.map(c => ({ operator: c.operator, criteria: { amount: c.amount, currencyCode: 'USD' } })) }] }] }] } })).deliveryProfileUpdate;
if (add.userErrors?.length) { console.error('conditions ERR', JSON.stringify(add.userErrors)); process.exit(1); }
}
console.log('reactivated + conditions restored.');
} else {
// recreate the method from scratch
const create = (await query(mut, { id: PROFILE, profile: { locationGroupsToUpdate: [{ id: lg, zonesToUpdate: [{ id: zone, methodDefinitionsToCreate: [{
name: METHOD_NAME, active: true,
rateDefinition: { price: { amount: snapPrice, currencyCode: 'USD' } },
priceConditionsToCreate: snapConds.map(c => ({ operator: c.operator, criteria: { amount: c.amount, currencyCode: 'USD' } })),
}] }] }] } })).deliveryProfileUpdate;
if (create.userErrors?.length) { console.error('recreate ERR', JSON.stringify(create.userErrors)); process.exit(1); }
console.log('recreated method "' + METHOD_NAME + '" $' + snapPrice + ' with band ' + snapConds.map(c => `${c.operator} $${c.amount}`).join(' & '));
}
console.log('\nband RESTORED.');