mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Convert all files to file-scoped name space
This commit is contained in:
@@ -7,31 +7,30 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging.Unix
|
||||
namespace Velopack.Packaging.Unix;
|
||||
|
||||
public class AppImageTool
|
||||
{
|
||||
public class AppImageTool
|
||||
[SupportedOSPlatform("linux")]
|
||||
public static void CreateLinuxAppImage(string appDir, string outputFile, RuntimeCpu machine, ILogger logger)
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
public static void CreateLinuxAppImage(string appDir, string outputFile, RuntimeCpu machine, ILogger logger)
|
||||
{
|
||||
var tool = HelperFile.AppImageToolX64;
|
||||
var tool = HelperFile.AppImageToolX64;
|
||||
|
||||
string arch = machine switch {
|
||||
RuntimeCpu.x86 => "i386",
|
||||
RuntimeCpu.x64 => "x86_64",
|
||||
RuntimeCpu.arm64 => "arm_aarch64",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(machine), machine, null)
|
||||
};
|
||||
string arch = machine switch {
|
||||
RuntimeCpu.x86 => "i386",
|
||||
RuntimeCpu.x64 => "x86_64",
|
||||
RuntimeCpu.arm64 => "arm_aarch64",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(machine), machine, null)
|
||||
};
|
||||
|
||||
var envVar = new Dictionary<string, string>() {
|
||||
{ "ARCH", arch }
|
||||
};
|
||||
|
||||
logger.Info("About to create .AppImage for architecture: " + arch);
|
||||
var envVar = new Dictionary<string, string>() {
|
||||
{ "ARCH", arch }
|
||||
};
|
||||
|
||||
logger.Info("About to create .AppImage for architecture: " + arch);
|
||||
|
||||
Chmod.ChmodFileAsExecutable(tool);
|
||||
Exe.InvokeAndThrowIfNonZero(tool, new[] { appDir, outputFile }, null, envVar);
|
||||
Chmod.ChmodFileAsExecutable(outputFile);
|
||||
}
|
||||
Chmod.ChmodFileAsExecutable(tool);
|
||||
Exe.InvokeAndThrowIfNonZero(tool, new[] { appDir, outputFile }, null, envVar);
|
||||
Chmod.ChmodFileAsExecutable(outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,44 +8,44 @@ using ELFSharp.ELF;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Packaging.Abstractions;
|
||||
|
||||
namespace Velopack.Packaging.Unix.Commands
|
||||
namespace Velopack.Packaging.Unix.Commands;
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
public class LinuxPackCommandRunner : PackageBuilder<LinuxPackOptions>
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
public class LinuxPackCommandRunner : PackageBuilder<LinuxPackOptions>
|
||||
protected string PortablePackagePath { get; set; }
|
||||
|
||||
public LinuxPackCommandRunner(ILogger logger, IFancyConsole console)
|
||||
: base(RuntimeOs.Linux, logger, console)
|
||||
{
|
||||
protected string PortablePackagePath { get; set; }
|
||||
}
|
||||
|
||||
public LinuxPackCommandRunner(ILogger logger, IFancyConsole console)
|
||||
: base(RuntimeOs.Linux, logger, console)
|
||||
{
|
||||
}
|
||||
protected override Task<string> PreprocessPackDir(Action<int> progress, string packDir)
|
||||
{
|
||||
var dir = TempDir.CreateSubdirectory("PreprocessPackDir.AppDir");
|
||||
var bin = dir.CreateSubdirectory("usr").CreateSubdirectory("bin");
|
||||
|
||||
protected override Task<string> PreprocessPackDir(Action<int> progress, string packDir)
|
||||
{
|
||||
var dir = TempDir.CreateSubdirectory("PreprocessPackDir.AppDir");
|
||||
var bin = dir.CreateSubdirectory("usr").CreateSubdirectory("bin");
|
||||
|
||||
if (Options.PackIsAppDir) {
|
||||
Log.Info("Using provided .AppDir, will skip building new one.");
|
||||
CopyFiles(new DirectoryInfo(Options.PackDirectory), dir, progress, true);
|
||||
} else {
|
||||
Log.Info("Building new .AppDir");
|
||||
var appRunPath = Path.Combine(dir.FullName, "AppRun");
|
||||
File.WriteAllText(appRunPath, """
|
||||
if (Options.PackIsAppDir) {
|
||||
Log.Info("Using provided .AppDir, will skip building new one.");
|
||||
CopyFiles(new DirectoryInfo(Options.PackDirectory), dir, progress, true);
|
||||
} else {
|
||||
Log.Info("Building new .AppDir");
|
||||
var appRunPath = Path.Combine(dir.FullName, "AppRun");
|
||||
File.WriteAllText(appRunPath, """
|
||||
#!/bin/sh
|
||||
HERE="$(dirname "$(readlink -f "${0}")")"
|
||||
export PATH="${HERE}"/usr/bin/:"${PATH}"
|
||||
EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1)
|
||||
exec "${EXEC}" $@
|
||||
""");
|
||||
Chmod.ChmodFileAsExecutable(appRunPath);
|
||||
Chmod.ChmodFileAsExecutable(appRunPath);
|
||||
|
||||
var mainExeName = Options.EntryExecutableName ?? Options.PackId;
|
||||
var mainExePath = Path.Combine(packDir, mainExeName);
|
||||
if (!File.Exists(mainExePath))
|
||||
throw new Exception($"Could not find main executable at '{mainExePath}'. Please specify with --exeName.");
|
||||
var mainExeName = Options.EntryExecutableName ?? Options.PackId;
|
||||
var mainExePath = Path.Combine(packDir, mainExeName);
|
||||
if (!File.Exists(mainExePath))
|
||||
throw new Exception($"Could not find main executable at '{mainExePath}'. Please specify with --exeName.");
|
||||
|
||||
File.WriteAllText(Path.Combine(dir.FullName, Options.PackId + ".desktop"), $"""
|
||||
File.WriteAllText(Path.Combine(dir.FullName, Options.PackId + ".desktop"), $"""
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name={Options.PackTitle ?? Options.PackId}
|
||||
@@ -56,56 +56,55 @@ Path=~
|
||||
Categories=Development;
|
||||
""");
|
||||
|
||||
// copy existing app files
|
||||
CopyFiles(new DirectoryInfo(packDir), bin, progress, true);
|
||||
// app icon
|
||||
File.Copy(Options.Icon, Path.Combine(dir.FullName, Options.PackId + Path.GetExtension(Options.Icon)), true);
|
||||
}
|
||||
|
||||
// velopack required files
|
||||
File.WriteAllText(Path.Combine(bin.FullName, "sq.version"), GenerateNuspecContent());
|
||||
File.Copy(HelperFile.GetUpdatePath(), Path.Combine(bin.FullName, "UpdateNix"), true);
|
||||
progress(100);
|
||||
return Task.FromResult(dir.FullName);
|
||||
// copy existing app files
|
||||
CopyFiles(new DirectoryInfo(packDir), bin, progress, true);
|
||||
// app icon
|
||||
File.Copy(Options.Icon, Path.Combine(dir.FullName, Options.PackId + Path.GetExtension(Options.Icon)), true);
|
||||
}
|
||||
|
||||
protected override Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath)
|
||||
{
|
||||
progress(-1);
|
||||
var machine = Options.TargetRuntime.HasArchitecture
|
||||
? Options.TargetRuntime.Architecture
|
||||
: GetMachineForBinary(MainExePath);
|
||||
AppImageTool.CreateLinuxAppImage(packDir, outputPath, machine, Log);
|
||||
PortablePackagePath = outputPath;
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
// velopack required files
|
||||
File.WriteAllText(Path.Combine(bin.FullName, "sq.version"), GenerateNuspecContent());
|
||||
File.Copy(HelperFile.GetUpdatePath(), Path.Combine(bin.FullName, "UpdateNix"), true);
|
||||
progress(100);
|
||||
return Task.FromResult(dir.FullName);
|
||||
}
|
||||
|
||||
protected virtual RuntimeCpu GetMachineForBinary(string path)
|
||||
{
|
||||
var elf = ELFReader.Load(path);
|
||||
protected override Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath)
|
||||
{
|
||||
progress(-1);
|
||||
var machine = Options.TargetRuntime.HasArchitecture
|
||||
? Options.TargetRuntime.Architecture
|
||||
: GetMachineForBinary(MainExePath);
|
||||
AppImageTool.CreateLinuxAppImage(packDir, outputPath, machine, Log);
|
||||
PortablePackagePath = outputPath;
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var machine = elf.Machine switch {
|
||||
Machine.AArch64 => RuntimeCpu.arm64,
|
||||
Machine.AMD64 => RuntimeCpu.x64,
|
||||
Machine.Intel386 => RuntimeCpu.x86,
|
||||
_ => throw new Exception($"Unsupported ELF machine type '{elf.Machine}'.")
|
||||
};
|
||||
protected virtual RuntimeCpu GetMachineForBinary(string path)
|
||||
{
|
||||
var elf = ELFReader.Load(path);
|
||||
|
||||
return machine;
|
||||
}
|
||||
var machine = elf.Machine switch {
|
||||
Machine.AArch64 => RuntimeCpu.arm64,
|
||||
Machine.AMD64 => RuntimeCpu.x64,
|
||||
Machine.Intel386 => RuntimeCpu.x86,
|
||||
_ => throw new Exception($"Unsupported ELF machine type '{elf.Machine}'.")
|
||||
};
|
||||
|
||||
protected override Task CreateReleasePackage(Action<int> progress, string packDir, string outputPath)
|
||||
{
|
||||
var dir = TempDir.CreateSubdirectory("CreateReleasePackage.Linux");
|
||||
File.Copy(PortablePackagePath, Path.Combine(dir.FullName, Options.PackId + ".AppImage"), true);
|
||||
return base.CreateReleasePackage(progress, dir.FullName, outputPath);
|
||||
}
|
||||
return machine;
|
||||
}
|
||||
|
||||
protected override Task<string> CreateDeltaPackage(Action<int> progress, string releasePkg, string prevReleasePkg, string outputPkg, DeltaMode mode)
|
||||
{
|
||||
progress(-1); // there is only one "file", so progress will not work
|
||||
return base.CreateDeltaPackage(progress, releasePkg, prevReleasePkg, outputPkg, mode);
|
||||
}
|
||||
protected override Task CreateReleasePackage(Action<int> progress, string packDir, string outputPath)
|
||||
{
|
||||
var dir = TempDir.CreateSubdirectory("CreateReleasePackage.Linux");
|
||||
File.Copy(PortablePackagePath, Path.Combine(dir.FullName, Options.PackId + ".AppImage"), true);
|
||||
return base.CreateReleasePackage(progress, dir.FullName, outputPath);
|
||||
}
|
||||
|
||||
protected override Task<string> CreateDeltaPackage(Action<int> progress, string releasePkg, string prevReleasePkg, string outputPkg, DeltaMode mode)
|
||||
{
|
||||
progress(-1); // there is only one "file", so progress will not work
|
||||
return base.CreateDeltaPackage(progress, releasePkg, prevReleasePkg, outputPkg, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,34 +5,33 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Velopack.Packaging.Abstractions;
|
||||
|
||||
namespace Velopack.Packaging.Unix.Commands
|
||||
namespace Velopack.Packaging.Unix.Commands;
|
||||
|
||||
public class LinuxPackOptions : IPackOptions
|
||||
{
|
||||
public class LinuxPackOptions : IPackOptions
|
||||
{
|
||||
public DirectoryInfo ReleaseDir { get; set; }
|
||||
public DirectoryInfo ReleaseDir { get; set; }
|
||||
|
||||
public string PackId { get; set; }
|
||||
public string PackId { get; set; }
|
||||
|
||||
public string PackVersion { get; set; }
|
||||
public string PackVersion { get; set; }
|
||||
|
||||
public string PackDirectory { get; set; }
|
||||
public string PackDirectory { get; set; }
|
||||
|
||||
public string PackAuthors { get; set; }
|
||||
public string PackAuthors { get; set; }
|
||||
|
||||
public string PackTitle { get; set; }
|
||||
public string PackTitle { get; set; }
|
||||
|
||||
public string EntryExecutableName { get; set; }
|
||||
public string EntryExecutableName { get; set; }
|
||||
|
||||
public string Icon { get; set; }
|
||||
public string Icon { get; set; }
|
||||
|
||||
public RID TargetRuntime { get; set; }
|
||||
public RID TargetRuntime { get; set; }
|
||||
|
||||
public string ReleaseNotes { get; set; }
|
||||
public string ReleaseNotes { get; set; }
|
||||
|
||||
public DeltaMode DeltaMode { get; set; } = DeltaMode.BestSpeed;
|
||||
public DeltaMode DeltaMode { get; set; } = DeltaMode.BestSpeed;
|
||||
|
||||
public string Channel { get; set; }
|
||||
public string Channel { get; set; }
|
||||
|
||||
public bool PackIsAppDir { get; set; }
|
||||
}
|
||||
public bool PackIsAppDir { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,241 +2,240 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Velopack.Packaging.Windows
|
||||
namespace Velopack.Packaging.Windows;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[ExcludeFromCodeCoverage]
|
||||
public static class AuthenticodeTools
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
[ExcludeFromCodeCoverage]
|
||||
public static class AuthenticodeTools
|
||||
[DllImport("Wintrust.dll", PreserveSig = true, SetLastError = false)]
|
||||
static extern uint WinVerifyTrust(IntPtr hWnd, IntPtr pgActionID, IntPtr pWinTrustData);
|
||||
|
||||
static uint winVerifyTrust(string fileName)
|
||||
{
|
||||
[DllImport("Wintrust.dll", PreserveSig = true, SetLastError = false)]
|
||||
static extern uint WinVerifyTrust(IntPtr hWnd, IntPtr pgActionID, IntPtr pWinTrustData);
|
||||
Guid wintrust_action_generic_verify_v2 = new Guid("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}");
|
||||
|
||||
static uint winVerifyTrust(string fileName)
|
||||
{
|
||||
Guid wintrust_action_generic_verify_v2 = new Guid("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}");
|
||||
uint result = 0;
|
||||
using (WINTRUST_FILE_INFO fileInfo = new WINTRUST_FILE_INFO(fileName, Guid.Empty))
|
||||
using (UnmanagedPointer guidPtr = new UnmanagedPointer(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid))), AllocMethod.HGlobal))
|
||||
using (UnmanagedPointer wvtDataPtr = new UnmanagedPointer(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINTRUST_DATA))), AllocMethod.HGlobal)) {
|
||||
WINTRUST_DATA data = new WINTRUST_DATA(fileInfo);
|
||||
IntPtr pGuid = guidPtr;
|
||||
IntPtr pData = wvtDataPtr;
|
||||
|
||||
uint result = 0;
|
||||
using (WINTRUST_FILE_INFO fileInfo = new WINTRUST_FILE_INFO(fileName, Guid.Empty))
|
||||
using (UnmanagedPointer guidPtr = new UnmanagedPointer(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid))), AllocMethod.HGlobal))
|
||||
using (UnmanagedPointer wvtDataPtr = new UnmanagedPointer(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINTRUST_DATA))), AllocMethod.HGlobal)) {
|
||||
WINTRUST_DATA data = new WINTRUST_DATA(fileInfo);
|
||||
IntPtr pGuid = guidPtr;
|
||||
IntPtr pData = wvtDataPtr;
|
||||
|
||||
Marshal.StructureToPtr(wintrust_action_generic_verify_v2, pGuid, true);
|
||||
Marshal.StructureToPtr(data, pData, true);
|
||||
|
||||
result = WinVerifyTrust(IntPtr.Zero, pGuid, pData);
|
||||
}
|
||||
return result;
|
||||
Marshal.StructureToPtr(wintrust_action_generic_verify_v2, pGuid, true);
|
||||
Marshal.StructureToPtr(data, pData, true);
|
||||
|
||||
result = WinVerifyTrust(IntPtr.Zero, pGuid, pData);
|
||||
}
|
||||
public static bool IsTrusted(string fileName)
|
||||
{
|
||||
return winVerifyTrust(fileName) == 0;
|
||||
return result;
|
||||
|
||||
}
|
||||
public static bool IsTrusted(string fileName)
|
||||
{
|
||||
return winVerifyTrust(fileName) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal struct WINTRUST_FILE_INFO : IDisposable
|
||||
{
|
||||
public WINTRUST_FILE_INFO(string fileName, Guid subject)
|
||||
{
|
||||
|
||||
cbStruct = (uint) Marshal.SizeOf(typeof(WINTRUST_FILE_INFO));
|
||||
pcwszFilePath = fileName;
|
||||
|
||||
if (subject != Guid.Empty) {
|
||||
pgKnownSubject = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
|
||||
Marshal.StructureToPtr(subject, pgKnownSubject, true);
|
||||
} else {
|
||||
pgKnownSubject = IntPtr.Zero;
|
||||
}
|
||||
|
||||
hFile = IntPtr.Zero;
|
||||
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal struct WINTRUST_FILE_INFO : IDisposable
|
||||
public uint cbStruct;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
public string pcwszFilePath;
|
||||
|
||||
public IntPtr hFile;
|
||||
public IntPtr pgKnownSubject;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
public WINTRUST_FILE_INFO(string fileName, Guid subject)
|
||||
{
|
||||
|
||||
cbStruct = (uint) Marshal.SizeOf(typeof(WINTRUST_FILE_INFO));
|
||||
pcwszFilePath = fileName;
|
||||
|
||||
if (subject != Guid.Empty) {
|
||||
pgKnownSubject = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
|
||||
Marshal.StructureToPtr(subject, pgKnownSubject, true);
|
||||
} else {
|
||||
pgKnownSubject = IntPtr.Zero;
|
||||
}
|
||||
|
||||
hFile = IntPtr.Zero;
|
||||
|
||||
}
|
||||
|
||||
public uint cbStruct;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
public string pcwszFilePath;
|
||||
|
||||
public IntPtr hFile;
|
||||
public IntPtr pgKnownSubject;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (pgKnownSubject != IntPtr.Zero) {
|
||||
Marshal.DestroyStructure(this.pgKnownSubject, typeof(Guid));
|
||||
Marshal.FreeHGlobal(this.pgKnownSubject);
|
||||
}
|
||||
}
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
enum AllocMethod
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
HGlobal, CoTaskMem
|
||||
};
|
||||
|
||||
enum UnionChoice
|
||||
{
|
||||
File = 1,
|
||||
Catalog,
|
||||
Blob,
|
||||
Signer,
|
||||
Cert
|
||||
};
|
||||
|
||||
enum UiChoice
|
||||
{
|
||||
All = 1,
|
||||
NoUI,
|
||||
NoBad,
|
||||
NoGood
|
||||
};
|
||||
enum RevocationCheckFlags
|
||||
{
|
||||
None = 0,
|
||||
WholeChain
|
||||
};
|
||||
enum StateAction
|
||||
{
|
||||
Ignore = 0,
|
||||
Verify,
|
||||
Close,
|
||||
AutoCache,
|
||||
AutoCacheFlush
|
||||
};
|
||||
enum TrustProviderFlags
|
||||
{
|
||||
UseIE4Trust = 1,
|
||||
NoIE4Chain = 2,
|
||||
NoPolicyUsage = 4,
|
||||
RevocationCheckNone = 16,
|
||||
RevocationCheckEndCert = 32,
|
||||
RevocationCheckChain = 64,
|
||||
RecovationCheckChainExcludeRoot = 128,
|
||||
Safer = 256,
|
||||
HashOnly = 512,
|
||||
UseDefaultOSVerCheck = 1024,
|
||||
LifetimeSigning = 2048
|
||||
};
|
||||
enum UIContext
|
||||
{
|
||||
Execute = 0,
|
||||
Install
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal struct WINTRUST_DATA : IDisposable
|
||||
{
|
||||
public WINTRUST_DATA(WINTRUST_FILE_INFO fileInfo)
|
||||
{
|
||||
this.cbStruct = (uint) Marshal.SizeOf(typeof(WINTRUST_DATA));
|
||||
pInfoStruct = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINTRUST_FILE_INFO)));
|
||||
|
||||
Marshal.StructureToPtr(fileInfo, pInfoStruct, false);
|
||||
|
||||
this.dwUnionChoice = UnionChoice.File;
|
||||
|
||||
pPolicyCallbackData = IntPtr.Zero;
|
||||
pSIPCallbackData = IntPtr.Zero;
|
||||
dwUIChoice = UiChoice.NoUI;
|
||||
fdwRevocationChecks = RevocationCheckFlags.None;
|
||||
dwStateAction = StateAction.Ignore;
|
||||
hWVTStateData = IntPtr.Zero;
|
||||
pwszURLReference = IntPtr.Zero;
|
||||
dwProvFlags = TrustProviderFlags.Safer;
|
||||
dwUIContext = UIContext.Execute;
|
||||
}
|
||||
|
||||
public uint cbStruct;
|
||||
public IntPtr pPolicyCallbackData;
|
||||
public IntPtr pSIPCallbackData;
|
||||
public UiChoice dwUIChoice;
|
||||
public RevocationCheckFlags fdwRevocationChecks;
|
||||
public UnionChoice dwUnionChoice;
|
||||
public IntPtr pInfoStruct;
|
||||
public StateAction dwStateAction;
|
||||
public IntPtr hWVTStateData;
|
||||
public TrustProviderFlags dwProvFlags;
|
||||
public UIContext dwUIContext;
|
||||
|
||||
IntPtr pwszURLReference;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (dwUnionChoice == UnionChoice.File) {
|
||||
WINTRUST_FILE_INFO info = new WINTRUST_FILE_INFO();
|
||||
Marshal.PtrToStructure(pInfoStruct, info);
|
||||
|
||||
info.Dispose();
|
||||
|
||||
Marshal.DestroyStructure(pInfoStruct, typeof(WINTRUST_FILE_INFO));
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(pInfoStruct);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal sealed class UnmanagedPointer : IDisposable
|
||||
{
|
||||
IntPtr m_ptr;
|
||||
AllocMethod m_meth;
|
||||
|
||||
internal UnmanagedPointer(IntPtr ptr, AllocMethod method)
|
||||
{
|
||||
m_meth = method;
|
||||
m_ptr = ptr;
|
||||
}
|
||||
|
||||
~UnmanagedPointer()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (m_ptr != IntPtr.Zero) {
|
||||
if (m_meth == AllocMethod.HGlobal) {
|
||||
Marshal.FreeHGlobal(m_ptr);
|
||||
} else if (m_meth == AllocMethod.CoTaskMem) {
|
||||
Marshal.FreeCoTaskMem(m_ptr);
|
||||
}
|
||||
|
||||
m_ptr = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (disposing) {
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public static implicit operator IntPtr(UnmanagedPointer ptr)
|
||||
{
|
||||
return ptr.m_ptr;
|
||||
if (pgKnownSubject != IntPtr.Zero) {
|
||||
Marshal.DestroyStructure(this.pgKnownSubject, typeof(Guid));
|
||||
Marshal.FreeHGlobal(this.pgKnownSubject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AllocMethod
|
||||
{
|
||||
HGlobal, CoTaskMem
|
||||
};
|
||||
|
||||
enum UnionChoice
|
||||
{
|
||||
File = 1,
|
||||
Catalog,
|
||||
Blob,
|
||||
Signer,
|
||||
Cert
|
||||
};
|
||||
|
||||
enum UiChoice
|
||||
{
|
||||
All = 1,
|
||||
NoUI,
|
||||
NoBad,
|
||||
NoGood
|
||||
};
|
||||
enum RevocationCheckFlags
|
||||
{
|
||||
None = 0,
|
||||
WholeChain
|
||||
};
|
||||
enum StateAction
|
||||
{
|
||||
Ignore = 0,
|
||||
Verify,
|
||||
Close,
|
||||
AutoCache,
|
||||
AutoCacheFlush
|
||||
};
|
||||
enum TrustProviderFlags
|
||||
{
|
||||
UseIE4Trust = 1,
|
||||
NoIE4Chain = 2,
|
||||
NoPolicyUsage = 4,
|
||||
RevocationCheckNone = 16,
|
||||
RevocationCheckEndCert = 32,
|
||||
RevocationCheckChain = 64,
|
||||
RecovationCheckChainExcludeRoot = 128,
|
||||
Safer = 256,
|
||||
HashOnly = 512,
|
||||
UseDefaultOSVerCheck = 1024,
|
||||
LifetimeSigning = 2048
|
||||
};
|
||||
enum UIContext
|
||||
{
|
||||
Execute = 0,
|
||||
Install
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal struct WINTRUST_DATA : IDisposable
|
||||
{
|
||||
public WINTRUST_DATA(WINTRUST_FILE_INFO fileInfo)
|
||||
{
|
||||
this.cbStruct = (uint) Marshal.SizeOf(typeof(WINTRUST_DATA));
|
||||
pInfoStruct = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINTRUST_FILE_INFO)));
|
||||
|
||||
Marshal.StructureToPtr(fileInfo, pInfoStruct, false);
|
||||
|
||||
this.dwUnionChoice = UnionChoice.File;
|
||||
|
||||
pPolicyCallbackData = IntPtr.Zero;
|
||||
pSIPCallbackData = IntPtr.Zero;
|
||||
dwUIChoice = UiChoice.NoUI;
|
||||
fdwRevocationChecks = RevocationCheckFlags.None;
|
||||
dwStateAction = StateAction.Ignore;
|
||||
hWVTStateData = IntPtr.Zero;
|
||||
pwszURLReference = IntPtr.Zero;
|
||||
dwProvFlags = TrustProviderFlags.Safer;
|
||||
dwUIContext = UIContext.Execute;
|
||||
}
|
||||
|
||||
public uint cbStruct;
|
||||
public IntPtr pPolicyCallbackData;
|
||||
public IntPtr pSIPCallbackData;
|
||||
public UiChoice dwUIChoice;
|
||||
public RevocationCheckFlags fdwRevocationChecks;
|
||||
public UnionChoice dwUnionChoice;
|
||||
public IntPtr pInfoStruct;
|
||||
public StateAction dwStateAction;
|
||||
public IntPtr hWVTStateData;
|
||||
public TrustProviderFlags dwProvFlags;
|
||||
public UIContext dwUIContext;
|
||||
|
||||
IntPtr pwszURLReference;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (dwUnionChoice == UnionChoice.File) {
|
||||
WINTRUST_FILE_INFO info = new WINTRUST_FILE_INFO();
|
||||
Marshal.PtrToStructure(pInfoStruct, info);
|
||||
|
||||
info.Dispose();
|
||||
|
||||
Marshal.DestroyStructure(pInfoStruct, typeof(WINTRUST_FILE_INFO));
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(pInfoStruct);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal sealed class UnmanagedPointer : IDisposable
|
||||
{
|
||||
IntPtr m_ptr;
|
||||
AllocMethod m_meth;
|
||||
|
||||
internal UnmanagedPointer(IntPtr ptr, AllocMethod method)
|
||||
{
|
||||
m_meth = method;
|
||||
m_ptr = ptr;
|
||||
}
|
||||
|
||||
~UnmanagedPointer()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (m_ptr != IntPtr.Zero) {
|
||||
if (m_meth == AllocMethod.HGlobal) {
|
||||
Marshal.FreeHGlobal(m_ptr);
|
||||
} else if (m_meth == AllocMethod.CoTaskMem) {
|
||||
Marshal.FreeCoTaskMem(m_ptr);
|
||||
}
|
||||
|
||||
m_ptr = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (disposing) {
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public static implicit operator IntPtr(UnmanagedPointer ptr)
|
||||
{
|
||||
return ptr.m_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,237 +4,236 @@ using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging.Windows
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class CodeSign
|
||||
{
|
||||
public ILogger Log { get; }
|
||||
namespace Velopack.Packaging.Windows;
|
||||
|
||||
public CodeSign(ILogger logger)
|
||||
{
|
||||
Log = logger;
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class CodeSign
|
||||
{
|
||||
public ILogger Log { get; }
|
||||
|
||||
public CodeSign(ILogger logger)
|
||||
{
|
||||
Log = logger;
|
||||
}
|
||||
|
||||
private bool CheckIsAlreadySigned(string filePath)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(filePath)) return true;
|
||||
|
||||
if (!File.Exists(filePath)) {
|
||||
Log.Warn($"Cannot sign '{filePath}', file does not exist.");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CheckIsAlreadySigned(string filePath)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(filePath)) return true;
|
||||
|
||||
if (!File.Exists(filePath)) {
|
||||
Log.Warn($"Cannot sign '{filePath}', file does not exist.");
|
||||
try {
|
||||
if (AuthenticodeTools.IsTrusted(filePath)) {
|
||||
Log.Debug($"'{filePath}' is already signed, skipping...");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (AuthenticodeTools.IsTrusted(filePath)) {
|
||||
Log.Debug($"'{filePath}' is already signed, skipping...");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Failed to determine signing status for " + filePath);
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Failed to determine signing status for " + filePath);
|
||||
}
|
||||
|
||||
public void SignPEFilesWithSignTool(string rootDir, string[] filePaths, string signArguments, int parallelism, Action<int> progress)
|
||||
{
|
||||
Queue<string> pendingSign = new Queue<string>();
|
||||
|
||||
foreach (var f in filePaths) {
|
||||
if (!CheckIsAlreadySigned(f)) {
|
||||
// try to find the path relative to rootDir
|
||||
if (String.IsNullOrEmpty(rootDir)) {
|
||||
pendingSign.Enqueue(f);
|
||||
} else {
|
||||
var partialPath = Utility.NormalizePath(f).Substring(Utility.NormalizePath(rootDir).Length).Trim('/', '\\');
|
||||
pendingSign.Enqueue(partialPath);
|
||||
}
|
||||
} else {
|
||||
Log.Debug($"'{f}' is already signed, and will not be signed again.");
|
||||
}
|
||||
}
|
||||
|
||||
if (filePaths.Length != pendingSign.Count) {
|
||||
var diff = filePaths.Length - pendingSign.Count;
|
||||
Log.Info($"{pendingSign.Count} files will be signed, {diff} will be skipped because they are already signed.");
|
||||
}
|
||||
|
||||
// here we invoke signtool.exe with 'cmd.exe /C' and redirect output to a file, because something
|
||||
// about how the dotnet tool host works prevents signtool from being able to open a token password
|
||||
// prompt, meaning signing fails for those with an HSM.
|
||||
using var _1 = Utility.GetTempFileName(out var signLogFile);
|
||||
|
||||
var totalToSign = pendingSign.Count;
|
||||
|
||||
do {
|
||||
List<string> filesToSign = new List<string>();
|
||||
for (int i = Math.Min(pendingSign.Count, parallelism); i > 0; i--) {
|
||||
filesToSign.Add(pendingSign.Dequeue());
|
||||
}
|
||||
|
||||
var filesToSignStr = String.Join(" ", filesToSign.Select(f => $"\"{f}\""));
|
||||
var command = $"/S /C \"\"{HelperFile.SignToolPath}\" sign {signArguments} {filesToSignStr} >> \"{signLogFile}\" 2>&1\"";
|
||||
|
||||
var psi = new ProcessStartInfo {
|
||||
FileName = "cmd.exe",
|
||||
Arguments = command,
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = rootDir,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
var process = Process.Start(psi);
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = "cmd.exe " + new Regex(@"\/p\s+?[^\s]+").Replace(command, "/p ********");
|
||||
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
|
||||
var output = File.Exists(signLogFile) ? File.ReadAllText(signLogFile).Trim() : "No output file was created.";
|
||||
throw new UserInfoException(
|
||||
$"Signing command failed. Specify --verbose argument to print signing command.\n" +
|
||||
$"Output was:" + Environment.NewLine + output);
|
||||
}
|
||||
|
||||
int processed = totalToSign - pendingSign.Count;
|
||||
Log.Info($"Code-signed {processed}/{totalToSign} files");
|
||||
progress((int) ((double) processed / totalToSign * 100));
|
||||
} while (pendingSign.Count > 0);
|
||||
|
||||
Log.Info("SignTool Output: " + Environment.NewLine + File.ReadAllText(signLogFile).Trim());
|
||||
}
|
||||
|
||||
public void SignPEFileWithTemplate(string filePath, string signTemplate)
|
||||
{
|
||||
if (VelopackRuntimeInfo.IsWindows && CheckIsAlreadySigned(filePath)) {
|
||||
Log.Debug($"'{filePath}' is already signed, and will not be signed again.");
|
||||
return;
|
||||
}
|
||||
|
||||
var command = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
|
||||
|
||||
var result = Exe.InvokeProcess(command, null, null);
|
||||
if (result.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
|
||||
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
|
||||
throw new Exception(
|
||||
$"Signing command failed. Specify --verbose argument to print signing command.\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
}
|
||||
|
||||
Log.Info("Sign successful: " + result.StdOutput);
|
||||
}
|
||||
|
||||
//private static ProcessStartInfo CreateProcessStartInfo(string fileName, string arguments, string workingDirectory = "")
|
||||
//{
|
||||
// var psi = new ProcessStartInfo(fileName, arguments);
|
||||
// psi.UseShellExecute = false;
|
||||
// psi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
// psi.ErrorDialog = false;
|
||||
// psi.CreateNoWindow = true;
|
||||
// psi.RedirectStandardOutput = true;
|
||||
// psi.RedirectStandardError = true;
|
||||
// psi.WorkingDirectory = workingDirectory;
|
||||
// return psi;
|
||||
//}
|
||||
|
||||
//private void SignPEFile(string filePath, string signParams, string signTemplate)
|
||||
//{
|
||||
// try {
|
||||
// if (AuthenticodeTools.IsTrusted(filePath)) {
|
||||
// Log.Debug($"'{filePath}' is already signed, skipping...");
|
||||
// return;
|
||||
// }
|
||||
// } catch (Exception ex) {
|
||||
// Log.Error(ex, "Failed to determine signing status for " + filePath);
|
||||
// }
|
||||
|
||||
// string cmd;
|
||||
// ProcessStartInfo psi;
|
||||
// if (!String.IsNullOrEmpty(signParams)) {
|
||||
// // use embedded signtool.exe with provided parameters
|
||||
// cmd = $"sign {signParams} \"{filePath}\"";
|
||||
// psi = CreateProcessStartInfo(HelperFile.SignToolPath, cmd);
|
||||
// cmd = "signtool.exe " + cmd;
|
||||
// } else if (!String.IsNullOrEmpty(signTemplate)) {
|
||||
// // escape custom sign command and pass it to cmd.exe
|
||||
// cmd = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
|
||||
// psi = CreateProcessStartInfo("cmd", $"/c {EscapeCmdExeMetachars(cmd)}");
|
||||
// } else {
|
||||
// Log.Debug($"{filePath} was not signed. (skipped; no signing parameters)");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var processResult = InvokeProcessUnsafeAsync(psi, CancellationToken.None)
|
||||
// .ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
// if (processResult.ExitCode != 0) {
|
||||
// var cmdWithPasswordHidden = new Regex(@"/p\s+\w+").Replace(cmd, "/p ********");
|
||||
// throw new Exception("Signing command failed: \n > " + cmdWithPasswordHidden + "\n" + processResult.StdOutput);
|
||||
// } else {
|
||||
// Log.Info("Sign successful: " + processResult.StdOutput);
|
||||
// }
|
||||
//}
|
||||
|
||||
//private static string EscapeCmdExeMetachars(string command)
|
||||
//{
|
||||
// var result = new StringBuilder();
|
||||
// foreach (var ch in command) {
|
||||
// switch (ch) {
|
||||
// case '(':
|
||||
// case ')':
|
||||
// case '%':
|
||||
// case '!':
|
||||
// case '^':
|
||||
// case '"':
|
||||
// case '<':
|
||||
// case '>':
|
||||
// case '&':
|
||||
// case '|':
|
||||
// result.Append('^');
|
||||
// break;
|
||||
// }
|
||||
// result.Append(ch);
|
||||
// }
|
||||
// return result.ToString();
|
||||
//}
|
||||
|
||||
//private class ProcessResult
|
||||
//{
|
||||
// public int ExitCode { get; set; }
|
||||
// public string StdOutput { get; set; }
|
||||
|
||||
// public ProcessResult(int exitCode, string stdOutput)
|
||||
// {
|
||||
// ExitCode = exitCode;
|
||||
// StdOutput = stdOutput;
|
||||
// }
|
||||
//}
|
||||
|
||||
//private static async Task<ProcessResult> InvokeProcessUnsafeAsync(ProcessStartInfo psi, CancellationToken ct)
|
||||
//{
|
||||
// var pi = Process.Start(psi);
|
||||
// await Task.Run(() => {
|
||||
// while (!ct.IsCancellationRequested) {
|
||||
// if (pi.WaitForExit(2000)) return;
|
||||
// }
|
||||
|
||||
// if (ct.IsCancellationRequested) {
|
||||
// pi.Kill();
|
||||
// ct.ThrowIfCancellationRequested();
|
||||
// }
|
||||
// }).ConfigureAwait(false);
|
||||
|
||||
// string textResult = await pi.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
|
||||
// if (String.IsNullOrWhiteSpace(textResult) || pi.ExitCode != 0) {
|
||||
// textResult = (textResult ?? "") + "\n" + await pi.StandardError.ReadToEndAsync().ConfigureAwait(false);
|
||||
|
||||
// if (String.IsNullOrWhiteSpace(textResult)) {
|
||||
// textResult = String.Empty;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return new ProcessResult(pi.ExitCode, textResult.Trim());
|
||||
//}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SignPEFilesWithSignTool(string rootDir, string[] filePaths, string signArguments, int parallelism, Action<int> progress)
|
||||
{
|
||||
Queue<string> pendingSign = new Queue<string>();
|
||||
|
||||
foreach (var f in filePaths) {
|
||||
if (!CheckIsAlreadySigned(f)) {
|
||||
// try to find the path relative to rootDir
|
||||
if (String.IsNullOrEmpty(rootDir)) {
|
||||
pendingSign.Enqueue(f);
|
||||
} else {
|
||||
var partialPath = Utility.NormalizePath(f).Substring(Utility.NormalizePath(rootDir).Length).Trim('/', '\\');
|
||||
pendingSign.Enqueue(partialPath);
|
||||
}
|
||||
} else {
|
||||
Log.Debug($"'{f}' is already signed, and will not be signed again.");
|
||||
}
|
||||
}
|
||||
|
||||
if (filePaths.Length != pendingSign.Count) {
|
||||
var diff = filePaths.Length - pendingSign.Count;
|
||||
Log.Info($"{pendingSign.Count} files will be signed, {diff} will be skipped because they are already signed.");
|
||||
}
|
||||
|
||||
// here we invoke signtool.exe with 'cmd.exe /C' and redirect output to a file, because something
|
||||
// about how the dotnet tool host works prevents signtool from being able to open a token password
|
||||
// prompt, meaning signing fails for those with an HSM.
|
||||
using var _1 = Utility.GetTempFileName(out var signLogFile);
|
||||
|
||||
var totalToSign = pendingSign.Count;
|
||||
|
||||
do {
|
||||
List<string> filesToSign = new List<string>();
|
||||
for (int i = Math.Min(pendingSign.Count, parallelism); i > 0; i--) {
|
||||
filesToSign.Add(pendingSign.Dequeue());
|
||||
}
|
||||
|
||||
var filesToSignStr = String.Join(" ", filesToSign.Select(f => $"\"{f}\""));
|
||||
var command = $"/S /C \"\"{HelperFile.SignToolPath}\" sign {signArguments} {filesToSignStr} >> \"{signLogFile}\" 2>&1\"";
|
||||
|
||||
var psi = new ProcessStartInfo {
|
||||
FileName = "cmd.exe",
|
||||
Arguments = command,
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = rootDir,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
var process = Process.Start(psi);
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = "cmd.exe " + new Regex(@"\/p\s+?[^\s]+").Replace(command, "/p ********");
|
||||
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
|
||||
var output = File.Exists(signLogFile) ? File.ReadAllText(signLogFile).Trim() : "No output file was created.";
|
||||
throw new UserInfoException(
|
||||
$"Signing command failed. Specify --verbose argument to print signing command.\n" +
|
||||
$"Output was:" + Environment.NewLine + output);
|
||||
}
|
||||
|
||||
int processed = totalToSign - pendingSign.Count;
|
||||
Log.Info($"Code-signed {processed}/{totalToSign} files");
|
||||
progress((int) ((double) processed / totalToSign * 100));
|
||||
} while (pendingSign.Count > 0);
|
||||
|
||||
Log.Info("SignTool Output: " + Environment.NewLine + File.ReadAllText(signLogFile).Trim());
|
||||
}
|
||||
|
||||
public void SignPEFileWithTemplate(string filePath, string signTemplate)
|
||||
{
|
||||
if (VelopackRuntimeInfo.IsWindows && CheckIsAlreadySigned(filePath)) {
|
||||
Log.Debug($"'{filePath}' is already signed, and will not be signed again.");
|
||||
return;
|
||||
}
|
||||
|
||||
var command = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
|
||||
|
||||
var result = Exe.InvokeProcess(command, null, null);
|
||||
if (result.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
|
||||
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
|
||||
throw new Exception(
|
||||
$"Signing command failed. Specify --verbose argument to print signing command.\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
}
|
||||
|
||||
Log.Info("Sign successful: " + result.StdOutput);
|
||||
}
|
||||
|
||||
//private static ProcessStartInfo CreateProcessStartInfo(string fileName, string arguments, string workingDirectory = "")
|
||||
//{
|
||||
// var psi = new ProcessStartInfo(fileName, arguments);
|
||||
// psi.UseShellExecute = false;
|
||||
// psi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
// psi.ErrorDialog = false;
|
||||
// psi.CreateNoWindow = true;
|
||||
// psi.RedirectStandardOutput = true;
|
||||
// psi.RedirectStandardError = true;
|
||||
// psi.WorkingDirectory = workingDirectory;
|
||||
// return psi;
|
||||
//}
|
||||
|
||||
//private void SignPEFile(string filePath, string signParams, string signTemplate)
|
||||
//{
|
||||
// try {
|
||||
// if (AuthenticodeTools.IsTrusted(filePath)) {
|
||||
// Log.Debug($"'{filePath}' is already signed, skipping...");
|
||||
// return;
|
||||
// }
|
||||
// } catch (Exception ex) {
|
||||
// Log.Error(ex, "Failed to determine signing status for " + filePath);
|
||||
// }
|
||||
|
||||
// string cmd;
|
||||
// ProcessStartInfo psi;
|
||||
// if (!String.IsNullOrEmpty(signParams)) {
|
||||
// // use embedded signtool.exe with provided parameters
|
||||
// cmd = $"sign {signParams} \"{filePath}\"";
|
||||
// psi = CreateProcessStartInfo(HelperFile.SignToolPath, cmd);
|
||||
// cmd = "signtool.exe " + cmd;
|
||||
// } else if (!String.IsNullOrEmpty(signTemplate)) {
|
||||
// // escape custom sign command and pass it to cmd.exe
|
||||
// cmd = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
|
||||
// psi = CreateProcessStartInfo("cmd", $"/c {EscapeCmdExeMetachars(cmd)}");
|
||||
// } else {
|
||||
// Log.Debug($"{filePath} was not signed. (skipped; no signing parameters)");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var processResult = InvokeProcessUnsafeAsync(psi, CancellationToken.None)
|
||||
// .ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
// if (processResult.ExitCode != 0) {
|
||||
// var cmdWithPasswordHidden = new Regex(@"/p\s+\w+").Replace(cmd, "/p ********");
|
||||
// throw new Exception("Signing command failed: \n > " + cmdWithPasswordHidden + "\n" + processResult.StdOutput);
|
||||
// } else {
|
||||
// Log.Info("Sign successful: " + processResult.StdOutput);
|
||||
// }
|
||||
//}
|
||||
|
||||
//private static string EscapeCmdExeMetachars(string command)
|
||||
//{
|
||||
// var result = new StringBuilder();
|
||||
// foreach (var ch in command) {
|
||||
// switch (ch) {
|
||||
// case '(':
|
||||
// case ')':
|
||||
// case '%':
|
||||
// case '!':
|
||||
// case '^':
|
||||
// case '"':
|
||||
// case '<':
|
||||
// case '>':
|
||||
// case '&':
|
||||
// case '|':
|
||||
// result.Append('^');
|
||||
// break;
|
||||
// }
|
||||
// result.Append(ch);
|
||||
// }
|
||||
// return result.ToString();
|
||||
//}
|
||||
|
||||
//private class ProcessResult
|
||||
//{
|
||||
// public int ExitCode { get; set; }
|
||||
// public string StdOutput { get; set; }
|
||||
|
||||
// public ProcessResult(int exitCode, string stdOutput)
|
||||
// {
|
||||
// ExitCode = exitCode;
|
||||
// StdOutput = stdOutput;
|
||||
// }
|
||||
//}
|
||||
|
||||
//private static async Task<ProcessResult> InvokeProcessUnsafeAsync(ProcessStartInfo psi, CancellationToken ct)
|
||||
//{
|
||||
// var pi = Process.Start(psi);
|
||||
// await Task.Run(() => {
|
||||
// while (!ct.IsCancellationRequested) {
|
||||
// if (pi.WaitForExit(2000)) return;
|
||||
// }
|
||||
|
||||
// if (ct.IsCancellationRequested) {
|
||||
// pi.Kill();
|
||||
// ct.ThrowIfCancellationRequested();
|
||||
// }
|
||||
// }).ConfigureAwait(false);
|
||||
|
||||
// string textResult = await pi.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
|
||||
// if (String.IsNullOrWhiteSpace(textResult) || pi.ExitCode != 0) {
|
||||
// textResult = (textResult ?? "") + "\n" + await pi.StandardError.ReadToEndAsync().ConfigureAwait(false);
|
||||
|
||||
// if (String.IsNullOrWhiteSpace(textResult)) {
|
||||
// textResult = String.Empty;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return new ProcessResult(pi.ExitCode, textResult.Trim());
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -8,145 +8,144 @@ using Microsoft.Extensions.Logging;
|
||||
using NuGet.Versioning;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging.Windows
|
||||
namespace Velopack.Packaging.Windows;
|
||||
|
||||
public class DotnetUtil
|
||||
{
|
||||
public class DotnetUtil
|
||||
public static NuGetVersion VerifyVelopackApp(string exeFile, ILogger log)
|
||||
{
|
||||
public static NuGetVersion VerifyVelopackApp(string exeFile, ILogger log)
|
||||
{
|
||||
try {
|
||||
NuGetVersion velopackVersion = null;
|
||||
IPEImage velopackDll = null;
|
||||
AssemblyDefinition mainAssy = null;
|
||||
mainAssy ??= LoadFullFramework(exeFile, ref velopackDll);
|
||||
mainAssy ??= LoadDncBundle(exeFile, ref velopackDll);
|
||||
try {
|
||||
NuGetVersion velopackVersion = null;
|
||||
IPEImage velopackDll = null;
|
||||
AssemblyDefinition mainAssy = null;
|
||||
mainAssy ??= LoadFullFramework(exeFile, ref velopackDll);
|
||||
mainAssy ??= LoadDncBundle(exeFile, ref velopackDll);
|
||||
|
||||
if (mainAssy == null) {
|
||||
// not a dotnet binary
|
||||
return null;
|
||||
if (mainAssy == null) {
|
||||
// not a dotnet binary
|
||||
return null;
|
||||
}
|
||||
|
||||
if (velopackDll != null) {
|
||||
try {
|
||||
var versionInfo = VersionInfoResource.FromDirectory(velopackDll.Resources);
|
||||
var actualInfo = versionInfo.GetChild<StringFileInfo>(StringFileInfo.StringFileInfoKey);
|
||||
var versionTable = actualInfo.Tables[0];
|
||||
var productVersion = versionTable.Where(v => v.Key == StringTable.ProductVersionKey).FirstOrDefault();
|
||||
velopackVersion = NuGetVersion.Parse(productVersion.Value);
|
||||
} catch (Exception ex) {
|
||||
// don't really care
|
||||
log.Debug(ex, "Unable to read Velopack.dll version info.");
|
||||
}
|
||||
}
|
||||
|
||||
if (velopackDll != null) {
|
||||
try {
|
||||
var versionInfo = VersionInfoResource.FromDirectory(velopackDll.Resources);
|
||||
var actualInfo = versionInfo.GetChild<StringFileInfo>(StringFileInfo.StringFileInfoKey);
|
||||
var versionTable = actualInfo.Tables[0];
|
||||
var productVersion = versionTable.Where(v => v.Key == StringTable.ProductVersionKey).FirstOrDefault();
|
||||
velopackVersion = NuGetVersion.Parse(productVersion.Value);
|
||||
} catch (Exception ex) {
|
||||
// don't really care
|
||||
log.Debug(ex, "Unable to read Velopack.dll version info.");
|
||||
}
|
||||
}
|
||||
var mainModule = mainAssy.Modules.Single();
|
||||
var entryPoint = mainModule.ManagedEntryPointMethod;
|
||||
|
||||
var mainModule = mainAssy.Modules.Single();
|
||||
var entryPoint = mainModule.ManagedEntryPointMethod;
|
||||
|
||||
foreach (var instr in entryPoint.CilMethodBody.Instructions) {
|
||||
if (instr.OpCode.Code is CilCode.Call or CilCode.Callvirt or CilCode.Calli) {
|
||||
SerializedMemberReference operand = instr.Operand as SerializedMemberReference;
|
||||
if (operand != null && operand.IsMethod) {
|
||||
if (operand.Name == "Run" && operand.DeclaringType.FullName == "Velopack.VelopackApp") {
|
||||
// success!
|
||||
if (velopackVersion != null) {
|
||||
log.Info($"Verified VelopackApp.Run() in '{entryPoint.FullName}', version {velopackVersion}.");
|
||||
if (velopackVersion != VelopackRuntimeInfo.VelopackProductVersion) {
|
||||
log.Warn(exeFile + " was built with a different version of Velopack than this tool. " +
|
||||
$"This may cause compatibility issues. Expected {VelopackRuntimeInfo.VelopackProductVersion}, " +
|
||||
$"but found {velopackVersion}.");
|
||||
}
|
||||
return velopackVersion;
|
||||
} else {
|
||||
log.Warn("VelopackApp verified at entry point, but ProductVersion could not be checked.");
|
||||
return null;
|
||||
foreach (var instr in entryPoint.CilMethodBody.Instructions) {
|
||||
if (instr.OpCode.Code is CilCode.Call or CilCode.Callvirt or CilCode.Calli) {
|
||||
SerializedMemberReference operand = instr.Operand as SerializedMemberReference;
|
||||
if (operand != null && operand.IsMethod) {
|
||||
if (operand.Name == "Run" && operand.DeclaringType.FullName == "Velopack.VelopackApp") {
|
||||
// success!
|
||||
if (velopackVersion != null) {
|
||||
log.Info($"Verified VelopackApp.Run() in '{entryPoint.FullName}', version {velopackVersion}.");
|
||||
if (velopackVersion != VelopackRuntimeInfo.VelopackProductVersion) {
|
||||
log.Warn(exeFile + " was built with a different version of Velopack than this tool. " +
|
||||
$"This may cause compatibility issues. Expected {VelopackRuntimeInfo.VelopackProductVersion}, " +
|
||||
$"but found {velopackVersion}.");
|
||||
}
|
||||
return velopackVersion;
|
||||
} else {
|
||||
log.Warn("VelopackApp verified at entry point, but ProductVersion could not be checked.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we've iterated the whole main method and not found the call, then the velopack builder is missing
|
||||
throw new UserInfoException($"Unable to verify VelopackApp, in application main method '{entryPoint.FullName}'. " +
|
||||
"Please ensure that 'VelopackApp.Build().Run()' is present in your Program.Main().");
|
||||
|
||||
} catch (Exception ex) when (ex is not UserInfoException) {
|
||||
log.Error("Unable to verify VelopackApp: " + ex.Message);
|
||||
}
|
||||
|
||||
return null;
|
||||
// if we've iterated the whole main method and not found the call, then the velopack builder is missing
|
||||
throw new UserInfoException($"Unable to verify VelopackApp, in application main method '{entryPoint.FullName}'. " +
|
||||
"Please ensure that 'VelopackApp.Build().Run()' is present in your Program.Main().");
|
||||
|
||||
} catch (Exception ex) when (ex is not UserInfoException) {
|
||||
log.Error("Unable to verify VelopackApp: " + ex.Message);
|
||||
}
|
||||
|
||||
private static AssemblyDefinition LoadFullFramework(string exeFile, ref IPEImage velopackDll)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private static AssemblyDefinition LoadFullFramework(string exeFile, ref IPEImage velopackDll)
|
||||
{
|
||||
try {
|
||||
var assy = AssemblyDefinition.FromFile(exeFile);
|
||||
var versionFile = Path.Combine(Path.GetDirectoryName(exeFile), "Velopack.dll");
|
||||
if (File.Exists(versionFile)) {
|
||||
velopackDll = PEImage.FromFile(versionFile);
|
||||
}
|
||||
return assy;
|
||||
} catch (BadImageFormatException) {
|
||||
// not a .Net Framework binary
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AssemblyDefinition LoadDncBundle(string exeFile, ref IPEImage velopackDll)
|
||||
{
|
||||
try {
|
||||
var bundle = BundleManifest.FromFile(exeFile);
|
||||
IList<BundleFile> embeddedFiles = null;
|
||||
|
||||
try {
|
||||
var assy = AssemblyDefinition.FromFile(exeFile);
|
||||
var versionFile = Path.Combine(Path.GetDirectoryName(exeFile), "Velopack.dll");
|
||||
embeddedFiles = bundle.Files;
|
||||
} catch {
|
||||
// not a SingleFileHost binary, so we'll search on disk
|
||||
var parentDir = Path.GetDirectoryName(exeFile);
|
||||
var versionFile = Path.Combine(parentDir, "Velopack.dll");
|
||||
if (File.Exists(versionFile)) {
|
||||
velopackDll = PEImage.FromFile(versionFile);
|
||||
}
|
||||
return assy;
|
||||
} catch (BadImageFormatException) {
|
||||
// not a .Net Framework binary
|
||||
|
||||
var diskFile = Path.Combine(parentDir, Path.GetFileNameWithoutExtension(exeFile) + ".dll");
|
||||
if (File.Exists(diskFile)) {
|
||||
return AssemblyDefinition.FromFile(diskFile);
|
||||
}
|
||||
|
||||
var runtimeConfigFile = Directory.EnumerateFiles(parentDir, "*.runtimeconfig.json").SingleOrDefault();
|
||||
var possNameRuntime = Path.Combine(parentDir,
|
||||
Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runtimeConfigFile)) + ".dll");
|
||||
if (File.Exists(possNameRuntime)) {
|
||||
return AssemblyDefinition.FromFile(possNameRuntime);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AssemblyDefinition LoadDncBundle(string exeFile, ref IPEImage velopackDll)
|
||||
{
|
||||
try {
|
||||
var bundle = BundleManifest.FromFile(exeFile);
|
||||
IList<BundleFile> embeddedFiles = null;
|
||||
|
||||
try {
|
||||
embeddedFiles = bundle.Files;
|
||||
} catch {
|
||||
// not a SingleFileHost binary, so we'll search on disk
|
||||
var parentDir = Path.GetDirectoryName(exeFile);
|
||||
var versionFile = Path.Combine(parentDir, "Velopack.dll");
|
||||
if (File.Exists(versionFile)) {
|
||||
velopackDll = PEImage.FromFile(versionFile);
|
||||
}
|
||||
|
||||
var diskFile = Path.Combine(parentDir, Path.GetFileNameWithoutExtension(exeFile) + ".dll");
|
||||
if (File.Exists(diskFile)) {
|
||||
return AssemblyDefinition.FromFile(diskFile);
|
||||
}
|
||||
|
||||
var runtimeConfigFile = Directory.EnumerateFiles(parentDir, "*.runtimeconfig.json").SingleOrDefault();
|
||||
var possNameRuntime = Path.Combine(parentDir,
|
||||
Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runtimeConfigFile)) + ".dll");
|
||||
if (File.Exists(possNameRuntime)) {
|
||||
return AssemblyDefinition.FromFile(possNameRuntime);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var velopackEmbedded = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == "Velopack.dll");
|
||||
if (velopackEmbedded != null && velopackEmbedded.TryGetReader(out var readerVel)) {
|
||||
velopackDll = PEImage.FromReader(readerVel);
|
||||
}
|
||||
|
||||
var runtimeConfig = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.RuntimeConfigJson);
|
||||
if (runtimeConfig != null) {
|
||||
var possName1 = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runtimeConfig.RelativePath)) + ".dll";
|
||||
var file = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == possName1);
|
||||
if (file != null && file.TryGetReader(out var reader)) {
|
||||
return AssemblyDefinition.FromReader(reader);
|
||||
}
|
||||
}
|
||||
|
||||
var possName2 = Path.GetFileNameWithoutExtension(exeFile) + ".dll";
|
||||
var file2 = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == possName2);
|
||||
if (file2 != null && file2.TryGetReader(out var reader2)) {
|
||||
return AssemblyDefinition.FromReader(reader2);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (BadImageFormatException) {
|
||||
// not an AppHost / SingleFileHost binary
|
||||
return null;
|
||||
var velopackEmbedded = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == "Velopack.dll");
|
||||
if (velopackEmbedded != null && velopackEmbedded.TryGetReader(out var readerVel)) {
|
||||
velopackDll = PEImage.FromReader(readerVel);
|
||||
}
|
||||
|
||||
var runtimeConfig = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.RuntimeConfigJson);
|
||||
if (runtimeConfig != null) {
|
||||
var possName1 = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runtimeConfig.RelativePath)) + ".dll";
|
||||
var file = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == possName1);
|
||||
if (file != null && file.TryGetReader(out var reader)) {
|
||||
return AssemblyDefinition.FromReader(reader);
|
||||
}
|
||||
}
|
||||
|
||||
var possName2 = Path.GetFileNameWithoutExtension(exeFile) + ".dll";
|
||||
var file2 = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == possName2);
|
||||
if (file2 != null && file2.TryGetReader(out var reader2)) {
|
||||
return AssemblyDefinition.FromReader(reader2);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (BadImageFormatException) {
|
||||
// not an AppHost / SingleFileHost binary
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,38 +6,37 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Velopack.NuGet;
|
||||
|
||||
namespace Velopack.Packaging.Windows
|
||||
namespace Velopack.Packaging.Windows;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class Rcedit
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class Rcedit
|
||||
public static void SetExeIcon(string exePath, string iconPath)
|
||||
{
|
||||
public static void SetExeIcon(string exePath, string iconPath)
|
||||
{
|
||||
var args = new[] { Path.GetFullPath(exePath), "--set-icon", iconPath };
|
||||
Utility.Retry(() => Exe.InvokeAndThrowIfNonZero(HelperFile.RceditPath, args, null));
|
||||
var args = new[] { Path.GetFullPath(exePath), "--set-icon", iconPath };
|
||||
Utility.Retry(() => Exe.InvokeAndThrowIfNonZero(HelperFile.RceditPath, args, null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static void SetPEVersionBlockFromPackageInfo(string exePath, PackageManifest package, string iconPath = null)
|
||||
{
|
||||
var realExePath = Path.GetFullPath(exePath);
|
||||
|
||||
List<string> args = new List<string>() {
|
||||
realExePath,
|
||||
"--set-version-string", "CompanyName", package.ProductCompany,
|
||||
"--set-version-string", "LegalCopyright", package.ProductCopyright,
|
||||
"--set-version-string", "FileDescription", package.ProductDescription,
|
||||
"--set-version-string", "ProductName", package.ProductName,
|
||||
"--set-file-version", package.Version.ToString(),
|
||||
"--set-product-version", package.Version.ToString(),
|
||||
};
|
||||
|
||||
if (iconPath != null) {
|
||||
args.Add("--set-icon");
|
||||
args.Add(Path.GetFullPath(iconPath));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static void SetPEVersionBlockFromPackageInfo(string exePath, PackageManifest package, string iconPath = null)
|
||||
{
|
||||
var realExePath = Path.GetFullPath(exePath);
|
||||
|
||||
List<string> args = new List<string>() {
|
||||
realExePath,
|
||||
"--set-version-string", "CompanyName", package.ProductCompany,
|
||||
"--set-version-string", "LegalCopyright", package.ProductCopyright,
|
||||
"--set-version-string", "FileDescription", package.ProductDescription,
|
||||
"--set-version-string", "ProductName", package.ProductName,
|
||||
"--set-file-version", package.Version.ToString(),
|
||||
"--set-product-version", package.Version.ToString(),
|
||||
};
|
||||
|
||||
if (iconPath != null) {
|
||||
args.Add("--set-icon");
|
||||
args.Add(Path.GetFullPath(iconPath));
|
||||
}
|
||||
|
||||
Utility.Retry(() => Exe.InvokeAndThrowIfNonZero(HelperFile.RceditPath, args, null));
|
||||
}
|
||||
Utility.Retry(() => Exe.InvokeAndThrowIfNonZero(HelperFile.RceditPath, args, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Velopack.Packaging.Abstractions
|
||||
namespace Velopack.Packaging.Abstractions;
|
||||
|
||||
public interface ICommand<TOpt> where TOpt : class
|
||||
{
|
||||
public interface ICommand<TOpt> where TOpt : class
|
||||
{
|
||||
Task Run(TOpt options);
|
||||
}
|
||||
Task Run(TOpt options);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Velopack.Packaging.Abstractions
|
||||
namespace Velopack.Packaging.Abstractions;
|
||||
|
||||
public interface IFancyConsole
|
||||
{
|
||||
public interface IFancyConsole
|
||||
{
|
||||
Task ExecuteProgressAsync(Func<IFancyConsoleProgress, Task> action);
|
||||
Task ExecuteProgressAsync(Func<IFancyConsoleProgress, Task> action);
|
||||
|
||||
void WriteTable(string tableName, IEnumerable<IEnumerable<string>> rows, bool hasHeaderRow = true);
|
||||
void WriteTable(string tableName, IEnumerable<IEnumerable<string>> rows, bool hasHeaderRow = true);
|
||||
|
||||
Task<bool> PromptYesNo(string prompt, bool? defaultValue = null, TimeSpan? timeout = null);
|
||||
Task<bool> PromptYesNo(string prompt, bool? defaultValue = null, TimeSpan? timeout = null);
|
||||
|
||||
void WriteLine(string text = "");
|
||||
}
|
||||
void WriteLine(string text = "");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace Velopack.Packaging.Abstractions
|
||||
namespace Velopack.Packaging.Abstractions;
|
||||
|
||||
public interface IFancyConsoleProgress
|
||||
{
|
||||
public interface IFancyConsoleProgress
|
||||
{
|
||||
Task RunTask(string name, Func<Action<int>, Task> fn);
|
||||
}
|
||||
Task RunTask(string name, Func<Action<int>, Task> fn);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Packaging.Abstractions
|
||||
namespace Velopack.Packaging.Abstractions;
|
||||
|
||||
public interface IOutputOptions
|
||||
{
|
||||
public interface IOutputOptions
|
||||
{
|
||||
DirectoryInfo ReleaseDir { get; }
|
||||
}
|
||||
DirectoryInfo ReleaseDir { get; }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace Velopack.Packaging.Abstractions
|
||||
namespace Velopack.Packaging.Abstractions;
|
||||
|
||||
public interface IPackOptions : INugetPackCommand, IPlatformOptions
|
||||
{
|
||||
public interface IPackOptions : INugetPackCommand, IPlatformOptions
|
||||
{
|
||||
string Channel { get; }
|
||||
DeltaMode DeltaMode { get; }
|
||||
string EntryExecutableName { get; }
|
||||
}
|
||||
string Channel { get; }
|
||||
DeltaMode DeltaMode { get; }
|
||||
string EntryExecutableName { get; }
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Packaging.Abstractions
|
||||
namespace Velopack.Packaging.Abstractions;
|
||||
|
||||
public interface IPlatformOptions : IOutputOptions
|
||||
{
|
||||
public interface IPlatformOptions : IOutputOptions
|
||||
{
|
||||
RID TargetRuntime { get; }
|
||||
}
|
||||
RID TargetRuntime { get; }
|
||||
}
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
using Velopack.Json;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
public class BuildAssets
|
||||
{
|
||||
public class BuildAssets
|
||||
public List<string> Files { get; set; } = new List<string>();
|
||||
|
||||
public List<VelopackAsset> GetReleaseEntries()
|
||||
{
|
||||
public List<string> Files { get; set; } = new List<string>();
|
||||
return Files.Where(x => x.EndsWith(".nupkg"))
|
||||
.Select(f => VelopackAsset.FromNupkg(f))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<VelopackAsset> GetReleaseEntries()
|
||||
{
|
||||
return Files.Where(x => x.EndsWith(".nupkg"))
|
||||
.Select(f => VelopackAsset.FromNupkg(f))
|
||||
.ToList();
|
||||
}
|
||||
public static void Write(string outputDir, string channel, IEnumerable<string> files)
|
||||
{
|
||||
var assets = new BuildAssets {
|
||||
Files = files.OrderBy(f => f).ToList(),
|
||||
};
|
||||
var path = Path.Combine(outputDir, $"assets.{channel}.json");
|
||||
var json = SimpleJson.SerializeObject(assets);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
|
||||
public static void Write(string outputDir, string channel, IEnumerable<string> files)
|
||||
{
|
||||
var assets = new BuildAssets {
|
||||
Files = files.OrderBy(f => f).ToList(),
|
||||
};
|
||||
var path = Path.Combine(outputDir, $"assets.{channel}.json");
|
||||
var json = SimpleJson.SerializeObject(assets);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
|
||||
public static BuildAssets Read(string outputDir, string channel)
|
||||
{
|
||||
var path = Path.Combine(outputDir, $"assets.{channel}.json");
|
||||
if (!File.Exists(path)) {
|
||||
throw new UserInfoException($"Could not find assets file for channel '{channel}' (looking for '{Path.GetFileName(path)}' in directory '{outputDir}'). " +
|
||||
$"If you've just created a Velopack release, verify you're calling this command with the same '--channel' as you did with 'pack'.");
|
||||
}
|
||||
return SimpleJson.DeserializeObject<BuildAssets>(File.ReadAllText(path));
|
||||
public static BuildAssets Read(string outputDir, string channel)
|
||||
{
|
||||
var path = Path.Combine(outputDir, $"assets.{channel}.json");
|
||||
if (!File.Exists(path)) {
|
||||
throw new UserInfoException($"Could not find assets file for channel '{channel}' (looking for '{Path.GetFileName(path)}' in directory '{outputDir}'). " +
|
||||
$"If you've just created a Velopack release, verify you're calling this command with the same '--channel' as you did with 'pack'.");
|
||||
}
|
||||
return SimpleJson.DeserializeObject<BuildAssets>(File.ReadAllText(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Packaging.Abstractions;
|
||||
|
||||
namespace Velopack.Packaging.Commands
|
||||
namespace Velopack.Packaging.Commands;
|
||||
|
||||
public class DeltaGenCommandRunner : ICommand<DeltaGenOptions>
|
||||
{
|
||||
public class DeltaGenCommandRunner : ICommand<DeltaGenOptions>
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFancyConsole _console;
|
||||
|
||||
public DeltaGenCommandRunner(ILogger logger, IFancyConsole console)
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFancyConsole _console;
|
||||
_logger = logger;
|
||||
_console = console;
|
||||
}
|
||||
|
||||
public DeltaGenCommandRunner(ILogger logger, IFancyConsole console)
|
||||
{
|
||||
_logger = logger;
|
||||
_console = console;
|
||||
}
|
||||
|
||||
public async Task Run(DeltaGenOptions options)
|
||||
{
|
||||
await _console.ExecuteProgressAsync(async (ctx) => {
|
||||
var pold = new ReleasePackage(options.BasePackage);
|
||||
var pnew = new ReleasePackage(options.NewPackage);
|
||||
await ctx.RunTask($"Building delta {pold.Version} -> {pnew.Version}", (progress) => {
|
||||
var delta = new DeltaPackageBuilder(_logger);
|
||||
delta.CreateDeltaPackage(pold, pnew, options.OutputFile, options.DeltaMode, progress);
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
public async Task Run(DeltaGenOptions options)
|
||||
{
|
||||
await _console.ExecuteProgressAsync(async (ctx) => {
|
||||
var pold = new ReleasePackage(options.BasePackage);
|
||||
var pnew = new ReleasePackage(options.NewPackage);
|
||||
await ctx.RunTask($"Building delta {pold.Version} -> {pnew.Version}", (progress) => {
|
||||
var delta = new DeltaPackageBuilder(_logger);
|
||||
delta.CreateDeltaPackage(pold, pnew, options.OutputFile, options.DeltaMode, progress);
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
namespace Velopack.Packaging.Commands
|
||||
namespace Velopack.Packaging.Commands;
|
||||
|
||||
public class DeltaGenOptions
|
||||
{
|
||||
public class DeltaGenOptions
|
||||
{
|
||||
public DeltaMode DeltaMode { get; set; }
|
||||
public DeltaMode DeltaMode { get; set; }
|
||||
|
||||
public string BasePackage { get; set; }
|
||||
public string BasePackage { get; set; }
|
||||
|
||||
public string NewPackage { get; set; }
|
||||
public string NewPackage { get; set; }
|
||||
|
||||
public string OutputFile { get; set; }
|
||||
}
|
||||
public string OutputFile { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,50 +3,49 @@ using Velopack.Compression;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
using Velopack.Packaging.Abstractions;
|
||||
|
||||
namespace Velopack.Packaging.Commands
|
||||
namespace Velopack.Packaging.Commands;
|
||||
|
||||
public class DeltaPatchCommandRunner : ICommand<DeltaPatchOptions>
|
||||
{
|
||||
public class DeltaPatchCommandRunner : ICommand<DeltaPatchOptions>
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFancyConsole _console;
|
||||
|
||||
public DeltaPatchCommandRunner(ILogger logger, IFancyConsole console)
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFancyConsole _console;
|
||||
_logger = logger;
|
||||
_console = console;
|
||||
}
|
||||
|
||||
public DeltaPatchCommandRunner(ILogger logger, IFancyConsole console)
|
||||
{
|
||||
_logger = logger;
|
||||
_console = console;
|
||||
public async Task Run(DeltaPatchOptions options)
|
||||
{
|
||||
if (options.PatchFiles.Length == 0) {
|
||||
throw new UserInfoException("Must specify at least one patch file.");
|
||||
}
|
||||
|
||||
public async Task Run(DeltaPatchOptions options)
|
||||
{
|
||||
if (options.PatchFiles.Length == 0) {
|
||||
throw new UserInfoException("Must specify at least one patch file.");
|
||||
foreach (var p in options.PatchFiles) {
|
||||
if (p == null || !p.Exists) {
|
||||
throw new UserInfoException($"Patch file '{p.FullName}' does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var p in options.PatchFiles) {
|
||||
if (p == null || !p.Exists) {
|
||||
throw new UserInfoException($"Patch file '{p.FullName}' does not exist.");
|
||||
}
|
||||
}
|
||||
var tmp = Utility.GetDefaultTempBaseDirectory();
|
||||
using var _1 = Utility.GetTempDirectory(out var workDir);
|
||||
|
||||
var tmp = Utility.GetDefaultTempBaseDirectory();
|
||||
using var _1 = Utility.GetTempDirectory(out var workDir);
|
||||
var delta = new DeltaEmbedded(HelperFile.GetZstdPath(), _logger, tmp);
|
||||
EasyZip.ExtractZipToDirectory(_logger, options.BasePackage, workDir);
|
||||
|
||||
var delta = new DeltaEmbedded(HelperFile.GetZstdPath(), _logger, tmp);
|
||||
EasyZip.ExtractZipToDirectory(_logger, options.BasePackage, workDir);
|
||||
|
||||
await _console.ExecuteProgressAsync(async (ctx) => {
|
||||
foreach (var f in options.PatchFiles) {
|
||||
await ctx.RunTask($"Applying {f.Name}", (progress) => {
|
||||
delta.ApplyDeltaPackageFast(workDir, f.FullName, progress);
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
await ctx.RunTask($"Building {Path.GetFileName(options.OutputFile)}", async (progress) => {
|
||||
await EasyZip.CreateZipFromDirectoryAsync(_logger, options.OutputFile, workDir, progress);
|
||||
await _console.ExecuteProgressAsync(async (ctx) => {
|
||||
foreach (var f in options.PatchFiles) {
|
||||
await ctx.RunTask($"Applying {f.Name}", (progress) => {
|
||||
delta.ApplyDeltaPackageFast(workDir, f.FullName, progress);
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
await ctx.RunTask($"Building {Path.GetFileName(options.OutputFile)}", async (progress) => {
|
||||
await EasyZip.CreateZipFromDirectoryAsync(_logger, options.OutputFile, workDir, progress);
|
||||
progress(100);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
namespace Velopack.Packaging.Commands
|
||||
namespace Velopack.Packaging.Commands;
|
||||
|
||||
public class DeltaPatchOptions
|
||||
{
|
||||
public class DeltaPatchOptions
|
||||
{
|
||||
public string BasePackage { get; set; }
|
||||
public string BasePackage { get; set; }
|
||||
|
||||
public FileInfo[] PatchFiles { get; set; }
|
||||
public FileInfo[] PatchFiles { get; set; }
|
||||
|
||||
public string OutputFile { get; set; }
|
||||
}
|
||||
public string OutputFile { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Compression;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
public class DeltaEmbedded
|
||||
{
|
||||
public class DeltaEmbedded
|
||||
private readonly DeltaImpl _delta;
|
||||
|
||||
public DeltaEmbedded(string zstdPath, ILogger logger, string baseTmpDir)
|
||||
{
|
||||
private readonly DeltaImpl _delta;
|
||||
_delta = new DeltaImpl(zstdPath, logger, baseTmpDir);
|
||||
}
|
||||
|
||||
public DeltaEmbedded(string zstdPath, ILogger logger, string baseTmpDir)
|
||||
public void ApplyDeltaPackageFast(string workingPath, string deltaPackageZip, Action<int> progress = null)
|
||||
{
|
||||
_delta.ApplyDeltaPackageFast(workingPath, deltaPackageZip, progress);
|
||||
}
|
||||
|
||||
private class DeltaImpl : DeltaPackage
|
||||
{
|
||||
private readonly Zstd _zstd;
|
||||
|
||||
public DeltaImpl(string zstdPath, ILogger logger, string baseTmpDir) : base(logger, baseTmpDir)
|
||||
{
|
||||
_delta = new DeltaImpl(zstdPath, logger, baseTmpDir);
|
||||
_zstd = new Zstd(zstdPath);
|
||||
}
|
||||
|
||||
public void ApplyDeltaPackageFast(string workingPath, string deltaPackageZip, Action<int> progress = null)
|
||||
protected override void ApplyZstdPatch(string baseFile, string patchFile, string outputFile)
|
||||
{
|
||||
_delta.ApplyDeltaPackageFast(workingPath, deltaPackageZip, progress);
|
||||
}
|
||||
|
||||
private class DeltaImpl : DeltaPackage
|
||||
{
|
||||
private readonly Zstd _zstd;
|
||||
|
||||
public DeltaImpl(string zstdPath, ILogger logger, string baseTmpDir) : base(logger, baseTmpDir)
|
||||
{
|
||||
_zstd = new Zstd(zstdPath);
|
||||
}
|
||||
|
||||
protected override void ApplyZstdPatch(string baseFile, string patchFile, string outputFile)
|
||||
{
|
||||
_zstd.ApplyPatch(baseFile, patchFile, outputFile);
|
||||
}
|
||||
_zstd.ApplyPatch(baseFile, patchFile, outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,28 +6,27 @@ using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Packaging.Exceptions
|
||||
namespace Velopack.Packaging.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Denotes that an error has occurred for which a stack trace should not be printed.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class UserInfoException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotes that an error has occurred for which a stack trace should not be printed.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class UserInfoException : Exception
|
||||
public UserInfoException()
|
||||
{
|
||||
public UserInfoException()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public UserInfoException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
public UserInfoException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public UserInfoException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
public UserInfoException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected UserInfoException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
protected UserInfoException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,16 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Packaging.Exceptions
|
||||
namespace Velopack.Packaging.Exceptions;
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class VelopackAppVerificationException : UserInfoException
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class VelopackAppVerificationException : UserInfoException
|
||||
public VelopackAppVerificationException(string message)
|
||||
: base(
|
||||
$"Failed to verify VelopackApp ({message}). " +
|
||||
$"Ensure you have added the startup code to the beginning of your Program.Main(): VelopackApp.Build().Run(); " +
|
||||
$"and then re-compile/re-publish your application.")
|
||||
{
|
||||
public VelopackAppVerificationException(string message)
|
||||
: base(
|
||||
$"Failed to verify VelopackApp ({message}). " +
|
||||
$"Ensure you have added the startup code to the beginning of your Program.Main(): VelopackApp.Build().Run(); " +
|
||||
$"and then re-compile/re-publish your application.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,79 +6,78 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
public static class Exe
|
||||
{
|
||||
public static class Exe
|
||||
public static void AssertSystemBinaryExists(string binaryName)
|
||||
{
|
||||
public static void AssertSystemBinaryExists(string binaryName)
|
||||
{
|
||||
try {
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
var output = InvokeAndThrowIfNonZero("where", new[] { binaryName }, null);
|
||||
if (String.IsNullOrWhiteSpace(output) || !File.Exists(output))
|
||||
throw new ProcessFailedException("", "");
|
||||
} else if (VelopackRuntimeInfo.IsOSX) {
|
||||
InvokeAndThrowIfNonZero("command", new[] { "-v", binaryName }, null);
|
||||
} else if (VelopackRuntimeInfo.IsLinux) {
|
||||
InvokeAndThrowIfNonZero("which", new[] { binaryName }, null);
|
||||
} else {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
} catch (ProcessFailedException) {
|
||||
throw new Exception($"Could not find '{binaryName}' on the system, ensure it is installed and on the PATH.");
|
||||
try {
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
var output = InvokeAndThrowIfNonZero("where", new[] { binaryName }, null);
|
||||
if (String.IsNullOrWhiteSpace(output) || !File.Exists(output))
|
||||
throw new ProcessFailedException("", "");
|
||||
} else if (VelopackRuntimeInfo.IsOSX) {
|
||||
InvokeAndThrowIfNonZero("command", new[] { "-v", binaryName }, null);
|
||||
} else if (VelopackRuntimeInfo.IsLinux) {
|
||||
InvokeAndThrowIfNonZero("which", new[] { binaryName }, null);
|
||||
} else {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static string InvokeAndThrowIfNonZero(string exePath, IEnumerable<string> args, string workingDir, IDictionary<string, string> envVar = null)
|
||||
{
|
||||
var result = InvokeProcess(exePath, args, workingDir, CancellationToken.None, envVar);
|
||||
ProcessFailedException.ThrowIfNonZero(result);
|
||||
return result.StdOutput;
|
||||
}
|
||||
|
||||
public static (int ExitCode, string StdOutput) InvokeProcess(ProcessStartInfo psi, CancellationToken ct)
|
||||
{
|
||||
var pi = Process.Start(psi);
|
||||
while (!ct.IsCancellationRequested) {
|
||||
if (pi.WaitForExit(500)) break;
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested && !pi.HasExited) {
|
||||
pi.Kill();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
string output = pi.StandardOutput.ReadToEnd();
|
||||
string error = pi.StandardError.ReadToEnd();
|
||||
var all = (output ?? "") + Environment.NewLine + (error ?? "");
|
||||
|
||||
return (pi.ExitCode, all.Trim());
|
||||
}
|
||||
|
||||
public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct = default, IDictionary<string, string> envVar = null)
|
||||
{
|
||||
var psi = CreateProcessStartInfo(fileName, workingDirectory);
|
||||
if (envVar != null) {
|
||||
foreach (var kvp in envVar) {
|
||||
psi.EnvironmentVariables[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
psi.AppendArgumentListSafe(args, out var argString);
|
||||
var p = InvokeProcess(psi, ct);
|
||||
return (p.ExitCode, p.StdOutput, $"{fileName} {argString}");
|
||||
}
|
||||
|
||||
public static ProcessStartInfo CreateProcessStartInfo(string fileName, string workingDirectory)
|
||||
{
|
||||
var psi = new ProcessStartInfo(fileName);
|
||||
psi.UseShellExecute = false;
|
||||
psi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
psi.ErrorDialog = false;
|
||||
psi.CreateNoWindow = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory;
|
||||
return psi;
|
||||
} catch (ProcessFailedException) {
|
||||
throw new Exception($"Could not find '{binaryName}' on the system, ensure it is installed and on the PATH.");
|
||||
}
|
||||
}
|
||||
|
||||
public static string InvokeAndThrowIfNonZero(string exePath, IEnumerable<string> args, string workingDir, IDictionary<string, string> envVar = null)
|
||||
{
|
||||
var result = InvokeProcess(exePath, args, workingDir, CancellationToken.None, envVar);
|
||||
ProcessFailedException.ThrowIfNonZero(result);
|
||||
return result.StdOutput;
|
||||
}
|
||||
|
||||
public static (int ExitCode, string StdOutput) InvokeProcess(ProcessStartInfo psi, CancellationToken ct)
|
||||
{
|
||||
var pi = Process.Start(psi);
|
||||
while (!ct.IsCancellationRequested) {
|
||||
if (pi.WaitForExit(500)) break;
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested && !pi.HasExited) {
|
||||
pi.Kill();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
string output = pi.StandardOutput.ReadToEnd();
|
||||
string error = pi.StandardError.ReadToEnd();
|
||||
var all = (output ?? "") + Environment.NewLine + (error ?? "");
|
||||
|
||||
return (pi.ExitCode, all.Trim());
|
||||
}
|
||||
|
||||
public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct = default, IDictionary<string, string> envVar = null)
|
||||
{
|
||||
var psi = CreateProcessStartInfo(fileName, workingDirectory);
|
||||
if (envVar != null) {
|
||||
foreach (var kvp in envVar) {
|
||||
psi.EnvironmentVariables[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
psi.AppendArgumentListSafe(args, out var argString);
|
||||
var p = InvokeProcess(psi, ct);
|
||||
return (p.ExitCode, p.StdOutput, $"{fileName} {argString}");
|
||||
}
|
||||
|
||||
public static ProcessStartInfo CreateProcessStartInfo(string fileName, string workingDirectory)
|
||||
{
|
||||
var psi = new ProcessStartInfo(fileName);
|
||||
psi.UseShellExecute = false;
|
||||
psi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
psi.ErrorDialog = false;
|
||||
psi.CreateNoWindow = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory;
|
||||
return psi;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,204 +9,204 @@ using Velopack.NuGet;
|
||||
using Velopack.Packaging.Abstractions;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
public abstract class PackageBuilder<T> : ICommand<T>
|
||||
where T : class, IPackOptions
|
||||
{
|
||||
public abstract class PackageBuilder<T> : ICommand<T>
|
||||
where T : class, IPackOptions
|
||||
protected RuntimeOs SupportedTargetOs { get; }
|
||||
|
||||
protected ILogger Log { get; }
|
||||
|
||||
protected IFancyConsole Console { get; }
|
||||
|
||||
protected DirectoryInfo TempDir { get; private set; }
|
||||
|
||||
protected T Options { get; private set; }
|
||||
|
||||
protected string MainExeName { get; private set; }
|
||||
|
||||
protected string MainExePath { get; private set; }
|
||||
|
||||
protected string Channel { get; private set; }
|
||||
|
||||
protected string RuntimeDependencies { get; private set; }
|
||||
|
||||
private readonly Regex REGEX_EXCLUDES = new Regex(@".*[\\\/]createdump.*|.*\.vshost\..*|.*\.nupkg$|.*\.pdb$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public PackageBuilder(RuntimeOs supportedOs, ILogger logger, IFancyConsole console)
|
||||
{
|
||||
protected RuntimeOs SupportedTargetOs { get; }
|
||||
SupportedTargetOs = supportedOs;
|
||||
Log = logger;
|
||||
Console = console;
|
||||
}
|
||||
|
||||
protected ILogger Log { get; }
|
||||
public async Task Run(T options)
|
||||
{
|
||||
if (options.TargetRuntime?.BaseRID != SupportedTargetOs)
|
||||
throw new UserInfoException($"To build packages for {SupportedTargetOs.GetOsLongName()}, " +
|
||||
$"the target rid must be {SupportedTargetOs} (actually was {options.TargetRuntime?.BaseRID}).");
|
||||
|
||||
protected IFancyConsole Console { get; }
|
||||
Log.Info("Beginning to package release.");
|
||||
Log.Info("Releases Directory: " + options.ReleaseDir.FullName);
|
||||
|
||||
protected DirectoryInfo TempDir { get; private set; }
|
||||
var releaseDir = options.ReleaseDir;
|
||||
var channel = options.Channel?.ToLower() ?? ReleaseEntryHelper.GetDefaultChannel(SupportedTargetOs);
|
||||
Channel = channel;
|
||||
|
||||
protected T Options { get; private set; }
|
||||
|
||||
protected string MainExeName { get; private set; }
|
||||
|
||||
protected string MainExePath { get; private set; }
|
||||
|
||||
protected string Channel { get; private set; }
|
||||
|
||||
protected string RuntimeDependencies { get; private set; }
|
||||
|
||||
private readonly Regex REGEX_EXCLUDES = new Regex(@".*[\\\/]createdump.*|.*\.vshost\..*|.*\.nupkg$|.*\.pdb$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public PackageBuilder(RuntimeOs supportedOs, ILogger logger, IFancyConsole console)
|
||||
{
|
||||
SupportedTargetOs = supportedOs;
|
||||
Log = logger;
|
||||
Console = console;
|
||||
var entryHelper = new ReleaseEntryHelper(releaseDir.FullName, channel, Log);
|
||||
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) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Run(T options)
|
||||
var packId = options.PackId;
|
||||
var packDirectory = options.PackDirectory;
|
||||
var packVersion = options.PackVersion;
|
||||
var semVer = SemanticVersion.Parse(packVersion);
|
||||
|
||||
// check that entry exe exists
|
||||
var mainExt = options.TargetRuntime.BaseRID == RuntimeOs.Windows ? ".exe" : "";
|
||||
var mainExeName = options.EntryExecutableName ?? (options.PackId + mainExt);
|
||||
var mainExePath = Path.Combine(packDirectory, mainExeName);
|
||||
|
||||
// TODO: this is a hack, fix this.
|
||||
if (!File.Exists(mainExePath) && VelopackRuntimeInfo.IsLinux)
|
||||
mainExePath = Path.Combine(packDirectory, "usr", "bin", mainExeName);
|
||||
|
||||
if (!File.Exists(mainExePath)) {
|
||||
throw new UserInfoException(
|
||||
$"Could not find main application executable (the one that runs 'VelopackApp.Build().Run()'). " + Environment.NewLine +
|
||||
$"I searched for '{mainExeName}' in {packDirectory}." + Environment.NewLine +
|
||||
$"If your main binary is not named '{mainExeName}', please specify the name with the argument: --exeName {{yourBinary.exe}}");
|
||||
}
|
||||
MainExeName = mainExeName;
|
||||
MainExePath = mainExePath;
|
||||
|
||||
using var _1 = Utility.GetTempDirectory(out var pkgTempDir);
|
||||
TempDir = new DirectoryInfo(pkgTempDir);
|
||||
Options = options;
|
||||
RuntimeDependencies = GetRuntimeDependencies();
|
||||
|
||||
ConcurrentBag<(string from, string to)> filesToCopy = new();
|
||||
|
||||
string getIncompletePath(string fileName)
|
||||
{
|
||||
if (options.TargetRuntime?.BaseRID != SupportedTargetOs)
|
||||
throw new UserInfoException($"To build packages for {SupportedTargetOs.GetOsLongName()}, " +
|
||||
$"the target rid must be {SupportedTargetOs} (actually was {options.TargetRuntime?.BaseRID}).");
|
||||
var incomplete = Path.Combine(pkgTempDir, fileName);
|
||||
var final = Path.Combine(releaseDir.FullName, fileName);
|
||||
try { File.Delete(incomplete); } catch { }
|
||||
filesToCopy.Add((incomplete, final));
|
||||
return incomplete;
|
||||
}
|
||||
|
||||
Log.Info("Beginning to package release.");
|
||||
Log.Info("Releases Directory: " + options.ReleaseDir.FullName);
|
||||
|
||||
var releaseDir = options.ReleaseDir;
|
||||
var channel = options.Channel?.ToLower() ?? ReleaseEntryHelper.GetDefaultChannel(SupportedTargetOs);
|
||||
Channel = channel;
|
||||
|
||||
var entryHelper = new ReleaseEntryHelper(releaseDir.FullName, channel, Log);
|
||||
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) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
var packId = options.PackId;
|
||||
var packDirectory = options.PackDirectory;
|
||||
var packVersion = options.PackVersion;
|
||||
var semVer = SemanticVersion.Parse(packVersion);
|
||||
|
||||
// check that entry exe exists
|
||||
var mainExt = options.TargetRuntime.BaseRID == RuntimeOs.Windows ? ".exe" : "";
|
||||
var mainExeName = options.EntryExecutableName ?? (options.PackId + mainExt);
|
||||
var mainExePath = Path.Combine(packDirectory, mainExeName);
|
||||
|
||||
// TODO: this is a hack, fix this.
|
||||
if (!File.Exists(mainExePath) && VelopackRuntimeInfo.IsLinux)
|
||||
mainExePath = Path.Combine(packDirectory, "usr", "bin", mainExeName);
|
||||
|
||||
if (!File.Exists(mainExePath)) {
|
||||
throw new UserInfoException(
|
||||
$"Could not find main application executable (the one that runs 'VelopackApp.Build().Run()'). " + Environment.NewLine +
|
||||
$"I searched for '{mainExeName}' in {packDirectory}." + Environment.NewLine +
|
||||
$"If your main binary is not named '{mainExeName}', please specify the name with the argument: --exeName {{yourBinary.exe}}");
|
||||
}
|
||||
MainExeName = mainExeName;
|
||||
MainExePath = mainExePath;
|
||||
|
||||
using var _1 = Utility.GetTempDirectory(out var pkgTempDir);
|
||||
TempDir = new DirectoryInfo(pkgTempDir);
|
||||
Options = options;
|
||||
RuntimeDependencies = GetRuntimeDependencies();
|
||||
|
||||
ConcurrentBag<(string from, string to)> filesToCopy = new();
|
||||
|
||||
string getIncompletePath(string fileName)
|
||||
{
|
||||
var incomplete = Path.Combine(pkgTempDir, fileName);
|
||||
var final = Path.Combine(releaseDir.FullName, fileName);
|
||||
try { File.Delete(incomplete); } catch { }
|
||||
filesToCopy.Add((incomplete, final));
|
||||
return incomplete;
|
||||
}
|
||||
|
||||
await Console.ExecuteProgressAsync(async (ctx) => {
|
||||
ReleasePackage prev = null;
|
||||
await ctx.RunTask("Pre-process steps", async (progress) => {
|
||||
prev = entryHelper.GetPreviousFullRelease(NuGetVersion.Parse(packVersion));
|
||||
packDirectory = await PreprocessPackDir(progress, packDirectory);
|
||||
});
|
||||
|
||||
if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
|
||||
await ctx.RunTask("Code-sign application", async (progress) => {
|
||||
await CodeSign(progress, packDirectory);
|
||||
});
|
||||
}
|
||||
|
||||
var portableTask = ctx.RunTask("Building portable package", async (progress) => {
|
||||
var suggestedName = ReleaseEntryHelper.GetSuggestedPortableName(packId, channel);
|
||||
var path = getIncompletePath(suggestedName);
|
||||
await CreatePortablePackage(progress, packDirectory, path);
|
||||
});
|
||||
|
||||
// TODO: hack, this is a prerequisite for building full package but only on linux
|
||||
if (VelopackRuntimeInfo.IsLinux) await portableTask;
|
||||
|
||||
string releasePath = null;
|
||||
await ctx.RunTask($"Building release {packVersion}", async (progress) => {
|
||||
var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, false);
|
||||
releasePath = getIncompletePath(suggestedName);
|
||||
await CreateReleasePackage(progress, packDirectory, releasePath);
|
||||
});
|
||||
|
||||
Task setupTask = null;
|
||||
if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
|
||||
setupTask = ctx.RunTask("Building setup package", async (progress) => {
|
||||
var suggestedName = ReleaseEntryHelper.GetSuggestedSetupName(packId, channel);
|
||||
var path = getIncompletePath(suggestedName);
|
||||
await CreateSetupPackage(progress, releasePath, packDirectory, path);
|
||||
});
|
||||
}
|
||||
|
||||
if (prev != null && options.DeltaMode != DeltaMode.None) {
|
||||
await ctx.RunTask($"Building delta {prev.Version} -> {packVersion}", async (progress) => {
|
||||
var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, true);
|
||||
var deltaPkg = await CreateDeltaPackage(progress, releasePath, prev.PackageFile, getIncompletePath(suggestedName), options.DeltaMode);
|
||||
});
|
||||
}
|
||||
|
||||
if (!VelopackRuntimeInfo.IsLinux) await portableTask;
|
||||
if (setupTask != null) await setupTask;
|
||||
|
||||
await ctx.RunTask("Post-process steps", (progress) => {
|
||||
var expectedAssets = VelopackRuntimeInfo.IsLinux ? 2 : 3;
|
||||
if (prev != null && options.DeltaMode != DeltaMode.None) expectedAssets += 1;
|
||||
if (filesToCopy.Count != expectedAssets) {
|
||||
throw new Exception($"Expected {expectedAssets} assets to be created, but only {filesToCopy.Count} were.");
|
||||
}
|
||||
|
||||
foreach (var f in filesToCopy) {
|
||||
Utility.MoveFile(f.from, f.to, true);
|
||||
}
|
||||
|
||||
ReleaseEntryHelper.UpdateReleaseFiles(releaseDir.FullName, Log);
|
||||
BuildAssets.Write(releaseDir.FullName, channel, filesToCopy.Select(x => x.to));
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
await Console.ExecuteProgressAsync(async (ctx) => {
|
||||
ReleasePackage prev = null;
|
||||
await ctx.RunTask("Pre-process steps", async (progress) => {
|
||||
prev = entryHelper.GetPreviousFullRelease(NuGetVersion.Parse(packVersion));
|
||||
packDirectory = await PreprocessPackDir(progress, packDirectory);
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual string GetRuntimeDependencies()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
|
||||
await ctx.RunTask("Code-sign application", async (progress) => {
|
||||
await CodeSign(progress, packDirectory);
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual string GenerateNuspecContent()
|
||||
{
|
||||
var packId = Options.PackId;
|
||||
var packTitle = Options.PackTitle ?? Options.PackId;
|
||||
var packAuthors = Options.PackAuthors ?? Options.PackId;
|
||||
var packVersion = Options.PackVersion;
|
||||
var releaseNotes = Options.ReleaseNotes;
|
||||
var rid = Options.TargetRuntime;
|
||||
var portableTask = ctx.RunTask("Building portable package", async (progress) => {
|
||||
var suggestedName = ReleaseEntryHelper.GetSuggestedPortableName(packId, channel);
|
||||
var path = getIncompletePath(suggestedName);
|
||||
await CreatePortablePackage(progress, packDirectory, path);
|
||||
});
|
||||
|
||||
string releaseNotesText = "";
|
||||
if (!String.IsNullOrEmpty(releaseNotes)) {
|
||||
var markdown = File.ReadAllText(releaseNotes);
|
||||
var html = Markdown.ToHtml(markdown);
|
||||
releaseNotesText = $"""
|
||||
// TODO: hack, this is a prerequisite for building full package but only on linux
|
||||
if (VelopackRuntimeInfo.IsLinux) await portableTask;
|
||||
|
||||
string releasePath = null;
|
||||
await ctx.RunTask($"Building release {packVersion}", async (progress) => {
|
||||
var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, false);
|
||||
releasePath = getIncompletePath(suggestedName);
|
||||
await CreateReleasePackage(progress, packDirectory, releasePath);
|
||||
});
|
||||
|
||||
Task setupTask = null;
|
||||
if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
|
||||
setupTask = ctx.RunTask("Building setup package", async (progress) => {
|
||||
var suggestedName = ReleaseEntryHelper.GetSuggestedSetupName(packId, channel);
|
||||
var path = getIncompletePath(suggestedName);
|
||||
await CreateSetupPackage(progress, releasePath, packDirectory, path);
|
||||
});
|
||||
}
|
||||
|
||||
if (prev != null && options.DeltaMode != DeltaMode.None) {
|
||||
await ctx.RunTask($"Building delta {prev.Version} -> {packVersion}", async (progress) => {
|
||||
var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, true);
|
||||
var deltaPkg = await CreateDeltaPackage(progress, releasePath, prev.PackageFile, getIncompletePath(suggestedName), options.DeltaMode);
|
||||
});
|
||||
}
|
||||
|
||||
if (!VelopackRuntimeInfo.IsLinux) await portableTask;
|
||||
if (setupTask != null) await setupTask;
|
||||
|
||||
await ctx.RunTask("Post-process steps", (progress) => {
|
||||
var expectedAssets = VelopackRuntimeInfo.IsLinux ? 2 : 3;
|
||||
if (prev != null && options.DeltaMode != DeltaMode.None) expectedAssets += 1;
|
||||
if (filesToCopy.Count != expectedAssets) {
|
||||
throw new Exception($"Expected {expectedAssets} assets to be created, but only {filesToCopy.Count} were.");
|
||||
}
|
||||
|
||||
foreach (var f in filesToCopy) {
|
||||
Utility.MoveFile(f.from, f.to, true);
|
||||
}
|
||||
|
||||
ReleaseEntryHelper.UpdateReleaseFiles(releaseDir.FullName, Log);
|
||||
BuildAssets.Write(releaseDir.FullName, channel, filesToCopy.Select(x => x.to));
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual string GetRuntimeDependencies()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual string GenerateNuspecContent()
|
||||
{
|
||||
var packId = Options.PackId;
|
||||
var packTitle = Options.PackTitle ?? Options.PackId;
|
||||
var packAuthors = Options.PackAuthors ?? Options.PackId;
|
||||
var packVersion = Options.PackVersion;
|
||||
var releaseNotes = Options.ReleaseNotes;
|
||||
var rid = Options.TargetRuntime;
|
||||
|
||||
string releaseNotesText = "";
|
||||
if (!String.IsNullOrEmpty(releaseNotes)) {
|
||||
var markdown = File.ReadAllText(releaseNotes);
|
||||
var html = Markdown.ToHtml(markdown);
|
||||
releaseNotesText = $"""
|
||||
<releaseNotes>{SecurityElement.Escape(markdown)}</releaseNotes>
|
||||
<releaseNotesHtml><![CDATA[{"\n"}{html}{"\n"}]]></releaseNotesHtml>
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
||||
string osMinVersionText = "";
|
||||
if (rid?.HasVersion == true) {
|
||||
osMinVersionText = $"<osMinVersion>{rid.Version}</osMinVersion>";
|
||||
}
|
||||
string osMinVersionText = "";
|
||||
if (rid?.HasVersion == true) {
|
||||
osMinVersionText = $"<osMinVersion>{rid.Version}</osMinVersion>";
|
||||
}
|
||||
|
||||
string machineArchitectureText = "";
|
||||
if (rid?.HasArchitecture == true) {
|
||||
machineArchitectureText = $"<machineArchitecture>{rid.Architecture}</machineArchitecture>";
|
||||
}
|
||||
string machineArchitectureText = "";
|
||||
if (rid?.HasArchitecture == true) {
|
||||
machineArchitectureText = $"<machineArchitecture>{rid.Architecture}</machineArchitecture>";
|
||||
}
|
||||
|
||||
string runtimeDependenciesText = "";
|
||||
if (!String.IsNullOrWhiteSpace(RuntimeDependencies)) {
|
||||
runtimeDependenciesText = $"<runtimeDependencies>{RuntimeDependencies}</runtimeDependencies>";
|
||||
}
|
||||
string runtimeDependenciesText = "";
|
||||
if (!String.IsNullOrWhiteSpace(RuntimeDependencies)) {
|
||||
runtimeDependenciesText = $"<runtimeDependencies>{RuntimeDependencies}</runtimeDependencies>";
|
||||
}
|
||||
|
||||
string nuspec = $"""
|
||||
string nuspec = $"""
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
@@ -227,93 +227,93 @@ namespace Velopack.Packaging
|
||||
</package>
|
||||
""".Trim();
|
||||
|
||||
return nuspec;
|
||||
return nuspec;
|
||||
}
|
||||
|
||||
protected abstract Task<string> PreprocessPackDir(Action<int> progress, string packDir);
|
||||
|
||||
protected virtual Task CodeSign(Action<int> progress, string packDir)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected abstract Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath);
|
||||
|
||||
protected virtual Task<string> CreateDeltaPackage(Action<int> progress, string releasePkg, string prevReleasePkg, string outputPath, DeltaMode mode)
|
||||
{
|
||||
var deltaBuilder = new DeltaPackageBuilder(Log);
|
||||
var (dp, stats) = deltaBuilder.CreateDeltaPackage(new ReleasePackage(prevReleasePkg), new ReleasePackage(releasePkg), outputPath, mode, progress);
|
||||
return Task.FromResult(dp.PackageFile);
|
||||
}
|
||||
|
||||
protected virtual Task CreateSetupPackage(Action<int> progress, string releasePkg, string packDir, string outputPath)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual async Task CreateReleasePackage(Action<int> progress, string packDir, string outputPath)
|
||||
{
|
||||
var stagingDir = TempDir.CreateSubdirectory("CreateReleasePackage");
|
||||
|
||||
var nuspecPath = Path.Combine(stagingDir.FullName, Options.PackId + ".nuspec");
|
||||
File.WriteAllText(nuspecPath, GenerateNuspecContent());
|
||||
|
||||
var appDir = stagingDir.CreateSubdirectory("lib").CreateSubdirectory("app");
|
||||
CopyFiles(new DirectoryInfo(packDir), appDir, Utility.CreateProgressDelegate(progress, 0, 30));
|
||||
|
||||
var metadataFiles = GetReleaseMetadataFiles();
|
||||
foreach (var kvp in metadataFiles) {
|
||||
File.Copy(kvp.Value, Path.Combine(stagingDir.FullName, kvp.Key), true);
|
||||
}
|
||||
|
||||
protected abstract Task<string> PreprocessPackDir(Action<int> progress, string packDir);
|
||||
AddContentTypesAndRel(nuspecPath);
|
||||
|
||||
protected virtual Task CodeSign(Action<int> progress, string packDir)
|
||||
await EasyZip.CreateZipFromDirectoryAsync(Log, outputPath, stagingDir.FullName, Utility.CreateProgressDelegate(progress, 30, 100));
|
||||
progress(100);
|
||||
}
|
||||
|
||||
protected virtual Dictionary<string, string> GetReleaseMetadataFiles()
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
protected virtual void CopyFiles(DirectoryInfo source, DirectoryInfo target, Action<int> progress, bool excludeAnnoyances = false)
|
||||
{
|
||||
var numFiles = source.EnumerateFiles("*", SearchOption.AllDirectories).Count();
|
||||
int currentFile = 0;
|
||||
|
||||
void CopyFilesInternal(DirectoryInfo source, DirectoryInfo target)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected abstract Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath);
|
||||
|
||||
protected virtual Task<string> CreateDeltaPackage(Action<int> progress, string releasePkg, string prevReleasePkg, string outputPath, DeltaMode mode)
|
||||
{
|
||||
var deltaBuilder = new DeltaPackageBuilder(Log);
|
||||
var (dp, stats) = deltaBuilder.CreateDeltaPackage(new ReleasePackage(prevReleasePkg), new ReleasePackage(releasePkg), outputPath, mode, progress);
|
||||
return Task.FromResult(dp.PackageFile);
|
||||
}
|
||||
|
||||
protected virtual Task CreateSetupPackage(Action<int> progress, string releasePkg, string packDir, string outputPath)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual async Task CreateReleasePackage(Action<int> progress, string packDir, string outputPath)
|
||||
{
|
||||
var stagingDir = TempDir.CreateSubdirectory("CreateReleasePackage");
|
||||
|
||||
var nuspecPath = Path.Combine(stagingDir.FullName, Options.PackId + ".nuspec");
|
||||
File.WriteAllText(nuspecPath, GenerateNuspecContent());
|
||||
|
||||
var appDir = stagingDir.CreateSubdirectory("lib").CreateSubdirectory("app");
|
||||
CopyFiles(new DirectoryInfo(packDir), appDir, Utility.CreateProgressDelegate(progress, 0, 30));
|
||||
|
||||
var metadataFiles = GetReleaseMetadataFiles();
|
||||
foreach (var kvp in metadataFiles) {
|
||||
File.Copy(kvp.Value, Path.Combine(stagingDir.FullName, kvp.Key), true);
|
||||
foreach (var fileInfo in source.GetFiles()) {
|
||||
var path = Path.Combine(target.FullName, fileInfo.Name);
|
||||
currentFile++;
|
||||
progress((int) ((double) currentFile / numFiles * 100));
|
||||
if (excludeAnnoyances && REGEX_EXCLUDES.IsMatch(path)) {
|
||||
Log.Debug("Skipping because matched exclude pattern: " + path);
|
||||
continue;
|
||||
}
|
||||
fileInfo.CopyTo(path, true);
|
||||
}
|
||||
|
||||
AddContentTypesAndRel(nuspecPath);
|
||||
|
||||
await EasyZip.CreateZipFromDirectoryAsync(Log, outputPath, stagingDir.FullName, Utility.CreateProgressDelegate(progress, 30, 100));
|
||||
progress(100);
|
||||
}
|
||||
|
||||
protected virtual Dictionary<string, string> GetReleaseMetadataFiles()
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
protected virtual void CopyFiles(DirectoryInfo source, DirectoryInfo target, Action<int> progress, bool excludeAnnoyances = false)
|
||||
{
|
||||
var numFiles = source.EnumerateFiles("*", SearchOption.AllDirectories).Count();
|
||||
int currentFile = 0;
|
||||
|
||||
void CopyFilesInternal(DirectoryInfo source, DirectoryInfo target)
|
||||
{
|
||||
foreach (var fileInfo in source.GetFiles()) {
|
||||
var path = Path.Combine(target.FullName, fileInfo.Name);
|
||||
currentFile++;
|
||||
progress((int) ((double) currentFile / numFiles * 100));
|
||||
if (excludeAnnoyances && REGEX_EXCLUDES.IsMatch(path)) {
|
||||
Log.Debug("Skipping because matched exclude pattern: " + path);
|
||||
continue;
|
||||
}
|
||||
fileInfo.CopyTo(path, true);
|
||||
}
|
||||
|
||||
foreach (var sourceSubDir in source.GetDirectories()) {
|
||||
var targetSubDir = target.CreateSubdirectory(sourceSubDir.Name);
|
||||
CopyFilesInternal(sourceSubDir, targetSubDir);
|
||||
}
|
||||
foreach (var sourceSubDir in source.GetDirectories()) {
|
||||
var targetSubDir = target.CreateSubdirectory(sourceSubDir.Name);
|
||||
CopyFilesInternal(sourceSubDir, targetSubDir);
|
||||
}
|
||||
|
||||
CopyFilesInternal(source, target);
|
||||
}
|
||||
|
||||
protected virtual void AddContentTypesAndRel(string nuspecPath)
|
||||
{
|
||||
var rootDirectory = Path.GetDirectoryName(nuspecPath);
|
||||
var extensions = Directory.EnumerateFiles(rootDirectory, "*", SearchOption.AllDirectories)
|
||||
.Select(p => Path.GetExtension(p).TrimStart('.').ToLower())
|
||||
.Distinct()
|
||||
.Select(ext => $""" <Default Extension="{ext}" ContentType="application/octet" />""")
|
||||
.ToArray();
|
||||
CopyFilesInternal(source, target);
|
||||
}
|
||||
|
||||
var contentType = $"""
|
||||
protected virtual void AddContentTypesAndRel(string nuspecPath)
|
||||
{
|
||||
var rootDirectory = Path.GetDirectoryName(nuspecPath);
|
||||
var extensions = Directory.EnumerateFiles(rootDirectory, "*", SearchOption.AllDirectories)
|
||||
.Select(p => Path.GetExtension(p).TrimStart('.').ToLower())
|
||||
.Distinct()
|
||||
.Select(ext => $""" <Default Extension="{ext}" ContentType="application/octet" />""")
|
||||
.ToArray();
|
||||
|
||||
var contentType = $"""
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
|
||||
@@ -321,18 +321,17 @@ namespace Velopack.Packaging
|
||||
</Types>
|
||||
""";
|
||||
|
||||
File.WriteAllText(Path.Combine(rootDirectory, NugetUtil.ContentTypeFileName), contentType);
|
||||
File.WriteAllText(Path.Combine(rootDirectory, NugetUtil.ContentTypeFileName), contentType);
|
||||
|
||||
var relsDir = Path.Combine(rootDirectory, "_rels");
|
||||
Directory.CreateDirectory(relsDir);
|
||||
var relsDir = Path.Combine(rootDirectory, "_rels");
|
||||
Directory.CreateDirectory(relsDir);
|
||||
|
||||
var rels = $"""
|
||||
var rels = $"""
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Type="http://schemas.microsoft.com/packaging/2010/07/manifest" Target="/{Path.GetFileName(nuspecPath)}" Id="R1" />
|
||||
</Relationships>
|
||||
""";
|
||||
File.WriteAllText(Path.Combine(relsDir, ".rels"), rels);
|
||||
}
|
||||
File.WriteAllText(Path.Combine(relsDir, ".rels"), rels);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,252 +4,251 @@ using Velopack.Json;
|
||||
using Velopack.NuGet;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
public class ReleaseEntryHelper
|
||||
{
|
||||
public class ReleaseEntryHelper
|
||||
private readonly string _outputDir;
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _channel;
|
||||
private Dictionary<string, List<VelopackAsset>> _releases;
|
||||
|
||||
public ReleaseEntryHelper(string outputDir, string channel, ILogger logger)
|
||||
{
|
||||
private readonly string _outputDir;
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _channel;
|
||||
private Dictionary<string, List<VelopackAsset>> _releases;
|
||||
_outputDir = outputDir;
|
||||
_logger = logger;
|
||||
_channel = channel ?? GetDefaultChannel();
|
||||
_releases = GetReleasesFromDir(outputDir);
|
||||
}
|
||||
|
||||
public ReleaseEntryHelper(string outputDir, string channel, ILogger logger)
|
||||
{
|
||||
_outputDir = outputDir;
|
||||
_logger = logger;
|
||||
_channel = channel ?? GetDefaultChannel();
|
||||
_releases = GetReleasesFromDir(outputDir);
|
||||
private static Dictionary<string, List<VelopackAsset>> GetReleasesFromDir(string dir)
|
||||
{
|
||||
var rel = new Dictionary<string, List<VelopackAsset>>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var releaseFile in Directory.EnumerateFiles(dir, "*.nupkg")) {
|
||||
var zip = new ZipPackage(releaseFile);
|
||||
var ch = zip.Channel ?? GetDefaultChannel(VelopackRuntimeInfo.SystemOs);
|
||||
if (!rel.ContainsKey(ch))
|
||||
rel[ch] = new List<VelopackAsset>();
|
||||
rel[ch].Add(VelopackAsset.FromZipPackage(zip));
|
||||
}
|
||||
return rel;
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<VelopackAsset>> GetReleasesFromDir(string dir)
|
||||
{
|
||||
var rel = new Dictionary<string, List<VelopackAsset>>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var releaseFile in Directory.EnumerateFiles(dir, "*.nupkg")) {
|
||||
var zip = new ZipPackage(releaseFile);
|
||||
var ch = zip.Channel ?? GetDefaultChannel(VelopackRuntimeInfo.SystemOs);
|
||||
if (!rel.ContainsKey(ch))
|
||||
rel[ch] = new List<VelopackAsset>();
|
||||
rel[ch].Add(VelopackAsset.FromZipPackage(zip));
|
||||
}
|
||||
return rel;
|
||||
}
|
||||
|
||||
public bool DoesSimilarVersionExist(SemanticVersion version)
|
||||
{
|
||||
if (!_releases.ContainsKey(_channel) || !_releases[_channel].Any())
|
||||
return false;
|
||||
foreach (var release in _releases[_channel]) {
|
||||
if (version <= release.Version) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public bool DoesSimilarVersionExist(SemanticVersion version)
|
||||
{
|
||||
if (!_releases.ContainsKey(_channel) || !_releases[_channel].Any())
|
||||
return false;
|
||||
foreach (var release in _releases[_channel]) {
|
||||
if (version <= release.Version) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ReleasePackage GetPreviousFullRelease(SemanticVersion version)
|
||||
{
|
||||
var releases = _releases.ContainsKey(_channel) ? _releases[_channel] : null;
|
||||
if (releases == null || !releases.Any()) return null;
|
||||
var entry = releases
|
||||
.Where(x => x.Type == VelopackAssetType.Full)
|
||||
.Where(x => x.Version < version)
|
||||
.OrderByDescending(x => x.Version)
|
||||
.FirstOrDefault();
|
||||
if (entry == null) return null;
|
||||
var file = Path.Combine(_outputDir, entry.FileName);
|
||||
return new ReleasePackage(file);
|
||||
}
|
||||
|
||||
public VelopackAsset GetLatestFullRelease()
|
||||
{
|
||||
var releases = _releases.ContainsKey(_channel) ? _releases[_channel] : null;
|
||||
if (releases == null || !releases.Any()) return null;
|
||||
return releases.Where(z => z.Type == VelopackAssetType.Full).MaxBy(z => z.Version).First();
|
||||
}
|
||||
|
||||
public IEnumerable<VelopackAsset> GetLatestAssets()
|
||||
{
|
||||
if (!_releases.ContainsKey(_channel) || !_releases[_channel].Any())
|
||||
return Enumerable.Empty<VelopackAsset>();
|
||||
|
||||
var latest = _releases[_channel].MaxBy(x => x.Version).First();
|
||||
_logger.Info($"Latest release: {latest.FileName}");
|
||||
|
||||
var assets = _releases[_channel]
|
||||
.Where(x => x.Version == latest.Version)
|
||||
.OrderByDescending(x => x.Version)
|
||||
.ThenBy(x => x.Type)
|
||||
.ToArray();
|
||||
|
||||
foreach (var asset in assets) {
|
||||
_logger.Info($" Discovered asset: {asset.FileName}");
|
||||
}
|
||||
|
||||
public ReleasePackage GetPreviousFullRelease(SemanticVersion version)
|
||||
{
|
||||
var releases = _releases.ContainsKey(_channel) ? _releases[_channel] : null;
|
||||
if (releases == null || !releases.Any()) return null;
|
||||
var entry = releases
|
||||
.Where(x => x.Type == VelopackAssetType.Full)
|
||||
.Where(x => x.Version < version)
|
||||
.OrderByDescending(x => x.Version)
|
||||
.FirstOrDefault();
|
||||
if (entry == null) return null;
|
||||
var file = Path.Combine(_outputDir, entry.FileName);
|
||||
return new ReleasePackage(file);
|
||||
return assets;
|
||||
}
|
||||
|
||||
public static void UpdateReleaseFiles(string outputDir, ILogger log)
|
||||
{
|
||||
var releases = GetReleasesFromDir(outputDir);
|
||||
foreach (var releaseFile in Directory.EnumerateFiles(outputDir, "RELEASES*")) {
|
||||
File.Delete(releaseFile);
|
||||
}
|
||||
|
||||
public VelopackAsset GetLatestFullRelease()
|
||||
{
|
||||
var releases = _releases.ContainsKey(_channel) ? _releases[_channel] : null;
|
||||
if (releases == null || !releases.Any()) return null;
|
||||
return releases.Where(z => z.Type == VelopackAssetType.Full).MaxBy(z => z.Version).First();
|
||||
}
|
||||
|
||||
public IEnumerable<VelopackAsset> GetLatestAssets()
|
||||
{
|
||||
if (!_releases.ContainsKey(_channel) || !_releases[_channel].Any())
|
||||
return Enumerable.Empty<VelopackAsset>();
|
||||
|
||||
var latest = _releases[_channel].MaxBy(x => x.Version).First();
|
||||
_logger.Info($"Latest release: {latest.FileName}");
|
||||
|
||||
var assets = _releases[_channel]
|
||||
.Where(x => x.Version == latest.Version)
|
||||
.OrderByDescending(x => x.Version)
|
||||
.ThenBy(x => x.Type)
|
||||
.ToArray();
|
||||
|
||||
foreach (var asset in assets) {
|
||||
_logger.Info($" Discovered asset: {asset.FileName}");
|
||||
foreach (var kvp in releases) {
|
||||
var exclude = kvp.Value.Where(x => x.Version.ReleaseLabels.Any(r => r.Contains('.')) || x.Version.HasMetadata).ToArray();
|
||||
if (exclude.Any()) {
|
||||
log.Warn($"Excluding {exclude.Length} assets from legacy RELEASES file, because they " +
|
||||
$"contain an invalid character in the version: {string.Join(", ", exclude.Select(x => x.FileName))}");
|
||||
}
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
public static void UpdateReleaseFiles(string outputDir, ILogger log)
|
||||
{
|
||||
var releases = GetReleasesFromDir(outputDir);
|
||||
foreach (var releaseFile in Directory.EnumerateFiles(outputDir, "RELEASES*")) {
|
||||
File.Delete(releaseFile);
|
||||
}
|
||||
foreach (var kvp in releases) {
|
||||
var exclude = kvp.Value.Where(x => x.Version.ReleaseLabels.Any(r => r.Contains('.')) || x.Version.HasMetadata).ToArray();
|
||||
if (exclude.Any()) {
|
||||
log.Warn($"Excluding {exclude.Length} assets from legacy RELEASES file, because they " +
|
||||
$"contain an invalid character in the version: {string.Join(", ", exclude.Select(x => x.FileName))}");
|
||||
}
|
||||
|
||||
// We write a legacy RELEASES file to allow older applications to update to velopack
|
||||
// We write a legacy RELEASES file to allow older applications to update to velopack
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var name = Utility.GetReleasesFileName(kvp.Key);
|
||||
var path = Path.Combine(outputDir, name);
|
||||
ReleaseEntry.WriteReleaseFile(kvp.Value.Except(exclude).Select(ReleaseEntry.FromVelopackAsset), path);
|
||||
var name = Utility.GetReleasesFileName(kvp.Key);
|
||||
var path = Path.Combine(outputDir, name);
|
||||
ReleaseEntry.WriteReleaseFile(kvp.Value.Except(exclude).Select(ReleaseEntry.FromVelopackAsset), path);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
var indexPath = Path.Combine(outputDir, Utility.GetVeloReleaseIndexName(kvp.Key));
|
||||
var feed = new VelopackAssetFeed() {
|
||||
Assets = kvp.Value.OrderByDescending(v => v.Version).ThenBy(v => v.Type).ToArray(),
|
||||
};
|
||||
File.WriteAllText(indexPath, GetAssetFeedJson(feed));
|
||||
}
|
||||
var indexPath = Path.Combine(outputDir, Utility.GetVeloReleaseIndexName(kvp.Key));
|
||||
var feed = new VelopackAssetFeed() {
|
||||
Assets = kvp.Value.OrderByDescending(v => v.Version).ThenBy(v => v.Type).ToArray(),
|
||||
};
|
||||
File.WriteAllText(indexPath, GetAssetFeedJson(feed));
|
||||
}
|
||||
|
||||
public static IEnumerable<VelopackAsset> MergeAssets(IEnumerable<VelopackAsset> priority, IEnumerable<VelopackAsset> secondary)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
return priority.Concat(secondary).DistinctBy(x => x.FileName);
|
||||
#else
|
||||
return priority.Concat(secondary).GroupBy(x => x.FileName).Select(g => g.First());
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string GetAssetFeedJson(VelopackAssetFeed feed)
|
||||
{
|
||||
return SimpleJson.SerializeObject(feed);
|
||||
}
|
||||
|
||||
public static string GetSuggestedReleaseName(string id, string version, string channel, bool delta)
|
||||
{
|
||||
var suffix = GetUniqueAssetSuffix(channel);
|
||||
version = SemanticVersion.Parse(version).ToNormalizedString();
|
||||
if (VelopackRuntimeInfo.IsWindows && 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)
|
||||
{
|
||||
var suffix = GetUniqueAssetSuffix(channel);
|
||||
if (VelopackRuntimeInfo.IsLinux) {
|
||||
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)
|
||||
{
|
||||
var suffix = GetUniqueAssetSuffix(channel);
|
||||
if (VelopackRuntimeInfo.IsWindows)
|
||||
return $"{id}{suffix}-Setup.exe";
|
||||
else if (VelopackRuntimeInfo.IsOSX)
|
||||
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 = null)
|
||||
{
|
||||
os ??= VelopackRuntimeInfo.SystemOs;
|
||||
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
|
||||
{
|
||||
AllPackages,
|
||||
OnlyLatest,
|
||||
}
|
||||
|
||||
//public class AssetUploadInfo
|
||||
//{
|
||||
// public List<FileInfo> Files { get; } = new List<FileInfo>();
|
||||
|
||||
// public List<ReleaseEntry> Releases { get; } = new List<ReleaseEntry>();
|
||||
//}
|
||||
|
||||
//public static AssetUploadInfo GetUploadAssets(AssetsMode mode, string channel, string releasesDir)
|
||||
//{
|
||||
// var ret = new AssetUploadInfo();
|
||||
// var os = VelopackRuntimeInfo.SystemOs;
|
||||
// channel ??= GetDefaultChannel(os);
|
||||
// var suffix = GetPkgSuffix(os, channel);
|
||||
|
||||
// if (!_releases.ContainsKey(channel))
|
||||
// throw new UserInfoException("No releases found for channel: " + channel);
|
||||
|
||||
// ret.ReleasesFileName = Utility.GetReleasesFileName(channel);
|
||||
// var relPath = GetReleasePath(channel);
|
||||
// if (!File.Exists(relPath))
|
||||
// throw new UserInfoException($"Could not find RELEASES file for channel {channel} at {relPath}");
|
||||
|
||||
// ReleaseEntry latest = GetLatestFullRelease(channel);
|
||||
// if (latest == null) {
|
||||
// throw new UserInfoException("No full releases found for channel: " + channel);
|
||||
// } else {
|
||||
// _logger.Info("Latest local release: " + latest.OriginalFilename);
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, "*.nupkg")) {
|
||||
// var entry = _releases[channel].FirstOrDefault(x => Path.GetFileName(rel).Equals(x.OriginalFilename, StringComparison.OrdinalIgnoreCase));
|
||||
// if (entry != null) {
|
||||
// if (mode != AssetsMode.OnlyLatest || latest.Version == entry.Version) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// ret.Releases.Add(entry);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}-Portable.zip")) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}.AppImage")) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}-Setup.exe")) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}-Setup.pkg")) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// }
|
||||
|
||||
// return ret;
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static IEnumerable<VelopackAsset> MergeAssets(IEnumerable<VelopackAsset> priority, IEnumerable<VelopackAsset> secondary)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
return priority.Concat(secondary).DistinctBy(x => x.FileName);
|
||||
#else
|
||||
return priority.Concat(secondary).GroupBy(x => x.FileName).Select(g => g.First());
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string GetAssetFeedJson(VelopackAssetFeed feed)
|
||||
{
|
||||
return SimpleJson.SerializeObject(feed);
|
||||
}
|
||||
|
||||
public static string GetSuggestedReleaseName(string id, string version, string channel, bool delta)
|
||||
{
|
||||
var suffix = GetUniqueAssetSuffix(channel);
|
||||
version = SemanticVersion.Parse(version).ToNormalizedString();
|
||||
if (VelopackRuntimeInfo.IsWindows && 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)
|
||||
{
|
||||
var suffix = GetUniqueAssetSuffix(channel);
|
||||
if (VelopackRuntimeInfo.IsLinux) {
|
||||
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)
|
||||
{
|
||||
var suffix = GetUniqueAssetSuffix(channel);
|
||||
if (VelopackRuntimeInfo.IsWindows)
|
||||
return $"{id}{suffix}-Setup.exe";
|
||||
else if (VelopackRuntimeInfo.IsOSX)
|
||||
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 = null)
|
||||
{
|
||||
os ??= VelopackRuntimeInfo.SystemOs;
|
||||
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
|
||||
{
|
||||
AllPackages,
|
||||
OnlyLatest,
|
||||
}
|
||||
|
||||
//public class AssetUploadInfo
|
||||
//{
|
||||
// public List<FileInfo> Files { get; } = new List<FileInfo>();
|
||||
|
||||
// public List<ReleaseEntry> Releases { get; } = new List<ReleaseEntry>();
|
||||
//}
|
||||
|
||||
//public static AssetUploadInfo GetUploadAssets(AssetsMode mode, string channel, string releasesDir)
|
||||
//{
|
||||
// var ret = new AssetUploadInfo();
|
||||
// var os = VelopackRuntimeInfo.SystemOs;
|
||||
// channel ??= GetDefaultChannel(os);
|
||||
// var suffix = GetPkgSuffix(os, channel);
|
||||
|
||||
// if (!_releases.ContainsKey(channel))
|
||||
// throw new UserInfoException("No releases found for channel: " + channel);
|
||||
|
||||
// ret.ReleasesFileName = Utility.GetReleasesFileName(channel);
|
||||
// var relPath = GetReleasePath(channel);
|
||||
// if (!File.Exists(relPath))
|
||||
// throw new UserInfoException($"Could not find RELEASES file for channel {channel} at {relPath}");
|
||||
|
||||
// ReleaseEntry latest = GetLatestFullRelease(channel);
|
||||
// if (latest == null) {
|
||||
// throw new UserInfoException("No full releases found for channel: " + channel);
|
||||
// } else {
|
||||
// _logger.Info("Latest local release: " + latest.OriginalFilename);
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, "*.nupkg")) {
|
||||
// var entry = _releases[channel].FirstOrDefault(x => Path.GetFileName(rel).Equals(x.OriginalFilename, StringComparison.OrdinalIgnoreCase));
|
||||
// if (entry != null) {
|
||||
// if (mode != AssetsMode.OnlyLatest || latest.Version == entry.Version) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// ret.Releases.Add(entry);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}-Portable.zip")) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}.AppImage")) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}-Setup.exe")) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// }
|
||||
|
||||
// foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}-Setup.pkg")) {
|
||||
// _logger.Info($"Discovered asset: {rel}");
|
||||
// ret.Files.Add(new FileInfo(rel));
|
||||
// }
|
||||
|
||||
// return ret;
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,43 +1,42 @@
|
||||
using Velopack.Packaging;
|
||||
|
||||
namespace Velopack.Vpk.Commands
|
||||
namespace Velopack.Vpk.Commands;
|
||||
|
||||
public class DeltaGenCommand : BaseCommand
|
||||
{
|
||||
public class DeltaGenCommand : BaseCommand
|
||||
public DeltaMode DeltaMode { get; set; }
|
||||
|
||||
public string BasePackage { get; set; }
|
||||
|
||||
public string NewPackage { get; set; }
|
||||
|
||||
public string OutputFile { get; set; }
|
||||
|
||||
public DeltaGenCommand()
|
||||
: base("generate", "Generate a delta patch from two full releases.")
|
||||
|
||||
{
|
||||
public DeltaMode DeltaMode { get; set; }
|
||||
AddOption<DeltaMode>((v) => DeltaMode = v, "--mode")
|
||||
.SetDefault(DeltaMode.BestSpeed)
|
||||
.SetDescription("Set the delta generation mode.");
|
||||
|
||||
public string BasePackage { get; set; }
|
||||
AddOption<FileInfo>((v) => BasePackage = v.ToFullNameOrNull(), "--base", "-b")
|
||||
.SetDescription("The base package for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.RequiresExtension(".nupkg")
|
||||
.MustExist()
|
||||
.SetRequired();
|
||||
|
||||
public string NewPackage { get; set; }
|
||||
AddOption<FileInfo>((v) => NewPackage = v.ToFullNameOrNull(), "--new", "-n")
|
||||
.SetDescription("The resulting package for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.RequiresExtension(".nupkg")
|
||||
.MustExist()
|
||||
.SetRequired();
|
||||
|
||||
public string OutputFile { get; set; }
|
||||
|
||||
public DeltaGenCommand()
|
||||
: base("generate", "Generate a delta patch from two full releases.")
|
||||
|
||||
{
|
||||
AddOption<DeltaMode>((v) => DeltaMode = v, "--mode")
|
||||
.SetDefault(DeltaMode.BestSpeed)
|
||||
.SetDescription("Set the delta generation mode.");
|
||||
|
||||
AddOption<FileInfo>((v) => BasePackage = v.ToFullNameOrNull(), "--base", "-b")
|
||||
.SetDescription("The base package for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.RequiresExtension(".nupkg")
|
||||
.MustExist()
|
||||
.SetRequired();
|
||||
|
||||
AddOption<FileInfo>((v) => NewPackage = v.ToFullNameOrNull(), "--new", "-n")
|
||||
.SetDescription("The resulting package for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.RequiresExtension(".nupkg")
|
||||
.MustExist()
|
||||
.SetRequired();
|
||||
|
||||
AddOption<FileInfo>((v) => OutputFile = v.ToFullNameOrNull(), "--output", "-o")
|
||||
.SetDescription("The output file path for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.SetRequired();
|
||||
}
|
||||
AddOption<FileInfo>((v) => OutputFile = v.ToFullNameOrNull(), "--output", "-o")
|
||||
.SetDescription("The output file path for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.SetRequired();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
namespace Velopack.Vpk.Commands
|
||||
namespace Velopack.Vpk.Commands;
|
||||
|
||||
public class DeltaPatchCommand : BaseCommand
|
||||
{
|
||||
public class DeltaPatchCommand : BaseCommand
|
||||
public string BasePackage { get; set; }
|
||||
|
||||
public FileInfo[] PatchFiles { get; set; }
|
||||
|
||||
public string OutputFile { get; set; }
|
||||
|
||||
public DeltaPatchCommand()
|
||||
: base("patch", "Patch a base package and retrieve the original new package.")
|
||||
|
||||
{
|
||||
public string BasePackage { get; set; }
|
||||
AddOption<FileInfo>((v) => BasePackage = v.ToFullNameOrNull(), "--base", "-b")
|
||||
.SetDescription("The base package for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.RequiresExtension(".nupkg")
|
||||
.MustExist()
|
||||
.SetRequired();
|
||||
|
||||
public FileInfo[] PatchFiles { get; set; }
|
||||
AddOption<FileInfo[]>((v) => PatchFiles = v, "--patch", "-p")
|
||||
.SetDescription("The resulting package for the created patch.")
|
||||
.AllowMultiple()
|
||||
.SetArgumentHelpName("PATH");
|
||||
|
||||
public string OutputFile { get; set; }
|
||||
|
||||
public DeltaPatchCommand()
|
||||
: base("patch", "Patch a base package and retrieve the original new package.")
|
||||
|
||||
{
|
||||
AddOption<FileInfo>((v) => BasePackage = v.ToFullNameOrNull(), "--base", "-b")
|
||||
.SetDescription("The base package for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.RequiresExtension(".nupkg")
|
||||
.MustExist()
|
||||
.SetRequired();
|
||||
|
||||
AddOption<FileInfo[]>((v) => PatchFiles = v, "--patch", "-p")
|
||||
.SetDescription("The resulting package for the created patch.")
|
||||
.AllowMultiple()
|
||||
.SetArgumentHelpName("PATH");
|
||||
|
||||
AddOption<FileInfo>((v) => OutputFile = v.ToFullNameOrNull(), "--output", "-o")
|
||||
.SetDescription("The output file path for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.SetRequired();
|
||||
}
|
||||
AddOption<FileInfo>((v) => OutputFile = v.ToFullNameOrNull(), "--output", "-o")
|
||||
.SetDescription("The output file path for the created patch.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.SetRequired();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,96 +7,95 @@ using Microsoft.NET.HostModel.Bundle;
|
||||
using Octokit;
|
||||
using Velopack.Packaging;
|
||||
|
||||
namespace Velopack.Vpk.Commands
|
||||
namespace Velopack.Vpk.Commands;
|
||||
|
||||
public class LinuxPackCommand : PlatformCommand
|
||||
{
|
||||
public class LinuxPackCommand : PlatformCommand
|
||||
public string PackId { get; private set; }
|
||||
|
||||
public string PackVersion { get; private set; }
|
||||
|
||||
public string PackDirectory { get; private set; }
|
||||
|
||||
public string PackAuthors { get; private set; }
|
||||
|
||||
public string PackTitle { get; private set; }
|
||||
|
||||
public string EntryExecutableName { get; private set; }
|
||||
|
||||
public string Icon { get; private set; }
|
||||
|
||||
public string ReleaseNotes { get; set; }
|
||||
|
||||
public bool PackIsAppDir { get; private set; }
|
||||
|
||||
public DeltaMode DeltaMode { get; set; } = DeltaMode.BestSpeed;
|
||||
|
||||
public LinuxPackCommand()
|
||||
: this("pack", "Create's a Linux .AppImage bundle from a folder containing application files.")
|
||||
{ }
|
||||
|
||||
public LinuxPackCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
public string PackId { get; private set; }
|
||||
AddOption<string>((v) => PackId = v, "--packId", "-u")
|
||||
.SetDescription("Unique Id for application bundle.")
|
||||
.SetArgumentHelpName("ID")
|
||||
.SetRequired()
|
||||
.RequiresValidNuGetId();
|
||||
|
||||
public string PackVersion { get; private set; }
|
||||
// TODO add parser straight to SemanticVersion?
|
||||
AddOption<string>((v) => PackVersion = v, "--packVersion", "-v")
|
||||
.SetDescription("Current version for application bundle.")
|
||||
.SetArgumentHelpName("VERSION")
|
||||
.SetRequired()
|
||||
.RequiresSemverCompliant();
|
||||
|
||||
public string PackDirectory { get; private set; }
|
||||
var packDir = AddOption<DirectoryInfo>((v) => PackDirectory = v.ToFullNameOrNull(), "--packDir", "-p")
|
||||
.SetDescription("Directory containing application files from dotnet publish")
|
||||
.SetArgumentHelpName("DIR")
|
||||
.MustNotBeEmpty();
|
||||
|
||||
public string PackAuthors { get; private set; }
|
||||
AddOption<string>((v) => PackAuthors = v, "--packAuthors")
|
||||
.SetDescription("Company name or comma-delimited list of authors.")
|
||||
.SetArgumentHelpName("AUTHORS");
|
||||
|
||||
public string PackTitle { get; private set; }
|
||||
AddOption<string>((v) => PackTitle = v, "--packTitle")
|
||||
.SetDescription("Display/friendly name for application.")
|
||||
.SetArgumentHelpName("NAME");
|
||||
|
||||
public string EntryExecutableName { get; private set; }
|
||||
AddOption<string>((v) => EntryExecutableName = v, "-e", "--mainExe")
|
||||
.SetDescription("The file name of the main/entry executable.")
|
||||
.SetArgumentHelpName("NAME");
|
||||
|
||||
public string Icon { get; private set; }
|
||||
var icon = AddOption<FileInfo>((v) => Icon = v.ToFullNameOrNull(), "-i", "--icon")
|
||||
.SetDescription("Path to the icon file for this bundle.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.MustExist();
|
||||
|
||||
public string ReleaseNotes { get; set; }
|
||||
AddOption<FileInfo>((v) => ReleaseNotes = v.ToFullNameOrNull(), "--releaseNotes")
|
||||
.SetDescription("File with markdown-formatted notes for this version.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.MustExist();
|
||||
|
||||
public bool PackIsAppDir { get; private set; }
|
||||
AddOption<DeltaMode>((v) => DeltaMode = v, "--delta")
|
||||
.SetDefault(DeltaMode.BestSpeed)
|
||||
.SetDescription("Set the delta generation mode.");
|
||||
|
||||
public DeltaMode DeltaMode { get; set; } = DeltaMode.BestSpeed;
|
||||
var appDir = AddOption<DirectoryInfo>((v) => {
|
||||
var t = v.ToFullNameOrNull();
|
||||
if (t != null) {
|
||||
PackDirectory = t;
|
||||
PackIsAppDir = true;
|
||||
}
|
||||
}, "--appDir")
|
||||
.SetDescription("Directory containing application in .AppDir format")
|
||||
.SetArgumentHelpName("DIR")
|
||||
.MustNotBeEmpty();
|
||||
|
||||
public LinuxPackCommand()
|
||||
: this("pack", "Create's a Linux .AppImage bundle from a folder containing application files.")
|
||||
{ }
|
||||
|
||||
public LinuxPackCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
AddOption<string>((v) => PackId = v, "--packId", "-u")
|
||||
.SetDescription("Unique Id for application bundle.")
|
||||
.SetArgumentHelpName("ID")
|
||||
.SetRequired()
|
||||
.RequiresValidNuGetId();
|
||||
|
||||
// TODO add parser straight to SemanticVersion?
|
||||
AddOption<string>((v) => PackVersion = v, "--packVersion", "-v")
|
||||
.SetDescription("Current version for application bundle.")
|
||||
.SetArgumentHelpName("VERSION")
|
||||
.SetRequired()
|
||||
.RequiresSemverCompliant();
|
||||
|
||||
var packDir = AddOption<DirectoryInfo>((v) => PackDirectory = v.ToFullNameOrNull(), "--packDir", "-p")
|
||||
.SetDescription("Directory containing application files from dotnet publish")
|
||||
.SetArgumentHelpName("DIR")
|
||||
.MustNotBeEmpty();
|
||||
|
||||
AddOption<string>((v) => PackAuthors = v, "--packAuthors")
|
||||
.SetDescription("Company name or comma-delimited list of authors.")
|
||||
.SetArgumentHelpName("AUTHORS");
|
||||
|
||||
AddOption<string>((v) => PackTitle = v, "--packTitle")
|
||||
.SetDescription("Display/friendly name for application.")
|
||||
.SetArgumentHelpName("NAME");
|
||||
|
||||
AddOption<string>((v) => EntryExecutableName = v, "-e", "--mainExe")
|
||||
.SetDescription("The file name of the main/entry executable.")
|
||||
.SetArgumentHelpName("NAME");
|
||||
|
||||
var icon = AddOption<FileInfo>((v) => Icon = v.ToFullNameOrNull(), "-i", "--icon")
|
||||
.SetDescription("Path to the icon file for this bundle.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.MustExist();
|
||||
|
||||
AddOption<FileInfo>((v) => ReleaseNotes = v.ToFullNameOrNull(), "--releaseNotes")
|
||||
.SetDescription("File with markdown-formatted notes for this version.")
|
||||
.SetArgumentHelpName("PATH")
|
||||
.MustExist();
|
||||
|
||||
AddOption<DeltaMode>((v) => DeltaMode = v, "--delta")
|
||||
.SetDefault(DeltaMode.BestSpeed)
|
||||
.SetDescription("Set the delta generation mode.");
|
||||
|
||||
var appDir = AddOption<DirectoryInfo>((v) => {
|
||||
var t = v.ToFullNameOrNull();
|
||||
if (t != null) {
|
||||
PackDirectory = t;
|
||||
PackIsAppDir = true;
|
||||
}
|
||||
}, "--appDir")
|
||||
.SetDescription("Directory containing application in .AppDir format")
|
||||
.SetArgumentHelpName("DIR")
|
||||
.MustNotBeEmpty();
|
||||
|
||||
this.AreMutuallyExclusive(packDir, appDir);
|
||||
this.AtLeastOneRequired(packDir, appDir);
|
||||
this.AreMutuallyExclusive(icon, appDir);
|
||||
this.AtLeastOneRequired(icon, appDir);
|
||||
}
|
||||
this.AreMutuallyExclusive(packDir, appDir);
|
||||
this.AtLeastOneRequired(packDir, appDir);
|
||||
this.AreMutuallyExclusive(icon, appDir);
|
||||
this.AtLeastOneRequired(icon, appDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,293 +7,292 @@ using Spectre.Console.Rendering;
|
||||
using Velopack.Vpk.Logging;
|
||||
using static System.CommandLine.Help.HelpBuilder;
|
||||
|
||||
namespace Velopack.Vpk.Commands
|
||||
namespace Velopack.Vpk.Commands;
|
||||
|
||||
public class LongHelpCommand : CliOption<bool>
|
||||
{
|
||||
public class LongHelpCommand : CliOption<bool>
|
||||
private CliAction _action;
|
||||
|
||||
public LongHelpCommand() : this("--help", ["-h", "-H", "--vhelp"])
|
||||
{
|
||||
private CliAction _action;
|
||||
}
|
||||
|
||||
public LongHelpCommand() : this("--help", ["-h", "-H", "--vhelp"])
|
||||
public LongHelpCommand(string name, params string[] aliases)
|
||||
: base(name, aliases)
|
||||
{
|
||||
Recursive = true;
|
||||
Description = "Show help (-h) or extended help (-H).";
|
||||
Arity = ArgumentArity.Zero;
|
||||
}
|
||||
|
||||
public override CliAction Action {
|
||||
get => _action ??= new LongHelpAction();
|
||||
set => _action = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
public sealed class LongHelpAction : SynchronousCliAction
|
||||
{
|
||||
public override int Invoke(ParseResult parseResult)
|
||||
{
|
||||
}
|
||||
var longHelpMode = parseResult.Tokens.Any(t => t.Value == "-H" || t.Value == "--vhelp");
|
||||
var command = parseResult.CommandResult.Command;
|
||||
|
||||
public LongHelpCommand(string name, params string[] aliases)
|
||||
: base(name, aliases)
|
||||
{
|
||||
Recursive = true;
|
||||
Description = "Show help (-h) or extended help (-H).";
|
||||
Arity = ArgumentArity.Zero;
|
||||
}
|
||||
var pad = new Padding(2, 0);
|
||||
|
||||
public override CliAction Action {
|
||||
get => _action ??= new LongHelpAction();
|
||||
set => _action = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
List<IRenderable> output =
|
||||
[
|
||||
new Text("Description:"),
|
||||
Text.NewLine,
|
||||
new Padder(new Markup($"[bold]{command.Description}[/]"), pad),
|
||||
Text.NewLine,
|
||||
new Text("Usage:"),
|
||||
Text.NewLine,
|
||||
new Padder(new Markup($"[bold]{Markup.Escape(GetUsage(command))}[/]"), pad),
|
||||
];
|
||||
|
||||
public sealed class LongHelpAction : SynchronousCliAction
|
||||
{
|
||||
public override int Invoke(ParseResult parseResult)
|
||||
Table CreateTable()
|
||||
{
|
||||
var longHelpMode = parseResult.Tokens.Any(t => t.Value == "-H" || t.Value == "--vhelp");
|
||||
var command = parseResult.CommandResult.Command;
|
||||
|
||||
var pad = new Padding(2, 0);
|
||||
|
||||
List<IRenderable> output =
|
||||
[
|
||||
new Text("Description:"),
|
||||
Text.NewLine,
|
||||
new Padder(new Markup($"[bold]{command.Description}[/]"), pad),
|
||||
Text.NewLine,
|
||||
new Text("Usage:"),
|
||||
Text.NewLine,
|
||||
new Padder(new Markup($"[bold]{Markup.Escape(GetUsage(command))}[/]"), pad),
|
||||
];
|
||||
|
||||
Table CreateTable()
|
||||
{
|
||||
var t = new Table();
|
||||
t.NoBorder();
|
||||
t.LeftAligned();
|
||||
t.HideHeaders();
|
||||
t.Collapse();
|
||||
t.AddColumn(new TableColumn("Name") { Padding = pad });
|
||||
t.AddColumn("Description");
|
||||
return t;
|
||||
}
|
||||
|
||||
int hiddenOptions = 0;
|
||||
|
||||
void AddOptionRows(Table table, IEnumerable<CliOption> options)
|
||||
{
|
||||
foreach (var argument in options) {
|
||||
if (argument.Hidden && !longHelpMode) {
|
||||
hiddenOptions++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var columns = GetTwoColumnRowOption(argument);
|
||||
var aliasText = columns.FirstColumnText;
|
||||
var argIdx = aliasText.IndexOf(" <");
|
||||
if (argIdx > 0) {
|
||||
aliasText = $"[bold]{aliasText.Substring(0, argIdx)}[/]{aliasText.Substring(argIdx)}";
|
||||
} else {
|
||||
aliasText = $"[bold]{aliasText}[/]";
|
||||
}
|
||||
aliasText = aliasText.Replace("(REQUIRED)", "[red](REQ)[/]");
|
||||
|
||||
var descriptionText = Markup.Escape(columns.SecondColumnText);
|
||||
if (longHelpMode && command is BaseCommand bc) {
|
||||
var envVarName = bc.GetEnvVariableName(argument);
|
||||
if (envVarName != null) {
|
||||
descriptionText = $"[italic]ENV=[bold blue]{envVarName}[/][/] " + descriptionText;
|
||||
}
|
||||
}
|
||||
table.AddRow(new Markup(aliasText), new Markup(descriptionText));
|
||||
}
|
||||
}
|
||||
|
||||
// look for global options (only rendered if long mode)
|
||||
//var globalOptions = new List<CliOption>();
|
||||
//foreach (var p in command.Parents) {
|
||||
// CliCommand parentCommand = null;
|
||||
// if ((parentCommand = p as CliCommand) is not null) {
|
||||
// if (parentCommand.HasOptions()) {
|
||||
// foreach (var option in parentCommand.Options) {
|
||||
// if (option is { Recursive: true, Hidden: false }) {
|
||||
// if (longHelpMode) {
|
||||
// globalOptions.Add(option);
|
||||
// } else {
|
||||
// hiddenOptions++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//if (globalOptions.Any()) {
|
||||
// output.Add(Text.NewLine);
|
||||
// output.Add(new Text($"Global Options:"));
|
||||
// output.Add(Text.NewLine);
|
||||
// var globalOptionsTable = CreateTable();
|
||||
// AddOptionRows(globalOptionsTable, globalOptions);
|
||||
// output.Add(new Padder(globalOptionsTable, pad));
|
||||
//}
|
||||
|
||||
if (command.HasOptions()) {
|
||||
output.Add(Text.NewLine);
|
||||
output.Add(new Text($"Options:"));
|
||||
output.Add(Text.NewLine);
|
||||
var optionsTable = CreateTable();
|
||||
AddOptionRows(optionsTable, command.Options);
|
||||
output.Add(new Padder(optionsTable, pad));
|
||||
}
|
||||
|
||||
if (hiddenOptions > 0) {
|
||||
output.Add(Text.NewLine);
|
||||
output.Add(new Markup($"[italic]([red]*[/]) {hiddenOptions} option(s) were hidden. Use [bold]-H / --vhelp[/] to show all options.[/]"));
|
||||
output.Add(Text.NewLine);
|
||||
}
|
||||
|
||||
if (command.HasSubcommands()) {
|
||||
output.Add(Text.NewLine);
|
||||
output.Add(new Text("Commands:"));
|
||||
output.Add(Text.NewLine);
|
||||
|
||||
var commandsTable = CreateTable();
|
||||
foreach (var cmd in command.Subcommands) {
|
||||
var columns = GetTwoColumnRowCommand(cmd);
|
||||
commandsTable.AddRow(new Markup($"[bold]{columns.FirstColumnText}[/]"), new Text(columns.SecondColumnText));
|
||||
}
|
||||
|
||||
output.Add(new Padder(commandsTable, pad));
|
||||
}
|
||||
|
||||
AnsiConsole.Write(new RenderableCollection(output));
|
||||
return 0;
|
||||
var t = new Table();
|
||||
t.NoBorder();
|
||||
t.LeftAligned();
|
||||
t.HideHeaders();
|
||||
t.Collapse();
|
||||
t.AddColumn(new TableColumn("Name") { Padding = pad });
|
||||
t.AddColumn("Description");
|
||||
return t;
|
||||
}
|
||||
|
||||
public TwoColumnHelpRow GetTwoColumnRowCommand(CliCommand command)
|
||||
int hiddenOptions = 0;
|
||||
|
||||
void AddOptionRows(Table table, IEnumerable<CliOption> options)
|
||||
{
|
||||
if (command is null) {
|
||||
throw new ArgumentNullException(nameof(command));
|
||||
}
|
||||
var firstColumnText = Default.GetCommandUsageLabel(command);
|
||||
var symbolDescription = command.Description ?? string.Empty;
|
||||
var secondColumnText = symbolDescription.Trim();
|
||||
return new TwoColumnHelpRow(firstColumnText, secondColumnText);
|
||||
}
|
||||
|
||||
public TwoColumnHelpRow GetTwoColumnRowOption(CliOption symbol)
|
||||
{
|
||||
if (symbol is null) {
|
||||
throw new ArgumentNullException(nameof(symbol));
|
||||
}
|
||||
|
||||
var firstColumnText = Default.GetOptionUsageLabel(symbol);
|
||||
var symbolDescription = symbol.Description ?? string.Empty;
|
||||
|
||||
var defaultValueDescription = "";
|
||||
if (symbol.HasDefaultValue) {
|
||||
// TODO: this is a hack, but the property is internal. what do you want me to do?
|
||||
var argument = symbol.GetType()?.GetProperty("Argument", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(symbol) as CliArgument;
|
||||
if (argument is not null) {
|
||||
defaultValueDescription = $"[default: {Default.GetArgumentDefaultValue(argument)}]";
|
||||
}
|
||||
}
|
||||
|
||||
var secondColumnText = $"{symbolDescription} {defaultValueDescription}".Trim();
|
||||
return new TwoColumnHelpRow(firstColumnText, secondColumnText);
|
||||
}
|
||||
|
||||
private string GetUsage(CliCommand command)
|
||||
{
|
||||
return string.Join(" ", GetUsageParts().Where(x => !string.IsNullOrWhiteSpace(x)));
|
||||
|
||||
IEnumerable<string> GetUsageParts()
|
||||
{
|
||||
bool displayOptionTitle = false;
|
||||
|
||||
IEnumerable<CliCommand> parentCommands =
|
||||
command
|
||||
.RecurseWhileNotNull(c => c.Parents.OfType<CliCommand>().FirstOrDefault())
|
||||
.Reverse();
|
||||
|
||||
foreach (var parentCommand in parentCommands) {
|
||||
if (!displayOptionTitle) {
|
||||
displayOptionTitle = parentCommand.HasOptions() && parentCommand.Options.Any(x => x.Recursive && !x.Hidden);
|
||||
}
|
||||
|
||||
yield return parentCommand.Name;
|
||||
|
||||
if (parentCommand.HasArguments()) {
|
||||
yield return FormatArgumentUsage(parentCommand.Arguments);
|
||||
}
|
||||
}
|
||||
|
||||
var hasCommandWithHelp = command.HasSubcommands() && command.Subcommands.Any(x => !x.Hidden);
|
||||
|
||||
if (hasCommandWithHelp) {
|
||||
yield return "[command]";
|
||||
}
|
||||
|
||||
displayOptionTitle = displayOptionTitle || (command.HasOptions() && command.Options.Any(x => !x.Hidden));
|
||||
|
||||
if (displayOptionTitle) {
|
||||
yield return "[options]";
|
||||
}
|
||||
|
||||
if (!command.TreatUnmatchedTokensAsErrors) {
|
||||
yield return "[[--] <additional arguments>...]]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatArgumentUsage(IList<CliArgument> arguments)
|
||||
{
|
||||
var sb = new StringBuilder(arguments.Count * 100);
|
||||
|
||||
var end = default(List<char>);
|
||||
|
||||
for (var i = 0; i < arguments.Count; i++) {
|
||||
var argument = arguments[i];
|
||||
if (argument.Hidden) {
|
||||
foreach (var argument in options) {
|
||||
if (argument.Hidden && !longHelpMode) {
|
||||
hiddenOptions++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var arityIndicator =
|
||||
argument.Arity.MaximumNumberOfValues > 1
|
||||
? "..."
|
||||
: "";
|
||||
|
||||
var isOptional = IsOptional(argument);
|
||||
|
||||
if (isOptional) {
|
||||
sb.Append($"[<{argument.Name}>{arityIndicator}");
|
||||
(end ??= new()).Add(']');
|
||||
var columns = GetTwoColumnRowOption(argument);
|
||||
var aliasText = columns.FirstColumnText;
|
||||
var argIdx = aliasText.IndexOf(" <");
|
||||
if (argIdx > 0) {
|
||||
aliasText = $"[bold]{aliasText.Substring(0, argIdx)}[/]{aliasText.Substring(argIdx)}";
|
||||
} else {
|
||||
sb.Append($"<{argument.Name}>{arityIndicator}");
|
||||
aliasText = $"[bold]{aliasText}[/]";
|
||||
}
|
||||
aliasText = aliasText.Replace("(REQUIRED)", "[red](REQ)[/]");
|
||||
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
if (sb.Length > 0) {
|
||||
sb.Length--;
|
||||
|
||||
if (end is { }) {
|
||||
while (end.Count > 0) {
|
||||
sb.Append(end[end.Count - 1]);
|
||||
end.RemoveAt(end.Count - 1);
|
||||
var descriptionText = Markup.Escape(columns.SecondColumnText);
|
||||
if (longHelpMode && command is BaseCommand bc) {
|
||||
var envVarName = bc.GetEnvVariableName(argument);
|
||||
if (envVarName != null) {
|
||||
descriptionText = $"[italic]ENV=[bold blue]{envVarName}[/][/] " + descriptionText;
|
||||
}
|
||||
}
|
||||
table.AddRow(new Markup(aliasText), new Markup(descriptionText));
|
||||
}
|
||||
}
|
||||
|
||||
// look for global options (only rendered if long mode)
|
||||
//var globalOptions = new List<CliOption>();
|
||||
//foreach (var p in command.Parents) {
|
||||
// CliCommand parentCommand = null;
|
||||
// if ((parentCommand = p as CliCommand) is not null) {
|
||||
// if (parentCommand.HasOptions()) {
|
||||
// foreach (var option in parentCommand.Options) {
|
||||
// if (option is { Recursive: true, Hidden: false }) {
|
||||
// if (longHelpMode) {
|
||||
// globalOptions.Add(option);
|
||||
// } else {
|
||||
// hiddenOptions++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//if (globalOptions.Any()) {
|
||||
// output.Add(Text.NewLine);
|
||||
// output.Add(new Text($"Global Options:"));
|
||||
// output.Add(Text.NewLine);
|
||||
// var globalOptionsTable = CreateTable();
|
||||
// AddOptionRows(globalOptionsTable, globalOptions);
|
||||
// output.Add(new Padder(globalOptionsTable, pad));
|
||||
//}
|
||||
|
||||
if (command.HasOptions()) {
|
||||
output.Add(Text.NewLine);
|
||||
output.Add(new Text($"Options:"));
|
||||
output.Add(Text.NewLine);
|
||||
var optionsTable = CreateTable();
|
||||
AddOptionRows(optionsTable, command.Options);
|
||||
output.Add(new Padder(optionsTable, pad));
|
||||
}
|
||||
|
||||
if (hiddenOptions > 0) {
|
||||
output.Add(Text.NewLine);
|
||||
output.Add(new Markup($"[italic]([red]*[/]) {hiddenOptions} option(s) were hidden. Use [bold]-H / --vhelp[/] to show all options.[/]"));
|
||||
output.Add(Text.NewLine);
|
||||
}
|
||||
|
||||
if (command.HasSubcommands()) {
|
||||
output.Add(Text.NewLine);
|
||||
output.Add(new Text("Commands:"));
|
||||
output.Add(Text.NewLine);
|
||||
|
||||
var commandsTable = CreateTable();
|
||||
foreach (var cmd in command.Subcommands) {
|
||||
var columns = GetTwoColumnRowCommand(cmd);
|
||||
commandsTable.AddRow(new Markup($"[bold]{columns.FirstColumnText}[/]"), new Text(columns.SecondColumnText));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
output.Add(new Padder(commandsTable, pad));
|
||||
}
|
||||
|
||||
bool IsOptional(CliArgument argument) =>
|
||||
argument.Arity.MinimumNumberOfValues == 0;
|
||||
AnsiConsole.Write(new RenderableCollection(output));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public TwoColumnHelpRow GetTwoColumnRowCommand(CliCommand command)
|
||||
{
|
||||
if (command is null) {
|
||||
throw new ArgumentNullException(nameof(command));
|
||||
}
|
||||
var firstColumnText = Default.GetCommandUsageLabel(command);
|
||||
var symbolDescription = command.Description ?? string.Empty;
|
||||
var secondColumnText = symbolDescription.Trim();
|
||||
return new TwoColumnHelpRow(firstColumnText, secondColumnText);
|
||||
}
|
||||
|
||||
public TwoColumnHelpRow GetTwoColumnRowOption(CliOption symbol)
|
||||
{
|
||||
if (symbol is null) {
|
||||
throw new ArgumentNullException(nameof(symbol));
|
||||
}
|
||||
|
||||
var firstColumnText = Default.GetOptionUsageLabel(symbol);
|
||||
var symbolDescription = symbol.Description ?? string.Empty;
|
||||
|
||||
var defaultValueDescription = "";
|
||||
if (symbol.HasDefaultValue) {
|
||||
// TODO: this is a hack, but the property is internal. what do you want me to do?
|
||||
var argument = symbol.GetType()?.GetProperty("Argument", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(symbol) as CliArgument;
|
||||
if (argument is not null) {
|
||||
defaultValueDescription = $"[default: {Default.GetArgumentDefaultValue(argument)}]";
|
||||
}
|
||||
}
|
||||
|
||||
var secondColumnText = $"{symbolDescription} {defaultValueDescription}".Trim();
|
||||
return new TwoColumnHelpRow(firstColumnText, secondColumnText);
|
||||
}
|
||||
|
||||
private string GetUsage(CliCommand command)
|
||||
{
|
||||
return string.Join(" ", GetUsageParts().Where(x => !string.IsNullOrWhiteSpace(x)));
|
||||
|
||||
IEnumerable<string> GetUsageParts()
|
||||
{
|
||||
bool displayOptionTitle = false;
|
||||
|
||||
IEnumerable<CliCommand> parentCommands =
|
||||
command
|
||||
.RecurseWhileNotNull(c => c.Parents.OfType<CliCommand>().FirstOrDefault())
|
||||
.Reverse();
|
||||
|
||||
foreach (var parentCommand in parentCommands) {
|
||||
if (!displayOptionTitle) {
|
||||
displayOptionTitle = parentCommand.HasOptions() && parentCommand.Options.Any(x => x.Recursive && !x.Hidden);
|
||||
}
|
||||
|
||||
yield return parentCommand.Name;
|
||||
|
||||
if (parentCommand.HasArguments()) {
|
||||
yield return FormatArgumentUsage(parentCommand.Arguments);
|
||||
}
|
||||
}
|
||||
|
||||
var hasCommandWithHelp = command.HasSubcommands() && command.Subcommands.Any(x => !x.Hidden);
|
||||
|
||||
if (hasCommandWithHelp) {
|
||||
yield return "[command]";
|
||||
}
|
||||
|
||||
displayOptionTitle = displayOptionTitle || (command.HasOptions() && command.Options.Any(x => !x.Hidden));
|
||||
|
||||
if (displayOptionTitle) {
|
||||
yield return "[options]";
|
||||
}
|
||||
|
||||
if (!command.TreatUnmatchedTokensAsErrors) {
|
||||
yield return "[[--] <additional arguments>...]]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class HelpExtensions
|
||||
{
|
||||
public static bool HasArguments(this CliCommand command) => command.Arguments?.Count > 0;
|
||||
public static bool HasSubcommands(this CliCommand command) => command.Subcommands?.Count > 0;
|
||||
public static bool HasOptions(this CliCommand command) => command.Options?.Count > 0;
|
||||
|
||||
internal static IEnumerable<T> RecurseWhileNotNull<T>(
|
||||
this T source,
|
||||
Func<T, T> next)
|
||||
where T : class
|
||||
private string FormatArgumentUsage(IList<CliArgument> arguments)
|
||||
{
|
||||
while (source is not null) {
|
||||
yield return source;
|
||||
var sb = new StringBuilder(arguments.Count * 100);
|
||||
|
||||
source = next(source);
|
||||
var end = default(List<char>);
|
||||
|
||||
for (var i = 0; i < arguments.Count; i++) {
|
||||
var argument = arguments[i];
|
||||
if (argument.Hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var arityIndicator =
|
||||
argument.Arity.MaximumNumberOfValues > 1
|
||||
? "..."
|
||||
: "";
|
||||
|
||||
var isOptional = IsOptional(argument);
|
||||
|
||||
if (isOptional) {
|
||||
sb.Append($"[<{argument.Name}>{arityIndicator}");
|
||||
(end ??= new()).Add(']');
|
||||
} else {
|
||||
sb.Append($"<{argument.Name}>{arityIndicator}");
|
||||
}
|
||||
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
if (sb.Length > 0) {
|
||||
sb.Length--;
|
||||
|
||||
if (end is { }) {
|
||||
while (end.Count > 0) {
|
||||
sb.Append(end[end.Count - 1]);
|
||||
end.RemoveAt(end.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
bool IsOptional(CliArgument argument) =>
|
||||
argument.Arity.MinimumNumberOfValues == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class HelpExtensions
|
||||
{
|
||||
public static bool HasArguments(this CliCommand command) => command.Arguments?.Count > 0;
|
||||
public static bool HasSubcommands(this CliCommand command) => command.Subcommands?.Count > 0;
|
||||
public static bool HasOptions(this CliCommand command) => command.Options?.Count > 0;
|
||||
|
||||
internal static IEnumerable<T> RecurseWhileNotNull<T>(
|
||||
this T source,
|
||||
Func<T, T> next)
|
||||
where T : class
|
||||
{
|
||||
while (source is not null) {
|
||||
yield return source;
|
||||
|
||||
source = next(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
using Velopack.Packaging;
|
||||
|
||||
namespace Velopack.Vpk.Commands
|
||||
namespace Velopack.Vpk.Commands;
|
||||
|
||||
public abstract class OutputCommand : BaseCommand
|
||||
{
|
||||
public abstract class OutputCommand : BaseCommand
|
||||
public string ReleaseDir { get; private set; }
|
||||
|
||||
public string Channel { get; private set; }
|
||||
|
||||
protected CliOption<DirectoryInfo> ReleaseDirectoryOption { get; private set; }
|
||||
|
||||
protected CliOption<string> ChannelOption { get; private set; }
|
||||
|
||||
protected OutputCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
public string ReleaseDir { get; private set; }
|
||||
ReleaseDirectoryOption = AddOption<DirectoryInfo>((v) => ReleaseDir = v.ToFullNameOrNull(), "-o", "--outputDir")
|
||||
.SetDescription("Output directory for created packages.")
|
||||
.SetArgumentHelpName("DIR")
|
||||
.SetDefault(new DirectoryInfo("Releases"));
|
||||
|
||||
public string Channel { get; private set; }
|
||||
ChannelOption = AddOption<string>((v) => Channel = v, "-c", "--channel")
|
||||
.SetDescription("The channel to use for this release.")
|
||||
.RequiresValidNuGetId()
|
||||
.SetArgumentHelpName("NAME")
|
||||
.SetDefault(ReleaseEntryHelper.GetDefaultChannel(VelopackRuntimeInfo.SystemOs));
|
||||
}
|
||||
|
||||
protected CliOption<DirectoryInfo> ReleaseDirectoryOption { get; private set; }
|
||||
|
||||
protected CliOption<string> ChannelOption { get; private set; }
|
||||
|
||||
protected OutputCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
ReleaseDirectoryOption = AddOption<DirectoryInfo>((v) => ReleaseDir = v.ToFullNameOrNull(), "-o", "--outputDir")
|
||||
.SetDescription("Output directory for created packages.")
|
||||
.SetArgumentHelpName("DIR")
|
||||
.SetDefault(new DirectoryInfo("Releases"));
|
||||
|
||||
ChannelOption = AddOption<string>((v) => Channel = v, "-c", "--channel")
|
||||
.SetDescription("The channel to use for this release.")
|
||||
.RequiresValidNuGetId()
|
||||
.SetArgumentHelpName("NAME")
|
||||
.SetDefault(ReleaseEntryHelper.GetDefaultChannel(VelopackRuntimeInfo.SystemOs));
|
||||
}
|
||||
|
||||
public DirectoryInfo GetReleaseDirectory()
|
||||
{
|
||||
var di = new DirectoryInfo(ReleaseDir);
|
||||
if (!di.Exists) di.Create();
|
||||
return di;
|
||||
}
|
||||
public DirectoryInfo GetReleaseDirectory()
|
||||
{
|
||||
var di = new DirectoryInfo(ReleaseDir);
|
||||
if (!di.Exists) di.Create();
|
||||
return di;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
namespace Velopack.Vpk.Commands
|
||||
namespace Velopack.Vpk.Commands;
|
||||
|
||||
public abstract class PlatformCommand : OutputCommand
|
||||
{
|
||||
public abstract class PlatformCommand : OutputCommand
|
||||
public string TargetRuntime { get; set; }
|
||||
|
||||
protected CliOption<string> TargetRuntimeOption { get; private set; }
|
||||
|
||||
protected PlatformCommand(string name, string description) : base(name, description)
|
||||
{
|
||||
public string TargetRuntime { get; set; }
|
||||
|
||||
protected CliOption<string> TargetRuntimeOption { get; private set; }
|
||||
|
||||
protected PlatformCommand(string name, string description) : base(name, description)
|
||||
{
|
||||
TargetRuntimeOption = AddOption<string>((v) => TargetRuntime = v, "-r", "--runtime")
|
||||
.SetDescription("The target runtime to build packages for.")
|
||||
.SetArgumentHelpName("RID")
|
||||
.SetDefault(VelopackRuntimeInfo.SystemOs.GetOsShortName())
|
||||
.MustBeSupportedRid();
|
||||
}
|
||||
|
||||
public RID GetRid() => RID.Parse(TargetRuntime ?? VelopackRuntimeInfo.SystemOs.GetOsShortName());
|
||||
|
||||
public RuntimeOs GetRuntimeOs() => GetRid().BaseRID;
|
||||
TargetRuntimeOption = AddOption<string>((v) => TargetRuntime = v, "-r", "--runtime")
|
||||
.SetDescription("The target runtime to build packages for.")
|
||||
.SetArgumentHelpName("RID")
|
||||
.SetDefault(VelopackRuntimeInfo.SystemOs.GetOsShortName())
|
||||
.MustBeSupportedRid();
|
||||
}
|
||||
|
||||
public RID GetRid() => RID.Parse(TargetRuntime ?? VelopackRuntimeInfo.SystemOs.GetOsShortName());
|
||||
|
||||
public RuntimeOs GetRuntimeOs() => GetRid().BaseRID;
|
||||
}
|
||||
|
||||
@@ -1,58 +1,57 @@
|
||||
using Velopack.Packaging.Abstractions;
|
||||
|
||||
namespace Velopack.Vpk.Logging
|
||||
namespace Velopack.Vpk.Logging;
|
||||
|
||||
public class BasicConsole : IFancyConsole
|
||||
{
|
||||
public class BasicConsole : IFancyConsole
|
||||
private readonly ILogger logger;
|
||||
private readonly DefaultPromptValueFactory defaultFactory;
|
||||
|
||||
public BasicConsole(ILogger logger, DefaultPromptValueFactory defaultFactory)
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly DefaultPromptValueFactory defaultFactory;
|
||||
this.logger = logger;
|
||||
this.defaultFactory = defaultFactory;
|
||||
}
|
||||
|
||||
public BasicConsole(ILogger logger, DefaultPromptValueFactory defaultFactory)
|
||||
public async Task ExecuteProgressAsync(Func<IFancyConsoleProgress, Task> action)
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
await action(new Progress(logger));
|
||||
logger.Info($"Finished in {DateTime.UtcNow - start}.");
|
||||
}
|
||||
|
||||
public Task<bool> PromptYesNo(string prompt, bool? defaultValue = null, TimeSpan? timeout = null)
|
||||
{
|
||||
return Task.FromResult(defaultValue ?? defaultFactory.DefaultPromptValue);
|
||||
}
|
||||
|
||||
public void WriteLine(string text = "")
|
||||
{
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
|
||||
public void WriteTable(string tableName, IEnumerable<IEnumerable<string>> rows, bool hasHeaderRow = true)
|
||||
{
|
||||
Console.WriteLine(tableName);
|
||||
foreach (var row in rows) {
|
||||
Console.WriteLine(" " + String.Join(" ", row));
|
||||
}
|
||||
}
|
||||
|
||||
private class Progress : IFancyConsoleProgress
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public Progress(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.defaultFactory = defaultFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task ExecuteProgressAsync(Func<IFancyConsoleProgress, Task> action)
|
||||
public async Task RunTask(string name, Func<Action<int>, Task> fn)
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
await action(new Progress(logger));
|
||||
logger.Info($"Finished in {DateTime.UtcNow - start}.");
|
||||
}
|
||||
|
||||
public Task<bool> PromptYesNo(string prompt, bool? defaultValue = null, TimeSpan? timeout = null)
|
||||
{
|
||||
return Task.FromResult(defaultValue ?? defaultFactory.DefaultPromptValue);
|
||||
}
|
||||
|
||||
public void WriteLine(string text = "")
|
||||
{
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
|
||||
public void WriteTable(string tableName, IEnumerable<IEnumerable<string>> rows, bool hasHeaderRow = true)
|
||||
{
|
||||
Console.WriteLine(tableName);
|
||||
foreach (var row in rows) {
|
||||
Console.WriteLine(" " + String.Join(" ", row));
|
||||
}
|
||||
}
|
||||
|
||||
private class Progress : IFancyConsoleProgress
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public Progress(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task RunTask(string name, Func<Action<int>, Task> fn)
|
||||
{
|
||||
_logger.Info("Starting: " + name);
|
||||
await Task.Run(() => fn(_ => { }));
|
||||
_logger.Info("Complete: " + name);
|
||||
}
|
||||
_logger.Info("Starting: " + name);
|
||||
await Task.Run(() => fn(_ => { }));
|
||||
_logger.Info("Complete: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Vpk.Logging
|
||||
namespace Velopack.Vpk.Logging;
|
||||
|
||||
public record DefaultPromptValueFactory(bool DefaultPromptValue)
|
||||
{
|
||||
public record DefaultPromptValueFactory(bool DefaultPromptValue)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,137 +8,136 @@ using System.Threading.Tasks;
|
||||
using Spectre.Console;
|
||||
using Velopack.Packaging.Abstractions;
|
||||
|
||||
namespace Velopack.Vpk.Logging
|
||||
namespace Velopack.Vpk.Logging;
|
||||
|
||||
public class SpectreConsole : IFancyConsole
|
||||
{
|
||||
public class SpectreConsole : IFancyConsole
|
||||
private readonly ILogger logger;
|
||||
private readonly DefaultPromptValueFactory defaultFactory;
|
||||
|
||||
public SpectreConsole(ILogger logger, DefaultPromptValueFactory defaultFactory)
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly DefaultPromptValueFactory defaultFactory;
|
||||
this.logger = logger;
|
||||
this.defaultFactory = defaultFactory;
|
||||
}
|
||||
|
||||
public SpectreConsole(ILogger logger, DefaultPromptValueFactory defaultFactory)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.defaultFactory = defaultFactory;
|
||||
public async Task ExecuteProgressAsync(Func<IFancyConsoleProgress, Task> action)
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
await AnsiConsole.Progress()
|
||||
.AutoRefresh(true)
|
||||
.AutoClear(false)
|
||||
.HideCompleted(false)
|
||||
.Columns(new ProgressColumn[] {
|
||||
new SpinnerColumn(),
|
||||
new TaskDescriptionColumn(),
|
||||
new ProgressBarColumn(),
|
||||
new PercentageColumn(),
|
||||
new ElapsedTimeColumn(),
|
||||
})
|
||||
.StartAsync(async ctx => await action(new Progress(logger, ctx)));
|
||||
logger.Info($"[bold]Finished in {DateTime.UtcNow - start}.[/]");
|
||||
}
|
||||
|
||||
public async Task<bool> PromptYesNo(string prompt, bool? defaultValue = null, TimeSpan? timeout = null)
|
||||
{
|
||||
var def = defaultValue ?? defaultFactory.DefaultPromptValue;
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(timeout ?? TimeSpan.FromSeconds(30));
|
||||
|
||||
try {
|
||||
// all of this nonsense in CancellableTextPrompt.cs is to work-around a bug in Spectre.
|
||||
// Once the following issue is merged it can be removed.
|
||||
// https://github.com/spectreconsole/spectre.console/pull/1439
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
var comparer = StringComparer.CurrentCultureIgnoreCase;
|
||||
var textPrompt = "[underline bold orange3]QUESTION:[/]" + Environment.NewLine + prompt;
|
||||
var clip = new CancellableTextPrompt<char>(textPrompt, comparer);
|
||||
clip.Choices.Add('y');
|
||||
clip.Choices.Add('n');
|
||||
clip.ShowChoices = true;
|
||||
clip.ShowDefaultValue = true;
|
||||
clip.DefaultValue = new DefaultPromptValue<char>(def ? 'y' : 'n');
|
||||
var result = await clip.ShowAsync(AnsiConsole.Console, cts.Token).ConfigureAwait(false);
|
||||
AnsiConsole.WriteLine();
|
||||
return comparer.Compare("y", result.ToString()) == 0;
|
||||
} catch (OperationCanceledException) {
|
||||
AnsiConsole.Write($" Accepted default value ({(def ? "y" : "n")}) because the prompt timed out." + Environment.NewLine + Environment.NewLine);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteProgressAsync(Func<IFancyConsoleProgress, Task> action)
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
await AnsiConsole.Progress()
|
||||
.AutoRefresh(true)
|
||||
.AutoClear(false)
|
||||
.HideCompleted(false)
|
||||
.Columns(new ProgressColumn[] {
|
||||
new SpinnerColumn(),
|
||||
new TaskDescriptionColumn(),
|
||||
new ProgressBarColumn(),
|
||||
new PercentageColumn(),
|
||||
new ElapsedTimeColumn(),
|
||||
})
|
||||
.StartAsync(async ctx => await action(new Progress(logger, ctx)));
|
||||
logger.Info($"[bold]Finished in {DateTime.UtcNow - start}.[/]");
|
||||
}
|
||||
public void WriteLine(string text = "")
|
||||
{
|
||||
AnsiConsole.Markup(text + Environment.NewLine);
|
||||
}
|
||||
|
||||
public async Task<bool> PromptYesNo(string prompt, bool? defaultValue = null, TimeSpan? timeout = null)
|
||||
{
|
||||
var def = defaultValue ?? defaultFactory.DefaultPromptValue;
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(timeout ?? TimeSpan.FromSeconds(30));
|
||||
public void WriteTable(string tableName, IEnumerable<IEnumerable<string>> rows, bool hasHeaderRow = true)
|
||||
{
|
||||
// Create a table
|
||||
var table = new Table();
|
||||
table.Title($"[bold underline]{tableName}[/]");
|
||||
table.Expand();
|
||||
table.LeftAligned();
|
||||
|
||||
try {
|
||||
// all of this nonsense in CancellableTextPrompt.cs is to work-around a bug in Spectre.
|
||||
// Once the following issue is merged it can be removed.
|
||||
// https://github.com/spectreconsole/spectre.console/pull/1439
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
var comparer = StringComparer.CurrentCultureIgnoreCase;
|
||||
var textPrompt = "[underline bold orange3]QUESTION:[/]" + Environment.NewLine + prompt;
|
||||
var clip = new CancellableTextPrompt<char>(textPrompt, comparer);
|
||||
clip.Choices.Add('y');
|
||||
clip.Choices.Add('n');
|
||||
clip.ShowChoices = true;
|
||||
clip.ShowDefaultValue = true;
|
||||
clip.DefaultValue = new DefaultPromptValue<char>(def ? 'y' : 'n');
|
||||
var result = await clip.ShowAsync(AnsiConsole.Console, cts.Token).ConfigureAwait(false);
|
||||
AnsiConsole.WriteLine();
|
||||
return comparer.Compare("y", result.ToString()) == 0;
|
||||
} catch (OperationCanceledException) {
|
||||
AnsiConsole.Write($" Accepted default value ({(def ? "y" : "n")}) because the prompt timed out." + Environment.NewLine + Environment.NewLine);
|
||||
return def;
|
||||
// Add some columns
|
||||
if (hasHeaderRow) {
|
||||
var headerRow = rows.First();
|
||||
rows = rows.Skip(1);
|
||||
foreach (var header in headerRow) {
|
||||
table.AddColumn(header);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteLine(string text = "")
|
||||
{
|
||||
AnsiConsole.Markup(text + Environment.NewLine);
|
||||
}
|
||||
|
||||
public void WriteTable(string tableName, IEnumerable<IEnumerable<string>> rows, bool hasHeaderRow = true)
|
||||
{
|
||||
// Create a table
|
||||
var table = new Table();
|
||||
table.Title($"[bold underline]{tableName}[/]");
|
||||
table.Expand();
|
||||
table.LeftAligned();
|
||||
|
||||
// Add some columns
|
||||
if (hasHeaderRow) {
|
||||
var headerRow = rows.First();
|
||||
rows = rows.Skip(1);
|
||||
foreach (var header in headerRow) {
|
||||
table.AddColumn(header);
|
||||
}
|
||||
} else {
|
||||
var numColumns = rows.First().Count();
|
||||
for (int i = 0; i < numColumns; i++) {
|
||||
table.AddColumn($"Column {i}");
|
||||
}
|
||||
table.HideHeaders();
|
||||
} else {
|
||||
var numColumns = rows.First().Count();
|
||||
for (int i = 0; i < numColumns; i++) {
|
||||
table.AddColumn($"Column {i}");
|
||||
}
|
||||
|
||||
// add rows
|
||||
foreach (var row in rows) {
|
||||
table.AddRow(row.ToArray());
|
||||
}
|
||||
|
||||
// Render the table to the console
|
||||
AnsiConsole.Write(table);
|
||||
table.HideHeaders();
|
||||
}
|
||||
|
||||
private class Progress : IFancyConsoleProgress
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ProgressContext _context;
|
||||
// add rows
|
||||
foreach (var row in rows) {
|
||||
table.AddRow(row.ToArray());
|
||||
}
|
||||
|
||||
public Progress(ILogger logger, ProgressContext context)
|
||||
// Render the table to the console
|
||||
AnsiConsole.Write(table);
|
||||
}
|
||||
|
||||
private class Progress : IFancyConsoleProgress
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ProgressContext _context;
|
||||
|
||||
public Progress(ILogger logger, ProgressContext context)
|
||||
{
|
||||
_logger = logger;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task RunTask(string name, Func<Action<int>, Task> fn)
|
||||
{
|
||||
_logger.Log(LogLevel.Debug, "Starting: " + name);
|
||||
|
||||
var task = _context.AddTask($"[italic]{name}[/]");
|
||||
task.StartTask();
|
||||
|
||||
void progress(int p)
|
||||
{
|
||||
_logger = logger;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task RunTask(string name, Func<Action<int>, Task> fn)
|
||||
{
|
||||
_logger.Log(LogLevel.Debug, "Starting: " + name);
|
||||
|
||||
var task = _context.AddTask($"[italic]{name}[/]");
|
||||
task.StartTask();
|
||||
|
||||
void progress(int p)
|
||||
{
|
||||
if (p < 0) {
|
||||
task.IsIndeterminate = true;
|
||||
} else {
|
||||
task.IsIndeterminate = false;
|
||||
task.Value = Math.Min(100, p);
|
||||
}
|
||||
if (p < 0) {
|
||||
task.IsIndeterminate = true;
|
||||
} else {
|
||||
task.IsIndeterminate = false;
|
||||
task.Value = Math.Min(100, p);
|
||||
}
|
||||
|
||||
await Task.Run(() => fn(progress)).ConfigureAwait(false);
|
||||
task.IsIndeterminate = false;
|
||||
task.StopTask();
|
||||
|
||||
_logger.Log(LogLevel.Debug, $"[bold]Complete: {name}[/]");
|
||||
}
|
||||
|
||||
await Task.Run(() => fn(progress)).ConfigureAwait(false);
|
||||
task.IsIndeterminate = false;
|
||||
task.StopTask();
|
||||
|
||||
_logger.Log(LogLevel.Debug, $"[bold]Complete: {name}[/]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,115 +10,114 @@ using Squirrel;
|
||||
|
||||
[assembly: AssemblyMetadata("SquirrelAwareVersion", "1")]
|
||||
|
||||
namespace LegacyTestApp
|
||||
namespace LegacyTestApp;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
internal class Program
|
||||
static int Main(string[] args)
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
#if CLOWD
|
||||
SquirrelAwareApp.HandleEvents(
|
||||
onInitialInstall: (v, t) => debugFile("args.txt", String.Join(" ", args)),
|
||||
onAppUpdate: (v, t) => debugFile("args.txt", String.Join(" ", args)),
|
||||
onAppUninstall: (v, t) => debugFile("args.txt", String.Join(" ", args)),
|
||||
onEveryRun: (v, t, f) => debugFile("args.txt", String.Join(" ", args))
|
||||
);
|
||||
SquirrelAwareApp.HandleEvents(
|
||||
onInitialInstall: (v, t) => debugFile("args.txt", String.Join(" ", args)),
|
||||
onAppUpdate: (v, t) => debugFile("args.txt", String.Join(" ", args)),
|
||||
onAppUninstall: (v, t) => debugFile("args.txt", String.Join(" ", args)),
|
||||
onEveryRun: (v, t, f) => debugFile("args.txt", String.Join(" ", args))
|
||||
);
|
||||
#elif VELOPACK
|
||||
VelopackApp.Build()
|
||||
.WithAfterInstallFastCallback(v => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithBeforeUpdateFastCallback(v => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithBeforeUninstallFastCallback(v => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithAfterUpdateFastCallback(v => debugFile("args.txt", String.Join(" ", args)))
|
||||
.Run();
|
||||
VelopackApp.Build()
|
||||
.WithAfterInstallFastCallback(v => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithBeforeUpdateFastCallback(v => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithBeforeUninstallFastCallback(v => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithAfterUpdateFastCallback(v => debugFile("args.txt", String.Join(" ", args)))
|
||||
.Run();
|
||||
#else
|
||||
SquirrelAwareApp.HandleEvents(
|
||||
onInitialInstall: v => debugFile("args.txt", String.Join(" ", args)),
|
||||
onAppUpdate: v => debugFile("args.txt", String.Join(" ", args)),
|
||||
onAppUninstall: v => debugFile("args.txt", String.Join(" ", args)),
|
||||
onFirstRun: () => debugFile("args.txt", String.Join(" ", args))
|
||||
);
|
||||
SquirrelAwareApp.HandleEvents(
|
||||
onInitialInstall: v => debugFile("args.txt", String.Join(" ", args)),
|
||||
onAppUpdate: v => debugFile("args.txt", String.Join(" ", args)),
|
||||
onAppUninstall: v => debugFile("args.txt", String.Join(" ", args)),
|
||||
onFirstRun: () => debugFile("args.txt", String.Join(" ", args))
|
||||
);
|
||||
#endif
|
||||
|
||||
try {
|
||||
try {
|
||||
#if !VELOPACK
|
||||
SquirrelLogger.Register();
|
||||
SquirrelLogger.Register();
|
||||
#endif
|
||||
|
||||
if (args.Length == 1 && args[0] == "version") {
|
||||
if (args.Length == 1 && args[0] == "version") {
|
||||
#if VELOPACK
|
||||
var um = new UpdateManager("n/a", logger: new SquirrelLogger());
|
||||
Console.WriteLine(um.CurrentVersion?.ToString() ?? "unknown_version");
|
||||
var um = new UpdateManager("n/a", logger: new SquirrelLogger());
|
||||
Console.WriteLine(um.CurrentVersion?.ToString() ?? "unknown_version");
|
||||
#else
|
||||
using var um = new UpdateManager("");
|
||||
Console.WriteLine(um.CurrentlyInstalledVersion()?.ToString() ?? "unknown_version");
|
||||
using var um = new UpdateManager("");
|
||||
Console.WriteLine(um.CurrentlyInstalledVersion()?.ToString() ?? "unknown_version");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args.Length == 2) {
|
||||
if (args[0] == "check") {
|
||||
#if VELOPACK
|
||||
var um = new UpdateManager(args[1]);
|
||||
var info = um.CheckForUpdates();
|
||||
if (info == null || info.TargetFullRelease == null) {
|
||||
Console.WriteLine("no updates");
|
||||
return 0;
|
||||
} else {
|
||||
Console.WriteLine("update: " + info.TargetFullRelease.Version);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
using var um = new UpdateManager(args[1]);
|
||||
var info = um.CheckForUpdate().GetAwaiter().GetResult();
|
||||
if (info == null || info.ReleasesToApply == null || info.FutureReleaseEntry == null || info.ReleasesToApply.Count == 0) {
|
||||
Console.WriteLine("no updates");
|
||||
return 0;
|
||||
} else {
|
||||
Console.WriteLine("update: " + info.FutureReleaseEntry.Version);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (args[0] == "download") {
|
||||
#if VELOPACK
|
||||
var um = new UpdateManager(args[1]);
|
||||
var info = um.CheckForUpdates();
|
||||
if (info == null) return -1;
|
||||
um.DownloadUpdates(info);
|
||||
return 0;
|
||||
#else
|
||||
using var um = new UpdateManager(args[1]);
|
||||
var entry = um.UpdateApp().GetAwaiter().GetResult();
|
||||
return entry == null ? -1 : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (args[0] == "apply") {
|
||||
#if VELOPACK
|
||||
var um = new UpdateManager(args[1]);
|
||||
um.ApplyUpdatesAndRestart();
|
||||
#else
|
||||
UpdateManager.RestartApp();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args.Length == 2) {
|
||||
if (args[0] == "check") {
|
||||
#if VELOPACK
|
||||
var um = new UpdateManager(args[1]);
|
||||
var info = um.CheckForUpdates();
|
||||
if (info == null || info.TargetFullRelease == null) {
|
||||
Console.WriteLine("no updates");
|
||||
return 0;
|
||||
} else {
|
||||
Console.WriteLine("update: " + info.TargetFullRelease.Version);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
using var um = new UpdateManager(args[1]);
|
||||
var info = um.CheckForUpdate().GetAwaiter().GetResult();
|
||||
if (info == null || info.ReleasesToApply == null || info.FutureReleaseEntry == null || info.ReleasesToApply.Count == 0) {
|
||||
Console.WriteLine("no updates");
|
||||
return 0;
|
||||
} else {
|
||||
Console.WriteLine("update: " + info.FutureReleaseEntry.Version);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (args[0] == "download") {
|
||||
#if VELOPACK
|
||||
var um = new UpdateManager(args[1]);
|
||||
var info = um.CheckForUpdates();
|
||||
if (info == null) return -1;
|
||||
um.DownloadUpdates(info);
|
||||
return 0;
|
||||
#else
|
||||
using var um = new UpdateManager(args[1]);
|
||||
var entry = um.UpdateApp().GetAwaiter().GetResult();
|
||||
return entry == null ? -1 : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (args[0] == "apply") {
|
||||
#if VELOPACK
|
||||
var um = new UpdateManager(args[1]);
|
||||
um.ApplyUpdatesAndRestart();
|
||||
#else
|
||||
UpdateManager.RestartApp();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine("exception: " + ex.ToString());
|
||||
if (Debugger.IsAttached) throw;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Console.WriteLine("Unhandled args: " + String.Join(", ", args));
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine("exception: " + ex.ToString());
|
||||
if (Debugger.IsAttached) throw;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void debugFile(string name, string message)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "..", name);
|
||||
File.AppendAllText(path, message + Environment.NewLine);
|
||||
}
|
||||
Console.WriteLine("Unhandled args: " + String.Join(", ", args));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void debugFile(string name, string message)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "..", name);
|
||||
File.AppendAllText(path, message + Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace LegacyTestApp
|
||||
{
|
||||
namespace LegacyTestApp;
|
||||
|
||||
#if VELOPACK
|
||||
using Microsoft.Extensions.Logging;
|
||||
class SquirrelLogger : ILogger
|
||||
using Microsoft.Extensions.Logging;
|
||||
class SquirrelLogger : ILogger
|
||||
{
|
||||
public IDisposable BeginScope<TState>(TState state) where TState : notnull
|
||||
{
|
||||
public IDisposable BeginScope<TState>(TState state) where TState : notnull
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
Console.WriteLine(formatter(state, exception));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
class SquirrelLogger : Squirrel.SimpleSplat.ILogger
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
protected SquirrelLogger()
|
||||
{
|
||||
}
|
||||
|
||||
public Squirrel.SimpleSplat.LogLevel Level { get; set; }
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
Squirrel.SimpleSplat.SquirrelLocator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Squirrel.SimpleSplat.ILogger));
|
||||
}
|
||||
|
||||
public void Write(string message, Squirrel.SimpleSplat.LogLevel logLevel)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
Console.WriteLine(formatter(state, exception));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
class SquirrelLogger : Squirrel.SimpleSplat.ILogger
|
||||
{
|
||||
protected SquirrelLogger()
|
||||
{
|
||||
}
|
||||
|
||||
public Squirrel.SimpleSplat.LogLevel Level { get; set; }
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
Squirrel.SimpleSplat.SquirrelLocator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Squirrel.SimpleSplat.ILogger));
|
||||
}
|
||||
|
||||
public void Write(string message, Squirrel.SimpleSplat.LogLevel logLevel)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7,102 +7,101 @@ using Velopack.Packaging.Exceptions;
|
||||
using Velopack.Packaging.Windows;
|
||||
using Velopack.Packaging.Windows.Commands;
|
||||
|
||||
namespace Velopack.Packaging.Tests
|
||||
namespace Velopack.Packaging.Tests;
|
||||
|
||||
public class DotnetUtilTests
|
||||
{
|
||||
public class DotnetUtilTests
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public DotnetUtilTests(ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
_output = output;
|
||||
}
|
||||
|
||||
public DotnetUtilTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
[SkippableFact]
|
||||
public void NonDotnetBinaryPasses()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
Assert.Null(DotnetUtil.VerifyVelopackApp(PathHelper.GetRustAsset("testapp.exe"), logger));
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void NonDotnetBinaryPasses()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
Assert.Null(DotnetUtil.VerifyVelopackApp(PathHelper.GetRustAsset("testapp.exe"), logger));
|
||||
}
|
||||
[SkippableFact]
|
||||
public void PublishSingleFilePasses()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var dir);
|
||||
var sample = PathHelper.GetAvaloniaSample();
|
||||
Exe.InvokeAndThrowIfNonZero(
|
||||
"dotnet",
|
||||
new string[] { "publish", "--no-self-contained", "-r", "win-x64", "-o", dir,
|
||||
"-p:UseLocalVelopack=true", "-p:PublishSingleFile=true" },
|
||||
sample);
|
||||
|
||||
[SkippableFact]
|
||||
public void PublishSingleFilePasses()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var dir);
|
||||
var sample = PathHelper.GetAvaloniaSample();
|
||||
Exe.InvokeAndThrowIfNonZero(
|
||||
"dotnet",
|
||||
new string[] { "publish", "--no-self-contained", "-r", "win-x64", "-o", dir,
|
||||
"-p:UseLocalVelopack=true", "-p:PublishSingleFile=true" },
|
||||
sample);
|
||||
var path = Path.Combine(dir, "AvaloniaCrossPlat.exe");
|
||||
Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(path, logger));
|
||||
|
||||
var path = Path.Combine(dir, "AvaloniaCrossPlat.exe");
|
||||
Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(path, logger));
|
||||
var newPath = Path.Combine(dir, "AvaloniaCrossPlat-asd2.exe");
|
||||
File.Move(path, newPath);
|
||||
Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(newPath, logger));
|
||||
}
|
||||
|
||||
var newPath = Path.Combine(dir, "AvaloniaCrossPlat-asd2.exe");
|
||||
File.Move(path, newPath);
|
||||
Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(newPath, logger));
|
||||
}
|
||||
[SkippableFact]
|
||||
public void PublishDotnet6Passes()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var dir);
|
||||
var sample = PathHelper.GetAvaloniaSample();
|
||||
Exe.InvokeAndThrowIfNonZero(
|
||||
"dotnet",
|
||||
new string[] { "publish", "--no-self-contained", "-r", "win-x64", "-o", dir,
|
||||
"-p:UseLocalVelopack=true" },
|
||||
sample);
|
||||
|
||||
[SkippableFact]
|
||||
public void PublishDotnet6Passes()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var dir);
|
||||
var sample = PathHelper.GetAvaloniaSample();
|
||||
Exe.InvokeAndThrowIfNonZero(
|
||||
"dotnet",
|
||||
new string[] { "publish", "--no-self-contained", "-r", "win-x64", "-o", dir,
|
||||
"-p:UseLocalVelopack=true" },
|
||||
sample);
|
||||
var path = Path.Combine(dir, "AvaloniaCrossPlat.exe");
|
||||
Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(path, logger));
|
||||
|
||||
var path = Path.Combine(dir, "AvaloniaCrossPlat.exe");
|
||||
Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(path, logger));
|
||||
var newPath = Path.Combine(dir, "AvaloniaCrossPlat-asd2.exe");
|
||||
File.Move(path, newPath);
|
||||
Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(newPath, logger));
|
||||
}
|
||||
|
||||
var newPath = Path.Combine(dir, "AvaloniaCrossPlat-asd2.exe");
|
||||
File.Move(path, newPath);
|
||||
Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(newPath, logger));
|
||||
}
|
||||
[SkippableFact]
|
||||
public void PublishNet48Passes()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var dir);
|
||||
var sample = PathHelper.GetWpfSample();
|
||||
Exe.InvokeAndThrowIfNonZero(
|
||||
"dotnet",
|
||||
new string[] { "publish", "-o", dir },
|
||||
sample);
|
||||
|
||||
[SkippableFact]
|
||||
public void PublishNet48Passes()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var dir);
|
||||
var sample = PathHelper.GetWpfSample();
|
||||
Exe.InvokeAndThrowIfNonZero(
|
||||
"dotnet",
|
||||
new string[] { "publish", "-o", dir },
|
||||
sample);
|
||||
var path = Path.Combine(dir, "VeloWpfSample.exe");
|
||||
Assert.NotNull(DotnetUtil.VerifyVelopackApp(path, logger));
|
||||
|
||||
var path = Path.Combine(dir, "VeloWpfSample.exe");
|
||||
Assert.NotNull(DotnetUtil.VerifyVelopackApp(path, logger));
|
||||
var newPath = Path.Combine(dir, "VeloWpfSample-asd2.exe");
|
||||
File.Move(path, newPath);
|
||||
Assert.NotNull(DotnetUtil.VerifyVelopackApp(newPath, logger));
|
||||
}
|
||||
|
||||
var newPath = Path.Combine(dir, "VeloWpfSample-asd2.exe");
|
||||
File.Move(path, newPath);
|
||||
Assert.NotNull(DotnetUtil.VerifyVelopackApp(newPath, logger));
|
||||
}
|
||||
[SkippableFact]
|
||||
public void UnawareDotnetAppFails()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var dir);
|
||||
var sample = PathHelper.GetTestRootPath("TestApp");
|
||||
Exe.InvokeAndThrowIfNonZero(
|
||||
"dotnet",
|
||||
new string[] { "publish", "--no-self-contained", "-r", "win-x64", "-o", dir,
|
||||
"-p:NoVelopackApp=true" },
|
||||
sample);
|
||||
|
||||
[SkippableFact]
|
||||
public void UnawareDotnetAppFails()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<DotnetUtilTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var dir);
|
||||
var sample = PathHelper.GetTestRootPath("TestApp");
|
||||
Exe.InvokeAndThrowIfNonZero(
|
||||
"dotnet",
|
||||
new string[] { "publish", "--no-self-contained", "-r", "win-x64", "-o", dir,
|
||||
"-p:NoVelopackApp=true" },
|
||||
sample);
|
||||
|
||||
var path = Path.Combine(dir, "TestApp.exe");
|
||||
Assert.Throws<UserInfoException>(() => DotnetUtil.VerifyVelopackApp(path, logger));
|
||||
}
|
||||
var path = Path.Combine(dir, "TestApp.exe");
|
||||
Assert.Throws<UserInfoException>(() => DotnetUtil.VerifyVelopackApp(path, logger));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,232 +6,231 @@ using Velopack.Sources;
|
||||
using Octokit;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging.Tests
|
||||
namespace Velopack.Packaging.Tests;
|
||||
|
||||
public class GithubDeploymentTests
|
||||
{
|
||||
public class GithubDeploymentTests
|
||||
public readonly static string GITHUB_TOKEN = Environment.GetEnvironmentVariable("VELOPACK_GITHUB_TEST_TOKEN");
|
||||
public readonly static string GITHUB_REPOURL = "https://github.com/caesay/VelopackGithubUpdateTest";
|
||||
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public GithubDeploymentTests(ITestOutputHelper output)
|
||||
{
|
||||
public readonly static string GITHUB_TOKEN = Environment.GetEnvironmentVariable("VELOPACK_GITHUB_TEST_TOKEN");
|
||||
public readonly static string GITHUB_REPOURL = "https://github.com/caesay/VelopackGithubUpdateTest";
|
||||
_output = output;
|
||||
}
|
||||
|
||||
private readonly ITestOutputHelper _output;
|
||||
[SkippableFact]
|
||||
public void WillRefuseToUploadMultipleWithoutMergeArg()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
|
||||
using var ghvar = GitHubReleaseTest.Create("nomerge", logger);
|
||||
var id = "GithubUpdateTest";
|
||||
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
|
||||
|
||||
public GithubDeploymentTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
var gh = new GitHubRepository(logger);
|
||||
var options = new GitHubUploadOptions {
|
||||
ReleaseName = ghvar.ReleaseName,
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
Token = GITHUB_TOKEN,
|
||||
Prerelease = false,
|
||||
Publish = true,
|
||||
};
|
||||
|
||||
[SkippableFact]
|
||||
public void WillRefuseToUploadMultipleWithoutMergeArg()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
|
||||
using var ghvar = GitHubReleaseTest.Create("nomerge", logger);
|
||||
var id = "GithubUpdateTest";
|
||||
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
|
||||
var gh = new GitHubRepository(logger);
|
||||
var options = new GitHubUploadOptions {
|
||||
ReleaseName = ghvar.ReleaseName,
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
Token = GITHUB_TOKEN,
|
||||
Prerelease = false,
|
||||
Publish = true,
|
||||
};
|
||||
TestApp.PackTestApp(id, $"0.0.2-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger);
|
||||
options.ReleaseDir = new DirectoryInfo(releaseDir2);
|
||||
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
Assert.ThrowsAny<UserInfoException>(() => gh.UploadMissingAssetsAsync(options).GetAwaiterResult());
|
||||
}
|
||||
|
||||
TestApp.PackTestApp(id, $"0.0.2-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger);
|
||||
options.ReleaseDir = new DirectoryInfo(releaseDir2);
|
||||
[SkippableFact]
|
||||
public void WillNotMergeMixmatchedTag()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
|
||||
using var ghvar = GitHubReleaseTest.Create("mixmatched", logger);
|
||||
var id = "GithubUpdateTest";
|
||||
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
|
||||
|
||||
Assert.ThrowsAny<UserInfoException>(() => gh.UploadMissingAssetsAsync(options).GetAwaiterResult());
|
||||
}
|
||||
var gh = new GitHubRepository(logger);
|
||||
var options = new GitHubUploadOptions {
|
||||
ReleaseName = ghvar.ReleaseName,
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
Token = GITHUB_TOKEN,
|
||||
Prerelease = false,
|
||||
Publish = true,
|
||||
Merge = true,
|
||||
};
|
||||
|
||||
[SkippableFact]
|
||||
public void WillNotMergeMixmatchedTag()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
|
||||
using var ghvar = GitHubReleaseTest.Create("mixmatched", logger);
|
||||
var id = "GithubUpdateTest";
|
||||
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
|
||||
var gh = new GitHubRepository(logger);
|
||||
var options = new GitHubUploadOptions {
|
||||
ReleaseName = ghvar.ReleaseName,
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
Token = GITHUB_TOKEN,
|
||||
Prerelease = false,
|
||||
Publish = true,
|
||||
Merge = true,
|
||||
};
|
||||
TestApp.PackTestApp(id, $"0.0.2-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger);
|
||||
options.ReleaseDir = new DirectoryInfo(releaseDir2);
|
||||
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
Assert.ThrowsAny<UserInfoException>(() => gh.UploadMissingAssetsAsync(options).GetAwaiterResult());
|
||||
}
|
||||
|
||||
TestApp.PackTestApp(id, $"0.0.2-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger);
|
||||
options.ReleaseDir = new DirectoryInfo(releaseDir2);
|
||||
[SkippableFact]
|
||||
public void WillMergeGithubReleases()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
|
||||
using var ghvar = GitHubReleaseTest.Create("yesmerge", logger);
|
||||
var id = "GithubUpdateTest";
|
||||
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
|
||||
|
||||
Assert.ThrowsAny<UserInfoException>(() => gh.UploadMissingAssetsAsync(options).GetAwaiterResult());
|
||||
}
|
||||
var gh = new GitHubRepository(logger);
|
||||
var options = new GitHubUploadOptions {
|
||||
ReleaseName = ghvar.ReleaseName,
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
Token = GITHUB_TOKEN,
|
||||
TagName = $"0.0.1-{ghvar.UniqueSuffix}",
|
||||
Prerelease = false,
|
||||
Publish = true,
|
||||
Merge = true,
|
||||
};
|
||||
|
||||
[SkippableFact]
|
||||
public void WillMergeGithubReleases()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
|
||||
using var ghvar = GitHubReleaseTest.Create("yesmerge", logger);
|
||||
var id = "GithubUpdateTest";
|
||||
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
|
||||
var gh = new GitHubRepository(logger);
|
||||
var options = new GitHubUploadOptions {
|
||||
ReleaseName = ghvar.ReleaseName,
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
Token = GITHUB_TOKEN,
|
||||
TagName = $"0.0.1-{ghvar.UniqueSuffix}",
|
||||
Prerelease = false,
|
||||
Publish = true,
|
||||
Merge = true,
|
||||
};
|
||||
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger, channel: "experimental");
|
||||
options.ReleaseDir = new DirectoryInfo(releaseDir2);
|
||||
options.Channel = "experimental";
|
||||
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
}
|
||||
|
||||
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger, channel: "experimental");
|
||||
options.ReleaseDir = new DirectoryInfo(releaseDir2);
|
||||
options.Channel = "experimental";
|
||||
[SkippableFact]
|
||||
public void CanDeployAndUpdateFromGithub()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
|
||||
var id = "GithubUpdateTest";
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
var (repoOwner, repoName) = GitHubRepository.GetOwnerAndRepo(GITHUB_REPOURL);
|
||||
using var ghvar = GitHubReleaseTest.Create("integration", logger);
|
||||
var releaseName = ghvar.ReleaseName;
|
||||
var uniqueSuffix = ghvar.UniqueSuffix;
|
||||
var client = ghvar.Client;
|
||||
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void CanDeployAndUpdateFromGithub()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
|
||||
var id = "GithubUpdateTest";
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
var (repoOwner, repoName) = GitHubRepository.GetOwnerAndRepo(GITHUB_REPOURL);
|
||||
using var ghvar = GitHubReleaseTest.Create("integration", logger);
|
||||
var releaseName = ghvar.ReleaseName;
|
||||
var uniqueSuffix = ghvar.UniqueSuffix;
|
||||
var client = ghvar.Client;
|
||||
|
||||
// create releases
|
||||
var notesPath = Path.Combine(releaseDir, "NOTES");
|
||||
var notesContent = $"""
|
||||
// create releases
|
||||
var notesPath = Path.Combine(releaseDir, "NOTES");
|
||||
var notesContent = $"""
|
||||
# Release {releaseName}
|
||||
This is just a _test_!
|
||||
""";
|
||||
File.WriteAllText(notesPath, notesContent);
|
||||
File.WriteAllText(notesPath, notesContent);
|
||||
|
||||
if (String.IsNullOrEmpty(GITHUB_TOKEN))
|
||||
throw new Exception("VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
if (String.IsNullOrEmpty(GITHUB_TOKEN))
|
||||
throw new Exception("VELOPACK_GITHUB_TEST_TOKEN is not set.");
|
||||
|
||||
var newVer = $"{VelopackRuntimeInfo.VelopackNugetVersion}";
|
||||
TestApp.PackTestApp(id, $"0.0.1", "t1", releaseDir, logger, notesPath, channel: uniqueSuffix);
|
||||
TestApp.PackTestApp(id, newVer, "t2", releaseDir, logger, notesPath, channel: uniqueSuffix);
|
||||
var newVer = $"{VelopackRuntimeInfo.VelopackNugetVersion}";
|
||||
TestApp.PackTestApp(id, $"0.0.1", "t1", releaseDir, logger, notesPath, channel: uniqueSuffix);
|
||||
TestApp.PackTestApp(id, newVer, "t2", releaseDir, logger, notesPath, channel: uniqueSuffix);
|
||||
|
||||
// deploy
|
||||
var gh = new GitHubRepository(logger);
|
||||
var options = new GitHubUploadOptions {
|
||||
ReleaseName = releaseName,
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
Token = GITHUB_TOKEN,
|
||||
Prerelease = false,
|
||||
Publish = true,
|
||||
Channel = uniqueSuffix,
|
||||
};
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
// deploy
|
||||
var gh = new GitHubRepository(logger);
|
||||
var options = new GitHubUploadOptions {
|
||||
ReleaseName = releaseName,
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
Token = GITHUB_TOKEN,
|
||||
Prerelease = false,
|
||||
Publish = true,
|
||||
Channel = uniqueSuffix,
|
||||
};
|
||||
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
|
||||
// check
|
||||
var newRelease = client.Repository.Release.GetAll(repoOwner, repoName).GetAwaiterResult().Single(s => s.Name == releaseName);
|
||||
Assert.False(newRelease.Draft);
|
||||
Assert.Equal(notesContent.Trim().ReplaceLineEndings("\n"), newRelease.Body.Trim());
|
||||
// check
|
||||
var newRelease = client.Repository.Release.GetAll(repoOwner, repoName).GetAwaiterResult().Single(s => s.Name == releaseName);
|
||||
Assert.False(newRelease.Draft);
|
||||
Assert.Equal(notesContent.Trim().ReplaceLineEndings("\n"), newRelease.Body.Trim());
|
||||
|
||||
// update
|
||||
var source = new GithubSource(GITHUB_REPOURL, GITHUB_TOKEN, false);
|
||||
var releases = source.GetReleaseFeed(channel: uniqueSuffix, logger: logger).GetAwaiterResult();
|
||||
// update
|
||||
var source = new GithubSource(GITHUB_REPOURL, GITHUB_TOKEN, false);
|
||||
var releases = source.GetReleaseFeed(channel: uniqueSuffix, logger: logger).GetAwaiterResult();
|
||||
|
||||
var ghrel = releases.Assets.Select(r => (GithubSource.GitBaseAsset) r).ToArray();
|
||||
foreach (var g in ghrel) {
|
||||
logger.Info($"Found asset: ({g.Release.Name}) {g.FileName}");
|
||||
}
|
||||
|
||||
var assetsInThisRelease = ghrel.Where(r => r.Release.Name == releaseName).ToArray();
|
||||
|
||||
Assert.Equal(2, assetsInThisRelease.Length);
|
||||
foreach (var r in assetsInThisRelease) {
|
||||
Assert.Equal(releaseName, r.Release.Name);
|
||||
Assert.Equal(id, r.PackageId);
|
||||
Assert.Equal(newVer, r.Version.ToNormalizedString());
|
||||
}
|
||||
|
||||
using var _2 = Utility.GetTempDirectory(out var releaseDirNew);
|
||||
gh.DownloadLatestFullPackageAsync(new GitHubDownloadOptions {
|
||||
Token = GITHUB_TOKEN,
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
ReleaseDir = new DirectoryInfo(releaseDirNew),
|
||||
Channel = uniqueSuffix,
|
||||
}).GetAwaiterResult();
|
||||
|
||||
var filename = $"{id}-{newVer}-{uniqueSuffix}-full.nupkg";
|
||||
Assert.True(File.Exists(Path.Combine(releaseDirNew, filename)));
|
||||
var ghrel = releases.Assets.Select(r => (GithubSource.GitBaseAsset) r).ToArray();
|
||||
foreach (var g in ghrel) {
|
||||
logger.Info($"Found asset: ({g.Release.Name}) {g.FileName}");
|
||||
}
|
||||
|
||||
private class GitHubReleaseTest : IDisposable
|
||||
{
|
||||
public string ReleaseName { get; }
|
||||
public string UniqueSuffix { get; }
|
||||
public GitHubClient Client { get; }
|
||||
public ILogger Logger { get; }
|
||||
var assetsInThisRelease = ghrel.Where(r => r.Release.Name == releaseName).ToArray();
|
||||
|
||||
public GitHubReleaseTest(string releaseName, string uniqueSuffix, GitHubClient client, ILogger logger)
|
||||
{
|
||||
ReleaseName = releaseName;
|
||||
UniqueSuffix = uniqueSuffix;
|
||||
Client = client;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public static GitHubReleaseTest Create(string method, ILogger logger)
|
||||
{
|
||||
var ci = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"));
|
||||
var uniqueSuffix = (ci ? "ci-" : "local-") + VelopackRuntimeInfo.SystemOs.GetOsShortName();
|
||||
var releaseName = $"{VelopackRuntimeInfo.VelopackNugetVersion}-{uniqueSuffix}-{method}";
|
||||
var (repoOwner, repoName) = GitHubRepository.GetOwnerAndRepo(GITHUB_REPOURL);
|
||||
|
||||
// delete release if already exists
|
||||
var client = new GitHubClient(new ProductHeaderValue("Velopack")) {
|
||||
Credentials = new Credentials(GITHUB_TOKEN)
|
||||
};
|
||||
var existingRelease = client.Repository.Release.GetAll(repoOwner, repoName).GetAwaiterResult().SingleOrDefault(s => s.Name == releaseName);
|
||||
if (existingRelease != null) {
|
||||
client.Repository.Release.Delete(repoOwner, repoName, existingRelease.Id).GetAwaiterResult();
|
||||
logger.Info("Deleted existing release: " + releaseName);
|
||||
}
|
||||
return new GitHubReleaseTest(releaseName, uniqueSuffix, client, logger);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var (repoOwner, repoName) = GitHubRepository.GetOwnerAndRepo(GITHUB_REPOURL);
|
||||
var finalRelease = Client.Repository.Release.GetAll(repoOwner, repoName).GetAwaiterResult().SingleOrDefault(s => s.Name == ReleaseName);
|
||||
if (finalRelease != null) {
|
||||
Client.Repository.Release.Delete(repoOwner, repoName, finalRelease.Id).GetAwaiterResult();
|
||||
Logger.Info($"Deleted final release '{ReleaseName}'");
|
||||
}
|
||||
}
|
||||
Assert.Equal(2, assetsInThisRelease.Length);
|
||||
foreach (var r in assetsInThisRelease) {
|
||||
Assert.Equal(releaseName, r.Release.Name);
|
||||
Assert.Equal(id, r.PackageId);
|
||||
Assert.Equal(newVer, r.Version.ToNormalizedString());
|
||||
}
|
||||
|
||||
using var _2 = Utility.GetTempDirectory(out var releaseDirNew);
|
||||
gh.DownloadLatestFullPackageAsync(new GitHubDownloadOptions {
|
||||
Token = GITHUB_TOKEN,
|
||||
RepoUrl = GITHUB_REPOURL,
|
||||
ReleaseDir = new DirectoryInfo(releaseDirNew),
|
||||
Channel = uniqueSuffix,
|
||||
}).GetAwaiterResult();
|
||||
|
||||
var filename = $"{id}-{newVer}-{uniqueSuffix}-full.nupkg";
|
||||
Assert.True(File.Exists(Path.Combine(releaseDirNew, filename)));
|
||||
}
|
||||
|
||||
private class GitHubReleaseTest : IDisposable
|
||||
{
|
||||
public string ReleaseName { get; }
|
||||
public string UniqueSuffix { get; }
|
||||
public GitHubClient Client { get; }
|
||||
public ILogger Logger { get; }
|
||||
|
||||
public GitHubReleaseTest(string releaseName, string uniqueSuffix, GitHubClient client, ILogger logger)
|
||||
{
|
||||
ReleaseName = releaseName;
|
||||
UniqueSuffix = uniqueSuffix;
|
||||
Client = client;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public static GitHubReleaseTest Create(string method, ILogger logger)
|
||||
{
|
||||
var ci = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"));
|
||||
var uniqueSuffix = (ci ? "ci-" : "local-") + VelopackRuntimeInfo.SystemOs.GetOsShortName();
|
||||
var releaseName = $"{VelopackRuntimeInfo.VelopackNugetVersion}-{uniqueSuffix}-{method}";
|
||||
var (repoOwner, repoName) = GitHubRepository.GetOwnerAndRepo(GITHUB_REPOURL);
|
||||
|
||||
// delete release if already exists
|
||||
var client = new GitHubClient(new ProductHeaderValue("Velopack")) {
|
||||
Credentials = new Credentials(GITHUB_TOKEN)
|
||||
};
|
||||
var existingRelease = client.Repository.Release.GetAll(repoOwner, repoName).GetAwaiterResult().SingleOrDefault(s => s.Name == releaseName);
|
||||
if (existingRelease != null) {
|
||||
client.Repository.Release.Delete(repoOwner, repoName, existingRelease.Id).GetAwaiterResult();
|
||||
logger.Info("Deleted existing release: " + releaseName);
|
||||
}
|
||||
return new GitHubReleaseTest(releaseName, uniqueSuffix, client, logger);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var (repoOwner, repoName) = GitHubRepository.GetOwnerAndRepo(GITHUB_REPOURL);
|
||||
var finalRelease = Client.Repository.Release.GetAll(repoOwner, repoName).GetAwaiterResult().SingleOrDefault(s => s.Name == ReleaseName);
|
||||
if (finalRelease != null) {
|
||||
Client.Repository.Release.Delete(repoOwner, repoName, finalRelease.Id).GetAwaiterResult();
|
||||
Logger.Info($"Deleted final release '{ReleaseName}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
|
||||
[assembly: TestFramework("Velopack.Packaging.Tests.TestsInit", "Velopack.Packaging.Tests")]
|
||||
|
||||
namespace Velopack.Packaging.Tests
|
||||
namespace Velopack.Packaging.Tests;
|
||||
|
||||
public class TestsInit : XunitTestFramework
|
||||
{
|
||||
public class TestsInit : XunitTestFramework
|
||||
public TestsInit(IMessageSink messageSink)
|
||||
: base(messageSink)
|
||||
{
|
||||
public TestsInit(IMessageSink messageSink)
|
||||
: base(messageSink)
|
||||
{
|
||||
HelperFile.AddSearchPath(PathHelper.GetRustBuildOutputDir());
|
||||
HelperFile.AddSearchPath(PathHelper.GetVendorLibDir());
|
||||
}
|
||||
HelperFile.AddSearchPath(PathHelper.GetRustBuildOutputDir());
|
||||
HelperFile.AddSearchPath(PathHelper.GetVendorLibDir());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,80 +8,79 @@ using Velopack.Deployment;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
using Velopack.Sources;
|
||||
|
||||
namespace Velopack.Packaging.Tests
|
||||
namespace Velopack.Packaging.Tests;
|
||||
|
||||
public class S3DeploymentTests
|
||||
{
|
||||
public class S3DeploymentTests
|
||||
public readonly static string B2_KEYID = "0035016844a4188000000000a";
|
||||
public readonly static string B2_SECRET = Environment.GetEnvironmentVariable("VELOPACK_B2_TEST_TOKEN");
|
||||
public readonly static string B2_BUCKET = "velopack-testing";
|
||||
public readonly static string B2_ENDPOINT = "s3.eu-central-003.backblazeb2.com";
|
||||
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public S3DeploymentTests(ITestOutputHelper output)
|
||||
{
|
||||
public readonly static string B2_KEYID = "0035016844a4188000000000a";
|
||||
public readonly static string B2_SECRET = Environment.GetEnvironmentVariable("VELOPACK_B2_TEST_TOKEN");
|
||||
public readonly static string B2_BUCKET = "velopack-testing";
|
||||
public readonly static string B2_ENDPOINT = "s3.eu-central-003.backblazeb2.com";
|
||||
_output = output;
|
||||
}
|
||||
|
||||
private readonly ITestOutputHelper _output;
|
||||
[SkippableFact]
|
||||
public void CanDeployToBackBlazeB2()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(B2_SECRET), "VELOPACK_B2_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<S3DeploymentTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
|
||||
public S3DeploymentTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
string channel = String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CI"))
|
||||
? VelopackRuntimeInfo.SystemOs.GetOsShortName()
|
||||
: "ci-" + VelopackRuntimeInfo.SystemOs.GetOsShortName();
|
||||
|
||||
[SkippableFact]
|
||||
public void CanDeployToBackBlazeB2()
|
||||
{
|
||||
Skip.If(String.IsNullOrWhiteSpace(B2_SECRET), "VELOPACK_B2_TEST_TOKEN is not set.");
|
||||
using var logger = _output.BuildLoggerFor<S3DeploymentTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
|
||||
string channel = String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CI"))
|
||||
? VelopackRuntimeInfo.SystemOs.GetOsShortName()
|
||||
: "ci-" + VelopackRuntimeInfo.SystemOs.GetOsShortName();
|
||||
|
||||
// get latest version, and increment patch by one
|
||||
var updateUrl = $"https://{B2_BUCKET}.{B2_ENDPOINT}/";
|
||||
var source = new SimpleWebSource(updateUrl);
|
||||
VelopackAssetFeed feed = new VelopackAssetFeed();
|
||||
try {
|
||||
feed = source.GetReleaseFeed(logger, channel).GetAwaiterResult();
|
||||
} catch (Exception ex) {
|
||||
logger.Warn(ex, "Failed to fetch release feed.");
|
||||
}
|
||||
var latest = feed.Assets.Where(a => a.Version != null && a.Type == VelopackAssetType.Full)
|
||||
.OrderByDescending(a => a.Version)
|
||||
.FirstOrDefault();
|
||||
var newVer = latest != null ? new SemanticVersion(1, 0, latest.Version.Patch + 1) : new SemanticVersion(1, 0, 0);
|
||||
|
||||
// create repo
|
||||
var repo = new S3Repository(logger);
|
||||
var options = new S3UploadOptions {
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
Bucket = B2_BUCKET,
|
||||
Channel = channel,
|
||||
Endpoint = "https://" + B2_ENDPOINT,
|
||||
KeyId = B2_KEYID,
|
||||
Secret = B2_SECRET,
|
||||
KeepMaxReleases = 4,
|
||||
};
|
||||
|
||||
// download latest version and create delta
|
||||
repo.DownloadLatestFullPackageAsync(options).GetAwaiterResult();
|
||||
var id = "B2TestApp";
|
||||
TestApp.PackTestApp(id, newVer.ToFullString(), $"b2-{DateTime.UtcNow.ToLongDateString()}", releaseDir, logger, channel: channel);
|
||||
if (latest != null) {
|
||||
// check delta was created
|
||||
Assert.True(Directory.EnumerateFiles(releaseDir, "*-delta.nupkg").Any(), "No delta package was created.");
|
||||
}
|
||||
|
||||
// upload new files
|
||||
repo.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
|
||||
// verify that new version has been uploaded
|
||||
// get latest version, and increment patch by one
|
||||
var updateUrl = $"https://{B2_BUCKET}.{B2_ENDPOINT}/";
|
||||
var source = new SimpleWebSource(updateUrl);
|
||||
VelopackAssetFeed feed = new VelopackAssetFeed();
|
||||
try {
|
||||
feed = source.GetReleaseFeed(logger, channel).GetAwaiterResult();
|
||||
latest = feed.Assets.Where(a => a.Version != null && a.Type == VelopackAssetType.Full)
|
||||
.OrderByDescending(a => a.Version)
|
||||
.FirstOrDefault();
|
||||
|
||||
Assert.True(latest != null, "No latest version found.");
|
||||
Assert.Equal(newVer, latest.Version);
|
||||
Assert.True(feed.Assets.Count(x => x.Type == VelopackAssetType.Full) <= options.KeepMaxReleases, "Too many releases were kept.");
|
||||
} catch (Exception ex) {
|
||||
logger.Warn(ex, "Failed to fetch release feed.");
|
||||
}
|
||||
var latest = feed.Assets.Where(a => a.Version != null && a.Type == VelopackAssetType.Full)
|
||||
.OrderByDescending(a => a.Version)
|
||||
.FirstOrDefault();
|
||||
var newVer = latest != null ? new SemanticVersion(1, 0, latest.Version.Patch + 1) : new SemanticVersion(1, 0, 0);
|
||||
|
||||
// create repo
|
||||
var repo = new S3Repository(logger);
|
||||
var options = new S3UploadOptions {
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
Bucket = B2_BUCKET,
|
||||
Channel = channel,
|
||||
Endpoint = "https://" + B2_ENDPOINT,
|
||||
KeyId = B2_KEYID,
|
||||
Secret = B2_SECRET,
|
||||
KeepMaxReleases = 4,
|
||||
};
|
||||
|
||||
// download latest version and create delta
|
||||
repo.DownloadLatestFullPackageAsync(options).GetAwaiterResult();
|
||||
var id = "B2TestApp";
|
||||
TestApp.PackTestApp(id, newVer.ToFullString(), $"b2-{DateTime.UtcNow.ToLongDateString()}", releaseDir, logger, channel: channel);
|
||||
if (latest != null) {
|
||||
// check delta was created
|
||||
Assert.True(Directory.EnumerateFiles(releaseDir, "*-delta.nupkg").Any(), "No delta package was created.");
|
||||
}
|
||||
|
||||
// upload new files
|
||||
repo.UploadMissingAssetsAsync(options).GetAwaiterResult();
|
||||
|
||||
// verify that new version has been uploaded
|
||||
feed = source.GetReleaseFeed(logger, channel).GetAwaiterResult();
|
||||
latest = feed.Assets.Where(a => a.Version != null && a.Type == VelopackAssetType.Full)
|
||||
.OrderByDescending(a => a.Version)
|
||||
.FirstOrDefault();
|
||||
|
||||
Assert.True(latest != null, "No latest version found.");
|
||||
Assert.Equal(newVer, latest.Version);
|
||||
Assert.True(feed.Assets.Count(x => x.Type == VelopackAssetType.Full) <= options.KeepMaxReleases, "Too many releases were kept.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,83 +8,82 @@ using Velopack.Packaging.Unix.Commands;
|
||||
using Velopack.Packaging.Windows.Commands;
|
||||
using Velopack.Vpk.Logging;
|
||||
|
||||
namespace Velopack.Packaging.Tests
|
||||
namespace Velopack.Packaging.Tests;
|
||||
|
||||
public static class TestApp
|
||||
{
|
||||
public static class TestApp
|
||||
public static void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger,
|
||||
string releaseNotes = null, string channel = null)
|
||||
{
|
||||
public static void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger,
|
||||
string releaseNotes = null, string channel = null)
|
||||
{
|
||||
var projDir = PathHelper.GetTestRootPath("TestApp");
|
||||
var testStringFile = Path.Combine(projDir, "Const.cs");
|
||||
var oldText = File.ReadAllText(testStringFile);
|
||||
var projDir = PathHelper.GetTestRootPath("TestApp");
|
||||
var testStringFile = Path.Combine(projDir, "Const.cs");
|
||||
var oldText = File.ReadAllText(testStringFile);
|
||||
|
||||
try {
|
||||
File.WriteAllText(testStringFile, $"class Const {{ public const string TEST_STRING = \"{testString}\"; }}");
|
||||
try {
|
||||
File.WriteAllText(testStringFile, $"class Const {{ public const string TEST_STRING = \"{testString}\"; }}");
|
||||
|
||||
var args = new string[] { "publish", "--no-self-contained", "-c", "Release", "-r", VelopackRuntimeInfo.SystemRid, "-o", "publish" };
|
||||
var args = new string[] { "publish", "--no-self-contained", "-c", "Release", "-r", VelopackRuntimeInfo.SystemRid, "-o", "publish" };
|
||||
|
||||
var psi = new ProcessStartInfo("dotnet");
|
||||
psi.WorkingDirectory = projDir;
|
||||
psi.AppendArgumentListSafe(args, out var debug);
|
||||
var psi = new ProcessStartInfo("dotnet");
|
||||
psi.WorkingDirectory = projDir;
|
||||
psi.AppendArgumentListSafe(args, out var debug);
|
||||
|
||||
logger.Info($"TEST: Running {psi.FileName} {debug}");
|
||||
logger.Info($"TEST: Running {psi.FileName} {debug}");
|
||||
|
||||
using var p = Process.Start(psi);
|
||||
p.WaitForExit();
|
||||
using var p = Process.Start(psi);
|
||||
p.WaitForExit();
|
||||
|
||||
if (p.ExitCode != 0)
|
||||
throw new Exception($"dotnet publish failed with exit code {p.ExitCode}");
|
||||
if (p.ExitCode != 0)
|
||||
throw new Exception($"dotnet publish failed with exit code {p.ExitCode}");
|
||||
|
||||
var console = new BasicConsole(logger, new DefaultPromptValueFactory(false));
|
||||
var console = new BasicConsole(logger, new DefaultPromptValueFactory(false));
|
||||
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
var options = new WindowsPackOptions {
|
||||
EntryExecutableName = "TestApp.exe",
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
PackId = id,
|
||||
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
|
||||
PackVersion = version,
|
||||
PackDirectory = Path.Combine(projDir, "publish"),
|
||||
ReleaseNotes = releaseNotes,
|
||||
Channel = channel,
|
||||
};
|
||||
var runner = new WindowsPackCommandRunner(logger, console);
|
||||
runner.Run(options).GetAwaiterResult();
|
||||
} else if (VelopackRuntimeInfo.IsOSX) {
|
||||
var options = new OsxPackOptions {
|
||||
EntryExecutableName = "TestApp",
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
PackId = id,
|
||||
Icon = Path.Combine(PathHelper.GetProjectDir(), "examples", "AvaloniaCrossPlat", "Velopack.icns"),
|
||||
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
|
||||
PackVersion = version,
|
||||
PackDirectory = Path.Combine(projDir, "publish"),
|
||||
ReleaseNotes = releaseNotes,
|
||||
Channel = channel,
|
||||
};
|
||||
var runner = new OsxPackCommandRunner(logger, console);
|
||||
runner.Run(options).GetAwaiterResult();
|
||||
} else if (VelopackRuntimeInfo.IsLinux) {
|
||||
var options = new LinuxPackOptions {
|
||||
EntryExecutableName = "TestApp",
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
PackId = id,
|
||||
Icon = Path.Combine(PathHelper.GetProjectDir(), "examples", "AvaloniaCrossPlat", "Velopack.png"),
|
||||
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
|
||||
PackVersion = version,
|
||||
PackDirectory = Path.Combine(projDir, "publish"),
|
||||
ReleaseNotes = releaseNotes,
|
||||
Channel = channel,
|
||||
};
|
||||
var runner = new LinuxPackCommandRunner(logger, console);
|
||||
runner.Run(options).GetAwaiterResult();
|
||||
} else {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
} finally {
|
||||
File.WriteAllText(testStringFile, oldText);
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
var options = new WindowsPackOptions {
|
||||
EntryExecutableName = "TestApp.exe",
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
PackId = id,
|
||||
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
|
||||
PackVersion = version,
|
||||
PackDirectory = Path.Combine(projDir, "publish"),
|
||||
ReleaseNotes = releaseNotes,
|
||||
Channel = channel,
|
||||
};
|
||||
var runner = new WindowsPackCommandRunner(logger, console);
|
||||
runner.Run(options).GetAwaiterResult();
|
||||
} else if (VelopackRuntimeInfo.IsOSX) {
|
||||
var options = new OsxPackOptions {
|
||||
EntryExecutableName = "TestApp",
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
PackId = id,
|
||||
Icon = Path.Combine(PathHelper.GetProjectDir(), "examples", "AvaloniaCrossPlat", "Velopack.icns"),
|
||||
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
|
||||
PackVersion = version,
|
||||
PackDirectory = Path.Combine(projDir, "publish"),
|
||||
ReleaseNotes = releaseNotes,
|
||||
Channel = channel,
|
||||
};
|
||||
var runner = new OsxPackCommandRunner(logger, console);
|
||||
runner.Run(options).GetAwaiterResult();
|
||||
} else if (VelopackRuntimeInfo.IsLinux) {
|
||||
var options = new LinuxPackOptions {
|
||||
EntryExecutableName = "TestApp",
|
||||
ReleaseDir = new DirectoryInfo(releaseDir),
|
||||
PackId = id,
|
||||
Icon = Path.Combine(PathHelper.GetProjectDir(), "examples", "AvaloniaCrossPlat", "Velopack.png"),
|
||||
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
|
||||
PackVersion = version,
|
||||
PackDirectory = Path.Combine(projDir, "publish"),
|
||||
ReleaseNotes = releaseNotes,
|
||||
Channel = channel,
|
||||
};
|
||||
var runner = new LinuxPackCommandRunner(logger, console);
|
||||
runner.Run(options).GetAwaiterResult();
|
||||
} else {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
} finally {
|
||||
File.WriteAllText(testStringFile, oldText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,278 +2,277 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Velopack.Tests.OldSquirrel
|
||||
namespace Velopack.Tests.OldSquirrel;
|
||||
|
||||
[DataContract]
|
||||
public class ReleaseEntry
|
||||
{
|
||||
[DataContract]
|
||||
public class ReleaseEntry
|
||||
[DataMember] public string SHA1 { get; protected set; }
|
||||
[DataMember] public string BaseUrl { get; protected set; }
|
||||
[DataMember] public string Filename { get; protected set; }
|
||||
[DataMember] public string Query { get; protected set; }
|
||||
[DataMember] public long Filesize { get; protected set; }
|
||||
[DataMember] public bool IsDelta { get; protected set; }
|
||||
[DataMember] public float? StagingPercentage { get; protected set; }
|
||||
|
||||
protected ReleaseEntry(string sha1, string filename, long filesize, bool isDelta, string baseUrl = null, string query = null, float? stagingPercentage = null)
|
||||
{
|
||||
[DataMember] public string SHA1 { get; protected set; }
|
||||
[DataMember] public string BaseUrl { get; protected set; }
|
||||
[DataMember] public string Filename { get; protected set; }
|
||||
[DataMember] public string Query { get; protected set; }
|
||||
[DataMember] public long Filesize { get; protected set; }
|
||||
[DataMember] public bool IsDelta { get; protected set; }
|
||||
[DataMember] public float? StagingPercentage { get; protected set; }
|
||||
Contract.Requires(sha1 != null && sha1.Length == 40);
|
||||
Contract.Requires(filename != null);
|
||||
Contract.Requires(filename.Contains(Path.DirectorySeparatorChar) == false);
|
||||
Contract.Requires(filesize > 0);
|
||||
|
||||
protected ReleaseEntry(string sha1, string filename, long filesize, bool isDelta, string baseUrl = null, string query = null, float? stagingPercentage = null)
|
||||
{
|
||||
Contract.Requires(sha1 != null && sha1.Length == 40);
|
||||
Contract.Requires(filename != null);
|
||||
Contract.Requires(filename.Contains(Path.DirectorySeparatorChar) == false);
|
||||
Contract.Requires(filesize > 0);
|
||||
|
||||
SHA1 = sha1; BaseUrl = baseUrl; Filename = filename; Query = query; Filesize = filesize; IsDelta = isDelta; StagingPercentage = stagingPercentage;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string EntryAsString {
|
||||
get {
|
||||
if (StagingPercentage != null) {
|
||||
return String.Format("{0} {1}{2} {3} # {4}", SHA1, BaseUrl, Filename, Filesize, stagingPercentageAsString(StagingPercentage.Value));
|
||||
} else {
|
||||
return String.Format("{0} {1}{2} {3}", SHA1, BaseUrl, Filename, Filesize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public SemanticVersion Version { get { return Filename.ToSemanticVersion(); } }
|
||||
|
||||
static readonly Regex packageNameRegex = new Regex(@"^([\w-]+)-\d+\..+\.nupkg$");
|
||||
[IgnoreDataMember]
|
||||
public string PackageName {
|
||||
get {
|
||||
var match = packageNameRegex.Match(Filename);
|
||||
return match.Success ?
|
||||
match.Groups[1].Value :
|
||||
Filename.Substring(0, Filename.IndexOfAny(new[] { '-', '.' }));
|
||||
}
|
||||
}
|
||||
|
||||
//public string GetReleaseNotes(string packageDirectory)
|
||||
//{
|
||||
// var zp = new ZipPackage(Path.Combine(packageDirectory, Filename));
|
||||
// var t = zp.Id;
|
||||
|
||||
// if (String.IsNullOrWhiteSpace(zp.ReleaseNotes)) {
|
||||
// throw new Exception(String.Format("Invalid 'ReleaseNotes' value in nuspec file at '{0}'", Path.Combine(packageDirectory, Filename)));
|
||||
// }
|
||||
|
||||
// return zp.ReleaseNotes;
|
||||
//}
|
||||
|
||||
//public Uri GetIconUrl(string packageDirectory)
|
||||
//{
|
||||
// var zp = new ZipPackage(Path.Combine(packageDirectory, Filename));
|
||||
// return zp.IconUrl;
|
||||
//}
|
||||
|
||||
static readonly Regex entryRegex = new Regex(@"^([0-9a-fA-F]{40})\s+(\S+)\s+(\d+)[\r]*$");
|
||||
static readonly Regex commentRegex = new Regex(@"\s*#.*$");
|
||||
static readonly Regex stagingRegex = new Regex(@"#\s+(\d{1,3})%$");
|
||||
public static ReleaseEntry ParseReleaseEntry(string entry)
|
||||
{
|
||||
Contract.Requires(entry != null);
|
||||
|
||||
float? stagingPercentage = null;
|
||||
var m = stagingRegex.Match(entry);
|
||||
if (m != null && m.Success) {
|
||||
stagingPercentage = Single.Parse(m.Groups[1].Value) / 100.0f;
|
||||
}
|
||||
|
||||
entry = commentRegex.Replace(entry, "");
|
||||
if (String.IsNullOrWhiteSpace(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
m = entryRegex.Match(entry);
|
||||
if (!m.Success) {
|
||||
throw new Exception("Invalid release entry: " + entry);
|
||||
}
|
||||
|
||||
if (m.Groups.Count != 4) {
|
||||
throw new Exception("Invalid release entry: " + entry);
|
||||
}
|
||||
|
||||
string filename = m.Groups[2].Value;
|
||||
|
||||
// Split the base URL and the filename if an URI is provided,
|
||||
// throws if a path is provided
|
||||
string baseUrl = null;
|
||||
string query = null;
|
||||
|
||||
if (Utility.IsHttpUrl(filename)) {
|
||||
var uri = new Uri(filename);
|
||||
var path = uri.LocalPath;
|
||||
var authority = uri.GetLeftPart(UriPartial.Authority);
|
||||
|
||||
if (String.IsNullOrEmpty(path) || String.IsNullOrEmpty(authority)) {
|
||||
throw new Exception("Invalid URL");
|
||||
}
|
||||
|
||||
var indexOfLastPathSeparator = path.LastIndexOf("/") + 1;
|
||||
baseUrl = authority + path.Substring(0, indexOfLastPathSeparator);
|
||||
filename = path.Substring(indexOfLastPathSeparator);
|
||||
|
||||
if (!String.IsNullOrEmpty(uri.Query)) {
|
||||
query = uri.Query;
|
||||
}
|
||||
}
|
||||
|
||||
if (filename.IndexOfAny(Path.GetInvalidFileNameChars()) > -1) {
|
||||
throw new Exception("Filename can either be an absolute HTTP[s] URL, *or* a file name");
|
||||
}
|
||||
|
||||
long size = Int64.Parse(m.Groups[3].Value);
|
||||
bool isDelta = filenameIsDeltaFile(filename);
|
||||
|
||||
return new ReleaseEntry(m.Groups[1].Value, filename, size, isDelta, baseUrl, query, stagingPercentage);
|
||||
}
|
||||
|
||||
public bool IsStagingMatch(Guid? userId)
|
||||
{
|
||||
// A "Staging match" is when a user falls into the affirmative
|
||||
// bucket - i.e. if the staging is at 10%, this user is the one out
|
||||
// of ten case.
|
||||
if (!StagingPercentage.HasValue) return true;
|
||||
if (!userId.HasValue) return false;
|
||||
|
||||
uint val = BitConverter.ToUInt32(userId.Value.ToByteArray(), 12);
|
||||
|
||||
double percentage = ((double) val / (double) UInt32.MaxValue);
|
||||
return percentage < StagingPercentage.Value;
|
||||
}
|
||||
|
||||
public static IEnumerable<ReleaseEntry> ParseReleaseFile(string fileContents)
|
||||
{
|
||||
if (String.IsNullOrEmpty(fileContents)) {
|
||||
return new ReleaseEntry[0];
|
||||
}
|
||||
|
||||
//fileContents = Utility.RemoveByteOrderMarkerIfPresent(fileContents);
|
||||
|
||||
var ret = fileContents.Split('\n')
|
||||
.Where(x => !String.IsNullOrWhiteSpace(x))
|
||||
.Select(ParseReleaseEntry)
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
|
||||
return ret.Any(x => x == null) ? null : ret;
|
||||
}
|
||||
|
||||
public static IEnumerable<ReleaseEntry> ParseReleaseFileAndApplyStaging(string fileContents, Guid? userToken)
|
||||
{
|
||||
if (String.IsNullOrEmpty(fileContents)) {
|
||||
return new ReleaseEntry[0];
|
||||
}
|
||||
|
||||
//fileContents = Utility.RemoveByteOrderMarkerIfPresent(fileContents);
|
||||
|
||||
var ret = fileContents.Split('\n')
|
||||
.Where(x => !String.IsNullOrWhiteSpace(x))
|
||||
.Select(ParseReleaseEntry)
|
||||
.Where(x => x != null && x.IsStagingMatch(userToken))
|
||||
.ToArray();
|
||||
|
||||
return ret.Any(x => x == null) ? null : ret;
|
||||
}
|
||||
|
||||
|
||||
//public static void WriteReleaseFile(IEnumerable<ReleaseEntry> releaseEntries, Stream stream)
|
||||
//{
|
||||
// Contract.Requires(releaseEntries != null && releaseEntries.Any());
|
||||
// Contract.Requires(stream != null);
|
||||
|
||||
// using (var sw = new StreamWriter(stream, Encoding.UTF8)) {
|
||||
// sw.Write(String.Join("\n", releaseEntries
|
||||
// .OrderBy(x => x.Version)
|
||||
// .ThenByDescending(x => x.IsDelta)
|
||||
// .Select(x => x.EntryAsString)));
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static void WriteReleaseFile(IEnumerable<ReleaseEntry> releaseEntries, string path)
|
||||
//{
|
||||
// Contract.Requires(releaseEntries != null && releaseEntries.Any());
|
||||
// Contract.Requires(!String.IsNullOrEmpty(path));
|
||||
|
||||
// using (var f = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) {
|
||||
// WriteReleaseFile(releaseEntries, f);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static ReleaseEntry GenerateFromFile(Stream file, string filename, string baseUrl = null)
|
||||
//{
|
||||
// Contract.Requires(file != null && file.CanRead);
|
||||
// Contract.Requires(!String.IsNullOrEmpty(filename));
|
||||
|
||||
// var hash = Utility.CalculateStreamSHA1(file);
|
||||
// return new ReleaseEntry(hash, filename, file.Length, filenameIsDeltaFile(filename), baseUrl);
|
||||
//}
|
||||
|
||||
//public static ReleaseEntry GenerateFromFile(string path, string baseUrl = null)
|
||||
//{
|
||||
// using (var inf = File.OpenRead(path)) {
|
||||
// return GenerateFromFile(inf, Path.GetFileName(path), baseUrl);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static List<ReleaseEntry> BuildReleasesFile(string releasePackagesDir)
|
||||
//{
|
||||
// var packagesDir = new DirectoryInfo(releasePackagesDir);
|
||||
|
||||
// // Generate release entries for all of the local packages
|
||||
// var entriesQueue = new ConcurrentQueue<ReleaseEntry>();
|
||||
// Parallel.ForEach(packagesDir.GetFiles("*.nupkg"), x => {
|
||||
// using (var file = x.OpenRead()) {
|
||||
// entriesQueue.Enqueue(GenerateFromFile(file, x.Name));
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Write the new RELEASES file to a temp file then move it into
|
||||
// // place
|
||||
// var entries = entriesQueue.ToList();
|
||||
// var tempFile = default(string);
|
||||
// Utility.WithTempFile(out tempFile, releasePackagesDir);
|
||||
|
||||
// try {
|
||||
// using (var of = File.OpenWrite(tempFile)) {
|
||||
// if (entries.Count > 0) WriteReleaseFile(entries, of);
|
||||
// }
|
||||
|
||||
// var target = Path.Combine(packagesDir.FullName, "RELEASES");
|
||||
// if (File.Exists(target)) {
|
||||
// File.Delete(target);
|
||||
// }
|
||||
|
||||
// File.Move(tempFile, target);
|
||||
// } finally {
|
||||
// if (File.Exists(tempFile)) Utility.DeleteFileHarder(tempFile, true);
|
||||
// }
|
||||
|
||||
// return entries;
|
||||
//}
|
||||
|
||||
static string stagingPercentageAsString(float percentage)
|
||||
{
|
||||
return String.Format("{0:F0}%", percentage * 100.0);
|
||||
}
|
||||
|
||||
static bool filenameIsDeltaFile(string filename)
|
||||
{
|
||||
return filename.EndsWith("-delta.nupkg", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
//public static ReleasePackage GetPreviousRelease(IEnumerable<ReleaseEntry> releaseEntries, IReleasePackage package, string targetDir)
|
||||
//{
|
||||
// if (releaseEntries == null || !releaseEntries.Any()) return null;
|
||||
|
||||
// return releaseEntries
|
||||
// .Where(x => x.IsDelta == false)
|
||||
// .Where(x => x.Version < package.ToSemanticVersion())
|
||||
// .OrderByDescending(x => x.Version)
|
||||
// .Select(x => new ReleasePackage(Path.Combine(targetDir, x.Filename), true))
|
||||
// .FirstOrDefault();
|
||||
//}
|
||||
SHA1 = sha1; BaseUrl = baseUrl; Filename = filename; Query = query; Filesize = filesize; IsDelta = isDelta; StagingPercentage = stagingPercentage;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string EntryAsString {
|
||||
get {
|
||||
if (StagingPercentage != null) {
|
||||
return String.Format("{0} {1}{2} {3} # {4}", SHA1, BaseUrl, Filename, Filesize, stagingPercentageAsString(StagingPercentage.Value));
|
||||
} else {
|
||||
return String.Format("{0} {1}{2} {3}", SHA1, BaseUrl, Filename, Filesize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public SemanticVersion Version { get { return Filename.ToSemanticVersion(); } }
|
||||
|
||||
static readonly Regex packageNameRegex = new Regex(@"^([\w-]+)-\d+\..+\.nupkg$");
|
||||
[IgnoreDataMember]
|
||||
public string PackageName {
|
||||
get {
|
||||
var match = packageNameRegex.Match(Filename);
|
||||
return match.Success ?
|
||||
match.Groups[1].Value :
|
||||
Filename.Substring(0, Filename.IndexOfAny(new[] { '-', '.' }));
|
||||
}
|
||||
}
|
||||
|
||||
//public string GetReleaseNotes(string packageDirectory)
|
||||
//{
|
||||
// var zp = new ZipPackage(Path.Combine(packageDirectory, Filename));
|
||||
// var t = zp.Id;
|
||||
|
||||
// if (String.IsNullOrWhiteSpace(zp.ReleaseNotes)) {
|
||||
// throw new Exception(String.Format("Invalid 'ReleaseNotes' value in nuspec file at '{0}'", Path.Combine(packageDirectory, Filename)));
|
||||
// }
|
||||
|
||||
// return zp.ReleaseNotes;
|
||||
//}
|
||||
|
||||
//public Uri GetIconUrl(string packageDirectory)
|
||||
//{
|
||||
// var zp = new ZipPackage(Path.Combine(packageDirectory, Filename));
|
||||
// return zp.IconUrl;
|
||||
//}
|
||||
|
||||
static readonly Regex entryRegex = new Regex(@"^([0-9a-fA-F]{40})\s+(\S+)\s+(\d+)[\r]*$");
|
||||
static readonly Regex commentRegex = new Regex(@"\s*#.*$");
|
||||
static readonly Regex stagingRegex = new Regex(@"#\s+(\d{1,3})%$");
|
||||
public static ReleaseEntry ParseReleaseEntry(string entry)
|
||||
{
|
||||
Contract.Requires(entry != null);
|
||||
|
||||
float? stagingPercentage = null;
|
||||
var m = stagingRegex.Match(entry);
|
||||
if (m != null && m.Success) {
|
||||
stagingPercentage = Single.Parse(m.Groups[1].Value) / 100.0f;
|
||||
}
|
||||
|
||||
entry = commentRegex.Replace(entry, "");
|
||||
if (String.IsNullOrWhiteSpace(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
m = entryRegex.Match(entry);
|
||||
if (!m.Success) {
|
||||
throw new Exception("Invalid release entry: " + entry);
|
||||
}
|
||||
|
||||
if (m.Groups.Count != 4) {
|
||||
throw new Exception("Invalid release entry: " + entry);
|
||||
}
|
||||
|
||||
string filename = m.Groups[2].Value;
|
||||
|
||||
// Split the base URL and the filename if an URI is provided,
|
||||
// throws if a path is provided
|
||||
string baseUrl = null;
|
||||
string query = null;
|
||||
|
||||
if (Utility.IsHttpUrl(filename)) {
|
||||
var uri = new Uri(filename);
|
||||
var path = uri.LocalPath;
|
||||
var authority = uri.GetLeftPart(UriPartial.Authority);
|
||||
|
||||
if (String.IsNullOrEmpty(path) || String.IsNullOrEmpty(authority)) {
|
||||
throw new Exception("Invalid URL");
|
||||
}
|
||||
|
||||
var indexOfLastPathSeparator = path.LastIndexOf("/") + 1;
|
||||
baseUrl = authority + path.Substring(0, indexOfLastPathSeparator);
|
||||
filename = path.Substring(indexOfLastPathSeparator);
|
||||
|
||||
if (!String.IsNullOrEmpty(uri.Query)) {
|
||||
query = uri.Query;
|
||||
}
|
||||
}
|
||||
|
||||
if (filename.IndexOfAny(Path.GetInvalidFileNameChars()) > -1) {
|
||||
throw new Exception("Filename can either be an absolute HTTP[s] URL, *or* a file name");
|
||||
}
|
||||
|
||||
long size = Int64.Parse(m.Groups[3].Value);
|
||||
bool isDelta = filenameIsDeltaFile(filename);
|
||||
|
||||
return new ReleaseEntry(m.Groups[1].Value, filename, size, isDelta, baseUrl, query, stagingPercentage);
|
||||
}
|
||||
|
||||
public bool IsStagingMatch(Guid? userId)
|
||||
{
|
||||
// A "Staging match" is when a user falls into the affirmative
|
||||
// bucket - i.e. if the staging is at 10%, this user is the one out
|
||||
// of ten case.
|
||||
if (!StagingPercentage.HasValue) return true;
|
||||
if (!userId.HasValue) return false;
|
||||
|
||||
uint val = BitConverter.ToUInt32(userId.Value.ToByteArray(), 12);
|
||||
|
||||
double percentage = ((double) val / (double) UInt32.MaxValue);
|
||||
return percentage < StagingPercentage.Value;
|
||||
}
|
||||
|
||||
public static IEnumerable<ReleaseEntry> ParseReleaseFile(string fileContents)
|
||||
{
|
||||
if (String.IsNullOrEmpty(fileContents)) {
|
||||
return new ReleaseEntry[0];
|
||||
}
|
||||
|
||||
//fileContents = Utility.RemoveByteOrderMarkerIfPresent(fileContents);
|
||||
|
||||
var ret = fileContents.Split('\n')
|
||||
.Where(x => !String.IsNullOrWhiteSpace(x))
|
||||
.Select(ParseReleaseEntry)
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
|
||||
return ret.Any(x => x == null) ? null : ret;
|
||||
}
|
||||
|
||||
public static IEnumerable<ReleaseEntry> ParseReleaseFileAndApplyStaging(string fileContents, Guid? userToken)
|
||||
{
|
||||
if (String.IsNullOrEmpty(fileContents)) {
|
||||
return new ReleaseEntry[0];
|
||||
}
|
||||
|
||||
//fileContents = Utility.RemoveByteOrderMarkerIfPresent(fileContents);
|
||||
|
||||
var ret = fileContents.Split('\n')
|
||||
.Where(x => !String.IsNullOrWhiteSpace(x))
|
||||
.Select(ParseReleaseEntry)
|
||||
.Where(x => x != null && x.IsStagingMatch(userToken))
|
||||
.ToArray();
|
||||
|
||||
return ret.Any(x => x == null) ? null : ret;
|
||||
}
|
||||
|
||||
|
||||
//public static void WriteReleaseFile(IEnumerable<ReleaseEntry> releaseEntries, Stream stream)
|
||||
//{
|
||||
// Contract.Requires(releaseEntries != null && releaseEntries.Any());
|
||||
// Contract.Requires(stream != null);
|
||||
|
||||
// using (var sw = new StreamWriter(stream, Encoding.UTF8)) {
|
||||
// sw.Write(String.Join("\n", releaseEntries
|
||||
// .OrderBy(x => x.Version)
|
||||
// .ThenByDescending(x => x.IsDelta)
|
||||
// .Select(x => x.EntryAsString)));
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static void WriteReleaseFile(IEnumerable<ReleaseEntry> releaseEntries, string path)
|
||||
//{
|
||||
// Contract.Requires(releaseEntries != null && releaseEntries.Any());
|
||||
// Contract.Requires(!String.IsNullOrEmpty(path));
|
||||
|
||||
// using (var f = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) {
|
||||
// WriteReleaseFile(releaseEntries, f);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static ReleaseEntry GenerateFromFile(Stream file, string filename, string baseUrl = null)
|
||||
//{
|
||||
// Contract.Requires(file != null && file.CanRead);
|
||||
// Contract.Requires(!String.IsNullOrEmpty(filename));
|
||||
|
||||
// var hash = Utility.CalculateStreamSHA1(file);
|
||||
// return new ReleaseEntry(hash, filename, file.Length, filenameIsDeltaFile(filename), baseUrl);
|
||||
//}
|
||||
|
||||
//public static ReleaseEntry GenerateFromFile(string path, string baseUrl = null)
|
||||
//{
|
||||
// using (var inf = File.OpenRead(path)) {
|
||||
// return GenerateFromFile(inf, Path.GetFileName(path), baseUrl);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static List<ReleaseEntry> BuildReleasesFile(string releasePackagesDir)
|
||||
//{
|
||||
// var packagesDir = new DirectoryInfo(releasePackagesDir);
|
||||
|
||||
// // Generate release entries for all of the local packages
|
||||
// var entriesQueue = new ConcurrentQueue<ReleaseEntry>();
|
||||
// Parallel.ForEach(packagesDir.GetFiles("*.nupkg"), x => {
|
||||
// using (var file = x.OpenRead()) {
|
||||
// entriesQueue.Enqueue(GenerateFromFile(file, x.Name));
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Write the new RELEASES file to a temp file then move it into
|
||||
// // place
|
||||
// var entries = entriesQueue.ToList();
|
||||
// var tempFile = default(string);
|
||||
// Utility.WithTempFile(out tempFile, releasePackagesDir);
|
||||
|
||||
// try {
|
||||
// using (var of = File.OpenWrite(tempFile)) {
|
||||
// if (entries.Count > 0) WriteReleaseFile(entries, of);
|
||||
// }
|
||||
|
||||
// var target = Path.Combine(packagesDir.FullName, "RELEASES");
|
||||
// if (File.Exists(target)) {
|
||||
// File.Delete(target);
|
||||
// }
|
||||
|
||||
// File.Move(tempFile, target);
|
||||
// } finally {
|
||||
// if (File.Exists(tempFile)) Utility.DeleteFileHarder(tempFile, true);
|
||||
// }
|
||||
|
||||
// return entries;
|
||||
//}
|
||||
|
||||
static string stagingPercentageAsString(float percentage)
|
||||
{
|
||||
return String.Format("{0:F0}%", percentage * 100.0);
|
||||
}
|
||||
|
||||
static bool filenameIsDeltaFile(string filename)
|
||||
{
|
||||
return filename.EndsWith("-delta.nupkg", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
//public static ReleasePackage GetPreviousRelease(IEnumerable<ReleaseEntry> releaseEntries, IReleasePackage package, string targetDir)
|
||||
//{
|
||||
// if (releaseEntries == null || !releaseEntries.Any()) return null;
|
||||
|
||||
// return releaseEntries
|
||||
// .Where(x => x.IsDelta == false)
|
||||
// .Where(x => x.Version < package.ToSemanticVersion())
|
||||
// .OrderByDescending(x => x.Version)
|
||||
// .Select(x => new ReleasePackage(Path.Combine(targetDir, x.Filename), true))
|
||||
// .FirstOrDefault();
|
||||
//}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Velopack.Tests.OldSquirrel
|
||||
namespace Velopack.Tests.OldSquirrel;
|
||||
|
||||
public static class VersionExtensions
|
||||
{
|
||||
public static class VersionExtensions
|
||||
static readonly Regex _suffixRegex = new Regex(@"(-full|-delta)?\.nupkg$", RegexOptions.Compiled);
|
||||
static readonly Regex _versionRegex = new Regex(@"\d+(\.\d+){0,3}(-[A-Za-z][0-9A-Za-z-]*)?$", RegexOptions.Compiled);
|
||||
|
||||
//public static SemanticVersion ToSemanticVersion(this IReleasePackage package)
|
||||
//{
|
||||
// return package.InputPackageFile.ToSemanticVersion();
|
||||
//}
|
||||
|
||||
public static SemanticVersion ToSemanticVersion(this string fileName)
|
||||
{
|
||||
static readonly Regex _suffixRegex = new Regex(@"(-full|-delta)?\.nupkg$", RegexOptions.Compiled);
|
||||
static readonly Regex _versionRegex = new Regex(@"\d+(\.\d+){0,3}(-[A-Za-z][0-9A-Za-z-]*)?$", RegexOptions.Compiled);
|
||||
|
||||
//public static SemanticVersion ToSemanticVersion(this IReleasePackage package)
|
||||
//{
|
||||
// return package.InputPackageFile.ToSemanticVersion();
|
||||
//}
|
||||
|
||||
public static SemanticVersion ToSemanticVersion(this string fileName)
|
||||
{
|
||||
var name = _suffixRegex.Replace(fileName, "");
|
||||
var version = _versionRegex.Match(name).Value;
|
||||
return new SemanticVersion(version);
|
||||
}
|
||||
var name = _suffixRegex.Replace(fileName, "");
|
||||
var version = _versionRegex.Match(name).Value;
|
||||
return new SemanticVersion(version);
|
||||
}
|
||||
}
|
||||
@@ -1,302 +1,301 @@
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Velopack.Tests.OldSquirrel
|
||||
namespace Velopack.Tests.OldSquirrel;
|
||||
|
||||
/// <summary>
|
||||
/// A hybrid implementation of SemVer that supports semantic versioning as described at http://semver.org while not strictly enforcing it to
|
||||
/// allow older 4-digit versioning schemes to continue working.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class SemanticVersion : IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>
|
||||
{
|
||||
/// <summary>
|
||||
/// A hybrid implementation of SemVer that supports semantic versioning as described at http://semver.org while not strictly enforcing it to
|
||||
/// allow older 4-digit versioning schemes to continue working.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class SemanticVersion : IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>
|
||||
private const RegexOptions _flags = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
|
||||
private static readonly Regex _semanticVersionRegex = new Regex(@"^(?<Version>\d+(\s*\.\s*\d+){0,3})(?<Release>-[a-z][0-9a-z-]*)?$", _flags);
|
||||
private static readonly Regex _strictSemanticVersionRegex = new Regex(@"^(?<Version>\d+(\.\d+){2})(?<Release>-[a-z][0-9a-z-]*)?$", _flags);
|
||||
private static readonly Regex _preReleaseVersionRegex = new Regex(@"(?<PreReleaseString>[a-z]+)(?<PreReleaseNumber>[0-9]+)$", _flags);
|
||||
private readonly string _originalString;
|
||||
|
||||
public SemanticVersion(string version)
|
||||
: this(Parse(version))
|
||||
{
|
||||
private const RegexOptions _flags = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
|
||||
private static readonly Regex _semanticVersionRegex = new Regex(@"^(?<Version>\d+(\s*\.\s*\d+){0,3})(?<Release>-[a-z][0-9a-z-]*)?$", _flags);
|
||||
private static readonly Regex _strictSemanticVersionRegex = new Regex(@"^(?<Version>\d+(\.\d+){2})(?<Release>-[a-z][0-9a-z-]*)?$", _flags);
|
||||
private static readonly Regex _preReleaseVersionRegex = new Regex(@"(?<PreReleaseString>[a-z]+)(?<PreReleaseNumber>[0-9]+)$", _flags);
|
||||
private readonly string _originalString;
|
||||
// The constructor normalizes the version string so that it we do not need to normalize it every time we need to operate on it.
|
||||
// The original string represents the original form in which the version is represented to be used when printing.
|
||||
_originalString = version;
|
||||
}
|
||||
|
||||
public SemanticVersion(string version)
|
||||
: this(Parse(version))
|
||||
{
|
||||
// The constructor normalizes the version string so that it we do not need to normalize it every time we need to operate on it.
|
||||
// The original string represents the original form in which the version is represented to be used when printing.
|
||||
_originalString = version;
|
||||
public SemanticVersion(int major, int minor, int build, int revision)
|
||||
: this(new Version(major, minor, build, revision))
|
||||
{
|
||||
}
|
||||
|
||||
public SemanticVersion(int major, int minor, int build, string specialVersion)
|
||||
: this(new Version(major, minor, build), specialVersion)
|
||||
{
|
||||
}
|
||||
|
||||
public SemanticVersion(Version version)
|
||||
: this(version, String.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public SemanticVersion(Version version, string specialVersion)
|
||||
: this(version, specialVersion, null)
|
||||
{
|
||||
}
|
||||
|
||||
private SemanticVersion(Version version, string specialVersion, string originalString)
|
||||
{
|
||||
if (version == null) {
|
||||
throw new ArgumentNullException("version");
|
||||
}
|
||||
Version = NormalizeVersionValue(version);
|
||||
SpecialVersion = specialVersion ?? String.Empty;
|
||||
_originalString = String.IsNullOrEmpty(originalString) ? version.ToString() + (!String.IsNullOrEmpty(specialVersion) ? '-' + specialVersion : null) : originalString;
|
||||
}
|
||||
|
||||
public SemanticVersion(int major, int minor, int build, int revision)
|
||||
: this(new Version(major, minor, build, revision))
|
||||
{
|
||||
}
|
||||
internal SemanticVersion(SemanticVersion semVer)
|
||||
{
|
||||
_originalString = semVer.ToString();
|
||||
Version = semVer.Version;
|
||||
SpecialVersion = semVer.SpecialVersion;
|
||||
}
|
||||
|
||||
public SemanticVersion(int major, int minor, int build, string specialVersion)
|
||||
: this(new Version(major, minor, build), specialVersion)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the normalized version portion.
|
||||
/// </summary>
|
||||
public Version Version {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public SemanticVersion(Version version)
|
||||
: this(version, String.Empty)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the optional special version.
|
||||
/// </summary>
|
||||
public string SpecialVersion {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public SemanticVersion(Version version, string specialVersion)
|
||||
: this(version, specialVersion, null)
|
||||
{
|
||||
}
|
||||
public string[] GetOriginalVersionComponents()
|
||||
{
|
||||
if (!String.IsNullOrEmpty(_originalString)) {
|
||||
string original;
|
||||
|
||||
private SemanticVersion(Version version, string specialVersion, string originalString)
|
||||
{
|
||||
if (version == null) {
|
||||
throw new ArgumentNullException("version");
|
||||
}
|
||||
Version = NormalizeVersionValue(version);
|
||||
SpecialVersion = specialVersion ?? String.Empty;
|
||||
_originalString = String.IsNullOrEmpty(originalString) ? version.ToString() + (!String.IsNullOrEmpty(specialVersion) ? '-' + specialVersion : null) : originalString;
|
||||
}
|
||||
|
||||
internal SemanticVersion(SemanticVersion semVer)
|
||||
{
|
||||
_originalString = semVer.ToString();
|
||||
Version = semVer.Version;
|
||||
SpecialVersion = semVer.SpecialVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the normalized version portion.
|
||||
/// </summary>
|
||||
public Version Version {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the optional special version.
|
||||
/// </summary>
|
||||
public string SpecialVersion {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string[] GetOriginalVersionComponents()
|
||||
{
|
||||
if (!String.IsNullOrEmpty(_originalString)) {
|
||||
string original;
|
||||
|
||||
// search the start of the SpecialVersion part, if any
|
||||
int dashIndex = _originalString.IndexOf('-');
|
||||
if (dashIndex != -1) {
|
||||
// remove the SpecialVersion part
|
||||
original = _originalString.Substring(0, dashIndex);
|
||||
} else {
|
||||
original = _originalString;
|
||||
}
|
||||
|
||||
return SplitAndPadVersionString(original);
|
||||
// search the start of the SpecialVersion part, if any
|
||||
int dashIndex = _originalString.IndexOf('-');
|
||||
if (dashIndex != -1) {
|
||||
// remove the SpecialVersion part
|
||||
original = _originalString.Substring(0, dashIndex);
|
||||
} else {
|
||||
return SplitAndPadVersionString(Version.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static string[] SplitAndPadVersionString(string version)
|
||||
{
|
||||
string[] a = version.Split('.');
|
||||
if (a.Length == 4) {
|
||||
return a;
|
||||
} else {
|
||||
// if 'a' has less than 4 elements, we pad the '0' at the end
|
||||
// to make it 4.
|
||||
var b = new string[4] { "0", "0", "0", "0" };
|
||||
Array.Copy(a, 0, b, 0, a.Length);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an optional special version.
|
||||
/// </summary>
|
||||
public static SemanticVersion Parse(string version)
|
||||
{
|
||||
if (String.IsNullOrEmpty(version)) {
|
||||
throw new ArgumentException("Argument_Cannot_Be_Null_Or_Empty", "version");
|
||||
original = _originalString;
|
||||
}
|
||||
|
||||
SemanticVersion semVer;
|
||||
if (!TryParse(version, out semVer)) {
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "InvalidVersionString", version), "version");
|
||||
}
|
||||
return semVer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an optional special version.
|
||||
/// </summary>
|
||||
public static bool TryParse(string version, out SemanticVersion value)
|
||||
{
|
||||
return TryParseInternal(version, _semanticVersionRegex, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a version string using strict semantic versioning rules that allows exactly 3 components and an optional special version.
|
||||
/// </summary>
|
||||
public static bool TryParseStrict(string version, out SemanticVersion value)
|
||||
{
|
||||
return TryParseInternal(version, _strictSemanticVersionRegex, out value);
|
||||
}
|
||||
|
||||
private static bool TryParseInternal(string version, Regex regex, out SemanticVersion semVer)
|
||||
{
|
||||
semVer = null;
|
||||
if (String.IsNullOrEmpty(version)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var match = regex.Match(version.Trim());
|
||||
Version versionValue;
|
||||
if (!match.Success || !Version.TryParse(match.Groups["Version"].Value, out versionValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
semVer = new SemanticVersion(NormalizeVersionValue(versionValue), match.Groups["Release"].Value.TrimStart('-'), version.Replace(" ", ""));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse the version token as a SemanticVersion.
|
||||
/// </summary>
|
||||
/// <returns>An instance of SemanticVersion if it parses correctly, null otherwise.</returns>
|
||||
public static SemanticVersion ParseOptionalVersion(string version)
|
||||
{
|
||||
SemanticVersion semVer;
|
||||
TryParse(version, out semVer);
|
||||
return semVer;
|
||||
}
|
||||
|
||||
private static Version NormalizeVersionValue(Version version)
|
||||
{
|
||||
return new Version(version.Major,
|
||||
version.Minor,
|
||||
Math.Max(version.Build, 0),
|
||||
Math.Max(version.Revision, 0));
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (Object.ReferenceEquals(obj, null)) {
|
||||
return 1;
|
||||
}
|
||||
SemanticVersion other = obj as SemanticVersion;
|
||||
if (other == null) {
|
||||
throw new ArgumentException("TypeMustBeASemanticVersion", "obj");
|
||||
}
|
||||
return CompareTo(other);
|
||||
}
|
||||
|
||||
public int CompareTo(SemanticVersion other)
|
||||
{
|
||||
if (Object.ReferenceEquals(other, null)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = Version.CompareTo(other.Version);
|
||||
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool empty = String.IsNullOrEmpty(SpecialVersion);
|
||||
bool otherEmpty = String.IsNullOrEmpty(other.SpecialVersion);
|
||||
if (empty && otherEmpty) {
|
||||
return 0;
|
||||
} else if (empty) {
|
||||
return 1;
|
||||
} else if (otherEmpty) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If both versions have a prerelease section with the same prefix
|
||||
// and end with digits, compare based on the digits' numeric order
|
||||
var match = _preReleaseVersionRegex.Match(SpecialVersion.Trim());
|
||||
var otherMatch = _preReleaseVersionRegex.Match(other.SpecialVersion.Trim());
|
||||
if (match.Success && otherMatch.Success &&
|
||||
string.Equals(
|
||||
match.Groups["PreReleaseString"].Value,
|
||||
otherMatch.Groups["PreReleaseString"].Value,
|
||||
StringComparison.OrdinalIgnoreCase)) {
|
||||
int delta =
|
||||
int.Parse(match.Groups["PreReleaseNumber"].Value) -
|
||||
int.Parse(otherMatch.Groups["PreReleaseNumber"].Value);
|
||||
|
||||
return delta != 0 ? delta / Math.Abs(delta) : 0;
|
||||
}
|
||||
|
||||
return StringComparer.OrdinalIgnoreCase.Compare(SpecialVersion, other.SpecialVersion);
|
||||
}
|
||||
|
||||
public static bool operator ==(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
if (Object.ReferenceEquals(version1, null)) {
|
||||
return Object.ReferenceEquals(version2, null);
|
||||
}
|
||||
return version1.Equals(version2);
|
||||
}
|
||||
|
||||
public static bool operator !=(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
return !(version1 == version2);
|
||||
}
|
||||
|
||||
public static bool operator <(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
if (version1 == null) {
|
||||
throw new ArgumentNullException("version1");
|
||||
}
|
||||
return version1.CompareTo(version2) < 0;
|
||||
}
|
||||
|
||||
public static bool operator <=(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
return (version1 == version2) || (version1 < version2);
|
||||
}
|
||||
|
||||
public static bool operator >(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
if (version1 == null) {
|
||||
throw new ArgumentNullException("version1");
|
||||
}
|
||||
return version2 < version1;
|
||||
}
|
||||
|
||||
public static bool operator >=(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
return (version1 == version2) || (version1 > version2);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _originalString;
|
||||
}
|
||||
|
||||
public bool Equals(SemanticVersion other)
|
||||
{
|
||||
return !Object.ReferenceEquals(null, other) &&
|
||||
Version.Equals(other.Version) &&
|
||||
SpecialVersion.Equals(other.SpecialVersion, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
SemanticVersion semVer = obj as SemanticVersion;
|
||||
return !Object.ReferenceEquals(null, semVer) && Equals(semVer);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hashCode = Version.GetHashCode();
|
||||
if (SpecialVersion != null) {
|
||||
hashCode = hashCode * 4567 + SpecialVersion.GetHashCode();
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
return SplitAndPadVersionString(original);
|
||||
} else {
|
||||
return SplitAndPadVersionString(Version.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static string[] SplitAndPadVersionString(string version)
|
||||
{
|
||||
string[] a = version.Split('.');
|
||||
if (a.Length == 4) {
|
||||
return a;
|
||||
} else {
|
||||
// if 'a' has less than 4 elements, we pad the '0' at the end
|
||||
// to make it 4.
|
||||
var b = new string[4] { "0", "0", "0", "0" };
|
||||
Array.Copy(a, 0, b, 0, a.Length);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an optional special version.
|
||||
/// </summary>
|
||||
public static SemanticVersion Parse(string version)
|
||||
{
|
||||
if (String.IsNullOrEmpty(version)) {
|
||||
throw new ArgumentException("Argument_Cannot_Be_Null_Or_Empty", "version");
|
||||
}
|
||||
|
||||
SemanticVersion semVer;
|
||||
if (!TryParse(version, out semVer)) {
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "InvalidVersionString", version), "version");
|
||||
}
|
||||
return semVer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an optional special version.
|
||||
/// </summary>
|
||||
public static bool TryParse(string version, out SemanticVersion value)
|
||||
{
|
||||
return TryParseInternal(version, _semanticVersionRegex, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a version string using strict semantic versioning rules that allows exactly 3 components and an optional special version.
|
||||
/// </summary>
|
||||
public static bool TryParseStrict(string version, out SemanticVersion value)
|
||||
{
|
||||
return TryParseInternal(version, _strictSemanticVersionRegex, out value);
|
||||
}
|
||||
|
||||
private static bool TryParseInternal(string version, Regex regex, out SemanticVersion semVer)
|
||||
{
|
||||
semVer = null;
|
||||
if (String.IsNullOrEmpty(version)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var match = regex.Match(version.Trim());
|
||||
Version versionValue;
|
||||
if (!match.Success || !Version.TryParse(match.Groups["Version"].Value, out versionValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
semVer = new SemanticVersion(NormalizeVersionValue(versionValue), match.Groups["Release"].Value.TrimStart('-'), version.Replace(" ", ""));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse the version token as a SemanticVersion.
|
||||
/// </summary>
|
||||
/// <returns>An instance of SemanticVersion if it parses correctly, null otherwise.</returns>
|
||||
public static SemanticVersion ParseOptionalVersion(string version)
|
||||
{
|
||||
SemanticVersion semVer;
|
||||
TryParse(version, out semVer);
|
||||
return semVer;
|
||||
}
|
||||
|
||||
private static Version NormalizeVersionValue(Version version)
|
||||
{
|
||||
return new Version(version.Major,
|
||||
version.Minor,
|
||||
Math.Max(version.Build, 0),
|
||||
Math.Max(version.Revision, 0));
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (Object.ReferenceEquals(obj, null)) {
|
||||
return 1;
|
||||
}
|
||||
SemanticVersion other = obj as SemanticVersion;
|
||||
if (other == null) {
|
||||
throw new ArgumentException("TypeMustBeASemanticVersion", "obj");
|
||||
}
|
||||
return CompareTo(other);
|
||||
}
|
||||
|
||||
public int CompareTo(SemanticVersion other)
|
||||
{
|
||||
if (Object.ReferenceEquals(other, null)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = Version.CompareTo(other.Version);
|
||||
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool empty = String.IsNullOrEmpty(SpecialVersion);
|
||||
bool otherEmpty = String.IsNullOrEmpty(other.SpecialVersion);
|
||||
if (empty && otherEmpty) {
|
||||
return 0;
|
||||
} else if (empty) {
|
||||
return 1;
|
||||
} else if (otherEmpty) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If both versions have a prerelease section with the same prefix
|
||||
// and end with digits, compare based on the digits' numeric order
|
||||
var match = _preReleaseVersionRegex.Match(SpecialVersion.Trim());
|
||||
var otherMatch = _preReleaseVersionRegex.Match(other.SpecialVersion.Trim());
|
||||
if (match.Success && otherMatch.Success &&
|
||||
string.Equals(
|
||||
match.Groups["PreReleaseString"].Value,
|
||||
otherMatch.Groups["PreReleaseString"].Value,
|
||||
StringComparison.OrdinalIgnoreCase)) {
|
||||
int delta =
|
||||
int.Parse(match.Groups["PreReleaseNumber"].Value) -
|
||||
int.Parse(otherMatch.Groups["PreReleaseNumber"].Value);
|
||||
|
||||
return delta != 0 ? delta / Math.Abs(delta) : 0;
|
||||
}
|
||||
|
||||
return StringComparer.OrdinalIgnoreCase.Compare(SpecialVersion, other.SpecialVersion);
|
||||
}
|
||||
|
||||
public static bool operator ==(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
if (Object.ReferenceEquals(version1, null)) {
|
||||
return Object.ReferenceEquals(version2, null);
|
||||
}
|
||||
return version1.Equals(version2);
|
||||
}
|
||||
|
||||
public static bool operator !=(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
return !(version1 == version2);
|
||||
}
|
||||
|
||||
public static bool operator <(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
if (version1 == null) {
|
||||
throw new ArgumentNullException("version1");
|
||||
}
|
||||
return version1.CompareTo(version2) < 0;
|
||||
}
|
||||
|
||||
public static bool operator <=(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
return (version1 == version2) || (version1 < version2);
|
||||
}
|
||||
|
||||
public static bool operator >(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
if (version1 == null) {
|
||||
throw new ArgumentNullException("version1");
|
||||
}
|
||||
return version2 < version1;
|
||||
}
|
||||
|
||||
public static bool operator >=(SemanticVersion version1, SemanticVersion version2)
|
||||
{
|
||||
return (version1 == version2) || (version1 > version2);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _originalString;
|
||||
}
|
||||
|
||||
public bool Equals(SemanticVersion other)
|
||||
{
|
||||
return !Object.ReferenceEquals(null, other) &&
|
||||
Version.Equals(other.Version) &&
|
||||
SpecialVersion.Equals(other.SpecialVersion, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
SemanticVersion semVer = obj as SemanticVersion;
|
||||
return !Object.ReferenceEquals(null, semVer) && Equals(semVer);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hashCode = Version.GetHashCode();
|
||||
if (SpecialVersion != null) {
|
||||
hashCode = hashCode * 4567 + SpecialVersion.GetHashCode();
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
namespace Velopack.Tests.OldSquirrel
|
||||
{
|
||||
internal static class Utility
|
||||
{
|
||||
public static bool IsHttpUrl(string urlOrPath)
|
||||
{
|
||||
var uri = default(Uri);
|
||||
if (!Uri.TryCreate(urlOrPath, UriKind.Absolute, out uri)) {
|
||||
return false;
|
||||
}
|
||||
namespace Velopack.Tests.OldSquirrel;
|
||||
|
||||
return uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps;
|
||||
internal static class Utility
|
||||
{
|
||||
public static bool IsHttpUrl(string urlOrPath)
|
||||
{
|
||||
var uri = default(Uri);
|
||||
if (!Uri.TryCreate(urlOrPath, UriKind.Absolute, out uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,506 +4,505 @@ using NuGet.Versioning;
|
||||
using OldReleaseEntry = Velopack.Tests.OldSquirrel.ReleaseEntry;
|
||||
using OldSemanticVersion = Velopack.Tests.OldSquirrel.SemanticVersion;
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public class ReleaseEntryTests
|
||||
{
|
||||
public class ReleaseEntryTests
|
||||
[Theory]
|
||||
[InlineData(@"MyCoolApp-1.0-full.nupkg", "MyCoolApp", "1.0", "")]
|
||||
[InlineData(@"MyCoolApp-1.0.0-full.nupkg", "MyCoolApp", "1.0.0", "")]
|
||||
[InlineData(@"MyCoolApp-1.0.0-delta.nupkg", "MyCoolApp", "1.0.0", "")]
|
||||
[InlineData(@"MyCoolApp-1.0.0-win-x64-full.nupkg", "MyCoolApp", "1.0.0", "win-x64")]
|
||||
[InlineData(@"MyCoolApp-123.456.789-win-x64-full.nupkg", "MyCoolApp", "123.456.789", "win-x64")]
|
||||
[InlineData(@"MyCoolApp-123.456.789-hello-win-x64-full.nupkg", "MyCoolApp", "123.456.789", "hello-win-x64")]
|
||||
public void NewEntryCanRoundTripToOldSquirrel(string fileName, string id, string version, string metadata)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(@"MyCoolApp-1.0-full.nupkg", "MyCoolApp", "1.0", "")]
|
||||
[InlineData(@"MyCoolApp-1.0.0-full.nupkg", "MyCoolApp", "1.0.0", "")]
|
||||
[InlineData(@"MyCoolApp-1.0.0-delta.nupkg", "MyCoolApp", "1.0.0", "")]
|
||||
[InlineData(@"MyCoolApp-1.0.0-win-x64-full.nupkg", "MyCoolApp", "1.0.0", "win-x64")]
|
||||
[InlineData(@"MyCoolApp-123.456.789-win-x64-full.nupkg", "MyCoolApp", "123.456.789", "win-x64")]
|
||||
[InlineData(@"MyCoolApp-123.456.789-hello-win-x64-full.nupkg", "MyCoolApp", "123.456.789", "hello-win-x64")]
|
||||
public void NewEntryCanRoundTripToOldSquirrel(string fileName, string id, string version, string metadata)
|
||||
{
|
||||
var size = 80396;
|
||||
var sha = "14db31d2647c6d2284882a2e101924a9c409ee67";
|
||||
var re = new ReleaseEntry(sha, fileName, size, null, null, null);
|
||||
StringBuilder file = new StringBuilder();
|
||||
file.AppendLine(re.EntryAsString);
|
||||
var size = 80396;
|
||||
var sha = "14db31d2647c6d2284882a2e101924a9c409ee67";
|
||||
var re = new ReleaseEntry(sha, fileName, size, null, null, null);
|
||||
StringBuilder file = new StringBuilder();
|
||||
file.AppendLine(re.EntryAsString);
|
||||
|
||||
var parsed = OldReleaseEntry.ParseReleaseFile(file.ToString());
|
||||
Assert.True(parsed.Count() == 1);
|
||||
var parsed = OldReleaseEntry.ParseReleaseFile(file.ToString());
|
||||
Assert.True(parsed.Count() == 1);
|
||||
|
||||
var oldEntry = parsed.First();
|
||||
var oldEntry = parsed.First();
|
||||
|
||||
Assert.Equal(fileName, oldEntry.Filename);
|
||||
Assert.Equal(id, oldEntry.PackageName);
|
||||
Assert.Equal(size, oldEntry.Filesize);
|
||||
Assert.Equal(sha, oldEntry.SHA1);
|
||||
Assert.Null(oldEntry.BaseUrl);
|
||||
Assert.Null(oldEntry.Query);
|
||||
Assert.True(oldEntry.Version.Version == OldSemanticVersion.Parse(version).Version);
|
||||
Assert.Equal(oldEntry.Version.SpecialVersion, metadata);
|
||||
Assert.Equal(fileName, oldEntry.Filename);
|
||||
Assert.Equal(id, oldEntry.PackageName);
|
||||
Assert.Equal(size, oldEntry.Filesize);
|
||||
Assert.Equal(sha, oldEntry.SHA1);
|
||||
Assert.Null(oldEntry.BaseUrl);
|
||||
Assert.Null(oldEntry.Query);
|
||||
Assert.True(oldEntry.Version.Version == OldSemanticVersion.Parse(version).Version);
|
||||
Assert.Equal(oldEntry.Version.SpecialVersion, metadata);
|
||||
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec MyCoolApp-1.0.nupkg 1004502", "MyCoolApp-1.0.nupkg", 1004502, null, null)]
|
||||
[InlineData(@"3a2eadd15dd984e4559f2b4d790ec8badaeb6a39 MyCoolApp-1.1.nupkg 1040561", "MyCoolApp-1.1.nupkg", 1040561, null, null)]
|
||||
[InlineData(@"14db31d2647c6d2284882a2e101924a9c409ee67 MyCoolApp-1.1.nupkg.delta 80396", "MyCoolApp-1.1.nupkg.delta", 80396, null, null)]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://test.org/Folder/MyCoolApp-1.2.nupkg 2569", "MyCoolApp-1.2.nupkg", 2569, "http://test.org/Folder/", null)]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://test.org/Folder/MyCoolApp-1.2.nupkg?query=param 2569", "MyCoolApp-1.2.nupkg", 2569, "http://test.org/Folder/", "?query=param")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 https://www.test.org/Folder/MyCoolApp-1.2-delta.nupkg 1231953", "MyCoolApp-1.2-delta.nupkg", 1231953, "https://www.test.org/Folder/", null)]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 https://www.test.org/Folder/MyCoolApp-1.2-delta.nupkg?query=param 1231953", "MyCoolApp-1.2-delta.nupkg", 1231953, "https://www.test.org/Folder/", "?query=param")]
|
||||
public void ParseValidReleaseEntryLines(string releaseEntry, string fileName, long fileSize, string baseUrl, string query)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
|
||||
Assert.Equal(fileName, fixture.OriginalFilename);
|
||||
Assert.Equal(fileSize, fixture.Filesize);
|
||||
Assert.Equal(baseUrl, fixture.BaseUrl);
|
||||
Assert.Equal(query, fixture.Query);
|
||||
|
||||
var old = OldReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(fileName, old.Filename);
|
||||
Assert.Equal(fileSize, old.Filesize);
|
||||
Assert.Equal(baseUrl, old.BaseUrl);
|
||||
Assert.Equal(query, old.Query);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec My.Cool.App-1.0-full.nupkg 1004502", "My.Cool.App")]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec My.Cool.App-1.1.nupkg 1004502", "My.Cool.App")]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec http://test.org/Folder/My.Cool.App-1.2.nupkg?query=param 1231953", "My.Cool.App")]
|
||||
public void ParseValidReleaseEntryLinesWithDots(string releaseEntry, string packageName)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(packageName, fixture.PackageId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec My-Cool-App-1.0-full.nupkg 1004502", "My-Cool-App")]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec My.Cool-App-1.1.nupkg 1004502", "My.Cool-App")]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec http://test.org/Folder/My.Cool-App-1.2.nupkg?query=param 1231953", "My.Cool-App")]
|
||||
public void ParseValidReleaseEntryLinesWithDashes(string releaseEntry, string packageName)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(packageName, fixture.PackageId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 file:/C/Folder/MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 C:\Folder\MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 ..\OtherFolder\MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 ../OtherFolder/MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 \\Somewhere\NetworkShare\MyCoolApp-0.0.nupkg.delta 0")]
|
||||
public void ParseThrowsWhenInvalidReleaseEntryLines(string releaseEntry)
|
||||
{
|
||||
Assert.Throws<Exception>(() => ReleaseEntry.ParseReleaseEntry(releaseEntry));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 file.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://path/file.nupkg 0")]
|
||||
public void EntryAsStringMatchesParsedInput(string releaseEntry)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(releaseEntry, fixture.EntryAsString);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Squirrel.Core.1.0.0.0.nupkg", 4457, "75255cfd229a1ed1447abe1104f5635e69975d30")]
|
||||
[InlineData("Squirrel.Core.1.1.0.0.nupkg", 15830, "9baf1dbacb09940086c8c62d9a9dbe69fe1f7593")]
|
||||
public void GenerateFromFileTest(string name, long size, string sha1)
|
||||
{
|
||||
var path = PathHelper.GetFixture(name);
|
||||
|
||||
using (var f = File.OpenRead(path)) {
|
||||
var fixture = ReleaseEntry.GenerateFromFile(f, "dontcare");
|
||||
Assert.Equal(size, fixture.Filesize);
|
||||
Assert.Equal(sha1, fixture.SHA1.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.nupkg 123", 1, 2, 0, 0, "", false)]
|
||||
[InlineData("1000000000000000000000000000000000000000 MyCoolApp-1.2-full.nupkg 123", 1, 2, 0, 0, "", false)]
|
||||
[InlineData("2000000000000000000000000000000000000000 MyCoolApp-1.2-delta.nupkg 123", 1, 2, 0, 0, "", true)]
|
||||
[InlineData("3000000000000000000000000000000000000000 MyCoolApp-1.2-beta1.nupkg 123", 1, 2, 0, 0, "beta1", false)]
|
||||
[InlineData("4000000000000000000000000000000000000000 MyCoolApp-1.2-beta1-full.nupkg 123", 1, 2, 0, 0, "beta1", false)]
|
||||
[InlineData("5000000000000000000000000000000000000000 MyCoolApp-1.2-beta1-delta.nupkg 123", 1, 2, 0, 0, "beta1", true)]
|
||||
[InlineData("6000000000000000000000000000000000000000 MyCoolApp-1.2.3.nupkg 123", 1, 2, 3, 0, "", false)]
|
||||
[InlineData("7000000000000000000000000000000000000000 MyCoolApp-1.2.3-full.nupkg 123", 1, 2, 3, 0, "", false)]
|
||||
[InlineData("8000000000000000000000000000000000000000 MyCoolApp-1.2.3-delta.nupkg 123", 1, 2, 3, 0, "", true)]
|
||||
[InlineData("9000000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1.nupkg 123", 1, 2, 3, 0, "beta1", false)]
|
||||
[InlineData("0100000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1-full.nupkg 123", 1, 2, 3, 0, "beta1", false)]
|
||||
[InlineData("0200000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1-delta.nupkg 123", 1, 2, 3, 0, "beta1", true)]
|
||||
[InlineData("0300000000000000000000000000000000000000 MyCoolApp-1.2.3.4.nupkg 123", 1, 2, 3, 4, "", false)]
|
||||
[InlineData("0400000000000000000000000000000000000000 MyCoolApp-1.2.3.4-full.nupkg 123", 1, 2, 3, 4, "", false)]
|
||||
[InlineData("0500000000000000000000000000000000000000 MyCoolApp-1.2.3.4-delta.nupkg 123", 1, 2, 3, 4, "", true)]
|
||||
[InlineData("0600000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1.nupkg 123", 1, 2, 3, 4, "beta1", false)]
|
||||
[InlineData("0700000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1-full.nupkg 123", 1, 2, 3, 4, "beta1", false)]
|
||||
[InlineData("0800000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1-delta.nupkg 123", 1, 2, 3, 4, "beta1", true)]
|
||||
|
||||
public void ParseVersionTest(string releaseEntry, int major, int minor, int patch, int revision, string prerelease, bool isDelta)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
|
||||
Assert.Equal(new NuGetVersion(major, minor, patch, revision, prerelease, null), fixture.Version);
|
||||
Assert.Equal(isDelta, fixture.IsDelta);
|
||||
|
||||
var old = OldReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(new NuGetVersion(major, minor, patch, revision, prerelease, null), new NuGetVersion(old.Version.ToString()));
|
||||
Assert.Equal(isDelta, old.IsDelta);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool-App-1.2.nupkg 123", "MyCool-App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool_App-1.2-full.nupkg 123", "MyCool_App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-beta1.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-beta1-full.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-beta1-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool-App-1.2.3.nupkg 123", "MyCool-App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool_App-1.2.3-full.nupkg 123", "MyCool_App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1-full.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool-App-1.2.3.4.nupkg 123", "MyCool-App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool_App-1.2.3.4-full.nupkg 123", "MyCool_App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3.4-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1-full.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool-App-1.2.3.4-beta1-delta.nupkg 123", "MyCool-App")]
|
||||
public void CheckPackageName(string releaseEntry, string expected)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(expected, fixture.PackageId);
|
||||
|
||||
var old = OldReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(expected, old.PackageName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.nupkg 123 # 10%", 1, 2, 0, 0, "", "", false, 0.1f)]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-full.nupkg 123 # 90%", 1, 2, 0, 0, "", "", false, 0.9f)]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-delta.nupkg 123", 1, 2, 0, 0, "", "", true, null)]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-delta.nupkg 123 # 5%", 1, 2, 0, 0, "", "", true, 0.05f)]
|
||||
public void ParseStagingPercentageTest(string releaseEntry, int major, int minor, int patch, int revision, string prerelease, string rid, bool isDelta, float? stagingPercentage)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
|
||||
Assert.Equal(new NuGetVersion(major, minor, patch, revision, prerelease, null), fixture.Version);
|
||||
Assert.Equal(isDelta, fixture.IsDelta);
|
||||
|
||||
if (stagingPercentage.HasValue) {
|
||||
Assert.True(Math.Abs(fixture.StagingPercentage.Value - stagingPercentage.Value) < 0.001);
|
||||
} else {
|
||||
Assert.Null(fixture.StagingPercentage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec MyCoolApp-1.0.nupkg 1004502", "MyCoolApp-1.0.nupkg", 1004502, null, null)]
|
||||
[InlineData(@"3a2eadd15dd984e4559f2b4d790ec8badaeb6a39 MyCoolApp-1.1.nupkg 1040561", "MyCoolApp-1.1.nupkg", 1040561, null, null)]
|
||||
[InlineData(@"14db31d2647c6d2284882a2e101924a9c409ee67 MyCoolApp-1.1.nupkg.delta 80396", "MyCoolApp-1.1.nupkg.delta", 80396, null, null)]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://test.org/Folder/MyCoolApp-1.2.nupkg 2569", "MyCoolApp-1.2.nupkg", 2569, "http://test.org/Folder/", null)]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://test.org/Folder/MyCoolApp-1.2.nupkg?query=param 2569", "MyCoolApp-1.2.nupkg", 2569, "http://test.org/Folder/", "?query=param")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 https://www.test.org/Folder/MyCoolApp-1.2-delta.nupkg 1231953", "MyCoolApp-1.2-delta.nupkg", 1231953, "https://www.test.org/Folder/", null)]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 https://www.test.org/Folder/MyCoolApp-1.2-delta.nupkg?query=param 1231953", "MyCoolApp-1.2-delta.nupkg", 1231953, "https://www.test.org/Folder/", "?query=param")]
|
||||
public void ParseValidReleaseEntryLines(string releaseEntry, string fileName, long fileSize, string baseUrl, string query)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
var old = OldReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
var legacyPre = !String.IsNullOrEmpty(prerelease) && !String.IsNullOrEmpty(rid) ? $"{prerelease}-{rid}" : String.IsNullOrEmpty(prerelease) ? rid : prerelease;
|
||||
Assert.Equal(new NuGetVersion(major, minor, patch, revision, legacyPre, null), new NuGetVersion(old.Version.ToString()));
|
||||
Assert.Equal(isDelta, old.IsDelta);
|
||||
|
||||
Assert.Equal(fileName, fixture.OriginalFilename);
|
||||
Assert.Equal(fileSize, fixture.Filesize);
|
||||
Assert.Equal(baseUrl, fixture.BaseUrl);
|
||||
Assert.Equal(query, fixture.Query);
|
||||
|
||||
var old = OldReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(fileName, old.Filename);
|
||||
Assert.Equal(fileSize, old.Filesize);
|
||||
Assert.Equal(baseUrl, old.BaseUrl);
|
||||
Assert.Equal(query, old.Query);
|
||||
if (stagingPercentage.HasValue) {
|
||||
Assert.True(Math.Abs(old.StagingPercentage.Value - stagingPercentage.Value) < 0.001);
|
||||
} else {
|
||||
Assert.Null(old.StagingPercentage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec My.Cool.App-1.0-full.nupkg 1004502", "My.Cool.App")]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec My.Cool.App-1.1.nupkg 1004502", "My.Cool.App")]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec http://test.org/Folder/My.Cool.App-1.2.nupkg?query=param 1231953", "My.Cool.App")]
|
||||
public void ParseValidReleaseEntryLinesWithDots(string releaseEntry, string packageName)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(packageName, fixture.PackageId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec My-Cool-App-1.0-full.nupkg 1004502", "My-Cool-App")]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec My.Cool-App-1.1.nupkg 1004502", "My.Cool-App")]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec http://test.org/Folder/My.Cool-App-1.2.nupkg?query=param 1231953", "My.Cool-App")]
|
||||
public void ParseValidReleaseEntryLinesWithDashes(string releaseEntry, string packageName)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(packageName, fixture.PackageId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 file:/C/Folder/MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 C:\Folder\MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 ..\OtherFolder\MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 ../OtherFolder/MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 \\Somewhere\NetworkShare\MyCoolApp-0.0.nupkg.delta 0")]
|
||||
public void ParseThrowsWhenInvalidReleaseEntryLines(string releaseEntry)
|
||||
{
|
||||
Assert.Throws<Exception>(() => ReleaseEntry.ParseReleaseEntry(releaseEntry));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 file.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://path/file.nupkg 0")]
|
||||
public void EntryAsStringMatchesParsedInput(string releaseEntry)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(releaseEntry, fixture.EntryAsString);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Squirrel.Core.1.0.0.0.nupkg", 4457, "75255cfd229a1ed1447abe1104f5635e69975d30")]
|
||||
[InlineData("Squirrel.Core.1.1.0.0.nupkg", 15830, "9baf1dbacb09940086c8c62d9a9dbe69fe1f7593")]
|
||||
public void GenerateFromFileTest(string name, long size, string sha1)
|
||||
{
|
||||
var path = PathHelper.GetFixture(name);
|
||||
|
||||
using (var f = File.OpenRead(path)) {
|
||||
var fixture = ReleaseEntry.GenerateFromFile(f, "dontcare");
|
||||
Assert.Equal(size, fixture.Filesize);
|
||||
Assert.Equal(sha1, fixture.SHA1.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.nupkg 123", 1, 2, 0, 0, "", false)]
|
||||
[InlineData("1000000000000000000000000000000000000000 MyCoolApp-1.2-full.nupkg 123", 1, 2, 0, 0, "", false)]
|
||||
[InlineData("2000000000000000000000000000000000000000 MyCoolApp-1.2-delta.nupkg 123", 1, 2, 0, 0, "", true)]
|
||||
[InlineData("3000000000000000000000000000000000000000 MyCoolApp-1.2-beta1.nupkg 123", 1, 2, 0, 0, "beta1", false)]
|
||||
[InlineData("4000000000000000000000000000000000000000 MyCoolApp-1.2-beta1-full.nupkg 123", 1, 2, 0, 0, "beta1", false)]
|
||||
[InlineData("5000000000000000000000000000000000000000 MyCoolApp-1.2-beta1-delta.nupkg 123", 1, 2, 0, 0, "beta1", true)]
|
||||
[InlineData("6000000000000000000000000000000000000000 MyCoolApp-1.2.3.nupkg 123", 1, 2, 3, 0, "", false)]
|
||||
[InlineData("7000000000000000000000000000000000000000 MyCoolApp-1.2.3-full.nupkg 123", 1, 2, 3, 0, "", false)]
|
||||
[InlineData("8000000000000000000000000000000000000000 MyCoolApp-1.2.3-delta.nupkg 123", 1, 2, 3, 0, "", true)]
|
||||
[InlineData("9000000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1.nupkg 123", 1, 2, 3, 0, "beta1", false)]
|
||||
[InlineData("0100000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1-full.nupkg 123", 1, 2, 3, 0, "beta1", false)]
|
||||
[InlineData("0200000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1-delta.nupkg 123", 1, 2, 3, 0, "beta1", true)]
|
||||
[InlineData("0300000000000000000000000000000000000000 MyCoolApp-1.2.3.4.nupkg 123", 1, 2, 3, 4, "", false)]
|
||||
[InlineData("0400000000000000000000000000000000000000 MyCoolApp-1.2.3.4-full.nupkg 123", 1, 2, 3, 4, "", false)]
|
||||
[InlineData("0500000000000000000000000000000000000000 MyCoolApp-1.2.3.4-delta.nupkg 123", 1, 2, 3, 4, "", true)]
|
||||
[InlineData("0600000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1.nupkg 123", 1, 2, 3, 4, "beta1", false)]
|
||||
[InlineData("0700000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1-full.nupkg 123", 1, 2, 3, 4, "beta1", false)]
|
||||
[InlineData("0800000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1-delta.nupkg 123", 1, 2, 3, 4, "beta1", true)]
|
||||
|
||||
public void ParseVersionTest(string releaseEntry, int major, int minor, int patch, int revision, string prerelease, bool isDelta)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
|
||||
Assert.Equal(new NuGetVersion(major, minor, patch, revision, prerelease, null), fixture.Version);
|
||||
Assert.Equal(isDelta, fixture.IsDelta);
|
||||
|
||||
var old = OldReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(new NuGetVersion(major, minor, patch, revision, prerelease, null), new NuGetVersion(old.Version.ToString()));
|
||||
Assert.Equal(isDelta, old.IsDelta);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool-App-1.2.nupkg 123", "MyCool-App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool_App-1.2-full.nupkg 123", "MyCool_App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-beta1.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-beta1-full.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-beta1-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool-App-1.2.3.nupkg 123", "MyCool-App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool_App-1.2.3-full.nupkg 123", "MyCool_App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1-full.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3-beta1-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool-App-1.2.3.4.nupkg 123", "MyCool-App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool_App-1.2.3.4-full.nupkg 123", "MyCool_App")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3.4-delta.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.3.4-beta1-full.nupkg 123", "MyCoolApp")]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCool-App-1.2.3.4-beta1-delta.nupkg 123", "MyCool-App")]
|
||||
public void CheckPackageName(string releaseEntry, string expected)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(expected, fixture.PackageId);
|
||||
|
||||
var old = OldReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(expected, old.PackageName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2.nupkg 123 # 10%", 1, 2, 0, 0, "", "", false, 0.1f)]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-full.nupkg 123 # 90%", 1, 2, 0, 0, "", "", false, 0.9f)]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-delta.nupkg 123", 1, 2, 0, 0, "", "", true, null)]
|
||||
[InlineData("0000000000000000000000000000000000000000 MyCoolApp-1.2-delta.nupkg 123 # 5%", 1, 2, 0, 0, "", "", true, 0.05f)]
|
||||
public void ParseStagingPercentageTest(string releaseEntry, int major, int minor, int patch, int revision, string prerelease, string rid, bool isDelta, float? stagingPercentage)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
|
||||
Assert.Equal(new NuGetVersion(major, minor, patch, revision, prerelease, null), fixture.Version);
|
||||
Assert.Equal(isDelta, fixture.IsDelta);
|
||||
|
||||
if (stagingPercentage.HasValue) {
|
||||
Assert.True(Math.Abs(fixture.StagingPercentage.Value - stagingPercentage.Value) < 0.001);
|
||||
} else {
|
||||
Assert.Null(fixture.StagingPercentage);
|
||||
}
|
||||
|
||||
var old = OldReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
var legacyPre = !String.IsNullOrEmpty(prerelease) && !String.IsNullOrEmpty(rid) ? $"{prerelease}-{rid}" : String.IsNullOrEmpty(prerelease) ? rid : prerelease;
|
||||
Assert.Equal(new NuGetVersion(major, minor, patch, revision, legacyPre, null), new NuGetVersion(old.Version.ToString()));
|
||||
Assert.Equal(isDelta, old.IsDelta);
|
||||
|
||||
if (stagingPercentage.HasValue) {
|
||||
Assert.True(Math.Abs(old.StagingPercentage.Value - stagingPercentage.Value) < 0.001);
|
||||
} else {
|
||||
Assert.Null(old.StagingPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseGeneratedReleaseEntryAsString()
|
||||
{
|
||||
var path = PathHelper.GetFixture("Squirrel.Core.1.1.0.0.nupkg");
|
||||
var entryAsString = ReleaseEntry.GenerateFromFile(path).EntryAsString;
|
||||
ReleaseEntry.ParseReleaseEntry(entryAsString);
|
||||
}
|
||||
|
||||
//[Fact]
|
||||
//public void GetLatestReleaseWithNullCollectionReturnsNull()
|
||||
//{
|
||||
// Assert.Null(ReleasePackageBuilder.GetPreviousRelease(
|
||||
// null, null, null, null));
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void GetLatestReleaseWithEmptyCollectionReturnsNull()
|
||||
//{
|
||||
// Assert.Null(ReleasePackageBuilder.GetPreviousRelease(
|
||||
// Enumerable.Empty<ReleaseEntry>(), null, null, null));
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void WhenCurrentReleaseMatchesLastReleaseReturnNull()
|
||||
//{
|
||||
// var package = new ReleasePackageBuilder("Espera-1.7.6-beta.nupkg");
|
||||
|
||||
// var releaseEntries = new[] {
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.6-beta.nupkg"))
|
||||
// };
|
||||
// Assert.Null(ReleasePackageBuilder.GetPreviousRelease(
|
||||
// releaseEntries, package, @"C:\temp\somefolder", null));
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void WhenMultipleReleaseMatchesReturnEarlierResult()
|
||||
//{
|
||||
// var expected = SemanticVersion.Parse("1.7.5-beta");
|
||||
// var package = new ReleasePackageBuilder("Espera-1.7.6-beta.nupkg");
|
||||
|
||||
// var releaseEntries = new[] {
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.6-beta.nupkg")),
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.5-beta.nupkg"))
|
||||
// };
|
||||
|
||||
// var actual = ReleasePackageBuilder.GetPreviousRelease(
|
||||
// releaseEntries,
|
||||
// package,
|
||||
// @"C:\temp\", null);
|
||||
|
||||
// Assert.Equal(expected, actual.Version);
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void WhenMultipleReleasesFoundReturnPreviousVersion()
|
||||
//{
|
||||
// var expected = SemanticVersion.Parse("1.7.6-beta");
|
||||
// var input = new ReleasePackageBuilder("Espera-1.7.7-beta.nupkg");
|
||||
|
||||
// var releaseEntries = new[] {
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.6-beta.nupkg")),
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.5-beta.nupkg"))
|
||||
// };
|
||||
|
||||
// var actual = ReleasePackageBuilder.GetPreviousRelease(
|
||||
// releaseEntries,
|
||||
// input,
|
||||
// @"C:\temp\", null);
|
||||
|
||||
// Assert.Equal(expected, actual.Version);
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void WhenMultipleReleasesFoundInOtherOrderReturnPreviousVersion()
|
||||
//{
|
||||
// var expected = SemanticVersion.Parse("1.7.6-beta");
|
||||
// var input = new ReleasePackageBuilder("Espera-1.7.7-beta.nupkg");
|
||||
|
||||
// var releaseEntries = new[] {
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.5-beta.nupkg")),
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.6-beta.nupkg"))
|
||||
// };
|
||||
|
||||
// var actual = ReleasePackageBuilder.GetPreviousRelease(
|
||||
// releaseEntries,
|
||||
// input,
|
||||
// @"C:\temp\", null);
|
||||
|
||||
// Assert.Equal(expected, actual.Version);
|
||||
//}
|
||||
|
||||
[Fact]
|
||||
public void WhenReleasesAreOutOfOrderSortByVersion()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var firstVersion = SemanticVersion.Parse("1.0.0");
|
||||
var secondVersion = SemanticVersion.Parse("1.1.0");
|
||||
var thirdVersion = SemanticVersion.Parse("1.2.0");
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-delta.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-delta.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(path)).ToArray();
|
||||
|
||||
Assert.Equal(firstVersion, releases[0].Version);
|
||||
Assert.Equal(secondVersion, releases[1].Version);
|
||||
Assert.Equal(true, releases[1].IsDelta);
|
||||
Assert.Equal(secondVersion, releases[2].Version);
|
||||
Assert.Equal(false, releases[2].IsDelta);
|
||||
Assert.Equal(thirdVersion, releases[3].Version);
|
||||
Assert.Equal(true, releases[3].IsDelta);
|
||||
Assert.Equal(thirdVersion, releases[4].Version);
|
||||
Assert.Equal(false, releases[4].IsDelta);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenPreReleasesAreOutOfOrderSortByNumericSuffixSemVer2()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var firstVersion = SemanticVersion.Parse("1.1.9-beta.105");
|
||||
var secondVersion = SemanticVersion.Parse("1.2.0-beta.9");
|
||||
var thirdVersion = SemanticVersion.Parse("1.2.0-beta.10");
|
||||
var fourthVersion = SemanticVersion.Parse("1.2.0-beta.100");
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-beta.1-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-beta.9-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-beta.100-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.9-beta.105-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-beta.10-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(path)).ToArray();
|
||||
|
||||
Assert.Equal(firstVersion, releases[0].Version);
|
||||
Assert.Equal(secondVersion, releases[2].Version);
|
||||
Assert.Equal(thirdVersion, releases[3].Version);
|
||||
Assert.Equal(fourthVersion, releases[4].Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StagingUsersGetBetaSoftware()
|
||||
{
|
||||
// NB: We're kind of using a hack here, in that we know that the
|
||||
// last 4 bytes are used as the percentage, and the percentage
|
||||
// effectively measures, "How close are you to zero". Guid.Empty
|
||||
// is v close to zero, because it is zero.
|
||||
var path = Path.GetTempFileName();
|
||||
var ourGuid = Guid.Empty;
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg", 0.1f)),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFileAndApplyStaging(File.ReadAllText(path), ourGuid).ToArray();
|
||||
Assert.Equal(3, releases.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BorkedUsersGetProductionSoftware()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var ourGuid = default(Guid?);
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg", 0.1f)),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFileAndApplyStaging(File.ReadAllText(path), ourGuid).ToArray();
|
||||
Assert.Equal(2, releases.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-ffffffffffff}")]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-888888888888}")]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-444444444444}")]
|
||||
public void UnluckyUsersGetProductionSoftware(string inputGuid)
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var ourGuid = Guid.ParseExact(inputGuid, "B");
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg", 0.1f)),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFileAndApplyStaging(File.ReadAllText(path), ourGuid).ToArray();
|
||||
Assert.Equal(2, releases.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-333333333333}")]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-111111111111}")]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-000000000000}")]
|
||||
public void LuckyUsersGetBetaSoftware(string inputGuid)
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var ourGuid = Guid.ParseExact(inputGuid, "B");
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg", 0.25f)),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFileAndApplyStaging(File.ReadAllText(path), ourGuid).ToArray();
|
||||
Assert.Equal(3, releases.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseReleaseFileShouldReturnNothingForBlankFiles()
|
||||
{
|
||||
Assert.True(ReleaseEntry.ParseReleaseFile("").Count() == 0);
|
||||
Assert.True(ReleaseEntry.ParseReleaseFile(null).Count() == 0);
|
||||
}
|
||||
|
||||
// [Fact]
|
||||
// public void FindCurrentVersionWithExactRidMatch()
|
||||
// {
|
||||
// string _ridReleaseEntries = """
|
||||
//0000000000000000000000000000000000000000 MyApp-1.3-win-x86.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win-x64.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win-x86.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-osx-x86.nupkg 123
|
||||
//""";
|
||||
|
||||
// var entries = ReleaseEntry.ParseReleaseFile(_ridReleaseEntries);
|
||||
|
||||
// var e = Utility.FindLatestFullVersion(entries, RID.Parse("win-x86"));
|
||||
// Assert.Equal("MyApp-1.4-win-x86.nupkg", e.OriginalFilename);
|
||||
// }
|
||||
|
||||
// [Fact]
|
||||
// public void FindCurrentVersionWithExactRidMatchNotLatest()
|
||||
// {
|
||||
// string _ridReleaseEntries = """
|
||||
//0000000000000000000000000000000000000000 MyApp-1.3-win-x86.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win-x64.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-osx-x86.nupkg 123
|
||||
//""";
|
||||
|
||||
// var entries = ReleaseEntry.ParseReleaseFile(_ridReleaseEntries);
|
||||
|
||||
// var e = Utility.FindLatestFullVersion(entries, RID.Parse("win-x86"));
|
||||
// Assert.Equal("MyApp-1.3-win.nupkg", e.OriginalFilename);
|
||||
// }
|
||||
|
||||
// [Fact]
|
||||
// public void FindCurrentVersionWithExactRidMatchOnlyArchitecture()
|
||||
// {
|
||||
// string _ridReleaseEntries = """
|
||||
//0000000000000000000000000000000000000000 MyApp-1.3-win-x86.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win-x64.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-osx-x86.nupkg 123
|
||||
//""";
|
||||
|
||||
// var entries = ReleaseEntry.ParseReleaseFile(_ridReleaseEntries);
|
||||
|
||||
// var e = Utility.FindLatestFullVersion(entries, RID.Parse("win-x86"));
|
||||
// Assert.Equal("MyApp-1.3-win.nupkg", e.OriginalFilename);
|
||||
// }
|
||||
|
||||
static string MockReleaseEntry(string name, float? percentage = null)
|
||||
{
|
||||
if (percentage.HasValue) {
|
||||
var ret = String.Format("94689fede03fed7ab59c24337673a27837f0c3ec {0} 1004502 # {1:F0}%", name, percentage * 100.0f);
|
||||
return ret;
|
||||
} else {
|
||||
return String.Format("94689fede03fed7ab59c24337673a27837f0c3ec {0} 1004502", name);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseGeneratedReleaseEntryAsString()
|
||||
{
|
||||
var path = PathHelper.GetFixture("Squirrel.Core.1.1.0.0.nupkg");
|
||||
var entryAsString = ReleaseEntry.GenerateFromFile(path).EntryAsString;
|
||||
ReleaseEntry.ParseReleaseEntry(entryAsString);
|
||||
}
|
||||
|
||||
//[Fact]
|
||||
//public void GetLatestReleaseWithNullCollectionReturnsNull()
|
||||
//{
|
||||
// Assert.Null(ReleasePackageBuilder.GetPreviousRelease(
|
||||
// null, null, null, null));
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void GetLatestReleaseWithEmptyCollectionReturnsNull()
|
||||
//{
|
||||
// Assert.Null(ReleasePackageBuilder.GetPreviousRelease(
|
||||
// Enumerable.Empty<ReleaseEntry>(), null, null, null));
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void WhenCurrentReleaseMatchesLastReleaseReturnNull()
|
||||
//{
|
||||
// var package = new ReleasePackageBuilder("Espera-1.7.6-beta.nupkg");
|
||||
|
||||
// var releaseEntries = new[] {
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.6-beta.nupkg"))
|
||||
// };
|
||||
// Assert.Null(ReleasePackageBuilder.GetPreviousRelease(
|
||||
// releaseEntries, package, @"C:\temp\somefolder", null));
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void WhenMultipleReleaseMatchesReturnEarlierResult()
|
||||
//{
|
||||
// var expected = SemanticVersion.Parse("1.7.5-beta");
|
||||
// var package = new ReleasePackageBuilder("Espera-1.7.6-beta.nupkg");
|
||||
|
||||
// var releaseEntries = new[] {
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.6-beta.nupkg")),
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.5-beta.nupkg"))
|
||||
// };
|
||||
|
||||
// var actual = ReleasePackageBuilder.GetPreviousRelease(
|
||||
// releaseEntries,
|
||||
// package,
|
||||
// @"C:\temp\", null);
|
||||
|
||||
// Assert.Equal(expected, actual.Version);
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void WhenMultipleReleasesFoundReturnPreviousVersion()
|
||||
//{
|
||||
// var expected = SemanticVersion.Parse("1.7.6-beta");
|
||||
// var input = new ReleasePackageBuilder("Espera-1.7.7-beta.nupkg");
|
||||
|
||||
// var releaseEntries = new[] {
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.6-beta.nupkg")),
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.5-beta.nupkg"))
|
||||
// };
|
||||
|
||||
// var actual = ReleasePackageBuilder.GetPreviousRelease(
|
||||
// releaseEntries,
|
||||
// input,
|
||||
// @"C:\temp\", null);
|
||||
|
||||
// Assert.Equal(expected, actual.Version);
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public void WhenMultipleReleasesFoundInOtherOrderReturnPreviousVersion()
|
||||
//{
|
||||
// var expected = SemanticVersion.Parse("1.7.6-beta");
|
||||
// var input = new ReleasePackageBuilder("Espera-1.7.7-beta.nupkg");
|
||||
|
||||
// var releaseEntries = new[] {
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.5-beta.nupkg")),
|
||||
// ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.7.6-beta.nupkg"))
|
||||
// };
|
||||
|
||||
// var actual = ReleasePackageBuilder.GetPreviousRelease(
|
||||
// releaseEntries,
|
||||
// input,
|
||||
// @"C:\temp\", null);
|
||||
|
||||
// Assert.Equal(expected, actual.Version);
|
||||
//}
|
||||
|
||||
[Fact]
|
||||
public void WhenReleasesAreOutOfOrderSortByVersion()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var firstVersion = SemanticVersion.Parse("1.0.0");
|
||||
var secondVersion = SemanticVersion.Parse("1.1.0");
|
||||
var thirdVersion = SemanticVersion.Parse("1.2.0");
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-delta.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-delta.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(path)).ToArray();
|
||||
|
||||
Assert.Equal(firstVersion, releases[0].Version);
|
||||
Assert.Equal(secondVersion, releases[1].Version);
|
||||
Assert.Equal(true, releases[1].IsDelta);
|
||||
Assert.Equal(secondVersion, releases[2].Version);
|
||||
Assert.Equal(false, releases[2].IsDelta);
|
||||
Assert.Equal(thirdVersion, releases[3].Version);
|
||||
Assert.Equal(true, releases[3].IsDelta);
|
||||
Assert.Equal(thirdVersion, releases[4].Version);
|
||||
Assert.Equal(false, releases[4].IsDelta);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenPreReleasesAreOutOfOrderSortByNumericSuffixSemVer2()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var firstVersion = SemanticVersion.Parse("1.1.9-beta.105");
|
||||
var secondVersion = SemanticVersion.Parse("1.2.0-beta.9");
|
||||
var thirdVersion = SemanticVersion.Parse("1.2.0-beta.10");
|
||||
var fourthVersion = SemanticVersion.Parse("1.2.0-beta.100");
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-beta.1-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-beta.9-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-beta.100-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.9-beta.105-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-beta.10-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(path)).ToArray();
|
||||
|
||||
Assert.Equal(firstVersion, releases[0].Version);
|
||||
Assert.Equal(secondVersion, releases[2].Version);
|
||||
Assert.Equal(thirdVersion, releases[3].Version);
|
||||
Assert.Equal(fourthVersion, releases[4].Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StagingUsersGetBetaSoftware()
|
||||
{
|
||||
// NB: We're kind of using a hack here, in that we know that the
|
||||
// last 4 bytes are used as the percentage, and the percentage
|
||||
// effectively measures, "How close are you to zero". Guid.Empty
|
||||
// is v close to zero, because it is zero.
|
||||
var path = Path.GetTempFileName();
|
||||
var ourGuid = Guid.Empty;
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg", 0.1f)),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFileAndApplyStaging(File.ReadAllText(path), ourGuid).ToArray();
|
||||
Assert.Equal(3, releases.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BorkedUsersGetProductionSoftware()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var ourGuid = default(Guid?);
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg", 0.1f)),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFileAndApplyStaging(File.ReadAllText(path), ourGuid).ToArray();
|
||||
Assert.Equal(2, releases.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-ffffffffffff}")]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-888888888888}")]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-444444444444}")]
|
||||
public void UnluckyUsersGetProductionSoftware(string inputGuid)
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var ourGuid = Guid.ParseExact(inputGuid, "B");
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg", 0.1f)),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFileAndApplyStaging(File.ReadAllText(path), ourGuid).ToArray();
|
||||
Assert.Equal(2, releases.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-333333333333}")]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-111111111111}")]
|
||||
[InlineData("{22b29e6f-bd2e-43d2-85ca-000000000000}")]
|
||||
public void LuckyUsersGetBetaSoftware(string inputGuid)
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
var ourGuid = Guid.ParseExact(inputGuid, "B");
|
||||
|
||||
var releaseEntries = new[] {
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.2.0-full.nupkg", 0.25f)),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.1.0-full.nupkg")),
|
||||
ReleaseEntry.ParseReleaseEntry(MockReleaseEntry("Espera-1.0.0-full.nupkg"))
|
||||
};
|
||||
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, path);
|
||||
|
||||
var releases = ReleaseEntry.ParseReleaseFileAndApplyStaging(File.ReadAllText(path), ourGuid).ToArray();
|
||||
Assert.Equal(3, releases.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseReleaseFileShouldReturnNothingForBlankFiles()
|
||||
{
|
||||
Assert.True(ReleaseEntry.ParseReleaseFile("").Count() == 0);
|
||||
Assert.True(ReleaseEntry.ParseReleaseFile(null).Count() == 0);
|
||||
}
|
||||
|
||||
// [Fact]
|
||||
// public void FindCurrentVersionWithExactRidMatch()
|
||||
// {
|
||||
// string _ridReleaseEntries = """
|
||||
//0000000000000000000000000000000000000000 MyApp-1.3-win-x86.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win-x64.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win-x86.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-osx-x86.nupkg 123
|
||||
//""";
|
||||
|
||||
// var entries = ReleaseEntry.ParseReleaseFile(_ridReleaseEntries);
|
||||
|
||||
// var e = Utility.FindLatestFullVersion(entries, RID.Parse("win-x86"));
|
||||
// Assert.Equal("MyApp-1.4-win-x86.nupkg", e.OriginalFilename);
|
||||
// }
|
||||
|
||||
// [Fact]
|
||||
// public void FindCurrentVersionWithExactRidMatchNotLatest()
|
||||
// {
|
||||
// string _ridReleaseEntries = """
|
||||
//0000000000000000000000000000000000000000 MyApp-1.3-win-x86.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win-x64.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-osx-x86.nupkg 123
|
||||
//""";
|
||||
|
||||
// var entries = ReleaseEntry.ParseReleaseFile(_ridReleaseEntries);
|
||||
|
||||
// var e = Utility.FindLatestFullVersion(entries, RID.Parse("win-x86"));
|
||||
// Assert.Equal("MyApp-1.3-win.nupkg", e.OriginalFilename);
|
||||
// }
|
||||
|
||||
// [Fact]
|
||||
// public void FindCurrentVersionWithExactRidMatchOnlyArchitecture()
|
||||
// {
|
||||
// string _ridReleaseEntries = """
|
||||
//0000000000000000000000000000000000000000 MyApp-1.3-win-x86.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win-x64.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-win.nupkg 123
|
||||
//0000000000000000000000000000000000000000 MyApp-1.4-osx-x86.nupkg 123
|
||||
//""";
|
||||
|
||||
// var entries = ReleaseEntry.ParseReleaseFile(_ridReleaseEntries);
|
||||
|
||||
// var e = Utility.FindLatestFullVersion(entries, RID.Parse("win-x86"));
|
||||
// Assert.Equal("MyApp-1.3-win.nupkg", e.OriginalFilename);
|
||||
// }
|
||||
|
||||
static string MockReleaseEntry(string name, float? percentage = null)
|
||||
{
|
||||
if (percentage.HasValue) {
|
||||
var ret = String.Format("94689fede03fed7ab59c24337673a27837f0c3ec {0} 1004502 # {1:F0}%", name, percentage * 100.0f);
|
||||
return ret;
|
||||
} else {
|
||||
return String.Format("94689fede03fed7ab59c24337673a27837f0c3ec {0} 1004502", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,40 +6,39 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Tests
|
||||
{
|
||||
public class RuntimeInfoTests
|
||||
{
|
||||
[Fact]
|
||||
public void NugetVersionAgreesWithNbgv()
|
||||
{
|
||||
var args = new List<string> { "get-version", "-v", "NuGetPackageVersion" };
|
||||
var psi = new ProcessStartInfo("nbgv");
|
||||
psi.AppendArgumentListSafe(args, out var _);
|
||||
var current = psi.Output(10_000);
|
||||
Assert.Equal(current, VelopackRuntimeInfo.VelopackNugetVersion.ToString());
|
||||
}
|
||||
namespace Velopack.Tests;
|
||||
|
||||
[Fact]
|
||||
public void PlatformIsCorrect()
|
||||
{
|
||||
public class RuntimeInfoTests
|
||||
{
|
||||
[Fact]
|
||||
public void NugetVersionAgreesWithNbgv()
|
||||
{
|
||||
var args = new List<string> { "get-version", "-v", "NuGetPackageVersion" };
|
||||
var psi = new ProcessStartInfo("nbgv");
|
||||
psi.AppendArgumentListSafe(args, out var _);
|
||||
var current = psi.Output(10_000);
|
||||
Assert.Equal(current, VelopackRuntimeInfo.VelopackNugetVersion.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlatformIsCorrect()
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
Assert.True(VelopackRuntimeInfo.IsWindows);
|
||||
Assert.Equal(RuntimeOs.Windows, VelopackRuntimeInfo.SystemOs);
|
||||
#else
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
Assert.True(VelopackRuntimeInfo.IsWindows);
|
||||
Assert.Equal(RuntimeOs.Windows, VelopackRuntimeInfo.SystemOs);
|
||||
#else
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
Assert.True(VelopackRuntimeInfo.IsWindows);
|
||||
Assert.Equal(RuntimeOs.Windows, VelopackRuntimeInfo.SystemOs);
|
||||
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
|
||||
Assert.True(VelopackRuntimeInfo.IsLinux);
|
||||
Assert.Equal(RuntimeOs.Linux, VelopackRuntimeInfo.SystemOs);
|
||||
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
|
||||
Assert.True(VelopackRuntimeInfo.IsOSX);
|
||||
Assert.Equal(RuntimeOs.OSX, VelopackRuntimeInfo.SystemOs);
|
||||
} else {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
#endif
|
||||
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
|
||||
Assert.True(VelopackRuntimeInfo.IsLinux);
|
||||
Assert.Equal(RuntimeOs.Linux, VelopackRuntimeInfo.SystemOs);
|
||||
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
|
||||
Assert.True(VelopackRuntimeInfo.IsOSX);
|
||||
Assert.Equal(RuntimeOs.OSX, VelopackRuntimeInfo.SystemOs);
|
||||
} else {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,107 +2,106 @@
|
||||
using System.Net.Http;
|
||||
using Velopack.Windows;
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public class RuntimeTests
|
||||
{
|
||||
public class RuntimeTests
|
||||
[Theory]
|
||||
[InlineData("net6", "net6-x64-desktop")]
|
||||
[InlineData("net6.0", "net6-x64-desktop")]
|
||||
[InlineData("net6-x64", "net6-x64-desktop")]
|
||||
[InlineData("net6-x86", "net6-x86-desktop")]
|
||||
[InlineData("net3.1", "netcoreapp3.1-x64-desktop")]
|
||||
[InlineData("netcoreapp3.1", "netcoreapp3.1-x64-desktop")]
|
||||
[InlineData("net3.1-x86", "netcoreapp3.1-x86-desktop")]
|
||||
[InlineData("net6.0.2", "net6.0.2-x64-desktop")]
|
||||
[InlineData("net6.0.2-x86", "net6.0.2-x86-desktop")]
|
||||
[InlineData("net6.0.1-x86", "net6.0.1-x86-desktop")]
|
||||
[InlineData("net6.0.0", "net6-x64-desktop")]
|
||||
[InlineData("net6.0-x64-desktop", "net6-x64-desktop")]
|
||||
[InlineData("net7.0-x64-runtime", "net7-x64-runtime")]
|
||||
[InlineData("net7.0-x64-asp", "net7-x64-asp")]
|
||||
[InlineData("net7.0-desktop", "net7-x64-desktop")]
|
||||
[InlineData("net7.0-runtime", "net7-x64-runtime")]
|
||||
public void DotnetParsesValidVersions(string input, string expected)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("net6", "net6-x64-desktop")]
|
||||
[InlineData("net6.0", "net6-x64-desktop")]
|
||||
[InlineData("net6-x64", "net6-x64-desktop")]
|
||||
[InlineData("net6-x86", "net6-x86-desktop")]
|
||||
[InlineData("net3.1", "netcoreapp3.1-x64-desktop")]
|
||||
[InlineData("netcoreapp3.1", "netcoreapp3.1-x64-desktop")]
|
||||
[InlineData("net3.1-x86", "netcoreapp3.1-x86-desktop")]
|
||||
[InlineData("net6.0.2", "net6.0.2-x64-desktop")]
|
||||
[InlineData("net6.0.2-x86", "net6.0.2-x86-desktop")]
|
||||
[InlineData("net6.0.1-x86", "net6.0.1-x86-desktop")]
|
||||
[InlineData("net6.0.0", "net6-x64-desktop")]
|
||||
[InlineData("net6.0-x64-desktop", "net6-x64-desktop")]
|
||||
[InlineData("net7.0-x64-runtime", "net7-x64-runtime")]
|
||||
[InlineData("net7.0-x64-asp", "net7-x64-asp")]
|
||||
[InlineData("net7.0-desktop", "net7-x64-desktop")]
|
||||
[InlineData("net7.0-runtime", "net7-x64-runtime")]
|
||||
public void DotnetParsesValidVersions(string input, string expected)
|
||||
{
|
||||
var p = Runtimes.DotnetInfo.Parse(input);
|
||||
Assert.Equal(expected, p.Id);
|
||||
}
|
||||
var p = Runtimes.DotnetInfo.Parse(input);
|
||||
Assert.Equal(expected, p.Id);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("net3.2")]
|
||||
[InlineData("net4.9")]
|
||||
[InlineData("net6.0.0.4")]
|
||||
[InlineData("net7.0-x64-base")]
|
||||
[InlineData("net6-basd")]
|
||||
[InlineData("net6-x64-aakaka")]
|
||||
public void DotnetParseThrowsInvalidVersion(string input)
|
||||
{
|
||||
Assert.ThrowsAny<Exception>(() => Runtimes.DotnetInfo.Parse(input));
|
||||
}
|
||||
[Theory]
|
||||
[InlineData("net3.2")]
|
||||
[InlineData("net4.9")]
|
||||
[InlineData("net6.0.0.4")]
|
||||
[InlineData("net7.0-x64-base")]
|
||||
[InlineData("net6-basd")]
|
||||
[InlineData("net6-x64-aakaka")]
|
||||
public void DotnetParseThrowsInvalidVersion(string input)
|
||||
{
|
||||
Assert.ThrowsAny<Exception>(() => Runtimes.DotnetInfo.Parse(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("net6", true)]
|
||||
[InlineData("net20.0", true)]
|
||||
[InlineData("net5.0.14-x86", true)]
|
||||
[InlineData("netcoreapp3.1-x86", true)]
|
||||
[InlineData("net48", true)]
|
||||
[InlineData("netcoreapp4.8", false)]
|
||||
[InlineData("net4.8", false)]
|
||||
[InlineData("net2.5", false)]
|
||||
[InlineData("vcredist110-x64", true)]
|
||||
[InlineData("vcredist110-x86", true)]
|
||||
[InlineData("vcredist110", true)]
|
||||
[InlineData("vcredist143", true)]
|
||||
[InlineData("asd", false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("net6-x64", true)]
|
||||
[InlineData("net6-x64-runtime", true)]
|
||||
[InlineData("net6-x64-desktop", true)]
|
||||
public void GetRuntimeTests(string input, bool expected)
|
||||
{
|
||||
var dn = Runtimes.GetRuntimeByName(input);
|
||||
Assert.Equal(expected, dn != null);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData("net6", true)]
|
||||
[InlineData("net20.0", true)]
|
||||
[InlineData("net5.0.14-x86", true)]
|
||||
[InlineData("netcoreapp3.1-x86", true)]
|
||||
[InlineData("net48", true)]
|
||||
[InlineData("netcoreapp4.8", false)]
|
||||
[InlineData("net4.8", false)]
|
||||
[InlineData("net2.5", false)]
|
||||
[InlineData("vcredist110-x64", true)]
|
||||
[InlineData("vcredist110-x86", true)]
|
||||
[InlineData("vcredist110", true)]
|
||||
[InlineData("vcredist143", true)]
|
||||
[InlineData("asd", false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("net6-x64", true)]
|
||||
[InlineData("net6-x64-runtime", true)]
|
||||
[InlineData("net6-x64-desktop", true)]
|
||||
public void GetRuntimeTests(string input, bool expected)
|
||||
{
|
||||
var dn = Runtimes.GetRuntimeByName(input);
|
||||
Assert.Equal(expected, dn != null);
|
||||
}
|
||||
|
||||
[Theory(Skip = "Only run when needed")]
|
||||
[InlineData("3.1", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("3.1", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("3.1", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("3.1", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("3.1", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("3.1", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("5.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("5.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("5.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("5.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("5.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("5.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("7.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("7.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("7.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("7.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("7.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("7.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
public async Task MicrosoftReturnsValidDotnetDownload(string minversion, RuntimeCpu architecture, Runtimes.DotnetRuntimeType runtimeType)
|
||||
{
|
||||
var dni = new Runtimes.DotnetInfo(minversion, architecture, runtimeType);
|
||||
var url = await dni.GetDownloadUrl();
|
||||
[Theory(Skip = "Only run when needed")]
|
||||
[InlineData("3.1", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("3.1", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("3.1", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("3.1", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("3.1", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("3.1", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("5.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("5.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("5.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("5.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("5.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("5.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("7.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("7.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("7.0", RuntimeCpu.x86, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
[InlineData("7.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.WindowsDesktop)]
|
||||
[InlineData("7.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.Runtime)]
|
||||
[InlineData("7.0", RuntimeCpu.x64, Runtimes.DotnetRuntimeType.AspNetCore)]
|
||||
public async Task MicrosoftReturnsValidDotnetDownload(string minversion, RuntimeCpu architecture, Runtimes.DotnetRuntimeType runtimeType)
|
||||
{
|
||||
var dni = new Runtimes.DotnetInfo(minversion, architecture, runtimeType);
|
||||
var url = await dni.GetDownloadUrl();
|
||||
|
||||
Assert.Contains(minversion, url, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains(architecture.ToString(), url, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains(minversion, url, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains(architecture.ToString(), url, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (runtimeType == Runtimes.DotnetRuntimeType.Runtime)
|
||||
Assert.Matches(@"/dotnet-runtime-\d", url);
|
||||
else if (runtimeType == Runtimes.DotnetRuntimeType.AspNetCore)
|
||||
Assert.Matches(@"/aspnetcore-runtime-\d", url);
|
||||
else if (runtimeType == Runtimes.DotnetRuntimeType.WindowsDesktop)
|
||||
Assert.Matches(@"/windowsdesktop-runtime-\d", url);
|
||||
if (runtimeType == Runtimes.DotnetRuntimeType.Runtime)
|
||||
Assert.Matches(@"/dotnet-runtime-\d", url);
|
||||
else if (runtimeType == Runtimes.DotnetRuntimeType.AspNetCore)
|
||||
Assert.Matches(@"/aspnetcore-runtime-\d", url);
|
||||
else if (runtimeType == Runtimes.DotnetRuntimeType.WindowsDesktop)
|
||||
Assert.Matches(@"/windowsdesktop-runtime-\d", url);
|
||||
|
||||
using var hc = new HttpClient();
|
||||
var result = await hc.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
result.EnsureSuccessStatusCode();
|
||||
}
|
||||
using var hc = new HttpClient();
|
||||
var result = await hc.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
result.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
@@ -8,56 +8,55 @@ using System.Threading.Tasks;
|
||||
using Velopack.Locators;
|
||||
using Velopack.Windows;
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class ShortcutTests
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class ShortcutTests
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public ShortcutTests(ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
_output = output;
|
||||
}
|
||||
|
||||
public ShortcutTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
[SkippableFact]
|
||||
public void CanCreateAndRemoveShortcuts()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<ShortcutTests>();
|
||||
string exeName = "NotSquirrelAwareApp.exe";
|
||||
|
||||
[SkippableFact]
|
||||
public void CanCreateAndRemoveShortcuts()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<ShortcutTests>();
|
||||
string exeName = "NotSquirrelAwareApp.exe";
|
||||
using var _1 = Utility.GetTempDirectory(out var rootDir);
|
||||
var packages = Directory.CreateDirectory(Path.Combine(rootDir, "packages"));
|
||||
var current = Directory.CreateDirectory(Path.Combine(rootDir, "current"));
|
||||
|
||||
using var _1 = Utility.GetTempDirectory(out var rootDir);
|
||||
var packages = Directory.CreateDirectory(Path.Combine(rootDir, "packages"));
|
||||
var current = Directory.CreateDirectory(Path.Combine(rootDir, "current"));
|
||||
PathHelper.CopyFixtureTo("AvaloniaCrossPlat-1.0.15-win-full.nupkg", packages.FullName);
|
||||
PathHelper.CopyFixtureTo(exeName, current.FullName);
|
||||
|
||||
PathHelper.CopyFixtureTo("AvaloniaCrossPlat-1.0.15-win-full.nupkg", packages.FullName);
|
||||
PathHelper.CopyFixtureTo(exeName, current.FullName);
|
||||
var locator = new TestVelopackLocator("AvaloniaCrossPlat", "1.0.0", packages.FullName, current.FullName, rootDir, null, null, logger);
|
||||
var sh = new Shortcuts(logger, locator);
|
||||
var flag = ShortcutLocation.StartMenuRoot | ShortcutLocation.Desktop;
|
||||
sh.DeleteShortcuts(exeName, flag);
|
||||
sh.CreateShortcut(exeName, flag, false, "");
|
||||
var shortcuts = sh.FindShortcuts(exeName, flag);
|
||||
Assert.Equal(2, shortcuts.Keys.Count);
|
||||
|
||||
var locator = new TestVelopackLocator("AvaloniaCrossPlat", "1.0.0", packages.FullName, current.FullName, rootDir, null, null, logger);
|
||||
var sh = new Shortcuts(logger, locator);
|
||||
var flag = ShortcutLocation.StartMenuRoot | ShortcutLocation.Desktop;
|
||||
sh.DeleteShortcuts(exeName, flag);
|
||||
sh.CreateShortcut(exeName, flag, false, "");
|
||||
var shortcuts = sh.FindShortcuts(exeName, flag);
|
||||
Assert.Equal(2, shortcuts.Keys.Count);
|
||||
var startDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs");
|
||||
var desktopDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
|
||||
var lnkName = "SquirrelAwareApp.lnk";
|
||||
|
||||
var startDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs");
|
||||
var desktopDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
|
||||
var lnkName = "SquirrelAwareApp.lnk";
|
||||
var start = shortcuts[ShortcutLocation.StartMenuRoot];
|
||||
var desktop = shortcuts[ShortcutLocation.Desktop];
|
||||
|
||||
var start = shortcuts[ShortcutLocation.StartMenuRoot];
|
||||
var desktop = shortcuts[ShortcutLocation.Desktop];
|
||||
var target = Path.Combine(current.FullName, exeName);
|
||||
Assert.Equal(Path.Combine(startDir, lnkName), start.ShortCutFile);
|
||||
Assert.Equal(target, start.Target);
|
||||
Assert.Equal(Path.Combine(desktopDir, lnkName), desktop.ShortCutFile);
|
||||
Assert.Equal(target, desktop.Target);
|
||||
|
||||
var target = Path.Combine(current.FullName, exeName);
|
||||
Assert.Equal(Path.Combine(startDir, lnkName), start.ShortCutFile);
|
||||
Assert.Equal(target, start.Target);
|
||||
Assert.Equal(Path.Combine(desktopDir, lnkName), desktop.ShortCutFile);
|
||||
Assert.Equal(target, desktop.Target);
|
||||
|
||||
sh.DeleteShortcuts(exeName, flag);
|
||||
var after = sh.FindShortcuts(exeName, flag);
|
||||
Assert.Equal(0, after.Keys.Count);
|
||||
}
|
||||
sh.DeleteShortcuts(exeName, flag);
|
||||
var after = sh.FindShortcuts(exeName, flag);
|
||||
Assert.Equal(0, after.Keys.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,136 +16,135 @@ using SimpleJsonNameAttribute = System.Text.Json.Serialization.JsonPropertyNameA
|
||||
using SimpleJsonNameAttribute = Velopack.Json.JsonPropertyNameAttribute;
|
||||
#endif
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public class SimpleJsonTests
|
||||
{
|
||||
public class SimpleJsonTests
|
||||
public static readonly JsonSerializerOptions Options = new JsonSerializerOptions {
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Converters = { new JsonStringEnumConverter(), new SemanticVersionConverter() },
|
||||
};
|
||||
|
||||
internal class SemanticVersionConverter : JsonConverter<SemanticVersion>
|
||||
{
|
||||
public static readonly JsonSerializerOptions Options = new JsonSerializerOptions {
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Converters = { new JsonStringEnumConverter(), new SemanticVersionConverter() },
|
||||
};
|
||||
|
||||
internal class SemanticVersionConverter : JsonConverter<SemanticVersion>
|
||||
public override SemanticVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
public override SemanticVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return SemanticVersion.Parse(reader.GetString());
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, SemanticVersion value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToFullString());
|
||||
}
|
||||
return SemanticVersion.Parse(reader.GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonPropertyNameAttribueWrks()
|
||||
public override void Write(Utf8JsonWriter writer, SemanticVersion value, JsonSerializerOptions options)
|
||||
{
|
||||
var obj = new TestGithubReleaseAsset {
|
||||
UrlSomething = "https://ho",
|
||||
BrowserDownloadUrl = "https://browser",
|
||||
ContentType = "via",
|
||||
};
|
||||
var json = JsonSerializer.Serialize(obj, Options);
|
||||
var dez = SimpleJson.DeserializeObject<GithubReleaseAsset>(json);
|
||||
Assert.Equal(obj.UrlSomething, dez.Url);
|
||||
Assert.Equal(obj.BrowserDownloadUrl, dez.BrowserDownloadUrl);
|
||||
Assert.Equal(obj.ContentType, dez.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonCanRoundTripComplexTypes()
|
||||
{
|
||||
var obj = new TestClass1 {
|
||||
NameAsd = "hello",
|
||||
UpcomingRelease = true,
|
||||
ReleasedAt = DateTime.UtcNow,
|
||||
Version = SemanticVersion.Parse("1.2.3-hello.23+metadata"),
|
||||
AssetType = VelopackAssetType.Delta,
|
||||
Greetings = new List<string> { "hi", "there" },
|
||||
};
|
||||
var json = JsonSerializer.Serialize(obj, Options);
|
||||
|
||||
Assert.Contains("\"Delta\"", json);
|
||||
|
||||
var dez = SimpleJson.DeserializeObject<TestClass2>(json);
|
||||
Assert.Equal(obj.NameAsd, dez.nameAsd);
|
||||
Assert.Equal(obj.UpcomingRelease, dez.upcomingRelease);
|
||||
Assert.Equal(obj.ReleasedAt, dez.releasedAt);
|
||||
Assert.Equal(obj.Version, dez.version);
|
||||
Assert.Equal(obj.AssetType, dez.assetType);
|
||||
Assert.Equal(obj.Greetings, dez.greetings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonCanParseReleasesJson()
|
||||
{
|
||||
var json = File.ReadAllText(PathHelper.GetFixture("testfeed.json"));
|
||||
var feed = SimpleJson.DeserializeObject<VelopackAssetFeed>(json);
|
||||
Assert.Equal(21, feed.Assets.Length);
|
||||
Assert.True(feed.Assets.First().Version == new SemanticVersion(1, 0, 11));
|
||||
}
|
||||
|
||||
public class TestGithubReleaseAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// The asset URL for this release asset. Requests to this URL will use API
|
||||
/// quota and return JSON unless the 'Accept' header is "application/octet-stream".
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string UrlSomething { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The browser URL for this release asset. This does not use API quota,
|
||||
/// however this URL only works for public repositories. If downloading
|
||||
/// assets from a private repository, the <see cref="Url"/> property must
|
||||
/// be used with an appropriate access token.
|
||||
/// </summary>
|
||||
[JsonPropertyName("browser_download_url")]
|
||||
public string BrowserDownloadUrl { get; set; }
|
||||
|
||||
/// <summary> The mime type of this release asset (as detected by GitHub). </summary>
|
||||
[JsonPropertyName("content_type")]
|
||||
public string ContentType { get; set; }
|
||||
}
|
||||
|
||||
|
||||
internal class TestClass1
|
||||
{
|
||||
public string NameAsd { get; set; }
|
||||
|
||||
[JsonPropertyName("upcoming_release888")]
|
||||
public bool UpcomingRelease { get; set; }
|
||||
|
||||
[JsonPropertyName("released_at")]
|
||||
public DateTime ReleasedAt { get; set; }
|
||||
|
||||
public SemanticVersion Version { get; set; }
|
||||
|
||||
[JsonPropertyName("t")]
|
||||
public VelopackAssetType AssetType { get; set; }
|
||||
|
||||
public List<string> Greetings { get; set; }
|
||||
}
|
||||
|
||||
internal class TestClass2
|
||||
{
|
||||
public string nameAsd { get; set; }
|
||||
|
||||
[SimpleJsonName("upcoming_release888")]
|
||||
public bool upcomingRelease { get; set; }
|
||||
|
||||
[SimpleJsonName("released_at")]
|
||||
public DateTime releasedAt { get; set; }
|
||||
|
||||
public SemanticVersion version { get; set; }
|
||||
|
||||
[SimpleJsonName("t")]
|
||||
public VelopackAssetType assetType { get; set; }
|
||||
|
||||
public List<string> greetings { get; set; }
|
||||
writer.WriteStringValue(value.ToFullString());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonPropertyNameAttribueWrks()
|
||||
{
|
||||
var obj = new TestGithubReleaseAsset {
|
||||
UrlSomething = "https://ho",
|
||||
BrowserDownloadUrl = "https://browser",
|
||||
ContentType = "via",
|
||||
};
|
||||
var json = JsonSerializer.Serialize(obj, Options);
|
||||
var dez = SimpleJson.DeserializeObject<GithubReleaseAsset>(json);
|
||||
Assert.Equal(obj.UrlSomething, dez.Url);
|
||||
Assert.Equal(obj.BrowserDownloadUrl, dez.BrowserDownloadUrl);
|
||||
Assert.Equal(obj.ContentType, dez.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonCanRoundTripComplexTypes()
|
||||
{
|
||||
var obj = new TestClass1 {
|
||||
NameAsd = "hello",
|
||||
UpcomingRelease = true,
|
||||
ReleasedAt = DateTime.UtcNow,
|
||||
Version = SemanticVersion.Parse("1.2.3-hello.23+metadata"),
|
||||
AssetType = VelopackAssetType.Delta,
|
||||
Greetings = new List<string> { "hi", "there" },
|
||||
};
|
||||
var json = JsonSerializer.Serialize(obj, Options);
|
||||
|
||||
Assert.Contains("\"Delta\"", json);
|
||||
|
||||
var dez = SimpleJson.DeserializeObject<TestClass2>(json);
|
||||
Assert.Equal(obj.NameAsd, dez.nameAsd);
|
||||
Assert.Equal(obj.UpcomingRelease, dez.upcomingRelease);
|
||||
Assert.Equal(obj.ReleasedAt, dez.releasedAt);
|
||||
Assert.Equal(obj.Version, dez.version);
|
||||
Assert.Equal(obj.AssetType, dez.assetType);
|
||||
Assert.Equal(obj.Greetings, dez.greetings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonCanParseReleasesJson()
|
||||
{
|
||||
var json = File.ReadAllText(PathHelper.GetFixture("testfeed.json"));
|
||||
var feed = SimpleJson.DeserializeObject<VelopackAssetFeed>(json);
|
||||
Assert.Equal(21, feed.Assets.Length);
|
||||
Assert.True(feed.Assets.First().Version == new SemanticVersion(1, 0, 11));
|
||||
}
|
||||
|
||||
public class TestGithubReleaseAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// The asset URL for this release asset. Requests to this URL will use API
|
||||
/// quota and return JSON unless the 'Accept' header is "application/octet-stream".
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string UrlSomething { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The browser URL for this release asset. This does not use API quota,
|
||||
/// however this URL only works for public repositories. If downloading
|
||||
/// assets from a private repository, the <see cref="Url"/> property must
|
||||
/// be used with an appropriate access token.
|
||||
/// </summary>
|
||||
[JsonPropertyName("browser_download_url")]
|
||||
public string BrowserDownloadUrl { get; set; }
|
||||
|
||||
/// <summary> The mime type of this release asset (as detected by GitHub). </summary>
|
||||
[JsonPropertyName("content_type")]
|
||||
public string ContentType { get; set; }
|
||||
}
|
||||
|
||||
|
||||
internal class TestClass1
|
||||
{
|
||||
public string NameAsd { get; set; }
|
||||
|
||||
[JsonPropertyName("upcoming_release888")]
|
||||
public bool UpcomingRelease { get; set; }
|
||||
|
||||
[JsonPropertyName("released_at")]
|
||||
public DateTime ReleasedAt { get; set; }
|
||||
|
||||
public SemanticVersion Version { get; set; }
|
||||
|
||||
[JsonPropertyName("t")]
|
||||
public VelopackAssetType AssetType { get; set; }
|
||||
|
||||
public List<string> Greetings { get; set; }
|
||||
}
|
||||
|
||||
internal class TestClass2
|
||||
{
|
||||
public string nameAsd { get; set; }
|
||||
|
||||
[SimpleJsonName("upcoming_release888")]
|
||||
public bool upcomingRelease { get; set; }
|
||||
|
||||
[SimpleJsonName("released_at")]
|
||||
public DateTime releasedAt { get; set; }
|
||||
|
||||
public SemanticVersion version { get; set; }
|
||||
|
||||
[SimpleJsonName("t")]
|
||||
public VelopackAssetType assetType { get; set; }
|
||||
|
||||
public List<string> greetings { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,147 +1,146 @@
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Velopack.Tests.TestHelpers
|
||||
namespace Velopack.Tests.TestHelpers;
|
||||
|
||||
public static class AssertExtensions
|
||||
{
|
||||
public static class AssertExtensions
|
||||
public static void ShouldBeAboutEqualTo(this DateTimeOffset expected, DateTimeOffset current)
|
||||
{
|
||||
public static void ShouldBeAboutEqualTo(this DateTimeOffset expected, DateTimeOffset current)
|
||||
{
|
||||
Assert.Equal(expected.Date, current.Date);
|
||||
Assert.Equal(expected.Offset, current.Offset);
|
||||
Assert.Equal(expected.Hour, current.Hour);
|
||||
Assert.Equal(expected.Minute, current.Minute);
|
||||
Assert.Equal(expected.Second, current.Second);
|
||||
}
|
||||
|
||||
public static void ShouldBeFalse(this bool currentObject)
|
||||
{
|
||||
Assert.False(currentObject);
|
||||
}
|
||||
|
||||
public static void ShouldBeNull(this object currentObject)
|
||||
{
|
||||
Assert.Null(currentObject);
|
||||
}
|
||||
|
||||
public static void ShouldBeEmpty(this IEnumerable items)
|
||||
{
|
||||
Assert.Empty(items);
|
||||
}
|
||||
|
||||
public static void ShouldNotBeEmpty(this IEnumerable items)
|
||||
{
|
||||
Assert.NotEmpty(items);
|
||||
}
|
||||
|
||||
public static void ShouldBeTrue(this bool currentObject)
|
||||
{
|
||||
Assert.True(currentObject);
|
||||
}
|
||||
|
||||
public static void ShouldEqual(this object compareFrom, object compareTo)
|
||||
{
|
||||
Assert.Equal(compareTo, compareFrom);
|
||||
}
|
||||
|
||||
public static void ShouldEqual<T>(this T compareFrom, T compareTo)
|
||||
{
|
||||
Assert.Equal(compareTo, compareFrom);
|
||||
}
|
||||
|
||||
public static void ShouldBeSameAs<T>(this T actual, T expected)
|
||||
{
|
||||
Assert.Same(expected, actual);
|
||||
}
|
||||
|
||||
public static void ShouldNotBeSameAs<T>(this T actual, T expected)
|
||||
{
|
||||
Assert.NotSame(expected, actual);
|
||||
}
|
||||
|
||||
public static void ShouldBeAssignableFrom<T>(this object instance) where T : class
|
||||
{
|
||||
Assert.IsAssignableFrom<T>(instance);
|
||||
}
|
||||
|
||||
public static void ShouldBeType(this object instance, Type type)
|
||||
{
|
||||
Assert.IsType(type, instance);
|
||||
}
|
||||
|
||||
public static void ShouldBeType<T>(this object instance)
|
||||
{
|
||||
Assert.IsType<T>(instance);
|
||||
}
|
||||
|
||||
public static void ShouldNotBeType<T>(this object instance)
|
||||
{
|
||||
Assert.IsNotType<T>(instance);
|
||||
}
|
||||
|
||||
public static void ShouldContain(this string current, string expectedSubstring, StringComparison comparison)
|
||||
{
|
||||
Assert.Contains(expectedSubstring, current, comparison);
|
||||
}
|
||||
|
||||
public static void ShouldStartWith(this string current, string expectedSubstring, StringComparison comparison)
|
||||
{
|
||||
Assert.True(current.StartsWith(expectedSubstring, comparison));
|
||||
}
|
||||
|
||||
public static void ShouldNotBeNull(this object currentObject)
|
||||
{
|
||||
Assert.NotNull(currentObject);
|
||||
}
|
||||
|
||||
public static void ShouldNotBeNullNorEmpty(this string value)
|
||||
{
|
||||
Assert.NotNull(value);
|
||||
Assert.NotEmpty(value);
|
||||
}
|
||||
|
||||
public static void ShouldNotEqual(this object compareFrom, object compareTo)
|
||||
{
|
||||
Assert.NotEqual(compareTo, compareFrom);
|
||||
}
|
||||
|
||||
public static void ShouldBeGreaterThan<T>(this T current, T other) where T : IComparable
|
||||
{
|
||||
Assert.True(current.CompareTo(other) > 0, current + " is not greater than " + other);
|
||||
}
|
||||
|
||||
public static void ShouldBeLessThan<T>(this T current, T other) where T : IComparable
|
||||
{
|
||||
Assert.True(current.CompareTo(other) < 0, current + " is not less than " + other);
|
||||
}
|
||||
|
||||
static string ToSafeString(this char c)
|
||||
{
|
||||
if (Char.IsControl(c) || Char.IsWhiteSpace(c)) {
|
||||
switch (c) {
|
||||
case '\r':
|
||||
return @"\r";
|
||||
case '\n':
|
||||
return @"\n";
|
||||
case '\t':
|
||||
return @"\t";
|
||||
case '\a':
|
||||
return @"\a";
|
||||
case '\v':
|
||||
return @"\v";
|
||||
case '\f':
|
||||
return @"\f";
|
||||
default:
|
||||
return String.Format("\\u{0:X};", (int) c);
|
||||
}
|
||||
}
|
||||
return c.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
Assert.Equal(expected.Date, current.Date);
|
||||
Assert.Equal(expected.Offset, current.Offset);
|
||||
Assert.Equal(expected.Hour, current.Hour);
|
||||
Assert.Equal(expected.Minute, current.Minute);
|
||||
Assert.Equal(expected.Second, current.Second);
|
||||
}
|
||||
|
||||
public enum DiffStyle
|
||||
public static void ShouldBeFalse(this bool currentObject)
|
||||
{
|
||||
Full,
|
||||
Minimal
|
||||
Assert.False(currentObject);
|
||||
}
|
||||
|
||||
public static void ShouldBeNull(this object currentObject)
|
||||
{
|
||||
Assert.Null(currentObject);
|
||||
}
|
||||
|
||||
public static void ShouldBeEmpty(this IEnumerable items)
|
||||
{
|
||||
Assert.Empty(items);
|
||||
}
|
||||
|
||||
public static void ShouldNotBeEmpty(this IEnumerable items)
|
||||
{
|
||||
Assert.NotEmpty(items);
|
||||
}
|
||||
|
||||
public static void ShouldBeTrue(this bool currentObject)
|
||||
{
|
||||
Assert.True(currentObject);
|
||||
}
|
||||
|
||||
public static void ShouldEqual(this object compareFrom, object compareTo)
|
||||
{
|
||||
Assert.Equal(compareTo, compareFrom);
|
||||
}
|
||||
|
||||
public static void ShouldEqual<T>(this T compareFrom, T compareTo)
|
||||
{
|
||||
Assert.Equal(compareTo, compareFrom);
|
||||
}
|
||||
|
||||
public static void ShouldBeSameAs<T>(this T actual, T expected)
|
||||
{
|
||||
Assert.Same(expected, actual);
|
||||
}
|
||||
|
||||
public static void ShouldNotBeSameAs<T>(this T actual, T expected)
|
||||
{
|
||||
Assert.NotSame(expected, actual);
|
||||
}
|
||||
|
||||
public static void ShouldBeAssignableFrom<T>(this object instance) where T : class
|
||||
{
|
||||
Assert.IsAssignableFrom<T>(instance);
|
||||
}
|
||||
|
||||
public static void ShouldBeType(this object instance, Type type)
|
||||
{
|
||||
Assert.IsType(type, instance);
|
||||
}
|
||||
|
||||
public static void ShouldBeType<T>(this object instance)
|
||||
{
|
||||
Assert.IsType<T>(instance);
|
||||
}
|
||||
|
||||
public static void ShouldNotBeType<T>(this object instance)
|
||||
{
|
||||
Assert.IsNotType<T>(instance);
|
||||
}
|
||||
|
||||
public static void ShouldContain(this string current, string expectedSubstring, StringComparison comparison)
|
||||
{
|
||||
Assert.Contains(expectedSubstring, current, comparison);
|
||||
}
|
||||
|
||||
public static void ShouldStartWith(this string current, string expectedSubstring, StringComparison comparison)
|
||||
{
|
||||
Assert.True(current.StartsWith(expectedSubstring, comparison));
|
||||
}
|
||||
|
||||
public static void ShouldNotBeNull(this object currentObject)
|
||||
{
|
||||
Assert.NotNull(currentObject);
|
||||
}
|
||||
|
||||
public static void ShouldNotBeNullNorEmpty(this string value)
|
||||
{
|
||||
Assert.NotNull(value);
|
||||
Assert.NotEmpty(value);
|
||||
}
|
||||
|
||||
public static void ShouldNotEqual(this object compareFrom, object compareTo)
|
||||
{
|
||||
Assert.NotEqual(compareTo, compareFrom);
|
||||
}
|
||||
|
||||
public static void ShouldBeGreaterThan<T>(this T current, T other) where T : IComparable
|
||||
{
|
||||
Assert.True(current.CompareTo(other) > 0, current + " is not greater than " + other);
|
||||
}
|
||||
|
||||
public static void ShouldBeLessThan<T>(this T current, T other) where T : IComparable
|
||||
{
|
||||
Assert.True(current.CompareTo(other) < 0, current + " is not less than " + other);
|
||||
}
|
||||
|
||||
static string ToSafeString(this char c)
|
||||
{
|
||||
if (Char.IsControl(c) || Char.IsWhiteSpace(c)) {
|
||||
switch (c) {
|
||||
case '\r':
|
||||
return @"\r";
|
||||
case '\n':
|
||||
return @"\n";
|
||||
case '\t':
|
||||
return @"\t";
|
||||
case '\a':
|
||||
return @"\a";
|
||||
case '\v':
|
||||
return @"\v";
|
||||
case '\f':
|
||||
return @"\f";
|
||||
default:
|
||||
return String.Format("\\u{0:X};", (int) c);
|
||||
}
|
||||
}
|
||||
return c.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DiffStyle
|
||||
{
|
||||
Full,
|
||||
Minimal
|
||||
}
|
||||
|
||||
@@ -6,133 +6,132 @@ using System.Reflection;
|
||||
|
||||
// Lovingly stolen from http://exposedobject.codeplex.com/
|
||||
|
||||
namespace Velopack.Tests.TestHelpers
|
||||
namespace Velopack.Tests.TestHelpers;
|
||||
|
||||
public class ExposedClass : DynamicObject
|
||||
{
|
||||
public class ExposedClass : DynamicObject
|
||||
private Type m_type;
|
||||
private Dictionary<string, Dictionary<int, List<MethodInfo>>> m_staticMethods;
|
||||
private Dictionary<string, Dictionary<int, List<MethodInfo>>> m_genStaticMethods;
|
||||
|
||||
private ExposedClass(Type type)
|
||||
{
|
||||
private Type m_type;
|
||||
private Dictionary<string, Dictionary<int, List<MethodInfo>>> m_staticMethods;
|
||||
private Dictionary<string, Dictionary<int, List<MethodInfo>>> m_genStaticMethods;
|
||||
m_type = type;
|
||||
|
||||
private ExposedClass(Type type)
|
||||
m_staticMethods =
|
||||
m_type
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(m => !m.IsGenericMethod)
|
||||
.GroupBy(m => m.Name)
|
||||
.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.GroupBy(r => r.GetParameters().Length).ToDictionary(r => r.Key, r => r.ToList()));
|
||||
|
||||
m_genStaticMethods =
|
||||
m_type
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(m => m.IsGenericMethod)
|
||||
.GroupBy(m => m.Name)
|
||||
.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.GroupBy(r => r.GetParameters().Length).ToDictionary(r => r.Key, r => r.ToList()));
|
||||
}
|
||||
|
||||
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
|
||||
{
|
||||
// Get type args of the call
|
||||
Type[] typeArgs = ExposedObjectHelper.GetTypeArgs(binder);
|
||||
if (typeArgs != null && typeArgs.Length == 0) typeArgs = null;
|
||||
|
||||
//
|
||||
// Try to call a non-generic instance method
|
||||
//
|
||||
if (typeArgs == null
|
||||
&& m_staticMethods.ContainsKey(binder.Name)
|
||||
&& m_staticMethods[binder.Name].ContainsKey(args.Length)
|
||||
&& ExposedObjectHelper.InvokeBestMethod(args, null, m_staticMethods[binder.Name][args.Length], out result))
|
||||
{
|
||||
m_type = type;
|
||||
|
||||
m_staticMethods =
|
||||
m_type
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(m => !m.IsGenericMethod)
|
||||
.GroupBy(m => m.Name)
|
||||
.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.GroupBy(r => r.GetParameters().Length).ToDictionary(r => r.Key, r => r.ToList()));
|
||||
|
||||
m_genStaticMethods =
|
||||
m_type
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(m => m.IsGenericMethod)
|
||||
.GroupBy(m => m.Name)
|
||||
.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.GroupBy(r => r.GetParameters().Length).ToDictionary(r => r.Key, r => r.ToList()));
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
|
||||
//
|
||||
// Try to call a generic instance method
|
||||
//
|
||||
if (m_staticMethods.ContainsKey(binder.Name)
|
||||
&& m_staticMethods[binder.Name].ContainsKey(args.Length))
|
||||
{
|
||||
// Get type args of the call
|
||||
Type[] typeArgs = ExposedObjectHelper.GetTypeArgs(binder);
|
||||
if (typeArgs != null && typeArgs.Length == 0) typeArgs = null;
|
||||
List<MethodInfo> methods = new List<MethodInfo>();
|
||||
|
||||
//
|
||||
// Try to call a non-generic instance method
|
||||
//
|
||||
if (typeArgs == null
|
||||
&& m_staticMethods.ContainsKey(binder.Name)
|
||||
&& m_staticMethods[binder.Name].ContainsKey(args.Length)
|
||||
&& ExposedObjectHelper.InvokeBestMethod(args, null, m_staticMethods[binder.Name][args.Length], out result))
|
||||
foreach (var method in m_genStaticMethods[binder.Name][args.Length])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Try to call a generic instance method
|
||||
//
|
||||
if (m_staticMethods.ContainsKey(binder.Name)
|
||||
&& m_staticMethods[binder.Name].ContainsKey(args.Length))
|
||||
{
|
||||
List<MethodInfo> methods = new List<MethodInfo>();
|
||||
|
||||
foreach (var method in m_genStaticMethods[binder.Name][args.Length])
|
||||
if (method.GetGenericArguments().Length == typeArgs.Length)
|
||||
{
|
||||
if (method.GetGenericArguments().Length == typeArgs.Length)
|
||||
{
|
||||
methods.Add(method.MakeGenericMethod(typeArgs));
|
||||
}
|
||||
}
|
||||
|
||||
if (ExposedObjectHelper.InvokeBestMethod(args, null, methods, out result))
|
||||
{
|
||||
return true;
|
||||
methods.Add(method.MakeGenericMethod(typeArgs));
|
||||
}
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
if (ExposedObjectHelper.InvokeBestMethod(args, null, methods, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
||||
{
|
||||
var propertyInfo = m_type.GetProperty(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
var propertyInfo = m_type.GetProperty(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
propertyInfo.SetValue(null, value, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
var fieldInfo = m_type.GetField(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
fieldInfo.SetValue(null, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
propertyInfo.SetValue(null, value, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object result)
|
||||
var fieldInfo = m_type.GetField(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
var propertyInfo = m_type.GetProperty(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
result = propertyInfo.GetValue(null, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
var fieldInfo = m_type.GetField(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
result = fieldInfo.GetValue(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
fieldInfo.SetValue(null, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static dynamic From(Type type)
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object result)
|
||||
{
|
||||
var propertyInfo = m_type.GetProperty(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
return new ExposedClass(type);
|
||||
result = propertyInfo.GetValue(null, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
var fieldInfo = m_type.GetField(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
result = fieldInfo.GetValue(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static dynamic From(Type type)
|
||||
{
|
||||
return new ExposedClass(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,164 +7,162 @@ using System.Reflection;
|
||||
|
||||
// Lovingly stolen from http://exposedobject.codeplex.com/
|
||||
|
||||
namespace Velopack.Tests.TestHelpers
|
||||
namespace Velopack.Tests.TestHelpers;
|
||||
|
||||
public class ExposedObject : DynamicObject
|
||||
{
|
||||
public class ExposedObject : DynamicObject
|
||||
private object m_object;
|
||||
private Type m_type;
|
||||
private Dictionary<string, Dictionary<int, List<MethodInfo>>> m_instanceMethods;
|
||||
private Dictionary<string, Dictionary<int, List<MethodInfo>>> m_genInstanceMethods;
|
||||
|
||||
private ExposedObject(object obj)
|
||||
{
|
||||
private object m_object;
|
||||
private Type m_type;
|
||||
private Dictionary<string, Dictionary<int, List<MethodInfo>>> m_instanceMethods;
|
||||
private Dictionary<string, Dictionary<int, List<MethodInfo>>> m_genInstanceMethods;
|
||||
m_object = obj;
|
||||
m_type = obj.GetType();
|
||||
|
||||
private ExposedObject(object obj)
|
||||
{
|
||||
m_object = obj;
|
||||
m_type = obj.GetType();
|
||||
m_instanceMethods =
|
||||
m_type
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => !m.IsGenericMethod)
|
||||
.GroupBy(m => m.Name)
|
||||
.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.GroupBy(r => r.GetParameters().Length).ToDictionary(r => r.Key, r => r.ToList()));
|
||||
|
||||
m_instanceMethods =
|
||||
m_type
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => !m.IsGenericMethod)
|
||||
.GroupBy(m => m.Name)
|
||||
.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.GroupBy(r => r.GetParameters().Length).ToDictionary(r => r.Key, r => r.ToList()));
|
||||
|
||||
m_genInstanceMethods =
|
||||
m_type
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => m.IsGenericMethod)
|
||||
.GroupBy(m => m.Name)
|
||||
.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.GroupBy(r => r.GetParameters().Length).ToDictionary(r => r.Key, r => r.ToList()));
|
||||
}
|
||||
|
||||
public object Object { get { return m_object; } }
|
||||
|
||||
public static dynamic New<T>()
|
||||
{
|
||||
return New(typeof(T));
|
||||
}
|
||||
|
||||
public static dynamic New(Type type)
|
||||
{
|
||||
return new ExposedObject(Activator.CreateInstance(type));
|
||||
}
|
||||
|
||||
public static dynamic From(object obj)
|
||||
{
|
||||
return new ExposedObject(obj);
|
||||
}
|
||||
|
||||
public static T Cast<T>(ExposedObject t)
|
||||
{
|
||||
return (T)t.m_object;
|
||||
}
|
||||
|
||||
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
|
||||
{
|
||||
// Get type args of the call
|
||||
Type[] typeArgs = ExposedObjectHelper.GetTypeArgs(binder);
|
||||
if (typeArgs != null && typeArgs.Length == 0) typeArgs = null;
|
||||
|
||||
//
|
||||
// Try to call a non-generic instance method
|
||||
//
|
||||
if (typeArgs == null
|
||||
&& m_instanceMethods.ContainsKey(binder.Name)
|
||||
&& m_instanceMethods[binder.Name].ContainsKey(args.Length)
|
||||
&& ExposedObjectHelper.InvokeBestMethod(args, m_object, m_instanceMethods[binder.Name][args.Length], out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Try to call a generic instance method
|
||||
//
|
||||
if (m_instanceMethods.ContainsKey(binder.Name)
|
||||
&& m_instanceMethods[binder.Name].ContainsKey(args.Length))
|
||||
{
|
||||
List<MethodInfo> methods = new List<MethodInfo>();
|
||||
|
||||
if (m_genInstanceMethods.ContainsKey(binder.Name) &&
|
||||
m_genInstanceMethods[binder.Name].ContainsKey(args.Length))
|
||||
{
|
||||
foreach (var method in m_genInstanceMethods[binder.Name][args.Length])
|
||||
{
|
||||
if (method.GetGenericArguments().Length == typeArgs.Length)
|
||||
{
|
||||
methods.Add(method.MakeGenericMethod(typeArgs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ExposedObjectHelper.InvokeBestMethod(args, m_object, methods, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
||||
{
|
||||
var propertyInfo = m_type.GetProperty(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
propertyInfo.SetValue(m_object, value, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
var fieldInfo = m_type.GetField(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
fieldInfo.SetValue(m_object, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object result)
|
||||
{
|
||||
var propertyInfo = m_object.GetType().GetProperty(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
result = propertyInfo.GetValue(m_object, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
var fieldInfo = m_object.GetType().GetField(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
result = fieldInfo.GetValue(m_object);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryConvert(ConvertBinder binder, out object result)
|
||||
{
|
||||
result = m_object;
|
||||
return true;
|
||||
}
|
||||
m_genInstanceMethods =
|
||||
m_type
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => m.IsGenericMethod)
|
||||
.GroupBy(m => m.Name)
|
||||
.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.GroupBy(r => r.GetParameters().Length).ToDictionary(r => r.Key, r => r.ToList()));
|
||||
}
|
||||
|
||||
public object Object { get { return m_object; } }
|
||||
|
||||
public static dynamic New<T>()
|
||||
{
|
||||
return New(typeof(T));
|
||||
}
|
||||
|
||||
public static dynamic New(Type type)
|
||||
{
|
||||
return new ExposedObject(Activator.CreateInstance(type));
|
||||
}
|
||||
|
||||
public static dynamic From(object obj)
|
||||
{
|
||||
return new ExposedObject(obj);
|
||||
}
|
||||
|
||||
public static T Cast<T>(ExposedObject t)
|
||||
{
|
||||
return (T)t.m_object;
|
||||
}
|
||||
|
||||
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
|
||||
{
|
||||
// Get type args of the call
|
||||
Type[] typeArgs = ExposedObjectHelper.GetTypeArgs(binder);
|
||||
if (typeArgs != null && typeArgs.Length == 0) typeArgs = null;
|
||||
|
||||
//
|
||||
// Try to call a non-generic instance method
|
||||
//
|
||||
if (typeArgs == null
|
||||
&& m_instanceMethods.ContainsKey(binder.Name)
|
||||
&& m_instanceMethods[binder.Name].ContainsKey(args.Length)
|
||||
&& ExposedObjectHelper.InvokeBestMethod(args, m_object, m_instanceMethods[binder.Name][args.Length], out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Try to call a generic instance method
|
||||
//
|
||||
if (m_instanceMethods.ContainsKey(binder.Name)
|
||||
&& m_instanceMethods[binder.Name].ContainsKey(args.Length))
|
||||
{
|
||||
List<MethodInfo> methods = new List<MethodInfo>();
|
||||
|
||||
if (m_genInstanceMethods.ContainsKey(binder.Name) &&
|
||||
m_genInstanceMethods[binder.Name].ContainsKey(args.Length))
|
||||
{
|
||||
foreach (var method in m_genInstanceMethods[binder.Name][args.Length])
|
||||
{
|
||||
if (method.GetGenericArguments().Length == typeArgs.Length)
|
||||
{
|
||||
methods.Add(method.MakeGenericMethod(typeArgs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ExposedObjectHelper.InvokeBestMethod(args, m_object, methods, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
||||
{
|
||||
var propertyInfo = m_type.GetProperty(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
propertyInfo.SetValue(m_object, value, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
var fieldInfo = m_type.GetField(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
fieldInfo.SetValue(m_object, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object result)
|
||||
{
|
||||
var propertyInfo = m_object.GetType().GetProperty(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
result = propertyInfo.GetValue(m_object, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
var fieldInfo = m_object.GetType().GetField(
|
||||
binder.Name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
result = fieldInfo.GetValue(m_object);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryConvert(ConvertBinder binder, out object result)
|
||||
{
|
||||
result = m_object;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,89 +6,88 @@ using System.Dynamic;
|
||||
|
||||
// Lovingly stolen from http://exposedobject.codeplex.com/
|
||||
|
||||
namespace Velopack.Tests.TestHelpers
|
||||
namespace Velopack.Tests.TestHelpers;
|
||||
|
||||
internal class ExposedObjectHelper
|
||||
{
|
||||
internal class ExposedObjectHelper
|
||||
private static Type s_csharpInvokePropertyType =
|
||||
typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
|
||||
.Assembly
|
||||
.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
|
||||
|
||||
internal static bool InvokeBestMethod(object[] args, object target, List<MethodInfo> instanceMethods, out object result)
|
||||
{
|
||||
private static Type s_csharpInvokePropertyType =
|
||||
typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
|
||||
.Assembly
|
||||
.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
|
||||
|
||||
internal static bool InvokeBestMethod(object[] args, object target, List<MethodInfo> instanceMethods, out object result)
|
||||
if (instanceMethods.Count == 1)
|
||||
{
|
||||
if (instanceMethods.Count == 1)
|
||||
// Just one matching instance method - call it
|
||||
if (TryInvoke(instanceMethods[0], target, args, out result))
|
||||
{
|
||||
// Just one matching instance method - call it
|
||||
if (TryInvoke(instanceMethods[0], target, args, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (instanceMethods.Count > 1)
|
||||
{
|
||||
// Find a method with best matching parameters
|
||||
MethodInfo best = null;
|
||||
Type[] bestParams = null;
|
||||
Type[] actualParams = args.Select(p => p == null ? typeof(object) : p.GetType()).ToArray();
|
||||
|
||||
Func<Type[], Type[], bool> isAssignableFrom = (a, b) =>
|
||||
{
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
{
|
||||
if (!a[i].IsAssignableFrom(b[i])) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
foreach (var method in instanceMethods.Where(m => m.GetParameters().Length == args.Length))
|
||||
{
|
||||
Type[] mParams = method.GetParameters().Select(x => x.ParameterType).ToArray();
|
||||
if (isAssignableFrom(mParams, actualParams))
|
||||
{
|
||||
if (best == null || isAssignableFrom(bestParams, mParams))
|
||||
{
|
||||
best = method;
|
||||
bestParams = mParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best != null && TryInvoke(best, target, args, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryInvoke(MethodInfo methodInfo, object target, object[] args, out object result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = methodInfo.Invoke(target, args);
|
||||
return true;
|
||||
}
|
||||
catch (TargetInvocationException) { }
|
||||
catch (TargetParameterCountException) { }
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
internal static Type[] GetTypeArgs(InvokeMemberBinder binder)
|
||||
else if (instanceMethods.Count > 1)
|
||||
{
|
||||
if (s_csharpInvokePropertyType.IsInstanceOfType(binder))
|
||||
// Find a method with best matching parameters
|
||||
MethodInfo best = null;
|
||||
Type[] bestParams = null;
|
||||
Type[] actualParams = args.Select(p => p == null ? typeof(object) : p.GetType()).ToArray();
|
||||
|
||||
Func<Type[], Type[], bool> isAssignableFrom = (a, b) =>
|
||||
{
|
||||
PropertyInfo typeArgsProperty = s_csharpInvokePropertyType.GetProperty("TypeArguments");
|
||||
return ((IEnumerable<Type>)typeArgsProperty.GetValue(binder, null)).ToArray();
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
{
|
||||
if (!a[i].IsAssignableFrom(b[i])) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
foreach (var method in instanceMethods.Where(m => m.GetParameters().Length == args.Length))
|
||||
{
|
||||
Type[] mParams = method.GetParameters().Select(x => x.ParameterType).ToArray();
|
||||
if (isAssignableFrom(mParams, actualParams))
|
||||
{
|
||||
if (best == null || isAssignableFrom(bestParams, mParams))
|
||||
{
|
||||
best = method;
|
||||
bestParams = mParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best != null && TryInvoke(best, target, args, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryInvoke(MethodInfo methodInfo, object target, object[] args, out object result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = methodInfo.Invoke(target, args);
|
||||
return true;
|
||||
}
|
||||
catch (TargetInvocationException) { }
|
||||
catch (TargetParameterCountException) { }
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
internal static Type[] GetTypeArgs(InvokeMemberBinder binder)
|
||||
{
|
||||
if (s_csharpInvokePropertyType.IsInstanceOfType(binder))
|
||||
{
|
||||
PropertyInfo typeArgsProperty = s_csharpInvokePropertyType.GetProperty("TypeArguments");
|
||||
return ((IEnumerable<Type>)typeArgsProperty.GetValue(binder, null)).ToArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public class FakeDownloader : Sources.IFileDownloader
|
||||
{
|
||||
public class FakeDownloader : Sources.IFileDownloader
|
||||
public string LastUrl { get; private set; }
|
||||
public string LastLocalFile { get; private set; }
|
||||
public string LastAuthHeader { get; private set; }
|
||||
public string LastAcceptHeader { get; private set; }
|
||||
public byte[] MockedResponseBytes { get; set; } = new byte[0];
|
||||
public bool WriteMockLocalFile { get; set; } = false;
|
||||
|
||||
public Task<byte[]> DownloadBytes(string url, string auth, string acc)
|
||||
{
|
||||
public string LastUrl { get; private set; }
|
||||
public string LastLocalFile { get; private set; }
|
||||
public string LastAuthHeader { get; private set; }
|
||||
public string LastAcceptHeader { get; private set; }
|
||||
public byte[] MockedResponseBytes { get; set; } = new byte[0];
|
||||
public bool WriteMockLocalFile { get; set; } = false;
|
||||
LastUrl = url;
|
||||
LastAuthHeader = auth;
|
||||
LastAcceptHeader = acc;
|
||||
return Task.FromResult(MockedResponseBytes);
|
||||
}
|
||||
|
||||
public Task<byte[]> DownloadBytes(string url, string auth, string acc)
|
||||
{
|
||||
LastUrl = url;
|
||||
LastAuthHeader = auth;
|
||||
LastAcceptHeader = acc;
|
||||
return Task.FromResult(MockedResponseBytes);
|
||||
}
|
||||
public async Task DownloadFile(string url, string targetFile, Action<int> progress, string auth, string acc, CancellationToken token)
|
||||
{
|
||||
LastLocalFile = targetFile;
|
||||
var resp = await DownloadBytes(url, auth, acc);
|
||||
progress?.Invoke(25);
|
||||
progress?.Invoke(50);
|
||||
progress?.Invoke(75);
|
||||
progress?.Invoke(100);
|
||||
if (WriteMockLocalFile)
|
||||
File.WriteAllBytes(targetFile, resp);
|
||||
}
|
||||
|
||||
public async Task DownloadFile(string url, string targetFile, Action<int> progress, string auth, string acc, CancellationToken token)
|
||||
{
|
||||
LastLocalFile = targetFile;
|
||||
var resp = await DownloadBytes(url, auth, acc);
|
||||
progress?.Invoke(25);
|
||||
progress?.Invoke(50);
|
||||
progress?.Invoke(75);
|
||||
progress?.Invoke(100);
|
||||
if (WriteMockLocalFile)
|
||||
File.WriteAllBytes(targetFile, resp);
|
||||
}
|
||||
|
||||
public async Task<string> DownloadString(string url, string auth, string acc)
|
||||
{
|
||||
return Encoding.UTF8.GetString(await DownloadBytes(url, auth, acc));
|
||||
}
|
||||
public async Task<string> DownloadString(string url, string auth, string acc)
|
||||
{
|
||||
return Encoding.UTF8.GetString(await DownloadBytes(url, auth, acc));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,111 +4,110 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using Velopack.Sources;
|
||||
|
||||
namespace Velopack.Tests.TestHelpers
|
||||
namespace Velopack.Tests.TestHelpers;
|
||||
|
||||
internal class FakeFixtureRepository : Sources.IFileDownloader
|
||||
{
|
||||
internal class FakeFixtureRepository : Sources.IFileDownloader
|
||||
private readonly string _pkgId;
|
||||
private readonly IEnumerable<ReleaseEntry> _releases;
|
||||
private readonly VelopackAssetFeed _releasesNew;
|
||||
private readonly string _releasesName;
|
||||
private readonly string _releasesNameNew;
|
||||
|
||||
public FakeFixtureRepository(string pkgId, bool mockLatestFullVer, string channel = null)
|
||||
{
|
||||
private readonly string _pkgId;
|
||||
private readonly IEnumerable<ReleaseEntry> _releases;
|
||||
private readonly VelopackAssetFeed _releasesNew;
|
||||
private readonly string _releasesName;
|
||||
private readonly string _releasesNameNew;
|
||||
_releasesName = Utility.GetReleasesFileName(channel);
|
||||
_releasesNameNew = Utility.GetVeloReleaseIndexName(channel);
|
||||
_pkgId = pkgId;
|
||||
var releases = ReleaseEntry.BuildReleasesFile(PathHelper.GetFixturesDir(), false)
|
||||
.Where(r => r.OriginalFilename.StartsWith(_pkgId))
|
||||
.ToList();
|
||||
|
||||
public FakeFixtureRepository(string pkgId, bool mockLatestFullVer, string channel = null)
|
||||
{
|
||||
_releasesName = Utility.GetReleasesFileName(channel);
|
||||
_releasesNameNew = Utility.GetVeloReleaseIndexName(channel);
|
||||
_pkgId = pkgId;
|
||||
var releases = ReleaseEntry.BuildReleasesFile(PathHelper.GetFixturesDir(), false)
|
||||
.Where(r => r.OriginalFilename.StartsWith(_pkgId))
|
||||
.ToList();
|
||||
var releasesNew = new SimpleFileSource(new DirectoryInfo(PathHelper.GetFixturesDir()))
|
||||
.GetReleaseFeed(NullLogger.Instance, null).GetAwaiterResult().Assets
|
||||
.Where(r => r.FileName.StartsWith(_pkgId))
|
||||
.ToList();
|
||||
|
||||
var releasesNew = new SimpleFileSource(new DirectoryInfo(PathHelper.GetFixturesDir()))
|
||||
.GetReleaseFeed(NullLogger.Instance, null).GetAwaiterResult().Assets
|
||||
.Where(r => r.FileName.StartsWith(_pkgId))
|
||||
.ToList();
|
||||
if (mockLatestFullVer) {
|
||||
var minFullVer = releases.Where(r => !r.IsDelta).OrderBy(r => r.Version).First();
|
||||
var maxfullVer = releases.Where(r => !r.IsDelta).OrderByDescending(r => r.Version).First();
|
||||
var maxDeltaVer = releases.Where(r => r.IsDelta).OrderByDescending(r => r.Version).First();
|
||||
|
||||
if (mockLatestFullVer) {
|
||||
var minFullVer = releases.Where(r => !r.IsDelta).OrderBy(r => r.Version).First();
|
||||
var maxfullVer = releases.Where(r => !r.IsDelta).OrderByDescending(r => r.Version).First();
|
||||
var maxDeltaVer = releases.Where(r => r.IsDelta).OrderByDescending(r => r.Version).First();
|
||||
// our fixtures don't have a full package for the latest version, we expect the tests to generate this file
|
||||
if (maxfullVer.Version < maxDeltaVer.Version) {
|
||||
var name = new ReleaseEntryName(maxfullVer.PackageId, maxDeltaVer.Version, false);
|
||||
releases.Add(new ReleaseEntry("0000000000000000000000000000000000000000", name.ToFileName(), maxfullVer.Filesize));
|
||||
|
||||
// our fixtures don't have a full package for the latest version, we expect the tests to generate this file
|
||||
if (maxfullVer.Version < maxDeltaVer.Version) {
|
||||
var name = new ReleaseEntryName(maxfullVer.PackageId, maxDeltaVer.Version, false);
|
||||
releases.Add(new ReleaseEntry("0000000000000000000000000000000000000000", name.ToFileName(), maxfullVer.Filesize));
|
||||
|
||||
releasesNew.Add(new VelopackAsset {
|
||||
PackageId = maxfullVer.PackageId,
|
||||
Version = maxDeltaVer.Version,
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"{maxfullVer.PackageId}-{maxDeltaVer.Version}-full.nupkg",
|
||||
Size = maxfullVer.Filesize,
|
||||
});
|
||||
}
|
||||
releasesNew.Add(new VelopackAsset {
|
||||
PackageId = maxfullVer.PackageId,
|
||||
Version = maxDeltaVer.Version,
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"{maxfullVer.PackageId}-{maxDeltaVer.Version}-full.nupkg",
|
||||
Size = maxfullVer.Filesize,
|
||||
});
|
||||
}
|
||||
|
||||
_releasesNew = new VelopackAssetFeed {
|
||||
Assets = releasesNew.ToArray(),
|
||||
};
|
||||
_releases = releases;
|
||||
}
|
||||
|
||||
public Task<byte[]> DownloadBytes(string url, string authorization = null, string accept = null)
|
||||
{
|
||||
if (url.Contains($"/{_releasesName}?")) {
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ReleaseEntry.WriteReleaseFile(_releases, ms);
|
||||
return Task.FromResult(ms.ToArray());
|
||||
}
|
||||
_releasesNew = new VelopackAssetFeed {
|
||||
Assets = releasesNew.ToArray(),
|
||||
};
|
||||
_releases = releases;
|
||||
}
|
||||
|
||||
if (url.Contains($"/{_releasesNameNew}?")) {
|
||||
var json = JsonSerializer.Serialize(_releasesNew, SimpleJsonTests.Options);
|
||||
return Task.FromResult(Encoding.UTF8.GetBytes(json));
|
||||
}
|
||||
|
||||
var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
|
||||
if (rel == null)
|
||||
throw new Exception("Fake release not found: " + url);
|
||||
|
||||
var filePath = PathHelper.GetFixture(rel.OriginalFilename);
|
||||
if (!File.Exists(filePath)) {
|
||||
throw new NotSupportedException("FakeFixtureRepository doesn't have: " + rel.OriginalFilename);
|
||||
}
|
||||
|
||||
return Task.FromResult(File.ReadAllBytes(filePath));
|
||||
public Task<byte[]> DownloadBytes(string url, string authorization = null, string accept = null)
|
||||
{
|
||||
if (url.Contains($"/{_releasesName}?")) {
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ReleaseEntry.WriteReleaseFile(_releases, ms);
|
||||
return Task.FromResult(ms.ToArray());
|
||||
}
|
||||
|
||||
public Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization = null, string accept = null, CancellationToken token = default)
|
||||
{
|
||||
var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
|
||||
var filePath = PathHelper.GetFixture(rel.OriginalFilename);
|
||||
if (!File.Exists(filePath)) {
|
||||
throw new NotSupportedException("FakeFixtureRepository doesn't have: " + rel.OriginalFilename);
|
||||
}
|
||||
|
||||
File.Copy(filePath, targetFile);
|
||||
progress(25);
|
||||
progress(50);
|
||||
progress(75);
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
if (url.Contains($"/{_releasesNameNew}?")) {
|
||||
var json = JsonSerializer.Serialize(_releasesNew, SimpleJsonTests.Options);
|
||||
return Task.FromResult(Encoding.UTF8.GetBytes(json));
|
||||
}
|
||||
|
||||
public Task<string> DownloadString(string url, string authorization = null, string accept = null)
|
||||
{
|
||||
if (url.Contains($"/{_releasesName}?")) {
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ReleaseEntry.WriteReleaseFile(_releases, ms);
|
||||
return Task.FromResult(Encoding.UTF8.GetString(ms.ToArray()));
|
||||
}
|
||||
var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
|
||||
if (rel == null)
|
||||
throw new Exception("Fake release not found: " + url);
|
||||
|
||||
if (url.Contains($"/{_releasesNameNew}?")) {
|
||||
var json = JsonSerializer.Serialize(_releasesNew, SimpleJsonTests.Options);
|
||||
return Task.FromResult(json);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("FakeFixtureRepository doesn't have: " + url);
|
||||
var filePath = PathHelper.GetFixture(rel.OriginalFilename);
|
||||
if (!File.Exists(filePath)) {
|
||||
throw new NotSupportedException("FakeFixtureRepository doesn't have: " + rel.OriginalFilename);
|
||||
}
|
||||
|
||||
return Task.FromResult(File.ReadAllBytes(filePath));
|
||||
}
|
||||
|
||||
public Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization = null, string accept = null, CancellationToken token = default)
|
||||
{
|
||||
var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
|
||||
var filePath = PathHelper.GetFixture(rel.OriginalFilename);
|
||||
if (!File.Exists(filePath)) {
|
||||
throw new NotSupportedException("FakeFixtureRepository doesn't have: " + rel.OriginalFilename);
|
||||
}
|
||||
|
||||
File.Copy(filePath, targetFile);
|
||||
progress(25);
|
||||
progress(50);
|
||||
progress(75);
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<string> DownloadString(string url, string authorization = null, string accept = null)
|
||||
{
|
||||
if (url.Contains($"/{_releasesName}?")) {
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ReleaseEntry.WriteReleaseFile(_releases, ms);
|
||||
return Task.FromResult(Encoding.UTF8.GetString(ms.ToArray()));
|
||||
}
|
||||
|
||||
if (url.Contains($"/{_releasesNameNew}?")) {
|
||||
var json = JsonSerializer.Serialize(_releasesNew, SimpleJsonTests.Options);
|
||||
return Task.FromResult(json);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("FakeFixtureRepository doesn't have: " + url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,91 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public sealed class StaticHttpServer : IDisposable
|
||||
{
|
||||
public sealed class StaticHttpServer : IDisposable
|
||||
public int Port { get; private set; }
|
||||
public string RootPath { get; private set; }
|
||||
|
||||
IDisposable inner;
|
||||
|
||||
public StaticHttpServer(int port, string rootPath)
|
||||
{
|
||||
public int Port { get; private set; }
|
||||
public string RootPath { get; private set; }
|
||||
Port = port; RootPath = rootPath;
|
||||
}
|
||||
|
||||
IDisposable inner;
|
||||
|
||||
public StaticHttpServer(int port, string rootPath)
|
||||
{
|
||||
Port = port; RootPath = rootPath;
|
||||
public IDisposable Start()
|
||||
{
|
||||
if (inner != null) {
|
||||
throw new InvalidOperationException("Already started!");
|
||||
}
|
||||
|
||||
public IDisposable Start()
|
||||
{
|
||||
if (inner != null) {
|
||||
throw new InvalidOperationException("Already started!");
|
||||
}
|
||||
var server = new HttpListener();
|
||||
server.Prefixes.Add(String.Format("http://+:{0}/", Port));
|
||||
server.Start();
|
||||
|
||||
var server = new HttpListener();
|
||||
server.Prefixes.Add(String.Format("http://+:{0}/", Port));
|
||||
server.Start();
|
||||
bool shouldStop = false;
|
||||
var listener = Task.Run(async () => {
|
||||
while (!shouldStop) {
|
||||
var ctx = await server.GetContextAsync();
|
||||
|
||||
bool shouldStop = false;
|
||||
var listener = Task.Run(async () => {
|
||||
while (!shouldStop) {
|
||||
var ctx = await server.GetContextAsync();
|
||||
|
||||
if (ctx.Request.HttpMethod != "GET") {
|
||||
closeResponseWith(ctx, 400, "GETs only");
|
||||
return;
|
||||
}
|
||||
|
||||
var target = Path.Combine(RootPath, ctx.Request.Url.AbsolutePath.Replace('/', Path.DirectorySeparatorChar).Substring(1));
|
||||
var fi = new FileInfo(target);
|
||||
|
||||
if (!fi.FullName.StartsWith(RootPath)) {
|
||||
closeResponseWith(ctx, 401, "Not authorized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fi.Exists) {
|
||||
closeResponseWith(ctx, 404, "Not found");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
using (var input = File.OpenRead(target)) {
|
||||
ctx.Response.StatusCode = 200;
|
||||
input.CopyTo(ctx.Response.OutputStream);
|
||||
ctx.Response.Close();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
closeResponseWith(ctx, 500, ex.ToString());
|
||||
}
|
||||
if (ctx.Request.HttpMethod != "GET") {
|
||||
closeResponseWith(ctx, 400, "GETs only");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
var ret = Disposable.Create(() => {
|
||||
shouldStop = true;
|
||||
server.Stop();
|
||||
listener.Wait(2000);
|
||||
var target = Path.Combine(RootPath, ctx.Request.Url.AbsolutePath.Replace('/', Path.DirectorySeparatorChar).Substring(1));
|
||||
var fi = new FileInfo(target);
|
||||
|
||||
inner = null;
|
||||
});
|
||||
if (!fi.FullName.StartsWith(RootPath)) {
|
||||
closeResponseWith(ctx, 401, "Not authorized");
|
||||
return;
|
||||
}
|
||||
|
||||
inner = ret;
|
||||
return ret;
|
||||
}
|
||||
if (!fi.Exists) {
|
||||
closeResponseWith(ctx, 404, "Not found");
|
||||
return;
|
||||
}
|
||||
|
||||
static void closeResponseWith(HttpListenerContext ctx, int statusCode, string message)
|
||||
{
|
||||
ctx.Response.StatusCode = statusCode;
|
||||
using (var sw = new StreamWriter(ctx.Response.OutputStream, Encoding.UTF8)) {
|
||||
sw.WriteLine(message);
|
||||
try {
|
||||
using (var input = File.OpenRead(target)) {
|
||||
ctx.Response.StatusCode = 200;
|
||||
input.CopyTo(ctx.Response.OutputStream);
|
||||
ctx.Response.Close();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
closeResponseWith(ctx, 500, ex.ToString());
|
||||
}
|
||||
}
|
||||
ctx.Response.Close();
|
||||
}
|
||||
});
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var toDispose = Interlocked.Exchange(ref inner, null);
|
||||
if (toDispose != null) {
|
||||
toDispose.Dispose();
|
||||
}
|
||||
var ret = Disposable.Create(() => {
|
||||
shouldStop = true;
|
||||
server.Stop();
|
||||
listener.Wait(2000);
|
||||
|
||||
inner = null;
|
||||
});
|
||||
|
||||
inner = ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void closeResponseWith(HttpListenerContext ctx, int statusCode, string message)
|
||||
{
|
||||
ctx.Response.StatusCode = statusCode;
|
||||
using (var sw = new StreamWriter(ctx.Response.OutputStream, Encoding.UTF8)) {
|
||||
sw.WriteLine(message);
|
||||
}
|
||||
ctx.Response.Close();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var toDispose = Interlocked.Exchange(ref inner, null);
|
||||
if (toDispose != null) {
|
||||
toDispose.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,378 +7,377 @@ using Velopack.Locators;
|
||||
using Velopack.Sources;
|
||||
using Velopack.Tests.TestHelpers;
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public class UpdateManagerTests
|
||||
{
|
||||
public class UpdateManagerTests
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public UpdateManagerTests(ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
_output = output;
|
||||
}
|
||||
|
||||
public UpdateManagerTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
private FakeDownloader GetMockDownloaderNoDelta()
|
||||
{
|
||||
var feed = new VelopackAssetFeed() {
|
||||
Assets = new VelopackAsset[] {
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 1, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.1.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 0, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.0.0.nupkg",
|
||||
SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec",
|
||||
Size = 1004502,
|
||||
},
|
||||
}
|
||||
};
|
||||
var json = JsonSerializer.Serialize(feed, SimpleJsonTests.Options);
|
||||
return new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(json) };
|
||||
}
|
||||
|
||||
private FakeDownloader GetMockDownloaderNoDelta()
|
||||
{
|
||||
var feed = new VelopackAssetFeed() {
|
||||
Assets = new VelopackAsset[] {
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 1, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.1.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 0, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.0.0.nupkg",
|
||||
SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec",
|
||||
Size = 1004502,
|
||||
},
|
||||
}
|
||||
};
|
||||
var json = JsonSerializer.Serialize(feed, SimpleJsonTests.Options);
|
||||
return new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(json) };
|
||||
}
|
||||
private FakeDownloader GetMockDownloaderWith2Delta()
|
||||
{
|
||||
var feed = new VelopackAssetFeed {
|
||||
Assets = new VelopackAsset[] {
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 1, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.1.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 0, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.0.0.nupkg",
|
||||
SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec",
|
||||
Size = 1004502,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 1, 0),
|
||||
Type = VelopackAssetType.Delta,
|
||||
FileName = $"MyCoolApp-1.1.0-delta.nupkg",
|
||||
SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67",
|
||||
Size = 80396,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 0, 0),
|
||||
Type = VelopackAssetType.Delta,
|
||||
FileName = $"MyCoolApp-1.0.0-delta.nupkg",
|
||||
SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67",
|
||||
Size = 80396,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 2, 0),
|
||||
Type = VelopackAssetType.Delta,
|
||||
FileName = $"MyCoolApp-1.2.0-delta.nupkg",
|
||||
SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67",
|
||||
Size = 80396,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 2, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.2.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
},
|
||||
}
|
||||
};
|
||||
var json = JsonSerializer.Serialize(feed, SimpleJsonTests.Options);
|
||||
return new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(json) };
|
||||
}
|
||||
|
||||
private FakeDownloader GetMockDownloaderWith2Delta()
|
||||
{
|
||||
var feed = new VelopackAssetFeed {
|
||||
Assets = new VelopackAsset[] {
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 1, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.1.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 0, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.0.0.nupkg",
|
||||
SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec",
|
||||
Size = 1004502,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 1, 0),
|
||||
Type = VelopackAssetType.Delta,
|
||||
FileName = $"MyCoolApp-1.1.0-delta.nupkg",
|
||||
SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67",
|
||||
Size = 80396,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 0, 0),
|
||||
Type = VelopackAssetType.Delta,
|
||||
FileName = $"MyCoolApp-1.0.0-delta.nupkg",
|
||||
SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67",
|
||||
Size = 80396,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 2, 0),
|
||||
Type = VelopackAssetType.Delta,
|
||||
FileName = $"MyCoolApp-1.2.0-delta.nupkg",
|
||||
SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67",
|
||||
Size = 80396,
|
||||
},
|
||||
new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 2, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.2.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
},
|
||||
}
|
||||
};
|
||||
var json = JsonSerializer.Serialize(feed, SimpleJsonTests.Options);
|
||||
return new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(json) };
|
||||
}
|
||||
[Fact]
|
||||
public void CheckFromLocal()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderNoDelta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 1, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.StartsWith($"http://any.com/releases.{VelopackRuntimeInfo.SystemOs.GetOsShortName()}.json?", dl.LastUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckFromLocal()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderNoDelta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 1, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.StartsWith($"http://any.com/releases.{VelopackRuntimeInfo.SystemOs.GetOsShortName()}.json?", dl.LastUrl);
|
||||
}
|
||||
[Fact]
|
||||
public void CheckFromLocalWithChannel()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderNoDelta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
|
||||
var opt = new UpdateOptions { ExplicitChannel = "experimental" };
|
||||
var um = new UpdateManager(source, opt, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 1, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.StartsWith("http://any.com/releases.experimental.json?", dl.LastUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckFromLocalWithChannel()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderNoDelta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
|
||||
var opt = new UpdateOptions { ExplicitChannel = "experimental" };
|
||||
var um = new UpdateManager(source, opt, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 1, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.StartsWith("http://any.com/releases.experimental.json?", dl.LastUrl);
|
||||
}
|
||||
[Fact]
|
||||
public void CheckForSameAsInstalledVersion()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var myVer = new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 2, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.2.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
};
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.2.0", tempPath, null, null, null, logger: logger, localPackage: myVer, channel: "stable");
|
||||
|
||||
[Fact]
|
||||
public void CheckForSameAsInstalledVersion()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var myVer = new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 2, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.2.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
};
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.2.0", tempPath, null, null, null, logger: logger, localPackage: myVer, channel: "stable");
|
||||
// checking for same version should return null
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.Null(info);
|
||||
Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl);
|
||||
|
||||
// checking for same version should return null
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.Null(info);
|
||||
Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl);
|
||||
// checking for same version WITHOUT explicit channel should return null
|
||||
var opt = new UpdateOptions { AllowVersionDowngrade = true };
|
||||
um = new UpdateManager(source, opt, logger, locator);
|
||||
Assert.Null(info);
|
||||
Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl);
|
||||
|
||||
// checking for same version WITHOUT explicit channel should return null
|
||||
var opt = new UpdateOptions { AllowVersionDowngrade = true };
|
||||
um = new UpdateManager(source, opt, logger, locator);
|
||||
Assert.Null(info);
|
||||
Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl);
|
||||
// checking for same version with explicit channel & downgrade allowed should return version
|
||||
opt = new UpdateOptions { ExplicitChannel = "experimental", AllowVersionDowngrade = true };
|
||||
um = new UpdateManager(source, opt, logger, locator);
|
||||
info = um.CheckForUpdates();
|
||||
Assert.True(info.IsDowngrade);
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.StartsWith("http://any.com/releases.experimental.json?", dl.LastUrl);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
|
||||
// checking for same version with explicit channel & downgrade allowed should return version
|
||||
opt = new UpdateOptions { ExplicitChannel = "experimental", AllowVersionDowngrade = true };
|
||||
um = new UpdateManager(source, opt, logger, locator);
|
||||
info = um.CheckForUpdates();
|
||||
Assert.True(info.IsDowngrade);
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.StartsWith("http://any.com/releases.experimental.json?", dl.LastUrl);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
[Fact]
|
||||
public void CheckForLowerThanInstalledVersion()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var myVer = new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(2, 0, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-2.0.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
};
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "2.0.0", tempPath, null, null, null, logger: logger, localPackage: myVer, channel: "stable");
|
||||
|
||||
[Fact]
|
||||
public void CheckForLowerThanInstalledVersion()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var myVer = new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(2, 0, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-2.0.0.nupkg",
|
||||
SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39",
|
||||
Size = 1040561,
|
||||
};
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "2.0.0", tempPath, null, null, null, logger: logger, localPackage: myVer, channel: "stable");
|
||||
// checking for lower version should return null
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.Null(info);
|
||||
Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl);
|
||||
|
||||
// checking for lower version should return null
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.Null(info);
|
||||
Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl);
|
||||
// checking for lower version with downgrade allowed should return lower version
|
||||
var opt = new UpdateOptions { AllowVersionDowngrade = true };
|
||||
um = new UpdateManager(source, opt, logger, locator);
|
||||
info = um.CheckForUpdates();
|
||||
Assert.True(info.IsDowngrade);
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
|
||||
// checking for lower version with downgrade allowed should return lower version
|
||||
var opt = new UpdateOptions { AllowVersionDowngrade = true };
|
||||
um = new UpdateManager(source, opt, logger, locator);
|
||||
info = um.CheckForUpdates();
|
||||
Assert.True(info.IsDowngrade);
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
[Fact]
|
||||
public void CheckFromLocalWithDelta()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var myVer = new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 0, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.0.0.nupkg",
|
||||
SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec",
|
||||
Size = 1004502,
|
||||
};
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
|
||||
[Fact]
|
||||
public void CheckFromLocalWithDelta()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var myVer = new VelopackAsset() {
|
||||
PackageId = "MyCoolApp",
|
||||
Version = new SemanticVersion(1, 0, 0),
|
||||
Type = VelopackAssetType.Full,
|
||||
FileName = $"MyCoolApp-1.0.0.nupkg",
|
||||
SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec",
|
||||
Size = 1004502,
|
||||
};
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, null, null, null, logger: logger, localPackage: myVer);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(2, info.DeltasToTarget.Count());
|
||||
}
|
||||
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, null, null, null, logger: logger, localPackage: myVer);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(2, info.DeltasToTarget.Count());
|
||||
}
|
||||
[Fact]
|
||||
public void NoDeltaIfNoBasePackage()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger: logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoDeltaIfNoBasePackage()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger: logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
[Fact]
|
||||
public void CheckFromLocalWithDeltaNoLocalPackage()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger: logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckFromLocalWithDeltaNoLocalPackage()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderWith2Delta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger: logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.False(info.IsDowngrade);
|
||||
Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
[Fact(Skip = "Consumes API Quota")]
|
||||
public void CheckGithub()
|
||||
{
|
||||
// https://github.com/caesay/SquirrelCustomLauncherTestApp
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
|
||||
var source = new GithubSource("https://github.com/caesay/SquirrelCustomLauncherTestApp", null, false);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 0, 1) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
|
||||
[Fact(Skip = "Consumes API Quota")]
|
||||
public void CheckGithub()
|
||||
{
|
||||
// https://github.com/caesay/SquirrelCustomLauncherTestApp
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
|
||||
var source = new GithubSource("https://github.com/caesay/SquirrelCustomLauncherTestApp", null, false);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(new SemanticVersion(1, 0, 1) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
}
|
||||
[Fact(Skip = "Consumes API Quota")]
|
||||
public void CheckGithubWithNonExistingChannel()
|
||||
{
|
||||
// https://github.com/caesay/SquirrelCustomLauncherTestApp
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
|
||||
var source = new GithubSource("https://github.com/caesay/SquirrelCustomLauncherTestApp", null, false);
|
||||
var opt = new UpdateOptions { ExplicitChannel = "hello" };
|
||||
var um = new UpdateManager(source, opt, logger, locator);
|
||||
Assert.Throws<ArgumentException>(() => um.CheckForUpdates());
|
||||
}
|
||||
|
||||
[Fact(Skip = "Consumes API Quota")]
|
||||
public void CheckGithubWithNonExistingChannel()
|
||||
{
|
||||
// https://github.com/caesay/SquirrelCustomLauncherTestApp
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
|
||||
var source = new GithubSource("https://github.com/caesay/SquirrelCustomLauncherTestApp", null, false);
|
||||
var opt = new UpdateOptions { ExplicitChannel = "hello" };
|
||||
var um = new UpdateManager(source, opt, logger, locator);
|
||||
Assert.Throws<ArgumentException>(() => um.CheckForUpdates());
|
||||
}
|
||||
[Fact]
|
||||
public void NoUpdatesIfCurrentEqualsRemoteVersion()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderNoDelta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.1.0", tempPath, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.Null(info);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoUpdatesIfCurrentEqualsRemoteVersion()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderNoDelta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.1.0", tempPath, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.Null(info);
|
||||
}
|
||||
[Fact]
|
||||
public void NoUpdatesIfCurrentGreaterThanRemoteVersion()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderNoDelta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.2.0", tempPath, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.Null(info);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoUpdatesIfCurrentGreaterThanRemoteVersion()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var tempPath);
|
||||
var dl = GetMockDownloaderNoDelta();
|
||||
var source = new SimpleWebSource("http://any.com", dl);
|
||||
var locator = new TestVelopackLocator("MyCoolApp", "1.2.0", tempPath, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.Null(info);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData("Clowd", "3.4.287")]
|
||||
[InlineData("slack", "1.1.8")]
|
||||
public void DownloadsLatestFullVersion(string id, string version)
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var packagesDir);
|
||||
var repo = new FakeFixtureRepository(id, false);
|
||||
var source = new SimpleWebSource("http://any.com", repo);
|
||||
var locator = new TestVelopackLocator(id, "1.0.0", packagesDir, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
|
||||
[Theory]
|
||||
[InlineData("Clowd", "3.4.287")]
|
||||
[InlineData("slack", "1.1.8")]
|
||||
public void DownloadsLatestFullVersion(string id, string version)
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var packagesDir);
|
||||
var repo = new FakeFixtureRepository(id, false);
|
||||
var source = new SimpleWebSource("http://any.com", repo);
|
||||
var locator = new TestVelopackLocator(id, "1.0.0", packagesDir, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(SemanticVersion.Parse(version) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
|
||||
var info = um.CheckForUpdates();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(SemanticVersion.Parse(version) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(0, info.DeltasToTarget.Count());
|
||||
um.DownloadUpdates(info);
|
||||
|
||||
um.DownloadUpdates(info);
|
||||
var target = Path.Combine(packagesDir, $"{id}-{version}-full.nupkg");
|
||||
Assert.True(File.Exists(target));
|
||||
um.VerifyPackageChecksum(info.TargetFullRelease);
|
||||
}
|
||||
|
||||
var target = Path.Combine(packagesDir, $"{id}-{version}-full.nupkg");
|
||||
Assert.True(File.Exists(target));
|
||||
um.VerifyPackageChecksum(info.TargetFullRelease);
|
||||
}
|
||||
[SkippableTheory]
|
||||
[InlineData("Clowd", "3.4.287", "3.4.292")]
|
||||
//[InlineData("slack", "1.1.8", "1.2.2")]
|
||||
public async Task DownloadsDeltasAndCreatesFullVersion(string id, string fromVersion, string toVersion)
|
||||
{
|
||||
Skip.If(VelopackRuntimeInfo.IsLinux);
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var packagesDir);
|
||||
var repo = new FakeFixtureRepository(id, true);
|
||||
var source = new SimpleWebSource("http://any.com", repo);
|
||||
|
||||
[SkippableTheory]
|
||||
[InlineData("Clowd", "3.4.287", "3.4.292")]
|
||||
//[InlineData("slack", "1.1.8", "1.2.2")]
|
||||
public async Task DownloadsDeltasAndCreatesFullVersion(string id, string fromVersion, string toVersion)
|
||||
{
|
||||
Skip.If(VelopackRuntimeInfo.IsLinux);
|
||||
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var packagesDir);
|
||||
var repo = new FakeFixtureRepository(id, true);
|
||||
var source = new SimpleWebSource("http://any.com", repo);
|
||||
var feed = await source.GetReleaseFeed(logger, VelopackRuntimeInfo.SystemOs.GetOsShortName());
|
||||
var basePkg = feed.Assets
|
||||
.Where(x => x.Type == VelopackAssetType.Full)
|
||||
.Single(x => x.Version == SemanticVersion.Parse(fromVersion));
|
||||
var basePkgFixturePath = PathHelper.GetFixture(basePkg.FileName);
|
||||
var basePkgPath = Path.Combine(packagesDir, basePkg.FileName);
|
||||
File.Copy(basePkgFixturePath, basePkgPath);
|
||||
|
||||
var feed = await source.GetReleaseFeed(logger, VelopackRuntimeInfo.SystemOs.GetOsShortName());
|
||||
var basePkg = feed.Assets
|
||||
.Where(x => x.Type == VelopackAssetType.Full)
|
||||
.Single(x => x.Version == SemanticVersion.Parse(fromVersion));
|
||||
var basePkgFixturePath = PathHelper.GetFixture(basePkg.FileName);
|
||||
var basePkgPath = Path.Combine(packagesDir, basePkg.FileName);
|
||||
File.Copy(basePkgFixturePath, basePkgPath);
|
||||
var updateExe = PathHelper.CopyUpdateTo(packagesDir);
|
||||
var locator = new TestVelopackLocator(id, fromVersion,
|
||||
packagesDir, null, null, updateExe, null, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
|
||||
var updateExe = PathHelper.CopyUpdateTo(packagesDir);
|
||||
var locator = new TestVelopackLocator(id, fromVersion,
|
||||
packagesDir, null, null, updateExe, null, logger);
|
||||
var um = new UpdateManager(source, null, logger, locator);
|
||||
var info = await um.CheckForUpdatesAsync();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(SemanticVersion.Parse(toVersion) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(3, info.DeltasToTarget.Count());
|
||||
Assert.NotNull(info.BaseRelease);
|
||||
|
||||
var info = await um.CheckForUpdatesAsync();
|
||||
Assert.NotNull(info);
|
||||
Assert.True(SemanticVersion.Parse(toVersion) == info.TargetFullRelease.Version);
|
||||
Assert.Equal(3, info.DeltasToTarget.Count());
|
||||
Assert.NotNull(info.BaseRelease);
|
||||
|
||||
await um.DownloadUpdatesAsync(info);
|
||||
var target = Path.Combine(packagesDir, $"{id}-{toVersion}-full.nupkg");
|
||||
Assert.True(File.Exists(target));
|
||||
}
|
||||
await um.DownloadUpdatesAsync(info);
|
||||
var target = Path.Combine(packagesDir, $"{id}-{toVersion}-full.nupkg");
|
||||
Assert.True(File.Exists(target));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,235 +4,234 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Velopack.Windows;
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public class UtilityTests
|
||||
{
|
||||
public class UtilityTests
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public UtilityTests(ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public UtilityTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
[InlineData("file.txt", "file.txt")]
|
||||
[InlineData("file", "file")]
|
||||
[InlineData("/file", "\\file")]
|
||||
[InlineData("/file/", "\\file")]
|
||||
[InlineData("one\\two\\..\\file", "one\\file")]
|
||||
[InlineData("C:/AnApp/file/", "C:\\AnApp\\file")]
|
||||
public void PathIsNormalized(string input, string expected)
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
var exp = Path.GetFullPath(expected);
|
||||
var normal = Utility.NormalizePath(input);
|
||||
Assert.Equal(exp, normal);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
[InlineData("C:\\AnApp", "C:\\AnApp\\file.exe", true)]
|
||||
[InlineData("C:\\AnApp\\", "C:\\AnApp\\file.exe", true)]
|
||||
[InlineData("C:\\AnApp", "C:\\AnApp\\sub\\dir\\file.exe", true)]
|
||||
[InlineData("C:\\AnApp\\", "C:\\AnApp\\sub\\dir\\file.exe", true)]
|
||||
[InlineData("C:\\AnAppTwo", "C:\\AnApp\\file.exe", false)]
|
||||
[InlineData("C:\\AnAppTwo\\", "C:\\AnApp\\file.exe", false)]
|
||||
[InlineData("C:\\AnAppTwo", "C:\\AnApp\\sub\\dir\\file.exe", false)]
|
||||
[InlineData("C:\\AnAppTwo\\", "C:\\AnApp\\sub\\dir\\file.exe", false)]
|
||||
[InlineData("AnAppThree", "AnAppThree\\file.exe", true)]
|
||||
public void FileIsInDirectory(string directory, string file, bool isIn)
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
var fileInDir = Utility.IsFileInDirectory(file, directory);
|
||||
Assert.Equal(isIn, fileInDir);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
[SupportedOSPlatform("windows")]
|
||||
public void SetAppIdOnShortcutTest()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
var sl = new ShellLink() {
|
||||
Target = @"C:\Windows\Notepad.exe",
|
||||
Description = "It's Notepad",
|
||||
};
|
||||
|
||||
sl.SetAppUserModelId("org.anaïsbetts.test");
|
||||
var path = Path.GetFullPath(@".\test.lnk");
|
||||
sl.Save(path);
|
||||
|
||||
Console.WriteLine("Saved to " + path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveByteOrderMarkerIfPresent()
|
||||
{
|
||||
var utf32Be = new byte[] { 0x00, 0x00, 0xFE, 0xFF };
|
||||
var utf32Le = new byte[] { 0xFF, 0xFE, 0x00, 0x00 };
|
||||
var utf16Be = new byte[] { 0xFE, 0xFF };
|
||||
var utf16Le = new byte[] { 0xFF, 0xFE };
|
||||
var utf8 = new byte[] { 0xEF, 0xBB, 0xBF };
|
||||
|
||||
var utf32BeHelloWorld = combine(utf32Be, Encoding.UTF8.GetBytes("hello world"));
|
||||
var utf32LeHelloWorld = combine(utf32Le, Encoding.UTF8.GetBytes("hello world"));
|
||||
var utf16BeHelloWorld = combine(utf16Be, Encoding.UTF8.GetBytes("hello world"));
|
||||
var utf16LeHelloWorld = combine(utf16Le, Encoding.UTF8.GetBytes("hello world"));
|
||||
var utf8HelloWorld = combine(utf8, Encoding.UTF8.GetBytes("hello world"));
|
||||
|
||||
var asciiMultipleChars = Encoding.ASCII.GetBytes("hello world");
|
||||
var asciiSingleChar = Encoding.ASCII.GetBytes("A");
|
||||
|
||||
var emptyString = string.Empty;
|
||||
string nullString = null;
|
||||
byte[] nullByteArray = { };
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(emptyString));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(nullString));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(nullByteArray));
|
||||
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf32Be));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf32Le));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf16Be));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf16Le));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf8));
|
||||
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf32BeHelloWorld));
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf32LeHelloWorld));
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf16BeHelloWorld));
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf16LeHelloWorld));
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf8HelloWorld));
|
||||
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(asciiMultipleChars));
|
||||
Assert.Equal("A", Utility.RemoveByteOrderMarkerIfPresent(asciiSingleChar));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShaCheckShouldBeCaseInsensitive()
|
||||
{
|
||||
var sha1FromExternalTool = "75255cfd229a1ed1447abe1104f5635e69975d30";
|
||||
var inputPackage = PathHelper.GetFixture("Squirrel.Core.1.0.0.0.nupkg");
|
||||
var stream = File.OpenRead(inputPackage);
|
||||
var sha1 = Utility.CalculateStreamSHA1(stream);
|
||||
|
||||
Assert.NotEqual(sha1FromExternalTool, sha1);
|
||||
Assert.Equal(sha1FromExternalTool, sha1, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeleteDeepRecursiveDirectoryStructure()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UtilityTests>();
|
||||
string tempDir;
|
||||
using (Utility.GetTempDirectory(out tempDir)) {
|
||||
for (var i = 0; i < 50; i++) {
|
||||
var directory = Path.Combine(tempDir, newId());
|
||||
CreateSampleDirectory(directory);
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories);
|
||||
|
||||
var count = files.Count();
|
||||
|
||||
logger.Info($"Created {count} files under directory {tempDir}");
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
Utility.DeleteFileOrDirectoryHard(tempDir);
|
||||
sw.Stop();
|
||||
logger.Info($"Delete took {sw.ElapsedMilliseconds}ms");
|
||||
|
||||
Assert.False(Directory.Exists(tempDir));
|
||||
}
|
||||
}
|
||||
|
||||
//[Fact]
|
||||
//public void CreateFakePackageSmokeTest()
|
||||
//{
|
||||
// string path;
|
||||
// using (Utility.GetTempDirectory(out path)) {
|
||||
// var output = IntegrationTestHelper.CreateFakeInstalledApp("0.3.0", path);
|
||||
// Assert.True(File.Exists(output));
|
||||
// }
|
||||
//}
|
||||
|
||||
[Theory]
|
||||
[InlineData("foo.dll", true)]
|
||||
[InlineData("foo.DlL", true)]
|
||||
[InlineData("C:\\Foo\\Bar\\foo.Exe", true)]
|
||||
[InlineData("Test.png", false)]
|
||||
[InlineData(".rels", false)]
|
||||
public void FileIsLikelyPEImageTest(string input, bool result)
|
||||
{
|
||||
Assert.Equal(result, Utility.FileIsLikelyPEImage(input));
|
||||
}
|
||||
|
||||
[Fact(Skip = "Only really need to run this test after changes to FileDownloader")]
|
||||
public async Task DownloaderReportsProgress()
|
||||
{
|
||||
// this probably should use a local http server instead.
|
||||
const string testUrl = "http://speedtest.tele2.net/1MB.zip";
|
||||
|
||||
var dl = Utility.CreateDefaultDownloader();
|
||||
|
||||
List<int> prog = new List<int>();
|
||||
using (Utility.GetTempFileName(out var tempPath))
|
||||
await dl.DownloadFile(testUrl, tempPath, prog.Add);
|
||||
|
||||
Assert.True(prog.Count > 10);
|
||||
Assert.Equal(100, prog.Last());
|
||||
Assert.True(prog[1] != 0);
|
||||
}
|
||||
|
||||
static void CreateSampleDirectory(string directory)
|
||||
{
|
||||
Random prng = new Random();
|
||||
while (true) {
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
for (var j = 0; j < 100; j++) {
|
||||
var file = Path.Combine(directory, newId());
|
||||
if (file.Length > 260) continue;
|
||||
File.WriteAllText(file, Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
if (prng.NextDouble() > 0.5) {
|
||||
var childDirectory = Path.Combine(directory, newId());
|
||||
if (childDirectory.Length > 248) return;
|
||||
directory = childDirectory;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static string newId()
|
||||
{
|
||||
var text = Guid.NewGuid().ToString();
|
||||
var bytes = Encoding.Unicode.GetBytes(text);
|
||||
var provider = SHA1.Create();
|
||||
var hashString = string.Empty;
|
||||
|
||||
foreach (var x in provider.ComputeHash(bytes)) {
|
||||
hashString += String.Format("{0:x2}", x);
|
||||
}
|
||||
|
||||
if (hashString.Length > 7) {
|
||||
return hashString.Substring(0, 7);
|
||||
}
|
||||
|
||||
return hashString;
|
||||
}
|
||||
|
||||
static byte[] combine(params byte[][] arrays)
|
||||
{
|
||||
var rv = new byte[arrays.Sum(a => a.Length)];
|
||||
var offset = 0;
|
||||
foreach (var array in arrays) {
|
||||
Buffer.BlockCopy(array, 0, rv, offset, array.Length);
|
||||
offset += array.Length;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
[InlineData("file.txt", "file.txt")]
|
||||
[InlineData("file", "file")]
|
||||
[InlineData("/file", "\\file")]
|
||||
[InlineData("/file/", "\\file")]
|
||||
[InlineData("one\\two\\..\\file", "one\\file")]
|
||||
[InlineData("C:/AnApp/file/", "C:\\AnApp\\file")]
|
||||
public void PathIsNormalized(string input, string expected)
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
var exp = Path.GetFullPath(expected);
|
||||
var normal = Utility.NormalizePath(input);
|
||||
Assert.Equal(exp, normal);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
[InlineData("C:\\AnApp", "C:\\AnApp\\file.exe", true)]
|
||||
[InlineData("C:\\AnApp\\", "C:\\AnApp\\file.exe", true)]
|
||||
[InlineData("C:\\AnApp", "C:\\AnApp\\sub\\dir\\file.exe", true)]
|
||||
[InlineData("C:\\AnApp\\", "C:\\AnApp\\sub\\dir\\file.exe", true)]
|
||||
[InlineData("C:\\AnAppTwo", "C:\\AnApp\\file.exe", false)]
|
||||
[InlineData("C:\\AnAppTwo\\", "C:\\AnApp\\file.exe", false)]
|
||||
[InlineData("C:\\AnAppTwo", "C:\\AnApp\\sub\\dir\\file.exe", false)]
|
||||
[InlineData("C:\\AnAppTwo\\", "C:\\AnApp\\sub\\dir\\file.exe", false)]
|
||||
[InlineData("AnAppThree", "AnAppThree\\file.exe", true)]
|
||||
public void FileIsInDirectory(string directory, string file, bool isIn)
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
var fileInDir = Utility.IsFileInDirectory(file, directory);
|
||||
Assert.Equal(isIn, fileInDir);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
[SupportedOSPlatform("windows")]
|
||||
public void SetAppIdOnShortcutTest()
|
||||
{
|
||||
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
|
||||
var sl = new ShellLink() {
|
||||
Target = @"C:\Windows\Notepad.exe",
|
||||
Description = "It's Notepad",
|
||||
};
|
||||
|
||||
sl.SetAppUserModelId("org.anaïsbetts.test");
|
||||
var path = Path.GetFullPath(@".\test.lnk");
|
||||
sl.Save(path);
|
||||
|
||||
Console.WriteLine("Saved to " + path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveByteOrderMarkerIfPresent()
|
||||
{
|
||||
var utf32Be = new byte[] { 0x00, 0x00, 0xFE, 0xFF };
|
||||
var utf32Le = new byte[] { 0xFF, 0xFE, 0x00, 0x00 };
|
||||
var utf16Be = new byte[] { 0xFE, 0xFF };
|
||||
var utf16Le = new byte[] { 0xFF, 0xFE };
|
||||
var utf8 = new byte[] { 0xEF, 0xBB, 0xBF };
|
||||
|
||||
var utf32BeHelloWorld = combine(utf32Be, Encoding.UTF8.GetBytes("hello world"));
|
||||
var utf32LeHelloWorld = combine(utf32Le, Encoding.UTF8.GetBytes("hello world"));
|
||||
var utf16BeHelloWorld = combine(utf16Be, Encoding.UTF8.GetBytes("hello world"));
|
||||
var utf16LeHelloWorld = combine(utf16Le, Encoding.UTF8.GetBytes("hello world"));
|
||||
var utf8HelloWorld = combine(utf8, Encoding.UTF8.GetBytes("hello world"));
|
||||
|
||||
var asciiMultipleChars = Encoding.ASCII.GetBytes("hello world");
|
||||
var asciiSingleChar = Encoding.ASCII.GetBytes("A");
|
||||
|
||||
var emptyString = string.Empty;
|
||||
string nullString = null;
|
||||
byte[] nullByteArray = { };
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(emptyString));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(nullString));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(nullByteArray));
|
||||
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf32Be));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf32Le));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf16Be));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf16Le));
|
||||
Assert.Equal(string.Empty, Utility.RemoveByteOrderMarkerIfPresent(utf8));
|
||||
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf32BeHelloWorld));
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf32LeHelloWorld));
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf16BeHelloWorld));
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf16LeHelloWorld));
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(utf8HelloWorld));
|
||||
|
||||
Assert.Equal("hello world", Utility.RemoveByteOrderMarkerIfPresent(asciiMultipleChars));
|
||||
Assert.Equal("A", Utility.RemoveByteOrderMarkerIfPresent(asciiSingleChar));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShaCheckShouldBeCaseInsensitive()
|
||||
{
|
||||
var sha1FromExternalTool = "75255cfd229a1ed1447abe1104f5635e69975d30";
|
||||
var inputPackage = PathHelper.GetFixture("Squirrel.Core.1.0.0.0.nupkg");
|
||||
var stream = File.OpenRead(inputPackage);
|
||||
var sha1 = Utility.CalculateStreamSHA1(stream);
|
||||
|
||||
Assert.NotEqual(sha1FromExternalTool, sha1);
|
||||
Assert.Equal(sha1FromExternalTool, sha1, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeleteDeepRecursiveDirectoryStructure()
|
||||
{
|
||||
using var logger = _output.BuildLoggerFor<UtilityTests>();
|
||||
string tempDir;
|
||||
using (Utility.GetTempDirectory(out tempDir)) {
|
||||
for (var i = 0; i < 50; i++) {
|
||||
var directory = Path.Combine(tempDir, newId());
|
||||
CreateSampleDirectory(directory);
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories);
|
||||
|
||||
var count = files.Count();
|
||||
|
||||
logger.Info($"Created {count} files under directory {tempDir}");
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
Utility.DeleteFileOrDirectoryHard(tempDir);
|
||||
sw.Stop();
|
||||
logger.Info($"Delete took {sw.ElapsedMilliseconds}ms");
|
||||
|
||||
Assert.False(Directory.Exists(tempDir));
|
||||
}
|
||||
}
|
||||
|
||||
//[Fact]
|
||||
//public void CreateFakePackageSmokeTest()
|
||||
//{
|
||||
// string path;
|
||||
// using (Utility.GetTempDirectory(out path)) {
|
||||
// var output = IntegrationTestHelper.CreateFakeInstalledApp("0.3.0", path);
|
||||
// Assert.True(File.Exists(output));
|
||||
// }
|
||||
//}
|
||||
|
||||
[Theory]
|
||||
[InlineData("foo.dll", true)]
|
||||
[InlineData("foo.DlL", true)]
|
||||
[InlineData("C:\\Foo\\Bar\\foo.Exe", true)]
|
||||
[InlineData("Test.png", false)]
|
||||
[InlineData(".rels", false)]
|
||||
public void FileIsLikelyPEImageTest(string input, bool result)
|
||||
{
|
||||
Assert.Equal(result, Utility.FileIsLikelyPEImage(input));
|
||||
}
|
||||
|
||||
[Fact(Skip = "Only really need to run this test after changes to FileDownloader")]
|
||||
public async Task DownloaderReportsProgress()
|
||||
{
|
||||
// this probably should use a local http server instead.
|
||||
const string testUrl = "http://speedtest.tele2.net/1MB.zip";
|
||||
|
||||
var dl = Utility.CreateDefaultDownloader();
|
||||
|
||||
List<int> prog = new List<int>();
|
||||
using (Utility.GetTempFileName(out var tempPath))
|
||||
await dl.DownloadFile(testUrl, tempPath, prog.Add);
|
||||
|
||||
Assert.True(prog.Count > 10);
|
||||
Assert.Equal(100, prog.Last());
|
||||
Assert.True(prog[1] != 0);
|
||||
}
|
||||
|
||||
static void CreateSampleDirectory(string directory)
|
||||
{
|
||||
Random prng = new Random();
|
||||
while (true) {
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
for (var j = 0; j < 100; j++) {
|
||||
var file = Path.Combine(directory, newId());
|
||||
if (file.Length > 260) continue;
|
||||
File.WriteAllText(file, Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
if (prng.NextDouble() > 0.5) {
|
||||
var childDirectory = Path.Combine(directory, newId());
|
||||
if (childDirectory.Length > 248) return;
|
||||
directory = childDirectory;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static string newId()
|
||||
{
|
||||
var text = Guid.NewGuid().ToString();
|
||||
var bytes = Encoding.Unicode.GetBytes(text);
|
||||
var provider = SHA1.Create();
|
||||
var hashString = string.Empty;
|
||||
|
||||
foreach (var x in provider.ComputeHash(bytes)) {
|
||||
hashString += String.Format("{0:x2}", x);
|
||||
}
|
||||
|
||||
if (hashString.Length > 7) {
|
||||
return hashString.Substring(0, 7);
|
||||
}
|
||||
|
||||
return hashString;
|
||||
}
|
||||
|
||||
static byte[] combine(params byte[][] arrays)
|
||||
{
|
||||
var rv = new byte[arrays.Sum(a => a.Length)];
|
||||
var offset = 0;
|
||||
foreach (var array in arrays) {
|
||||
Buffer.BlockCopy(array, 0, rv, offset, array.Length);
|
||||
offset += array.Length;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,85 +4,84 @@ using Velopack.NuGet;
|
||||
using Velopack.Tests.TestHelpers;
|
||||
using ZipPackage = Velopack.NuGet.ZipPackage;
|
||||
|
||||
namespace Velopack.Tests
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public class ZipPackageTests
|
||||
{
|
||||
public class ZipPackageTests
|
||||
[Fact]
|
||||
public void HasSameFilesAndDependenciesAsPackaging()
|
||||
{
|
||||
[Fact]
|
||||
public void HasSameFilesAndDependenciesAsPackaging()
|
||||
{
|
||||
using var _1 = Utility.GetTempDirectory(out var tempDir);
|
||||
var inputPackage = PathHelper.GetFixture("slack-1.1.8-full.nupkg");
|
||||
var copyPackage = Path.Combine(tempDir, "slack-1.1.8-full.nupkg");
|
||||
File.Copy(inputPackage, copyPackage);
|
||||
using var _1 = Utility.GetTempDirectory(out var tempDir);
|
||||
var inputPackage = PathHelper.GetFixture("slack-1.1.8-full.nupkg");
|
||||
var copyPackage = Path.Combine(tempDir, "slack-1.1.8-full.nupkg");
|
||||
File.Copy(inputPackage, copyPackage);
|
||||
|
||||
var zp = new ZipPackage(inputPackage);
|
||||
var zipf = zp.Files.OrderBy(f => f.Path).ToArray();
|
||||
var zipfLib = zp.Files.Where(f => f.IsLibFile()).OrderBy(f => f.Path).ToArray();
|
||||
var zp = new ZipPackage(inputPackage);
|
||||
var zipf = zp.Files.OrderBy(f => f.Path).ToArray();
|
||||
var zipfLib = zp.Files.Where(f => f.IsLibFile()).OrderBy(f => f.Path).ToArray();
|
||||
|
||||
using Package package = Package.Open(copyPackage);
|
||||
var packaging = GetFiles(package).OrderBy(f => f.Path).ToArray();
|
||||
var packagingLib = GetLibFiles(package).OrderBy(f => f.Path).ToArray();
|
||||
using Package package = Package.Open(copyPackage);
|
||||
var packaging = GetFiles(package).OrderBy(f => f.Path).ToArray();
|
||||
var packagingLib = GetLibFiles(package).OrderBy(f => f.Path).ToArray();
|
||||
|
||||
//for (int i = 0; i < zipf.Length; i++) {
|
||||
// if (zipf[i] != packagingLib[i])
|
||||
// throw new Exception();
|
||||
//}
|
||||
//for (int i = 0; i < zipf.Length; i++) {
|
||||
// if (zipf[i] != packagingLib[i])
|
||||
// throw new Exception();
|
||||
//}
|
||||
|
||||
Assert.Equal(packaging, zipf);
|
||||
Assert.Equal(packagingLib, zipfLib);
|
||||
}
|
||||
Assert.Equal(packaging, zipf);
|
||||
Assert.Equal(packagingLib, zipfLib);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesNuspecCorrectly()
|
||||
{
|
||||
var inputPackage = PathHelper.GetFixture("FullNuspec.1.0.0.nupkg");
|
||||
var zp = new ZipPackage(inputPackage);
|
||||
[Fact]
|
||||
public void ParsesNuspecCorrectly()
|
||||
{
|
||||
var inputPackage = PathHelper.GetFixture("FullNuspec.1.0.0.nupkg");
|
||||
var zp = new ZipPackage(inputPackage);
|
||||
|
||||
var dyn = ExposedObject.From(zp);
|
||||
var dyn = ExposedObject.From(zp);
|
||||
|
||||
Assert.Equal("FullNuspec", zp.Id);
|
||||
Assert.Equal(SemanticVersion.Parse("1.0.0"), zp.Version);
|
||||
Assert.Equal(new[] { "Anaïs Betts", "Caelan Sayler" }, dyn.Authors);
|
||||
Assert.Equal(new Uri("https://github.com/clowd/Clowd.Squirrel"), zp.ProjectUrl);
|
||||
Assert.Equal(new Uri("https://user-images.githubusercontent.com/1287295/131249078-9e131e51-0b66-4dc7-8c0a-99cbea6bcf80.png"), zp.IconUrl);
|
||||
Assert.Equal("A test description", dyn.Description);
|
||||
Assert.Equal("A summary", dyn.Summary);
|
||||
Assert.Equal("release notes\nwith multiple lines", zp.ReleaseNotes);
|
||||
Assert.Equal("Copyright ©", dyn.Copyright);
|
||||
Assert.Equal("en-US", zp.Language);
|
||||
Assert.Equal("Squirrel for Windows", dyn.Title);
|
||||
}
|
||||
Assert.Equal("FullNuspec", zp.Id);
|
||||
Assert.Equal(SemanticVersion.Parse("1.0.0"), zp.Version);
|
||||
Assert.Equal(new[] { "Anaïs Betts", "Caelan Sayler" }, dyn.Authors);
|
||||
Assert.Equal(new Uri("https://github.com/clowd/Clowd.Squirrel"), zp.ProjectUrl);
|
||||
Assert.Equal(new Uri("https://user-images.githubusercontent.com/1287295/131249078-9e131e51-0b66-4dc7-8c0a-99cbea6bcf80.png"), zp.IconUrl);
|
||||
Assert.Equal("A test description", dyn.Description);
|
||||
Assert.Equal("A summary", dyn.Summary);
|
||||
Assert.Equal("release notes\nwith multiple lines", zp.ReleaseNotes);
|
||||
Assert.Equal("Copyright ©", dyn.Copyright);
|
||||
Assert.Equal("en-US", zp.Language);
|
||||
Assert.Equal("Squirrel for Windows", dyn.Title);
|
||||
}
|
||||
|
||||
IEnumerable<ZipPackageFile> GetLibFiles(Package package)
|
||||
{
|
||||
return GetFiles(package, NugetUtil.LibDirectory);
|
||||
}
|
||||
IEnumerable<ZipPackageFile> GetLibFiles(Package package)
|
||||
{
|
||||
return GetFiles(package, NugetUtil.LibDirectory);
|
||||
}
|
||||
|
||||
IEnumerable<ZipPackageFile> GetFiles(Package package, string directory)
|
||||
{
|
||||
string folderPrefix = directory + Path.DirectorySeparatorChar;
|
||||
return GetFiles(package).Where(file => file.Path.StartsWith(folderPrefix, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
IEnumerable<ZipPackageFile> GetFiles(Package package, string directory)
|
||||
{
|
||||
string folderPrefix = directory + Path.DirectorySeparatorChar;
|
||||
return GetFiles(package).Where(file => file.Path.StartsWith(folderPrefix, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
List<ZipPackageFile> GetFiles(Package package)
|
||||
{
|
||||
return (from part in package.GetParts()
|
||||
where IsPackageFile(part)
|
||||
select new ZipPackageFile(part.Uri)).ToList();
|
||||
}
|
||||
List<ZipPackageFile> GetFiles(Package package)
|
||||
{
|
||||
return (from part in package.GetParts()
|
||||
where IsPackageFile(part)
|
||||
select new ZipPackageFile(part.Uri)).ToList();
|
||||
}
|
||||
|
||||
bool IsPackageFile(PackagePart part)
|
||||
{
|
||||
string path = NugetUtil.GetPath(part.Uri);
|
||||
string directory = Path.GetDirectoryName(path);
|
||||
string[] ExcludePaths = new[] { "_rels", "package" };
|
||||
return !ExcludePaths.Any(p => directory.StartsWith(p, StringComparison.OrdinalIgnoreCase)) && !IsManifest(path);
|
||||
}
|
||||
bool IsPackageFile(PackagePart part)
|
||||
{
|
||||
string path = NugetUtil.GetPath(part.Uri);
|
||||
string directory = Path.GetDirectoryName(path);
|
||||
string[] ExcludePaths = new[] { "_rels", "package" };
|
||||
return !ExcludePaths.Any(p => directory.StartsWith(p, StringComparison.OrdinalIgnoreCase)) && !IsManifest(path);
|
||||
}
|
||||
|
||||
bool IsManifest(string p)
|
||||
{
|
||||
return Path.GetExtension(p).Equals(NugetUtil.ManifestExtension, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
bool IsManifest(string p)
|
||||
{
|
||||
return Path.GetExtension(p).Equals(NugetUtil.ManifestExtension, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user