Skip to content

Commit 9beeb87

Browse files
HeyItsGilbertclaude
andcommitted
[1.0.0-alpha1] Upgrade to psake 5.0.0 with task caching and LLM output
Breaking changes: - Minimum PowerShell raised from 3.0 to 5.1 - psake dependency raised from 4.9.0 to 5.0.0 - Invoke-psake now returns PsakeBuildResult (replaces $psake.build_success) New features: - Content-addressed task caching via Inputs/Outputs on cacheable tasks (StageFiles, Analyze, Pester, GenerateMarkdown, GenerateMAML, GenerateUpdatableHelp) - LLM-optimized test output mode ($PSBPreference.Test.OutputMode = 'LLM') emits structured JSON with only failure details - External PesterConfiguration file support via $PSBPreference.Test.PesterConfigurationPath - Direct PesterConfiguration object passthrough via -Configuration parameter - Format-PSBuildResult function for Human/JSON/GitHubActions build result formatting - All psakeFile.ps1 tasks rewritten to declarative hashtable syntax - Invoke-Build IB.tasks.ps1 updated with matching Inputs/Outputs caching - Windows PowerShell 5.1 CI matrix entry added Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5e25263 commit 9beeb87

20 files changed

+1111
-227
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
os: [ubuntu-latest, windows-latest, macOS-latest]
15+
shell: [pwsh]
16+
include:
17+
- os: windows-latest
18+
shell: powershell
19+
name: 'Windows PowerShell 5.1'
1520
steps:
1621
- uses: actions/checkout@v4
1722
- name: Test
18-
shell: pwsh
23+
shell: ${{ matrix.shell }}
1924
env:
2025
DEBUG: ${{ runner.debug == '1' }}
2126
run: |

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## Unreleased
99

10+
## [1.0.0-alpha1] 2026-03-22
11+
12+
### Breaking Changes
13+
14+
- **Minimum PowerShell version raised from 3.0 to 5.1.** PowerShellBuild 1.0.0+ requires
15+
PowerShell 5.1 or later.
16+
- **psake 5.0.0 required.** The consumer-facing `psakeFile.ps1` now uses psake 5.0.0
17+
declarative task syntax with `Version 5` pinning.
18+
- **`Invoke-psake` now returns `PsakeBuildResult`.** Build scripts that check
19+
`$psake.build_success` should migrate to inspecting the returned object's `.Success` property.
20+
21+
### Added
22+
23+
- **Task caching** — Cacheable tasks (`StageFiles`, `Analyze`, `Pester`, `GenerateMarkdown`,
24+
`GenerateMAML`, `GenerateUpdatableHelp`) now declare `Inputs`/`Outputs` for psake 5.0.0's
25+
content-addressed caching. Unchanged tasks are automatically skipped on incremental builds.
26+
Disable with `$PSBPreference.Build.EnableTaskCaching = $false`.
27+
- **LLM-optimized test output** — New `$PSBPreference.Test.OutputMode` setting with values
28+
`'Detailed'` (default, verbose human output), `'Minimal'` (failures only, compact), and
29+
`'LLM'` (structured JSON with failure details, optimized for machine consumption).
30+
- **External PesterConfiguration support** — New `$PSBPreference.Test.PesterConfigurationPath`
31+
setting to load a `.psd1` file as the base PesterConfiguration. Explicit `$PSBPreference.Test`
32+
values overlay on top.
33+
- **`-Configuration` parameter on `Test-PSBuildPester`** — Pass a `[PesterConfiguration]` object
34+
directly for full control over Pester execution.
35+
- **`-OutputMode` parameter on `Test-PSBuildPester`** — Control output format per-invocation.
36+
- **`-PesterConfigurationPath` parameter on `Test-PSBuildPester`** — Load external config files.
37+
- **`Format-PSBuildResult`** — New public function to format psake 5.0.0's `PsakeBuildResult`
38+
for Human, JSON, or GitHubActions output.
39+
- **Declarative task syntax** — All tasks in `psakeFile.ps1` rewritten to use psake 5.0.0's
40+
hashtable-based declarative syntax.
41+
- **Invoke-Build parity**`IB.tasks.ps1` updated with matching `Inputs`/`Outputs` caching
42+
and new Pester parameter passthrough.
43+
44+
### Changed
45+
46+
- `$PSBPreference` now includes `Build.EnableTaskCaching`, `Test.OutputMode`, and
47+
`Test.PesterConfigurationPath` keys.
48+
- `Test-PSBuildPester` now always returns the Pester test result object for programmatic access.
49+
- Synchronized inline `LocalizedData` in `PowerShellBuild.psm1` with all strings from
50+
`en-US/Messages.psd1` (was missing signing-related strings from 0.8.0).
51+
1052
## [0.8.0] 2026-02-20
1153

