hookwright 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +25 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -94,7 +94,7 @@ A generated payload is useless if the receiver silently drops it, so every build
94
94
  - **JSON round-trip** — receivers that verify `HMAC(secret, timestamp + JSON.stringify(parsedBody))` can only match if `JSON.stringify(JSON.parse(raw)) === raw`. If it doesn't, you're told the exact byte offset.
95
95
  - **Phone** — parsed to E.164 using the shipping `country_code`, the same way the consumer does.
96
96
  - **Phone allow-list** — refuses to build for a number that isn't an approved test handset, so a live shopper can never be messaged by a test run.
97
- - **Product images** — every `image_url` is `HEAD`-checked; unreachable images fail the build.
97
+ - **Product images** — every `image_url` is `HEAD`-checked. A product with no image warns rather than blocking: the message simply renders without one.
98
98
  - **Endpoint exists** — before a signed payload is handed over, the destination is verified: the hostname must resolve, something must accept a TCP connection on the port, and the path must not 404. A typo'd host is caught here instead of looking like a silent delivery failure. The probe is `OPTIONS`, falling back to `HEAD` — **never a POST and never a body**, so it cannot create data on the receiving system. Timeouts and servers that reject `OPTIONS` warn rather than block. Skip with `--no-endpoint-check`, override with `--force`.
99
99
  - **Field coverage** — a table of every field the consumer reads, what it maps to, and its value. Missing required fields are flagged in red.
100
100
 
@@ -142,7 +142,7 @@ Adding one is a single file in `src/providers/` exporting `buildPayload`, `sign`
142
142
  ```bash
143
143
  npm install
144
144
  npm run dev # esbuild watch
145
- npm test # 185 tests, no network required
145
+ npm test # 190 tests, no network required
146
146
  npm start # build + run
147
147
  ```
148
148
 
package/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ var init_package = __esm({
24
24
  "package.json"() {
25
25
  package_default = {
26
26
  name: "hookwright",
27
- version: "1.2.0",
27
+ version: "1.2.2",
28
28
  description: "Build real, signed e-commerce webhooks from a live Shopify catalogue \u2014 interactive terminal UI, no backend",
29
29
  keywords: [
30
30
  "webhook",
@@ -449,7 +449,7 @@ var fieldMap = [
449
449
  { source: "data.customer.shipping_address.zip", target: "customer.zip" },
450
450
  { source: "data.line_items[0].name", target: "cart.productName", required: true },
451
451
  { source: "data.line_items[0].currency", target: "pricing.currency", required: true, note: "currency is read off the FIRST line item, not the root" },
452
- { source: "data.line_items[0].image_url", target: "cart.image", required: true, note: "first line item with an image wins" },
452
+ { source: "data.line_items[0].image_url", target: "cart.image", note: "first line item with an image wins; blank if the product has none" },
453
453
  { source: "data.line_items[0].quantity", target: "cart.quantity" },
454
454
  { source: "data.utm_parameters.utm_source", target: "utm.source" },
455
455
  { source: "data.utm_parameters.utm_medium", target: "utm.medium" },
@@ -621,7 +621,7 @@ var fieldMap2 = [
621
621
  { source: "customer.Shipping_address.Country_code", target: "customer.countryCode", required: true, note: "region used to parse the phone" },
622
622
  { source: "customer.Shipping_address.Zip", target: "customer.zip" },
623
623
  { source: "line_items[0].name", target: "cart.productName", required: true },
624
- { source: "line_items[0].image_url", target: "cart.image", required: true },
624
+ { source: "line_items[0].image_url", target: "cart.image", note: "blank if the product has no image" },
625
625
  { source: "line_items[0].quantity", target: "cart.quantity" }
626
626
  ];
627
627
  var editableFields2 = [
@@ -776,6 +776,9 @@ var EVENTS = [
776
776
  { eventName: "orders/create", type: "nitro_orders_create", label: "Order Created", selection: "cart", subject: "ordered items" },
777
777
  { eventName: "orders/updated", type: "nitro_orders_updated", label: "Order Updated", selection: "cart", subject: "ordered items" }
778
778
  ];
779
+ function eventFromPayload(payload) {
780
+ return payload?.eventName;
781
+ }
779
782
  function selectionFor(eventName) {
780
783
  return EVENTS.find((e) => e.eventName === eventName)?.selection ?? "cart";
781
784
  }
@@ -805,7 +808,7 @@ var FIELD_MAPS = {
805
808
  ...COMMON_FIELDS,
806
809
  ...PAGE_FIELDS,
807
810
  { source: "eventVal.title", target: "eventVal.title", required: true },
808
- { source: "eventVal.image", target: "eventVal.image", required: true },
811
+ { source: "eventVal.image", target: "eventVal.image", note: "blank if the product has no image" },
809
812
  { source: "eventVal.price", target: "eventVal.price", required: true },
810
813
  { source: "eventVal.resource_id", target: "eventVal.resource_id", required: true },
811
814
  { source: "eventVal.resource", target: "eventVal.resource" },
@@ -1061,6 +1064,7 @@ var nitro_default = {
1061
1064
  editableFields: editableFields3,
1062
1065
  editableFieldsFor,
1063
1066
  selectionFor,
1067
+ eventFromPayload,
1064
1068
  buildPayload: buildPayload3,
1065
1069
  sign: sign3,
1066
1070
  webhookUrl: webhookUrl3,
@@ -1773,10 +1777,11 @@ async function checkImages(payload, { timeoutMs = 1e4 } = {}) {
1773
1777
  }));
1774
1778
  }
1775
1779
  async function runAll({ payload, body, provider, config, checkImageUrls = true, checkEndpoint = true, eventName }) {
1776
- const checks = [checkGate(payload, provider, eventName), checkRoundTrip(body), checkPhone(payload), checkAllowedPhone(payload, config)];
1780
+ const event = eventName ?? (provider.eventFromPayload ? provider.eventFromPayload(payload) : void 0);
1781
+ const checks = [checkGate(payload, provider, event), checkRoundTrip(body), checkPhone(payload), checkAllowedPhone(payload, config)];
1777
1782
  if (checkImageUrls) checks.push(...await checkImages(payload));
1778
1783
  if (checkEndpoint) checks.push(await checkEndpointExists(provider.webhookUrl(config)));
1779
- const fields = eventName && provider.fieldMapFor ? provider.fieldMapFor(eventName) : provider.fieldMap;
1784
+ const fields = event && provider.fieldMapFor ? provider.fieldMapFor(event) : provider.fieldMap;
1780
1785
  const cov = coverage(payload, fields);
1781
1786
  const missing = cov.filter((c2) => c2.status === "MISSING");
1782
1787
  checks.push({
@@ -1918,7 +1923,8 @@ async function buildPayload4({ cfg, providerId = "cashfree-occ", items = [], dis
1918
1923
  };
1919
1924
  const file = savePayload({ provider: provider.id, payload, body, meta });
1920
1925
  audit("payload.built", { provider: provider.id, valid: report.ok, file });
1921
- return { provider, payload, body, report, file, meta, summary };
1926
+ const record = { provider: provider.id, payload, body, meta };
1927
+ return { provider, payload, body, report, file, meta, summary, record };
1922
1928
  }
1923
1929
 
1924
1930
  // src/core/send.mjs
@@ -2212,7 +2218,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
2212
2218
  setStep(STEP.COLLECTION);
2213
2219
  }).catch((err) => {
2214
2220
  if (alive) {
2215
- setError({ message: err.message, hint: err.hint });
2221
+ setError({ message: err.message, hint: err.hint, failures: err.failures });
2216
2222
  setStep(STEP.ERROR);
2217
2223
  }
2218
2224
  });
@@ -2231,7 +2237,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
2231
2237
  setStep(STEP.PICK);
2232
2238
  }).catch((err) => {
2233
2239
  if (!alive) return;
2234
- setError({ message: err.message, hint: err.hint });
2240
+ setError({ message: err.message, hint: err.hint, failures: err.failures });
2235
2241
  setStep(STEP.ERROR);
2236
2242
  });
2237
2243
  return () => {
@@ -2310,7 +2316,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
2310
2316
  setDraft({ ...next, discount });
2311
2317
  setStep(STEP.MODE);
2312
2318
  } catch (err) {
2313
- setError({ message: err.message, hint: err.hint });
2319
+ setError({ message: err.message, hint: err.hint, failures: err.failures });
2314
2320
  setStep(STEP.ERROR);
2315
2321
  }
2316
2322
  };
@@ -2334,7 +2340,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
2334
2340
  }
2335
2341
  setStep(STEP.REPORT);
2336
2342
  }).catch((err) => {
2337
- setError({ message: err.message, hint: err.hint });
2343
+ setError({ message: err.message, hint: err.hint, failures: err.failures });
2338
2344
  setStep(STEP.ERROR);
2339
2345
  });
2340
2346
  };
