devtunnel-cli 3.1.2 → 3.1.3

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": "devtunnel-cli",
3
- "version": "3.1.2",
3
+ "version": "3.1.3",
4
4
  "type": "module",
5
5
  "description": "DevTunnel-CLI — fast, zero-config tool to share local servers for development, testing, demos, and webhook debugging. npm i -g devtunnel-cli.",
6
6
  "main": "src/core/start.js",
package/src/core/RUN.js CHANGED
@@ -37,13 +37,20 @@ process.stdout.write('\x1B[2J\x1B[0f');
37
37
  console.clear();
38
38
 
39
39
  const startPath = join(__dirname, "start.js");
40
- const child = spawn("node", [startPath], {
40
+ // Use same Node binary as current process (works when "node" is not in PATH)
41
+ const nodeBin = process.execPath;
42
+ const child = spawn(nodeBin, [startPath], {
41
43
  stdio: "inherit",
42
44
  shell: false
43
45
  });
44
46
 
45
- child.on("error", (error) => {
46
- console.error("❌ Error starting app:", error.message);
47
+ child.on("error", (err) => {
48
+ if (err.code === "ENOENT") {
49
+ console.error("❌ Node.js is required but could not be found.");
50
+ console.error(" Install from: https://nodejs.org");
51
+ } else {
52
+ console.error("❌ Error starting app:", err.message);
53
+ }
47
54
  process.exit(1);
48
55
  });
49
56
 
package/src/core/index.js CHANGED
@@ -221,6 +221,11 @@ async function tryTunnelServices() {
221
221
  stdio: "pipe"
222
222
  });
223
223
 
224
+ tunnelProcess.on("error", (err) => {
225
+ console.error("\nERROR: Tunnel process failed to start:", err.code === "ENOENT" ? `"${service.command}" not found` : err.message);
226
+ process.exit(1);
227
+ });
228
+
224
229
  setupTunnelHandlers(service.name);
225
230
 
226
231
  // Wait a bit to see if tunnel starts successfully
@@ -253,6 +258,7 @@ async function tryTunnelServices() {
253
258
 
254
259
  function setupTunnelHandlers(serviceName) {
255
260
  if (!tunnelProcess) return;
261
+ if (!tunnelProcess.stdout || !tunnelProcess.stderr) return;
256
262
 
257
263
  tunnelProcess.stdout.on("data", (data) => {
258
264
  const output = data.toString();
@@ -311,7 +317,7 @@ function setupTunnelHandlers(serviceName) {
311
317
  });
312
318
  });
313
319
 
314
- tunnelProcess.stderr.on("data", (data) => {
320
+ tunnelProcess.stderr?.on("data", (data) => {
315
321
  const output = data.toString();
316
322
 
317
323
  // For Cloudflare, check if URL is in stderr (sometimes it is)
package/src/core/start.js CHANGED
@@ -23,7 +23,7 @@ function getPackageVersion() {
23
23
  return "3.1.1";
24
24
  }
25
25
 
26
- // Helper to run command
26
+ // Helper to run command (cross-platform)
27
27
  function runCommand(command, args = [], cwd = process.cwd()) {
28
28
  return new Promise((resolve) => {
29
29
  const proc = spawn(command, args, {
@@ -36,15 +36,16 @@ function runCommand(command, args = [], cwd = process.cwd()) {
36
36
  proc.stdout?.on("data", (data) => output += data.toString());
37
37
  proc.stderr?.on("data", (data) => output += data.toString());
38
38
 
39
- proc.on("close", (code) => resolve({ code, output }));
40
- proc.on("error", () => resolve({ code: 1, output: "" }));
39
+ proc.on("close", (code, signal) => resolve({ code: code ?? (signal ? 1 : 0), output }));
40
+ proc.on("error", (err) => resolve({ code: 1, output: "", error: err }));
41
41
  });
42
42
  }
43
43
 
44
- // Check if command exists
44
+ // Check if command exists (Windows: where, macOS/Linux: which)
45
45
  async function commandExists(command) {
46
- const result = await runCommand("where", [command]);
47
- return result.code === 0;
46
+ const isWin = process.platform === "win32";
47
+ const result = await runCommand(isWin ? "where" : "which", [command]);
48
+ return result.code === 0 && (result.output || "").trim().length > 0;
48
49
  }
49
50
 
50
51
  // Check if a port is in use (dev server running)
@@ -280,14 +281,21 @@ async function main() {
280
281
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
281
282
  console.log("");
282
283
 
283
- // Step 1: Check Node.js
284
+ // Step 1: Check Node.js (version and availability)
284
285
  console.log("[1/4] Checking Node.js...");
286
+ const minNodeMajor = 16;
287
+ const currentMajor = parseInt(process.version.slice(1).split(".")[0], 10);
288
+ if (isNaN(currentMajor) || currentMajor < minNodeMajor) {
289
+ console.log("ERROR: Node.js " + minNodeMajor + "+ required. Current:", process.version);
290
+ console.log("Install from: https://nodejs.org/");
291
+ process.exit(1);
292
+ }
285
293
  if (!await commandExists("node")) {
286
- console.log("ERROR: Node.js not found!");
294
+ console.log("ERROR: Node.js not found in PATH.");
287
295
  console.log("Install from: https://nodejs.org/");
288
296
  process.exit(1);
289
297
  }
290
- console.log("SUCCESS: Node.js installed");
298
+ console.log("SUCCESS: Node.js " + process.version + " installed");
291
299
  console.log("");
292
300
 
293
301
  // Step 2: Check Cloudflare (bundled or system-installed)
@@ -340,13 +348,19 @@ async function main() {
340
348
  console.log("[3/4] Checking dependencies...");
341
349
  const nodeModulesPath = join(PROJECT_ROOT, "node_modules");
342
350
  if (!existsSync(nodeModulesPath)) {
351
+ const hasNpm = await commandExists("npm");
352
+ if (!hasNpm) {
353
+ console.log("ERROR: npm not found. Node.js/npm is required.");
354
+ console.log("Install from: https://nodejs.org/");
355
+ process.exit(1);
356
+ }
343
357
  console.log("Installing dependencies...");
344
358
  console.log("");
345
359
  // Run npm install in the project root directory
346
360
  const result = await runCommand("npm", ["install"], PROJECT_ROOT);
347
361
  if (result.code !== 0) {
348
362
  console.log("");
349
- console.log("ERROR: npm install failed");
363
+ console.log("ERROR: npm install failed" + (result.error ? ": " + result.error.message : ""));
350
364
  process.exit(1);
351
365
  }
352
366
  console.log("");
@@ -608,11 +622,14 @@ async function main() {
608
622
  if (isHtmlProject && !portInUseNow) {
609
623
  console.log("Starting built-in static server for HTML project...");
610
624
  const staticServerPath = join(__dirname, "static-server.js");
611
- staticServerProcess = spawn("node", [staticServerPath, projectPath, devPort.toString()], {
625
+ const nodeBin = process.execPath;
626
+ staticServerProcess = spawn(nodeBin, [staticServerPath, projectPath, devPort.toString()], {
612
627
  stdio: "pipe",
613
628
  shell: false
614
629
  });
615
- staticServerProcess.on("error", () => { });
630
+ staticServerProcess.on("error", (err) => {
631
+ console.error("ERROR: Could not start static server:", err.code === "ENOENT" ? "Node not found" : err.message);
632
+ });
616
633
  const ready = await waitForServerReady(devPort, 10000);
617
634
  if (!ready) {
618
635
  if (staticServerProcess) staticServerProcess.kill();
@@ -627,31 +644,38 @@ async function main() {
627
644
  // Start proxy server
628
645
  console.log("Starting services...");
629
646
  console.log("");
647
+ const nodeBin = process.execPath;
630
648
  const proxyPath = join(__dirname, "proxy-server.js");
631
649
  const proxyArgs = [proxyPath, devPort.toString(), proxyPort.toString(), projectName];
632
650
  if (basePath) proxyArgs.push(basePath);
633
- const proxyProcess = spawn("node", proxyArgs, {
651
+ const proxyProcess = spawn(nodeBin, proxyArgs, {
634
652
  stdio: "inherit",
635
653
  shell: false
636
654
  });
637
655
 
656
+ const onChildError = (name, err) => {
657
+ console.error("\nERROR: " + name + " failed to start:", err.code === "ENOENT" ? "Node not found. Install from https://nodejs.org" : err.message);
658
+ process.exit(1);
659
+ };
660
+ proxyProcess.on("error", (err) => onChildError("Proxy server", err));
661
+
638
662
  // Wait for proxy to start
639
663
  await new Promise(resolve => setTimeout(resolve, 2000));
640
664
 
641
665
  // Run main tunnel app (connects to proxy port)
642
- // Use shell: false to properly handle paths with spaces
643
666
  const indexPath = join(__dirname, "index.js");
644
- const tunnelProcess = spawn("node", [indexPath, proxyPort.toString(), projectName, projectPath], {
667
+ const tunnelProcess = spawn(nodeBin, [indexPath, proxyPort.toString(), projectName, projectPath], {
645
668
  stdio: "inherit",
646
669
  shell: false
647
670
  });
671
+ tunnelProcess.on("error", (err) => onChildError("Tunnel service", err));
648
672
 
649
673
  // Handle cleanup
650
674
  const cleanup = () => {
651
675
  console.log("\nShutting down...");
652
- if (staticServerProcess) staticServerProcess.kill();
653
- proxyProcess.kill();
654
- tunnelProcess.kill();
676
+ if (staticServerProcess) try { staticServerProcess.kill(); } catch (_) {}
677
+ try { proxyProcess.kill(); } catch (_) {}
678
+ try { tunnelProcess.kill(); } catch (_) {}
655
679
  process.exit(0);
656
680
  };
657
681
 
@@ -670,6 +694,10 @@ async function main() {
670
694
 
671
695
  // Run
672
696
  main().catch((error) => {
673
- console.error("\nERROR:", error.message);
697
+ const msg = error && error.message ? error.message : String(error);
698
+ console.error("\nERROR:", msg);
699
+ if (error && error.code === "ENOENT") {
700
+ console.error(" A required command or file was not found. Check that Node.js is installed: https://nodejs.org");
701
+ }
674
702
  process.exit(1);
675
703
  });
@@ -75,7 +75,14 @@ export async function selectFolder() {
75
75
  return null;
76
76
 
77
77
  } catch (error) {
78
- console.error("Folder picker error:", error.message);
78
+ const msg = error && error.message ? error.message : String(error);
79
+ if (os === "win32" && (msg.includes("ENOENT") || msg.includes("spawn powershell"))) {
80
+ console.error("Folder picker: PowerShell not found. Run from a path or pass the project path as an argument.");
81
+ } else if (os !== "win32" && os !== "darwin" && (msg.includes("ENOENT") || msg.includes("spawn"))) {
82
+ console.error("Folder picker: zenity or kdialog not found. Install one (e.g. apt install zenity) or pass the project path as an argument.");
83
+ } else {
84
+ console.error("Folder picker error:", msg);
85
+ }
79
86
  return null;
80
87
  }
81
88
  }
@@ -96,15 +103,21 @@ function runPowerShell(script) {
96
103
  let stderr = "";
97
104
  proc.stderr?.on("data", (data) => stderr += data.toString());
98
105
 
99
- proc.on("close", (code) => {
106
+ proc.on("close", (code, signal) => {
100
107
  if (code === 0) {
101
108
  resolve();
102
109
  } else {
103
- reject(new Error(stderr || `PowerShell exited with code ${code}`));
110
+ reject(new Error(stderr || `PowerShell exited with code ${code ?? signal ?? 1}`));
104
111
  }
105
112
  });
106
113
 
107
- proc.on("error", reject);
114
+ proc.on("error", (err) => {
115
+ if (err.code === "ENOENT") {
116
+ reject(new Error("PowerShell not found. Install it or use a different method to select the folder."));
117
+ } else {
118
+ reject(err);
119
+ }
120
+ });
108
121
  });
109
122
  }
110
123
 
@@ -124,17 +137,24 @@ function runCommand(command, args, outputFile) {
124
137
  });
125
138
  }
126
139
 
127
- proc.on("close", (code) => {
128
- if (code === 0) {
140
+ proc.on("close", (code, signal) => {
141
+ const exitCode = code ?? (signal ? 1 : 0);
142
+ if (exitCode === 0) {
129
143
  if (outputFile && stdout) {
130
- writeFileSync(outputFile, stdout.trim());
144
+ try { writeFileSync(outputFile, stdout.trim()); } catch (_) {}
131
145
  }
132
146
  resolve();
133
147
  } else {
134
- reject(new Error(`Command failed with code ${code}`));
148
+ reject(new Error(`Command failed with code ${exitCode}`));
135
149
  }
136
150
  });
137
151
 
138
- proc.on("error", reject);
152
+ proc.on("error", (err) => {
153
+ if (err.code === "ENOENT") {
154
+ reject(new Error(`Command not found: ${command}`));
155
+ } else {
156
+ reject(err);
157
+ }
158
+ });
139
159
  });
140
160
  }