@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
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command as Command7 } from "commander";
|
|
5
|
-
import {
|
|
5
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
6
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6
7
|
|
|
7
8
|
// src/commands/templates.ts
|
|
8
9
|
import { Command } from "commander";
|
|
@@ -108,6 +109,12 @@ var ConfigSchema = z.object({
|
|
|
108
109
|
// src/core/template-manager.ts
|
|
109
110
|
var GITHUB_RAW_BASE = "https://raw.githubusercontent.com/endalk200/better-webhook/main";
|
|
110
111
|
var TEMPLATES_INDEX_URL = `${GITHUB_RAW_BASE}/templates/templates.json`;
|
|
112
|
+
var TEMPLATE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
113
|
+
function isValidTemplateId(id) {
|
|
114
|
+
if (!id || id.length > 128) return false;
|
|
115
|
+
if (id.includes("/") || id.includes("\\") || id.includes("..")) return false;
|
|
116
|
+
return TEMPLATE_ID_PATTERN.test(id);
|
|
117
|
+
}
|
|
111
118
|
var TemplateManager = class {
|
|
112
119
|
baseDir;
|
|
113
120
|
templatesDir;
|
|
@@ -361,6 +368,76 @@ var TemplateManager = class {
|
|
|
361
368
|
}
|
|
362
369
|
return deleted;
|
|
363
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Check if a template with the given ID already exists locally
|
|
373
|
+
*/
|
|
374
|
+
templateExists(templateId) {
|
|
375
|
+
return this.getLocalTemplate(templateId) !== null;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Generate a unique template ID from provider and event
|
|
379
|
+
*/
|
|
380
|
+
generateTemplateId(provider, event) {
|
|
381
|
+
const providerPart = provider || "custom";
|
|
382
|
+
const eventPart = event || "webhook";
|
|
383
|
+
const baseId = `${providerPart}-${eventPart}`.toLowerCase().replace(/\s+/g, "-");
|
|
384
|
+
if (!this.templateExists(baseId)) {
|
|
385
|
+
return baseId;
|
|
386
|
+
}
|
|
387
|
+
let counter = 1;
|
|
388
|
+
while (this.templateExists(`${baseId}-${counter}`)) {
|
|
389
|
+
counter++;
|
|
390
|
+
}
|
|
391
|
+
return `${baseId}-${counter}`;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Save a user-created template from a captured webhook
|
|
395
|
+
*/
|
|
396
|
+
saveUserTemplate(template, options = {}) {
|
|
397
|
+
const provider = template.provider || "custom";
|
|
398
|
+
const event = options.event || template.event || "webhook";
|
|
399
|
+
const templateId = options.id || this.generateTemplateId(provider, event);
|
|
400
|
+
const name = options.name || templateId;
|
|
401
|
+
const description = options.description || template.description;
|
|
402
|
+
if (!isValidTemplateId(templateId)) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Invalid template ID "${templateId}". IDs must start with alphanumeric, contain only letters, numbers, dots, underscores, and hyphens.`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
if (!options.overwrite && this.templateExists(templateId)) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
`Template with ID "${templateId}" already exists. Use --overwrite to replace it.`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
const providerDir = join(this.templatesDir, provider);
|
|
413
|
+
if (!existsSync(providerDir)) {
|
|
414
|
+
mkdirSync(providerDir, { recursive: true });
|
|
415
|
+
}
|
|
416
|
+
const metadata = {
|
|
417
|
+
id: templateId,
|
|
418
|
+
name,
|
|
419
|
+
provider,
|
|
420
|
+
event,
|
|
421
|
+
file: `${provider}/${templateId}.json`,
|
|
422
|
+
description,
|
|
423
|
+
source: "capture",
|
|
424
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
425
|
+
};
|
|
426
|
+
const saveData = {
|
|
427
|
+
...template,
|
|
428
|
+
provider,
|
|
429
|
+
event,
|
|
430
|
+
description,
|
|
431
|
+
_metadata: metadata
|
|
432
|
+
};
|
|
433
|
+
const filePath = join(providerDir, `${templateId}.json`);
|
|
434
|
+
writeFileSync(filePath, JSON.stringify(saveData, null, 2));
|
|
435
|
+
return {
|
|
436
|
+
id: templateId,
|
|
437
|
+
filePath,
|
|
438
|
+
template: saveData
|
|
439
|
+
};
|
|
440
|
+
}
|
|
364
441
|
};
|
|
365
442
|
var instance = null;
|
|
366
443
|
function getTemplateManager(baseDir) {
|
|
@@ -1537,6 +1614,7 @@ var capture = new Command3().name("capture").description("Start a server to capt
|
|
|
1537
1614
|
import { Command as Command4 } from "commander";
|
|
1538
1615
|
import chalk4 from "chalk";
|
|
1539
1616
|
import prompts3 from "prompts";
|
|
1617
|
+
import { homedir as homedir4 } from "os";
|
|
1540
1618
|
|
|
1541
1619
|
// src/core/replay-engine.ts
|
|
1542
1620
|
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -1670,15 +1748,93 @@ var ReplayEngine = class {
|
|
|
1670
1748
|
body = capture2.rawBody;
|
|
1671
1749
|
}
|
|
1672
1750
|
}
|
|
1751
|
+
const event = options?.event || this.detectEvent(capture2);
|
|
1673
1752
|
return {
|
|
1674
1753
|
url: options?.url || `http://localhost:3000${capture2.path}`,
|
|
1675
1754
|
method: capture2.method,
|
|
1676
1755
|
headers,
|
|
1677
1756
|
body,
|
|
1678
1757
|
provider: capture2.provider,
|
|
1758
|
+
event,
|
|
1679
1759
|
description: `Captured ${capture2.provider || "webhook"} at ${capture2.timestamp}`
|
|
1680
1760
|
};
|
|
1681
1761
|
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Detect event type from captured webhook headers/body
|
|
1764
|
+
*/
|
|
1765
|
+
detectEvent(capture2) {
|
|
1766
|
+
const headers = capture2.headers;
|
|
1767
|
+
const githubEvent = headers["x-github-event"];
|
|
1768
|
+
if (githubEvent) {
|
|
1769
|
+
return Array.isArray(githubEvent) ? githubEvent[0] : githubEvent;
|
|
1770
|
+
}
|
|
1771
|
+
if (capture2.provider === "stripe" && capture2.body) {
|
|
1772
|
+
const body = capture2.body;
|
|
1773
|
+
if (typeof body.type === "string") {
|
|
1774
|
+
return body.type;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
if (capture2.provider === "slack" && capture2.body) {
|
|
1778
|
+
const body = capture2.body;
|
|
1779
|
+
if (typeof body.type === "string") {
|
|
1780
|
+
return body.type;
|
|
1781
|
+
}
|
|
1782
|
+
const event = body.event;
|
|
1783
|
+
if (event && typeof event.type === "string") {
|
|
1784
|
+
return event.type;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
if (capture2.provider === "linear" && capture2.body) {
|
|
1788
|
+
const body = capture2.body;
|
|
1789
|
+
if (typeof body.type === "string") {
|
|
1790
|
+
return body.type;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
if (capture2.provider === "clerk" && capture2.body) {
|
|
1794
|
+
const body = capture2.body;
|
|
1795
|
+
if (typeof body.type === "string") {
|
|
1796
|
+
return body.type;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
if (capture2.provider === "ragie" && capture2.body) {
|
|
1800
|
+
const body = capture2.body;
|
|
1801
|
+
if (typeof body.event_type === "string") {
|
|
1802
|
+
return body.event_type;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
const shopifyTopic = headers["x-shopify-topic"];
|
|
1806
|
+
if (shopifyTopic) {
|
|
1807
|
+
return Array.isArray(shopifyTopic) ? shopifyTopic[0] : shopifyTopic;
|
|
1808
|
+
}
|
|
1809
|
+
if (capture2.provider === "sendgrid" && Array.isArray(capture2.body)) {
|
|
1810
|
+
const firstEvent = capture2.body[0];
|
|
1811
|
+
if (firstEvent && typeof firstEvent.event === "string") {
|
|
1812
|
+
return firstEvent.event;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
if (capture2.provider === "discord" && capture2.body) {
|
|
1816
|
+
const body = capture2.body;
|
|
1817
|
+
if (typeof body.type === "number") {
|
|
1818
|
+
return `type_${body.type}`;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
if (capture2.body && typeof capture2.body === "object") {
|
|
1822
|
+
const body = capture2.body;
|
|
1823
|
+
if (typeof body.type === "string") {
|
|
1824
|
+
return body.type;
|
|
1825
|
+
}
|
|
1826
|
+
if (typeof body.event_type === "string") {
|
|
1827
|
+
return body.event_type;
|
|
1828
|
+
}
|
|
1829
|
+
if (typeof body.event === "string") {
|
|
1830
|
+
return body.event;
|
|
1831
|
+
}
|
|
1832
|
+
if (typeof body.action === "string") {
|
|
1833
|
+
return body.action;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return void 0;
|
|
1837
|
+
}
|
|
1682
1838
|
/**
|
|
1683
1839
|
* Get a summary of a capture
|
|
1684
1840
|
*/
|
|
@@ -1767,6 +1923,13 @@ function getReplayEngine(capturesDir) {
|
|
|
1767
1923
|
}
|
|
1768
1924
|
|
|
1769
1925
|
// src/commands/captures.ts
|
|
1926
|
+
function toRelativePath(absolutePath) {
|
|
1927
|
+
const home = homedir4();
|
|
1928
|
+
if (absolutePath.startsWith(home)) {
|
|
1929
|
+
return "~" + absolutePath.slice(home.length);
|
|
1930
|
+
}
|
|
1931
|
+
return absolutePath;
|
|
1932
|
+
}
|
|
1770
1933
|
var listCommand2 = new Command4().name("list").alias("ls").description("List captured webhooks").option("-l, --limit <limit>", "Maximum number of captures to show", "20").option("-p, --provider <provider>", "Filter by provider").action((options) => {
|
|
1771
1934
|
const limit = parseInt(options.limit, 10);
|
|
1772
1935
|
if (isNaN(limit) || limit <= 0) {
|
|
@@ -1975,7 +2138,92 @@ var cleanCommand2 = new Command4().name("clean").alias("remove-all").description
|
|
|
1975
2138
|
console.log(chalk4.gray(` Storage: ${engine.getCapturesDir()}
|
|
1976
2139
|
`));
|
|
1977
2140
|
});
|
|
1978
|
-
var
|
|
2141
|
+
var saveAsTemplateCommand = new Command4().name("save-as-template").alias("sat").argument("<captureId>", "Capture ID or partial ID").description("Save a captured webhook as a reusable template").option("--id <id>", "Template ID (auto-generated if not provided)").option("--name <name>", "Template display name").option("--event <event>", "Event type (auto-detected if not provided)").option("--description <description>", "Template description").option("--url <url>", "Default target URL for the template").option("--overwrite", "Overwrite existing template with same ID").action(
|
|
2142
|
+
async (captureId, options) => {
|
|
2143
|
+
const engine = getReplayEngine();
|
|
2144
|
+
const templateManager = getTemplateManager();
|
|
2145
|
+
const captureFile = engine.getCapture(captureId);
|
|
2146
|
+
if (!captureFile) {
|
|
2147
|
+
console.log(chalk4.red(`
|
|
2148
|
+
\u274C Capture not found: ${captureId}
|
|
2149
|
+
`));
|
|
2150
|
+
process.exitCode = 1;
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
const { capture: capture2 } = captureFile;
|
|
2154
|
+
console.log(chalk4.bold("\n\u{1F4CB} Capture to save as template:\n"));
|
|
2155
|
+
console.log(` ${chalk4.white(capture2.id.slice(0, 8))}`);
|
|
2156
|
+
console.log(chalk4.gray(` ${capture2.method} ${capture2.path}`));
|
|
2157
|
+
if (capture2.provider) {
|
|
2158
|
+
console.log(chalk4.gray(` Provider: ${capture2.provider}`));
|
|
2159
|
+
}
|
|
2160
|
+
console.log();
|
|
2161
|
+
const template = engine.captureToTemplate(captureId, {
|
|
2162
|
+
url: options.url,
|
|
2163
|
+
event: options.event
|
|
2164
|
+
});
|
|
2165
|
+
let templateId = options.id;
|
|
2166
|
+
if (!templateId) {
|
|
2167
|
+
const suggestedId = `${capture2.provider || "custom"}-${template.event || "webhook"}`.toLowerCase().replace(/\s+/g, "-");
|
|
2168
|
+
const response = await prompts3({
|
|
2169
|
+
type: "text",
|
|
2170
|
+
name: "templateId",
|
|
2171
|
+
message: "Template ID:",
|
|
2172
|
+
initial: suggestedId,
|
|
2173
|
+
validate: (value) => value.trim().length > 0 || "Template ID is required"
|
|
2174
|
+
});
|
|
2175
|
+
if (!response.templateId) {
|
|
2176
|
+
console.log(chalk4.yellow("Cancelled"));
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
templateId = response.templateId;
|
|
2180
|
+
}
|
|
2181
|
+
if (!options.overwrite && templateId && templateManager.templateExists(templateId)) {
|
|
2182
|
+
const response = await prompts3({
|
|
2183
|
+
type: "confirm",
|
|
2184
|
+
name: "overwrite",
|
|
2185
|
+
message: `Template "${templateId}" already exists. Overwrite?`,
|
|
2186
|
+
initial: false
|
|
2187
|
+
});
|
|
2188
|
+
if (!response.overwrite) {
|
|
2189
|
+
console.log(chalk4.yellow("Cancelled"));
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
options.overwrite = true;
|
|
2193
|
+
}
|
|
2194
|
+
try {
|
|
2195
|
+
const result = templateManager.saveUserTemplate(template, {
|
|
2196
|
+
id: templateId,
|
|
2197
|
+
name: options.name,
|
|
2198
|
+
event: options.event || template.event,
|
|
2199
|
+
description: options.description,
|
|
2200
|
+
overwrite: options.overwrite
|
|
2201
|
+
});
|
|
2202
|
+
console.log(chalk4.green(`
|
|
2203
|
+
\u2713 Saved template: ${result.id}`));
|
|
2204
|
+
console.log(chalk4.gray(` File: ${toRelativePath(result.filePath)}`));
|
|
2205
|
+
console.log(chalk4.gray(` Provider: ${template.provider || "custom"}`));
|
|
2206
|
+
if (template.event) {
|
|
2207
|
+
console.log(chalk4.gray(` Event: ${template.event}`));
|
|
2208
|
+
}
|
|
2209
|
+
console.log();
|
|
2210
|
+
console.log(chalk4.gray(" Run it with:"));
|
|
2211
|
+
console.log(
|
|
2212
|
+
chalk4.cyan(
|
|
2213
|
+
` better-webhook run ${result.id} --url http://localhost:3000/webhooks
|
|
2214
|
+
`
|
|
2215
|
+
)
|
|
2216
|
+
);
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
const message = error instanceof Error ? error.message : "Failed to save template";
|
|
2219
|
+
console.log(chalk4.red(`
|
|
2220
|
+
\u274C ${message}
|
|
2221
|
+
`));
|
|
2222
|
+
process.exitCode = 1;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
);
|
|
2226
|
+
var captures = new Command4().name("captures").alias("c").description("Manage captured webhooks").addCommand(listCommand2).addCommand(showCommand).addCommand(searchCommand2).addCommand(deleteCommand).addCommand(cleanCommand2).addCommand(saveAsTemplateCommand);
|
|
1979
2227
|
|
|
1980
2228
|
// src/commands/replay.ts
|
|
1981
2229
|
import { Command as Command5 } from "commander";
|
|
@@ -2162,12 +2410,28 @@ var ReplayBodySchema = z2.object({
|
|
|
2162
2410
|
var TemplateDownloadBodySchema = z2.object({
|
|
2163
2411
|
id: z2.string().min(1)
|
|
2164
2412
|
});
|
|
2413
|
+
var TemplateIdSchema = z2.string().regex(
|
|
2414
|
+
/^[a-z0-9][a-z0-9._-]*$/i,
|
|
2415
|
+
"ID must start with alphanumeric and contain only letters, numbers, dots, underscores, and hyphens"
|
|
2416
|
+
).max(128, "ID must be 128 characters or less").refine(
|
|
2417
|
+
(val) => !val.includes("/") && !val.includes("\\") && !val.includes(".."),
|
|
2418
|
+
"ID cannot contain path separators or parent directory references"
|
|
2419
|
+
);
|
|
2165
2420
|
var RunTemplateBodySchema = z2.object({
|
|
2166
2421
|
templateId: z2.string().min(1),
|
|
2167
2422
|
url: z2.string().min(1),
|
|
2168
2423
|
secret: z2.string().optional(),
|
|
2169
2424
|
headers: z2.array(HeaderEntrySchema).optional()
|
|
2170
2425
|
});
|
|
2426
|
+
var SaveAsTemplateBodySchema = z2.object({
|
|
2427
|
+
captureId: z2.string().min(1),
|
|
2428
|
+
id: TemplateIdSchema.optional(),
|
|
2429
|
+
name: z2.string().optional(),
|
|
2430
|
+
event: z2.string().optional(),
|
|
2431
|
+
description: z2.string().optional(),
|
|
2432
|
+
url: z2.string().optional(),
|
|
2433
|
+
overwrite: z2.boolean().optional()
|
|
2434
|
+
});
|
|
2171
2435
|
function createDashboardApiRouter(options = {}) {
|
|
2172
2436
|
const router = express.Router();
|
|
2173
2437
|
const replayEngine = new ReplayEngine(options.capturesDir);
|
|
@@ -2183,22 +2447,26 @@ function createDashboardApiRouter(options = {}) {
|
|
|
2183
2447
|
};
|
|
2184
2448
|
const broadcastTemplates = async () => {
|
|
2185
2449
|
if (!broadcast) return;
|
|
2186
|
-
const local = templateManager.listLocalTemplates();
|
|
2187
|
-
let remote = [];
|
|
2188
2450
|
try {
|
|
2189
|
-
const
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2451
|
+
const local = templateManager.listLocalTemplates();
|
|
2452
|
+
let remote = [];
|
|
2453
|
+
try {
|
|
2454
|
+
const index = await templateManager.fetchRemoteIndex(false);
|
|
2455
|
+
const localIds = new Set(local.map((t) => t.id));
|
|
2456
|
+
remote = index.templates.map((metadata) => ({
|
|
2457
|
+
metadata,
|
|
2458
|
+
isDownloaded: localIds.has(metadata.id)
|
|
2459
|
+
}));
|
|
2460
|
+
} catch {
|
|
2461
|
+
remote = [];
|
|
2462
|
+
}
|
|
2463
|
+
broadcast({
|
|
2464
|
+
type: "templates_updated",
|
|
2465
|
+
payload: { local, remote }
|
|
2466
|
+
});
|
|
2467
|
+
} catch (error) {
|
|
2468
|
+
console.error("[dashboard-api] Failed to broadcast templates:", error);
|
|
2197
2469
|
}
|
|
2198
|
-
broadcast({
|
|
2199
|
-
type: "templates_updated",
|
|
2200
|
-
payload: { local, remote }
|
|
2201
|
-
});
|
|
2202
2470
|
};
|
|
2203
2471
|
router.get("/captures", (req, res) => {
|
|
2204
2472
|
const limitRaw = typeof req.query.limit === "string" ? req.query.limit : "";
|
|
@@ -2411,6 +2679,54 @@ function createDashboardApiRouter(options = {}) {
|
|
|
2411
2679
|
return jsonError(res, 400, error?.message || "Run failed");
|
|
2412
2680
|
}
|
|
2413
2681
|
});
|
|
2682
|
+
router.post(
|
|
2683
|
+
"/templates/from-capture",
|
|
2684
|
+
express.json({ limit: "5mb" }),
|
|
2685
|
+
async (req, res) => {
|
|
2686
|
+
const parsed = SaveAsTemplateBodySchema.safeParse(req.body);
|
|
2687
|
+
if (!parsed.success) {
|
|
2688
|
+
return jsonError(
|
|
2689
|
+
res,
|
|
2690
|
+
400,
|
|
2691
|
+
parsed.error.issues[0]?.message || "Invalid body"
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
const { captureId, id, name, event, description, url, overwrite } = parsed.data;
|
|
2695
|
+
if (url !== void 0) {
|
|
2696
|
+
try {
|
|
2697
|
+
new URL(url);
|
|
2698
|
+
} catch {
|
|
2699
|
+
return jsonError(res, 400, "Invalid url");
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
const captureFile = replayEngine.getCapture(captureId);
|
|
2703
|
+
if (!captureFile) {
|
|
2704
|
+
return jsonError(res, 404, "Capture not found");
|
|
2705
|
+
}
|
|
2706
|
+
const template = replayEngine.captureToTemplate(captureId, {
|
|
2707
|
+
url,
|
|
2708
|
+
event
|
|
2709
|
+
});
|
|
2710
|
+
try {
|
|
2711
|
+
const result = templateManager.saveUserTemplate(template, {
|
|
2712
|
+
id,
|
|
2713
|
+
name,
|
|
2714
|
+
event: event || template.event,
|
|
2715
|
+
description,
|
|
2716
|
+
overwrite
|
|
2717
|
+
});
|
|
2718
|
+
void broadcastTemplates();
|
|
2719
|
+
return res.json({
|
|
2720
|
+
success: true,
|
|
2721
|
+
id: result.id,
|
|
2722
|
+
filePath: result.filePath,
|
|
2723
|
+
template: result.template
|
|
2724
|
+
});
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
return jsonError(res, 400, error?.message || "Failed to save template");
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
);
|
|
2414
2730
|
return router;
|
|
2415
2731
|
}
|
|
2416
2732
|
|
|
@@ -2468,13 +2784,10 @@ async function startDashboardServer(options = {}) {
|
|
|
2468
2784
|
);
|
|
2469
2785
|
const host = options.host || "localhost";
|
|
2470
2786
|
const port = options.port ?? 4e3;
|
|
2471
|
-
const runtimeDir = (
|
|
2787
|
+
const runtimeDir = typeof __dirname !== "undefined" ? (
|
|
2472
2788
|
// eslint-disable-next-line no-undef
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
__dirname
|
|
2476
|
-
) : path.dirname(fileURLToPath(import.meta.url))
|
|
2477
|
-
);
|
|
2789
|
+
__dirname
|
|
2790
|
+
) : path.dirname(fileURLToPath(import.meta.url));
|
|
2478
2791
|
const { distDir: dashboardDistDir, indexHtml: dashboardIndexHtml } = resolveDashboardDistDir(runtimeDir);
|
|
2479
2792
|
app.use(express2.static(dashboardDistDir));
|
|
2480
2793
|
app.get("*", (req, res, next) => {
|
|
@@ -2608,8 +2921,12 @@ var dashboard = new Command6().name("dashboard").description("Start the local da
|
|
|
2608
2921
|
});
|
|
2609
2922
|
|
|
2610
2923
|
// src/index.ts
|
|
2611
|
-
var
|
|
2612
|
-
|
|
2924
|
+
var packageJsonPath = fileURLToPath2(
|
|
2925
|
+
new URL("../package.json", import.meta.url)
|
|
2926
|
+
);
|
|
2927
|
+
var packageJson = JSON.parse(
|
|
2928
|
+
readFileSync4(packageJsonPath, { encoding: "utf8" })
|
|
2929
|
+
);
|
|
2613
2930
|
var program = new Command7().name("better-webhook").description(
|
|
2614
2931
|
"Modern CLI for developing, capturing, and replaying webhooks locally"
|
|
2615
2932
|
).version(packageJson.version);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-webhook/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Modern CLI for developing, capturing, and replaying webhooks locally with dashboard UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"express": "^4.21.0",
|
|
56
56
|
"ora": "^8.1.0",
|
|
57
57
|
"prompts": "^2.4.2",
|
|
58
|
-
"undici": "^7.
|
|
58
|
+
"undici": "^7.18.2",
|
|
59
59
|
"ws": "^8.18.0",
|
|
60
60
|
"zod": "^3.23.8"
|
|
61
61
|
},
|
|
@@ -71,9 +71,10 @@
|
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build:cli": "tsup --format cjs,esm --dts && node ./scripts/copy-dashboard.mjs",
|
|
74
|
-
"build": "pnpm --filter @better-webhook/dashboard build && pnpm run build:cli
|
|
74
|
+
"build": "pnpm --filter @better-webhook/dashboard build && pnpm run build:cli",
|
|
75
75
|
"dev": "tsup --watch",
|
|
76
76
|
"start": "tsx src/index.ts",
|
|
77
|
-
"lint": "tsc"
|
|
77
|
+
"lint": "tsc",
|
|
78
|
+
"test": "vitest run"
|
|
78
79
|
}
|
|
79
80
|
}
|
package/dist/commands/capture.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { CaptureServer } from "../core/capture-server.js";
|
|
4
|
-
export const capture = new Command()
|
|
5
|
-
.name("capture")
|
|
6
|
-
.description("Start a server to capture incoming webhooks")
|
|
7
|
-
.option("-p, --port <port>", "Port to listen on", "3001")
|
|
8
|
-
.option("-h, --host <host>", "Host to bind to", "0.0.0.0")
|
|
9
|
-
.action(async (options) => {
|
|
10
|
-
const port = parseInt(options.port, 10);
|
|
11
|
-
if (isNaN(port) || port < 0 || port > 65535) {
|
|
12
|
-
console.error(chalk.red("Invalid port number"));
|
|
13
|
-
process.exitCode = 1;
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const server = new CaptureServer();
|
|
17
|
-
try {
|
|
18
|
-
await server.start(port, options.host);
|
|
19
|
-
const shutdown = async () => {
|
|
20
|
-
await server.stop();
|
|
21
|
-
process.exit(0);
|
|
22
|
-
};
|
|
23
|
-
process.on("SIGINT", shutdown);
|
|
24
|
-
process.on("SIGTERM", shutdown);
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
console.error(chalk.red(`Failed to start server: ${error.message}`));
|
|
28
|
-
process.exitCode = 1;
|
|
29
|
-
}
|
|
30
|
-
});
|