From aafa5990a4196d1903a1ba7cd2fc2bce1ecf7ac6 Mon Sep 17 00:00:00 2001 From: James Petty Date: Tue, 7 Apr 2026 17:02:40 -0400 Subject: [PATCH 1/3] lets go --- .claude/settings.local.json | 8 + CHANGELOG.md | 4 +- Plaster/Plaster.psd1 | 11 +- Plaster/Plaster.psm1 | 18 +- .../Get-PlasterManifestPathForCulture.ps1 | 4 +- .../New-TemplateObjectFromManifest.ps1 | 12 +- Plaster/Private/Test-JsonManifest.ps1 | 3 - Plaster/Private/Test-JsonManifestContent.ps1 | 8 +- Plaster/Private/Write-PlasterLog.ps1 | 10 - Plaster/Public/Invoke-Plaster.ps1 | 14 +- Plaster/en-US/Plaster.Resources.psd1 | 2 +- README.md | 10 +- ReleaseNotes.md | 33 +- examples/NewModule/plasterManifest.json | 2 +- tests/JsonTest.Tests.ps1 | 1251 +++++------------ tests/New-PlasterManifest.Tests.ps1 | 107 +- 16 files changed, 567 insertions(+), 930 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..81c60d1 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(ls -1 /c/scripts/Plaster-1/Plaster/Private/*.ps1)", + "Bash(pwsh:*)" + ] + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index b75878a..fdcf6c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [2.0.0] - 2025-06-18 +## [2.0.0] - 2026-04-07 ### Major Release - Plaster 2.0 @@ -40,7 +40,7 @@ maintaining full backward compatibility with existing templates and workflows. #### Build and Development -- **Modern Build System**: InvokeBuild-based build system replacing legacy psake +- **Modern Build System**: PowerShellBuild/psake-based build system with compiled module support - **Pester 5.x Support**: Updated test framework with modern Pester 5.x syntax - **Cross-Platform CI/CD**: GitHub Actions workflow supporting all platforms - **Code Coverage**: Integrated code coverage reporting with configurable diff --git a/Plaster/Plaster.psd1 b/Plaster/Plaster.psd1 index 0f28db9..848179d 100644 --- a/Plaster/Plaster.psd1 +++ b/Plaster/Plaster.psd1 @@ -67,7 +67,7 @@ For the complete changelog, see: https://github.com/PowerShellOrg/Plaster/blob/m CompanyName = 'PowerShell.org' # Copyright statement for this module - Copyright = '(c) PowerShell.org 2016-2025. All rights reserved.' + Copyright = '(c) PowerShell.org 2016-2026. All rights reserved.' # Description of the functionality provided by this module Description = 'Plaster is a template-based file and project generator written in PowerShell. Create consistent PowerShell projects with customizable templates supporting both XML and JSON formats.' @@ -99,10 +99,15 @@ For the complete changelog, see: https://github.com/PowerShellOrg/Plaster/blob/m # Functions to export from this module - explicitly list each function that should be # exported. This improves performance of PowerShell when discovering the commands in # module. - FunctionsToExport = '*' + FunctionsToExport = @( + 'Get-PlasterTemplate', + 'Invoke-Plaster', + 'New-PlasterManifest', + 'Test-PlasterManifest' + ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - CmdletsToExport = '*' + CmdletsToExport = @() # Variables to export from this module VariablesToExport = @() diff --git a/Plaster/Plaster.psm1 b/Plaster/Plaster.psm1 index 2dd3e8c..df075af 100644 --- a/Plaster/Plaster.psm1 +++ b/Plaster/Plaster.psm1 @@ -37,7 +37,7 @@ data LocalizedData { ManifestNotValid_F1=The Plaster manifest '{0}' is not valid. ManifestNotValidVerbose_F1=The Plaster manifest '{0}' is not valid. Specify -Verbose to see the specific schema errors. ManifestNotWellFormedXml_F2=The Plaster manifest '{0}' is not a well-formed XML file. {1} - ManifestWrongFilename_F1=The Plaster manifest filename '{0}' is not valid. The value of the Path argument must refer to a file named 'plasterManifest.xml' or 'plasterManifest_.xml'. Change the Plaster manifest filename and then try again. + ManifestWrongFilename_F1=The Plaster manifest filename '{0}' is not valid. The value of the Path argument must refer to a file named 'plasterManifest.xml', 'plasterManifest.json', or a culture-specific variant (e.g., 'plasterManifest_en-US.xml'). Change the Plaster manifest filename and then try again. MissingParameterPrompt_F1= NewModManifest_CreatingDir_F1=Creating destination directory for module manifest: {0} OpConflict=Conflict @@ -139,6 +139,22 @@ if (-not $script:XmlSchemaValidationSupported) { # Module logging configuration $script:LogLevel = if ($env:PLASTER_LOG_LEVEL) { $env:PLASTER_LOG_LEVEL } else { 'Information' } +# Dot-source functions when running from source (not compiled build) +# The build system (PowerShellBuild) compiles all functions into this PSM1. +# When running from source, we need to dot-source them from Private/ and Public/. +$privatePath = Join-Path $PSScriptRoot 'Private' +$publicPath = Join-Path $PSScriptRoot 'Public' +if (Test-Path $privatePath) { + foreach ($file in (Get-ChildItem -Path $privatePath -Filter '*.ps1' -Recurse)) { + . $file.FullName + } +} +if (Test-Path $publicPath) { + foreach ($file in (Get-ChildItem -Path $publicPath -Filter '*.ps1' -Recurse)) { + . $file.FullName + } +} + # Global variables and constants for Plaster 2.0 # Enhanced $TargetNamespace definition with proper scoping diff --git a/Plaster/Private/Get-PlasterManifestPathForCulture.ps1 b/Plaster/Private/Get-PlasterManifestPathForCulture.ps1 index 693e214..b650c89 100644 --- a/Plaster/Private/Get-PlasterManifestPathForCulture.ps1 +++ b/Plaster/Private/Get-PlasterManifestPathForCulture.ps1 @@ -63,7 +63,7 @@ function Get-PlasterManifestPathForCulture { return $plasterManifestPath } - # If no manifest is found, return $null. - # TODO: Should we throw an error instead? + # If no manifest is found, return $null. Callers (Invoke-Plaster, etc.) + # handle the missing manifest case and may fall back to JSON format. return $null } diff --git a/Plaster/Private/New-TemplateObjectFromManifest.ps1 b/Plaster/Private/New-TemplateObjectFromManifest.ps1 index 5bed6dd..8cb83d8 100644 --- a/Plaster/Private/New-TemplateObjectFromManifest.ps1 +++ b/Plaster/Private/New-TemplateObjectFromManifest.ps1 @@ -35,7 +35,17 @@ function New-TemplateObjectFromManifest { ) try{ - $manifestXml = Test-PlasterManifest -Path $ManifestPath + $manifestResult = Test-PlasterManifest -Path $ManifestPath + # Test-PlasterManifest may return an array if extra output leaks through; + # extract the XmlDocument from the result. + $manifestXml = if ($manifestResult -is [System.Xml.XmlDocument]) { + $manifestResult + } else { + $manifestResult | Where-Object { $_ -is [System.Xml.XmlDocument] } | Select-Object -First 1 + } + if ($null -eq $manifestXml) { + throw "Failed to load manifest from '$ManifestPath'" + } $metadata = $manifestXml["plasterManifest"]["metadata"] $manifestObj = [PSCustomObject]@{ diff --git a/Plaster/Private/Test-JsonManifest.ps1 b/Plaster/Private/Test-JsonManifest.ps1 index d6e1dd9..a920eee 100644 --- a/Plaster/Private/Test-JsonManifest.ps1 +++ b/Plaster/Private/Test-JsonManifest.ps1 @@ -65,14 +65,11 @@ function Test-JsonManifest { throw "Invalid template name: $($metadata.name). Must start with letter and contain only letters, numbers, underscore, or hyphen" } - # Parameters validation # Parameters validation if ($jsonObject.PSObject.Properties['parameters'] -and $jsonObject.parameters -and $jsonObject.parameters.Count -gt 0) { Test-JsonManifestParameters -Parameters $jsonObject.parameters } - # Content validation - # Content validation # Content validation if ($jsonObject.content -and $jsonObject.content.Count -gt 0) { Test-JsonManifestContent -Content $jsonObject.content diff --git a/Plaster/Private/Test-JsonManifestContent.ps1 b/Plaster/Private/Test-JsonManifestContent.ps1 index 9556e7f..04bf5f6 100644 --- a/Plaster/Private/Test-JsonManifestContent.ps1 +++ b/Plaster/Private/Test-JsonManifestContent.ps1 @@ -23,8 +23,12 @@ function Test-JsonManifestContent { } } 'file' { - if (-not $action.source -or -not $action.destination) { - throw "File action missing required 'source' or 'destination' property" + if (-not $action.source) { + throw "File action missing required 'source' property" + } + # destination can be empty string (means use source path) + if (-not $action.PSObject.Properties['destination']) { + throw "File action missing required 'destination' property" } } 'templateFile' { diff --git a/Plaster/Private/Write-PlasterLog.ps1 b/Plaster/Private/Write-PlasterLog.ps1 index 39d337c..39cf1ed 100644 --- a/Plaster/Private/Write-PlasterLog.ps1 +++ b/Plaster/Private/Write-PlasterLog.ps1 @@ -78,14 +78,4 @@ function Write-PlasterLog { Write-Debug $logMessage } } - - # Also write to host for immediate feedback during interactive sessions - if ($Level -in @('Error', 'Warning') -and $Host.Name -ne 'ServerRemoteHost') { - $color = switch ($Level) { - 'Error' { 'Red' } - 'Warning' { 'Yellow' } - default { 'White' } - } - Write-Host $logMessage -ForegroundColor $color - } } diff --git a/Plaster/Public/Invoke-Plaster.ps1 b/Plaster/Public/Invoke-Plaster.ps1 index a78b999..17eafc7 100644 --- a/Plaster/Public/Invoke-Plaster.ps1 +++ b/Plaster/Public/Invoke-Plaster.ps1 @@ -1,17 +1,15 @@ -## TODO: Create tests to ensure check for these. ## DEVELOPERS NOTES & CONVENTIONS ## ## 1. All text displayed to the user except for Write-Debug (or $PSCmdlet.WriteDebug()) text must be added to the ## string tables in: -## en-US\Plaster.psd1 +## en-US\Plaster.Resources.psd1 ## Plaster.psm1 -## 2. If a new manifest element is added, it must be added to the Schema\PlasterManifest-v1.xsd file and then -## processed in the appropriate function in this script. Any changes to attributes must be -## processed not only in the Resolve-ProcessParameter function but also in the dynamicparam function. -## +## 2. If a new manifest element is added, it must be added to the Schema (PlasterManifest-v1.xsd for XML, +## plaster-manifest-v2.json for JSON) and then processed in the appropriate function in this script. +## Any changes to attributes must be processed not only in the Resolve-ProcessParameter +## function but also in the dynamicparam function. ## 3. Non-exported functions should avoid using the PowerShell standard Verb-Noun naming convention. ## They should use PascalCase instead. -## ## 4. Please follow the scripting style of this file when adding new script. function Invoke-Plaster { @@ -174,7 +172,7 @@ function Invoke-Plaster { '@ if (!$NoLogo) { - $versionString = "v$PlasterVersion (JSON Enhanced)" + $versionString = "v$PlasterVersion" Write-Host $plasterLogo -ForegroundColor Blue Write-Host ((" " * (50 - $versionString.Length)) + $versionString) -ForegroundColor Cyan Write-Host ("=" * 50) -ForegroundColor Blue diff --git a/Plaster/en-US/Plaster.Resources.psd1 b/Plaster/en-US/Plaster.Resources.psd1 index b443c27..b35a28b 100644 --- a/Plaster/en-US/Plaster.Resources.psd1 +++ b/Plaster/en-US/Plaster.Resources.psd1 @@ -36,7 +36,7 @@ ManifestErrorReading_F1=Error reading Plaster manifest: {0} ManifestNotValid_F1=The Plaster manifest '{0}' is not valid. ManifestNotValidVerbose_F1=The Plaster manifest '{0}' is not valid. Specify -Verbose to see the specific schema errors. ManifestNotWellFormedXml_F2=The Plaster manifest '{0}' is not a well-formed XML file. {1} -ManifestWrongFilename_F1=The Plaster manifest filename '{0}' is not valid. The value of the Path argument must refer to a file named 'plasterManifest.xml' or 'plasterManifest_.xml'. Change the Plaster manifest filename and then try again. +ManifestWrongFilename_F1=The Plaster manifest filename '{0}' is not valid. The value of the Path argument must refer to a file named 'plasterManifest.xml', 'plasterManifest.json', or a culture-specific variant (e.g., 'plasterManifest_en-US.xml'). Change the Plaster manifest filename and then try again. MissingParameterPrompt_F1= NewModManifest_CreatingDir_F1=Creating destination directory for module manifest: {0} OpConflict=Conflict diff --git a/README.md b/README.md index efa665a..9938064 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/Plaster.svg)](https://www.powershellgallery.com/packages/Plaster) [![PowerShell Gallery Downloads](https://img.shields.io/powershellgallery/dt/Plaster.svg)](https://www.powershellgallery.com/packages/Plaster) -[![Build Status](https://github.com/PowerShell/Plaster/workflows/CI/badge.svg)](https://github.com/PowerShell/Plaster/actions) +[![Build Status](https://github.com/PowerShellOrg/Plaster/actions/workflows/PesterReports.yml/badge.svg)](https://github.com/PowerShellOrg/Plaster/actions) Plaster is a template-based file and project generator written in PowerShell. Its purpose is to streamline the creation of PowerShell module projects, Pester tests, DSC configurations, and more. File generation is performed using crafted templates which allow the user to fill in details and choose from options to get their desired output. @@ -43,7 +43,7 @@ Install-Module -Name Plaster -Scope CurrentUser ### From Source ```powershell -git clone https://github.com/PowerShell/Plaster.git +git clone https://github.com/PowerShellOrg/Plaster.git Import-Module .\Plaster\Plaster\Plaster.psd1 ``` @@ -87,7 +87,7 @@ code plasterManifest.xml ### JSON Manifest Example ```json { - "$schema": "https://raw.githubusercontent.com/PowerShell/Plaster/v2/schema/plaster-manifest-v2.json", + "$schema": "https://raw.githubusercontent.com/PowerShellOrg/Plaster/v2/schema/plaster-manifest-v2.json", "schemaVersion": "2.0", "metadata": { "name": "MyTemplate", @@ -259,7 +259,7 @@ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guid ### Development Setup ```powershell -git clone https://github.com/PowerShell/Plaster.git +git clone https://github.com/PowerShellOrg/Plaster.git cd Plaster Import-Module .\Plaster\Plaster.psd1 Invoke-Pester # Run tests @@ -278,4 +278,4 @@ This project is licensed under the MIT License - see [LICENSE](LICENSE) for deta --- -**Plaster 2.0** - Modern template scaffolding for PowerShell with JSON support, better tooling, and enhanced developer experience. šŸš€ +**Plaster 2.0** - Modern template scaffolding for PowerShell with JSON support, cross-platform compatibility, and enhanced developer experience. diff --git a/ReleaseNotes.md b/ReleaseNotes.md index fefc51c..5ef006b 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,7 +1,32 @@ -## What is New in Plaster 1.0.0 -December 16, 2016 +## What is New in Plaster 2.0.0 +April 2026 -- First official release shipped to the PowerShell Gallery! +### Breaking Changes +- Minimum PowerShell version updated to 5.1 (was 3.0) +- Test framework updated to Pester 5.x +- Default file encoding changed to UTF8-NoBOM + +### New Features +- **JSON Manifest Support**: Create templates using JSON (`plasterManifest.json`) with full JSON Schema validation and VS Code IntelliSense +- **Cross-Platform**: Full support for Windows, Linux, and macOS on PowerShell 7.x +- **Simplified Variables**: JSON manifests use `${ParameterName}` instead of `${PLASTER_PARAM_ParameterName}` +- **Native Arrays**: Multichoice defaults use JSON arrays `[0, 1, 2]` instead of comma-separated strings +- **Format Auto-Detection**: Plaster automatically detects and processes both XML and JSON manifests +- **Enhanced Logging**: Configurable logging via `$env:PLASTER_LOG_LEVEL` + +### Improvements +- Better error messages with actionable guidance +- Improved constrained runspace compatibility with PowerShell 7.x +- Platform-specific parameter store paths (XDG on Linux, standard paths on macOS/Windows) +- Optimized module loading and template processing +- Comprehensive Pester 5.x test suite + +### Bug Fixes +- Fixed .NET Core XML schema validation issues +- Resolved path handling on non-Windows platforms +- Fixed constrained runspace compatibility with PowerShell 7.x +- Corrected parameter default value storage on non-Windows platforms +- Fixed variable substitution edge cases ### Feedback -Please send your feedback to http://github.com/PowerShell/Plaster/issues +Please send your feedback to https://github.com/PowerShellOrg/Plaster/issues diff --git a/examples/NewModule/plasterManifest.json b/examples/NewModule/plasterManifest.json index 849e082..ca2239a 100644 --- a/examples/NewModule/plasterManifest.json +++ b/examples/NewModule/plasterManifest.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/PowerShell/Plaster/v2/schema/plaster-manifest-v2.json", + "$schema": "https://raw.githubusercontent.com/PowerShellOrg/Plaster/v2/schema/plaster-manifest-v2.json", "schemaVersion": "2.0", "metadata": { "id": "dcd95744-8abc-4ecb-a439-bf2cd37821bb", diff --git a/tests/JsonTest.Tests.ps1 b/tests/JsonTest.Tests.ps1 index cfe041f..dcb7c24 100644 --- a/tests/JsonTest.Tests.ps1 +++ b/tests/JsonTest.Tests.ps1 @@ -1,932 +1,411 @@ -# TODO: What is this? GS -# Phase 2 Test: JSON Template Creation and Execution - -Write-Host "=== Plaster 2.0 Phase 2 - JSON Template Test ===" -ForegroundColor Cyan - -# Create test directories -$jsonTemplateDir = Join-Path $env:TEMP "PlasterJsonTemplate" -$jsonOutputDir = Join-Path $env:TEMP "JsonTemplateOutput" - -New-Item -Path $jsonTemplateDir -ItemType Directory -Force | Out-Null -New-Item -Path $jsonOutputDir -ItemType Directory -Force | Out-Null - -Write-Host "Created test directories:" -ForegroundColor Green -Write-Host " Template: $jsonTemplateDir" -Write-Host " Output: $jsonOutputDir" - -# Create comprehensive JSON manifest -$jsonManifest = @{ - '$schema' = 'https://raw.githubusercontent.com/PowerShellOrg/Plaster/v2/schema/plaster-manifest-v2.json' - 'schemaVersion' = '2.0' - 'metadata' = @{ - 'name' = 'ModernPowerShellModule' - 'id' = '12345678-1234-1234-1234-123456789012' - 'version' = '1.0.0' - 'title' = 'Modern PowerShell Module Template (JSON)' - 'description' = 'Creates a modern PowerShell module using JSON template format with enhanced features' - 'author' = 'Plaster 2.0 Team' - 'tags' = @('Module', 'PowerShell', 'JSON', 'Modern', 'Cross-Platform') - 'templateType' = 'Project' +BeforeDiscovery { + if ($null -eq $env:BHProjectPath) { + $path = Join-Path -Path $PSScriptRoot -ChildPath '..\build.ps1' + . $path -Task Build } - 'parameters' = @( - @{ - 'name' = 'ModuleName' - 'type' = 'text' - 'prompt' = 'Enter the module name' - 'validation' = @{ - 'pattern' = '^[A-Za-z][A-Za-z0-9]*$' - 'message' = 'Module name must start with a letter and contain only letters and numbers' - } - }, - @{ - 'name' = 'ModuleAuthor' - 'type' = 'user-fullname' - 'prompt' = 'Enter your full name' - }, - @{ - 'name' = 'ModuleDescription' - 'type' = 'text' - 'prompt' = 'Enter a brief description' - 'validation' = @{ - 'minLength' = 10 - 'maxLength' = 200 - 'message' = 'Description must be between 10 and 200 characters' - } - }, - @{ - 'name' = 'ModuleVersion' - 'type' = 'text' - 'prompt' = 'Enter the initial version' - 'default' = '0.1.0' - 'validation' = @{ - 'pattern' = '^\d+\.\d+\.\d+$' - 'message' = 'Version must be in semantic versioning format (e.g., 1.0.0)' - } - }, - @{ - 'name' = 'IncludeTests' - 'type' = 'choice' - 'prompt' = 'Include Pester tests?' - 'choices' = @( - @{ 'label' = 'Yes'; 'value' = 'Yes'; 'help' = 'Include comprehensive Pester 5.x tests' }, - @{ 'label' = 'No'; 'value' = 'No'; 'help' = 'Skip test creation' } - ) - 'default' = 'Yes' - }, - @{ - 'name' = 'TestFramework' - 'type' = 'choice' - 'prompt' = 'Select test framework version' - 'choices' = @( - @{ 'label' = 'Pester 5.x'; 'value' = 'Pester5'; 'help' = 'Modern Pester framework' }, - @{ 'label' = 'Pester 4.x'; 'value' = 'Pester4'; 'help' = 'Legacy Pester framework' } - ) - 'default' = 'Pester5' - 'condition' = '${IncludeTests} == "Yes"' - 'dependsOn' = @('IncludeTests') - }, - @{ - 'name' = 'Features' - 'type' = 'multichoice' - 'prompt' = 'Select additional features' - 'choices' = @( - @{ 'label' = 'CI/CD'; 'value' = 'CICD'; 'help' = 'GitHub Actions workflow' }, - @{ 'label' = 'Documentation'; 'value' = 'Docs'; 'help' = 'Markdown documentation' }, - @{ 'label' = 'License'; 'value' = 'License'; 'help' = 'MIT license file' }, - @{ 'label' = 'Examples'; 'value' = 'Examples'; 'help' = 'Usage examples' } - ) - 'default' = @('Docs', 'License') - }, - @{ - 'name' = 'PowerShellVersion' - 'type' = 'choice' - 'prompt' = 'Minimum PowerShell version' - 'choices' = @( - @{ 'label' = '5.1'; 'value' = '5.1'; 'help' = 'Windows PowerShell 5.1+' }, - @{ 'label' = '7.0'; 'value' = '7.0'; 'help' = 'PowerShell Core 7.0+' }, - @{ 'label' = '7.2'; 'value' = '7.2'; 'help' = 'PowerShell 7.2+ (recommended)' } - ) - 'default' = '7.2' - } - ) - 'content' = @( - @{ - 'type' = 'message' - 'text' = 'Creating modern PowerShell module: ${ModuleName}' - }, - @{ - 'type' = 'directory' - 'destination' = 'src' - }, - @{ - 'type' = 'directory' - 'destination' = 'tests' - 'condition' = '${IncludeTests} == "Yes"' - }, - @{ - 'type' = 'directory' - 'destination' = 'docs' - 'condition' = '${Features} -contains "Docs"' - }, - @{ - 'type' = 'directory' - 'destination' = 'examples' - 'condition' = '${Features} -contains "Examples"' - }, - @{ - 'type' = 'directory' - 'destination' = '.github/workflows' - 'condition' = '${Features} -contains "CICD"' - }, - @{ - 'type' = 'newModuleManifest' - 'destination' = 'src/${ModuleName}.psd1' - 'moduleVersion' = '${ModuleVersion}' - 'rootModule' = '${ModuleName}.psm1' - 'author' = '${ModuleAuthor}' - 'description' = '${ModuleDescription}' - 'powerShellVersion' = '${PowerShellVersion}' - 'encoding' = 'UTF8-NoBOM' - }, - @{ - 'type' = 'templateFile' - 'source' = 'Module.psm1' - 'destination' = 'src/${ModuleName}.psm1' - 'encoding' = 'UTF8-NoBOM' - }, - @{ - 'type' = 'templateFile' - 'source' = 'README.md' - 'destination' = 'README.md' - 'encoding' = 'UTF8-NoBOM' - }, - @{ - 'type' = 'templateFile' - 'source' = 'CHANGELOG.md' - 'destination' = 'CHANGELOG.md' - 'encoding' = 'UTF8-NoBOM' - }, - @{ - 'type' = 'templateFile' - 'source' = 'Module.Tests.ps1' - 'destination' = 'tests/${ModuleName}.Tests.ps1' - 'condition' = '${IncludeTests} == "Yes"' - 'encoding' = 'UTF8-NoBOM' - }, - @{ - 'type' = 'templateFile' - 'source' = 'ci.yml' - 'destination' = '.github/workflows/ci.yml' - 'condition' = '${Features} -contains "CICD"' - 'encoding' = 'UTF8-NoBOM' - }, - @{ - 'type' = 'file' - 'source' = 'LICENSE' - 'destination' = 'LICENSE' - 'condition' = '${Features} -contains "License"' - }, - @{ - 'type' = 'templateFile' - 'source' = 'Example.ps1' - 'destination' = 'examples/${ModuleName}-Example.ps1' - 'condition' = '${Features} -contains "Examples"' - 'encoding' = 'UTF8-NoBOM' - }, - @{ - 'type' = 'message' - 'text' = 'Module ${ModuleName} created successfully!' - }, - @{ - 'type' = 'message' - 'text' = 'Features included: ${Features}' - }, - @{ - 'type' = 'message' - 'text' = 'Run tests with: Invoke-Pester tests/' - 'condition' = '${IncludeTests} == "Yes"' - } - ) + $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 } -# Convert to JSON and save -$jsonContent = $jsonManifest | ConvertTo-Json -Depth 10 -Set-Content -Path "$jsonTemplateDir/plasterManifest.json" -Value $jsonContent -Encoding UTF8 - -Write-Host "`nCreated JSON manifest:" -ForegroundColor Green -Write-Host " Size: $($jsonContent.Length) characters" -Write-Host " Parameters: $($jsonManifest.parameters.Count)" -Write-Host " Content Actions: $($jsonManifest.content.Count)" - -# Create template files -$moduleTemplate = @' -#Requires -Version ${PowerShellVersion} - -<# -.SYNOPSIS - ${ModuleDescription} - -.DESCRIPTION - ${ModuleName} - A modern PowerShell module created with Plaster 2.0 JSON templates. - - This module demonstrates the enhanced capabilities of Plaster 2.0 including: - - JSON template format support - - Enhanced parameter validation - - Cross-platform compatibility - - Modern PowerShell practices - -.AUTHOR - ${ModuleAuthor} - -.VERSION - ${ModuleVersion} - -.NOTES - Created with Plaster 2.0 JSON template on ${PLASTER_Date} - Template Type: ${PLASTER_ManifestType} - Platform: ${PLASTER_HostName} -#> - -using namespace System.Management.Automation - -# Module initialization -$ErrorActionPreference = 'Stop' -$InformationPreference = 'Continue' - -Write-Information "${ModuleName} v${ModuleVersion} loading..." - -# Import functions -$functionFolders = @('Public', 'Private') -foreach ($folder in $functionFolders) { - $folderPath = Join-Path $PSScriptRoot $folder - if (Test-Path $folderPath) { - $functions = Get-ChildItem -Path $folderPath -Filter '*.ps1' -Recurse - foreach ($function in $functions) { - try { - . $function.FullName - Write-Verbose "Imported function: $($function.BaseName)" - } - catch { - Write-Error "Failed to import function $($function.FullName): $_" - } - } +Describe 'JSON Manifest Support' { + BeforeEach { + $TemplateDir = "TestDrive:\JsonTemplateDir" + New-Item -ItemType Directory $TemplateDir | Out-Null + $OutDir = "TestDrive:\JsonOut" + New-Item -ItemType Directory $OutDir | Out-Null + } + AfterEach { + Remove-Item $OutDir -Recurse -Confirm:$false -ErrorAction SilentlyContinue + Remove-Item $TemplateDir -Recurse -Confirm:$false -ErrorAction SilentlyContinue } -} - -# Export public functions -$publicFunctions = Get-ChildItem -Path (Join-Path $PSScriptRoot 'Public') -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue -if ($publicFunctions) { - Export-ModuleMember -Function $publicFunctions.BaseName -} - -Write-Information "${ModuleName} v${ModuleVersion} loaded successfully" -'@ - -$readmeTemplate = @' -# ${ModuleName} - -${ModuleDescription} - -[![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/${ModuleName})](https://www.powershellgallery.com/packages/${ModuleName}) -[![PowerShell Gallery Downloads](https://img.shields.io/powershellgallery/dt/${ModuleName})](https://www.powershellgallery.com/packages/${ModuleName}) - -## Features - -- Modern PowerShell ${PowerShellVersion}+ module -- Cross-platform compatibility (Windows, Linux, macOS) -<% -if ($PLASTER_PARAM_IncludeTests -eq 'Yes') { - "- Comprehensive Pester $($PLASTER_PARAM_TestFramework) test suite" -} -%> -<% -if ($PLASTER_PARAM_Features -contains 'CICD') { - "- Automated CI/CD with GitHub Actions" -} -%> -<% -if ($PLASTER_PARAM_Features -contains 'Docs') { - "- Complete documentation" -} -%> - -## Installation - -### PowerShell Gallery -```powershell -Install-Module ${ModuleName} -Scope CurrentUser -``` - -### Manual Installation -```powershell -# Clone or download the repository -Import-Module ./src/${ModuleName}.psd1 -``` - -## Quick Start - -```powershell -# Import the module -Import-Module ${ModuleName} - -# Basic usage example -# Add your module's main functions here -``` - -## Documentation - -<% -if ($PLASTER_PARAM_Features -contains 'Docs') { - "- [User Guide](docs/UserGuide.md)" - "- [API Reference](docs/API.md)" - "- [Examples](examples/)" -} -%> - -## Development - -### Prerequisites -- PowerShell ${PowerShellVersion} or higher -<% -if ($PLASTER_PARAM_IncludeTests -eq 'Yes') { - "- Pester module for testing" -} -%> - -### Building -```powershell -# Run tests -<% -if ($PLASTER_PARAM_TestFramework -eq 'Pester5') { - "Invoke-Pester tests/ -Output Detailed" -} else { - "Invoke-Pester tests/" -} -%> - -# Import for development -Import-Module ./src/${ModuleName}.psd1 -Force -``` - -<% -if ($PLASTER_PARAM_Features -contains 'CICD') { - "## CI/CD" - "" - "This project uses GitHub Actions for continuous integration:" - "- Automated testing on Windows, Linux, and macOS" - "- PowerShell $($PLASTER_PARAM_PowerShellVersion)+ compatibility testing" - "- Code quality checks" -} -%> - -## Contributing - -Contributions are welcome! Please: - -1. Fork the repository -2. Create a feature branch -3. Add tests for new functionality -4. Ensure all tests pass -5. Submit a pull request - -## License - -<% -if ($PLASTER_PARAM_Features -contains 'License') { - "This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details." -} else { - "License information not specified." -} -%> - -## Author - -Created by ${ModuleAuthor} - ---- - -*Generated with Plaster 2.0 JSON template on ${PLASTER_Date}* -*Template Format: ${PLASTER_ManifestType} | Platform: ${PLASTER_HostName}* -'@ -$changelogTemplate = @' -# Changelog + Context 'Test-PlasterManifest with JSON' { + It 'Validates a well-formed JSON manifest' { + $jsonManifest = @{ + '$schema' = 'https://raw.githubusercontent.com/PowerShellOrg/Plaster/v2/schema/plaster-manifest-v2.json' + schemaVersion = '2.0' + metadata = @{ + name = 'TestTemplate' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'Test Template' + description = 'A test template' + author = 'Test Author' + templateType = 'Project' + } + parameters = @() + content = @( + @{ + type = 'message' + text = 'Hello from JSON template' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 -All notable changes to ${ModuleName} will be documented in this file. + $result = Test-PlasterManifest -Path $jsonPath + $result | Should -Not -BeNullOrEmpty + $result.plasterManifest | Should -Not -BeNullOrEmpty + } -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). + It 'Rejects JSON manifest with missing required metadata' { + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'TestTemplate' + # Missing: id, version, title, author + } + content = @( + @{ + type = 'message' + text = 'Hello' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 -## [Unreleased] + { Test-PlasterManifest -Path $jsonPath -ErrorAction Stop } | Should -Throw + } -### Added -- Initial project structure + It 'Rejects JSON manifest with invalid schema version' { + $jsonManifest = @{ + schemaVersion = '99.0' + metadata = @{ + name = 'TestTemplate' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'Test Template' + author = 'Test Author' + } + content = @( + @{ + type = 'message' + text = 'Hello' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 -## [${ModuleVersion}] - ${PLASTER_Date} + { Test-PlasterManifest -Path $jsonPath -ErrorAction Stop } | Should -Throw + } -### Added -- Initial release of ${ModuleName} -- Core module functionality -<% -if ($PLASTER_PARAM_IncludeTests -eq 'Yes') { - "- $($PLASTER_PARAM_TestFramework) test suite" -} -%> -<% -if ($PLASTER_PARAM_Features -contains 'CICD') { - "- GitHub Actions CI/CD pipeline" -} -%> -<% -if ($PLASTER_PARAM_Features -contains 'Docs') { - "- Documentation and examples" -} -%> - -### Technical Details -- Minimum PowerShell version: ${PowerShellVersion} -- Cross-platform support: Windows, Linux, macOS -- Created with: Plaster 2.0 (${PLASTER_ManifestType} format) -- Template features: ${Features} - -## Template Information - -This module was generated using: -- **Plaster Version**: ${PLASTER_Version} -- **Template Format**: ${PLASTER_ManifestType} -- **Generation Date**: ${PLASTER_Date} -- **Platform**: ${PLASTER_HostName} -'@ - -$testTemplate = @' -#Requires -Modules Pester -<% -if ($PLASTER_PARAM_TestFramework -eq 'Pester5') { - "#Requires -Version 5.1" -} -%> - -<# -.SYNOPSIS - Pester tests for ${ModuleName} - -.DESCRIPTION - <% - if ($PLASTER_PARAM_TestFramework -eq 'Pester5') { - "Comprehensive Pester 5.x test suite for ${ModuleName}" - } else { - "Pester 4.x test suite for ${ModuleName}" - } - %> - -.NOTES - Created with Plaster 2.0 JSON template - Template Format: ${PLASTER_ManifestType} - Author: ${ModuleAuthor} -#> - -<% -if ($PLASTER_PARAM_TestFramework -eq 'Pester5') { - "BeforeAll {" -} else { - "BeforeDiscovery {" -} -%> - # Module setup - $ModuleName = '${ModuleName}' - $ModuleRoot = Split-Path -Path $PSScriptRoot -Parent - $ModulePath = Join-Path $ModuleRoot "src\$ModuleName.psd1" - - # Import the module for testing - if (Test-Path $ModulePath) { - Import-Module $ModulePath -Force - } else { - throw "Module manifest not found: $ModulePath" - } + It 'Rejects JSON manifest with invalid GUID' { + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'TestTemplate' + id = 'not-a-guid' + version = '1.0.0' + title = 'Test Template' + author = 'Test Author' + } + content = @( + @{ + type = 'message' + text = 'Hello' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 - # Test data - $script:TestData = @{ - ModuleName = $ModuleName - ModuleVersion = '${ModuleVersion}' - Author = '${ModuleAuthor}' - MinimumPSVersion = '${PowerShellVersion}' + { Test-PlasterManifest -Path $jsonPath -ErrorAction Stop } | Should -Throw + } } -} -<% -if ($PLASTER_PARAM_TestFramework -eq 'Pester5') { - "AfterAll {" -} else { - "AfterEach {" -} -%> - # Cleanup - Remove-Module $ModuleName -Force -ErrorAction SilentlyContinue -} + Context 'ConvertFrom-JsonManifest' { + It 'Converts JSON manifest to XML document via Test-PlasterManifest' { + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'MyTemplate' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'My Template' + description = 'A template' + author = 'Author' + templateType = 'Item' + } + parameters = @() + content = @( + @{ + type = 'message' + text = 'Done' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 -Describe '${ModuleName} Module Tests' -Tag 'Unit' { - Context 'Module Structure' { - It 'Should import without errors' { - { Import-Module '${ModuleName}' -Force } | Should -Not -Throw + $result = Test-PlasterManifest -Path $jsonPath + $result | Should -Not -BeNullOrEmpty + $result.plasterManifest.metadata.name | Should -Be 'MyTemplate' + $result.plasterManifest.metadata.id | Should -Be '513d2fdc-3cce-47d9-9531-d85114efb224' } - It 'Should have the correct module name' { - $module = Get-Module '${ModuleName}' - $module.Name | Should -Be '${ModuleName}' + It 'Converts parameters with choices correctly' { + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'ChoiceTemplate' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'Choice Test' + author = 'Author' + } + parameters = @( + @{ + name = 'License' + type = 'choice' + prompt = 'Select a license' + default = '0' + choices = @( + @{ label = '&MIT'; value = 'MIT' }, + @{ label = '&Apache'; value = 'Apache' } + ) + } + ) + content = @( + @{ + type = 'message' + text = 'Done' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 + + $result = Test-PlasterManifest -Path $jsonPath + $paramNode = $result.plasterManifest.parameters.parameter + $paramNode.name | Should -Be 'License' + $paramNode.type | Should -Be 'choice' + $paramNode.ChildNodes.Count | Should -Be 2 + $paramNode.ChildNodes[0].value | Should -Be 'MIT' } - It 'Should have the correct version' { - $module = Get-Module '${ModuleName}' - $module.Version | Should -Be '${ModuleVersion}' - } + It 'Converts multichoice array defaults to comma-separated string' { + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'MultiTemplate' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'Multi Test' + author = 'Author' + } + parameters = @( + @{ + name = 'Features' + type = 'multichoice' + prompt = 'Select features' + default = @(0, 2) + choices = @( + @{ label = '&Tests'; value = 'Tests' }, + @{ label = '&Docs'; value = 'Docs' }, + @{ label = '&CI'; value = 'CI' } + ) + } + ) + content = @( + @{ + type = 'message' + text = 'Done' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 - It 'Should have the correct author' { - $module = Get-Module '${ModuleName}' - $module.Author | Should -Be '${ModuleAuthor}' + $result = Test-PlasterManifest -Path $jsonPath + $paramNode = $result.plasterManifest.parameters.parameter + $paramNode.default | Should -Be '0,2' } - It 'Should require PowerShell ${PowerShellVersion} or higher' { - $module = Get-Module '${ModuleName}' - $module.PowerShellVersion | Should -BeGreaterOrEqual '${PowerShellVersion}' + It 'Converts tags array to comma-separated string' { + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'TagTemplate' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'Tag Test' + author = 'Author' + tags = @('Module', 'PowerShell', 'Template') + } + parameters = @() + content = @( + @{ + type = 'message' + text = 'Done' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 + + $result = Test-PlasterManifest -Path $jsonPath + $result.plasterManifest.metadata.tags | Should -Be 'Module, PowerShell, Template' } } - Context 'Cross-Platform Compatibility' { - It 'Should work on Windows' -Skip:(-not $IsWindows) { - $module = Get-Module '${ModuleName}' - $module | Should -Not -BeNullOrEmpty - } + Context 'Manifest format detection' { + It 'Test-PlasterManifest accepts JSON files' { + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'Test' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'Test' + author = 'Author' + } + content = @( + @{ type = 'message'; text = 'Hi' } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 - It 'Should work on Linux' -Skip:(-not $IsLinux) { - $module = Get-Module '${ModuleName}' - $module | Should -Not -BeNullOrEmpty + $result = Test-PlasterManifest -Path $jsonPath + $result | Should -Not -BeNullOrEmpty } - It 'Should work on macOS' -Skip:(-not $IsMacOS) { - $module = Get-Module '${ModuleName}' - $module | Should -Not -BeNullOrEmpty + It 'Test-PlasterManifest accepts XML files' { + $xmlContent = @" + + + + Test + 513d2fdc-3cce-47d9-9531-d85114efb224 + 1.0.0 + Test + + + + + + + +"@ + $xmlPath = Join-Path $TemplateDir 'plasterManifest.xml' + Set-Content -Path $xmlPath -Value $xmlContent -Encoding UTF8 + + $result = Test-PlasterManifest -Path $xmlPath + $result | Should -Not -BeNullOrEmpty } } - Context 'Module Functions' { - <% - if ($PLASTER_PARAM_TestFramework -eq 'Pester5') { - "BeforeEach {" - } else { - "BeforeAll {" - } - %> - $module = Get-Module '${ModuleName}' - $exportedFunctions = $module.ExportedFunctions.Keys - } + Context 'Invoke-Plaster with JSON manifest' { + It 'Processes a simple JSON template with message action' { + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'SimpleJson' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'Simple JSON Template' + description = 'Test template' + author = 'Test' + templateType = 'Project' + } + parameters = @( + @{ + name = 'ProjectName' + type = 'text' + prompt = 'Project name' + default = 'TestProject' + } + ) + content = @( + @{ + type = 'message' + text = 'Creating project ${PLASTER_PARAM_ProjectName}' + } + ) + } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 - It 'Should export at least one function' { - $exportedFunctions.Count | Should -BeGreaterThan 0 + $DestPath = Join-Path $OutDir 'SimpleJsonProject' + $result = Invoke-Plaster -TemplatePath $TemplateDir -DestinationPath $DestPath -ProjectName 'MyProject' -NoLogo -Force -PassThru + $result.Success | Should -Be $true + $result.ManifestType | Should -Be 'JSON' } - It 'Should have help for all exported functions' { - foreach ($functionName in $exportedFunctions) { - $help = Get-Help $functionName - $help.Synopsis | Should -Not -BeNullOrEmpty - $help.Description | Should -Not -BeNullOrEmpty + It 'Copies files from a JSON template' { + # Create source file in template dir + Set-Content -Path (Join-Path $TemplateDir 'hello.txt') -Value 'Hello World' + + $jsonManifest = @{ + schemaVersion = '2.0' + metadata = @{ + name = 'FileJson' + id = '513d2fdc-3cce-47d9-9531-d85114efb224' + version = '1.0.0' + title = 'File Copy JSON Template' + description = 'Test file copy' + author = 'Test' + templateType = 'Item' + } + parameters = @() + content = @( + @{ + type = 'file' + source = 'hello.txt' + destination = 'hello.txt' + } + ) } + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + $jsonManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath -Encoding UTF8 + + $DestPath = Join-Path $OutDir 'FileCopyTest' + $result = Invoke-Plaster -TemplatePath $TemplateDir -DestinationPath $DestPath -NoLogo -Force -PassThru + $result.Success | Should -Be $true + Test-Path (Join-Path $DestPath 'hello.txt') | Should -Be $true + Get-Content (Join-Path $DestPath 'hello.txt') | Should -Be 'Hello World' } } - Context 'Template Metadata Validation' { - It 'Should contain template generation metadata' { - # This validates that the template system worked correctly - $moduleContent = Get-Content "$ModuleRoot\src\${ModuleName}.psm1" -Raw - $moduleContent | Should -Match '${ModuleAuthor}' - $moduleContent | Should -Match '${ModuleVersion}' - $moduleContent | Should -Match 'Plaster 2.0' + Context 'New-PlasterManifest JSON format' { + It 'Creates a valid JSON manifest file' { + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + New-PlasterManifest -Path $jsonPath -TemplateName 'TestMod' -TemplateType Project -Format JSON ` + -Id '513d2fdc-3cce-47d9-9531-d85114efb224' -Author 'Tester' -Description 'Test desc' + + Test-Path $jsonPath | Should -Be $true + $content = Get-Content $jsonPath -Raw | ConvertFrom-Json + $content.schemaVersion | Should -Be '2.0' + $content.metadata.name | Should -Be 'TestMod' + $content.metadata.templateType | Should -Be 'Project' + $content.metadata.author | Should -Be 'Tester' } - It 'Should have JSON template markers' { - $moduleContent = Get-Content "$ModuleRoot\src\${ModuleName}.psm1" -Raw - $moduleContent | Should -Match 'PLASTER_ManifestType' - } - } -} + It 'Creates a valid XML manifest when Format is XML' { + $xmlPath = Join-Path $TemplateDir 'plasterManifest.xml' + New-PlasterManifest -Path $xmlPath -TemplateName 'TestMod' -TemplateType Item -Format XML ` + -Id '513d2fdc-3cce-47d9-9531-d85114efb224' -<% -if ($PLASTER_PARAM_Features -contains 'Examples') { - "Describe '${ModuleName} Examples' -Tag 'Integration' {" - " Context 'Example Scripts' {" - " It 'Should have example scripts' {" - " Test-Path \"$ModuleRoot\\examples\" | Should -Be $true" - " }" - " " - " It 'Example scripts should be valid PowerShell' {" - " $exampleFiles = Get-ChildItem \"$ModuleRoot\\examples\" -Filter '*.ps1' -ErrorAction SilentlyContinue" - " foreach ($file in $exampleFiles) {" - " { & $file.FullName } | Should -Not -Throw" - " }" - " }" - " }" - "}" -} -%> -'@ - -$ciTemplate = @' -name: CI/CD Pipeline - -on: - push: - branches: [ main, master, develop ] - pull_request: - branches: [ main, master ] - release: - types: [ published ] - -env: - MODULE_NAME: ${ModuleName} - -jobs: - test: - name: Test on ${{ matrix.os }} - PS ${{ matrix.powershell }} - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - powershell: ['${PowerShellVersion}', '7.3', '7.4'] - <% - if ($PLASTER_PARAM_PowerShellVersion -eq '5.1') { - "exclude:" - " # PowerShell 5.1 is Windows only" - " - os: ubuntu-latest" - " powershell: '5.1'" - " - os: macos-latest" - " powershell: '5.1'" + Test-Path $xmlPath | Should -Be $true + $result = Test-PlasterManifest -Path $xmlPath + $result | Should -Not -BeNullOrEmpty } - %> - - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Setup PowerShell - uses: actions/setup-powershell@v1 - with: - powershell-version: ${{ matrix.powershell }} - - - name: Install Pester - shell: pwsh - run: | - <% - if ($PLASTER_PARAM_TestFramework -eq 'Pester5') { - "Install-Module Pester -MinimumVersion 5.0 -Force -Scope CurrentUser" - } else { - "Install-Module Pester -RequiredVersion 4.10.1 -Force -Scope CurrentUser" - } - %> - - - name: Run Module Tests - shell: pwsh - run: | - Import-Module ./src/${ModuleName}.psd1 -Force - <% - if ($PLASTER_PARAM_TestFramework -eq 'Pester5') { - "Invoke-Pester ./tests/ -Output Detailed" - } else { - "Invoke-Pester ./tests/ -PassThru" - } - %> - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-${{ matrix.os }}-ps${{ matrix.powershell }} - path: TestResults*.xml - retention-days: 7 - - quality: - name: Code Quality Analysis - runs-on: windows-latest - - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Setup PowerShell - uses: actions/setup-powershell@v1 - with: - powershell-version: '7.4' - - - name: Install PSScriptAnalyzer - shell: pwsh - run: Install-Module PSScriptAnalyzer -Force -Scope CurrentUser - - - name: Run PSScriptAnalyzer - shell: pwsh - run: | - $results = Invoke-ScriptAnalyzer -Path ./src -Recurse -Settings PSGallery - if ($results) { - $results | Format-Table -AutoSize - Write-Error "PSScriptAnalyzer found $($results.Count) issue(s)" - } - - publish: - name: Publish to PowerShell Gallery - runs-on: windows-latest - needs: [test, quality] - if: github.event_name == 'release' - - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Setup PowerShell - uses: actions/setup-powershell@v1 - with: - powershell-version: '7.4' - - - name: Publish Module - shell: pwsh - env: - PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} - run: | - if (-not $env:PSGALLERY_API_KEY) { - throw "PowerShell Gallery API key not found" - } - Publish-Module -Path ./src -NuGetApiKey $env:PSGALLERY_API_KEY -Verbose -'@ - -$exampleTemplate = @' -<# -.SYNOPSIS - Example script for ${ModuleName} - -.DESCRIPTION - This script demonstrates basic usage of the ${ModuleName} module. - - Created with Plaster 2.0 JSON template. - -.AUTHOR - ${ModuleAuthor} - -.EXAMPLE - .\${ModuleName}-Example.ps1 - - Runs the example demonstrating module functionality. -#> - -#Requires -Version ${PowerShellVersion} -#Requires -Modules ${ModuleName} - -[CmdletBinding()] -param() -# Import the module -Import-Module ${ModuleName} -Force + It 'Defaults to JSON format' { + $jsonPath = Join-Path $TemplateDir 'plasterManifest.json' + New-PlasterManifest -Path $jsonPath -TemplateName 'DefaultFmt' -TemplateType Item ` + -Id '513d2fdc-3cce-47d9-9531-d85114efb224' -Author 'Tester' -Write-Host "=== ${ModuleName} Example ===" -ForegroundColor Cyan -Write-Host "Module Version: $(Get-Module ${ModuleName}).Version" -ForegroundColor Green -Write-Host "Author: ${ModuleAuthor}" -ForegroundColor Green -Write-Host "Created: ${PLASTER_Date}" -ForegroundColor Green -Write-Host "Template: ${PLASTER_ManifestType} format" -ForegroundColor Green - -# Example usage -Write-Host "`nModule Functions:" -ForegroundColor Yellow -$functions = Get-Command -Module ${ModuleName} -if ($functions) { - $functions | ForEach-Object { - Write-Host " - $($_.Name)" -ForegroundColor White - } -} else { - Write-Host " No public functions exported yet. Add your functions to the Public folder." -ForegroundColor Gray -} - -Write-Host "`nExample completed successfully!" -ForegroundColor Green -'@ - -$licenseContent = @' -MIT License - -Copyright (c) ${PLASTER_Year} ${ModuleAuthor} - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'@ - -# Save all template files -Set-Content -Path "$jsonTemplateDir/Module.psm1" -Value $moduleTemplate -Encoding UTF8 -Set-Content -Path "$jsonTemplateDir/README.md" -Value $readmeTemplate -Encoding UTF8 -Set-Content -Path "$jsonTemplateDir/CHANGELOG.md" -Value $changelogTemplate -Encoding UTF8 -Set-Content -Path "$jsonTemplateDir/Module.Tests.ps1" -Value $testTemplate -Encoding UTF8 -Set-Content -Path "$jsonTemplateDir/ci.yml" -Value $ciTemplate -Encoding UTF8 -Set-Content -Path "$jsonTemplateDir/Example.ps1" -Value $exampleTemplate -Encoding UTF8 -Set-Content -Path "$jsonTemplateDir/LICENSE" -Value $licenseContent -Encoding UTF8 - -Write-Host "`nCreated template files:" -ForegroundColor Green -Get-ChildItem $jsonTemplateDir | ForEach-Object { - Write-Host " - $($_.Name)" -ForegroundColor White -} - -# Test 1: Validate JSON manifest -Write-Host "`n=== Step 1: JSON Manifest Validation ===" -ForegroundColor Cyan - -try { - # Note: This requires the JsonManifestHandler.ps1 to be loaded - $manifestContent = Get-Content "$jsonTemplateDir/plasterManifest.json" -Raw - - # For now, let's do basic JSON validation - $jsonObject = $manifestContent | ConvertFrom-Json - Write-Host "āœ… JSON is valid and parseable" -ForegroundColor Green - Write-Host " Schema Version: $($jsonObject.schemaVersion)" -ForegroundColor White - Write-Host " Template Name: $($jsonObject.metadata.name)" -ForegroundColor White - Write-Host " Parameters: $($jsonObject.parameters.Count)" -ForegroundColor White - Write-Host " Content Actions: $($jsonObject.content.Count)" -ForegroundColor White -} catch { - Write-Host "āŒ JSON validation failed: $_" -ForegroundColor Red - return -} - -# Test 2: Execute JSON template (when Phase 2 is complete) -Write-Host "`n=== Step 2: JSON Template Execution ===" -ForegroundColor Cyan -Write-Host "šŸ“ This step will work when Phase 2 JSON support is fully integrated" -ForegroundColor Yellow - -# Simulate the enhanced Invoke-Plaster parameters for JSON -$jsonTestParams = @{ - TemplatePath = $jsonTemplateDir - DestinationPath = $jsonOutputDir - ModuleName = "MyJsonModule" - ModuleAuthor = "JSON Template Tester" - ModuleDescription = "A test module created with Plaster 2.0 JSON template" - ModuleVersion = "1.0.0" - IncludeTests = "Yes" - TestFramework = "Pester5" - Features = @("CICD", "Docs", "License", "Examples") - PowerShellVersion = "7.2" - Force = $true - PassThru = $true -} - -Write-Host "Prepared JSON template parameters:" -ForegroundColor Green -$jsonTestParams.GetEnumerator() | Sort-Object Key | ForEach-Object { - if ($_.Value -is [array]) { - Write-Host " $($_.Key): $($_.Value -join ', ')" -ForegroundColor White - } else { - Write-Host " $($_.Key): $($_.Value)" -ForegroundColor White + $content = Get-Content $jsonPath -Raw | ConvertFrom-Json + $content.schemaVersion | Should -Be '2.0' + } } } - -# Test 3: Compare with XML equivalent -Write-Host "`n=== Step 3: JSON vs XML Comparison ===" -ForegroundColor Cyan - -$xmlEquivalentSize = 2847 # Approximate size of equivalent XML manifest -$jsonSize = $jsonContent.Length - -Write-Host "Template Format Comparison:" -ForegroundColor Yellow -Write-Host " JSON Size: $jsonSize characters" -ForegroundColor White -Write-Host " XML Size: ~$xmlEquivalentSize characters (estimated)" -ForegroundColor White -Write-Host " Reduction: $([math]::Round((1 - $jsonSize/$xmlEquivalentSize) * 100, 1))%" -ForegroundColor Green - -Write-Host "`nJSON Template Advantages Demonstrated:" -ForegroundColor Yellow -Write-Host " āœ… More readable parameter definitions" -ForegroundColor Green -Write-Host " āœ… Built-in validation with patterns and lengths" -ForegroundColor Green -Write-Host " āœ… Parameter dependencies (TestFramework depends on IncludeTests)" -ForegroundColor Green -Write-Host " āœ… Multiple choice parameters with better syntax" -ForegroundColor Green -Write-Host " āœ… Enhanced metadata with structured tags array" -ForegroundColor Green -Write-Host " āœ… Cleaner conditional logic expressions" -ForegroundColor Green - -# Test 4: Feature demonstration -Write-Host "`n=== Step 4: Enhanced Features Demo ===" -ForegroundColor Cyan - -Write-Host "JSON-Specific Features:" -ForegroundColor Yellow -Write-Host " šŸ” Schema Validation: JSON Schema support for IntelliSense" -ForegroundColor White -Write-Host " šŸŽÆ Parameter Validation: Regex patterns, min/max length" -ForegroundColor White -Write-Host " šŸ”— Dependencies: Parameters that depend on other parameters" -ForegroundColor White -Write-Host " šŸ“ Better Syntax: Cleaner, more intuitive structure" -ForegroundColor White -Write-Host " šŸ› ļø Tool Support: Native VS Code support with schema" -ForegroundColor White - -# Cleanup note -Write-Host "`n=== Test Complete ===" -ForegroundColor Cyan -Write-Host "Test files created in:" -ForegroundColor Green -Write-Host " Template: $jsonTemplateDir" -ForegroundColor White -Write-Host " Output: $jsonOutputDir" -ForegroundColor White -Write-Host "`nTo clean up:" -ForegroundColor Yellow -Write-Host " Remove-Item '$jsonTemplateDir' -Recurse -Force" -ForegroundColor Gray -Write-Host " Remove-Item '$jsonOutputDir' -Recurse -Force" -ForegroundColor Gray - -Write-Host "`nšŸŽ‰ Phase 2 JSON Template Test completed successfully!" -ForegroundColor Green -Write-Host " Ready for full JSON integration in Plaster 2.0" -ForegroundColor Green \ No newline at end of file diff --git a/tests/New-PlasterManifest.Tests.ps1 b/tests/New-PlasterManifest.Tests.ps1 index e2b6ebc..a51482b 100644 --- a/tests/New-PlasterManifest.Tests.ps1 +++ b/tests/New-PlasterManifest.Tests.ps1 @@ -27,7 +27,6 @@ BeforeDiscovery { $actualManifest | Should -BeExactly $expectedManifest } } -# TODO: Add JSON tests Describe 'New-PlasterManifest Command Tests' { BeforeEach { $TemplateDir = "TestDrive:\TemplateRootTemp" @@ -214,3 +213,109 @@ Describe 'New-PlasterManifest Command Tests' { } #> } + +Describe 'New-PlasterManifest JSON Format Tests' { + BeforeEach { + $TemplateDir = "TestDrive:\JsonTemplateDir" + New-Item -ItemType Directory $TemplateDir | Out-Null + $PlasterManifestPath = "$TemplateDir\plasterManifest.json" + } + AfterEach { + Remove-Item $TemplateDir -Recurse -Confirm:$false -ErrorAction SilentlyContinue + } + + Context 'Generates a valid JSON manifest' { + It 'Creates JSON with basic parameters' { + $newPlasterManifestSplat = @{ + Path = $PlasterManifestPath + Id = '1a1b0933-78b2-4a3e-bf48-492591e69521' + TemplateName = 'JsonTemplate' + TemplateType = 'Project' + Format = 'JSON' + Author = 'TestAuthor' + Description = 'A JSON test template' + } + New-PlasterManifest @newPlasterManifestSplat + + Test-Path $PlasterManifestPath | Should -Be $true + $content = Get-Content $PlasterManifestPath -Raw | ConvertFrom-Json + $content.schemaVersion | Should -Be '2.0' + $content.metadata.name | Should -Be 'JsonTemplate' + $content.metadata.id | Should -Be '1a1b0933-78b2-4a3e-bf48-492591e69521' + $content.metadata.version | Should -Be '1.0.0' + $content.metadata.templateType | Should -Be 'Project' + $content.metadata.author | Should -Be 'TestAuthor' + $content.metadata.description | Should -Be 'A JSON test template' + } + + It 'Handles tags in JSON format' { + $newPlasterManifestSplat = @{ + Path = $PlasterManifestPath + Id = '1a1b0933-78b2-4a3e-bf48-492591e69521' + TemplateName = 'TagTest' + TemplateType = 'Item' + Format = 'JSON' + Author = 'Test' + Tags = 'Module', 'PowerShell', 'Template' + } + New-PlasterManifest @newPlasterManifestSplat + + $content = Get-Content $PlasterManifestPath -Raw | ConvertFrom-Json + $content.metadata.tags | Should -HaveCount 3 + $content.metadata.tags | Should -Contain 'Module' + $content.metadata.tags | Should -Contain 'PowerShell' + } + + It 'JSON manifest does not require XML entity escaping' { + $newPlasterManifestSplat = @{ + Path = $PlasterManifestPath + Id = '1a1b0933-78b2-4a3e-bf48-492591e69521' + TemplateName = 'EscapeTest' + TemplateType = 'Project' + Format = 'JSON' + Author = 'Test' + Description = 'This is & awesome.' + } + New-PlasterManifest @newPlasterManifestSplat + + $content = Get-Content $PlasterManifestPath -Raw | ConvertFrom-Json + $content.metadata.description | Should -Be 'This is & awesome.' + } + + It 'AddContent parameter works with JSON format' { + Copy-Item $PSScriptRoot\Recurse $TemplateDir -Recurse + + $newPlasterManifestSplat = @{ + Path = $PlasterManifestPath + Id = '1a1b0933-78b2-4a3e-bf48-492591e69521' + TemplateName = 'ContentTest' + TemplateType = 'Project' + Format = 'JSON' + Author = 'Test' + AddContent = $true + } + New-PlasterManifest @newPlasterManifestSplat + + $content = Get-Content $PlasterManifestPath -Raw | ConvertFrom-Json + $content.content.Count | Should -BeGreaterThan 0 + $content.content[0].type | Should -Be 'file' + $content.content[0].source | Should -Not -BeNullOrEmpty + } + + It 'Defaults to JSON format when Format not specified' { + $defaultPath = "$TemplateDir\plasterManifest.json" + $newPlasterManifestSplat = @{ + Path = $defaultPath + Id = '1a1b0933-78b2-4a3e-bf48-492591e69521' + TemplateName = 'DefaultFormat' + TemplateType = 'Item' + Author = 'Test' + } + New-PlasterManifest @newPlasterManifestSplat + + Test-Path $defaultPath | Should -Be $true + $content = Get-Content $defaultPath -Raw | ConvertFrom-Json + $content.schemaVersion | Should -Be '2.0' + } + } +} From e8945f57a84ccf85c48e4482a594aa85d6b2a98f Mon Sep 17 00:00:00 2001 From: James Petty Date: Tue, 7 Apr 2026 19:23:08 -0400 Subject: [PATCH 2/3] yes --- .claude/settings.local.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 81c60d1..8cc0a6a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,11 @@ "permissions": { "allow": [ "Bash(ls -1 /c/scripts/Plaster-1/Plaster/Private/*.ps1)", - "Bash(pwsh:*)" + "Bash(pwsh:*)", + "WebFetch(domain:pester.dev)", + "Bash(xargs head:*)", + "Bash(hugo server:*)", + "Bash(hugo --printPathWarnings)" ] } } From be0bd3b2f8eef31b1c98ed7177098712109bc8d3 Mon Sep 17 00:00:00 2001 From: James Petty Date: Thu, 18 Jun 2026 07:40:22 -0400 Subject: [PATCH 3/3] Add Plaster demos (XML + JSON) and fix three source-run path bugs Adds a demos/ folder with four runnable presentation demos covering both manifest formats with dummy data: 1. XML greeter script (classic ${PLASTER_PARAM_X} syntax) 2. JSON greeter (same template, modern ${X} syntax) 3. JSON module: multichoice, pattern validation, user-fullname, conditional content, newModuleManifest, modify 4. Discovery + authoring: Get-PlasterTemplate / New-PlasterManifest / Test-PlasterManifest, then scaffold from the authored template Fixes three latent bugs surfaced by running templates from source, all from $PSScriptRoot resolving to Public/ when functions are dot-sourced: - Test-PlasterManifest: XSD schema path now falls back one level up - Get-PlasterTemplate: bundled Templates/ path now falls back one level up - ConvertFrom-JsonContentAction: suppress AppendChild pipeline leakage in 'modify' actions (was returning Object[] instead of a single element) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Private/ConvertFrom-JsonContentAction.ps1 | 6 +- Plaster/Public/Get-PlasterTemplate.ps1 | 11 ++- Plaster/Public/Test-PlasterManifest.ps1 | 7 ++ demos/.gitignore | 2 + demos/Demo4-Discovery-Authoring.ps1 | 86 ++++++++++++++++ demos/README.md | 81 +++++++++++++++ demos/Run-Demos.ps1 | 96 ++++++++++++++++++ demos/templates/01-xml-greeter/greeter.ps1 | 10 ++ .../01-xml-greeter/plasterManifest.xml | 26 +++++ demos/templates/02-json-greeter/greeter.ps1 | 10 ++ .../02-json-greeter/plasterManifest.json | 32 ++++++ demos/templates/03-json-module/build.ps1 | 7 ++ demos/templates/03-json-module/gitignore.txt | 5 + demos/templates/03-json-module/module.psm1 | 14 +++ .../03-json-module/plasterManifest.json | 99 +++++++++++++++++++ demos/templates/03-json-module/readme.md | 7 ++ demos/templates/03-json-module/tests.ps1 | 8 ++ 17 files changed, 502 insertions(+), 5 deletions(-) create mode 100644 demos/.gitignore create mode 100644 demos/Demo4-Discovery-Authoring.ps1 create mode 100644 demos/README.md create mode 100644 demos/Run-Demos.ps1 create mode 100644 demos/templates/01-xml-greeter/greeter.ps1 create mode 100644 demos/templates/01-xml-greeter/plasterManifest.xml create mode 100644 demos/templates/02-json-greeter/greeter.ps1 create mode 100644 demos/templates/02-json-greeter/plasterManifest.json create mode 100644 demos/templates/03-json-module/build.ps1 create mode 100644 demos/templates/03-json-module/gitignore.txt create mode 100644 demos/templates/03-json-module/module.psm1 create mode 100644 demos/templates/03-json-module/plasterManifest.json create mode 100644 demos/templates/03-json-module/readme.md create mode 100644 demos/templates/03-json-module/tests.ps1 diff --git a/Plaster/Private/ConvertFrom-JsonContentAction.ps1 b/Plaster/Private/ConvertFrom-JsonContentAction.ps1 index ad9f03a..a11c377 100644 --- a/Plaster/Private/ConvertFrom-JsonContentAction.ps1 +++ b/Plaster/Private/ConvertFrom-JsonContentAction.ps1 @@ -82,18 +82,18 @@ function ConvertFrom-JsonContentAction { if ($modification.isRegex) { $originalElement.SetAttribute('expand', 'true') } - $replaceElement.AppendChild($originalElement) + $null = $replaceElement.AppendChild($originalElement) $substituteElement = $XmlDocument.CreateElement('substitute', $TargetNamespace) $substituteElement.InnerText = $modification.replace $substituteElement.SetAttribute('expand', 'true') - $replaceElement.AppendChild($substituteElement) + $null = $replaceElement.AppendChild($substituteElement) if ($modification.condition) { $replaceElement.SetAttribute('condition', $modification.condition) } - $element.AppendChild($replaceElement) + $null = $element.AppendChild($replaceElement) } } } diff --git a/Plaster/Public/Get-PlasterTemplate.ps1 b/Plaster/Public/Get-PlasterTemplate.ps1 index 00d4466..3691e9c 100644 --- a/Plaster/Public/Get-PlasterTemplate.ps1 +++ b/Plaster/Public/Get-PlasterTemplate.ps1 @@ -80,9 +80,16 @@ function Get-PlasterTemplate { Get-ManifestsUnderPath @getManifestsUnderPathSplat } } else { - # Return all templates included with Plaster + # Return all templates included with Plaster. + # When running from source this function is dot-sourced from Public/, so + # $PSScriptRoot points at Public/ and Templates/ lives one level up. The + # compiled build flattens everything to the module root, where the first path is correct. + $templatesRoot = Join-Path $PSScriptRoot 'Templates' + if (-not (Test-Path -LiteralPath $templatesRoot)) { + $templatesRoot = Join-Path (Split-Path $PSScriptRoot -Parent) 'Templates' + } $getManifestsUnderPathSplat = @{ - RootPath = "$PSScriptRoot\Templates" + RootPath = $templatesRoot Recurse = $true Name = $Name Tag = $Tag diff --git a/Plaster/Public/Test-PlasterManifest.ps1 b/Plaster/Public/Test-PlasterManifest.ps1 index 4b64452..92e8f72 100644 --- a/Plaster/Public/Test-PlasterManifest.ps1 +++ b/Plaster/Public/Test-PlasterManifest.ps1 @@ -16,6 +16,13 @@ function Test-PlasterManifest { begin { $schemaPath = [System.IO.Path]::Combine($PSScriptRoot, "Schema", "PlasterManifest-v1.xsd") + # When running from source, this function is dot-sourced from Public/, so + # $PSScriptRoot points at Public/ and Schema/ lives one level up. The compiled + # build flattens everything to the module root, where the first path is correct. + if (-not (Test-Path -LiteralPath $schemaPath)) { + $schemaPath = [System.IO.Path]::Combine((Split-Path $PSScriptRoot -Parent), "Schema", "PlasterManifest-v1.xsd") + } + # Schema validation is not available on .NET Core - at the moment. if ('System.Xml.Schema.XmlSchemaSet' -as [type]) { $xmlSchemaSet = New-Object System.Xml.Schema.XmlSchemaSet diff --git a/demos/.gitignore b/demos/.gitignore new file mode 100644 index 0000000..37bd086 --- /dev/null +++ b/demos/.gitignore @@ -0,0 +1,2 @@ +# Generated by Run-Demos.ps1 / Demo4-Discovery-Authoring.ps1 +output/ diff --git a/demos/Demo4-Discovery-Authoring.ps1 b/demos/Demo4-Discovery-Authoring.ps1 new file mode 100644 index 0000000..51a1620 --- /dev/null +++ b/demos/Demo4-Discovery-Authoring.ps1 @@ -0,0 +1,86 @@ +<# +.SYNOPSIS + Demo 4 - Template discovery and live manifest authoring. +.DESCRIPTION + Shows the three "meta" cmdlets that surround Invoke-Plaster: + * Get-PlasterTemplate - discover templates (bundled + your own folders) + * New-PlasterManifest - author a brand-new JSON manifest from a folder of files + * Test-PlasterManifest - validate a manifest before you ship it + + Ends by scaffolding from the template it just authored, proving the round-trip. +#> +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +$root = Split-Path $PSScriptRoot -Parent +$templates = Join-Path $PSScriptRoot 'templates' +$outputDir = Join-Path $PSScriptRoot 'output' + +Import-Module (Join-Path $root 'Plaster\Plaster.psd1') -Force + +function Write-Header($Text) { + Write-Host '' + Write-Host ('=' * 70) -ForegroundColor DarkBlue + Write-Host " $Text" -ForegroundColor Yellow + Write-Host ('=' * 70) -ForegroundColor DarkBlue +} + +# ---------------------------------------------------------------------------- +Write-Header 'DEMO 4a - Discover templates with Get-PlasterTemplate' + +Write-Host "`n Templates that ship with Plaster:" -ForegroundColor Green +Get-PlasterTemplate | Format-Table Name, Version, Title -AutoSize + +Write-Host " Your own templates (any folder, -Recurse):" -ForegroundColor Green +Get-PlasterTemplate -Path $templates -Recurse | Format-Table Name, Version, Title, Tags -AutoSize + +# ---------------------------------------------------------------------------- +Write-Header 'DEMO 4b - Author a new manifest with New-PlasterManifest' + +# Start from a folder that already has some files we want to template. +$scratch = Join-Path $outputDir 'authored-template' +if (Test-Path $scratch) { Remove-Item $scratch -Recurse -Force } +New-Item $scratch -ItemType Directory | Out-Null +'Write-Host "Hello from <%= $PLASTER_PARAM_Thing %>"' | Set-Content (Join-Path $scratch 'thing.ps1') +'# Notes about <%= $PLASTER_PARAM_Thing %>' | Set-Content (Join-Path $scratch 'NOTES.md') + +Write-Host "`n Generating plasterManifest.json (-AddContent scans the folder)..." -ForegroundColor Green +New-PlasterManifest -Path (Join-Path $scratch 'plasterManifest.json') ` + -TemplateName 'MyTinyTemplate' -TemplateType Item ` + -Title 'My Tiny Template' -Description 'Authored live on stage' ` + -Author 'Grace Hopper' -Tags Demo, Authoring -AddContent + +Write-Host " --- authored plasterManifest.json ---" -ForegroundColor DarkGray +Get-Content (Join-Path $scratch 'plasterManifest.json') | ForEach-Object { " $_" } + +# ---------------------------------------------------------------------------- +Write-Header 'DEMO 4c - Validate it with Test-PlasterManifest' + +$manifest = Test-PlasterManifest -Path (Join-Path $scratch 'plasterManifest.json') 3>$null +if ($manifest) { + Write-Host "`n Valid. name='$($manifest.plasterManifest.metadata.name)' type='$($manifest.plasterManifest.templateType)'" -ForegroundColor Green +} + +# ---------------------------------------------------------------------------- +Write-Header 'DEMO 4d - Use the template we just authored' + +# Two quick edits to make the round-trip meaningful: +# 1. Add a 'Thing' parameter so there is a $PLASTER_PARAM_Thing to substitute. +# 2. New-PlasterManifest -AddContent emits 'file' actions (verbatim copy). Switch +# them to 'templateFile' so the <%= ... %> placeholders actually expand. +$json = Get-Content (Join-Path $scratch 'plasterManifest.json') -Raw | ConvertFrom-Json +$json.parameters = @( + [pscustomobject]@{ name = 'Thing'; type = 'text'; prompt = 'Name the thing'; default = 'Sproket' } +) +foreach ($action in $json.content) { $action.type = 'templateFile' } +$json | ConvertTo-Json -Depth 10 | Set-Content (Join-Path $scratch 'plasterManifest.json') + +$dst = Join-Path $outputDir '04-authored-output' +if (Test-Path $dst) { Remove-Item $dst -Recurse -Force } +Invoke-Plaster -TemplatePath $scratch -DestinationPath $dst -NoLogo -Thing 'Sproket' + +Write-Host "`n --- thing.ps1 (generated from our authored template) ---" -ForegroundColor DarkGray +Get-Content (Join-Path $dst 'thing.ps1') | ForEach-Object { " $_" } + +Write-Host "`nDemo 4 complete.`n" -ForegroundColor Cyan diff --git a/demos/README.md b/demos/README.md new file mode 100644 index 0000000..4fbecdd --- /dev/null +++ b/demos/README.md @@ -0,0 +1,81 @@ +# Plaster Presentation Demos + +Three ready-to-run demos showing **Plaster** scaffolding with both manifest formats, +using dummy data (Contoso / Fabrikam / Acme / Ada Lovelace). + +| # | Format | Template | Shows off | +|---|--------|----------|-----------| +| 1 | **XML** | `templates/01-xml-greeter` | Classic format, `${PLASTER_PARAM_X}` syntax, `choice` param, `templateFile`, `message` | +| 2 | **JSON** | `templates/02-json-greeter` | Same template, modern format: `${X}` syntax, `&`-labels without XML escaping | +| 3 | **JSON** | `templates/03-json-module` | `multichoice` (native `[0,1]` array default), `pattern` validation, `user-fullname` (git), **conditional** content, `newModuleManifest`, `modify` | +| 4 | both | `Demo4-Discovery-Authoring.ps1` | The surrounding cmdlets: `Get-PlasterTemplate` (discover), `New-PlasterManifest` (author), `Test-PlasterManifest` (validate), then scaffold from the authored template | + +## Run it (non-interactive — safe for a live stage) + +```powershell +# From the repo root +.\demos\Run-Demos.ps1 # runs all four +.\demos\Run-Demos.ps1 -Demo 2 # just one (1, 2, 3, 4) +.\demos\Demo4-Discovery-Authoring.ps1 # the discovery/authoring demo on its own +``` + +Every parameter is passed on the command line, so nothing prompts and the run is +deterministic. Generated projects land in `demos/output/`. + +## Run it interactively (to show the prompts live) + +Plaster turns each template parameter into a real cmdlet parameter *and* prompts for +any you omit. To demo the interactive Q&A, run a template by hand and leave parameters off: + +```powershell +Import-Module .\Plaster\Plaster.psd1 -Force +Invoke-Plaster -TemplatePath .\demos\templates\03-json-module ` + -DestinationPath .\demos\output\live +``` + +You'll get prompts for the module name (with regex validation), author (pre-filled from +`git config user.name`), a single-choice license menu, and a multi-select feature list. +Run it from a real terminal — the VS Code integrated terminal works; piped/non-TTY hosts do not. + +## Talking points + +- **One engine, two formats.** Internally Plaster converts JSON manifests to the same XML + structure the engine has always used, so JSON is purely an authoring convenience with + zero behavioral difference. (See `Plaster/Private/ConvertFrom-JsonManifest.ps1`.) +- **Variable syntax differs by location:** + - *Manifest attributes* (`destination`, message `text`): JSON lets you write the short + `${ModuleName}`; Plaster rewrites it to `${PLASTER_PARAM_ModuleName}` automatically. + - *`condition` expressions* and *template file bodies* (`<%= ... %>`): use the **full** + `$PLASTER_PARAM_ModuleName` form — those paths are not rewritten. +- **Conditions exclude files.** In Demo 3, `Build` is deselected, so `build.ps1` is never + created — show the output tree to make the point. +- **`modify` edits an already-generated file.** Demo 3 generates `README.md` with a + `__LICENSE__` placeholder, then a `modify` action swaps in the chosen license. +- **Safety.** Templates are declarative and expressions run in a *constrained runspace* — + a template can't run arbitrary destructive code. (See `New-ConstrainedRunspace.ps1`.) + +## Side-by-side: same template, two formats + +`01-xml-greeter/plasterManifest.xml` vs `02-json-greeter/plasterManifest.json` produce an +equivalent script. Good slide material for the XML-vs-JSON contrast: + +- XML: `` → JSON: `{ "label": "&Hello" }` (no escaping) +- XML: `default="0"` string → JSON: `"default": 0` (and `[0, 1]` for multichoice) +- XML: `${PLASTER_PARAM_ScriptName}` → JSON: `${ScriptName}` + +## Note on module fixes + +Running the templates from source surfaced three small bugs that these demos depend on; all +are fixed in this branch: + +1. `Test-PlasterManifest` resolved the XML schema (`PlasterManifest-v1.xsd`) relative to + `Public/` when run from source — added a one-level-up fallback. +2. `ConvertFrom-JsonContentAction` leaked `AppendChild` return values into the pipeline for + `modify` actions, returning an array instead of a single element — suppressed with `$null =`. +3. `Get-PlasterTemplate` (no args) looked for the bundled `Templates/` folder relative to + `Public/` when run from source — added the same one-level-up fallback. + +All three share one root cause: when the module runs from source its functions are +dot-sourced from `Public/`/`Private/`, so `$PSScriptRoot` points one level below the module +root. The compiled build in `Output/` flattens everything to the root, so it was already fine — +but it has been rebuilt (`.\build.ps1`) anyway so source and compiled match. diff --git a/demos/Run-Demos.ps1 b/demos/Run-Demos.ps1 new file mode 100644 index 0000000..1e56d0d --- /dev/null +++ b/demos/Run-Demos.ps1 @@ -0,0 +1,96 @@ +<# +.SYNOPSIS + Runs the Plaster presentation demos non-interactively. +.DESCRIPTION + Imports Plaster from source and scaffolds three projects into demos/output: + 1. XML manifest - classic greeter script + 2. JSON manifest - same greeter, modern format + 3. JSON manifest - full-featured module (multichoice, conditions, modify, newModuleManifest) + + Non-interactive: every template parameter is passed on the command line, so + nothing prompts. Great for a reliable live run. To demo the INTERACTIVE + experience instead, run one template by hand, e.g.: + + Invoke-Plaster -TemplatePath .\demos\templates\02-json-greeter ` + -DestinationPath .\demos\output\greeter-interactive +.PARAMETER Demo + Which demo(s) to run: 1, 2, 3, or All (default). +#> +[CmdletBinding()] +param( + [ValidateSet('1', '2', '3', '4', 'All')] + [string]$Demo = 'All' +) + +$ErrorActionPreference = 'Stop' +$root = Split-Path $PSScriptRoot -Parent +$outputDir = Join-Path $PSScriptRoot 'output' +$templates = Join-Path $PSScriptRoot 'templates' + +$moduleToLoad = Join-Path $root 'Plaster\Plaster.psd1' +Write-Host "Loading Plaster from: $moduleToLoad" -ForegroundColor DarkGray +Import-Module $moduleToLoad -Force + +# Fresh output folder each run +if (Test-Path $outputDir) { Remove-Item $outputDir -Recurse -Force } +New-Item $outputDir -ItemType Directory | Out-Null + +function Show-Tree($Path) { + Get-ChildItem $Path -Recurse -File | + ForEach-Object { ' ' + $_.FullName.Substring($Path.Length + 1) } | + Sort-Object +} + +function Write-Header($Text) { + Write-Host '' + Write-Host ('=' * 70) -ForegroundColor DarkBlue + Write-Host " $Text" -ForegroundColor Yellow + Write-Host ('=' * 70) -ForegroundColor DarkBlue +} + +if ($Demo -in '1', 'All') { + Write-Header 'DEMO 1 - XML manifest (classic greeter script)' + $dst = Join-Path $outputDir '01-xml-greeter' + Invoke-Plaster -TemplatePath (Join-Path $templates '01-xml-greeter') ` + -DestinationPath $dst -NoLogo ` + -ScriptName 'Greet-Contoso' -Greeting 'Howdy' + Write-Host "`n Files created:" -ForegroundColor Green + Show-Tree $dst + Write-Host "`n --- Greet-Contoso.ps1 ---" -ForegroundColor DarkGray + Get-Content (Join-Path $dst 'Greet-Contoso.ps1') | ForEach-Object { " $_" } +} + +if ($Demo -in '2', 'All') { + Write-Header 'DEMO 2 - JSON manifest (same greeter, modern format)' + $dst = Join-Path $outputDir '02-json-greeter' + Invoke-Plaster -TemplatePath (Join-Path $templates '02-json-greeter') ` + -DestinationPath $dst -NoLogo ` + -ScriptName 'Greet-Fabrikam' -Greeting 'Salutations' + Write-Host "`n Files created:" -ForegroundColor Green + Show-Tree $dst + Write-Host "`n --- Greet-Fabrikam.ps1 ---" -ForegroundColor DarkGray + Get-Content (Join-Path $dst 'Greet-Fabrikam.ps1') | ForEach-Object { " $_" } +} + +if ($Demo -in '3', 'All') { + Write-Header 'DEMO 3 - JSON manifest (full module: multichoice + conditions + modify)' + $dst = Join-Path $outputDir '03-json-module' + # Features is multichoice -> pass an array. Drop 'Build' to show a condition + # excluding a file (no build.ps1 is generated). + Invoke-Plaster -TemplatePath (Join-Path $templates '03-json-module') ` + -DestinationPath $dst -NoLogo ` + -ModuleName 'AcmeWidget' -Author 'Ada Lovelace' ` + -License 'Apache' -Features 'Pester', 'Git' + Write-Host "`n Files created (note: build.ps1 was skipped by condition):" -ForegroundColor Green + Show-Tree $dst + Write-Host "`n --- AcmeWidget/README.md (note __LICENSE__ was replaced) ---" -ForegroundColor DarkGray + Get-Content (Join-Path $dst 'AcmeWidget\README.md') | ForEach-Object { " $_" } +} + +if ($Demo -in '4', 'All') { + # Demo 4 (discovery + authoring) lives in its own script because it drives the + # surrounding cmdlets rather than scaffolding from a fixed template. + & (Join-Path $PSScriptRoot 'Demo4-Discovery-Authoring.ps1') +} + +Write-Host "`nAll done. Output is in: $outputDir`n" -ForegroundColor Cyan diff --git a/demos/templates/01-xml-greeter/greeter.ps1 b/demos/templates/01-xml-greeter/greeter.ps1 new file mode 100644 index 0000000..38dd8fb --- /dev/null +++ b/demos/templates/01-xml-greeter/greeter.ps1 @@ -0,0 +1,10 @@ +function <%= $PLASTER_PARAM_ScriptName %> { + [CmdletBinding()] + param( + [Parameter(Position = 0)] + [string]$Name = 'World' + ) + "<%= $PLASTER_PARAM_Greeting %>, $Name!" +} + +# Generated by Plaster (XML manifest) on <%= (Get-Date).ToString('yyyy-MM-dd') %> diff --git a/demos/templates/01-xml-greeter/plasterManifest.xml b/demos/templates/01-xml-greeter/plasterManifest.xml new file mode 100644 index 0000000..90fc204 --- /dev/null +++ b/demos/templates/01-xml-greeter/plasterManifest.xml @@ -0,0 +1,26 @@ + + + + XmlGreeter + 9f3b1c20-0001-4a10-9aaa-111111111111 + 1.0.0 + Greeter Script (XML format) + Classic XML manifest. Scaffolds a small greeter script. + Contoso Demo Team + Demo, Script, XML + + + + + + + + + + + + Created '${PLASTER_PARAM_ScriptName}.ps1' that says '${PLASTER_PARAM_Greeting}'. + + diff --git a/demos/templates/02-json-greeter/greeter.ps1 b/demos/templates/02-json-greeter/greeter.ps1 new file mode 100644 index 0000000..2e987da --- /dev/null +++ b/demos/templates/02-json-greeter/greeter.ps1 @@ -0,0 +1,10 @@ +function <%= $PLASTER_PARAM_ScriptName %> { + [CmdletBinding()] + param( + [Parameter(Position = 0)] + [string]$Name = 'World' + ) + "<%= $PLASTER_PARAM_Greeting %>, $Name!" +} + +# Generated by Plaster (JSON manifest) on <%= (Get-Date).ToString('yyyy-MM-dd') %> diff --git a/demos/templates/02-json-greeter/plasterManifest.json b/demos/templates/02-json-greeter/plasterManifest.json new file mode 100644 index 0000000..8cb9d61 --- /dev/null +++ b/demos/templates/02-json-greeter/plasterManifest.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://raw.githubusercontent.com/PowerShellOrg/Plaster/v2/schema/plaster-manifest-v2.json", + "schemaVersion": "2.0", + "metadata": { + "name": "JsonGreeter", + "id": "9f3b1c20-0002-4a10-9aaa-222222222222", + "version": "1.0.0", + "title": "Greeter Script (JSON format)", + "description": "Same template as the XML demo, in the modern JSON format.", + "author": "Contoso Demo Team", + "tags": ["Demo", "Script", "JSON"], + "templateType": "Item" + }, + "parameters": [ + { "name": "ScriptName", "type": "text", "prompt": "Name of the script", "default": "Greet-World" }, + { + "name": "Greeting", + "type": "choice", + "prompt": "Pick a greeting", + "default": 0, + "choices": [ + { "label": "&Hello", "value": "Hello", "help": "Friendly" }, + { "label": "&Howdy", "value": "Howdy", "help": "Casual" }, + { "label": "&Salutations", "value": "Salutations", "help": "Formal" } + ] + } + ], + "content": [ + { "type": "templateFile", "source": "greeter.ps1", "destination": "${ScriptName}.ps1" }, + { "type": "message", "text": "\nCreated '${ScriptName}.ps1' that says '${Greeting}'." } + ] +} diff --git a/demos/templates/03-json-module/build.ps1 b/demos/templates/03-json-module/build.ps1 new file mode 100644 index 0000000..ff1dab2 --- /dev/null +++ b/demos/templates/03-json-module/build.ps1 @@ -0,0 +1,7 @@ +#requires -Modules Pester +# Minimal build script for <%= $PLASTER_PARAM_ModuleName %> +[CmdletBinding()] +param([string]$Task = 'Test') + +Write-Host "Running '$Task' for <%= $PLASTER_PARAM_ModuleName %>..." -ForegroundColor Cyan +Invoke-Pester -Path "$PSScriptRoot/tests" diff --git a/demos/templates/03-json-module/gitignore.txt b/demos/templates/03-json-module/gitignore.txt new file mode 100644 index 0000000..73b7d10 --- /dev/null +++ b/demos/templates/03-json-module/gitignore.txt @@ -0,0 +1,5 @@ +Output/ +*.log +.vs/ +.vscode/ +TestResults/ diff --git a/demos/templates/03-json-module/module.psm1 b/demos/templates/03-json-module/module.psm1 new file mode 100644 index 0000000..58319af --- /dev/null +++ b/demos/templates/03-json-module/module.psm1 @@ -0,0 +1,14 @@ +# <%= $PLASTER_PARAM_ModuleName %>.psm1 +# Author: <%= $PLASTER_PARAM_Author %> + +function Get-<%= $PLASTER_PARAM_ModuleName %>Info { + [CmdletBinding()] + param() + [pscustomobject]@{ + Module = '<%= $PLASTER_PARAM_ModuleName %>' + Author = '<%= $PLASTER_PARAM_Author %>' + License = '<%= $PLASTER_PARAM_License %>' + } +} + +Export-ModuleMember -Function Get-<%= $PLASTER_PARAM_ModuleName %>Info diff --git a/demos/templates/03-json-module/plasterManifest.json b/demos/templates/03-json-module/plasterManifest.json new file mode 100644 index 0000000..d374624 --- /dev/null +++ b/demos/templates/03-json-module/plasterManifest.json @@ -0,0 +1,99 @@ +{ + "$schema": "https://raw.githubusercontent.com/PowerShellOrg/Plaster/v2/schema/plaster-manifest-v2.json", + "schemaVersion": "2.0", + "metadata": { + "name": "AcmeModule", + "id": "9f3b1c20-0003-4a10-9aaa-333333333333", + "version": "1.0.0", + "title": "Acme PowerShell Module (JSON, full-featured)", + "description": "Showcases multichoice, pattern validation, conditional content, newModuleManifest and modify.", + "author": "Contoso Demo Team", + "tags": ["Demo", "Module", "JSON"], + "templateType": "Project" + }, + "parameters": [ + { + "name": "ModuleName", + "type": "text", + "prompt": "Module name (letters, numbers, underscore)", + "default": "AcmeWidget", + "pattern": "^[A-Za-z][A-Za-z0-9_]*$" + }, + { + "name": "Author", + "type": "user-fullname", + "prompt": "Author name" + }, + { + "name": "License", + "type": "choice", + "prompt": "Choose a license", + "default": 0, + "choices": [ + { "label": "&MIT", "value": "MIT" }, + { "label": "&Apache 2.0", "value": "Apache" }, + { "label": "&None", "value": "None" } + ] + }, + { + "name": "Features", + "type": "multichoice", + "prompt": "Select optional features", + "default": [0, 1], + "choices": [ + { "label": "&Pester tests", "value": "Pester", "help": "Add a Pester test scaffold" }, + { "label": "&Build script", "value": "Build", "help": "Add a build.ps1" }, + { "label": "&Git ignore", "value": "Git", "help": "Add a .gitignore" } + ] + } + ], + "content": [ + { + "type": "newModuleManifest", + "destination": "${ModuleName}/${ModuleName}.psd1", + "moduleVersion": "0.1.0", + "rootModule": "${ModuleName}.psm1", + "author": "${Author}", + "description": "The ${ModuleName} module." + }, + { + "type": "templateFile", + "source": "module.psm1", + "destination": "${ModuleName}/${ModuleName}.psm1" + }, + { + "type": "templateFile", + "source": "readme.md", + "destination": "${ModuleName}/README.md" + }, + { + "type": "templateFile", + "source": "tests.ps1", + "destination": "${ModuleName}/tests/${ModuleName}.Tests.ps1", + "condition": "$PLASTER_PARAM_Features -contains 'Pester'" + }, + { + "type": "templateFile", + "source": "build.ps1", + "destination": "${ModuleName}/build.ps1", + "condition": "$PLASTER_PARAM_Features -contains 'Build'" + }, + { + "type": "file", + "source": "gitignore.txt", + "destination": "${ModuleName}/.gitignore", + "condition": "$PLASTER_PARAM_Features -contains 'Git'" + }, + { + "type": "modify", + "path": "${ModuleName}/README.md", + "modifications": [ + { "type": "replace", "search": "__LICENSE__", "replace": "${License}" } + ] + }, + { + "type": "message", + "text": "\nModule '${ModuleName}' scaffolded with license '${License}'." + } + ] +} diff --git a/demos/templates/03-json-module/readme.md b/demos/templates/03-json-module/readme.md new file mode 100644 index 0000000..0b0b35b --- /dev/null +++ b/demos/templates/03-json-module/readme.md @@ -0,0 +1,7 @@ +# <%= $PLASTER_PARAM_ModuleName %> + +The **<%= $PLASTER_PARAM_ModuleName %>** module by <%= $PLASTER_PARAM_Author %>. + +## License + +This project is licensed under the __LICENSE__ license. diff --git a/demos/templates/03-json-module/tests.ps1 b/demos/templates/03-json-module/tests.ps1 new file mode 100644 index 0000000..35b0ccc --- /dev/null +++ b/demos/templates/03-json-module/tests.ps1 @@ -0,0 +1,8 @@ +Describe '<%= $PLASTER_PARAM_ModuleName %>' { + BeforeAll { + Import-Module "$PSScriptRoot/../<%= $PLASTER_PARAM_ModuleName %>.psd1" -Force + } + It 'reports its own name' { + (Get-<%= $PLASTER_PARAM_ModuleName %>Info).Module | Should -Be '<%= $PLASTER_PARAM_ModuleName %>' + } +}