@better-webhook/cli 3.8.0 → 3.10.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.
Files changed (59) hide show
  1. package/LICENSE +0 -1
  2. package/dist/_binary_entry.js +29 -0
  3. package/dist/commands/capture.d.ts +2 -0
  4. package/dist/commands/capture.js +33 -0
  5. package/dist/commands/captures.d.ts +2 -0
  6. package/dist/commands/captures.js +316 -0
  7. package/dist/commands/dashboard.d.ts +2 -0
  8. package/dist/commands/dashboard.js +70 -0
  9. package/dist/commands/index.d.ts +6 -0
  10. package/dist/commands/index.js +6 -0
  11. package/dist/commands/replay.d.ts +2 -0
  12. package/dist/commands/replay.js +140 -0
  13. package/dist/commands/run.d.ts +2 -0
  14. package/dist/commands/run.js +182 -0
  15. package/dist/commands/templates.d.ts +2 -0
  16. package/dist/commands/templates.js +285 -0
  17. package/dist/core/capture-server.d.ts +37 -0
  18. package/dist/core/capture-server.js +400 -0
  19. package/dist/core/capture-server.test.d.ts +1 -0
  20. package/dist/core/capture-server.test.js +86 -0
  21. package/dist/core/cli-version.d.ts +1 -0
  22. package/dist/core/cli-version.js +30 -0
  23. package/dist/core/cli-version.test.d.ts +1 -0
  24. package/dist/core/cli-version.test.js +42 -0
  25. package/dist/core/dashboard-api.d.ts +8 -0
  26. package/dist/core/dashboard-api.js +333 -0
  27. package/dist/core/dashboard-server.d.ts +24 -0
  28. package/dist/core/dashboard-server.js +224 -0
  29. package/dist/core/debug-output.d.ts +3 -0
  30. package/dist/core/debug-output.js +69 -0
  31. package/dist/core/debug-verify.d.ts +25 -0
  32. package/dist/core/debug-verify.js +253 -0
  33. package/dist/core/executor.d.ts +11 -0
  34. package/dist/core/executor.js +152 -0
  35. package/dist/core/index.d.ts +5 -0
  36. package/dist/core/index.js +5 -0
  37. package/dist/core/replay-engine.d.ts +20 -0
  38. package/dist/core/replay-engine.js +293 -0
  39. package/dist/core/replay-engine.test.d.ts +1 -0
  40. package/dist/core/replay-engine.test.js +482 -0
  41. package/dist/core/runtime-paths.d.ts +2 -0
  42. package/dist/core/runtime-paths.js +65 -0
  43. package/dist/core/runtime-paths.test.d.ts +1 -0
  44. package/dist/core/runtime-paths.test.js +50 -0
  45. package/dist/core/signature.d.ts +25 -0
  46. package/dist/core/signature.js +224 -0
  47. package/dist/core/signature.test.d.ts +1 -0
  48. package/dist/core/signature.test.js +38 -0
  49. package/dist/core/template-manager.d.ts +33 -0
  50. package/dist/core/template-manager.js +313 -0
  51. package/dist/core/template-manager.test.d.ts +1 -0
  52. package/dist/core/template-manager.test.js +236 -0
  53. package/dist/index.cjs +3472 -262
  54. package/dist/index.d.cts +2 -1
  55. package/dist/index.d.ts +2 -1
  56. package/dist/index.js +3509 -276
  57. package/dist/types/index.d.ts +312 -0
  58. package/dist/types/index.js +87 -0
  59. package/package.json +1 -1
