Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ dotnet_diagnostic.IDE0072.severity = none
dotnet_diagnostic.IL3050.severity = none
dotnet_diagnostic.IL2026.severity = none

[StaticWebHost.cs]
dotnet_diagnostic.IL3050.severity = none
dotnet_diagnostic.IL2026.severity = none

[tests/**/*.cs]
dotnet_diagnostic.IDE0058.severity = none
dotnet_diagnostic.IDE0022.severity = none
Expand Down
9 changes: 9 additions & 0 deletions src/Elastic.Markdown/BuildContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ public string? UrlPathPrefix
init => _urlPathPrefix = value;
}

private readonly string? _staticUrlPathPrefix;
public string? StaticUrlPathPrefix
{
get => !string.IsNullOrWhiteSpace(_staticUrlPathPrefix)
? $"/{_staticUrlPathPrefix.Trim('/')}"
: UrlPathPrefix;
init => _staticUrlPathPrefix = value;
}

public BuildContext(IFileSystem fileSystem)
: this(new DiagnosticsCollector([]), fileSystem, fileSystem, null, null) { }

Expand Down
1 change: 1 addition & 0 deletions src/Elastic.Markdown/Slices/HtmlWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public async Task<string> RenderLayout(MarkdownFile markdown, MarkdownDocument d
TopLevelNavigationItems = [.. topLevelNavigationItems],
NavigationHtml = navigationHtml,
UrlPathPrefix = markdown.UrlPathPrefix,
StaticUrlPathPrefix = DocumentationSet.Build.StaticUrlPathPrefix,
Applies = markdown.YamlFrontMatter?.AppliesTo,
GithubEditUrl = editUrl,
AllowIndexing = DocumentationSet.Build.AllowIndexing && !markdown.Hidden,
Expand Down
1 change: 1 addition & 0 deletions src/Elastic.Markdown/Slices/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
NavigationHtml = Model.NavigationHtml,
TopLevelNavigationItems = Model.TopLevelNavigationItems,
UrlPathPrefix = Model.UrlPathPrefix,
StaticUrlPathPrefix = Model.StaticUrlPathPrefix,
GithubEditUrl = Model.GithubEditUrl,
AllowIndexing = Model.AllowIndexing,
Features = Model.Features
Expand Down
10 changes: 6 additions & 4 deletions src/Elastic.Markdown/Slices/_ViewModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class IndexViewModel

public required string NavigationHtml { get; init; }
public required string? UrlPathPrefix { get; init; }
public required string? StaticUrlPathPrefix { get; init; }
public required string? GithubEditUrl { get; init; }
public required ApplicableTo? Applies { get; init; }
public required bool AllowIndexing { get; init; }
Expand All @@ -43,9 +44,10 @@ public class LayoutViewModel
public required MarkdownFile CurrentDocument { get; init; }
public required MarkdownFile? Previous { get; init; }
public required MarkdownFile? Next { get; init; }
public required string NavigationHtml { get; set; }
public required string? UrlPathPrefix { get; set; }
public required string? GithubEditUrl { get; set; }
public required string NavigationHtml { get; init; }
public required string? StaticUrlPathPrefix { get; init; }
public required string? UrlPathPrefix { get; init; }
public required string? GithubEditUrl { get; init; }
public required bool AllowIndexing { get; init; }
public required FeatureFlags Features { get; init; }

Expand All @@ -67,7 +69,7 @@ public MarkdownFile[] Parents
public string Static(string path)
{
path = $"_static/{path.TrimStart('/')}";
return $"{UrlPathPrefix}/{path}";
return $"{StaticUrlPathPrefix}/{path}";
}

public string Link(string path)
Expand Down
10 changes: 8 additions & 2 deletions src/docs-assembler/AssembleContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,19 @@ public class AssembleContext
// This property is used to determine if the site should be indexed by search engines
public bool AllowIndexing { get; init; }

public AssembleContext(DiagnosticsCollector collector, IFileSystem readFileSystem, IFileSystem writeFileSystem, string? checkoutDirectory, string? output)
public AssembleContext(
DiagnosticsCollector collector,
IFileSystem readFileSystem,
IFileSystem writeFileSystem,
string? checkoutDirectory,
string? output
)
{
Collector = collector;
ReadFileSystem = readFileSystem;
WriteFileSystem = writeFileSystem;

var configPath = Path.Combine(Paths.Root.FullName, "src/docs-assembler/assembler.yml");
var configPath = Path.Combine(Paths.Root.FullName, "src", "docs-assembler", "assembler.yml");
// temporarily fallback to embedded assembler.yml
// This will live in docs-content soon
if (!ReadFileSystem.File.Exists(configPath))
Expand Down
19 changes: 14 additions & 5 deletions src/docs-assembler/Building/AssemblerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using Documentation.Assembler.Configuration;
using Documentation.Assembler.Sourcing;
using Elastic.Markdown;
using Elastic.Markdown.CrossLinks;
Expand All @@ -14,7 +15,7 @@ public class AssemblerBuilder(ILoggerFactory logger, AssembleContext context)
{
private readonly ILogger<AssemblerBuilder> _logger = logger.CreateLogger<AssemblerBuilder>();

public async Task BuildAllAsync(IReadOnlyCollection<Checkout> checkouts, string environment, Cancel ctx)
public async Task BuildAllAsync(IReadOnlyCollection<Checkout> checkouts, PublishEnvironment environment, Cancel ctx)
{
var crossLinkFetcher = new AssemblerCrossLinkFetcher(logger, context.Configuration);
var uriResolver = new PublishEnvironmentUriResolver(context.Configuration, environment);
Expand All @@ -24,7 +25,7 @@ public async Task BuildAllAsync(IReadOnlyCollection<Checkout> checkouts, string
{
try
{
await BuildAsync(checkout, crossLinkResolver, ctx);
await BuildAsync(checkout, environment, crossLinkResolver, ctx);
}
catch (Exception e) when (e.Message.Contains("Can not locate docset.yml file in"))
{
Expand All @@ -39,14 +40,22 @@ public async Task BuildAllAsync(IReadOnlyCollection<Checkout> checkouts, string
}
}

private async Task BuildAsync(Checkout checkout, CrossLinkResolver crossLinkResolver, Cancel ctx)
private async Task BuildAsync(Checkout checkout, PublishEnvironment environment, CrossLinkResolver crossLinkResolver, Cancel ctx)
{
var path = checkout.Directory.FullName;
var pathPrefix = checkout.Repository.PathPrefix;
var output = pathPrefix != null ? Path.Combine(context.OutputDirectory.FullName, pathPrefix) : context.OutputDirectory.FullName;
var localPathPrefix = checkout.Repository.PathPrefix;
var pathPrefix = (environment.PathPrefix, localPathPrefix) switch
{
(null or "", null or "") => null,
(null or "", _) => localPathPrefix,
(_, null or "") => environment.PathPrefix,
var (globalPrefix, docsetPrefix) => $"{globalPrefix}/{docsetPrefix}"
};
var output = localPathPrefix != null ? Path.Combine(context.OutputDirectory.FullName, localPathPrefix) : context.OutputDirectory.FullName;

var buildContext = new BuildContext(context.Collector, context.ReadFileSystem, context.WriteFileSystem, path, output)
{
StaticUrlPathPrefix = environment.PathPrefix,
UrlPathPrefix = pathPrefix,
Force = true,
AllowIndexing = true
Expand Down
10 changes: 4 additions & 6 deletions src/docs-assembler/Building/PublishEnvironmentUriResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ public class PublishEnvironmentUriResolver : IUriEnvironmentResolver

private FrozenDictionary<string, Repository> AllRepositories { get; }

public PublishEnvironmentUriResolver(AssemblyConfiguration configuration, string environment)
public PublishEnvironmentUriResolver(AssemblyConfiguration configuration, PublishEnvironment environment)
{
if (!configuration.Environments.TryGetValue(environment, out var e))
throw new Exception($"Could not find environment {environment}");
if (!Uri.TryCreate(e.Uri, UriKind.Absolute, out var uri))
throw new Exception($"Could not parse uri {e.Uri} in environment {environment}");
if (!Uri.TryCreate(environment.Uri, UriKind.Absolute, out var uri))
throw new Exception($"Could not parse uri {environment.Uri} in environment {environment}");

BaseUri = uri;
PublishEnvironment = e;
PublishEnvironment = environment;
PreviewResolver = new PreviewEnvironmentUriResolver();
AllRepositories = configuration.ReferenceRepositories.Values.Concat<Repository>([configuration.Narrative])
.ToFrozenDictionary(e => e.Name, e => e);
Expand Down
8 changes: 6 additions & 2 deletions src/docs-assembler/Cli/RepositoryCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public async Task<int> BuildAll(
{
AssignOutputLogger();
var githubEnvironmentInput = githubActionsService.GetInput("environment");
environment ??= !string.IsNullOrEmpty(githubEnvironmentInput) ? githubEnvironmentInput : "production";
environment ??= !string.IsNullOrEmpty(githubEnvironmentInput) ? githubEnvironmentInput : "dev";

await using var collector = new ConsoleDiagnosticsCollector(logger, githubActionsService);
_ = collector.StartAsync(ctx);
Expand All @@ -70,13 +70,17 @@ public async Task<int> BuildAll(
Force = force ?? false,
AllowIndexing = allowIndexing ?? false,
};

if (!assembleContext.Configuration.Environments.TryGetValue(environment, out var env))
throw new Exception($"Could not find environment {environment}");

var cloner = new RepositoryCheckoutProvider(logger, assembleContext);
var checkouts = cloner.GetAll().ToArray();
if (checkouts.Length == 0)
throw new Exception("No checkouts found");

var builder = new AssemblerBuilder(logger, assembleContext);
await builder.BuildAllAsync(checkouts, environment, ctx);
await builder.BuildAllAsync(checkouts, env, ctx);

if (strict ?? false)
return collector.Errors + collector.Warnings;
Expand Down
2 changes: 1 addition & 1 deletion src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private string Capture(IDirectoryInfo? workingDirectory, string binary, params s
var arguments = new StartArguments(binary, args)
{
WorkingDirectory = workingDirectory?.FullName,
WaitForStreamReadersTimeout = TimeSpan.FromSeconds(3),
//WaitForStreamReadersTimeout = TimeSpan.FromSeconds(3),
Timeout = TimeSpan.FromSeconds(3),
WaitForExit = TimeSpan.FromSeconds(3),
ConsoleOutWriter = NoopConsoleWriter.Instance
Expand Down
3 changes: 3 additions & 0 deletions src/docs-assembler/assembler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ environments:
preview:
uri: https://docs-v3-preview.elastic.dev
path_prefix:
dev:
uri: http://localhost:4000
path_prefix: docs
narrative:
checkout_strategy: full
references:
Expand Down
18 changes: 18 additions & 0 deletions src/docs-builder/Cli/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ public async Task Serve(string? path = null, int port = 3000, Cancel ctx = defau
await host.StopAsync(ctx);
}

/// <summary>
/// Serve html files directly
/// </summary>
/// <param name="path">-p, Path to serve the documentation.
/// Defaults to the`{pwd}/docs` folder
/// </param>
/// <param name="port">Port to serve the documentation.</param>
/// <param name="ctx"></param>
[Command("serve-static")]
[ConsoleAppFilter<CheckForUpdatesFilter>]
public async Task ServeStatic(string? path = null, int port = 4000, Cancel ctx = default)
{
AssignOutputLogger();
var host = new StaticWebHost(path, port, new FileSystem());
await host.RunAsync(ctx);
await host.StopAsync(ctx);
}

/// <summary>
/// Converts a source markdown folder or file to an output folder
/// <para>global options:</para>
Expand Down
98 changes: 98 additions & 0 deletions src/docs-builder/Http/StaticWebHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using Elastic.Documentation.Tooling;
using Elastic.Markdown;
using Elastic.Markdown.Diagnostics;
using Elastic.Markdown.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Documentation.Builder.Http;

public class StaticWebHost
{
private readonly WebApplication _webApplication;

private readonly BuildContext _context;

public StaticWebHost(string? path, int port, IFileSystem fileSystem)
{
var builder = WebApplication.CreateSlimBuilder();
DocumentationTooling.CreateServiceCollection(builder.Services, LogLevel.Warning);

_ = builder.Logging
.AddFilter("Microsoft.AspNetCore.Hosting.Diagnostics", LogLevel.Error)
.AddFilter("Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware", LogLevel.Error)
.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.Information);
_ = builder.WebHost.UseUrls($"http://localhost:{port}");

_webApplication = builder.Build();
_context = new BuildContext(new DiagnosticsCollector([]), fileSystem, fileSystem, path, null);
SetUpRoutes();
}

public async Task RunAsync(Cancel ctx) => await _webApplication.RunAsync(ctx);

public async Task StopAsync(Cancel ctx) => await _context.Collector.StopAsync(ctx);

private void SetUpRoutes()
{
_ =
_webApplication
.UseRouting();

_ = _webApplication.MapGet("/", (Cancel _) => Results.Redirect("docs"));

_ = _webApplication.MapGet("docs/", (Cancel ctx) =>
ServeDocumentationFile("index.html", ctx));

_ = _webApplication.MapGet("docs/{**slug}", (string slug, Cancel ctx) =>
ServeDocumentationFile(slug, ctx));
}

private static async Task<IResult> ServeDocumentationFile(string slug, Cancel _)
{
// from the injected top level navigation which expects us to run on elastic.co
if (slug.StartsWith("static-res/"))
return Results.NotFound();

await Task.CompletedTask;
var path = Path.Combine(Paths.Root.FullName, ".artifacts", "assembly");
var localPath = Path.Combine(path, slug.Replace('/', Path.DirectorySeparatorChar));
var fileInfo = new FileInfo(localPath);
var directoryInfo = new DirectoryInfo(localPath);
if (directoryInfo.Exists)
fileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "index.html"));

if (fileInfo.Exists)
{
var mimetype = fileInfo.Extension switch
{
".js" => "text/javascript",
".css" => "text/css",
".png" => "image/png",
".jpg" => "image/jpeg",
".gif" => "image/gif",
".svg" => "image/svg+xml",
".ico" => "image/x-icon",
".json" => "application/json",
".map" => "application/json",
".txt" => "text/plain",
".xml" => "text/xml",
".yml" => "text/yaml",
".md" => "text/markdown",
_ => "text/html"
};
return Results.File(fileInfo.FullName, mimetype);
}


return Results.NotFound();
}
}