From cd823abb020af62611aa6afe16e1ac5b49e961b0 Mon Sep 17 00:00:00 2001
From: qwsdcvghyu89 <61093706+qwsdcvghyu89@users.noreply.github.com>
Date: Sun, 21 Sep 2025 15:03:14 +1000
Subject: [PATCH] Reapply "First Commit"
This reverts commit 3a6783ff7b7eca56f5579b794eccf191382faf75.
---
.../.idea/.gitignore | 13 +
.../.idea/encodings.xml | 4 +
.../.idea/indexLayout.xml | 8 +
.../.idea.aeqw89.tools.Publish/.idea/vcs.xml | 6 +
aeqw89.tools.Publish.sln | 16 +
aeqw89.tools.Publish.sln.DotSettings.user | 15 +
aeqw89.tools.Publish/.gitignore | 0
aeqw89.tools.Publish/Content.cs | 7 +
aeqw89.tools.Publish/Exceptions.Designer.cs | 224 +++++++++
aeqw89.tools.Publish/Exceptions.resx | 75 +++
aeqw89.tools.Publish/IncrementTarget.cs | 7 +
aeqw89.tools.Publish/Item.cs | 55 ++
aeqw89.tools.Publish/ItemGroup.cs | 26 +
aeqw89.tools.Publish/Mode.cs | 6 +
aeqw89.tools.Publish/PackageReference.cs | 30 ++
aeqw89.tools.Publish/PrivateAssetsValue.cs | 6 +
aeqw89.tools.Publish/Program.cs | 473 ++++++++++++++++++
aeqw89.tools.Publish/Project.cs | 54 ++
aeqw89.tools.Publish/ProjectFile.cs | 216 ++++++++
aeqw89.tools.Publish/ProjectReference.cs | 7 +
aeqw89.tools.Publish/PropertyGroup.cs | 28 ++
aeqw89.tools.Publish/RemotePath.cs | 90 ++++
aeqw89.tools.Publish/SshHosts.cs | 423 ++++++++++++++++
.../aeqw89.tools.Publish.csproj | 32 ++
.../net9.0/BouncyCastle.Cryptography.dll | Bin 0 -> 4870808 bytes
...sions.DependencyInjection.Abstractions.dll | Bin 0 -> 63768 bytes
...rosoft.Extensions.Logging.Abstractions.dll | Bin 0 -> 65288 bytes
.../bin/Debug/net9.0/Renci.SshNet.dll | Bin 0 -> 522752 bytes
.../bin/Debug/net9.0/Spectre.Console.Cli.dll | Bin 0 -> 192512 bytes
.../bin/Debug/net9.0/Spectre.Console.dll | Bin 0 -> 754176 bytes
.../bin/Debug/net9.0/VsTools.Projects.dll | Bin 0 -> 30208 bytes
.../net9.0/aeqw89.tools.Publish.deps.json | 176 +++++++
.../bin/Debug/net9.0/aeqw89.tools.Publish.dll | Bin 0 -> 66048 bytes
.../bin/Debug/net9.0/aeqw89.tools.Publish.exe | Bin 0 -> 145408 bytes
.../bin/Debug/net9.0/aeqw89.tools.Publish.pdb | Bin 0 -> 31956 bytes
.../aeqw89.tools.Publish.runtimeconfig.json | 12 +
.../de/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
.../es/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
.../fr/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
.../it/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
.../ja/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
.../ko/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
.../pt/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
.../ru/Spectre.Console.Cli.resources.dll | Bin 0 -> 5632 bytes
.../sv/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
.../zh-Hans/Spectre.Console.Cli.resources.dll | Bin 0 -> 5120 bytes
...CoreApp,Version=v9.0.AssemblyAttributes.cs | 4 +
.../Debug/net9.0/aeqw89.t.65D2674F.Up2Date | 0
.../aeqw89.tools.Publish.AssemblyInfo.cs | 22 +
...w89.tools.Publish.AssemblyInfoInputs.cache | 1 +
.../aeqw89.tools.Publish.Exceptions.resources | Bin 0 -> 2735 bytes
....GeneratedMSBuildEditorConfig.editorconfig | 15 +
.../aeqw89.tools.Publish.GlobalUsings.g.cs | 8 +
.../net9.0/aeqw89.tools.Publish.assets.cache | Bin 0 -> 7731 bytes
...ols.Publish.csproj.AssemblyReference.cache | Bin 0 -> 3222 bytes
...ols.Publish.csproj.CoreCompileInputs.cache | 1 +
....tools.Publish.csproj.FileListAbsolute.txt | 35 ++
...ools.Publish.csproj.GenerateResource.cache | Bin 0 -> 64 bytes
.../obj/Debug/net9.0/aeqw89.tools.Publish.dll | Bin 0 -> 66048 bytes
...eqw89.tools.Publish.genruntimeconfig.cache | 1 +
.../obj/Debug/net9.0/aeqw89.tools.Publish.pdb | Bin 0 -> 31956 bytes
.../obj/Debug/net9.0/apphost.exe | Bin 0 -> 145408 bytes
.../Debug/net9.0/ref/aeqw89.tools.Publish.dll | Bin 0 -> 17920 bytes
.../net9.0/refint/aeqw89.tools.Publish.dll | Bin 0 -> 17920 bytes
...w89.tools.Publish.csproj.nuget.dgspec.json | 86 ++++
.../aeqw89.tools.Publish.csproj.nuget.g.props | 15 +
...eqw89.tools.Publish.csproj.nuget.g.targets | 6 +
aeqw89.tools.Publish/obj/project.assets.json | 464 +++++++++++++++++
aeqw89.tools.Publish/obj/project.nuget.cache | 16 +
.../obj/project.packagespec.json | 1 +
.../obj/rider.project.model.nuget.info | 1 +
.../obj/rider.project.restore.info | 1 +
72 files changed, 2686 insertions(+)
create mode 100644 .idea/.idea.aeqw89.tools.Publish/.idea/.gitignore
create mode 100644 .idea/.idea.aeqw89.tools.Publish/.idea/encodings.xml
create mode 100644 .idea/.idea.aeqw89.tools.Publish/.idea/indexLayout.xml
create mode 100644 .idea/.idea.aeqw89.tools.Publish/.idea/vcs.xml
create mode 100644 aeqw89.tools.Publish.sln
create mode 100644 aeqw89.tools.Publish.sln.DotSettings.user
create mode 100644 aeqw89.tools.Publish/.gitignore
create mode 100644 aeqw89.tools.Publish/Content.cs
create mode 100644 aeqw89.tools.Publish/Exceptions.Designer.cs
create mode 100644 aeqw89.tools.Publish/Exceptions.resx
create mode 100644 aeqw89.tools.Publish/IncrementTarget.cs
create mode 100644 aeqw89.tools.Publish/Item.cs
create mode 100644 aeqw89.tools.Publish/ItemGroup.cs
create mode 100644 aeqw89.tools.Publish/Mode.cs
create mode 100644 aeqw89.tools.Publish/PackageReference.cs
create mode 100644 aeqw89.tools.Publish/PrivateAssetsValue.cs
create mode 100644 aeqw89.tools.Publish/Program.cs
create mode 100644 aeqw89.tools.Publish/Project.cs
create mode 100644 aeqw89.tools.Publish/ProjectFile.cs
create mode 100644 aeqw89.tools.Publish/ProjectReference.cs
create mode 100644 aeqw89.tools.Publish/PropertyGroup.cs
create mode 100644 aeqw89.tools.Publish/RemotePath.cs
create mode 100644 aeqw89.tools.Publish/SshHosts.cs
create mode 100644 aeqw89.tools.Publish/aeqw89.tools.Publish.csproj
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/BouncyCastle.Cryptography.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/Microsoft.Extensions.Logging.Abstractions.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/Renci.SshNet.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/Spectre.Console.Cli.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/Spectre.Console.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/VsTools.Projects.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/aeqw89.tools.Publish.deps.json
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/aeqw89.tools.Publish.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/aeqw89.tools.Publish.exe
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/aeqw89.tools.Publish.pdb
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/aeqw89.tools.Publish.runtimeconfig.json
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/de/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/es/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/fr/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/it/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/ja/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/ko/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/pt/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/ru/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/sv/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/bin/Debug/net9.0/zh-Hans/Spectre.Console.Cli.resources.dll
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.t.65D2674F.Up2Date
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.AssemblyInfo.cs
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.AssemblyInfoInputs.cache
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.Exceptions.resources
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.GeneratedMSBuildEditorConfig.editorconfig
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.GlobalUsings.g.cs
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.assets.cache
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.csproj.AssemblyReference.cache
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.csproj.CoreCompileInputs.cache
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.csproj.FileListAbsolute.txt
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.csproj.GenerateResource.cache
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.dll
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.genruntimeconfig.cache
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/aeqw89.tools.Publish.pdb
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/apphost.exe
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/ref/aeqw89.tools.Publish.dll
create mode 100644 aeqw89.tools.Publish/obj/Debug/net9.0/refint/aeqw89.tools.Publish.dll
create mode 100644 aeqw89.tools.Publish/obj/aeqw89.tools.Publish.csproj.nuget.dgspec.json
create mode 100644 aeqw89.tools.Publish/obj/aeqw89.tools.Publish.csproj.nuget.g.props
create mode 100644 aeqw89.tools.Publish/obj/aeqw89.tools.Publish.csproj.nuget.g.targets
create mode 100644 aeqw89.tools.Publish/obj/project.assets.json
create mode 100644 aeqw89.tools.Publish/obj/project.nuget.cache
create mode 100644 aeqw89.tools.Publish/obj/project.packagespec.json
create mode 100644 aeqw89.tools.Publish/obj/rider.project.model.nuget.info
create mode 100644 aeqw89.tools.Publish/obj/rider.project.restore.info
diff --git a/.idea/.idea.aeqw89.tools.Publish/.idea/.gitignore b/.idea/.idea.aeqw89.tools.Publish/.idea/.gitignore
new file mode 100644
index 0000000..2784543
--- /dev/null
+++ b/.idea/.idea.aeqw89.tools.Publish/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/projectSettingsUpdater.xml
+/.idea.aeqw89.tools.Publish.iml
+/modules.xml
+/contentModel.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.aeqw89.tools.Publish/.idea/encodings.xml b/.idea/.idea.aeqw89.tools.Publish/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.aeqw89.tools.Publish/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.aeqw89.tools.Publish/.idea/indexLayout.xml b/.idea/.idea.aeqw89.tools.Publish/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.aeqw89.tools.Publish/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.aeqw89.tools.Publish/.idea/vcs.xml b/.idea/.idea.aeqw89.tools.Publish/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/.idea.aeqw89.tools.Publish/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aeqw89.tools.Publish.sln b/aeqw89.tools.Publish.sln
new file mode 100644
index 0000000..5edac2c
--- /dev/null
+++ b/aeqw89.tools.Publish.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aeqw89.tools.Publish", "aeqw89.tools.Publish\aeqw89.tools.Publish.csproj", "{B7F78AEB-BAC3-4D34-A655-BC0B5677EA42}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B7F78AEB-BAC3-4D34-A655-BC0B5677EA42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B7F78AEB-BAC3-4D34-A655-BC0B5677EA42}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B7F78AEB-BAC3-4D34-A655-BC0B5677EA42}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B7F78AEB-BAC3-4D34-A655-BC0B5677EA42}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/aeqw89.tools.Publish.sln.DotSettings.user b/aeqw89.tools.Publish.sln.DotSettings.user
new file mode 100644
index 0000000..57d78f9
--- /dev/null
+++ b/aeqw89.tools.Publish.sln.DotSettings.user
@@ -0,0 +1,15 @@
+
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ True
+ True
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/.gitignore b/aeqw89.tools.Publish/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/aeqw89.tools.Publish/Content.cs b/aeqw89.tools.Publish/Content.cs
new file mode 100644
index 0000000..03a7e7a
--- /dev/null
+++ b/aeqw89.tools.Publish/Content.cs
@@ -0,0 +1,7 @@
+using System.Xml;
+
+namespace aeqw89.tools.Publish;
+
+internal sealed class Content : Item {
+ public Content(XmlElement node) : base(node) { }
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/Exceptions.Designer.cs b/aeqw89.tools.Publish/Exceptions.Designer.cs
new file mode 100644
index 0000000..ce608e7
--- /dev/null
+++ b/aeqw89.tools.Publish/Exceptions.Designer.cs
@@ -0,0 +1,224 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace aeqw89.tools.Publish {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Exceptions {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Exceptions() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("aeqw89.tools.Publish.Exceptions", typeof(Exceptions).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The cloud host '{0}' is not an entry on this user's config file..
+ ///
+ internal static string cloud_host_not_found {
+ get {
+ return ResourceManager.GetString("cloud_host_not_found", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The mode '{0}' is invalid, the valid modes are [overwrite|increment].
+ ///
+ internal static string could_not_parse_mode {
+ get {
+ return ResourceManager.GetString("could_not_parse_mode", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The increment target '{0}' is invalid, the valid increment targets are [patch|minor|patch].
+ ///
+ internal static string could_not_parse_target {
+ get {
+ return ResourceManager.GetString("could_not_parse_target", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The 'dotnet nuget push' command failed with error message '{0}'.
+ ///
+ internal static string dotnet_nuget_push_failure {
+ get {
+ return ResourceManager.GetString("dotnet_nuget_push_failure", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to pack; ensure that 'dotnet build' succeeds before running this program..
+ ///
+ internal static string dotnet_pack_failure {
+ get {
+ return ResourceManager.GetString("dotnet_pack_failure", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Could not delete temporary directory '{0}' due to error '{1}'.
+ ///
+ internal static string failed_to_clean_up {
+ get {
+ return ResourceManager.GetString("failed_to_clean_up", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failde to prepare an upload directory on the path {0} for the remote host '{1}', after being detected as a {2} host. Server error is '{3}'.
+ ///
+ internal static string failed_to_prepare_server_directory {
+ get {
+ return ResourceManager.GetString("failed_to_prepare_server_directory", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The flag '{0}' requires exactly '{1}' parameters. You have entered '{2}'..
+ ///
+ internal static string flag_parameter_length_incorrect {
+ get {
+ return ResourceManager.GetString("flag_parameter_length_incorrect", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The '{0}' flag requires that argument with index '{1}' be of type '{2}'. You have entered '{3}' which has failed to be converted..
+ ///
+ internal static string flag_parameter_type_incorrect {
+ get {
+ return ResourceManager.GetString("flag_parameter_type_incorrect", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The directory '{0}' contains multiple .csproj files; this tool can only process one at a time..
+ ///
+ internal static string found_multiple_csproj {
+ get {
+ return ResourceManager.GetString("found_multiple_csproj", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Something went wrong loading this file; {0}.
+ ///
+ internal static string generic_error {
+ get {
+ return ResourceManager.GetString("generic_error", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to You must specify at least one destination..
+ ///
+ internal static string missing_destinations {
+ get {
+ return ResourceManager.GetString("missing_destinations", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to You must specify an increment target if you specified an increment mode; allowed increment targets are [patch|minor|major].
+ ///
+ internal static string missing_increment_target {
+ get {
+ return ResourceManager.GetString("missing_increment_target", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to You must specify a mode; allowed modes are [overwrite|increment].
+ ///
+ internal static string missing_mode {
+ get {
+ return ResourceManager.GetString("missing_mode", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No project file was found within the current directory..
+ ///
+ internal static string no_project_in_directory {
+ get {
+ return ResourceManager.GetString("no_project_in_directory", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The project file '{0}' is irreparable becuase it is missing a '{1}' property, and the value cannot be guessed..
+ ///
+ internal static string project_file_irreparable {
+ get {
+ return ResourceManager.GetString("project_file_irreparable", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Something went wrong; an attempt was made to load a non .csproj file as a project file..
+ ///
+ internal static string tried_loading_non_csproj_file {
+ get {
+ return ResourceManager.GetString("tried_loading_non_csproj_file", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The version string '{0}' is in an unidentifiable format..
+ ///
+ internal static string version_string_not_formatted_correctly {
+ get {
+ return ResourceManager.GetString("version_string_not_formatted_correctly", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/aeqw89.tools.Publish/Exceptions.resx b/aeqw89.tools.Publish/Exceptions.resx
new file mode 100644
index 0000000..640185f
--- /dev/null
+++ b/aeqw89.tools.Publish/Exceptions.resx
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ You must specify a mode; allowed modes are [overwrite|increment]
+
+
+ The mode '{0}' is invalid, the valid modes are [overwrite|increment]
+
+
+ The increment target '{0}' is invalid, the valid increment targets are [patch|minor|patch]
+
+
+ You must specify an increment target if you specified an increment mode; allowed increment targets are [patch|minor|major]
+
+
+ You must specify at least one destination.
+
+
+ No project file was found within the current directory.
+
+
+ The flag '{0}' requires exactly '{1}' parameters. You have entered '{2}'.
+
+
+ The '{0}' flag requires that argument with index '{1}' be of type '{2}'. You have entered '{3}' which has failed to be converted.
+
+
+ The version string '{0}' is in an unidentifiable format.
+
+
+ Something went wrong; an attempt was made to load a non .csproj file as a project file.
+
+
+ Something went wrong loading this file; {0}
+
+
+ The directory '{0}' contains multiple .csproj files; this tool can only process one at a time.
+
+
+ The project file '{0}' is irreparable becuase it is missing a '{1}' property, and the value cannot be guessed.
+
+
+ Failed to pack; ensure that 'dotnet build' succeeds before running this program.
+
+
+ Could not delete temporary directory '{0}' due to error '{1}'
+
+
+ The cloud host '{0}' is not an entry on this user's config file.
+
+
+ Failde to prepare an upload directory on the path {0} for the remote host '{1}', after being detected as a {2} host. Server error is '{3}'
+
+
+ The 'dotnet nuget push' command failed with error message '{0}'
+
+
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/IncrementTarget.cs b/aeqw89.tools.Publish/IncrementTarget.cs
new file mode 100644
index 0000000..57242c8
--- /dev/null
+++ b/aeqw89.tools.Publish/IncrementTarget.cs
@@ -0,0 +1,7 @@
+namespace aeqw89.tools.Publish;
+
+public enum IncrementTarget {
+ Patch,
+ Minor,
+ Major,
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/Item.cs b/aeqw89.tools.Publish/Item.cs
new file mode 100644
index 0000000..6a4ed7e
--- /dev/null
+++ b/aeqw89.tools.Publish/Item.cs
@@ -0,0 +1,55 @@
+using System.Xml;
+
+namespace aeqw89.tools.Publish;
+
+internal class Item {
+ public string ElementName { get; protected set; }
+ public string Include {
+ get => Node.GetAttribute("Include");
+ set => Node.SetAttribute("Include", value);
+ }
+
+ public string Version {
+ get => Node.GetAttribute("Version");
+ set => Node.SetAttribute("Version", value);
+ }
+
+ public string? Value {
+ get => Node.InnerText;
+ set { Node.InnerText = value ?? string.Empty; }
+ }
+ public XmlElement Node { get; }
+
+ protected Item(XmlElement node) {
+ Node = node;
+ ElementName = node.Name;
+ }
+
+ public static Item FromElement(XmlElement element) {
+ return element.Name switch {
+ "PackageReference" => new PackageReference(element),
+ "ProjectReference" => new ProjectReference(element),
+ "Content" => new Content(element),
+ _ => new Item(element),
+ };
+ }
+
+ public void AddChild(Item child) {
+ var imported = Node.OwnerDocument!.ImportNode(child.Node, true);
+ Node.AppendChild(imported);
+ }
+
+ public string? GetAttribute(string name) => Node.HasAttribute(name) ? Node.GetAttribute(name) : null;
+ public void SetAttribute(string name, string? value) {
+ if (value is null) {
+ if (Node.HasAttribute(name)) Node.RemoveAttribute(name);
+ return;
+ }
+ Node.SetAttribute(name, value);
+ }
+
+ public IEnumerable- GetChildElements() {
+ foreach (var e in Node.ChildNodes.OfType())
+ yield return new Item(e);
+ }
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/ItemGroup.cs b/aeqw89.tools.Publish/ItemGroup.cs
new file mode 100644
index 0000000..88824a4
--- /dev/null
+++ b/aeqw89.tools.Publish/ItemGroup.cs
@@ -0,0 +1,26 @@
+using System.Xml;
+
+namespace aeqw89.tools.Publish;
+
+internal class ItemGroup {
+ private readonly XmlElement _element;
+ public List
- Items { get; }
+
+ public void Remove() {
+ _element.ParentNode!.RemoveChild(_element);
+ }
+
+ public ItemGroup(XmlElement element) {
+ _element = element;
+ Items = element.ChildNodes
+ .OfType()
+ .Select(Item.FromElement)
+ .ToList();
+ }
+
+ public void Add(Item item) {
+ var imported = _element.OwnerDocument!.ImportNode(item.Node, true);
+ _element.AppendChild(imported);
+ Items.Add(Item.FromElement((XmlElement)imported));
+ }
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/Mode.cs b/aeqw89.tools.Publish/Mode.cs
new file mode 100644
index 0000000..8d13c46
--- /dev/null
+++ b/aeqw89.tools.Publish/Mode.cs
@@ -0,0 +1,6 @@
+namespace aeqw89.tools.Publish;
+
+public enum Mode {
+ Overwrite,
+ Increment,
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/PackageReference.cs b/aeqw89.tools.Publish/PackageReference.cs
new file mode 100644
index 0000000..7413954
--- /dev/null
+++ b/aeqw89.tools.Publish/PackageReference.cs
@@ -0,0 +1,30 @@
+using System.Xml;
+
+namespace aeqw89.tools.Publish;
+
+internal sealed class PackageReference : Item {
+ public PackageReference(XmlElement node) : base(node) { }
+
+ // inside PackageReference
+ public string? GetPackageVersion() {
+ // Prefer attribute, then child
+ var attr = GetAttribute("Version");
+ if (!string.IsNullOrEmpty(attr)) return attr;
+
+ var child = Node.SelectSingleNode("./Version") as XmlElement;
+ return child?.InnerText;
+ }
+
+ public void SetPackageVersion(string? version) {
+ if (Node.HasAttribute("Version") || (Node.SelectSingleNode("./Version") == null)) {
+ // If attribute exists (or no child yet), use attribute
+ SetAttribute("Version", version);
+ return;
+ }
+
+ // Else write to existing child
+ var child = (XmlElement)Node.SelectSingleNode("./Version")!;
+ child.InnerText = version ?? string.Empty;
+ }
+
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/PrivateAssetsValue.cs b/aeqw89.tools.Publish/PrivateAssetsValue.cs
new file mode 100644
index 0000000..39e923a
--- /dev/null
+++ b/aeqw89.tools.Publish/PrivateAssetsValue.cs
@@ -0,0 +1,6 @@
+namespace aeqw89.tools.Publish;
+
+public enum PrivateAssetsValue {
+ All,
+ None,
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/Program.cs b/aeqw89.tools.Publish/Program.cs
new file mode 100644
index 0000000..8a37de3
--- /dev/null
+++ b/aeqw89.tools.Publish/Program.cs
@@ -0,0 +1,473 @@
+using System.Collections;
+using System.Diagnostics;
+using Renci.SshNet;
+using Spectre.Console;
+using Spectre.Console.Cli;
+using VsTools.Projects;
+
+namespace aeqw89.tools.Publish;
+
+/*
+ * Structure of the program:
+ * - publish (executable)
+ * - overwrite
+ * - destinations
+ * - flags
+ * - increment
+ * - patch|minor|major
+ * - destinations
+ * - flags
+ * e.g. publish overwrite|increment [patch|minor|major] destinations [flags]
+ */
+
+public static class Program {
+ public static Mode Mode { get; set; }
+ public static IncrementTarget? Target { get; set; }
+ public static string[] Destinations { get; set; }
+ public static Dictionary Flags { get; set; }
+ public static bool Verbose { get; set; } = false;
+
+ public static void ReadArgs(string[] args) {
+ if (args.Length < 1) {
+ ShowError(Exceptions.missing_mode.EscapeMarkup());
+ ShowHelp();
+ return;
+ }
+
+ Mode = args[0] switch {
+ "overwrite" => Mode.Overwrite,
+ "increment" => Mode.Increment,
+ _ => (Mode)(-1)
+ };
+
+ if (Mode == (Mode)(-1)) {
+ ShowError(Exceptions.could_not_parse_mode.EscapeMarkup(), args[0].EscapeMarkup());
+ ShowHelp();
+ return;
+ }
+
+ if (args.Length < 2) {
+ if (Mode == Mode.Increment)
+ ShowError(Exceptions.missing_increment_target.EscapeMarkup());
+ else if (Mode == Mode.Overwrite)
+ ShowError(Exceptions.missing_destinations.EscapeMarkup());
+ ShowHelp();
+ return;
+ }
+
+ Destinations = args[1..];
+ Flags = [];
+ if (Mode == Mode.Increment) {
+ if (args.Length < 3) {
+ ShowError(Exceptions.missing_destinations.EscapeMarkup());
+ ShowHelp();
+ return;
+ }
+
+ Destinations = args[2..];
+
+ Target = args[1] switch {
+ "patch" => IncrementTarget.Patch,
+ "minor" => IncrementTarget.Minor,
+ "major" => IncrementTarget.Major,
+ _ => (IncrementTarget)(-1)
+ };
+
+ if (Target == (IncrementTarget)(-1)) {
+ ShowError(Exceptions.could_not_parse_target.EscapeMarkup(), args[1].EscapeMarkup());
+ ShowHelp();
+ return;
+ }
+ }
+
+ string? firstFlag = Destinations.FirstOrDefault(x => x.StartsWith('-'));
+ if (firstFlag == null) return;
+ string[] flags = Destinations.SkipWhile(x => x != firstFlag).ToArray();
+ Flags = ReadFlags(flags);
+ Destinations = Destinations.TakeWhile(x => x != firstFlag).ToArray();
+ Verbose = Flags.ContainsKey("--verbose") || Flags.ContainsKey("-v");
+ }
+
+ private static Dictionary ReadFlags(string[] flags) {
+ Dictionary result = [];
+ List collected = [];
+ string lastKey = flags[0];
+ if (flags.Length == 1)
+ result[lastKey] = [];
+ foreach (var flag in flags.Skip(1)) {
+ if (flag.StartsWith('-')) {
+ result[lastKey] = collected.ToArray();
+ collected = [];
+ lastKey = flag;
+ } else
+ collected.Add(flag);
+ }
+
+ return result;
+ }
+
+ public static async Task Main(string[] args) {
+ ReadArgs(args);
+
+ string packageId = "";
+ string version = "";
+
+ var result = AnsiConsole.Status()
+ .Spinner(Spinner.Known.Dots)
+ .Start("Preparing project", ctx => {
+ ctx.Status = "Locating project file";
+ if (!ProjectFile.TryLoad(Environment.CurrentDirectory, out var projectFile, out var error)) {
+ ShowError(error.EscapeMarkup());
+ return false;
+ }
+
+ packageId = projectFile.GetPackageId();
+
+ try {
+ projectFile.Backup();
+ if (Verbose)
+ AnsiConsole.WriteLine(
+ $"Created project file backup at {projectFile.GetDefaultBackupLocation()}");
+
+ ctx.Status = "Repairing project file";
+ if (!Flags.ContainsKey("--skip-repair"))
+ if (!projectFile.TryRepair(out error)) {
+ ShowError(error.EscapeMarkup());
+ projectFile.Restore();
+ return false;
+ }
+
+ if (Mode == Mode.Increment && !Flags.ContainsKey("--simulate")) {
+ int delta = 1;
+ if (Flags.TryGetValue("--delta", out var deltaStrings)) {
+ if (deltaStrings.Length != 1) {
+ ShowError(Exceptions.flag_parameter_length_incorrect.EscapeMarkup(), "--delta", 1,
+ deltaStrings.Length);
+ projectFile.Restore();
+ ShowHelp();
+ return false;
+ }
+
+ if (!int.TryParse(deltaStrings[0], out delta)) {
+ ShowError(Exceptions.flag_parameter_type_incorrect.EscapeMarkup(), "--delta", 0, nameof(Int32),
+ deltaStrings[0]);
+ projectFile.Restore();
+ ShowHelp();
+ return false;
+ }
+ }
+
+ ctx.Status = "Updating version";
+ var version = projectFile.GetVersion();
+ version = ChangeVersion(version,
+ Target == IncrementTarget.Patch ? delta : 0,
+ Target == IncrementTarget.Minor ? delta : 0,
+ Target == IncrementTarget.Major ? delta : 0,
+ (x, y) => x + y);
+
+ projectFile.SetVersion(version);
+ }
+ }
+ catch (Exception e) {
+ ShowError(Exceptions.generic_error.EscapeMarkup(), e.ToString().EscapeMarkup());
+ projectFile.Restore();
+ return false;
+ }
+
+ version = projectFile.GetVersion();
+
+ if (!Flags.ContainsKey("--simulate")) {
+ try {
+ var packageReferences = projectFile.GetPackageReferences();
+ foreach (var reference in packageReferences.Where(x => !projectFile.IsTransitive(x)))
+ projectFile.SetPrivateAssets(reference, PrivateAssetsValue.All);
+ foreach (var reference in packageReferences.Where(x => projectFile.IsTransitive(x)))
+ projectFile.RemovePackage(reference);
+
+ HashSet visited = [];
+ var projectReferences = new Queue
- (projectFile.GetProjectReferences().Cast
- ());
+ while (projectReferences.Count != 0) {
+ var reference = projectReferences.Dequeue();
+ visited.Add(reference.Include);
+
+ if (Verbose)
+ AnsiConsole.WriteLine($"Processing project reference {reference.Include} out of {visited.Count} so far");
+
+ projectFile.SetPrivateAssets(reference, PrivateAssetsValue.All);
+ string pathToReferencedProjectFile = projectFile.GetAbsoluteIncludePath(reference);
+ if (!ProjectFile.TryLoad(pathToReferencedProjectFile, out var referencedProjectFile,
+ out error)) {
+ ShowError(error.EscapeMarkup());
+ projectFile.Restore();
+ return false;
+ }
+
+ var referencedPackageReferences = referencedProjectFile.GetPackageReferences();
+ foreach (var package in referencedPackageReferences) {
+ if (Verbose)
+ AnsiConsole.WriteLine($"Hoisting package {package.Include} from {pathToReferencedProjectFile}");
+ var hoisted = projectFile.AddPackage(package);
+ projectFile.SetTransitive(hoisted, true);
+ projectFile.SetPrivateAssets(hoisted, PrivateAssetsValue.None);
+ referencedProjectFile.SetPrivateAssets(package, PrivateAssetsValue.All);
+ }
+
+ var referencedProjectReferences = referencedProjectFile.GetProjectReferences();
+ foreach (var project in referencedProjectReferences) {
+ if (!visited.Contains(project.Include))
+ projectReferences.Enqueue(project);
+ }
+ }
+ } catch (Exception e) {
+ ShowError(Exceptions.generic_error.EscapeMarkup(), e.ToString().EscapeMarkup());
+ projectFile.Restore();
+ return false;
+ }
+ }
+
+ projectFile.Save();
+ return true;
+ });
+
+ if (!result) {
+ return;
+ }
+
+ var outDir = Path.GetRandomFileName();
+ result = AnsiConsole.Status()
+ .Spinner(Spinner.Known.Dots)
+ .Start("Creating package with 'dotnet pack' ", ctx => {
+ var p = Process.Start(new ProcessStartInfo() {
+ FileName = "dotnet",
+ Arguments = $"pack -o {outDir}",
+ WorkingDirectory = Environment.CurrentDirectory,
+ UseShellExecute = Verbose,
+ RedirectStandardOutput = !Verbose,
+ RedirectStandardError = !Verbose
+ });
+ p?.WaitForExit();
+ return p?.ExitCode == 0;
+ });
+
+ if (!result) {
+ ShowError(Exceptions.dotnet_pack_failure.EscapeMarkup());
+ return;
+ }
+
+ var package = Directory.GetFiles(outDir, "*.nupkg").FirstOrDefault();
+ if (package == null) {
+ ShowError(Exceptions.generic_error.EscapeMarkup());
+ return;
+ }
+
+ var inMemory = await File.ReadAllBytesAsync(package);
+ var size = new FileInfo(package).Length;
+ const long bufferSize = 80 * 1024; // 80 KB
+ try {
+ await AnsiConsole.Progress()
+ .AutoClear(true)
+ .HideCompleted(true)
+ .Columns(new ProgressColumn[] {
+ new TaskDescriptionColumn(),
+ new ProgressBarColumn()
+ .RemainingStyle(Style.Parse("dim gray slowblink"))
+ .CompletedStyle(Style.Parse("green strikethrough"))
+ .FinishedStyle("green strikethrough"),
+ new DownloadedColumn(),
+ new RemainingTimeColumn(),
+ new TransferSpeedColumn(),
+ })
+ .StartAsync(async ctx => {
+ await Parallel.ForEachAsync(Destinations, new ParallelOptions() {
+ MaxDegreeOfParallelism = Environment.ProcessorCount,
+ }, async (dest, ct) => {
+ using var reader = new MemoryStream(inMemory);
+ var task = ctx.AddTask(dest, new ProgressTaskSettings() {
+ MaxValue = size
+ });
+
+ if (dest.StartsWith("local-")) {
+ var name = dest[("local-".Length)..];
+ var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ name, Path.GetFileName(package));
+ if (!Directory.Exists(Path.GetDirectoryName(path)))
+ Directory.CreateDirectory(Path.GetDirectoryName(path)!);
+ await using var writer = File.OpenWrite(path);
+ var buffer = new byte[bufferSize];
+ int read;
+ do {
+ read = await reader.ReadAsync(buffer, ct);
+ writer.Write(buffer, 0, read);
+ task.Increment(read);
+ } while (read > 0);
+ }
+
+ else if (dest.StartsWith("cloud-")) {
+ var name = dest[("cloud-".Length)..];
+ var connectionTask = ctx.AddTaskBefore($"Preparing cloud-{name}", new ProgressTaskSettings() {
+ MaxValue = 100
+ }, task);
+
+ if (!SshHosts.TryGetHost(name, out var host)) {
+ ShowError(Exceptions.cloud_host_not_found.EscapeMarkup(), name);
+ return;
+ }
+
+ var connectionInfo = SshHosts.GetConnection(name);
+ using var sshClient = new SshClient(connectionInfo);
+ if (!sshClient.IsConnected)
+ await sshClient.ConnectAsync(ct);
+ connectionTask.Increment(33);
+
+ var winC = sshClient.RunCommand("cmd /c ver");
+ var othC = sshClient.RunCommand("uname -s");
+
+ var os = (winC.ExitStatus, othC.ExitStatus) switch {
+ (0, _) => "windows",
+ (_, 0) => "linux",
+ _ => "unknown"
+ };
+
+ string remoteDirectory;
+ string packageFileDirectory;
+ if (os == "windows") {
+ var userDirC = sshClient.RunCommand("cmd /c echo %USERPROFILE%");
+ if (userDirC.ExitStatus != 0) {
+ ShowError(Exceptions.failed_to_prepare_server_directory, "n/a", name, os, userDirC.Result);
+ return;
+ }
+
+ var userDir = userDirC.Result.Trim();
+ remoteDirectory = RemotePath.Combine(RemoteOs.Windows,userDir, "dotnet-packages");
+ packageFileDirectory = RemotePath.Combine(RemoteOs.Windows, remoteDirectory, Path.GetFileName(package));
+
+ var mkdirC = sshClient.RunCommand($"cmd /c if not exist \"{remoteDirectory}\" mkdir \"{remoteDirectory}\"");
+ if (mkdirC.ExitStatus != 0) {
+ ShowError(Exceptions.failed_to_prepare_server_directory, remoteDirectory, name, os, mkdirC.Result);
+ return;
+ }
+ }
+ else if (os == "linux") {
+ var homeDirC = sshClient.RunCommand("printf %s \"$HOME\"");
+ if (homeDirC.ExitStatus != 0) {
+ ShowError(Exceptions.failed_to_prepare_server_directory, "n/a", name, os, homeDirC.Result);
+ return;
+ }
+ var homeDir = homeDirC.Result.Trim(); // no CRLF on unix, but Trim() is safest
+ remoteDirectory = RemotePath.Combine(RemoteOs.Unix, homeDir, ".dotnet-packages");
+ packageFileDirectory = RemotePath.Combine(RemoteOs.Unix, remoteDirectory, Path.GetFileName(package));
+
+ // Use -p and single quotes to handle spaces safely
+ var mkdirC = sshClient.RunCommand($"mkdir -p '{remoteDirectory}'");
+ if (mkdirC.ExitStatus != 0) {
+ ShowError(Exceptions.failed_to_prepare_server_directory, remoteDirectory, name, os, mkdirC.Result);
+ return;
+ }
+ }
+ else {
+ ShowError(Exceptions.failed_to_prepare_server_directory, "n/a", name, os, "Unsupported OS");
+ return;
+ }
+ connectionTask.Increment(33);
+
+ sshClient.Disconnect();
+
+ using var client = new SftpClient(connectionInfo);
+ if (!client.IsConnected)
+ await client.ConnectAsync(ct);
+ connectionTask.Increment(33);
+ connectionTask.StopTask();
+
+ await using var writer = client.OpenWrite(packageFileDirectory);
+ byte[] buffer = new byte[bufferSize];
+ int read;
+ do {
+ read = await reader.ReadAsync(buffer, ct);
+ writer.Write(buffer, 0, read);
+ task.Increment(read);
+ } while (read > 0);
+ }
+
+ else if (dest == "github") {
+ var p = Process.Start(new ProcessStartInfo() {
+ FileName = "dotnet",
+ Arguments = $"nuget push {package} --source github",
+ WorkingDirectory = Environment.CurrentDirectory,
+ UseShellExecute = false,
+ RedirectStandardOutput = !Verbose,
+ RedirectStandardError = !Verbose
+ });
+
+ if (p == null) {
+ ShowError(Exceptions.generic_error.EscapeMarkup());
+ }
+
+ task.Increment(size / 2);
+ if (p != null)
+ await p.WaitForExitAsync(ct);
+ if (p?.ExitCode != 0) {
+ ShowError(Exceptions.dotnet_nuget_push_failure, p.ExitCode);
+ }
+ task.Increment(size / 2);
+ }
+
+ task.StopTask();
+ });
+ });
+ }
+ finally {
+ try {
+ Directory.Delete(outDir, true);
+ }
+ catch (Exception e) {
+ ShowError(string.Format(Exceptions.failed_to_clean_up.EscapeMarkup(), outDir.EscapeMarkup(), e.ToString().EscapeMarkup()));
+ }
+ }
+ AnsiConsole.MarkupLine("Completed processing of all destinations.");
+ AnsiConsole.MarkupLine("Example usage:\n\t ".EscapeMarkup(), packageId, version);
+ }
+
+ ///
+ /// Updates the version string by applying the specified operation to the major, minor, and patch components of the version.
+ ///
+ /// The current version string in the format "major.minor.patch[-tag]".
+ /// The value to apply to the patch component.
+ /// The value to apply to the minor component.
+ /// The value to apply to the major component.
+ /// A function that defines the adjustment operation to be performed on each version component.
+ /// A new version string with the updated major, minor, and patch components, preserving any existing tag.
+ /// Thrown if the version string is not in the correct format.
+ private static string ChangeVersion(string version, int patch, int minor, int major,
+ Func operation) {
+ string[] split = version.Split('.');
+ if (split.Length != 3) {
+ throw new Exception(string.Format(Exceptions.version_string_not_formatted_correctly, version));
+ }
+
+ string tag = "";
+ if (split[2].Contains('-')) {
+ var split2 = split[2].Split('-');
+ split[2] = split2[0];
+ tag = "-" + split2[1];
+ }
+
+ if (split.Any(x => !int.TryParse(x, out _)))
+ throw new Exception(string.Format(Exceptions.version_string_not_formatted_correctly, version));
+
+ int[] parsedVersion = split.Select(int.Parse).ToArray();
+
+ return
+ $"{operation(parsedVersion[0], major)}.{operation(parsedVersion[1], minor)}.{operation(parsedVersion[2], patch)}{tag}";
+ }
+
+ private static void ShowError(string message, params object[] args) {
+ AnsiConsole.MarkupLine($"[bold red]{message}[/]", args);
+ }
+
+ private static void ShowHelp() {
+ AnsiConsole.Markup(("Usage: publish overwrite|increment [patch|minor|major] destinations [flags]\n" +
+ "\t if mode: overwrite destinations [flags]\n" +
+ "\t if mode: increment patch|minor|major [flags]\n").EscapeMarkup());
+
+ }
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/Project.cs b/aeqw89.tools.Publish/Project.cs
new file mode 100644
index 0000000..c5d2e73
--- /dev/null
+++ b/aeqw89.tools.Publish/Project.cs
@@ -0,0 +1,54 @@
+using System.Xml;
+
+namespace aeqw89.tools.Publish;
+
+internal class Project {
+ public string Path { get; }
+ private XmlDocument Document { get; }
+ public List PropertyGroups { get; }
+ public List ItemGroups { get; }
+
+ private Project(string path, XmlDocument doc) {
+ Path = path;
+ Document = doc;
+
+ // Build PropertyGroups
+ PropertyGroups = doc.DocumentElement!
+ .SelectNodes("./PropertyGroup")!
+ .OfType()
+ .Select(e => new PropertyGroup(e))
+ .ToList();
+
+ // Build ItemGroups (+ their Items)
+ ItemGroups = doc.DocumentElement!
+ .SelectNodes("./ItemGroup")!
+ .OfType()
+ .Select(e => new ItemGroup(e))
+ .ToList();
+
+ // Ensure at least one ItemGroup exists (some csprojs omit it until first item is added)
+ if (ItemGroups.Count == 0) {
+ var ig = doc.CreateElement("ItemGroup", doc.DocumentElement!.NamespaceURI);
+ doc.DocumentElement!.AppendChild(ig);
+ ItemGroups.Add(new ItemGroup((XmlElement)ig));
+ }
+ }
+
+ public static Project Load(string path) {
+ var doc = new XmlDocument();
+ doc.PreserveWhitespace = false;
+ doc.Load(path);
+ return new Project(path, doc);
+ }
+
+ public void Save() {
+ var settings = new XmlWriterSettings {
+ Indent = true,
+ IndentChars = " ",
+ NewLineChars = Environment.NewLine,
+ NewLineHandling = NewLineHandling.Replace
+ };
+ using var writer = XmlWriter.Create(Path, settings);
+ Document.Save(writer);
+ }
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/ProjectFile.cs b/aeqw89.tools.Publish/ProjectFile.cs
new file mode 100644
index 0000000..20f1270
--- /dev/null
+++ b/aeqw89.tools.Publish/ProjectFile.cs
@@ -0,0 +1,216 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Xml;
+
+namespace aeqw89.tools.Publish;
+
+// -------------------------------------------------------------------------------------------------
+// Minimal replacements for VsTools.Projects so ProjectFile can keep its public surface intact
+// -------------------------------------------------------------------------------------------------
+
+// -------------------------------------------------------------------------------------------------
+// Your original public surface, now backed by System.Xml
+// -------------------------------------------------------------------------------------------------
+
+internal class ProjectFile {
+ public Project Project { get; private set; }
+ public string Path { get; private set; }
+ private PropertyGroup MainPropertyGroup { get; set; }
+
+ private ProjectFile(Project project, string path) {
+ MainPropertyGroup = project.PropertyGroups.FirstOrDefault()!;
+ Project = project;
+ Path = path;
+ }
+
+ public static bool TryLoad(string path, [NotNullWhen(true)] out ProjectFile? projectFile, [NotNullWhen(false)] out string? error) {
+ projectFile = null;
+ error = null;
+
+ if (!path.EndsWith(".csproj")) {
+ if (!Directory.Exists(path)) {
+ error = Exceptions.tried_loading_non_csproj_file;
+ return false;
+ }
+
+ var csproj = Directory.EnumerateFiles(path, "*.csproj", SearchOption.TopDirectoryOnly).ToArray();
+ if (csproj.Length == 0) {
+ error = Exceptions.no_project_in_directory;
+ return false;
+ }
+
+ if (csproj.Length > 1) {
+ error = Exceptions.found_multiple_csproj;
+ return false;
+ }
+
+ path = csproj[0];
+ }
+
+ try {
+ projectFile = new ProjectFile(Project.Load(path), path);
+ return true;
+ }
+ catch (Exception e) {
+ error = string.Format(Exceptions.generic_error, e);
+ return false;
+ }
+ }
+
+ public void Backup(string? backupPath = null) {
+ backupPath ??= Path + ".bak";
+ File.Copy(Path, backupPath, true);
+ }
+
+ public string GetDefaultBackupLocation() => Path + ".bak";
+
+ public bool Restore(string? backupPath = null) {
+ backupPath ??= GetDefaultBackupLocation();
+ if (!File.Exists(backupPath)) return false;
+ File.Copy(backupPath, Path, true);
+ return true;
+ }
+
+ public void Save() {
+ // Check for empty property or item groups
+ foreach (var ig in Project.ItemGroups.ToList().Where(ig => ig.Items.Count == 0)) {
+ ig.Remove();
+ }
+ foreach (var pg in Project.PropertyGroups.ToList().Where(pg => pg.Count == 0)) {
+ pg.Remove();
+ }
+
+ Project.Save();
+ }
+
+ public bool TryRepair([NotNullWhen(false)] out string? error) {
+ // Ensure we have a PropertyGroup with TargetFramework
+ if (MainPropertyGroup?.HasProperty("TargetFramework") != true) {
+ if (!Project.PropertyGroups.Any(x => x.HasProperty("TargetFramework"))) {
+ error = string.Format(Exceptions.project_file_irreparable, Path, "TargetFramework");
+ return false;
+ }
+ MainPropertyGroup = Project.PropertyGroups.First(x => x.HasProperty("TargetFramework"));
+ }
+
+ List failed = [];
+
+ void set(string key, string value, bool required = false) {
+ if (!MainPropertyGroup.HasProperty(key) || string.IsNullOrEmpty(MainPropertyGroup.GetProperty(key))) {
+ if (required) {
+ failed.Add(key);
+ return;
+ }
+ MainPropertyGroup.SetProperty(key, value);
+ }
+ }
+
+ set("Version", "1.0.0");
+ set("Title", System.IO.Path.GetFileNameWithoutExtension(Path));
+ set("Authors", "");
+ set("Company", "");
+ set("Description", "");
+ set("PackageProjectUrl", "", required: false);
+ set("RepositoryUrl", "", required: true);
+ set("PackageId", "", required: true);
+
+ if (failed.Count > 0) {
+ error = string.Format(Exceptions.project_file_irreparable, Path, string.Join(", ", failed));
+ return false;
+ }
+
+ error = null;
+ return true;
+ }
+
+ public List GetPackageReferences() {
+ return Project.ItemGroups
+ .SelectMany(g => g.Items)
+ .Where(i => i.ElementName == "PackageReference")
+ .OfType()
+ .ToList();
+ }
+
+ public List GetProjectReferences() {
+ return Project.ItemGroups
+ .SelectMany(g => g.Items)
+ .Where(i => i.ElementName == "ProjectReference")
+ .OfType()
+ .ToList();
+ }
+
+ public List GetPackageDependencies() {
+ return Project.ItemGroups
+ .SelectMany(g => g.Items)
+ .Where(i => i.ElementName == "Content")
+ .OfType()
+ .ToList();
+ }
+
+ private void CreateOrUpdateChild(Item item, string childName, string value) {
+ var child = item.Node.SelectSingleNode(childName) as XmlElement;
+ if (child != null) {
+ child.InnerText = value;
+ return;
+ }
+
+ child = item.Node.OwnerDocument!.CreateElement(childName, item.Node.NamespaceURI);
+ child.InnerText = value;
+ item.Node.AppendChild(child);
+ }
+
+ public string GetPackageId() {
+ return MainPropertyGroup.GetProperty("PackageId");
+ }
+
+ public void SetPrivateAssets(Item item, PrivateAssetsValue value) {
+ if (value == PrivateAssetsValue.None) {
+ var child = item.Node.SelectSingleNode("./PrivateAssets") as XmlElement;
+ if (child != null)
+ item.Node.RemoveChild(child);
+ return;
+ }
+
+ CreateOrUpdateChild(item, "PrivateAssets", value.ToString().ToLowerInvariant());
+ }
+
+ public void SetTransitive(Item item, bool value)
+ => CreateOrUpdateChild(item, "Transitive", value.ToString().ToLowerInvariant());
+
+ public bool IsTransitive(Item item) {
+ var transitive = item.Node.SelectSingleNode("./Transitive") as XmlElement;
+ return transitive?.InnerText == "true";
+ }
+
+ public string GetAbsoluteIncludePath(Item item) {
+ return System.IO.Path.GetFullPath(item.Include);
+ }
+
+ public Item AddPackage(Item otherPackage) {
+ var allItems = Project.ItemGroups.SelectMany(x => x.Items);
+ var existing = allItems.FirstOrDefault(x =>
+ x.ElementName == "PackageReference" && x.Include == otherPackage.Include);
+ if (existing != null) return existing;
+
+ var itemGroup = Project.ItemGroups.First();
+ // Import the original XmlElement to preserve Version (attr or child) and any metadata
+ itemGroup.Add(otherPackage);
+ return itemGroup.Items.Last();
+ }
+
+
+ public bool RemovePackage(Item otherPackage) {
+ var grp = Project.ItemGroups
+ .FirstOrDefault(g => g.Items.Any(y => y.ElementName == "PackageReference" && y.Include == otherPackage.Include));
+
+ var pkg = grp?.Items.FirstOrDefault(x => x.ElementName == otherPackage.ElementName && x.Include == otherPackage.Include);
+ if (pkg == null) return false;
+
+ pkg.Node.ParentNode!.RemoveChild(pkg.Node);
+ grp!.Items.Remove(pkg);
+ return true;
+ }
+
+ public string GetVersion() => MainPropertyGroup.GetProperty("Version");
+
+ public void SetVersion(string version) => MainPropertyGroup.SetProperty("Version", version);
+}
diff --git a/aeqw89.tools.Publish/ProjectReference.cs b/aeqw89.tools.Publish/ProjectReference.cs
new file mode 100644
index 0000000..acf6799
--- /dev/null
+++ b/aeqw89.tools.Publish/ProjectReference.cs
@@ -0,0 +1,7 @@
+using System.Xml;
+
+namespace aeqw89.tools.Publish;
+
+internal sealed class ProjectReference : Item {
+ public ProjectReference(XmlElement node) : base(node) { }
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/PropertyGroup.cs b/aeqw89.tools.Publish/PropertyGroup.cs
new file mode 100644
index 0000000..56f966f
--- /dev/null
+++ b/aeqw89.tools.Publish/PropertyGroup.cs
@@ -0,0 +1,28 @@
+using System.Xml;
+
+namespace aeqw89.tools.Publish;
+
+internal class PropertyGroup {
+ private readonly XmlElement _element;
+ public PropertyGroup(XmlElement element) => _element = element;
+
+ public bool HasProperty(string name) => _element.SelectSingleNode($"./{name}") is XmlElement;
+
+ public string GetProperty(string name) {
+ var node = _element.SelectSingleNode($"./{name}") as XmlElement;
+ return node?.InnerText ?? string.Empty;
+ }
+
+ public void SetProperty(string name, string value) {
+ var node = _element.SelectSingleNode($"./{name}") as XmlElement;
+ if (node == null) {
+ node = _element.OwnerDocument!.CreateElement(name, _element.NamespaceURI);
+ _element.AppendChild(node);
+ }
+ node.InnerText = value ?? string.Empty;
+ }
+
+ public int Count => _element.ChildNodes.OfType().Count();
+
+ public void Remove() => _element.ParentNode!.RemoveChild(_element);
+}
\ No newline at end of file
diff --git a/aeqw89.tools.Publish/RemotePath.cs b/aeqw89.tools.Publish/RemotePath.cs
new file mode 100644
index 0000000..6f9d29b
--- /dev/null
+++ b/aeqw89.tools.Publish/RemotePath.cs
@@ -0,0 +1,90 @@
+// Required namespaces: System, System.Linq, System.Text.RegularExpressions
+
+using System.Text.RegularExpressions;
+
+namespace aeqw89.tools.Publish;
+
+public enum RemoteOs { Windows, Unix }
+
+public static partial class RemotePath
+{
+ public static string Combine(RemoteOs os, params string[] parts)
+ {
+ if (parts == null || parts.Length == 0) return string.Empty;
+
+ // Normalize null/empty segments and trim whitespace
+ var cleaned = parts.Where(p => !string.IsNullOrWhiteSpace(p))
+ .Select(p => p.Trim())
+ .ToArray();
+ if (cleaned.Length == 0) return string.Empty;
+
+ return os == RemoteOs.Windows
+ ? CombineWindows(cleaned)
+ : CombineUnix(cleaned);
+ }
+
+ private static string CombineUnix(string[] parts)
+ {
+ // Keep a single leading '/' if the first segment is rooted
+ bool rooted = parts[0].StartsWith("/", StringComparison.Ordinal);
+ var body = parts.Select((p, i) =>
+ {
+ var s = p.Replace("\\", "/");
+ // Trim both ends except preserve leading '/' only for first segment
+ if (i == 0)
+ return s.TrimEnd('/');
+ return s.Trim('/'); // internal segments never carry separators
+ })
+ .Where(s => s.Length > 0)
+ .ToArray();
+
+ var joined = string.Join("/", body);
+ return rooted ? (joined.StartsWith("/") ? joined : "/" + joined) : joined;
+ }
+
+ private static string CombineWindows(string[] parts)
+ {
+ // Windows separators are backslashes for cmd.exe/PowerShell paths.
+ // Preserve drive letters like "C:" and UNC roots like "\\server".
+ var first = parts[0].Replace("/", "\\").Trim();
+ var rest = parts.Skip(1)
+ .Select(p => p.Replace("/", "\\").Trim('\\'))
+ .Where(s => s.Length > 0)
+ .ToArray();
+
+ // Detect UNC root (\\server\share...) or device prefix (\\?\ or \\.\)
+ bool isUncOrDevice = first.StartsWith(@"\\", StringComparison.Ordinal);
+ bool isDrive = IsDrive().IsMatch(first);
+
+ if (isUncOrDevice)
+ {
+ // Keep exactly two leading backslashes; trim trailing ones
+ first = @"\\" + first.TrimStart('\\').TrimEnd('\\');
+ }
+ else if (isDrive)
+ {
+ // Normalize "C:" or "C:\" to "C:\"
+ first = first.Length == 2 ? first + "\\" : first;
+ }
+ else
+ {
+ // For relative first segment, strip surrounding slashes
+ first = first.Trim('\\');
+ }
+
+ string joined = rest.Length > 0
+ ? first + (first.EndsWith("\\") ? "" : "\\") + string.Join("\\", rest)
+ : first;
+
+ // Collapse any accidental doubles that can appear from user input (but keep UNC prefix)
+ if (!joined.StartsWith(@"\\"))
+ joined = Collapse().Replace(joined, @"\");
+
+ return joined;
+ }
+
+ [System.Text.RegularExpressions.GeneratedRegex(@"^[A-Za-z]:\\?$")]
+ private static partial System.Text.RegularExpressions.Regex IsDrive();
+ [GeneratedRegex(@"\\{2,}")]
+ private static partial Regex Collapse();
+}
diff --git a/aeqw89.tools.Publish/SshHosts.cs b/aeqw89.tools.Publish/SshHosts.cs
new file mode 100644
index 0000000..ff6ada2
--- /dev/null
+++ b/aeqw89.tools.Publish/SshHosts.cs
@@ -0,0 +1,423 @@
+// Required namespaces:
+// System, System.IO, System.Linq, System.Text, System.Text.RegularExpressions, System.Collections.Generic, Renci.SshNet
+
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace aeqw89.tools.Publish;
+
+public record Host(
+ string Name,
+ string Hostname,
+ string User,
+ string Password,
+ int Port,
+ List IdentityFiles,
+ Renci.SshNet.ProxyTypes? ProxyType,
+ string? ProxyHost,
+ int? ProxyPort,
+ string? ProxyUser,
+ string? ProxyPassword
+);
+
+public static class SshHosts {
+ public static List Hosts { get; set; }
+
+ static SshHosts() {
+ Hosts = new List();
+
+ var path = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ ".ssh",
+ "config"
+ );
+
+ if (!File.Exists(path)) return;
+
+ var lines = File.ReadAllLines(path);
+
+ string[]? currentNames = null;
+ string? currentHostName = null;
+ string? currentUser = null;
+ string? currentPassword = null;
+ int? currentPort = null;
+ List? currentIdentityFiles = null;
+
+ // Proxy (explicit)
+ Renci.SshNet.ProxyTypes? currentProxyType = null;
+ string? currentProxyHost = null;
+ int? currentProxyPort = null;
+ string? currentProxyUser = null;
+ string? currentProxyPassword = null;
+
+ // Proxy (derived from ProxyCommand)
+ string? currentProxyCommand = null;
+
+ void ResetBlock()
+ {
+ currentNames = null;
+ currentHostName = null;
+ currentUser = null;
+ currentPassword = null;
+ currentPort = null;
+ currentIdentityFiles = null;
+
+ currentProxyType = null;
+ currentProxyHost = null;
+ currentProxyPort = null;
+ currentProxyUser = null;
+ currentProxyPassword = null;
+
+ currentProxyCommand = null;
+ }
+
+ void MaybeDeriveProxyFromProxyCommand()
+ {
+ if (string.IsNullOrWhiteSpace(currentProxyCommand)) return;
+ if (currentProxyType != null && currentProxyHost != null && currentProxyPort != null) return;
+
+ // Very common forms:
+ // nc -x host:port -X 5 %h %p (SOCKS5)
+ // nc -x host:port -X 4 %h %p (SOCKS4)
+ // nc -x host:port %h %p (default: treat as SOCKS5)
+ // nc -X connect -x host:port %h %p (HTTP CONNECT)
+ // connect -S host:port %h %p (treat as SOCKS5)
+ var s = currentProxyCommand;
+
+ // host:port extraction
+ var hp = Regex.Match(s, @"(?:(?:-x|-S)\s+|\s)([A-Za-z0-9.\-]+):(\d{1,5})");
+ if (hp.Success) {
+ currentProxyHost ??= hp.Groups[1].Value;
+ if (int.TryParse(hp.Groups[2].Value, out var pp) && pp > 0 && pp <= 65535)
+ currentProxyPort ??= pp;
+ }
+
+ // type extraction
+ if (Regex.IsMatch(s, @"-X\s+5\b", RegexOptions.IgnoreCase))
+ currentProxyType ??= Renci.SshNet.ProxyTypes.Socks5;
+ else if (Regex.IsMatch(s, @"-X\s+4\b", RegexOptions.IgnoreCase))
+ currentProxyType ??= Renci.SshNet.ProxyTypes.Socks4;
+ else if (Regex.IsMatch(s, @"-X\s+connect\b", RegexOptions.IgnoreCase))
+ currentProxyType ??= Renci.SshNet.ProxyTypes.Http;
+ else if (s.Contains("connect ", System.StringComparison.OrdinalIgnoreCase))
+ currentProxyType ??= Renci.SshNet.ProxyTypes.Http;
+ else if (s.Contains("nc ", System.StringComparison.OrdinalIgnoreCase))
+ currentProxyType ??= Renci.SshNet.ProxyTypes.Socks5;
+
+ // If we still don't have enough, leave proxy unset.
+ }
+
+ void Flush()
+ {
+ if (currentNames == null || currentNames.Length == 0) {
+ ResetBlock();
+ return;
+ }
+
+ // Try deriving proxy from ProxyCommand if explicit values weren't set
+ MaybeDeriveProxyFromProxyCommand();
+
+ foreach (var n in currentNames) {
+ var hn = string.IsNullOrWhiteSpace(currentHostName) ? n : currentHostName!;
+ var idFiles = new List();
+ if (currentIdentityFiles != null) {
+ foreach (var f in currentIdentityFiles) idFiles.Add(ExpandPath(f));
+ }
+
+ Hosts.Add(new Host(
+ Name: n,
+ Hostname: hn,
+ User: currentUser ?? string.Empty,
+ Password: currentPassword ?? string.Empty,
+ Port: currentPort ?? 22,
+ IdentityFiles: idFiles,
+ ProxyType: currentProxyType,
+ ProxyHost: currentProxyHost,
+ ProxyPort: currentProxyPort,
+ ProxyUser: currentProxyUser,
+ ProxyPassword: currentProxyPassword
+ ));
+ }
+
+ ResetBlock();
+ }
+
+ foreach (var raw in lines) {
+ var line = raw.Trim();
+ if (line.Length == 0 || line.StartsWith("#")) continue;
+
+ int idx = line.IndexOfAny(new[] { ' ', '\t' });
+ string key, value;
+ if (idx < 0) {
+ key = line;
+ value = string.Empty;
+ } else {
+ key = line[..idx];
+ value = line[idx..].Trim();
+ }
+
+ switch (key.ToLowerInvariant()) {
+ case "host":
+ Flush();
+ currentNames = SplitArgs(value).ToArray();
+ break;
+
+ case "hostname":
+ if (currentNames != null) currentHostName = value;
+ break;
+
+ case "user":
+ if (currentNames != null) currentUser = value;
+ break;
+
+ case "password":
+ // Non-standard; supported here as a convenience (used for password auth or key passphrase)
+ if (currentNames != null) currentPassword = value;
+ break;
+
+ case "port":
+ if (currentNames != null && int.TryParse(value, out var p) && p > 0 && p <= 65535)
+ currentPort = p;
+ break;
+
+ case "identityfile":
+ if (currentNames != null) {
+ currentIdentityFiles ??= new List();
+ foreach (var f in SplitArgs(value)) {
+ if (!string.IsNullOrWhiteSpace(f)) currentIdentityFiles.Add(f);
+ }
+ }
+ break;
+
+ // --- Proxy settings (explicit custom keys) ---
+ case "proxytype":
+ if (currentNames != null) {
+ var v = value.ToLowerInvariant();
+ currentProxyType =
+ v switch {
+ "socks5" or "socks" or "socks5h" => Renci.SshNet.ProxyTypes.Socks5,
+ "socks4" => Renci.SshNet.ProxyTypes.Socks4,
+ "http" or "https" or "connect" => Renci.SshNet.ProxyTypes.Http,
+ _ => currentProxyType
+ };
+ }
+ break;
+
+ case "proxyhost":
+ if (currentNames != null) currentProxyHost = value;
+ break;
+
+ case "proxyport":
+ if (currentNames != null && int.TryParse(value, out var prxP) && prxP > 0 && prxP <= 65535)
+ currentProxyPort = prxP;
+ break;
+
+ case "proxyuser":
+ if (currentNames != null) currentProxyUser = value;
+ break;
+
+ case "proxypassword":
+ if (currentNames != null) currentProxyPassword = value;
+ break;
+
+ // --- OpenSSH-style hints we try to interpret ---
+ case "proxycommand":
+ if (currentNames != null) currentProxyCommand = value;
+ break;
+
+ // (Note: ProxyJump is non-trivial to replicate in SSH.NET; not parsed here.)
+
+ default:
+ break;
+ }
+ }
+
+ Flush();
+ }
+
+ // Builds a ConnectionInfo from a parsed host, ensuring all ~/.ssh private keys are tried.
+ private static Renci.SshNet.ConnectionInfo BuildConnection(Host h) {
+ var authMethods = new List();
+
+ // 1) Collect candidate key file paths: from config + everything that looks like a private key in ~/.ssh
+ var keyPaths = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var f in h.IdentityFiles)
+ if (!string.IsNullOrWhiteSpace(f))
+ keyPaths.Add(f);
+
+ foreach (var f in EnumerateAllSshPrivateKeys())
+ keyPaths.Add(f);
+
+ // 2) Load keys (try with passphrase, then without)
+ var pkFiles = new List();
+ foreach (var p in keyPaths) {
+ if (!File.Exists(p)) continue;
+ try {
+ if (!string.IsNullOrEmpty(h.Password)) {
+ try {
+ pkFiles.Add(new Renci.SshNet.PrivateKeyFile(p, h.Password));
+ continue;
+ }
+ catch { /* fall through to try without passphrase */
+ }
+ }
+
+ pkFiles.Add(new Renci.SshNet.PrivateKeyFile(p));
+ }
+ catch { /* skip unreadable/unparsable key */
+ }
+ }
+
+ if (pkFiles.Count > 0)
+ authMethods.Add(new Renci.SshNet.PrivateKeyAuthenticationMethod(h.User, pkFiles.ToArray()));
+
+ // 3) Optional password auth (remote account password)
+ if (!string.IsNullOrEmpty(h.Password))
+ authMethods.Add(new Renci.SshNet.PasswordAuthenticationMethod(h.User, h.Password));
+
+ if (authMethods.Count == 0)
+ throw new InvalidOperationException($"No authentication methods available for host '{h.Name}'.");
+
+ // 4) Proxy-aware ConnectionInfo
+ if (h.ProxyType.HasValue && !string.IsNullOrWhiteSpace(h.ProxyHost) && h.ProxyPort.HasValue) {
+ return new Renci.SshNet.ConnectionInfo(
+ h.Hostname,
+ h.Port,
+ h.User,
+ h.ProxyType.Value,
+ h.ProxyHost!,
+ h.ProxyPort.Value,
+ h.ProxyUser,
+ h.ProxyPassword,
+ authMethods.ToArray()
+ );
+ }
+
+ return new Renci.SshNet.ConnectionInfo(h.Hostname, h.Port, h.User, authMethods.ToArray());
+ }
+
+ // Enumerate everything in ~/.ssh that *looks* like a private key.
+ // Skips obvious non-keys like *.pub, known_hosts, config, and directories.
+ private static IEnumerable EnumerateAllSshPrivateKeys() {
+ var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ var sshDir = Path.Combine(home, ".ssh");
+ if (!Directory.Exists(sshDir)) yield break;
+
+ // Common private key basenames to prioritize
+ var preferred = new HashSet(StringComparer.OrdinalIgnoreCase) {
+ "id_rsa", "id_ecdsa", "id_ed25519", "id_dsa"
+ };
+
+ // First yield preferred names if present
+ foreach (var name in preferred) {
+ var p = Path.Combine(sshDir, name);
+ if (File.Exists(p)) yield return p;
+ }
+
+ // Then yield everything else that looks like a key
+ foreach (var file in Directory.EnumerateFiles(sshDir)) {
+ var name = Path.GetFileName(file);
+
+ // Skip ones we already yielded
+ if (preferred.Contains(name)) continue;
+
+ // Exclude common non-key files and public keys
+ if (name.EndsWith(".pub", StringComparison.OrdinalIgnoreCase)) continue;
+ if (name.Equals("config", StringComparison.OrdinalIgnoreCase)) continue;
+ if (name.Equals("known_hosts", StringComparison.OrdinalIgnoreCase)) continue;
+ if (name.Equals("authorized_keys", StringComparison.OrdinalIgnoreCase)) continue;
+ if (name.EndsWith(".crt", StringComparison.OrdinalIgnoreCase)) continue;
+ if (name.EndsWith(".csr", StringComparison.OrdinalIgnoreCase)) continue;
+ if (name.EndsWith(".pem.pub", StringComparison.OrdinalIgnoreCase)) continue; // guard for odd combos
+ if (name.EndsWith(".txt", StringComparison.OrdinalIgnoreCase)) continue;
+ if (name.EndsWith(".conf", StringComparison.OrdinalIgnoreCase)) continue;
+
+ // Heuristic: accept files with no extension, or with .key/.pem
+ var ext = Path.GetExtension(name);
+ if (string.IsNullOrEmpty(ext) ||
+ ext.Equals(".key", StringComparison.OrdinalIgnoreCase) ||
+ ext.Equals(".pem", StringComparison.OrdinalIgnoreCase)) {
+ yield return file;
+ }
+ }
+ }
+
+
+ public static Host Get(string name) {
+ for (int i = 0; i < Hosts.Count; i++) {
+ if (string.Equals(Hosts[i].Name, name, StringComparison.OrdinalIgnoreCase)) {
+ return Hosts[i];
+ }
+ }
+ throw new KeyNotFoundException($"SSH host '{name}' not found.");
+ }
+
+ public static bool TryGetHost(string name, out Host host) {
+ for (int i = 0; i < Hosts.Count; i++) {
+ if (string.Equals(Hosts[i].Name, name, StringComparison.OrdinalIgnoreCase)) {
+ host = Hosts[i];
+ return true;
+ }
+ }
+ host = default!;
+ return false;
+ }
+
+ public static Renci.SshNet.ConnectionInfo GetConnection(string name)
+ {
+ var h = Get(name);
+ return BuildConnection(h);
+ }
+
+ public static bool TryGetConnection(string name, out Renci.SshNet.ConnectionInfo connectionInfo)
+ {
+ if (TryGetHost(name, out var h)) {
+ try {
+ connectionInfo = BuildConnection(h);
+ return true;
+ } catch {
+ // If auth material is unusable, return false.
+ }
+ }
+ connectionInfo = default!;
+ return false;
+ }
+
+ // --- Helpers ---
+
+ // Splits a value into whitespace-delimited tokens while respecting double/single quotes.
+ static IEnumerable SplitArgs(string input)
+ {
+ if (string.IsNullOrWhiteSpace(input)) yield break;
+
+ bool inSingle = false, inDouble = false;
+ var sb = new StringBuilder();
+
+ for (int i = 0; i < input.Length; i++) {
+ char c = input[i];
+
+ if (c == '"' && !inSingle) { inDouble = !inDouble; continue; }
+ if (c == '\'' && !inDouble) { inSingle = !inSingle; continue; }
+
+ if (!inSingle && !inDouble && char.IsWhiteSpace(c)) {
+ if (sb.Length > 0) { yield return sb.ToString(); sb.Clear(); }
+ } else {
+ sb.Append(c);
+ }
+ }
+ if (sb.Length > 0) yield return sb.ToString();
+ }
+
+ // Expands leading "~" to user home. Leaves %h, %r, etc. untouched.
+ static string ExpandPath(string p)
+ {
+ if (string.IsNullOrEmpty(p)) return p;
+ if (p == "~") return Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ if (p.StartsWith("~/")) {
+ var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ return Path.Combine(home, p[2..]);
+ }
+ return p;
+ }
+}
diff --git a/aeqw89.tools.Publish/aeqw89.tools.Publish.csproj b/aeqw89.tools.Publish/aeqw89.tools.Publish.csproj
new file mode 100644
index 0000000..26c14ea
--- /dev/null
+++ b/aeqw89.tools.Publish/aeqw89.tools.Publish.csproj
@@ -0,0 +1,32 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Exceptions.Designer.cs
+
+
+
+
+
+ True
+ True
+ Exceptions.resx
+
+
+
+
diff --git a/aeqw89.tools.Publish/bin/Debug/net9.0/BouncyCastle.Cryptography.dll b/aeqw89.tools.Publish/bin/Debug/net9.0/BouncyCastle.Cryptography.dll
new file mode 100644
index 0000000000000000000000000000000000000000..bf1a8a176befe550e2d81e3916cb200077a65ca8
GIT binary patch
literal 4870808
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