From b125406afb7b64fe343d0d08bab0cb59df3a9944 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sat, 16 May 2026 14:44:43 +0000 Subject: [PATCH 1/3] Initial plan From b50a5054b4398156cc208a231d87e3628aeedd47 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sat, 16 May 2026 14:54:42 +0000 Subject: [PATCH 2/3] Add Publish and Pack menu options with dialog UI - Add idPublishProject and idPackProject command IDs to Menus.vsct - Add Publish and Pack buttons to project context menu - Implement CommandPublish.cs with dotnet publish integration - Implement CommandPack.cs with dotnet pack integration - Create PublishDialog.xaml with options for target OS, self-contained, single file - Update Menus.cs with new command IDs - Add new files to ProjectPackage2022.csproj - Add System.Windows.Forms reference for FolderBrowserDialog Agent-Logs-Url: https://github.com/X-Sharp/XSharpPublic/sessions/7eff031e-40f9-40df-9a2f-106ce7951a82 Co-authored-by: RobertvanderHulst <14240939+RobertvanderHulst@users.noreply.github.com> --- .../ProjectPackage/Commands/CommandPack.cs | 162 +++++++++++++++ .../ProjectPackage/Commands/CommandPublish.cs | 196 ++++++++++++++++++ .../Commands/PublishDialog.xaml | 84 ++++++++ .../Commands/PublishDialog.xaml.cs | 136 ++++++++++++ src/VisualStudio/ProjectPackage/Menus.cs | 2 + src/VisualStudio/ProjectPackage/Menus.vsct | 30 +++ .../ProjectPackage/ProjectPackage2022.csproj | 10 + 7 files changed, 620 insertions(+) create mode 100644 src/VisualStudio/ProjectPackage/Commands/CommandPack.cs create mode 100644 src/VisualStudio/ProjectPackage/Commands/CommandPublish.cs create mode 100644 src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml create mode 100644 src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml.cs diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandPack.cs b/src/VisualStudio/ProjectPackage/Commands/CommandPack.cs new file mode 100644 index 0000000000..4598a40b63 --- /dev/null +++ b/src/VisualStudio/ProjectPackage/Commands/CommandPack.cs @@ -0,0 +1,162 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Task = System.Threading.Tasks.Task; + +namespace XSharp.Project +{ + [Command(PackageIds.idPackProject)] + internal sealed class CommandPack : BaseCommand + { + protected override void BeforeQueryStatus(EventArgs e) + { + base.BeforeQueryStatus(e); + ThreadHelper.JoinableTaskFactory.Run(CheckAvailabilityAsync); + } + + private async Task CheckAvailabilityAsync() + { + Command.Visible = await Commands.ProjectIsXSharpProjectAsync(); + if (Command.Visible) + { + var project = await VS.Solutions.GetActiveProjectAsync(); + var path = project.FullPath; + var prj = XSharpProjectNode.FindProject(path); + // Only show for SDK-style projects as they support dotnet pack + Command.Visible = prj != null && prj.IsSdkProject; + } + } + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var project = await VS.Solutions.GetActiveProjectAsync(); + if (project == null) + { + await VS.MessageBox.ShowErrorAsync("Pack", "No active project selected."); + return; + } + + var projectPath = project.FullPath; + var prj = XSharpProjectNode.FindProject(projectPath); + + if (prj == null || !prj.IsSdkProject) + { + await VS.MessageBox.ShowErrorAsync("Pack", "Pack is only available for SDK-style projects."); + return; + } + + // Show confirmation dialog + var result = await VS.MessageBox.ShowAsync( + "Create NuGet Package", + "This will create a NuGet package from the project using 'dotnet pack'.\n\n" + + "The package will be created in the project's bin folder.\n\n" + + "Continue?", + Microsoft.VisualStudio.Shell.Interop.OLEMSGICON.OLEMSGICON_QUERY, + Microsoft.VisualStudio.Shell.Interop.OLEMSGBUTTON.OLEMSGBUTTON_OKCANCEL); + + if (result == Microsoft.VisualStudio.VSConstants.MessageBoxResult.IDOK) + { + await PackProjectAsync(projectPath); + } + } + + private async Task PackProjectAsync(string projectPath) + { + try + { + await VS.StatusBar.ShowMessageAsync("Creating NuGet package..."); + + // Build dotnet pack command + var arguments = $"pack \"{projectPath}\" -c Release"; + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = arguments, + WorkingDirectory = Path.GetDirectoryName(projectPath), + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + var outputPane = await VS.Windows.CreateOutputWindowPaneAsync("X# Pack"); + await outputPane.ActivateAsync(); + await outputPane.ClearAsync(); + await outputPane.WriteLineAsync($"Creating NuGet package for: {Path.GetFileName(projectPath)}"); + await outputPane.WriteLineAsync($"Command: dotnet {arguments}"); + await outputPane.WriteLineAsync(""); + + var process = new Process { StartInfo = psi }; + + string packagePath = null; + + process.OutputDataReceived += async (s, ea) => + { + if (!string.IsNullOrEmpty(ea.Data)) + { + await outputPane.WriteLineAsync(ea.Data); + + // Try to extract the package path from output + if (ea.Data.Contains(".nupkg")) + { + var parts = ea.Data.Split(new[] { '\'' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 2) + { + packagePath = parts[1]; + } + } + } + }; + + process.ErrorDataReceived += async (s, ea) => + { + if (!string.IsNullOrEmpty(ea.Data)) + { + await outputPane.WriteLineAsync($"ERROR: {ea.Data}"); + } + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + await Task.Run(() => process.WaitForExit()); + + if (process.ExitCode == 0) + { + await outputPane.WriteLineAsync(""); + await outputPane.WriteLineAsync("Pack succeeded."); + await VS.StatusBar.ShowMessageAsync("NuGet package created successfully."); + + var message = "NuGet package created successfully."; + if (!string.IsNullOrEmpty(packagePath)) + { + message += $"\n\nPackage location:\n{packagePath}"; + } + + await VS.MessageBox.ShowAsync("Pack", + message, + Microsoft.VisualStudio.Shell.Interop.OLEMSGICON.OLEMSGICON_INFO, + Microsoft.VisualStudio.Shell.Interop.OLEMSGBUTTON.OLEMSGBUTTON_OK); + } + else + { + await outputPane.WriteLineAsync(""); + await outputPane.WriteLineAsync($"Pack failed with exit code {process.ExitCode}."); + await VS.StatusBar.ShowMessageAsync("Pack failed."); + await VS.MessageBox.ShowErrorAsync("Pack", "Pack failed. See Output window for details."); + } + } + catch (Exception ex) + { + await VS.MessageBox.ShowErrorAsync("Pack Error", $"Failed to create NuGet package:\n{ex.Message}"); + } + } + } +} diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandPublish.cs b/src/VisualStudio/ProjectPackage/Commands/CommandPublish.cs new file mode 100644 index 0000000000..040e80774c --- /dev/null +++ b/src/VisualStudio/ProjectPackage/Commands/CommandPublish.cs @@ -0,0 +1,196 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Task = System.Threading.Tasks.Task; + +namespace XSharp.Project +{ + [Command(PackageIds.idPublishProject)] + internal sealed class CommandPublish : BaseCommand + { + protected override void BeforeQueryStatus(EventArgs e) + { + base.BeforeQueryStatus(e); + ThreadHelper.JoinableTaskFactory.Run(CheckAvailabilityAsync); + } + + private async Task CheckAvailabilityAsync() + { + Command.Visible = await Commands.ProjectIsXSharpProjectAsync(); + if (Command.Visible) + { + var project = await VS.Solutions.GetActiveProjectAsync(); + var path = project.FullPath; + var prj = XSharpProjectNode.FindProject(path); + // Only show for SDK-style projects as they support dotnet publish + Command.Visible = prj != null && prj.IsSdkProject; + } + } + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var project = await VS.Solutions.GetActiveProjectAsync(); + if (project == null) + { + await VS.MessageBox.ShowErrorAsync("Publish", "No active project selected."); + return; + } + + var projectPath = project.FullPath; + var prj = XSharpProjectNode.FindProject(projectPath); + + if (prj == null || !prj.IsSdkProject) + { + await VS.MessageBox.ShowErrorAsync("Publish", "Publish is only available for SDK-style projects."); + return; + } + + // Show publish dialog to get options + var dialog = new PublishDialog(projectPath); + var result = dialog.ShowModal(); + + if (result == true) + { + await PublishProjectAsync(projectPath, dialog.PublishOptions); + } + } + + private async Task PublishProjectAsync(string projectPath, PublishOptions options) + { + try + { + await VS.StatusBar.ShowMessageAsync("Publishing project..."); + + var arguments = BuildPublishArguments(projectPath, options); + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = arguments, + WorkingDirectory = Path.GetDirectoryName(projectPath), + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + var outputPane = await VS.Windows.CreateOutputWindowPaneAsync("X# Publish"); + await outputPane.ActivateAsync(); + await outputPane.ClearAsync(); + await outputPane.WriteLineAsync($"Publishing project: {Path.GetFileName(projectPath)}"); + await outputPane.WriteLineAsync($"Command: dotnet {arguments}"); + await outputPane.WriteLineAsync(""); + + var process = new Process { StartInfo = psi }; + + process.OutputDataReceived += async (s, ea) => + { + if (!string.IsNullOrEmpty(ea.Data)) + { + await outputPane.WriteLineAsync(ea.Data); + } + }; + + process.ErrorDataReceived += async (s, ea) => + { + if (!string.IsNullOrEmpty(ea.Data)) + { + await outputPane.WriteLineAsync($"ERROR: {ea.Data}"); + } + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + await Task.Run(() => process.WaitForExit()); + + if (process.ExitCode == 0) + { + await outputPane.WriteLineAsync(""); + await outputPane.WriteLineAsync("Publish succeeded."); + await VS.StatusBar.ShowMessageAsync("Publish succeeded."); + await VS.MessageBox.ShowAsync("Publish", + $"Project published successfully to:\n{options.OutputPath}", + Microsoft.VisualStudio.Shell.Interop.OLEMSGICON.OLEMSGICON_INFO, + Microsoft.VisualStudio.Shell.Interop.OLEMSGBUTTON.OLEMSGBUTTON_OK); + } + else + { + await outputPane.WriteLineAsync(""); + await outputPane.WriteLineAsync($"Publish failed with exit code {process.ExitCode}."); + await VS.StatusBar.ShowMessageAsync("Publish failed."); + await VS.MessageBox.ShowErrorAsync("Publish", "Publish failed. See Output window for details."); + } + } + catch (Exception ex) + { + await VS.MessageBox.ShowErrorAsync("Publish Error", $"Failed to publish project:\n{ex.Message}"); + } + } + + private string BuildPublishArguments(string projectPath, PublishOptions options) + { + var args = $"publish \"{projectPath}\""; + + if (!string.IsNullOrEmpty(options.Configuration)) + { + args += $" -c {options.Configuration}"; + } + + if (!string.IsNullOrEmpty(options.TargetFramework)) + { + args += $" -f {options.TargetFramework}"; + } + + if (!string.IsNullOrEmpty(options.Runtime)) + { + args += $" -r {options.Runtime}"; + } + + if (!string.IsNullOrEmpty(options.OutputPath)) + { + args += $" -o \"{options.OutputPath}\""; + } + + if (options.SelfContained.HasValue) + { + args += options.SelfContained.Value ? " --self-contained" : " --no-self-contained"; + } + + if (options.SingleFile) + { + args += " -p:PublishSingleFile=true"; + } + + if (options.ReadyToRun) + { + args += " -p:PublishReadyToRun=true"; + } + + if (options.Trimmed) + { + args += " -p:PublishTrimmed=true"; + } + + return args; + } + } + + public class PublishOptions + { + public string Configuration { get; set; } = "Release"; + public string TargetFramework { get; set; } + public string Runtime { get; set; } + public string OutputPath { get; set; } + public bool? SelfContained { get; set; } + public bool SingleFile { get; set; } + public bool ReadyToRun { get; set; } + public bool Trimmed { get; set; } + } +} diff --git a/src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml b/src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml new file mode 100644 index 0000000000..6d0a30d6de --- /dev/null +++ b/src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + +