Refactor projects & add NSwag codegen

This commit is contained in:
Caelan Sayler
2024-12-24 14:36:26 +00:00
committed by Caelan
parent 2d6d155444
commit 6eb35b8a81
90 changed files with 6267 additions and 702 deletions

View File

@@ -55,6 +55,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.Build", "src\vpk\V
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.IcoLib", "src\vpk\Velopack.IcoLib\Velopack.IcoLib.csproj", "{8A0A980A-D51C-458E-8942-00BC900FD2D0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.IcoLib", "src\vpk\Velopack.IcoLib\Velopack.IcoLib.csproj", "{8A0A980A-D51C-458E-8942-00BC900FD2D0}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Velopack.Flow", "src\vpk\Velopack.Flow\Velopack.Flow.csproj", "{DC836A20-E770-451A-B26E-4CC74633F741}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Velopack.Core", "src\vpk\Velopack.Core\Velopack.Core.csproj", "{2A60F8BE-EAD4-4229-A04E-52A37C8F1D0E}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -121,6 +125,14 @@ Global
{8A0A980A-D51C-458E-8942-00BC900FD2D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A0A980A-D51C-458E-8942-00BC900FD2D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A0A980A-D51C-458E-8942-00BC900FD2D0}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A0A980A-D51C-458E-8942-00BC900FD2D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A0A980A-D51C-458E-8942-00BC900FD2D0}.Release|Any CPU.Build.0 = Release|Any CPU {8A0A980A-D51C-458E-8942-00BC900FD2D0}.Release|Any CPU.Build.0 = Release|Any CPU
{DC836A20-E770-451A-B26E-4CC74633F741}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC836A20-E770-451A-B26E-4CC74633F741}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC836A20-E770-451A-B26E-4CC74633F741}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC836A20-E770-451A-B26E-4CC74633F741}.Release|Any CPU.Build.0 = Release|Any CPU
{2A60F8BE-EAD4-4229-A04E-52A37C8F1D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A60F8BE-EAD4-4229-A04E-52A37C8F1D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A60F8BE-EAD4-4229-A04E-52A37C8F1D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A60F8BE-EAD4-4229-A04E-52A37C8F1D0E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -6,6 +6,8 @@ using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("Velopack.Packaging.Tests, PublicKey=" + SNK.SHA1)] [assembly: InternalsVisibleTo("Velopack.Packaging.Tests, PublicKey=" + SNK.SHA1)]
[assembly: InternalsVisibleTo("Velopack.CommandLine.Tests, PublicKey=" + SNK.SHA1)] [assembly: InternalsVisibleTo("Velopack.CommandLine.Tests, PublicKey=" + SNK.SHA1)]
[assembly: InternalsVisibleTo("Velopack, PublicKey=" + SNK.SHA1)] [assembly: InternalsVisibleTo("Velopack, PublicKey=" + SNK.SHA1)]
[assembly: InternalsVisibleTo("Velopack.Core, PublicKey=" + SNK.SHA1)]
[assembly: InternalsVisibleTo("Velopack.Flow, PublicKey=" + SNK.SHA1)]
[assembly: InternalsVisibleTo("Velopack.Deployment, PublicKey=" + SNK.SHA1)] [assembly: InternalsVisibleTo("Velopack.Deployment, PublicKey=" + SNK.SHA1)]
[assembly: InternalsVisibleTo("Velopack.Packaging, PublicKey=" + SNK.SHA1)] [assembly: InternalsVisibleTo("Velopack.Packaging, PublicKey=" + SNK.SHA1)]
[assembly: InternalsVisibleTo("Velopack.Packaging.Windows, PublicKey=" + SNK.SHA1)] [assembly: InternalsVisibleTo("Velopack.Packaging.Windows, PublicKey=" + SNK.SHA1)]

View File

@@ -4,7 +4,7 @@ using System.Threading.Tasks;
using Microsoft.Build.Framework; using Microsoft.Build.Framework;
using Microsoft.Build.Utilities; using Microsoft.Build.Utilities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Packaging.Abstractions; using Velopack.Core.Abstractions;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
using Task = System.Threading.Tasks.Task; using Task = System.Threading.Tasks.Task;

View File

