devtunnel-cli 3.0.45 → 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/README.md +18 -7
- package/package.json +89 -86
- package/src/core/RUN.js +64 -64
- package/src/core/index.js +374 -374
- package/src/core/proxy-server.js +105 -105
- package/src/core/setup-cloudflared.js +427 -427
- package/src/core/start.js +2 -2
- package/src/core/static-server.js +109 -109
- package/src/utils/folder-picker.js +140 -140
- package/src/utils/pages/index.html +1 -1
- package/src/utils/tunnel-helpers.js +35 -35
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
|
|
20
|
+
return pkg.version || "3.1.0";
|
|
21
21
|
}
|
|
22
22
|
} catch (err) { }
|
|
23
|
-
return "3.0
|
|
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
|
|
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 () {
|
|
@@ -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
|
+
}
|