@@ -2356,11 +2362,11 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
2356
2362
  };
2357
2363
  const doSend = () => {
2358
2364
  setStep(STEP.SENDING);
2359
- dispatch({ cfg: config, record: { provider: built.provider.id, payload: built.payload, body: built.body } }).then((result) => {
2365
+ dispatch({ cfg: config, record: built.record }).then((result) => {
2360
2366
  setSendResult(result);
2361
2367
  setStep(STEP.RESULT);
2362
2368
  }).catch((err) => {
2363
- setError({ message: err.message, hint: err.hint });
2369
+ setError({ message: err.message, hint: err.hint, failures: err.failures });
2364
2370
  setStep(STEP.ERROR);
2365
2371
  });
2366
2372
  };
@@ -2594,6 +2600,11 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
2594
2600
  ] }),
2595
2601
  step === STEP.ERROR && error && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2596
2602
  /* @__PURE__ */ jsx8(Alert, { variant: "error", children: error.message }),
2603
+ error.failures?.length ? /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", marginTop: 1, children: error.failures.map((f, i) => /* @__PURE__ */ jsxs8(Box8, { children: [
2604
+ /* @__PURE__ */ jsx8(Text8, { color: palette.bad, children: ` ${glyph.cross} ` }),
2605
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: `${f.name}: ` }),
2606
+ /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: f.detail })
2607
+ ] }, i)) }) : null,
2597
2608
  error.hint ? /* @__PURE__ */ jsx8(Text8, { color: palette.warn, children: ` ${error.hint}` }) : null,
2598
2609
  /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "press enter to return to the menu" }) })
2599
2610
  ] })
@@ -3581,7 +3592,7 @@ async function cmdBuild(args, config) {
3581
3592
  async function cmdSend(args, config, built) {
3582
3593
  const bad = requireConfig(config);
3583
3594
  if (bad) return bad;
3584
- let record = built ? { provider: built.provider.id, payload: built.payload, body: built.body } : null;
3595
+ let record = built?.record ?? null;
3585
3596
  if (!record && typeof args.flags.file === "string") record = loadPayload(args.flags.file);
3586
3597
  if (!record) record = loadLast();
3587
3598
  if (!record) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hookwright",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Build real, signed e-commerce webhooks from a live Shopify catalogue — interactive terminal UI, no backend",
5
5
  "keywords": [
6
6
  "webhook",