From f6623ecf95d4e32fdb17c4751e099980fa0b9361 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 18:34:36 +0000 Subject: [PATCH 1/3] Add --no-run and --save-only flags for non-interactive CLI usage Adds three new CLI flags to support programmatic/automation use cases where PSW is invoked from other tools (e.g., MCP servers, CI/CD) and stdin may not be a TTY: - --no-run: Omits the 'dotnet run' command from the generated script, allowing install+build without starting the web server - --save-only: Writes the generated script to a file and exits immediately, bypassing all interactive Spectre.Console prompts - --output : Specifies the output file path for --save-only Changes span the shared library (PackagesViewModel, ScriptGeneratorService), CLI models (CommandLineOptions, ScriptModel), and both CliModeWorkflow and TemplateWorkflow. https://claude.ai/code/session_01GjPpGbCY4XLHANx3eztDyz --- src/PSW.Shared/Models/PackagesViewModel.cs | 6 ++ .../Services/ScriptGeneratorService.cs | 5 +- src/PackageCliTool/CHANGELOG.md | 7 ++ .../Extensions/ScriptModelExtensions.cs | 3 +- src/PackageCliTool/Models/Api/ScriptModel.cs | 4 ++ .../Models/CommandLineOptions.cs | 26 +++++++- src/PackageCliTool/UI/ConsoleDisplay.cs | 10 +++ .../Workflows/CliModeWorkflow.cs | 66 +++++++++++++++++++ .../Workflows/TemplateWorkflow.cs | 41 ++++++++++++ 9 files changed, 165 insertions(+), 3 deletions(-) diff --git a/src/PSW.Shared/Models/PackagesViewModel.cs b/src/PSW.Shared/Models/PackagesViewModel.cs index 2030b75..f1581e1 100644 --- a/src/PSW.Shared/Models/PackagesViewModel.cs +++ b/src/PSW.Shared/Models/PackagesViewModel.cs @@ -125,4 +125,10 @@ public bool CanIncludeDocker [Display(Name = "Remove comments")] public bool RemoveComments { get; set; } public bool HasQueryString { get; set; } + + /// + /// When true, the generated script will not include the 'dotnet run' command. + /// Used by the CLI --no-run flag for automation scenarios where the server should not be started. + /// + public bool SkipDotnetRun { get; set; } } \ No newline at end of file diff --git a/src/PSW.Shared/Services/ScriptGeneratorService.cs b/src/PSW.Shared/Services/ScriptGeneratorService.cs index 1dc5816..89c75b0 100644 --- a/src/PSW.Shared/Services/ScriptGeneratorService.cs +++ b/src/PSW.Shared/Services/ScriptGeneratorService.cs @@ -48,7 +48,10 @@ public string GenerateScript(PackagesViewModel model) outputList.AddRange(GenerateAddPackagesScript(model, renderPackageName)); - outputList.AddRange(GenerateRunProjectScript(model, renderPackageName)); + if (!model.SkipDotnetRun) + { + outputList.AddRange(GenerateRunProjectScript(model, renderPackageName)); + } if (model.RemoveComments) { diff --git a/src/PackageCliTool/CHANGELOG.md b/src/PackageCliTool/CHANGELOG.md index d6f95ea..3e4f15c 100644 --- a/src/PackageCliTool/CHANGELOG.md +++ b/src/PackageCliTool/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to Package Script Writer CLI will be documented in this file The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- **Non-interactive Mode (`--save-only`)** - Save the generated script to a file (via `--output `) and exit immediately without showing interactive prompts. Enables programmatic use from other tools (e.g., MCP servers, CI/CD pipelines) where stdin is not a TTY +- **Build-only Mode (`--no-run`)** - Skip the `dotnet run` command from the generated script. When combined with `--auto-run`, this allows installing and building an Umbraco project without starting the web server +- **Script Output File (`--output `)** - Specify an output file path for the generated script, used with `--save-only` + ## [1.1.2] - 2026-01-16 ### Fixed diff --git a/src/PackageCliTool/Extensions/ScriptModelExtensions.cs b/src/PackageCliTool/Extensions/ScriptModelExtensions.cs index e597f0f..f279922 100644 --- a/src/PackageCliTool/Extensions/ScriptModelExtensions.cs +++ b/src/PackageCliTool/Extensions/ScriptModelExtensions.cs @@ -35,7 +35,8 @@ public static PackagesViewModel ToViewModel(this Models.Api.ScriptModel scriptMo StarterKitPackage = scriptModel.StarterKitPackage, IncludeDockerfile = scriptModel.IncludeDockerfile, IncludeDockerCompose = scriptModel.IncludeDockerCompose, - EnableContentDeliveryApi = scriptModel.EnableContentDeliveryApi + EnableContentDeliveryApi = scriptModel.EnableContentDeliveryApi, + SkipDotnetRun = scriptModel.SkipDotnetRun }; } } diff --git a/src/PackageCliTool/Models/Api/ScriptModel.cs b/src/PackageCliTool/Models/Api/ScriptModel.cs index 05e6347..c5e2507 100644 --- a/src/PackageCliTool/Models/Api/ScriptModel.cs +++ b/src/PackageCliTool/Models/Api/ScriptModel.cs @@ -86,4 +86,8 @@ public class ScriptModel /// Gets or sets whether to remove comments from the generated script [JsonPropertyName("removeComments")] public bool RemoveComments { get; set; } + + /// Gets or sets whether to skip the 'dotnet run' command in the generated script + [JsonPropertyName("skipDotnetRun")] + public bool SkipDotnetRun { get; set; } } diff --git a/src/PackageCliTool/Models/CommandLineOptions.cs b/src/PackageCliTool/Models/CommandLineOptions.cs index f9d40fd..e608e50 100644 --- a/src/PackageCliTool/Models/CommandLineOptions.cs +++ b/src/PackageCliTool/Models/CommandLineOptions.cs @@ -82,9 +82,18 @@ public class CommandLineOptions /// Gets or sets whether to automatically run the generated script public bool AutoRun { get; set; } + /// Gets or sets whether to skip the 'dotnet run' command in the generated script + public bool NoRun { get; set; } + /// Gets or sets the directory where the script should be run public string? RunDirectory { get; set; } + /// Gets or sets whether to save the script to a file and exit immediately without interactive prompts + public bool SaveOnly { get; set; } + + /// Gets or sets the output file path for saving the generated script + public string? OutputFile { get; set; } + /// Gets or sets whether to enable verbose logging public bool VerboseMode { get; set; } @@ -180,7 +189,10 @@ public bool HasAnyOptions() RemoveComments.HasValue || IncludePrerelease.HasValue || AutoRun || - !string.IsNullOrWhiteSpace(RunDirectory); + NoRun || + !string.IsNullOrWhiteSpace(RunDirectory) || + SaveOnly || + !string.IsNullOrWhiteSpace(OutputFile); } /// @@ -373,10 +385,22 @@ public static CommandLineOptions Parse(string[] args) options.AutoRun = true; break; + case "--no-run": + options.NoRun = true; + break; + case "--run-dir": options.RunDirectory = GetNextArgument(args, ref i); break; + case "--save-only": + options.SaveOnly = true; + break; + + case "--output": + options.OutputFile = GetNextArgument(args, ref i); + break; + case "--verbose": options.VerboseMode = true; break; diff --git a/src/PackageCliTool/UI/ConsoleDisplay.cs b/src/PackageCliTool/UI/ConsoleDisplay.cs index 5388dda..7c150a9 100644 --- a/src/PackageCliTool/UI/ConsoleDisplay.cs +++ b/src/PackageCliTool/UI/ConsoleDisplay.cs @@ -52,6 +52,9 @@ psw versions [green] --admin-password[/] Admin password for unattended install [green] --auto-run[/] Automatically run the generated script [green] --clear-cache[/] Clear all cached API responses + [green] --no-run[/] Skip 'dotnet run' from the generated script + [green] --output[/] Output file path for saving the generated script + [green] --save-only[/] Save script to file (via --output) and exit without prompts [green] --connection-string[/] Connection string (for SQLServer/SQLAzure) [green] --database-type[/] Database type (SQLite, LocalDb, SQLServer, SQLAzure, SQLCE) [green]-d, --default[/] Generate a default script with minimal configuration @@ -127,6 +130,13 @@ Generate custom script with packages (latest versions): Interactive mode (no flags): [cyan]psw[/] +[bold yellow]AUTOMATION EXAMPLES:[/] + Auto-run but skip 'dotnet run' (install + build only): + [cyan]psw -d -n MyProject -s MyProject -u --database-type SQLite --admin-email admin@test.com --admin-password MyPass123! --auto-run --no-run[/] + + Save script to file without interactive prompts: + [cyan]psw -d -n MyProject -s MyProject -u --database-type SQLite --admin-email admin@test.com --admin-password MyPass123! --output install.sh --save-only[/] + [bold yellow]TEMPLATE EXAMPLES:[/] Save current configuration as template: [cyan]psw template save my-blog --template-description ""My blog setup"" --template-tags ""blog,umbraco14""[/] diff --git a/src/PackageCliTool/Workflows/CliModeWorkflow.cs b/src/PackageCliTool/Workflows/CliModeWorkflow.cs index 82dd74e..5a4ba9d 100644 --- a/src/PackageCliTool/Workflows/CliModeWorkflow.cs +++ b/src/PackageCliTool/Workflows/CliModeWorkflow.cs @@ -145,6 +145,12 @@ private async Task GenerateDefaultScriptAsync(CommandLineOptions options) HandleStarterKitPackage(options, model); HandleTemplatePackage(options, model); + // Apply --no-run flag + if (options.NoRun) + { + model.SkipDotnetRun = true; + } + var script = await AnsiConsole.Status() .Spinner(Spinner.Known.Star) .SpinnerStyle(Style.Parse("green")) @@ -161,6 +167,13 @@ private async Task GenerateDefaultScriptAsync(CommandLineOptions options) templateName: model.TemplateName, description: $"Default script for {model.ProjectName}"); + // Handle --save-only: write script to file and exit immediately + if (options.SaveOnly) + { + await SaveScriptToFileAsync(script, options); + return; + } + ConsoleDisplay.DisplayGeneratedScript(script, "Generated Default Installation Script"); // Handle auto-run or interactive run @@ -249,6 +262,12 @@ private async Task GenerateCustomScriptFromOptionsAsync(CommandLineOptions optio HandleStarterKitPackage(options, model); HandleTemplatePackage(options, model); + // Apply --no-run flag + if (options.NoRun) + { + model.SkipDotnetRun = true; + } + // Generate the script _logger?.LogInformation("Generating installation script via API"); @@ -268,6 +287,13 @@ private async Task GenerateCustomScriptFromOptionsAsync(CommandLineOptions optio templateName: model.TemplateName, description: $"Custom script for {model.ProjectName ?? "project"}"); + // Handle --save-only: write script to file and exit immediately + if (options.SaveOnly) + { + await SaveScriptToFileAsync(script, options); + return; + } + ConsoleDisplay.DisplayGeneratedScript(script); // Handle auto-run or interactive run @@ -400,6 +426,33 @@ private void HandleTemplatePackage(CommandLineOptions options, ScriptModel model } } + /// + /// Saves the generated script to a file and exits + /// + private async Task SaveScriptToFileAsync(string script, CommandLineOptions options) + { + if (string.IsNullOrWhiteSpace(options.OutputFile)) + { + AnsiConsole.MarkupLine("[red]Error: --save-only requires --output to specify the output file path[/]"); + Environment.ExitCode = 1; + return; + } + + var outputPath = Path.GetFullPath(options.OutputFile); + var outputDir = Path.GetDirectoryName(outputPath); + + if (!string.IsNullOrWhiteSpace(outputDir) && !Directory.Exists(outputDir)) + { + Directory.CreateDirectory(outputDir); + _logger?.LogInformation("Created output directory: {Directory}", outputDir); + } + + await File.WriteAllTextAsync(outputPath, script); + + _logger?.LogInformation("Script saved to: {Path}", outputPath); + AnsiConsole.MarkupLine($"[green]✓ Script saved to:[/] {outputPath}"); + } + /// /// Handles script execution based on options /// @@ -586,6 +639,12 @@ private async Task LoadAndExecuteCommunityTemplateAsync(CommandLineOptions optio // Convert template to ScriptModel var model = ConvertTemplateToScriptModel(template, options); + // Apply --no-run flag + if (options.NoRun) + { + model.SkipDotnetRun = true; + } + // Generate script var script = await AnsiConsole.Status() .Spinner(Spinner.Known.Dots) @@ -603,6 +662,13 @@ private async Task LoadAndExecuteCommunityTemplateAsync(CommandLineOptions optio templateName: template.Metadata.Name, description: $"Community template: {template.Metadata.Description}"); + // Handle --save-only: write script to file and exit immediately + if (options.SaveOnly) + { + await SaveScriptToFileAsync(script, options); + return; + } + // Display script ConsoleDisplay.DisplayGeneratedScript(script, $"Generated Script from '{template.Metadata.Name}'"); diff --git a/src/PackageCliTool/Workflows/TemplateWorkflow.cs b/src/PackageCliTool/Workflows/TemplateWorkflow.cs index 741d021..da0b2ae 100644 --- a/src/PackageCliTool/Workflows/TemplateWorkflow.cs +++ b/src/PackageCliTool/Workflows/TemplateWorkflow.cs @@ -278,6 +278,13 @@ private async Task LoadTemplateAsync(CommandLineOptions options) .Validate(pwd => pwd.Length >= 10 ? ValidationResult.Success() : ValidationResult.Error("Password must be at least 10 characters"))); } + // Apply --no-run flag + if (options.NoRun) + { + scriptModel.SkipDotnetRun = true; + overrides["NoRun"] = true; + } + // Display configuration summary DisplayConfigurationSummary(template, overrides); @@ -300,6 +307,13 @@ private async Task LoadTemplateAsync(CommandLineOptions options) templateName: template.Metadata.Name, description: $"From template: {template.Metadata.Name}"); + // Handle --save-only: write script to file and exit immediately + if (options.SaveOnly) + { + await SaveScriptToFileAsync(script, options); + return; + } + // Display script ConsoleDisplay.DisplayGeneratedScript(script, "Generated Installation Script"); @@ -343,6 +357,33 @@ private async Task LoadTemplateAsync(CommandLineOptions options) } } + /// + /// Saves the generated script to a file and exits + /// + private async Task SaveScriptToFileAsync(string script, CommandLineOptions options) + { + if (string.IsNullOrWhiteSpace(options.OutputFile)) + { + AnsiConsole.MarkupLine("[red]Error: --save-only requires --output to specify the output file path[/]"); + Environment.ExitCode = 1; + return; + } + + var outputPath = Path.GetFullPath(options.OutputFile); + var outputDir = Path.GetDirectoryName(outputPath); + + if (!string.IsNullOrWhiteSpace(outputDir) && !Directory.Exists(outputDir)) + { + Directory.CreateDirectory(outputDir); + _logger?.LogInformation("Created output directory: {Directory}", outputDir); + } + + await File.WriteAllTextAsync(outputPath, script); + + _logger?.LogInformation("Script saved to: {Path}", outputPath); + AnsiConsole.MarkupLine($"[green]✓ Script saved to:[/] {outputPath}"); + } + /// /// Handles script actions after generation /// From bdfa6871a6b56e14abec7d27c8839b4305c1065b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 19:25:11 +0000 Subject: [PATCH 2/3] Update CLI documentation with --no-run, --save-only, and --output flags Add the new automation flags to the Execution section of the command reference and include usage examples in the CLI Mode section. https://claude.ai/code/session_01GjPpGbCY4XLHANx3eztDyz --- .github/cli-documentation.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/cli-documentation.md b/.github/cli-documentation.md index 51ca6b5..ad4357a 100644 --- a/.github/cli-documentation.md +++ b/.github/cli-documentation.md @@ -256,6 +256,21 @@ psw -p "uSync|17.0.0" -n MyProject -s MySolution \ --admin-email admin@test.com \ --admin-password "SecurePass123!" \ --auto-run + +# Auto-run but skip 'dotnet run' (install + build only, no server start) +psw -d -n MyProject -s MyProject \ + -u --database-type SQLite \ + --admin-email admin@test.com \ + --admin-password "SecurePass123!" \ + --auto-run --no-run + +# Save script to file without interactive prompts (for programmatic use) +psw -d -n MyProject -s MyProject \ + -u --database-type SQLite \ + --admin-email admin@test.com \ + --admin-password "SecurePass123!" \ + -p "Umbraco.Forms" \ + --output install.sh --save-only ``` ### Command Reference @@ -310,7 +325,10 @@ psw --default # Generate default script ```bash --auto-run # Automatically run the generated script +--no-run # Skip 'dotnet run' from the generated script --run-dir # Directory to run script in +--output # Output file path for saving the generated script +--save-only # Save script to file (via --output) and exit without prompts ``` #### Template Commands From cc06054a1f1325ccbf153bd8efe5073e1308ad55 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 19:32:38 +0000 Subject: [PATCH 3/3] Add tests for --no-run, --save-only, and --output flags - CommandLineOptionsTests: 10 new tests covering parsing of --no-run, --save-only, --output flags, their HasAnyOptions() behavior, combined usage with other flags, and default values - ScriptModelTests: 3 new tests for SkipDotnetRun JSON serialization, deserialization, and default value; updated existing round-trip and all-properties tests to include SkipDotnetRun - ScriptModelExtensionsTests: New test file with 4 tests verifying ToViewModel() correctly maps SkipDotnetRun from ScriptModel to PackagesViewModel https://claude.ai/code/session_01GjPpGbCY4XLHANx3eztDyz --- .../CommandLineOptionsTests.cs | 162 ++++++++++++++++++ .../ScriptModelExtensionsTests.cs | 115 +++++++++++++ src/PackageCliTool.Tests/ScriptModelTests.cs | 63 ++++++- 3 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 src/PackageCliTool.Tests/ScriptModelExtensionsTests.cs diff --git a/src/PackageCliTool.Tests/CommandLineOptionsTests.cs b/src/PackageCliTool.Tests/CommandLineOptionsTests.cs index 8ed10dd..ad6ca12 100644 --- a/src/PackageCliTool.Tests/CommandLineOptionsTests.cs +++ b/src/PackageCliTool.Tests/CommandLineOptionsTests.cs @@ -686,4 +686,166 @@ public void Parse_WithCommunityTemplateAndUnattendedOverrides_ParsesAll() options.DatabaseType.Should().Be("SqlServer"); options.AdminEmail.Should().Be("custom@example.com"); } + + #region Non-Interactive Mode Flags + + [Fact] + public void Parse_WithNoRunFlag_SetsNoRun() + { + // Arrange + var args = new[] { "--no-run" }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.NoRun.Should().BeTrue(); + } + + [Fact] + public void Parse_WithSaveOnlyFlag_SetsSaveOnly() + { + // Arrange + var args = new[] { "--save-only" }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.SaveOnly.Should().BeTrue(); + } + + [Fact] + public void Parse_WithOutputFlag_SetsOutputFile() + { + // Arrange + var args = new[] { "--output", "install.sh" }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.OutputFile.Should().Be("install.sh"); + } + + [Fact] + public void Parse_WithNoRunAndAutoRun_SetsBothFlags() + { + // Arrange + var args = new[] { "--auto-run", "--no-run" }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.AutoRun.Should().BeTrue(); + options.NoRun.Should().BeTrue(); + } + + [Fact] + public void Parse_WithSaveOnlyAndOutput_SetsBothFlags() + { + // Arrange + var args = new[] { "--output", "install.sh", "--save-only" }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.SaveOnly.Should().BeTrue(); + options.OutputFile.Should().Be("install.sh"); + } + + [Fact] + public void HasAnyOptions_WithNoRunFlag_ReturnsTrue() + { + // Arrange + var args = new[] { "--no-run" }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.HasAnyOptions().Should().BeTrue(); + } + + [Fact] + public void HasAnyOptions_WithSaveOnlyFlag_ReturnsTrue() + { + // Arrange + var args = new[] { "--save-only" }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.HasAnyOptions().Should().BeTrue(); + } + + [Fact] + public void HasAnyOptions_WithOutputFlag_ReturnsTrue() + { + // Arrange + var args = new[] { "--output", "install.sh" }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.HasAnyOptions().Should().BeTrue(); + } + + [Fact] + public void Parse_WithAllAutomationFlags_ParsesCorrectly() + { + // Arrange + var args = new[] + { + "-d", + "-n", "MyProject", + "-s", "MyProject", + "-u", + "--database-type", "SQLite", + "--admin-email", "admin@test.com", + "--admin-password", "SecurePass1234", + "-p", "Umbraco.Forms", + "--output", "install.sh", + "--save-only", + "--no-run" + }; + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.UseDefault.Should().BeTrue(); + options.ProjectName.Should().Be("MyProject"); + options.SolutionName.Should().Be("MyProject"); + options.UseUnattended.Should().BeTrue(); + options.DatabaseType.Should().Be("SQLite"); + options.AdminEmail.Should().Be("admin@test.com"); + options.AdminPassword.Should().Be("SecurePass1234"); + options.Packages.Should().Be("Umbraco.Forms"); + options.OutputFile.Should().Be("install.sh"); + options.SaveOnly.Should().BeTrue(); + options.NoRun.Should().BeTrue(); + options.HasAnyOptions().Should().BeTrue(); + } + + [Fact] + public void Parse_WithNoArguments_NewFlagsDefaultToFalse() + { + // Arrange + var args = Array.Empty(); + + // Act + var options = CommandLineOptions.Parse(args); + + // Assert + options.NoRun.Should().BeFalse(); + options.SaveOnly.Should().BeFalse(); + options.OutputFile.Should().BeNull(); + } + + #endregion } diff --git a/src/PackageCliTool.Tests/ScriptModelExtensionsTests.cs b/src/PackageCliTool.Tests/ScriptModelExtensionsTests.cs new file mode 100644 index 0000000..475477a --- /dev/null +++ b/src/PackageCliTool.Tests/ScriptModelExtensionsTests.cs @@ -0,0 +1,115 @@ +using FluentAssertions; +using PackageCliTool.Extensions; +using PackageCliTool.Models.Api; +using Xunit; + +namespace PackageCliTool.Tests; + +/// +/// Unit tests for ScriptModelExtensions.ToViewModel() +/// +public class ScriptModelExtensionsTests +{ + [Fact] + public void ToViewModel_WithSkipDotnetRunTrue_MapsCorrectly() + { + // Arrange + var scriptModel = new ScriptModel + { + TemplateName = "Umbraco.Templates", + ProjectName = "TestProject", + SkipDotnetRun = true + }; + + // Act + var viewModel = scriptModel.ToViewModel(); + + // Assert + viewModel.SkipDotnetRun.Should().BeTrue(); + } + + [Fact] + public void ToViewModel_WithSkipDotnetRunFalse_MapsCorrectly() + { + // Arrange + var scriptModel = new ScriptModel + { + TemplateName = "Umbraco.Templates", + ProjectName = "TestProject", + SkipDotnetRun = false + }; + + // Act + var viewModel = scriptModel.ToViewModel(); + + // Assert + viewModel.SkipDotnetRun.Should().BeFalse(); + } + + [Fact] + public void ToViewModel_WithDefaultScriptModel_SkipDotnetRunIsFalse() + { + // Arrange + var scriptModel = new ScriptModel + { + TemplateName = "Umbraco.Templates", + ProjectName = "TestProject" + }; + + // Act + var viewModel = scriptModel.ToViewModel(); + + // Assert + viewModel.SkipDotnetRun.Should().BeFalse(); + } + + [Fact] + public void ToViewModel_MapsAllProperties() + { + // Arrange + var scriptModel = new ScriptModel + { + TemplateName = "Umbraco.Templates", + TemplateVersion = "14.0.0", + CreateSolutionFile = true, + SolutionName = "MySolution", + ProjectName = "MyProject", + UseUnattendedInstall = true, + DatabaseType = "SQLite", + ConnectionString = "Data Source=umbraco.db", + UserFriendlyName = "Admin", + UserEmail = "admin@test.com", + UserPassword = "password123", + Packages = "uSync", + IncludeStarterKit = true, + StarterKitPackage = "clean", + IncludeDockerfile = true, + IncludeDockerCompose = true, + EnableContentDeliveryApi = true, + SkipDotnetRun = true + }; + + // Act + var viewModel = scriptModel.ToViewModel(); + + // Assert + viewModel.TemplateName.Should().Be(scriptModel.TemplateName); + viewModel.TemplateVersion.Should().Be(scriptModel.TemplateVersion); + viewModel.CreateSolutionFile.Should().Be(scriptModel.CreateSolutionFile); + viewModel.SolutionName.Should().Be(scriptModel.SolutionName); + viewModel.ProjectName.Should().Be(scriptModel.ProjectName); + viewModel.UseUnattendedInstall.Should().Be(scriptModel.UseUnattendedInstall); + viewModel.DatabaseType.Should().Be(scriptModel.DatabaseType); + viewModel.ConnectionString.Should().Be(scriptModel.ConnectionString); + viewModel.UserFriendlyName.Should().Be(scriptModel.UserFriendlyName); + viewModel.UserEmail.Should().Be(scriptModel.UserEmail); + viewModel.UserPassword.Should().Be(scriptModel.UserPassword); + viewModel.Packages.Should().Be(scriptModel.Packages); + viewModel.IncludeStarterKit.Should().Be(scriptModel.IncludeStarterKit); + viewModel.StarterKitPackage.Should().Be(scriptModel.StarterKitPackage); + viewModel.IncludeDockerfile.Should().Be(scriptModel.IncludeDockerfile); + viewModel.IncludeDockerCompose.Should().Be(scriptModel.IncludeDockerCompose); + viewModel.EnableContentDeliveryApi.Should().Be(scriptModel.EnableContentDeliveryApi); + viewModel.SkipDotnetRun.Should().Be(scriptModel.SkipDotnetRun); + } +} diff --git a/src/PackageCliTool.Tests/ScriptModelTests.cs b/src/PackageCliTool.Tests/ScriptModelTests.cs index 01d019e..f5dc18c 100644 --- a/src/PackageCliTool.Tests/ScriptModelTests.cs +++ b/src/PackageCliTool.Tests/ScriptModelTests.cs @@ -82,7 +82,8 @@ public void ScriptModel_Serialization_IncludesAllProperties() IncludeDockerfile = true, IncludeDockerCompose = true, OnelinerOutput = true, - RemoveComments = true + RemoveComments = true, + SkipDotnetRun = true }; // Act @@ -110,6 +111,7 @@ public void ScriptModel_Serialization_IncludesAllProperties() deserialized.IncludeDockerCompose.Should().Be(model.IncludeDockerCompose); deserialized.OnelinerOutput.Should().Be(model.OnelinerOutput); deserialized.RemoveComments.Should().Be(model.RemoveComments); + deserialized.SkipDotnetRun.Should().Be(model.SkipDotnetRun); } [Fact] @@ -270,7 +272,8 @@ public void ScriptModel_RoundTrip_PreservesAllData() IncludeDockerfile = true, IncludeDockerCompose = true, OnelinerOutput = false, - RemoveComments = true + RemoveComments = true, + SkipDotnetRun = true }; // Act - Serialize and deserialize @@ -330,6 +333,7 @@ public void ScriptModel_DefaultValues_AreCorrect() model.IncludeDockerCompose.Should().BeFalse(); model.OnelinerOutput.Should().BeFalse(); model.RemoveComments.Should().BeFalse(); + model.SkipDotnetRun.Should().BeFalse(); } [Fact] @@ -354,4 +358,59 @@ public void ScriptModel_NullValues_SerializeCorrectly() deserialized.Packages.Should().BeNull(); deserialized.StarterKitPackage.Should().BeNull(); } + + [Fact] + public void ScriptModel_WithSkipDotnetRun_SerializesCorrectly() + { + // Arrange + var model = new ScriptModel + { + TemplateName = "Umbraco.Templates", + ProjectName = "TestProject", + SkipDotnetRun = true + }; + + // Act + var json = JsonSerializer.Serialize(model); + var deserialized = JsonSerializer.Deserialize(json); + + // Assert + json.Should().Contain("\"skipDotnetRun\":true"); + deserialized!.SkipDotnetRun.Should().BeTrue(); + } + + [Fact] + public void ScriptModel_Deserialization_WithSkipDotnetRun_MapsCorrectly() + { + // Arrange + var json = @"{ + ""templateName"": ""Umbraco.Templates"", + ""projectName"": ""TestProject"", + ""skipDotnetRun"": true + }"; + + // Act + var model = JsonSerializer.Deserialize(json); + + // Assert + model.Should().NotBeNull(); + model!.SkipDotnetRun.Should().BeTrue(); + } + + [Fact] + public void ScriptModel_Deserialization_WithoutSkipDotnetRun_DefaultsToFalse() + { + // Arrange - JSON without skipDotnetRun field + var json = @"{ + ""templateName"": ""Umbraco.Templates"", + ""projectName"": ""TestProject"" + }"; + + // Act + var model = JsonSerializer.Deserialize(json); + + // Assert + model.Should().NotBeNull(); + model!.SkipDotnetRun.Should().BeFalse(); + } }