Create SquirrelCli project, merge SyncReleases into it, refactor command line args

This commit is contained in:
Caelan Sayler
2021-10-30 18:02:52 +01:00
parent 96f4d507f0
commit f091d5145b
28 changed files with 995 additions and 317 deletions

View File

@@ -11,8 +11,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Setup", "src\Setup\Setup.vc
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Update", "src\Update\Update.csproj", "{1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Update", "src\Update\Update.csproj", "{1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SyncReleases", "src\SyncReleases\SyncReleases.csproj", "{EB521191-1EBF-4D06-8541-ED192E2EE378}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionLevel", "SolutionLevel", "{ED657D2C-F8A0-4012-A64F-7367D41BE4D2}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionLevel", "SolutionLevel", "{ED657D2C-F8A0-4012-A64F-7367D41BE4D2}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig .editorconfig = .editorconfig
@@ -26,6 +24,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WriteZipToSetup", "src\Writ
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StubExecutable", "src\StubExecutable\StubExecutable.vcxproj", "{C028DB2A-E7C5-4232-8C22-D5FBA2176136}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StubExecutable", "src\StubExecutable\StubExecutable.vcxproj", "{C028DB2A-E7C5-4232-8C22-D5FBA2176136}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SquirrelCli", "src\SquirrelCli\SquirrelCli.csproj", "{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
CIBuild|Any CPU = CIBuild|Any CPU CIBuild|Any CPU = CIBuild|Any CPU
@@ -244,54 +244,6 @@ Global
{1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|x64.Build.0 = Release|Any CPU {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|x64.Build.0 = Release|Any CPU
{1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|x86.ActiveCfg = Release|Any CPU {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|x86.ActiveCfg = Release|Any CPU
{1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|x86.Build.0 = Release|Any CPU {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|x86.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.CIBuild|Any CPU.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.CIBuild|Any CPU.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.CIBuild|Mixed Platforms.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.CIBuild|Mixed Platforms.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.CIBuild|x64.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.CIBuild|x64.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.CIBuild|x86.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.CIBuild|x86.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Coverage|Any CPU.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Coverage|Any CPU.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Coverage|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Coverage|Mixed Platforms.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Coverage|x64.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Coverage|x64.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Coverage|x86.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Coverage|x86.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Debug|x64.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Debug|x64.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Debug|x86.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Debug|x86.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Debug|x64.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Debug|x64.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Debug|x86.ActiveCfg = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Debug|x86.Build.0 = Debug|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Release|Any CPU.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Release|Mixed Platforms.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Release|x64.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Release|x64.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Release|x86.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Mono Release|x86.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Release|Any CPU.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Release|x64.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Release|x64.Build.0 = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Release|x86.ActiveCfg = Release|Any CPU
{EB521191-1EBF-4D06-8541-ED192E2EE378}.Release|x86.Build.0 = Release|Any CPU
{4D3C8B70-075D-48A5-9FF3-EDB87347B136}.CIBuild|Any CPU.ActiveCfg = Debug|Win32 {4D3C8B70-075D-48A5-9FF3-EDB87347B136}.CIBuild|Any CPU.ActiveCfg = Debug|Win32
{4D3C8B70-075D-48A5-9FF3-EDB87347B136}.CIBuild|Any CPU.Build.0 = Debug|Win32 {4D3C8B70-075D-48A5-9FF3-EDB87347B136}.CIBuild|Any CPU.Build.0 = Debug|Win32
{4D3C8B70-075D-48A5-9FF3-EDB87347B136}.CIBuild|Mixed Platforms.ActiveCfg = Release|Win32 {4D3C8B70-075D-48A5-9FF3-EDB87347B136}.CIBuild|Mixed Platforms.ActiveCfg = Release|Win32
@@ -386,6 +338,54 @@ Global
{C028DB2A-E7C5-4232-8C22-D5FBA2176136}.Release|x64.Build.0 = Release|x64 {C028DB2A-E7C5-4232-8C22-D5FBA2176136}.Release|x64.Build.0 = Release|x64
{C028DB2A-E7C5-4232-8C22-D5FBA2176136}.Release|x86.ActiveCfg = Release|Win32 {C028DB2A-E7C5-4232-8C22-D5FBA2176136}.Release|x86.ActiveCfg = Release|Win32
{C028DB2A-E7C5-4232-8C22-D5FBA2176136}.Release|x86.Build.0 = Release|Win32 {C028DB2A-E7C5-4232-8C22-D5FBA2176136}.Release|x86.Build.0 = Release|Win32
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.CIBuild|Any CPU.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.CIBuild|Any CPU.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.CIBuild|Mixed Platforms.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.CIBuild|Mixed Platforms.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.CIBuild|x64.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.CIBuild|x64.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.CIBuild|x86.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.CIBuild|x86.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Coverage|Any CPU.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Coverage|Any CPU.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Coverage|Mixed Platforms.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Coverage|Mixed Platforms.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Coverage|x64.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Coverage|x64.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Coverage|x86.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Coverage|x86.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Debug|x64.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Debug|x64.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Debug|x86.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Debug|x86.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Debug|x64.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Debug|x64.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Debug|x86.ActiveCfg = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Debug|x86.Build.0 = Debug|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Release|Any CPU.Build.0 = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Release|Mixed Platforms.Build.0 = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Release|x64.ActiveCfg = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Release|x64.Build.0 = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Release|x86.ActiveCfg = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Mono Release|x86.Build.0 = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Release|Any CPU.Build.0 = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Release|x64.ActiveCfg = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Release|x64.Build.0 = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Release|x86.ActiveCfg = Release|Any CPU
{19E8EBF5-0277-422F-BF49-C66D9DBA5AA4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -20,11 +20,11 @@ foreach ($Folder in $Folders) {
# Build single-exe packaged projects # Build single-exe packaged projects
dotnet publish -v minimal -c Release "$PSScriptRoot\src\Update\Update.csproj" -o "$Out" dotnet publish -v minimal -c Release "$PSScriptRoot\src\Update\Update.csproj" -o "$Out"
dotnet publish -v minimal -c Release "$PSScriptRoot\src\SyncReleases\SyncReleases.csproj" -o "$Out" dotnet publish -v minimal -c Release "$PSScriptRoot\src\SquirrelCli\SquirrelCli.csproj" -o "$Out"
# Copy over all files we need # Copy over all files we need
Move-Item "$Out\Update.exe" -Destination "$Out\Squirrel.exe" # Move-Item "$Out\Update.exe" -Destination "$Out\Squirrel.exe"
Move-Item "$Out\Update.com" -Destination "$Out\Squirrel.com" # Move-Item "$Out\Update.com" -Destination "$Out\Squirrel.com"
# Move-Item "$Out\Update.pdb" -Destination "$Out\Squirrel.pdb" # Move-Item "$Out\Update.pdb" -Destination "$Out\Squirrel.pdb"
# New-Item -Path "$Out\lib" -ItemType "directory" | Out-Null # New-Item -Path "$Out\lib" -ItemType "directory" | Out-Null
@@ -38,7 +38,9 @@ Copy-Item "$In\Win32\WriteZipToSetup.pdb" -Destination "$Out"
Copy-Item -Path "$PSScriptRoot\vendor\7zip\*" -Destination "$Out" -Recurse Copy-Item -Path "$PSScriptRoot\vendor\7zip\*" -Destination "$Out" -Recurse
Copy-Item -Path "$PSScriptRoot\vendor\wix\*" -Destination "$Out" -Recurse Copy-Item -Path "$PSScriptRoot\vendor\wix\*" -Destination "$Out" -Recurse
Copy-Item "$PSScriptRoot\.nuget\NuGet.exe" -Destination "$Out" Copy-Item "$PSScriptRoot\vendor\NuGet.exe" -Destination "$Out"
Copy-Item "$PSScriptRoot\vendor\rcedit.exe" -Destination "$Out"
Copy-Item "$PSScriptRoot\vendor\signtool.exe" -Destination "$Out"
Remove-Item "$Out\*.pdb" Remove-Item "$Out\*.pdb"

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Squirrel.Update namespace Squirrel.Lib
{ {
internal static class AuthenticodeTools internal static class AuthenticodeTools
{ {

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.Options;
namespace Squirrel.Lib
{
internal class OptionValidationException : Exception
{
public OptionValidationException(string message) : base(message)
{
}
public OptionValidationException(string propertyName, string message) : base($"Argument '{propertyName}': {message}")
{
}
}
internal abstract class ValidatedOptionSet : OptionSet
{
protected virtual bool IsNullOrDefault(string propertyName)
{
var p = this.GetType().GetProperty(propertyName);
object argument = p.GetValue(this, null);
// deal with normal scenarios
if (argument == null) return true;
// deal with non-null nullables
Type methodType = argument.GetType();
if (Nullable.GetUnderlyingType(methodType) != null) return false;
// deal with boxed value types
Type argumentType = argument.GetType();
if (argumentType.IsValueType && argumentType != methodType) {
object obj = Activator.CreateInstance(argument.GetType());
return obj.Equals(argument);
}
return false;
}
protected virtual void IsRequired(params string[] propertyNames)
{
foreach (var property in propertyNames) {
IsRequired(property);
}
}
protected virtual void IsRequired(string propertyName)
{
if (IsNullOrDefault(propertyName))
throw new OptionValidationException($"Argument '{propertyName}' is required");
}
protected virtual void IsValidFile(string propertyName)
{
var p = this.GetType().GetProperty(propertyName);
var path = p.GetValue(this, null) as string;
if (path != null)
if (!File.Exists(path))
throw new OptionValidationException($"Argument '{propertyName}': Expected file to exist at this location but no file was found");
}
protected virtual void IsValidUrl(string propertyName)
{
var p = this.GetType().GetProperty(propertyName);
var val = p.GetValue(this, null) as string;
if (val != null)
if (!Utility.IsHttpUrl(val))
throw new OptionValidationException(propertyName, "Must start with http or https and be a valid URI.");
}
public abstract void Validate();
public virtual void WriteOptionDescriptions()
{
WriteOptionDescriptions(Console.Out);
}
}
internal abstract class CommandAction
{
public string Command { get; protected set; }
public string Description { get; protected set; }
public abstract void Execute(IEnumerable<string> args);
public abstract void PrintHelp();
}
internal class CommandAction<T> : CommandAction where T : ValidatedOptionSet, new()
{
public T Options { get; }
public Action<T> Action { get; }
public CommandAction(string command, string description, T options, Action<T> action)
{
Command = command;
Description = description;
Options = options;
Action = action;
}
public override void Execute(IEnumerable<string> args)
{
Options.Parse(args);
Options.Validate();
Action(Options);
}
public override void PrintHelp()
{
Options.WriteOptionDescriptions();
}
}
internal class CommandSet : List<CommandAction>
{
//public CommandSet() : base(StringComparer.InvariantCultureIgnoreCase) { }
public void Add<T>(string command, string description, T options, Action<T> action) where T : ValidatedOptionSet, new()
{
this.Add(new CommandAction<T>(command, description, options, action));
}
public virtual void Execute(string[] args)
{
if (args.Length == 0)
throw new OptionValidationException("Must specify a command to execute.");
var combined = String.Join(" ", args);
CommandAction cmd = null;
foreach (var k in this.OrderByDescending(k => k.Command.Length)) {
if (combined.StartsWith(k.Command, StringComparison.InvariantCultureIgnoreCase)) {
cmd = k;
break;
}
}
if (cmd == null)
throw new OptionValidationException($"Command was not specified or does not exist.");
cmd.Execute(combined.Substring(cmd.Command.Length).Split(' '));
}
public virtual void WriteHelp()
{
var exeName = Path.GetFileName(AssemblyRuntimeInfo.EntryExePath);
Console.WriteLine($"Usage: {exeName} [command] [options]");
Console.WriteLine();
Console.WriteLine("Commands:");
var array = this.ToArray();
for (var i = 0; i < array.Length; i++) {
var c = array[i];
// print command name + desc
Console.WriteLine();
Utility.ConsoleWriteWithColor(c.Command, ConsoleColor.Blue);
if (!String.IsNullOrWhiteSpace(c.Description))
Console.Write(": " + c.Description);
//Console.Write(c.Command);
//if(String.IsNullOrWhiteSpace(c.Description))
// Console.WriteLine();
//else
// Console.WriteLine(": " + c.Description);
//Console.Write(c.);
// group similar command parameters together
if (i + 1 < array.Length) {
if (c.GetType() == array[i + 1].GetType()) {
continue;
}
}
Console.WriteLine();
c.PrintHelp();
//Console.WriteLine();
//c.Value.WriteOptionDescriptions();
//Console.WriteLine();
}
}
}
}

View File

@@ -6,3 +6,5 @@ using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("Squirrel.Tests")] [assembly: InternalsVisibleTo("Squirrel.Tests")]
[assembly: InternalsVisibleTo("Update")] [assembly: InternalsVisibleTo("Update")]
[assembly: InternalsVisibleTo("SyncReleases")] [assembly: InternalsVisibleTo("SyncReleases")]
[assembly: InternalsVisibleTo("SquirrelCli")]
[assembly: InternalsVisibleTo("Squirrel")]

View File

@@ -8,6 +8,7 @@
<Title>Squirrel</Title> <Title>Squirrel</Title>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>9</LangVersion> <LangVersion>9</LangVersion>
<AssemblyName>Squirrel.Lib</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -341,16 +341,24 @@ namespace Squirrel
} }
} }
public static string FindHelperExecutable(string toFind, IEnumerable<string> additionalDirs = null) public static string FindHelperExecutable(string toFind, IEnumerable<string> additionalDirs = null, bool throwWhenNotFound = false)
{ {
if (File.Exists(toFind))
return Path.GetFullPath(toFind);
additionalDirs = additionalDirs ?? Enumerable.Empty<string>(); additionalDirs = additionalDirs ?? Enumerable.Empty<string>();
var dirs = (new[] { AppContext.BaseDirectory, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) }) var dirs = (new[] { AppContext.BaseDirectory, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) })
.Concat(additionalDirs ?? Enumerable.Empty<string>()); .Concat(additionalDirs ?? Enumerable.Empty<string>()).Select(Path.GetFullPath);
var exe = @".\" + toFind; var exe = @".\" + toFind;
return dirs var result = dirs
.Select(x => Path.Combine(x, toFind)) .Select(x => Path.Combine(x, toFind))
.FirstOrDefault(x => File.Exists(x)) ?? exe; .FirstOrDefault(x => File.Exists(x));
if (result == null && throwWhenNotFound)
throw new Exception($"Could not find helper '{exe}'.");
return result ?? exe;
} }
static string find7Zip() static string find7Zip()
@@ -668,6 +676,47 @@ namespace Squirrel
return This.Log().LogIfThrows(LogLevel.Error, message, block); return This.Log().LogIfThrows(LogLevel.Error, message, block);
} }
public static void WarnIfThrows(this IFullLogger This, Action block, string message = null)
{
This.LogIfThrows(LogLevel.Warn, message, block);
}
public static Task WarnIfThrows(this IFullLogger This, Func<Task> block, string message = null)
{
return This.LogIfThrows(LogLevel.Warn, message, block);
}
public static Task<T> WarnIfThrows<T>(this IFullLogger This, Func<Task<T>> block, string message = null)
{
return This.LogIfThrows(LogLevel.Warn, message, block);
}
public static void ErrorIfThrows(this IFullLogger This, Action block, string message = null)
{
This.LogIfThrows(LogLevel.Error, message, block);
}
public static Task ErrorIfThrows(this IFullLogger This, Func<Task> block, string message = null)
{
return This.LogIfThrows(LogLevel.Error, message, block);
}
public static Task<T> ErrorIfThrows<T>(this IFullLogger This, Func<Task<T>> block, string message = null)
{
return This.LogIfThrows(LogLevel.Error, message, block);
}
public static void ConsoleWriteWithColor(string text, ConsoleColor color)
{
var fc = Console.ForegroundColor;
var bc = Console.BackgroundColor;
Console.ForegroundColor = color;
Console.BackgroundColor = ConsoleColor.Black;
Console.Write(text);
Console.ForegroundColor = fc;
Console.BackgroundColor = bc;
}
static IFullLogger logger; static IFullLogger logger;
static IFullLogger Log() static IFullLogger Log()
{ {

129
src/SquirrelCli/Options.cs Normal file
View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Squirrel.Lib;
namespace SquirrelCli
{
internal abstract class BaseOptions : ValidatedOptionSet
{
public string releaseDir { get; private set; } = ".\\Releases";
public BaseOptions()
{
Add("r=|releaseDir=", "Release directory containing releasified packages", v => releaseDir = v);
}
}
internal class ReleasifyOptions : BaseOptions
{
public string package { get; set; }
public string splashImage { get; private set; }
public string iconPath { get; private set; }
public string signParams { get; private set; }
public string framework { get; private set; }
public bool noDelta { get; private set; }
public string baseUrl { get; private set; }
public ReleasifyOptions()
{
Add("p=|package=", "Path to a nuget package to releasify", v => package = v);
Add("s=|splashImage=", "Image to be displayed during installation (can be jpg, png, gif, etc)", v => splashImage = v);
Add("i=|iconPath=", "Ico file that will be used where possible", v => iconPath = v);
Add("n=|signParams=", "Sign the installer via SignTool.exe with the parameters given", v => signParams = v);
Add("f=|framework=", "Set the required .NET framework version, e.g. net461", v => framework = v);
Add("no-delta", "Don't generate delta packages to save time", v => noDelta = true);
Add("b=|baseUrl=", "Provides a base URL to prefix the RELEASES file packages with", v => baseUrl = v, true);
}
public override void Validate()
{
IsValidFile(nameof(iconPath));
IsValidFile(nameof(splashImage));
IsValidUrl(nameof(baseUrl));
IsRequired(nameof(package));
IsValidFile(nameof(package));
}
}
internal class PackOptions : ReleasifyOptions
{
public string packName { get; private set; }
public string packVersion { get; private set; }
public string packAuthors { get; private set; }
public string packDirectory { get; private set; }
public PackOptions()
{
Add("packName=", "desc", v => packName = v);
Add("packVersion=", "desc", v => packVersion = v);
Add("packAuthors=", "desc", v => packAuthors = v);
Add("packDirectory=", "desc", v => packDirectory = v);
// remove 'package' argument
Remove("package");
Remove("p");
}
public override void Validate()
{
IsRequired(nameof(packName), nameof(packVersion), nameof(packAuthors), nameof(packDirectory));
IsValidFile(nameof(iconPath));
IsValidFile(nameof(splashImage));
IsValidUrl(nameof(baseUrl));
}
}
internal class SyncBackblazeOptions : BaseOptions
{
public string b2KeyId { get; private set; }
public string b2AppKey { get; private set; }
public string b2BucketId { get; private set; }
public SyncBackblazeOptions()
{
Add("b2BucketId=", "Id or name of the bucket in B2, S3, etc", v => b2BucketId = v);
Add("b2keyid=", "B2 Auth Key Id", v => b2KeyId = v);
Add("b2key=", "B2 Auth Key", v => b2AppKey = v);
}
public override void Validate()
{
IsRequired(nameof(b2KeyId), nameof(b2AppKey), nameof(b2BucketId));
}
}
internal class SyncHttpOptions : BaseOptions
{
public string url { get; private set; }
public string token { get; private set; }
public SyncHttpOptions()
{
Add("url=", "Url to the simple http folder where the releases are found", v => url = v);
}
public override void Validate()
{
IsRequired(nameof(url));
}
}
internal class SyncGithubOptions : BaseOptions
{
public string repoUrl { get; private set; }
public string token { get; private set; }
public SyncGithubOptions()
{
Add("repoUrl=", "Url to the github repository (eg. 'https://github.com/myname/myrepo')", v => repoUrl = v);
Add("token=", "The oauth token to use as login credentials", v => token = v);
}
public override void Validate()
{
IsRequired(nameof(repoUrl));
}
}
}

407
src/SquirrelCli/Program.cs Normal file
View File

@@ -0,0 +1,407 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Mono.Options;
using Squirrel;
using Squirrel.Json;
using Squirrel.Lib;
using Squirrel.NuGet;
using Squirrel.SimpleSplat;
using SquirrelCli.Sources;
namespace SquirrelCli
{
class Program : IEnableLogger
{
public static int Main(string[] args)
{
//var pg = new Program();
var commands = new CommandSet {
{ "releasify", "Take an existing nuget package and turn it into a Squirrel release", new ReleasifyOptions(), Releasify },
{ "pack", "Creates a nuget package from a folder and releasifies it in a single step", new PackOptions(), Pack },
{ "b2-down", "Download recent releases from BackBlaze B2", new SyncBackblazeOptions(), o => new BackblazeRepository(o).DownloadRecentPackages().Wait() },
{ "b2-up", "Upload releases to BackBlaze B2", new SyncBackblazeOptions(), o => new BackblazeRepository(o).UploadMissingPackages().Wait() },
{ "http-down", "Download recent releases from an HTTP source", new SyncHttpOptions(), o => new SimpleWebRepository(o).DownloadRecentPackages().Wait() },
//{ "http-up", "sync", new SyncHttpOptions(), o => new SimpleWebRepository(o).UploadMissingPackages().Wait() },
{ "github-down", "Download recent releases from GitHub", new SyncGithubOptions(), o => new GitHubRepository(o).DownloadRecentPackages().Wait() },
//{ "github-up", "sync", new SyncGithubOptions(), o => new GitHubRepository(o).UploadMissingPackages().Wait() },
};
var logger = new ConsoleLogger();
try {
// check for help argument
bool help = false;
new OptionSet() { { "h|?|help", _ => help = true }, }.Parse(args);
if (help) {
commands.WriteHelp();
return -1;
} else {
// parse cli and run command
SquirrelLocator.CurrentMutable.Register(() => logger, typeof(Squirrel.SimpleSplat.ILogger));
commands.Execute(args);
}
return 0;
} catch (Exception ex) {
Console.WriteLine();
logger.Write(ex.ToString(), LogLevel.Error);
Console.WriteLine();
commands.WriteHelp();
return -1;
}
}
static IFullLogger Log => SquirrelLocator.Current.GetService<ILogManager>().GetLogger(typeof(Program));
static string[] VendorDirs => new string[] {
Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "..", "..", "..", "vendor")
};
static string BootstrapperPath => Utility.FindHelperExecutable("Setup.exe", throwWhenNotFound: true);
static string UpdatePath => Utility.FindHelperExecutable("Update.exe", throwWhenNotFound: true);
static string NugetPath => Utility.FindHelperExecutable("NuGet.exe", VendorDirs, throwWhenNotFound: true);
static void Pack(PackOptions options)
{
using (Utility.WithTempDirectory(out var tmpDir)) {
string nuspec = $@"
<?xml version=""1.0"" encoding=""utf-8""?>
<package>
<metadata>
<id>{options.packName}</id>
<title>{options.packName}</title>
<description>{options.packName}</description>
<authors>{options.packAuthors}</authors>
<version>0</version>
</metadata>
<files>
<file src=""**"" target=""lib\app\"" exclude=""*.pdb;*.nupkg;*.vshost.*""/>
</files>
</package>
".Trim();
var nuspecPath = Path.Combine(tmpDir, options.packName + ".nuspec");
File.WriteAllText(nuspecPath, nuspec);
var args = $"pack \"{nuspecPath}\" -BasePath \"{options.packDirectory}\" -OutputDirectory \"{tmpDir}\" -Version {options.packVersion}";
Log.Info($"Packing '{options.packDirectory}' into nupkg.");
var res = Utility.InvokeProcessAsync(NugetPath, args, CancellationToken.None).Result;
if (res.Item1 != 0)
throw new Exception($"Failed nuget pack (exit {res.Item1}): \r\n " + res.Item2);
var nupkgPath = Directory.EnumerateFiles(tmpDir).Where(f => f.EndsWith(".nupkg")).FirstOrDefault();
if (nupkgPath == null)
throw new Exception($"Failed to generate nupkg, unspecified error");
options.package = nupkgPath;
Releasify(options);
}
}
static void Releasify(ReleasifyOptions options)
{
var targetDir = options.releaseDir ?? Path.Combine(".", "Releases");
if (!Directory.Exists(targetDir)) {
Directory.CreateDirectory(targetDir);
}
var frameworkVersion = options.framework;
var signingOpts = options.signParams;
var package = options.package;
var baseUrl = options.baseUrl;
var generateDeltas = !options.noDelta;
var backgroundGif = options.splashImage;
var setupIcon = options.iconPath;
// validate that the provided "frameworkVersion" is supported by Setup.exe
if (!String.IsNullOrWhiteSpace(frameworkVersion)) {
var chkFrameworkResult = Utility.InvokeProcessAsync(BootstrapperPath, "--checkFramework " + frameworkVersion, CancellationToken.None).Result;
if (chkFrameworkResult.Item1 != 0) {
throw new ArgumentException($"Unsupported FrameworkVersion: '{frameworkVersion}'. {chkFrameworkResult.Item2}");
}
}
// copy input package to target output directory
if (!package.EndsWith(".nupkg", StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException("package must be packed with nuget and end in '.nupkg'");
var di = new DirectoryInfo(targetDir);
File.Copy(package, Path.Combine(di.FullName, Path.GetFileName(package)), true);
var allNuGetFiles = di.EnumerateFiles()
.Where(x => x.Name.EndsWith(".nupkg", StringComparison.InvariantCultureIgnoreCase));
var toProcess = allNuGetFiles.Where(x => !x.Name.Contains("-delta") && !x.Name.Contains("-full"));
var processed = new List<string>();
var releaseFilePath = Path.Combine(di.FullName, "RELEASES");
var previousReleases = new List<ReleaseEntry>();
if (File.Exists(releaseFilePath)) {
previousReleases.AddRange(ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFilePath, Encoding.UTF8)));
}
foreach (var file in toProcess) {
Log.Info("Creating release package: " + file.FullName);
var rp = new ReleasePackage(file.FullName);
rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), contentsPostProcessHook: pkgPath => {
// create sub executable for all exe's in this package (except Squirrel!)
new DirectoryInfo(pkgPath).GetAllFilesRecursively()
.Where(x => x.Name.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
.Where(x => !x.Name.Contains("squirrel.exe", StringComparison.InvariantCultureIgnoreCase))
.Where(x => Utility.IsFileTopLevelInPackage(x.FullName, pkgPath))
.Where(x => Utility.ExecutableUsesWin32Subsystem(x.FullName))
.ForEachAsync(x => createExecutableStubForExe(x.FullName))
.Wait();
// copy myself into the package so Squirrel can also be updated
// how we find the lib dir is a huge hack here, but 'ReleasePackage' verifies there can only be one of these so it should be fine.
var re = new Regex(@"lib[\\\/][^\\\/]*[\\\/]?", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
var libDir = Directory
.EnumerateDirectories(pkgPath, "*", SearchOption.AllDirectories)
.Where(d => re.IsMatch(d))
.OrderBy(d => d.Length)
.FirstOrDefault();
File.Copy(UpdatePath, Path.Combine(libDir, "Squirrel.exe"));
// sign all exe's in this package
if (signingOpts == null) return;
new DirectoryInfo(pkgPath).GetAllFilesRecursively()
.Where(x => Utility.FileIsLikelyPEImage(x.Name))
.ForEachAsync(async x => {
if (isPEFileSigned(x.FullName)) {
Log.Info("{0} is already signed, skipping", x.FullName);
return;
}
Log.Info("About to sign {0}", x.FullName);
await signPEFile(x.FullName, signingOpts);
}, 1)
.Wait();
});
processed.Add(rp.ReleasePackageFile);
var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir);
if (prev != null && generateDeltas) {
var deltaBuilder = new DeltaPackageBuilder(null);
var dp = deltaBuilder.CreateDeltaPackage(prev, rp,
Path.Combine(di.FullName, rp.SuggestedReleaseFileName.Replace("full", "delta")));
processed.Insert(0, dp.InputPackageFile);
}
}
foreach (var file in toProcess) { File.Delete(file.FullName); }
var newReleaseEntries = processed
.Select(packageFilename => ReleaseEntry.GenerateFromFile(packageFilename, baseUrl))
.ToList();
var distinctPreviousReleases = previousReleases
.Where(x => !newReleaseEntries.Select(e => e.Version).Contains(x.Version));
var releaseEntries = distinctPreviousReleases.Concat(newReleaseEntries).ToList();
ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath);
var targetSetupExe = Path.Combine(di.FullName, "Setup.exe");
var newestFullRelease = Squirrel.EnumerableExtensions.MaxBy(releaseEntries, x => x.Version).Where(x => !x.IsDelta).First();
File.Copy(BootstrapperPath, targetSetupExe, true);
var zipPath = createSetupEmbeddedZip(Path.Combine(di.FullName, newestFullRelease.Filename), di.FullName, signingOpts, setupIcon).Result;
var writeZipToSetup = Utility.FindHelperExecutable("WriteZipToSetup.exe");
try {
string arguments = $"\"{targetSetupExe}\" \"{zipPath}\"";
if (!String.IsNullOrWhiteSpace(frameworkVersion)) {
arguments += $" --set-required-framework \"{frameworkVersion}\"";
}
if (!String.IsNullOrWhiteSpace(backgroundGif)) {
arguments += $" --set-splash \"{Path.GetFullPath(backgroundGif)}\"";
}
var result = Utility.InvokeProcessAsync(writeZipToSetup, arguments, CancellationToken.None).Result;
if (result.Item1 != 0) throw new Exception("Failed to write Zip to Setup.exe!\n\n" + result.Item2);
} catch (Exception ex) {
Log.ErrorException("Failed to update Setup.exe with new Zip file", ex);
throw;
} finally {
File.Delete(zipPath);
}
Utility.Retry(() =>
setPEVersionInfoAndIcon(targetSetupExe, new ZipPackage(package), setupIcon).Wait());
if (signingOpts != null) {
signPEFile(targetSetupExe, signingOpts).Wait();
}
//if (generateMsi) {
// createMsiPackage(targetSetupExe, new ZipPackage(package), packageAs64Bit).Wait();
// if (signingOpts != null) {
// signPEFile(targetSetupExe.Replace(".exe", ".msi"), signingOpts).Wait();
// }
//}
}
static async Task<string> createSetupEmbeddedZip(string fullPackage, string releasesDir, string signingOpts, string setupIcon)
{
string tempPath;
Log.Info("Building embedded zip file for Setup.exe");
using (Utility.WithTempDirectory(out tempPath, null)) {
Log.ErrorIfThrows(() => {
File.Copy(UpdatePath, Path.Combine(tempPath, "Update.exe"));
File.Copy(fullPackage, Path.Combine(tempPath, Path.GetFileName(fullPackage)));
}, "Failed to write package files to temp dir: " + tempPath);
if (!String.IsNullOrWhiteSpace(setupIcon)) {
Log.ErrorIfThrows(() => {
File.Copy(setupIcon, Path.Combine(tempPath, "setupIcon.ico"));
}, "Failed to write icon to temp dir: " + tempPath);
}
var releases = new[] { ReleaseEntry.GenerateFromFile(fullPackage) };
ReleaseEntry.WriteReleaseFile(releases, Path.Combine(tempPath, "RELEASES"));
var target = Path.GetTempFileName();
File.Delete(target);
// Sign Update.exe so that virus scanners don't think we're
// pulling one over on them
if (signingOpts != null) {
var di = new DirectoryInfo(tempPath);
var files = di.EnumerateFiles()
.Where(x => x.Name.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
.Select(x => x.FullName);
await files.ForEachAsync(x => signPEFile(x, signingOpts));
}
Log.ErrorIfThrows(() =>
ZipFile.CreateFromDirectory(tempPath, target, CompressionLevel.Optimal, false),
"Failed to create Zip file from directory: " + tempPath);
return target;
}
}
static async Task signPEFile(string exePath, string signingOpts)
{
// Try to find SignTool.exe
var exe = @".\signtool.exe";
if (!File.Exists(exe)) {
exe = Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "signtool.exe");
// Run down PATH and hope for the best
if (!File.Exists(exe)) exe = "signtool.exe";
}
var processResult = await Utility.InvokeProcessAsync(exe,
String.Format("sign {0} \"{1}\"", signingOpts, exePath), CancellationToken.None);
if (processResult.Item1 != 0) {
var optsWithPasswordHidden = new Regex(@"/p\s+\w+").Replace(signingOpts, "/p ********");
var msg = String.Format("Failed to sign, command invoked was: '{0} sign {1} {2}'",
exe, optsWithPasswordHidden, exePath);
throw new Exception(msg);
} else {
Console.WriteLine(processResult.Item2);
}
}
static bool isPEFileSigned(string path)
{
try {
return AuthenticodeTools.IsTrusted(path);
} catch (Exception ex) {
Log.ErrorException("Failed to determine signing status for " + path, ex);
return false;
}
}
static async Task createExecutableStubForExe(string fullName)
{
var exe = Utility.FindHelperExecutable(@"StubExecutable.exe");
var target = Path.Combine(
Path.GetDirectoryName(fullName),
Path.GetFileNameWithoutExtension(fullName) + "_ExecutionStub.exe");
await Utility.CopyToAsync(exe, target);
await Utility.InvokeProcessAsync(
Utility.FindHelperExecutable("WriteZipToSetup.exe"),
String.Format("--copy-stub-resources \"{0}\" \"{1}\"", fullName, target),
CancellationToken.None);
}
static async Task setPEVersionInfoAndIcon(string exePath, IPackage package, string iconPath = null)
{
var realExePath = Path.GetFullPath(exePath);
var company = String.Join(",", package.Authors);
var verStrings = new Dictionary<string, string>() {
{ "CompanyName", company },
{ "LegalCopyright", package.Copyright ?? "Copyright © " + DateTime.Now.Year.ToString() + " " + company },
{ "FileDescription", package.Summary ?? package.Description ?? "Installer for " + package.Id },
{ "ProductName", package.Description ?? package.Summary ?? package.Id },
};
var args = verStrings.Aggregate(new StringBuilder("\"" + realExePath + "\""), (acc, x) => { acc.AppendFormat(" --set-version-string \"{0}\" \"{1}\"", x.Key, x.Value); return acc; });
args.AppendFormat(" --set-file-version {0} --set-product-version {0}", package.Version.ToString());
if (iconPath != null) {
args.AppendFormat(" --set-icon \"{0}\"", Path.GetFullPath(iconPath));
}
// Try to find rcedit.exe
string exe = Utility.FindHelperExecutable("rcedit.exe");
var processResult = await Utility.InvokeProcessAsync(exe, args.ToString(), CancellationToken.None);
if (processResult.Item1 != 0) {
var msg = String.Format(
"Failed to modify resources, command invoked was: '{0} {1}'\n\nOutput was:\n{2}",
exe, args, processResult.Item2);
throw new Exception(msg);
} else {
Console.WriteLine(processResult.Item2);
}
}
}
class ConsoleLogger : Squirrel.SimpleSplat.ILogger
{
readonly object gate = 42;
public LogLevel Level { get; set; } = LogLevel.Info;
public void Write(string message, LogLevel logLevel)
{
if (logLevel < Level) {
return;
}
lock (gate) {
string lvl = logLevel.ToString().Substring(0, 4).ToUpper();
if (logLevel == LogLevel.Error || logLevel == LogLevel.Fatal) {
Utility.ConsoleWriteWithColor($"[{lvl}] {message}\r\n", ConsoleColor.Red);
} else if (logLevel == LogLevel.Warn) {
Utility.ConsoleWriteWithColor($"[{lvl}] {message}\r\n", ConsoleColor.Yellow);
} else {
Console.WriteLine($"[{lvl}] {message}");
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<CompressionInSingleFile>true</CompressionInSingleFile>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<ApplicationIcon>squirrel.ico</ApplicationIcon>
<AssemblyName>Squirrel</AssemblyName>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<ItemGroup>
<Content Include="squirrel.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.IO" Version="4.3.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
<PackageReference Include="B2Net" Version="0.7.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Squirrel\Squirrel.csproj" />
</ItemGroup>
</Project>

View File

@@ -8,17 +8,19 @@ using B2Net;
using B2Net.Models; using B2Net.Models;
using Squirrel; using Squirrel;
namespace Squirrel.SyncReleases.Sources namespace SquirrelCli.Sources
{ {
internal class BackblazeRepository : IPackageRepository internal class BackblazeRepository : IPackageRepository
{ {
private B2StorageProvider _b2; private B2StorageProvider _b2;
public BackblazeRepository(string keyId, string appKey, string bucketId) private DirectoryInfo releasesDir;
public BackblazeRepository(SyncBackblazeOptions options)
{ {
_b2 = new B2StorageProvider(keyId, appKey, bucketId); _b2 = new B2StorageProvider(options.b2KeyId, options.b2AppKey, options.b2BucketId);
releasesDir = new DirectoryInfo(options.releaseDir);
} }
public async Task DownloadRecentPackages(DirectoryInfo releasesDir) public async Task DownloadRecentPackages()
{ {
Console.WriteLine("Downloading RELEASES"); Console.WriteLine("Downloading RELEASES");
var releasesBytes = await _b2.DownloadFile("RELEASES"); var releasesBytes = await _b2.DownloadFile("RELEASES");
@@ -45,7 +47,7 @@ namespace Squirrel.SyncReleases.Sources
} }
} }
public async Task UploadMissingPackages(DirectoryInfo releasesDir) public async Task UploadMissingPackages()
{ {
foreach (var f in releasesDir.GetFiles()) { foreach (var f in releasesDir.GetFiles()) {
await _b2.UploadFile(File.ReadAllBytes(f.FullName), f.Name); await _b2.UploadFile(File.ReadAllBytes(f.FullName), f.Name);

View File

@@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Threading.Tasks;
namespace SquirrelCli.Sources
{
internal class GitHubRepository : IPackageRepository
{
private SyncGithubOptions _options;
public GitHubRepository(SyncGithubOptions options)
{
_options = options;
}
public Task DownloadRecentPackages()
{
return SyncImplementations.SyncFromGitHub(_options.repoUrl, _options.token, new DirectoryInfo(_options.releaseDir));
}
public Task UploadMissingPackages()
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,11 @@
using System.IO;
using System.Threading.Tasks;
namespace SquirrelCli.Sources
{
internal interface IPackageRepository
{
public Task DownloadRecentPackages();
public Task UploadMissingPackages();
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.IO;
using System.Threading.Tasks;
namespace SquirrelCli.Sources
{
internal class SimpleWebRepository : IPackageRepository
{
private readonly SyncHttpOptions options;
public SimpleWebRepository(SyncHttpOptions options)
{
this.options = options;
}
public Task DownloadRecentPackages()
{
return SyncImplementations.SyncRemoteReleases(new Uri(options.url), new DirectoryInfo(options.releaseDir));
}
public Task UploadMissingPackages()
{
throw new NotImplementedException();
}
}
}

View File

@@ -8,7 +8,7 @@ using Octokit;
using System.Reflection; using System.Reflection;
using System.Net; using System.Net;
namespace Squirrel.SyncReleases.Sources namespace SquirrelCli.Sources
{ {
internal class SyncImplementations internal class SyncImplementations
{ {
@@ -151,6 +151,7 @@ namespace Squirrel.SyncReleases.Sources
} }
} }
#pragma warning disable SYSLIB0014 // Type or member is obsolete
class NotBrokenWebClient : WebClient class NotBrokenWebClient : WebClient
{ {
protected override WebRequest GetWebRequest(Uri address) protected override WebRequest GetWebRequest(Uri address)
@@ -164,4 +165,5 @@ namespace Squirrel.SyncReleases.Sources
return hwr; return hwr;
} }
} }
#pragma warning restore SYSLIB0014 // Type or member is obsolete
} }

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.2.0.0" name="Squirrel.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View File

@@ -1,150 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Mono.Options;
using Octokit;
using Squirrel.SimpleSplat;
using Squirrel;
using Squirrel.Json;
using Squirrel.SyncReleases.Sources;
namespace Squirrel.SyncReleases
{
class Program : IEnableLogger
{
static OptionSet opts;
public static int Main(string[] args)
{
var pg = new Program();
try {
return pg.main(args).GetAwaiter().GetResult();
} catch (Exception ex) {
Console.Error.WriteLine(ex);
Console.Error.WriteLine("> SyncReleases.exe -h for help");
return -1;
}
}
async Task<int> main(string[] args)
{
using (var logger = new SetupLogLogger(false) { Level = Squirrel.SimpleSplat.LogLevel.Info }) {
Squirrel.SimpleSplat.SquirrelLocator.CurrentMutable.Register(() => logger, typeof(Squirrel.SimpleSplat.ILogger));
var releaseDir = default(string);
var repoUrl = default(string);
var token = default(string);
var provider = default(string);
var upload = default(bool);
var showHelp = default(bool);
var b2KeyId = default(string);
var b2AppKey = default(string);
var bucketId = default(string);
opts = new OptionSet() {
"Usage: SyncReleases.exe [OPTS]",
"Utility to download from or upload packages to a remote package release repository",
"Can be used to fully automate the distribution of new releases from CI or other scripts",
"",
"Options:",
{ "h|?|help", "Display help and exit", v => showHelp = true },
{ "r=|releaseDir=", "Path to the local release directory to sync with remote", v => releaseDir = v},
{ "u=|url=", "A GitHub repository url, or a remote web url", v => repoUrl = v},
{ "t=|token=", "The OAuth token to use as login credentials", v => token = v},
{ "p=|provider=", "Specify the release repository type, can be 'github', 'web', or 'b2'", v => provider = v },
{ "upload", "Upload releases in the releaseDir to the remote repository", v => upload = true },
{ "bucketId=", "Id or name of the bucket in B2, S3, etc", v => bucketId = v },
{ "b2keyid=", "B2 Auth Key Id", v => b2KeyId = v },
{ "b2key=", "B2 Auth Key", v => b2AppKey = v },
};
opts.Parse(args);
if (showHelp) {
ShowHelp();
return 0;
}
if (String.IsNullOrWhiteSpace(provider)) throw new ArgumentNullException(nameof(provider));
if (String.IsNullOrWhiteSpace(releaseDir)) throw new ArgumentNullException(nameof(releaseDir));
var releaseDirectoryInfo = new DirectoryInfo(releaseDir ?? Path.Combine(".", "Releases"));
if (!releaseDirectoryInfo.Exists) releaseDirectoryInfo.Create();
IPackageRepository repository;
if (provider.Equals("github", StringComparison.OrdinalIgnoreCase)) {
if (String.IsNullOrWhiteSpace(repoUrl)) throw new ArgumentNullException(nameof(repoUrl));
if (String.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(repoUrl));
repository = new GitHubRepository(repoUrl, token);
} else if (provider.Equals("web", StringComparison.OrdinalIgnoreCase)) {
if (String.IsNullOrWhiteSpace(repoUrl)) throw new ArgumentNullException(nameof(repoUrl));
repository = new SimpleWebRepository(new Uri(repoUrl));
} else if (provider.Equals("b2", StringComparison.OrdinalIgnoreCase)) {
if (String.IsNullOrWhiteSpace(b2KeyId)) throw new ArgumentNullException(nameof(b2KeyId));
if (String.IsNullOrWhiteSpace(b2AppKey)) throw new ArgumentNullException(nameof(b2AppKey));
if (String.IsNullOrWhiteSpace(bucketId)) throw new ArgumentNullException(nameof(bucketId));
repository = new BackblazeRepository(b2KeyId, b2AppKey, bucketId);
} else {
throw new Exception("Release provider missing or invalid");
}
var mode = upload ? "Uploading" : "Downloading";
Console.WriteLine(mode + " using provider " + repository.GetType().Name);
if (upload) {
await repository.UploadMissingPackages(releaseDirectoryInfo);
} else {
await repository.DownloadRecentPackages(releaseDirectoryInfo);
}
}
return 0;
}
public void ShowHelp()
{
opts.WriteOptionDescriptions(Console.Out);
}
}
class SetupLogLogger : Squirrel.SimpleSplat.ILogger, IDisposable
{
StreamWriter inner;
readonly object gate = 42;
public Squirrel.SimpleSplat.LogLevel Level { get; set; }
public SetupLogLogger(bool saveInTemp)
{
var dir = saveInTemp ?
Path.GetTempPath() :
AppContext.BaseDirectory;
var file = Path.Combine(dir, "SquirrelSetup.log");
if (File.Exists(file)) File.Delete(file);
inner = new StreamWriter(file, false, Encoding.UTF8);
}
public void Write(string message, LogLevel logLevel)
{
if (logLevel < Level) {
return;
}
lock (gate) inner.WriteLine(message);
}
public void Dispose()
{
lock (gate) inner.Dispose();
}
}
}

View File

@@ -1,28 +0,0 @@
using System;
using System.IO;
using System.Threading.Tasks;
namespace Squirrel.SyncReleases.Sources
{
internal class GitHubRepository : IPackageRepository
{
public string RepoUrl { get; }
public string Token { get; }
public GitHubRepository(string repoUrl, string token)
{
RepoUrl = repoUrl;
Token = token;
}
public Task DownloadRecentPackages(DirectoryInfo releasesDir)
{
return SyncImplementations.SyncFromGitHub(RepoUrl, Token, releasesDir);
}
public Task UploadMissingPackages(DirectoryInfo releasesDir)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,11 +0,0 @@
using System.IO;
using System.Threading.Tasks;
namespace Squirrel.SyncReleases.Sources
{
internal interface IPackageRepository
{
public Task DownloadRecentPackages(DirectoryInfo releasesDir);
public Task UploadMissingPackages(DirectoryInfo releasesDir);
}
}

View File

@@ -1,27 +0,0 @@
using System;
using System.IO;
using System.Threading.Tasks;
namespace Squirrel.SyncReleases.Sources
{
internal class SimpleWebRepository : IPackageRepository
{
public Uri TargetUri { get; }
public SimpleWebRepository(Uri targetUri)
{
TargetUri = targetUri;
}
public Task DownloadRecentPackages(DirectoryInfo releasesDir)
{
return SyncImplementations.SyncRemoteReleases(TargetUri, releasesDir);
}
public Task UploadMissingPackages(DirectoryInfo releasesDir)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<LangVersion>9</LangVersion>
<RootNamespace>Squirrel.SyncReleases</RootNamespace>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<PublishTrimmed>true</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.IO" Version="4.3.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
<PackageReference Include="B2Net" Version="0.7.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Squirrel\Squirrel.csproj" />
</ItemGroup>
</Project>

View File

@@ -13,6 +13,7 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squirrel.NuGet; using Squirrel.NuGet;
using Squirrel.Lib;
namespace Squirrel.Update namespace Squirrel.Update
{ {

Binary file not shown.

View File

@@ -22,12 +22,6 @@
<ApplicationIcon>update.ico</ApplicationIcon> <ApplicationIcon>update.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="rcedit.exe" CopyToOutputDirectory="PreserveNewest" />
<None Include="signtool.exe" CopyToOutputDirectory="PreserveNewest" />
<None Include="update.com" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Squirrel\Squirrel.csproj" /> <ProjectReference Include="..\Squirrel\Squirrel.csproj" />
</ItemGroup> </ItemGroup>