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.
This commit is contained in:
qwsdcvghyu89
2025-09-27 17:19:30 +10:00
parent 6904e0cfd8
commit 207805bad3
2 changed files with 431 additions and 331 deletions
+426 -330
View File
@@ -1,5 +1,6 @@
using System.Collections; using System.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using Renci.SshNet; using Renci.SshNet;
using Spectre.Console; using Spectre.Console;
using aeqw89.xml.ProjectFile; using aeqw89.xml.ProjectFile;
@@ -26,6 +27,8 @@ public static class Program {
public static Dictionary<string, string[]> Flags { get; set; } public static Dictionary<string, string[]> Flags { get; set; }
public static bool Verbose { get; set; } = false; public static bool Verbose { get; set; } = false;
public static List<Action> RestoreActions { get; set; } = [];
public static void ReadArgs(string[] args) { public static void ReadArgs(string[] args) {
if (args.Length < 1) { if (args.Length < 1) {
ShowError(Exceptions.missing_mode.EscapeMarkup()); ShowError(Exceptions.missing_mode.EscapeMarkup());
@@ -108,362 +111,442 @@ public static class Program {
public static async Task Main(string[] args) { public static async Task Main(string[] args) {
ReadArgs(args); ReadArgs(args);
Console.CancelKeyPress += (sender, eventArgs) => {
RestoreActions.ForEach(x => x());
};
string packageId = ""; string packageId = "";
string version = ""; string version = "";
int destinationsProcessed = 0;
var result = AnsiConsole.Status() try {
.Spinner(Spinner.Known.Dots) var result = AnsiConsole.Status()
.Start<bool>("Preparing project", ctx => { .Spinner(Spinner.Known.Dots)
ctx.Status = "Locating project file"; .Start<bool>("Preparing project", ctx => {
if (!ProjectFile.TryLoad(Environment.CurrentDirectory, out var projectFile, out var error)) { ctx.Status = "Locating project file";
ShowError(error.EscapeMarkup()); if (!ProjectFile.TryLoad(Environment.CurrentDirectory, out var projectFile, out var error)) {
return false; ShowError(error.EscapeMarkup());
} return false;
packageId = projectFile.GetPackageId();
try {
projectFile.Backup();
Console.CancelKeyPress += (sender, eventArgs) => {
projectFile.Restore();
AnsiConsole.MarkupLine("[yellow]Restored project file from 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);
} }
}
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 { try {
var packageReferences = projectFile.GetPackageReferences(); projectFile.Backup();
foreach (var reference in packageReferences.Where(x => !projectFile.IsTransitive(x))) RestoreActions.Add(() => {
projectFile.SetPrivateAssets(reference, PrivateAssetsValue.All); projectFile.Restore();
foreach (var reference in packageReferences.Where(x => projectFile.IsTransitive(x))) AnsiConsole.MarkupLine("[yellow]Restored project file from backup.[/]");
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) if (Verbose)
AnsiConsole.WriteLine($"Processing project reference {reference.Include} out of {visited.Count} so far"); AnsiConsole.WriteLine(
$"Created project file backup at {projectFile.GetDefaultBackupLocation()}");
projectFile.SetPrivateAssets(reference, PrivateAssetsValue.All);
string pathToReferencedProjectFile = projectFile.GetAbsoluteIncludePath(reference); ctx.Status = "Repairing project file";
if (!ProjectFile.TryLoad(pathToReferencedProjectFile, out var referencedProjectFile, if (!Flags.ContainsKey("--skip-repair"))
out error)) { if (!projectFile.TryRepair(out error)) {
ShowError(error.EscapeMarkup()); ShowError(error.EscapeMarkup());
projectFile.Restore(); projectFile.Restore();
return false; return false;
} }
var referencedPackageReferences = referencedProjectFile.GetPackageReferences(); if (Mode == Mode.Increment && !Flags.ContainsKey("--simulate")) {
foreach (var package in referencedPackageReferences) { int delta = 1;
if (Verbose) if (Flags.TryGetValue("--delta", out var deltaStrings)) {
AnsiConsole.WriteLine($"Hoisting package {package.Include} from {pathToReferencedProjectFile}"); if (deltaStrings.Length != 1) {
var hoisted = projectFile.AddPackage(package); ShowError(Exceptions.flag_parameter_length_incorrect.EscapeMarkup(), "--delta", 1,
projectFile.SetTransitive(hoisted, true); deltaStrings.Length);
projectFile.SetPrivateAssets(hoisted, PrivateAssetsValue.None); projectFile.Restore();
referencedProjectFile.SetPrivateAssets(package, PrivateAssetsValue.All); 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(); ctx.Status = "Updating version";
foreach (var project in referencedProjectReferences) { var version = projectFile.GetVersion();
if (!visited.Contains(project.Include)) version = ChangeVersion(version, delta, Target ?? IncrementTarget.Patch);
projectReferences.Enqueue(project);
} projectFile.SetVersion(version);
} }
} catch (Exception e) { }
catch (Exception e) {
ShowError(Exceptions.generic_error.EscapeMarkup(), e.ToString().EscapeMarkup()); ShowError(Exceptions.generic_error.EscapeMarkup(), e.ToString().EscapeMarkup());
projectFile.Restore(); RestoreActions.ForEach(x => x());
return false; 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) { string processError = "";
return; var exitCode = await AnsiConsole.Status()
} .Spinner(Spinner.Known.Dots)
.StartAsync<int>("Creating package with 'dotnet pack' ", async ctx => {
var outDir = Path.GetRandomFileName(); var p = Process.Start(new ProcessStartInfo() {
Console.CancelKeyPress += (sender, eventArgs) => { FileName = "dotnet",
try { Arguments = $"pack -o {outDir}",
Directory.Delete(outDir, true); WorkingDirectory = Environment.CurrentDirectory,
AnsiConsole.MarkupLine("[yellow]Cleaned up temporary directory[/]"); UseShellExecute = false,
} RedirectStandardOutput = true,
catch (Exception e) { RedirectStandardError = true
ShowError(string.Format(Exceptions.failed_to_clean_up.EscapeMarkup(), outDir.EscapeMarkup(),
e.ToString().EscapeMarkup()));
}
};
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 = Verbose,
RedirectStandardOutput = !Verbose,
RedirectStandardError = !Verbose
});
CancellationTokenSource cts = new CancellationTokenSource();
p?.ErrorDataReceived += (sender, eventArgs) => {
cts.Cancel();
};
p?.OutputDataReceived += (sender, eventArgs) => {
if (eventArgs.Data?.ToLower().Contains("press any key") == true)
cts.Cancel();
};
await (p?.WaitForExitAsync(cts.Token) ?? Task.CompletedTask);
processError = p?.StandardError?.ReadToEnd() ?? "";
return p?.ExitCode ?? -1;
});
if (exitCode != 0) {
ShowError(processError.EscapeMarkup());
ShowError(Exceptions.dotnet_pack_failure.EscapeMarkup(), exitCode);
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(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 = !Verbose,
RedirectStandardError = !Verbose
});
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
p?.ErrorDataReceived += (sender, eventArgs) => {
cts.Cancel();
};
p?.OutputDataReceived += (sender, eventArgs) => {
if (eventArgs.Data?.ToLower().Contains("press any key") == true)
cts.Cancel();
};
if (p == null) {
ShowError(Exceptions.generic_error.EscapeMarkup());
}
task.Increment(size / 2);
if (p != null)
await p.WaitForExitAsync(cts.Token);
processError += p?.StandardError?.ReadToEnd() ?? "";
if (p?.ExitCode != 0) {
ShowError(processError.EscapeMarkup());
ShowError(Exceptions.dotnet_nuget_push_failure, p?.ExitCode ?? -1);
}
task.Increment(size / 2);
}
task.StopTask();
}); });
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 { 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) { finally {
ShowError(string.Format(Exceptions.failed_to_clean_up.EscapeMarkup(), outDir.EscapeMarkup(), e.ToString().EscapeMarkup())); 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> /// <summary>
@@ -476,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> /// <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> /// <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> /// <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, private static string ChangeVersion(string version, int delta, IncrementTarget target) {
Func<int, int, int> operation) {
string[] split = version.Split('.'); string[] split = version.Split('.');
if (split.Length != 3) { if (split.Length != 3) {
throw new Exception(string.Format(Exceptions.version_string_not_formatted_correctly, version)); throw new Exception(string.Format(Exceptions.version_string_not_formatted_correctly, version));
@@ -494,9 +576,23 @@ public static class Program {
throw new Exception(string.Format(Exceptions.version_string_not_formatted_correctly, version)); throw new Exception(string.Format(Exceptions.version_string_not_formatted_correctly, version));
int[] parsedVersion = split.Select(int.Parse).ToArray(); 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 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) { 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("Version", "1.0.0");
set("PackageVersion", "1.0.0");
set("Title", System.IO.Path.GetFileNameWithoutExtension(Path)); set("Title", System.IO.Path.GetFileNameWithoutExtension(Path));
set("Authors", ""); set("Authors", "");
set("Company", ""); set("Company", "");
@@ -213,5 +214,8 @@ internal class ProjectFile {
public string GetVersion() => MainPropertyGroup.GetProperty("Version"); 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);
}
} }