Skip to content

Commit 0d6115b

Browse files
MpdreamzclaudeCopilot
authored
feat(tooling): migrate docs-builder CLI from ConsoleAppFramework to Nullean.Argh (#3202)
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Mpdreamz <245275+Mpdreamz@users.noreply.github.com>
1 parent 9e5d1f3 commit 0d6115b

59 files changed

Lines changed: 1347 additions & 1749 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Directory.Packages.props

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@
6969
<PackageVersion Include="FSharp.Core" Version="10.1.201" />
7070
</ItemGroup>
7171
<ItemGroup>
72-
<PackageVersion Include="ConsoleAppFramework" Version="5.7.13" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
73-
<PackageVersion Include="ConsoleAppFramework.Abstractions" Version="5.7.2" />
72+
<PackageVersion Include="Nullean.Argh" Version="0.15.1" />
73+
<PackageVersion Include="Nullean.Argh.Hosting" Version="0.15.1" />
74+
<PackageVersion Include="Nullean.Argh.Interfaces" Version="0.15.1" />
7475
<PackageVersion Include="Crayon" Version="2.0.69" />
7576
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
7677
<PackageVersion Include="Errata" Version="0.16.0" />
@@ -87,7 +88,7 @@
8788
<PackageVersion Include="Proc" Version="0.13.0" />
8889
<PackageVersion Include="RazorSlices" Version="0.9.5" />
8990
<PackageVersion Include="Samboy063.Tomlet" Version="6.0.0" />
90-
<PackageVersion Include="Sep" Version="0.11.0" />
91+
<PackageVersion Include="Sep" Version="0.12.5" />
9192
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
9293
<PackageVersion Include="SoftCircuits.IniFileParser" Version="2.7.0" />
9394
<PackageVersion Include="System.IO.Abstractions" Version="22.1.1" />

aspire/AppHost.cs

Lines changed: 161 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -2,125 +2,173 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5-
6-
using ConsoleAppFramework;
75
using Elastic.Documentation;
6+
using Nullean.Argh;
87
using static Elastic.Documentation.Aspire.ResourceNames;
98

10-
GlobalCli.Process(ref args, out _, out var globalArguments);
9+
// Extract global doc-builder flags before argh routing so they can be forwarded
10+
// to docs-builder sub-process invocations (--log-level, --config-source, etc.).
11+
AspireHost.GlobalArguments = AspireHost.ExtractGlobalArgs(ref args);
1112

12-
await ConsoleApp.RunAsync(args, BuildAspireHost);
13-
return;
13+
var app = new ArghApp();
14+
app.MapRoot(AspireHost.Run);
15+
return await app.RunAsync(args);
1416

15-
// ReSharper disable once RedundantLambdaParameterType
16-
// ReSharper disable once VariableHidesOuterVariable
17-
async Task BuildAspireHost(bool startElasticsearch, bool assumeCloned, bool assumeBuild, bool skipPrivateRepositories, Cancel ctx)
18-
{
19-
var builder = DistributedApplication.CreateBuilder(args);
20-
21-
var llmUrl = builder.AddParameter("LlmGatewayUrl", secret: true);
22-
var llmServiceAccountPath = builder.AddParameter("LlmGatewayServiceAccountPath", secret: true);
23-
24-
var elasticsearchUrl = builder.AddParameter("DocumentationElasticUrl", secret: true);
25-
var elasticsearchApiKey = builder.AddParameter("DocumentationElasticApiKey", secret: true);
26-
27-
var cloneAll = builder.AddProject<Projects.docs_builder>(AssemblerClone);
28-
string[] cloneArgs = assumeCloned ? ["--assume-cloned"] : [];
29-
cloneAll = cloneAll.WithArgs(["assembler", "clone", .. globalArguments, .. cloneArgs]);
30-
31-
var buildAll = builder.AddProject<Projects.docs_builder>(AssemblerBuild);
32-
string[] buildArgs = assumeBuild ? ["--assume-build"] : [];
33-
buildAll = buildAll
34-
.WithArgs(["assembler", "build", .. globalArguments, .. buildArgs])
35-
.WaitForCompletion(cloneAll)
36-
.WithParentRelationship(cloneAll);
37-
38-
var elasticsearchLocal = builder.AddElasticsearch(ElasticsearchLocal)
39-
.WithEnvironment("LICENSE", "trial");
40-
if (!startElasticsearch)
41-
elasticsearchLocal = elasticsearchLocal.WithExplicitStart();
42-
43-
var elasticsearchRemote = builder.AddExternalService(ElasticsearchRemote, elasticsearchUrl);
44-
45-
var api = builder.AddProject<Projects.Elastic_Documentation_Api_App>(Api)
46-
.WithArgs(globalArguments)
47-
.WithEnvironment("ENVIRONMENT", "dev")
48-
.WithEnvironment("LLM_GATEWAY_FUNCTION_URL", llmUrl)
49-
.WithEnvironment("LLM_GATEWAY_SERVICE_ACCOUNT_KEY_PATH", llmServiceAccountPath);
50-
51-
// ReSharper disable once RedundantAssignment
52-
api = startElasticsearch
53-
? api
54-
.WithReference(elasticsearchLocal)
55-
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchLocal.GetEndpoint("http"))
56-
.WithEnvironment(context => context.EnvironmentVariables["DOCUMENTATION_ELASTIC_PASSWORD"] = elasticsearchLocal.Resource.PasswordParameter)
57-
.WithParentRelationship(elasticsearchLocal)
58-
.WaitFor(elasticsearchLocal)
59-
.WithExplicitStart()
60-
: api.WithReference(elasticsearchRemote)
61-
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchUrl)
62-
.WithEnvironment("DOCUMENTATION_ELASTIC_APIKEY", elasticsearchApiKey)
63-
.WithExplicitStart();
17+
// ── Aspire host command ───────────────────────────────────────────────────────────────────────────
6418

