@garuhq/cli 0.3.0 → 0.4.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 +24 -0
- package/dist/index.cjs +128 -17
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
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.4.0] — 2026-05-19
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `garu webhooks events` command tree — inspect and replay webhook
|
|
11
|
+
deliveries from the CLI. The dashboard "Reenviar" button is now
|
|
12
|
+
available from the API key auth path as well, which means support
|
|
13
|
+
+ on-call workflows can resend events from a terminal instead of
|
|
14
|
+
having to log into the dashboard.
|
|
15
|
+
- `garu webhooks events list [--status <s>] [--event-type <t>] [--endpoint-id <n>] [--page <n>] [--limit <n>]`
|
|
16
|
+
— paginated listing with status badges (green `success`, yellow
|
|
17
|
+
`pending`, red `failed`) in TTY mode.
|
|
18
|
+
- `garu webhooks events get <id>` — fetch one webhook event with
|
|
19
|
+
the full endpoint snapshot, response status, and (truncated)
|
|
20
|
+
response body.
|
|
21
|
+
- `garu webhooks events retry <id>` — re-deliver a webhook event
|
|
22
|
+
(resets to `pending` and triggers an immediate attempt). Works on
|
|
23
|
+
any status; use this when a customer reports a missed event.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `@garuhq/node` SDK bumped to 0.11.0 for the new `webhookEvents`
|
|
28
|
+
resource and its response-shape normalization fix.
|
|
29
|
+
|
|
6
30
|
## [0.3.0] — 2026-04-28
|
|
7
31
|
|
|
8
32
|
Rolls up the unpublished 0.2.0 work plus a new update notifier into a single
|
package/dist/index.cjs
CHANGED
|
@@ -15,7 +15,7 @@ var pc__default = /*#__PURE__*/_interopDefault(pc);
|
|
|
15
15
|
// src/index.ts
|
|
16
16
|
|
|
17
17
|
// src/version.ts
|
|
18
|
-
var CLI_VERSION = "0.
|
|
18
|
+
var CLI_VERSION = "0.4.0";
|
|
19
19
|
var CliError = class extends Error {
|
|
20
20
|
code;
|
|
21
21
|
exitCode;
|
|
@@ -459,6 +459,98 @@ async function logoutCommand(opts = {}) {
|
|
|
459
459
|
printSuccess(`Profile '${opts.profile}' removed.`, opts);
|
|
460
460
|
return { cleared: opts.profile };
|
|
461
461
|
}
|
|
462
|
+
async function getClient2(opts) {
|
|
463
|
+
if (opts.garu) return opts.garu;
|
|
464
|
+
const auth = await resolveAuth({
|
|
465
|
+
...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
|
|
466
|
+
...opts.profile !== void 0 ? { profile: opts.profile } : {}
|
|
467
|
+
});
|
|
468
|
+
return createGaruClient({
|
|
469
|
+
auth,
|
|
470
|
+
...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
async function webhooksEventsListCommand(opts) {
|
|
474
|
+
const garu = await getClient2(opts);
|
|
475
|
+
const params = {};
|
|
476
|
+
if (opts.page !== void 0) params.page = opts.page;
|
|
477
|
+
if (opts.limit !== void 0) params.limit = opts.limit;
|
|
478
|
+
if (opts.status !== void 0) params.status = opts.status;
|
|
479
|
+
if (opts.eventType !== void 0) params.eventType = opts.eventType;
|
|
480
|
+
if (opts.endpointId !== void 0) params.endpointId = opts.endpointId;
|
|
481
|
+
const result = await garu.webhookEvents.list(params);
|
|
482
|
+
printResult(result, { ...opts, prettyPrint: prettyWebhookEventList });
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
async function webhooksEventsGetCommand(opts) {
|
|
486
|
+
const garu = await getClient2(opts);
|
|
487
|
+
const event = await garu.webhookEvents.get(opts.id);
|
|
488
|
+
printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
|
|
489
|
+
return event;
|
|
490
|
+
}
|
|
491
|
+
async function webhooksEventsRetryCommand(opts) {
|
|
492
|
+
const garu = await getClient2(opts);
|
|
493
|
+
const event = await garu.webhookEvents.retry(opts.id);
|
|
494
|
+
printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
|
|
495
|
+
return event;
|
|
496
|
+
}
|
|
497
|
+
function statusBadge(status) {
|
|
498
|
+
const padded = status.padEnd(7);
|
|
499
|
+
if (status === "success") return pc__default.default.green(padded);
|
|
500
|
+
if (status === "failed") return pc__default.default.red(padded);
|
|
501
|
+
return pc__default.default.yellow(padded);
|
|
502
|
+
}
|
|
503
|
+
function prettyWebhookEventList(list) {
|
|
504
|
+
if (list.data.length === 0) {
|
|
505
|
+
return `No webhook events found (page ${list.meta.page}/${list.meta.totalPages || 1})`;
|
|
506
|
+
}
|
|
507
|
+
const header = `Webhook events (page ${list.meta.page}/${list.meta.totalPages || "?"}, ${list.meta.total} total)`;
|
|
508
|
+
const rows = list.data.map(
|
|
509
|
+
(e) => ` ${String(e.id).padStart(8)} ${statusBadge(e.status)} attempts=${String(e.attempts).padStart(2)} ${e.eventType.padEnd(38)} ${e.createdAt}`
|
|
510
|
+
);
|
|
511
|
+
return [header, ...rows].join("\n");
|
|
512
|
+
}
|
|
513
|
+
function prettyWebhookEvent(event) {
|
|
514
|
+
const lines = [
|
|
515
|
+
`Webhook event ${event.id}`,
|
|
516
|
+
` status: ${statusBadge(event.status)}`,
|
|
517
|
+
` eventType: ${event.eventType}`,
|
|
518
|
+
` attempts: ${event.attempts}`,
|
|
519
|
+
` endpoint: [${event.webhookEndpoint.id}] ${event.webhookEndpoint.url}`,
|
|
520
|
+
` createdAt: ${event.createdAt}`
|
|
521
|
+
];
|
|
522
|
+
if (event.lastAttemptAt) lines.push(` lastAttemptAt: ${event.lastAttemptAt}`);
|
|
523
|
+
if (event.nextRetryAt) lines.push(` nextRetryAt: ${event.nextRetryAt}`);
|
|
524
|
+
if (event.responseStatus !== null) lines.push(` responseStatus: ${event.responseStatus}`);
|
|
525
|
+
if (event.responseBody) {
|
|
526
|
+
const body = event.responseBody.length > 200 ? `${event.responseBody.slice(0, 200)}\u2026` : event.responseBody;
|
|
527
|
+
lines.push(` responseBody: ${body}`);
|
|
528
|
+
}
|
|
529
|
+
return lines.join("\n");
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/lib/parse.ts
|
|
533
|
+
function parsePositiveIntId(raw, label) {
|
|
534
|
+
const id = Number.parseInt(raw, 10);
|
|
535
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
536
|
+
throw new CliError("invalid_input", `${label} must be a positive integer (got '${raw}')`);
|
|
537
|
+
}
|
|
538
|
+
return id;
|
|
539
|
+
}
|
|
540
|
+
function parsePaymentMethod(raw) {
|
|
541
|
+
if (raw === "pix" || raw === "credit_card" || raw === "boleto") return raw;
|
|
542
|
+
throw new CliError(
|
|
543
|
+
"invalid_input",
|
|
544
|
+
`--type must be 'pix', 'credit_card', or 'boleto' (got '${raw}')`
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
function parseWebhookEventStatus(raw) {
|
|
548
|
+
if (raw === "pending" || raw === "success" || raw === "failed") return raw;
|
|
549
|
+
throw new CliError(
|
|
550
|
+
"invalid_input",
|
|
551
|
+
`--status must be 'pending', 'success', or 'failed' (got '${raw}')`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
462
554
|
|
|
463
555
|
// src/index.ts
|
|
464
556
|
function buildCli() {
|
|
@@ -524,7 +616,7 @@ function buildCli() {
|
|
|
524
616
|
});
|
|
525
617
|
charges.command("get <id>").description("Fetch a single charge by ID").action(async (id) => {
|
|
526
618
|
const base = toCommandOptions(program);
|
|
527
|
-
await chargesGetCommand({ ...base, id:
|
|
619
|
+
await chargesGetCommand({ ...base, id: parsePositiveIntId(id, "Charge ID") }).catch(
|
|
528
620
|
(err) => printErrorAndExit(err, base)
|
|
529
621
|
);
|
|
530
622
|
});
|
|
@@ -532,12 +624,45 @@ function buildCli() {
|
|
|
532
624
|
const base = toCommandOptions(program);
|
|
533
625
|
await chargesRefundCommand({
|
|
534
626
|
...base,
|
|
535
|
-
id:
|
|
627
|
+
id: parsePositiveIntId(id, "Charge ID"),
|
|
536
628
|
amount: cmdOpts.amount,
|
|
537
629
|
reason: cmdOpts.reason,
|
|
538
630
|
idempotencyKey: cmdOpts.idempotencyKey
|
|
539
631
|
}).catch((err) => printErrorAndExit(err, base));
|
|
540
632
|
});
|
|
633
|
+
const webhooks = program.command("webhooks").description("Inspect and replay webhook deliveries");
|
|
634
|
+
const events = webhooks.command("events").description("List, inspect, and resend webhook events");
|
|
635
|
+
events.command("list").description("List webhook events with filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option("--status <status>", "filter: pending, success, or failed").option("--event-type <type>", "filter by Garu event type, e.g. transaction.payment.paid").option(
|
|
636
|
+
"--endpoint-id <n>",
|
|
637
|
+
"filter by destination endpoint id",
|
|
638
|
+
(v) => parseInt(v, 10)
|
|
639
|
+
).action(
|
|
640
|
+
async (cmdOpts) => {
|
|
641
|
+
const base = toCommandOptions(program);
|
|
642
|
+
await webhooksEventsListCommand({
|
|
643
|
+
...base,
|
|
644
|
+
page: cmdOpts.page,
|
|
645
|
+
limit: cmdOpts.limit,
|
|
646
|
+
status: cmdOpts.status ? parseWebhookEventStatus(cmdOpts.status) : void 0,
|
|
647
|
+
eventType: cmdOpts.eventType,
|
|
648
|
+
endpointId: cmdOpts.endpointId
|
|
649
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
events.command("get <id>").description("Fetch a single webhook event by ID").action(async (id) => {
|
|
653
|
+
const base = toCommandOptions(program);
|
|
654
|
+
await webhooksEventsGetCommand({
|
|
655
|
+
...base,
|
|
656
|
+
id: parsePositiveIntId(id, "Webhook event ID")
|
|
657
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
658
|
+
});
|
|
659
|
+
events.command("retry <id>").description("Re-deliver a webhook event (resets to pending and triggers an immediate attempt)").action(async (id) => {
|
|
660
|
+
const base = toCommandOptions(program);
|
|
661
|
+
await webhooksEventsRetryCommand({
|
|
662
|
+
...base,
|
|
663
|
+
id: parsePositiveIntId(id, "Webhook event ID")
|
|
664
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
665
|
+
});
|
|
541
666
|
program.command("doctor").description("Environment diagnostic").action(async () => {
|
|
542
667
|
const base = toCommandOptions(program);
|
|
543
668
|
await doctorCommand(base).catch((err) => printErrorAndExit(err, base));
|
|
@@ -553,20 +678,6 @@ function toCommandOptions(program) {
|
|
|
553
678
|
if (g.quiet) out.quiet = true;
|
|
554
679
|
return out;
|
|
555
680
|
}
|
|
556
|
-
function parsePaymentMethod(raw) {
|
|
557
|
-
if (raw === "pix" || raw === "credit_card" || raw === "boleto") return raw;
|
|
558
|
-
throw new CliError(
|
|
559
|
-
"invalid_input",
|
|
560
|
-
`--type must be 'pix', 'credit_card', or 'boleto' (got '${raw}')`
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
function parseId(raw) {
|
|
564
|
-
const id = Number.parseInt(raw, 10);
|
|
565
|
-
if (!Number.isFinite(id) || id <= 0) {
|
|
566
|
-
throw new CliError("invalid_input", `Charge ID must be a positive integer (got '${raw}')`);
|
|
567
|
-
}
|
|
568
|
-
return id;
|
|
569
|
-
}
|
|
570
681
|
async function setupUpdateNotifier() {
|
|
571
682
|
try {
|
|
572
683
|
const { default: updateNotifier } = await import('update-notifier');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@garuhq/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Official command-line interface for the Garu payment gateway.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://garu.com.br",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@garuhq/node": "0.
|
|
48
|
+
"@garuhq/node": "0.11.0",
|
|
49
49
|
"@inquirer/prompts": "8.4.1",
|
|
50
50
|
"commander": "12.0.0",
|
|
51
51
|
"picocolors": "1.0.0",
|