devtunnel-cli 3.0.39 → 3.0.41

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
@@ -14,10 +14,7 @@
14
14
 
15
15
  DevTunnel-CLI is designed for **DEVELOPMENT**, **TESTING**, **DEMOS**, and **WEBHOOK DEBUGGING**. It provides fast, frictionless access to your local dev servers from anywhere.
16
16
 
17
- **This tool is NOT intended for:**
18
- - Production environments
19
- - Long-lived public services
20
- - Hosting production traffic
17
+ DevTunnel-CLI is optimized for development workflows — rapid sharing, demos, and debugging. For production-facing use, consider managed or enterprise tunnel solutions that provide governance, identity, and long-lived endpoints.
21
18
 
22
19
  DevTunnel-CLI is built for developers who need instant, temporary public URLs to share work-in-progress, test on mobile devices, demo features to clients, or debug webhooks from third-party services.
23
20
 
@@ -28,16 +25,19 @@ DevTunnel-CLI is built for developers who need instant, temporary public URLs to
28
25
  ### Step-by-Step Guide
29
26
 
30
27
  **1. Install DevTunnel (one-time setup):**
28
+
31
29
  ```bash
32
- npm install -g devtunnel-cli
30
+ npm i -g devtunnel-cli
33
31
  ```
34
32
 
35
33
  **2. Navigate to your project directory:**
34
+
36
35
  ```bash
37
36
  cd your-project
38
37
  ```
39
38
 
40
39
  **3. Have your app running (in one terminal):**
40
+
41
41
  ```bash
42
42
  npm run dev
43
43
  # OR php artisan serve (Laravel)
@@ -46,6 +46,7 @@ npm run dev
46
46
  ```
47
47
 
48
48
  **4. Run DevTunnel (in another terminal, same directory):**
49
+
49
50
  ```bash
50
51
  cd your-project # Same directory as your project
51
52
  devtunnel-cli # Auto-detects project type and port!
@@ -63,7 +64,7 @@ devtunnel-cli # Auto-detects project type and port!
63
64
  - šŸŒ **Cross-Platform** - Windows, macOS, Linux
64
65
  - šŸš€ **Any Framework** - Node, React, Laravel, plain HTML, PHP/XAMPP
65
66
  - šŸ“„ **HTML** - Default port 5500; built-in static server if none running
66
- - 🐘 **PHP/XAMPP** - Port 80; supports htdocs subfolders (e.g. http://localhost/YourProject/)
67
+ - 🐘 **PHP/XAMPP** - Port 80; supports htdocs subfolders (e.g. <http://localhost/YourProject/>)
67
68
  - šŸ”„ **Multi-Service** - Cloudflare, Ngrok, LocalTunnel fallback
68
69
  - šŸ”Œ **Multiple Ports** - DevTunnel-CLI supports multiple ports; auto-detects or lets you choose
69
70
  - šŸ“¹ **Streaming Support** - Handles video/audio files (with limitations for large files)
@@ -74,7 +75,7 @@ devtunnel-cli # Auto-detects project type and port!
74
75
 
75
76
  **Important:** Run `devtunnel-cli` from the same directory as your project!
76
77
 
77
- 1. **Install DevTunnel** (one-time): `npm install -g devtunnel-cli`
78
+ 1. **Install DevTunnel** (one-time): `npm i -g devtunnel-cli`
78
79
  2. **Go to your project**: `cd your-project` (Node, Laravel, HTML, or XAMPP folder)
79
80
  3. **Have your app running**: `npm run dev`, `php artisan serve`, or XAMPP. For HTML, optional — DevTunnel can serve it.
80
81
  4. **Open a new terminal** in the same project directory
@@ -82,6 +83,7 @@ devtunnel-cli # Auto-detects project type and port!
82
83
  6. **Get your public URL** and share it! šŸŒ
83
84
 
84
85
  **Example (Node):**
86
+
85
87
  ```bash
86
88
  # Terminal 1
87
89
  cd my-react-app
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "devtunnel-cli",
3
- "version": "3.0.39",
3
+ "version": "3.0.41",
4
4
  "type": "module",