@@ -1,53 +1,54 @@
using System; using System.Threading;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Build.Framework; using Microsoft.Build.Framework;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Packaging.Flow; using Velopack.Flow;
namespace Velopack.Build; namespace Velopack.Build;
public class PublishTask : MSBuildAsyncTask public class PublishTask : MSBuildAsyncTask
{ {
private static HttpClient HttpClient { get; } = new(new HmacAuthHttpClientHandler {
InnerHandler = new HttpClientHandler()
}) {
Timeout = TimeSpan.FromMinutes(60)
};
[Required] [Required]
public string ReleaseDirectory { get; set; } = ""; public string ReleaseDirectory { get; set; } = "";
public string ServiceUrl { get; set; } = VelopackServiceOptions.DefaultBaseUrl; public string? ServiceUrl { get; set; }
public string? Channel { get; set; } public string? Channel { get; set; }
public string? ApiKey { get; set; } public string? ApiKey { get; set; }
public bool NoWaitForLive { get; set; } public double Timeout { get; set; } = 30d;
public bool WaitForLive { get; set; }
protected override async Task<bool> ExecuteAsync(CancellationToken cancellationToken) protected override async Task<bool> ExecuteAsync(CancellationToken cancellationToken)
{ {
throw new NotImplementedException(); //System.Diagnostics.Debugger.Launch();
// //System.Diagnostics.Debugger.Launch(); var options = new VelopackFlowServiceOptions {
// IVelopackFlowServiceClient client = new VelopackFlowServiceClient(HttpClient, Logger, Logger); VelopackBaseUrl = ServiceUrl,
// if (!await client.LoginAsync(new() { ApiKey = ApiKey,
// AllowDeviceCodeFlow = false, Timeout = Timeout,
// AllowInteractiveLogin = false, };
// VelopackBaseUrl = ServiceUrl,
// ApiKey = ApiKey var loginOptions = new VelopackFlowLoginOptions() {
// }, false, cancellationToken).ConfigureAwait(false)) { AllowCacheCredentials = true,
// Logger.LogWarning("Not logged into Velopack Flow service, skipping publish. Please run vpk login."); AllowDeviceCodeFlow = false,
// return true; AllowInteractiveLogin = false,
// } };
//
// // todo: currently it's not possible to cross-compile for different OSes using Velopack.Build var client = new VelopackFlowServiceClient(options, Logger, Logger);
// var targetOs = VelopackRuntimeInfo.SystemOs; CancellationToken token = CancellationToken.None;
// if (!await client.LoginAsync(loginOptions, false, token)) {
// await client.UploadLatestReleaseAssetsAsync(Channel, ReleaseDirectory, ServiceUrl, targetOs, NoWaitForLive, cancellationToken) Logger.LogWarning("Not logged into Velopack Flow service, skipping publish. Please run vpk login.");
// .ConfigureAwait(false); return true;
// }
// return true;
// todo: currently it's not possible to cross-compile for different OSes using Velopack.Build
var targetOs = VelopackRuntimeInfo.SystemOs;
await client.UploadLatestReleaseAssetsAsync(Channel, ReleaseDirectory, targetOs, WaitForLive, cancellationToken)
.ConfigureAwait(false);
return true;
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using Riok.Mapperly.Abstractions; using Riok.Mapperly.Abstractions;
using Velopack.Core;
using Velopack.Packaging; using Velopack.Packaging;
using Velopack.Packaging.Unix.Commands; using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands; using Velopack.Packaging.Windows.Commands;

View File

@@ -16,6 +16,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Velopack.Flow\Velopack.Flow.csproj" />
<ProjectReference Include="..\Velopack.Packaging.Unix\Velopack.Packaging.Unix.csproj" /> <ProjectReference Include="..\Velopack.Packaging.Unix\Velopack.Packaging.Unix.csproj" />
<ProjectReference Include="..\Velopack.Packaging.Windows\Velopack.Packaging.Windows.csproj" /> <ProjectReference Include="..\Velopack.Packaging.Windows\Velopack.Packaging.Windows.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,4 @@
namespace Velopack.Packaging.Abstractions; namespace Velopack.Core.Abstractions;
public interface ICommand<TOpt> where TOpt : class public interface ICommand<TOpt> where TOpt : class
{ {

View File

@@ -1,4 +1,5 @@
namespace Velopack.Packaging.Abstractions; namespace Velopack.Core.Abstractions;
public interface IConsole public interface IConsole
{ {
void WriteLine(string message = ""); void WriteLine(string message = "");

View File

@@ -1,4 +1,4 @@
namespace Velopack.Packaging.Abstractions; namespace Velopack.Core.Abstractions;
public interface IFancyConsole : IConsole public interface IFancyConsole : IConsole
{ {

View File

@@ -1,4 +1,4 @@
namespace Velopack.Packaging.Abstractions; namespace Velopack.Core.Abstractions;
public interface IFancyConsoleProgress public interface IFancyConsoleProgress
{ {

View File

@@ -1,8 +1,9 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Velopack.Packaging.Exceptions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging; #nullable disable
namespace Velopack.Core;
public class BuildAssets public class BuildAssets
{ {
@@ -14,7 +15,7 @@ public class BuildAssets
public List<VelopackAsset> GetReleaseEntries() public List<VelopackAsset> GetReleaseEntries()
{ {
return GetFilePaths().Where(x => x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) return GetFilePaths().Where(x => x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase))
.Select(f => VelopackAsset.FromNupkg(f)) .Select(VelopackAsset.FromNupkg)
.ToList(); .ToList();
} }

View File

@@ -0,0 +1,55 @@
using NuGet.Versioning;
namespace Velopack.Core;
public static class DefaultName
{
public static string GetSuggestedReleaseName(string id, string version, string channel, bool delta, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
version = SemanticVersion.Parse(version).ToNormalizedString();
if (os == RuntimeOs.Windows && channel == GetDefaultChannel(RuntimeOs.Windows)) {
return $"{id}-{version}{(delta ? "-delta" : "-full")}.nupkg";
}
return $"{id}-{version}{suffix}{(delta ? "-delta" : "-full")}.nupkg";
}
public static string GetSuggestedPortableName(string id, string channel, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
if (os == RuntimeOs.Linux) {
if (channel == GetDefaultChannel(RuntimeOs.Linux)) {
return $"{id}.AppImage";
} else {
return $"{id}{suffix}.AppImage";
}
} else {
return $"{id}{suffix}-Portable.zip";
}
}
public static string GetSuggestedSetupName(string id, string channel, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
if (os == RuntimeOs.Windows)
return $"{id}{suffix}-Setup.exe";
else if (os == RuntimeOs.OSX)
return $"{id}{suffix}-Setup.pkg";
else
throw new PlatformNotSupportedException("Platform not supported.");
}
private static string GetUniqueAssetSuffix(string channel)
{
return "-" + channel;
}
public static string GetDefaultChannel(RuntimeOs os)
{
if (os == RuntimeOs.Windows) return "win";
if (os == RuntimeOs.OSX) return "osx";
if (os == RuntimeOs.Linux) return "linux";
throw new NotSupportedException("Unsupported OS: " + os);
}
}

View File

@@ -4,7 +4,7 @@
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
namespace Velopack.Packaging; namespace Velopack.Core;
public static class HttpClientExtensions public static class HttpClientExtensions
{ {
@@ -16,7 +16,7 @@ public static class HttpClientExtensions
var response = await client.GetAsync(requestUri, cancellationToken); var response = await client.GetAsync(requestUri, cancellationToken);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
return Newtonsoft.Json.JsonConvert.DeserializeObject<TValue>(await response.Content.ReadAsStringAsync()); return SimpleJson.DeserializeObject<TValue>(await response.Content.ReadAsStringAsync());
} }
public static async Task<HttpResponseMessage> PostAsJsonAsync<TValue>( public static async Task<HttpResponseMessage> PostAsJsonAsync<TValue>(
@@ -25,7 +25,7 @@ public static class HttpClientExtensions
TValue value, TValue value,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(value), Encoding.UTF8, "application/json"); var content = new StringContent(SimpleJson.SerializeObject(value), Encoding.UTF8, "application/json");
return await client.PostAsync(requestUri, content, cancellationToken); return await client.PostAsync(requestUri, content, cancellationToken);
} }
@@ -35,7 +35,7 @@ public static class HttpClientExtensions
TValue value, TValue value,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(value), Encoding.UTF8, "application/json"); var content = new StringContent(SimpleJson.SerializeObject(value), Encoding.UTF8, "application/json");
return await client.PutAsync(requestUri, content, cancellationToken); return await client.PutAsync(requestUri, content, cancellationToken);
} }
@@ -44,7 +44,7 @@ public static class HttpClientExtensions
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var json = await content.ReadAsStringAsync(); var json = await content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<TValue>(json); return SimpleJson.DeserializeObject<TValue>(json);
} }
public static async Task<string> ReadAsStringAsync(this HttpContent content, CancellationToken _) public static async Task<string> ReadAsStringAsync(this HttpContent content, CancellationToken _)

View File

@@ -2,7 +2,7 @@
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using NuGet.Versioning; using NuGet.Versioning;
namespace Velopack.Packaging; namespace Velopack.Core;
public class SimpleJson public class SimpleJson
{ {
@@ -11,7 +11,7 @@ public class SimpleJson
NullValueHandling = NullValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore,
}; };
public static T DeserializeObject<T>(string json) public static T? DeserializeObject<T>(string json)
{ {
return JsonConvert.DeserializeObject<T>(json, Options); return JsonConvert.DeserializeObject<T>(json, Options);
} }
@@ -23,15 +23,15 @@ public class SimpleJson
private class SemanticVersionConverter : JsonConverter<SemanticVersion> private class SemanticVersionConverter : JsonConverter<SemanticVersion>
{ {
public override SemanticVersion ReadJson(JsonReader reader, Type objectType, SemanticVersion existingValue, bool hasExistingValue, public override SemanticVersion? ReadJson(JsonReader reader, Type objectType, SemanticVersion? existingValue, bool hasExistingValue,
JsonSerializer serializer) JsonSerializer serializer)
{ {
string s = reader.Value as string; string? s = reader.Value as string;
if (s == null) return null; if (s == null) return null;
return SemanticVersion.Parse(s); return SemanticVersion.Parse(s);
} }
public override void WriteJson(JsonWriter writer, SemanticVersion value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, SemanticVersion? value, JsonSerializer serializer)
{ {
if (value != null) { if (value != null) {
writer.WriteValue(value.ToFullString()); writer.WriteValue(value.ToFullString());

View File

@@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Velopack.Packaging.Exceptions; namespace Velopack.Core;
/// <summary> /// <summary>
/// Denotes that an error has occurred for which a stack trace should not be printed. /// Denotes that an error has occurred for which a stack trace should not be printed.

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA2007;CS8002</NoWarn>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\lib-csharp\Velopack.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
</Project>

View File

@@ -1,6 +1,7 @@
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octokit; using Octokit;
using Velopack.Core;
using Velopack.NuGet; using Velopack.NuGet;
using Velopack.Packaging; using Velopack.Packaging;
using Velopack.Packaging.Exceptions; using Velopack.Packaging.Exceptions;
@@ -131,7 +132,7 @@ public class GitHubRepository(ILogger logger) : SourceRepository<GitHubDownloadO
}, },
"Uploading " + releasesFileName); "Uploading " + releasesFileName);
if (options.Channel == ReleaseEntryHelper.GetDefaultChannel(RuntimeOs.Windows)) { if (options.Channel == DefaultName.GetDefaultChannel(RuntimeOs.Windows)) {
var legacyReleasesContent = ReleaseEntryHelper.GetLegacyMigrationReleaseFeedString(feed); var legacyReleasesContent = ReleaseEntryHelper.GetLegacyMigrationReleaseFeedString(feed);
var legacyReleasesBytes = Encoding.UTF8.GetBytes(legacyReleasesContent); var legacyReleasesBytes = Encoding.UTF8.GetBytes(legacyReleasesContent);
await RetryAsync( await RetryAsync(

View File

@@ -4,9 +4,9 @@ using Gitea.Net.Api;
using Gitea.Net.Client; using Gitea.Net.Client;
using Gitea.Net.Model; using Gitea.Net.Model;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Core;
using Velopack.NuGet; using Velopack.NuGet;
using Velopack.Packaging; using Velopack.Packaging;
using Velopack.Packaging.Exceptions;
using Velopack.Sources; using Velopack.Sources;
using Velopack.Util; using Velopack.Util;
@@ -155,7 +155,7 @@ public class GiteaRepository : SourceRepository<GiteaDownloadOptions, GiteaSourc
await apiInstance.RepoCreateReleaseAttachmentAsync(repoOwner, repoName, release.Id, releasesFileName, new MemoryStream(Encoding.UTF8.GetBytes(json))); await apiInstance.RepoCreateReleaseAttachmentAsync(repoOwner, repoName, release.Id, releasesFileName, new MemoryStream(Encoding.UTF8.GetBytes(json)));
}, "Uploading " + releasesFileName); }, "Uploading " + releasesFileName);
if (options.Channel == ReleaseEntryHelper.GetDefaultChannel(RuntimeOs.Windows)) { if (options.Channel == DefaultName.GetDefaultChannel(RuntimeOs.Windows)) {
var legacyReleasesContent = ReleaseEntryHelper.GetLegacyMigrationReleaseFeedString(feed); var legacyReleasesContent = ReleaseEntryHelper.GetLegacyMigrationReleaseFeedString(feed);
var legacyReleasesBytes = Encoding.UTF8.GetBytes(legacyReleasesContent); var legacyReleasesBytes = Encoding.UTF8.GetBytes(legacyReleasesContent);
await RetryAsync(async () => { await RetryAsync(async () => {

View File

@@ -15,6 +15,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Velopack.Core\Velopack.Core.csproj" />
<ProjectReference Include="..\Velopack.Packaging\Velopack.Packaging.csproj" /> <ProjectReference Include="..\Velopack.Packaging\Velopack.Packaging.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -1,5 +1,6 @@
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Core;
using Velopack.Packaging; using Velopack.Packaging;
using Velopack.Util; using Velopack.Util;

View File

@@ -1,6 +1,5 @@
using System.Security.Cryptography; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging; using Velopack.Core;
using Velopack.Packaging;
using Velopack.Packaging.Abstractions; using Velopack.Packaging.Abstractions;
using Velopack.Sources; using Velopack.Sources;
using Velopack.Util; using Velopack.Util;
@@ -14,7 +13,7 @@ public class RepositoryOptions : IOutputOptions
public RuntimeOs TargetOs { get; set; } public RuntimeOs TargetOs { get; set; }
public string Channel { public string Channel {
get => _channel ?? ReleaseEntryHelper.GetDefaultChannel(TargetOs); get => _channel ?? DefaultName.GetDefaultChannel(TargetOs);
set => _channel = value; set => _channel = value;
} }

View File

@@ -0,0 +1,25 @@
using Microsoft.Extensions.Logging;
using Velopack.Core.Abstractions;
namespace Velopack.Flow.Commands;
public class ApiCommandRunner(ILogger logger, IFancyConsole console) : ICommand<ApiOptions>
{
public async Task Run(ApiOptions options)
{
var loginOptions = new VelopackFlowLoginOptions() {
AllowCacheCredentials = true,
AllowDeviceCodeFlow = false,
AllowInteractiveLogin = false,
};
var client = new VelopackFlowServiceClient(options, logger, console);
CancellationToken token = CancellationToken.None;
if (!await client.LoginAsync(loginOptions, false, token)) {
return;
}
string response = await client.InvokeEndpointAsync(options, options.Endpoint, options.Method, options.Body, token);
Console.WriteLine(response);
}
}

View File

@@ -0,0 +1,8 @@
namespace Velopack.Flow.Commands;
public sealed class ApiOptions : VelopackFlowServiceOptions
{
public string Endpoint { get; set; } = "";
public string Method { get; set; } = "";
public string? Body { get; set; }
}

View File

@@ -0,0 +1,13 @@
using Microsoft.Extensions.Logging;
using Velopack.Core.Abstractions;
namespace Velopack.Flow.Commands;
public class LoginCommandRunner(ILogger logger, IFancyConsole console) : ICommand<LoginOptions>
{
public async Task Run(LoginOptions options)
{
var client = new VelopackFlowServiceClient(options, logger, console);
await client.LoginAsync(null, false, CancellationToken.None);
}
}

View File

@@ -0,0 +1,3 @@
namespace Velopack.Flow.Commands;
public sealed class LoginOptions : VelopackFlowServiceOptions;

View File

@@ -0,0 +1,13 @@
using Microsoft.Extensions.Logging;
using Velopack.Core.Abstractions;
namespace Velopack.Flow.Commands;
public class LogoutCommandRunner(ILogger logger, IFancyConsole console) : ICommand<LogoutOptions>
{
public async Task Run(LogoutOptions options)
{
var client = new VelopackFlowServiceClient(options, logger, console);
await client.LogoutAsync(CancellationToken.None);
}
}

View File

@@ -0,0 +1,3 @@
namespace Velopack.Flow.Commands;
public sealed class LogoutOptions : VelopackFlowServiceOptions;

View File

@@ -0,0 +1,29 @@
using Microsoft.Extensions.Logging;
using Velopack.Core.Abstractions;
namespace Velopack.Flow.Commands;
public class PublishCommandRunner(ILogger logger, IFancyConsole console) : ICommand<PublishOptions>
{
public async Task Run(PublishOptions options)
{
var loginOptions = new VelopackFlowLoginOptions() {
AllowCacheCredentials = true,
AllowDeviceCodeFlow = false,
AllowInteractiveLogin = false,
};
var client = new VelopackFlowServiceClient(options, logger, console);
CancellationToken token = CancellationToken.None;
if (!await client.LoginAsync(loginOptions, false, token)) {
return;
}
await client.UploadLatestReleaseAssetsAsync(
options.Channel,
options.ReleaseDirectory,
options.TargetOs,
options.WaitForLive,
token);
}
}

View File

@@ -0,0 +1,12 @@
namespace Velopack.Flow.Commands;
public sealed class PublishOptions : VelopackFlowServiceOptions
{
public RuntimeOs TargetOs { get; set; }
public string ReleaseDirectory { get; set; } = "";
public string? Channel { get; set; }
public bool WaitForLive { get; set; }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
dotnet tool update --global NSwag.ConsoleCore
nswag openapi2csclient /input:https://api.velopack.io/swagger/v1/swagger.json /output:FlowApi.cs /namespace:Velopack.Flow /classname:FlowApi

View File

@@ -0,0 +1,98 @@
using System.Globalization;
using System.Net.Http;
using System.Text;
namespace Velopack.Flow;
public partial class Profile
{
public string GetDisplayName()
{
return DisplayName ?? Email ?? "<unknown>";
}
}
public partial class FlowApi
{
public virtual async Task DownloadInstallerLatestToFileAsync(string packageId, string channel, DownloadAssetType? assetType, string localFilePath,
CancellationToken cancellationToken)
{
if (packageId == null)
throw new ArgumentNullException("packageId");
if (channel == null)
throw new ArgumentNullException("channel");
var client_ = _httpClient;
var disposeClient_ = false;
try {
using (var request_ = new HttpRequestMessage()) {
request_.Method = new HttpMethod("GET");
var urlBuilder_ = new StringBuilder();
if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
// Operation Path: "v1/download/{packageId}/{channel}"
urlBuilder_.Append("v1/download/");
urlBuilder_.Append(Uri.EscapeDataString(ConvertToString(packageId, CultureInfo.InvariantCulture)));
urlBuilder_.Append('/');
urlBuilder_.Append(Uri.EscapeDataString(ConvertToString(channel, CultureInfo.InvariantCulture)));
urlBuilder_.Append('?');
if (assetType != null) {
urlBuilder_.Append(Uri.EscapeDataString("assetType")).Append('=')
.Append(Uri.EscapeDataString(ConvertToString(assetType, CultureInfo.InvariantCulture))).Append('&');
}
urlBuilder_.Length--;
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new Uri(url_, UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try {
var headers_ = new Dictionary<string, IEnumerable<string>>();
foreach (var item_ in response_.Headers)
headers_[item_.Key] = item_.Value;
if (response_.Content != null && response_.Content.Headers != null) {
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = (int) response_.StatusCode;
if (status_ == 404) {
string responseText_ = (response_.Content == null) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("A server side error occurred.", status_, responseText_, headers_, null);
} else if (status_ == 200 || status_ == 204) {
using var fs = File.Create(localFilePath);
#if NET6_0_OR_GREATER
await response_.Content.CopyToAsync(fs, cancellationToken);
#else
await response_.Content.CopyToAsync(fs);
#endif
return;
} else {
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException(
"The HTTP status code of the response was not expected (" + status_ + ").",
status_,
responseData_,
headers_,
null);
}
} finally {
if (disposeResponse_)
response_.Dispose();
}
}
} finally {
if (disposeClient_)
client_.Dispose();
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
namespace Velopack.Packaging.Flow; namespace Velopack.Flow;
public class HmacAuthHttpClientHandler : DelegatingHandler public class HmacAuthHttpClientHandler : DelegatingHandler
{ {

View File

@@ -1,9 +1,7 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
#nullable enable namespace Velopack.Flow;
namespace Velopack.Packaging.Flow;
public static class HmacHelper public static class HmacHelper
{ {
@@ -45,6 +43,7 @@ public static class HmacHelper
if (signatureData is null) { if (signatureData is null) {
throw new ArgumentNullException(nameof(signatureData)); throw new ArgumentNullException(nameof(signatureData));
} }
using HMAC hmac = new HMACSHA256(); using HMAC hmac = new HMACSHA256();
hmac.Key = secret ?? throw new ArgumentNullException(nameof(secret)); hmac.Key = secret ?? throw new ArgumentNullException(nameof(secret));
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(signatureData))); return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(signatureData)));

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA2007;CS8002</NoWarn>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.2" />
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.66.2" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.66.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" Aliases="HttpFormatting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Velopack.Core\Velopack.Core.csproj" />
<ProjectReference Include="..\Velopack.Packaging\Velopack.Packaging.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,6 @@
#nullable enable namespace Velopack.Flow;
namespace Velopack.Packaging.Flow;
public class VelopackLoginOptions : VelopackServiceOptions public class VelopackFlowLoginOptions
{ {
public bool AllowCacheCredentials { get; set; } = true; public bool AllowCacheCredentials { get; set; } = true;
public bool AllowInteractiveLogin { get; set; } = true; public bool AllowInteractiveLogin { get; set; } = true;

View File

@@ -5,8 +5,10 @@ using NuGet.Versioning;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Text; using System.Text;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Velopack.Core;
using Velopack.Core.Abstractions;
using Velopack.NuGet; using Velopack.NuGet;
using Velopack.Packaging.Abstractions; using Velopack.Packaging;
using Velopack.Util; using Velopack.Util;
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
@@ -16,29 +18,12 @@ using System.Net.Http;
#endif #endif
#nullable enable #nullable enable
namespace Velopack.Packaging.Flow; namespace Velopack.Flow;
public interface IVelopackFlowServiceClient
{
Task<bool> LoginAsync(VelopackLoginOptions? options, bool suppressOutput, CancellationToken cancellationToken);
Task LogoutAsync(VelopackServiceOptions? options, CancellationToken cancellationToken);
Task<Profile?> GetProfileAsync(VelopackServiceOptions? options, CancellationToken cancellationToken);
Task<string> InvokeEndpointAsync(VelopackServiceOptions? options, string endpointUri,
string method,
string? body,
CancellationToken cancellationToken);
Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory, string? serviceUrl, RuntimeOs os,
bool noWaitForLive, CancellationToken cancellationToken);
}
public class VelopackFlowServiceClient( public class VelopackFlowServiceClient(
IHttpMessageHandlerFactory HttpMessageHandlerFactory, VelopackFlowServiceOptions Options,
ILogger Logger, ILogger Logger,
IFancyConsole Console) : IVelopackFlowServiceClient IFancyConsole Console)
{ {
private static readonly string[] Scopes = ["openid", "offline_access"]; private static readonly string[] Scopes = ["openid", "offline_access"];
@@ -48,7 +33,7 @@ public class VelopackFlowServiceClient(
private HttpClient GetHttpClient(Action<int>? progress = null) private HttpClient GetHttpClient(Action<int>? progress = null)
{ {
HttpMessageHandler primaryHandler = HttpMessageHandlerFactory.CreateHandler("flow"); HttpMessageHandler primaryHandler = new HmacAuthHttpClientHandler();
if (progress != null) { if (progress != null) {
var ph = new HttpFormatting::System.Net.Http.Handlers.ProgressMessageHandler(primaryHandler); var ph = new HttpFormatting::System.Net.Http.Handlers.ProgressMessageHandler(primaryHandler);
@@ -59,37 +44,51 @@ public class VelopackFlowServiceClient(
ph.HttpReceiveProgress += (_, args) => { ph.HttpReceiveProgress += (_, args) => {
progress(args.ProgressPercentage); progress(args.ProgressPercentage);
}; };
primaryHandler = ph; primaryHandler = ph;
} }
var client = new HttpClient(primaryHandler); var client = new HttpClient(primaryHandler);
client.DefaultRequestHeaders.Authorization = Authorization; client.DefaultRequestHeaders.Authorization = Authorization;
client.Timeout = TimeSpan.FromMinutes(Options.Timeout);
return client; return client;
} }
public async Task<bool> LoginAsync(VelopackLoginOptions? options, bool suppressOutput, CancellationToken cancellationToken) private FlowApi GetFlowApi(Action<int>? progress = null)
{ {
options ??= new VelopackLoginOptions(); var client = GetHttpClient(progress);
if (!suppressOutput) { var api = new FlowApi(client);
Logger.LogInformation("Preparing to login to Velopack ({ServiceUrl})", options.VelopackBaseUrl); if (!String.IsNullOrWhiteSpace(Options.VelopackBaseUrl)) {
api.BaseUrl = Options.VelopackBaseUrl;
} }
var authConfiguration = await GetAuthConfigurationAsync(options, cancellationToken); return api;
}
public async Task<bool> LoginAsync(VelopackFlowLoginOptions? loginOptions, bool suppressOutput, CancellationToken cancellationToken)
{
loginOptions ??= new VelopackFlowLoginOptions();
var serviceUrl = Options.VelopackBaseUrl ?? GetFlowApi().BaseUrl;
if (!suppressOutput) {
Logger.LogInformation("Preparing to login to Velopack ({serviceUrl})", serviceUrl);
}
var authConfiguration = await GetAuthConfigurationAsync(cancellationToken);
var pca = await BuildPublicApplicationAsync(authConfiguration); var pca = await BuildPublicApplicationAsync(authConfiguration);
if (!string.IsNullOrWhiteSpace(options.ApiKey)) { if (!string.IsNullOrWhiteSpace(Options.ApiKey)) {
Authorization = new(HmacHelper.HmacScheme, options.ApiKey); Authorization = new(HmacHelper.HmacScheme, Options.ApiKey);
} else { } else {
AuthenticationResult? rv = null; AuthenticationResult? rv = null;
if (options.AllowCacheCredentials) { if (loginOptions.AllowCacheCredentials) {
rv = await AcquireSilentlyAsync(pca, cancellationToken); rv = await AcquireSilentlyAsync(pca, cancellationToken);
} }
if (rv is null && options.AllowInteractiveLogin) { if (rv is null && loginOptions.AllowInteractiveLogin) {
rv = await AcquireInteractiveAsync(pca, authConfiguration, cancellationToken); rv = await AcquireInteractiveAsync(pca, authConfiguration, cancellationToken);
} }
if (rv is null && options.AllowDeviceCodeFlow) { if (rv is null && loginOptions.AllowDeviceCodeFlow) {
rv = await AcquireByDeviceCodeAsync(pca, cancellationToken); rv = await AcquireByDeviceCodeAsync(pca, cancellationToken);
} }
@@ -101,7 +100,7 @@ public class VelopackFlowServiceClient(
Authorization = new("Bearer", rv.IdToken ?? rv.AccessToken); Authorization = new("Bearer", rv.IdToken ?? rv.AccessToken);
} }
var profile = await GetProfileAsync(options, cancellationToken); var profile = await GetProfileAsync(cancellationToken);
if (!suppressOutput) { if (!suppressOutput) {
Logger.LogInformation("{UserName} logged into Velopack", profile?.GetDisplayName()); Logger.LogInformation("{UserName} logged into Velopack", profile?.GetDisplayName());
@@ -110,9 +109,9 @@ public class VelopackFlowServiceClient(
return true; return true;
} }
public async Task LogoutAsync(VelopackServiceOptions? options, CancellationToken cancellationToken) public async Task LogoutAsync(CancellationToken cancellationToken)
{ {
var authConfiguration = await GetAuthConfigurationAsync(options, cancellationToken); var authConfiguration = await GetAuthConfigurationAsync(cancellationToken);
var pca = await BuildPublicApplicationAsync(authConfiguration); var pca = await BuildPublicApplicationAsync(authConfiguration);
@@ -125,31 +124,30 @@ public class VelopackFlowServiceClient(
Logger.LogInformation("Cleared saved login(s) for Velopack"); Logger.LogInformation("Cleared saved login(s) for Velopack");
} }
public async Task<Profile?> GetProfileAsync(VelopackServiceOptions? options, CancellationToken cancellationToken) public async Task<Profile?> GetProfileAsync(CancellationToken cancellationToken)
{ {
AssertAuthenticated(); AssertAuthenticated();
var endpoint = GetEndpoint("v1/user/profile", options?.VelopackBaseUrl); var client = GetFlowApi();
return await client.GetUserProfileAsync(cancellationToken);
var client = GetHttpClient();
return await client.GetFromJsonAsync<Profile>(endpoint, cancellationToken);
} }
public async Task<string> InvokeEndpointAsync( public async Task<string> InvokeEndpointAsync(
VelopackServiceOptions? options, VelopackFlowServiceOptions? options,
string endpointUri, string endpointUri,
string method, string method,
string? body, string? body,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
AssertAuthenticated(); AssertAuthenticated();
var endpoint = GetEndpoint(endpointUri, options?.VelopackBaseUrl);
var client = GetHttpClient();
var endpoint = new FlowApi(client).BaseUrl;
HttpRequestMessage request = new(new HttpMethod(method), endpoint); HttpRequestMessage request = new(new HttpMethod(method), endpoint);
if (body is not null) { if (body is not null) {
request.Content = new StringContent(body, Encoding.UTF8, "application/json"); request.Content = new StringContent(body, Encoding.UTF8, "application/json");
} }
var client = GetHttpClient();
HttpResponseMessage response = await client.SendAsync(request, cancellationToken); HttpResponseMessage response = await client.SendAsync(request, cancellationToken);
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
@@ -166,14 +164,14 @@ public class VelopackFlowServiceClient(
} }
} }
public async Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory, string? serviceUrl, public async Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory,
RuntimeOs os, bool noWaitForLive, CancellationToken cancellationToken) RuntimeOs os, bool waitForLive, CancellationToken cancellationToken)
{ {
AssertAuthenticated(); AssertAuthenticated();
channel ??= ReleaseEntryHelper.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 == VelopackAssetType.Full); var fullAsset = assets.GetReleaseEntries().SingleOrDefault(a => a.Type == Velopack.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);
@@ -188,7 +186,7 @@ public class VelopackFlowServiceClient(
.Concat([(fullAssetPath, FileType.Release)]) .Concat([(fullAssetPath, FileType.Release)])
.ToArray(); .ToArray();
Logger.LogInformation("Beginning upload to Velopack Flow (serviceUrl={ServiceUrl})", serviceUrl); Logger.LogInformation("Beginning upload to Velopack Flow");
await Console.ExecuteProgressAsync( await Console.ExecuteProgressAsync(
async (progress) => { async (progress) => {
@@ -196,7 +194,7 @@ public class VelopackFlowServiceClient(
$"Creating release {version}", $"Creating release {version}",
async (report) => { async (report) => {
report(-1); report(-1);
var result = await CreateReleaseGroupAsync(packageId, version, channel, serviceUrl, cancellationToken); var result = await CreateReleaseGroupAsync(packageId, version, channel, cancellationToken);
report(100); report(100);
return result; return result;
}); });
@@ -209,7 +207,6 @@ public class VelopackFlowServiceClient(
async (report) => { async (report) => {
await UploadReleaseAssetAsync( await UploadReleaseAssetAsync(
assetTuple.Item1, assetTuple.Item1,
serviceUrl,
releaseGroup.Id, releaseGroup.Id,
assetTuple.Item2, assetTuple.Item2,
report, report,
@@ -225,7 +222,7 @@ public class VelopackFlowServiceClient(
var prevZip = await progress.RunTask( var prevZip = await progress.RunTask(
$"Downloading delta base for {version}", $"Downloading delta base for {version}",
async (report) => { async (report) => {
await DownloadLatestRelease(packageId, channel, serviceUrl, prevVersion, report, cancellationToken); await DownloadLatestRelease(packageId, channel, prevVersion, report, cancellationToken);
return new ZipPackage(prevVersion); return new ZipPackage(prevVersion);
}); });
@@ -234,7 +231,7 @@ public class VelopackFlowServiceClient(
$"Latest version in channel {channel} is greater than or equal to local (remote={prevZip.Version}, local={version})"); $"Latest version in channel {channel} is greater than or equal to local (remote={prevZip.Version}, local={version})");
} }
var suggestedDeltaName = ReleaseEntryHelper.GetSuggestedReleaseName(packageId, version.ToFullString(), channel, true, RuntimeOs.Unknown); var suggestedDeltaName = DefaultName.GetSuggestedReleaseName(packageId, version.ToFullString(), channel, true, RuntimeOs.Unknown);
var deltaPath = Path.Combine(releaseDirectory, suggestedDeltaName); var deltaPath = Path.Combine(releaseDirectory, suggestedDeltaName);
await progress.RunTask( await progress.RunTask(
@@ -254,7 +251,6 @@ public class VelopackFlowServiceClient(
async (report) => { async (report) => {
await UploadReleaseAssetAsync( await UploadReleaseAssetAsync(
deltaPath, deltaPath,
serviceUrl,
releaseGroup.Id, releaseGroup.Id,
FileType.Release, FileType.Release,
report, report,
@@ -269,50 +265,35 @@ public class VelopackFlowServiceClient(
$"Publishing release {version}", $"Publishing release {version}",
async (report) => { async (report) => {
report(-1); report(-1);
var result = await PublishReleaseGroupAsync(releaseGroup, serviceUrl, cancellationToken); var result = await PublishReleaseGroupAsync(releaseGroup.Id, cancellationToken);
report(100); report(100);
return result; return result;
}); });
if (!noWaitForLive) { if (waitForLive) {
await progress.RunTask( await progress.RunTask(
"Waiting for release to go live", "Waiting for release to go live",
async (report) => { async (report) => {
report(-1); report(-1);
await WaitUntilReleaseGroupLive(publishedGroup.Id, serviceUrl, cancellationToken); await WaitUntilReleaseGroupLive(publishedGroup.Id, cancellationToken);
report(100); report(100);
}); });
} }
}); });
} }
private async Task DownloadLatestRelease(string packageId, string channel, string? velopackBaseUrl, string localPath, private async Task DownloadLatestRelease(string packageId, string channel, string localPath, Action<int> progress, CancellationToken cancellationToken)
Action<int> progress, CancellationToken cancellationToken)
{ {
var client = GetHttpClient(progress); var client = GetFlowApi(progress);
var endpoint = GetEndpoint($"v1/download/{packageId}/{channel}", velopackBaseUrl) + $"?assetType=Full"; await client.DownloadInstallerLatestToFileAsync(packageId, channel, DownloadAssetType.Full, localPath, cancellationToken);
using var fs = File.Create(localPath);
var response = await client.GetAsync(endpoint, cancellationToken);
response.EnsureSuccessStatusCode();
#if NET6_0_OR_GREATER
await response.Content.CopyToAsync(fs, cancellationToken);
#else
await response.Content.CopyToAsync(fs);
#endif
} }
private async Task WaitUntilReleaseGroupLive(Guid releaseGroupId, string? velopackBaseUrl, CancellationToken cancellationToken) private async Task WaitUntilReleaseGroupLive(Guid releaseGroupId, CancellationToken cancellationToken)
{ {
var client = GetHttpClient(); var client = GetFlowApi();
var endpoint = GetEndpoint($"v1/releaseGroups/{releaseGroupId}", velopackBaseUrl);
for (int i = 0; i < 300; i++) { for (int i = 0; i < 300; i++) {
var response = await client.GetAsync(endpoint, cancellationToken); var releaseGroup = await client.GetReleaseGroupAsync(releaseGroupId, cancellationToken);
response.EnsureSuccessStatusCode();
var releaseGroup = await response.Content.ReadFromJsonAsync<ReleaseGroup>(cancellationToken: cancellationToken);
if (releaseGroup?.FileUploads == null) { if (releaseGroup?.FileUploads == null) {
Logger.LogWarning("Failed to get release group status, it may not be live yet."); Logger.LogWarning("Failed to get release group status, it may not be live yet.");
return; return;
@@ -329,9 +310,7 @@ public class VelopackFlowServiceClient(
Logger.LogWarning("Release did not go live within 5 minutes (timeout)."); Logger.LogWarning("Release did not go live within 5 minutes (timeout).");
} }
private async Task<ReleaseGroup> CreateReleaseGroupAsync( private async Task<ReleaseGroup> CreateReleaseGroupAsync(string packageId, SemanticVersion version, string channel, CancellationToken cancellationToken)
string packageId, SemanticVersion version, string channel,
string? velopackBaseUrl, CancellationToken cancellationToken)
{ {
CreateReleaseGroupRequest request = new() { CreateReleaseGroupRequest request = new() {
ChannelIdentifier = channel, ChannelIdentifier = channel,
@@ -339,70 +318,37 @@ public class VelopackFlowServiceClient(
Version = version.ToNormalizedString() Version = version.ToNormalizedString()
}; };
var client = GetHttpClient(); var client = GetFlowApi();
var endpoint = GetEndpoint("v1/releaseGroups/create", velopackBaseUrl); return await client.CreateReleaseGroupAsync(request, cancellationToken);
var response = await client.PostAsJsonAsync(endpoint, request, cancellationToken);
if (!response.IsSuccessStatusCode) {
string content = await response.Content.ReadAsStringAsync(cancellationToken);
throw new InvalidOperationException(
$"Failed to create release group with version {version.ToNormalizedString()}" +
$"{Environment.NewLine}Response status code: {response.StatusCode}{Environment.NewLine}{content}");
} }
return await response.Content.ReadFromJsonAsync<ReleaseGroup>(cancellationToken: cancellationToken) private async Task UploadReleaseAssetAsync(string filePath, Guid releaseGroupId, FileType fileType, Action<int> progress,
?? throw new InvalidOperationException($"Failed to create release group with version {version.ToNormalizedString()}"); CancellationToken cancellationToken)
}
private async Task UploadReleaseAssetAsync(string filePath, string? velopackBaseUrl, Guid releaseGroupId,
FileType fileType, Action<int> progress, CancellationToken cancellationToken)
{ {
using var formData = new MultipartFormDataContent(); using var stream = File.OpenRead(filePath);
formData.Add(new StringContent(releaseGroupId.ToString()), "ReleaseGroupId"); var file = new FileParameter(stream);
formData.Add(new StringContent(fileType.ToString()), "FileType"); var client = GetFlowApi(progress);
await client.UploadReleaseAsync(releaseGroupId, fileType, file, cancellationToken);
using var fileStream = File.OpenRead(filePath);
using var fileContent = new StreamContent(fileStream);
formData.Add(fileContent, "File", Path.GetFileName(filePath));
var endpoint = GetEndpoint("v1/releases/upload", velopackBaseUrl);
var client = GetHttpClient(progress);
var response = await client.PostAsync(endpoint, formData, cancellationToken);
response.EnsureSuccessStatusCode();
} }
private async Task<ReleaseGroup> PublishReleaseGroupAsync( private async Task<ReleaseGroup> PublishReleaseGroupAsync(Guid releaseGroupId, CancellationToken cancellationToken)
ReleaseGroup releaseGroup, string? velopackBaseUrl, CancellationToken cancellationToken)
{ {
UpdateReleaseGroupRequest request = new() { UpdateReleaseGroupRequest request = new() {
State = ReleaseGroupState.Published State = ReleaseGroupState.Published
}; };
var client = GetHttpClient(); var client = GetFlowApi();
var endpoint = GetEndpoint($"v1/releaseGroups/{releaseGroup.Id}", velopackBaseUrl); return await client.UpdateReleaseGroupAsync(releaseGroupId, request, cancellationToken);
var response = await client.PutAsJsonAsync(endpoint, request, cancellationToken);
if (!response.IsSuccessStatusCode) {
string content = await response.Content.ReadAsStringAsync(cancellationToken);
throw new InvalidOperationException(
$"Failed to publish release group with id {releaseGroup.Id}.{Environment.NewLine}Response status code: {response.StatusCode}{Environment.NewLine}{content}");
} }
return await response.Content.ReadFromJsonAsync<ReleaseGroup>(cancellationToken: cancellationToken) private async Task<AuthConfiguration> GetAuthConfigurationAsync(CancellationToken cancellationToken)
?? throw new InvalidOperationException($"Failed to publish release group with id {releaseGroup.Id}");
}
private async Task<AuthConfiguration> GetAuthConfigurationAsync(VelopackServiceOptions? options, CancellationToken cancellationToken)
{ {
if (AuthConfiguration is not null) if (AuthConfiguration is not null)
return AuthConfiguration; return AuthConfiguration;
var endpoint = GetEndpoint("v1/auth/config", options); var client = GetFlowApi();
var authConfig = await client.GetV1AuthConfigAsync(cancellationToken);
var client = GetHttpClient();
var authConfig = await client.GetFromJsonAsync<AuthConfiguration>(endpoint, cancellationToken);
if (authConfig is null) if (authConfig is null)
throw new Exception("Failed to get auth configuration."); throw new Exception("Failed to get auth configuration.");
if (authConfig.B2CAuthority is null) if (authConfig.B2CAuthority is null)
@@ -415,16 +361,6 @@ public class VelopackFlowServiceClient(
return authConfig; return authConfig;
} }
private static Uri GetEndpoint(string relativePath, VelopackServiceOptions? options)
=> GetEndpoint(relativePath, options?.VelopackBaseUrl);
private static Uri GetEndpoint(string relativePath, string? velopackBaseUrl)
{
var baseUrl = velopackBaseUrl ?? VelopackServiceOptions.DefaultBaseUrl;
var endpoint = new Uri(relativePath, UriKind.Relative);
return new(new Uri(baseUrl), endpoint);
}
private void AssertAuthenticated() private void AssertAuthenticated()
{ {
if (Authorization is null) { if (Authorization is null) {
@@ -524,11 +460,4 @@ public class VelopackFlowServiceClient(
cacheHelper.RegisterCache(pca.UserTokenCache); cacheHelper.RegisterCache(pca.UserTokenCache);
return pca; return pca;
} }
private enum FileType
{
Unknown,
Release,
Installer,
}
} }

View File

@@ -0,0 +1,10 @@
namespace Velopack.Flow;
public class VelopackFlowServiceOptions
{
public string? VelopackBaseUrl { get; set; } = string.Empty;
public string? ApiKey { get; set; } = string.Empty;
public double Timeout { get; set; } = 30d;
}

View File

@@ -1,6 +1,7 @@
using ELFSharp.ELF; using ELFSharp.ELF;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Packaging.Abstractions; using Velopack.Core;
using Velopack.Core.Abstractions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging.Unix.Commands; namespace Velopack.Packaging.Unix.Commands;

View File

@@ -2,8 +2,8 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NuGet.Versioning; using NuGet.Versioning;
using Velopack.Packaging.Abstractions; using Velopack.Core;
using Velopack.Packaging.Exceptions; using Velopack.Core.Abstractions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging.Unix.Commands; namespace Velopack.Packaging.Unix.Commands;

View File

@@ -1,7 +1,6 @@
using System.Collections.Concurrent; using System.Runtime.Versioning;
using System.Runtime.Versioning;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Packaging.Abstractions; using Velopack.Core.Abstractions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging.Unix.Commands; namespace Velopack.Packaging.Unix.Commands;

View File

@@ -1,6 +1,7 @@
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Security; using System.Security;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Core;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging.Unix; namespace Velopack.Packaging.Unix;

View File

@@ -1,7 +1,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Packaging.Exceptions; using Velopack.Core;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging.Windows; namespace Velopack.Packaging.Windows;

View File

@@ -1,11 +1,9 @@
using System.IO.Compression; using System.Runtime.Versioning;
using System.Runtime.Versioning;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Compression; using Velopack.Compression;
using Velopack.Core;
using Velopack.Core.Abstractions;
using Velopack.NuGet; using Velopack.NuGet;
using Velopack.Packaging.Abstractions;
using Velopack.Packaging.Exceptions;
using Velopack.Packaging.NuGet;
using Velopack.Util; using Velopack.Util;
using Velopack.Windows; using Velopack.Windows;

View File

@@ -1,4 +1,6 @@
namespace Velopack.Packaging.Windows.Commands; using Velopack.Core;
namespace Velopack.Packaging.Windows.Commands;
public class WindowsReleasifyOptions : WindowsSigningOptions public class WindowsReleasifyOptions : WindowsSigningOptions
{ {

View File

@@ -6,8 +6,8 @@ using AsmResolver.PE.DotNet.Cil;
using AsmResolver.PE.Win32Resources.Version; using AsmResolver.PE.Win32Resources.Version;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NuGet.Versioning; using NuGet.Versioning;
using Velopack.Packaging.Abstractions; using Velopack.Core;
using Velopack.Packaging.Exceptions; using Velopack.Core.Abstractions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging.Windows; namespace Velopack.Packaging.Windows;

View File

@@ -2,5 +2,5 @@
public interface IPlatformOptions : IOutputOptions public interface IPlatformOptions : IOutputOptions
{ {
RID TargetRuntime { get; set; } RID? TargetRuntime { get; set; }
} }

View File

@@ -1,5 +1,5 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Packaging.Abstractions; using Velopack.Core.Abstractions;
namespace Velopack.Packaging.Commands; namespace Velopack.Packaging.Commands;
@@ -16,10 +16,13 @@ public class DeltaGenCommandRunner : ICommand<DeltaGenOptions>
public async Task Run(DeltaGenOptions options) public async Task Run(DeltaGenOptions options)
{ {
await _console.ExecuteProgressAsync(async (ctx) => { await _console.ExecuteProgressAsync(
async (ctx) => {
var pold = new ReleasePackage(options.BasePackage); var pold = new ReleasePackage(options.BasePackage);
var pnew = new ReleasePackage(options.NewPackage); var pnew = new ReleasePackage(options.NewPackage);
await ctx.RunTask($"Building delta {pold.Version} -> {pnew.Version}", (progress) => { await ctx.RunTask(
$"Building delta {pold.Version} -> {pnew.Version}",
(progress) => {
var delta = new DeltaPackageBuilder(_logger); var delta = new DeltaPackageBuilder(_logger);
delta.CreateDeltaPackage(pold, pnew, options.OutputFile, options.DeltaMode, progress); delta.CreateDeltaPackage(pold, pnew, options.OutputFile, options.DeltaMode, progress);
progress(100); progress(100);

View File

@@ -1,4 +1,6 @@
namespace Velopack.Packaging.Commands; using Velopack.Core;
namespace Velopack.Packaging.Commands;
public class DeltaGenOptions public class DeltaGenOptions
{ {

View File

@@ -1,7 +1,7 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Compression; using Velopack.Compression;
using Velopack.Packaging.Exceptions; using Velopack.Core;
using Velopack.Packaging.Abstractions; using Velopack.Core.Abstractions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging.Commands; namespace Velopack.Packaging.Commands;
@@ -35,15 +35,21 @@ public class DeltaPatchCommandRunner : ICommand<DeltaPatchOptions>
var delta = new DeltaEmbedded(HelperFile.GetZstdPath(), _logger, tmp); var delta = new DeltaEmbedded(HelperFile.GetZstdPath(), _logger, tmp);
EasyZip.ExtractZipToDirectory(_logger, options.BasePackage, workDir); EasyZip.ExtractZipToDirectory(_logger, options.BasePackage, workDir);
await _console.ExecuteProgressAsync(async (ctx) => { await _console.ExecuteProgressAsync(
async (ctx) => {
foreach (var f in options.PatchFiles) { foreach (var f in options.PatchFiles) {
await ctx.RunTask($"Applying {f.Name}", (progress) => { await ctx.RunTask(
$"Applying {f.Name}",
(progress) => {
delta.ApplyDeltaPackageFast(workDir, f.FullName, progress); delta.ApplyDeltaPackageFast(workDir, f.FullName, progress);
progress(100); progress(100);
return Task.CompletedTask; return Task.CompletedTask;
}); });
} }
await ctx.RunTask($"Building {Path.GetFileName(options.OutputFile)}", async (progress) => {
await ctx.RunTask(
$"Building {Path.GetFileName(options.OutputFile)}",
async (progress) => {
await EasyZip.CreateZipFromDirectoryAsync(_logger, options.OutputFile, workDir, progress); await EasyZip.CreateZipFromDirectoryAsync(_logger, options.OutputFile, workDir, progress);
progress(100); progress(100);
}); });

View File

@@ -2,6 +2,7 @@
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Velopack.Compression; using Velopack.Compression;
using Velopack.Core;
using Velopack.Packaging.Exceptions; using Velopack.Packaging.Exceptions;
using Velopack.Util; using Velopack.Util;
@@ -26,11 +27,13 @@ public class DeltaPackageBuilder
public int Removed { get; set; } public int Removed { get; set; }
} }
public (ReleasePackage package, DeltaStats stats) CreateDeltaPackage(ReleasePackage basePackage, ReleasePackage newPackage, string outputFile, DeltaMode mode, Action<int> progress) public (ReleasePackage package, DeltaStats stats) CreateDeltaPackage(ReleasePackage basePackage, ReleasePackage newPackage, string outputFile,
DeltaMode mode, Action<int> progress)
{ {
if (basePackage == null) throw new ArgumentNullException(nameof(basePackage)); if (basePackage == null) throw new ArgumentNullException(nameof(basePackage));
if (newPackage == null) throw new ArgumentNullException(nameof(newPackage)); if (newPackage == null) throw new ArgumentNullException(nameof(newPackage));
if (String.IsNullOrEmpty(outputFile) || File.Exists(outputFile)) throw new ArgumentException("The output file is null or already exists", nameof(outputFile)); if (String.IsNullOrEmpty(outputFile) || File.Exists(outputFile))
throw new ArgumentException("The output file is null or already exists", nameof(outputFile));
Zstd zstd = null; Zstd zstd = null;
try { try {
@@ -137,6 +140,7 @@ public class DeltaPackageBuilder
File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8); File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8);
Interlocked.Increment(ref fChanged); Interlocked.Increment(ref fChanged);
} }
targetFile.Delete(); targetFile.Delete();
baseLibFiles.Remove(relativePath); baseLibFiles.Remove(relativePath);
var p = Interlocked.Increment(ref fProcessed); var p = Interlocked.Increment(ref fProcessed);
@@ -152,19 +156,25 @@ public class DeltaPackageBuilder
} }
try { try {
Parallel.ForEach(newLibFiles, new ParallelOptions() { MaxDegreeOfParallelism = numParallel }, (f) => { Parallel.ForEach(
newLibFiles,
new ParallelOptions() { MaxDegreeOfParallelism = numParallel },
(f) => {
// we try to use zstd first, if it fails we'll try bsdiff // we try to use zstd first, if it fails we'll try bsdiff
if (zstd != null) { if (zstd != null) {
try { try {
createDeltaForSingleFile(f, tempInfo, true); createDeltaForSingleFile(f, tempInfo, true);
return; // success, so return from this function return; // success, so return from this function
} catch (ProcessFailedException ex) { } catch (ProcessFailedException ex) {
_logger.Error($"Failed to create zstd diff for file '{f.FullName}' (will try to fallback to legacy bsdiff format - this will be much slower). " + Environment.NewLine + ex.Message); _logger.Error(
$"Failed to create zstd diff for file '{f.FullName}' (will try to fallback to legacy bsdiff format - this will be much slower). " +
Environment.NewLine + ex.Message);
} catch (Exception ex) { } catch (Exception ex) {
_logger.Error($"Failed to create zstd diff for file '{f.FullName}'. " + Environment.NewLine + ex.Message); _logger.Error($"Failed to create zstd diff for file '{f.FullName}'. " + Environment.NewLine + ex.Message);
throw; throw;
} }
} }
// if we're here, either zstd is not available or it failed // if we're here, either zstd is not available or it failed
try { try {
createDeltaForSingleFile(f, tempInfo, false); createDeltaForSingleFile(f, tempInfo, false);
@@ -177,14 +187,16 @@ public class DeltaPackageBuilder
} }
}); });
} catch { } catch {
throw new UserInfoException("Delta creation failed for one or more files. See log for details. To skip delta generation, use the '--delta none' argument."); throw new UserInfoException(
"Delta creation failed for one or more files. See log for details. To skip delta generation, use the '--delta none' argument.");
} }
EasyZip.CreateZipFromDirectoryAsync(_logger, outputFile, tempInfo.FullName, CoreUtil.CreateProgressDelegate(progress, 70, 100)).GetAwaiterResult(); EasyZip.CreateZipFromDirectoryAsync(_logger, outputFile, tempInfo.FullName, CoreUtil.CreateProgressDelegate(progress, 70, 100)).GetAwaiterResult();
progress(100); progress(100);
fRemoved = baseLibFiles.Count; fRemoved = baseLibFiles.Count;
_logger.Info($"Delta processed {fProcessed:D4} files. " _logger.Info(
$"Delta processed {fProcessed:D4} files. "
+ $"{fChanged:D4} patched, {fSame:D4} unchanged, {fNew:D4} new, {fRemoved:D4} removed"); + $"{fChanged:D4} patched, {fSame:D4} unchanged, {fNew:D4} new, {fRemoved:D4} removed");
_logger.Debug( _logger.Debug(

View File

@@ -9,7 +9,8 @@ public class ProcessFailedException : Exception
public string StdOutput { get; } public string StdOutput { get; }
public ProcessFailedException(string command, string stdOutput, string stdErr) public ProcessFailedException(string command, string stdOutput, string stdErr)
: base($"Process failed: '{command}'{Environment.NewLine}Output was -{Environment.NewLine}{stdOutput}{Environment.NewLine}StdErr was -{Environment.NewLine}{stdErr}") : base(
$"Process failed: '{command}'{Environment.NewLine}Output was -{Environment.NewLine}{stdOutput}{Environment.NewLine}StdErr was -{Environment.NewLine}{stdErr}")
{ {
Command = command; Command = command;
StdOutput = stdOutput; StdOutput = stdOutput;

View File

@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Velopack.Core;
namespace Velopack.Packaging.Exceptions; namespace Velopack.Packaging.Exceptions;

View File

@@ -1,5 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using Velopack.Core;
using Velopack.Packaging.Exceptions; using Velopack.Packaging.Exceptions;
using Velopack.Util; using Velopack.Util;
@@ -141,7 +142,8 @@ public static class Exe
return (process.ExitCode, sOut.ToString().Trim(), sErr.ToString().Trim()); return (process.ExitCode, sOut.ToString().Trim(), sErr.ToString().Trim());
} }
public static (int ExitCode, string StdOutput, string StdErr, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct = default, public static (int ExitCode, string StdOutput, string StdErr, string Command) InvokeProcess(string fileName, IEnumerable<string> args,
string workingDirectory, CancellationToken ct = default,
IDictionary<string, string> envVar = null) IDictionary<string, string> envVar = null)
{ {
var psi = CreateProcessStartInfo(fileName, workingDirectory); var psi = CreateProcessStartInfo(fileName, workingDirectory);

View File

@@ -1,9 +0,0 @@
namespace Velopack.Packaging.Flow;
#nullable enable
public class AuthConfiguration
{
public string? B2CAuthority { get; set; }
public string? RedirectUri { get; set; }
public string? ClientId { get; set; }
}

View File

@@ -1,9 +0,0 @@
#nullable enable
namespace Velopack.Packaging.Flow;
internal sealed class CreateReleaseGroupRequest
{
public string? PackageId { get; set; }
public string? Version { get; set; }
public string? ChannelIdentifier { get; set; }
}

View File

@@ -1,14 +0,0 @@
namespace Velopack.Packaging.Flow;
#nullable enable
public class Profile
{
public string? Id { get; set; }
public string? DisplayName { get; set; }
public string? Email { get; set; }
public string? GetDisplayName()
{
return DisplayName ?? Email ?? "<unknown>";
}
}

View File

@@ -1,20 +0,0 @@
#if NET6_0_OR_GREATER
#else
using System.Net.Http;
#endif
#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<FileUpload>? FileUploads { get; set; }
}

View File

@@ -1,20 +0,0 @@
#nullable enable
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
namespace Velopack.Packaging.Flow;
internal sealed class UpdateReleaseGroupRequest
{
public string? NotesHtml { get; set; }
public string? NotesMarkdown { get; set; }
public ReleaseGroupState? State { get; set; }
}
[JsonConverter(typeof(StringEnumConverter))]
internal enum ReleaseGroupState
{
Draft,
Published,
Unlisted
}

View File

@@ -1,10 +0,0 @@
#nullable enable
namespace Velopack.Packaging.Flow;
public class UploadOptions(Stream releaseData, string fileName, string channel) : VelopackServiceOptions
{
public Stream ReleaseData { get; } = releaseData;
public string FileName { get; } = fileName;
public string Channel { get; } = channel;
}

View File

@@ -1,10 +0,0 @@
namespace Velopack.Packaging.Flow;
public class VelopackServiceOptions
{
public const string DefaultBaseUrl = "https://api.velopack.io/";
public string VelopackBaseUrl { get; set; } = DefaultBaseUrl;
public string ApiKey { get; set; } = string.Empty;
}

View File

@@ -5,9 +5,10 @@ using Markdig;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NuGet.Versioning; using NuGet.Versioning;
using Velopack.Compression; using Velopack.Compression;
using Velopack.Core;
using Velopack.Core.Abstractions;
using Velopack.NuGet; using Velopack.NuGet;
using Velopack.Packaging.Abstractions; using Velopack.Packaging.Abstractions;
using Velopack.Packaging.Exceptions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Packaging; namespace Velopack.Packaging;
@@ -44,8 +45,9 @@ public abstract class PackageBuilder<T> : ICommand<T>
options.TargetRuntime = RID.Parse(TargetOs.GetOsShortName()); options.TargetRuntime = RID.Parse(TargetOs.GetOsShortName());
} }
if (options.TargetRuntime.BaseRID != TargetOs) { if (options.TargetRuntime!.BaseRID != TargetOs) {
throw new UserInfoException($"To build packages for {TargetOs.GetOsLongName()}, " + throw new UserInfoException(
$"To build packages for {TargetOs.GetOsLongName()}, " +
$"the target rid must be {TargetOs} (actually was {options.TargetRuntime?.BaseRID}). " + $"the target rid must be {TargetOs} (actually was {options.TargetRuntime?.BaseRID}). " +
$"If your real intention was to cross-compile a release for {options.TargetRuntime?.BaseRID} then you " + $"If your real intention was to cross-compile a release for {options.TargetRuntime?.BaseRID} then you " +
$"should provide an OS directive: eg. 'vpk [{options.TargetRuntime?.BaseRID.GetOsShortName()}] pack ...'"); $"should provide an OS directive: eg. 'vpk [{options.TargetRuntime?.BaseRID.GetOsShortName()}] pack ...'");
@@ -55,13 +57,16 @@ public abstract class PackageBuilder<T> : ICommand<T>
Log.Info("Releases Directory: " + options.ReleaseDir.FullName); Log.Info("Releases Directory: " + options.ReleaseDir.FullName);
var releaseDir = options.ReleaseDir; var releaseDir = options.ReleaseDir;
var channel = options.Channel?.ToLower() ?? ReleaseEntryHelper.GetDefaultChannel(TargetOs); var channel = options.Channel?.ToLower() ?? DefaultName.GetDefaultChannel(TargetOs);
options.Channel = channel; options.Channel = channel;
var entryHelper = new ReleaseEntryHelper(releaseDir.FullName, channel, Log, TargetOs); var entryHelper = new ReleaseEntryHelper(releaseDir.FullName, channel, Log, TargetOs);
if (entryHelper.DoesSimilarVersionExist(SemanticVersion.Parse(options.PackVersion))) { if (entryHelper.DoesSimilarVersionExist(SemanticVersion.Parse(options.PackVersion))) {
if (await Console.PromptYesNo("A release in this channel with the same or greater version already exists. Do you want to continue and potentially overwrite files?") != true) { if (await Console.PromptYesNo(
throw new UserInfoException($"There is a release in channel {channel} which is equal or greater to the current version {options.PackVersion}. Please increase the current package version or remove that release."); "A release in this channel with the same or greater version already exists. Do you want to continue and potentially overwrite files?") !=
true) {
throw new UserInfoException(
$"There is a release in channel {channel} which is equal or greater to the current version {options.PackVersion}. Please increase the current package version or remove that release.");
} }
} }
@@ -85,10 +90,12 @@ public abstract class PackageBuilder<T> : ICommand<T>
break; break;
} }
} }
if (mainExePath == null) { if (mainExePath == null) {
throw new UserInfoException( throw new UserInfoException(
$"Could not find main application executable (the one that runs 'VelopackApp.Build().Run()'). " + Environment.NewLine + $"Could not find main application executable (the one that runs 'VelopackApp.Build().Run()'). " + Environment.NewLine +
$"If your main binary is not named '{mainExeName}', please specify the name with the argument: --mainExe {{yourBinary.exe}}" + Environment.NewLine + $"If your main binary is not named '{mainExeName}', please specify the name with the argument: --mainExe {{yourBinary.exe}}" +
Environment.NewLine +
$"I searched the following paths and none exist: " + Environment.NewLine + $"I searched the following paths and none exist: " + Environment.NewLine +
String.Join(Environment.NewLine, mainSearchPaths) String.Join(Environment.NewLine, mainSearchPaths)
); );
@@ -105,62 +112,83 @@ public abstract class PackageBuilder<T> : ICommand<T>
var incomplete = Path.Combine(pkgTempDir, fileName); var incomplete = Path.Combine(pkgTempDir, fileName);
var final = Path.Combine(releaseDir.FullName, fileName); var final = Path.Combine(releaseDir.FullName, fileName);
try { File.Delete(incomplete); } catch { } try { File.Delete(incomplete); } catch { }
filesToCopy.Add((incomplete, final)); filesToCopy.Add((incomplete, final));
return incomplete; return incomplete;
} }
await Console.ExecuteProgressAsync(async (ctx) => { await Console.ExecuteProgressAsync(
async (ctx) => {
ReleasePackage prev = null; ReleasePackage prev = null;
await ctx.RunTask("Pre-process steps", async (progress) => { await ctx.RunTask(
"Pre-process steps",
async (progress) => {
prev = entryHelper.GetPreviousFullRelease(NuGetVersion.Parse(packVersion)); prev = entryHelper.GetPreviousFullRelease(NuGetVersion.Parse(packVersion));
packDirectory = await PreprocessPackDir(progress, packDirectory); packDirectory = await PreprocessPackDir(progress, packDirectory);
}); });
if (TargetOs != RuntimeOs.Linux) { if (TargetOs != RuntimeOs.Linux) {
await ctx.RunTask("Code-sign application", async (progress) => { await ctx.RunTask(
"Code-sign application",
async (progress) => {
await CodeSign(progress, packDirectory); await CodeSign(progress, packDirectory);
}); });
} }
Task portableTask = null; Task portableTask = null;
if (TargetOs == RuntimeOs.Linux || !Options.NoPortable) { if (TargetOs == RuntimeOs.Linux || !Options.NoPortable) {
portableTask = ctx.RunTask("Building portable package", async (progress) => { portableTask = ctx.RunTask(
var suggestedName = ReleaseEntryHelper.GetSuggestedPortableName(packId, channel, TargetOs); "Building portable package",
async (progress) => {
var suggestedName = DefaultName.GetSuggestedPortableName(packId, channel, TargetOs);
var path = getIncompletePath(suggestedName); var path = getIncompletePath(suggestedName);
await CreatePortablePackage(progress, packDirectory, path); await CreatePortablePackage(progress, packDirectory, path);
}); });
} }
// TODO: hack, this is a prerequisite for building full package but only on linux // TODO: hack, this is a prerequisite for building full package but only on linux
if (TargetOs == RuntimeOs.Linux) await portableTask; if (TargetOs == RuntimeOs.Linux) await portableTask!;
string releasePath = null; string releasePath = null;
await ctx.RunTask($"Building release {packVersion}", async (progress) => { await ctx.RunTask(
var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, false, TargetOs); $"Building release {packVersion}",
async (progress) => {
var suggestedName = DefaultName.GetSuggestedReleaseName(packId, packVersion, channel, false, TargetOs);
releasePath = getIncompletePath(suggestedName); releasePath = getIncompletePath(suggestedName);
await CreateReleasePackage(progress, packDirectory, releasePath); await CreateReleasePackage(progress, packDirectory, releasePath);
}); });
Task setupTask = null; Task setupTask = null;
if (!Options.NoInst && TargetOs != RuntimeOs.Linux) { if (!Options.NoInst && TargetOs != RuntimeOs.Linux) {
setupTask = ctx.RunTask("Building setup package", async (progress) => { setupTask = ctx.RunTask(
var suggestedName = ReleaseEntryHelper.GetSuggestedSetupName(packId, channel, TargetOs); "Building setup package",
async (progress) => {
var suggestedName = DefaultName.GetSuggestedSetupName(packId, channel, TargetOs);
var path = getIncompletePath(suggestedName); var path = getIncompletePath(suggestedName);
await CreateSetupPackage(progress, releasePath, packDirectory, path); await CreateSetupPackage(progress, releasePath, packDirectory, path);
}); });
} }
if (prev != null && options.DeltaMode != DeltaMode.None) { if (prev != null && options.DeltaMode != DeltaMode.None) {
await ctx.RunTask($"Building delta {prev.Version} -> {packVersion}", async (progress) => { await ctx.RunTask(
var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, true, TargetOs); $"Building delta {prev.Version} -> {packVersion}",
var deltaPkg = await CreateDeltaPackage(progress, releasePath, prev.PackageFile, getIncompletePath(suggestedName), options.DeltaMode); async (progress) => {
var suggestedName = DefaultName.GetSuggestedReleaseName(packId, packVersion, channel, true, TargetOs);
var deltaPkg = await CreateDeltaPackage(
progress,
releasePath,
prev.PackageFile,
getIncompletePath(suggestedName),
options.DeltaMode);
}); });
} }
if (TargetOs != RuntimeOs.Linux && portableTask != null) await portableTask; if (TargetOs != RuntimeOs.Linux && portableTask != null) await portableTask;
if (setupTask != null) await setupTask; if (setupTask != null) await setupTask;
await ctx.RunTask("Post-process steps", (progress) => { await ctx.RunTask(
"Post-process steps",
(progress) => {
foreach (var f in filesToCopy) { foreach (var f in filesToCopy) {
IoUtil.MoveFile(f.from, f.to, true); IoUtil.MoveFile(f.from, f.to, true);
} }
@@ -187,12 +215,14 @@ public abstract class PackageBuilder<T> : ICommand<T>
var rid = Options.TargetRuntime; var rid = Options.TargetRuntime;
string extraMetadata = ""; string extraMetadata = "";
void addMetadata(string key, string value) void addMetadata(string key, string value)
{ {
if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value)) { if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value)) {
if (!SecurityElement.IsValidText(value)) { if (!SecurityElement.IsValidText(value)) {
value = $"""<![CDATA[{"\n"}{value}{"\n"}]]>"""; value = $"""<![CDATA[{"\n"}{value}{"\n"}]]>""";
} }
extraMetadata += $"<{key}>{value}</{key}>{Environment.NewLine}"; extraMetadata += $"<{key}>{value}</{key}>{Environment.NewLine}";
} }
} }
@@ -315,6 +345,7 @@ public abstract class PackageBuilder<T> : ICommand<T>
Log.Debug("Skipping because matched exclude pattern: " + path); Log.Debug("Skipping because matched exclude pattern: " + path);
continue; continue;
} }
fileInfo.CopyTo(path, true); fileInfo.CopyTo(path, true);
} }

View File

@@ -1,6 +1,7 @@
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NuGet.Versioning; using NuGet.Versioning;
using Velopack.Core;
using Velopack.NuGet; using Velopack.NuGet;
using Velopack.Util; using Velopack.Util;
@@ -17,7 +18,7 @@ public class ReleaseEntryHelper
{ {
_outputDir = outputDir; _outputDir = outputDir;
_logger = logger; _logger = logger;
_channel = channel ?? GetDefaultChannel(os); _channel = channel ?? DefaultName.GetDefaultChannel(os);
_releases = GetReleasesFromDir(outputDir); _releases = GetReleasesFromDir(outputDir);
} }
@@ -26,11 +27,12 @@ public class ReleaseEntryHelper
var rel = new Dictionary<string, List<VelopackAsset>>(StringComparer.OrdinalIgnoreCase); var rel = new Dictionary<string, List<VelopackAsset>>(StringComparer.OrdinalIgnoreCase);
foreach (var releaseFile in Directory.EnumerateFiles(dir, "*.nupkg")) { foreach (var releaseFile in Directory.EnumerateFiles(dir, "*.nupkg")) {
var zip = new ZipPackage(releaseFile); var zip = new ZipPackage(releaseFile);
var ch = zip.Channel ?? GetDefaultChannel(VelopackRuntimeInfo.SystemOs); var ch = zip.Channel ?? DefaultName.GetDefaultChannel(VelopackRuntimeInfo.SystemOs);
if (!rel.ContainsKey(ch)) if (!rel.ContainsKey(ch))
rel[ch] = new List<VelopackAsset>(); rel[ch] = new List<VelopackAsset>();
rel[ch].Add(VelopackAsset.FromZipPackage(zip)); rel[ch].Add(VelopackAsset.FromZipPackage(zip));
} }
return rel; return rel;
} }
@@ -43,6 +45,7 @@ public class ReleaseEntryHelper
return true; return true;
} }
} }
return false; return false;
} }
@@ -94,10 +97,12 @@ public class ReleaseEntryHelper
foreach (var releaseFile in Directory.EnumerateFiles(outputDir, "RELEASES*")) { foreach (var releaseFile in Directory.EnumerateFiles(outputDir, "RELEASES*")) {
File.Delete(releaseFile); File.Delete(releaseFile);
} }
foreach (var kvp in releases) { foreach (var kvp in releases) {
var exclude = kvp.Value.Where(x => x.Version.ReleaseLabels.Any(r => r.Contains('.')) || x.Version.HasMetadata).ToArray(); var exclude = kvp.Value.Where(x => x.Version.ReleaseLabels.Any(r => r.Contains('.')) || x.Version.HasMetadata).ToArray();
if (exclude.Any()) { if (exclude.Any()) {
log.Warn($"Excluding {exclude.Length} asset(s) from legacy RELEASES file, because they " + log.Warn(
$"Excluding {exclude.Length} asset(s) from legacy RELEASES file, because they " +
$"contain an invalid character in the version: {string.Join(", ", exclude.Select(x => x.FileName))}"); $"contain an invalid character in the version: {string.Join(", ", exclude.Select(x => x.FileName))}");
} }
@@ -151,54 +156,6 @@ public class ReleaseEntryHelper
return Encoding.UTF8.GetString(ms.ToArray()); return Encoding.UTF8.GetString(ms.ToArray());
} }
public static string GetSuggestedReleaseName(string id, string version, string channel, bool delta, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
version = SemanticVersion.Parse(version).ToNormalizedString();
if (os == RuntimeOs.Windows && channel == GetDefaultChannel(RuntimeOs.Windows)) {
return $"{id}-{version}{(delta ? "-delta" : "-full")}.nupkg";
}
return $"{id}-{version}{suffix}{(delta ? "-delta" : "-full")}.nupkg";
}
public static string GetSuggestedPortableName(string id, string channel, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
if (os == RuntimeOs.Linux) {
if (channel == GetDefaultChannel(RuntimeOs.Linux)) {
return $"{id}.AppImage";
} else {
return $"{id}{suffix}.AppImage";
}
} else {
return $"{id}{suffix}-Portable.zip";
}
}
public static string GetSuggestedSetupName(string id, string channel, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
if (os == RuntimeOs.Windows)
return $"{id}{suffix}-Setup.exe";
else if (os == RuntimeOs.OSX)
return $"{id}{suffix}-Setup.pkg";
else
throw new PlatformNotSupportedException("Platform not supported.");
}
private static string GetUniqueAssetSuffix(string channel)
{
return "-" + channel;
}
public static string GetDefaultChannel(RuntimeOs os)
{
if (os == RuntimeOs.Windows) return "win";
if (os == RuntimeOs.OSX) return "osx";
if (os == RuntimeOs.Linux) return "linux";
throw new NotSupportedException("Unsupported OS: " + os);
}
public enum AssetsMode public enum AssetsMode
{ {
AllPackages, AllPackages,
@@ -267,6 +224,4 @@ public class ReleaseEntryHelper
// return ret; // return ret;
//} //}
} }

View File

@@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\lib-csharp\Velopack.csproj" /> <ProjectReference Include="..\..\lib-csharp\Velopack.csproj" />
<ProjectReference Include="..\Velopack.Core\Velopack.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,4 +1,5 @@
using Velopack.Packaging.Exceptions; using Velopack.Core;
using Velopack.Packaging.Exceptions;
namespace Velopack.Packaging; namespace Velopack.Packaging;
@@ -30,7 +31,8 @@ public class Zstd
} }
if (windowLog > 30) { if (windowLog > 30) {
throw new UserInfoException($"The file '{Path.GetFileName(oldFile)}' is too large for delta compression. You can disable delta generation using '--delta none'."); throw new UserInfoException(
$"The file '{Path.GetFileName(oldFile)}' is too large for delta compression. You can disable delta generation using '--delta none'.");
} }
if (mode == DeltaMode.BestSize) { if (mode == DeltaMode.BestSize) {
@@ -64,7 +66,11 @@ public class Zstd
{ {
int count = 0; int count = 0;
v >>= 1; v >>= 1;
while (v > 0) { v >>= 1; count++; } while (v > 0) {
v >>= 1;
count++;
}
return count; return count;
} }
} }

View File

@@ -1,4 +1,5 @@
using Velopack.Packaging; using Velopack.Core;
using Velopack.Packaging;
namespace Velopack.Vpk.Commands; namespace Velopack.Vpk.Commands;

View File

@@ -1,26 +0,0 @@
using System.Threading;
using Serilog.Core;
using Velopack.Packaging.Abstractions;
using Velopack.Packaging.Flow;
namespace Velopack.Vpk.Commands.Flow;
public class ApiCommandRunner(IVelopackFlowServiceClient Client) : ICommand<ApiOptions>
{
public async Task Run(ApiOptions options)
{
CancellationToken token = CancellationToken.None;
if (!await Client.LoginAsync(new VelopackLoginOptions() {
AllowCacheCredentials = true,
AllowDeviceCodeFlow = false,
AllowInteractiveLogin = false,
ApiKey = options.ApiKey,
VelopackBaseUrl = options.VelopackBaseUrl
}, true, token)) {
return;
}
string response = await Client.InvokeEndpointAsync(options, options.Endpoint, options.Method, options.Body, token);
Console.WriteLine(response);
}
}

View File

@@ -1,11 +0,0 @@
#nullable enable
using Velopack.Packaging.Flow;
namespace Velopack.Vpk.Commands.Flow;
public sealed class ApiOptions : VelopackServiceOptions
{
public string Endpoint { get; set; } = "";
public string Method { get; set; } = "";
public string? Body { get; set; }
}

View File

@@ -1,17 +0,0 @@
using System.Threading;
using Velopack.Packaging.Abstractions;
using Velopack.Packaging.Flow;
namespace Velopack.Vpk.Commands.Flow;
#nullable enable
public class LoginCommandRunner(IVelopackFlowServiceClient Client) : ICommand<LoginOptions>
{
public async Task Run(LoginOptions options)
{
await Client.LoginAsync(new() {
VelopackBaseUrl = options.VelopackBaseUrl,
ApiKey = options.ApiKey,
}, false, CancellationToken.None);
}
}

View File

@@ -1,5 +0,0 @@
using Velopack.Packaging.Flow;
namespace Velopack.Vpk.Commands.Flow;
public sealed class LoginOptions : VelopackServiceOptions;

View File

@@ -1,14 +0,0 @@
using System.Threading;
using Velopack.Packaging.Abstractions;
using Velopack.Packaging.Flow;
#nullable enable
namespace Velopack.Vpk.Commands.Flow;
internal class LogoutCommandRunner(IVelopackFlowServiceClient Client) : ICommand<LogoutOptions>
{
public async Task Run(LogoutOptions options)
{
await Client.LogoutAsync(options, CancellationToken.None);
}
}

View File

@@ -1,5 +0,0 @@
using Velopack.Packaging.Flow;
namespace Velopack.Vpk.Commands.Flow;
public sealed class LogoutOptions : VelopackServiceOptions;

View File

@@ -7,7 +7,7 @@ public class PublishCommand : VelopackServiceCommand
public string? Channel { get; set; } public string? Channel { get; set; }
public bool NoWaitForLive { get; set; } public bool WaitForLive { get; set; }
public PublishCommand() public PublishCommand()
: base("publish", "Uploads a release to Velopack's hosted service") : base("publish", "Uploads a release to Velopack's hosted service")
@@ -21,7 +21,7 @@ public class PublishCommand : VelopackServiceCommand
.SetArgumentHelpName("NAME") .SetArgumentHelpName("NAME")
.SetDescription("The channel used for the release."); .SetDescription("The channel used for the release.");
AddOption<bool>(v => NoWaitForLive = v, "--noWaitForLive") AddOption<bool>(v => WaitForLive = v, "--waitForLive")
.SetDescription("Skip waiting for the release to finish processing and go live."); .SetDescription("Wait for the release to finish processing and go live.");
} }
} }

View File

@@ -1,25 +0,0 @@
using System.Threading;
using Velopack.Packaging.Abstractions;
using Velopack.Packaging.Flow;
namespace Velopack.Vpk.Commands.Flow;
public class PublishCommandRunner(IVelopackFlowServiceClient Client) : ICommand<PublishOptions>
{
public async Task Run(PublishOptions options)
{
CancellationToken token = CancellationToken.None;
if (!await Client.LoginAsync(new VelopackLoginOptions() {
AllowCacheCredentials = true,
AllowDeviceCodeFlow = false,
AllowInteractiveLogin = false,
ApiKey = options.ApiKey,
VelopackBaseUrl = options.VelopackBaseUrl
}, false, token)) {
return;
}
await Client.UploadLatestReleaseAssetsAsync(options.Channel, options.ReleaseDirectory,
options.VelopackBaseUrl, options.TargetOs, options.NoWaitForLive, token);
}
}

View File

@@ -1,15 +0,0 @@
using Velopack.Packaging.Flow;
namespace Velopack.Vpk.Commands.Flow;
#nullable enable
public sealed class PublishOptions : VelopackServiceOptions
{
public RuntimeOs TargetOs { get; set; }
public string ReleaseDirectory { get; set; } = "";
public string? Channel { get; set; }
public bool NoWaitForLive { get; set; }
}

View File

@@ -1,6 +1,4 @@
using Velopack.Packaging.Flow; namespace Velopack.Vpk.Commands.Flow;
namespace Velopack.Vpk.Commands.Flow;
public abstract class VelopackServiceCommand : BaseCommand public abstract class VelopackServiceCommand : BaseCommand
{ {
@@ -8,16 +6,22 @@ public abstract class VelopackServiceCommand : BaseCommand
public string ApiKey { get; private set; } public string ApiKey { get; private set; }
public double Timeout { get; private set; }
protected VelopackServiceCommand(string name, string description) protected VelopackServiceCommand(string name, string description)
: base(name, description) : base(name, description)
{ {
AddOption<string>(v => VelopackBaseUrl = v, "--baseUrl") AddOption<string>(v => VelopackBaseUrl = v, "--baseUrl")
.SetDescription("The base Uri for the Velopack API service.") .SetDescription("The base Uri for the Velopack API service.")
.SetArgumentHelpName("URI") .SetArgumentHelpName("URI");
.SetDefault(VelopackServiceOptions.DefaultBaseUrl);
AddOption<string>(v => ApiKey = v, "--api-key") AddOption<string>(v => ApiKey = v, "--api-key")
.SetDescription("The API key to use to authenticate with Velopack API service.") .SetDescription("The API key to use to authenticate with Velopack API service.")
.SetArgumentHelpName("ApiKey"); .SetArgumentHelpName("ApiKey");
AddOption<double>((v) => Timeout = v, "--timeout")
.SetDescription("Network timeout in minutes.")
.SetArgumentHelpName("MINUTES")
.SetDefault(30);
} }
} }

View File

@@ -1,4 +1,5 @@
using Velopack.Packaging; using Velopack.Core;
using Velopack.Packaging;
namespace Velopack.Vpk.Commands; namespace Velopack.Vpk.Commands;
@@ -24,7 +25,7 @@ public abstract class OutputCommand : BaseCommand
.SetDescription("The channel to use for this release.") .SetDescription("The channel to use for this release.")
.RequiresValidNuGetId() .RequiresValidNuGetId()
.SetArgumentHelpName("NAME") .SetArgumentHelpName("NAME")
.SetDefault(ReleaseEntryHelper.GetDefaultChannel(targetOs == RuntimeOs.Unknown ? VelopackRuntimeInfo.SystemOs : targetOs)); .SetDefault(DefaultName.GetDefaultChannel(targetOs == RuntimeOs.Unknown ? VelopackRuntimeInfo.SystemOs : targetOs));
} }
public DirectoryInfo GetReleaseDirectory() public DirectoryInfo GetReleaseDirectory()

View File

@@ -1,4 +1,4 @@
using Velopack.Packaging.Abstractions; using Velopack.Core.Abstractions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Vpk.Logging; namespace Velopack.Vpk.Logging;

View File

@@ -1,6 +1,6 @@
using System.Threading; using System.Threading;
using Spectre.Console; using Spectre.Console;
using Velopack.Packaging.Abstractions; using Velopack.Core.Abstractions;
using Velopack.Util; using Velopack.Util;
namespace Velopack.Vpk.Logging; namespace Velopack.Vpk.Logging;

View File

@@ -1,5 +1,6 @@
using Riok.Mapperly.Abstractions; using Riok.Mapperly.Abstractions;
using Velopack.Deployment; using Velopack.Deployment;
using Velopack.Flow.Commands;
using Velopack.Packaging.Commands; using Velopack.Packaging.Commands;
using Velopack.Packaging.Unix.Commands; using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands; using Velopack.Packaging.Windows.Commands;

View File

@@ -5,11 +5,13 @@ using Microsoft.Extensions.Hosting;
using Serilog; using Serilog;
using Serilog.Core; using Serilog.Core;
using Serilog.Events; using Serilog.Events;
using Velopack.Core;
using Velopack.Core.Abstractions;
using Velopack.Deployment; using Velopack.Deployment;
using Velopack.Packaging.Abstractions; using Velopack.Flow;
using Velopack.Flow.Commands;
using Velopack.Packaging.Commands; using Velopack.Packaging.Commands;
using Velopack.Packaging.Exceptions; using Velopack.Packaging.Exceptions;
using Velopack.Packaging.Flow;
using Velopack.Packaging.Unix.Commands; using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands; using Velopack.Packaging.Windows.Commands;
using Velopack.Util; using Velopack.Util;
@@ -94,7 +96,6 @@ public class Program
SetupConfig(builder); SetupConfig(builder);
SetupLogging(builder, verbose, legacyConsole); SetupLogging(builder, verbose, legacyConsole);
SetupVelopackService(builder.Services);
RuntimeOs targetOs = VelopackRuntimeInfo.SystemOs; RuntimeOs targetOs = VelopackRuntimeInfo.SystemOs;
if (new bool[] { directiveWin, directiveLinux, directiveOsx }.Count(x => x) > 1) { if (new bool[] { directiveWin, directiveLinux, directiveOsx }.Count(x => x) > 1) {
@@ -208,15 +209,6 @@ public class Program
Log.Logger = conf.CreateLogger(); Log.Logger = conf.CreateLogger();
builder.Logging.AddSerilog(); builder.Logging.AddSerilog();
} }
private static void SetupVelopackService(IServiceCollection services)
{
services.AddSingleton<IVelopackFlowServiceClient, VelopackFlowServiceClient>();
services.AddSingleton<HmacAuthHttpClientHandler>();
services.AddHttpClient().ConfigureHttpClientDefaults(x =>
x.AddHttpMessageHandler<HmacAuthHttpClientHandler>()
.ConfigureHttpClient(httpClient => httpClient.Timeout = TimeSpan.FromMinutes(60)));
}
} }
public static class ProgramCommandExtensions public static class ProgramCommandExtensions

View File

@@ -33,6 +33,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Velopack.Deployment\Velopack.Deployment.csproj" /> <ProjectReference Include="..\Velopack.Deployment\Velopack.Deployment.csproj" />
<ProjectReference Include="..\Velopack.Flow\Velopack.Flow.csproj" />
<ProjectReference Include="..\Velopack.Packaging.Unix\Velopack.Packaging.Unix.csproj" /> <ProjectReference Include="..\Velopack.Packaging.Unix\Velopack.Packaging.Unix.csproj" />
<ProjectReference Include="..\Velopack.Packaging.Windows\Velopack.Packaging.Windows.csproj" /> <ProjectReference Include="..\Velopack.Packaging.Windows\Velopack.Packaging.Windows.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,5 @@
using Neovolve.Logging.Xunit; using Neovolve.Logging.Xunit;
using Velopack.Core;
using Velopack.Packaging.Exceptions; using Velopack.Packaging.Exceptions;
using Velopack.Packaging.Windows; using Velopack.Packaging.Windows;
using Velopack.Util; using Velopack.Util;

View File

@@ -1,6 +1,7 @@
using Velopack.Deployment; using Velopack.Deployment;
using Velopack.Sources; using Velopack.Sources;
using Octokit; using Octokit;
using Velopack.Core;
using Velopack.Packaging.Exceptions; using Velopack.Packaging.Exceptions;
using Velopack.Util; using Velopack.Util;

View File

@@ -5,6 +5,7 @@ using System.Xml.Linq;
using Microsoft.Win32; using Microsoft.Win32;
using NuGet.Packaging; using NuGet.Packaging;
using Velopack.Compression; using Velopack.Compression;
using Velopack.Core;
using Velopack.Packaging.Commands; using Velopack.Packaging.Commands;
using Velopack.Packaging.Exceptions; using Velopack.Packaging.Exceptions;
using Velopack.Packaging.Windows.Commands; using Velopack.Packaging.Windows.Commands;

View File

@@ -1,7 +1,7 @@
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
#pragma warning disable CS0612 // Type or member is obsolete #pragma warning disable CS0612 // Type or member is obsolete
using System.Text; using System.Text;
using Velopack.Packaging; using Velopack.Core;
using Velopack.Sources; using Velopack.Sources;
using Velopack.Util; using Velopack.Util;
@@ -39,7 +39,8 @@ internal class FakeFixtureRepository : Sources.IFileDownloader
var name = new ReleaseEntryName(maxfullVer.PackageId, maxDeltaVer.Version, false); var name = new ReleaseEntryName(maxfullVer.PackageId, maxDeltaVer.Version, false);
releases.Add(new ReleaseEntry("0000000000000000000000000000000000000000", name.ToFileName(), maxfullVer.Filesize)); releases.Add(new ReleaseEntry("0000000000000000000000000000000000000000", name.ToFileName(), maxfullVer.Filesize));
releasesNew.Add(new VelopackAsset { releasesNew.Add(
new VelopackAsset {
PackageId = maxfullVer.PackageId, PackageId = maxfullVer.PackageId,
Version = maxDeltaVer.Version, Version = maxDeltaVer.Version,
Type = VelopackAssetType.Full, Type = VelopackAssetType.Full,
@@ -80,7 +81,8 @@ internal class FakeFixtureRepository : Sources.IFileDownloader
return Task.FromResult(File.ReadAllBytes(filePath)); return Task.FromResult(File.ReadAllBytes(filePath));
} }
public Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization = null, string accept = null, double timeout = 30, CancellationToken token = default) public Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization = null, string accept = null, double timeout = 30,
CancellationToken token = default)
{ {
var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename)); var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
var filePath = PathHelper.GetFixture(rel.OriginalFilename); var filePath = PathHelper.GetFixture(rel.OriginalFilename);

View File

@@ -1,7 +1,7 @@
using System.Text; using System.Text;
using NuGet.Versioning; using NuGet.Versioning;
using Velopack.Compression; using Velopack.Compression;
using Velopack.Packaging; using Velopack.Core;
using Velopack.Locators; using Velopack.Locators;
using Velopack.Sources; using Velopack.Sources;
using Velopack.Tests.TestHelpers; using Velopack.Tests.TestHelpers;

View File

@@ -19,7 +19,7 @@
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="..\..\src\vpk\Velopack.Packaging\SimpleJson.cs" Link="SimpleJson.cs" /> <Compile Include="..\..\src\vpk\Velopack.Core\SimpleJson.cs" Link="SimpleJson.cs"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>