dodopayments-cli 3.0.0 → 3.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dodopayments-cli",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Official Dodo Payments CLI — manage payments, customers, products, subscriptions, and webhooks from your terminal. Includes an interactive TUI, an AI assistant powered by MCP, and offline webhook testing.",
5
5
  "keywords": [
6
6
  "dodopayments",
package/scripts/build.ts CHANGED
@@ -6,8 +6,20 @@
6
6
  *
7
7
  * The runtime path uses bunfig.toml's preload instead -- both code paths
8
8
  * end up registering the same plugin, just at different lifecycles.
9
+ *
10
+ * After bundling, we inject a tiny Node-compatible prelude that re-execs
11
+ * the bundle under Bun if it's launched by Node. This is required because
12
+ * `target: 'bun'` emits Bun-only runtime APIs (e.g. `import.meta.require`)
13
+ * and our native deps (@opentui/core, keytar) load through Bun's loader.
14
+ *
15
+ * Without this prelude, `bun install -g dodopayments-cli` works on disk
16
+ * but the symlinked `dodo` binary is invoked via `#!/usr/bin/env node`,
17
+ * which crashes immediately with "Qg is not a function" because Node has
18
+ * no `import.meta.require`. The prelude makes Node hand control off to
19
+ * Bun transparently, and gives a clear error if Bun isn't installed.
9
20
  */
10
21
 
22
+ import { readFileSync, writeFileSync } from 'node:fs';
11
23
  import solidPlugin from '@opentui/solid/bun-plugin';
12
24
 
13
25
  const result = await Bun.build({
@@ -16,6 +28,7 @@ const result = await Bun.build({
16
28
  target: 'bun',
17
29
  minify: true,
18
30
  plugins: [solidPlugin],
31
+ external: ['keytar'],
19
32
  });
20
33
 
21
34
  if (!result.success) {
@@ -25,7 +38,100 @@ if (!result.success) {
25
38
  }
26
39
 
27
40
  const main = result.outputs.find((o) => o.kind === 'entry-point');
28
- if (main) {
29
- const sizeKb = (main.size / 1024).toFixed(0);
30
- console.log(`\u2713 Built ${main.path} (${sizeKb} KB)`);
41
+ if (!main) {
42
+ console.error('Build succeeded but no entry-point output was produced.');
43
+ process.exit(1);
31
44
  }
45
+
46
+ const outPath = main.path;
47
+
48
+ /**
49
+ * Node-safe prelude. Must contain ZERO Bun-only APIs.
50
+ *
51
+ * Why this works:
52
+ * - Top-level await in ESM BLOCKS subsequent top-level statements
53
+ * until it resolves. So under Node, the `await import(...)` inside
54
+ * the `if` block runs to completion (and calls process.exit) before
55
+ * any of the bundle's Bun-only top-level code is reached.
56
+ * - Under Bun, the `if` is skipped and the bundle runs normally.
57
+ *
58
+ * Why not wrap the rest of the bundle in `else { ... }`:
59
+ * - The bundle contains top-level `import ...` declarations, which
60
+ * are syntactically forbidden inside a block. So we cannot wrap.
61
+ * Instead we rely on `process.exit()` inside the `if` to keep Node
62
+ * from ever continuing past it.
63
+ *
64
+ * Bun detection:
65
+ * - `typeof Bun !== 'undefined'` is the documented way to detect Bun.
66
+ * Node has no such global. Deno also lacks it; Deno would also fail
67
+ * on other Bun-only bits below, so the explicit error message is
68
+ * the correct outcome for any non-Bun runtime.
69
+ */
70
+ const prelude = `if (typeof Bun === 'undefined') {
71
+ const { spawnSync } = await import('node:child_process');
72
+ const { existsSync } = await import('node:fs');
73
+ const { fileURLToPath } = await import('node:url');
74
+ const path = await import('node:path');
75
+ const os = await import('node:os');
76
+
77
+ const scriptPath = fileURLToPath(import.meta.url);
78
+ const isWindows = process.platform === 'win32';
79
+
80
+ const which = spawnSync(isWindows ? 'where' : 'which', ['bun'], { encoding: 'utf8' });
81
+ let bun = null;
82
+ if (which.status === 0 && which.stdout) {
83
+ const first = which.stdout.split(/\\r?\\n/).find(Boolean);
84
+ if (first && existsSync(first)) bun = first;
85
+ }
86
+ if (!bun) {
87
+ const candidates = [
88
+ path.join(os.homedir(), '.bun', 'bin', isWindows ? 'bun.exe' : 'bun'),
89
+ '/usr/local/bin/bun',
90
+ '/opt/homebrew/bin/bun',
91
+ ];
92
+ for (const c of candidates) {
93
+ if (existsSync(c)) { bun = c; break; }
94
+ }
95
+ }
96
+
97
+ if (!bun) {
98
+ process.stderr.write(
99
+ 'dodopayments-cli requires the Bun runtime when installed via npm/bun.\\n' +
100
+ '\\n' +
101
+ 'Install Bun: https://bun.com/docs/installation\\n' +
102
+ 'Or download a standalone binary (no runtime required):\\n' +
103
+ ' https://github.com/dodopayments/dodopayments-cli/releases\\n'
104
+ );
105
+ process.exit(1);
106
+ }
107
+
108
+ const child = spawnSync(bun, [scriptPath, ...process.argv.slice(2)], {
109
+ stdio: 'inherit',
110
+ env: process.env,
111
+ });
112
+ if (child.error) {
113
+ process.stderr.write('Failed to launch bun: ' + child.error.message + '\\n');
114
+ process.exit(1);
115
+ }
116
+ if (typeof child.signal === 'string' && child.signal) {
117
+ process.kill(process.pid, child.signal);
118
+ }
119
+ process.exit(child.status ?? 0);
120
+ }
121
+ `;
122
+
123
+ const original = readFileSync(outPath, 'utf8');
124
+
125
+ // Splice the prelude immediately after the shebang (Bun emits it on line 1
126
+ // when bundling an executable). Everything below the shebang is untouched.
127
+ const newlineIdx = original.indexOf('\n');
128
+ const firstLine = newlineIdx >= 0 ? original.slice(0, newlineIdx) : '';
129
+ const hasShebang = firstLine.startsWith('#!');
130
+ const shebang = hasShebang ? firstLine : '';
131
+ const body = hasShebang ? original.slice(newlineIdx + 1) : original;
132
+
133
+ const patched = (shebang ? `${shebang}\n` : '') + prelude + body;
134
+ writeFileSync(outPath, patched);
135
+
136
+ const sizeKb = (Buffer.byteLength(patched, 'utf8') / 1024).toFixed(0);
137
+ console.log(`\u2713 Built ${outPath} (${sizeKb} KB)`);
Binary file