claudekit-cli 2.5.0 → 2.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/bin/ck.js CHANGED
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Wrapper script that detects platform and executes the correct binary
5
- * This is the entry point that NPM symlinks to when installing globally
4
+ * Wrapper script that detects platform and executes the correct binary.
5
+ * Falls back to Node.js execution if binary fails (e.g., Alpine/musl).
6
+ * This is the entry point that NPM symlinks to when installing globally.
6
7
  */
7
8
 
8
9
  import { spawn } from "node:child_process";
9
10
  import { existsSync } from "node:fs";
10
11
  import { dirname, join } from "node:path";
11
- import { fileURLToPath } from "node:url";
12
+ import { fileURLToPath, pathToFileURL } from "node:url";
12
13
 
13
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
15
 
@@ -16,47 +17,143 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
16
17
  const platform = process.platform;
17
18
  const arch = process.arch;
18
19
 
19
- // Map to binary filename
20
- const getBinaryPath = () => {
21
- const ext = platform === "win32" ? ".exe" : "";
20
+ /**
21
+ * Extract error message safely with type guard
22
+ */
23
+ const getErrorMessage = (err) => {
24
+ return err instanceof Error ? err.message : String(err);
25
+ };
26
+
27
+ /**
28
+ * Run CLI via Node.js as fallback (slower but works on all platforms).
29
+ * The imported dist/index.js handles its own process lifecycle via the cac CLI framework.
30
+ * @param {boolean} showWarning - Whether to show fallback warning message
31
+ * @throws {Error} If dist/index.js is missing or fails to load
32
+ */
33
+ const runWithNode = async (showWarning = false) => {
34
+ const distPath = join(__dirname, "..", "dist", "index.js");
35
+ if (!existsSync(distPath)) {
36
+ throw new Error("Compiled distribution not found. This may indicate a packaging issue.");
37
+ }
38
+ if (showWarning) {
39
+ console.error("⚠️ Native binary failed, using Node.js fallback (slower startup)");
40
+ }
41
+ // The CLI module handles process.exit() internally after command execution
42
+ // Convert to file:// URL for cross-platform ESM compatibility (Windows paths require this)
43
+ const distUrl = pathToFileURL(distPath).href;
44
+ try {
45
+ await import(distUrl);
46
+ } catch (importErr) {
47
+ throw new Error(`Failed to load CLI module: ${getErrorMessage(importErr)}`);
48
+ }
49
+ };
22
50
 
51
+ /**
52
+ * Map platform/arch to binary filename
53
+ */
54
+ const getBinaryPath = () => {
23
55
  const binaryMap = {
24
- "darwin-arm64": `ck-darwin-arm64${ext}`,
25
- "darwin-x64": `ck-darwin-x64${ext}`,
26
- "linux-x64": `ck-linux-x64${ext}`,
27
- "win32-x64": `ck-win32-x64${ext}`,
56
+ "darwin-arm64": "ck-darwin-arm64",
57
+ "darwin-x64": "ck-darwin-x64",
58
+ "linux-x64": "ck-linux-x64",
59
+ "win32-x64": "ck-win32-x64.exe",
28
60
  };
29
61
 
30
62
  const key = `${platform}-${arch}`;
31
63
  const binaryName = binaryMap[key];
32
64
 
33
65
  if (!binaryName) {
34
- console.error(`❌ Unsupported platform: ${platform}-${arch}`);
35
- console.error("Supported platforms: macOS (arm64, x64), Linux (x64), Windows (x64)");
36
- process.exit(1);
66
+ // Unsupported platform - try Node.js fallback
67
+ return null;
37
68
  }
38
69
 
39
70
  return join(__dirname, binaryName);
40
71
  };
41
72
 
42
- const binaryPath = getBinaryPath();
73
+ /**
74
+ * Execute binary with fallback to Node.js on failure.
75
+ * Uses Promise-based approach to avoid race conditions between error and exit events.
76
+ *
77
+ * Note: This Promise intentionally never rejects - all error paths call process.exit()
78
+ * directly since this is a CLI entry point. The Promise is used purely for async flow
79
+ * control and race condition prevention, not for error propagation.
80
+ *
81
+ * @param {string} binaryPath - Path to the platform-specific binary
82
+ * @returns {Promise<void>} Resolves when fallback completes (binary exit calls process.exit directly)
83
+ */
84
+ const runBinary = (binaryPath) => {
85
+ return new Promise((resolve) => {
86
+ const child = spawn(binaryPath, process.argv.slice(2), {
87
+ stdio: "inherit",
88
+ windowsHide: true,
89
+ });
90
+
91
+ let errorOccurred = false;
43
92
 
44
- // Check if binary exists
45
- if (!existsSync(binaryPath)) {
46
- console.error(`❌ Binary not found: ${binaryPath}`);
47
- console.error("Please report this issue at: https://github.com/claudekit/claudekit-cli/issues");
48
- process.exit(1);
49
- }
93
+ child.on("error", async (err) => {
94
+ // Binary execution failed (e.g., ENOENT on Alpine/musl due to missing glibc)
95
+ // Fall back to Node.js execution
96
+ errorOccurred = true;
97
+ try {
98
+ await runWithNode(true);
99
+ resolve();
100
+ } catch (fallbackErr) {
101
+ console.error(`❌ Binary failed: ${getErrorMessage(err)}`);
102
+ console.error(`❌ Fallback also failed: ${getErrorMessage(fallbackErr)}`);
103
+ console.error(
104
+ "Please report this issue at: https://github.com/mrgoonie/claudekit-cli/issues",
105
+ );
106
+ process.exit(1);
107
+ }
108
+ });
50
109
 
51
- // Execute the binary with all arguments
52
- const child = spawn(binaryPath, process.argv.slice(2), {
53
- stdio: "inherit",
54
- windowsHide: true,
55
- });
110
+ child.on("exit", (code, signal) => {
111
+ // Don't handle exit if error handler is managing fallback
112
+ if (errorOccurred) return;
113
+
114
+ if (signal) {
115
+ process.kill(process.pid, signal);
116
+ }
117
+ process.exit(code || 0);
118
+ });
119
+ });
120
+ };
56
121
 
57
- child.on("exit", (code, signal) => {
58
- if (signal) {
59
- process.kill(process.pid, signal);
122
+ /**
123
+ * Handle fallback execution with error reporting
124
+ * @param {string} errorPrefix - Prefix for error message if fallback fails
125
+ * @param {boolean} showIssueLink - Whether to show issue reporting link
126
+ */
127
+ const handleFallback = async (errorPrefix, showIssueLink = false) => {
128
+ try {
129
+ await runWithNode();
130
+ } catch (err) {
131
+ console.error(`❌ ${errorPrefix}: ${getErrorMessage(err)}`);
132
+ if (showIssueLink) {
133
+ console.error(
134
+ "Please report this issue at: https://github.com/mrgoonie/claudekit-cli/issues",
135
+ );
136
+ }
137
+ process.exit(1);
60
138
  }
61
- process.exit(code || 0);
62
- });
139
+ };
140
+
141
+ /**
142
+ * Main execution - determine which path to take
143
+ */
144
+ const main = async () => {
145
+ const binaryPath = getBinaryPath();
146
+
147
+ if (!binaryPath) {
148
+ // No binary for this platform - use Node.js fallback
149
+ await handleFallback("Failed to run CLI");
150
+ } else if (!existsSync(binaryPath)) {
151
+ // Binary should exist but doesn't - try fallback
152
+ await handleFallback("Binary not found and fallback failed", true);
153
+ } else {
154
+ // Execute the binary (handles its own fallback on error)
155
+ await runBinary(binaryPath);
156
+ }
157
+ };
158
+
159
+ main();