@better-webhook/cli 3.5.0 → 3.6.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 CHANGED
@@ -43,6 +43,37 @@ These providers are auto-detected from headers but signature generation is not y
43
43
 
44
44
  ## Installation
45
45
 
46
+ ### Standalone Binary (Recommended)
47
+
48
+ Download a standalone binary - no Node.js required:
49
+
50
+ **macOS (Homebrew)**
51
+
52
+ ```bash
53
+ brew install endalk200/tap/better-webhook
54
+ ```
55
+
56
+ **Manual Download**
57
+
58
+ Download the latest binary for your platform from [GitHub Releases](https://github.com/endalk200/better-webhook/releases):
59
+
60
+ | Platform | Download |
61
+ | ------------- | --------------------------------------------------------------------------------------------- |
62
+ | macOS (ARM) | [better-webhook-darwin-arm64](https://github.com/endalk200/better-webhook/releases/latest) |
63
+ | macOS (Intel) | [better-webhook-darwin-x64](https://github.com/endalk200/better-webhook/releases/latest) |
64
+ | Linux (x64) | [better-webhook-linux-x64](https://github.com/endalk200/better-webhook/releases/latest) |
65
+ | Linux (ARM) | [better-webhook-linux-arm64](https://github.com/endalk200/better-webhook/releases/latest) |
66
+ | Windows | [better-webhook-windows-x64.exe](https://github.com/endalk200/better-webhook/releases/latest) |
67
+
68
+ ```bash
69
+ # Example: macOS ARM
70
+ curl -L https://github.com/endalk200/better-webhook/releases/latest/download/better-webhook-darwin-arm64 -o better-webhook
71
+ chmod +x better-webhook
72
+ sudo mv better-webhook /usr/local/bin/
73
+ ```
74
+
75
+ ### NPM (Alternative)
76
+
46
77
  ```bash
47
78
  # NPM
48
79
  npm install -g @better-webhook/cli
package/dist/index.cjs CHANGED
@@ -2738,6 +2738,89 @@ function createDashboardApiRouter(options = {}) {
2738
2738
 
2739
2739
  // src/core/dashboard-server.ts
2740
2740
  var import_meta = {};
2741
+ function isStandaloneBinary() {
2742
+ if (typeof STANDALONE_BINARY !== "undefined" && STANDALONE_BINARY) {
2743
+ return true;
2744
+ }
2745
+ if (typeof globalThis.embeddedDashboardFiles !== "undefined" && globalThis.embeddedDashboardFiles) {
2746
+ return true;
2747
+ }
2748
+ return false;
2749
+ }
2750
+ function getMimeType(filePath) {
2751
+ const ext = import_path4.default.extname(filePath).toLowerCase();
2752
+ const mimeTypes = {
2753
+ ".html": "text/html; charset=utf-8",
2754
+ ".js": "application/javascript; charset=utf-8",
2755
+ ".css": "text/css; charset=utf-8",
2756
+ ".json": "application/json; charset=utf-8",
2757
+ ".png": "image/png",
2758
+ ".jpg": "image/jpeg",
2759
+ ".jpeg": "image/jpeg",
2760
+ ".gif": "image/gif",
2761
+ ".svg": "image/svg+xml",
2762
+ ".ico": "image/x-icon",
2763
+ ".woff": "font/woff",
2764
+ ".woff2": "font/woff2",
2765
+ ".ttf": "font/ttf",
2766
+ ".eot": "application/vnd.ms-fontobject"
2767
+ };
2768
+ return mimeTypes[ext] || "application/octet-stream";
2769
+ }
2770
+ function createEmbeddedDashboardMiddleware() {
2771
+ const filePathMap = /* @__PURE__ */ new Map();
2772
+ let indexHtmlPath = null;
2773
+ if (typeof globalThis.embeddedDashboardFiles !== "undefined") {
2774
+ for (const [key, filePath] of Object.entries(
2775
+ globalThis.embeddedDashboardFiles
2776
+ )) {
2777
+ const servePath = "/" + key.replace(/^dashboard\//, "");
2778
+ filePathMap.set(servePath, filePath);
2779
+ if (servePath === "/index.html") {
2780
+ indexHtmlPath = filePath;
2781
+ }
2782
+ }
2783
+ }
2784
+ const staticMiddleware = async (req, res, next) => {
2785
+ if (!Bun) {
2786
+ return next();
2787
+ }
2788
+ const requestPath = req.path === "/" ? "/index.html" : req.path;
2789
+ const filePath = filePathMap.get(requestPath);
2790
+ if (filePath) {
2791
+ try {
2792
+ const file = Bun.file(filePath);
2793
+ const content = await file.arrayBuffer();
2794
+ res.setHeader("Content-Type", getMimeType(requestPath));
2795
+ res.setHeader("Content-Length", content.byteLength);
2796
+ res.send(Buffer.from(content));
2797
+ } catch (err) {
2798
+ console.error(`Failed to serve embedded file ${requestPath}:`, err);
2799
+ next();
2800
+ }
2801
+ } else {
2802
+ next();
2803
+ }
2804
+ };
2805
+ const spaFallback = async (req, res, next) => {
2806
+ if (req.path.startsWith("/api") || req.path === "/health") {
2807
+ return next();
2808
+ }
2809
+ if (!Bun || !indexHtmlPath) {
2810
+ return next();
2811
+ }
2812
+ try {
2813
+ const file = Bun.file(indexHtmlPath);
2814
+ const content = await file.arrayBuffer();
2815
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
2816
+ res.setHeader("Content-Length", content.byteLength);
2817
+ res.send(Buffer.from(content));
2818
+ } catch {
2819
+ next();
2820
+ }
2821
+ };
2822
+ return { staticMiddleware, spaFallback };
2823
+ }
2741
2824
  function resolveDashboardDistDir(runtimeDir) {
2742
2825
  const candidates = [
2743
2826
  // Bundled CLI: dist/index.js -> dist/dashboard
@@ -2791,18 +2874,24 @@ async function startDashboardServer(options = {}) {
2791
2874
  );
2792
2875
  const host = options.host || "localhost";
2793
2876
  const port = options.port ?? 4e3;
2794
- const runtimeDir = typeof __dirname !== "undefined" ? (
2795
- // eslint-disable-next-line no-undef
2796
- __dirname
2797
- ) : import_path4.default.dirname((0, import_url.fileURLToPath)(import_meta.url));
2798
- const { distDir: dashboardDistDir, indexHtml: dashboardIndexHtml } = resolveDashboardDistDir(runtimeDir);
2799
- app.use(import_express2.default.static(dashboardDistDir));
2800
- app.get("*", (req, res, next) => {
2801
- if (req.path.startsWith("/api") || req.path === "/health") return next();
2802
- res.sendFile(dashboardIndexHtml, (err) => {
2803
- if (err) next();
2877
+ if (isStandaloneBinary()) {
2878
+ const { staticMiddleware, spaFallback } = createEmbeddedDashboardMiddleware();
2879
+ app.use(staticMiddleware);
2880
+ app.get("*", spaFallback);
2881
+ } else {
2882
+ const runtimeDir = typeof __dirname !== "undefined" ? (
2883
+ // eslint-disable-next-line no-undef
2884
+ __dirname
2885
+ ) : import_path4.default.dirname((0, import_url.fileURLToPath)(import_meta.url));
2886
+ const { distDir: dashboardDistDir, indexHtml: dashboardIndexHtml } = resolveDashboardDistDir(runtimeDir);
2887
+ app.use(import_express2.default.static(dashboardDistDir));
2888
+ app.get("*", (req, res, next) => {
2889
+ if (req.path.startsWith("/api") || req.path === "/health") return next();
2890
+ res.sendFile(dashboardIndexHtml, (err) => {
2891
+ if (err) next();
2892
+ });
2804
2893
  });
2805
- });
2894
+ }
2806
2895
  const server = (0, import_http2.createServer)(app);
2807
2896
  const wss = new import_ws2.WebSocketServer({ server, path: "/ws" });
2808
2897
  wss.on("connection", async (ws) => {
@@ -2929,14 +3018,24 @@ var dashboard = new import_commander6.Command().name("dashboard").description("S
2929
3018
 
2930
3019
  // src/index.ts
2931
3020
  var import_meta2 = {};
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
- );
3021
+ function getVersion() {
3022
+ if (typeof CLI_VERSION !== "undefined") {
3023
+ return CLI_VERSION;
3024
+ }
3025
+ try {
3026
+ const packageJsonPath = (0, import_node_url.fileURLToPath)(
3027
+ new URL("../package.json", import_meta2.url)
3028
+ );
3029
+ const packageJson = JSON.parse(
3030
+ (0, import_node_fs.readFileSync)(packageJsonPath, { encoding: "utf8" })
3031
+ );
3032
+ return packageJson.version;
3033
+ } catch {
3034
+ return "0.0.0-unknown";
3035
+ }
3036
+ }
2938
3037
  var program = new import_commander7.Command().name("better-webhook").description(
2939
3038
  "Modern CLI for developing, capturing, and replaying webhooks locally"
2940
- ).version(packageJson.version);
3039
+ ).version(getVersion());
2941
3040
  program.addCommand(templates).addCommand(run).addCommand(capture).addCommand(captures).addCommand(replay).addCommand(dashboard);
2942
3041
  program.parseAsync(process.argv);
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command as Command7 } from "commander";
5
- import { readFileSync as readFileSync4 } from "fs";
5
+ import { readFileSync as readFileSync5 } from "fs";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
 
8
8
  // src/commands/templates.ts
@@ -2731,6 +2731,89 @@ function createDashboardApiRouter(options = {}) {
2731
2731
  }
2732
2732
 
2733
2733
  // src/core/dashboard-server.ts
2734
+ function isStandaloneBinary() {
2735
+ if (typeof STANDALONE_BINARY !== "undefined" && STANDALONE_BINARY) {
2736
+ return true;
2737
+ }
2738
+ if (typeof globalThis.embeddedDashboardFiles !== "undefined" && globalThis.embeddedDashboardFiles) {
2739
+ return true;
2740
+ }
2741
+ return false;
2742
+ }
2743
+ function getMimeType(filePath) {
2744
+ const ext = path.extname(filePath).toLowerCase();
2745
+ const mimeTypes = {
2746
+ ".html": "text/html; charset=utf-8",
2747
+ ".js": "application/javascript; charset=utf-8",
2748
+ ".css": "text/css; charset=utf-8",
2749
+ ".json": "application/json; charset=utf-8",
2750
+ ".png": "image/png",
2751
+ ".jpg": "image/jpeg",
2752
+ ".jpeg": "image/jpeg",
2753
+ ".gif": "image/gif",
2754
+ ".svg": "image/svg+xml",
2755
+ ".ico": "image/x-icon",
2756
+ ".woff": "font/woff",
2757
+ ".woff2": "font/woff2",
2758
+ ".ttf": "font/ttf",
2759
+ ".eot": "application/vnd.ms-fontobject"
2760
+ };
2761
+ return mimeTypes[ext] || "application/octet-stream";
2762
+ }
2763
+ function createEmbeddedDashboardMiddleware() {
2764
+ const filePathMap = /* @__PURE__ */ new Map();
2765
+ let indexHtmlPath = null;
2766
+ if (typeof globalThis.embeddedDashboardFiles !== "undefined") {
2767
+ for (const [key, filePath] of Object.entries(
2768
+ globalThis.embeddedDashboardFiles
2769
+ )) {
2770
+ const servePath = "/" + key.replace(/^dashboard\//, "");
2771
+ filePathMap.set(servePath, filePath);
2772
+ if (servePath === "/index.html") {
2773
+ indexHtmlPath = filePath;
2774
+ }
2775
+ }
2776
+ }
2777
+ const staticMiddleware = async (req, res, next) => {
2778
+ if (!Bun) {
2779
+ return next();
2780
+ }
2781
+ const requestPath = req.path === "/" ? "/index.html" : req.path;
2782
+ const filePath = filePathMap.get(requestPath);
2783
+ if (filePath) {
2784
+ try {
2785
+ const file = Bun.file(filePath);
2786
+ const content = await file.arrayBuffer();
2787
+ res.setHeader("Content-Type", getMimeType(requestPath));
2788
+ res.setHeader("Content-Length", content.byteLength);
2789
+ res.send(Buffer.from(content));
2790
+ } catch (err) {
2791
+ console.error(`Failed to serve embedded file ${requestPath}:`, err);
2792
+ next();
2793
+ }
2794
+ } else {
2795
+ next();
2796
+ }
2797
+ };
2798
+ const spaFallback = async (req, res, next) => {
2799
+ if (req.path.startsWith("/api") || req.path === "/health") {
2800
+ return next();
2801
+ }
2802
+ if (!Bun || !indexHtmlPath) {
2803
+ return next();
2804
+ }
2805
+ try {
2806
+ const file = Bun.file(indexHtmlPath);
2807
+ const content = await file.arrayBuffer();
2808
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
2809
+ res.setHeader("Content-Length", content.byteLength);
2810
+ res.send(Buffer.from(content));
2811
+ } catch {
2812
+ next();
2813
+ }
2814
+ };
2815
+ return { staticMiddleware, spaFallback };
2816
+ }
2734
2817
  function resolveDashboardDistDir(runtimeDir) {
2735
2818
  const candidates = [
2736
2819
  // Bundled CLI: dist/index.js -> dist/dashboard
@@ -2784,18 +2867,24 @@ async function startDashboardServer(options = {}) {
2784
2867
  );
2785
2868
  const host = options.host || "localhost";
2786
2869
  const port = options.port ?? 4e3;
2787
- const runtimeDir = typeof __dirname !== "undefined" ? (
2788
- // eslint-disable-next-line no-undef
2789
- __dirname
2790
- ) : path.dirname(fileURLToPath(import.meta.url));
2791
- const { distDir: dashboardDistDir, indexHtml: dashboardIndexHtml } = resolveDashboardDistDir(runtimeDir);
2792
- app.use(express2.static(dashboardDistDir));
2793
- app.get("*", (req, res, next) => {
2794
- if (req.path.startsWith("/api") || req.path === "/health") return next();
2795
- res.sendFile(dashboardIndexHtml, (err) => {
2796
- if (err) next();
2870
+ if (isStandaloneBinary()) {
2871
+ const { staticMiddleware, spaFallback } = createEmbeddedDashboardMiddleware();
2872
+ app.use(staticMiddleware);
2873
+ app.get("*", spaFallback);
2874
+ } else {
2875
+ const runtimeDir = typeof __dirname !== "undefined" ? (
2876
+ // eslint-disable-next-line no-undef
2877
+ __dirname
2878
+ ) : path.dirname(fileURLToPath(import.meta.url));
2879
+ const { distDir: dashboardDistDir, indexHtml: dashboardIndexHtml } = resolveDashboardDistDir(runtimeDir);
2880
+ app.use(express2.static(dashboardDistDir));
2881
+ app.get("*", (req, res, next) => {
2882
+ if (req.path.startsWith("/api") || req.path === "/health") return next();
2883
+ res.sendFile(dashboardIndexHtml, (err) => {
2884
+ if (err) next();
2885
+ });
2797
2886
  });
2798
- });
2887
+ }
2799
2888
  const server = createServer2(app);
2800
2889
  const wss = new WebSocketServer2({ server, path: "/ws" });
2801
2890
  wss.on("connection", async (ws) => {
@@ -2921,14 +3010,24 @@ var dashboard = new Command6().name("dashboard").description("Start the local da
2921
3010
  });
2922
3011
 
2923
3012
  // src/index.ts
2924
- var packageJsonPath = fileURLToPath2(
2925
- new URL("../package.json", import.meta.url)
2926
- );
2927
- var packageJson = JSON.parse(
2928
- readFileSync4(packageJsonPath, { encoding: "utf8" })
2929
- );
3013
+ function getVersion() {
3014
+ if (typeof CLI_VERSION !== "undefined") {
3015
+ return CLI_VERSION;
3016
+ }
3017
+ try {
3018
+ const packageJsonPath = fileURLToPath2(
3019
+ new URL("../package.json", import.meta.url)
3020
+ );
3021
+ const packageJson = JSON.parse(
3022
+ readFileSync5(packageJsonPath, { encoding: "utf8" })
3023
+ );
3024
+ return packageJson.version;
3025
+ } catch {
3026
+ return "0.0.0-unknown";
3027
+ }
3028
+ }
2930
3029
  var program = new Command7().name("better-webhook").description(
2931
3030
  "Modern CLI for developing, capturing, and replaying webhooks locally"
2932
- ).version(packageJson.version);
3031
+ ).version(getVersion());
2933
3032
  program.addCommand(templates).addCommand(run).addCommand(capture).addCommand(captures).addCommand(replay).addCommand(dashboard);
2934
3033
  program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-webhook/cli",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "Modern CLI for developing, capturing, and replaying webhooks locally with dashboard UI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,6 +60,7 @@
60
60
  "zod": "^3.23.8"
61
61
  },
62
62
  "devDependencies": {
63
+ "@types/bun": "^1.2.18",
63
64
  "@types/express": "^4.17.21",
64
65
  "@types/node": "^24.3.1",
65
66
  "@types/prompts": "^2.4.9",
@@ -72,6 +73,8 @@
72
73
  "scripts": {
73
74
  "build:cli": "tsup --format cjs,esm --dts && node ./scripts/copy-dashboard.mjs",
74
75
  "build": "pnpm --filter @better-webhook/dashboard build && pnpm run build:cli",
76
+ "build:binary": "bun ./scripts/build-binary.ts",
77
+ "build:binary:all": "bun ./scripts/build-binary.ts --all",
75
78
  "dev": "tsup --watch",
76
79
  "start": "tsx src/index.ts",
77
80
  "lint": "tsc",