From 0fba844267d2cbbfa990e0f1e9e79cd07ca2c563 Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Mon, 23 Dec 2024 14:29:08 +0000 Subject: [PATCH] Make buildassets relative path and finish new publish logic --- .../Velopack.Deployment/GitHubRepository.cs | 4 +- .../Velopack.Deployment/GiteaRepository.cs | 4 +- .../Velopack.Deployment/_ObjectRepository.cs | 7 +- src/vpk/Velopack.Packaging/BuildAssets.cs | 36 ++++- .../Velopack.Packaging/Flow/ReleaseGroup.cs | 7 + .../Flow/VelopackFlowServiceClient.cs | 123 +++++++----------- 6 files changed, 89 insertions(+), 92 deletions(-) diff --git a/src/vpk/Velopack.Deployment/GitHubRepository.cs b/src/vpk/Velopack.Deployment/GitHubRepository.cs index 2f6d85fa..a983da03 100644 --- a/src/vpk/Velopack.Deployment/GitHubRepository.cs +++ b/src/vpk/Velopack.Deployment/GitHubRepository.cs @@ -61,7 +61,7 @@ public class GitHubRepository(ILogger logger) : SourceRepository UploadFileAsAsset(client, release, a), $"Uploading asset '{Path.GetFileName(a)}'.."); } diff --git a/src/vpk/Velopack.Deployment/GiteaRepository.cs b/src/vpk/Velopack.Deployment/GiteaRepository.cs index ad25b27a..b4006cd3 100644 --- a/src/vpk/Velopack.Deployment/GiteaRepository.cs +++ b/src/vpk/Velopack.Deployment/GiteaRepository.cs @@ -82,7 +82,7 @@ public class GiteaRepository : SourceRepository UploadFileAsAsset(apiInstance, release, repoOwner, repoName, a), $"Uploading asset '{Path.GetFileName(a)}'.."); } diff --git a/src/vpk/Velopack.Deployment/_ObjectRepository.cs b/src/vpk/Velopack.Deployment/_ObjectRepository.cs index 23d74efc..c55ff0d2 100644 --- a/src/vpk/Velopack.Deployment/_ObjectRepository.cs +++ b/src/vpk/Velopack.Deployment/_ObjectRepository.cs @@ -51,12 +51,11 @@ public abstract class ObjectRepository : DownRepository : DownRepository Files { get; set; } = new List(); + [JsonIgnore] + private string _outputDir; + + public List RelativeFileNames { get; set; } = new List(); public List GetReleaseEntries() { - return Files.Where(x => x.EndsWith(".nupkg")) + return GetFilePaths().Where(x => x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) .Select(f => VelopackAsset.FromNupkg(f)) .ToList(); } + public List GetNonReleaseAssetPaths() + { + return GetFilePaths().Where(x => !x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + + public List GetFilePaths() + { + return RelativeFileNames.Select(f => Path.GetFullPath(Path.Combine(_outputDir, f))).ToList(); + } + public static void Write(string outputDir, string channel, IEnumerable files) { var assets = new BuildAssets { - Files = files.OrderBy(f => f).ToList(), + RelativeFileNames = files.OrderBy(f => f) + .Select(f => PathUtil.MakePathRelativeTo(outputDir, f)) + .ToList(), }; var path = Path.Combine(outputDir, $"assets.{channel}.json"); var json = SimpleJson.SerializeObject(assets); @@ -27,9 +45,13 @@ public class BuildAssets { var path = Path.Combine(outputDir, $"assets.{channel}.json"); if (!File.Exists(path)) { - throw new UserInfoException($"Could not find assets file for channel '{channel}' (looking for '{Path.GetFileName(path)}' in directory '{outputDir}'). " + + throw new UserInfoException( + $"Could not find assets file for channel '{channel}' (looking for '{Path.GetFileName(path)}' in directory '{outputDir}'). " + $"If you've just created a Velopack release, verify you're calling this command with the same '--channel' as you did with 'pack'."); } - return SimpleJson.DeserializeObject(File.ReadAllText(path)); + + var assets = SimpleJson.DeserializeObject(File.ReadAllText(path)); + assets._outputDir = outputDir; + return assets; } -} +} \ No newline at end of file diff --git a/src/vpk/Velopack.Packaging/Flow/ReleaseGroup.cs b/src/vpk/Velopack.Packaging/Flow/ReleaseGroup.cs index dabe60f0..d5d3200b 100644 --- a/src/vpk/Velopack.Packaging/Flow/ReleaseGroup.cs +++ b/src/vpk/Velopack.Packaging/Flow/ReleaseGroup.cs @@ -6,8 +6,15 @@ using System.Net.Http; #nullable enable namespace Velopack.Packaging.Flow; +internal sealed class FileUpload +{ + public string? Status { get; set; } + public string? Md5Hash { get; set; } +} + internal sealed class ReleaseGroup { public Guid Id { get; set; } public string? Version { get; set; } + public List? FileUploads { get; set; } } \ No newline at end of file diff --git a/src/vpk/Velopack.Packaging/Flow/VelopackFlowServiceClient.cs b/src/vpk/Velopack.Packaging/Flow/VelopackFlowServiceClient.cs index d25a68d6..9143409b 100644 --- a/src/vpk/Velopack.Packaging/Flow/VelopackFlowServiceClient.cs +++ b/src/vpk/Velopack.Packaging/Flow/VelopackFlowServiceClient.cs @@ -11,7 +11,6 @@ using Velopack.Util; #if NET6_0_OR_GREATER using System.Net.Http.Json; - #else using System.Net.Http; #endif @@ -43,7 +42,7 @@ public class VelopackFlowServiceClient( { private static readonly string[] Scopes = ["openid", "offline_access"]; - private AuthenticationHeaderValue Authorization = null; + private AuthenticationHeaderValue? Authorization = null; private AuthConfiguration? AuthConfiguration { get; set; } @@ -173,47 +172,28 @@ public class VelopackFlowServiceClient( AssertAuthenticated(); channel ??= ReleaseEntryHelper.GetDefaultChannel(os); - ReleaseEntryHelper helper = new(releaseDirectory, channel, Logger, os); - var latestAssets = helper.GetLatestAssets().Where(f => f.Type != VelopackAssetType.Delta).ToList(); + BuildAssets assets = BuildAssets.Read(releaseDirectory, channel); + var fullAsset = assets.GetReleaseEntries().SingleOrDefault(a => a.Type == VelopackAssetType.Full); - List installers = []; - - List files = latestAssets.Select(x => x.FileName).ToList(); - string? packageId = null; - SemanticVersion? version = null; - if (latestAssets.Count > 0) { - packageId = latestAssets[0].PackageId; - version = latestAssets[0].Version; - - if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) { - var setupName = ReleaseEntryHelper.GetSuggestedSetupName(packageId, channel, os); - if (File.Exists(Path.Combine(releaseDirectory, setupName))) { - installers.Add(setupName); - } - } - - var portableName = ReleaseEntryHelper.GetSuggestedPortableName(packageId, channel, os); - if (File.Exists(Path.Combine(releaseDirectory, portableName))) { - installers.Add(portableName); - } - } - - if (packageId is null) { - Logger.LogError("No package ID found in release directory {ReleaseDirectory}", releaseDirectory); + if (fullAsset is null) { + Logger.LogError("No full asset found in release directory {ReleaseDirectory} (or it's missing from assets file)", releaseDirectory); return; } - if (version is null) { - Logger.LogError("No version found in release directory {ReleaseDirectory}", releaseDirectory); - return; - } + var fullAssetPath = Path.Combine(releaseDirectory, fullAsset.FileName); + var packageId = fullAsset.PackageId; + var version = fullAsset.Version; - Logger.LogInformation("Uploading {AssetCount} assets to Velopack Flow ({ServiceUrl})", latestAssets.Count + installers.Count, serviceUrl); + var filesToUpload = assets.GetNonReleaseAssetPaths().Select(p => (p, FileType.Installer)) + .Concat([(fullAssetPath, FileType.Release)]) + .ToArray(); + + Logger.LogInformation("Beginning upload to Velopack Flow (serviceUrl={ServiceUrl})", serviceUrl); await Console.ExecuteProgressAsync( async (progress) => { ReleaseGroup releaseGroup = await progress.RunTask( - "Preparing to upload assets", + $"Creating release {version}", async (report) => { report(-1); var result = await CreateReleaseGroupAsync(packageId, version, channel, serviceUrl, cancellationToken); @@ -222,19 +202,19 @@ public class VelopackFlowServiceClient( }); var backgroundTasks = new List(); - foreach (var assetFileName in files) { + foreach (var assetTuple in filesToUpload) { backgroundTasks.Add( progress.RunTask( - $"Uploading {Path.GetFileName(assetFileName)}", + $"Uploading {Path.GetFileName(assetTuple.Item1)}", async (report) => { await UploadReleaseAssetAsync( - releaseDirectory, - assetFileName, + assetTuple.Item1, serviceUrl, releaseGroup.Id, - FileType.Release, + assetTuple.Item2, report, cancellationToken); + report(100); }) ); } @@ -249,7 +229,7 @@ public class VelopackFlowServiceClient( return new ZipPackage(prevVersion); }); - if (prevZip.Version >= version) { + if (prevZip.Version! >= version) { throw new InvalidOperationException( $"Latest version in channel {channel} is greater than or equal to local (remote={prevZip.Version}, local={version})"); } @@ -262,8 +242,9 @@ public class VelopackFlowServiceClient( (report) => { var delta = new DeltaPackageBuilder(Logger); var pOld = new ReleasePackage(prevVersion); - var pNew = new ReleasePackage(releaseDirectory); + var pNew = new ReleasePackage(fullAssetPath); delta.CreateDeltaPackage(pOld, pNew, deltaPath, DeltaMode.BestSpeed, report); + report(100); return Task.CompletedTask; }); @@ -272,13 +253,13 @@ public class VelopackFlowServiceClient( $"Uploading {Path.GetFileName(deltaPath)}", async (report) => { await UploadReleaseAssetAsync( - releaseDirectory, deltaPath, serviceUrl, releaseGroup.Id, FileType.Release, report, cancellationToken); + report(100); }) ); @@ -295,40 +276,21 @@ public class VelopackFlowServiceClient( if (!noWaitForLive) { await progress.RunTask( - "Waiting for release to be live", + "Waiting for release to go live", async (report) => { report(-1); await WaitUntilReleaseGroupLive(publishedGroup.Id, serviceUrl, cancellationToken); + report(100); }); } }); - - // ReleaseGroup releaseGroup = await CreateReleaseGroupAsync(packageId, version, channel, serviceUrl, cancellationToken); - // - // foreach (var assetFileName in files) { - // await UploadReleaseAssetAsync(releaseDirectory, assetFileName, serviceUrl, releaseGroup.Id, FileType.Release, cancellationToken) - // .ConfigureAwait(false); - // - // Logger.LogInformation("Uploaded {FileName} to Velopack Flow", assetFileName); - // } - // - // foreach (var installerFile in installers) { - // await UploadReleaseAssetAsync(releaseDirectory, installerFile, serviceUrl, releaseGroup.Id, FileType.Installer, cancellationToken) - // .ConfigureAwait(false); - // - // Logger.LogInformation("Uploaded {FileName} installer to Velopack Flow", installerFile); - // } - // - // await PublishReleaseGroupAsync(releaseGroup, serviceUrl, cancellationToken); - - // TODO wait for published } private async Task DownloadLatestRelease(string packageId, string channel, string? velopackBaseUrl, string localPath, Action progress, CancellationToken cancellationToken) { var client = GetHttpClient(progress); - var endpoint = GetEndpoint($"v1/download/{packageId}/{channel}", velopackBaseUrl); + var endpoint = GetEndpoint($"v1/download/{packageId}/{channel}", velopackBaseUrl) + $"?assetType=Full"; using var fs = File.Create(localPath); @@ -342,19 +304,29 @@ public class VelopackFlowServiceClient( #endif } - private async Task WaitUntilReleaseGroupLive(Guid releaseGroupId, string? velopackBaseUrl, CancellationToken cancellationToken) { var client = GetHttpClient(); var endpoint = GetEndpoint($"v1/releaseGroups/{releaseGroupId}", velopackBaseUrl); - for (int i = 0; i < 120; i++) { + for (int i = 0; i < 300; i++) { var response = await client.GetAsync(endpoint, cancellationToken); response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(cancellationToken); - Console.WriteLine(content); + var releaseGroup = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); + if (releaseGroup?.FileUploads == null) { + Logger.LogWarning("Failed to get release group status, it may not be live yet."); + return; + } + + if (releaseGroup.FileUploads.All(f => f.Status?.ToLower().Equals("processed") == true)) { + Logger.LogInformation("Release is now live."); + return; + } + await Task.Delay(1000, cancellationToken); } + + Logger.LogWarning("Release did not go live within 5 minutes (timeout)."); } private async Task CreateReleaseGroupAsync( @@ -382,20 +354,17 @@ public class VelopackFlowServiceClient( ?? throw new InvalidOperationException($"Failed to create release group with version {version.ToNormalizedString()}"); } - private async Task UploadReleaseAssetAsync(string releaseDirectory, string fileName, string? velopackBaseUrl, Guid releaseGroupId, + private async Task UploadReleaseAssetAsync(string filePath, string? velopackBaseUrl, Guid releaseGroupId, FileType fileType, Action progress, CancellationToken cancellationToken) { - using var formData = new MultipartFormDataContent { - { new StringContent(releaseGroupId.ToString()), "ReleaseGroupId" }, - { new StringContent(fileType.ToString()), "FileType" } - }; + using var formData = new MultipartFormDataContent(); + formData.Add(new StringContent(releaseGroupId.ToString()), "ReleaseGroupId"); + formData.Add(new StringContent(fileType.ToString()), "FileType"); - var latestPath = Path.Combine(releaseDirectory, fileName); - - using var fileStream = File.OpenRead(latestPath); + using var fileStream = File.OpenRead(filePath); using var fileContent = new StreamContent(fileStream); - formData.Add(fileContent, "File", fileName); + formData.Add(fileContent, "File", Path.GetFileName(filePath)); var endpoint = GetEndpoint("v1/releases/upload", velopackBaseUrl);