65-
var mcp = builder.AddProject<Projects.Elastic_Documentation_Mcp_Remote>(RemoteMcp)
66-
.WithArgs(globalArguments)
67-
.WithEnvironment("ENVIRONMENT", "dev");
68-
69-
// ReSharper disable once RedundantAssignment
70-
mcp = startElasticsearch
71-
? mcp
72-
.WithReference(elasticsearchLocal)
73-
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchLocal.GetEndpoint("http"))
74-
.WithEnvironment(context => context.EnvironmentVariables["DOCUMENTATION_ELASTIC_PASSWORD"] = elasticsearchLocal.Resource.PasswordParameter)
75-
.WithParentRelationship(elasticsearchLocal)
76-
.WaitFor(elasticsearchLocal)
77-
.WithExplicitStart()
78-
: mcp.WithReference(elasticsearchRemote)
79-
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchUrl)
80-
.WithEnvironment("DOCUMENTATION_ELASTIC_APIKEY", elasticsearchApiKey)
19+
internal static class AspireHost
20+
{
21+
internal static string[] GlobalArguments = [];
22+
23+
/// <summary>
24+
/// Starts the Elastic documentation Aspire AppHost.
25+
/// </summary>
26+
/// <param name="startElasticsearch">Start a local Elasticsearch container</param>
27+
/// <param name="assumeCloned">Skip cloning; assume repositories are already present on disk</param>
28+
/// <param name="assumeBuild">Skip building; assume build output already exists</param>
29+
/// <param name="skipPrivateRepositories">Skip cloning private repositories</param>
30+
[NoOptionsInjection]
31+
internal static async Task Run(
32+
bool startElasticsearch = false,
33+
bool assumeCloned = false,
34+
bool assumeBuild = false,
35+
bool skipPrivateRepositories = false,
36+
CancellationToken ct = default)
37+
{
38+
var builder = DistributedApplication.CreateBuilder();
39+
40+
var llmUrl = builder.AddParameter("LlmGatewayUrl", secret: true);
41+
var llmServiceAccountPath = builder.AddParameter("LlmGatewayServiceAccountPath", secret: true);
42+
43+
var elasticsearchUrl = builder.AddParameter("DocumentationElasticUrl", secret: true);
44+
var elasticsearchApiKey = builder.AddParameter("DocumentationElasticApiKey", secret: true);
45+
46+
var cloneAll = builder.AddProject<Projects.docs_builder>(AssemblerClone);
47+
string[] cloneArgs = assumeCloned ? ["--assume-cloned"] : [];
48+
cloneAll = cloneAll.WithArgs(["assembler", "clone", .. GlobalArguments, .. cloneArgs]);
49+
50+
var buildAll = builder.AddProject<Projects.docs_builder>(AssemblerBuild);
51+
string[] buildArgs = assumeBuild ? ["--assume-build"] : [];
52+
buildAll = buildAll
53+
.WithArgs(["assembler", "build", .. GlobalArguments, .. buildArgs])
54+
.WaitForCompletion(cloneAll)
55+
.WithParentRelationship(cloneAll);
56+
57+
var elasticsearchLocal = builder.AddElasticsearch(ElasticsearchLocal)
58+
.WithEnvironment("LICENSE", "trial");
59+
if (!startElasticsearch)
60+
elasticsearchLocal = elasticsearchLocal.WithExplicitStart();
61+
62+
var elasticsearchRemote = builder.AddExternalService(ElasticsearchRemote, elasticsearchUrl);
63+
64+
var api = builder.AddProject<Projects.Elastic_Documentation_Api_App>(Api)
65+
.WithArgs(GlobalArguments)
66+
.WithEnvironment("ENVIRONMENT", "dev")
67+
.WithEnvironment("LLM_GATEWAY_FUNCTION_URL", llmUrl)
68+
.WithEnvironment("LLM_GATEWAY_SERVICE_ACCOUNT_KEY_PATH", llmServiceAccountPath);
69+
70+
// ReSharper disable once RedundantAssignment
71+
api = startElasticsearch
72+
? api
73+
.WithReference(elasticsearchLocal)
74+
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchLocal.GetEndpoint("http"))
75+
.WithEnvironment(context => context.EnvironmentVariables["DOCUMENTATION_ELASTIC_PASSWORD"] = elasticsearchLocal.Resource.PasswordParameter)
76+
.WithParentRelationship(elasticsearchLocal)
77+
.WaitFor(elasticsearchLocal)
78+
.WithExplicitStart()
79+
: api.WithReference(elasticsearchRemote)
80+
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchUrl)
81+
.WithEnvironment("DOCUMENTATION_ELASTIC_APIKEY", elasticsearchApiKey)
82+
.WithExplicitStart();
83+
84+
var mcp = builder.AddProject<Projects.Elastic_Documentation_Mcp_Remote>(RemoteMcp)
85+
.WithArgs(GlobalArguments)
86+
.WithEnvironment("ENVIRONMENT", "dev");
87+
88+
// ReSharper disable once RedundantAssignment
89+
mcp = startElasticsearch
90+
? mcp
91+
.WithReference(elasticsearchLocal)
92+
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchLocal.GetEndpoint("http"))
93+
.WithEnvironment(context => context.EnvironmentVariables["DOCUMENTATION_ELASTIC_PASSWORD"] = elasticsearchLocal.Resource.PasswordParameter)
94+
.WithParentRelationship(elasticsearchLocal)
95+
.WaitFor(elasticsearchLocal)
96+
.WithExplicitStart()
97+
: mcp.WithReference(elasticsearchRemote)
98+
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchUrl)
99+
.WithEnvironment("DOCUMENTATION_ELASTIC_APIKEY", elasticsearchApiKey)
100+
.WithExplicitStart();
101+
102+
var indexElasticsearch = builder.AddProject<Projects.docs_builder>(ElasticsearchIngest)
103+
.WithArgs(["assembler", "index", .. GlobalArguments])
104+
.WaitForCompletion(cloneAll)
81105
.WithExplicitStart();
82106

