@absolutejs/absolute 0.19.0-beta.665 → 0.19.0-beta.667
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/dist/cli/index.js +624 -167
- package/dist/index.js +3 -2
- package/dist/index.js.map +3 -3
- package/dist/src/cli/utils.d.ts +1 -1
- package/dist/src/cli/workspaceTui.d.ts +27 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -257,11 +257,12 @@ var COMPOSE_PATH = "db/docker-compose.db.yml", DEFAULT_SERVER_ENTRY = "src/backe
|
|
|
257
257
|
}
|
|
258
258
|
pids.forEach(safeKill);
|
|
259
259
|
console.log(`\x1B[2m${formatTimestamp()}\x1B[0m \x1B[33m[cli]\x1B[0m \x1B[33mKilled ${pids.length} stale ${pids.length === 1 ? "process" : "processes"} on port ${port}.\x1B[0m`);
|
|
260
|
-
}, printHelp = () => {
|
|
260
|
+
}, printHelp = (subject = "server") => {
|
|
261
|
+
const title = subject === "workspace" ? "workspace" : subject;
|
|
261
262
|
console.log("");
|
|
262
263
|
console.log("\x1B[1mShortcuts:\x1B[0m");
|
|
263
|
-
console.log(
|
|
264
|
-
console.log(
|
|
264
|
+
console.log(` \x1B[36mr\x1B[0m / restart \u2014 Restart ${title}`);
|
|
265
|
+
console.log(` \x1B[36mp\x1B[0m / pause \u2014 Pause/resume ${title}`);
|
|
265
266
|
console.log(" \x1B[36mo\x1B[0m / open \u2014 Open in browser");
|
|
266
267
|
console.log(" \x1B[36mc\x1B[0m / clear \u2014 Clear terminal");
|
|
267
268
|
console.log(" \x1B[36mq\x1B[0m / quit \u2014 Graceful shutdown");
|
|
@@ -668,7 +669,7 @@ var exports_compile = {};
|
|
|
668
669
|
__export(exports_compile, {
|
|
669
670
|
compile: () => compile
|
|
670
671
|
});
|
|
671
|
-
var {env:
|
|
672
|
+
var {env: env3 } = globalThis.Bun;
|
|
672
673
|
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync8, unlinkSync } from "fs";
|
|
673
674
|
import { basename as basename2, join as join6, relative, resolve as resolve7 } from "path";
|
|
674
675
|
var cliTag3 = (color, message) => `\x1B[2m${formatTimestamp()}\x1B[0m ${color}[cli]\x1B[0m ${color}${message}\x1B[0m`, collectFiles2 = (dir) => {
|
|
@@ -847,7 +848,7 @@ console.log(\`
|
|
|
847
848
|
\`);
|
|
848
849
|
`;
|
|
849
850
|
}, stubPlugin, FRAMEWORK_EXTERNALS, compile = async (serverEntry, outdir, outfile, configPath2) => {
|
|
850
|
-
const prerenderPort = Number(
|
|
851
|
+
const prerenderPort = Number(env3.COMPILE_PORT) || Number(env3.PORT) || DEFAULT_PORT + 1;
|
|
851
852
|
killStaleProcesses(prerenderPort);
|
|
852
853
|
const entryName = basename2(serverEntry).replace(/\.[^.]+$/, "");
|
|
853
854
|
const resolvedOutdir = resolve7(outdir ?? "dist");
|
|
@@ -1049,17 +1050,17 @@ var run = async (name, command) => {
|
|
|
1049
1050
|
}, shellEscape = (value) => `'${value.replaceAll("'", "'\\''")}'`, runShell = async (name, command) => run(name, ["/bin/bash", "-lc", command]), findBin = (name) => {
|
|
1050
1051
|
const local = resolve8("node_modules", ".bin", name);
|
|
1051
1052
|
return existsSync10(local) ? local : null;
|
|
1052
|
-
},
|
|
1053
|
+
}, stripAnsi2 = (str) => str.replace(/\x1b\[[0-9;]*m/g, ""), formatSvelteOutput = (output) => {
|
|
1053
1054
|
const cwd = `${process.cwd()}/`;
|
|
1054
|
-
const summaryMatch =
|
|
1055
|
+
const summaryMatch = stripAnsi2(output).match(/svelte-check found (\d+) error/);
|
|
1055
1056
|
const errorCount = summaryMatch ? parseInt(summaryMatch[1] ?? "0", 10) : 0;
|
|
1056
1057
|
const formatted = output.split(`
|
|
1057
1058
|
`).filter((line) => {
|
|
1058
|
-
const plain =
|
|
1059
|
+
const plain = stripAnsi2(line);
|
|
1059
1060
|
return !plain.startsWith("Loading svelte-check") && !plain.startsWith("Getting Svelte") && !plain.startsWith("====") && !plain.startsWith("svelte-check found") && !/^\d+ (START|COMPLETED)/.test(plain) && plain.trim() !== "";
|
|
1060
1061
|
}).flatMap((line) => {
|
|
1061
1062
|
const result = line.replaceAll(cwd, "");
|
|
1062
|
-
const plain =
|
|
1063
|
+
const plain = stripAnsi2(result);
|
|
1063
1064
|
const pathMatch = plain.match(/^(\S+\.svelte):(\d+:\d+)$/);
|
|
1064
1065
|
if (pathMatch) {
|
|
1065
1066
|
return [
|
|
@@ -1067,9 +1068,9 @@ var run = async (name, command) => {
|
|
|
1067
1068
|
];
|
|
1068
1069
|
}
|
|
1069
1070
|
if (result.includes("\x1B[35m")) {
|
|
1070
|
-
const plainLine =
|
|
1071
|
-
const before =
|
|
1072
|
-
const token =
|
|
1071
|
+
const plainLine = stripAnsi2(result);
|
|
1072
|
+
const before = stripAnsi2(result.split("\x1B[35m")[0] ?? "");
|
|
1073
|
+
const token = stripAnsi2((result.split("\x1B[35m")[1] ?? "").split(/\x1b\[3[69]m/)[0] ?? "");
|
|
1073
1074
|
if (!token)
|
|
1074
1075
|
return [result];
|
|
1075
1076
|
const expanded = before.replace(/\t/g, " ");
|
|
@@ -2378,13 +2379,547 @@ var start = async (serverEntry, outdir, configPath2) => {
|
|
|
2378
2379
|
// src/cli/scripts/workspace.ts
|
|
2379
2380
|
init_constants();
|
|
2380
2381
|
init_loadConfig();
|
|
2381
|
-
init_startupBanner();
|
|
2382
|
-
var {$: $3 } = globalThis.Bun;
|
|
2383
2382
|
import { existsSync as existsSync8 } from "fs";
|
|
2384
2383
|
import { resolve as resolve6 } from "path";
|
|
2384
|
+
|
|
2385
|
+
// src/cli/workspaceTui.ts
|
|
2386
|
+
init_constants();
|
|
2387
|
+
import { openSync as openSync2 } from "fs";
|
|
2388
|
+
import { ReadStream as ReadStream2 } from "tty";
|
|
2389
|
+
var MAX_LOG_ENTRIES = 400;
|
|
2390
|
+
var ESCAPE = "\x1B";
|
|
2391
|
+
var ANSI_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
2392
|
+
var SHORTCUTS2 = new Map([
|
|
2393
|
+
["c", "clear"],
|
|
2394
|
+
["h", "help"],
|
|
2395
|
+
["o", "open"],
|
|
2396
|
+
["p", "pause"],
|
|
2397
|
+
["q", "quit"],
|
|
2398
|
+
["r", "restart"]
|
|
2399
|
+
]);
|
|
2400
|
+
var colors = {
|
|
2401
|
+
bold: "\x1B[1m",
|
|
2402
|
+
cyan: "\x1B[36m",
|
|
2403
|
+
dim: "\x1B[2m",
|
|
2404
|
+
green: "\x1B[32m",
|
|
2405
|
+
red: "\x1B[31m",
|
|
2406
|
+
reset: "\x1B[0m",
|
|
2407
|
+
yellow: "\x1B[33m"
|
|
2408
|
+
};
|
|
2409
|
+
var helpLines = [
|
|
2410
|
+
"Hotkeys",
|
|
2411
|
+
" h Toggle help",
|
|
2412
|
+
" o Open the first public service",
|
|
2413
|
+
" r Restart the workspace",
|
|
2414
|
+
" p Pause or resume all services",
|
|
2415
|
+
" c Clear the log pane",
|
|
2416
|
+
" q Quit",
|
|
2417
|
+
" $ Enter shell mode",
|
|
2418
|
+
"",
|
|
2419
|
+
"Shell mode",
|
|
2420
|
+
' Type a shell command after "$" and press enter.',
|
|
2421
|
+
" Press Esc to exit shell mode or dismiss help.",
|
|
2422
|
+
" Use \u2191 and \u2193 to recall prior shell commands."
|
|
2423
|
+
];
|
|
2424
|
+
var trySetRawMode2 = () => {
|
|
2425
|
+
if (typeof process.stdin.setRawMode !== "function") {
|
|
2426
|
+
return null;
|
|
2427
|
+
}
|
|
2428
|
+
try {
|
|
2429
|
+
process.stdin.setRawMode(true);
|
|
2430
|
+
} catch {
|
|
2431
|
+
return null;
|
|
2432
|
+
}
|
|
2433
|
+
return process.stdin;
|
|
2434
|
+
};
|
|
2435
|
+
var openTtyStream2 = () => {
|
|
2436
|
+
const fromStdin = trySetRawMode2();
|
|
2437
|
+
if (fromStdin) {
|
|
2438
|
+
return fromStdin;
|
|
2439
|
+
}
|
|
2440
|
+
try {
|
|
2441
|
+
const ttyStream = new ReadStream2(openSync2("/dev/tty", "r"));
|
|
2442
|
+
ttyStream.setRawMode(true);
|
|
2443
|
+
return ttyStream;
|
|
2444
|
+
} catch {
|
|
2445
|
+
return null;
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
var stripAnsi = (value) => value.replace(ANSI_REGEX, "");
|
|
2449
|
+
var visibleLength = (value) => stripAnsi(value).length;
|
|
2450
|
+
var truncateText = (value, width) => {
|
|
2451
|
+
if (width <= 0) {
|
|
2452
|
+
return "";
|
|
2453
|
+
}
|
|
2454
|
+
if (value.length <= width) {
|
|
2455
|
+
return value;
|
|
2456
|
+
}
|
|
2457
|
+
if (width <= 1) {
|
|
2458
|
+
return value.slice(0, width);
|
|
2459
|
+
}
|
|
2460
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
2461
|
+
};
|
|
2462
|
+
var padLine = (value, width) => {
|
|
2463
|
+
const plainLength = visibleLength(value);
|
|
2464
|
+
if (plainLength >= width) {
|
|
2465
|
+
return value;
|
|
2466
|
+
}
|
|
2467
|
+
return `${value}${" ".repeat(width - plainLength)}`;
|
|
2468
|
+
};
|
|
2469
|
+
var wrapText = (value, width) => {
|
|
2470
|
+
if (width <= 0) {
|
|
2471
|
+
return [""];
|
|
2472
|
+
}
|
|
2473
|
+
const lines = [];
|
|
2474
|
+
for (const rawLine of value.split(`
|
|
2475
|
+
`)) {
|
|
2476
|
+
const line = rawLine.trimEnd();
|
|
2477
|
+
if (line.length === 0) {
|
|
2478
|
+
lines.push("");
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
if (line.length <= width) {
|
|
2482
|
+
lines.push(line);
|
|
2483
|
+
continue;
|
|
2484
|
+
}
|
|
2485
|
+
const words = line.split(/\s+/);
|
|
2486
|
+
let current = "";
|
|
2487
|
+
for (const word of words) {
|
|
2488
|
+
if (current.length === 0) {
|
|
2489
|
+
if (word.length <= width) {
|
|
2490
|
+
current = word;
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
for (let index = 0;index < word.length; index += width) {
|
|
2494
|
+
lines.push(word.slice(index, index + width));
|
|
2495
|
+
}
|
|
2496
|
+
continue;
|
|
2497
|
+
}
|
|
2498
|
+
const next = `${current} ${word}`;
|
|
2499
|
+
if (next.length <= width) {
|
|
2500
|
+
current = next;
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2503
|
+
lines.push(current);
|
|
2504
|
+
if (word.length <= width) {
|
|
2505
|
+
current = word;
|
|
2506
|
+
continue;
|
|
2507
|
+
}
|
|
2508
|
+
for (let index = 0;index < word.length; index += width) {
|
|
2509
|
+
lines.push(word.slice(index, index + width));
|
|
2510
|
+
}
|
|
2511
|
+
current = "";
|
|
2512
|
+
}
|
|
2513
|
+
if (current.length > 0) {
|
|
2514
|
+
lines.push(current);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
return lines.length > 0 ? lines : [""];
|
|
2518
|
+
};
|
|
2519
|
+
var formatTimestamp2 = () => new Date().toLocaleTimeString([], {
|
|
2520
|
+
hour: "numeric",
|
|
2521
|
+
hour12: true,
|
|
2522
|
+
minute: "2-digit",
|
|
2523
|
+
second: "2-digit"
|
|
2524
|
+
});
|
|
2525
|
+
var getStatusColor = (status2) => {
|
|
2526
|
+
if (status2 === "ready")
|
|
2527
|
+
return colors.green;
|
|
2528
|
+
if (status2 === "paused")
|
|
2529
|
+
return colors.yellow;
|
|
2530
|
+
if (status2 === "starting" || status2 === "restarting")
|
|
2531
|
+
return colors.cyan;
|
|
2532
|
+
if (status2 === "error")
|
|
2533
|
+
return colors.red;
|
|
2534
|
+
return colors.dim;
|
|
2535
|
+
};
|
|
2536
|
+
var getLogColor = (level) => {
|
|
2537
|
+
if (level === "error")
|
|
2538
|
+
return colors.red;
|
|
2539
|
+
if (level === "warn")
|
|
2540
|
+
return colors.yellow;
|
|
2541
|
+
if (level === "success")
|
|
2542
|
+
return colors.green;
|
|
2543
|
+
return colors.reset;
|
|
2544
|
+
};
|
|
2545
|
+
var getSourceColor = (source) => {
|
|
2546
|
+
if (source === "workspace")
|
|
2547
|
+
return colors.cyan;
|
|
2548
|
+
if (source === "shell")
|
|
2549
|
+
return colors.yellow;
|
|
2550
|
+
return colors.reset;
|
|
2551
|
+
};
|
|
2552
|
+
var getTargetLabel = (service) => {
|
|
2553
|
+
if (service.visibility === "public" && service.url) {
|
|
2554
|
+
return service.url;
|
|
2555
|
+
}
|
|
2556
|
+
if (service.port) {
|
|
2557
|
+
return `localhost:${service.port} (internal)`;
|
|
2558
|
+
}
|
|
2559
|
+
return service.visibility === "internal" ? "internal service" : "no endpoint";
|
|
2560
|
+
};
|
|
2561
|
+
var getWorkspaceStatus = (services) => {
|
|
2562
|
+
if (services.some((service) => service.status === "error")) {
|
|
2563
|
+
return "error";
|
|
2564
|
+
}
|
|
2565
|
+
if (services.some((service) => service.status === "paused")) {
|
|
2566
|
+
return "paused";
|
|
2567
|
+
}
|
|
2568
|
+
if (services.some((service) => ["pending", "starting", "restarting"].includes(service.status))) {
|
|
2569
|
+
return "booting";
|
|
2570
|
+
}
|
|
2571
|
+
return "ready";
|
|
2572
|
+
};
|
|
2573
|
+
var getStatusSummary = (services) => {
|
|
2574
|
+
const publicServices = services.filter((service) => service.visibility === "public" && service.url).map((service) => `${service.name} ${service.url}`);
|
|
2575
|
+
const internalServices = services.filter((service) => service.visibility === "internal").map((service) => service.port ? `${service.name} localhost:${service.port}` : service.name);
|
|
2576
|
+
const parts = [];
|
|
2577
|
+
if (publicServices.length > 0) {
|
|
2578
|
+
parts.push(`Public: ${publicServices.join(", ")}`);
|
|
2579
|
+
}
|
|
2580
|
+
if (internalServices.length > 0) {
|
|
2581
|
+
parts.push(`Internal: ${internalServices.join(", ")}`);
|
|
2582
|
+
}
|
|
2583
|
+
return parts.join(" | ");
|
|
2584
|
+
};
|
|
2585
|
+
var createWorkspaceTui = ({
|
|
2586
|
+
actions,
|
|
2587
|
+
services
|
|
2588
|
+
}) => {
|
|
2589
|
+
let input = null;
|
|
2590
|
+
let disposed = false;
|
|
2591
|
+
let renderTimer = null;
|
|
2592
|
+
let shellMode = false;
|
|
2593
|
+
let helpVisible = false;
|
|
2594
|
+
let promptBuffer = "";
|
|
2595
|
+
let escapeTimer = null;
|
|
2596
|
+
let escapeBuffer = "";
|
|
2597
|
+
const shellHistory = [];
|
|
2598
|
+
let shellHistoryIndex = UNFOUND_INDEX;
|
|
2599
|
+
const serviceStates = new Map(services.map((service) => [
|
|
2600
|
+
service.name,
|
|
2601
|
+
{
|
|
2602
|
+
...service,
|
|
2603
|
+
status: "pending"
|
|
2604
|
+
}
|
|
2605
|
+
]));
|
|
2606
|
+
const logEntries = [];
|
|
2607
|
+
const setRawMode = (enabled) => {
|
|
2608
|
+
if (!input || typeof input.setRawMode !== "function") {
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
try {
|
|
2612
|
+
input.setRawMode(enabled);
|
|
2613
|
+
} catch {}
|
|
2614
|
+
};
|
|
2615
|
+
const scheduleRender = () => {
|
|
2616
|
+
if (disposed || renderTimer) {
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
renderTimer = setTimeout(() => {
|
|
2620
|
+
renderTimer = null;
|
|
2621
|
+
render();
|
|
2622
|
+
}, 16);
|
|
2623
|
+
};
|
|
2624
|
+
const clearPendingEscape = () => {
|
|
2625
|
+
if (!escapeTimer) {
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
clearTimeout(escapeTimer);
|
|
2629
|
+
escapeTimer = null;
|
|
2630
|
+
};
|
|
2631
|
+
const armEscapeTimer = () => {
|
|
2632
|
+
clearPendingEscape();
|
|
2633
|
+
escapeTimer = setTimeout(() => {
|
|
2634
|
+
escapeTimer = null;
|
|
2635
|
+
escapeBuffer = "";
|
|
2636
|
+
exitEscapeMode();
|
|
2637
|
+
}, 30);
|
|
2638
|
+
};
|
|
2639
|
+
const resetPrompt = () => {
|
|
2640
|
+
promptBuffer = "";
|
|
2641
|
+
shellMode = false;
|
|
2642
|
+
shellHistoryIndex = UNFOUND_INDEX;
|
|
2643
|
+
scheduleRender();
|
|
2644
|
+
};
|
|
2645
|
+
const exitEscapeMode = () => {
|
|
2646
|
+
clearPendingEscape();
|
|
2647
|
+
escapeBuffer = "";
|
|
2648
|
+
if (helpVisible) {
|
|
2649
|
+
helpVisible = false;
|
|
2650
|
+
} else {
|
|
2651
|
+
resetPrompt();
|
|
2652
|
+
}
|
|
2653
|
+
scheduleRender();
|
|
2654
|
+
};
|
|
2655
|
+
const render = () => {
|
|
2656
|
+
if (disposed) {
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
const width = process.stdout.columns ?? 100;
|
|
2660
|
+
const height = process.stdout.rows ?? 28;
|
|
2661
|
+
const servicesSnapshot = [...serviceStates.values()];
|
|
2662
|
+
const workspaceStatus = getWorkspaceStatus(servicesSnapshot);
|
|
2663
|
+
const title = `${colors.bold}Absolute Workspace${colors.reset} ${colors.dim}\xB7 ${workspaceStatus}${colors.reset}`;
|
|
2664
|
+
const summaryText = truncateText(getStatusSummary(servicesSnapshot), width);
|
|
2665
|
+
const summary = `${colors.dim}${summaryText}${colors.reset}`;
|
|
2666
|
+
const divider = `${colors.dim}${"\u2500".repeat(Math.max(width, 1))}${colors.reset}`;
|
|
2667
|
+
const serviceNameWidth = Math.max(7, ...servicesSnapshot.map((service) => service.name.length));
|
|
2668
|
+
const visibilityWidth = 8;
|
|
2669
|
+
const statusWidth = 10;
|
|
2670
|
+
const rows = [];
|
|
2671
|
+
rows.push(padLine(title, width));
|
|
2672
|
+
rows.push(padLine(summary, width));
|
|
2673
|
+
rows.push(divider);
|
|
2674
|
+
for (const service of servicesSnapshot) {
|
|
2675
|
+
const stateColor = getStatusColor(service.status);
|
|
2676
|
+
const detail = service.detail ? ` \xB7 ${service.detail}` : "";
|
|
2677
|
+
const targetWidth = Math.max(width - serviceNameWidth - visibilityWidth - statusWidth - 6, 8);
|
|
2678
|
+
const target = truncateText(`${getTargetLabel(service)}${detail}`, targetWidth);
|
|
2679
|
+
const row = `${colors.bold}${service.name.padEnd(serviceNameWidth)}${colors.reset} ${colors.dim}${service.visibility.padEnd(visibilityWidth)}${colors.reset} ${stateColor}${service.status.padEnd(statusWidth)}${colors.reset} ${target}`;
|
|
2680
|
+
rows.push(padLine(row, width));
|
|
2681
|
+
}
|
|
2682
|
+
rows.push(divider);
|
|
2683
|
+
const footerLines = 3;
|
|
2684
|
+
const fixedHeight = rows.length + footerLines;
|
|
2685
|
+
const logHeight = Math.max(height - fixedHeight, 3);
|
|
2686
|
+
const contentLines = helpVisible ? helpLines : logEntries.flatMap((entry) => {
|
|
2687
|
+
const prefixPlain = `${entry.timestamp} [${entry.source}] `;
|
|
2688
|
+
const prefixColor = `${colors.dim}${entry.timestamp}${colors.reset} ${getSourceColor(entry.source)}[${entry.source}]${colors.reset} `;
|
|
2689
|
+
const wrapped = wrapText(entry.message, Math.max(width - prefixPlain.length, 12));
|
|
2690
|
+
return wrapped.map((line, index) => {
|
|
2691
|
+
if (index === 0) {
|
|
2692
|
+
return `${prefixColor}${getLogColor(entry.level)}${line}${colors.reset}`;
|
|
2693
|
+
}
|
|
2694
|
+
return `${" ".repeat(prefixPlain.length)}${getLogColor(entry.level)}${line}${colors.reset}`;
|
|
2695
|
+
});
|
|
2696
|
+
});
|
|
2697
|
+
const visibleContent = contentLines.length > logHeight ? contentLines.slice(contentLines.length - logHeight) : contentLines;
|
|
2698
|
+
for (const line of visibleContent) {
|
|
2699
|
+
rows.push(padLine(line, width));
|
|
2700
|
+
}
|
|
2701
|
+
for (let index = visibleContent.length;index < logHeight; index++) {
|
|
2702
|
+
rows.push(" ".repeat(width));
|
|
2703
|
+
}
|
|
2704
|
+
rows.push(divider);
|
|
2705
|
+
const footerText = helpVisible ? "Esc or h closes help" : "Hotkeys: h help o open r restart p pause c clear logs q quit $ shell";
|
|
2706
|
+
rows.push(padLine(`${colors.dim}${truncateText(footerText, width)}${colors.reset}`, width));
|
|
2707
|
+
const promptLine = shellMode ? `${colors.yellow}$ ${colors.reset}${truncateText(promptBuffer, Math.max(width - 2, 0))}` : `${colors.dim}Press a hotkey or $ for shell mode${colors.reset}`;
|
|
2708
|
+
rows.push(padLine(promptLine, width));
|
|
2709
|
+
const screen = rows.slice(0, height).map((line) => `\x1B[2K${line}`).join(`
|
|
2710
|
+
`);
|
|
2711
|
+
process.stdout.write(`\x1B[H${screen}`);
|
|
2712
|
+
if (shellMode) {
|
|
2713
|
+
const promptColumn = Math.min(promptBuffer.length + 3, width);
|
|
2714
|
+
const promptRow = Math.min(rows.length, height);
|
|
2715
|
+
process.stdout.write(`\x1B[${promptRow};${promptColumn}H\x1B[?25h`);
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
process.stdout.write("\x1B[?25l");
|
|
2719
|
+
};
|
|
2720
|
+
const setServiceStatus = (name, status2, detail) => {
|
|
2721
|
+
const existing = serviceStates.get(name);
|
|
2722
|
+
if (!existing) {
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
existing.status = status2;
|
|
2726
|
+
existing.detail = detail;
|
|
2727
|
+
scheduleRender();
|
|
2728
|
+
};
|
|
2729
|
+
const addLog = (source, message, level = "info") => {
|
|
2730
|
+
const cleanMessage = stripAnsi(message).trimEnd();
|
|
2731
|
+
if (!cleanMessage) {
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
for (const line of cleanMessage.split(`
|
|
2735
|
+
`)) {
|
|
2736
|
+
logEntries.push({
|
|
2737
|
+
level,
|
|
2738
|
+
message: line,
|
|
2739
|
+
source,
|
|
2740
|
+
timestamp: formatTimestamp2()
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2743
|
+
if (logEntries.length > MAX_LOG_ENTRIES) {
|
|
2744
|
+
logEntries.splice(0, logEntries.length - MAX_LOG_ENTRIES);
|
|
2745
|
+
}
|
|
2746
|
+
scheduleRender();
|
|
2747
|
+
};
|
|
2748
|
+
const clearLogs = () => {
|
|
2749
|
+
logEntries.length = 0;
|
|
2750
|
+
scheduleRender();
|
|
2751
|
+
};
|
|
2752
|
+
const navigateShellHistory = (direction) => {
|
|
2753
|
+
if (!shellMode || shellHistory.length === 0) {
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
if (direction === "up") {
|
|
2757
|
+
if (shellHistoryIndex < shellHistory.length - 1) {
|
|
2758
|
+
shellHistoryIndex++;
|
|
2759
|
+
}
|
|
2760
|
+
} else if (shellHistoryIndex <= 0) {
|
|
2761
|
+
shellHistoryIndex = UNFOUND_INDEX;
|
|
2762
|
+
promptBuffer = "";
|
|
2763
|
+
scheduleRender();
|
|
2764
|
+
return;
|
|
2765
|
+
} else {
|
|
2766
|
+
shellHistoryIndex--;
|
|
2767
|
+
}
|
|
2768
|
+
promptBuffer = shellHistoryIndex === UNFOUND_INDEX ? "" : shellHistory[shellHistory.length - 1 - shellHistoryIndex] ?? "";
|
|
2769
|
+
scheduleRender();
|
|
2770
|
+
};
|
|
2771
|
+
const runShortcut = async (action) => {
|
|
2772
|
+
if (action === "clear") {
|
|
2773
|
+
clearLogs();
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
if (action === "help") {
|
|
2777
|
+
helpVisible = !helpVisible;
|
|
2778
|
+
scheduleRender();
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
await actions[action]();
|
|
2782
|
+
};
|
|
2783
|
+
const submitShellCommand = async () => {
|
|
2784
|
+
const command = promptBuffer.trim();
|
|
2785
|
+
if (!command) {
|
|
2786
|
+
resetPrompt();
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
shellHistory.push(command);
|
|
2790
|
+
resetPrompt();
|
|
2791
|
+
addLog("shell", `$ ${command}`, "info");
|
|
2792
|
+
await actions.shell(command);
|
|
2793
|
+
};
|
|
2794
|
+
const handleEscapeSequence = (char) => {
|
|
2795
|
+
if (!escapeBuffer) {
|
|
2796
|
+
escapeBuffer = ESCAPE;
|
|
2797
|
+
}
|
|
2798
|
+
escapeBuffer += char;
|
|
2799
|
+
if (escapeBuffer === `${ESCAPE}[`) {
|
|
2800
|
+
armEscapeTimer();
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2803
|
+
if (escapeBuffer === `${ESCAPE}[A`) {
|
|
2804
|
+
clearPendingEscape();
|
|
2805
|
+
escapeBuffer = "";
|
|
2806
|
+
navigateShellHistory("up");
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
if (escapeBuffer === `${ESCAPE}[B`) {
|
|
2810
|
+
clearPendingEscape();
|
|
2811
|
+
escapeBuffer = "";
|
|
2812
|
+
navigateShellHistory("down");
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
exitEscapeMode();
|
|
2816
|
+
};
|
|
2817
|
+
const handleChar = async (char) => {
|
|
2818
|
+
if (char === "\x03") {
|
|
2819
|
+
await actions.quit();
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
if (char === ESCAPE) {
|
|
2823
|
+
escapeBuffer = ESCAPE;
|
|
2824
|
+
armEscapeTimer();
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
if (escapeBuffer) {
|
|
2828
|
+
handleEscapeSequence(char);
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
if (char === "\x7F" || char === "\b") {
|
|
2832
|
+
if (!shellMode) {
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
if (promptBuffer.length > 0) {
|
|
2836
|
+
promptBuffer = promptBuffer.slice(0, UNFOUND_INDEX);
|
|
2837
|
+
scheduleRender();
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2840
|
+
resetPrompt();
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
if (char === "\r" || char === `
|
|
2844
|
+
`) {
|
|
2845
|
+
if (shellMode) {
|
|
2846
|
+
await submitShellCommand();
|
|
2847
|
+
}
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
if (char.charCodeAt(0) < ASCII_SPACE) {
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
if (!shellMode) {
|
|
2854
|
+
if (char === "$") {
|
|
2855
|
+
shellMode = true;
|
|
2856
|
+
promptBuffer = "";
|
|
2857
|
+
scheduleRender();
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
const shortcut = SHORTCUTS2.get(char.toLowerCase());
|
|
2861
|
+
if (shortcut) {
|
|
2862
|
+
await runShortcut(shortcut);
|
|
2863
|
+
}
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
promptBuffer += char;
|
|
2867
|
+
scheduleRender();
|
|
2868
|
+
};
|
|
2869
|
+
const onResize = () => {
|
|
2870
|
+
scheduleRender();
|
|
2871
|
+
};
|
|
2872
|
+
const onData = (chunk) => {
|
|
2873
|
+
const chars = chunk.toString();
|
|
2874
|
+
(async () => {
|
|
2875
|
+
for (const char of chars) {
|
|
2876
|
+
await handleChar(char);
|
|
2877
|
+
}
|
|
2878
|
+
})();
|
|
2879
|
+
};
|
|
2880
|
+
const start2 = () => {
|
|
2881
|
+
process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H\x1B[?25l");
|
|
2882
|
+
input = openTtyStream2();
|
|
2883
|
+
if (input) {
|
|
2884
|
+
input.resume();
|
|
2885
|
+
input.on("data", onData);
|
|
2886
|
+
} else {
|
|
2887
|
+
addLog("workspace", "Interactive TTY input is unavailable in this terminal.", "warn");
|
|
2888
|
+
}
|
|
2889
|
+
process.stdout.on("resize", onResize);
|
|
2890
|
+
render();
|
|
2891
|
+
};
|
|
2892
|
+
const dispose = () => {
|
|
2893
|
+
if (disposed) {
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
disposed = true;
|
|
2897
|
+
clearPendingEscape();
|
|
2898
|
+
if (renderTimer) {
|
|
2899
|
+
clearTimeout(renderTimer);
|
|
2900
|
+
renderTimer = null;
|
|
2901
|
+
}
|
|
2902
|
+
process.stdout.off("resize", onResize);
|
|
2903
|
+
if (input) {
|
|
2904
|
+
input.off("data", onData);
|
|
2905
|
+
setRawMode(false);
|
|
2906
|
+
if (input !== process.stdin) {
|
|
2907
|
+
input.destroy();
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
process.stdout.write("\x1B[?25h\x1B[?1049l");
|
|
2911
|
+
};
|
|
2912
|
+
return {
|
|
2913
|
+
addLog,
|
|
2914
|
+
clearLogs,
|
|
2915
|
+
dispose,
|
|
2916
|
+
setServiceStatus,
|
|
2917
|
+
start: start2
|
|
2918
|
+
};
|
|
2919
|
+
};
|
|
2920
|
+
|
|
2921
|
+
// src/cli/scripts/workspace.ts
|
|
2385
2922
|
init_utils();
|
|
2386
|
-
var serviceTag = (name, color) => `\x1B[2m${formatTimestamp()}\x1B[0m ${color}[${name}]\x1B[0m`;
|
|
2387
|
-
var workspaceTag = (color, message) => `${serviceTag("workspace", color)} ${color}${message}\x1B[0m`;
|
|
2388
2923
|
var sleep = (ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
2389
2924
|
var getVisibility = (service) => service.visibility ?? "public";
|
|
2390
2925
|
var getServiceUrl = (service) => {
|
|
@@ -2393,7 +2928,6 @@ var getServiceUrl = (service) => {
|
|
|
2393
2928
|
}
|
|
2394
2929
|
return `http://localhost:${service.port}/`;
|
|
2395
2930
|
};
|
|
2396
|
-
var getServicePortLabel = (service) => service.port ? `${service.port}` : "no-port";
|
|
2397
2931
|
var getHealthcheckUrl = (service) => {
|
|
2398
2932
|
if (service.healthcheck) {
|
|
2399
2933
|
return service.healthcheck;
|
|
@@ -2474,60 +3008,9 @@ var topologicallySortServices = (services) => {
|
|
|
2474
3008
|
}
|
|
2475
3009
|
return ordered;
|
|
2476
3010
|
};
|
|
2477
|
-
var
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
if (line.trim().length === 0) {
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
onBeforeWrite?.();
|
|
2484
|
-
dest.write(`${serviceTag(name, color)} ${line}
|
|
2485
|
-
`);
|
|
2486
|
-
onAfterWrite?.();
|
|
2487
|
-
};
|
|
2488
|
-
return {
|
|
2489
|
-
push: (chunk) => {
|
|
2490
|
-
buffer += Buffer.from(chunk).toString();
|
|
2491
|
-
const lines = buffer.split(`
|
|
2492
|
-
`);
|
|
2493
|
-
buffer = lines.pop() ?? "";
|
|
2494
|
-
for (const line of lines) {
|
|
2495
|
-
writeLine(line);
|
|
2496
|
-
}
|
|
2497
|
-
},
|
|
2498
|
-
flush: () => {
|
|
2499
|
-
if (!buffer || buffer.trim().length === 0) {
|
|
2500
|
-
buffer = "";
|
|
2501
|
-
return;
|
|
2502
|
-
}
|
|
2503
|
-
writeLine(buffer);
|
|
2504
|
-
buffer = "";
|
|
2505
|
-
}
|
|
2506
|
-
};
|
|
2507
|
-
};
|
|
2508
|
-
var pipeProcessLogs = (name, processHandle, onBeforeWrite, onAfterWrite) => {
|
|
2509
|
-
const stdoutForwarder = createLineForwarder(name, "\x1B[36m", process.stdout, onBeforeWrite, onAfterWrite);
|
|
2510
|
-
const stderrForwarder = createLineForwarder(name, "\x1B[31m", process.stderr, onBeforeWrite, onAfterWrite);
|
|
2511
|
-
let bufferingStartupLogs = true;
|
|
2512
|
-
const stdoutStartupChunks = [];
|
|
2513
|
-
const stderrStartupChunks = [];
|
|
2514
|
-
const releaseStartupLogs = () => {
|
|
2515
|
-
if (!bufferingStartupLogs) {
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
bufferingStartupLogs = false;
|
|
2519
|
-
for (const chunk of stdoutStartupChunks) {
|
|
2520
|
-
stdoutForwarder.push(chunk);
|
|
2521
|
-
}
|
|
2522
|
-
stdoutStartupChunks.length = 0;
|
|
2523
|
-
stdoutForwarder.flush();
|
|
2524
|
-
for (const chunk of stderrStartupChunks) {
|
|
2525
|
-
stderrForwarder.push(chunk);
|
|
2526
|
-
}
|
|
2527
|
-
stderrStartupChunks.length = 0;
|
|
2528
|
-
stderrForwarder.flush();
|
|
2529
|
-
};
|
|
2530
|
-
const forward = async (stream, forwarder, startupBuffer) => {
|
|
3011
|
+
var pipeProcessLogs = (name, processHandle, appendLog) => {
|
|
3012
|
+
const forward = async (stream, level) => {
|
|
3013
|
+
let buffer = "";
|
|
2531
3014
|
const reader = stream.getReader();
|
|
2532
3015
|
try {
|
|
2533
3016
|
while (true) {
|
|
@@ -2538,28 +3021,33 @@ var pipeProcessLogs = (name, processHandle, onBeforeWrite, onAfterWrite) => {
|
|
|
2538
3021
|
if (!value) {
|
|
2539
3022
|
continue;
|
|
2540
3023
|
}
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
3024
|
+
buffer += Buffer.from(value).toString();
|
|
3025
|
+
const lines = buffer.split(`
|
|
3026
|
+
`);
|
|
3027
|
+
buffer = lines.pop() ?? "";
|
|
3028
|
+
for (const line of lines) {
|
|
3029
|
+
if (line.trim().length === 0) {
|
|
3030
|
+
continue;
|
|
3031
|
+
}
|
|
3032
|
+
appendLog(name, line, level);
|
|
2544
3033
|
}
|
|
2545
|
-
forwarder.push(value);
|
|
2546
3034
|
}
|
|
2547
3035
|
} finally {
|
|
2548
|
-
if (
|
|
2549
|
-
|
|
3036
|
+
if (buffer.trim().length > 0) {
|
|
3037
|
+
appendLog(name, buffer, level);
|
|
2550
3038
|
}
|
|
2551
3039
|
reader.releaseLock();
|
|
2552
3040
|
}
|
|
2553
3041
|
};
|
|
2554
|
-
forward(processHandle.stdout,
|
|
2555
|
-
forward(processHandle.stderr,
|
|
2556
|
-
return { releaseStartupLogs };
|
|
3042
|
+
forward(processHandle.stdout, "info");
|
|
3043
|
+
forward(processHandle.stderr, "error");
|
|
2557
3044
|
};
|
|
2558
3045
|
var resolveService = (name, service, options) => {
|
|
2559
3046
|
const cwd = resolve6(service.cwd ?? ".");
|
|
2560
3047
|
const envVars = {
|
|
2561
3048
|
...process.env,
|
|
2562
3049
|
...service.env,
|
|
3050
|
+
ABSOLUTE_WORKSPACE_MANAGED: "1",
|
|
2563
3051
|
ABSOLUTE_WORKSPACE_SERVICE_NAME: name,
|
|
2564
3052
|
ABSOLUTE_WORKSPACE_SERVICE_VISIBILITY: getVisibility(service),
|
|
2565
3053
|
FORCE_COLOR: "1",
|
|
@@ -2598,13 +3086,6 @@ var resolveService = (name, service, options) => {
|
|
|
2598
3086
|
visibility: getVisibility(service)
|
|
2599
3087
|
};
|
|
2600
3088
|
};
|
|
2601
|
-
var renderWorkspaceStepStart = (message) => {
|
|
2602
|
-
process.stdout.write(`${workspaceTag("\x1B[36m", message)}\r`);
|
|
2603
|
-
};
|
|
2604
|
-
var renderWorkspaceStepDone = (message, color = "\x1B[32m") => {
|
|
2605
|
-
process.stdout.write(`\r\x1B[2K${workspaceTag(color, message)}
|
|
2606
|
-
`);
|
|
2607
|
-
};
|
|
2608
3089
|
var workspace = async (subcommand, options) => {
|
|
2609
3090
|
if (subcommand !== "dev") {
|
|
2610
3091
|
throw new Error(subcommand ? `Unknown workspace command: ${subcommand}` : "No workspace subcommand specified. Use `absolute workspace dev`.");
|
|
@@ -2616,20 +3097,28 @@ var workspace = async (subcommand, options) => {
|
|
|
2616
3097
|
let shuttingDown = false;
|
|
2617
3098
|
let restarting = false;
|
|
2618
3099
|
let paused = false;
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
}
|
|
2631
|
-
|
|
2632
|
-
|
|
3100
|
+
const tui = createWorkspaceTui({
|
|
3101
|
+
actions: {
|
|
3102
|
+
open: () => openInBrowser(),
|
|
3103
|
+
pause: () => {
|
|
3104
|
+
togglePause();
|
|
3105
|
+
},
|
|
3106
|
+
quit: () => {
|
|
3107
|
+
shutdown(0);
|
|
3108
|
+
},
|
|
3109
|
+
restart: () => restartWorkspace(),
|
|
3110
|
+
shell: (command) => runShellCommand2(command)
|
|
3111
|
+
},
|
|
3112
|
+
services: orderedNames.map((name) => {
|
|
3113
|
+
const service = workspaceConfig.services[name];
|
|
3114
|
+
return {
|
|
3115
|
+
name,
|
|
3116
|
+
port: service?.port,
|
|
3117
|
+
url: service ? getServiceUrl(service) : null,
|
|
3118
|
+
visibility: service ? getVisibility(service) : "public"
|
|
3119
|
+
};
|
|
3120
|
+
})
|
|
3121
|
+
});
|
|
2633
3122
|
const killProcesses = async () => {
|
|
2634
3123
|
const snapshot = [...running];
|
|
2635
3124
|
running.length = 0;
|
|
@@ -2654,9 +3143,7 @@ var workspace = async (subcommand, options) => {
|
|
|
2654
3143
|
return;
|
|
2655
3144
|
}
|
|
2656
3145
|
shuttingDown = true;
|
|
2657
|
-
|
|
2658
|
-
interactive.dispose();
|
|
2659
|
-
}
|
|
3146
|
+
tui.dispose();
|
|
2660
3147
|
if (paused) {
|
|
2661
3148
|
for (const service of running) {
|
|
2662
3149
|
sendSignalToService(service.process, "SIGCONT");
|
|
@@ -2666,33 +3153,7 @@ var workspace = async (subcommand, options) => {
|
|
|
2666
3153
|
await killProcesses();
|
|
2667
3154
|
process.exit(exitCode);
|
|
2668
3155
|
};
|
|
2669
|
-
const buildWorkspaceReadySummary = () => {
|
|
2670
|
-
const publicServices = orderedNames.map((name) => {
|
|
2671
|
-
const service = workspaceConfig.services[name];
|
|
2672
|
-
if (!service || getVisibility(service) !== "public") {
|
|
2673
|
-
return null;
|
|
2674
|
-
}
|
|
2675
|
-
const url = getServiceUrl(service);
|
|
2676
|
-
return url ? `${name} ${url}` : name;
|
|
2677
|
-
}).filter((value) => Boolean(value));
|
|
2678
|
-
const internalServices = orderedNames.map((name) => {
|
|
2679
|
-
const service = workspaceConfig.services[name];
|
|
2680
|
-
if (!service || getVisibility(service) !== "internal") {
|
|
2681
|
-
return null;
|
|
2682
|
-
}
|
|
2683
|
-
return service.port ? `${name} on ${service.port}` : name;
|
|
2684
|
-
}).filter((value) => Boolean(value));
|
|
2685
|
-
const segments = [];
|
|
2686
|
-
if (publicServices.length > 0) {
|
|
2687
|
-
segments.push(...publicServices);
|
|
2688
|
-
}
|
|
2689
|
-
if (internalServices.length > 0) {
|
|
2690
|
-
segments.push(`internal: ${internalServices.join(", ")}`);
|
|
2691
|
-
}
|
|
2692
|
-
return segments.join(" \xB7 ");
|
|
2693
|
-
};
|
|
2694
3156
|
const startServices = async () => {
|
|
2695
|
-
workspaceReady = false;
|
|
2696
3157
|
for (const name of orderedNames) {
|
|
2697
3158
|
const service = workspaceConfig.services[name];
|
|
2698
3159
|
if (!service) {
|
|
@@ -2706,7 +3167,7 @@ var workspace = async (subcommand, options) => {
|
|
|
2706
3167
|
if (resolved.service.kind === "absolute" && resolved.configPath && !existsSync8(resolved.configPath)) {
|
|
2707
3168
|
throw new Error(`${name} references missing config "${resolved.configPath}"`);
|
|
2708
3169
|
}
|
|
2709
|
-
|
|
3170
|
+
tui.setServiceStatus(name, restarting ? "restarting" : "starting");
|
|
2710
3171
|
const processHandle = Bun.spawn(resolved.command, {
|
|
2711
3172
|
cwd: resolved.cwd,
|
|
2712
3173
|
env: resolved.env,
|
|
@@ -2714,12 +3175,11 @@ var workspace = async (subcommand, options) => {
|
|
|
2714
3175
|
stdin: "ignore",
|
|
2715
3176
|
stdout: "pipe"
|
|
2716
3177
|
});
|
|
2717
|
-
|
|
3178
|
+
pipeProcessLogs(name, processHandle, tui.addLog);
|
|
2718
3179
|
const runningService = {
|
|
2719
3180
|
name,
|
|
2720
3181
|
process: processHandle,
|
|
2721
|
-
resolved
|
|
2722
|
-
releaseStartupLogs
|
|
3182
|
+
resolved
|
|
2723
3183
|
};
|
|
2724
3184
|
running.push(runningService);
|
|
2725
3185
|
processHandle.exited.then((exitCode) => {
|
|
@@ -2729,59 +3189,70 @@ var workspace = async (subcommand, options) => {
|
|
|
2729
3189
|
if (!running.includes(runningService)) {
|
|
2730
3190
|
return;
|
|
2731
3191
|
}
|
|
2732
|
-
|
|
3192
|
+
tui.setServiceStatus(name, "error", `exit code ${exitCode || 1}`);
|
|
3193
|
+
tui.addLog("workspace", `${name} exited with code ${exitCode || 1}. Shutting down workspace.`, "error");
|
|
2733
3194
|
shutdown(exitCode || 1);
|
|
2734
3195
|
});
|
|
2735
3196
|
await waitForHealthcheck(getHealthcheckUrl(resolved.service));
|
|
2736
|
-
|
|
2737
|
-
releaseStartupLogs();
|
|
3197
|
+
tui.setServiceStatus(name, "ready");
|
|
2738
3198
|
}
|
|
2739
|
-
workspaceReady = true;
|
|
2740
|
-
console.log(workspaceTag("\x1B[32m", `Ready \xB7 ${buildWorkspaceReadySummary()}`));
|
|
2741
|
-
printHint();
|
|
2742
|
-
interactive?.showPrompt();
|
|
2743
3199
|
};
|
|
2744
3200
|
const restartWorkspace = async () => {
|
|
2745
3201
|
if (shuttingDown || restarting) {
|
|
2746
3202
|
return;
|
|
2747
3203
|
}
|
|
2748
3204
|
restarting = true;
|
|
2749
|
-
workspaceReady = false;
|
|
2750
3205
|
if (paused) {
|
|
2751
3206
|
for (const service of running) {
|
|
2752
3207
|
sendSignalToService(service.process, "SIGCONT");
|
|
2753
3208
|
}
|
|
2754
3209
|
paused = false;
|
|
2755
3210
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
3211
|
+
tui.addLog("workspace", "Restarting workspace...", "info");
|
|
3212
|
+
for (const name of orderedNames) {
|
|
3213
|
+
tui.setServiceStatus(name, "restarting");
|
|
3214
|
+
}
|
|
2758
3215
|
await killProcesses();
|
|
2759
3216
|
restarting = false;
|
|
2760
3217
|
await startServices();
|
|
3218
|
+
tui.addLog("workspace", "Workspace ready.", "success");
|
|
2761
3219
|
};
|
|
2762
3220
|
const togglePause = () => {
|
|
2763
3221
|
if (paused) {
|
|
2764
3222
|
for (const service of running) {
|
|
2765
3223
|
sendSignalToService(service.process, "SIGCONT");
|
|
3224
|
+
tui.setServiceStatus(service.name, "ready");
|
|
2766
3225
|
}
|
|
2767
3226
|
paused = false;
|
|
2768
|
-
|
|
3227
|
+
tui.addLog("workspace", "Workspace resumed.", "success");
|
|
2769
3228
|
} else {
|
|
2770
3229
|
for (const service of running) {
|
|
2771
3230
|
sendSignalToService(service.process, "SIGSTOP");
|
|
3231
|
+
tui.setServiceStatus(service.name, "paused");
|
|
2772
3232
|
}
|
|
2773
3233
|
paused = true;
|
|
2774
|
-
|
|
3234
|
+
tui.addLog("workspace", "Workspace paused.", "warn");
|
|
2775
3235
|
}
|
|
2776
3236
|
};
|
|
2777
3237
|
const runShellCommand2 = async (command) => {
|
|
2778
|
-
|
|
3238
|
+
const processHandle = Bun.spawn(["bash", "-lc", command], {
|
|
3239
|
+
env: { ...process.env, FORCE_COLOR: "1" },
|
|
3240
|
+
stderr: "pipe",
|
|
3241
|
+
stdout: "pipe"
|
|
3242
|
+
});
|
|
3243
|
+
pipeProcessLogs("shell", processHandle, tui.addLog);
|
|
3244
|
+
const exitCode = await processHandle.exited;
|
|
3245
|
+
if (exitCode === 0) {
|
|
3246
|
+
tui.addLog("workspace", `Shell command finished: ${command}`, "success");
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
tui.addLog("workspace", `Shell command failed with exit code ${exitCode}: ${command}`, "error");
|
|
2779
3250
|
};
|
|
2780
3251
|
const openInBrowser = async () => {
|
|
2781
3252
|
const publicService = orderedNames.map((name) => workspaceConfig.services[name]).find((service) => service && getVisibility(service) === "public");
|
|
2782
3253
|
const url = publicService ? getServiceUrl(publicService) : null;
|
|
2783
3254
|
if (!url) {
|
|
2784
|
-
|
|
3255
|
+
tui.addLog("workspace", "No public service to open.", "warn");
|
|
2785
3256
|
return;
|
|
2786
3257
|
}
|
|
2787
3258
|
const { platform: platform4 } = process;
|
|
@@ -2802,35 +3273,21 @@ var workspace = async (subcommand, options) => {
|
|
|
2802
3273
|
stderr: "ignore",
|
|
2803
3274
|
stdout: "ignore"
|
|
2804
3275
|
});
|
|
2805
|
-
|
|
3276
|
+
tui.addLog("workspace", `Opening ${url}`, "info");
|
|
2806
3277
|
} catch {
|
|
2807
|
-
|
|
3278
|
+
tui.addLog("workspace", `Could not open browser automatically. Visit ${url}`, "warn");
|
|
2808
3279
|
}
|
|
2809
3280
|
};
|
|
2810
|
-
interactive = createInteractiveHandler({
|
|
2811
|
-
shell: runShellCommand2,
|
|
2812
|
-
clear: () => {
|
|
2813
|
-
process.stdout.write("\x1Bc");
|
|
2814
|
-
},
|
|
2815
|
-
help: () => {
|
|
2816
|
-
printHelp();
|
|
2817
|
-
},
|
|
2818
|
-
open: () => openInBrowser(),
|
|
2819
|
-
pause: () => {
|
|
2820
|
-
togglePause();
|
|
2821
|
-
},
|
|
2822
|
-
quit: () => {
|
|
2823
|
-
shutdown(0);
|
|
2824
|
-
},
|
|
2825
|
-
restart: () => restartWorkspace()
|
|
2826
|
-
});
|
|
2827
3281
|
process.on("SIGINT", () => {
|
|
2828
3282
|
shutdown(0);
|
|
2829
3283
|
});
|
|
2830
3284
|
process.on("SIGTERM", () => {
|
|
2831
3285
|
shutdown(0);
|
|
2832
3286
|
});
|
|
3287
|
+
tui.start();
|
|
3288
|
+
tui.addLog("workspace", `Workspace booting ${orderedNames.length} services.`, "info");
|
|
2833
3289
|
await startServices();
|
|
3290
|
+
tui.addLog("workspace", "Workspace ready.", "success");
|
|
2834
3291
|
await new Promise(() => {});
|
|
2835
3292
|
};
|
|
2836
3293
|
|