update buildassets and fix publish after api update

This commit is contained in:
Caelan Sayler
2024-12-28 15:59:43 +00:00
committed by Caelan
parent 066d43c50e
commit 863e9a16ba
8 changed files with 92 additions and 64 deletions

View File

@@ -15,7 +15,11 @@ namespace Velopack
/// <summary> A full update package. </summary> /// <summary> A full update package. </summary>
Full = 1, Full = 1,
/// <summary> A delta update package. </summary> /// <summary> A delta update package. </summary>
Delta, Delta = 2,
/// <summary> A portable application zip archive. </summary>
Portable = 3,
/// <summary> An application installer archive. </summary>
Installer = 4,
} }
/// <summary> /// <summary>

View File

@@ -1,47 +1,60 @@
using Newtonsoft.Json; using System.Collections.Concurrent;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Core; namespace Velopack.Core;
public class BuildAssets public class BuildAssets(string outputDir, string channel)
{ {
[JsonIgnore] public int Count => Assets.Count;
private string? _outputDir;
public List<string>? RelativeFileNames { get; set; } = []; class Asset
{
public string RelativeFileName { get; set; } = string.Empty;
public VelopackAssetType Type { get; set; }
}
private ConcurrentBag<Asset> Assets { get; set; } = [];
public List<VelopackAsset> GetReleaseEntries() public List<VelopackAsset> GetReleaseEntries()
{ {
return GetFilePaths() return GetAssets()
.Where(x => x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) .Where(x => x.Type is VelopackAssetType.Delta or VelopackAssetType.Full)
.Select(VelopackAsset.FromNupkg) .Select(x => VelopackAsset.FromNupkg(x.Path))
.ToList(); .ToList();
} }
public List<string> GetNonReleaseAssetPaths() public IEnumerable<(string Path, VelopackAssetType Type)> GetAssets()
{ {
return GetFilePaths() return Assets.Select(asset => (Path.Combine(outputDir, asset.RelativeFileName), asset.Type));
.Where(x => !x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase))
.ToList();
} }
public IEnumerable<string> GetFilePaths() public IEnumerable<string> GetFilePaths()
{ {
if (RelativeFileNames is { } relativeFileNames && _outputDir is { } outputDir) { return Assets.Select(x => Path.Combine(outputDir, x.RelativeFileName));
return relativeFileNames.Select(f => Path.GetFullPath(Path.Combine(outputDir, f))).ToList();
}
return [];
} }
public static void Write(string outputDir, string channel, IEnumerable<string> files) public string MakeAssetPath(string relativePath, VelopackAssetType type)
{
// var relativeFileName = PathUtil.MakePathRelativeTo(outputDir, fullPath);
Assets.Add(new Asset { RelativeFileName = relativePath, Type = type });
return Path.Combine(outputDir, relativePath);
}
public void MoveBagTo(string newOutputDir)
{
foreach (var asset in Assets) {
var from = Path.Combine(outputDir, asset.RelativeFileName);
var to = Path.Combine(newOutputDir, asset.RelativeFileName);
IoUtil.MoveFile(from, to, true);
}
outputDir = newOutputDir;
}
public void Write()
{ {
var assets = new BuildAssets {
RelativeFileNames = files.OrderBy(f => f)
.Select(f => PathUtil.MakePathRelativeTo(outputDir, f))
.ToList(),
};
var path = Path.Combine(outputDir, $"assets.{channel}.json"); var path = Path.Combine(outputDir, $"assets.{channel}.json");
var json = SimpleJson.SerializeObject(assets); var json = SimpleJson.SerializeObject(Assets);
File.WriteAllText(path, json); File.WriteAllText(path, json);
} }
@@ -54,8 +67,9 @@ public class BuildAssets
$"If you've just created a Velopack release, verify you're calling this command with the same '--channel' as you did with 'pack'."); $"If you've just created a Velopack release, verify you're calling this command with the same '--channel' as you did with 'pack'.");
} }
var assets = SimpleJson.DeserializeObject<BuildAssets>(File.ReadAllText(path)) ?? new(); var me = new BuildAssets(outputDir, channel) {
assets._outputDir = outputDir; Assets = SimpleJson.DeserializeObject<ConcurrentBag<Asset>>(File.ReadAllText(path)) ?? []
return assets; };
return me;
} }
} }

View File

