diff --git a/src/docs-builder/Cli/CheckForUpdatesFilter.cs b/src/docs-builder/Cli/CheckForUpdatesFilter.cs new file mode 100644 index 0000000..aa9c8d4 --- /dev/null +++ b/src/docs-builder/Cli/CheckForUpdatesFilter.cs @@ -0,0 +1,95 @@ +// 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.Reflection; +using ConsoleAppFramework; +using Elastic.Markdown.Helpers; + +namespace Documentation.Builder.Cli; + +internal class CheckForUpdatesFilter : ConsoleAppFilter +{ + private readonly FileInfo _stateFile; + + public CheckForUpdatesFilter(ConsoleAppFilter next) : base(next) + { + // ~/Library/Application\ Support/ on osx + // XDG_DATA_HOME or home/.local/share on linux + // %LOCAL_APPLICATION_DATA% windows + var localPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var elasticPath = Path.Combine(localPath, "elastic"); + _stateFile = new FileInfo(Path.Combine(elasticPath, "docs-build-check.state")); + } + + public override async Task InvokeAsync(ConsoleAppContext context, Cancel ctx) + { + await Next.InvokeAsync(context, ctx); + var latestVersionUrl = await GetLatestVersion(ctx); + if (latestVersionUrl is null) + ConsoleApp.LogError("Unable to determine latest version"); + else + CompareWithAssemblyVersion(latestVersionUrl); + } + + private void CompareWithAssemblyVersion(Uri latestVersionUrl) + { + var versionPath = latestVersionUrl.AbsolutePath.Split('/').Last(); + if (!SemVersion.TryParse(versionPath, out var latestVersion)) + { + ConsoleApp.LogError($"Unable to parse latest version from {latestVersionUrl}"); + return; + } + + var assemblyVersion = Assembly.GetExecutingAssembly().GetCustomAttributes() + .FirstOrDefault()?.InformationalVersion; + if (SemVersion.TryParse(assemblyVersion ?? "", out var currentSemVersion)) + { + if (latestVersion <= currentSemVersion) + return; + ConsoleApp.Log(""); + ConsoleApp.Log($"A new version of docs-builder is available: {latestVersion} currently on version {currentSemVersion}"); + ConsoleApp.Log(""); + ConsoleApp.Log($" {latestVersionUrl}"); + ConsoleApp.Log(""); + ConsoleApp.Log("Read more about updating here:"); + ConsoleApp.Log(" https://elastic.github.io/docs-builder/contribute/locally.html#step-one "); + ConsoleApp.Log(""); + return; + } + + ConsoleApp.LogError($"Unable to parse current version from docs-builder binary"); + } + + private async ValueTask GetLatestVersion(CancellationToken ctx) + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) + return null; + + // only check for new versions once per hour + if (_stateFile.Exists && _stateFile.LastWriteTimeUtc >= DateTime.UtcNow.Subtract(TimeSpan.FromHours(1))) + { + var url = await File.ReadAllTextAsync(_stateFile.FullName, ctx); + if (Uri.TryCreate(url, UriKind.Absolute, out var uri)) + return uri; + } + + try + { + var httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false }); + var response = await httpClient.GetAsync("https://github.com/elastic/docs-builder/releases/latest", ctx); + var redirectUrl = response.Headers.Location; + if (redirectUrl is not null && _stateFile.Directory is not null) + { + // ensure the 'elastic' folder exists. + if (!Directory.Exists(_stateFile.Directory.FullName)) + Directory.CreateDirectory(_stateFile.Directory.FullName); + await File.WriteAllTextAsync(_stateFile.FullName, redirectUrl.ToString(), ctx); + } + return redirectUrl; + } + // ReSharper disable once RedundantEmptyFinallyBlock + // ignore on purpose + finally { } + } +} diff --git a/src/docs-builder/Cli/Commands.cs b/src/docs-builder/Cli/Commands.cs index d7b9ecc..afa70d6 100644 --- a/src/docs-builder/Cli/Commands.cs +++ b/src/docs-builder/Cli/Commands.cs @@ -25,6 +25,7 @@ internal class Commands(ILoggerFactory logger, ICoreService githubActionsService /// Port to serve the documentation. /// [Command("serve")] + [ConsoleAppFilter] public async Task Serve(string? path = null, int port = 3000, Cancel ctx = default) { var host = new DocumentationWebHost(path, port, logger, new FileSystem()); @@ -45,6 +46,7 @@ public async Task Serve(string? path = null, int port = 3000, Cancel ctx = defau [Command("generate")] [ConsoleAppFilter] [ConsoleAppFilter] + [ConsoleAppFilter] public async Task Generate( string? path = null, string? output = null, @@ -89,6 +91,7 @@ public async Task Generate( [Command("")] [ConsoleAppFilter] [ConsoleAppFilter] + [ConsoleAppFilter] public async Task GenerateDefault( string? path = null, string? output = null, diff --git a/src/docs-builder/Program.cs b/src/docs-builder/Program.cs index 8f0c050..0fed8f7 100644 --- a/src/docs-builder/Program.cs +++ b/src/docs-builder/Program.cs @@ -27,10 +27,10 @@ services.AddSingleton(); services.AddSingleton(); - await using var serviceProvider = services.BuildServiceProvider(); var logger = serviceProvider.GetRequiredService>(); ConsoleApp.ServiceProvider = serviceProvider; + var isHelp = args.Contains("-h") || args.Contains("--help"); if (!isHelp) ConsoleApp.Log = msg => logger.LogInformation(msg);