in src/tooling/docs-builder/Cli/Commands.cs [20:230]
internal sealed class Commands(ILoggerFactory logger, ICoreService githubActionsService)
{
[SuppressMessage("Usage", "CA2254:Template should be a static expression")]
private void AssignOutputLogger()
{
var log = logger.CreateLogger<Program>();
ConsoleApp.Log = msg => log.LogInformation(msg);
ConsoleApp.LogError = msg => log.LogError(msg);
}
/// <summary>
/// Continuously serve a documentation folder at http://localhost:3000.
/// File systems changes will be reflected without having to restart the server.
/// </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")]
[ConsoleAppFilter<CheckForUpdatesFilter>]
public async Task Serve(string? path = null, int port = 3000, Cancel ctx = default)
{
AssignOutputLogger();
var host = new DocumentationWebHost(path, port, logger, new FileSystem());
await host.RunAsync(ctx);
await host.StopAsync(ctx);
}
/// <summary>
/// Serve html files directly
/// </summary>
/// <param name="port">Port to serve the documentation.</param>
/// <param name="ctx"></param>
[Command("serve-static")]
[ConsoleAppFilter<CheckForUpdatesFilter>]
public async Task ServeStatic(int port = 4000, Cancel ctx = default)
{
AssignOutputLogger();
var host = new StaticWebHost(port);
await host.RunAsync(ctx);
await host.StopAsync(ctx);
}
/// <summary>
/// Converts a source markdown folder or file to an output folder
/// <para>global options:</para>
/// --log-level level
/// </summary>
/// <param name="path"> -p, Defaults to the`{pwd}/docs` folder</param>
/// <param name="output"> -o, Defaults to `.artifacts/html` </param>
/// <param name="pathPrefix"> Specifies the path prefix for urls </param>
/// <param name="force"> Force a full rebuild of the destination folder</param>
/// <param name="strict"> Treat warnings as errors and fail the build on warnings</param>
/// <param name="allowIndexing"> Allow indexing and following of html files</param>
/// <param name="metadataOnly"> Only emit documentation metadata to output</param>
/// <param name="canonicalBaseUrl"> The base URL for the canonical url tag</param>
/// <param name="ctx"></param>
[Command("generate")]
[ConsoleAppFilter<StopwatchFilter>]
[ConsoleAppFilter<CatchExceptionFilter>]
[ConsoleAppFilter<CheckForUpdatesFilter>]
public async Task<int> Generate(
string? path = null,
string? output = null,
string? pathPrefix = null,
bool? force = null,
bool? strict = null,
bool? allowIndexing = null,
bool? metadataOnly = null,
string? canonicalBaseUrl = null,
Cancel ctx = default
)
{
AssignOutputLogger();
pathPrefix ??= githubActionsService.GetInput("prefix");
var fileSystem = new FileSystem();
await using var collector = new ConsoleDiagnosticsCollector(logger, githubActionsService).StartAsync(ctx);
var runningOnCi = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"));
BuildContext context;
Uri? canonicalBaseUri;
if (runningOnCi)
{
ConsoleApp.Log($"Build running on CI, forcing a full rebuild of the destination folder");
force = true;
}
if (canonicalBaseUrl is null)
canonicalBaseUri = new Uri("https://docs-v3-preview.elastic.dev");
else if (!Uri.TryCreate(canonicalBaseUrl, UriKind.Absolute, out canonicalBaseUri))
throw new ArgumentException($"The canonical base url '{canonicalBaseUrl}' is not a valid absolute uri");
try
{
context = new BuildContext(collector, fileSystem, fileSystem, path, output)
{
UrlPathPrefix = pathPrefix,
Force = force ?? false,
AllowIndexing = allowIndexing ?? false,
CanonicalBaseUrl = canonicalBaseUri
};
}
// On CI, we are running on merge commit which may have changes against an older
// docs folder (this can happen on out of date PR's).
// At some point in the future we can remove this try catch
catch (Exception e) when (runningOnCi && e.Message.StartsWith("Can not locate docset.yml file in"))
{
var outputDirectory = !string.IsNullOrWhiteSpace(output)
? fileSystem.DirectoryInfo.New(output)
: fileSystem.DirectoryInfo.New(Path.Combine(Paths.WorkingDirectoryRoot.FullName, ".artifacts/docs/html"));
// we temporarily do not error when pointed to a non documentation folder.
_ = fileSystem.Directory.CreateDirectory(outputDirectory.FullName);
ConsoleApp.Log($"Skipping build as we are running on a merge commit and the docs folder is out of date and has no docset.yml. {e.Message}");
await githubActionsService.SetOutputAsync("skip", "true");
return 0;
}
if (runningOnCi)
await githubActionsService.SetOutputAsync("skip", "false");
// always delete output folder on CI
var set = new DocumentationSet(context, logger);
if (runningOnCi)
set.ClearOutputDirectory();
if (bool.TryParse(githubActionsService.GetInput("metadata-only"), out var metaValue) && metaValue)
metadataOnly ??= metaValue;
var exporter = metadataOnly.HasValue && metadataOnly.Value ? new NoopDocumentationFileExporter() : null;
var generator = new DocumentationGenerator(set, logger, null, null, exporter);
_ = await generator.GenerateAll(ctx);
if (runningOnCi)
await githubActionsService.SetOutputAsync("landing-page-path", set.MarkdownFiles.First().Value.Url);
await collector.StopAsync(ctx);
if (bool.TryParse(githubActionsService.GetInput("strict"), out var strictValue) && strictValue)
strict ??= strictValue;
if (strict ?? false)
return context.Collector.Errors + context.Collector.Warnings;
return context.Collector.Errors;
}
/// <summary>
/// Converts a source markdown folder or file to an output folder
/// </summary>
/// <param name="path"> -p, Defaults to the`{pwd}/docs` folder</param>
/// <param name="output"> -o, Defaults to `.artifacts/html` </param>
/// <param name="pathPrefix"> Specifies the path prefix for urls </param>
/// <param name="force"> Force a full rebuild of the destination folder</param>
/// <param name="strict"> Treat warnings as errors and fail the build on warnings</param>
/// <param name="allowIndexing"> Allow indexing and following of html files</param>
/// <param name="metadataOnly"> Only emit documentation metadata to output</param>
/// <param name="canonicalBaseUrl"> The base URL for the canonical url tag</param>
/// <param name="ctx"></param>
[Command("")]
[ConsoleAppFilter<StopwatchFilter>]
[ConsoleAppFilter<CatchExceptionFilter>]
[ConsoleAppFilter<CheckForUpdatesFilter>]
public async Task<int> GenerateDefault(
string? path = null,
string? output = null,
string? pathPrefix = null,
bool? force = null,
bool? strict = null,
bool? allowIndexing = null,
bool? metadataOnly = null,
string? canonicalBaseUrl = null,
Cancel ctx = default
) =>
await Generate(path, output, pathPrefix, force, strict, allowIndexing, metadataOnly, canonicalBaseUrl, ctx);
/// <summary>
/// Move a file from one location to another and update all links in the documentation
/// </summary>
/// <param name="source">The source file or folder path to move from</param>
/// <param name="target">The target file or folder path to move to</param>
/// <param name="path"> -p, Defaults to the`{pwd}` folder</param>
/// <param name="dryRun">Dry run the move operation</param>
/// <param name="ctx"></param>
[Command("mv")]
[ConsoleAppFilter<StopwatchFilter>]
[ConsoleAppFilter<CatchExceptionFilter>]
[ConsoleAppFilter<CheckForUpdatesFilter>]
public async Task<int> Move(
[Argument] string source,
[Argument] string target,
bool? dryRun = null,
string? path = null,
Cancel ctx = default
)
{
AssignOutputLogger();
var fileSystem = new FileSystem();
await using var collector = new ConsoleDiagnosticsCollector(logger, null).StartAsync(ctx);
var context = new BuildContext(collector, fileSystem, fileSystem, path, null);
var set = new DocumentationSet(context, logger);
var moveCommand = new Move(fileSystem, fileSystem, set, logger);
var result = await moveCommand.Execute(source, target, dryRun ?? false, ctx);
await collector.StopAsync(ctx);
return result;
}
}