devtunnel-cli 3.0.37 → 3.0.39

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
@@ -96,92 +96,6 @@ devtunnel-cli
96
96
 
97
97
  ---
98
98
 
99
- ## 🔒 Security & Access Model
100
-
101
- DevTunnel-CLI intentionally **does NOT use authentication or access control**. This is a deliberate design choice to ensure fast, frictionless development workflows.
102
-
103
- **How Access Works:**
104
- - Anyone with the generated temporary URL can access the tunnel until it is stopped
105
- - No login, password, or identity verification required
106
- - Access is limited by **possession of the URL only**
107
-
108
- **Why This Design?**
109
- - **Speed**: Get public URLs instantly without authentication setup
110
- - **Simplicity**: Zero configuration — just run and share
111
- - **Friction-free collaboration**: Share with teammates without managing accounts or permissions
112
-
113
- **Temporary URL Behavior:**
114
- - URLs are **short-lived** and **unlisted**
115
- - URLs are **destroyed** when the tunnel stops
116
- - New random URLs are generated each time you run DevTunnel-CLI
117
- - URLs are not indexed by search engines
118
-
119
- **Best Practices:**
120
- - Only share URLs with trusted collaborators
121
- - Stop the tunnel when not in use
122
- - Never expose sensitive data or production databases through DevTunnel-CLI
123
- - Use for development and testing only
124
-
125
- ---
126
-
127
- ## ⚠️ Limitations
128
-
129
- DevTunnel-CLI has intentional limitations that make it ideal for development but unsuitable for other use cases:
130
-
131
- ### No Authentication or Access Control
132
- - **By design**: No identity-based access control
133
- - **By design**: No user-level permission management
134
- - **By design**: No password protection or login system
135
- - Anyone with the URL can access your tunnel
136
-
137
- ### Not Suitable For
138
- - ❌ Production environments
139
- - ❌ Long-lived public services
140
- - ❌ Hosting production traffic
141
- - ❌ Sensitive data exposure
142
- - ❌ Public-facing applications
143
-
144
- ### Perfect For
145
- - ✅ Development and testing
146
- - ✅ Team collaboration and code reviews
147
- - ✅ Mobile device testing
148
- - ✅ Client demos and work-in-progress sharing
149
- - ✅ Webhook debugging with third-party services
150
- - ✅ Temporary public access to localhost
151
-
152
- ### File Size & Streaming Limits
153
- - ✅ Small files (<10MB): Works perfectly
154
- - ✅ Medium files (10-50MB): Works well, may have slight delays
155
- - ⚠️ Large files (>50MB): May timeout depending on connection speed
156
- - ⚠️ Very large files (>100MB): Not recommended for Cloudflare free tier
157
-
158
- ---
159
-
160
- ## 🆚 Comparison: DevTunnel-CLI vs. Enterprise Tunnels
161
-
162
- DevTunnel-CLI is optimized for **speed and simplicity** rather than governance and authentication.
163
-
164
- | Feature | DevTunnel-CLI | Enterprise Tunnels (e.g., Microsoft Dev Tunnels) |
165
- |---------|---------------|--------------------------------------------------|
166
- | **Setup Time** | Instant (0 config) | Requires account, authentication setup |
167
- | **Authentication** | None (by design) | User-based auth, SSO, identity management |
168
- | **Access Control** | URL possession only | Fine-grained permissions, user/group policies |
169
- | **Use Case** | Development, testing, demos | Enterprise dev, governed access, compliance |
170
- | **Speed** | Instant sharing | May require approval workflows |
171
- | **Ideal For** | Solo devs, small teams, fast iteration | Large orgs, regulated industries, prod-like envs |
172
-
173
- **Choose DevTunnel-CLI when:**
174
- - You need instant, frictionless sharing
175
- - You're working on non-sensitive development projects
176
- - Speed and simplicity are priorities
177
-
178
- **Choose enterprise tunnels when:**
179
- - You need identity-based access control
180
- - You're in a regulated or compliance-heavy environment
181
- - You need audit logs and governance
182
-
183
- ---
184
-
185
99
  ## 📖 Documentation