@@ -62,7 +62,7 @@ public class GitHubRepository(ILogger logger) : SourceRepository<GitHubDownloadO
var semVer = options.TagName ?? latest.Version.ToString(); var semVer = options.TagName ?? latest.Version.ToString();
var releaseName = string.IsNullOrWhiteSpace(options.ReleaseName) ? semVer.ToString() : options.ReleaseName; var releaseName = string.IsNullOrWhiteSpace(options.ReleaseName) ? semVer.ToString() : options.ReleaseName;
Log.Info($"Preparing to upload {build.RelativeFileNames.Count} asset(s) to GitHub"); Log.Info($"Preparing to upload {build.Count} asset(s) to GitHub");
var client = new GitHubClient(new ProductHeaderValue("Velopack")) { var client = new GitHubClient(new ProductHeaderValue("Velopack")) {
Credentials = new Credentials(options.Token) Credentials = new Credentials(options.Token)

View File

@@ -82,7 +82,7 @@ public class GiteaRepository : SourceRepository<GiteaDownloadOptions, GiteaSourc
config.BasePath = baseUri + "/api/v1"; config.BasePath = baseUri + "/api/v1";
config.Timeout = (int)TimeSpan.FromMinutes(options.Timeout).TotalMilliseconds; config.Timeout = (int)TimeSpan.FromMinutes(options.Timeout).TotalMilliseconds;
Log.Info($"Preparing to upload {build.RelativeFileNames.Count} asset(s) to Gitea"); Log.Info($"Preparing to upload {build.Count} asset(s) to Gitea");
// Set token if provided // Set token if provided
if (!string.IsNullOrWhiteSpace(options.Token)) { if (!string.IsNullOrWhiteSpace(options.Token)) {

View File

@@ -53,7 +53,7 @@ public abstract class ObjectRepository<TDown, TUp, TClient> : DownRepository<TDo
var build = BuildAssets.Read(options.ReleaseDir.FullName, options.Channel); var build = BuildAssets.Read(options.ReleaseDir.FullName, options.Channel);
var client = CreateClient(options); var client = CreateClient(options);
Log.Info($"Preparing to upload {build.RelativeFileNames.Count} local asset(s)."); Log.Info($"Preparing to upload {build.Count} local asset(s).");
var remoteReleases = await GetReleasesAsync(options); var remoteReleases = await GetReleasesAsync(options);
Log.Info($"There are {remoteReleases.Assets.Length} asset(s) in remote releases file."); Log.Info($"There are {remoteReleases.Assets.Length} asset(s) in remote releases file.");

View File

@@ -20,7 +20,7 @@ public class ApiErrorResult
public UserInfoException? ToUserInfoException() public UserInfoException? ToUserInfoException()
{ {
if (!String.IsNullOrWhiteSpace(Detail)) { if (!String.IsNullOrWhiteSpace(Detail)) {
return new UserInfoException(Detail); return new UserInfoException(Detail!);
} }
return null; return null;
@@ -37,4 +37,20 @@ public static class FlowApiExtensions
return null; return null;
} }
public static FileType ToFileType(this VelopackAssetType type)
{
switch (type) {
case VelopackAssetType.Full:
return FileType.Full;
case VelopackAssetType.Delta:
return FileType.Delta;
case VelopackAssetType.Portable:
return FileType.Portable;
case VelopackAssetType.Installer:
return FileType.Setup;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
} }

View File

@@ -130,7 +130,7 @@ public class VelopackFlowServiceClient(
channel ??= DefaultName.GetDefaultChannel(os); channel ??= DefaultName.GetDefaultChannel(os);
BuildAssets assets = BuildAssets.Read(releaseDirectory, channel); BuildAssets assets = BuildAssets.Read(releaseDirectory, channel);
var fullAsset = assets.GetReleaseEntries().SingleOrDefault(a => a.Type == Velopack.VelopackAssetType.Full); var fullAsset = assets.GetReleaseEntries().SingleOrDefault(a => a.Type == VelopackAssetType.Full);
if (fullAsset is null) { if (fullAsset is null) {
Logger.LogError("No full asset found in release directory {ReleaseDirectory} (or it's missing from assets file)", releaseDirectory); Logger.LogError("No full asset found in release directory {ReleaseDirectory} (or it's missing from assets file)", releaseDirectory);
@@ -141,10 +141,9 @@ public class VelopackFlowServiceClient(
var packageId = fullAsset.PackageId; var packageId = fullAsset.PackageId;
var version = fullAsset.Version; var version = fullAsset.Version;
var filesToUpload = assets.GetNonReleaseAssetPaths() var filesToUpload = assets.GetAssets()
.Select(p => (p, FileType.Installer)) .Where(p => p.Type is not VelopackAssetType.Delta)
.Concat([(fullAssetPath, FileType.Release)]) .Select(p => (p.Path, p.Type.ToFileType()))
.Where(kvp => !kvp.Item1.Contains("-Portable.zip"))
.ToArray(); .ToArray();
Logger.LogInformation("Beginning upload to Velopack Flow"); Logger.LogInformation("Beginning upload to Velopack Flow");
@@ -161,6 +160,7 @@ public class VelopackFlowServiceClient(
await CreateChannelIfNotExists(client, packageId, channel, cancellationToken); await CreateChannelIfNotExists(client, packageId, channel, cancellationToken);
report(50); report(50);
var result = await CreateReleaseGroupAsync(client, packageId, version, channel, cancellationToken); var result = await CreateReleaseGroupAsync(client, packageId, version, channel, cancellationToken);
Logger.LogInformation("Created release {Version} ({ReleaseGroupId})", version, result.Id);
report(100); report(100);
return result; return result;
}); });
@@ -218,7 +218,7 @@ public class VelopackFlowServiceClient(
await UploadReleaseAssetAsync( await UploadReleaseAssetAsync(
deltaPath, deltaPath,
releaseGroup.Id, releaseGroup.Id,
FileType.Release, FileType.Delta,
report, report,
cancellationToken); cancellationToken);
report(100); report(100);
@@ -283,14 +283,20 @@ public class VelopackFlowServiceClient(
{ {
for (int i = 0; i < 300; i++) { for (int i = 0; i < 300; i++) {
var releaseGroup = await client.GetReleaseGroupAsync(releaseGroupId, cancellationToken); var releaseGroup = await client.GetReleaseGroupAsync(releaseGroupId, cancellationToken);
if (releaseGroup?.FileUploads == null) {
Logger.LogWarning("Failed to get release group status, it may not be live yet."); if (releaseGroup.ProcessingState is ReleaseGroupProcessingState.Completed) {
Logger.LogInformation("Release is now live.");
return; return;
} }
if (releaseGroup.FileUploads.All(f => f.Status?.ToLowerInvariant().Equals("processed") == true)) { if (releaseGroup.ProcessingState is ReleaseGroupProcessingState.Failed) {
Logger.LogInformation("Release is now live."); foreach (var file in releaseGroup.FileUploads) {
return; if (file.State == FileUploadState.Failed) {
Logger.LogError("File {FileName} failed to upload: {Error}", file.FileName, file.StateMessage);
}
}
throw new UserInfoException("There were one or more errors publishing this release.");
} }
await Task.Delay(1000, cancellationToken); await Task.Delay(1000, cancellationToken);
@@ -325,14 +331,14 @@ public class VelopackFlowServiceClient(
{ {
var client = GetFlowApi(progress); var client = GetFlowApi(progress);
using var stream = File.OpenRead(filePath); using var stream = File.OpenRead(filePath);
var file = new FileParameter(stream); var file = new FileParameter(stream, Path.GetFileName(filePath));
await client.UploadReleaseAsync(releaseGroupId, fileType, file, cancellationToken); await client.UploadReleaseAsync(releaseGroupId, fileType, file, cancellationToken);
} }
private static async Task<ReleaseGroup> PublishReleaseGroupAsync(FlowApi client, Guid releaseGroupId, CancellationToken cancellationToken) private static async Task<ReleaseGroup> PublishReleaseGroupAsync(FlowApi client, Guid releaseGroupId, CancellationToken cancellationToken)
{ {
UpdateReleaseGroupRequest request = new() { UpdateReleaseGroupRequest request = new() {
State = ReleaseGroupState.Published State = ReleaseGroupPublishState.Published,
}; };
return await client.UpdateReleaseGroupAsync(releaseGroupId, request, cancellationToken); return await client.UpdateReleaseGroupAsync(releaseGroupId, request, cancellationToken);

View File

@@ -105,17 +105,7 @@ public abstract class PackageBuilder<T> : ICommand<T>
options.EntryExecutableName = Path.GetFileName(mainExePath); options.EntryExecutableName = Path.GetFileName(mainExePath);
Options = options; Options = options;
ConcurrentBag<(string from, string to)> filesToCopy = new(); var assetCache = new BuildAssets(pkgTempDir, channel);
string getIncompletePath(string fileName)
{
var incomplete = Path.Combine(pkgTempDir, fileName);
var final = Path.Combine(releaseDir.FullName, fileName);
try { File.Delete(incomplete); } catch { }
filesToCopy.Add((incomplete, final));
return incomplete;
}
await Console.ExecuteProgressAsync( await Console.ExecuteProgressAsync(
async (ctx) => { async (ctx) => {
@@ -141,7 +131,7 @@ public abstract class PackageBuilder<T> : ICommand<T>
"Building portable package", "Building portable package",
async (progress) => { async (progress) => {
var suggestedName = DefaultName.GetSuggestedPortableName(packId, channel, TargetOs); var suggestedName = DefaultName.GetSuggestedPortableName(packId, channel, TargetOs);
var path = getIncompletePath(suggestedName); var path = assetCache.MakeAssetPath(suggestedName, VelopackAssetType.Portable);
await CreatePortablePackage(progress, packDirectory, path); await CreatePortablePackage(progress, packDirectory, path);
}); });
} }
@@ -154,7 +144,7 @@ public abstract class PackageBuilder<T> : ICommand<T>
$"Building release {packVersion}", $"Building release {packVersion}",
async (progress) => { async (progress) => {
var suggestedName = DefaultName.GetSuggestedReleaseName(packId, packVersion, channel, false, TargetOs); var suggestedName = DefaultName.GetSuggestedReleaseName(packId, packVersion, channel, false, TargetOs);
releasePath = getIncompletePath(suggestedName); releasePath = assetCache.MakeAssetPath(suggestedName, VelopackAssetType.Full);
await CreateReleasePackage(progress, packDirectory, releasePath); await CreateReleasePackage(progress, packDirectory, releasePath);
}); });
@@ -164,7 +154,7 @@ public abstract class PackageBuilder<T> : ICommand<T>
"Building setup package", "Building setup package",
async (progress) => { async (progress) => {
var suggestedName = DefaultName.GetSuggestedSetupName(packId, channel, TargetOs); var suggestedName = DefaultName.GetSuggestedSetupName(packId, channel, TargetOs);
var path = getIncompletePath(suggestedName); var path = assetCache.MakeAssetPath(suggestedName, VelopackAssetType.Installer);
await CreateSetupPackage(progress, releasePath, packDirectory, path); await CreateSetupPackage(progress, releasePath, packDirectory, path);
}); });
} }
@@ -174,11 +164,12 @@ public abstract class PackageBuilder<T> : ICommand<T>
$"Building delta {prev.Version} -> {packVersion}", $"Building delta {prev.Version} -> {packVersion}",
async (progress) => { async (progress) => {
var suggestedName = DefaultName.GetSuggestedReleaseName(packId, packVersion, channel, true, TargetOs); var suggestedName = DefaultName.GetSuggestedReleaseName(packId, packVersion, channel, true, TargetOs);
var path = assetCache.MakeAssetPath(suggestedName, VelopackAssetType.Delta);
var deltaPkg = await CreateDeltaPackage( var deltaPkg = await CreateDeltaPackage(
progress, progress,
releasePath, releasePath,
prev.PackageFile, prev.PackageFile,
getIncompletePath(suggestedName), path,
options.DeltaMode); options.DeltaMode);
}); });
} }
@@ -189,12 +180,9 @@ public abstract class PackageBuilder<T> : ICommand<T>
await ctx.RunTask( await ctx.RunTask(
"Post-process steps", "Post-process steps",
(progress) => { (progress) => {
foreach (var f in filesToCopy) { assetCache.MoveBagTo(releaseDir.FullName);
IoUtil.MoveFile(f.from, f.to, true); assetCache.Write();
}
ReleaseEntryHelper.UpdateReleaseFiles(releaseDir.FullName, Log); ReleaseEntryHelper.UpdateReleaseFiles(releaseDir.FullName, Log);
BuildAssets.Write(releaseDir.FullName, channel, filesToCopy.Select(x => x.to));
progress(100); progress(100);
return Task.CompletedTask; return Task.CompletedTask;
}); });