@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
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>better-webhook dashboard</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Dlqdzwyc.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CxrRCNTh.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/dist/index.cjs
CHANGED
|
@@ -25,7 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
27
|
var import_commander7 = require("commander");
|
|
28
|
-
var
|
|
28
|
+
var import_node_fs = require("fs");
|
|
29
|
+
var import_node_url = require("url");
|
|
29
30
|
|
|
30
31
|
// src/commands/templates.ts
|
|
31
32
|
var import_commander = require("commander");
|
|
@@ -123,6 +124,12 @@ var ConfigSchema = import_zod.z.object({
|
|
|
123
124
|
// src/core/template-manager.ts
|
|
124
125
|
var GITHUB_RAW_BASE = "https://raw.githubusercontent.com/endalk200/better-webhook/main";
|
|
125
126
|
var TEMPLATES_INDEX_URL = `${GITHUB_RAW_BASE}/templates/templates.json`;
|
|
127
|
+
var TEMPLATE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
128
|
+
function isValidTemplateId(id) {
|
|
129
|
+
if (!id || id.length > 128) return false;
|
|
130
|
+
if (id.includes("/") || id.includes("\\") || id.includes("..")) return false;
|
|
131
|
+
return TEMPLATE_ID_PATTERN.test(id);
|
|
132
|
+
}
|
|
126
133
|
var TemplateManager = class {
|
|
127
134
|
baseDir;
|
|
128
135
|
templatesDir;
|
|
@@ -376,6 +383,76 @@ var TemplateManager = class {
|
|
|
376
383
|
}
|
|
377
384
|
return deleted;
|
|
378
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Check if a template with the given ID already exists locally
|
|
388
|
+
*/
|
|
389
|
+
templateExists(templateId) {
|
|
390
|
+
return this.getLocalTemplate(templateId) !== null;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Generate a unique template ID from provider and event
|
|
394
|
+
*/
|
|
395
|
+
generateTemplateId(provider, event) {
|
|
396
|
+
const providerPart = provider || "custom";
|
|
397
|
+
const eventPart = event || "webhook";
|
|
398
|
+
const baseId = `${providerPart}-${eventPart}`.toLowerCase().replace(/\s+/g, "-");
|
|
399
|
+
if (!this.templateExists(baseId)) {
|
|
400
|
+
return baseId;
|
|
401
|
+
}
|
|
402
|
+
let counter = 1;
|
|
403
|
+
while (this.templateExists(`${baseId}-${counter}`)) {
|
|
404
|
+
counter++;
|
|
405
|
+
}
|
|
406
|
+
return `${baseId}-${counter}`;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Save a user-created template from a captured webhook
|
|
410
|
+
*/
|
|
411
|
+
saveUserTemplate(template, options = {}) {
|
|
412
|
+
const provider = template.provider || "custom";
|
|
413
|
+
const event = options.event || template.event || "webhook";
|
|
414
|
+
const templateId = options.id || this.generateTemplateId(provider, event);
|
|
415
|
+
const name = options.name || templateId;
|
|
416
|
+
const description = options.description || template.description;
|
|
417
|
+
if (!isValidTemplateId(templateId)) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Invalid template ID "${templateId}". IDs must start with alphanumeric, contain only letters, numbers, dots, underscores, and hyphens.`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (!options.overwrite && this.templateExists(templateId)) {
|
|
423
|
+
throw new Error(
|
|
424
|
+
`Template with ID "${templateId}" already exists. Use --overwrite to replace it.`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
const providerDir = (0, import_path.join)(this.templatesDir, provider);
|
|
428
|
+
if (!(0, import_fs.existsSync)(providerDir)) {
|
|
429
|
+
(0, import_fs.mkdirSync)(providerDir, { recursive: true });
|
|
430
|
+
}
|
|
431
|
+
const metadata = {
|
|
432
|
+
id: templateId,
|
|
433
|
+
name,
|
|
434
|
+
provider,
|
|
435
|
+
event,
|
|
436
|
+
file: `${provider}/${templateId}.json`,
|
|
437
|
+
description,
|
|
438
|
+
source: "capture",
|
|
439
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
440
|
+
};
|
|
441
|
+
const saveData = {
|
|
442
|
+
...template,
|
|
443
|
+
provider,
|
|
444
|
+
event,
|
|
445
|
+
description,
|
|
446
|
+
_metadata: metadata
|
|
447
|
+
};
|
|
448
|
+
const filePath = (0, import_path.join)(providerDir, `${templateId}.json`);
|
|
449
|
+
(0, import_fs.writeFileSync)(filePath, JSON.stringify(saveData, null, 2));
|
|
450
|
+
return {
|
|
451
|
+
id: templateId,
|
|
452
|
+
filePath,
|
|
453
|
+
template: saveData
|
|
454
|
+
};
|
|
455
|
+
}
|
|
379
456
|
};
|
|
380
457
|
var instance = null;
|
|
381
458
|
function getTemplateManager(baseDir) {
|
|
@@ -1543,6 +1620,7 @@ var capture = new import_commander3.Command().name("capture").description("Start
|
|
|
1543
1620
|
var import_commander4 = require("commander");
|
|
1544
1621
|
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
1545
1622
|
var import_prompts3 = __toESM(require("prompts"), 1);
|
|
1623
|
+
var import_os4 = require("os");
|
|
1546
1624
|
|
|
1547
1625
|
// src/core/replay-engine.ts
|
|
1548
1626
|
var import_fs3 = require("fs");
|
|
@@ -1676,15 +1754,93 @@ var ReplayEngine = class {
|
|
|
1676
1754
|
body = capture2.rawBody;
|
|
1677
1755
|
}
|
|
1678
1756
|
}
|
|
1757
|
+
const event = options?.event || this.detectEvent(capture2);
|
|
1679
1758
|
return {
|
|
1680
1759
|
url: options?.url || `http://localhost:3000${capture2.path}`,
|
|
1681
1760
|
method: capture2.method,
|
|
1682
1761
|
headers,
|
|
1683
1762
|
body,
|
|
1684
1763
|
provider: capture2.provider,
|
|
1764
|
+
event,
|
|
1685
1765
|
description: `Captured ${capture2.provider || "webhook"} at ${capture2.timestamp}`
|
|
1686
1766
|
};
|
|
1687
1767
|
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Detect event type from captured webhook headers/body
|
|
1770
|
+
*/
|
|
1771
|
+
detectEvent(capture2) {
|
|
1772
|
+
const headers = capture2.headers;
|
|
1773
|
+
const githubEvent = headers["x-github-event"];
|
|
1774
|
+
if (githubEvent) {
|
|
1775
|
+
return Array.isArray(githubEvent) ? githubEvent[0] : githubEvent;
|
|
1776
|
+
}
|
|
1777
|
+
if (capture2.provider === "stripe" && capture2.body) {
|
|
1778
|
+
const body = capture2.body;
|
|
1779
|
+
if (typeof body.type === "string") {
|
|
1780
|
+
return body.type;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
if (capture2.provider === "slack" && capture2.body) {
|
|
1784
|
+
const body = capture2.body;
|
|
1785
|
+
if (typeof body.type === "string") {
|
|
1786
|
+
return body.type;
|
|
1787
|
+
}
|
|
1788
|
+
const event = body.event;
|
|
1789
|
+
if (event && typeof event.type === "string") {
|
|
1790
|
+
return event.type;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
if (capture2.provider === "linear" && capture2.body) {
|
|
1794
|
+
const body = capture2.body;
|
|
1795
|
+
if (typeof body.type === "string") {
|
|
1796
|
+
return body.type;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
if (capture2.provider === "clerk" && capture2.body) {
|
|
1800
|
+
const body = capture2.body;
|
|
1801
|
+
if (typeof body.type === "string") {
|
|
1802
|
+
return body.type;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
if (capture2.provider === "ragie" && capture2.body) {
|
|
1806
|
+
const body = capture2.body;
|
|
1807
|
+
if (typeof body.event_type === "string") {
|
|
1808
|
+
return body.event_type;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
const shopifyTopic = headers["x-shopify-topic"];
|
|
1812
|
+
if (shopifyTopic) {
|
|
1813
|
+
return Array.isArray(shopifyTopic) ? shopifyTopic[0] : shopifyTopic;
|
|
1814
|
+
}
|
|
1815
|
+
if (capture2.provider === "sendgrid" && Array.isArray(capture2.body)) {
|
|
1816
|
+
const firstEvent = capture2.body[0];
|
|
1817
|
+
if (firstEvent && typeof firstEvent.event === "string") {
|
|
1818
|
+
return firstEvent.event;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
if (capture2.provider === "discord" && capture2.body) {
|
|
1822
|
+
const body = capture2.body;
|
|
1823
|
+
if (typeof body.type === "number") {
|
|
1824
|
+
return `type_${body.type}`;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
if (capture2.body && typeof capture2.body === "object") {
|
|
1828
|
+
const body = capture2.body;
|
|
1829
|
+
if (typeof body.type === "string") {
|
|
1830
|
+
return body.type;
|
|
1831
|
+
}
|
|
1832
|
+
if (typeof body.event_type === "string") {
|
|
1833
|
+
return body.event_type;
|
|
1834
|
+
}
|
|
1835
|
+
if (typeof body.event === "string") {
|
|
1836
|
+
return body.event;
|
|
1837
|
+
}
|
|
1838
|
+
if (typeof body.action === "string") {
|
|
1839
|
+
return body.action;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
return void 0;
|
|
1843
|
+
}
|
|
1688
1844
|
/**
|
|
1689
1845
|
* Get a summary of a capture
|
|
1690
1846
|
*/
|
|
@@ -1773,6 +1929,13 @@ function getReplayEngine(capturesDir) {
|
|
|
1773
1929
|
}
|
|
1774
1930
|
|
|
1775
1931
|
// src/commands/captures.ts
|
|
1932
|
+
function toRelativePath(absolutePath) {
|
|
1933
|
+
const home = (0, import_os4.homedir)();
|
|
1934
|
+
if (absolutePath.startsWith(home)) {
|
|
1935
|
+
return "~" + absolutePath.slice(home.length);
|
|
1936
|
+
}
|
|
1937
|
+
return absolutePath;
|
|
1938
|
+
}
|
|
1776
1939
|
var listCommand2 = new import_commander4.Command().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) => {
|
|
1777
1940
|
const limit = parseInt(options.limit, 10);
|
|
1778
1941
|
if (isNaN(limit) || limit <= 0) {
|
|
@@ -1981,7 +2144,92 @@ var cleanCommand2 = new import_commander4.Command().name("clean").alias("remove-
|
|
|
1981
2144
|
console.log(import_chalk4.default.gray(` Storage: ${engine.getCapturesDir()}
|
|
1982
2145
|
`));
|
|
1983
2146
|
});
|
|
1984
|
-
var
|
|
2147
|
+
var saveAsTemplateCommand = new import_commander4.Command().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(
|
|
2148
|
+
async (captureId, options) => {
|
|
2149
|
+
const engine = getReplayEngine();
|
|
2150
|
+
const templateManager = getTemplateManager();
|
|
2151
|
+
const captureFile = engine.getCapture(captureId);
|
|
2152
|
+
if (!captureFile) {
|
|
2153
|
+
console.log(import_chalk4.default.red(`
|
|
2154
|
+
\u274C Capture not found: ${captureId}
|
|
2155
|
+
`));
|
|
2156
|
+
process.exitCode = 1;
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
const { capture: capture2 } = captureFile;
|
|
2160
|
+
console.log(import_chalk4.default.bold("\n\u{1F4CB} Capture to save as template:\n"));
|
|
2161
|
+
console.log(` ${import_chalk4.default.white(capture2.id.slice(0, 8))}`);
|
|
2162
|
+
console.log(import_chalk4.default.gray(` ${capture2.method} ${capture2.path}`));
|
|
2163
|
+
if (capture2.provider) {
|
|
2164
|
+
console.log(import_chalk4.default.gray(` Provider: ${capture2.provider}`));
|
|
2165
|
+
}
|
|
2166
|
+
console.log();
|
|
2167
|
+
const template = engine.captureToTemplate(captureId, {
|
|
2168
|
+
url: options.url,
|
|
2169
|
+
event: options.event
|
|
2170
|
+
});
|
|
2171
|
+
let templateId = options.id;
|
|
2172
|
+
if (!templateId) {
|
|
2173
|
+
const suggestedId = `${capture2.provider || "custom"}-${template.event || "webhook"}`.toLowerCase().replace(/\s+/g, "-");
|
|
2174
|
+
const response = await (0, import_prompts3.default)({
|
|
2175
|
+
type: "text",
|
|
2176
|
+
name: "templateId",
|
|
2177
|
+
message: "Template ID:",
|
|
2178
|
+
initial: suggestedId,
|
|
2179
|
+
validate: (value) => value.trim().length > 0 || "Template ID is required"
|
|
2180
|
+
});
|
|
2181
|
+
if (!response.templateId) {
|
|
2182
|
+
console.log(import_chalk4.default.yellow("Cancelled"));
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
templateId = response.templateId;
|
|
2186
|
+
}
|
|
2187
|
+
if (!options.overwrite && templateId && templateManager.templateExists(templateId)) {
|
|
2188
|
+
const response = await (0, import_prompts3.default)({
|
|
2189
|
+
type: "confirm",
|
|
2190
|
+
name: "overwrite",
|
|
2191
|
+
message: `Template "${templateId}" already exists. Overwrite?`,
|
|
2192
|
+
initial: false
|
|
2193
|
+
});
|
|
2194
|
+
if (!response.overwrite) {
|
|
2195
|
+
console.log(import_chalk4.default.yellow("Cancelled"));
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
options.overwrite = true;
|
|
2199
|
+
}
|
|
2200
|
+
try {
|
|
2201
|
+
const result = templateManager.saveUserTemplate(template, {
|
|
2202
|
+
id: templateId,
|
|
2203
|
+
name: options.name,
|
|
2204
|
+
event: options.event || template.event,
|
|
2205
|
+
description: options.description,
|
|
2206
|
+
overwrite: options.overwrite
|
|
2207
|
+
});
|
|
2208
|
+
console.log(import_chalk4.default.green(`
|
|
2209
|
+
\u2713 Saved template: ${result.id}`));
|
|
2210
|
+
console.log(import_chalk4.default.gray(` File: ${toRelativePath(result.filePath)}`));
|
|
2211
|
+
console.log(import_chalk4.default.gray(` Provider: ${template.provider || "custom"}`));
|
|
2212
|
+
if (template.event) {
|
|
2213
|
+
console.log(import_chalk4.default.gray(` Event: ${template.event}`));
|
|
2214
|
+
}
|
|
2215
|
+
console.log();
|
|
2216
|
+
console.log(import_chalk4.default.gray(" Run it with:"));
|
|
2217
|
+
console.log(
|
|
2218
|
+
import_chalk4.default.cyan(
|
|
2219
|
+
` better-webhook run ${result.id} --url http://localhost:3000/webhooks
|
|
2220
|
+
`
|
|
2221
|
+
)
|
|
2222
|
+
);
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
const message = error instanceof Error ? error.message : "Failed to save template";
|
|
2225
|
+
console.log(import_chalk4.default.red(`
|
|
2226
|
+
\u274C ${message}
|
|
2227
|
+
`));
|
|
2228
|
+
process.exitCode = 1;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
);
|
|
2232
|
+
var captures = new import_commander4.Command().name("captures").alias("c").description("Manage captured webhooks").addCommand(listCommand2).addCommand(showCommand).addCommand(searchCommand2).addCommand(deleteCommand).addCommand(cleanCommand2).addCommand(saveAsTemplateCommand);
|
|
1985
2233
|
|
|
1986
2234
|
// src/commands/replay.ts
|
|
1987
2235
|
var import_commander5 = require("commander");
|
|
@@ -2168,12 +2416,28 @@ var ReplayBodySchema = import_zod2.z.object({
|
|
|
2168
2416
|
var TemplateDownloadBodySchema = import_zod2.z.object({
|
|
2169
2417
|
id: import_zod2.z.string().min(1)
|
|
2170
2418
|
});
|
|
2419
|
+
var TemplateIdSchema = import_zod2.z.string().regex(
|
|
2420
|
+
/^[a-z0-9][a-z0-9._-]*$/i,
|
|
2421
|
+
"ID must start with alphanumeric and contain only letters, numbers, dots, underscores, and hyphens"
|
|
2422
|
+
).max(128, "ID must be 128 characters or less").refine(
|
|
2423
|
+
(val) => !val.includes("/") && !val.includes("\\") && !val.includes(".."),
|
|
2424
|
+
"ID cannot contain path separators or parent directory references"
|
|
2425
|
+
);
|
|
2171
2426
|
var RunTemplateBodySchema = import_zod2.z.object({
|
|
2172
2427
|
templateId: import_zod2.z.string().min(1),
|
|
2173
2428
|
url: import_zod2.z.string().min(1),
|
|
2174
2429
|
secret: import_zod2.z.string().optional(),
|
|
2175
2430
|
headers: import_zod2.z.array(HeaderEntrySchema).optional()
|
|
2176
2431
|
});
|
|
2432
|
+
var SaveAsTemplateBodySchema = import_zod2.z.object({
|
|
2433
|
+
captureId: import_zod2.z.string().min(1),
|
|
2434
|
+
id: TemplateIdSchema.optional(),
|
|
2435
|
+
name: import_zod2.z.string().optional(),
|
|
2436
|
+
event: import_zod2.z.string().optional(),
|
|
2437
|
+
description: import_zod2.z.string().optional(),
|
|
2438
|
+
url: import_zod2.z.string().optional(),
|
|
2439
|
+
overwrite: import_zod2.z.boolean().optional()
|
|
2440
|
+
});
|
|
2177
2441
|
function createDashboardApiRouter(options = {}) {
|
|
2178
2442
|
const router = import_express.default.Router();
|
|
2179
2443
|
const replayEngine = new ReplayEngine(options.capturesDir);
|
|
@@ -2189,22 +2453,26 @@ function createDashboardApiRouter(options = {}) {
|
|
|
2189
2453
|
};
|
|
2190
2454
|
const broadcastTemplates = async () => {
|
|
2191
2455
|
if (!broadcast) return;
|
|
2192
|
-
const local = templateManager.listLocalTemplates();
|
|
2193
|
-
let remote = [];
|
|
2194
2456
|
try {
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2457
|
+
const local = templateManager.listLocalTemplates();
|
|
2458
|
+
let remote = [];
|
|
2459
|
+
try {
|
|
2460
|
+
const index = await templateManager.fetchRemoteIndex(false);
|
|
2461
|
+
const localIds = new Set(local.map((t) => t.id));
|
|
2462
|
+
remote = index.templates.map((metadata) => ({
|
|
2463
|
+
metadata,
|
|
2464
|
+
isDownloaded: localIds.has(metadata.id)
|
|
2465
|
+
}));
|
|
2466
|
+
} catch {
|
|
2467
|
+
remote = [];
|
|
2468
|
+
}
|
|
2469
|
+
broadcast({
|
|
2470
|
+
type: "templates_updated",
|
|
2471
|
+
payload: { local, remote }
|
|
2472
|
+
});
|
|
2473
|
+
} catch (error) {
|
|
2474
|
+
console.error("[dashboard-api] Failed to broadcast templates:", error);
|
|
2203
2475
|
}
|
|
2204
|
-
broadcast({
|
|
2205
|
-
type: "templates_updated",
|
|
2206
|
-
payload: { local, remote }
|
|
2207
|
-
});
|
|
2208
2476
|
};
|
|
2209
2477
|
router.get("/captures", (req, res) => {
|
|
2210
2478
|
const limitRaw = typeof req.query.limit === "string" ? req.query.limit : "";
|
|
@@ -2417,6 +2685,54 @@ function createDashboardApiRouter(options = {}) {
|
|
|
2417
2685
|
return jsonError(res, 400, error?.message || "Run failed");
|
|
2418
2686
|
}
|
|
2419
2687
|
});
|
|
2688
|
+
router.post(
|
|
2689
|
+
"/templates/from-capture",
|
|
2690
|
+
import_express.default.json({ limit: "5mb" }),
|
|
2691
|
+
async (req, res) => {
|
|
2692
|
+
const parsed = SaveAsTemplateBodySchema.safeParse(req.body);
|
|
2693
|
+
if (!parsed.success) {
|
|
2694
|
+
return jsonError(
|
|
2695
|
+
res,
|
|
2696
|
+
400,
|
|
2697
|
+
parsed.error.issues[0]?.message || "Invalid body"
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
const { captureId, id, name, event, description, url, overwrite } = parsed.data;
|
|
2701
|
+
if (url !== void 0) {
|
|
2702
|
+
try {
|
|
2703
|
+
new URL(url);
|
|
2704
|
+
} catch {
|
|
2705
|
+
return jsonError(res, 400, "Invalid url");
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
const captureFile = replayEngine.getCapture(captureId);
|
|
2709
|
+
if (!captureFile) {
|
|
2710
|
+
return jsonError(res, 404, "Capture not found");
|
|
2711
|
+
}
|
|
2712
|
+
const template = replayEngine.captureToTemplate(captureId, {
|
|
2713
|
+
url,
|
|
2714
|
+
event
|
|
2715
|
+
});
|
|
2716
|
+
try {
|
|
2717
|
+
const result = templateManager.saveUserTemplate(template, {
|
|
2718
|
+
id,
|
|
2719
|
+
name,
|
|
2720
|
+
event: event || template.event,
|
|
2721
|
+
description,
|
|
2722
|
+
overwrite
|
|
2723
|
+
});
|
|
2724
|
+
void broadcastTemplates();
|
|
2725
|
+
return res.json({
|
|
2726
|
+
success: true,
|
|
2727
|
+
id: result.id,
|
|
2728
|
+
filePath: result.filePath,
|
|
2729
|
+
template: result.template
|
|
2730
|
+
});
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
return jsonError(res, 400, error?.message || "Failed to save template");
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
);
|
|
2420
2736
|
return router;
|
|
2421
2737
|
}
|
|
2422
2738
|
|
|
@@ -2475,13 +2791,10 @@ async function startDashboardServer(options = {}) {
|
|
|
2475
2791
|
);
|
|
2476
2792
|
const host = options.host || "localhost";
|
|
2477
2793
|
const port = options.port ?? 4e3;
|
|
2478
|
-
const runtimeDir = (
|
|
2794
|
+
const runtimeDir = typeof __dirname !== "undefined" ? (
|
|
2479
2795
|
// eslint-disable-next-line no-undef
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
__dirname
|
|
2483
|
-
) : import_path4.default.dirname((0, import_url.fileURLToPath)(import_meta.url))
|
|
2484
|
-
);
|
|
2796
|
+
__dirname
|
|
2797
|
+
) : import_path4.default.dirname((0, import_url.fileURLToPath)(import_meta.url));
|
|
2485
2798
|
const { distDir: dashboardDistDir, indexHtml: dashboardIndexHtml } = resolveDashboardDistDir(runtimeDir);
|
|
2486
2799
|
app.use(import_express2.default.static(dashboardDistDir));
|
|
2487
2800
|
app.get("*", (req, res, next) => {
|
|
@@ -2616,8 +2929,12 @@ var dashboard = new import_commander6.Command().name("dashboard").description("S
|
|
|
2616
2929
|
|
|
2617
2930
|
// src/index.ts
|
|
2618
2931
|
var import_meta2 = {};
|
|
2619
|
-
var
|
|
2620
|
-
|
|
2932
|
+
var packageJsonPath = (0, import_node_url.fileURLToPath)(
|
|
2933
|
+
new URL("../package.json", import_meta2.url)
|
|
2934
|
+
);
|
|
2935
|
+
var packageJson = JSON.parse(
|
|
2936
|
+
(0, import_node_fs.readFileSync)(packageJsonPath, { encoding: "utf8" })
|
|
2937
|
+
);
|
|
2621
2938
|
var program = new import_commander7.Command().name("better-webhook").description(
|
|
2622
2939
|
"Modern CLI for developing, capturing, and replaying webhooks locally"
|
|
2623
2940
|
).version(packageJson.version);
|