186
100
 
187
101
  - [Features](docs/FEATURES.md)
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "devtunnel-cli",
3
- "version": "3.0.37",
3
+ "version": "3.0.39",
4
4
  "type": "module",
5
- "description": "DevTunnel-CLI (DevTunnel, devtunnel, dev-tunnel) - Development tool for sharing local servers worldwide. Zero config, supports multiple ports. For dev, testing, demos, webhook debugging. Not for production. npm install -g devtunnel-cli. Works with Vite, React, Laravel, HTML, PHP/XAMPP.",
5
+ "description": "DevTunnel-CLI fast, zero-config tool to share local servers for development, testing, demos, and webhook debugging. npm install -g devtunnel-cli.",
6
6
  "main": "src/core/start.js",
7
7
  "files": [
8
8
  "README.md",
@@ -15,7 +15,8 @@
15
15
  "start": "node src/core/RUN.js",
16
16
  "dev": "node src/core/RUN.js",
17
17
  "run": "node src/core/RUN.js",
18
- "tunnel": "node src/core/index.js"
18
+ "tunnel": "node src/core/index.js",
19
+ "sync-version": "node sync-version.js"
19
20
  },
20
21
  "keywords": [
21
22
  "DevTunnel-CLI",
@@ -62,6 +63,7 @@
62
63
  ],
63
64
  "author": "maiz",
64
65
  "license": "MIT",
66
+ "preferGlobal": true,
65
67
  "repository": {
66
68
  "type": "git",
67
69
  "url": "git+https://github.com/maiz-an/DevTunnel-CLI.git"
@@ -79,4 +81,4 @@
79
81
  "prompts": "^2.4.2"
80
82
  },
81
83
  "devDependencies": {}
82
- }
84
+ }
package/src/core/start.js CHANGED
@@ -17,25 +17,25 @@ function getPackageVersion() {
17
17
  const pkgPath = join(PROJECT_ROOT, "package.json");
18
18
  if (existsSync(pkgPath)) {
19
19
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
20
- return pkg.version || "3.0.35";
20
+ return pkg.version || "3.0.38";
21
21
  }
22
- } catch (err) {}
23
- return "3.0.35";
22
+ } catch (err) { }
23
+ return "3.0.38";
24
24
  }
25
25
 
26
26
  // Helper to run command
