From 08a221202f7e9a11326ea378af8538cdaa279287 Mon Sep 17 00:00:00 2001 From: qwsdcvghyu89 <61093706+qwsdcvghyu89@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:07:48 +1000 Subject: [PATCH] Added new destination type : gitea, and revamped build workflow --- aeqw89.tools.Publish/.gitignore | 5 +- aeqw89.tools.Publish/Destination.cs | 36 ++- aeqw89.tools.Publish/Exceptions.Designer.cs | 224 ------------------ aeqw89.tools.Publish/Program.cs | 28 ++- aeqw89.tools.Publish/ProjectFile.cs | 5 + aeqw89.tools.Publish/PublishAll.targets | 46 ++++ aeqw89.tools.Publish/Result.cs | 26 ++ .../aeqw89.tools.Publish.csproj | 7 +- 8 files changed, 133 insertions(+), 244 deletions(-) delete mode 100644 aeqw89.tools.Publish/Exceptions.Designer.cs create mode 100644 aeqw89.tools.Publish/PublishAll.targets diff --git a/aeqw89.tools.Publish/.gitignore b/aeqw89.tools.Publish/.gitignore index 7a0795f..4f8bc8b 100644 --- a/aeqw89.tools.Publish/.gitignore +++ b/aeqw89.tools.Publish/.gitignore @@ -1,2 +1,3 @@ -bin/** -obj/** +**/bin +**/obj +**/dist \ No newline at end of file diff --git a/aeqw89.tools.Publish/Destination.cs b/aeqw89.tools.Publish/Destination.cs index 650e234..cb2c8d3 100644 --- a/aeqw89.tools.Publish/Destination.cs +++ b/aeqw89.tools.Publish/Destination.cs @@ -135,21 +135,41 @@ sealed record CloudDestiantion(DestinationContext Context) : IDestination { } } -sealed record GithubDestination(DestinationContext Context, bool Verbose = false) : IDestination { + +sealed record GitDestination(DestinationContext Context, string Source, string ApiKey, bool Verbose = false) : IDestination { + private static Result GetApiKey(string host) { + var key = Environment.GetEnvironmentVariable($"git-{host}-packages-key"); + if (key is null) return new ReadableError(string.Format("No key stored in EnvironmentVariables with name {0}", $"git-{host}-packages-key"), false); + return key.Ok(); + } + public static Result CreateForGitea(DestinationContext context, string repoOwner, bool verbose = false) { + // gitea source structure = https://gitea.example.com/api/packages/{owner}/nuget/index.json + var keyResult = GetApiKey("gitea"); + if (keyResult is ReadableError error) return error; + return new GitDestination(context, $"https://gitea.example.com/api/packages/{repoOwner}/nuget/index.json", keyResult.Unwrap(), verbose).Ok(); + } + + public static Result CreateForGithub(DestinationContext context, string repoOwner, bool verbose = false) { + // github source structure = https://nuget.pkg.github.com/NAMESPACE/index.json + // namespace usually = repoOwner + var keyResult = GetApiKey("github"); + if (keyResult is ReadableError error) return error; + return new GitDestination(context, $"https://nuget.pkg.github.com/{repoOwner}/index.json", keyResult.Unwrap(), verbose).Ok(); + } + public async Task> WaitForCompletion(CancellationToken ct = default) { - var p = Process.Start(new ProcessStartInfo() { + using var p = Process.Start(new ProcessStartInfo() { FileName = "dotnet", - Arguments = $"nuget push \"{Context.PackageFile.FullName}\" --source github", + Arguments = $"nuget push \"{Context.PackageFile.FullName}\" -s {Source} -k {ApiKey}", WorkingDirectory = Environment.CurrentDirectory, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }); - var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); StringBuilder errorLines = new(); p?.ErrorDataReceived += (sender, eventArgs) => { - cts.Cancel(); if (Verbose && eventArgs.Data != null) AnsiConsole.WriteLine(eventArgs.Data); errorLines.Append(eventArgs.Data); @@ -169,16 +189,16 @@ sealed record GithubDestination(DestinationContext Context, bool Verbose = false try { await (p?.WaitForExitAsync(cts.Token) ?? Task.CompletedTask); } - catch (TaskCanceledException) { + catch (OperationCanceledException) { p?.Kill(); + await (p?.WaitForExitAsync(ct)); } - if (p?.ExitCode != 0) { Context.Task.StopTask(); return new ReadableError(errorLines.ToString().EscapeMarkup() + "\n" + string.Format(Exceptions.dotnet_nuget_push_failure, p?.ExitCode ?? -1), false); } - Context.Task.Increment(Context.BufferSize / 2); + Context.Task.Increment(Context.PackageSize / 2); return Success.AsResult(); } } \ No newline at end of file diff --git a/aeqw89.tools.Publish/Exceptions.Designer.cs b/aeqw89.tools.Publish/Exceptions.Designer.cs deleted file mode 100644 index 89a52e9..0000000 --- a/aeqw89.tools.Publish/Exceptions.Designer.cs +++ /dev/null @@ -1,224 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 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 with exit code '{0}'; 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/Program.cs b/aeqw89.tools.Publish/Program.cs index 7421d93..0996836 100644 --- a/aeqw89.tools.Publish/Program.cs +++ b/aeqw89.tools.Publish/Program.cs @@ -5,7 +5,6 @@ using Renci.SshNet; using Spectre.Console; using aeqw89.xml.ProjectFile; - namespace aeqw89.tools.Publish; /* @@ -116,7 +115,7 @@ public static class Program { return result; } - record ProjectResult(string PackageId, string Version); + record ProjectResult(string PackageId, string Version, string RepoOwner); static async Task> PrepareProject(RunContext rctx, StatusContext ctx) { ctx.Status = "Locating project file"; if (!ProjectFile.TryLoad(Environment.CurrentDirectory, out var projectFile, out var error)) @@ -124,6 +123,7 @@ public static class Program { string packageId = projectFile.GetPackageId(); string version; + string repoOwner; try { projectFile.Backup(); @@ -161,8 +161,8 @@ public static class Program { ctx.Status = "Updating version"; version = projectFile.GetVersion(); version = ProjectFile.ChangeVersion(version, delta, rctx.Args.Target ?? IncrementTarget.Patch).Unwrap(rctx); - projectFile.SetVersion(version); + } } catch (Exception e) { @@ -170,6 +170,8 @@ public static class Program { } version = projectFile.GetVersion(); + repoOwner = projectFile.GetRepositoryOwner(); + if (!rctx.Args.Flags.ContainsKey("--simulate")) { try { @@ -221,7 +223,7 @@ public static class Program { } projectFile.Save(); - return new Ok(new ProjectResult(packageId, version)); + return new Ok(new ProjectResult(packageId, version, repoOwner)); } static async Task> PackProject(RunContext rctx, StatusContext ctx) { @@ -242,6 +244,7 @@ public static class Program { enum DestinationType { Local, Github, + Main, Cloud } @@ -286,7 +289,10 @@ public static class Program { destType = DestinationType.Cloud; } else if (dest == "github") { destType = DestinationType.Github; - } else { + } else if (dest == "main") { + destType = DestinationType.Main; + } + else { lock(rctx) { ShowError(string.Format(Exceptions.destination_unrecognizable, dest)); ShowHelp(); @@ -298,16 +304,20 @@ public static class Program { var dctx = new DestinationContext(task, ctx, reader, pkg.FileInfo, destType switch { DestinationType.Cloud => dest["cloud-".Length..], DestinationType.Github => dest["github".Length..], + DestinationType.Main => dest["main".Length..], DestinationType.Local => dest["local-".Length..] }, BufferSize, pkg.Size()); - IDestination destination = destType switch { - DestinationType.Local => new LocalDestination(dctx), - DestinationType.Github => new GithubDestination(dctx, rctx.Args.Verbose), - DestinationType.Cloud => new CloudDestiantion(dctx), + Result destinationResult = destType switch { + DestinationType.Local => new LocalDestination(dctx).Ok(), + DestinationType.Github => GitDestination.CreateForGithub(dctx, project.RepoOwner, rctx.Args.Verbose).UpcastSuccess(), + DestinationType.Main => GitDestination.CreateForGitea(dctx, project.RepoOwner, rctx.Args.Verbose).UpcastSuccess(), + DestinationType.Cloud => new CloudDestiantion(dctx).Ok(), _ => throw new UnreachableException() }; + var destination = destinationResult.Unwrap(rctx); + var result = await destination.WaitForCompletion(ct); lock(rctx) { if (result.Unwrap(rctx) is not null) { diff --git a/aeqw89.tools.Publish/ProjectFile.cs b/aeqw89.tools.Publish/ProjectFile.cs index 8819c18..770d0db 100644 --- a/aeqw89.tools.Publish/ProjectFile.cs +++ b/aeqw89.tools.Publish/ProjectFile.cs @@ -114,6 +114,7 @@ internal class ProjectFile { set("PackageProjectUrl", "", required: false); set("RepositoryUrl", "", required: true); set("PackageId", "", required: true); + set("RepositoryOwner", "", required: true); if (failed.Count > 0) { error = string.Format(Exceptions.project_file_irreparable, Path, string.Join(", ", failed)); @@ -124,6 +125,10 @@ internal class ProjectFile { return true; } + public string GetRepositoryOwner() { + return MainPropertyGroup.GetProperty("RepositoryOwner"); + } + public List GetPackageReferences() { return Project.ItemGroups .SelectMany(g => g.Items) diff --git a/aeqw89.tools.Publish/PublishAll.targets b/aeqw89.tools.Publish/PublishAll.targets new file mode 100644 index 0000000..0d58539 --- /dev/null +++ b/aeqw89.tools.Publish/PublishAll.targets @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + $(AssemblyName) + $(Version) + 1.3.0 + $(MSBuildProjectDirectory)\dist\ + $(MSBuildProjectDirectory)\bin\Release\publish\ + + + + + + + + + + + + + \ No newline at end of file diff --git a/aeqw89.tools.Publish/Result.cs b/aeqw89.tools.Publish/Result.cs index 219e052..681a07d 100644 --- a/aeqw89.tools.Publish/Result.cs +++ b/aeqw89.tools.Publish/Result.cs @@ -28,10 +28,36 @@ internal static class ResultExtensions { public static async Task Unwrap(this Task> result, Program.RunContext? rctx = null) { return (await result).Unwrap(rctx); } + + public static Result Cast(this Result result) where TTo : notnull, TFrom where ETo: notnull, EFrom { + return result switch { + TFrom tfrom => ((TTo)tfrom).Ok(), + EFrom efrom => ((ETo)efrom), + _ => throw new UnreachableException() + }; + } + + public static Result Upcast(this Result result) + where TFrom : TTo + where EFrom : ETo + { + return result switch { + TFrom tfrom => ((TTo)tfrom).Ok(), // Foo -> IFoo, implicit upcast, can't throw + EFrom efrom => ((ETo)efrom), + _ => throw new UnreachableException() + }; + } + + public static Result CastSuccess(this Result result) where TTo : notnull, TFrom where E: notnull + => Cast(result); + + public static Result UpcastSuccess(this Result result) where TFrom : notnull, TTo where E: notnull + => Upcast(result); } internal static class ObjectExtensions { public static Ok Ok(this T any) { return new Ok(any); } + } \ No newline at end of file diff --git a/aeqw89.tools.Publish/aeqw89.tools.Publish.csproj b/aeqw89.tools.Publish/aeqw89.tools.Publish.csproj index 5ea3a04..6c5a684 100644 --- a/aeqw89.tools.Publish/aeqw89.tools.Publish.csproj +++ b/aeqw89.tools.Publish/aeqw89.tools.Publish.csproj @@ -1,4 +1,9 @@  + + + + win-x64;linux-x64;osx-arm64;osx-x64 + Exe @@ -9,7 +14,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all