From 7959aeaac38ffd99cc0f179d61917f21116f1a48 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Mon, 25 May 2026 11:59:59 -0700 Subject: [PATCH 1/3] fix: export ConvertTo-JsonManifest as a public function (#453) Move ConvertTo-JsonManifest from Private/ to Public/ so the build includes it in FunctionsToExport. Also correct the README examples which referenced non-existent -InputObject and -OutputPath parameters. Co-Authored-By: Claude Sonnet 4.6 --- Plaster/{Private => Public}/ConvertTo-JsonManifest.ps1 | 0 README.md | 8 ++++---- examples/TemplateModule/README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename Plaster/{Private => Public}/ConvertTo-JsonManifest.ps1 (100%) diff --git a/Plaster/Private/ConvertTo-JsonManifest.ps1 b/Plaster/Public/ConvertTo-JsonManifest.ps1 similarity index 100% rename from Plaster/Private/ConvertTo-JsonManifest.ps1 rename to Plaster/Public/ConvertTo-JsonManifest.ps1 diff --git a/README.md b/README.md index efa665a..90e241f 100644 --- a/README.md +++ b/README.md @@ -152,12 +152,12 @@ Define what the template does: ### Converting XML to JSON ```powershell -# Automatic conversion +# Convert an XML manifest to JSON $xmlManifest = Test-PlasterManifest -Path .\plasterManifest.xml -ConvertTo-JsonManifest -InputObject $xmlManifest -OutputPath .\plasterManifest.json +ConvertTo-JsonManifest -XmlManifest $xmlManifest | Set-Content .\plasterManifest.json -# Or use New-PlasterManifest with conversion -New-PlasterManifest -TemplateName MyTemplate -TemplateType Project -ConvertFromXml +# Or pipe directly +Test-PlasterManifest -Path .\plasterManifest.xml | ConvertTo-JsonManifest | Set-Content .\plasterManifest.json ``` ### Variable Syntax Updates diff --git a/examples/TemplateModule/README.md b/examples/TemplateModule/README.md index c6b11a3..a14f812 100644 --- a/examples/TemplateModule/README.md +++ b/examples/TemplateModule/README.md @@ -107,7 +107,7 @@ Plaster 2.0 includes automatic conversion capabilities: $xmlPath = ".\plasterManifest.xml" $jsonPath = ".\plasterManifest.json" $manifest = Test-PlasterManifest -Path $xmlPath -ConvertTo-JsonManifest -InputObject $manifest -OutputPath $jsonPath +ConvertTo-JsonManifest -XmlManifest $manifest | Set-Content $jsonPath ``` ## Template Discovery From a54ac11b0c3a5d3cf07630d5aeb6bca994bdb61b Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Mon, 25 May 2026 12:02:31 -0700 Subject: [PATCH 2/3] docs: add comment-based help and fill in PlatyPS stub for ConvertTo-JsonManifest Co-Authored-By: Claude Sonnet 4.6 --- Plaster/Public/ConvertTo-JsonManifest.ps1 | 37 ++++++++ docs/en-US/ConvertTo-JsonManifest.md | 108 ++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 docs/en-US/ConvertTo-JsonManifest.md diff --git a/Plaster/Public/ConvertTo-JsonManifest.ps1 b/Plaster/Public/ConvertTo-JsonManifest.ps1 index 8668839..34ca40d 100644 --- a/Plaster/Public/ConvertTo-JsonManifest.ps1 +++ b/Plaster/Public/ConvertTo-JsonManifest.ps1 @@ -1,4 +1,41 @@ function ConvertTo-JsonManifest { + <# + .SYNOPSIS + Converts a Plaster XML manifest to JSON format. + + .DESCRIPTION + Converts an XML-format Plaster manifest (plasterManifest.xml) to the JSON format + (plasterManifest.json) used by Plaster 2.0. Accepts an XmlDocument from + Test-PlasterManifest via the pipeline or the -XmlManifest parameter and returns + the resulting JSON as a string. + + .PARAMETER XmlManifest + The parsed XML manifest to convert. Use Test-PlasterManifest to load and validate + a plasterManifest.xml file before passing it to this function. + + .PARAMETER Compress + Omits white space and indented formatting in the output JSON string. + + .EXAMPLE + $xml = Test-PlasterManifest -Path .\plasterManifest.xml + ConvertTo-JsonManifest -XmlManifest $xml | Set-Content .\plasterManifest.json + + Converts plasterManifest.xml and writes the result to plasterManifest.json. + + .EXAMPLE + Test-PlasterManifest -Path .\plasterManifest.xml | ConvertTo-JsonManifest | Set-Content .\plasterManifest.json + + Pipes the validated manifest directly into ConvertTo-JsonManifest. + + .INPUTS + System.Xml.XmlDocument + + .OUTPUTS + System.String + + .LINK + Test-PlasterManifest + #> [CmdletBinding()] [OutputType([string])] param( diff --git a/docs/en-US/ConvertTo-JsonManifest.md b/docs/en-US/ConvertTo-JsonManifest.md new file mode 100644 index 0000000..b5538dc --- /dev/null +++ b/docs/en-US/ConvertTo-JsonManifest.md @@ -0,0 +1,108 @@ +--- +external help file: Plaster-help.xml +Module Name: Plaster +online version: https://github.com/PowerShellOrg/Plaster/blob/master/docs/en-US/ConvertTo-JsonManifest.md +schema: 2.0.0 +--- + +# ConvertTo-JsonManifest + +## SYNOPSIS +Converts a Plaster XML manifest to JSON format. + +## SYNTAX + +``` +ConvertTo-JsonManifest [-XmlManifest] [-Compress] [-ProgressAction ] + [] +``` + +## DESCRIPTION +Converts an XML-format Plaster manifest (plasterManifest.xml) to the JSON format +(plasterManifest.json) used by Plaster 2.0. Accepts an XmlDocument from +Test-PlasterManifest via the pipeline or the -XmlManifest parameter and returns +the resulting JSON as a string. + +## EXAMPLES + +### Example 1 +```powershell +$xml = Test-PlasterManifest -Path .\plasterManifest.xml +ConvertTo-JsonManifest -XmlManifest $xml | Set-Content .\plasterManifest.json +``` + +Converts plasterManifest.xml and writes the result to plasterManifest.json. + +### Example 2 +```powershell +Test-PlasterManifest -Path .\plasterManifest.xml | ConvertTo-JsonManifest | Set-Content .\plasterManifest.json +``` + +Pipes the validated manifest directly into ConvertTo-JsonManifest. + +## PARAMETERS + +### -Compress +Omits white space and indented formatting in the output JSON string. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -XmlManifest +The parsed XML manifest to convert. Use Test-PlasterManifest to load and validate +a plasterManifest.xml file before passing it to this function. + +```yaml +Type: XmlDocument +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Xml.XmlDocument +The validated XML manifest returned by Test-PlasterManifest. + +## OUTPUTS + +### System.String +A JSON string representation of the Plaster manifest. + +## NOTES + +## RELATED LINKS + +[Test-PlasterManifest](https://github.com/PowerShellOrg/Plaster/blob/master/docs/en-US/Test-PlasterManifest.md) From a20defe7ca0eb63ff65aa63ed9a63ec6e5d9aab9 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Mon, 25 May 2026 12:08:21 -0700 Subject: [PATCH 3/3] test: add Pester tests for ConvertTo-JsonManifest; fix ConvertTo-Json double-binding bug Tests cover output format, metadata mapping, parameters (text, choice, multichoice, condition), and content actions (file, templateFile, message, requireModule). Writing the tests exposed two bugs in the function: - $jsonObject was passed both via pipeline and via -InputObject in the splatted hashtable, causing ParameterBindingException on every call - The -Compress flag logic was inverted (set false when unset, nothing when set) Co-Authored-By: Claude Sonnet 4.6 --- Plaster/Public/ConvertTo-JsonManifest.ps1 | 5 +- tests/ConvertTo-JsonManifest.Tests.ps1 | 228 ++++++++++++++++++++++ 2 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 tests/ConvertTo-JsonManifest.Tests.ps1 diff --git a/Plaster/Public/ConvertTo-JsonManifest.ps1 b/Plaster/Public/ConvertTo-JsonManifest.ps1 index 34ca40d..7aa90b7 100644 --- a/Plaster/Public/ConvertTo-JsonManifest.ps1 +++ b/Plaster/Public/ConvertTo-JsonManifest.ps1 @@ -164,12 +164,11 @@ function ConvertTo-JsonManifest { # Convert to JSON $jsonParams = @{ - InputObject = $jsonObject Depth = 10 } - if (-not $Compress) { - $jsonParams['Compress'] = $false + if ($Compress) { + $jsonParams['Compress'] = $true } $jsonResult = $jsonObject | ConvertTo-Json @jsonParams diff --git a/tests/ConvertTo-JsonManifest.Tests.ps1 b/tests/ConvertTo-JsonManifest.Tests.ps1 new file mode 100644 index 0000000..85d02e2 --- /dev/null +++ b/tests/ConvertTo-JsonManifest.Tests.ps1 @@ -0,0 +1,228 @@ +BeforeDiscovery { + if ($null -eq $env:BHProjectPath) { + $path = Join-Path -Path $PSScriptRoot -ChildPath '..\build.ps1' + . $path -Task Build + } + $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest + $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' + $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName + $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" + Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore + Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop +} + +Describe 'ConvertTo-JsonManifest' { + + BeforeAll { + $script:MinimalXml = @' + + + + TestTemplate + 00000000-0000-0000-0000-000000000001 + 1.0.0 + Test Template + A test template + Tester + + + + + +'@ + function script:New-XmlManifest { + param([string]$Xml = $script:MinimalXml) + $doc = New-Object System.Xml.XmlDocument + $doc.LoadXml($Xml) + $doc + } + } + + Context 'Output format' { + It 'Returns a non-empty string' { + $result = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) + $result | Should -BeOfType [string] + $result | Should -Not -BeNullOrEmpty + } + + It 'Output is valid JSON' { + $result = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) + { $result | ConvertFrom-Json } | Should -Not -Throw + } + + It 'Includes the $schema and schemaVersion fields' { + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) | ConvertFrom-Json + $json.'$schema' | Should -Not -BeNullOrEmpty + $json.schemaVersion | Should -Be '2.0' + } + + It 'Produces indented output by default' { + $result = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) + $result | Should -Match "`n" + } + + It 'Produces compact output with -Compress' { + $result = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) -Compress + $result | Should -Not -Match "`n" + } + + It 'Accepts input from the pipeline' { + $result = New-XmlManifest | ConvertTo-JsonManifest + { $result | ConvertFrom-Json } | Should -Not -Throw + } + } + + Context 'Metadata mapping' { + It 'Maps standard metadata fields' { + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) | ConvertFrom-Json + $json.metadata.name | Should -Be 'TestTemplate' + $json.metadata.version | Should -Be '1.0.0' + $json.metadata.title | Should -Be 'Test Template' + $json.metadata.description | Should -Be 'A test template' + $json.metadata.author | Should -Be 'Tester' + } + + It 'Defaults templateType to Project when not specified' { + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) | ConvertFrom-Json + $json.metadata.templateType | Should -Be 'Project' + } + + It 'Preserves an explicit templateType attribute' { + $xml = $script:MinimalXml -replace '', 'PowerShell, Module, Scaffold' + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $json.metadata.tags | Should -HaveCount 3 + $json.metadata.tags | Should -Contain 'PowerShell' + $json.metadata.tags | Should -Contain 'Module' + $json.metadata.tags | Should -Contain 'Scaffold' + } + } + + Context 'Parameters mapping' { + It 'Omits the parameters key when there are no parameters' { + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) | ConvertFrom-Json + $json.PSObject.Properties.Name | Should -Not -Contain 'parameters' + } + + It 'Maps a simple text parameter' { + $xml = $script:MinimalXml -replace '', @' + + + +'@ + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $json.parameters | Should -HaveCount 1 + $p = $json.parameters[0] + $p.name | Should -Be 'ModuleName' + $p.type | Should -Be 'text' + $p.prompt | Should -Be 'Module name' + $p.default | Should -Be 'MyModule' + $p.store | Should -Be 'text' + } + + It 'Maps a choice parameter with choices' { + $xml = $script:MinimalXml -replace '', @' + + + + + + +'@ + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $p = $json.parameters[0] + $p.name | Should -Be 'License' + $p.type | Should -Be 'choice' + $p.choices | Should -HaveCount 2 + $p.choices[0].label | Should -Be 'MIT' + $p.choices[0].value | Should -Be 'MIT' + $p.choices[0].help | Should -Be 'MIT License' + $p.choices[1].label | Should -Be 'Apache' + } + + It 'Splits multichoice defaults into an array' { + $xml = $script:MinimalXml -replace '', @' + + + + + + +'@ + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $json.parameters[0].default | Should -HaveCount 2 + } + + It 'Includes condition when present' { + $xml = $script:MinimalXml -replace '', @' + + + +'@ + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $json.parameters[0].condition | Should -Not -BeNullOrEmpty + } + } + + Context 'Content mapping' { + It 'Emits an empty content array when there is no content' { + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest) | ConvertFrom-Json + $json.PSObject.Properties.Name | Should -Contain 'content' + $json.content | Should -HaveCount 0 + } + + It 'Maps a file action' { + $xml = $script:MinimalXml -replace '', @' + + + +'@ + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $action = $json.content[0] + $action.type | Should -Be 'file' + $action.source | Should -Be 'src\module.psm1' + $action.destination | Should -Be 'src\${PLASTER_PARAM_ModuleName}.psm1' + } + + It 'Maps a templateFile action' { + $xml = $script:MinimalXml -replace '', @' + + + +'@ + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $json.content[0].type | Should -Be 'templateFile' + } + + It 'Maps a message action' { + $xml = $script:MinimalXml -replace '', @' + + Template scaffolded successfully. + +'@ + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $action = $json.content[0] + $action.type | Should -Be 'message' + $action.text | Should -Be 'Template scaffolded successfully.' + } + + It 'Maps a requireModule action' { + $xml = $script:MinimalXml -replace '', @' + + + +'@ + $json = ConvertTo-JsonManifest -XmlManifest (New-XmlManifest -Xml $xml) | ConvertFrom-Json + $action = $json.content[0] + $action.type | Should -Be 'requireModule' + $action.name | Should -Be 'Pester' + $action.minimumVersion | Should -Be '5.0.0' + } + } +}