- Journal.DevTool: C# TUI dev tool (SDT) using Spectre.Console - Phosphor green (#00FF41) terminal aesthetic with amber/red accents - Grouped build target menu driven by devtool.json config - Topological dependency resolver for DependsOn chains - Live stdout/stderr streaming per target step - Interactive environment variable editor (dropdown + free-text) - Toolchain management screen: Python venv create/install/upgrade, Node npm install - Workspace switcher: reads sdt-workspace.json, hot-switches between projects - Outputs as 'sdt' executable (net10.0) - devtool.json: project config for SDT - All build targets: sidecar, web, webgateway, tauri, tauri-nsis, all (virtual) - Dev targets: run-gateway - Test targets: test (smoke), gate (migration) - Cache targets: nuget-export, nuget-import - Toolchains: Python 3.14 (cpu/gpu/nlp profiles) + Node/npm (Journal.App) - Env vars: AI provider, log level, NLP backend, path overrides - justfile: just command runner recipes wrapping existing scripts - Correct dependency ordering (sidecar before tauri, web before webgateway) - OS-aware runtime detection (win-x64 / linux-x64) - Recipes: sidecar, web, webgateway, tauri, tauri-nsis, all, run, dev-app, test, gate, build, nuget-export/import, sdt - Journal.slnx: added Journal.DevTool project
109 lines
4.3 KiB
C#
109 lines
4.3 KiB
C#
using Sdt.Config;
|
|
using Spectre.Console;
|
|
|
|
namespace Sdt.Tui;
|
|
|
|
public sealed class WorkspaceScreen
|
|
{
|
|
private readonly WorkspaceConfig _workspace;
|
|
private readonly string _workspaceRoot;
|
|
private readonly string _currentProjectRoot;
|
|
|
|
public WorkspaceScreen(WorkspaceConfig workspace, string workspaceRoot, string currentProjectRoot)
|
|
{
|
|
_workspace = workspace;
|
|
_workspaceRoot = workspaceRoot;
|
|
_currentProjectRoot = currentProjectRoot;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shows the project switcher. Returns the absolute path to the selected project root,
|
|
/// or null if the user cancelled.
|
|
/// </summary>
|
|
public string? SelectProject()
|
|
{
|
|
AnsiConsole.Clear();
|
|
AnsiConsole.Write(Theme.SectionRule("WORKSPACE — " + _workspace.Name));
|
|
AnsiConsole.WriteLine();
|
|
|
|
var projects = _workspace.Projects;
|
|
if (projects.Count == 0)
|
|
{
|
|
AnsiConsole.MarkupLine(Theme.Warn("No projects defined in sdt-workspace.json."));
|
|
AnsiConsole.MarkupLine(Theme.Faint("Add entries to the \"projects\" array."));
|
|
AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back..."));
|
|
Console.ReadKey(intercept: true);
|
|
return null;
|
|
}
|
|
|
|
// Build choice list with current project marked
|
|
var choices = new List<WorkspaceMenuItem>();
|
|
foreach (var proj in projects)
|
|
{
|
|
var absPath = WorkspaceLoader.ResolveProjectRoot(_workspaceRoot, proj);
|
|
var devtoolPath = Path.Combine(absPath, "devtool.json");
|
|
var isCurrent = string.Equals(absPath, _currentProjectRoot, StringComparison.OrdinalIgnoreCase);
|
|
var exists = File.Exists(devtoolPath);
|
|
|
|
var label = isCurrent
|
|
? $"[bold {Theme.GreenBold}]► {Markup.Escape(proj.Name)}[/] [{Theme.GreenDim}](current)[/]"
|
|
: $"[{Theme.Green}] {Markup.Escape(proj.Name)}[/]";
|
|
|
|
var desc = !exists
|
|
? $" [{Theme.Red}]devtool.json not found[/]"
|
|
: string.IsNullOrWhiteSpace(proj.Description)
|
|
? $" [{Theme.GreenDim}]{Markup.Escape(absPath)}[/]"
|
|
: $" [{Theme.GreenDim}]{Markup.Escape(proj.Description)}[/]";
|
|
|
|
choices.Add(new WorkspaceMenuItem(label + "\n" + desc, absPath, exists && !isCurrent));
|
|
}
|
|
|
|
choices.Add(new WorkspaceMenuItem($"[{Theme.GreenDim}]← Cancel[/]", null, true));
|
|
|
|
// Show project table for overview
|
|
var table = new Table()
|
|
.Border(TableBorder.Rounded)
|
|
.BorderStyle(Theme.DimStyle)
|
|
.AddColumn(new TableColumn($"[{Theme.Amber}]Project[/]"))
|
|
.AddColumn(new TableColumn($"[{Theme.Amber}]Path[/]"))
|
|
.AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(12));
|
|
|
|
foreach (var proj in projects)
|
|
{
|
|
var absPath = WorkspaceLoader.ResolveProjectRoot(_workspaceRoot, proj);
|
|
var isCurrent = string.Equals(absPath, _currentProjectRoot, StringComparison.OrdinalIgnoreCase);
|
|
var hasConfig = File.Exists(Path.Combine(absPath, "devtool.json"));
|
|
|
|
table.AddRow(
|
|
isCurrent
|
|
? $"[bold {Theme.GreenBold}]► {Markup.Escape(proj.Name)}[/]"
|
|
: Theme.G(proj.Name),
|
|
Theme.Faint(proj.Path),
|
|
hasConfig ? Theme.Ok("ready") : Theme.Fail("no config"));
|
|
}
|
|
|
|
AnsiConsole.Write(table);
|
|
AnsiConsole.WriteLine();
|
|
|
|
var switchable = choices.Where(c => c.Selectable).ToList();
|
|
if (switchable.Count == 1) // only Cancel
|
|
{
|
|
AnsiConsole.MarkupLine(Theme.Warn("No other projects available to switch to."));
|
|
AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back..."));
|
|
Console.ReadKey(intercept: true);
|
|
return null;
|
|
}
|
|
|
|
var selected = AnsiConsole.Prompt(
|
|
new SelectionPrompt<WorkspaceMenuItem>()
|
|
.Title($"[{Theme.Green}]Switch to project:[/]")
|
|
.PageSize(15)
|
|
.UseConverter(m => m.Display)
|
|
.AddChoices(switchable));
|
|
|
|
return selected.AbsPath; // null = cancelled
|
|
}
|
|
|
|
private sealed record WorkspaceMenuItem(string Display, string? AbsPath, bool Selectable);
|
|
}
|