devtunnel-cli 3.0.16 → 3.0.18
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/proxy-server.js +8 -1
- package/src/core/start.js +602 -573
- package/src/core/static-server.js +87 -0
package/src/core/start.js
CHANGED
|
@@ -1,573 +1,602 @@
|
|
|
1
|
-
import { spawn } from "child_process";
|
|
2
|
-
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import { join, dirname, basename } from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import http from "http";
|
|
6
|
-
import prompts from "prompts";
|
|
7
|
-
import { selectFolder } from "../utils/folder-picker.js";
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
|
|
12
|
-
// Get project root directory dynamically (two levels up from src/core/)
|
|
13
|
-
const PROJECT_ROOT = dirname(dirname(__dirname));
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
proc
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
server.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (devScript
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
console.log("");
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
console.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
console.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
console.log(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
console.log("
|
|
240
|
-
console.log("");
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (!
|
|
292
|
-
console.log("
|
|
293
|
-
console.log("");
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
console.log("
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
console.log(
|
|
481
|
-
console.log("");
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (!
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
console.log("");
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
proxyProcess
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
//
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join, dirname, basename } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import http from "http";
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
import { selectFolder } from "../utils/folder-picker.js";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
// Get project root directory dynamically (two levels up from src/core/)
|
|
13
|
+
const PROJECT_ROOT = dirname(dirname(__dirname));
|
|
14
|
+
|
|
15
|
+
function getPackageVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const pkgPath = join(PROJECT_ROOT, "package.json");
|
|
18
|
+
if (existsSync(pkgPath)) {
|
|
19
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
20
|
+
return pkg.version || "3.0.17";
|
|
21
|
+
}
|
|
22
|
+
} catch (err) {}
|
|
23
|
+
return "3.0.17";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Helper to run command
|
|
27
|
+
function runCommand(command, args = [], cwd = process.cwd()) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const proc = spawn(command, args, {
|
|
30
|
+
shell: true,
|
|
31
|
+
stdio: "pipe",
|
|
32
|
+
cwd: cwd
|
|
33
|
+
});
|
|
34
|
+
let output = "";
|
|
35
|
+
|
|
36
|
+
proc.stdout?.on("data", (data) => output += data.toString());
|
|
37
|
+
proc.stderr?.on("data", (data) => output += data.toString());
|
|
38
|
+
|
|
39
|
+
proc.on("close", (code) => resolve({ code, output }));
|
|
40
|
+
proc.on("error", () => resolve({ code: 1, output: "" }));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if command exists
|
|
45
|
+
async function commandExists(command) {
|
|
46
|
+
const result = await runCommand("where", [command]);
|
|
47
|
+
return result.code === 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if a port is in use (dev server running)
|
|
51
|
+
function checkPortInUse(port) {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
const server = http.createServer();
|
|
54
|
+
|
|
55
|
+
server.once('error', (err) => {
|
|
56
|
+
// Port is in use
|
|
57
|
+
if (err.code === 'EADDRINUSE') {
|
|
58
|
+
resolve(true);
|
|
59
|
+
} else {
|
|
60
|
+
resolve(false);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
server.listen(port, () => {
|
|
65
|
+
// Port is available (not in use)
|
|
66
|
+
server.once('close', () => resolve(false));
|
|
67
|
+
server.close();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Detect port from package.json
|
|
73
|
+
function detectPortFromPackage(packagePath) {
|
|
74
|
+
try {
|
|
75
|
+
if (!existsSync(packagePath)) return null;
|
|
76
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
77
|
+
const scripts = packageJson.scripts || {};
|
|
78
|
+
|
|
79
|
+
// Check for common dev commands
|
|
80
|
+
const devScript = scripts.dev || scripts.start || scripts.serve;
|
|
81
|
+
if (!devScript) return null;
|
|
82
|
+
|
|
83
|
+
// Try to extract port from script
|
|
84
|
+
const portMatch = devScript.match(/--port\s+(\d+)|:(\d+)|port[=:](\d+)/i);
|
|
85
|
+
if (portMatch) {
|
|
86
|
+
return parseInt(portMatch[1] || portMatch[2] || portMatch[3]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Default ports based on framework
|
|
90
|
+
if (devScript.includes('vite')) return 5173;
|
|
91
|
+
if (devScript.includes('next')) return 3000;
|
|
92
|
+
if (devScript.includes('react-scripts')) return 3000;
|
|
93
|
+
if (devScript.includes('webpack')) return 8080;
|
|
94
|
+
if (devScript.includes('express')) return 3000;
|
|
95
|
+
|
|
96
|
+
return null;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Detect Laravel/PHP project (composer.json + artisan)
|
|
103
|
+
function detectLaravelProject(currentDir) {
|
|
104
|
+
const composerPath = join(currentDir, "composer.json");
|
|
105
|
+
const artisanPath = join(currentDir, "artisan");
|
|
106
|
+
if (!existsSync(composerPath) || !existsSync(artisanPath)) return null;
|
|
107
|
+
try {
|
|
108
|
+
const composerJson = JSON.parse(readFileSync(composerPath, "utf8"));
|
|
109
|
+
const projectName = (composerJson.name && composerJson.name.replace(/^laravel\//i, "")) || basename(currentDir);
|
|
110
|
+
return { name: projectName, defaultPort: 8000 }; // php artisan serve
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Detect plain HTML project (index.html in root)
|
|
117
|
+
function detectHtmlProject(currentDir) {
|
|
118
|
+
const indexPath = join(currentDir, "index.html");
|
|
119
|
+
if (!existsSync(indexPath)) return null;
|
|
120
|
+
return { name: basename(currentDir), defaultPort: 8080 }; // common for HTML/Live Server/XAMPP
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check common ports for running dev servers (includes Laravel 8000, XAMPP/Live Server 8080/5500)
|
|
124
|
+
async function detectRunningDevServer() {
|
|
125
|
+
const commonPorts = [3000, 5173, 8080, 8000, 5000, 4000, 5500, 3001, 5174];
|
|
126
|
+
const detected = [];
|
|
127
|
+
|
|
128
|
+
for (const port of commonPorts) {
|
|
129
|
+
const inUse = await checkPortInUse(port);
|
|
130
|
+
if (inUse) {
|
|
131
|
+
// Try to verify it's actually a dev server by making a request
|
|
132
|
+
try {
|
|
133
|
+
const response = await new Promise((resolve) => {
|
|
134
|
+
const req = http.get(`http://localhost:${port}`, { timeout: 2000 }, (res) => {
|
|
135
|
+
resolve(res.statusCode);
|
|
136
|
+
});
|
|
137
|
+
req.on('error', () => resolve(null));
|
|
138
|
+
req.on('timeout', () => {
|
|
139
|
+
req.destroy();
|
|
140
|
+
resolve(null);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
// If we get any HTTP response, it's likely a dev server
|
|
144
|
+
if (response !== null) {
|
|
145
|
+
detected.push(port);
|
|
146
|
+
}
|
|
147
|
+
} catch (err) {
|
|
148
|
+
// Port is in use, add it anyway (might be a dev server)
|
|
149
|
+
detected.push(port);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return detected;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Auto-detect project in current directory (Laravel/PHP first, then Node/npm, then HTML)
|
|
158
|
+
async function autoDetectProject() {
|
|
159
|
+
const currentDir = process.cwd();
|
|
160
|
+
const packagePath = join(currentDir, "package.json");
|
|
161
|
+
const runningPorts = await detectRunningDevServer();
|
|
162
|
+
|
|
163
|
+
// 1) Laravel/PHP (composer.json + artisan) — default port 8000 (php artisan serve)
|
|
164
|
+
const laravel = detectLaravelProject(currentDir);
|
|
165
|
+
if (laravel) {
|
|
166
|
+
const detectedPort = runningPorts.length > 0 ? runningPorts[0] : laravel.defaultPort;
|
|
167
|
+
return {
|
|
168
|
+
path: currentDir,
|
|
169
|
+
name: laravel.name,
|
|
170
|
+
port: detectedPort,
|
|
171
|
+
projectType: "laravel"
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 2) Node/npm (package.json)
|
|
176
|
+
if (existsSync(packagePath)) {
|
|
177
|
+
try {
|
|
178
|
+
const packageJson = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
179
|
+
const projectName = packageJson.name || basename(currentDir);
|
|
180
|
+
const detectedPort =
|
|
181
|
+
runningPorts.length > 0 ? runningPorts[0] : detectPortFromPackage(packagePath);
|
|
182
|
+
return {
|
|
183
|
+
path: currentDir,
|
|
184
|
+
name: projectName,
|
|
185
|
+
port: detectedPort,
|
|
186
|
+
projectType: "node"
|
|
187
|
+
};
|
|
188
|
+
} catch (err) {
|
|
189
|
+
// fall through to HTML check
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 3) Plain HTML (index.html) — default port 8080 (Live Server, XAMPP, etc.)
|
|
194
|
+
const html = detectHtmlProject(currentDir);
|
|
195
|
+
if (html) {
|
|
196
|
+
const detectedPort = runningPorts.length > 0 ? runningPorts[0] : html.defaultPort;
|
|
197
|
+
return {
|
|
198
|
+
path: currentDir,
|
|
199
|
+
name: html.name,
|
|
200
|
+
port: detectedPort,
|
|
201
|
+
projectType: "html"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ASCII Logo - Compatible with all OS and terminals
|
|
209
|
+
function showLogo() {
|
|
210
|
+
console.log("");
|
|
211
|
+
console.log(" ██████████ ███████████ ████ ");
|
|
212
|
+
console.log("▒▒███▒▒▒▒███ ▒█▒▒▒███▒▒▒█ ▒▒███ ");
|
|
213
|
+
console.log(" ▒███ ▒▒███ ██████ █████ █████▒ ▒███ ▒ █████ ████ ████████ ████████ ██████ ▒███ ");
|
|
214
|
+
console.log(" ▒███ ▒███ ███▒▒███▒▒███ ▒▒███ ▒███ ▒▒███ ▒███ ▒▒███▒▒███ ▒▒███▒▒███ ███▒▒███ ▒███ ");
|
|
215
|
+
console.log(" ▒███ ▒███▒███████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███████ ▒███ ");
|
|
216
|
+
console.log(" ▒███ ███ ▒███▒▒▒ ▒▒███ ███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███▒▒▒ ▒███ ");
|
|
217
|
+
console.log(" ██████████ ▒▒██████ ▒▒█████ █████ ▒▒████████ ████ █████ ████ █████▒▒██████ █████");
|
|
218
|
+
console.log("▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ");
|
|
219
|
+
console.log(" ");
|
|
220
|
+
console.log(" ");
|
|
221
|
+
console.log("");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function main() {
|
|
225
|
+
// Clear screen - works on Windows, macOS, Linux
|
|
226
|
+
// ANSI escape codes for clear screen + cursor to top
|
|
227
|
+
process.stdout.write('\x1B[2J\x1B[0f');
|
|
228
|
+
console.clear(); // Fallback for terminals that don't support ANSI
|
|
229
|
+
|
|
230
|
+
// Show ASCII logo
|
|
231
|
+
showLogo();
|
|
232
|
+
|
|
233
|
+
console.log(`DevTunnel v${getPackageVersion()}`);
|
|
234
|
+
console.log("Share your local dev servers worldwide");
|
|
235
|
+
console.log("");
|
|
236
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
237
|
+
console.log("Repository: https://github.com/maiz-an/DevTunnel");
|
|
238
|
+
console.log("npm Package: https://www.npmjs.com/package/devtunnel");
|
|
239
|
+
console.log("Website: https://devtunnel.vercel.app");
|
|
240
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
241
|
+
console.log("");
|
|
242
|
+
|
|
243
|
+
// Step 1: Check Node.js
|
|
244
|
+
console.log("[1/4] Checking Node.js...");
|
|
245
|
+
if (!await commandExists("node")) {
|
|
246
|
+
console.log("ERROR: Node.js not found!");
|
|
247
|
+
console.log("Install from: https://nodejs.org/");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
console.log("SUCCESS: Node.js installed");
|
|
251
|
+
console.log("");
|
|
252
|
+
|
|
253
|
+
// Step 2: Check Cloudflare (bundled or system-installed)
|
|
254
|
+
console.log("[2/4] Checking Cloudflare...");
|
|
255
|
+
|
|
256
|
+
// Import bundled cloudflared helpers
|
|
257
|
+
const { setupCloudflared, hasBundledCloudflared } = await import("./setup-cloudflared.js");
|
|
258
|
+
|
|
259
|
+
let cloudflareAvailable = false;
|
|
260
|
+
|
|
261
|
+
if (hasBundledCloudflared()) {
|
|
262
|
+
console.log("SUCCESS: Using bundled Cloudflare (no install needed)");
|
|
263
|
+
cloudflareAvailable = true;
|
|
264
|
+
} else if (await commandExists("cloudflared")) {
|
|
265
|
+
console.log("SUCCESS: Cloudflare installed on system");
|
|
266
|
+
cloudflareAvailable = true;
|
|
267
|
+
} else {
|
|
268
|
+
console.log("First time setup - Downloading Cloudflare...");
|
|
269
|
+
console.log("This only happens once (~40MB, 10-30 seconds)");
|
|
270
|
+
console.log("");
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const bundledPath = await setupCloudflared();
|
|
274
|
+
|
|
275
|
+
if (bundledPath) {
|
|
276
|
+
console.log("SUCCESS: Cloudflare ready to use");
|
|
277
|
+
cloudflareAvailable = true;
|
|
278
|
+
} else {
|
|
279
|
+
console.log("Could not download Cloudflare");
|
|
280
|
+
console.log("Will use alternative tunnel services");
|
|
281
|
+
console.log("");
|
|
282
|
+
}
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.log(`Setup error: ${err.message}`);
|
|
285
|
+
console.log("Will use alternative tunnel services");
|
|
286
|
+
console.log("");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Show what's available
|
|
291
|
+
if (!cloudflareAvailable) {
|
|
292
|
+
console.log("DevTunnel has multi-service fallback:");
|
|
293
|
+
console.log(" Cloudflare (fastest, no password)");
|
|
294
|
+
console.log(" Ngrok (fast alternative)");
|
|
295
|
+
console.log(" LocalTunnel (backup option)");
|
|
296
|
+
console.log("");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Step 3: Check dependencies
|
|
300
|
+
console.log("[3/4] Checking dependencies...");
|
|
301
|
+
const nodeModulesPath = join(PROJECT_ROOT, "node_modules");
|
|
302
|
+
if (!existsSync(nodeModulesPath)) {
|
|
303
|
+
console.log("Installing dependencies...");
|
|
304
|
+
console.log("");
|
|
305
|
+
// Run npm install in the project root directory
|
|
306
|
+
const result = await runCommand("npm", ["install"], PROJECT_ROOT);
|
|
307
|
+
if (result.code !== 0) {
|
|
308
|
+
console.log("");
|
|
309
|
+
console.log("ERROR: npm install failed");
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
console.log("");
|
|
313
|
+
console.log("SUCCESS: Dependencies installed");
|
|
314
|
+
} else {
|
|
315
|
+
console.log("SUCCESS: Dependencies already installed");
|
|
316
|
+
}
|
|
317
|
+
console.log("");
|
|
318
|
+
|
|
319
|
+
// Step 4: Auto-detect or select project
|
|
320
|
+
console.log("[4/4] Detecting project...");
|
|
321
|
+
|
|
322
|
+
let projectPath, projectName, devPort;
|
|
323
|
+
|
|
324
|
+
// Try to auto-detect project in current directory
|
|
325
|
+
const autoDetected = await autoDetectProject();
|
|
326
|
+
|
|
327
|
+
if (autoDetected && autoDetected.port) {
|
|
328
|
+
// Auto-detected project with port
|
|
329
|
+
projectPath = autoDetected.path;
|
|
330
|
+
projectName = autoDetected.name;
|
|
331
|
+
|
|
332
|
+
// Double-check: verify the port is actually in use
|
|
333
|
+
const portInUse = await checkPortInUse(autoDetected.port);
|
|
334
|
+
|
|
335
|
+
if (!portInUse) {
|
|
336
|
+
// Detected port is not actually running, check for other running servers
|
|
337
|
+
const portSource =
|
|
338
|
+
autoDetected.projectType === "laravel"
|
|
339
|
+
? "Laravel (php artisan serve)"
|
|
340
|
+
: autoDetected.projectType === "html"
|
|
341
|
+
? "HTML project"
|
|
342
|
+
: "package.json";
|
|
343
|
+
console.log(`Detected port ${autoDetected.port} (${portSource}), but no server running on that port`);
|
|
344
|
+
console.log("Checking for running dev servers...");
|
|
345
|
+
|
|
346
|
+
const runningPorts = await detectRunningDevServer();
|
|
347
|
+
if (runningPorts.length > 0) {
|
|
348
|
+
if (runningPorts.length === 1) {
|
|
349
|
+
devPort = runningPorts[0];
|
|
350
|
+
console.log(`Found running dev server on port: ${devPort}`);
|
|
351
|
+
} else {
|
|
352
|
+
console.log(`Found ${runningPorts.length} running dev server(s) on port(s): ${runningPorts.join(', ')}`);
|
|
353
|
+
const portResponse = await prompts({
|
|
354
|
+
type: "select",
|
|
355
|
+
name: "port",
|
|
356
|
+
message: "Select port:",
|
|
357
|
+
choices: runningPorts.map(p => ({ title: `Port ${p}`, value: p }))
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (!portResponse.port) {
|
|
361
|
+
console.log("ERROR: No port selected");
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
devPort = portResponse.port;
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
// No running servers, use detected port (user might start it later)
|
|
369
|
+
devPort = autoDetected.port;
|
|
370
|
+
console.log(`Using detected port: ${devPort} (make sure dev server is running)`);
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
// Port is in use, use it
|
|
374
|
+
devPort = autoDetected.port;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.log(`Detected project: ${projectName}`);
|
|
378
|
+
console.log(`Using port: ${devPort}`);
|
|
379
|
+
console.log(`Using current directory: ${projectPath}`);
|
|
380
|
+
console.log("");
|
|
381
|
+
|
|
382
|
+
// Confirm with user
|
|
383
|
+
const confirm = await prompts({
|
|
384
|
+
type: "confirm",
|
|
385
|
+
name: "value",
|
|
386
|
+
message: "Use detected project?",
|
|
387
|
+
initial: true
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (!confirm.value) {
|
|
391
|
+
// User wants to select manually
|
|
392
|
+
console.log("");
|
|
393
|
+
console.log("Selecting project manually...");
|
|
394
|
+
console.log("");
|
|
395
|
+
|
|
396
|
+
const selectedPath = await selectFolder();
|
|
397
|
+
if (!selectedPath || selectedPath.length === 0) {
|
|
398
|
+
console.log("ERROR: No folder selected");
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
projectPath = selectedPath;
|
|
403
|
+
projectName = basename(selectedPath);
|
|
404
|
+
|
|
405
|
+
// Try to detect port for selected project (Laravel → 8000, Node from package.json, else 5173)
|
|
406
|
+
const selectedPackagePath = join(selectedPath, "package.json");
|
|
407
|
+
const laravelSelected = detectLaravelProject(selectedPath);
|
|
408
|
+
const detectedPort = laravelSelected
|
|
409
|
+
? laravelSelected.defaultPort
|
|
410
|
+
: detectPortFromPackage(selectedPackagePath);
|
|
411
|
+
|
|
412
|
+
const portResponse = await prompts({
|
|
413
|
+
type: "number",
|
|
414
|
+
name: "port",
|
|
415
|
+
message: "Enter your dev server port:",
|
|
416
|
+
initial: detectedPort || 5173
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
if (!portResponse.port) {
|
|
420
|
+
console.log("ERROR: No port entered");
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
devPort = portResponse.port;
|
|
425
|
+
}
|
|
426
|
+
} else if (autoDetected && !autoDetected.port) {
|
|
427
|
+
// Project detected but no port
|
|
428
|
+
projectPath = autoDetected.path;
|
|
429
|
+
projectName = autoDetected.name;
|
|
430
|
+
|
|
431
|
+
console.log(`Detected project: ${projectName}`);
|
|
432
|
+
console.log(`Using current directory: ${projectPath}`);
|
|
433
|
+
console.log("Checking for running dev servers...");
|
|
434
|
+
|
|
435
|
+
const runningPorts = await detectRunningDevServer();
|
|
436
|
+
|
|
437
|
+
if (runningPorts.length > 0) {
|
|
438
|
+
console.log(`Found ${runningPorts.length} running dev server(s) on port(s): ${runningPorts.join(', ')}`);
|
|
439
|
+
|
|
440
|
+
if (runningPorts.length === 1) {
|
|
441
|
+
devPort = runningPorts[0];
|
|
442
|
+
console.log(`Using port: ${devPort}`);
|
|
443
|
+
} else {
|
|
444
|
+
// Multiple ports detected, let user choose
|
|
445
|
+
const portResponse = await prompts({
|
|
446
|
+
type: "select",
|
|
447
|
+
name: "port",
|
|
448
|
+
message: "Select port:",
|
|
449
|
+
choices: runningPorts.map(p => ({ title: `Port ${p}`, value: p }))
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
if (!portResponse.port) {
|
|
453
|
+
console.log("ERROR: No port selected");
|
|
454
|
+
process.exit(1);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
devPort = portResponse.port;
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
// No running server, ask for port
|
|
461
|
+
const portResponse = await prompts({
|
|
462
|
+
type: "number",
|
|
463
|
+
name: "port",
|
|
464
|
+
message: "Enter your dev server port:",
|
|
465
|
+
initial: 5173
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
if (!portResponse.port) {
|
|
469
|
+
console.log("ERROR: No port entered");
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
devPort = portResponse.port;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
console.log("");
|
|
477
|
+
} else {
|
|
478
|
+
// No auto-detection, use folder picker
|
|
479
|
+
console.log("No project detected in current directory");
|
|
480
|
+
console.log("Opening folder picker...");
|
|
481
|
+
console.log("");
|
|
482
|
+
|
|
483
|
+
projectPath = await selectFolder();
|
|
484
|
+
|
|
485
|
+
if (!projectPath || projectPath.length === 0) {
|
|
486
|
+
console.log("ERROR: No folder selected");
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
projectName = basename(projectPath);
|
|
491
|
+
console.log(`Selected: ${projectPath}`);
|
|
492
|
+
console.log("");
|
|
493
|
+
|
|
494
|
+
// Try to detect port for selected project (Laravel → 8000, HTML → 8080, Node from package.json)
|
|
495
|
+
const selectedPackagePath = join(projectPath, "package.json");
|
|
496
|
+
const laravelSelected = detectLaravelProject(projectPath);
|
|
497
|
+
const htmlSelected = detectHtmlProject(projectPath);
|
|
498
|
+
let detectedPort = laravelSelected
|
|
499
|
+
? laravelSelected.defaultPort
|
|
500
|
+
: htmlSelected
|
|
501
|
+
? htmlSelected.defaultPort
|
|
502
|
+
: detectPortFromPackage(selectedPackagePath);
|
|
503
|
+
|
|
504
|
+
// Check for running servers
|
|
505
|
+
const runningPorts = await detectRunningDevServer();
|
|
506
|
+
|
|
507
|
+
let initialPort = detectedPort || 5173;
|
|
508
|
+
if (runningPorts.length > 0 && !detectedPort) {
|
|
509
|
+
initialPort = runningPorts[0];
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const portResponse = await prompts({
|
|
513
|
+
type: "number",
|
|
514
|
+
name: "port",
|
|
515
|
+
message: "Enter your dev server port:",
|
|
516
|
+
initial: initialPort
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
if (!portResponse.port) {
|
|
520
|
+
console.log("ERROR: No port entered");
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
devPort = portResponse.port;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
console.log("");
|
|
528
|
+
const proxyPort = devPort + 1000; // Use port 1000 higher for proxy
|
|
529
|
+
|
|
530
|
+
console.log("");
|
|
531
|
+
console.log("Configuration:");
|
|
532
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
533
|
+
console.log(`Project: ${projectName}`);
|
|
534
|
+
console.log(`Dev Server: localhost:${devPort}`);
|
|
535
|
+
console.log(`Proxy Port: ${proxyPort}`);
|
|
536
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
537
|
+
console.log("");
|
|
538
|
+
|
|
539
|
+
// For HTML projects with no server running: start built-in static server
|
|
540
|
+
let staticServerProcess = null;
|
|
541
|
+
const isHtmlProject = !!detectHtmlProject(projectPath);
|
|
542
|
+
const portInUseNow = await checkPortInUse(devPort);
|
|
543
|
+
if (isHtmlProject && !portInUseNow) {
|
|
544
|
+
console.log("Starting built-in static server for HTML project...");
|
|
545
|
+
const staticServerPath = join(__dirname, "static-server.js");
|
|
546
|
+
staticServerProcess = spawn("node", [staticServerPath, projectPath, devPort.toString()], {
|
|
547
|
+
stdio: "pipe",
|
|
548
|
+
shell: false
|
|
549
|
+
});
|
|
550
|
+
staticServerProcess.on("error", () => {});
|
|
551
|
+
await new Promise((r) => setTimeout(r, 1200));
|
|
552
|
+
console.log(`Static server running at http://localhost:${devPort}`);
|
|
553
|
+
console.log("");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Start proxy server
|
|
557
|
+
console.log("Starting services...");
|
|
558
|
+
console.log("");
|
|
559
|
+
const proxyPath = join(__dirname, "proxy-server.js");
|
|
560
|
+
const proxyProcess = spawn("node", [proxyPath, devPort.toString(), proxyPort.toString(), projectName], {
|
|
561
|
+
stdio: "inherit",
|
|
562
|
+
shell: false
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Wait for proxy to start
|
|
566
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
567
|
+
|
|
568
|
+
// Run main tunnel app (connects to proxy port)
|
|
569
|
+
// Use shell: false to properly handle paths with spaces
|
|
570
|
+
const indexPath = join(__dirname, "index.js");
|
|
571
|
+
const tunnelProcess = spawn("node", [indexPath, proxyPort.toString(), projectName, projectPath], {
|
|
572
|
+
stdio: "inherit",
|
|
573
|
+
shell: false
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Handle cleanup
|
|
577
|
+
const cleanup = () => {
|
|
578
|
+
console.log("\nShutting down...");
|
|
579
|
+
if (staticServerProcess) staticServerProcess.kill();
|
|
580
|
+
proxyProcess.kill();
|
|
581
|
+
tunnelProcess.kill();
|
|
582
|
+
process.exit(0);
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
tunnelProcess.on("close", (code) => {
|
|
586
|
+
cleanup();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
proxyProcess.on("close", () => {
|
|
590
|
+
cleanup();
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// Handle Ctrl+C
|
|
594
|
+
process.on("SIGINT", cleanup);
|
|
595
|
+
process.on("SIGTERM", cleanup);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Run
|
|
599
|
+
main().catch((error) => {
|
|
600
|
+
console.error("\nERROR:", error.message);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
});
|