5
- "description": "DevTunnel-CLI — fast, zero-config tool to share local servers for development, testing, demos, and webhook debugging. npm install -g devtunnel-cli.",
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",
7
7
  "files": [
8
8
  "README.md",
@@ -16,7 +16,9 @@
16
16
  "dev": "node src/core/RUN.js",
17
17
  "run": "node src/core/RUN.js",
18
18
  "tunnel": "node src/core/index.js",
19
- "sync-version": "node sync-version.js"
19
+ "sync-version": "node sync-version.js",
20
+ "test": "node -e \"console.log('ok')\"",
21
+ "prepublishOnly": "npm test"
20
22
  },
21
23
  "keywords": [
22
24
  "DevTunnel-CLI",
@@ -69,7 +69,7 @@ async function isAdmin() {
69
69
  if (process.platform !== 'win32') {
70
70
  return process.getuid && process.getuid() === 0;
71
71
  }
72
-
72
+
73
73
  try {
74
74
  const { stdout } = await execAsync('net session');
75
75
  return stdout.length > 0;
@@ -84,7 +84,7 @@ function showPermissionSolutions(dirPath) {
84
84
  console.log(' 1. Run terminal as Administrator (Right-click → Run as administrator)');
85
85
  console.log(' 2. DevTunnel will automatically request admin privileges if needed');
86
86
  } else {
87
- console.log(' 1. Run with sudo: sudo npm install -g devtunnel-cli');
87
+ console.log(' 1. Run with sudo: sudo npm i -g devtunnel-cli');
88
88
  }
89
89
  console.log(' 2. Check if antivirus is blocking file writes');
90
90
  console.log(' 3. Check folder permissions for:', dirPath);
@@ -104,10 +104,10 @@ function downloadFile(url, dest, retryCount = 0) {
104
104
  }
105
105
 
106
106
  const tempDest = dest + '.download';
107
-
107
+
108
108
  // Clean up any existing temp file first
109
109
  safeUnlink(tempDest);
110
-
110
+
111
111
  let file;
112
112
  try {
113
113
  file = fs.createWriteStream(tempDest);
@@ -119,9 +119,9 @@ function downloadFile(url, dest, retryCount = 0) {
119
119
  }
120
120
  return;
121
121
  }
122
-
123
- const request = https.get(url, {
124
- headers: {
122
+
123
+ const request = https.get(url, {
124
+ headers: {
125
125
  'User-Agent': 'DevTunnel/3.0',
126
126
  'Accept': '*/*'
127
127
  },
@@ -170,9 +170,9 @@ function downloadFile(url, dest, retryCount = 0) {
170
170
  fs.unlinkSync(dest);
171
171
  }
172
172
  fs.renameSync(tempDest, dest);
173
-
173
+
174
174
  console.log('\nāœ… Download complete');
175
-
175
+
176
176
  // Make executable on Unix-like systems
177
177
  if (process.platform !== 'win32') {
178
178
  try {
@@ -183,7 +183,7 @@ function downloadFile(url, dest, retryCount = 0) {
183
183
  console.log(' Run: chmod +x ' + dest);
184
184
  }
185
185
  }
186
-
186
+
187
187
  // Verify file size
188
188
  const stats = fs.statSync(dest);
189
189
  if (stats.size < 1000000) { // Less than 1MB is suspicious
@@ -191,7 +191,7 @@ function downloadFile(url, dest, retryCount = 0) {
191
191
  reject(new Error('Downloaded file is too small (corrupted)'));
192
192
  return;
193
193
  }
194
-
194
+
195
195
  resolve();
196
196
  } catch (err) {
197
197
  reject(new Error(`Cannot finalize download: ${err.message}`));
@@ -230,30 +230,30 @@ async function downloadWithRetry(urls, dest, maxRetries = 3) {
230
230
  for (let urlIndex = 0; urlIndex < urls.length; urlIndex++) {
231
231
  const url = urls[urlIndex];
232
232
  console.log(`šŸ“„ Source: ${urlIndex === 0 ? 'GitHub' : 'Mirror'} (${urlIndex + 1}/${urls.length})`);
233
-
233
+
234
234
  for (let retry = 0; retry < maxRetries; retry++) {
235
235
  try {
236
236
  if (retry > 0) {
237
237
  console.log(`šŸ”„ Retry ${retry}/${maxRetries - 1}...`);
238
238
  await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s before retry
239
239
  }
240
-
240
+
241
241
  await downloadFile(url, dest, retry);
242
242
  return true; // Success!
243
-
243
+
244
244
  } catch (err) {
245
245
  const isLastRetry = retry === maxRetries - 1;
246
246
  const isLastUrl = urlIndex === urls.length - 1;
247
-
247
+
248
248
  if (err.message.includes('Permission denied') || err.message.includes('EPERM') || err.message.includes('EACCES')) {
249
249
  console.log(`\nāŒ Permission Error: ${err.message}`);
250
-
250
+
251
251
  if (process.platform === 'win32' && retry === 0) {
252
252
  const admin = await isAdmin();
253
253
  if (!admin) {
254
254
  console.log('\nšŸ” Attempting to request administrator privileges...');
255
255
  console.log(' Please click "Yes" on the UAC prompt\n');
256
-
256
+
257
257
  try {
258
258
  const nodePath = process.execPath;
259
259
  const scriptPath = process.argv[1];
@@ -266,16 +266,16 @@ async function downloadWithRetry(urls, dest, maxRetries = 3) {
266
266
  stdio: 'inherit',
267
267
  shell: false
268
268
  });
269
-
269
+
270
270
  proc.on('close', () => {
271
271
  process.exit(0);
272
272
  });
273
-
273
+
274
274
  proc.on('error', () => {
275
275
  console.log('\nāš ļø Could not elevate privileges automatically');
276
276
  showPermissionSolutions(path.dirname(dest));
277
277
  });
278
-
278
+
279
279
  return false;
280
280
  } catch {
281
281
  showPermissionSolutions(path.dirname(dest));
@@ -283,7 +283,7 @@ async function downloadWithRetry(urls, dest, maxRetries = 3) {
283
283
  }
284
284
  }
285
285
  }
286
-
286
+
287
287
  showPermissionSolutions(path.dirname(dest));
288
288
  throw err;
289
289
  } else if (err.message.includes('ENOTFOUND') || err.message.includes('ECONNREFUSED')) {
@@ -293,11 +293,11 @@ async function downloadWithRetry(urls, dest, maxRetries = 3) {
293
293
  } else {
294
294
  console.log(`\nāŒ Error: ${err.message}`);
295
295
  }
296
-
296
+
297
297
  if (isLastRetry && isLastUrl) {
298
298
  throw new Error(`All download attempts failed: ${err.message}`);
299
299
  }
300
-
300
+
301
301
  if (isLastRetry) {
302
302
  console.log('šŸ’” Trying alternative source...\n');
303
303
  break;
@@ -305,18 +305,18 @@ async function downloadWithRetry(urls, dest, maxRetries = 3) {
305
305
  }
306
306
  }
307
307
  }
308
-
308
+
309
309
  throw new Error('All download sources failed');
310
310
  }
311
311
 
312
312
  export async function setupCloudflared() {
313
313
  const platform = process.platform;
314
314
  const binaryPath = getBinaryPath();
315
-
315
+
316
316
  console.log('\n╔════════════════════════════════════════╗');
317
317
  console.log('ā•‘ šŸ“¦ Cloudflare Setup (First Run) ā•‘');
318
318
  console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n');
319
-
319
+
320
320
  // Check if binary already exists
321
321
  if (fs.existsSync(binaryPath)) {
322
322
  try {
@@ -327,7 +327,7 @@ export async function setupCloudflared() {
327
327
  testProc.on('error', () => resolve(false));
328
328
  setTimeout(() => resolve(false), 5000);
329
329
  });
330
-
330
+
331
331
  if (works) {
332
332
  console.log('āœ… Cloudflare already installed and working\n');
333
333
  return binaryPath;
@@ -351,13 +351,13 @@ export async function setupCloudflared() {
351
351
  console.log(`šŸ–„ļø Platform: ${getPlatformName()}`);
352
352
  console.log(`šŸ“ Install to: ${binaryPath}`);
353
353
  console.log(`šŸ“Š Size: ~40 MB\n`);
354
-
354
+
355
355
  // Check disk space
356
356
  if (!hasEnoughDiskSpace()) {
357
357
  console.error('āŒ ERROR: Not enough disk space (need 50+ MB)\n');
358
358
  return null;
359
359
  }
360
-
360
+
361
361
  // Check write permissions
362
362
  try {
363
363
  const dir = path.dirname(binaryPath);
@@ -373,12 +373,12 @@ export async function setupCloudflared() {
373
373
  console.error(` Reason: ${err.message}\n`);
374
374
  return null;
375
375
  }
376
-
376
+
377
377
  console.log('šŸ“„ Starting download...\n');
378
-
378
+
379
379
  try {
380
380
  await downloadWithRetry(urls, binaryPath);
381
-
381
+
382
382
  // Final verification
383
383
  console.log('\nšŸ” Verifying installation...');
384
384
  const testProc = spawn(binaryPath, ['--version'], { shell: true, stdio: 'pipe' });
@@ -387,7 +387,7 @@ export async function setupCloudflared() {
387
387
  testProc.on('error', () => resolve(false));
388
388
  setTimeout(() => resolve(false), 5000);
389
389
  });
390
-
390
+
391
391
  if (works) {
392
392
  console.log('āœ… Verification successful!');
393
393
  console.log('āœ… Cloudflare ready to use\n');
@@ -397,13 +397,13 @@ export async function setupCloudflared() {
397
397
  safeUnlink(binaryPath);
398
398
  return null;
399
399
  }
400
-
400
+
401
401
  } catch (err) {
402
402
  console.error('\n╔════════════════════════════════════════╗');
403
403
  console.error('ā•‘ āŒ Installation Failed ā•‘');
404
404
  console.error('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n');
405
405
  console.error(`Reason: ${err.message}\n`);
406
-
406
+
407
407
  if (err.message.includes('Permission denied') || err.message.includes('EPERM') || err.message.includes('EACCES')) {
408
408
  showPermissionSolutions(path.dirname(binaryPath));
409
409
  } else {
@@ -413,9 +413,9 @@ export async function setupCloudflared() {
413
413
  console.log(' 3. Try running as administrator');
414
414
  console.log(' 4. Install manually: https://github.com/cloudflare/cloudflared/releases\n');
415
415
  }
416
-
416
+
417
417
  console.log('šŸ”„ DevTunnel will use fallback tunnels (Ngrok/LocalTunnel)\n');
418
-
418
+
419
419
  return null;
420
420
  }
421
421
  }
package/src/core/start.js CHANGED
@@ -17,10 +17,10 @@ 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.38";
20
+ return pkg.version || "3.0.41";
21
21
  }
22
22
  } catch (err) { }
23
- return "3.0.38";
23
+ return "3.0.41";
24
24
  }
25
25
 
26
26
  // Helper to run command
@@ -5,7 +5,7 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <meta name="description"
8
- content="DevTunnel - Share local dev servers worldwide. Zero configuration tunnel for any framework. Install via npm: npm install -g devtunnel-cli">
8
+ content="DevTunnel - Share local dev servers worldwide. Zero configuration tunnel for any framework. Install via npm: npm i -g devtunnel-cli">
9
9
  <meta name="keywords"
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">
@@ -13,7 +13,7 @@
13
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
- content="Zero configuration tunnel for any framework. Install via npm: npm install -g devtunnel-cli">
16
+ content="Zero configuration tunnel for any framework. Install via npm: npm i -g devtunnel-cli">
17
17
  <meta property="og:url" content="https://devtunnel-cli.vercel.app/">
18
18
  <meta property="og:type" content="website">
19
19
  <meta property="og:image" content="https://devtunnel-cli.vercel.app/og-image.png">
@@ -83,7 +83,7 @@
83
83
 
84
84
  <h2>⚔ Quick Start</h2>
85
85
  <h3>Install via npm (Recommended)</h3>
86
- <pre><code>npm install -g devtunnel-cli
86
+ <pre><code>npm i -g devtunnel-cli
87
87
  devtunnel-cli</code></pre>
88
88
 
89
89
  <h2>✨ Features</h2>
@@ -112,7 +112,21 @@ devtunnel-cli</code></pre>
112
112
  </ul>
113
113
 
114
114
  <hr>
115
- <p><small>Version 3.0.38 | Made with ā¤ļøŽ for developers worldwide</small></p>
115
+ <p><small>Version <span id="pkg-version">3.0.41</span> | Made with ā¤ļøŽ for developers worldwide</small></p>
116
+
117
+ <script>
118
+ (async function () {
119
+ try {
120
+ const res = await fetch('https://registry.npmjs.org/devtunnel-cli');
121
+ if (!res.ok) return;
122
+ const data = await res.json();
123
+ const latest = data && data['dist-tags'] && data['dist-tags'].latest;
124
+ if (latest) document.getElementById('pkg-version').textContent = latest;
125
+ } catch (e) {
126
+ // keep static version if fetch fails
127
+ }
128
+ })();
129
+ </script>
116
130
  </body>
117
131
 
118
132
  </html>