@garuhq/cli 0.4.1 → 0.5.0

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,67 @@
3
3
  All notable changes to `@garuhq/cli` are documented in this file. Format:
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
5
5
 
6
+ ## [0.5.0] — 2026-05-19
7
+
8
+ ### Added
9
+
10
+ - `garu webhooks events resend <id>` — audit-trail-preserving replay
11
+ of a webhook event. Unlike `retry`, this does **not** mutate the
12
+ original row: the gateway inserts a fresh event with its own
13
+ numeric id that points back at the source via `manualResendOf`,
14
+ then dispatches that clone. The original failure record (response
15
+ status, response body, attempts, timestamps) stays intact.
16
+ - In pretty mode the CLI prints `✓ Resent event <src> → new event
17
+ <clone>` to stderr so the new id is impossible to miss; the
18
+ cloned event itself is rendered on stdout with a new `resendOf:`
19
+ line.
20
+ - In JSON mode the cloned event is the full stdout payload —
21
+ `.id` is the new event, `.manualResendOf` is the source.
22
+ - Recipient handlers will see this as a distinct delivery: the
23
+ gateway POSTs the clone with `Idempotency-Key:
24
+ resend_<originalId>`.
25
+
26
+ ### Deprecated
27
+
28
+ - `garu webhooks events retry <id>` — kept for backwards
29
+ compatibility but now marked `[deprecated: prefer resend]` in
30
+ `--help`. `retry` resets the original row in place, which means
31
+ once the replay succeeds the historical record of the prior
32
+ failure is gone. For incident response, support workflows, and
33
+ any backfill where the audit trail matters, use `resend` instead.
34
+
35
+ ### Changed
36
+
37
+ - `@garuhq/node` SDK bumped to 0.12.0 for the new
38
+ `webhookEvents.resend()` method and the `manualResendOf` field on
39
+ `WebhookEvent`.
40
+
41
+ ## [0.4.2] — 2026-05-19
42
+
43
+ ### Fixed
44
+
45
+ - **Error codes**: HTTP 403 responses (valid key, not your resource —
46
+ e.g. retrying a webhook event that belongs to another seller) were
47
+ previously surfaced as `auth_error`, which read like "your key is
48
+ bad". They now surface as `permission_error`. `auth_error` is now
49
+ reserved for HTTP 401 (bad/missing key).
50
+ - **`garu --version`** correctly reports `0.4.2`. The `0.4.1` release
51
+ shipped with `src/version.ts:CLI_VERSION` still pinned to `'0.4.0'`
52
+ because that file was missed during the bump. A new `scripts/
53
+ check-version-sync.mjs` runs as part of `prepublishOnly` and fails
54
+ the build if `package.json:version` and `src/version.ts:CLI_VERSION`
55
+ drift apart.
56
+
57
+ ### Changed
58
+
59
+ - **Error output**: `printErrorAndExit` now includes `status` and
60
+ `body` from `GaruAPIError`-derived failures.
61
+ - In `--json` mode, the payload gains optional `status` and `body`
62
+ fields: `{"error":{"code","message","status","body"}}`. Allows
63
+ self-diagnosis of 4xx responses without dropping to `curl`.
64
+ - In pretty mode, the HTTP status is shown as a dimmed line below
65
+ the existing `code:` line.
66
+
6
67
  ## [0.4.1] — 2026-05-19
7
68
 
8
69
  ### Fixed
package/dist/index.cjs CHANGED
@@ -15,19 +15,28 @@ var pc__default = /*#__PURE__*/_interopDefault(pc);
15
15
  // src/index.ts
16
16
 
17
17
  // src/version.ts
