devtunnel-cli 3.0.44 → 3.1.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/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.44";
20
+ return pkg.version || "3.1.0";
21
21
  }
22
22
  } catch (err) { }
23
- return "3.0.44";
23
+ return "3.1.0";
24
24
  }
25
25
 
26
26
  // Helper to run command
@@ -1,109 +1,109 @@
1
- /**
2
- * Minimal static file server for HTML projects.
3
- * Used only when devtunnel detects an HTML project and no server is running.
4
- * Usage: node static-server.js <directory> <port>
5
- */
6
- import http from "http";
7
- import fs from "fs";
8
- import path from "path";
9
- const ROOT = path.resolve(process.argv[2] || ".");
10
- const PORT = parseInt(process.argv[3] || "5500", 10);
11
-
12
- const MIME = {
13
- ".html": "text/html",
14
- ".htm": "text/html",
15
- ".css": "text/css",
16
- ".js": "application/javascript",
17
- ".json": "application/json",
18
- ".png": "image/png",
19
- ".jpg": "image/jpeg",
20
- ".jpeg": "image/jpeg",
21
- ".gif": "image/gif",
22
- ".ico": "image/x-icon",
23
- ".svg": "image/svg+xml",
24
- ".webp": "image/webp",
25
- ".woff": "font/woff",
26
- ".woff2": "font/woff2",
27
- ".ttf": "font/ttf",
28
- ".txt": "text/plain",
29
- };
30
-
31
- const server = http.createServer((req, res) => {
32
- if (req.method !== "GET" && req.method !== "HEAD") {
33
- res.writeHead(405, { "Content-Type": "text/plain" });
34
- res.end("Method Not Allowed");
35
- return;
36
- }
37
- // Use only pathname (strip query string and hash) so /style.css?v=1 resolves to style.css
38
- const pathname = (req.url || "/").split("?")[0].split("#")[0];
39
- // Forward slashes only so /css/style.css works on Windows (path.normalize can break with \)
40
- const relative = (pathname || "/").replace(/^\/+/, "").replace(/\\/g, "/") || ".";
41
- let p = path.join(ROOT, relative);
42
- if (!path.isAbsolute(p)) p = path.join(ROOT, p);
43
- if (!p.startsWith(ROOT)) {
44
- res.writeHead(403, { "Content-Type": "text/plain" });
45
- res.end("Forbidden");
46
- return;
47
- }
48
- fs.stat(p, (err, stat) => {
49
- if (err) {
50
- res.writeHead(404, { "Content-Type": "text/plain" });
51
- res.end("Not Found");
52
- return;
53
- }
54
- if (stat.isDirectory()) {
55
- p = path.join(p, "index.html");
56
- return fs.stat(p, (e2, s2) => {
57
- if (e2 || !s2.isFile()) {
58
- res.writeHead(404, { "Content-Type": "text/plain" });
59
- res.end("Not Found");
60
- return;
61
- }
62
- serveFile(p, s2, req, res);
63
- });
64
- }
65
- serveFile(p, stat, req, res);
66
- });
67
- });
68
-
69
- function serveFile(filePath, stat, req, res) {
70
- const ext = path.extname(filePath);
71
- const contentType = MIME[ext] || "application/octet-stream";
72
- res.setHeader("Content-Type", contentType);
73
- if (req.method === "HEAD") {
74
- res.setHeader("Content-Length", stat.size);
75
- res.end();
76
- return;
77
- }
78
- // For HTML: rewrite absolute localhost URLs so CSS/JS work when viewed through tunnel
79
- if (ext === ".html" || ext === ".htm") {
80
- try {
81
- let body = fs.readFileSync(filePath, "utf8");
82
- body = body.replace(/https?:\/\/127\.0\.0\.1:\d+\//g, "/");
83
- body = body.replace(/https?:\/\/localhost:\d+\//g, "/");
84
- res.setHeader("Content-Length", Buffer.byteLength(body, "utf8"));
85
- res.end(body);
86
- return;
87
- } catch (err) {
88
- res.writeHead(500, { "Content-Type": "text/plain" });
89
- res.end("Internal Server Error");
90
- return;
91
- }
92
- }
93
- res.setHeader("Content-Length", stat.size);
94
- const stream = fs.createReadStream(filePath);
95
- stream.on("error", () => {
96
- if (!res.headersSent) {
97
- res.writeHead(500, { "Content-Type": "text/plain" });
98
- res.end("Internal Server Error");
99
- }
100
- });
101
- stream.pipe(res);
102
- }
103
-
104
- server.listen(PORT, "127.0.0.1", () => {
105
- // Server ready; no console output to avoid cluttering devtunnel output
106
- });
107
- server.on("error", (err) => {
108
- process.exit(1);
109
- });
1
+ /**
2
+ * Minimal static file server for HTML projects.
3
+ * Used only when devtunnel detects an HTML project and no server is running.
4
+ * Usage: node static-server.js <directory> <port>
5
+ */
6
+ import http from "http";
7
+ import fs from "fs";
8
+ import path from "path";
9
+ const ROOT = path.resolve(process.argv[2] || ".");
10
+ const PORT = parseInt(process.argv[3] || "5500", 10);
11
+
12
+ const MIME = {
13
+ ".html": "text/html",
14
+ ".htm": "text/html",
15
+ ".css": "text/css",
16
+ ".js": "application/javascript",
17
+ ".json": "application/json",
18
+ ".png": "image/png",
19
+ ".jpg": "image/jpeg",
20
+ ".jpeg": "image/jpeg",
21
+ ".gif": "image/gif",
22
+ ".ico": "image/x-icon",
23
+ ".svg": "image/svg+xml",
24
+ ".webp": "image/webp",
25
+ ".woff": "font/woff",
26
+ ".woff2": "font/woff2",
27
+ ".ttf": "font/ttf",
28
+ ".txt": "text/plain",
29
+ };
30
+
31
+ const server = http.createServer((req, res) => {
32
+ if (req.method !== "GET" && req.method !== "HEAD") {
33
+ res.writeHead(405, { "Content-Type": "text/plain" });
34
+ res.end("Method Not Allowed");
35
+ return;
36
+ }
37
+ // Use only pathname (strip query string and hash) so /style.css?v=1 resolves to style.css
38
+ const pathname = (req.url || "/").split("?")[0].split("#")[0];
39
+ // Forward slashes only so /css/style.css works on Windows (path.normalize can break with \)
40
+ const relative = (pathname || "/").replace(/^\/+/, "").replace(/\\/g, "/") || ".";
41
+ let p = path.join(ROOT, relative);
42
+ if (!path.isAbsolute(p)) p = path.join(ROOT, p);
43
+ if (!p.startsWith(ROOT)) {
44
+ res.writeHead(403, { "Content-Type": "text/plain" });
45
+ res.end("Forbidden");
46
+ return;
47
+ }
48
+ fs.stat(p, (err, stat) => {
49
+ if (err) {
50
+ res.writeHead(404, { "Content-Type": "text/plain" });
51
+ res.end("Not Found");
52
+ return;
53
+ }
54
+ if (stat.isDirectory()) {
55
+ p = path.join(p, "index.html");
56
+ return fs.stat(p, (e2, s2) => {
57
+ if (e2 || !s2.isFile()) {
58
+ res.writeHead(404, { "Content-Type": "text/plain" });
59
+ res.end("Not Found");
60
+ return;
61
+ }
62
+ serveFile(p, s2, req, res);
63
+ });
64
+ }
65
+ serveFile(p, stat, req, res);
66
+ });
67
+ });
68
+
69
+ function serveFile(filePath, stat, req, res) {
70
+ const ext = path.extname(filePath);
71
+ const contentType = MIME[ext] || "application/octet-stream";
72
+ res.setHeader("Content-Type", contentType);
73
+ if (req.method === "HEAD") {
74
+ res.setHeader("Content-Length", stat.size);
75
+ res.end();
76
+ return;
77
+ }
78
+ // For HTML: rewrite absolute localhost URLs so CSS/JS work when viewed through tunnel
79
+ if (ext === ".html" || ext === ".htm") {
80
+ try {
81
+ let body = fs.readFileSync(filePath, "utf8");
82
+ body = body.replace(/https?:\/\/127\.0\.0\.1:\d+\//g, "/");
83
+ body = body.replace(/https?:\/\/localhost:\d+\//g, "/");
84
+ res.setHeader("Content-Length", Buffer.byteLength(body, "utf8"));
85
+ res.end(body);
86
+ return;
87
+ } catch (err) {
88
+ res.writeHead(500, { "Content-Type": "text/plain" });
89
+ res.end("Internal Server Error");
90
+ return;
91
+ }
92
+ }
93
+ res.setHeader("Content-Length", stat.size);
94
+ const stream = fs.createReadStream(filePath);
95
+ stream.on("error", () => {
96
+ if (!res.headersSent) {
97
+ res.writeHead(500, { "Content-Type": "text/plain" });
98
+ res.end("Internal Server Error");
99
+ }
100
+ });
101
+ stream.pipe(res);
102
+ }
103
+
104
+ server.listen(PORT, "127.0.0.1", () => {
105
+ // Server ready; no console output to avoid cluttering devtunnel output
106
+ });
107
+ server.on("error", (err) => {
108
+ process.exit(1);
109
+ });
@@ -1,140 +1,140 @@
1
- import { spawn } from "child_process";
2
- import { platform } from "os";
3
- import { writeFileSync, readFileSync, unlinkSync, existsSync } from "fs";
4
- import { join } from "path";
5
- import { tmpdir } from "os";
6
-
7
- // Cross-platform native folder picker
8
- export async function selectFolder() {
9
- const os = platform();
10
- const tempFile = join(tmpdir(), `folder-picker-${Date.now()}.txt`);
11
-
12
- try {
13
- if (os === "win32") {
14
- // Windows - Use MODERN OpenFileDialog (like website file uploads)
15
- const script = `
16
- Add-Type -AssemblyName System.Windows.Forms
17
- [System.Windows.Forms.Application]::EnableVisualStyles()
18
-
19
- $dialog = New-Object System.Windows.Forms.OpenFileDialog
20
- $dialog.Title = "Select your project folder"
21
- $dialog.Filter = "All files (*.*)|*.*"
22
- $dialog.CheckFileExists = $false
23
- $dialog.CheckPathExists = $true
24
- $dialog.ValidateNames = $false
25
- $dialog.FileName = "Folder Selection"
26
- $dialog.Multiselect = $false
27
- $dialog.InitialDirectory = [Environment]::GetFolderPath("UserProfile")
28
-
29
- $result = $dialog.ShowDialog()
30
- if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
31
- $folderPath = Split-Path -Parent $dialog.FileName
32
- if (-not $folderPath) {
33
- $folderPath = $dialog.FileName
34
- }
35
- $folderPath | Out-File -FilePath "${tempFile.replace(/\\/g, '\\\\')}" -Encoding UTF8 -NoNewline
36
- }
37
- `;
38
-
39
- await runPowerShell(script);
40
-
41
- } else if (os === "darwin") {
42
- // macOS - Use osascript
43
- const script = `
44
- set folderPath to choose folder with prompt "Select your project folder"
45
- set posixPath to POSIX path of folderPath
46
- do shell script "echo " & quoted form of posixPath & " > '${tempFile}'"
47
- `;
48
-
49
- await runCommand("osascript", ["-e", script]);
50
-
51
- } else {
52
- // Linux - Try zenity first, then kdialog
53
- try {
54
- await runCommand("zenity", [
55
- "--file-selection",
56
- "--directory",
57
- "--title=Select your project folder"
58
- ], tempFile);
59
- } catch {
60
- await runCommand("kdialog", [
61
- "--getexistingdirectory",
62
- process.env.HOME || "/",
63
- "--title", "Select your project folder"
64
- ], tempFile);
65
- }
66
- }
67
-
68
- // Read the selected folder
69
- if (existsSync(tempFile)) {
70
- const folderPath = readFileSync(tempFile, "utf8").trim();
71
- unlinkSync(tempFile);
72
- return folderPath;
73
- }
74
-
75
- return null;
76
-
77
- } catch (error) {
78
- console.error("Folder picker error:", error.message);
79
- return null;
80
- }
81
- }
82
-
83
- // Run PowerShell command
84
- function runPowerShell(script) {
85
- return new Promise((resolve, reject) => {
86
- const proc = spawn("powershell", [
87
- "-NoProfile",
88
- "-NonInteractive",
89
- "-ExecutionPolicy", "Bypass",
90
- "-Command", script
91
- ], {
92
- stdio: ["ignore", "pipe", "pipe"],
93
- shell: false
94
- });
95
-
96
- let stderr = "";
97
- proc.stderr?.on("data", (data) => stderr += data.toString());
98
-
99
- proc.on("close", (code) => {
100
- if (code === 0) {
101
- resolve();
102
- } else {
103
- reject(new Error(stderr || `PowerShell exited with code ${code}`));
104
- }
105
- });
106
-
107
- proc.on("error", reject);
108
- });
109
- }
110
-
111
- // Run generic command
112
- function runCommand(command, args, outputFile) {
113
- return new Promise((resolve, reject) => {
114
- const proc = spawn(command, args, {
115
- stdio: outputFile ? ["ignore", "pipe", "pipe"] : "pipe",
116
- shell: true
117
- });
118
-
119
- let stdout = "";
120
-
121
- if (outputFile) {
122
- proc.stdout?.on("data", (data) => {
123
- stdout += data.toString();
124
- });
125
- }
126
-
127
- proc.on("close", (code) => {
128
- if (code === 0) {
129
- if (outputFile && stdout) {
130
- writeFileSync(outputFile, stdout.trim());
131
- }
132
- resolve();
133
- } else {
134
- reject(new Error(`Command failed with code ${code}`));
135
- }
136
- });
137
-
138
- proc.on("error", reject);
139
- });
140
- }
1
+ import { spawn } from "child_process";
2
+ import { platform } from "os";
3
+ import { writeFileSync, readFileSync, unlinkSync, existsSync } from "fs";
4
+ import { join } from "path";
5
+ import { tmpdir } from "os";
6
+
7
+ // Cross-platform native folder picker
8
+ export async function selectFolder() {
9
+ const os = platform();
10
+ const tempFile = join(tmpdir(), `folder-picker-${Date.now()}.txt`);
11
+
12
+ try {
13
+ if (os === "win32") {
14
+ // Windows - Use MODERN OpenFileDialog (like website file uploads)
15
+ const script = `
16
+ Add-Type -AssemblyName System.Windows.Forms
17
+ [System.Windows.Forms.Application]::EnableVisualStyles()
18
+
19
+ $dialog = New-Object System.Windows.Forms.OpenFileDialog
20
+ $dialog.Title = "Select your project folder"
21
+ $dialog.Filter = "All files (*.*)|*.*"
22
+ $dialog.CheckFileExists = $false
23
+ $dialog.CheckPathExists = $true
24
+ $dialog.ValidateNames = $false
25
+ $dialog.FileName = "Folder Selection"
26
+ $dialog.Multiselect = $false
27
+ $dialog.InitialDirectory = [Environment]::GetFolderPath("UserProfile")
28
+
29
+ $result = $dialog.ShowDialog()
30
+ if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
31
+ $folderPath = Split-Path -Parent $dialog.FileName
32
+ if (-not $folderPath) {
33
+ $folderPath = $dialog.FileName
34
+ }
35
+ $folderPath | Out-File -FilePath "${tempFile.replace(/\\/g, '\\\\')}" -Encoding UTF8 -NoNewline
36
+ }
37
+ `;
38
+
39
+ await runPowerShell(script);
40
+
41
+ } else if (os === "darwin") {
42
+ // macOS - Use osascript
43
+ const script = `
44
+ set folderPath to choose folder with prompt "Select your project folder"
45
+ set posixPath to POSIX path of folderPath
46
+ do shell script "echo " & quoted form of posixPath & " > '${tempFile}'"
47
+ `;
48
+
49
+ await runCommand("osascript", ["-e", script]);
50
+
51
+ } else {
52
+ // Linux - Try zenity first, then kdialog
53
+ try {
54
+ await runCommand("zenity", [
55
+ "--file-selection",
56
+ "--directory",
57
+ "--title=Select your project folder"
58
+ ], tempFile);
59
+ } catch {
60
+ await runCommand("kdialog", [
61
+ "--getexistingdirectory",
62
+ process.env.HOME || "/",
63
+ "--title", "Select your project folder"
64
+ ], tempFile);
65
+ }
66
+ }
67
+
68
+ // Read the selected folder
69
+ if (existsSync(tempFile)) {
70
+ const folderPath = readFileSync(tempFile, "utf8").trim();
71
+ unlinkSync(tempFile);
72
+ return folderPath;
73
+ }
74
+
75
+ return null;
76
+
77
+ } catch (error) {
78
+ console.error("Folder picker error:", error.message);
79
+ return null;
80
+ }
81
+ }
82
+
83
+ // Run PowerShell command
84
+ function runPowerShell(script) {
85
+ return new Promise((resolve, reject) => {
86
+ const proc = spawn("powershell", [
87
+ "-NoProfile",
88
+ "-NonInteractive",
89
+ "-ExecutionPolicy", "Bypass",
90
+ "-Command", script
91
+ ], {
92
+ stdio: ["ignore", "pipe", "pipe"],
93
+ shell: false
94
+ });
95
+
96
+ let stderr = "";
97
+ proc.stderr?.on("data", (data) => stderr += data.toString());
98
+
99
+ proc.on("close", (code) => {
100
+ if (code === 0) {
101
+ resolve();
102
+ } else {
103
+ reject(new Error(stderr || `PowerShell exited with code ${code}`));
104
+ }
105
+ });
106
+
107
+ proc.on("error", reject);
108
+ });
109
+ }
110
+
111
+ // Run generic command
112
+ function runCommand(command, args, outputFile) {
113
+ return new Promise((resolve, reject) => {
114
+ const proc = spawn(command, args, {
115
+ stdio: outputFile ? ["ignore", "pipe", "pipe"] : "pipe",
116
+ shell: true
117
+ });
118
+
119
+ let stdout = "";
120
+
121
+ if (outputFile) {
122
+ proc.stdout?.on("data", (data) => {
123
+ stdout += data.toString();
124
+ });
125
+ }
126
+
127
+ proc.on("close", (code) => {
128
+ if (code === 0) {
129
+ if (outputFile && stdout) {
130
+ writeFileSync(outputFile, stdout.trim());
131
+ }
132
+ resolve();
133
+ } else {
134
+ reject(new Error(`Command failed with code ${code}`));
135
+ }
136
+ });
137
+
138
+ proc.on("error", reject);
139
+ });
140
+ }
@@ -112,7 +112,7 @@ devtunnel-cli</code></pre>
112
112
  </ul>
113
113
 
114
114
  <hr>
115
- <p><small>Version <span id="pkg-version">3.0.44</span> | Made with ❤︎ for developers worldwide</small></p>
115
+ <p><small>Version <span id="pkg-version">3.1.0</span> | Made with ❤︎ for developers worldwide</small></p>
116
116
 
117
117
  <script>
118
118
  (async function () {
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // Warn if installed locally (without -g). For security, devtunnel-cli should be installed globally.
4
+ const path = require('path');
5
+ try {
6
+ const dir = __dirname; // e.g. .../node_modules/devtunnel-cli/src/utils
7
+ const inNodeModules = dir.includes('node_modules');
8
+ const npmPrefix = process.env.npm_config_prefix || (process.platform === 'win32' ? (process.env.APPDATA || '') + '\\npm' : '/usr/local');
9
+ const globalNodeModules = path.join(npmPrefix, 'node_modules');
10
+ const isGlobal = inNodeModules && (dir.startsWith(globalNodeModules) || path.resolve(dir).startsWith(path.resolve(globalNodeModules)));
11
+ if (inNodeModules && !isGlobal) {
12
+ console.error('\n \x1b[33mWARN: devtunnel-cli should be installed globally to avoid vulnerabilities.\x1b[0m');
13
+ console.error(' Run: \x1b[1mnpm i -g devtunnel-cli\x1b[0m\n');
14
+ }
15
+ } catch (e) { /* ignore */ }
@@ -1,35 +1,35 @@
1
- // Helper functions for different tunnel services
2
-
3
- export async function startLocalTunnel(port) {
4
- try {
5
- const localtunnel = await import("localtunnel");
6
- const tunnel = await localtunnel.default({ port });
7
-
8
- console.log("\n" + "=".repeat(50));
9
- console.log("✅ PUBLIC URL:");
10
- console.log(` ${tunnel.url}`);
11
- console.log("=".repeat(50) + "\n");
12
-
13
- tunnel.on("close", () => {
14
- console.log("\n🛑 Tunnel closed");
15
- process.exit(0);
16
- });
17
-
18
- // Keep the process running
19
- process.on("SIGINT", () => {
20
- tunnel.close();
21
- });
22
-
23
- } catch (error) {
24
- console.error("❌ LocalTunnel error:", error.message);
25
- process.exit(1);
26
- }
27
- }
28
-
29
- // If called directly from command line
30
- if (process.argv[2] === "localtunnel") {
31
- const port = parseInt(process.argv[3]);
32
- if (port) {
33
- startLocalTunnel(port);
34
- }
35
- }
1
+ // Helper functions for different tunnel services
2
+
3
+ export async function startLocalTunnel(port) {
4
+ try {
5
+ const localtunnel = await import("localtunnel");
6
+ const tunnel = await localtunnel.default({ port });
7
+
8
+ console.log("\n" + "=".repeat(50));
9
+ console.log("✅ PUBLIC URL:");
10
+ console.log(` ${tunnel.url}`);
11
+ console.log("=".repeat(50) + "\n");
12
+
13
+ tunnel.on("close", () => {
14
+ console.log("\n🛑 Tunnel closed");
15
+ process.exit(0);
16
+ });
17
+
18
+ // Keep the process running
19
+ process.on("SIGINT", () => {
20
+ tunnel.close();
21
+ });
22
+
23
+ } catch (error) {
24
+ console.error("❌ LocalTunnel error:", error.message);
25
+ process.exit(1);
26
+ }
27
+ }
28
+
29
+ // If called directly from command line
30
+ if (process.argv[2] === "localtunnel") {
31
+ const port = parseInt(process.argv[3]);
32
+ if (port) {
33
+ startLocalTunnel(port);
34
+ }
35
+ }