@better-webhook/cli 3.4.3 ā 3.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/README.md +35 -0
- package/dist/dashboard/assets/index-CxrRCNTh.css +1 -0
- package/dist/dashboard/assets/index-Dlqdzwyc.js +23 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.cjs +341 -24
- package/dist/index.js +341 -24
- package/package.json +5 -4
- package/dist/commands/capture.d.ts +0 -2
- package/dist/commands/capture.js +0 -30
- package/dist/commands/captures.d.ts +0 -2
- package/dist/commands/captures.js +0 -217
- package/dist/commands/dashboard.d.ts +0 -2
- package/dist/commands/dashboard.js +0 -65
- package/dist/commands/index.d.ts +0 -6
- package/dist/commands/index.js +0 -6
- package/dist/commands/replay.d.ts +0 -2
- package/dist/commands/replay.js +0 -140
- package/dist/commands/run.d.ts +0 -2
- package/dist/commands/run.js +0 -181
- package/dist/commands/templates.d.ts +0 -2
- package/dist/commands/templates.js +0 -285
- package/dist/core/capture-server.d.ts +0 -31
- package/dist/core/capture-server.js +0 -298
- package/dist/core/dashboard-api.d.ts +0 -8
- package/dist/core/dashboard-api.js +0 -271
- package/dist/core/dashboard-server.d.ts +0 -20
- package/dist/core/dashboard-server.js +0 -124
- package/dist/core/executor.d.ts +0 -11
- package/dist/core/executor.js +0 -130
- package/dist/core/index.d.ts +0 -5
- package/dist/core/index.js +0 -5
- package/dist/core/replay-engine.d.ts +0 -18
- package/dist/core/replay-engine.js +0 -208
- package/dist/core/signature.d.ts +0 -24
- package/dist/core/signature.js +0 -199
- package/dist/core/template-manager.d.ts +0 -24
- package/dist/core/template-manager.js +0 -246
- package/dist/dashboard/assets/index-BSfTbn4Y.js +0 -23
- package/dist/dashboard/assets/index-zDTVdss_.css +0 -1
- package/dist/types/index.d.ts +0 -299
- package/dist/types/index.js +0 -86
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import prompts from "prompts";
|
|
4
|
-
import { getReplayEngine } from "../core/replay-engine.js";
|
|
5
|
-
const listCommand = new Command()
|
|
6
|
-
.name("list")
|
|
7
|
-
.alias("ls")
|
|
8
|
-
.description("List captured webhooks")
|
|
9
|
-
.option("-l, --limit <limit>", "Maximum number of captures to show", "20")
|
|
10
|
-
.option("-p, --provider <provider>", "Filter by provider")
|
|
11
|
-
.action((options) => {
|
|
12
|
-
const limit = parseInt(options.limit, 10);
|
|
13
|
-
if (isNaN(limit) || limit <= 0) {
|
|
14
|
-
console.error(chalk.red("Invalid limit value"));
|
|
15
|
-
process.exitCode = 1;
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const engine = getReplayEngine();
|
|
19
|
-
let captures = engine.listCaptures(limit);
|
|
20
|
-
if (options.provider) {
|
|
21
|
-
captures = captures.filter((c) => c.capture.provider?.toLowerCase() === options.provider?.toLowerCase());
|
|
22
|
-
}
|
|
23
|
-
if (captures.length === 0) {
|
|
24
|
-
console.log(chalk.yellow("\nš No captured webhooks found."));
|
|
25
|
-
console.log(chalk.gray(" Start capturing with: better-webhook capture\n"));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
console.log(chalk.bold("\nš¦ Captured Webhooks\n"));
|
|
29
|
-
for (const { file, capture } of captures) {
|
|
30
|
-
const date = new Date(capture.timestamp).toLocaleString();
|
|
31
|
-
const provider = capture.provider
|
|
32
|
-
? chalk.cyan(`[${capture.provider}]`)
|
|
33
|
-
: chalk.gray("[unknown]");
|
|
34
|
-
const size = capture.contentLength || capture.rawBody?.length || 0;
|
|
35
|
-
console.log(` ${chalk.white(capture.id.slice(0, 8))} ${provider}`);
|
|
36
|
-
console.log(chalk.gray(` ${capture.method} ${capture.path}`));
|
|
37
|
-
console.log(chalk.gray(` ${date} | ${size} bytes`));
|
|
38
|
-
console.log(chalk.gray(` File: ${file}`));
|
|
39
|
-
console.log();
|
|
40
|
-
}
|
|
41
|
-
console.log(chalk.gray(` Showing ${captures.length} captures`));
|
|
42
|
-
console.log(chalk.gray(` Storage: ${engine.getCapturesDir()}\n`));
|
|
43
|
-
});
|
|
44
|
-
const showCommand = new Command()
|
|
45
|
-
.name("show")
|
|
46
|
-
.argument("<captureId>", "Capture ID or partial ID")
|
|
47
|
-
.description("Show detailed information about a capture")
|
|
48
|
-
.option("-b, --body", "Show full body content")
|
|
49
|
-
.action((captureId, options) => {
|
|
50
|
-
const engine = getReplayEngine();
|
|
51
|
-
const captureFile = engine.getCapture(captureId);
|
|
52
|
-
if (!captureFile) {
|
|
53
|
-
console.log(chalk.red(`\nā Capture not found: ${captureId}\n`));
|
|
54
|
-
process.exitCode = 1;
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const { capture } = captureFile;
|
|
58
|
-
console.log(chalk.bold("\nš Capture Details\n"));
|
|
59
|
-
console.log(` ${chalk.gray("ID:")} ${capture.id}`);
|
|
60
|
-
console.log(` ${chalk.gray("File:")} ${captureFile.file}`);
|
|
61
|
-
console.log(` ${chalk.gray("Timestamp:")} ${new Date(capture.timestamp).toLocaleString()}`);
|
|
62
|
-
console.log(` ${chalk.gray("Method:")} ${capture.method}`);
|
|
63
|
-
console.log(` ${chalk.gray("Path:")} ${capture.path}`);
|
|
64
|
-
console.log(` ${chalk.gray("URL:")} ${capture.url}`);
|
|
65
|
-
if (capture.provider) {
|
|
66
|
-
console.log(` ${chalk.gray("Provider:")} ${chalk.cyan(capture.provider)}`);
|
|
67
|
-
}
|
|
68
|
-
console.log(` ${chalk.gray("Content-Type:")} ${capture.contentType || "unknown"}`);
|
|
69
|
-
console.log(` ${chalk.gray("Content-Length:")} ${capture.contentLength || 0} bytes`);
|
|
70
|
-
const queryKeys = Object.keys(capture.query);
|
|
71
|
-
if (queryKeys.length > 0) {
|
|
72
|
-
console.log(chalk.bold("\n Query Parameters:"));
|
|
73
|
-
for (const [key, value] of Object.entries(capture.query)) {
|
|
74
|
-
const queryValue = Array.isArray(value) ? value.join(", ") : value;
|
|
75
|
-
console.log(chalk.gray(` ${key}: ${queryValue}`));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
console.log(chalk.bold("\n Headers:"));
|
|
79
|
-
for (const [key, value] of Object.entries(capture.headers)) {
|
|
80
|
-
const headerValue = Array.isArray(value) ? value.join(", ") : value;
|
|
81
|
-
const display = headerValue.length > 80
|
|
82
|
-
? headerValue.slice(0, 80) + "..."
|
|
83
|
-
: headerValue;
|
|
84
|
-
console.log(chalk.gray(` ${key}: ${display}`));
|
|
85
|
-
}
|
|
86
|
-
if (options.body && capture.body) {
|
|
87
|
-
console.log(chalk.bold("\n Body:"));
|
|
88
|
-
if (typeof capture.body === "object") {
|
|
89
|
-
console.log(chalk.gray(JSON.stringify(capture.body, null, 2)
|
|
90
|
-
.split("\n")
|
|
91
|
-
.map((l) => ` ${l}`)
|
|
92
|
-
.join("\n")));
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
console.log(chalk.gray(` ${capture.body}`));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else if (capture.body) {
|
|
99
|
-
console.log(chalk.bold("\n Body:"));
|
|
100
|
-
const preview = JSON.stringify(capture.body).slice(0, 200);
|
|
101
|
-
console.log(chalk.gray(` ${preview}${preview.length >= 200 ? "..." : ""}`));
|
|
102
|
-
console.log(chalk.gray(" Use --body to see full content"));
|
|
103
|
-
}
|
|
104
|
-
console.log();
|
|
105
|
-
});
|
|
106
|
-
const searchCommand = new Command()
|
|
107
|
-
.name("search")
|
|
108
|
-
.argument("<query>", "Search query")
|
|
109
|
-
.description("Search captures by path, method, or provider")
|
|
110
|
-
.action((query) => {
|
|
111
|
-
const engine = getReplayEngine();
|
|
112
|
-
const results = engine.searchCaptures(query);
|
|
113
|
-
if (results.length === 0) {
|
|
114
|
-
console.log(chalk.yellow(`\nš No captures found for: "${query}"\n`));
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
console.log(chalk.bold(`\nš Search Results for "${query}"\n`));
|
|
118
|
-
for (const { file, capture } of results) {
|
|
119
|
-
const date = new Date(capture.timestamp).toLocaleString();
|
|
120
|
-
const provider = capture.provider
|
|
121
|
-
? chalk.cyan(`[${capture.provider}]`)
|
|
122
|
-
: "";
|
|
123
|
-
console.log(` ${chalk.white(capture.id.slice(0, 8))} ${provider}`);
|
|
124
|
-
console.log(chalk.gray(` ${capture.method} ${capture.path}`));
|
|
125
|
-
console.log(chalk.gray(` ${date}`));
|
|
126
|
-
console.log();
|
|
127
|
-
}
|
|
128
|
-
console.log(chalk.gray(` Found: ${results.length} captures\n`));
|
|
129
|
-
});
|
|
130
|
-
const deleteCommand = new Command()
|
|
131
|
-
.name("delete")
|
|
132
|
-
.alias("rm")
|
|
133
|
-
.argument("<captureId>", "Capture ID or partial ID to delete")
|
|
134
|
-
.description("Delete a specific captured webhook")
|
|
135
|
-
.option("-f, --force", "Skip confirmation prompt")
|
|
136
|
-
.action(async (captureId, options) => {
|
|
137
|
-
const engine = getReplayEngine();
|
|
138
|
-
const captureFile = engine.getCapture(captureId);
|
|
139
|
-
if (!captureFile) {
|
|
140
|
-
console.log(chalk.red(`\nā Capture not found: ${captureId}\n`));
|
|
141
|
-
process.exitCode = 1;
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const { capture } = captureFile;
|
|
145
|
-
if (!options.force) {
|
|
146
|
-
console.log(chalk.bold("\nšļø Capture to delete:\n"));
|
|
147
|
-
console.log(` ${chalk.white(capture.id.slice(0, 8))}`);
|
|
148
|
-
console.log(chalk.gray(` ${capture.method} ${capture.path}`));
|
|
149
|
-
console.log(chalk.gray(` ${new Date(capture.timestamp).toLocaleString()}`));
|
|
150
|
-
console.log();
|
|
151
|
-
const response = await prompts({
|
|
152
|
-
type: "confirm",
|
|
153
|
-
name: "confirm",
|
|
154
|
-
message: "Delete this capture?",
|
|
155
|
-
initial: false,
|
|
156
|
-
});
|
|
157
|
-
if (!response.confirm) {
|
|
158
|
-
console.log(chalk.yellow("Cancelled"));
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const deleted = engine.deleteCapture(captureId);
|
|
163
|
-
if (deleted) {
|
|
164
|
-
console.log(chalk.green(`\nā Deleted capture: ${capture.id.slice(0, 8)}\n`));
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
console.log(chalk.red(`\nā Failed to delete capture\n`));
|
|
168
|
-
process.exitCode = 1;
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
const cleanCommand = new Command()
|
|
172
|
-
.name("clean")
|
|
173
|
-
.alias("remove-all")
|
|
174
|
-
.description("Remove all captured webhooks")
|
|
175
|
-
.option("-f, --force", "Skip confirmation prompt")
|
|
176
|
-
.action(async (options) => {
|
|
177
|
-
const engine = getReplayEngine();
|
|
178
|
-
const captures = engine.listCaptures(10000);
|
|
179
|
-
if (captures.length === 0) {
|
|
180
|
-
console.log(chalk.yellow("\nš No captures to remove.\n"));
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
console.log(chalk.bold(`\nšļø Found ${captures.length} captured webhook(s)\n`));
|
|
184
|
-
const byProvider = new Map();
|
|
185
|
-
for (const c of captures) {
|
|
186
|
-
const provider = c.capture.provider || "unknown";
|
|
187
|
-
byProvider.set(provider, (byProvider.get(provider) || 0) + 1);
|
|
188
|
-
}
|
|
189
|
-
for (const [provider, count] of byProvider) {
|
|
190
|
-
console.log(chalk.gray(` ${provider}: ${count}`));
|
|
191
|
-
}
|
|
192
|
-
console.log();
|
|
193
|
-
if (!options.force) {
|
|
194
|
-
const response = await prompts({
|
|
195
|
-
type: "confirm",
|
|
196
|
-
name: "confirm",
|
|
197
|
-
message: `Delete all ${captures.length} capture(s)?`,
|
|
198
|
-
initial: false,
|
|
199
|
-
});
|
|
200
|
-
if (!response.confirm) {
|
|
201
|
-
console.log(chalk.yellow("Cancelled"));
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
const deleted = engine.deleteAllCaptures();
|
|
206
|
-
console.log(chalk.green(`\nā Removed ${deleted} capture(s)`));
|
|
207
|
-
console.log(chalk.gray(` Storage: ${engine.getCapturesDir()}\n`));
|
|
208
|
-
});
|
|
209
|
-
export const captures = new Command()
|
|
210
|
-
.name("captures")
|
|
211
|
-
.alias("c")
|
|
212
|
-
.description("Manage captured webhooks")
|
|
213
|
-
.addCommand(listCommand)
|
|
214
|
-
.addCommand(showCommand)
|
|
215
|
-
.addCommand(searchCommand)
|
|
216
|
-
.addCommand(deleteCommand)
|
|
217
|
-
.addCommand(cleanCommand);
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { startDashboardServer } from "../core/dashboard-server.js";
|
|
4
|
-
export const dashboard = new Command()
|
|
5
|
-
.name("dashboard")
|
|
6
|
-
.description("Start the local dashboard (UI + API + WebSocket) server")
|
|
7
|
-
.option("-p, --port <port>", "Port to listen on", "4000")
|
|
8
|
-
.option("-h, --host <host>", "Host to bind to", "localhost")
|
|
9
|
-
.option("--capture-port <port>", "Capture server port", "3001")
|
|
10
|
-
.option("--capture-host <host>", "Capture server host", "0.0.0.0")
|
|
11
|
-
.option("--no-capture", "Do not start the capture server")
|
|
12
|
-
.option("--captures-dir <dir>", "Override captures directory")
|
|
13
|
-
.option("--templates-dir <dir>", "Override templates base directory")
|
|
14
|
-
.action(async (options) => {
|
|
15
|
-
const port = Number.parseInt(String(options.port), 10);
|
|
16
|
-
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
17
|
-
console.error(chalk.red("Invalid port number"));
|
|
18
|
-
process.exitCode = 1;
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
const capturePort = Number.parseInt(String(options.capturePort), 10);
|
|
23
|
-
if (!Number.isFinite(capturePort) ||
|
|
24
|
-
capturePort < 0 ||
|
|
25
|
-
capturePort > 65535) {
|
|
26
|
-
console.error(chalk.red("Invalid capture port number"));
|
|
27
|
-
process.exitCode = 1;
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const { url, server, capture } = await startDashboardServer({
|
|
31
|
-
host: options.host,
|
|
32
|
-
port,
|
|
33
|
-
captureHost: options.captureHost,
|
|
34
|
-
capturePort,
|
|
35
|
-
startCapture: options.capture !== false,
|
|
36
|
-
capturesDir: options.capturesDir,
|
|
37
|
-
templatesBaseDir: options.templatesDir,
|
|
38
|
-
});
|
|
39
|
-
console.log(chalk.bold("\nš§ Dashboard Server\n"));
|
|
40
|
-
console.log(chalk.gray(` Dashboard: ${url}/`));
|
|
41
|
-
console.log(chalk.gray(` Health: ${url}/health`));
|
|
42
|
-
console.log(chalk.gray(` API Base: ${url}/api`));
|
|
43
|
-
console.log(chalk.gray(` WebSocket: ${url.replace("http://", "ws://")}/ws`));
|
|
44
|
-
if (capture) {
|
|
45
|
-
console.log();
|
|
46
|
-
console.log(chalk.bold("š£ Capture Server"));
|
|
47
|
-
console.log(chalk.gray(` Capture: ${capture.url}`));
|
|
48
|
-
console.log(chalk.gray(` Tip: Send webhooks to any path, e.g. ${capture.url}/webhooks/github`));
|
|
49
|
-
}
|
|
50
|
-
console.log();
|
|
51
|
-
const shutdown = async () => {
|
|
52
|
-
if (capture) {
|
|
53
|
-
await capture.server.stop();
|
|
54
|
-
}
|
|
55
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
56
|
-
process.exit(0);
|
|
57
|
-
};
|
|
58
|
-
process.on("SIGINT", shutdown);
|
|
59
|
-
process.on("SIGTERM", shutdown);
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
console.error(chalk.red(`Failed to start dashboard server: ${error?.message || error}`));
|
|
63
|
-
process.exitCode = 1;
|
|
64
|
-
}
|
|
65
|
-
});
|
package/dist/commands/index.d.ts
DELETED
package/dist/commands/index.js
DELETED
package/dist/commands/replay.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import ora from "ora";
|
|
3
|
-
import prompts from "prompts";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import { getReplayEngine } from "../core/replay-engine.js";
|
|
6
|
-
export const replay = new Command()
|
|
7
|
-
.name("replay")
|
|
8
|
-
.argument("[captureId]", "Capture ID to replay")
|
|
9
|
-
.argument("[targetUrl]", "Target URL to replay to")
|
|
10
|
-
.description("Replay a captured webhook to a target URL")
|
|
11
|
-
.option("-m, --method <method>", "Override HTTP method")
|
|
12
|
-
.option("-H, --header <header>", "Add or override header (format: key:value)", (value, previous) => {
|
|
13
|
-
const [key, ...valueParts] = value.split(":");
|
|
14
|
-
const headerValue = valueParts.join(":");
|
|
15
|
-
if (!key || !headerValue) {
|
|
16
|
-
throw new Error("Header format should be key:value");
|
|
17
|
-
}
|
|
18
|
-
return (previous || []).concat([
|
|
19
|
-
{ key: key.trim(), value: headerValue.trim() },
|
|
20
|
-
]);
|
|
21
|
-
}, [])
|
|
22
|
-
.option("-v, --verbose", "Show detailed request/response information")
|
|
23
|
-
.action(async (captureId, targetUrl, options) => {
|
|
24
|
-
const engine = getReplayEngine();
|
|
25
|
-
if (!captureId) {
|
|
26
|
-
const captures = engine.listCaptures(50);
|
|
27
|
-
if (captures.length === 0) {
|
|
28
|
-
console.log(chalk.yellow("\nš No captured webhooks found."));
|
|
29
|
-
console.log(chalk.gray(" Start capturing with: better-webhook capture\n"));
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const choices = captures.map((c) => {
|
|
33
|
-
const date = new Date(c.capture.timestamp).toLocaleString();
|
|
34
|
-
const provider = c.capture.provider ? `[${c.capture.provider}]` : "";
|
|
35
|
-
return {
|
|
36
|
-
title: `${c.capture.id.slice(0, 8)} ${provider} ${c.capture.method} ${c.capture.path}`,
|
|
37
|
-
description: date,
|
|
38
|
-
value: c.capture.id,
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
const response = await prompts({
|
|
42
|
-
type: "select",
|
|
43
|
-
name: "captureId",
|
|
44
|
-
message: "Select a capture to replay:",
|
|
45
|
-
choices,
|
|
46
|
-
});
|
|
47
|
-
if (!response.captureId) {
|
|
48
|
-
console.log(chalk.yellow("Cancelled"));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
captureId = response.captureId;
|
|
52
|
-
}
|
|
53
|
-
const captureFile = engine.getCapture(captureId);
|
|
54
|
-
if (!captureFile) {
|
|
55
|
-
console.log(chalk.red(`\nā Capture not found: ${captureId}\n`));
|
|
56
|
-
process.exitCode = 1;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (!targetUrl) {
|
|
60
|
-
const response = await prompts({
|
|
61
|
-
type: "text",
|
|
62
|
-
name: "url",
|
|
63
|
-
message: "Enter target URL:",
|
|
64
|
-
initial: `http://localhost:3000${captureFile.capture.path}`,
|
|
65
|
-
validate: (value) => {
|
|
66
|
-
try {
|
|
67
|
-
new URL(value);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return "Please enter a valid URL";
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
if (!response.url) {
|
|
76
|
-
console.log(chalk.yellow("Cancelled"));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
targetUrl = response.url;
|
|
80
|
-
}
|
|
81
|
-
const { capture } = captureFile;
|
|
82
|
-
console.log(chalk.bold("\nš Replaying Webhook\n"));
|
|
83
|
-
console.log(chalk.gray(` Capture ID: ${capture.id.slice(0, 8)}`));
|
|
84
|
-
console.log(chalk.gray(` Original: ${capture.method} ${capture.path}`));
|
|
85
|
-
if (capture.provider) {
|
|
86
|
-
console.log(chalk.gray(` Provider: ${capture.provider}`));
|
|
87
|
-
}
|
|
88
|
-
console.log(chalk.gray(` Target: ${targetUrl}`));
|
|
89
|
-
console.log();
|
|
90
|
-
const spinner = ora("Replaying webhook...").start();
|
|
91
|
-
try {
|
|
92
|
-
const result = await engine.replay(captureId, {
|
|
93
|
-
targetUrl: targetUrl,
|
|
94
|
-
method: options?.method,
|
|
95
|
-
headers: options?.header,
|
|
96
|
-
});
|
|
97
|
-
spinner.stop();
|
|
98
|
-
const statusColor = result.status >= 200 && result.status < 300
|
|
99
|
-
? chalk.green
|
|
100
|
-
: result.status >= 400
|
|
101
|
-
? chalk.red
|
|
102
|
-
: chalk.yellow;
|
|
103
|
-
console.log(chalk.bold("š„ Response\n"));
|
|
104
|
-
console.log(` Status: ${statusColor(`${result.status} ${result.statusText}`)}`);
|
|
105
|
-
console.log(` Duration: ${chalk.cyan(`${result.duration}ms`)}`);
|
|
106
|
-
if (options?.verbose) {
|
|
107
|
-
console.log(chalk.bold("\n Headers:"));
|
|
108
|
-
for (const [key, value] of Object.entries(result.headers)) {
|
|
109
|
-
const headerValue = Array.isArray(value) ? value.join(", ") : value;
|
|
110
|
-
console.log(chalk.gray(` ${key}: ${headerValue}`));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (result.json !== undefined) {
|
|
114
|
-
console.log(chalk.bold("\n Body:"));
|
|
115
|
-
console.log(chalk.gray(JSON.stringify(result.json, null, 2)
|
|
116
|
-
.split("\n")
|
|
117
|
-
.map((l) => ` ${l}`)
|
|
118
|
-
.join("\n")));
|
|
119
|
-
}
|
|
120
|
-
else if (result.bodyText) {
|
|
121
|
-
console.log(chalk.bold("\n Body:"));
|
|
122
|
-
const preview = result.bodyText.length > 500
|
|
123
|
-
? result.bodyText.slice(0, 500) + "..."
|
|
124
|
-
: result.bodyText;
|
|
125
|
-
console.log(chalk.gray(` ${preview}`));
|
|
126
|
-
}
|
|
127
|
-
console.log();
|
|
128
|
-
if (result.status >= 200 && result.status < 300) {
|
|
129
|
-
console.log(chalk.green("ā Replay completed successfully\n"));
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
console.log(chalk.yellow(`ā Replay completed with status ${result.status}\n`));
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
spinner.fail("Replay failed");
|
|
137
|
-
console.error(chalk.red(`\nā ${error.message}\n`));
|
|
138
|
-
process.exitCode = 1;
|
|
139
|
-
}
|
|
140
|
-
});
|
package/dist/commands/run.d.ts
DELETED
package/dist/commands/run.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import ora from "ora";
|
|
3
|
-
import prompts from "prompts";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import { getTemplateManager } from "../core/template-manager.js";
|
|
6
|
-
import { executeTemplate } from "../core/executor.js";
|
|
7
|
-
function getSecretEnvVarName(provider) {
|
|
8
|
-
const envVarMap = {
|
|
9
|
-
github: "GITHUB_WEBHOOK_SECRET",
|
|
10
|
-
stripe: "STRIPE_WEBHOOK_SECRET",
|
|
11
|
-
shopify: "SHOPIFY_WEBHOOK_SECRET",
|
|
12
|
-
twilio: "TWILIO_WEBHOOK_SECRET",
|
|
13
|
-
ragie: "RAGIE_WEBHOOK_SECRET",
|
|
14
|
-
slack: "SLACK_WEBHOOK_SECRET",
|
|
15
|
-
linear: "LINEAR_WEBHOOK_SECRET",
|
|
16
|
-
clerk: "CLERK_WEBHOOK_SECRET",
|
|
17
|
-
sendgrid: "SENDGRID_WEBHOOK_SECRET",
|
|
18
|
-
discord: "DISCORD_WEBHOOK_SECRET",
|
|
19
|
-
custom: "WEBHOOK_SECRET",
|
|
20
|
-
};
|
|
21
|
-
return envVarMap[provider] || "WEBHOOK_SECRET";
|
|
22
|
-
}
|
|
23
|
-
export const run = new Command()
|
|
24
|
-
.name("run")
|
|
25
|
-
.argument("[templateId]", "Template ID to run")
|
|
26
|
-
.description("Run a webhook template against a target URL")
|
|
27
|
-
.requiredOption("-u, --url <url>", "Target URL to send the webhook to")
|
|
28
|
-
.option("-s, --secret <secret>", "Secret for signature generation")
|
|
29
|
-
.option("-H, --header <header>", "Add custom header (format: key:value)", (value, previous) => {
|
|
30
|
-
const [key, ...valueParts] = value.split(":");
|
|
31
|
-
const headerValue = valueParts.join(":");
|
|
32
|
-
if (!key || !headerValue) {
|
|
33
|
-
throw new Error("Header format should be key:value");
|
|
34
|
-
}
|
|
35
|
-
return (previous || []).concat([
|
|
36
|
-
{ key: key.trim(), value: headerValue.trim() },
|
|
37
|
-
]);
|
|
38
|
-
}, [])
|
|
39
|
-
.option("-v, --verbose", "Show detailed request/response information")
|
|
40
|
-
.action(async (templateId, options) => {
|
|
41
|
-
const manager = getTemplateManager();
|
|
42
|
-
if (!templateId) {
|
|
43
|
-
const spinner = ora("Loading templates...").start();
|
|
44
|
-
try {
|
|
45
|
-
const local = manager.listLocalTemplates();
|
|
46
|
-
const remote = await manager.listRemoteTemplates();
|
|
47
|
-
spinner.stop();
|
|
48
|
-
if (local.length === 0 && remote.length === 0) {
|
|
49
|
-
console.log(chalk.yellow("\nš No templates available."));
|
|
50
|
-
console.log(chalk.gray(" Download templates with: better-webhook templates download\n"));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
const choices = [];
|
|
54
|
-
if (local.length > 0) {
|
|
55
|
-
for (const t of local) {
|
|
56
|
-
choices.push({
|
|
57
|
-
title: `${t.id} ${chalk.green("(local)")}`,
|
|
58
|
-
description: `${t.metadata.provider} - ${t.metadata.event}`,
|
|
59
|
-
value: t.id,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const remoteOnly = remote.filter((t) => !t.isDownloaded);
|
|
64
|
-
for (const t of remoteOnly) {
|
|
65
|
-
choices.push({
|
|
66
|
-
title: `${t.metadata.id} ${chalk.gray("(remote)")}`,
|
|
67
|
-
description: `${t.metadata.provider} - ${t.metadata.event}`,
|
|
68
|
-
value: `remote:${t.metadata.id}`,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
const response = await prompts({
|
|
72
|
-
type: "select",
|
|
73
|
-
name: "templateId",
|
|
74
|
-
message: "Select a template to run:",
|
|
75
|
-
choices,
|
|
76
|
-
});
|
|
77
|
-
if (!response.templateId) {
|
|
78
|
-
console.log(chalk.yellow("Cancelled"));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
templateId = response.templateId;
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
spinner.fail("Failed to load templates");
|
|
85
|
-
console.error(chalk.red(error.message));
|
|
86
|
-
process.exitCode = 1;
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (templateId?.startsWith("remote:")) {
|
|
91
|
-
const remoteId = templateId.replace("remote:", "");
|
|
92
|
-
const downloadSpinner = ora(`Downloading ${remoteId}...`).start();
|
|
93
|
-
try {
|
|
94
|
-
await manager.downloadTemplate(remoteId);
|
|
95
|
-
downloadSpinner.succeed(`Downloaded ${remoteId}`);
|
|
96
|
-
templateId = remoteId;
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
downloadSpinner.fail(`Failed to download: ${error.message}`);
|
|
100
|
-
process.exitCode = 1;
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const localTemplate = manager.getLocalTemplate(templateId);
|
|
105
|
-
if (!localTemplate) {
|
|
106
|
-
console.log(chalk.red(`\nā Template not found: ${templateId}`));
|
|
107
|
-
console.log(chalk.gray(" Download it with: better-webhook templates download " +
|
|
108
|
-
templateId +
|
|
109
|
-
"\n"));
|
|
110
|
-
process.exitCode = 1;
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const targetUrl = options.url;
|
|
114
|
-
let secret = options?.secret;
|
|
115
|
-
if (!secret && localTemplate.metadata.provider) {
|
|
116
|
-
const envVarName = getSecretEnvVarName(localTemplate.metadata.provider);
|
|
117
|
-
secret = process.env[envVarName];
|
|
118
|
-
}
|
|
119
|
-
console.log(chalk.bold("\nš Executing Webhook\n"));
|
|
120
|
-
console.log(chalk.gray(` Template: ${templateId}`));
|
|
121
|
-
console.log(chalk.gray(` Provider: ${localTemplate.metadata.provider}`));
|
|
122
|
-
console.log(chalk.gray(` Event: ${localTemplate.metadata.event}`));
|
|
123
|
-
console.log(chalk.gray(` Target: ${targetUrl}`));
|
|
124
|
-
if (secret) {
|
|
125
|
-
console.log(chalk.gray(` Signature: Will be generated`));
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
console.log(chalk.yellow(` ā ļø No secret provided - signature will not be generated`));
|
|
129
|
-
}
|
|
130
|
-
console.log();
|
|
131
|
-
const spinner = ora("Sending webhook...").start();
|
|
132
|
-
try {
|
|
133
|
-
const result = await executeTemplate(localTemplate.template, {
|
|
134
|
-
url: targetUrl,
|
|
135
|
-
secret,
|
|
136
|
-
headers: options?.header,
|
|
137
|
-
});
|
|
138
|
-
spinner.stop();
|
|
139
|
-
const statusColor = result.status >= 200 && result.status < 300
|
|
140
|
-
? chalk.green
|
|
141
|
-
: result.status >= 400
|
|
142
|
-
? chalk.red
|
|
143
|
-
: chalk.yellow;
|
|
144
|
-
console.log(chalk.bold("š„ Response\n"));
|
|
145
|
-
console.log(` Status: ${statusColor(`${result.status} ${result.statusText}`)}`);
|
|
146
|
-
console.log(` Duration: ${chalk.cyan(`${result.duration}ms`)}`);
|
|
147
|
-
if (options?.verbose) {
|
|
148
|
-
console.log(chalk.bold("\n Headers:"));
|
|
149
|
-
for (const [key, value] of Object.entries(result.headers)) {
|
|
150
|
-
const headerValue = Array.isArray(value) ? value.join(", ") : value;
|
|
151
|
-
console.log(chalk.gray(` ${key}: ${headerValue}`));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
if (result.json !== undefined) {
|
|
155
|
-
console.log(chalk.bold("\n Body:"));
|
|
156
|
-
console.log(chalk.gray(JSON.stringify(result.json, null, 2)
|
|
157
|
-
.split("\n")
|
|
158
|
-
.map((l) => ` ${l}`)
|
|
159
|
-
.join("\n")));
|
|
160
|
-
}
|
|
161
|
-
else if (result.bodyText) {
|
|
162
|
-
console.log(chalk.bold("\n Body:"));
|
|
163
|
-
const preview = result.bodyText.length > 500
|
|
164
|
-
? result.bodyText.slice(0, 500) + "..."
|
|
165
|
-
: result.bodyText;
|
|
166
|
-
console.log(chalk.gray(` ${preview}`));
|
|
167
|
-
}
|
|
168
|
-
console.log();
|
|
169
|
-
if (result.status >= 200 && result.status < 300) {
|
|
170
|
-
console.log(chalk.green("ā Webhook delivered successfully\n"));
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
console.log(chalk.yellow(`ā Webhook delivered with status ${result.status}\n`));
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
spinner.fail("Request failed");
|
|
178
|
-
console.error(chalk.red(`\nā ${error.message}\n`));
|
|
179
|
-
process.exitCode = 1;
|
|
180
|
-
}
|
|
181
|
-
});
|