@better-webhook/cli 0.3.0 → 0.3.1
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/dist/index.cjs +271 -71
- package/dist/index.js +235 -57
- package/package.json +7 -3
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
3
25
|
|
|
4
26
|
// src/index.ts
|
|
5
27
|
var import_commander4 = require("commander");
|
|
@@ -7,33 +29,11 @@ var import_commander4 = require("commander");
|
|
|
7
29
|
// src/commands/webhooks.ts
|
|
8
30
|
var import_commander = require("commander");
|
|
9
31
|
var import_path2 = require("path");
|
|
10
|
-
var
|
|
32
|
+
var import_fs2 = require("fs");
|
|
11
33
|
|
|
12
34
|
// src/utils/index.ts
|
|
13
35
|
var import_fs = require("fs");
|
|
14
36
|
var import_path = require("path");
|
|
15
|
-
function findWebhooksDir(cwd) {
|
|
16
|
-
return (0, import_path.resolve)(cwd, ".webhooks");
|
|
17
|
-
}
|
|
18
|
-
function listWebhookFiles(dir) {
|
|
19
|
-
return listJsonFiles(dir);
|
|
20
|
-
}
|
|
21
|
-
function findCapturesDir(cwd) {
|
|
22
|
-
return (0, import_path.resolve)(cwd, ".webhook-captures");
|
|
23
|
-
}
|
|
24
|
-
function listJsonFiles(dir) {
|
|
25
|
-
try {
|
|
26
|
-
const entries = (0, import_fs.readdirSync)(dir);
|
|
27
|
-
return entries.filter(
|
|
28
|
-
(e) => (0, import_fs.statSync)((0, import_path.join)(dir, e)).isFile() && (0, import_path.extname)(e) === ".json"
|
|
29
|
-
);
|
|
30
|
-
} catch {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/loader.ts
|
|
36
|
-
var import_fs2 = require("fs");
|
|
37
37
|
|
|
38
38
|
// src/schema.ts
|
|
39
39
|
var import_zod = require("zod");
|
|
@@ -69,24 +69,7 @@ ${issues}`);
|
|
|
69
69
|
return parsed.data;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// src/
|
|
73
|
-
function loadWebhookFile(path) {
|
|
74
|
-
let rawContent;
|
|
75
|
-
try {
|
|
76
|
-
rawContent = (0, import_fs2.readFileSync)(path, "utf8");
|
|
77
|
-
} catch (e) {
|
|
78
|
-
throw new Error(`Failed to read file ${path}: ${e.message}`);
|
|
79
|
-
}
|
|
80
|
-
let json;
|
|
81
|
-
try {
|
|
82
|
-
json = JSON.parse(rawContent);
|
|
83
|
-
} catch (e) {
|
|
84
|
-
throw new Error(`Invalid JSON in file ${path}: ${e.message}`);
|
|
85
|
-
}
|
|
86
|
-
return validateWebhookJSON(json, path);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/http.ts
|
|
72
|
+
// src/utils/http.ts
|
|
90
73
|
var import_undici = require("undici");
|
|
91
74
|
async function executeWebhook(def) {
|
|
92
75
|
const headerMap = {};
|
|
@@ -122,16 +105,58 @@ async function executeWebhook(def) {
|
|
|
122
105
|
};
|
|
123
106
|
}
|
|
124
107
|
|
|
108
|
+
// src/utils/index.ts
|
|
109
|
+
function findWebhooksDir(cwd) {
|
|
110
|
+
return (0, import_path.resolve)(cwd, ".webhooks");
|
|
111
|
+
}
|
|
112
|
+
function listWebhookFiles(dir) {
|
|
113
|
+
return listJsonFiles(dir);
|
|
114
|
+
}
|
|
115
|
+
function findCapturesDir(cwd) {
|
|
116
|
+
return (0, import_path.resolve)(cwd, ".webhook-captures");
|
|
117
|
+
}
|
|
118
|
+
function listJsonFiles(dir) {
|
|
119
|
+
try {
|
|
120
|
+
const entries = (0, import_fs.readdirSync)(dir);
|
|
121
|
+
return entries.filter(
|
|
122
|
+
(e) => (0, import_fs.statSync)((0, import_path.join)(dir, e)).isFile() && (0, import_path.extname)(e) === ".json"
|
|
123
|
+
);
|
|
124
|
+
} catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function loadWebhookFile(path) {
|
|
129
|
+
let rawContent;
|
|
130
|
+
try {
|
|
131
|
+
rawContent = (0, import_fs.readFileSync)(path, "utf8");
|
|
132
|
+
} catch (e) {
|
|
133
|
+
throw new Error(`Failed to read file ${path}: ${e.message}`);
|
|
134
|
+
}
|
|
135
|
+
let json;
|
|
136
|
+
try {
|
|
137
|
+
json = JSON.parse(rawContent);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
throw new Error(`Invalid JSON in file ${path}: ${e.message}`);
|
|
140
|
+
}
|
|
141
|
+
return validateWebhookJSON(json, path);
|
|
142
|
+
}
|
|
143
|
+
|
|
125
144
|
// src/commands/webhooks.ts
|
|
126
|
-
var
|
|
145
|
+
var import_fs3 = require("fs");
|
|
127
146
|
var import_undici2 = require("undici");
|
|
147
|
+
|
|
148
|
+
// src/config.ts
|
|
128
149
|
var TEMPLATE_REPO_BASE = "https://raw.githubusercontent.com/endalk200/better-webhook/main";
|
|
129
150
|
var TEMPLATES = {
|
|
130
151
|
"stripe-invoice.payment_succeeded": "templates/stripe-invoice.payment_succeeded.json"
|
|
131
152
|
};
|
|
153
|
+
|
|
154
|
+
// src/commands/webhooks.ts
|
|
155
|
+
var import_prompts = __toESM(require("prompts"), 1);
|
|
156
|
+
var import_ora = __toESM(require("ora"), 1);
|
|
132
157
|
function statExists(p) {
|
|
133
158
|
try {
|
|
134
|
-
(0,
|
|
159
|
+
(0, import_fs2.statSync)(p);
|
|
135
160
|
return true;
|
|
136
161
|
} catch {
|
|
137
162
|
return false;
|
|
@@ -142,27 +167,127 @@ var listCommand = new import_commander.Command().name("list").description("List
|
|
|
142
167
|
const dir = findWebhooksDir(cwd);
|
|
143
168
|
const files = listWebhookFiles(dir);
|
|
144
169
|
if (!files.length) {
|
|
145
|
-
console.log("No webhook definitions found in .webhooks");
|
|
170
|
+
console.log("\u{1F4ED} No webhook definitions found in .webhooks directory.");
|
|
171
|
+
console.log("\u{1F4A1} Create webhook files or download templates with:");
|
|
172
|
+
console.log(" better-webhook webhooks download --all");
|
|
146
173
|
return;
|
|
147
174
|
}
|
|
148
|
-
|
|
175
|
+
console.log("Available webhook definitions:");
|
|
176
|
+
files.forEach((f) => console.log(` \u2022 ${(0, import_path2.basename)(f, ".json")}`));
|
|
149
177
|
});
|
|
150
|
-
var runCommand = new import_commander.Command().name("run").argument(
|
|
178
|
+
var runCommand = new import_commander.Command().name("run").argument(
|
|
179
|
+
"[nameOrPath]",
|
|
180
|
+
"Webhook name (in .webhooks) or path to JSON file (optional - will prompt if not provided)"
|
|
181
|
+
).description(
|
|
151
182
|
"Run a webhook by name (in .webhooks) or by providing a path to a JSON file"
|
|
152
|
-
).option("-u, --url <url>", "Override destination URL").option("-m, --method <method>", "Override HTTP method").action(async (nameOrPath, options) => {
|
|
183
|
+
).option("-u, --url <url>", "Override destination URL").option("-m, --method <method>", "Override HTTP method").action(async (nameOrPath, options = {}) => {
|
|
153
184
|
const cwd = process.cwd();
|
|
185
|
+
const webhooksDir = findWebhooksDir(cwd);
|
|
186
|
+
let selectedNameOrPath = nameOrPath;
|
|
187
|
+
if (!selectedNameOrPath) {
|
|
188
|
+
const spinner = (0, import_ora.default)("Loading available webhooks...").start();
|
|
189
|
+
const localFiles = listWebhookFiles(webhooksDir);
|
|
190
|
+
const templates = Object.keys(TEMPLATES);
|
|
191
|
+
spinner.stop();
|
|
192
|
+
if (localFiles.length === 0 && templates.length === 0) {
|
|
193
|
+
console.log("\u{1F4ED} No webhook definitions found.");
|
|
194
|
+
console.log(
|
|
195
|
+
"\u{1F4A1} Create webhook files in .webhooks directory or download templates with:"
|
|
196
|
+
);
|
|
197
|
+
console.log(" better-webhook webhooks download --all");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const choices = [];
|
|
201
|
+
if (localFiles.length > 0) {
|
|
202
|
+
choices.push(
|
|
203
|
+
{
|
|
204
|
+
title: "--- Local Webhooks (.webhooks) ---",
|
|
205
|
+
description: "",
|
|
206
|
+
value: "",
|
|
207
|
+
type: "separator"
|
|
208
|
+
},
|
|
209
|
+
...localFiles.map((file) => ({
|
|
210
|
+
title: (0, import_path2.basename)(file, ".json"),
|
|
211
|
+
description: `Local file: ${file}`,
|
|
212
|
+
value: (0, import_path2.basename)(file, ".json"),
|
|
213
|
+
type: "local"
|
|
214
|
+
}))
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
if (templates.length > 0) {
|
|
218
|
+
choices.push(
|
|
219
|
+
{
|
|
220
|
+
title: "--- Available Templates ---",
|
|
221
|
+
description: "",
|
|
222
|
+
value: "",
|
|
223
|
+
type: "separator"
|
|
224
|
+
},
|
|
225
|
+
...templates.map((template) => ({
|
|
226
|
+
title: template,
|
|
227
|
+
description: `Template: ${TEMPLATES[template]}`,
|
|
228
|
+
value: template,
|
|
229
|
+
type: "template"
|
|
230
|
+
}))
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
const response = await (0, import_prompts.default)({
|
|
234
|
+
type: "select",
|
|
235
|
+
name: "webhook",
|
|
236
|
+
message: "Select a webhook to run:",
|
|
237
|
+
choices: choices.filter((choice) => choice.value !== ""),
|
|
238
|
+
// Remove separators for selection
|
|
239
|
+
initial: 0
|
|
240
|
+
});
|
|
241
|
+
if (!response.webhook) {
|
|
242
|
+
console.log("\u274C No webhook selected. Exiting.");
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
selectedNameOrPath = response.webhook;
|
|
247
|
+
if (selectedNameOrPath && templates.includes(selectedNameOrPath)) {
|
|
248
|
+
const downloadSpinner = (0, import_ora.default)(
|
|
249
|
+
`Downloading template: ${selectedNameOrPath}...`
|
|
250
|
+
).start();
|
|
251
|
+
try {
|
|
252
|
+
const rel = TEMPLATES[selectedNameOrPath];
|
|
253
|
+
const rawUrl = `${TEMPLATE_REPO_BASE}/${rel}`;
|
|
254
|
+
const { statusCode, body } = await (0, import_undici2.request)(rawUrl);
|
|
255
|
+
if (statusCode !== 200) {
|
|
256
|
+
throw new Error(`HTTP ${statusCode}`);
|
|
257
|
+
}
|
|
258
|
+
const text = await body.text();
|
|
259
|
+
const json = JSON.parse(text);
|
|
260
|
+
validateWebhookJSON(json, rawUrl);
|
|
261
|
+
(0, import_fs3.mkdirSync)(webhooksDir, { recursive: true });
|
|
262
|
+
const fileName = (0, import_path2.basename)(rel);
|
|
263
|
+
const destPath = (0, import_path2.join)(webhooksDir, fileName);
|
|
264
|
+
(0, import_fs3.writeFileSync)(destPath, JSON.stringify(json, null, 2));
|
|
265
|
+
selectedNameOrPath = (0, import_path2.basename)(fileName, ".json");
|
|
266
|
+
downloadSpinner.succeed(`Downloaded template: ${selectedNameOrPath}`);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
downloadSpinner.fail(`Failed to download template: ${error.message}`);
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (!selectedNameOrPath) {
|
|
275
|
+
console.error("\u274C No webhook selected.");
|
|
276
|
+
process.exitCode = 1;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
154
279
|
let filePath;
|
|
155
|
-
if (
|
|
156
|
-
filePath = (0, import_path2.join)(
|
|
280
|
+
if (selectedNameOrPath.endsWith(".json") && !selectedNameOrPath.includes("/") && !selectedNameOrPath.startsWith(".")) {
|
|
281
|
+
filePath = (0, import_path2.join)(webhooksDir, selectedNameOrPath);
|
|
157
282
|
} else {
|
|
158
283
|
const candidate = (0, import_path2.join)(
|
|
159
|
-
|
|
160
|
-
|
|
284
|
+
webhooksDir,
|
|
285
|
+
selectedNameOrPath + (selectedNameOrPath.endsWith(".json") ? "" : ".json")
|
|
161
286
|
);
|
|
162
287
|
if (statExists(candidate)) {
|
|
163
288
|
filePath = candidate;
|
|
164
289
|
} else {
|
|
165
|
-
filePath = (0, import_path2.resolve)(cwd,
|
|
290
|
+
filePath = (0, import_path2.resolve)(cwd, selectedNameOrPath);
|
|
166
291
|
}
|
|
167
292
|
}
|
|
168
293
|
if (!statExists(filePath)) {
|
|
@@ -181,8 +306,12 @@ var runCommand = new import_commander.Command().name("run").argument("<nameOrPat
|
|
|
181
306
|
if (options.url) def = { ...def, url: options.url };
|
|
182
307
|
if (options.method)
|
|
183
308
|
def = { ...def, method: options.method.toUpperCase() };
|
|
309
|
+
const executeSpinner = (0, import_ora.default)(
|
|
310
|
+
`Executing webhook: ${(0, import_path2.basename)(filePath, ".json")}...`
|
|
311
|
+
).start();
|
|
184
312
|
try {
|
|
185
313
|
const result = await executeWebhook(def);
|
|
314
|
+
executeSpinner.succeed("Webhook executed successfully!");
|
|
186
315
|
console.log("Status:", result.status);
|
|
187
316
|
console.log("Headers:");
|
|
188
317
|
for (const [k, v] of Object.entries(result.headers)) {
|
|
@@ -196,7 +325,8 @@ var runCommand = new import_commander.Command().name("run").argument("<nameOrPat
|
|
|
196
325
|
console.log(result.bodyText);
|
|
197
326
|
}
|
|
198
327
|
} catch (err) {
|
|
199
|
-
|
|
328
|
+
executeSpinner.fail("Request failed");
|
|
329
|
+
console.error("Error:", err.message);
|
|
200
330
|
process.exitCode = 1;
|
|
201
331
|
}
|
|
202
332
|
});
|
|
@@ -211,7 +341,7 @@ var downloadCommand = new import_commander.Command().name("download").argument("
|
|
|
211
341
|
}
|
|
212
342
|
const cwd = process.cwd();
|
|
213
343
|
const dir = findWebhooksDir(cwd);
|
|
214
|
-
(0,
|
|
344
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
215
345
|
const toDownload = opts.all ? Object.keys(TEMPLATES) : name ? [name] : [];
|
|
216
346
|
if (!toDownload.length) {
|
|
217
347
|
console.log("Available templates:");
|
|
@@ -254,13 +384,13 @@ var downloadCommand = new import_commander.Command().name("download").argument("
|
|
|
254
384
|
}
|
|
255
385
|
const fileName = (0, import_path2.basename)(rel);
|
|
256
386
|
const destPath = (0, import_path2.join)(dir, fileName);
|
|
257
|
-
if ((0,
|
|
387
|
+
if ((0, import_fs3.existsSync)(destPath) && !opts.force) {
|
|
258
388
|
console.log(
|
|
259
389
|
`Skipping existing file ${fileName} (use --force to overwrite)`
|
|
260
390
|
);
|
|
261
391
|
continue;
|
|
262
392
|
}
|
|
263
|
-
(0,
|
|
393
|
+
(0, import_fs3.writeFileSync)(destPath, JSON.stringify(json, null, 2));
|
|
264
394
|
console.log(`Downloaded ${templateName} -> .webhooks/${fileName}`);
|
|
265
395
|
} catch (e) {
|
|
266
396
|
console.error(`Error downloading ${templateName}: ${e.message}`);
|
|
@@ -272,12 +402,12 @@ var webhooks = new import_commander.Command().name("webhooks").description("Mana
|
|
|
272
402
|
|
|
273
403
|
// src/commands/capture.ts
|
|
274
404
|
var import_commander2 = require("commander");
|
|
275
|
-
var
|
|
405
|
+
var import_fs6 = require("fs");
|
|
276
406
|
var import_path5 = require("path");
|
|
277
407
|
|
|
278
408
|
// src/capture.ts
|
|
279
409
|
var import_http2 = require("http");
|
|
280
|
-
var
|
|
410
|
+
var import_fs4 = require("fs");
|
|
281
411
|
var import_path3 = require("path");
|
|
282
412
|
var import_crypto = require("crypto");
|
|
283
413
|
var WebhookCaptureServer = class {
|
|
@@ -285,8 +415,8 @@ var WebhookCaptureServer = class {
|
|
|
285
415
|
capturesDir;
|
|
286
416
|
constructor(capturesDir) {
|
|
287
417
|
this.capturesDir = capturesDir;
|
|
288
|
-
if (!(0,
|
|
289
|
-
(0,
|
|
418
|
+
if (!(0, import_fs4.existsSync)(capturesDir)) {
|
|
419
|
+
(0, import_fs4.mkdirSync)(capturesDir, { recursive: true });
|
|
290
420
|
}
|
|
291
421
|
}
|
|
292
422
|
start(startPort = 3001, maxAttempts = 20) {
|
|
@@ -408,7 +538,7 @@ var WebhookCaptureServer = class {
|
|
|
408
538
|
const filename = `${timestamp.replace(/[:.]/g, "-")}_${id}.json`;
|
|
409
539
|
const filepath = (0, import_path3.join)(this.capturesDir, filename);
|
|
410
540
|
try {
|
|
411
|
-
(0,
|
|
541
|
+
(0, import_fs4.writeFileSync)(filepath, JSON.stringify(captured, null, 2));
|
|
412
542
|
console.log(
|
|
413
543
|
`\u{1F4E6} Captured ${req.method} ${urlParts.pathname} -> ${filename}`
|
|
414
544
|
);
|
|
@@ -440,7 +570,7 @@ var WebhookCaptureServer = class {
|
|
|
440
570
|
};
|
|
441
571
|
|
|
442
572
|
// src/replay.ts
|
|
443
|
-
var
|
|
573
|
+
var import_fs5 = require("fs");
|
|
444
574
|
var import_path4 = require("path");
|
|
445
575
|
var WebhookReplayer = class {
|
|
446
576
|
constructor(capturesDir) {
|
|
@@ -482,7 +612,7 @@ var WebhookReplayer = class {
|
|
|
482
612
|
filepath = (0, import_path4.join)(this.capturesDir, files[0] ?? "");
|
|
483
613
|
}
|
|
484
614
|
try {
|
|
485
|
-
const content = (0,
|
|
615
|
+
const content = (0, import_fs5.readFileSync)(filepath, "utf8");
|
|
486
616
|
return JSON.parse(content);
|
|
487
617
|
} catch (error) {
|
|
488
618
|
throw new Error(
|
|
@@ -626,9 +756,9 @@ var templateCommand = new import_commander2.Command().name("template").argument(
|
|
|
626
756
|
templateName = `captured_${date}_${pathPart}_${capture2.id}`;
|
|
627
757
|
}
|
|
628
758
|
const outputDir = options?.outputDir ? (0, import_path5.resolve)(cwd, options.outputDir) : findWebhooksDir(cwd);
|
|
629
|
-
(0,
|
|
759
|
+
(0, import_fs6.mkdirSync)(outputDir, { recursive: true });
|
|
630
760
|
const templatePath = (0, import_path5.join)(outputDir, `${templateName}.json`);
|
|
631
|
-
(0,
|
|
761
|
+
(0, import_fs6.writeFileSync)(templatePath, JSON.stringify(template, null, 2));
|
|
632
762
|
console.log(`\u2705 Template created: ${templatePath}`);
|
|
633
763
|
console.log(
|
|
634
764
|
`\u{1F504} Run it with: better-webhook webhooks run ${templateName}`
|
|
@@ -666,7 +796,15 @@ var capture = new import_commander2.Command().name("capture").description(
|
|
|
666
796
|
|
|
667
797
|
// src/commands/replay.ts
|
|
668
798
|
var import_commander3 = require("commander");
|
|
669
|
-
var
|
|
799
|
+
var import_prompts2 = __toESM(require("prompts"), 1);
|
|
800
|
+
var import_ora2 = __toESM(require("ora"), 1);
|
|
801
|
+
var replay = new import_commander3.Command().name("replay").argument(
|
|
802
|
+
"[captureId]",
|
|
803
|
+
"ID of the captured webhook to replay (optional - will prompt if not provided)"
|
|
804
|
+
).argument(
|
|
805
|
+
"[targetUrl]",
|
|
806
|
+
"Target URL to replay the webhook to (optional - will prompt if not provided)"
|
|
807
|
+
).description("Replay a captured webhook to a target URL").option("-m, --method <method>", "Override HTTP method").option(
|
|
670
808
|
"-H, --header <header>",
|
|
671
809
|
"Add custom header (format: key:value)",
|
|
672
810
|
(value, previous) => {
|
|
@@ -681,15 +819,77 @@ var replay = new import_commander3.Command().name("replay").argument("<captureId
|
|
|
681
819
|
},
|
|
682
820
|
[]
|
|
683
821
|
).action(
|
|
684
|
-
async (captureId, targetUrl, options) => {
|
|
822
|
+
async (captureId, targetUrl, options = {}) => {
|
|
685
823
|
const cwd = process.cwd();
|
|
686
824
|
const capturesDir = findCapturesDir(cwd);
|
|
687
825
|
const replayer = new WebhookReplayer(capturesDir);
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
826
|
+
const spinner = (0, import_ora2.default)("Loading captured webhooks...").start();
|
|
827
|
+
const captured = replayer.listCaptured();
|
|
828
|
+
spinner.stop();
|
|
829
|
+
if (captured.length === 0) {
|
|
830
|
+
console.log("\u{1F4ED} No captured webhooks found.");
|
|
831
|
+
console.log(
|
|
832
|
+
"\u{1F4A1} Run 'better-webhook capture' to start capturing webhooks first."
|
|
833
|
+
);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
let selectedCaptureId = captureId;
|
|
837
|
+
let selectedTargetUrl = targetUrl;
|
|
838
|
+
if (!selectedCaptureId) {
|
|
839
|
+
const choices = captured.map((c) => {
|
|
840
|
+
const date = new Date(c.capture.timestamp).toLocaleString();
|
|
841
|
+
const bodySize = c.capture.rawBody?.length ?? 0;
|
|
842
|
+
return {
|
|
843
|
+
title: `${c.capture.id} - ${c.capture.method} ${c.capture.url}`,
|
|
844
|
+
description: `${date} | Body: ${bodySize} bytes`,
|
|
845
|
+
value: c.capture.id
|
|
846
|
+
};
|
|
847
|
+
});
|
|
848
|
+
const response = await (0, import_prompts2.default)({
|
|
849
|
+
type: "select",
|
|
850
|
+
name: "captureId",
|
|
851
|
+
message: "Select a captured webhook to replay:",
|
|
852
|
+
choices,
|
|
853
|
+
initial: 0
|
|
854
|
+
});
|
|
855
|
+
if (!response.captureId) {
|
|
856
|
+
console.log("\u274C No webhook selected. Exiting.");
|
|
857
|
+
process.exitCode = 1;
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
selectedCaptureId = response.captureId;
|
|
861
|
+
}
|
|
862
|
+
if (!selectedTargetUrl) {
|
|
863
|
+
const response = await (0, import_prompts2.default)({
|
|
864
|
+
type: "text",
|
|
865
|
+
name: "targetUrl",
|
|
866
|
+
message: "Enter the target URL to replay to:",
|
|
867
|
+
initial: "http://localhost:3000/webhook",
|
|
868
|
+
validate: (value) => {
|
|
869
|
+
try {
|
|
870
|
+
new URL(value);
|
|
871
|
+
return true;
|
|
872
|
+
} catch {
|
|
873
|
+
return "Please enter a valid URL";
|
|
874
|
+
}
|
|
875
|
+
}
|
|
692
876
|
});
|
|
877
|
+
if (!response.targetUrl) {
|
|
878
|
+
console.log("\u274C No target URL provided. Exiting.");
|
|
879
|
+
process.exitCode = 1;
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
selectedTargetUrl = response.targetUrl;
|
|
883
|
+
}
|
|
884
|
+
try {
|
|
885
|
+
const result = await replayer.replay(
|
|
886
|
+
selectedCaptureId,
|
|
887
|
+
selectedTargetUrl,
|
|
888
|
+
{
|
|
889
|
+
method: options.method,
|
|
890
|
+
headers: options.header
|
|
891
|
+
}
|
|
892
|
+
);
|
|
693
893
|
console.log("\u2705 Replay completed successfully!");
|
|
694
894
|
console.log("Status:", result.status);
|
|
695
895
|
console.log("Headers:");
|
package/dist/index.js
CHANGED
|
@@ -9,30 +9,8 @@ import { join as join2, resolve as resolve2, basename } from "path";
|
|
|
9
9
|
import { statSync as statSync2 } from "fs";
|
|
10
10
|
|
|
11
11
|
// src/utils/index.ts
|
|
12
|
-
import { readdirSync, statSync } from "fs";
|
|
12
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
13
13
|
import { join, resolve, extname } from "path";
|
|
14
|
-
function findWebhooksDir(cwd) {
|
|
15
|
-
return resolve(cwd, ".webhooks");
|
|
16
|
-
}
|
|
17
|
-
function listWebhookFiles(dir) {
|
|
18
|
-
return listJsonFiles(dir);
|
|
19
|
-
}
|
|
20
|
-
function findCapturesDir(cwd) {
|
|
21
|
-
return resolve(cwd, ".webhook-captures");
|
|
22
|
-
}
|
|
23
|
-
function listJsonFiles(dir) {
|
|
24
|
-
try {
|
|
25
|
-
const entries = readdirSync(dir);
|
|
26
|
-
return entries.filter(
|
|
27
|
-
(e) => statSync(join(dir, e)).isFile() && extname(e) === ".json"
|
|
28
|
-
);
|
|
29
|
-
} catch {
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// src/loader.ts
|
|
35
|
-
import { readFileSync } from "fs";
|
|
36
14
|
|
|
37
15
|
// src/schema.ts
|
|
38
16
|
import { z } from "zod";
|
|
@@ -68,24 +46,7 @@ ${issues}`);
|
|
|
68
46
|
return parsed.data;
|
|
69
47
|
}
|
|
70
48
|
|
|
71
|
-
// src/
|
|
72
|
-
function loadWebhookFile(path) {
|
|
73
|
-
let rawContent;
|
|
74
|
-
try {
|
|
75
|
-
rawContent = readFileSync(path, "utf8");
|
|
76
|
-
} catch (e) {
|
|
77
|
-
throw new Error(`Failed to read file ${path}: ${e.message}`);
|
|
78
|
-
}
|
|
79
|
-
let json;
|
|
80
|
-
try {
|
|
81
|
-
json = JSON.parse(rawContent);
|
|
82
|
-
} catch (e) {
|
|
83
|
-
throw new Error(`Invalid JSON in file ${path}: ${e.message}`);
|
|
84
|
-
}
|
|
85
|
-
return validateWebhookJSON(json, path);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// src/http.ts
|
|
49
|
+
// src/utils/http.ts
|
|
89
50
|
import { request } from "undici";
|
|
90
51
|
async function executeWebhook(def) {
|
|
91
52
|
const headerMap = {};
|
|
@@ -121,13 +82,55 @@ async function executeWebhook(def) {
|
|
|
121
82
|
};
|
|
122
83
|
}
|
|
123
84
|
|
|
85
|
+
// src/utils/index.ts
|
|
86
|
+
function findWebhooksDir(cwd) {
|
|
87
|
+
return resolve(cwd, ".webhooks");
|
|
88
|
+
}
|
|
89
|
+
function listWebhookFiles(dir) {
|
|
90
|
+
return listJsonFiles(dir);
|
|
91
|
+
}
|
|
92
|
+
function findCapturesDir(cwd) {
|
|
93
|
+
return resolve(cwd, ".webhook-captures");
|
|
94
|
+
}
|
|
95
|
+
function listJsonFiles(dir) {
|
|
96
|
+
try {
|
|
97
|
+
const entries = readdirSync(dir);
|
|
98
|
+
return entries.filter(
|
|
99
|
+
(e) => statSync(join(dir, e)).isFile() && extname(e) === ".json"
|
|
100
|
+
);
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function loadWebhookFile(path) {
|
|
106
|
+
let rawContent;
|
|
107
|
+
try {
|
|
108
|
+
rawContent = readFileSync(path, "utf8");
|
|
109
|
+
} catch (e) {
|
|
110
|
+
throw new Error(`Failed to read file ${path}: ${e.message}`);
|
|
111
|
+
}
|
|
112
|
+
let json;
|
|
113
|
+
try {
|
|
114
|
+
json = JSON.parse(rawContent);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
throw new Error(`Invalid JSON in file ${path}: ${e.message}`);
|
|
117
|
+
}
|
|
118
|
+
return validateWebhookJSON(json, path);
|
|
119
|
+
}
|
|
120
|
+
|
|
124
121
|
// src/commands/webhooks.ts
|
|
125
122
|
import { mkdirSync, writeFileSync, existsSync } from "fs";
|
|
126
123
|
import { request as request2 } from "undici";
|
|
124
|
+
|
|
125
|
+
// src/config.ts
|
|
127
126
|
var TEMPLATE_REPO_BASE = "https://raw.githubusercontent.com/endalk200/better-webhook/main";
|
|
128
127
|
var TEMPLATES = {
|
|
129
128
|
"stripe-invoice.payment_succeeded": "templates/stripe-invoice.payment_succeeded.json"
|
|
130
129
|
};
|
|
130
|
+
|
|
131
|
+
// src/commands/webhooks.ts
|
|
132
|
+
import prompts from "prompts";
|
|
133
|
+
import ora from "ora";
|
|
131
134
|
function statExists(p) {
|
|
132
135
|
try {
|
|
133
136
|
statSync2(p);
|
|
@@ -141,27 +144,127 @@ var listCommand = new Command().name("list").description("List available webhook
|
|
|
141
144
|
const dir = findWebhooksDir(cwd);
|
|
142
145
|
const files = listWebhookFiles(dir);
|
|
143
146
|
if (!files.length) {
|
|
144
|
-
console.log("No webhook definitions found in .webhooks");
|
|
147
|
+
console.log("\u{1F4ED} No webhook definitions found in .webhooks directory.");
|
|
148
|
+
console.log("\u{1F4A1} Create webhook files or download templates with:");
|
|
149
|
+
console.log(" better-webhook webhooks download --all");
|
|
145
150
|
return;
|
|
146
151
|
}
|
|
147
|
-
|
|
152
|
+
console.log("Available webhook definitions:");
|
|
153
|
+
files.forEach((f) => console.log(` \u2022 ${basename(f, ".json")}`));
|
|
148
154
|
});
|
|
149
|
-
var runCommand = new Command().name("run").argument(
|
|
155
|
+
var runCommand = new Command().name("run").argument(
|
|
156
|
+
"[nameOrPath]",
|
|
157
|
+
"Webhook name (in .webhooks) or path to JSON file (optional - will prompt if not provided)"
|
|
158
|
+
).description(
|
|
150
159
|
"Run a webhook by name (in .webhooks) or by providing a path to a JSON file"
|
|
151
|
-
).option("-u, --url <url>", "Override destination URL").option("-m, --method <method>", "Override HTTP method").action(async (nameOrPath, options) => {
|
|
160
|
+
).option("-u, --url <url>", "Override destination URL").option("-m, --method <method>", "Override HTTP method").action(async (nameOrPath, options = {}) => {
|
|
152
161
|
const cwd = process.cwd();
|
|
162
|
+
const webhooksDir = findWebhooksDir(cwd);
|
|
163
|
+
let selectedNameOrPath = nameOrPath;
|
|
164
|
+
if (!selectedNameOrPath) {
|
|
165
|
+
const spinner = ora("Loading available webhooks...").start();
|
|
166
|
+
const localFiles = listWebhookFiles(webhooksDir);
|
|
167
|
+
const templates = Object.keys(TEMPLATES);
|
|
168
|
+
spinner.stop();
|
|
169
|
+
if (localFiles.length === 0 && templates.length === 0) {
|
|
170
|
+
console.log("\u{1F4ED} No webhook definitions found.");
|
|
171
|
+
console.log(
|
|
172
|
+
"\u{1F4A1} Create webhook files in .webhooks directory or download templates with:"
|
|
173
|
+
);
|
|
174
|
+
console.log(" better-webhook webhooks download --all");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const choices = [];
|
|
178
|
+
if (localFiles.length > 0) {
|
|
179
|
+
choices.push(
|
|
180
|
+
{
|
|
181
|
+
title: "--- Local Webhooks (.webhooks) ---",
|
|
182
|
+
description: "",
|
|
183
|
+
value: "",
|
|
184
|
+
type: "separator"
|
|
185
|
+
},
|
|
186
|
+
...localFiles.map((file) => ({
|
|
187
|
+
title: basename(file, ".json"),
|
|
188
|
+
description: `Local file: ${file}`,
|
|
189
|
+
value: basename(file, ".json"),
|
|
190
|
+
type: "local"
|
|
191
|
+
}))
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (templates.length > 0) {
|
|
195
|
+
choices.push(
|
|
196
|
+
{
|
|
197
|
+
title: "--- Available Templates ---",
|
|
198
|
+
description: "",
|
|
199
|
+
value: "",
|
|
200
|
+
type: "separator"
|
|
201
|
+
},
|
|
202
|
+
...templates.map((template) => ({
|
|
203
|
+
title: template,
|
|
204
|
+
description: `Template: ${TEMPLATES[template]}`,
|
|
205
|
+
value: template,
|
|
206
|
+
type: "template"
|
|
207
|
+
}))
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const response = await prompts({
|
|
211
|
+
type: "select",
|
|
212
|
+
name: "webhook",
|
|
213
|
+
message: "Select a webhook to run:",
|
|
214
|
+
choices: choices.filter((choice) => choice.value !== ""),
|
|
215
|
+
// Remove separators for selection
|
|
216
|
+
initial: 0
|
|
217
|
+
});
|
|
218
|
+
if (!response.webhook) {
|
|
219
|
+
console.log("\u274C No webhook selected. Exiting.");
|
|
220
|
+
process.exitCode = 1;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
selectedNameOrPath = response.webhook;
|
|
224
|
+
if (selectedNameOrPath && templates.includes(selectedNameOrPath)) {
|
|
225
|
+
const downloadSpinner = ora(
|
|
226
|
+
`Downloading template: ${selectedNameOrPath}...`
|
|
227
|
+
).start();
|
|
228
|
+
try {
|
|
229
|
+
const rel = TEMPLATES[selectedNameOrPath];
|
|
230
|
+
const rawUrl = `${TEMPLATE_REPO_BASE}/${rel}`;
|
|
231
|
+
const { statusCode, body } = await request2(rawUrl);
|
|
232
|
+
if (statusCode !== 200) {
|
|
233
|
+
throw new Error(`HTTP ${statusCode}`);
|
|
234
|
+
}
|
|
235
|
+
const text = await body.text();
|
|
236
|
+
const json = JSON.parse(text);
|
|
237
|
+
validateWebhookJSON(json, rawUrl);
|
|
238
|
+
mkdirSync(webhooksDir, { recursive: true });
|
|
239
|
+
const fileName = basename(rel);
|
|
240
|
+
const destPath = join2(webhooksDir, fileName);
|
|
241
|
+
writeFileSync(destPath, JSON.stringify(json, null, 2));
|
|
242
|
+
selectedNameOrPath = basename(fileName, ".json");
|
|
243
|
+
downloadSpinner.succeed(`Downloaded template: ${selectedNameOrPath}`);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
downloadSpinner.fail(`Failed to download template: ${error.message}`);
|
|
246
|
+
process.exitCode = 1;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!selectedNameOrPath) {
|
|
252
|
+
console.error("\u274C No webhook selected.");
|
|
253
|
+
process.exitCode = 1;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
153
256
|
let filePath;
|
|
154
|
-
if (
|
|
155
|
-
filePath = join2(
|
|
257
|
+
if (selectedNameOrPath.endsWith(".json") && !selectedNameOrPath.includes("/") && !selectedNameOrPath.startsWith(".")) {
|
|
258
|
+
filePath = join2(webhooksDir, selectedNameOrPath);
|
|
156
259
|
} else {
|
|
157
260
|
const candidate = join2(
|
|
158
|
-
|
|
159
|
-
|
|
261
|
+
webhooksDir,
|
|
262
|
+
selectedNameOrPath + (selectedNameOrPath.endsWith(".json") ? "" : ".json")
|
|
160
263
|
);
|
|
161
264
|
if (statExists(candidate)) {
|
|
162
265
|
filePath = candidate;
|
|
163
266
|
} else {
|
|
164
|
-
filePath = resolve2(cwd,
|
|
267
|
+
filePath = resolve2(cwd, selectedNameOrPath);
|
|
165
268
|
}
|
|
166
269
|
}
|
|
167
270
|
if (!statExists(filePath)) {
|
|
@@ -180,8 +283,12 @@ var runCommand = new Command().name("run").argument("<nameOrPath>", "Webhook nam
|
|
|
180
283
|
if (options.url) def = { ...def, url: options.url };
|
|
181
284
|
if (options.method)
|
|
182
285
|
def = { ...def, method: options.method.toUpperCase() };
|
|
286
|
+
const executeSpinner = ora(
|
|
287
|
+
`Executing webhook: ${basename(filePath, ".json")}...`
|
|
288
|
+
).start();
|
|
183
289
|
try {
|
|
184
290
|
const result = await executeWebhook(def);
|
|
291
|
+
executeSpinner.succeed("Webhook executed successfully!");
|
|
185
292
|
console.log("Status:", result.status);
|
|
186
293
|
console.log("Headers:");
|
|
187
294
|
for (const [k, v] of Object.entries(result.headers)) {
|
|
@@ -195,7 +302,8 @@ var runCommand = new Command().name("run").argument("<nameOrPath>", "Webhook nam
|
|
|
195
302
|
console.log(result.bodyText);
|
|
196
303
|
}
|
|
197
304
|
} catch (err) {
|
|
198
|
-
|
|
305
|
+
executeSpinner.fail("Request failed");
|
|
306
|
+
console.error("Error:", err.message);
|
|
199
307
|
process.exitCode = 1;
|
|
200
308
|
}
|
|
201
309
|
});
|
|
@@ -665,7 +773,15 @@ var capture = new Command2().name("capture").description(
|
|
|
665
773
|
|
|
666
774
|
// src/commands/replay.ts
|
|
667
775
|
import { Command as Command3 } from "commander";
|
|
668
|
-
|
|
776
|
+
import prompts2 from "prompts";
|
|
777
|
+
import ora2 from "ora";
|
|
778
|
+
var replay = new Command3().name("replay").argument(
|
|
779
|
+
"[captureId]",
|
|
780
|
+
"ID of the captured webhook to replay (optional - will prompt if not provided)"
|
|
781
|
+
).argument(
|
|
782
|
+
"[targetUrl]",
|
|
783
|
+
"Target URL to replay the webhook to (optional - will prompt if not provided)"
|
|
784
|
+
).description("Replay a captured webhook to a target URL").option("-m, --method <method>", "Override HTTP method").option(
|
|
669
785
|
"-H, --header <header>",
|
|
670
786
|
"Add custom header (format: key:value)",
|
|
671
787
|
(value, previous) => {
|
|
@@ -680,15 +796,77 @@ var replay = new Command3().name("replay").argument("<captureId>", "ID of the ca
|
|
|
680
796
|
},
|
|
681
797
|
[]
|
|
682
798
|
).action(
|
|
683
|
-
async (captureId, targetUrl, options) => {
|
|
799
|
+
async (captureId, targetUrl, options = {}) => {
|
|
684
800
|
const cwd = process.cwd();
|
|
685
801
|
const capturesDir = findCapturesDir(cwd);
|
|
686
802
|
const replayer = new WebhookReplayer(capturesDir);
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
803
|
+
const spinner = ora2("Loading captured webhooks...").start();
|
|
804
|
+
const captured = replayer.listCaptured();
|
|
805
|
+
spinner.stop();
|
|
806
|
+
if (captured.length === 0) {
|
|
807
|
+
console.log("\u{1F4ED} No captured webhooks found.");
|
|
808
|
+
console.log(
|
|
809
|
+
"\u{1F4A1} Run 'better-webhook capture' to start capturing webhooks first."
|
|
810
|
+
);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
let selectedCaptureId = captureId;
|
|
814
|
+
let selectedTargetUrl = targetUrl;
|
|
815
|
+
if (!selectedCaptureId) {
|
|
816
|
+
const choices = captured.map((c) => {
|
|
817
|
+
const date = new Date(c.capture.timestamp).toLocaleString();
|
|
818
|
+
const bodySize = c.capture.rawBody?.length ?? 0;
|
|
819
|
+
return {
|
|
820
|
+
title: `${c.capture.id} - ${c.capture.method} ${c.capture.url}`,
|
|
821
|
+
description: `${date} | Body: ${bodySize} bytes`,
|
|
822
|
+
value: c.capture.id
|
|
823
|
+
};
|
|
824
|
+
});
|
|
825
|
+
const response = await prompts2({
|
|
826
|
+
type: "select",
|
|
827
|
+
name: "captureId",
|
|
828
|
+
message: "Select a captured webhook to replay:",
|
|
829
|
+
choices,
|
|
830
|
+
initial: 0
|
|
691
831
|
});
|
|
832
|
+
if (!response.captureId) {
|
|
833
|
+
console.log("\u274C No webhook selected. Exiting.");
|
|
834
|
+
process.exitCode = 1;
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
selectedCaptureId = response.captureId;
|
|
838
|
+
}
|
|
839
|
+
if (!selectedTargetUrl) {
|
|
840
|
+
const response = await prompts2({
|
|
841
|
+
type: "text",
|
|
842
|
+
name: "targetUrl",
|
|
843
|
+
message: "Enter the target URL to replay to:",
|
|
844
|
+
initial: "http://localhost:3000/webhook",
|
|
845
|
+
validate: (value) => {
|
|
846
|
+
try {
|
|
847
|
+
new URL(value);
|
|
848
|
+
return true;
|
|
849
|
+
} catch {
|
|
850
|
+
return "Please enter a valid URL";
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
if (!response.targetUrl) {
|
|
855
|
+
console.log("\u274C No target URL provided. Exiting.");
|
|
856
|
+
process.exitCode = 1;
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
selectedTargetUrl = response.targetUrl;
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
const result = await replayer.replay(
|
|
863
|
+
selectedCaptureId,
|
|
864
|
+
selectedTargetUrl,
|
|
865
|
+
{
|
|
866
|
+
method: options.method,
|
|
867
|
+
headers: options.header
|
|
868
|
+
}
|
|
869
|
+
);
|
|
692
870
|
console.log("\u2705 Replay completed successfully!");
|
|
693
871
|
console.log("Status:", result.status);
|
|
694
872
|
console.log("Headers:");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-webhook/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "CLI for developing and replaying webhook payloads locally.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,18 +45,22 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"commander": "^14.0.1",
|
|
48
|
+
"ora": "^9.0.0",
|
|
49
|
+
"prompts": "^2.4.2",
|
|
48
50
|
"undici": "^7.16.0",
|
|
49
51
|
"zod": "^4.1.8"
|
|
50
52
|
},
|
|
51
53
|
"devDependencies": {
|
|
52
54
|
"@types/node": "^24.3.1",
|
|
55
|
+
"@types/prompts": "^2.4.9",
|
|
53
56
|
"tsup": "^8.5.0",
|
|
54
57
|
"tsx": "^4.19.2",
|
|
55
58
|
"typescript": "^5.6.3",
|
|
56
59
|
"@better-webhook/typescript-config": "0.0.0"
|
|
57
60
|
},
|
|
58
61
|
"scripts": {
|
|
59
|
-
"build": "tsup",
|
|
60
|
-
"dev": "tsup --watch"
|
|
62
|
+
"build": "tsup --format cjs,esm --dts",
|
|
63
|
+
"dev": "tsup --watch",
|
|
64
|
+
"lint": "tsc"
|
|
61
65
|
}
|
|
62
66
|
}
|