83-
var indexElasticsearch = builder.AddProject<Projects.docs_builder>(ElasticsearchIngest)
84-
.WithArgs(["assembler", "index", .. globalArguments])
85-
.WaitForCompletion(cloneAll)
86-
.WithExplicitStart();
87-
88-
// ReSharper disable once RedundantAssignment
89-
indexElasticsearch = startElasticsearch
90-
? indexElasticsearch
91-
.WaitFor(elasticsearchLocal)
92-
.WithReference(elasticsearchLocal)
93-
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchLocal.GetEndpoint("http"))
94-
.WithEnvironment(context => context.EnvironmentVariables["DOCUMENTATION_ELASTIC_PASSWORD"] = elasticsearchLocal.Resource.PasswordParameter)
95-
.WithParentRelationship(elasticsearchLocal)
96-
: indexElasticsearch
97-
.WithReference(elasticsearchRemote)
98-
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchUrl)
99-
.WithEnvironment("DOCUMENTATION_ELASTIC_APIKEY", elasticsearchApiKey)
100-
.WithParentRelationship(elasticsearchRemote);
101-
102-
var serveStatic = builder.AddProject<Projects.docs_builder>(AssemblerServe)
103-
.WithEnvironment("LLM_GATEWAY_FUNCTION_URL", llmUrl)
104-
.WithEnvironment("LLM_GATEWAY_SERVICE_ACCOUNT_KEY_PATH", llmServiceAccountPath)
105-
.WithHttpEndpoint(port: 4000, isProxied: false)
106-
.WithArgs(["assembler", "serve", .. globalArguments])
107-
.WithHttpHealthCheck("/", 200)
108-
.WaitForCompletion(buildAll)
109-
.WithParentRelationship(cloneAll);
110-
111-
serveStatic = startElasticsearch
112-
? serveStatic
113-
.WithReference(elasticsearchLocal)
114-
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchLocal.GetEndpoint("http"))
115-
.WithEnvironment(context => context.EnvironmentVariables["DOCUMENTATION_ELASTIC_PASSWORD"] = elasticsearchLocal.Resource.PasswordParameter)
116-
: serveStatic
117-
.WithReference(elasticsearchRemote)
118-
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchUrl)
119-
.WithEnvironment("DOCUMENTATION_ELASTIC_APIKEY", elasticsearchApiKey);
120-
121-
122-
// ReSharper disable once RedundantAssignment
123-
serveStatic = startElasticsearch ? serveStatic.WaitFor(elasticsearchLocal) : serveStatic.WaitFor(buildAll);
124-
125-
await builder.Build().RunAsync(ctx);
107+
// ReSharper disable once RedundantAssignment
108+
indexElasticsearch = startElasticsearch
109+
? indexElasticsearch
110+
.WaitFor(elasticsearchLocal)
111+
.WithReference(elasticsearchLocal)
112+
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchLocal.GetEndpoint("http"))
113+
.WithEnvironment(context => context.EnvironmentVariables["DOCUMENTATION_ELASTIC_PASSWORD"] = elasticsearchLocal.Resource.PasswordParameter)
114+
.WithParentRelationship(elasticsearchLocal)
115+
: indexElasticsearch
116+
.WithReference(elasticsearchRemote)
117+
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchUrl)
118+
.WithEnvironment("DOCUMENTATION_ELASTIC_APIKEY", elasticsearchApiKey)
119+
.WithParentRelationship(elasticsearchRemote);
120+
121+
var serveStatic = builder.AddProject<Projects.docs_builder>(AssemblerServe)
122+
.WithEnvironment("LLM_GATEWAY_FUNCTION_URL", llmUrl)
123+
.WithEnvironment("LLM_GATEWAY_SERVICE_ACCOUNT_KEY_PATH", llmServiceAccountPath)
124+
.WithHttpEndpoint(port: 4000, isProxied: false)
125+
.WithArgs(["assembler", "serve", .. GlobalArguments])
126+
.WithHttpHealthCheck("/", 200)
127+
.WaitForCompletion(buildAll)
128+
.WithParentRelationship(cloneAll);
129+
130+
serveStatic = startElasticsearch
131+
? serveStatic
132+
.WithReference(elasticsearchLocal)
133+
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchLocal.GetEndpoint("http"))
134+
.WithEnvironment(context => context.EnvironmentVariables["DOCUMENTATION_ELASTIC_PASSWORD"] = elasticsearchLocal.Resource.PasswordParameter)
135+
: serveStatic
136+
.WithReference(elasticsearchRemote)
137+
.WithEnvironment("DOCUMENTATION_ELASTIC_URL", elasticsearchUrl)
138+
.WithEnvironment("DOCUMENTATION_ELASTIC_APIKEY", elasticsearchApiKey);
139+
140+
// ReSharper disable once RedundantAssignment
141+
serveStatic = startElasticsearch ? serveStatic.WaitFor(elasticsearchLocal) : serveStatic.WaitFor(buildAll);
142+
143+
await builder.Build().RunAsync(ct);
144+
}
145+
146+
/// <summary>
147+
/// Extracts global doc-builder flags (--log-level, --config-source, --skip-private-repositories)
148+
/// from <paramref name="args"/> in-place, returning them for forwarding to docs-builder sub-processes.
149+
/// </summary>
150+
internal static string[] ExtractGlobalArgs(ref string[] args)
151+
{
152+
var global = new List<string>();
153+
var remaining = new List<string>();
154+
for (var i = 0; i < args.Length; i++)
155+
{
156+
if (args[i] == "--log-level" && i + 1 < args.Length)
157+
{
158+
global.Add("--log-level");
159+
global.Add(args[++i]);
160+
}
161+
else if (args[i] is "--config-source" or "--configuration-source" or "-c" && i + 1 < args.Length)
162+
{
163+
global.Add("--config-source");
164+
global.Add(args[++i]);
165+
}
166+
else if (args[i] == "--skip-private-repositories")
167+
global.Add("--skip-private-repositories");
168+
else
169+
remaining.Add(args[i]);
170+
}
171+
args = [.. remaining];
172+
return [.. global];
173+
}
126174
}