@@ -0,0 +1,482 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { existsSync, rmSync, mkdirSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { ReplayEngine } from "./replay-engine.js";
6
+ describe("ReplayEngine", () => {
7
+ let tempDir;
8
+ let engine;
9
+ beforeEach(() => {
10
+ tempDir = join(tmpdir(), `better-webhook-replay-test-${Date.now()}`);
11
+ mkdirSync(tempDir, { recursive: true });
12
+ engine = new ReplayEngine(tempDir);
13
+ });
14
+ afterEach(() => {
15
+ if (existsSync(tempDir)) {
16
+ rmSync(tempDir, { recursive: true, force: true });
17
+ }
18
+ });
19
+ function createCapture(overrides = {}) {
20
+ return {
21
+ id: `capture-${Date.now()}`,
22
+ timestamp: new Date().toISOString(),
23
+ method: "POST",
24
+ url: "http://localhost:3001/webhooks/test",
25
+ path: "/webhooks/test",
26
+ headers: {},
27
+ body: {},
28
+ rawBody: "{}",
29
+ query: {},
30
+ ...overrides,
31
+ };
32
+ }
33
+ function saveCapture(capture) {
34
+ const fileName = `${capture.timestamp.replace(/[:.]/g, "-")}-${capture.id}.json`;
35
+ const filePath = join(tempDir, fileName);
36
+ writeFileSync(filePath, JSON.stringify(capture, null, 2));
37
+ return fileName;
38
+ }
39
+ describe("captureToTemplate with event detection", () => {
40
+ describe("GitHub event detection", () => {
41
+ it("should detect event from x-github-event header", () => {
42
+ const capture = createCapture({
43
+ provider: "github",
44
+ headers: {
45
+ "x-github-event": "push",
46
+ "x-github-delivery": "abc123",
47
+ },
48
+ body: { ref: "refs/heads/main" },
49
+ });
50
+ saveCapture(capture);
51
+ const template = engine.captureToTemplate(capture.id);
52
+ expect(template.event).toBe("push");
53
+ expect(template.provider).toBe("github");
54
+ });
55
+ it("should handle array header value for github event", () => {
56
+ const capture = createCapture({
57
+ provider: "github",
58
+ headers: {
59
+ "x-github-event": ["pull_request", "extra"],
60
+ },
61
+ });
62
+ saveCapture(capture);
63
+ const template = engine.captureToTemplate(capture.id);
64
+ expect(template.event).toBe("pull_request");
65
+ });
66
+ });
67
+ describe("Stripe event detection", () => {
68
+ it("should detect event from body type field", () => {
69
+ const capture = createCapture({
70
+ provider: "stripe",
71
+ body: {
72
+ id: "evt_123",
73
+ type: "payment_intent.succeeded",
74
+ data: { object: {} },
75
+ },
76
+ });
77
+ saveCapture(capture);
78
+ const template = engine.captureToTemplate(capture.id);
79
+ expect(template.event).toBe("payment_intent.succeeded");
80
+ expect(template.provider).toBe("stripe");
81
+ });
82
+ });
83
+ describe("Slack event detection", () => {
84
+ it("should detect event from body type field", () => {
85
+ const capture = createCapture({
86
+ provider: "slack",
87
+ body: {
88
+ type: "url_verification",
89
+ challenge: "abc123",
90
+ },
91
+ });
92
+ saveCapture(capture);
93
+ const template = engine.captureToTemplate(capture.id);
94
+ expect(template.event).toBe("url_verification");
95
+ });
96
+ it("should prefer body.type over nested event.type for Slack", () => {
97
+ const capture = createCapture({
98
+ provider: "slack",
99
+ body: {
100
+ type: "event_callback",
101
+ event: {
102
+ type: "message",
103
+ text: "Hello",
104
+ },
105
+ },
106
+ });
107
+ saveCapture(capture);
108
+ const template = engine.captureToTemplate(capture.id);
109
+ expect(template.event).toBe("event_callback");
110
+ });
111
+ it("should fallback to nested event.type when body.type is missing", () => {
112
+ const capture = createCapture({
113
+ provider: "slack",
114
+ body: {
115
+ event: {
116
+ type: "message",
117
+ text: "Hello",
118
+ },
119
+ },
120
+ });
121
+ saveCapture(capture);
122
+ const template = engine.captureToTemplate(capture.id);
123
+ expect(template.event).toBe("message");
124
+ });
125
+ });
126
+ describe("Linear event detection", () => {
127
+ it("should detect event from body type field", () => {
128
+ const capture = createCapture({
129
+ provider: "linear",
130
+ body: {
131
+ type: "Issue",
132
+ action: "create",
133
+ data: {},
134
+ },
135
+ });
136
+ saveCapture(capture);
137
+ const template = engine.captureToTemplate(capture.id);
138
+ expect(template.event).toBe("Issue");
139
+ });
140
+ });
141
+ describe("Clerk event detection", () => {
142
+ it("should detect event from body type field", () => {
143
+ const capture = createCapture({
144
+ provider: "clerk",
145
+ body: {
146
+ type: "user.created",
147
+ data: { id: "user_123" },
148
+ },
149
+ });
150
+ saveCapture(capture);
151
+ const template = engine.captureToTemplate(capture.id);
152
+ expect(template.event).toBe("user.created");
153
+ });
154
+ });
155
+ describe("Ragie event detection", () => {
156
+ it("should detect event from body event_type field", () => {
157
+ const capture = createCapture({
158
+ provider: "ragie",
159
+ body: {
160
+ event_type: "document_status_updated",
161
+ payload: {},
162
+ },
163
+ });
164
+ saveCapture(capture);
165
+ const template = engine.captureToTemplate(capture.id);
166
+ expect(template.event).toBe("document_status_updated");
167
+ });
168
+ });
169
+ describe("Recall event detection", () => {
170
+ it("should detect event from body event field", () => {
171
+ const capture = createCapture({
172
+ provider: "recall",
173
+ body: {
174
+ event: "transcript.data",
175
+ data: {
176
+ data: {
177
+ words: [],
178
+ },
179
+ },
180
+ },
181
+ });
182
+ saveCapture(capture);
183
+ const template = engine.captureToTemplate(capture.id);
184
+ expect(template.event).toBe("transcript.data");
185
+ });
186
+ });
187
+ describe("Shopify event detection", () => {
188
+ it("should detect event from x-shopify-topic header", () => {
189
+ const capture = createCapture({
190
+ provider: "shopify",
191
+ headers: {
192
+ "x-shopify-topic": "orders/create",
193
+ "x-shopify-hmac-sha256": "abc123",
194
+ },
195
+ body: { id: 123, email: "test@example.com" },
196
+ });
197
+ saveCapture(capture);
198
+ const template = engine.captureToTemplate(capture.id);
199
+ expect(template.event).toBe("orders/create");
200
+ expect(template.provider).toBe("shopify");
201
+ });
202
+ it("should handle array header value for shopify topic", () => {
203
+ const capture = createCapture({
204
+ provider: "shopify",
205
+ headers: {
206
+ "x-shopify-topic": ["products/update", "extra"],
207
+ },
208
+ });
209
+ saveCapture(capture);
210
+ const template = engine.captureToTemplate(capture.id);
211
+ expect(template.event).toBe("products/update");
212
+ });
213
+ });
214
+ describe("SendGrid event detection", () => {
215
+ it("should detect event from first array item event field", () => {
216
+ const capture = createCapture({
217
+ provider: "sendgrid",
218
+ body: [
219
+ { event: "delivered", email: "test@example.com" },
220
+ { event: "open", email: "test@example.com" },
221
+ ],
222
+ });
223
+ saveCapture(capture);
224
+ const template = engine.captureToTemplate(capture.id);
225
+ expect(template.event).toBe("delivered");
226
+ });
227
+ it("should return undefined for empty SendGrid array", () => {
228
+ const capture = createCapture({
229
+ provider: "sendgrid",
230
+ body: [],
231
+ });
232
+ saveCapture(capture);
233
+ const template = engine.captureToTemplate(capture.id);
234
+ expect(template.event).toBeUndefined();
235
+ });
236
+ });
237
+ describe("Discord event detection", () => {
238
+ it("should detect event from body type number", () => {
239
+ const capture = createCapture({
240
+ provider: "discord",
241
+ body: {
242
+ type: 1,
243
+ id: "123",
244
+ },
245
+ });
246
+ saveCapture(capture);
247
+ const template = engine.captureToTemplate(capture.id);
248
+ expect(template.event).toBe("type_1");
249
+ });
250
+ });
251
+ describe("custom event override", () => {
252
+ it("should use provided event over detected event", () => {
253
+ const capture = createCapture({
254
+ provider: "github",
255
+ headers: {
256
+ "x-github-event": "push",
257
+ },
258
+ });
259
+ saveCapture(capture);
260
+ const template = engine.captureToTemplate(capture.id, {
261
+ event: "custom_event",
262
+ });
263
+ expect(template.event).toBe("custom_event");
264
+ });
265
+ });
266
+ describe("unknown provider", () => {
267
+ it("should return undefined event when provider not recognized and no common fields", () => {
268
+ const capture = createCapture({
269
+ provider: "custom",
270
+ body: { some: "data" },
271
+ });
272
+ saveCapture(capture);
273
+ const template = engine.captureToTemplate(capture.id);
274
+ expect(template.event).toBeUndefined();
275
+ });
276
+ it("should detect event from body.type when no provider set (fallback)", () => {
277
+ const capture = createCapture({
278
+ body: { type: "some_event" },
279
+ });
280
+ saveCapture(capture);
281
+ const template = engine.captureToTemplate(capture.id);
282
+ expect(template.event).toBe("some_event");
283
+ });
284
+ it("should return undefined event when no detectable event field", () => {
285
+ const capture = createCapture({
286
+ body: { data: "some_data" },
287
+ });
288
+ saveCapture(capture);
289
+ const template = engine.captureToTemplate(capture.id);
290
+ expect(template.event).toBeUndefined();
291
+ });
292
+ });
293
+ describe("fallback event detection for unknown providers", () => {
294
+ it("should detect event from body.type for unknown provider", () => {
295
+ const capture = createCapture({
296
+ provider: "custom",
297
+ body: { type: "custom.event.created" },
298
+ });
299
+ saveCapture(capture);
300
+ const template = engine.captureToTemplate(capture.id);
301
+ expect(template.event).toBe("custom.event.created");
302
+ });
303
+ it("should detect event from body.event_type for unknown provider", () => {
304
+ const capture = createCapture({
305
+ provider: "custom",
306
+ body: { event_type: "order_placed" },
307
+ });
308
+ saveCapture(capture);
309
+ const template = engine.captureToTemplate(capture.id);
310
+ expect(template.event).toBe("order_placed");
311
+ });
312
+ it("should detect event from body.event for unknown provider", () => {
313
+ const capture = createCapture({
314
+ provider: "custom",
315
+ body: { event: "user.signup" },
316
+ });
317
+ saveCapture(capture);
318
+ const template = engine.captureToTemplate(capture.id);
319
+ expect(template.event).toBe("user.signup");
320
+ });
321
+ it("should detect event from body.action for unknown provider", () => {
322
+ const capture = createCapture({
323
+ provider: "custom",
324
+ body: { action: "completed" },
325
+ });
326
+ saveCapture(capture);
327
+ const template = engine.captureToTemplate(capture.id);
328
+ expect(template.event).toBe("completed");
329
+ });
330
+ it("should prioritize type > event_type > event > action for unknown provider", () => {
331
+ const capture = createCapture({
332
+ provider: "custom",
333
+ body: {
334
+ type: "primary_type",
335
+ event_type: "secondary_type",
336
+ event: "tertiary_type",
337
+ action: "quaternary_type",
338
+ },
339
+ });
340
+ saveCapture(capture);
341
+ const template = engine.captureToTemplate(capture.id);
342
+ expect(template.event).toBe("primary_type");
343
+ });
344
+ it("should fallback to event_type when type is missing for unknown provider", () => {
345
+ const capture = createCapture({
346
+ provider: "custom",
347
+ body: {
348
+ event_type: "secondary_type",
349
+ event: "tertiary_type",
350
+ action: "quaternary_type",
351
+ },
352
+ });
353
+ saveCapture(capture);
354
+ const template = engine.captureToTemplate(capture.id);
355
+ expect(template.event).toBe("secondary_type");
356
+ });
357
+ it("should fallback to event when type and event_type are missing for unknown provider", () => {
358
+ const capture = createCapture({
359
+ provider: "custom",
360
+ body: {
361
+ event: "tertiary_type",
362
+ action: "quaternary_type",
363
+ },
364
+ });
365
+ saveCapture(capture);
366
+ const template = engine.captureToTemplate(capture.id);
367
+ expect(template.event).toBe("tertiary_type");
368
+ });
369
+ it("should fallback to action when type, event_type, and event are missing for unknown provider", () => {
370
+ const capture = createCapture({
371
+ provider: "custom",
372
+ body: {
373
+ action: "quaternary_type",
374
+ data: { some: "value" },
375
+ },
376
+ });
377
+ saveCapture(capture);
378
+ const template = engine.captureToTemplate(capture.id);
379
+ expect(template.event).toBe("quaternary_type");
380
+ });
381
+ it("should ignore non-string values in fallback fields for unknown provider", () => {
382
+ const capture = createCapture({
383
+ provider: "custom",
384
+ body: {
385
+ type: 123,
386
+ event_type: { nested: "object" },
387
+ event: ["array"],
388
+ action: "valid_string",
389
+ },
390
+ });
391
+ saveCapture(capture);
392
+ const template = engine.captureToTemplate(capture.id);
393
+ expect(template.event).toBe("valid_string");
394
+ });
395
+ it("should return undefined when all fallback fields are non-string for unknown provider", () => {
396
+ const capture = createCapture({
397
+ provider: "custom",
398
+ body: {
399
+ type: 123,
400
+ event_type: null,
401
+ event: undefined,
402
+ action: { not: "a string" },
403
+ },
404
+ });
405
+ saveCapture(capture);
406
+ const template = engine.captureToTemplate(capture.id);
407
+ expect(template.event).toBeUndefined();
408
+ });
409
+ it("should handle null body for unknown provider", () => {
410
+ const capture = createCapture({
411
+ provider: "custom",
412
+ body: null,
413
+ });
414
+ saveCapture(capture);
415
+ const template = engine.captureToTemplate(capture.id);
416
+ expect(template.event).toBeUndefined();
417
+ });
418
+ it("should use fallback for undefined provider", () => {
419
+ const capture = createCapture({
420
+ body: { event_type: "fallback_event" },
421
+ });
422
+ saveCapture(capture);
423
+ const template = engine.captureToTemplate(capture.id);
424
+ expect(template.event).toBe("fallback_event");
425
+ });
426
+ });
427
+ });
428
+ describe("captureToTemplate basic functionality", () => {
429
+ it("should convert capture to template", () => {
430
+ const capture = createCapture({
431
+ method: "POST",
432
+ path: "/webhooks/github",
433
+ headers: {
434
+ "content-type": "application/json",
435
+ "user-agent": "GitHub-Hookshot",
436
+ },
437
+ body: { action: "created" },
438
+ provider: "github",
439
+ });
440
+ saveCapture(capture);
441
+ const template = engine.captureToTemplate(capture.id);
442
+ expect(template.method).toBe("POST");
443
+ expect(template.url).toBe("http://localhost:3000/webhooks/github");
444
+ expect(template.body).toEqual({ action: "created" });
445
+ expect(template.provider).toBe("github");
446
+ });
447
+ it("should use custom URL when provided", () => {
448
+ const capture = createCapture({
449
+ path: "/webhooks/test",
450
+ });
451
+ saveCapture(capture);
452
+ const template = engine.captureToTemplate(capture.id, {
453
+ url: "https://example.com/webhook",
454
+ });
455
+ expect(template.url).toBe("https://example.com/webhook");
456
+ });
457
+ it("should filter out signature headers", () => {
458
+ const capture = createCapture({
459
+ headers: {
460
+ "content-type": "application/json",
461
+ "x-hub-signature-256": "sha256=abc123",
462
+ "stripe-signature": "t=123,v1=abc",
463
+ "x-shopify-hmac-sha256": "abc123",
464
+ "webhook-signature": "v1,abc123",
465
+ },
466
+ });
467
+ saveCapture(capture);
468
+ const template = engine.captureToTemplate(capture.id);
469
+ const headerKeys = template.headers.map((h) => h.key.toLowerCase());
470
+ expect(headerKeys).toContain("content-type");
471
+ expect(headerKeys).not.toContain("x-hub-signature-256");
472
+ expect(headerKeys).not.toContain("stripe-signature");
473
+ expect(headerKeys).not.toContain("x-shopify-hmac-sha256");
474
+ expect(headerKeys).not.toContain("webhook-signature");
475
+ });
476
+ it("should throw error when capture not found", () => {
477
+ expect(() => {
478
+ engine.captureToTemplate("non-existent-id");
479
+ }).toThrow("Capture not found");
480
+ });
481
+ });
482
+ });
@@ -0,0 +1,2 @@
1
+ export declare function findCliPackageRoot(startDir: string): string | undefined;
2
+ export declare function resolveRuntimeDir(): string;
@@ -0,0 +1,65 @@
1
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const CLI_PACKAGE_NAMES = new Set(["@better-webhook/cli", "better-webhook"]);
5
+ function resolveRealPath(filePath) {
6
+ try {
7
+ return realpathSync(filePath);
8
+ }
9
+ catch {
10
+ return filePath;
11
+ }
12
+ }
13
+ function readPackageName(packageJsonPath) {
14
+ if (!existsSync(packageJsonPath)) {
15
+ return undefined;
16
+ }
17
+ try {
18
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, { encoding: "utf8" }));
19
+ return typeof packageJson.name === "string" ? packageJson.name : undefined;
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ function isCliPackageName(packageName) {
26
+ return typeof packageName === "string" && CLI_PACKAGE_NAMES.has(packageName);
27
+ }
28
+ export function findCliPackageRoot(startDir) {
29
+ let currentDir = resolveRealPath(path.resolve(startDir));
30
+ while (true) {
31
+ const packageJsonPath = path.join(currentDir, "package.json");
32
+ const packageName = readPackageName(packageJsonPath);
33
+ if (isCliPackageName(packageName)) {
34
+ return currentDir;
35
+ }
36
+ const parentDir = path.dirname(currentDir);
37
+ if (parentDir === currentDir) {
38
+ return undefined;
39
+ }
40
+ currentDir = parentDir;
41
+ }
42
+ }
43
+ export function resolveRuntimeDir() {
44
+ if (typeof __dirname !== "undefined") {
45
+ return resolveRealPath(__dirname);
46
+ }
47
+ const moduleUrl = import.meta.url;
48
+ if (typeof moduleUrl === "string" && moduleUrl.startsWith("file:")) {
49
+ return path.dirname(resolveRealPath(fileURLToPath(moduleUrl)));
50
+ }
51
+ const entryPath = process.argv[1];
52
+ if (entryPath) {
53
+ const entryDir = path.dirname(resolveRealPath(path.resolve(entryPath)));
54
+ const cliPackageRoot = findCliPackageRoot(entryDir);
55
+ if (cliPackageRoot) {
56
+ return cliPackageRoot;
57
+ }
58
+ return entryDir;
59
+ }
60
+ const cwdPackageRoot = findCliPackageRoot(process.cwd());
61
+ if (cwdPackageRoot) {
62
+ return cwdPackageRoot;
63
+ }
64
+ return process.cwd();
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync, } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { findCliPackageRoot } from "./runtime-paths.js";
6
+ const tempDirs = [];
7
+ function createTempDir(prefix) {
8
+ const tempDir = mkdtempSync(path.join(tmpdir(), prefix));
9
+ tempDirs.push(tempDir);
10
+ return tempDir;
11
+ }
12
+ function writePackageJson(dirPath, metadata) {
13
+ mkdirSync(dirPath, { recursive: true });
14
+ writeFileSync(path.join(dirPath, "package.json"), JSON.stringify(metadata));
15
+ }
16
+ afterEach(() => {
17
+ for (const tempDir of tempDirs) {
18
+ rmSync(tempDir, { recursive: true, force: true });
19
+ }
20
+ tempDirs.length = 0;
21
+ });
22
+ describe("findCliPackageRoot", () => {
23
+ it("finds CLI package root from nested directories", () => {
24
+ const tempDir = createTempDir("better-webhook-runtime-paths-");
25
+ const cliRoot = path.join(tempDir, "node_modules", "@better-webhook", "cli");
26
+ const nestedDir = path.join(cliRoot, "dist", "core");
27
+ writePackageJson(cliRoot, {
28
+ name: "@better-webhook/cli",
29
+ version: "3.8.0",
30
+ });
31
+ mkdirSync(nestedDir, { recursive: true });
32
+ expect(findCliPackageRoot(nestedDir)).toBe(realpathSync(cliRoot));
33
+ });
34
+ it("returns undefined when no matching CLI package is found", () => {
35
+ const tempDir = createTempDir("better-webhook-runtime-paths-");
36
+ const appRoot = path.join(tempDir, "app");
37
+ const nestedDir = path.join(appRoot, "src", "core");
38
+ writePackageJson(appRoot, { name: "consumer-app", version: "1.0.0" });
39
+ mkdirSync(nestedDir, { recursive: true });
40
+ expect(findCliPackageRoot(nestedDir)).toBeUndefined();
41
+ });
42
+ it("supports legacy better-webhook package name", () => {
43
+ const tempDir = createTempDir("better-webhook-runtime-paths-");
44
+ const cliRoot = path.join(tempDir, "legacy-cli");
45
+ const nestedDir = path.join(cliRoot, "dist");
46
+ writePackageJson(cliRoot, { name: "better-webhook", version: "2.0.0" });
47
+ mkdirSync(nestedDir, { recursive: true });
48
+ expect(findCliPackageRoot(nestedDir)).toBe(realpathSync(cliRoot));
49
+ });
50
+ });
@@ -0,0 +1,25 @@
1
+ import type { WebhookProvider, GeneratedSignature, HeaderEntry } from "../types/index.js";
2
+ export declare function generateStripeSignature(payload: string, secret: string, timestamp?: number): GeneratedSignature;
3
+ export declare function generateGitHubSignature(payload: string, secret: string): GeneratedSignature;
4
+ export declare function generateShopifySignature(payload: string, secret: string): GeneratedSignature;
5
+ export declare function generateTwilioSignature(payload: string, secret: string, url: string): GeneratedSignature;
6
+ export declare function generateSlackSignature(payload: string, secret: string, timestamp?: number): GeneratedSignature;
7
+ export declare function generateLinearSignature(payload: string, secret: string): GeneratedSignature;
8
+ export declare function generateClerkSignature(payload: string, secret: string, timestamp?: number, webhookId?: string): GeneratedSignature;
9
+ export declare function generateSendGridSignature(payload: string, secret: string, timestamp?: number): GeneratedSignature;
10
+ export declare function generateRagieSignature(payload: string, secret: string): GeneratedSignature;
11
+ export declare function generateRecallSignature(payload: string, secret: string, timestamp?: number, webhookId?: string): GeneratedSignature;
12
+ export declare function generateSignature(provider: WebhookProvider, payload: string, secret: string, options?: {
13
+ timestamp?: number;
14
+ url?: string;
15
+ webhookId?: string;
16
+ }): GeneratedSignature | null;
17
+ export declare function getProviderHeaders(provider: WebhookProvider, options?: {
18
+ timestamp?: number;
19
+ webhookId?: string;
20
+ event?: string;
21
+ }): HeaderEntry[];
22
+ export declare function verifySignature(provider: WebhookProvider, payload: string, signature: string, secret: string, options?: {
23
+ timestamp?: number;
24
+ url?: string;
25
+ }): boolean;