journal/Journal.DevTool/Config/ConfigLoader.cs
stan44 5b383858ae feat(sdt): add Journal.DevTool TUI, devtool.json, and justfile
- 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
2026-02-27 13:12:36 -06:00

45 lines
1.5 KiB
C#

using System.Text.Json;
namespace Sdt.Config;
public static class ConfigLoader
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip,
};
/// <summary>
/// Walks up from <paramref name="startDir"/> (or CWD) until it finds devtool.json.
/// Returns null if not found.
/// </summary>
public static (DevToolConfig Config, string ProjectRoot)? FindAndLoad(string? startDir = null)
{
var dir = new DirectoryInfo(startDir ?? Directory.GetCurrentDirectory());
while (dir is not null)
{
var candidate = Path.Combine(dir.FullName, "devtool.json");
if (File.Exists(candidate))
{
try
{
var json = File.ReadAllText(candidate);
var config = JsonSerializer.Deserialize<DevToolConfig>(json, JsonOptions)
?? throw new InvalidOperationException("devtool.json deserialized to null.");
return (config, dir.FullName);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to parse devtool.json at {candidate}: {ex.Message}", ex);
}
}
dir = dir.Parent!;
}
return null;
}
}