1254
### Added

CLAUDE.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
**PowerShellBuild** is a PowerShell module that provides a standardized set of build, test, and publish tasks for PowerShell module projects. It supports two popular PowerShell task-runner frameworks:
66

7-
- **psake** (4.9.0+) — task-based build system
8-
- **Invoke-Build** (5.8.1+) — alternative task runner
7+
- **psake** (5.0.0+) — task-based build system with declarative syntax and content-addressed caching
8+
- **Invoke-Build** (5.8.1+) — alternative task runner with native Inputs/Outputs caching
99

10-
The module version is **0.7.3** and targets PowerShell 3.0+. It is cross-platform and tested on Windows, Linux, and macOS.
10+
The module version is **1.0.0-alpha1** and targets PowerShell 5.1+. It is cross-platform and tested on Windows (including Windows PowerShell 5.1), Linux, and macOS.
1111

1212
---
1313

@@ -26,8 +26,8 @@ PowerShellBuild/
2626
├── Build/
2727
│ └── Convert-PSAke.ps1 # Utility: converts psake tasks to Invoke-Build
2828
├── PowerShellBuild/ # THE MODULE SOURCE (System Under Test)
29-
│ ├── Public/ # Exported (public) functions — 9 functions
30-
│ ├── Private/ # Internal functions — 1 function
29+
│ ├── Public/ # Exported (public) functions — 13 functions
30+
│ ├── Private/ # Internal functions — 2 functions
3131
│ ├── en-US/
3232
│ │ └── Messages.psd1 # Localized string resources
3333
│ ├── PowerShellBuild.psd1 # Module manifest (version, deps, exports)
@@ -67,8 +67,8 @@ The hashtable is organized into sections:
6767
| Section | Purpose |
6868
|---------|---------|
6969
| `General` | ProjectRoot, SrcRootDir, ModuleName, ModuleVersion, ModuleManifestPath |
70-
| `Build` | OutDir, ModuleOutDir, CompileModule, CompileDirectories, CopyDirectories, Exclude |
71-
| `Test` | Enabled, RootDir, OutputFile, OutputFormat, ScriptAnalysis, CodeCoverage, ImportModule, SkipRemainingOnFailure, OutputVerbosity |
70+
| `Build` | OutDir, ModuleOutDir, CompileModule, CompileDirectories, CopyDirectories, Exclude, EnableTaskCaching |
71+
| `Test` | Enabled, RootDir, OutputFile, OutputFormat, ScriptAnalysis, CodeCoverage, ImportModule, SkipRemainingOnFailure, OutputVerbosity, OutputMode, PesterConfigurationPath |
7272
| `Help` | UpdatableHelpOutDir, DefaultLocale, ConvertReadMeToAboutHelp |
7373
| `Docs` | RootDir, Overwrite, AlphabeticParamsOrder, ExcludeDontShow, UseFullTypeName |
7474
| `Publish` | PSRepository, PSRepositoryApiKey, PSRepositoryCredential |
@@ -124,8 +124,11 @@ All functions reside in `PowerShellBuild/Public/`.
124124
| `Test-PSBuildPester` | Runs Pester tests with configurable output and coverage |
125125
| `Test-PSBuildScriptAnalysis` | Runs PSScriptAnalyzer with configurable severity threshold |
126126
| `Publish-PSBuildModule` | Publishes the built module to a PowerShell repository |
127+
| `Format-PSBuildResult` | Formats a PsakeBuildResult for Human, JSON, or GitHubActions output |
127128

128-
Private helper: `Remove-ExcludedItem` — filters file system items by regex patterns during builds.
129+
Private helpers:
130+
- `Remove-ExcludedItem` — filters file system items by regex patterns during builds
131+
- `ConvertTo-PSBuildLLMOutput` — converts Pester results to structured JSON for LLM consumption
129132

130133
### Invoke-Build Alias
131134

