taskplane 0.1.1 → 0.1.3
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/dashboard/server.cjs +63 -10
- package/package.json +1 -1
package/dashboard/server.cjs
CHANGED
|
@@ -18,12 +18,14 @@ const { execFileSync, exec } = require("child_process");
|
|
|
18
18
|
// ─── Configuration ──────────────────────────────────────────────────────────
|
|
19
19
|
|
|
20
20
|
const PUBLIC_DIR = path.join(__dirname, "public");
|
|
21
|
-
const DEFAULT_PORT =
|
|
21
|
+
const DEFAULT_PORT = 8100;
|
|
22
|
+
const MAX_PORT_ATTEMPTS = 20;
|
|
22
23
|
const POLL_INTERVAL = 2000; // ms between state checks
|
|
23
24
|
|
|
24
25
|
// REPO_ROOT is resolved after parseArgs() — see initialization below.
|
|
25
26
|
let REPO_ROOT;
|
|
26
27
|
let BATCH_STATE_PATH;
|
|
28
|
+
let BATCH_HISTORY_PATH;
|
|
27
29
|
|
|
28
30
|
// ─── CLI Args ───────────────────────────────────────────────────────────────
|
|
29
31
|
|
|
@@ -373,7 +375,7 @@ function broadcastState() {
|
|
|
373
375
|
|
|
374
376
|
// ─── Batch History API ──────────────────────────────────────────────────────
|
|
375
377
|
|
|
376
|
-
|
|
378
|
+
// BATCH_HISTORY_PATH is initialized in main() alongside REPO_ROOT.
|
|
377
379
|
|
|
378
380
|
function loadHistory() {
|
|
379
381
|
try {
|
|
@@ -426,7 +428,7 @@ function serveHistoryEntry(req, res, batchId) {
|
|
|
426
428
|
|
|
427
429
|
// ─── HTTP Server ────────────────────────────────────────────────────────────
|
|
428
430
|
|
|
429
|
-
function createServer(
|
|
431
|
+
function createServer() {
|
|
430
432
|
const server = http.createServer((req, res) => {
|
|
431
433
|
const pathname = new URL(req.url, "http://localhost").pathname;
|
|
432
434
|
|
|
@@ -455,10 +457,6 @@ function createServer(port) {
|
|
|
455
457
|
}
|
|
456
458
|
});
|
|
457
459
|
|
|
458
|
-
server.listen(port, () => {
|
|
459
|
-
console.log(`\n Orchestrator Dashboard → http://localhost:${port}\n`);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
460
|
return server;
|
|
463
461
|
}
|
|
464
462
|
|
|
@@ -472,14 +470,69 @@ function openBrowser(url) {
|
|
|
472
470
|
|
|
473
471
|
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
474
472
|
|
|
475
|
-
|
|
473
|
+
/** Try to listen on a port. Resolves with the port on success, rejects on EADDRINUSE. */
|
|
474
|
+
function tryListen(server, port) {
|
|
475
|
+
return new Promise((resolve, reject) => {
|
|
476
|
+
const onError = (err) => {
|
|
477
|
+
server.removeListener("listening", onListening);
|
|
478
|
+
reject(err);
|
|
479
|
+
};
|
|
480
|
+
const onListening = () => {
|
|
481
|
+
server.removeListener("error", onError);
|
|
482
|
+
resolve(port);
|
|
483
|
+
};
|
|
484
|
+
server.once("error", onError);
|
|
485
|
+
server.once("listening", onListening);
|
|
486
|
+
server.listen(port);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** Find an available port starting from `start`, trying up to MAX_PORT_ATTEMPTS. */
|
|
491
|
+
async function findPort(server, start, explicit) {
|
|
492
|
+
// If the user explicitly passed --port, only try that one
|
|
493
|
+
if (explicit) {
|
|
494
|
+
try {
|
|
495
|
+
return await tryListen(server, start);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
if (err.code === "EADDRINUSE") {
|
|
498
|
+
console.error(`\n Port ${start} is already in use.`);
|
|
499
|
+
console.error(` Try: taskplane dashboard --port ${start + 1}\n`);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
throw err;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// Auto-scan for an available port
|
|
506
|
+
for (let port = start; port < start + MAX_PORT_ATTEMPTS; port++) {
|
|
507
|
+
try {
|
|
508
|
+
return await tryListen(server, port);
|
|
509
|
+
} catch (err) {
|
|
510
|
+
if (err.code === "EADDRINUSE") {
|
|
511
|
+
// Close the server so we can retry on the next port
|
|
512
|
+
server.close();
|
|
513
|
+
server = createServer();
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
throw err;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
console.error(`\n No available port found in range ${start}-${start + MAX_PORT_ATTEMPTS - 1}.\n`);
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function main() {
|
|
476
524
|
const opts = parseArgs();
|
|
477
525
|
|
|
478
526
|
// Resolve project root: --root flag > cwd
|
|
479
527
|
REPO_ROOT = path.resolve(opts.root || process.cwd());
|
|
480
528
|
BATCH_STATE_PATH = path.join(REPO_ROOT, ".pi", "batch-state.json");
|
|
529
|
+
BATCH_HISTORY_PATH = path.join(REPO_ROOT, ".pi", "batch-history.json");
|
|
530
|
+
|
|
531
|
+
const server = createServer();
|
|
532
|
+
const explicitPort = process.argv.slice(2).includes("--port");
|
|
533
|
+
const port = await findPort(server, opts.port, explicitPort);
|
|
481
534
|
|
|
482
|
-
|
|
535
|
+
console.log(`\n Orchestrator Dashboard → http://localhost:${port}\n`);
|
|
483
536
|
|
|
484
537
|
// Broadcast state to all SSE clients on interval
|
|
485
538
|
const pollTimer = setInterval(broadcastState, POLL_INTERVAL);
|
|
@@ -505,7 +558,7 @@ function main() {
|
|
|
505
558
|
|
|
506
559
|
// Auto-open browser
|
|
507
560
|
if (opts.open) {
|
|
508
|
-
setTimeout(() => openBrowser(`http://localhost:${
|
|
561
|
+
setTimeout(() => openBrowser(`http://localhost:${port}`), 500);
|
|
509
562
|
}
|
|
510
563
|
|
|
511
564
|
// Graceful shutdown
|