18
- var CLI_VERSION = "0.4.0";
18
+ var CLI_VERSION = "0.5.0";
19
19
  var CliError = class extends Error {
20
20
  code;
21
21
  exitCode;
22
- constructor(code, message, exitCode = 1) {
22
+ /** HTTP status code when the error came from a `GaruAPIError`, else `null`. */
23
+ status;
24
+ /** Raw backend response body when the error came from a `GaruAPIError`, else `undefined`. */
25
+ body;
26
+ constructor(code, message, exitCode = 1, status = null, body = void 0) {
23
27
  super(message);
24
28
  this.name = "CliError";
25
29
  this.code = code;
26
30
  this.exitCode = exitCode;
31
+ this.status = status;
32
+ this.body = body;
27
33
  }
28
34
  };
29
35
  function toCliError(err) {
30
36
  if (err instanceof CliError) return err;
37
+ if (err instanceof node.GaruAPIError) {
38
+ return new CliError(mapSdkCodeToCliCode(err.code), err.message, 1, err.status, err.body);
39
+ }
31
40
  if (err instanceof node.GaruError) {
32
41
  return new CliError(mapSdkCodeToCliCode(err.code), err.message, 1);
33
42
  }
@@ -37,8 +46,9 @@ function toCliError(err) {
37
46
  function mapSdkCodeToCliCode(sdkCode) {
38
47
  switch (sdkCode) {
39
48
  case "authentication_error":
40
- case "permission_error":
41
49
  return "auth_error";
50
+ case "permission_error":
51
+ return "permission_error";
42
52
  case "not_found":
43
53
  return "not_found";
44
54
  case "validation_error":
@@ -154,6 +164,8 @@ function printSuccess(message, opts = {}) {
154
164
  function printErrorAndExit(err, opts = {}) {
155
165
  const cliErr = toCliError(err);
156
166
  const payload = { error: { code: cliErr.code, message: cliErr.message } };
167
+ if (cliErr.status !== null) payload.error.status = cliErr.status;
168
+ if (cliErr.body !== void 0) payload.error.body = cliErr.body;
157
169
  if (resolveMode(opts) === "json") {
158
170
  process.stdout.write(`${JSON.stringify(payload)}
159
171
  `);
@@ -162,6 +174,10 @@ function printErrorAndExit(err, opts = {}) {
162
174
  `);
163
175
  if (cliErr.code !== "unknown_error") {
164
176
  process.stderr.write(`${pc__default.default.dim(` code: ${cliErr.code}`)}
177
+ `);
178
+ }
179
+ if (cliErr.status !== null) {
180
+ process.stderr.write(`${pc__default.default.dim(` status: ${cliErr.status}`)}
165
181
  `);
166
182
  }
167
183
  }
@@ -494,6 +510,13 @@ async function webhooksEventsRetryCommand(opts) {
494
510
  printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
495
511
  return event;
496
512
  }
513
+ async function webhooksEventsResendCommand(opts) {
514
+ const garu = await getClient2(opts);
515
+ const clone = await garu.webhookEvents.resend(opts.id);
516
+ printSuccess(`Resent event ${opts.id} \u2192 new event ${clone.id}`, opts);
517
+ printResult(clone, { ...opts, prettyPrint: prettyWebhookEvent });
518
+ return clone;
519
+ }
497
520
  function statusBadge(status) {
498
521
  const padded = status.padEnd(7);
499
522
  if (status === "success") return pc__default.default.green(padded);
@@ -519,6 +542,7 @@ function prettyWebhookEvent(event) {
519
542
  ` endpoint: [${event.webhookEndpoint.id}] ${event.webhookEndpoint.url}`,
520
543
  ` createdAt: ${event.createdAt}`
521
544
  ];
545
+ if (event.manualResendOf !== null) lines.push(` resendOf: ${event.manualResendOf}`);
522
546
  if (event.lastAttemptAt) lines.push(` lastAttemptAt: ${event.lastAttemptAt}`);
523
547
  if (event.nextRetryAt) lines.push(` nextRetryAt: ${event.nextRetryAt}`);
524
548
  if (event.responseStatus !== null) lines.push(` responseStatus: ${event.responseStatus}`);
@@ -656,13 +680,24 @@ function buildCli() {
656
680
  id: parsePositiveIntId(id, "Webhook event ID")
657
681
  }).catch((err) => printErrorAndExit(err, base));
658
682
  });
659
- events.command("retry <id>").description("Re-deliver a webhook event (resets to pending and triggers an immediate attempt)").action(async (id) => {
683
+ events.command("retry <id>").description(
684
+ "[deprecated: prefer `resend`] Re-deliver a webhook event in place (resets the original row to pending and triggers an immediate attempt; destroys the prior failure record)"
685
+ ).action(async (id) => {
660
686
  const base = toCommandOptions(program);
661
687
  await webhooksEventsRetryCommand({
662
688
  ...base,
663
689
  id: parsePositiveIntId(id, "Webhook event ID")
664
690
  }).catch((err) => printErrorAndExit(err, base));
665
691
  });
692
+ events.command("resend <id>").description(
693
+ "Re-deliver a webhook event by cloning it (audit-trail preserving: original row is untouched, clone gets a new id and points back via manualResendOf)"
694
+ ).action(async (id) => {
695
+ const base = toCommandOptions(program);
696
+ await webhooksEventsResendCommand({
697
+ ...base,
698
+ id: parsePositiveIntId(id, "Webhook event ID")
699
+ }).catch((err) => printErrorAndExit(err, base));
700
+ });
666
701
  program.command("doctor").description("Environment diagnostic").action(async () => {
667
702
  const base = toCommandOptions(program);
668
703
  await doctorCommand(base).catch((err) => printErrorAndExit(err, base));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuhq/cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Official command-line interface for the Garu payment gateway.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://garu.com.br",
@@ -42,10 +42,11 @@
42
42
  "typecheck": "tsc --noEmit",
43
43
  "test": "vitest run",
44
44
  "test:watch": "vitest",
45
- "prepublishOnly": "npm run typecheck && npm test && npm run build"
45
+ "check:version-sync": "node scripts/check-version-sync.mjs",
46
+ "prepublishOnly": "npm run check:version-sync && npm run typecheck && npm test && npm run build"
46
47
  },
47
48
  "dependencies": {
48
- "@garuhq/node": "0.11.1",
49
+ "@garuhq/node": "0.12.0",
49
50
  "@inquirer/prompts": "8.4.1",
50
51
  "commander": "12.0.0",
51
52
  "picocolors": "1.0.0",