diff --git a/.gitignore b/.gitignore
index c87b8d0..72c21b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -180,9 +180,6 @@ DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
-# Click-Once directory
-publish/
-
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
diff --git a/action.yml b/action.yml
index a9b25fc..9ac58b1 100644
--- a/action.yml
+++ b/action.yml
@@ -7,10 +7,25 @@ branding:
inputs:
prefix:
- description: 'The relative location of the documentation'
+ description: 'Path prefix for all urls'
required: false
runs:
- using: 'docker'
- image: "docker://ghcr.io/elastic/docs-builder:edge"
-
\ No newline at end of file
+ - id: repo-basename
+ run: 'echo "value=`basename ${{ github.repository }}`" >> $GITHUB_OUTPUT'
+ - uses: actions/checkout@v4
+ - name: Setup Pages
+ id: pages
+ uses: actions/configure-pages@v5.0.0
+ - name: Build documentation
+ uses: elastic/docs-builder@main
+ with:
+ prefix: '${{ steps.repo-basename.outputs.value }}'
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3.0.1
+ with:
+ path: .artifacts/docs/html
+
+ - name: Deploy artifact
+ id: deployment
+ uses: actions/deploy-pages@v4.0.5
diff --git a/actions/generator/action.yml b/actions/generator/action.yml
new file mode 100644
index 0000000..6eec713
--- /dev/null
+++ b/actions/generator/action.yml
@@ -0,0 +1,16 @@
+name: 'Documentation Generator'
+description: 'Generates a random yet deterministic documentation set'
+
+branding:
+ icon: 'filter'
+ color: 'red'
+
+inputs:
+ output:
+ description: 'Path to output the documentation'
+ required: false
+
+runs:
+ using: 'docker'
+ image: "docker://ghcr.io/elastic/docs-generator:edge"
+
diff --git a/actions/publish/action.yml b/actions/publish/action.yml
new file mode 100644
index 0000000..385f85b
--- /dev/null
+++ b/actions/publish/action.yml
@@ -0,0 +1,35 @@
+name: 'Documentation Publisher'
+description: 'Builds and publishes documentation to github pages'
+
+branding:
+ icon: 'filter'
+ color: 'red'
+
+outputs:
+ page_url:
+ description: "The github actions url"
+ value: ${{steps.deployment.outputs.page_url}}
+
+runs:
+ using: "composite"
+ steps:
+ - id: repo-basename
+ run: 'echo "value=`basename ${{ github.repository }}`" >> $GITHUB_OUTPUT'
+ - uses: actions/checkout@v4
+ - name: Setup Pages
+ id: pages
+ uses: actions/configure-pages@v5.0.0
+ - name: Build documentation
+ uses: elastic/docs-builder@main
+ with:
+ prefix: '${{ steps.repo-basename.outputs.value }}'
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3.0.1
+ with:
+ path: .artifacts/docs/html
+
+ - name: Deploy artifact
+ id: deployment
+ uses: actions/deploy-pages@v4.0.5
+
+
diff --git a/docs-builder.sln b/docs-builder.sln
index 50e4344..46d70b8 100644
--- a/docs-builder.sln
+++ b/docs-builder.sln
@@ -32,6 +32,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Markdown.Tests", "t
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs-generator", "src\docs-generator\docs-generator.csproj", "{61904527-9753-4379-B546-56B6A29073AC}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "actions", "actions", "{245023D2-D3CA-47B9-831D-DAB91A2FFDC7}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "generator", "generator", "{1C340CCF-9AAC-4163-A7BB-60528076E98B}"
+ ProjectSection(SolutionItems) = preProject
+ actions\generator\action.yml = actions\generator\action.yml
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "publish", "publish", "{CD2887E3-BDA9-434B-A5BF-9ED38DE20332}"
+ ProjectSection(SolutionItems) = preProject
+ actions\publish\action.yml = actions\publish\action.yml
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -72,5 +84,7 @@ Global
{01F05AD0-E0E0-401F-A7EC-905928E1E9F0} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
{B27C5107-128B-465A-B8F8-8985399E4CFB} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5}
{61904527-9753-4379-B546-56B6A29073AC} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
+ {1C340CCF-9AAC-4163-A7BB-60528076E98B} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7}
+ {CD2887E3-BDA9-434B-A5BF-9ED38DE20332} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7}
EndGlobalSection
EndGlobal
diff --git a/src/docs-generator/Cli/ArgsFilter.cs b/src/docs-generator/Cli/ArgsFilter.cs
new file mode 100644
index 0000000..896987e
--- /dev/null
+++ b/src/docs-generator/Cli/ArgsFilter.cs
@@ -0,0 +1,34 @@
+// 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
+namespace Documentation.Generator.Cli;
+
+///
+/// This exists temporarily for .NET 8.
+/// The container builds prepends `dotnet [app].dll` as arguments
+/// Fixed in .NET 9: https://github.com/dotnet/sdk-container-builds/issues/559
+///
+public class Arguments
+{
+ public required string[] Args { get; init; }
+ public required bool IsHelp { get; init; }
+
+ public static Arguments Filter(string[] args) =>
+ new Arguments { Args = Enumerate(args).ToArray(), IsHelp = args.Contains("-h") || args.Contains("--help") };
+
+ private static IEnumerable Enumerate(string[] args)
+ {
+ for (var i = 0; i < args.Length; i++)
+ {
+ switch (i)
+ {
+ case 0 when args[i] == "dotnet":
+ case 1 when args[i].EndsWith(".dll"):
+ continue;
+ default:
+ yield return args[i];
+ break;
+ }
+ }
+ }
+}
diff --git a/src/docs-generator/Cli/Commands.cs b/src/docs-generator/Cli/Commands.cs
new file mode 100644
index 0000000..409d175
--- /dev/null
+++ b/src/docs-generator/Cli/Commands.cs
@@ -0,0 +1,160 @@
+// 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 Actions.Core.Services;
+using ConsoleAppFramework;
+using Documentation.Generator.Domain;
+using Microsoft.Extensions.Logging;
+
+namespace Documentation.Generator.Cli;
+
+internal class Commands(ILoggerFactory logger, ICoreService githubActionsService)
+{
+ private readonly ILogger _logger = logger.CreateLogger();
+
+ [Command("")]
+ public async Task Generate(
+ int? seedFileSystem = null,
+ int? seedContent = null,
+ string? output = null,
+ bool? clear = null,
+ Cancel ctx = default
+ )
+ {
+ output ??= githubActionsService.GetInput("output");
+ var cleanOutputDirectory = clear ?? true;
+ var outputFolder = !string.IsNullOrWhiteSpace(output)
+ ? new DirectoryInfo(output)
+ : new DirectoryInfo(Path.Combine(Paths.Root.FullName, ".artifacts/docs/markdown"));
+ var stateFile = new FileInfo(Path.Combine(outputFolder.FullName, "generator.state"));
+
+ LoadStateFromFile(stateFile, clear, ref seedFileSystem, ref cleanOutputDirectory);
+
+ Determinism.Random = new Determinism(seedFileSystem, seedContent);
+
+ _logger.LogInformation(
+ $"Running generator with file seed: {Determinism.Random.SeedFileSystem} and content seed: {Determinism.Random.SeedContent}");
+
+ Generators.FolderName.UseSeed(Determinism.Random.SeedFileSystem);
+ Generators.File.UseSeed(Determinism.Random.SeedFileSystem);
+ Generators.Section.UseSeed(Determinism.Random.SeedContent);
+
+ Generators.FolderNames = Generators.FolderName
+ .Generate(Determinism.Random.FileSystem.Number(3, 15))
+ .SelectMany(p => Generators.CreateSubPaths(p.Folder, Determinism.Random.FileSystem.Number(0, 3), 0))
+ .Distinct()
+ .ToArray();
+
+ var folders = new List();
+ foreach (var folder in Generators.FolderNames)
+ {
+ var mdFolder = new Folder
+ {
+ Path = folder,
+ Files = Generators.File
+ .Generate(Determinism.Random.FileSystem.Number(1, 4))
+ .Select(f =>
+ {
+ f.Directory = folder;
+ return f;
+ })
+ .ToArray()
+ };
+ folders.Add(mdFolder);
+ }
+
+ var files = folders.SelectMany(f => f.Files).ToArray();
+ foreach (var folder in folders)
+ {
+ foreach (var file in folder.Files)
+ {
+ var length = Determinism.Random.Contents.Number(1, 10);
+ file.Links = Enumerable.Range(0, length)
+ .Select(i => files[Determinism.Random.Contents.Number(0, files.Length - 1)])
+ .Select(f => f.GetRandomLink())
+ .ToList();
+ file.RewriteLinksIntoSections();
+ }
+ }
+
+ _logger.LogInformation($"Writing to {outputFolder.FullName}");
+
+ if (outputFolder.Exists && cleanOutputDirectory)
+ Directory.Delete(outputFolder.FullName, true);
+
+ var updateFiles = files
+ .Where(f => cleanOutputDirectory || f.IncludeInUpdate)
+ .ToArray();
+ foreach (var file in updateFiles)
+ {
+ var directory = Path.Combine(outputFolder.FullName, file.Directory);
+ _logger.LogInformation($"Writing to {directory}");
+ Directory.CreateDirectory(directory);
+
+ WriteMarkdownFile(outputFolder, file);
+ }
+
+ var name = $"random-docset-{seedContent}-{seedFileSystem}";
+ WriteIndexMarkdownFile(name, outputFolder);
+
+ var docset = Path.Combine(outputFolder.FullName, "docset.yml");
+ File.WriteAllText(docset, $"project: {name}{Environment.NewLine}");
+ File.AppendAllText(docset, $"toc:{Environment.NewLine}");
+ foreach (var folder in folders)
+ File.AppendAllText(docset, $" - folder: {folder.Path}{Environment.NewLine}");
+
+ File.AppendAllText(docset, $" - file: index.md{Environment.NewLine}");
+ File.AppendAllText(docset, Environment.NewLine);
+
+ File.WriteAllText(stateFile.FullName, $"{Determinism.Random.SeedFileSystem}|{Determinism.Random.SeedContent}");
+
+ return await Task.FromResult(0);
+ }
+
+ private void WriteIndexMarkdownFile(string name, DirectoryInfo directoryInfo)
+ {
+ var filePath = Path.Combine(directoryInfo.FullName, "index.md");
+ File.WriteAllText(filePath,
+ $"""
+ ---
+ title: {name} Documentation Set
+ ---
+
+ """);
+ File.AppendAllText(filePath, "This docset is generated using docs-generator");
+ File.AppendAllText(filePath, Environment.NewLine);
+ }
+
+ private void WriteMarkdownFile(DirectoryInfo directoryInfo, MarkdownFile markdownFile)
+ {
+ var filePath = Path.Combine(directoryInfo.FullName, markdownFile.RelativePath);
+ File.WriteAllText(filePath,
+ $"""
+ ---
+ title: {markdownFile.Title}
+ ---
+
+ """);
+ foreach (var section in markdownFile.Sections)
+ {
+ File.AppendAllText(filePath, Environment.NewLine);
+ var header = new string('#', section.Level);
+ File.AppendAllText(filePath, $"{header} {section.Header}{Environment.NewLine}");
+ File.AppendAllText(filePath, Environment.NewLine);
+
+ File.AppendAllText(filePath, section.Paragraphs);
+ File.AppendAllText(filePath, Environment.NewLine);
+ }
+ }
+
+ void LoadStateFromFile(FileInfo fileInfo, bool? clear, ref int? seedFs, ref bool cleanOutput)
+ {
+ if (!fileInfo.Exists) return;
+ var state = File.ReadAllText(fileInfo.FullName).Split("|");
+ if (state.Length != 2) return;
+ seedFs ??= int.TryParse(state[0], out var seed) ? seed : seedFs;
+ _logger.LogInformation($"Seeding with {seedFs} from previous run {fileInfo.FullName}");
+ cleanOutput = clear ?? false;
+ }
+}
diff --git a/src/docs-generator/Program.cs b/src/docs-generator/Program.cs
index 248565a..7a73f7f 100644
--- a/src/docs-generator/Program.cs
+++ b/src/docs-generator/Program.cs
@@ -7,150 +7,39 @@
// ReSharper disable RedundantLambdaParameterType
-using System.Security.Cryptography.X509Certificates;
-using Bogus;
+using Actions.Core.Extensions;
using ConsoleAppFramework;
+using Documentation.Generator.Cli;
using Documentation.Generator.Domain;
-using Soenneker.Utils.AutoBogus;
-using Soenneker.Utils.AutoBogus.Config;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
-await ConsoleApp.RunAsync(args, async Task (
- int? seedFileSystem = null,
- int? seedContent = null,
- string? output = null,
- bool? clear = null
-) =>
-{
- var cleanOutputDirectory = clear ?? true;
- var outputFolder = !string.IsNullOrWhiteSpace(output)
- ? new DirectoryInfo(output)
- : new DirectoryInfo(Path.Combine(Paths.Root.FullName, ".artifacts/docs/markdown"));
- var stateFile = new FileInfo(Path.Combine(outputFolder.FullName, "generator.state"));
-
- LoadStateFromFile(stateFile, clear, ref seedFileSystem, ref cleanOutputDirectory);
-
- Determinism.Random = new Determinism(seedFileSystem, seedContent);
-
- Console.WriteLine($"Running generator with file seed: {Determinism.Random.SeedFileSystem} and content seed: {Determinism.Random.SeedContent}");
- Generators.FolderName.UseSeed(Determinism.Random.SeedFileSystem);
- Generators.File.UseSeed(Determinism.Random.SeedFileSystem);
- Generators.Section.UseSeed(Determinism.Random.SeedContent);
-
- Generators.FolderNames = Generators.FolderName
- .Generate(Determinism.Random.FileSystem.Number(3, 15))
- .SelectMany(p => Generators.CreateSubPaths(p.Folder, Determinism.Random.FileSystem.Number(0, 3), 0))
- .Distinct()
- .ToArray();
-
- var folders = new List();
- foreach (var folder in Generators.FolderNames)
- {
- var mdFolder = new Folder
- {
- Path = folder,
- Files = Generators.File
- .Generate(Determinism.Random.FileSystem.Number(1, 4))
- .Select(f =>
- {
- f.Directory = folder;
- return f;
- })
- .ToArray()
- };
- folders.Add(mdFolder);
- }
+var arguments = Arguments.Filter(args);
- var files = folders.SelectMany(f => f.Files).ToArray();
- foreach (var folder in folders)
- {
- foreach (var file in folder.Files)
- {
- var length = Determinism.Random.Contents.Number(1, 10);
- file.Links = Enumerable.Range(0, length)
- .Select(i => files[Determinism.Random.Contents.Number(0, files.Length - 1)])
- .Select(f => f.GetRandomLink())
- .ToList();
- file.RewriteLinksIntoSections();
- }
- }
-
- Console.WriteLine($"Writing to {outputFolder.FullName}");
-
- if (outputFolder.Exists && cleanOutputDirectory)
- Directory.Delete(outputFolder.FullName, true);
-
- var updateFiles = files
- .Where(f => cleanOutputDirectory || f.IncludeInUpdate)
- .ToArray();
- foreach (var file in updateFiles)
+var services = new ServiceCollection();
+services.AddGitHubActionsCore();
+services.AddLogging(x =>
+{
+ x.ClearProviders();
+ x.SetMinimumLevel(LogLevel.Information);
+ x.AddSimpleConsole(c =>
{
- var directory = Path.Combine(outputFolder.FullName, file.Directory);
- Console.WriteLine($"Writing to {directory}");
- Directory.CreateDirectory(directory);
-
- WriteMarkdownFile(outputFolder, file);
- }
-
- var name = $"random-docset-{seedContent}-{seedFileSystem}";
- WriteIndexMarkdownFile(name, outputFolder);
-
- var docset = Path.Combine(outputFolder.FullName, "docset.yml");
- File.WriteAllText(docset, $"project: {name}{Environment.NewLine}");
- File.AppendAllText(docset, $"toc:{Environment.NewLine}");
- foreach (var folder in folders)
- File.AppendAllText(docset, $" - folder: {folder.Path}{Environment.NewLine}");
-
- File.AppendAllText(docset, $" - file: index.md{Environment.NewLine}");
- File.AppendAllText(docset, Environment.NewLine);
-
- File.WriteAllText(stateFile.FullName, $"{Determinism.Random.SeedFileSystem}|{Determinism.Random.SeedContent}");
-
- return await Task.FromResult(0);
+ c.SingleLine = true;
+ c.IncludeScopes = true;
+ c.UseUtcTimestamp = true;
+ c.TimestampFormat = Environment.UserInteractive ? ":: " : "[yyyy-MM-ddTHH:mm:ss] ";
+ });
});
-void WriteIndexMarkdownFile(string name, DirectoryInfo directoryInfo)
-{
- var filePath = Path.Combine(directoryInfo.FullName, "index.md");
- File.WriteAllText(filePath,
- $"""
- ---
- title: {name} Documentation Set
- ---
-
- """);
- File.AppendAllText(filePath, "This docset is generated using docs-generator");
- File.AppendAllText(filePath, Environment.NewLine);
-}
-
-void WriteMarkdownFile(DirectoryInfo directoryInfo, MarkdownFile markdownFile)
-{
- var filePath = Path.Combine(directoryInfo.FullName, markdownFile.RelativePath);
- File.WriteAllText(filePath,
- $"""
- ---
- title: {markdownFile.Title}
- ---
-
- """);
- foreach (var section in markdownFile.Sections)
- {
- File.AppendAllText(filePath, Environment.NewLine);
- var header = new string('#', section.Level);
- File.AppendAllText(filePath, $"{header} {section.Header}{Environment.NewLine}");
- File.AppendAllText(filePath, Environment.NewLine);
+await using var serviceProvider = services.BuildServiceProvider();
+var logger = serviceProvider.GetRequiredService>();
+ConsoleApp.ServiceProvider = serviceProvider;
+if (!arguments.IsHelp)
+ ConsoleApp.Log = msg => logger.LogInformation(msg);
+ConsoleApp.LogError = msg => logger.LogError(msg);
- File.AppendAllText(filePath, section.Paragraphs);
- File.AppendAllText(filePath, Environment.NewLine);
- }
-}
+var app = ConsoleApp.Create();
+app.Add();
-void LoadStateFromFile(FileInfo fileInfo, bool? clear, ref int? seedFs, ref bool cleanOutput)
-{
- if (!fileInfo.Exists) return;
- var state = File.ReadAllText(fileInfo.FullName).Split("|");
- if (state.Length != 2) return;
- seedFs ??= int.TryParse(state[0], out var seed) ? seed : seedFs;
- Console.WriteLine($"Seeding with {seedFs} from previous run {fileInfo.FullName}");
- cleanOutput = clear ?? false;
-}
+await app.RunAsync(arguments.Args).ConfigureAwait(false);