aspire/aspire.csproj

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,11 @@
1010
<UserSecretsId>72f50f33-6fb9-4d08-bff3-39568fe370b3</UserSecretsId>
1111
<IsTestProject>false</IsTestProject>
1212
<RootNamespace>Elastic.Documentation.Aspire</RootNamespace>
13-
<NoWarn>IDE0350</NoWarn>
13+
<NoWarn>IDE0350;IDE0060</NoWarn>
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17-
<PackageReference Include="ConsoleAppFramework">
18-
<PrivateAssets>all</PrivateAssets>
19-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20-
</PackageReference>
17+
<PackageReference Include="Nullean.Argh" />
2118
<PackageReference Include="Aspire.Hosting.AppHost"/>
2219
<PackageReference Include="Elastic.Aspire.Hosting.Elasticsearch"/>
2320
</ItemGroup>

src/Elastic.Documentation.Configuration/DocumentationEndpoints.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class ElasticsearchEndpoint
5858
public bool DisableSslVerification { get; set; }
5959
public X509Certificate? Certificate { get; set; }
6060
public bool CertificateIsNotRoot { get; set; }
61-
public int? BootstrapTimeout { get; set; }
61+
public TimeSpan? BootstrapTimeout { get; set; }
6262
public bool ForceReindex { get; set; }
6363

6464
/// <summary>

src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
<!-- CS0618: Obsolete members - DisplayName is deprecated but still used by YAML deserialization -->
88
<NoWarn>$(NoWarn);CS0618</NoWarn>
99
<IsAotCompatible>true</IsAotCompatible>
10+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
11+
<NoWarn>$(NoWarn);CS1591;CS1573;CS1572;CS1571;CS1570;CS1574</NoWarn>
1012
</PropertyGroup>
1113

1214
<ItemGroup>
@@ -16,6 +18,7 @@
1618
<ItemGroup>
1719
<PackageReference Include="DotNet.Glob" />
1820
<PackageReference Include="NetEscapades.EnumGenerators" />
21+
<PackageReference Include="Nullean.Argh.Interfaces" />
1922
<PackageReference Include="Nullean.ScopedFileSystem" />
2023
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
2124
<PackageReference Include="Samboy063.Tomlet" />

0 commit comments

Comments
 (0)