27
27
  function runCommand(command, args = [], cwd = process.cwd()) {
28
28
  return new Promise((resolve) => {
29
- const proc = spawn(command, args, {
30
- shell: true,
29
+ const proc = spawn(command, args, {
30
+ shell: true,
31
31
  stdio: "pipe",
32
32
  cwd: cwd
33
33
  });
34
34
  let output = "";
35
-
35
+
36
36
  proc.stdout?.on("data", (data) => output += data.toString());
37
37
  proc.stderr?.on("data", (data) => output += data.toString());
38
-
38
+
39
39
  proc.on("close", (code) => resolve({ code, output }));
40
40
  proc.on("error", () => resolve({ code: 1, output: "" }));
41
41
  });
@@ -51,7 +51,7 @@ async function commandExists(command) {
51
51
  function checkPortInUse(port) {
52
52
  return new Promise((resolve) => {
53
53
  const server = http.createServer();
54
-
54
+
55
55
  server.once('error', (err) => {
56
56
  // Port is in use
57
57
  if (err.code === 'EADDRINUSE') {
@@ -60,7 +60,7 @@ function checkPortInUse(port) {
60
60
  resolve(false);
61
61
  }
62
62
  });
63
-
63
+
64
64
  server.listen(port, () => {
65
65
  // Port is available (not in use)
66
66
  server.once('close', () => resolve(false));
@@ -79,7 +79,7 @@ async function waitForServerReady(port, timeoutMs = 10000) {
79
79
  req.on("error", () => resolve(null));
80
80
  });
81
81
  if (code !== null && code >= 200 && code < 500) return true;
82
- } catch (err) {}
82
+ } catch (err) { }
83
83
  await new Promise((r) => setTimeout(r, 300));
84
84
  }
85
85
  return false;
@@ -91,24 +91,24 @@ function detectPortFromPackage(packagePath) {
91
91
  if (!existsSync(packagePath)) return null;
92
92
  const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
93
93
  const scripts = packageJson.scripts || {};
94
-
94
+
95
95
  // Check for common dev commands
96
96
  const devScript = scripts.dev || scripts.start || scripts.serve;
97
97
  if (!devScript) return null;
98
-
98
+
99
99
  // Try to extract port from script
100
100
  const portMatch = devScript.match(/--port\s+(\d+)|:(\d+)|port[=:](\d+)/i);
101
101
  if (portMatch) {
102
102
  return parseInt(portMatch[1] || portMatch[2] || portMatch[3]);
103
103
  }
104
-
104
+
105
105
  // Default ports based on framework
106
106
  if (devScript.includes('vite')) return 5173;
107
107
  if (devScript.includes('next')) return 3000;
108
108
  if (devScript.includes('react-scripts')) return 3000;
109
109
  if (devScript.includes('webpack')) return 8080;
110
110
  if (devScript.includes('express')) return 3000;
111
-
111
+
112
112
  return null;
113
113
  } catch (err) {
114
114
  return null;
@@ -148,7 +148,7 @@ function detectPhpProject(currentDir) {
148
148
  async function detectRunningDevServer() {
149
149
  const commonPorts = [3000, 5173, 5500, 8080, 8000, 80, 5000, 4000, 3001, 5174]; // 80 for XAMPP
150
150
  const detected = [];
151
-
151
+
152
152
  for (const port of commonPorts) {
153
153
  const inUse = await checkPortInUse(port);
154
154
  if (inUse) {
@@ -174,7 +174,7 @@ async function detectRunningDevServer() {
174
174
  }
175
175
  }
176
176
  }
177
-
177
+
178
178
  return detected;
179
179
  }
180
180
 
@@ -244,6 +244,8 @@ async function autoDetectProject() {
244
244
  // ASCII Logo - Compatible with all OS and terminals
245
245
  function showLogo() {
246
246
  console.log("");
247
+ console.log(" ");
248
+ console.log(" ");
247
249
  console.log("8888888b. 88888888888 888 .d8888b. 888 8888888 ");
248
250
  console.log('888 "Y88b 888 888 d88P Y88b 888 888 ');
249
251
  console.log("888 888 888 888 888 888 888 888 ");
@@ -262,17 +264,19 @@ async function main() {
262
264
  // ANSI escape codes for clear screen + cursor to top
263
265
  process.stdout.write('\x1B[2J\x1B[0f');
264
266
  console.clear(); // Fallback for terminals that don't support ANSI
265
-
267
+
266
268
  // Show ASCII logo
267
269
  showLogo();
268
-
270
+
269
271
  console.log(`DevTunnel v${getPackageVersion()}`);
270
272
  console.log("Share your local dev servers worldwide");
271
273
  console.log("");
272
274
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
275
+ console.log("");
273
276
  console.log("Repository: https://github.com/maiz-an/DevTunnel-CLI");
274
277
  console.log("npm Package: https://www.npmjs.com/package/devtunnel-cli");
275
- console.log("Website: https://maizan.me/devtunnel-cli");
278
+ console.log("Website: https://devtunnel-cli.vercel.app");
279
+ console.log("");
276
280
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
277
281
  console.log("");
278
282
 
@@ -288,12 +292,12 @@ async function main() {
288
292
 
289
293
  // Step 2: Check Cloudflare (bundled or system-installed)
290
294
  console.log("[2/4] Checking Cloudflare...");
291
-
295
+
292
296
  // Import bundled cloudflared helpers
293
297
  const { setupCloudflared, hasBundledCloudflared } = await import("./setup-cloudflared.js");
294
-
298
+
295
299
  let cloudflareAvailable = false;
296
-
300
+
297
301
  if (hasBundledCloudflared()) {
298
302
  console.log("SUCCESS: Using bundled Cloudflare (no install needed)");
299
303
  cloudflareAvailable = true;
@@ -304,10 +308,10 @@ async function main() {
304
308
  console.log("First time setup - Downloading Cloudflare...");
305
309
  console.log("This only happens once (~40MB, 10-30 seconds)");
306
310
  console.log("");
307
-
311
+
308
312
  try {
309
313
  const bundledPath = await setupCloudflared();
310
-
314
+
311
315
  if (bundledPath) {
312
316
  console.log("SUCCESS: Cloudflare ready to use");
313
317
  cloudflareAvailable = true;
@@ -322,7 +326,7 @@ async function main() {
322
326
  console.log("");
323
327
  }
324
328
  }
325
-
329
+
326
330
  // Show what's available
327
331
  if (!cloudflareAvailable) {
328
332
  console.log("DevTunnel has multi-service fallback:");
@@ -354,20 +358,20 @@ async function main() {
354
358
 
355
359
  // Step 4: Auto-detect or select project
356
360
  console.log("[4/4] Detecting project...");
357
-
361
+
358
362
  let projectPath, projectName, devPort;
359
-
363
+
360
364
  // Try to auto-detect project in current directory
361
365
  const autoDetected = await autoDetectProject();
362
-
366
+
363
367
  if (autoDetected && autoDetected.port) {
364
368
  // Auto-detected project with port
365
369
  projectPath = autoDetected.path;
366
370
  projectName = autoDetected.name;
367
-
371
+
368
372
  // Double-check: verify the port is actually in use
369
373
  const portInUse = await checkPortInUse(autoDetected.port);
370
-
374
+
371
375
  if (!portInUse) {
372
376
  // Detected port is not actually running, check for other running servers
373
377
  const portSource =
@@ -380,7 +384,7 @@ async function main() {
380
384
  : "package.json";
381
385
  console.log(`Detected port ${autoDetected.port} (${portSource}), but no server running on that port`);
382
386
  console.log("Checking for running dev servers...");
383
-
387
+
384
388
  const runningPorts = await detectRunningDevServer();
385
389
  if (runningPorts.length > 0) {
386
390
  if (runningPorts.length === 1) {
@@ -394,12 +398,12 @@ async function main() {
394
398
  message: "Select port:",
395
399
  choices: runningPorts.map(p => ({ title: `Port ${p}`, value: p }))
396
400
  });
397
-
401
+
398
402
  if (!portResponse.port) {
399
403
  console.log("ERROR: No port selected");
400
404
  process.exit(1);
401
405
  }
402
-
406
+
403
407
  devPort = portResponse.port;
404
408
  }
405
409
  } else {
@@ -411,12 +415,12 @@ async function main() {
411
415
  // Port is in use, use it
412
416
  devPort = autoDetected.port;
413
417
  }
414
-
418
+
415
419
  console.log(`Detected project: ${projectName}`);
416
420
  console.log(`Using port: ${devPort}`);
417
421
  console.log(`Using current directory: ${projectPath}`);
418
422
  console.log("");
419
-
423
+
420
424
  // Confirm with user
421
425
  const confirm = await prompts({
422
426
  type: "confirm",
@@ -424,22 +428,22 @@ async function main() {
424
428
  message: "Use detected project?",
425
429
  initial: true
426
430
  });
427
-
431
+
428
432
  if (!confirm.value) {
429
433
  // User wants to select manually
430
434
  console.log("");
431
435
  console.log("Selecting project manually...");
432
436
  console.log("");
433
-
437
+
434
438
  const selectedPath = await selectFolder();
435
439
  if (!selectedPath || selectedPath.length === 0) {
436
440
  console.log("ERROR: No folder selected");
437
441
  process.exit(1);
438
442
  }
439
-
443
+
440
444
  projectPath = selectedPath;
441
445
  projectName = basename(selectedPath);
442
-
446
+
443
447
  // Try to detect port for selected project (Laravel → 8000, HTML → 5500, Node from package.json)
444
448
  const selectedPackagePath = join(selectedPath, "package.json");
445
449
  const laravelSelected = detectLaravelProject(selectedPath);
@@ -449,19 +453,19 @@ async function main() {
449
453
  : htmlSelected
450
454
  ? htmlSelected.defaultPort
451
455
  : detectPortFromPackage(selectedPackagePath);
452
-
456
+
453
457
  const portResponse = await prompts({
454
458
  type: "number",
455
459
  name: "port",
456
460
  message: "Enter your dev server port:",
457
461
  initial: detectedPort || 5173
458
462
  });
459
-
463
+
460
464
  if (!portResponse.port) {
461
465
  console.log("ERROR: No port entered");
462
466
  process.exit(1);
463
467
  }
464
-
468
+
465
469
  devPort = portResponse.port;
466
470
  } else {
467
471
  // User confirmed – let them keep default port or type another (e.g. HTML default 5500, can change)
@@ -479,16 +483,16 @@ async function main() {
479
483
  // Project detected but no port
480
484
  projectPath = autoDetected.path;
481
485
  projectName = autoDetected.name;
482
-
486
+
483
487
  console.log(`Detected project: ${projectName}`);
484
488
  console.log(`Using current directory: ${projectPath}`);
485
489
  console.log("Checking for running dev servers...");
486
-
490
+
487
491
  const runningPorts = await detectRunningDevServer();
488
-
492
+
489
493
  if (runningPorts.length > 0) {
490
494
  console.log(`Found ${runningPorts.length} running dev server(s) on port(s): ${runningPorts.join(', ')}`);
491
-
495
+
492
496
  if (runningPorts.length === 1) {
493
497
  devPort = runningPorts[0];
494
498
  console.log(`Using port: ${devPort}`);
@@ -500,12 +504,12 @@ async function main() {
500
504
  message: "Select port:",
501
505
  choices: runningPorts.map(p => ({ title: `Port ${p}`, value: p }))
502
506
  });
503
-
507
+
504
508
  if (!portResponse.port) {
505
509
  console.log("ERROR: No port selected");
506
510
  process.exit(1);
507
511
  }
508
-
512
+
509
513
  devPort = portResponse.port;
510
514
  }
511
515
  } else {
@@ -516,33 +520,33 @@ async function main() {
516
520
  message: "Enter your dev server port:",
517
521
  initial: 5173
518
522
  });
519
-
523
+
520
524
  if (!portResponse.port) {
521
525
  console.log("ERROR: No port entered");
522
526
  process.exit(1);
523
527
  }
524
-
528
+
525
529
  devPort = portResponse.port;
526
530
  }
527
-
531
+
528
532
  console.log("");
529
533
  } else {
530
534
  // No auto-detection, use folder picker
531
535
  console.log("No project detected in current directory");
532
536
  console.log("Opening folder picker...");
533
537
  console.log("");
534
-
538
+
535
539
  projectPath = await selectFolder();
536
-
540
+
537
541
  if (!projectPath || projectPath.length === 0) {
538
542
  console.log("ERROR: No folder selected");
539
543
  process.exit(1);
540
544
  }
541
-
545
+
542
546
  projectName = basename(projectPath);
543
547
  console.log(`Selected: ${projectPath}`);
544
548
  console.log("");
545
-
549
+
546
550
  // Try to detect port for selected project (Laravel → 8000, HTML → 5500, PHP → 80, Node from package.json)
547
551
  const selectedPackagePath = join(projectPath, "package.json");
548
552
  const laravelSelected = detectLaravelProject(projectPath);
@@ -555,30 +559,30 @@ async function main() {
555
559
  : phpSelected
556
560
  ? phpSelected.defaultPort // 80
557
561
  : detectPortFromPackage(selectedPackagePath);
558
-
562
+
559
563
  // Check for running servers
560
564
  const runningPorts = await detectRunningDevServer();
561
-
565
+
562
566
  let initialPort = detectedPort || 5173;
563
567
  if (runningPorts.length > 0 && !detectedPort) {
564
568
  initialPort = runningPorts[0];
565
569
  }
566
-
570
+
567
571
  const portResponse = await prompts({
568
572
  type: "number",
569
573
  name: "port",
570
574
  message: "Enter your dev server port:",
571
575
  initial: initialPort
572
576
  });
573
-
577
+
574
578
  if (!portResponse.port) {
575
579
  console.log("ERROR: No port entered");
576
580
  process.exit(1);
577
581
  }
578
-
582
+
579
583
  devPort = portResponse.port;
580
584
  }
581
-
585
+
582
586
  console.log("");
583
587
  const proxyPort = devPort + 1000; // Use port 1000 higher for proxy
584
588
 
@@ -608,7 +612,7 @@ async function main() {
608
612
  stdio: "pipe",
609
613
  shell: false
610
614
  });
611
- staticServerProcess.on("error", () => {});
615
+ staticServerProcess.on("error", () => { });
612
616
  const ready = await waitForServerReady(devPort, 10000);
613
617
  if (!ready) {
614
618
  if (staticServerProcess) staticServerProcess.kill();
@@ -10,17 +10,17 @@
10
10
  content="dev tunnel, localhost tunnel, cloudflare, ngrok, port forwarding, local development, vite, react, nextjs, laravel, php, html, npm install, devtunnel">
11
11
  <meta name="author" content="maiz">
12
12
  <meta name="robots" content="index, follow">
13
- <link rel="canonical" href="https://maizan.me/devtunnel-cli/">
13
+ <link rel="canonical" href="https://devtunnel-cli.vercel.app/">
14
14
  <meta property="og:title" content="DevTunnel - Share Local Servers Worldwide">
15
15
  <meta property="og:description"
16
16
  content="Zero configuration tunnel for any framework. Install via npm: npm install -g devtunnel-cli">
17
- <meta property="og:url" content="https://maizan.me/devtunnel-cli/">
17
+ <meta property="og:url" content="https://devtunnel-cli.vercel.app/">
18
18
  <meta property="og:type" content="website">
19
- <meta property="og:image" content="https://maizan.me/devtunnel-cli/og-image.png">
19
+ <meta property="og:image" content="https://devtunnel-cli.vercel.app/og-image.png">
20
20
  <meta name="twitter:card" content="summary_large_image">
21
21
  <meta name="twitter:title" content="DevTunnel - Share Local Servers Worldwide">
22
22
  <meta name="twitter:description" content="Zero configuration tunnel for any framework">
23
- <meta name="twitter:image" content="https://maizan.me/devtunnel-cli/og-image.png">
23
+ <meta name="twitter:image" content="https://devtunnel-cli.vercel.app/og-image.png">
24
24
  <title>DevTunnel - Share Local Servers Worldwide</title>
25
25
  <style>
26
26
  body {
@@ -77,7 +77,7 @@
77
77
 
78
78
  <div class="links">
79
79
  <a href="https://www.npmjs.com/package/devtunnel-cli" class="badge">📦 npm Package</a>
80
- <a href="https://maizan.me/devtunnel-cli" class="badge">🌐 Website</a>
80
+ <a href="https://devtunnel-cli.vercel.app" class="badge">🌐 Website</a>
81
81
  <a href="https://github.com/maiz-an/DevTunnel-CLI" class="badge">💻 GitHub</a>
82
82
  </div>
83
83
 
@@ -106,13 +106,13 @@ devtunnel-cli</code></pre>
106
106
  <h2>🔗 Links</h2>
107
107
  <ul>
108
108
  <li><a href="https://www.npmjs.com/package/devtunnel-cli">npm Package (devtunnel-cli)</a></li>
109
- <li><a href="https://maizan.me/devtunnel-cli">Official Website</a></li>
109
+ <li><a href="https://devtunnel-cli.vercel.app">Official Website</a></li>
110
110
  <li><a href="https://github.com/maiz-an/DevTunnel-CLI">GitHub Repository</a></li>
111
111
  <li><a href="https://github.com/maiz-an/DevTunnel-CLI/issues">Report Issues</a></li>
112
112
  </ul>
113
113
 
114
114
  <hr>
115
- <p><small>Version 3.0.35 | Made with ❤︎ for developers worldwide</small></p>
115
+ <p><small>Version 3.0.38 | Made with ❤︎ for developers worldwide</small></p>
116
116
  </body>
117
117
 
118
118
  </html>