internal sealed class CheckForUpdatesFilter()

in src/tooling/docs-builder/Cli/CheckForUpdatesFilter.cs [12:87]


internal sealed class CheckForUpdatesFilter(ConsoleAppFilter next) : ConsoleAppFilter(next)
{
	private readonly FileInfo _stateFile = new(Path.Combine(Paths.ApplicationData.FullName, "docs-build-check.state"));

	public override async Task InvokeAsync(ConsoleAppContext context, Cancel ctx)
	{
		await Next.InvokeAsync(context, ctx);
		if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
			return;

		var latestVersionUrl = await GetLatestVersion(ctx);
		if (latestVersionUrl is null)
			ConsoleApp.LogError("Unable to determine latest version");
		else
			CompareWithAssemblyVersion(latestVersionUrl);
	}

	private static 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<AssemblyInformationalVersionAttribute>()
			.FirstOrDefault()?.InformationalVersion;
		if (SemVersion.TryParse(assemblyVersion ?? "", out var currentSemVersion))
		{
			var currentVersion = new SemVersion(currentSemVersion.Major, currentSemVersion.Minor, currentSemVersion.Patch);
			if (latestVersion <= currentVersion)
				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#step-one	");
			ConsoleApp.Log("");
			return;
		}

		ConsoleApp.LogError($"Unable to parse current version from docs-builder binary");
	}

	private async ValueTask<Uri?> GetLatestVersion(Cancel ctx)
	{
		// 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 { }
	}
}