5 Commits
Author SHA1 Message Date
qwsdcvghyu89 207805bad3 Improve project file version handling and error recovery
Refactored the publish workflow to use a more robust error handling and cleanup mechanism with a RestoreActions list. The version increment logic now resets lower version components appropriately based on the increment target. ProjectFile now sets both Version and PackageVersion properties to ensure consistency. Improved process output handling and destination processing feedback.
2025-09-27 17:19:30 +10:00
qwsdcvghyu89 6904e0cfd8 Improve process handling and async support in publish tool
Refactored process execution to use async methods and cancellation tokens for better responsiveness, especially when handling 'dotnet pack' and 'dotnet nuget push' commands. Updated .csproj to use C# preview language features and added 'bin/' to .gitignore.
2025-09-27 16:26:12 +10:00
qwsdcvghyu89 093da5d0f3 Improve error reporting for process failures
Captures and displays standard error output when 'dotnet pack' or 'dotnet nuget push' commands fail, providing more detailed error information to the user.
2025-09-27 16:15:30 +10:00
qwsdcvghyu89 c6570c1e2c Improve error handling for dotnet pack failures
Enhanced the error message for 'dotnet pack' failures to include the exit code. Updated the resource string and its usage to provide more informative feedback. Added cleanup of the temporary directory on cancellation and improved progress bar visibility.
2025-09-27 16:11:58 +10:00
qwsdcvghyu89 ad729133a6 Restore project file on cancel event
Adds a handler for Console.CancelKeyPress to restore the project file from backup if the process is interrupted, ensuring changes are not left in an inconsistent state.
2025-09-27 15:56:20 +10:00
6 changed files with 435 additions and 293 deletions
+1
View File
@@ -0,0 +1 @@
bin/
+1 -1
View File
@@ -96,7 +96,7 @@ namespace aeqw89.tools.Publish {
}
/// <summary>
/// Looks up a localized string similar to Failed to pack; ensure that &apos;dotnet build&apos; succeeds before running this program..
/// Looks up a localized string similar to Failed to pack with exit code &apos;{0}&apos;; ensure that &apos;dotnet build&apos; succeeds before running this program..
/// </summary>
internal static string dotnet_pack_failure {
get {
+1 -1
View File
@@ -58,7 +58,7 @@
<value>The project file '{0}' is irreparable becuase it is missing a '{1}' property, and the value cannot be guessed.</value>
</data>
<data name="dotnet_pack_failure" xml:space="preserve">
<value>Failed to pack; ensure that 'dotnet build' succeeds before running this program.</value>
<value>Failed to pack with exit code '{0}'; ensure that 'dotnet build' succeeds before running this program.</value>
</data>
<data name="failed_to_clean_up" xml:space="preserve">
<value>Could not delete temporary directory '{0}' due to error '{1}'</value>
+426 -290
View File
@@ -1,5 +1,6 @@
using System.Collections;
using System.Diagnostics;
using System.Text;
using Renci.SshNet;
using Spectre.Console;
using aeqw89.xml.ProjectFile;
@@ -26,6 +27,8 @@ public static class Program {
public static Dictionary<string, string[]> Flags { get; set; }
public static bool Verbose { get; set; } = false;
public static List<Action> RestoreActions { get; set; } = [];
public static void ReadArgs(string[] args) {
if (args.Length < 1) {
ShowError(Exceptions.missing_mode.EscapeMarkup());
@@ -108,322 +111,442 @@ public static class Program {
public static async Task Main(string[] args) {
ReadArgs(args);
Console.CancelKeyPress += (sender, eventArgs) => {
RestoreActions.ForEach(x => x());
};
string packageId = "";
string version = "";
int destinationsProcessed = 0;
var result = AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.Start<bool>("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 : -1,
Target == IncrementTarget.Minor ? delta : -1,
Target == IncrementTarget.Major ? delta : -1,
(x, y) => y < 0 ? x : x + y);
projectFile.SetVersion(version);
try {
var result = AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.Start<bool>("Preparing project", ctx => {
ctx.Status = "Locating project file";
if (!ProjectFile.TryLoad(Environment.CurrentDirectory, out var projectFile, out var error)) {
ShowError(error.EscapeMarkup());
return false;
}
}
catch (Exception e) {
ShowError(Exceptions.generic_error.EscapeMarkup(), e.ToString().EscapeMarkup());
projectFile.Restore();
return false;
}
version = projectFile.GetVersion();
packageId = projectFile.GetPackageId();
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);
projectFile.Backup();
RestoreActions.Add(() => {
projectFile.Restore();
AnsiConsole.MarkupLine("[yellow]Restored project file from backup.[/]");
});
HashSet<string> visited = [];
var projectReferences = new Queue<Item>(projectFile.GetProjectReferences().Cast<Item>());
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)) {
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;
}
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);
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;
}
}
var referencedProjectReferences = referencedProjectFile.GetProjectReferences();
foreach (var project in referencedProjectReferences) {
if (!visited.Contains(project.Include))
projectReferences.Enqueue(project);
}
ctx.Status = "Updating version";
var version = projectFile.GetVersion();
version = ChangeVersion(version, delta, Target ?? IncrementTarget.Patch);
projectFile.SetVersion(version);
}
} catch (Exception e) {
}
catch (Exception e) {
ShowError(Exceptions.generic_error.EscapeMarkup(), e.ToString().EscapeMarkup());
projectFile.Restore();
RestoreActions.ForEach(x => x());
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<string> visited = [];
var projectReferences = new Queue<Item>(projectFile.GetProjectReferences().Cast<Item>());
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());
RestoreActions.ForEach(x => x());
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());
RestoreActions.ForEach(x => x());
return false;
}
}
projectFile.Save();
return true;
});
if (!result) {
return;
}
var outDir = Path.GetRandomFileName();
RestoreActions.Add(() => {
try {
if (!Directory.Exists(outDir)) return;
Directory.Delete(outDir, true);
AnsiConsole.MarkupLine("[yellow]Cleaned up temporary directory[/]");
}
catch (Exception e) {
ShowError(string.Format(Exceptions.failed_to_clean_up.EscapeMarkup(), outDir.EscapeMarkup(),
e.ToString().EscapeMarkup()));
}
projectFile.Save();
return true;
});
if (!result) {
return;
}
var outDir = Path.GetRandomFileName();
result = AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.Start<bool>("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();
string processError = "";
var exitCode = await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.StartAsync<int>("Creating package with 'dotnet pack' ", async ctx => {
var p = Process.Start(new ProcessStartInfo() {
FileName = "dotnet",
Arguments = $"pack -o {outDir}",
WorkingDirectory = Environment.CurrentDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
});
CancellationTokenSource cts = new CancellationTokenSource();
StringBuilder errorLines = new();
p?.ErrorDataReceived += (sender, eventArgs) => {
cts.Cancel();
if (Verbose && eventArgs.Data != null)
AnsiConsole.WriteLine(eventArgs.Data);
};
bool success = false;
p?.OutputDataReceived += (sender, eventArgs) => {
if (eventArgs.Data?.ToLower().Contains("press any key") == true)
cts.Cancel();
if (Verbose && eventArgs.Data != null)
AnsiConsole.WriteLine(eventArgs.Data);
// Successfully created package 'C:\Users\qwsdc\source\repos\Beam\aeqw89.Beam\tozsxqaj.alp\Beam.1.0.0.nupkg'.
if (eventArgs.Data?.ToLower()
.Contains($"successfully created package '{Path.GetFullPath(outDir)}") == true) {
AnsiConsole.MarkupLine($"[bold]{eventArgs.Data}[/]");
success = true;
}
};
p?.BeginOutputReadLine();
p?.BeginErrorReadLine();
try {
await (p?.WaitForExitAsync(cts.Token) ?? Task.CompletedTask);
}
catch (TaskCanceledException) {
p?.Kill();
}
processError = errorLines.ToString().EscapeMarkup();
return success == true ? 0 : p?.ExitCode ?? -1;
});
}
finally {
if (exitCode != 0) {
ShowError(processError.EscapeMarkup());
ShowError(Exceptions.dotnet_pack_failure.EscapeMarkup(), exitCode);
RestoreActions.ForEach(x => x());
return;
}
if (Verbose)
AnsiConsole.MarkupLine("Successfully created package with exit code [green]{0}[/]. Processing destinations.", exitCode);
var package = Directory.GetFiles(outDir, "*.nupkg").FirstOrDefault();
if (package == null) {
ShowError(Exceptions.generic_error.EscapeMarkup());
RestoreActions.ForEach(x => x());
return;
}
var inMemory = await File.ReadAllBytesAsync(package);
var size = new FileInfo(package).Length;
const long bufferSize = 80 * 1024; // 80 KB
try {
Directory.Delete(outDir, true);
await AnsiConsole.Progress()
.AutoClear(true)
.HideCompleted(false)
.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 = true,
RedirectStandardError = true
});
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);
};
p?.OutputDataReceived += (sender, eventArgs) => {
if (eventArgs.Data?.ToLower().Contains("press any key") == true)
cts.Cancel();
if (Verbose && eventArgs.Data != null)
AnsiConsole.WriteLine(eventArgs.Data);
};
p?.BeginOutputReadLine();
p?.BeginErrorReadLine();
if (p == null) {
ShowError(Exceptions.generic_error.EscapeMarkup());
}
task.Increment(size / 2);
if (p != null)
try {
await (p?.WaitForExitAsync(cts.Token) ?? Task.CompletedTask);
}
catch (TaskCanceledException) {
p?.Kill();
}
if (p?.ExitCode != 0) {
ShowError(errorLines.ToString().EscapeMarkup());
ShowError(Exceptions.dotnet_nuget_push_failure, p?.ExitCode ?? -1);
task.StopTask();
return;
}
task.Increment(size / 2);
}
Interlocked.Increment(ref destinationsProcessed);
task.StopTask();
});
});
}
catch (Exception e) {
ShowError(string.Format(Exceptions.failed_to_clean_up.EscapeMarkup(), outDir.EscapeMarkup(), e.ToString().EscapeMarkup()));
finally {
try {
Directory.Delete(outDir, true);
}
catch (Exception e) {
ShowError(string.Format(Exceptions.failed_to_clean_up.EscapeMarkup(), outDir.EscapeMarkup(),
e.ToString().EscapeMarkup()));
}
}
}
catch(Exception e) {
ShowError(Exceptions.generic_error.EscapeMarkup(), e.ToString().EscapeMarkup());;
RestoreActions.ForEach(x => x());
}
if (destinationsProcessed == 0) {
AnsiConsole.MarkupLine("[bold red]No destinations were processed. Reverting changes to project file.[/]");
RestoreActions.ForEach(x => x());
}
else {
AnsiConsole.MarkupLine("Completed processing of all destinations.");
AnsiConsole.MarkupLine(
"Example usage:\n\t <PackageReference Include=\"{0}\" Version=\"{1}\" />".EscapeMarkup(), packageId,
version);
}
AnsiConsole.MarkupLine("Completed processing of all destinations.");
AnsiConsole.MarkupLine("Example usage:\n\t <PackageReference Include=\"{0}\" Version=\"{1}\" />".EscapeMarkup(), packageId, version);
}
/// <summary>
@@ -436,8 +559,7 @@ public static class Program {
/// <param name="operation">A function that defines the adjustment operation to be performed on each version component.</param>
/// <returns>A new version string with the updated major, minor, and patch components, preserving any existing tag.</returns>
/// <exception cref="Exception">Thrown if the version string is not in the correct format.</exception>
private static string ChangeVersion(string version, int patch, int minor, int major,
Func<int, int, int> operation) {
private static string ChangeVersion(string version, int delta, IncrementTarget target) {
string[] split = version.Split('.');
if (split.Length != 3) {
throw new Exception(string.Format(Exceptions.version_string_not_formatted_correctly, version));
@@ -454,9 +576,23 @@ public static class Program {
throw new Exception(string.Format(Exceptions.version_string_not_formatted_correctly, version));
int[] parsedVersion = split.Select(int.Parse).ToArray();
switch (target) {
case IncrementTarget.Major:
parsedVersion[0] += delta;
parsedVersion[1] = 0;
parsedVersion[2] = 0;
break;
case IncrementTarget.Minor:
parsedVersion[1] += delta;
parsedVersion[2] = 0;
break;
case IncrementTarget.Patch:
parsedVersion[2] += delta;
break;
}
return
$"{operation(parsedVersion[0], major)}.{operation(parsedVersion[1], minor)}.{operation(parsedVersion[2], patch)}{tag}";
$"{parsedVersion[0]}.{parsedVersion[1]}.{parsedVersion[2]}{tag}";
}
private static void ShowError(string message, params object[] args) {
+5 -1
View File
@@ -106,6 +106,7 @@ internal class ProjectFile {
}
set("Version", "1.0.0");
set("PackageVersion", "1.0.0");
set("Title", System.IO.Path.GetFileNameWithoutExtension(Path));
set("Authors", "");
set("Company", "");
@@ -213,5 +214,8 @@ internal class ProjectFile {
public string GetVersion() => MainPropertyGroup.GetProperty("Version");
public void SetVersion(string version) => MainPropertyGroup.SetProperty("Version", version);
public void SetVersion(string version) {
MainPropertyGroup.SetProperty("Version", version);
MainPropertyGroup.SetProperty("PackageVersion", version);
}
}
@@ -5,6 +5,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>