Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 207805bad3 | |||
| 6904e0cfd8 | |||
| 093da5d0f3 | |||
| c6570c1e2c |
@@ -0,0 +1 @@
|
||||
bin/
|
||||
+1
-1
@@ -96,7 +96,7 @@ namespace aeqw89.tools.Publish {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to pack; ensure that 'dotnet build' succeeds before running this program..
|
||||
/// Looks up a localized string similar to Failed to pack with exit code '{0}'; ensure that 'dotnet build' succeeds before running this program..
|
||||
/// </summary>
|
||||
internal static string dotnet_pack_failure {
|
||||
get {
|
||||
|
||||
@@ -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>
|
||||
|
||||
+410
-279
@@ -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,327 +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();
|
||||
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);
|
||||
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");
|
||||
if (Verbose)
|
||||
AnsiConsole.WriteLine(
|
||||
$"Created project file backup at {projectFile.GetDefaultBackupLocation()}");
|
||||
|
||||
projectFile.SetPrivateAssets(reference, PrivateAssetsValue.All);
|
||||
string pathToReferencedProjectFile = projectFile.GetAbsoluteIncludePath(reference);
|
||||
if (!ProjectFile.TryLoad(pathToReferencedProjectFile, out var referencedProjectFile,
|
||||
out error)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
projectFile.Save();
|
||||
return true;
|
||||
});
|
||||
version = projectFile.GetVersion();
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
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
|
||||
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;
|
||||
});
|
||||
p?.WaitForExit();
|
||||
return p?.ExitCode == 0;
|
||||
|
||||
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()));
|
||||
}
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
ShowError(Exceptions.dotnet_pack_failure.EscapeMarkup());
|
||||
return;
|
||||
}
|
||||
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
|
||||
});
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
else if (dest.StartsWith("cloud-")) {
|
||||
var name = dest[("cloud-".Length)..];
|
||||
var connectionTask = ctx.AddTaskBefore($"Preparing cloud-{name}", new ProgressTaskSettings() {
|
||||
MaxValue = 100
|
||||
}, task);
|
||||
p?.BeginOutputReadLine();
|
||||
p?.BeginErrorReadLine();
|
||||
|
||||
if (!SshHosts.TryGetHost(name, out var host)) {
|
||||
ShowError(Exceptions.cloud_host_not_found.EscapeMarkup(), name);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await (p?.WaitForExitAsync(cts.Token) ?? Task.CompletedTask);
|
||||
}
|
||||
catch (TaskCanceledException) {
|
||||
p?.Kill();
|
||||
}
|
||||
|
||||
var connectionInfo = SshHosts.GetConnection(name);
|
||||
using var sshClient = new SshClient(connectionInfo);
|
||||
if (!sshClient.IsConnected)
|
||||
await sshClient.ConnectAsync(ct);
|
||||
connectionTask.Increment(33);
|
||||
processError = errorLines.ToString().EscapeMarkup();
|
||||
return success == true ? 0 : p?.ExitCode ?? -1;
|
||||
});
|
||||
|
||||
var winC = sshClient.RunCommand("cmd /c ver");
|
||||
var othC = sshClient.RunCommand("uname -s");
|
||||
if (exitCode != 0) {
|
||||
ShowError(processError.EscapeMarkup());
|
||||
ShowError(Exceptions.dotnet_pack_failure.EscapeMarkup(), exitCode);
|
||||
RestoreActions.ForEach(x => x());
|
||||
return;
|
||||
}
|
||||
|
||||
var os = (winC.ExitStatus, othC.ExitStatus) switch {
|
||||
(0, _) => "windows",
|
||||
(_, 0) => "linux",
|
||||
_ => "unknown"
|
||||
};
|
||||
if (Verbose)
|
||||
AnsiConsole.MarkupLine("Successfully created package with exit code [green]{0}[/]. Processing destinations.", exitCode);
|
||||
|
||||
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 package = Directory.GetFiles(outDir, "*.nupkg").FirstOrDefault();
|
||||
if (package == null) {
|
||||
ShowError(Exceptions.generic_error.EscapeMarkup());
|
||||
RestoreActions.ForEach(x => x());
|
||||
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 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 (p == null) {
|
||||
ShowError(Exceptions.generic_error.EscapeMarkup());
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
else if (dest.StartsWith("cloud-")) {
|
||||
var name = dest[("cloud-".Length)..];
|
||||
var connectionTask = ctx.AddTaskBefore($"Preparing cloud-{name}",
|
||||
new ProgressTaskSettings() {
|
||||
MaxValue = 100
|
||||
}, task);
|
||||
|
||||
task.StopTask();
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
Directory.Delete(outDir, true);
|
||||
}
|
||||
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>
|
||||
@@ -441,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));
|
||||
@@ -459,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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user