@@ -207,7 +210,7 @@ Defined in `requirements.psd1`, installed via **PSDepend**:
207210
|--------|---------|
208211
| BuildHelpers | 2.0.16 |
209212
| Pester | ≥ 5.6.1 |
210-
| psake | 4.9.0 |
213+
| psake | 5.0.0 |
211214
| PSScriptAnalyzer | 1.24.0 |
212215
| InvokeBuild | 5.8.1 |
213216
| platyPS | 0.14.2 |
@@ -386,7 +389,7 @@ After a successful build, output is in `Output/PowerShellBuild/<version>/`:
386389
```
387390
Output/
388391
└── PowerShellBuild/
389-
└── 0.7.3/
392+
└── 1.0.0/
390393
├── Public/ # (when CompileModule = $false)
391394
├── Private/
392395
├── en-US/

PowerShellBuild/IB.tasks.ps1

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ Task Clean Init, {
1313
}
1414

1515
# Synopsis: Builds module based on source directory
16-
Task StageFiles Clean, {
16+
Task StageFiles -Inputs {
17+
Get-ChildItem -Path $PSBPreference.General.SrcRootDir -Recurse -File |
18+
Where-Object { $_.Extension -in '.ps1', '.psm1', '.psd1', '.ps1xml', '.txt' }
19+
} -Outputs {
20+
if (Test-Path $PSBPreference.Build.ModuleOutDir) {
21+
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File
22+
}
23+
} Clean, {
1724
$buildParams = @{
1825
Path = $PSBPreference.General.SrcRootDir
1926
ModuleName = $PSBPreference.General.ModuleName
@@ -59,13 +66,19 @@ $analyzePreReqs = {
5966
}
6067

6168
# Synopsis: Execute PSScriptAnalyzer tests
62-
Task Analyze -If (. $analyzePreReqs) Build, {
69+
Task Analyze -If (. $analyzePreReqs) -Inputs {
70+
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -Include '*.ps1', '*.psm1', '*.psd1'
71+
} -Outputs {
72+
Join-Path $PSBPreference.Build.OutDir '.analyze-ok'
73+
} Build, {
6374
$analyzeParams = @{
6475
Path = $PSBPreference.Build.ModuleOutDir
6576
SeverityThreshold = $PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel
6677
SettingsPath = $PSBPreference.Test.ScriptAnalysis.SettingsPath
6778
}
6879
Test-PSBuildScriptAnalysis @analyzeParams
80+
# Write marker file for cache validation
81+
Set-Content -Path (Join-Path $PSBPreference.Build.OutDir '.analyze-ok') -Value (Get-Date -Format 'o')
6982
}
7083

7184
$pesterPreReqs = {
@@ -86,7 +99,13 @@ $pesterPreReqs = {
8699
}
87100

88101
# Synopsis: Execute Pester tests
89-
Task Pester -If (. $pesterPreReqs) Build, {
102+
Task Pester -If (. $pesterPreReqs) -Inputs {
103+
$testFiles = Get-ChildItem -Path $PSBPreference.Test.RootDir -Recurse -File -Filter '*.ps1'
104+
$moduleFiles = Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -ErrorAction SilentlyContinue
105+
@($testFiles) + @($moduleFiles)
106+
} -Outputs {
107+
$PSBPreference.Test.OutputFile
108+
} Build, {
90109
$pesterParams = @{
91110
Path = $PSBPreference.Test.RootDir
92111
ModuleName = $PSBPreference.General.ModuleName
@@ -101,6 +120,10 @@ Task Pester -If (. $pesterPreReqs) Build, {
101120
ImportModule = $PSBPreference.Test.ImportModule
102121
SkipRemainingOnFailure = $PSBPreference.Test.SkipRemainingOnFailure
103122
OutputVerbosity = $PSBPreference.Test.OutputVerbosity
123+
OutputMode = $PSBPreference.Test.OutputMode
124+
}
125+
if ($PSBPreference.Test.PesterConfigurationPath) {
126+
$pesterParams.PesterConfigurationPath = $PSBPreference.Test.PesterConfigurationPath
104127
}
105128
Test-PSBuildPester @pesterParams
106129
}
@@ -117,7 +140,15 @@ $genMarkdownPreReqs = {
117140
}
118141

119142
# Synopsis: Generates PlatyPS markdown files from module help
120-
Task GenerateMarkdown -if (. $genMarkdownPreReqs) StageFiles, {
143+
Task GenerateMarkdown -if (. $genMarkdownPreReqs) -Inputs {
144+
if (Test-Path $PSBPreference.Build.ModuleOutDir) {
145+
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -Include '*.ps1', '*.psm1'
146+
}
147+
} -Outputs {
148+
if (Test-Path $PSBPreference.Docs.RootDir) {
149+
Get-ChildItem -Path $PSBPreference.Docs.RootDir -Recurse -File -Filter '*.md'
150+
}
151+
} StageFiles, {
121152
$buildMDParams = @{
122153
ModulePath = $PSBPreference.Build.ModuleOutDir
123154
ModuleName = $PSBPreference.General.ModuleName
@@ -141,7 +172,15 @@ $genHelpFilesPreReqs = {
141172
}
142173

143174
# Synopsis: Generates MAML-based help from PlatyPS markdown files
144-
Task GenerateMAML -if (. $genHelpFilesPreReqs) GenerateMarkdown, {
175+
Task GenerateMAML -if (. $genHelpFilesPreReqs) -Inputs {
176+
if (Test-Path $PSBPreference.Docs.RootDir) {
177+
Get-ChildItem -Path $PSBPreference.Docs.RootDir -Recurse -File -Filter '*.md'
178+
}
179+
} -Outputs {
180+
if (Test-Path $PSBPreference.Build.ModuleOutDir) {
181+
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -Filter '*-help.xml'
182+
}
183+
} GenerateMarkdown, {
145184
Build-PSBuildMAMLHelp -Path $PSBPreference.Docs.RootDir -DestinationPath $PSBPreference.Build.ModuleOutDir
146185
}
147186

@@ -155,7 +194,15 @@ $genUpdatableHelpPreReqs = {
155194
}
156195

157196
# Synopsis: Create updatable help .cab file based on PlatyPS markdown help
158-
Task GenerateUpdatableHelp -if (. $genUpdatableHelpPreReqs) BuildHelp, {
197+
Task GenerateUpdatableHelp -if (. $genUpdatableHelpPreReqs) -Inputs {
198+
if (Test-Path $PSBPreference.Build.ModuleOutDir) {
199+
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -Filter '*-help.xml'
200+
}
201+
} -Outputs {
202+
if (Test-Path $PSBPreference.Help.UpdatableHelpOutDir) {
203+
Get-ChildItem -Path $PSBPreference.Help.UpdatableHelpOutDir -Recurse -File -Filter '*.cab'
204+
}
205+
} BuildHelp, {
159206
Build-PSBuildUpdatableHelp -DocsPath $PSBPreference.Docs.RootDir -OutputPath $PSBPreference.Help.UpdatableHelpOutDir
160207
}
161208

PowerShellBuild/PowerShellBuild.psd1

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
@{
22
RootModule = 'PowerShellBuild.psm1'
3-
ModuleVersion = '0.8.0'
3+
ModuleVersion = '1.0.0'
44
GUID = '15431eb8-be2d-4154-b8ad-4cb68a488e3d'
55
Author = 'Brandon Olin'
66
CompanyName = 'Community'
77
Copyright = '(c) Brandon Olin. All rights reserved.'
88
Description = 'A common psake and Invoke-Build task module for PowerShell projects'
9-
PowerShellVersion = '3.0'
9+
PowerShellVersion = '5.1'
1010
RequiredModules = @(
1111
@{ModuleName = 'BuildHelpers'; ModuleVersion = '2.0.16' }
1212
@{ModuleName = 'Pester'; ModuleVersion = '5.6.1' }
1313
@{ModuleName = 'platyPS'; ModuleVersion = '0.14.1' }
14-
@{ModuleName = 'psake'; ModuleVersion = '4.9.0' }
14+
@{ModuleName = 'psake'; ModuleVersion = '5.0.0' }
1515
)
1616
FunctionsToExport = @(
1717
'Build-PSBuildMAMLHelp'
@@ -26,12 +26,14 @@
2626
'Publish-PSBuildModule'
2727
'Test-PSBuildPester'
2828
'Test-PSBuildScriptAnalysis'
29+
'Format-PSBuildResult'
2930
)
3031
CmdletsToExport = @()
3132
VariablesToExport = @()
3233
AliasesToExport = @('*tasks')
3334
PrivateData = @{
3435
PSData = @{
36+
Prerelease = 'alpha1'
3537
Tags = @('psake', 'build', 'InvokeBuild')
3638
LicenseUri = 'https://raw.githubusercontent.com/psake/PowerShellBuild/master/LICENSE'
3739
ProjectUri = 'https://github.com/psake/PowerShellBuild'

PowerShellBuild/PowerShellBuild.psm1

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ PSScriptAnalyzerResults=PSScriptAnalyzer results:
3636
ScriptAnalyzerErrors=One or more ScriptAnalyzer errors were found!
3737
ScriptAnalyzerWarnings=One or more ScriptAnalyzer warnings were found!
3838
ScriptAnalyzerIssues=One or more ScriptAnalyzer issues were found!
39+
NoCertificateFound=No valid code signing certificate was found. Verify the configured CertificateSource and that a certificate with a private key is available.
40+
CertificateResolvedFromStore=Resolved code signing certificate from store [{0}]: Subject=[{1}]
41+
CertificateResolvedFromThumbprint=Resolved code signing certificate by thumbprint [{0}]: Subject=[{1}]
42+
CertificateResolvedFromEnvVar=Resolved code signing certificate from environment variable [{0}]
43+
CertificateResolvedFromPfxFile=Resolved code signing certificate from PFX file [{0}]
44+
SigningModuleFiles=Signing [{0}] file(s) matching [{1}] in [{2}]...
45+
CreatingFileCatalog=Creating file catalog [{0}] (version {1})...
46+
FileCatalogCreated=File catalog created: [{0}]
47+
CertificateSourceAutoResolved=CertificateSource is 'Auto'. Resolved to '{0}'.
48+
CertificateMissingPrivateKey=The resolved certificate does not have an accessible private key. Code signing requires a certificate with a private key. Subject=[{0}]
49+
CertificateExpired=The resolved certificate has expired (NotAfter: {0}). Code signing requires a valid, unexpired certificate. Subject=[{1}]
50+
CertificateMissingCodeSigningEku=The resolved certificate does not have the Code Signing Enhanced Key Usage (EKU: 1.3.6.1.5.5.7.3.3). Subject=[{0}]
51+
CertificateSourceStoreNotSupported=CertificateSource 'Store' is only supported on Windows platforms.
52+
LLMOutputHeader=Test results (structured output):
53+
MinimalFailureLine=[FAIL] {0} ({1}:{2}) - {3}
54+
PesterConfigLoaded=Loaded PesterConfiguration from [{0}]
55+
InvalidPesterConfigPath=PesterConfiguration file [{0}] not found
3956
'@
4057
}
4158
$importLocalizedDataSplat = @{
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
function ConvertTo-PSBuildLLMOutput {
2+
<#
3+
.SYNOPSIS
4+
Converts Pester test results to structured JSON optimized for LLM consumption.
5+
.DESCRIPTION
6+
Takes a Pester TestResult (PassThru) object and produces a concise JSON structure
7+
containing a summary and an array of failure details. Designed for machine consumption
8+
where only actionable information (failures) matters.
9+
.PARAMETER TestResult
10+
The Pester test result object returned by Invoke-Pester with -PassThru.
11+
#>
12+
[CmdletBinding()]
13+
[OutputType([string])]
14+
param(
15+
[Parameter(Mandatory)]
16+
[object]$TestResult
17+
)
18+
19+
$failures = [System.Collections.Generic.List[object]]::new()
20+
21+
foreach ($container in $TestResult.Containers) {
22+
Get-FailedTestsFromBlock -Blocks $container.Blocks -ContainerName $container.Name -Failures $failures
23+
}
24+
25+
$output = [ordered]@{
26+
summary = [ordered]@{
27+
total = $TestResult.TotalCount
28+
passed = $TestResult.PassedCount
29+
failed = $TestResult.FailedCount
30+
skipped = $TestResult.SkippedCount
31+
duration = [Math]::Round($TestResult.Duration.TotalSeconds, 2)
32+
}
33+
failures = $failures.ToArray()
34+
}
35+
36+
$output | ConvertTo-Json -Depth 10
37+
}
38+
39+
function Get-FailedTestsFromBlock {
40+
<#
41+
.SYNOPSIS
42+
Recursively collects failed tests from Pester block hierarchy.
43+
#>
44+
[CmdletBinding()]
45+
param(
46+
[object[]]$Blocks,
47+
[string]$ContainerName,
48+
[System.Collections.Generic.List[object]]$Failures
49+
)
50+
51+
foreach ($block in $Blocks) {
52+
foreach ($test in $block.Tests) {
53+
if ($test.Result -eq 'Failed') {
54+
$errorMessage = if ($test.ErrorRecord -and $test.ErrorRecord.Count -gt 0) {
55+
$test.ErrorRecord[0].DisplayErrorMessage
56+
} elseif ($test.ErrorRecord) {
57+
"$($test.ErrorRecord)"
58+
} else {
59+
'Unknown error'
60+
}
61+
62+
$file = $null
63+
$line = $null
64+
if ($test.ScriptBlock -and $test.ScriptBlock.File) {
65+
$file = $test.ScriptBlock.File
66+
$line = $test.ScriptBlock.StartPosition.StartLine
67+
}
68+
69+
$Failures.Add([ordered]@{
70+
test = $test.ExpandedPath
71+
container = $ContainerName
72+
file = $file
73+
line = $line
74+
error = $errorMessage
75+
duration = [Math]::Round($test.Duration.TotalMilliseconds, 1)
76+
})
77+
}
78+
}
79+
80+
# Recurse into nested blocks
81+
if ($block.Blocks -and $block.Blocks.Count -gt 0) {
82+
Get-FailedTestsFromBlock -Blocks $block.Blocks -ContainerName $ContainerName -Failures $Failures
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)