namespace Journal.WebGateway.Infrastructure; public sealed record RepoRootResolution(string Root, bool IsCustom, string Source); public static class PathResolver { public static RepoRootResolution ResolveRepoRoot(IConfiguration config) { var fromConfig = config.GetValue("GatewaySettings:RepoRoot"); if (!string.IsNullOrWhiteSpace(fromConfig) && Directory.Exists(fromConfig)) return new RepoRootResolution(Path.GetFullPath(fromConfig), true, "config"); var fromEnv = Environment.GetEnvironmentVariable("JOURNAL_PROJECT_ROOT"); if (!string.IsNullOrWhiteSpace(fromEnv) && Directory.Exists(fromEnv)) return new RepoRootResolution(Path.GetFullPath(fromEnv), true, "env"); if (IsPublishedLayout()) { throw new InvalidOperationException( "Published WebGateway requires an explicit root. Set GatewaySettings:RepoRoot or JOURNAL_PROJECT_ROOT before launch."); } foreach (var start in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory }) { var resolved = FindRepoRoot(start); if (resolved is not null) return new RepoRootResolution(resolved, false, "auto"); } return new RepoRootResolution(Path.GetFullPath(Directory.GetCurrentDirectory()), false, "cwd"); } private static string? FindRepoRoot(string start) { var cursor = Path.GetFullPath(start); while (!string.IsNullOrWhiteSpace(cursor)) { if (File.Exists(Path.Combine(cursor, "Journal.slnx")) || Directory.Exists(Path.Combine(cursor, "Journal.Sidecar")) || File.Exists(Path.Combine(cursor, "Journal.Sidecar.exe")) || File.Exists(Path.Combine(cursor, "Journal.Sidecar")) || Directory.Exists(Path.Combine(cursor, "Journal.Core"))) return cursor; var parent = Directory.GetParent(cursor); if (parent is null || string.Equals(parent.FullName, cursor, StringComparison.OrdinalIgnoreCase)) return null; cursor = parent.FullName; } return null; } public static string ResolveWebDist(string repoRootPath, IConfiguration config) { var fromConfig = config.GetValue("GatewaySettings:WebDist"); if (!string.IsNullOrWhiteSpace(fromConfig)) return Path.GetFullPath(fromConfig); var fromEnv = Environment.GetEnvironmentVariable("JOURNAL_WEB_DIST"); if (!string.IsNullOrWhiteSpace(fromEnv)) return Path.GetFullPath(fromEnv); var packagedWwwRoot = Path.Combine(AppContext.BaseDirectory, "wwwroot"); if (Directory.Exists(packagedWwwRoot)) return packagedWwwRoot; return Path.Combine(repoRootPath, "Journal.App", "build"); } private static bool IsPublishedLayout() { var baseDir = Path.GetFullPath(AppContext.BaseDirectory); return Directory.Exists(Path.Combine(baseDir, "wwwroot")) && !File.Exists(Path.Combine(baseDir, "Journal.slnx")) && !Directory.Exists(Path.Combine(baseDir, "Journal.WebGateway")); } }