FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add Plaster demos (XML + JSON) and fix three source-run path bugs · PowerShellOrg/Plaster@be0bd3b · GitHub

Commit be0bd3b

Browse files
andcommitted
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) <noreply@anthropic.com>
1 parent e8945f5 commit be0bd3b

17 files changed

Lines changed: 502 additions & 5 deletions

‎Plaster/Private/ConvertFrom-JsonContentAction.ps1‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,18 @@ function ConvertFrom-JsonContentAction {
8282
if ($modification.isRegex) {
8383
$originalElement.SetAttribute('expand', 'true')
8484
}
85-
$replaceElement.AppendChild($originalElement)
85+
$null = $replaceElement.AppendChild($originalElement)
8686

8787
$substituteElement = $XmlDocument.CreateElement('substitute', $TargetNamespace)
8888
$substituteElement.InnerText = $modification.replace
8989
$substituteElement.SetAttribute('expand', 'true')
90-
$replaceElement.AppendChild($substituteElement)
90+
$null = $replaceElement.AppendChild($substituteElement)
9191

9292
if ($modification.condition) {
9393
$replaceElement.SetAttribute('condition', $modification.condition)
9494
}
9595

96-
$element.AppendChild($replaceElement)
96+
$null = $element.AppendChild($replaceElement)
9797
}
9898
}
9999
}

