From 67516fcfd54d7aee152fea748fe597958399ef17 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 06:44:20 +0000 Subject: [PATCH 1/2] Initial plan From 23b7cbcd981764c738464ee937f5b71afc2575dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 06:48:35 +0000 Subject: [PATCH 2/2] feat(arc): resolve url from launchSettings and update defaults Agent-Logs-Url: https://github.com/Cratis/cli/sessions/a2170f86-4a72-48e4-abf8-ef2ca2f8f48e Co-authored-by: einari <134365+einari@users.noreply.github.com> --- Documentation/arc/commands.md | 2 +- Documentation/arc/index.md | 5 +- Documentation/arc/queries.md | 2 +- Source/Cli.Specs/Usings.cs | 1 + .../given/a_temp_arc_directory.cs | 66 ++++++++++++++ .../and_environment_variable_is_set.cs | 22 +++++ .../when_resolving_url/and_flag_is_set.cs | 17 ++++ ...s_exists_in_lowercase_properties_folder.cs | 21 +++++ ...ch_settings_exists_in_properties_folder.cs | 21 +++++ .../and_no_override_is_configured.cs | 17 ++++ Source/Cli/Commands/Arc/ArcDefaults.cs | 2 +- Source/Cli/Commands/Arc/ArcSettings.cs | 88 ++++++++++++++++++- .../Arc/Commands/ListCommandsCommand.cs | 2 +- .../Arc/Queries/ListQueriesCommand.cs | 2 +- 14 files changed, 259 insertions(+), 9 deletions(-) create mode 100644 Source/Cli.Specs/for_ArcSettings/given/a_temp_arc_directory.cs create mode 100644 Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_environment_variable_is_set.cs create mode 100644 Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_flag_is_set.cs create mode 100644 Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_launch_settings_exists_in_lowercase_properties_folder.cs create mode 100644 Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_launch_settings_exists_in_properties_folder.cs create mode 100644 Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_no_override_is_configured.cs diff --git a/Documentation/arc/commands.md b/Documentation/arc/commands.md index b4caa89..f5d7963 100644 --- a/Documentation/arc/commands.md +++ b/Documentation/arc/commands.md @@ -12,7 +12,7 @@ cratis arc commands list [--url ] [-o ] | Option | Description | |---|---| -| `--url ` | Base URL of the Arc application (default: `https://localhost:5001`). | +| `--url ` | Base URL of the Arc application (default: `http://localhost:5000`). | | `-o, --output ` | Output format: `table`, `plain`, `json`, `json-compact`. | ## Output diff --git a/Documentation/arc/index.md b/Documentation/arc/index.md index 1f44fa1..7f34c7b 100644 --- a/Documentation/arc/index.md +++ b/Documentation/arc/index.md @@ -8,7 +8,7 @@ All `cratis arc` commands accept the following connection flag: | Flag | Description | |---|---| -| `--url ` | Base URL of the Arc application. Overrides the `ARC_URL` environment variable. Defaults to `https://localhost:5001`. | +| `--url ` | Base URL of the Arc application. Overrides the `ARC_URL` environment variable. Defaults to `http://localhost:5000`. | ## Connection Resolution Order @@ -16,7 +16,8 @@ The CLI resolves the Arc application URL in this order: 1. `--url` flag 2. `ARC_URL` environment variable -3. Default: `https://localhost:5001` +3. `Properties/launchSettings.json` or `properties/launchSettings.json` `applicationUrl` +4. Default: `http://localhost:5000` ## Sub-Commands diff --git a/Documentation/arc/queries.md b/Documentation/arc/queries.md index f22b5d5..1e8a165 100644 --- a/Documentation/arc/queries.md +++ b/Documentation/arc/queries.md @@ -12,7 +12,7 @@ cratis arc queries list [--url ] [-o ] | Option | Description | |---|---| -| `--url ` | Base URL of the Arc application (default: `https://localhost:5001`). | +| `--url ` | Base URL of the Arc application (default: `http://localhost:5000`). | | `-o, --output ` | Output format: `table`, `plain`, `json`, `json-compact`. | ## Output diff --git a/Source/Cli.Specs/Usings.cs b/Source/Cli.Specs/Usings.cs index c9727fb..efc5842 100644 --- a/Source/Cli.Specs/Usings.cs +++ b/Source/Cli.Specs/Usings.cs @@ -1,6 +1,7 @@ // Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +global using Cratis.Cli.Commands.Arc; global using Cratis.Cli.Commands.Chronicle; global using Cratis.Cli.Commands.Chronicle.Diagnose; global using Cratis.Cli.Commands.Chronicle.Observers; diff --git a/Source/Cli.Specs/for_ArcSettings/given/a_temp_arc_directory.cs b/Source/Cli.Specs/for_ArcSettings/given/a_temp_arc_directory.cs new file mode 100644 index 0000000..7c15fb0 --- /dev/null +++ b/Source/Cli.Specs/for_ArcSettings/given/a_temp_arc_directory.cs @@ -0,0 +1,66 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_ArcSettings.given; + +public class a_temp_arc_directory : Specification, IDisposable +{ + protected string _tempDirectory = string.Empty; + string? _previousCurrentDirectory; + string? _previousArcUrl; + + void Establish() + { + _tempDirectory = Path.Combine(Path.GetTempPath(), $"cratis-cli-arc-specs-{Guid.NewGuid():N}"); + Directory.CreateDirectory(_tempDirectory); + + _previousCurrentDirectory = Directory.GetCurrentDirectory(); + _previousArcUrl = Environment.GetEnvironmentVariable(ArcDefaults.UrlEnvVar); + + Environment.SetEnvironmentVariable(ArcDefaults.UrlEnvVar, null); + Directory.SetCurrentDirectory(_tempDirectory); + } + + protected void WriteLaunchSettings(string folderName, string applicationUrl) + { + var propertiesPath = Path.Combine(_tempDirectory, folderName); + Directory.CreateDirectory(propertiesPath); + + var content = + "{\n" + + " \"profiles\": {\n" + + " \"Core\": {\n" + + " \"commandName\": \"Project\",\n" + + $" \"applicationUrl\": \"{applicationUrl}\"\n" + + " }\n" + + " }\n" + + "}\n"; + File.WriteAllText(Path.Combine(propertiesPath, "launchSettings.json"), content); + } + + /// +#pragma warning disable CA1033 + void IDisposable.Dispose() +#pragma warning restore CA1033 + { + CleanUp(); + } + + protected virtual void CleanUp() + { + Directory.SetCurrentDirectory(_previousCurrentDirectory ?? Environment.CurrentDirectory); + Environment.SetEnvironmentVariable(ArcDefaults.UrlEnvVar, _previousArcUrl); + + try + { + if (Directory.Exists(_tempDirectory)) + { + Directory.Delete(_tempDirectory, true); + } + } + catch + { + // Best-effort cleanup. + } + } +} diff --git a/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_environment_variable_is_set.cs b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_environment_variable_is_set.cs new file mode 100644 index 0000000..9de0871 --- /dev/null +++ b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_environment_variable_is_set.cs @@ -0,0 +1,22 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_ArcSettings.when_resolving_url; + +[Collection(CliSpecsCollection.Name)] +public class and_environment_variable_is_set : given.a_temp_arc_directory +{ + ArcSettings _settings = null!; + string _result = string.Empty; + + void Establish() + { + WriteLaunchSettings("Properties", "http://localhost:5100/"); + Environment.SetEnvironmentVariable(ArcDefaults.UrlEnvVar, "http://localhost:5300/"); + _settings = new ArcSettings(); + } + + void Because() => _result = _settings.ResolveUrl(); + + [Fact] void should_return_environment_variable() => _result.ShouldEqual("http://localhost:5300"); +} diff --git a/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_flag_is_set.cs b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_flag_is_set.cs new file mode 100644 index 0000000..eb16956 --- /dev/null +++ b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_flag_is_set.cs @@ -0,0 +1,17 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_ArcSettings.when_resolving_url; + +[Collection(CliSpecsCollection.Name)] +public class and_flag_is_set : given.a_temp_arc_directory +{ + ArcSettings _settings = null!; + string _result = string.Empty; + + void Establish() => _settings = new ArcSettings { Url = "http://localhost:6001/" }; + + void Because() => _result = _settings.ResolveUrl(); + + [Fact] void should_return_the_flag_value() => _result.ShouldEqual("http://localhost:6001"); +} diff --git a/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_launch_settings_exists_in_lowercase_properties_folder.cs b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_launch_settings_exists_in_lowercase_properties_folder.cs new file mode 100644 index 0000000..717c68e --- /dev/null +++ b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_launch_settings_exists_in_lowercase_properties_folder.cs @@ -0,0 +1,21 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_ArcSettings.when_resolving_url; + +[Collection(CliSpecsCollection.Name)] +public class and_launch_settings_exists_in_lowercase_properties_folder : given.a_temp_arc_directory +{ + ArcSettings _settings = null!; + string _result = string.Empty; + + void Establish() + { + WriteLaunchSettings("properties", "http://localhost:5200/"); + _settings = new ArcSettings(); + } + + void Because() => _result = _settings.ResolveUrl(); + + [Fact] void should_return_the_application_url() => _result.ShouldEqual("http://localhost:5200"); +} diff --git a/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_launch_settings_exists_in_properties_folder.cs b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_launch_settings_exists_in_properties_folder.cs new file mode 100644 index 0000000..988b0c4 --- /dev/null +++ b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_launch_settings_exists_in_properties_folder.cs @@ -0,0 +1,21 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_ArcSettings.when_resolving_url; + +[Collection(CliSpecsCollection.Name)] +public class and_launch_settings_exists_in_properties_folder : given.a_temp_arc_directory +{ + ArcSettings _settings = null!; + string _result = string.Empty; + + void Establish() + { + WriteLaunchSettings("Properties", "http://localhost:5100/"); + _settings = new ArcSettings(); + } + + void Because() => _result = _settings.ResolveUrl(); + + [Fact] void should_return_the_application_url() => _result.ShouldEqual("http://localhost:5100"); +} diff --git a/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_no_override_is_configured.cs b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_no_override_is_configured.cs new file mode 100644 index 0000000..f5e9b36 --- /dev/null +++ b/Source/Cli.Specs/for_ArcSettings/when_resolving_url/and_no_override_is_configured.cs @@ -0,0 +1,17 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_ArcSettings.when_resolving_url; + +[Collection(CliSpecsCollection.Name)] +public class and_no_override_is_configured : given.a_temp_arc_directory +{ + ArcSettings _settings = null!; + string _result = string.Empty; + + void Establish() => _settings = new ArcSettings(); + + void Because() => _result = _settings.ResolveUrl(); + + [Fact] void should_return_default_url() => _result.ShouldEqual(ArcDefaults.DefaultUrl); +} diff --git a/Source/Cli/Commands/Arc/ArcDefaults.cs b/Source/Cli/Commands/Arc/ArcDefaults.cs index b577b63..6b8051d 100644 --- a/Source/Cli/Commands/Arc/ArcDefaults.cs +++ b/Source/Cli/Commands/Arc/ArcDefaults.cs @@ -11,7 +11,7 @@ public static class ArcDefaults /// /// The default URL for an Arc application. /// - public const string DefaultUrl = "https://localhost:5001"; + public const string DefaultUrl = "http://localhost:5000"; /// /// Environment variable name for the Arc application URL. diff --git a/Source/Cli/Commands/Arc/ArcSettings.cs b/Source/Cli/Commands/Arc/ArcSettings.cs index 5d0f108..a619b59 100644 --- a/Source/Cli/Commands/Arc/ArcSettings.cs +++ b/Source/Cli/Commands/Arc/ArcSettings.cs @@ -1,6 +1,8 @@ // Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Text.Json; + namespace Cratis.Cli.Commands.Arc; /// @@ -12,11 +14,11 @@ public class ArcSettings : GlobalSettings /// Gets or sets the base URL of the Arc application. /// [CommandOption("--url ")] - [Description("Base URL of the Arc application (e.g. https://localhost:5001)")] + [Description("Base URL of the Arc application (e.g. http://localhost:5000)")] public string? Url { get; set; } /// - /// Resolves the effective base URL by checking the flag, environment variable, then default. + /// Resolves the effective base URL by checking the flag, environment variable, launch settings, then default. /// /// The resolved base URL string. #pragma warning disable CA1055 // URI string return type — string is intentional here for consistency with connection helpers @@ -34,6 +36,88 @@ public string ResolveUrl() return envVar.TrimEnd('/'); } + var launchSettingsUrl = TryGetApplicationUrlFromLaunchSettings(); + if (!string.IsNullOrWhiteSpace(launchSettingsUrl)) + { + return launchSettingsUrl; + } + return ArcDefaults.DefaultUrl; } + + static string? TryGetApplicationUrlFromLaunchSettings() + { + var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); + while (currentDirectory is not null) + { + var upperCasePath = Path.Combine(currentDirectory.FullName, "Properties", "launchSettings.json"); + var upperCaseUrl = TryReadApplicationUrl(upperCasePath); + if (!string.IsNullOrWhiteSpace(upperCaseUrl)) + { + return upperCaseUrl; + } + + var lowerCasePath = Path.Combine(currentDirectory.FullName, "properties", "launchSettings.json"); + var lowerCaseUrl = TryReadApplicationUrl(lowerCasePath); + if (!string.IsNullOrWhiteSpace(lowerCaseUrl)) + { + return lowerCaseUrl; + } + + currentDirectory = currentDirectory.Parent; + } + + return null; + } + + static string? TryReadApplicationUrl(string path) + { + if (!File.Exists(path)) + { + return null; + } + + try + { + using var stream = File.OpenRead(path); + using var document = JsonDocument.Parse(stream); + if (!document.RootElement.TryGetProperty("profiles", out var profiles) || profiles.ValueKind != JsonValueKind.Object) + { + return null; + } + + foreach (var profile in profiles.EnumerateObject()) + { + if (profile.Value.ValueKind != JsonValueKind.Object || + !profile.Value.TryGetProperty("applicationUrl", out var applicationUrlProperty) || + applicationUrlProperty.ValueKind != JsonValueKind.String) + { + continue; + } + + var applicationUrl = applicationUrlProperty.GetString(); + if (string.IsNullOrWhiteSpace(applicationUrl)) + { + continue; + } + + var firstUrl = applicationUrl.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(firstUrl)) + { + return firstUrl.TrimEnd('/'); + } + } + } + catch (IOException) + { + } + catch (UnauthorizedAccessException) + { + } + catch (JsonException) + { + } + + return null; + } } diff --git a/Source/Cli/Commands/Arc/Commands/ListCommandsCommand.cs b/Source/Cli/Commands/Arc/Commands/ListCommandsCommand.cs index 0353ef3..5829a2c 100644 --- a/Source/Cli/Commands/Arc/Commands/ListCommandsCommand.cs +++ b/Source/Cli/Commands/Arc/Commands/ListCommandsCommand.cs @@ -9,7 +9,7 @@ namespace Cratis.Cli.Commands.Arc.Commands; [LlmDescription("Lists all registered command endpoints in the Arc application. Returns name, namespace, HTTP route, type, and documentation summary for each command.")] [CliCommand("list", "List registered command endpoints", Branch = typeof(ArcBranch.Commands))] [CliExample("arc", "commands", "list")] -[CliExample("arc", "commands", "list", "--url", "https://localhost:5001")] +[CliExample("arc", "commands", "list", "--url", "http://localhost:5000")] [CliExample("arc", "commands", "list", "-o", "json")] [LlmOutputAdvice("plain", "plain is significantly smaller than JSON for large command lists.")] public class ListCommandsCommand : ArcCommand diff --git a/Source/Cli/Commands/Arc/Queries/ListQueriesCommand.cs b/Source/Cli/Commands/Arc/Queries/ListQueriesCommand.cs index fac7747..9d4cca6 100644 --- a/Source/Cli/Commands/Arc/Queries/ListQueriesCommand.cs +++ b/Source/Cli/Commands/Arc/Queries/ListQueriesCommand.cs @@ -9,7 +9,7 @@ namespace Cratis.Cli.Commands.Arc.Queries; [LlmDescription("Lists all registered query endpoints in the Arc application. Returns name, namespace, HTTP route, fully qualified type, and documentation summary for each query.")] [CliCommand("list", "List registered query endpoints", Branch = typeof(ArcBranch.Queries))] [CliExample("arc", "queries", "list")] -[CliExample("arc", "queries", "list", "--url", "https://localhost:5001")] +[CliExample("arc", "queries", "list", "--url", "http://localhost:5000")] [CliExample("arc", "queries", "list", "-o", "json")] [LlmOutputAdvice("plain", "plain is significantly smaller than JSON for large query lists.")] public class ListQueriesCommand : ArcCommand