diff --git a/src/tooling/docs-assembler/assembler.yml b/config/assembler.yml similarity index 100% rename from src/tooling/docs-assembler/assembler.yml rename to config/assembler.yml diff --git a/src/tooling/docs-assembler/legacy-url-mappings.yml b/config/legacy-url-mappings.yml similarity index 100% rename from src/tooling/docs-assembler/legacy-url-mappings.yml rename to config/legacy-url-mappings.yml diff --git a/src/tooling/docs-assembler/navigation.yml b/config/navigation.yml similarity index 100% rename from src/tooling/docs-assembler/navigation.yml rename to config/navigation.yml diff --git a/src/Elastic.Documentation.Configuration/versions.yml b/config/versions.yml similarity index 100% rename from src/Elastic.Documentation.Configuration/versions.yml rename to config/versions.yml diff --git a/docs-builder.sln b/docs-builder.sln index 3db0f8194..03a3c206d 100644 --- a/docs-builder.sln +++ b/docs-builder.sln @@ -111,6 +111,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Legac EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.LegacyDocs.Tests", "tests\Elastic.Documentation.LegacyDocs.Tests\Elastic.Documentation.LegacyDocs.Tests.csproj", "{164F55EC-9412-4CD4-81AD-3598B57632A6}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{6FAB566B-76E3-4E90-8A5C-E75B0F1E1C63}" + ProjectSection(SolutionItems) = preProject + config\versions.yml = config\versions.yml + config\assembler.yml = config\assembler.yml + config\legacy-url-mappings.yml = config\legacy-url-mappings.yml + config\navigation.yml = config\navigation.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/docs/configure/content-sources.md b/docs/configure/content-sources.md index ea2a868e2..9c1f41960 100644 --- a/docs/configure/content-sources.md +++ b/docs/configure/content-sources.md @@ -35,7 +35,7 @@ Tagged This is the default. To get started, follow our [guide](/migration/guide/index.md) to set up the new docs folder structure and CI configuration. -Once setup ensure the repository is added to our `assembler.yml` under `references`. +Once setup ensure the repository is added to our [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) under `references`. For example say you want to onboard `elastic/my-repository` into the production build: @@ -59,7 +59,7 @@ Once the repository is added, its navigation still needs to be injected into to ### Tagged If you want to have more control over the timing of when your docs go live to production. Configure the repository -in our `assembler.yml` to have a fixed git reference (typically a branch) deploy the `current` content source to production. +in our [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) to have a fixed git reference (typically a branch) deploy the `current` content source to production. ```yaml references: @@ -101,4 +101,4 @@ jobs: pull-requests: write ``` -1. Ensure version branches are built and publish their links ahead of time. \ No newline at end of file +1. Ensure version branches are built and publish their links ahead of time. diff --git a/docs/configure/site/content.md b/docs/configure/site/content.md index 0f335ed8e..582187af6 100644 --- a/docs/configure/site/content.md +++ b/docs/configure/site/content.md @@ -1,13 +1,13 @@ # `assembler.yml` -The global navigation is defined in the `assembler.yml` file. This file can roughly be thought of as the V3 equivalent of conf.yaml in the asciidoc build system. This file, which writers own, allows for arbitrary nesting of `docset.yml` and `toc.yml` references. This file will live in the `elastic/docs-content` repository, but will not build or have any influence over the `docs-builder` builds in that repo. +The global navigation is defined in the [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) file. This file can roughly be thought of as the V3 equivalent of conf.yaml in the asciidoc build system. This file, which writers own, allows for arbitrary nesting of `docset.yml` and `toc.yml` references. This file will live in the `elastic/docs-content` repository, but will not build or have any influence over the `docs-builder` builds in that repo. -The global navigation that is defined in `assembler.yml` can be composed of three main resources: +The global navigation that is defined in [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) can be composed of three main resources: 1. Local TOC files: toc.yml files that live in the docs-content repository. 2. External TOC files: A subset of a content set (represented by a toc.yml file) that is external to the docs-content repository. 3. External content set declarations: An entire docset.yml file that is external to the docs-content repository. -The `assembler.yml` file might look something like this: +The [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) file might look something like this: ```yaml diff --git a/docs/configure/site/index.md b/docs/configure/site/index.md index b20e5af7f..b18464a61 100644 --- a/docs/configure/site/index.md +++ b/docs/configure/site/index.md @@ -22,8 +22,8 @@ TBD In both the AsciiDoctor- and V3-based system, there is site-wide configuration where you list all content sources, where to find those sources, and in what order they should be added to the site. In the AsciiDoctor system, this all happens in one YAML file in the `/docs` repo. In the V3 system: -* Content configuration happens in the [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/src/tooling/docs-assembler/assembler.yml) file in `docs-builder`. -* Navigation configuration happens in the [`navigation.yml`](https://github.com/elastic/docs-builder/blob/main/src/tooling/docs-assembler/navigation.yml) file in `docs-builder`. +* Content configuration happens in the [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) file in `docs-builder`. +* Navigation configuration happens in the [`navigation.yml`](https://github.com/elastic/docs-builder/blob/main/config/navigation.yml) file in `docs-builder`. [assembler.yml](./content.md) diff --git a/docs/contribute/_snippets/tag-processing.md b/docs/contribute/_snippets/tag-processing.md index f36c96424..32227ff2f 100644 --- a/docs/contribute/_snippets/tag-processing.md +++ b/docs/contribute/_snippets/tag-processing.md @@ -12,10 +12,9 @@ Specifically for versioned products, badges will display differently when the `a Example: {applies_to}`stack: removed 99.99` -This is computed at build time (there is a docs build every 30 minutes). The documentation team tracks and maintains released versions for these products centrally in [versions.yml](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation.Configuration/versions.yml). - +This is computed at build time (there is a docs build every 30 minutes). The documentation team tracks and maintains released versions for these products centrally in [`versions.yml`](https://github.com/elastic/docs-builder/blob/main/config/versions.yml). When multiple lifecycle statuses and versions are specified in the sources, several badges are shown. :::{note} Visuals and wording in the output documentation are subject to changes and optimizations. -::: \ No newline at end of file +::: diff --git a/docs/contribute/_snippets/tagged-warning.md b/docs/contribute/_snippets/tagged-warning.md index d99601a01..e189ed229 100644 --- a/docs/contribute/_snippets/tagged-warning.md +++ b/docs/contribute/_snippets/tagged-warning.md @@ -3,5 +3,5 @@ Some repositories use a [tagged branching strategy](/contribute/branching-strate For detailed backporting guidance, refer to the example in [Choose the docs branching strategy for a repository](/contribute/branching-strategy.md#workflow-2-tagged). -To determine the published branches for a repository, find the repository in [assembler.yml](https://github.com/elastic/docs-builder/blob/main/src/tooling/docs-assembler/assembler.yml). +To determine the published branches for a repository, find the repository in [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml). ::: diff --git a/docs/contribute/add-repo.md b/docs/contribute/add-repo.md index 0175d9d7e..3009122f8 100644 --- a/docs/contribute/add-repo.md +++ b/docs/contribute/add-repo.md @@ -45,7 +45,7 @@ Then, successfully run a docs build on the `main` branch. This is a requirement. ::::{step} Add the repository to the assembler and navigation configs -Edit the [assembler.yml](https://github.com/elastic/docs-builder/blob/main/src/tooling/docs-assembler/assembler.yml) file to add the repository. Refer to [assembler.yml](../configure/site/content.md) for more information. +Edit the [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) file to add the repository. Refer to [assembler.yml](../configure/site/content.md) for more information. For example, to add the `elastic/yadda-docs` repository: @@ -58,7 +58,7 @@ references: In this file, you can optionally specify custom branches to deploy docs from, depending on your preferred [branching strategy](branching-strategy.md). You might want to change your branching strategy so you can have more control over when content added for a specific release is published. ::: -Then, edit the [navigation.yml](https://github.com/elastic/docs-builder/blob/main/src/tooling/docs-assembler/navigation.yml) file to add the repository to the navigation. +Then, edit the [`navigation.yml`](https://github.com/elastic/docs-builder/blob/main/config/navigation.yml) file to add the repository to the navigation. For example, to add the `elastic/yadda-docs` repository under **Reference**: diff --git a/docs/contribute/branching-strategy.md b/docs/contribute/branching-strategy.md index 297e632e0..6f045d528 100644 --- a/docs/contribute/branching-strategy.md +++ b/docs/contribute/branching-strategy.md @@ -39,7 +39,7 @@ After it has been established that a repository should publish from a version br 1. [Add new triggers to the `docs-build` CI integration](/configure/content-sources.md#ci-configuration). Merge these changes to `main` or `master` and the intended version branches. 2. Open a PR to trigger the CI integration and confirm that the docs build. -3. Open a PR updating the [docs assembler file](https://github.com/elastic/docs-builder/blob/main/src/tooling/docs-assembler/assembler.yml): +3. Open a PR updating the [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml): * Specify which is the `current` branch for the repository. This branch is the branch from which docs are deployed to production at [elastic.co/docs](http://elastic.co/docs). * Specify which is the `next` branch for the repository. The branch defined as `next` publishes docs internally to [staging-website.elastic.co/docs](http://staging-website.elastic.co/docs) * Setting this branch to the next version branch in line is a good practice to preview docs change for an upcoming version. @@ -60,10 +60,10 @@ When you publish from specific version branches, you need to bump the version br Add an action as part of that repo’s release process for the release manager to update this same assembler file and bump the `current` branch with each release, as appropriate. The `next` branch also needs to be bumped if it is not set to `main`. -When these releases happen, create a PR against the [assembler file](https://github.com/elastic/docs-builder/blob/main/src/tooling/docs-assembler/assembler.yml) that defines the new `current` branch, to merge on release day. +When these releases happen, create a PR against the [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) that defines the new `current` branch, to merge on release day. :::{tip} -Regardless of the branching strategy, you also need to update the current version in [versions.yml](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation.Configuration/versions.yml) as part of your release process. This version number is used in documentation variables and drives our [dynamic version badge logic](/contribute/cumulative-docs.md#how-do-these-tags-behave-in-the-output). +Regardless of the branching strategy, you also need to update the current version in [`versions.yml`](https://github.com/elastic/docs-builder/blob/main/config/versions.yml) as part of your release process. This version number is used in documentation variables and drives our [dynamic version badge logic](/contribute/cumulative-docs.md#how-do-these-tags-behave-in-the-output). ::: @@ -123,7 +123,7 @@ The changes must then be backported to their relevant version branches, and no f For example, in a situation where 9.0, 9.1, and 9.2 are already released, and the 9.3 branch has already been cut: -* The branch set as `current` in the [docs assembler](https://github.com/elastic/docs-builder/blob/625e75b35841be938a8df76a62deeee811ba52d4/src/tooling/docs-assembler/assembler.yml#L70) is 9.2. +* The branch set as `current` in the [`assembler.yml`](https://github.com/elastic/docs-builder/blob/main/config/assembler.yml) is 9.2. * The branch set as `next` (where the content development first happens), is `main`. * 9.4 changes are only done on the `main` branch. * 9.3 changes are done on the `main` branch and backported to the 9.3 branch. diff --git a/docs/contribute/cumulative-docs.md b/docs/contribute/cumulative-docs.md index a1eb9029d..588aba8fb 100644 --- a/docs/contribute/cumulative-docs.md +++ b/docs/contribute/cumulative-docs.md @@ -31,7 +31,7 @@ This tagging system is mandatory for all of the public-facing documentation. We ## How version awareness works in Docs V3 -Docs V3 uses a central version config called [versions.yml](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation.Configuration/versions.yml), which tracks the latest released versions of our products. It also tracks the earliest version of each product documented in the Docs V3 system (the earliest available on elastic.co/docs). +Docs V3 uses a central version config called [`versions.yml`](https://github.com/elastic/docs-builder/blob/main/config/versions.yml), which tracks the latest released versions of our products. It also tracks the earliest version of each product documented in the Docs V3 system (the earliest available on elastic.co/docs). This central version config is used in certain inline version variables, and drives our [dynamic rendering logic](#how-do-these-tags-behave-in-the-output). This logic allows us to label documentation related to unreleased versions as `planned`, continuously release documentation, and document our Serverless and {{stack}} offerings in one place. @@ -142,4 +142,4 @@ We do not do date-based tagging for unversioned products. ## How do these tags behave in the output? :::{include} /contribute/_snippets/tag-processing.md -::: \ No newline at end of file +::: diff --git a/docs/syntax/version-variables.md b/docs/syntax/version-variables.md index fc852ad1e..42a06a0da 100644 --- a/docs/syntax/version-variables.md +++ b/docs/syntax/version-variables.md @@ -28,7 +28,7 @@ can be printed in any kind of ways. ## Available versioning schemes. -This is dictated by the [versions.yml](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation.Configuration/versions.yml) configuration file +This is dictated by the [`versions.yml`](https://github.com/elastic/docs-builder/blob/main/config/versions.yml) configuration file * `stack` * `ece` diff --git a/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs index 285cdca6b..ae102d87c 100644 --- a/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.Text.RegularExpressions; +using Elastic.Documentation.Extensions; using YamlDotNet.Serialization; using YamlStaticContext = Elastic.Documentation.Configuration.Serialization.YamlStaticContext; @@ -10,6 +11,9 @@ namespace Elastic.Documentation.Configuration.Assembler; public record AssemblyConfiguration { + public static AssemblyConfiguration Create(ConfigurationFileProvider provider) => + Deserialize(provider.AssemblerFile.ReadToEnd()); + public static AssemblyConfiguration Deserialize(string yaml) { var input = new StringReader(yaml); diff --git a/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs b/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs new file mode 100644 index 000000000..1f3d9f15d --- /dev/null +++ b/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs @@ -0,0 +1,78 @@ +// 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 Microsoft.Extensions.DependencyInjection; + +namespace Elastic.Documentation.Configuration; + +public class ConfigurationFileProvider +{ + private readonly IFileSystem _fileSystem; + private readonly string _assemblyName; + + public ConfigurationFileProvider(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + _assemblyName = typeof(ConfigurationFileProvider).Assembly.GetName().Name!; + TemporaryDirectory = fileSystem.Directory.CreateTempSubdirectory("docs-builder-config"); + + VersionFile = CreateTemporaryConfigurationFile("versions.yml"); + AssemblerFile = CreateTemporaryConfigurationFile("assembler.yml"); + NavigationFile = CreateTemporaryConfigurationFile("navigation.yml"); + LegacyUrlMappingsFile = CreateTemporaryConfigurationFile("legacy-url-mappings.yml"); + } + + private IDirectoryInfo TemporaryDirectory { get; } + + public IFileInfo NavigationFile { get; } + + public IFileInfo VersionFile { get; } + + public IFileInfo AssemblerFile { get; } + + public IFileInfo LegacyUrlMappingsFile { get; } + + private IFileInfo CreateTemporaryConfigurationFile(string fileName) + { + using var stream = GetLocalOrEmbedded(fileName); + var context = stream.ReadToEnd(); + var fi = _fileSystem.FileInfo.New(Path.Combine(TemporaryDirectory.FullName, fileName)); + _fileSystem.File.WriteAllText(fi.FullName, context); + return fi; + } + + private StreamReader GetLocalOrEmbedded(string fileName) + { + var configPath = GetLocalPath(fileName); + if (!_fileSystem.File.Exists(configPath)) + return GetEmbeddedStream(fileName); + var reader = _fileSystem.File.OpenText(configPath); + return reader; + } + + private StreamReader GetEmbeddedStream(string fileName) + { + var resourceName = $"{_assemblyName}.{fileName}"; + var resourceStream = typeof(ConfigurationFileProvider).Assembly.GetManifestResourceStream(resourceName)!; + var reader = new StreamReader(resourceStream, leaveOpen: false); + return reader; + } + + public static string LocalConfigurationDirectory => Path.Combine(Paths.WorkingDirectoryRoot.FullName, "config"); + + private static string GetLocalPath(string file) => Path.Combine(LocalConfigurationDirectory, file); +} + +public static class ConfigurationFileProviderServiceCollectionExtensions +{ + public static IServiceCollection AddConfigurationFileProvider(this IServiceCollection services, + Action configure) + { + var provider = new ConfigurationFileProvider(new FileSystem()); + _ = services.AddSingleton(provider); + configure(services, provider); + return services; + } +} diff --git a/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj b/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj index f8bc96be8..abc882b8f 100644 --- a/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj +++ b/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj @@ -18,6 +18,9 @@ - + + + + diff --git a/src/Elastic.Documentation.Configuration/Versions/Version.cs b/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs similarity index 100% rename from src/Elastic.Documentation.Configuration/Versions/Version.cs rename to src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs diff --git a/src/Elastic.Documentation.Configuration/Versions/VersionsConfigurationExtensions.cs b/src/Elastic.Documentation.Configuration/Versions/VersionsConfigurationExtensions.cs index 6986fccf4..b79f3743b 100644 --- a/src/Elastic.Documentation.Configuration/Versions/VersionsConfigurationExtensions.cs +++ b/src/Elastic.Documentation.Configuration/Versions/VersionsConfigurationExtensions.cs @@ -2,9 +2,8 @@ // 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.Configuration.Serialization; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -12,19 +11,15 @@ namespace Elastic.Documentation.Configuration.Versions; public static class VersionsConfigurationExtensions { - private static readonly string ResourceName = "Elastic.Documentation.Configuration.versions.yml"; - - public static IServiceCollection AddVersions(this IServiceCollection services) + public static VersionsConfiguration CreateVersionConfiguration(this ConfigurationFileProvider provider) { - var assembly = typeof(VersionsConfigurationExtensions).Assembly; - using var stream = assembly.GetManifestResourceStream(ResourceName) ?? throw new FileNotFoundException(ResourceName); - using var reader = new StreamReader(stream); + var path = provider.VersionFile; var deserializer = new StaticDeserializerBuilder(new YamlStaticContext()) .WithNamingConvention(UnderscoredNamingConvention.Instance) .Build(); - var dto = deserializer.Deserialize(reader); + var dto = deserializer.Deserialize(path.OpenText()); var versions = dto.VersioningSystems.ToDictionary( kvp => ToVersioningSystemId(kvp.Key), @@ -35,10 +30,7 @@ public static IServiceCollection AddVersions(this IServiceCollection services) Current = ToSemVersion(kvp.Value.Current) }); var config = new VersionsConfiguration { VersioningSystems = versions }; - - _ = services.AddSingleton>(new OptionsWrapper(config)); - - return services; + return config; } private static VersioningSystemId ToVersioningSystemId(string id) diff --git a/src/Elastic.Documentation/Extensions/IFileInfoExtensions.cs b/src/Elastic.Documentation/Extensions/IFileInfoExtensions.cs new file mode 100644 index 000000000..2a80ef6cf --- /dev/null +++ b/src/Elastic.Documentation/Extensions/IFileInfoExtensions.cs @@ -0,0 +1,21 @@ +// 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; + +namespace Elastic.Documentation.Extensions; + +public static class IFileInfoExtensions +{ + public static string ReadToEnd(this IFileInfo fileInfo) + { + fileInfo.Refresh(); + if (!fileInfo.Exists) + return string.Empty; + + using var stream = fileInfo.OpenRead(); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } +} diff --git a/src/tooling/Elastic.Documentation.Tooling/DocumentationTooling.cs b/src/tooling/Elastic.Documentation.Tooling/DocumentationTooling.cs index 4220d0935..a85980927 100644 --- a/src/tooling/Elastic.Documentation.Tooling/DocumentationTooling.cs +++ b/src/tooling/Elastic.Documentation.Tooling/DocumentationTooling.cs @@ -3,7 +3,10 @@ // See the LICENSE file in the project root for more information using Actions.Core.Extensions; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Configuration.Versions; +using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Tooling.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -14,22 +17,32 @@ namespace Elastic.Documentation.Tooling; public static class DocumentationTooling { - public static ServiceProvider CreateServiceProvider(ref string[] args, Action? configure = null) + public static ServiceProvider CreateServiceProvider(ref string[] args, Action? configure = null) { var defaultLogLevel = LogLevel.Information; ProcessCommandLineArguments(ref args, ref defaultLogLevel); var services = new ServiceCollection(); - CreateServiceCollection(services, defaultLogLevel); - configure?.Invoke(services); + CreateServiceCollection(services, defaultLogLevel, configure); return services.BuildServiceProvider(); } - public static void CreateServiceCollection(IServiceCollection services, LogLevel defaultLogLevel) + public static void CreateServiceCollection( + IServiceCollection services, + LogLevel defaultLogLevel, + Action? configure = null + ) { _ = services .AddGitHubActionsCore() - .AddVersions(); + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddConfigurationFileProvider((s, p) => + { + _ = s.AddSingleton(p.CreateVersionConfiguration()); + configure?.Invoke(s, p); + }); services.TryAddEnumerable(ServiceDescriptor.Singleton()); _ = services.AddLogging(x => x .ClearProviders() diff --git a/src/tooling/docs-assembler/AssembleContext.cs b/src/tooling/docs-assembler/AssembleContext.cs index 0c2efca3d..2440b80a6 100644 --- a/src/tooling/docs-assembler/AssembleContext.cs +++ b/src/tooling/docs-assembler/AssembleContext.cs @@ -18,26 +18,24 @@ public class AssembleContext public DiagnosticsCollector Collector { get; } - public AssemblyConfiguration Configuration { get; set; } + public AssemblyConfiguration Configuration { get; } - public IFileInfo ConfigurationPath { get; } + public ConfigurationFileProvider ConfigurationFileProvider { get; } - public IFileInfo NavigationPath { get; } + public IDirectoryInfo CheckoutDirectory { get; } - public IFileInfo HistoryMappingPath { get; } - - public IDirectoryInfo CheckoutDirectory { get; set; } - - public IDirectoryInfo OutputDirectory { get; set; } + public IDirectoryInfo OutputDirectory { get; } public bool Force { get; init; } /// This property is used to determine if the site should be indexed by search engines public bool AllowIndexing { get; init; } - public PublishEnvironment Environment { get; set; } + public PublishEnvironment Environment { get; } public AssembleContext( + AssemblyConfiguration configuration, + ConfigurationFileProvider configurationFileProvider, string environment, DiagnosticsCollector collector, IFileSystem readFileSystem, @@ -50,23 +48,8 @@ public AssembleContext( ReadFileSystem = readFileSystem; WriteFileSystem = writeFileSystem; - var configPath = Path.Combine(Paths.WorkingDirectoryRoot.FullName, "src", "tooling", "docs-assembler", "assembler.yml"); - // temporarily fallback to embedded assembler.yml - // This will live in docs-content soon - if (!ReadFileSystem.File.Exists(configPath)) - ExtractAssemblerConfiguration(configPath, "assembler.yml"); - ConfigurationPath = ReadFileSystem.FileInfo.New(configPath); - Configuration = AssemblyConfiguration.Deserialize(ReadFileSystem.File.ReadAllText(ConfigurationPath.FullName)); - - var navigationPath = Path.Combine(Paths.WorkingDirectoryRoot.FullName, "src", "tooling", "docs-assembler", "navigation.yml"); - if (!ReadFileSystem.File.Exists(navigationPath)) - ExtractAssemblerConfiguration(navigationPath, "navigation.yml"); - NavigationPath = ReadFileSystem.FileInfo.New(navigationPath); - - var historyMappingPath = Path.Combine(Paths.WorkingDirectoryRoot.FullName, "src", "tooling", "docs-assembler", "legacy-url-mappings.yml"); - if (!ReadFileSystem.File.Exists(historyMappingPath)) - ExtractAssemblerConfiguration(historyMappingPath, "legacy-url-mappings.yml"); - HistoryMappingPath = ReadFileSystem.FileInfo.New(historyMappingPath); + Configuration = configuration; + ConfigurationFileProvider = configurationFileProvider; if (!Configuration.Environments.TryGetValue(environment, out var env)) throw new Exception($"Could not find environment {environment}"); @@ -77,24 +60,5 @@ public AssembleContext( CheckoutDirectory = ReadFileSystem.DirectoryInfo.New(checkoutDirectory ?? defaultCheckoutDirectory); var defaultOutputDirectory = Path.Combine(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly"); OutputDirectory = ReadFileSystem.DirectoryInfo.New(output ?? defaultOutputDirectory); - - - } - - private void ExtractAssemblerConfiguration(string configPath, string file) - { - var embeddedStaticFiles = Assembly.GetExecutingAssembly() - .GetManifestResourceNames() - .ToList(); - var configFile = embeddedStaticFiles.First(f => f.EndsWith(file)); - using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(configFile); - if (resourceStream == null) - return; - - var outputFile = WriteFileSystem.FileInfo.New(configPath); - if (outputFile.Directory is { Exists: false }) - outputFile.Directory.Create(); - using var stream = outputFile.OpenWrite(); - resourceStream.CopyTo(stream); } } diff --git a/src/tooling/docs-assembler/AssembleSources.cs b/src/tooling/docs-assembler/AssembleSources.cs index 344b841a3..f0aa51606 100644 --- a/src/tooling/docs-assembler/AssembleSources.cs +++ b/src/tooling/docs-assembler/AssembleSources.cs @@ -63,7 +63,7 @@ private AssembleSources(ILoggerFactory logFactory, AssembleContext assembleConte { AssembleContext = assembleContext; NavigationTocMappings = GetTocMappings(assembleContext); - HistoryMappings = GetHistoryMapping(assembleContext); + HistoryMappings = GetLegacyUrlMappings(assembleContext); var linkIndexProvider = Aws3LinkIndexReader.CreateAnonymous(); var crossLinkFetcher = new AssemblerCrossLinkFetcher(logFactory, assembleContext.Configuration, assembleContext.Environment, linkIndexProvider); @@ -97,7 +97,7 @@ private AssembleSources(ILoggerFactory logFactory, AssembleContext assembleConte var file = tocFiles.FirstOrDefault(f => f.Exists); if (file is null) { - assembleContext.Collector.EmitWarning(assembleContext.ConfigurationPath.FullName, $"Unable to find toc file in {tocDirectory}"); + assembleContext.Collector.EmitWarning(assembleContext.ConfigurationFileProvider.AssemblerFile, $"Unable to find toc file in {tocDirectory}"); file = tocFiles.First(); } @@ -113,10 +113,10 @@ private AssembleSources(ILoggerFactory logFactory, AssembleContext assembleConte .ToFrozenDictionary(); } - private static FrozenDictionary> GetHistoryMapping(AssembleContext context) + private static FrozenDictionary> GetLegacyUrlMappings(AssembleContext context) { var dictionary = new Dictionary>(); - var reader = new YamlStreamReader(context.HistoryMappingPath, context.Collector); + var reader = new YamlStreamReader(context.ConfigurationFileProvider.LegacyUrlMappingsFile, context.Collector); string? stack = null; foreach (var entry in reader.Read()) { @@ -154,7 +154,7 @@ static void ReadHistoryMappings(IDictionary> public static FrozenDictionary GetTocMappings(AssembleContext context) { var dictionary = new Dictionary(); - var reader = new YamlStreamReader(context.NavigationPath, context.Collector); + var reader = new YamlStreamReader(context.ConfigurationFileProvider.NavigationFile, context.Collector); var entries = new List>(); foreach (var entry in reader.Read()) { diff --git a/src/tooling/docs-assembler/Building/AssemblerBuilder.cs b/src/tooling/docs-assembler/Building/AssemblerBuilder.cs index 6354e19fd..4fceeaf11 100644 --- a/src/tooling/docs-assembler/Building/AssemblerBuilder.cs +++ b/src/tooling/docs-assembler/Building/AssemblerBuilder.cs @@ -20,7 +20,8 @@ public enum ExportOption { Html = 0, LLMText = 1, - Elasticsearch = 2 + Elasticsearch = 2, + Configuration = 3 } public class AssemblerBuilder( @@ -55,30 +56,34 @@ public async Task BuildAllAsync(FrozenDictionary(3); if (exportOptions.Contains(ExportOption.LLMText)) markdownExporters.Add(new LLMTextExporter()); + if (exportOptions.Contains(ExportOption.Configuration)) + markdownExporters.Add(new ConfigurationExporter(logFactory, context)); if (exportOptions.Contains(ExportOption.Elasticsearch) && esExporter is { }) markdownExporters.Add(esExporter); - var noopBuild = !exportOptions.Contains(ExportOption.Html); + var noHtmlOutput = !exportOptions.Contains(ExportOption.Html); var tasks = markdownExporters.Select(async e => await e.StartAsync(ctx)); await Task.WhenAll(tasks); + var fs = context.ReadFileSystem; + var reportPath = context.ConfigurationFileProvider.AssemblerFile; foreach (var (_, set) in assembleSets) { var checkout = set.Checkout; if (checkout.Repository.Skip) { - context.Collector.EmitWarning(context.ConfigurationPath.FullName, $"Skipping {checkout.Repository.Origin} as its marked as skip in configuration"); + context.Collector.EmitWarning(reportPath, $"Skipping {checkout.Repository.Origin} as its marked as skip in configuration"); continue; } try { - var result = await BuildAsync(set, noopBuild, markdownExporters.ToArray(), ctx); + var result = await BuildAsync(set, noHtmlOutput, markdownExporters.ToArray(), ctx); CollectRedirects(redirects, result.Redirects, checkout.Repository.Name, set.DocumentationSet.LinkResolver); } catch (Exception e) when (e.Message.Contains("Can not locate docset.yml file in")) { - context.Collector.EmitWarning(context.ConfigurationPath.FullName, $"Skipping {checkout.Repository.Origin} as its not yet been migrated to V3"); + context.Collector.EmitWarning(reportPath, $"Skipping {checkout.Repository.Origin} as its not yet been migrated to V3"); } catch (Exception e) { diff --git a/src/tooling/docs-assembler/Cli/ContentSourceCommands.cs b/src/tooling/docs-assembler/Cli/ContentSourceCommands.cs index b2e0ae9b0..9ff91f494 100644 --- a/src/tooling/docs-assembler/Cli/ContentSourceCommands.cs +++ b/src/tooling/docs-assembler/Cli/ContentSourceCommands.cs @@ -6,6 +6,7 @@ using Actions.Core.Services; using ConsoleAppFramework; using Documentation.Assembler.Building; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.LinkIndex; using Elastic.Documentation.Tooling.Diagnostics.Console; @@ -13,7 +14,12 @@ namespace Documentation.Assembler.Cli; -internal sealed class ContentSourceCommands(ICoreService githubActionsService, ILoggerFactory logFactory) +internal sealed class ContentSourceCommands( + ILoggerFactory logFactory, + AssemblyConfiguration configuration, + ConfigurationFileProvider configurationFileProvider, + ICoreService githubActionsService +) { [Command("validate")] public async Task Validate(Cancel ctx = default) @@ -26,7 +32,8 @@ public async Task Validate(Cancel ctx = default) _ = collector.StartAsync(ctx); // environment does not matter to check the configuration, defaulting to dev - var context = new AssembleContext("dev", collector, new FileSystem(), new FileSystem(), null, null) + var fs = new FileSystem(); + var context = new AssembleContext(configuration, configurationFileProvider, "dev", collector, fs, fs, null, null) { Force = false, AllowIndexing = false @@ -36,11 +43,12 @@ public async Task Validate(Cancel ctx = default) var links = await fetcher.FetchLinkIndex(ctx); var repositories = context.Configuration.ReferenceRepositories.Values.Concat([context.Configuration.Narrative]).ToList(); + var reportPath = context.ConfigurationFileProvider.AssemblerFile; foreach (var repository in repositories) { if (!links.Repositories.TryGetValue(repository.Name, out var registryMapping)) { - collector.EmitError(context.ConfigurationPath, $"'{repository}' does not exist in link index"); + collector.EmitError(reportPath, $"'{repository}' does not exist in link index"); continue; } @@ -48,12 +56,12 @@ public async Task Validate(Cancel ctx = default) var next = repository.GetBranch(ContentSource.Next); if (!registryMapping.TryGetValue(next, out _)) { - collector.EmitError(context.ConfigurationPath, + collector.EmitError(reportPath, $"'{repository.Name}' has not yet published links.json for configured 'next' content source: '{next}' see {linkIndexReader.RegistryUrl}"); } if (!registryMapping.TryGetValue(current, out _)) { - collector.EmitError(context.ConfigurationPath, + collector.EmitError(reportPath, $"'{repository.Name}' has not yet published links.json for configured 'current' content source: '{current}' see {linkIndexReader.RegistryUrl}"); } } @@ -89,7 +97,7 @@ public async Task Match([Argument] string? repository = null, [Argument] st _ = collector.StartAsync(ctx); // environment does not matter to check the configuration, defaulting to dev - var assembleContext = new AssembleContext("dev", collector, new FileSystem(), new FileSystem(), null, null) + var assembleContext = new AssembleContext(configuration, configurationFileProvider, "dev", collector, new FileSystem(), new FileSystem(), null, null) { Force = false, AllowIndexing = false diff --git a/src/tooling/docs-assembler/Cli/DeployCommands.cs b/src/tooling/docs-assembler/Cli/DeployCommands.cs index 335892c77..d8bf3c495 100644 --- a/src/tooling/docs-assembler/Cli/DeployCommands.cs +++ b/src/tooling/docs-assembler/Cli/DeployCommands.cs @@ -10,6 +10,8 @@ using Amazon.S3.Transfer; using ConsoleAppFramework; using Documentation.Assembler.Deploying; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Serialization; using Elastic.Documentation.Tooling.Diagnostics.Console; using Elastic.Documentation.Tooling.Filters; @@ -17,7 +19,12 @@ namespace Documentation.Assembler.Cli; -internal sealed class DeployCommands(ILoggerFactory logFactory, ICoreService githubActionsService) +internal sealed class DeployCommands( + AssemblyConfiguration assemblyConfiguration, + ConfigurationFileProvider configurationFileProvider, + ILoggerFactory logFactory, + ICoreService githubActionsService +) { [SuppressMessage("Usage", "CA2254:Template should be a static expression")] private void AssignOutputLogger() @@ -40,7 +47,8 @@ public async Task Plan( { NoHints = true }.StartAsync(ctx); - var assembleContext = new AssembleContext(environment, collector, new FileSystem(), new FileSystem(), null, null); + var fs = new FileSystem(); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationFileProvider, environment, collector, fs, fs, null, null); var s3Client = new AmazonS3Client(); IDocsSyncPlanStrategy planner = new AwsS3SyncPlanStrategy(logFactory, s3Client, s3BucketName, assembleContext); var plan = await planner.Plan(ctx); @@ -77,7 +85,8 @@ public async Task Apply( { NoHints = true }.StartAsync(ctx); - var assembleContext = new AssembleContext(environment, collector, new FileSystem(), new FileSystem(), null, null); + var fs = new FileSystem(); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationFileProvider, environment, collector, fs, fs, null, null); var s3Client = new AmazonS3Client(); var transferUtility = new TransferUtility(s3Client, new TransferUtilityConfig { diff --git a/src/tooling/docs-assembler/Cli/NavigationCommands.cs b/src/tooling/docs-assembler/Cli/NavigationCommands.cs index 115210d87..05616cb45 100644 --- a/src/tooling/docs-assembler/Cli/NavigationCommands.cs +++ b/src/tooling/docs-assembler/Cli/NavigationCommands.cs @@ -10,13 +10,18 @@ using Documentation.Assembler.Navigation; using Elastic.Documentation; using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Tooling.Diagnostics.Console; -using Elastic.Documentation.Tooling.Filters; using Microsoft.Extensions.Logging; namespace Documentation.Assembler.Cli; -internal sealed class NavigationCommands(ILoggerFactory logFactory, ICoreService githubActionsService) +internal sealed class NavigationCommands( + AssemblyConfiguration configuration, + ConfigurationFileProvider configurationFileProvider, + ILoggerFactory logFactory, + ICoreService githubActionsService +) { /// Validates navigation.yml does not contain colliding path prefixes /// @@ -24,7 +29,7 @@ internal sealed class NavigationCommands(ILoggerFactory logFactory, ICoreService public async Task Validate(Cancel ctx = default) { await using var collector = new ConsoleDiagnosticsCollector(logFactory, githubActionsService).StartAsync(ctx); - var assembleContext = new AssembleContext("dev", collector, new FileSystem(), new FileSystem(), null, null); + var assembleContext = new AssembleContext(configuration, configurationFileProvider, "dev", collector, new FileSystem(), new FileSystem(), null, null); // this validates all path prefixes are unique, early exit if duplicates are detected if (!GlobalNavigationFile.ValidatePathPrefixes(assembleContext) || assembleContext.Collector.Errors > 0) @@ -51,11 +56,11 @@ public async Task ValidateLocalLinkReference([Argument] string? file = null await using var collector = new ConsoleDiagnosticsCollector(logFactory, githubActionsService).StartAsync(ctx); - var assembleContext = new AssembleContext("dev", collector, new FileSystem(), new FileSystem(), null, null); - var fs = new FileSystem(); + var assembleContext = new AssembleContext(configuration, configurationFileProvider, "dev", collector, fs, fs, null, null); + var root = fs.DirectoryInfo.New(Paths.WorkingDirectoryRoot.FullName); - var repository = GitCheckoutInformation.Create(root, new FileSystem(), logFactory.CreateLogger(nameof(GitCheckoutInformation))).RepositoryName + var repository = GitCheckoutInformation.Create(root, fs, logFactory.CreateLogger(nameof(GitCheckoutInformation))).RepositoryName ?? throw new Exception("Unable to determine repository name"); var namespaceChecker = new NavigationPrefixChecker(logFactory, assembleContext); diff --git a/src/tooling/docs-assembler/Cli/RepositoryCommands.cs b/src/tooling/docs-assembler/Cli/RepositoryCommands.cs index 30b8f3740..ce2d9d3aa 100644 --- a/src/tooling/docs-assembler/Cli/RepositoryCommands.cs +++ b/src/tooling/docs-assembler/Cli/RepositoryCommands.cs @@ -13,7 +13,6 @@ using Documentation.Assembler.Legacy; using Documentation.Assembler.Navigation; using Documentation.Assembler.Sourcing; -using Elastic.Documentation; using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Configuration.Versions; @@ -23,11 +22,16 @@ using Elastic.Markdown.Exporters; using Elastic.Markdown.IO; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Documentation.Assembler.Cli; -internal sealed class RepositoryCommands(ILoggerFactory logFactory, ICoreService githubActionsService, IOptions versionsConfigOption) +internal sealed class RepositoryCommands( + AssemblyConfiguration assemblyConfiguration, + VersionsConfiguration versionsConfig, + ConfigurationFileProvider configurationFileProvider, + ILoggerFactory logFactory, + ICoreService githubActionsService +) { private readonly ILogger _log = logFactory.CreateLogger(); @@ -57,7 +61,8 @@ public async Task CloneAll( await using var collector = new ConsoleDiagnosticsCollector(logFactory, githubActionsService).StartAsync(ctx); - var assembleContext = new AssembleContext(environment, collector, new FileSystem(), new FileSystem(), null, null); + var fs = new FileSystem(); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationFileProvider, environment, collector, fs, fs, null, null); var cloner = new AssemblerRepositorySourcer(logFactory, assembleContext); _ = await cloner.CloneAll(fetchLatest ?? false, ctx); @@ -72,7 +77,7 @@ public async Task CloneAll( /// Builds all repositories /// Force a full rebuild of the destination folder /// Treat warnings as errors and fail the build on warnings - /// Allow indexing and following of html files + /// Allow indexing and following of HTML files /// The environment to build /// configure exporters explicitly available (html,llmtext,es), defaults to html /// @@ -85,7 +90,7 @@ public async Task BuildAll( [ExporterParser] IReadOnlySet? exporters = null, Cancel ctx = default) { - exporters ??= new HashSet([ExportOption.Html]); + exporters ??= new HashSet([ExportOption.Html, ExportOption.Configuration]); AssignOutputLogger(); var githubEnvironmentInput = githubActionsService.GetInput("environment"); @@ -100,7 +105,8 @@ public async Task BuildAll( _log.LogInformation("Creating assemble context"); - var assembleContext = new AssembleContext(environment, collector, new FileSystem(), new FileSystem(), null, null) + var fs = new FileSystem(); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationFileProvider, environment, collector, fs, fs, null, null) { Force = force ?? false, AllowIndexing = allowIndexing ?? false @@ -123,7 +129,7 @@ public async Task BuildAll( throw new Exception("No checkouts found"); _log.LogInformation("Preparing all assemble sources for build"); - var assembleSources = await AssembleSources.AssembleAsync(logFactory, assembleContext, checkouts, versionsConfigOption.Value, ctx); + var assembleSources = await AssembleSources.AssembleAsync(logFactory, assembleContext, checkouts, versionsConfig, ctx); var navigationFile = new GlobalNavigationFile(assembleContext, assembleSources); _log.LogInformation("Create global navigation"); @@ -161,8 +167,9 @@ public async Task UpdateLinkIndexAll(ContentSource contentSource, Cancel ct var collector = new ConsoleDiagnosticsCollector(logFactory, githubActionsService); // The environment ist not relevant here. // It's only used to get the list of repositories. - var assembleContext = new AssembleContext("prod", collector, new FileSystem(), new FileSystem(), null, null); - var cloner = new RepositorySourcer(logFactory, assembleContext.CheckoutDirectory, new FileSystem(), collector); + var fs = new FileSystem(); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationFileProvider, "prod", collector, fs, fs, null, null); + var cloner = new RepositorySourcer(logFactory, assembleContext.CheckoutDirectory, fs, collector); var repositories = new Dictionary(assembleContext.Configuration.ReferenceRepositories) { { NarrativeRepository.RepositoryName, assembleContext.Configuration.Narrative } @@ -182,7 +189,7 @@ await Parallel.ForEachAsync(repositories, collector, new FileSystem(), new FileSystem(), - versionsConfigOption.Value, + versionsConfig, checkout.Directory.FullName, outputPath ); @@ -223,7 +230,7 @@ public class ExporterParserAttribute : Attribute, IArgumentParser s, out IReadOnlySet result) { - result = new HashSet([ExportOption.Html]); + result = new HashSet([ExportOption.Html, ExportOption.Configuration]); var set = new HashSet(); var ranges = s.Split(','); foreach (var range in ranges) @@ -235,6 +242,7 @@ public static bool TryParse(ReadOnlySpan s, out IReadOnlySet "es" => ExportOption.Elasticsearch, "elasticsearch" => ExportOption.Elasticsearch, "html" => ExportOption.Html, + "config" => ExportOption.Configuration, _ => null }; if (export.HasValue) diff --git a/src/tooling/docs-assembler/Exporters/ConfigurationExporter.cs b/src/tooling/docs-assembler/Exporters/ConfigurationExporter.cs new file mode 100644 index 000000000..6f7f0b792 --- /dev/null +++ b/src/tooling/docs-assembler/Exporters/ConfigurationExporter.cs @@ -0,0 +1,52 @@ +// 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.Markdown.Exporters; +using Microsoft.Extensions.Logging; + +namespace Documentation.Assembler.Exporters; + +public class ConfigurationExporter(ILoggerFactory logFactory, AssembleContext context) : IMarkdownExporter +{ + private readonly ILogger _logger = logFactory.CreateLogger(); + + /// + public ValueTask StartAsync(CancellationToken ctx = default) => default; + + /// + public ValueTask StopAsync(CancellationToken ctx = default) => default; + + /// + public ValueTask ExportAsync(MarkdownExportFileContext fileContext, CancellationToken ctx) => default; + + /// + public ValueTask FinishExportAsync(IDirectoryInfo outputFolder, CancellationToken ctx) + { + var fs = context.WriteFileSystem; + var configFolder = fs.DirectoryInfo.New(Path.Combine(context.OutputDirectory.FullName, "config")); + if (!configFolder.Exists) + configFolder.Create(); + + _logger.LogInformation("Exporting configuration"); + + var assemblerConfig = context.ConfigurationFileProvider.AssemblerFile; + _logger.LogInformation("Exporting {Name} to {ConfigFolder}", assemblerConfig.Name, configFolder.FullName); + context.WriteFileSystem.File.Copy(assemblerConfig.FullName, Path.Combine(configFolder.FullName, assemblerConfig.Name), true); + + var navigationConfig = context.ConfigurationFileProvider.NavigationFile; + _logger.LogInformation("Exporting {Name} to {ConfigFolder}", navigationConfig.Name, configFolder.FullName); + context.WriteFileSystem.File.Copy(navigationConfig.FullName, Path.Combine(configFolder.FullName, navigationConfig.Name), true); + + var legacyUrlMappingsConfig = context.ConfigurationFileProvider.LegacyUrlMappingsFile; + _logger.LogInformation("Exporting {Name} to {ConfigFolder}", legacyUrlMappingsConfig.Name, configFolder.FullName); + context.WriteFileSystem.File.Copy(legacyUrlMappingsConfig.FullName, Path.Combine(configFolder.FullName, legacyUrlMappingsConfig.Name), true); + + var versionsConfig = context.ConfigurationFileProvider.VersionFile; + _logger.LogInformation("Exporting {Name} to {ConfigFolder}", versionsConfig.Name, configFolder.FullName); + context.WriteFileSystem.File.Copy(versionsConfig.FullName, Path.Combine(configFolder.FullName, versionsConfig.Name), true); + + return default; + } +} diff --git a/src/tooling/docs-assembler/Exporters/ElasticsearchMarkdownExporter.cs b/src/tooling/docs-assembler/Exporters/ElasticsearchMarkdownExporter.cs index 954ff3204..f56261ae3 100644 --- a/src/tooling/docs-assembler/Exporters/ElasticsearchMarkdownExporter.cs +++ b/src/tooling/docs-assembler/Exporters/ElasticsearchMarkdownExporter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Search; using Elastic.Documentation.Serialization; diff --git a/src/tooling/docs-assembler/Navigation/GlobalNavigationFile.cs b/src/tooling/docs-assembler/Navigation/GlobalNavigationFile.cs index d78f4f070..ffe72d4b2 100644 --- a/src/tooling/docs-assembler/Navigation/GlobalNavigationFile.cs +++ b/src/tooling/docs-assembler/Navigation/GlobalNavigationFile.cs @@ -27,13 +27,17 @@ public GlobalNavigationFile(AssembleContext context, AssembleSources assembleSou { _context = context; _assembleSources = assembleSources; + NavigationFile = context.ConfigurationFileProvider.NavigationFile; TableOfContents = Deserialize("toc"); Phantoms = Deserialize("phantoms"); - ScopeDirectory = _context.NavigationPath.Directory!; + ScopeDirectory = NavigationFile.Directory!; } + private IFileInfo NavigationFile { get; } + public static bool ValidatePathPrefixes(AssembleContext context) { + var fileProvider = context.ConfigurationFileProvider; var sourcePathPrefixes = GetAllPathPrefixes(context); var pathPrefixSet = new HashSet(); var valid = true; @@ -43,9 +47,7 @@ public static bool ValidatePathPrefixes(AssembleContext context) if (pathPrefixSet.Add(prefix)) continue; var duplicateOf = sourcePathPrefixes.First(p => p.Host == pathPrefix.Host && p.AbsolutePath == pathPrefix.AbsolutePath); - context.Collector.EmitError(context.NavigationPath.FullName, - $"Duplicate path prefix: {pathPrefix} duplicate: {duplicateOf}" - ); + context.Collector.EmitError(fileProvider.NavigationFile, $"Duplicate path prefix: {pathPrefix} duplicate: {duplicateOf}"); valid = false; } return valid; @@ -60,7 +62,7 @@ public static ImmutableHashSet GetPhantomPrefixes(AssembleContext context) private static ImmutableHashSet GetSourceUris(string key, AssembleContext context) { - var reader = new YamlStreamReader(context.NavigationPath, context.Collector); + var reader = new YamlStreamReader(context.ConfigurationFileProvider.NavigationFile, context.Collector); var set = new HashSet(); foreach (var entry in reader.Read()) { @@ -142,15 +144,15 @@ static void ReadPathPrefixes(YamlStreamReader reader, KeyValuePair - _context.Collector.EmitWarning(_context.NavigationPath.FullName, message); + _context.Collector.EmitWarning(NavigationFile, message); public void EmitError(string message) => - _context.Collector.EmitWarning(_context.NavigationPath.FullName, message); + _context.Collector.EmitWarning(NavigationFile, message); private IReadOnlyCollection Deserialize(string key) { - var reader = new YamlStreamReader(_context.NavigationPath, _context.Collector); + var reader = new YamlStreamReader(NavigationFile, _context.Collector); try { foreach (var entry in reader.Read()) diff --git a/src/tooling/docs-assembler/Navigation/GlobalNavigationPathProvider.cs b/src/tooling/docs-assembler/Navigation/GlobalNavigationPathProvider.cs index b9c10a978..bc5d83f3b 100644 --- a/src/tooling/docs-assembler/Navigation/GlobalNavigationPathProvider.cs +++ b/src/tooling/docs-assembler/Navigation/GlobalNavigationPathProvider.cs @@ -113,7 +113,7 @@ public GlobalNavigationPathProvider(GlobalNavigationFile navigationFile, Assembl } var fallBack = fs.Path.Combine(outputDirectory.FullName, "_failed", repositoryName, relativePath); - _context.Collector.EmitError(_context.NavigationPath, $"No toc for output path: '{lookup}' falling back to: '{fallBack}'"); + _context.Collector.EmitError(_context.ConfigurationFileProvider.NavigationFile, $"No toc for output path: '{lookup}' falling back to: '{fallBack}'"); return fs.FileInfo.New(fallBack); } diff --git a/src/tooling/docs-assembler/Program.cs b/src/tooling/docs-assembler/Program.cs index 7f05b496e..449b46c89 100644 --- a/src/tooling/docs-assembler/Program.cs +++ b/src/tooling/docs-assembler/Program.cs @@ -6,16 +6,16 @@ using Actions.Core.Services; using ConsoleAppFramework; using Documentation.Assembler.Cli; -using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Tooling; using Elastic.Documentation.Tooling.Filters; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -await using var serviceProvider = DocumentationTooling.CreateServiceProvider(ref args, services => services - .AddSingleton() - .AddSingleton() -); +await using var serviceProvider = DocumentationTooling.CreateServiceProvider(ref args, (s, p) => +{ + _ = s.AddSingleton(AssemblyConfiguration.Create(p)); +}); ConsoleApp.ServiceProvider = serviceProvider; diff --git a/src/tooling/docs-assembler/docs-assembler.csproj b/src/tooling/docs-assembler/docs-assembler.csproj index 3d445036b..ccdd564d1 100644 --- a/src/tooling/docs-assembler/docs-assembler.csproj +++ b/src/tooling/docs-assembler/docs-assembler.csproj @@ -34,9 +34,4 @@ - - - - - diff --git a/src/tooling/docs-builder/Cli/Commands.cs b/src/tooling/docs-builder/Cli/Commands.cs index c14ededc6..adf680a7a 100644 --- a/src/tooling/docs-builder/Cli/Commands.cs +++ b/src/tooling/docs-builder/Cli/Commands.cs @@ -20,7 +20,7 @@ namespace Documentation.Builder.Cli; -internal sealed class Commands(ILoggerFactory logFactory, ICoreService githubActionsService, IOptions versionsConfigOption) +internal sealed class Commands(ILoggerFactory logFactory, ICoreService githubActionsService, VersionsConfiguration versionsConfig) { private readonly ILogger _log = logFactory.CreateLogger(); @@ -36,7 +36,7 @@ internal sealed class Commands(ILoggerFactory logFactory, ICoreService githubAct [Command("serve")] public async Task Serve(string? path = null, int port = 3000, Cancel ctx = default) { - var host = new DocumentationWebHost(logFactory, path, port, new FileSystem(), new MockFileSystem(), versionsConfigOption.Value); + var host = new DocumentationWebHost(logFactory, path, port, new FileSystem(), new MockFileSystem(), versionsConfig); _log.LogInformation("Find your documentation at http://localhost:{Port}/{Path}", port, host.GeneratorState.Generator.DocumentationSet.FirstInterestingUrl.TrimStart('/') ); @@ -106,7 +106,7 @@ public async Task Generate( try { - context = new BuildContext(collector, fileSystem, fileSystem, versionsConfigOption.Value, path, output) + context = new BuildContext(collector, fileSystem, fileSystem, versionsConfig, path, output) { UrlPathPrefix = pathPrefix, Force = force ?? false, @@ -208,7 +208,7 @@ public async Task Move( { var fileSystem = new FileSystem(); await using var collector = new ConsoleDiagnosticsCollector(logFactory, null).StartAsync(ctx); - var context = new BuildContext(collector, fileSystem, fileSystem, versionsConfigOption.Value, path, null); + var context = new BuildContext(collector, fileSystem, fileSystem, versionsConfig, path, null); var set = new DocumentationSet(context, logFactory); var moveCommand = new Move(logFactory, fileSystem, fileSystem, set); diff --git a/src/tooling/docs-builder/Cli/DiffCommands.cs b/src/tooling/docs-builder/Cli/DiffCommands.cs index a32812857..9f9983c4f 100644 --- a/src/tooling/docs-builder/Cli/DiffCommands.cs +++ b/src/tooling/docs-builder/Cli/DiffCommands.cs @@ -12,11 +12,10 @@ using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Tooling.Diagnostics.Console; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Documentation.Builder.Cli; -internal sealed class DiffCommands(ILoggerFactory logFactory, ICoreService githubActionsService, IOptions versionsConfigOption) +internal sealed class DiffCommands(ILoggerFactory logFactory, ICoreService githubActionsService, VersionsConfiguration versionsConfig) { /// /// Validates redirect updates in the current branch using the redirect file against changes reported by git. @@ -34,7 +33,7 @@ public async Task ValidateRedirects([Argument] string? path = null, Cancel var fs = new FileSystem(); var root = fs.DirectoryInfo.New(Paths.WorkingDirectoryRoot.FullName); - var buildContext = new BuildContext(collector, fs, fs, versionsConfigOption.Value, root.FullName, null); + var buildContext = new BuildContext(collector, fs, fs, versionsConfig, root.FullName, null); var sourceFile = buildContext.ConfigurationPath; var redirectFileName = sourceFile.Name.StartsWith('_') ? "_redirects.yml" : "redirects.yml"; var redirectFileInfo = sourceFile.FileSystem.FileInfo.New(Path.Combine(sourceFile.Directory!.FullName, redirectFileName)); diff --git a/src/tooling/docs-builder/Program.cs b/src/tooling/docs-builder/Program.cs index 62c18079f..e823ca0ea 100644 --- a/src/tooling/docs-builder/Program.cs +++ b/src/tooling/docs-builder/Program.cs @@ -9,7 +9,7 @@ using Elastic.Documentation.Tooling.Filters; using Microsoft.Extensions.Logging; -await using var serviceProvider = DocumentationTooling.CreateServiceProvider(ref args, _ => { }); +await using var serviceProvider = DocumentationTooling.CreateServiceProvider(ref args, (s, p) => { }); ConsoleApp.ServiceProvider = serviceProvider; var app = ConsoleApp.Create(); diff --git a/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs b/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs index 557b42b04..e343e3f79 100644 --- a/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs +++ b/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs @@ -23,7 +23,18 @@ public AssemblerConfigurationTests() FileSystem.Path.Combine(Paths.GetSolutionDirectory()!.FullName, ".artifacts", "checkouts") ); Collector = new DiagnosticsCollector([]); - Context = new AssembleContext("dev", Collector, FileSystem, FileSystem, CheckoutDirectory.FullName, null); + var configurationFileProvider = new ConfigurationFileProvider(FileSystem); + var config = AssemblyConfiguration.Create(configurationFileProvider); + Context = new AssembleContext(config, configurationFileProvider, "dev", Collector, FileSystem, FileSystem, CheckoutDirectory.FullName, null); + } + + [Fact] + public void ReadsConfigurationFiles() + { + Context.ConfigurationFileProvider.VersionFile.Name.Should().Be("versions.yml"); + Context.ConfigurationFileProvider.NavigationFile.Name.Should().Be("navigation.yml"); + Context.ConfigurationFileProvider.AssemblerFile.Name.Should().Be("assembler.yml"); + Context.ConfigurationFileProvider.LegacyUrlMappingsFile.Name.Should().Be("legacy-url-mappings.yml"); } [Fact] diff --git a/tests/docs-assembler.Tests/src/docs-assembler.Tests/DocsSyncTests.cs b/tests/docs-assembler.Tests/src/docs-assembler.Tests/DocsSyncTests.cs index e8163b826..7253d0567 100644 --- a/tests/docs-assembler.Tests/src/docs-assembler.Tests/DocsSyncTests.cs +++ b/tests/docs-assembler.Tests/src/docs-assembler.Tests/DocsSyncTests.cs @@ -7,6 +7,7 @@ using Amazon.S3.Transfer; using Documentation.Assembler.Deploying; using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Diagnostics; using FakeItEasy; using FluentAssertions; @@ -35,7 +36,9 @@ public async Task TestPlan() CurrentDirectory = Path.Combine(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly") }); - var context = new AssembleContext("dev", collector, fileSystem, fileSystem, null, Path.Combine(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly")); + var configurationFileProvider = new ConfigurationFileProvider(fileSystem); + var config = AssemblyConfiguration.Create(configurationFileProvider); + var context = new AssembleContext(config, configurationFileProvider, "dev", collector, fileSystem, fileSystem, null, Path.Combine(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly")); A.CallTo(() => mockS3Client.ListObjectsV2Async(A._, A._)) .Returns(new Amazon.S3.Model.ListObjectsV2Response { @@ -97,7 +100,9 @@ public async Task TestApply() { CurrentDirectory = Path.Combine(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly") }); - var context = new AssembleContext("dev", collector, fileSystem, fileSystem, null, Path.Combine(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly")); + var configurationFileProvider = new ConfigurationFileProvider(fileSystem); + var config = AssemblyConfiguration.Create(configurationFileProvider); + var context = new AssembleContext(config, configurationFileProvider, "dev", collector, fileSystem, fileSystem, null, Path.Combine(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly")); var plan = new SyncPlan { Count = 6, diff --git a/tests/docs-assembler.Tests/src/docs-assembler.Tests/GlobalNavigationTests.cs b/tests/docs-assembler.Tests/src/docs-assembler.Tests/GlobalNavigationTests.cs index 23bedfdb9..7ea6727d8 100644 --- a/tests/docs-assembler.Tests/src/docs-assembler.Tests/GlobalNavigationTests.cs +++ b/tests/docs-assembler.Tests/src/docs-assembler.Tests/GlobalNavigationTests.cs @@ -37,7 +37,9 @@ public GlobalNavigationPathProviderTests() ? checkoutDirectory.GetDirectories().FirstOrDefault(d => d.Name is "next" or "current") ?? checkoutDirectory : checkoutDirectory; Collector = new DiagnosticsCollector([]); - Context = new AssembleContext("dev", Collector, FileSystem, FileSystem, CheckoutDirectory.FullName, null); + var configurationFileProvider = new ConfigurationFileProvider(FileSystem); + var config = AssemblyConfiguration.Create(configurationFileProvider); + Context = new AssembleContext(config, configurationFileProvider, "dev", Collector, FileSystem, FileSystem, CheckoutDirectory.FullName, null); } private Checkout CreateCheckout(IFileSystem fs, string name) => @@ -88,7 +90,10 @@ public async Task ReadAllPathPrefixes() await using var collector = new DiagnosticsCollector([]); - var assembleContext = new AssembleContext("dev", collector, new FileSystem(), new FileSystem(), null, null); + var fileSystem = new FileSystem(); + var configurationFileProvider = new ConfigurationFileProvider(fileSystem); + var config = AssemblyConfiguration.Create(configurationFileProvider); + var assembleContext = new AssembleContext(config, configurationFileProvider, "dev", collector, fileSystem, fileSystem, null, null); var pathPrefixes = GlobalNavigationFile.GetAllPathPrefixes(assembleContext); @@ -275,7 +280,9 @@ public async Task UriResolving() await using var collector = new DiagnosticsCollector([]).StartAsync(TestContext.Current.CancellationToken); var fs = new FileSystem(); - var assembleContext = new AssembleContext("prod", collector, fs, fs, null, null); + var configurationFileProvider = new ConfigurationFileProvider(fs); + var config = AssemblyConfiguration.Create(configurationFileProvider); + var assembleContext = new AssembleContext(config, configurationFileProvider, "prod", collector, fs, fs, null, null); var repos = assembleContext.Configuration.ReferenceRepositories .Where(kv => !kv.Value.Skip) .Select(kv => kv.Value.Name)