devtunnel-cli 3.0.3 → 3.0.5
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 +1 -1
- package/src/core/start.js +221 -220
package/package.json
CHANGED
package/src/core/start.js
CHANGED
|
@@ -1,220 +1,221 @@
|
|
|
1
|
-
import { spawn } from "child_process";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { join, dirname, basename } from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import prompts from "prompts";
|
|
6
|
-
import { selectFolder } from "../utils/folder-picker.js";
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = dirname(__filename);
|
|
10
|
-
|
|
11
|
-
// Get project root directory dynamically (two levels up from src/core/)
|
|
12
|
-
const PROJECT_ROOT = dirname(dirname(__dirname));
|
|
13
|
-
|
|
14
|
-
// Helper to run command
|
|
15
|
-
function runCommand(command, args = [], cwd = process.cwd()) {
|
|
16
|
-
return new Promise((resolve) => {
|
|
17
|
-
const proc = spawn(command, args, {
|
|
18
|
-
shell: true,
|
|
19
|
-
stdio: "pipe",
|
|
20
|
-
cwd: cwd
|
|
21
|
-
});
|
|
22
|
-
let output = "";
|
|
23
|
-
|
|
24
|
-
proc.stdout?.on("data", (data) => output += data.toString());
|
|
25
|
-
proc.stderr?.on("data", (data) => output += data.toString());
|
|
26
|
-
|
|
27
|
-
proc.on("close", (code) => resolve({ code, output }));
|
|
28
|
-
proc.on("error", () => resolve({ code: 1, output: "" }));
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Check if command exists
|
|
33
|
-
async function commandExists(command) {
|
|
34
|
-
const result = await runCommand("where", [command]);
|
|
35
|
-
return result.code === 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ASCII Logo - Compatible with all OS and terminals
|
|
39
|
-
function showLogo() {
|
|
40
|
-
console.log("");
|
|
41
|
-
console.log("
|
|
42
|
-
console.log("
|
|
43
|
-
console.log("
|
|
44
|
-
console.log("
|
|
45
|
-
console.log("
|
|
46
|
-
console.log("
|
|
47
|
-
console.log("
|
|
48
|
-
console.log("
|
|
49
|
-
console.log("");
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
console.log("
|
|
64
|
-
console.log("
|
|
65
|
-
console.log("
|
|
66
|
-
console.log("
|
|
67
|
-
console.log("
|
|
68
|
-
console.log("
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.log("
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.log("
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
console.log("
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.log("
|
|
117
|
-
console.log("
|
|
118
|
-
console.log("
|
|
119
|
-
console.log("");
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
console.log("
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
console.log("
|
|
171
|
-
console.log(
|
|
172
|
-
console.log(`
|
|
173
|
-
console.log(`
|
|
174
|
-
console.log(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
process.on("
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join, dirname, basename } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import prompts from "prompts";
|
|
6
|
+
import { selectFolder } from "../utils/folder-picker.js";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// Get project root directory dynamically (two levels up from src/core/)
|
|
12
|
+
const PROJECT_ROOT = dirname(dirname(__dirname));
|
|
13
|
+
|
|
14
|
+
// Helper to run command
|
|
15
|
+
function runCommand(command, args = [], cwd = process.cwd()) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const proc = spawn(command, args, {
|
|
18
|
+
shell: true,
|
|
19
|
+
stdio: "pipe",
|
|
20
|
+
cwd: cwd
|
|
21
|
+
});
|
|
22
|
+
let output = "";
|
|
23
|
+
|
|
24
|
+
proc.stdout?.on("data", (data) => output += data.toString());
|
|
25
|
+
proc.stderr?.on("data", (data) => output += data.toString());
|
|
26
|
+
|
|
27
|
+
proc.on("close", (code) => resolve({ code, output }));
|
|
28
|
+
proc.on("error", () => resolve({ code: 1, output: "" }));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if command exists
|
|
33
|
+
async function commandExists(command) {
|
|
34
|
+
const result = await runCommand("where", [command]);
|
|
35
|
+
return result.code === 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ASCII Logo - Compatible with all OS and terminals
|
|
39
|
+
function showLogo() {
|
|
40
|
+
console.log("");
|
|
41
|
+
console.log(" ");
|
|
42
|
+
console.log(" ▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄ ");
|
|
43
|
+
console.log(" ██▀▀▀██ ▀▀▀██▀▀▀ ▀▀██ ");
|
|
44
|
+
console.log(" ██ ██ ▄████▄ ██▄ ▄██ ██ ██ ██ ██▄████▄ ██▄████▄ ▄████▄ ██ ");
|
|
45
|
+
console.log(" ██ ██ ██▄▄▄▄██ ██ ██ ██ ██ ██ ██▀ ██ ██▀ ██ ██▄▄▄▄██ ██ ");
|
|
46
|
+
console.log(" ██ ██ ██▀▀▀▀▀▀ ▀█▄▄█▀ ██ ██ ██ ██ ██ ██ ██ ██▀▀▀▀▀▀ ██ ");
|
|
47
|
+
console.log(" ██▄▄▄██ ▀██▄▄▄▄█ ████ ██ ██▄▄▄███ ██ ██ ██ ██ ▀██▄▄▄▄█ ██▄▄▄ ");
|
|
48
|
+
console.log(" ▀▀▀▀▀ ▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀ ▀▀ ▀▀ ▀▀▀▀▀ ▀▀▀▀ ");
|
|
49
|
+
console.log(" ");
|
|
50
|
+
console.log(" ");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Main function
|
|
54
|
+
async function main() {
|
|
55
|
+
// Clear screen - works on Windows, macOS, Linux
|
|
56
|
+
// ANSI escape codes for clear screen + cursor to top
|
|
57
|
+
process.stdout.write('\x1B[2J\x1B[0f');
|
|
58
|
+
console.clear(); // Fallback for terminals that don't support ANSI
|
|
59
|
+
|
|
60
|
+
// Show ASCII logo
|
|
61
|
+
showLogo();
|
|
62
|
+
|
|
63
|
+
console.log("DevTunnel v3.0.5");
|
|
64
|
+
console.log("Share your local dev servers worldwide");
|
|
65
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
66
|
+
console.log("Developer: maiz");
|
|
67
|
+
console.log("Repository: https://github.com/maiz-an/DevTunnel");
|
|
68
|
+
console.log("Website: https://devtunnel.vercel.app");
|
|
69
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
70
|
+
|
|
71
|
+
// Step 1: Check Node.js
|
|
72
|
+
console.log("[1/4] Checking Node.js...");
|
|
73
|
+
if (!await commandExists("node")) {
|
|
74
|
+
console.log("ERROR: Node.js not found!");
|
|
75
|
+
console.log("Install from: https://nodejs.org/");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
console.log("SUCCESS: Node.js installed\n");
|
|
79
|
+
|
|
80
|
+
// Step 2: Check Cloudflare (bundled or system-installed)
|
|
81
|
+
console.log("[2/4] Checking Cloudflare...");
|
|
82
|
+
|
|
83
|
+
// Import bundled cloudflared helpers
|
|
84
|
+
const { setupCloudflared, hasBundledCloudflared } = await import("./setup-cloudflared.js");
|
|
85
|
+
|
|
86
|
+
let cloudflareAvailable = false;
|
|
87
|
+
|
|
88
|
+
if (hasBundledCloudflared()) {
|
|
89
|
+
console.log("SUCCESS: Using bundled Cloudflare (no install needed)");
|
|
90
|
+
cloudflareAvailable = true;
|
|
91
|
+
} else if (await commandExists("cloudflared")) {
|
|
92
|
+
console.log("SUCCESS: Cloudflare installed on system");
|
|
93
|
+
cloudflareAvailable = true;
|
|
94
|
+
} else {
|
|
95
|
+
console.log("First time setup - Downloading Cloudflare...");
|
|
96
|
+
console.log("This only happens once (~40MB, 10-30 seconds)\n");
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const bundledPath = await setupCloudflared();
|
|
100
|
+
|
|
101
|
+
if (bundledPath) {
|
|
102
|
+
console.log("SUCCESS: Cloudflare ready to use");
|
|
103
|
+
cloudflareAvailable = true;
|
|
104
|
+
} else {
|
|
105
|
+
console.log("Could not download Cloudflare");
|
|
106
|
+
console.log("Will use alternative tunnel services\n");
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.log(`Setup error: ${err.message}`);
|
|
110
|
+
console.log("Will use alternative tunnel services\n");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Show what's available
|
|
115
|
+
if (!cloudflareAvailable) {
|
|
116
|
+
console.log("DevTunnel has multi-service fallback:");
|
|
117
|
+
console.log(" Cloudflare (fastest, no password)");
|
|
118
|
+
console.log(" Ngrok (fast alternative)");
|
|
119
|
+
console.log(" LocalTunnel (backup option)");
|
|
120
|
+
console.log("");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Step 3: Check dependencies
|
|
124
|
+
console.log("[3/4] Checking dependencies...");
|
|
125
|
+
const nodeModulesPath = join(PROJECT_ROOT, "node_modules");
|
|
126
|
+
if (!existsSync(nodeModulesPath)) {
|
|
127
|
+
console.log("Installing dependencies...\n");
|
|
128
|
+
// Run npm install in the project root directory
|
|
129
|
+
const result = await runCommand("npm", ["install"], PROJECT_ROOT);
|
|
130
|
+
if (result.code !== 0) {
|
|
131
|
+
console.log("\nERROR: npm install failed");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
console.log("\nSUCCESS: Dependencies installed");
|
|
135
|
+
} else {
|
|
136
|
+
console.log("SUCCESS: Dependencies already installed");
|
|
137
|
+
}
|
|
138
|
+
console.log("");
|
|
139
|
+
|
|
140
|
+
// Step 4: Select folder using native OS dialog
|
|
141
|
+
console.log("[4/4] Select your project folder...");
|
|
142
|
+
console.log("Opening folder picker...\n");
|
|
143
|
+
|
|
144
|
+
const projectPath = await selectFolder();
|
|
145
|
+
|
|
146
|
+
if (!projectPath || projectPath.length === 0) {
|
|
147
|
+
console.log("ERROR: No folder selected");
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const projectName = basename(projectPath);
|
|
152
|
+
console.log(`Selected: ${projectPath}\n`);
|
|
153
|
+
|
|
154
|
+
// Get port
|
|
155
|
+
const portResponse = await prompts({
|
|
156
|
+
type: "number",
|
|
157
|
+
name: "port",
|
|
158
|
+
message: "Enter your dev server port:",
|
|
159
|
+
initial: 5173
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!portResponse.port) {
|
|
163
|
+
console.log("ERROR: No port entered");
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const devPort = portResponse.port;
|
|
168
|
+
const proxyPort = devPort + 1000; // Use port 1000 higher for proxy
|
|
169
|
+
|
|
170
|
+
console.log("\nConfiguration:");
|
|
171
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
172
|
+
console.log(`Project: ${projectName}`);
|
|
173
|
+
console.log(`Dev Server: localhost:${devPort}`);
|
|
174
|
+
console.log(`Proxy Port: ${proxyPort}`);
|
|
175
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
176
|
+
|
|
177
|
+
// Start proxy server
|
|
178
|
+
console.log("Starting services...\n");
|
|
179
|
+
const proxyPath = join(__dirname, "proxy-server.js");
|
|
180
|
+
const proxyProcess = spawn("node", [proxyPath, devPort.toString(), proxyPort.toString(), projectName], {
|
|
181
|
+
stdio: "inherit",
|
|
182
|
+
shell: false
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Wait for proxy to start
|
|
186
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
187
|
+
|
|
188
|
+
// Run main tunnel app (connects to proxy port)
|
|
189
|
+
// Use shell: false to properly handle paths with spaces
|
|
190
|
+
const indexPath = join(__dirname, "index.js");
|
|
191
|
+
const tunnelProcess = spawn("node", [indexPath, proxyPort.toString(), projectName, projectPath], {
|
|
192
|
+
stdio: "inherit",
|
|
193
|
+
shell: false
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Handle cleanup
|
|
197
|
+
const cleanup = () => {
|
|
198
|
+
console.log("\nShutting down...");
|
|
199
|
+
proxyProcess.kill();
|
|
200
|
+
tunnelProcess.kill();
|
|
201
|
+
process.exit(0);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
tunnelProcess.on("close", (code) => {
|
|
205
|
+
cleanup();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
proxyProcess.on("close", () => {
|
|
209
|
+
cleanup();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Handle Ctrl+C
|
|
213
|
+
process.on("SIGINT", cleanup);
|
|
214
|
+
process.on("SIGTERM", cleanup);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Run
|
|
218
|
+
main().catch((error) => {
|
|
219
|
+
console.error("\nERROR:", error.message);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|