‎Plaster/Public/Get-PlasterTemplate.ps1‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,16 @@ function Get-PlasterTemplate {
8080
Get-ManifestsUnderPath @getManifestsUnderPathSplat
8181
}
8282
} else {
83-
# Return all templates included with Plaster
83+
# Return all templates included with Plaster.
84+
# When running from source this function is dot-sourced from Public/, so
85+
# $PSScriptRoot points at Public/ and Templates/ lives one level up. The
86+
# compiled build flattens everything to the module root, where the first path is correct.
87+
$templatesRoot = Join-Path $PSScriptRoot 'Templates'
88+
if (-not (Test-Path -LiteralPath $templatesRoot)) {
89+
$templatesRoot = Join-Path (Split-Path $PSScriptRoot -Parent) 'Templates'
90+
}
8491
$getManifestsUnderPathSplat = @{
85-
RootPath = "$PSScriptRoot\Templates"
92+
RootPath = $templatesRoot
8693
Recurse = $true
8794
Name = $Name
8895
Tag = $Tag

‎Plaster/Public/Test-PlasterManifest.ps1‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ function Test-PlasterManifest {
1616
begin {
1717
$schemaPath = [System.IO.Path]::Combine($PSScriptRoot, "Schema", "PlasterManifest-v1.xsd")
1818

19+
# When running from source, this function is dot-sourced from Public/, so
20+
# $PSScriptRoot points at Public/ and Schema/ lives one level up. The compiled
21+
# build flattens everything to the module root, where the first path is correct.
22+
if (-not (Test-Path -LiteralPath $schemaPath)) {
23+
$schemaPath = [System.IO.Path]::Combine((Split-Path $PSScriptRoot -Parent), "Schema", "PlasterManifest-v1.xsd")
24+
}
25+
1926
# Schema validation is not available on .NET Core - at the moment.
2027
if ('System.Xml.Schema.XmlSchemaSet' -as [type]) {
2128
$xmlSchemaSet = New-Object System.Xml.Schema.XmlSchemaSet

‎demos/.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Generated by Run-Demos.ps1 / Demo4-Discovery-Authoring.ps1
2+
output/
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<#
2+
.SYNOPSIS
3+
Demo 4 - Template discovery and live manifest authoring.
4+
.DESCRIPTION
5+
Shows the three "meta" cmdlets that surround Invoke-Plaster:
6+
* Get-PlasterTemplate - discover templates (bundled + your own folders)
7+
* New-PlasterManifest - author a brand-new JSON manifest from a folder of files
8+
* Test-PlasterManifest - validate a manifest before you ship it
9+
10+
Ends by scaffolding from the template it just authored, proving the round-trip.
11+
#>
12+
[CmdletBinding()]
13+
param()
14+
15+
$ErrorActionPreference = 'Stop'
16+
$root = Split-Path $PSScriptRoot -Parent
17+
$templates = Join-Path $PSScriptRoot 'templates'
18+
$outputDir = Join-Path $PSScriptRoot 'output'
19+
20+
Import-Module (Join-Path $root 'Plaster\Plaster.psd1') -Force
21+
22+
function Write-Header($Text) {
23+
Write-Host ''
24+
Write-Host ('=' * 70) -ForegroundColor DarkBlue
25+
Write-Host " $Text" -ForegroundColor Yellow
26+
Write-Host ('=' * 70) -ForegroundColor DarkBlue
27+
}
28+
29+
# ----------------------------------------------------------------------------
30+
Write-Header 'DEMO 4a - Discover templates with Get-PlasterTemplate'
31+
32+
Write-Host "`n Templates that ship with Plaster:" -ForegroundColor Green
33+
Get-PlasterTemplate | Format-Table Name, Version, Title -AutoSize
34+
35+
Write-Host " Your own templates (any folder, -Recurse):" -ForegroundColor Green
36+
Get-PlasterTemplate -Path $templates -Recurse | Format-Table Name, Version, Title, Tags -AutoSize
37+
38+
# ----------------------------------------------------------------------------
39+
Write-Header 'DEMO 4b - Author a new manifest with New-PlasterManifest'
40+
41+
# Start from a folder that already has some files we want to template.
42+
$scratch = Join-Path $outputDir 'authored-template'
43+
if (Test-Path $scratch) { Remove-Item $scratch -Recurse -Force }
44+
New-Item $scratch -ItemType Directory | Out-Null
45+
'Write-Host "Hello from <%= $PLASTER_PARAM_Thing %>"' | Set-Content (Join-Path $scratch 'thing.ps1')
46+
'# Notes about <%= $PLASTER_PARAM_Thing %>' | Set-Content (Join-Path $scratch 'NOTES.md')
47+
48+
Write-Host "`n Generating plasterManifest.json (-AddContent scans the folder)..." -ForegroundColor Green
49+
New-PlasterManifest -Path (Join-Path $scratch 'plasterManifest.json') `
50+
-TemplateName 'MyTinyTemplate' -TemplateType Item `
51+
-Title 'My Tiny Template' -Description 'Authored live on stage' `
52+
-Author 'Grace Hopper' -Tags Demo, Authoring -AddContent
53+
54+
Write-Host " --- authored plasterManifest.json ---" -ForegroundColor DarkGray
55+
Get-Content (Join-Path $scratch 'plasterManifest.json') | ForEach-Object { " $_" }
56+
57+
# ----------------------------------------------------------------------------
58+
Write-Header 'DEMO 4c - Validate it with Test-PlasterManifest'
59+
60+
$manifest = Test-PlasterManifest -Path (Join-Path $scratch 'plasterManifest.json') 3>$null
61+
if ($manifest) {
62+
Write-Host "`n Valid. name='$($manifest.plasterManifest.metadata.name)' type='$($manifest.plasterManifest.templateType)'" -ForegroundColor Green
63+
}
64+
65+
# ----------------------------------------------------------------------------
66+
Write-Header 'DEMO 4d - Use the template we just authored'
67+
68+
# Two quick edits to make the round-trip meaningful:
69+
# 1. Add a 'Thing' parameter so there is a $PLASTER_PARAM_Thing to substitute.
70+
# 2. New-PlasterManifest -AddContent emits 'file' actions (verbatim copy). Switch
71+
# them to 'templateFile' so the <%= ... %> placeholders actually expand.
72+
$json = Get-Content (Join-Path $scratch 'plasterManifest.json') -Raw | ConvertFrom-Json
73+
$json.parameters = @(
74+
[pscustomobject]@{ name = 'Thing'; type = 'text'; prompt = 'Name the thing'; default = 'Sproket' }
75+
)
76+
foreach ($action in $json.content) { $action.type = 'templateFile' }
77+
$json | ConvertTo-Json -Depth 10 | Set-Content (Join-Path $scratch 'plasterManifest.json')
78+
79+
$dst = Join-Path $outputDir '04-authored-output'
80+
if (Test-Path $dst) { Remove-Item $dst -Recurse -Force }
81+
Invoke-Plaster -TemplatePath $scratch -DestinationPath $dst -NoLogo -Thing 'Sproket'
82+
83+
Write-Host "`n --- thing.ps1 (generated from our authored template) ---" -ForegroundColor DarkGray
84+
Get-Content (Join-Path $dst 'thing.ps1') | ForEach-Object { " $_" }
85+
86+
Write-Host "`nDemo 4 complete.`n" -ForegroundColor Cyan

‎demos/README.md‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Plaster Presentation Demos
2+
3+
Three ready-to-run demos showing **Plaster** scaffolding with both manifest formats,
4+
using dummy data (Contoso / Fabrikam / Acme / Ada Lovelace).
5+
6+
| # | Format | Template | Shows off |
7+
|---|--------|----------|-----------|
8+
| 1 | **XML** | `templates/01-xml-greeter` | Classic format, `${PLASTER_PARAM_X}` syntax, `choice` param, `templateFile`, `message` |
9+
| 2 | **JSON** | `templates/02-json-greeter` | Same template, modern format: `${X}` syntax, `&`-labels without XML escaping |
10+
| 3 | **JSON** | `templates/03-json-module` | `multichoice` (native `[0,1]` array default), `pattern` validation, `user-fullname` (git), **conditional** content, `newModuleManifest`, `modify` |
11+
| 4 | both | `Demo4-Discovery-Authoring.ps1` | The surrounding cmdlets: `Get-PlasterTemplate` (discover), `New-PlasterManifest` (author), `Test-PlasterManifest` (validate), then scaffold from the authored template |
12+
13+
## Run it (non-interactive — safe for a live stage)
14+
15+
```powershell
16+
# From the repo root
17+
.\demos\Run-Demos.ps1 # runs all four
18+
.\demos\Run-Demos.ps1 -Demo 2 # just one (1, 2, 3, 4)
19+
.\demos\Demo4-Discovery-Authoring.ps1 # the discovery/authoring demo on its own
20+
```
21+
22+
Every parameter is passed on the command line, so nothing prompts and the run is
23+
deterministic. Generated projects land in `demos/output/`.
24+
25+
## Run it interactively (to show the prompts live)
26+
27+
Plaster turns each template parameter into a real cmdlet parameter *and* prompts for
28+
any you omit. To demo the interactive Q&A, run a template by hand and leave parameters off:
29+
30+
```powershell
31+
Import-Module .\Plaster\Plaster.psd1 -Force
32+
Invoke-Plaster -TemplatePath .\demos\templates\03-json-module `
33+
-DestinationPath .\demos\output\live
34+
```
35+
36+
You'll get prompts for the module name (with regex validation), author (pre-filled from
37+
`git config user.name`), a single-choice license menu, and a multi-select feature list.
38+
Run it from a real terminal — the VS Code integrated terminal works; piped/non-TTY hosts do not.
39+
40+
## Talking points
41+
42+
- **One engine, two formats.** Internally Plaster converts JSON manifests to the same XML
43+
structure the engine has always used, so JSON is purely an authoring convenience with
44+
zero behavioral difference. (See `Plaster/Private/ConvertFrom-JsonManifest.ps1`.)
45+
- **Variable syntax differs by location:**
46+
- *Manifest attributes* (`destination`, message `text`): JSON lets you write the short
47+
`${ModuleName}`; Plaster rewrites it to `${PLASTER_PARAM_ModuleName}` automatically.
48+
- *`condition` expressions* and *template file bodies* (`<%= ... %>`): use the **full**
49+
`$PLASTER_PARAM_ModuleName` form — those paths are not rewritten.
50+
- **Conditions exclude files.** In Demo 3, `Build` is deselected, so `build.ps1` is never
51+
created — show the output tree to make the point.
52+
- **`modify` edits an already-generated file.** Demo 3 generates `README.md` with a
53+
`__LICENSE__` placeholder, then a `modify` action swaps in the chosen license.
54+
- **Safety.** Templates are declarative and expressions run in a *constrained runspace*
55+
a template can't run arbitrary destructive code. (See `New-ConstrainedRunspace.ps1`.)
56+
57+
## Side-by-side: same template, two formats
58+
59+
`01-xml-greeter/plasterManifest.xml` vs `02-json-greeter/plasterManifest.json` produce an
60+
equivalent script. Good slide material for the XML-vs-JSON contrast:
61+
62+
- XML: `<choice label="&amp;Hello" .../>` → JSON: `{ "label": "&Hello" }` (no escaping)
63+
- XML: `default="0"` string → JSON: `"default": 0` (and `[0, 1]` for multichoice)
64+
- XML: `${PLASTER_PARAM_ScriptName}` → JSON: `${ScriptName}`
65+
66+
## Note on module fixes
67+
68+
Running the templates from source surfaced three small bugs that these demos depend on; all
69+
are fixed in this branch:
70+
71+
1. `Test-PlasterManifest` resolved the XML schema (`PlasterManifest-v1.xsd`) relative to
72+
`Public/` when run from source — added a one-level-up fallback.
73+
2. `ConvertFrom-JsonContentAction` leaked `AppendChild` return values into the pipeline for
74+
`modify` actions, returning an array instead of a single element — suppressed with `$null =`.
75+
3. `Get-PlasterTemplate` (no args) looked for the bundled `Templates/` folder relative to
76+
`Public/` when run from source — added the same one-level-up fallback.
77+
78+
All three share one root cause: when the module runs from source its functions are
79+
dot-sourced from `Public/`/`Private/`, so `$PSScriptRoot` points one level below the module
80+
root. The compiled build in `Output/` flattens everything to the root, so it was already fine —
81+
but it has been rebuilt (`.\build.ps1`) anyway so source and compiled match.

‎demos/Run-Demos.ps1‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<#
2+
.SYNOPSIS
3+
Runs the Plaster presentation demos non-interactively.
4+
.DESCRIPTION
5+
Imports Plaster from source and scaffolds three projects into demos/output:
6+
1. XML manifest - classic greeter script
7+
2. JSON manifest - same greeter, modern format
8+
3. JSON manifest - full-featured module (multichoice, conditions, modify, newModuleManifest)
9+
10+
Non-interactive: every template parameter is passed on the command line, so
11+
nothing prompts. Great for a reliable live run. To demo the INTERACTIVE
12+
experience instead, run one template by hand, e.g.:
13+
14+
Invoke-Plaster -TemplatePath .\demos\templates\02-json-greeter `
15+
-DestinationPath .\demos\output\greeter-interactive
16+
.PARAMETER Demo
17+
Which demo(s) to run: 1, 2, 3, or All (default).
18+
#>
19+
[CmdletBinding()]
20+
param(
21+
[ValidateSet('1', '2', '3', '4', 'All')]
22+
[string]$Demo = 'All'
23+
)
24+
25+
$ErrorActionPreference = 'Stop'
26+
$root = Split-Path $PSScriptRoot -Parent
27+
$outputDir = Join-Path $PSScriptRoot 'output'
28+
$templates = Join-Path $PSScriptRoot 'templates'
29+
30+
$moduleToLoad = Join-Path $root 'Plaster\Plaster.psd1'
31+
Write-Host "Loading Plaster from: $moduleToLoad" -ForegroundColor DarkGray
32+
Import-Module $moduleToLoad -Force
33+
34+
# Fresh output folder each run
35+
if (Test-Path $outputDir) { Remove-Item $outputDir -Recurse -Force }
36+
New-Item $outputDir -ItemType Directory | Out-Null
37+
38+
function Show-Tree($Path) {
39+
Get-ChildItem $Path -Recurse -File |
40+
ForEach-Object { ' ' + $_.FullName.Substring($Path.Length + 1) } |
41+
Sort-Object
42+
}
43+
44+
function Write-Header($Text) {
45+
Write-Host ''
46+
Write-Host ('=' * 70) -ForegroundColor DarkBlue
47+
Write-Host " $Text" -ForegroundColor Yellow
48+
Write-Host ('=' * 70) -ForegroundColor DarkBlue
49+
}
50+
51+
if ($Demo -in '1', 'All') {
52+
Write-Header 'DEMO 1 - XML manifest (classic greeter script)'
53+
$dst = Join-Path $outputDir '01-xml-greeter'
54+
Invoke-Plaster -TemplatePath (Join-Path $templates '01-xml-greeter') `
55+
-DestinationPath $dst -NoLogo `
56+
-ScriptName 'Greet-Contoso' -Greeting 'Howdy'
57+
Write-Host "`n Files created:" -ForegroundColor Green
58+
Show-Tree $dst
59+
Write-Host "`n --- Greet-Contoso.ps1 ---" -ForegroundColor DarkGray
60+
Get-Content (Join-Path $dst 'Greet-Contoso.ps1') | ForEach-Object { " $_" }
61+
}
62+
63+
if ($Demo -in '2', 'All') {
64+
Write-Header 'DEMO 2 - JSON manifest (same greeter, modern format)'
65+
$dst = Join-Path $outputDir '02-json-greeter'
66+
Invoke-Plaster -TemplatePath (Join-Path $templates '02-json-greeter') `
67+
-DestinationPath $dst -NoLogo `
68+
-ScriptName 'Greet-Fabrikam' -Greeting 'Salutations'
69+
Write-Host "`n Files created:" -ForegroundColor Green
70+
Show-Tree $dst
71+
Write-Host "`n --- Greet-Fabrikam.ps1 ---" -ForegroundColor DarkGray
72+
Get-Content (Join-Path $dst 'Greet-Fabrikam.ps1') | ForEach-Object { " $_" }
73+
}
74+
75+
if ($Demo -in '3', 'All') {
76+
Write-Header 'DEMO 3 - JSON manifest (full module: multichoice + conditions + modify)'
77+
$dst = Join-Path $outputDir '03-json-module'
78+
# Features is multichoice -> pass an array. Drop 'Build' to show a condition
79+
# excluding a file (no build.ps1 is generated).
80+
Invoke-Plaster -TemplatePath (Join-Path $templates '03-json-module') `
81+
-DestinationPath $dst -NoLogo `
82+
-ModuleName 'AcmeWidget' -Author 'Ada Lovelace' `
83+
-License 'Apache' -Features 'Pester', 'Git'
84+
Write-Host "`n Files created (note: build.ps1 was skipped by condition):" -ForegroundColor Green
85+
Show-Tree $dst
86+
Write-Host "`n --- AcmeWidget/README.md (note __LICENSE__ was replaced) ---" -ForegroundColor DarkGray
87+
Get-Content (Join-Path $dst 'AcmeWidget\README.md') | ForEach-Object { " $_" }
88+
}
89+
90+
if ($Demo -in '4', 'All') {
91+
# Demo 4 (discovery + authoring) lives in its own script because it drives the
92+
# surrounding cmdlets rather than scaffolding from a fixed template.
93+
& (Join-Path $PSScriptRoot 'Demo4-Discovery-Authoring.ps1')
94+
}
95+
96+
Write-Host "`nAll done. Output is in: $outputDir`n" -ForegroundColor Cyan
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function <%= $PLASTER_PARAM_ScriptName %> {
2+
[CmdletBinding()]
3+
param(
4+
[Parameter(Position = 0)]
5+
[string]$Name = 'World'
6+
)
7+
"<%= $PLASTER_PARAM_Greeting %>, $Name!"
8+
}
9+
10+
# Generated by Plaster (XML manifest) on <%= (Get-Date).ToString('yyyy-MM-dd') %>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<plasterManifest xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1"
3+
schemaVersion="1.1"
4+
templateType="Item">
5+
<metadata>
6+
<name>XmlGreeter</name>
7+
<id>9f3b1c20-0001-4a10-9aaa-111111111111</id>
8+
<version>1.0.0</version>
9+
<title>Greeter Script (XML format)</title>
10+
<description>Classic XML manifest. Scaffolds a small greeter script.</description>
11+
<author>Contoso Demo Team</author>
12+
<tags>Demo, Script, XML</tags>
13+
</metadata>
14+
<parameters>
15+
<parameter name="ScriptName" type="text" prompt="Name of the script" default="Greet-World" />
16+
<parameter name="Greeting" type="choice" prompt="Pick a greeting" default="0">
17+
<choice label="&amp;Hello" value="Hello" help="Friendly" />
18+
<choice label="&amp;Howdy" value="Howdy" help="Casual" />
19+
<choice label="&amp;Salutations" value="Salutations" help="Formal" />
20+
</parameter>
21+
</parameters>
22+
<content>
23+
<templateFile source="greeter.ps1" destination="${PLASTER_PARAM_ScriptName}.ps1" />
24+
<message>&#10;Created '${PLASTER_PARAM_ScriptName}.ps1' that says '${PLASTER_PARAM_Greeting}'.</message>
25+
</content>
26+
</plasterManifest>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function <%= $PLASTER_PARAM_ScriptName %> {
2+
[CmdletBinding()]
3+
param(
4+
[Parameter(Position = 0)]
5+
[string]$Name = 'World'
6+
)
7+
"<%= $PLASTER_PARAM_Greeting %>, $Name!"
8+
}
9+
10+
# Generated by Plaster (JSON manifest) on <%= (Get-Date).ToString('yyyy-MM-dd') %>

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL