@absolutejs/absolute 0.19.0-beta.666 → 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 CHANGED
@@ -669,7 +669,7 @@ var exports_compile = {};
669
669
  __export(exports_compile, {
670
670
  compile: () => compile
671
671
  });
672
- var {env: env4 } = globalThis.Bun;
672
+ var {env: env3 } = globalThis.Bun;
673
673
  import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync8, unlinkSync } from "fs";
674
674
  import { basename as basename2, join as join6, relative, resolve as resolve7 } from "path";
675
675
  var cliTag3 = (color, message) => `\x1B[2m${formatTimestamp()}\x1B[0m ${color}[cli]\x1B[0m ${color}${message}\x1B[0m`, collectFiles2 = (dir) => {
@@ -848,7 +848,7 @@ console.log(\`
848
848
  \`);
849
849
  `;
850
850
  }, stubPlugin, FRAMEWORK_EXTERNALS, compile = async (serverEntry, outdir, outfile, configPath2) => {
851
- const prerenderPort = Number(env4.COMPILE_PORT) || Number(env4.PORT) || DEFAULT_PORT + 1;
851
+ const prerenderPort = Number(env3.COMPILE_PORT) || Number(env3.PORT) || DEFAULT_PORT + 1;
852
852
  killStaleProcesses(prerenderPort);
853
853
  const entryName = basename2(serverEntry).replace(/\.[^.]+$/, "");
854
854
  const resolvedOutdir = resolve7(outdir ?? "dist");
@@ -1050,17 +1050,17 @@ var run = async (name, command) => {
1050
1050
  }, shellEscape = (value) => `'${value.replaceAll("'", "'\\''")}'`, runShell = async (name, command) => run(name, ["/bin/bash", "-lc", command]), findBin = (name) => {
1051
1051
  const local = resolve8("node_modules", ".bin", name);
1052
1052
  return existsSync10(local) ? local : null;
1053
- }, stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, ""), formatSvelteOutput = (output) => {
1053
+ }, stripAnsi2 = (str) => str.replace(/\x1b\[[0-9;]*m/g, ""), formatSvelteOutput = (output) => {
1054
1054
  const cwd = `${process.cwd()}/`;
1055
- const summaryMatch = stripAnsi(output).match(/svelte-check found (\d+) error/);
1055
+ const summaryMatch = stripAnsi2(output).match(/svelte-check found (\d+) error/);
1056
1056
  const errorCount = summaryMatch ? parseInt(summaryMatch[1] ?? "0", 10) : 0;
1057
1057
  const formatted = output.split(`
1058
1058
  `).filter((line) => {
1059
- const plain = stripAnsi(line);
1059
+ const plain = stripAnsi2(line);
1060
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() !== "";
1061
1061
  }).flatMap((line) => {
1062
1062
  const result = line.replaceAll(cwd, "");
1063
- const plain = stripAnsi(result);
1063
+ const plain = stripAnsi2(result);
1064
1064
  const pathMatch = plain.match(/^(\S+\.svelte):(\d+:\d+)$/);
1065
1065
  if (pathMatch) {
1066
1066
  return [
@@ -1068,9 +1068,9 @@ var run = async (name, command) => {
1068
1068
  ];
1069
1069
  }
1070
1070
  if (result.includes("\x1B[35m")) {
1071
- const plainLine = stripAnsi(result);
1072
- const before = stripAnsi(result.split("\x1B[35m")[0] ?? "");
1073
- const token = stripAnsi((result.split("\x1B[35m")[1] ?? "").split(/\x1b\[3[69]m/)[0] ?? "");
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] ?? "");
1074
1074
  if (!token)
1075
1075
  return [result];
1076
1076
  const expanded = before.replace(/\t/g, " ");
@@ -2379,13 +2379,547 @@ var start = async (serverEntry, outdir, configPath2) => {
2379
2379
  // src/cli/scripts/workspace.ts
2380
2380
  init_constants();
2381
2381
  init_loadConfig();
2382
- init_startupBanner();
2383
- var {$: $3 } = globalThis.Bun;
2384
2382
  import { existsSync as existsSync8 } from "fs";
2385
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
2386
2922
  init_utils();
2387
- var serviceTag = (name, color) => `\x1B[2m${formatTimestamp()}\x1B[0m ${color}[${name}]\x1B[0m`;
2388
- var workspaceTag = (color, message) => `${serviceTag("workspace", color)} ${color}${message}\x1B[0m`;
2389
2923
  var sleep = (ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
2390
2924
  var getVisibility = (service) => service.visibility ?? "public";
2391
2925
  var getServiceUrl = (service) => {
@@ -2394,7 +2928,6 @@ var getServiceUrl = (service) => {
2394
2928
  }
2395
2929
  return `http://localhost:${service.port}/`;
2396
2930
  };
2397
- var getServicePortLabel = (service) => service.port ? `${service.port}` : "no-port";
2398
2931
  var getHealthcheckUrl = (service) => {
2399
2932
  if (service.healthcheck) {
2400
2933
  return service.healthcheck;
@@ -2475,60 +3008,9 @@ var topologicallySortServices = (services) => {
2475
3008
  }
2476
3009
  return ordered;
2477
3010
  };
2478
- var createLineForwarder = (name, color, dest, onBeforeWrite, onAfterWrite) => {
2479
- let buffer = "";
2480
- const writeLine = (line) => {
2481
- if (line.trim().length === 0) {
2482
- return;
2483
- }
2484
- onBeforeWrite?.();
2485
- dest.write(`${serviceTag(name, color)} ${line}
2486
- `);
2487
- onAfterWrite?.();
2488
- };
2489
- return {
2490
- push: (chunk) => {
2491
- buffer += Buffer.from(chunk).toString();
2492
- const lines = buffer.split(`
2493
- `);
2494
- buffer = lines.pop() ?? "";
2495
- for (const line of lines) {
2496
- writeLine(line);
2497
- }
2498
- },
2499
- flush: () => {
2500
- if (!buffer || buffer.trim().length === 0) {
2501
- buffer = "";
2502
- return;
2503
- }
2504
- writeLine(buffer);
2505
- buffer = "";
2506
- }
2507
- };
2508
- };
2509
- var pipeProcessLogs = (name, processHandle, onBeforeWrite, onAfterWrite) => {
2510
- const stdoutForwarder = createLineForwarder(name, "\x1B[36m", process.stdout, onBeforeWrite, onAfterWrite);
2511
- const stderrForwarder = createLineForwarder(name, "\x1B[31m", process.stderr, onBeforeWrite, onAfterWrite);
2512
- let bufferingStartupLogs = true;
2513
- const stdoutStartupChunks = [];
2514
- const stderrStartupChunks = [];
2515
- const releaseStartupLogs = () => {
2516
- if (!bufferingStartupLogs) {
2517
- return;
2518
- }
2519
- bufferingStartupLogs = false;
2520
- for (const chunk of stdoutStartupChunks) {
2521
- stdoutForwarder.push(chunk);
2522
- }
2523
- stdoutStartupChunks.length = 0;
2524
- stdoutForwarder.flush();
2525
- for (const chunk of stderrStartupChunks) {
2526
- stderrForwarder.push(chunk);
2527
- }
2528
- stderrStartupChunks.length = 0;
2529
- stderrForwarder.flush();
2530
- };
2531
- const forward = async (stream, forwarder, startupBuffer) => {
3011
+ var pipeProcessLogs = (name, processHandle, appendLog) => {
3012
+ const forward = async (stream, level) => {
3013
+ let buffer = "";
2532
3014
  const reader = stream.getReader();
2533
3015
  try {
2534
3016
  while (true) {
@@ -2539,28 +3021,33 @@ var pipeProcessLogs = (name, processHandle, onBeforeWrite, onAfterWrite) => {
2539
3021
  if (!value) {
2540
3022
  continue;
2541
3023
  }
2542
- if (bufferingStartupLogs) {
2543
- startupBuffer.push(value);
2544
- continue;
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);
2545
3033
  }
2546
- forwarder.push(value);
2547
3034
  }
2548
3035
  } finally {
2549
- if (!bufferingStartupLogs) {
2550
- forwarder.flush();
3036
+ if (buffer.trim().length > 0) {
3037
+ appendLog(name, buffer, level);
2551
3038
  }
2552
3039
  reader.releaseLock();
2553
3040
  }
2554
3041
  };
2555
- forward(processHandle.stdout, stdoutForwarder, stdoutStartupChunks);
2556
- forward(processHandle.stderr, stderrForwarder, stderrStartupChunks);
2557
- return { releaseStartupLogs };
3042
+ forward(processHandle.stdout, "info");
3043
+ forward(processHandle.stderr, "error");
2558
3044
  };
2559
3045
  var resolveService = (name, service, options) => {
2560
3046
  const cwd = resolve6(service.cwd ?? ".");
2561
3047
  const envVars = {
2562
3048
  ...process.env,
2563
3049
  ...service.env,
3050
+ ABSOLUTE_WORKSPACE_MANAGED: "1",
2564
3051
  ABSOLUTE_WORKSPACE_SERVICE_NAME: name,
2565
3052
  ABSOLUTE_WORKSPACE_SERVICE_VISIBILITY: getVisibility(service),
2566
3053
  FORCE_COLOR: "1",
@@ -2599,13 +3086,6 @@ var resolveService = (name, service, options) => {
2599
3086
  visibility: getVisibility(service)
2600
3087
  };
2601
3088
  };
2602
- var renderWorkspaceStepStart = (message) => {
2603
- process.stdout.write(`${workspaceTag("\x1B[36m", message)}\r`);
2604
- };
2605
- var renderWorkspaceStepDone = (message, color = "\x1B[32m") => {
2606
- process.stdout.write(`\r\x1B[2K${workspaceTag(color, message)}
2607
- `);
2608
- };
2609
3089
  var workspace = async (subcommand, options) => {
2610
3090
  if (subcommand !== "dev") {
2611
3091
  throw new Error(subcommand ? `Unknown workspace command: ${subcommand}` : "No workspace subcommand specified. Use `absolute workspace dev`.");
@@ -2617,20 +3097,28 @@ var workspace = async (subcommand, options) => {
2617
3097
  let shuttingDown = false;
2618
3098
  let restarting = false;
2619
3099
  let paused = false;
2620
- let workspaceReady = false;
2621
- let interactive = null;
2622
- const withPromptHidden = () => {
2623
- if (!workspaceReady) {
2624
- return;
2625
- }
2626
- interactive?.clearPrompt();
2627
- };
2628
- const restorePrompt = () => {
2629
- if (!workspaceReady) {
2630
- return;
2631
- }
2632
- interactive?.showPrompt();
2633
- };
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
+ });
2634
3122
  const killProcesses = async () => {
2635
3123
  const snapshot = [...running];
2636
3124
  running.length = 0;
@@ -2655,9 +3143,7 @@ var workspace = async (subcommand, options) => {
2655
3143
  return;
2656
3144
  }
2657
3145
  shuttingDown = true;
2658
- if (interactive) {
2659
- interactive.dispose();
2660
- }
3146
+ tui.dispose();
2661
3147
  if (paused) {
2662
3148
  for (const service of running) {
2663
3149
  sendSignalToService(service.process, "SIGCONT");
@@ -2667,33 +3153,7 @@ var workspace = async (subcommand, options) => {
2667
3153
  await killProcesses();
2668
3154
  process.exit(exitCode);
2669
3155
  };
2670
- const buildWorkspaceReadySummary = () => {
2671
- const publicServices = orderedNames.map((name) => {
2672
- const service = workspaceConfig.services[name];
2673
- if (!service || getVisibility(service) !== "public") {
2674
- return null;
2675
- }
2676
- const url = getServiceUrl(service);
2677
- return url ? `${name} ${url}` : name;
2678
- }).filter((value) => Boolean(value));
2679
- const internalServices = orderedNames.map((name) => {
2680
- const service = workspaceConfig.services[name];
2681
- if (!service || getVisibility(service) !== "internal") {
2682
- return null;
2683
- }
2684
- return service.port ? `${name} on ${service.port}` : name;
2685
- }).filter((value) => Boolean(value));
2686
- const segments = [];
2687
- if (publicServices.length > 0) {
2688
- segments.push(...publicServices);
2689
- }
2690
- if (internalServices.length > 0) {
2691
- segments.push(`internal: ${internalServices.join(", ")}`);
2692
- }
2693
- return segments.join(" \xB7 ");
2694
- };
2695
3156
  const startServices = async () => {
2696
- workspaceReady = false;
2697
3157
  for (const name of orderedNames) {
2698
3158
  const service = workspaceConfig.services[name];
2699
3159
  if (!service) {
@@ -2707,7 +3167,7 @@ var workspace = async (subcommand, options) => {
2707
3167
  if (resolved.service.kind === "absolute" && resolved.configPath && !existsSync8(resolved.configPath)) {
2708
3168
  throw new Error(`${name} references missing config "${resolved.configPath}"`);
2709
3169
  }
2710
- renderWorkspaceStepStart(`Starting ${name} (${resolved.visibility}, ${getServicePortLabel(resolved.service)})...`);
3170
+ tui.setServiceStatus(name, restarting ? "restarting" : "starting");
2711
3171
  const processHandle = Bun.spawn(resolved.command, {
2712
3172
  cwd: resolved.cwd,
2713
3173
  env: resolved.env,
@@ -2715,12 +3175,11 @@ var workspace = async (subcommand, options) => {
2715
3175
  stdin: "ignore",
2716
3176
  stdout: "pipe"
2717
3177
  });
2718
- const { releaseStartupLogs } = pipeProcessLogs(name, processHandle, withPromptHidden, restorePrompt);
3178
+ pipeProcessLogs(name, processHandle, tui.addLog);
2719
3179
  const runningService = {
2720
3180
  name,
2721
3181
  process: processHandle,
2722
- resolved,
2723
- releaseStartupLogs
3182
+ resolved
2724
3183
  };
2725
3184
  running.push(runningService);
2726
3185
  processHandle.exited.then((exitCode) => {
@@ -2730,59 +3189,70 @@ var workspace = async (subcommand, options) => {
2730
3189
  if (!running.includes(runningService)) {
2731
3190
  return;
2732
3191
  }
2733
- console.error(workspaceTag("\x1B[31m", `${name} exited with code ${exitCode}. Shutting down workspace.`));
3192
+ tui.setServiceStatus(name, "error", `exit code ${exitCode || 1}`);
3193
+ tui.addLog("workspace", `${name} exited with code ${exitCode || 1}. Shutting down workspace.`, "error");
2734
3194
  shutdown(exitCode || 1);
2735
3195
  });
2736
3196
  await waitForHealthcheck(getHealthcheckUrl(resolved.service));
2737
- renderWorkspaceStepDone(`${name} ready`);
2738
- releaseStartupLogs();
3197
+ tui.setServiceStatus(name, "ready");
2739
3198
  }
2740
- workspaceReady = true;
2741
- console.log(workspaceTag("\x1B[32m", `Ready \xB7 ${buildWorkspaceReadySummary()}`));
2742
- printHint();
2743
- interactive?.showPrompt();
2744
3199
  };
2745
3200
  const restartWorkspace = async () => {
2746
3201
  if (shuttingDown || restarting) {
2747
3202
  return;
2748
3203
  }
2749
3204
  restarting = true;
2750
- workspaceReady = false;
2751
3205
  if (paused) {
2752
3206
  for (const service of running) {
2753
3207
  sendSignalToService(service.process, "SIGCONT");
2754
3208
  }
2755
3209
  paused = false;
2756
3210
  }
2757
- withPromptHidden();
2758
- console.log(workspaceTag("\x1B[36m", "Restarting workspace..."));
3211
+ tui.addLog("workspace", "Restarting workspace...", "info");
3212
+ for (const name of orderedNames) {
3213
+ tui.setServiceStatus(name, "restarting");
3214
+ }
2759
3215
  await killProcesses();
2760
3216
  restarting = false;
2761
3217
  await startServices();
3218
+ tui.addLog("workspace", "Workspace ready.", "success");
2762
3219
  };
2763
3220
  const togglePause = () => {
2764
3221
  if (paused) {
2765
3222
  for (const service of running) {
2766
3223
  sendSignalToService(service.process, "SIGCONT");
3224
+ tui.setServiceStatus(service.name, "ready");
2767
3225
  }
2768
3226
  paused = false;
2769
- console.log(workspaceTag("\x1B[32m", "Workspace resumed."));
3227
+ tui.addLog("workspace", "Workspace resumed.", "success");
2770
3228
  } else {
2771
3229
  for (const service of running) {
2772
3230
  sendSignalToService(service.process, "SIGSTOP");
3231
+ tui.setServiceStatus(service.name, "paused");
2773
3232
  }
2774
3233
  paused = true;
2775
- console.log(`${workspaceTag("\x1B[33m", "Workspace paused.")} \x1B[90m[paused]\x1B[0m`);
3234
+ tui.addLog("workspace", "Workspace paused.", "warn");
2776
3235
  }
2777
3236
  };
2778
3237
  const runShellCommand2 = async (command) => {
2779
- await $3`${{ raw: command }}`.env({ ...process.env, FORCE_COLOR: "1" }).nothrow();
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");
2780
3250
  };
2781
3251
  const openInBrowser = async () => {
2782
3252
  const publicService = orderedNames.map((name) => workspaceConfig.services[name]).find((service) => service && getVisibility(service) === "public");
2783
3253
  const url = publicService ? getServiceUrl(publicService) : null;
2784
3254
  if (!url) {
2785
- console.log(workspaceTag("\x1B[33m", "No public service to open."));
3255
+ tui.addLog("workspace", "No public service to open.", "warn");
2786
3256
  return;
2787
3257
  }
2788
3258
  const { platform: platform4 } = process;
@@ -2803,35 +3273,21 @@ var workspace = async (subcommand, options) => {
2803
3273
  stderr: "ignore",
2804
3274
  stdout: "ignore"
2805
3275
  });
2806
- console.log(workspaceTag("\x1B[36m", `Opening ${url}`));
3276
+ tui.addLog("workspace", `Opening ${url}`, "info");
2807
3277
  } catch {
2808
- console.log(workspaceTag("\x1B[33m", `Could not open browser. Visit ${url}`));
3278
+ tui.addLog("workspace", `Could not open browser automatically. Visit ${url}`, "warn");
2809
3279
  }
2810
3280
  };
2811
- interactive = createInteractiveHandler({
2812
- shell: runShellCommand2,
2813
- clear: () => {
2814
- process.stdout.write("\x1Bc");
2815
- },
2816
- help: () => {
2817
- printHelp("workspace");
2818
- },
2819
- open: () => openInBrowser(),
2820
- pause: () => {
2821
- togglePause();
2822
- },
2823
- quit: () => {
2824
- shutdown(0);
2825
- },
2826
- restart: () => restartWorkspace()
2827
- });
2828
3281
  process.on("SIGINT", () => {
2829
3282
  shutdown(0);
2830
3283
  });
2831
3284
  process.on("SIGTERM", () => {
2832
3285
  shutdown(0);
2833
3286
  });
3287
+ tui.start();
3288
+ tui.addLog("workspace", `Workspace booting ${orderedNames.length} services.`, "info");
2834
3289
  await startServices();
3290
+ tui.addLog("workspace", "Workspace ready.", "success");
2835
3291
  await new Promise(() => {});
2836
3292
  };
2837
3293