diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index b05d98f..45f9170 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -29,7 +29,7 @@ }, { "name": "powershellbuild", - "source": "./powershellbuild", + "source": "./plugins/powershellbuild", "description": "PowerShellBuild — standardized build, test, and publish tasks for PowerShell module development", "version": "1.0.0", "author": { diff --git a/plugins/powershellbuild/evals/evals.json b/plugins/powershellbuild/evals/evals.json index 52da79a..74d3f67 100644 --- a/plugins/powershellbuild/evals/evals.json +++ b/plugins/powershellbuild/evals/evals.json @@ -5,19 +5,115 @@ "id": 1, "prompt": "I'm starting a new PowerShell module project called 'PSUtilities'. It'll have Public and Private function folders and a tests/ directory with Pester 5 tests. Can you scaffold the full build system using PowerShellBuild — I need build.ps1, psakeFile.ps1, and requirements.psd1. I want code coverage at 80% and PSScriptAnalyzer enabled.", "expected_output": "Three files: build.ps1 (Bootstrap + Invoke-psake entry point), psakeFile.ps1 (Import-Module PowerShellBuild, properties block with PSBPreference settings for coverage threshold 0.80 and ScriptAnalysis, tasks using -FromModule PowerShellBuild), requirements.psd1 (psake, PowerShellBuild, Pester, PSScriptAnalyzer, platyPS). Structure follows PowerShellBuild conventions.", - "files": [] + "files": [], + "assertions": [ + { + "name": "has_all_three_files", + "type": "regex", + "value": "(?i)(build\\.ps1|psakeFile\\.ps1|requirements\\.psd1)", + "description": "Should generate build.ps1, psakeFile.ps1, and requirements.psd1" + }, + { + "name": "uses_from_module", + "type": "contains", + "value": "-FromModule PowerShellBuild", + "description": "psakeFile.ps1 should use -FromModule PowerShellBuild for task definitions" + }, + { + "name": "has_code_coverage_threshold", + "type": "regex", + "value": "CodeCoverage\\.Threshold\\s*=\\s*0\\.80", + "description": "Code coverage threshold should be set to 0.80 (80%)" + }, + { + "name": "has_script_analysis_enabled", + "type": "regex", + "value": "ScriptAnalysis\\.Enabled\\s*=\\s*\\$true", + "description": "PSScriptAnalyzer should be explicitly enabled via PSBPreference" + }, + { + "name": "has_required_dependencies", + "type": "regex", + "value": "(?i)(PowerShellBuild|psake|Pester|PSScriptAnalyzer)", + "description": "requirements.psd1 should include psake, PowerShellBuild, Pester, and PSScriptAnalyzer" + } + ] }, { "id": 2, "prompt": "My PowerShellBuild project builds fine locally but I need to set up GitHub Actions CI. I want it to run tests on every push and PR, then publish to PSGallery automatically when I push to main. The API key should come from a GitHub secret called PSGALLERY_API_KEY.", "expected_output": "A GitHub Actions workflow YAML with two jobs: test (runs on push/PR, calls ./build.ps1 -Task Test -Bootstrap on windows-latest) and publish (depends on test, only on main branch, passes PSGALLERY_API_KEY secret as env var). Uses actions/checkout@v4.", - "files": [] + "files": [], + "assertions": [ + { + "name": "has_two_jobs", + "type": "regex", + "value": "(?i)(test:|publish:)", + "description": "Workflow should have separate test and publish jobs" + }, + { + "name": "triggers_on_push_and_pr", + "type": "regex", + "value": "(?i)(push|pull_request)", + "description": "Test job should trigger on both push and pull_request events" + }, + { + "name": "publish_only_on_main", + "type": "regex", + "value": "(?i)(refs/heads/main|github\\.ref.*main)", + "description": "Publish job should only run on main branch" + }, + { + "name": "has_psgallery_secret", + "type": "contains", + "value": "PSGALLERY_API_KEY", + "description": "PSGALLERY_API_KEY should be passed from GitHub secrets" + }, + { + "name": "uses_build_ps1", + "type": "regex", + "value": "(?i)(\\.[\\/]build\\.ps1|build\\.ps1)", + "description": "Workflow should call ./build.ps1 rather than Invoke-psake directly" + } + ] }, { "id": 3, "prompt": "I want to use Invoke-Build instead of psake for my PowerShellBuild project. Can you show me how to set up the .build.ps1 file? I need to compile the module into a single PSM1 from Classes, Private, and Public folders, run code coverage, and exclude any .Tests.ps1 files from the build output.", "expected_output": "A .build.ps1 using Invoke-Build syntax: Import-Module PowerShellBuild -Force, . PowerShellBuild.IB.Tasks, then PSBPreference settings for CompileModule=$true, CompileDirectories=@('Classes','Private','Public'), Exclude=@('*.Tests.ps1'), CodeCoverage.Enabled=$true. Default task set to Build or Test.", - "files": [] + "files": [], + "assertions": [ + { + "name": "uses_ib_tasks", + "type": "contains", + "value": "PowerShellBuild.IB.Tasks", + "description": "Should use . PowerShellBuild.IB.Tasks to load Invoke-Build tasks" + }, + { + "name": "has_compile_module", + "type": "regex", + "value": "CompileModule\\s*=\\s*\\$true", + "description": "CompileModule should be set to $true" + }, + { + "name": "has_compile_directories", + "type": "regex", + "value": "CompileDirectories\\s*=", + "description": "CompileDirectories should include Classes, Private, and Public" + }, + { + "name": "has_exclude_tests", + "type": "regex", + "value": "(?i)(Exclude.*Tests\\.ps1|Tests\\.ps1.*Exclude)", + "description": "Exclude pattern should filter out *.Tests.ps1 files" + }, + { + "name": "has_code_coverage_enabled", + "type": "regex", + "value": "CodeCoverage\\.Enabled\\s*=\\s*\\$true", + "description": "Code coverage should be enabled via PSBPreference" + } + ] } ] } diff --git a/plugins/powershellbuild/skills/powershellbuild/SKILL.md b/plugins/powershellbuild/skills/powershellbuild/SKILL.md index c3817b0..2295ef1 100644 --- a/plugins/powershellbuild/skills/powershellbuild/SKILL.md +++ b/plugins/powershellbuild/skills/powershellbuild/SKILL.md @@ -1,6 +1,6 @@ --- name: powershellbuild -description: PowerShellBuild module for standardized PowerShell module development. Use when Claude needs to help set up or troubleshoot PowerShellBuild-based projects, generate psakeFile.ps1 or .build.ps1 files using PowerShellBuild tasks, configure build/test/publish pipelines for PowerShell modules, or work with PSBPreference settings. Triggers include mentions of PowerShellBuild, PSBPreference, PowerShell module builds, Pester, PSScriptAnalyzer, PlatyPS, PSGallery publishing, or any request to set up a PowerShell module build system. +description: This skill should be used when the user asks to "set up PowerShellBuild", "create a psakeFile with -FromModule", "configure PSBPreference", "publish a PowerShell module to PSGallery", "set up Pester tests for a module", or mentions PowerShellBuild, PSBPreference, -FromModule PowerShellBuild, PowerShellBuild.IB.Tasks, PSScriptAnalyzer integration, PlatyPS help generation, code coverage thresholds, or PowerShell module build/test/publish pipelines. --- # PowerShellBuild @@ -15,10 +15,10 @@ PowerShellBuild provides standardized build, test, and publish tasks for PowerSh - **Not sure / starting fresh** → default to psake (simpler syntax) **What do you need?** -- Set up a new module project → [Complete Example](#complete-example) +- Set up a new module project → See `references/complete-example.md` - Override build behavior → [Configuration ($PSBPreference)](#configuration-psbpreference) - Customize task dependencies → [Modifying Task Dependencies](#modifying-task-dependencies) -- CI/CD setup → [CI/CD Integration](#cicd-integration) +- CI/CD setup → See `references/ci-cd.md` ## Quick Start @@ -140,82 +140,6 @@ $PSBTestDependency = 'Pester' # skip analysis $PSBPublishDependency = 'Build' # publish without tests (not recommended) ``` -## Complete Example - -### build.ps1 - -```powershell -[cmdletbinding(DefaultParameterSetName = 'Task')] -param( - [parameter(ParameterSetName = 'Task', position = 0)] - [string[]]$Task = 'default', - - [switch]$Bootstrap, - - [parameter(ParameterSetName = 'Help')] - [switch]$Help -) - -$ErrorActionPreference = 'Stop' - -if ($Bootstrap.IsPresent) { - Get-PackageProvider -Name Nuget -ForceBootstrap | Out-Null - Set-PSRepository -Name PSGallery -InstallationPolicy Trusted - if (-not (Get-Module -Name PSDepend -ListAvailable)) { - Install-Module -Name PSDepend -Repository PSGallery -Scope CurrentUser - } - Import-Module -Name PSDepend -Verbose:$false - Invoke-PSDepend -Path './requirements.psd1' -Install -Import -Force -WarningAction SilentlyContinue -} - -$psakeFile = './psakeFile.ps1' -if ($PSCmdlet.ParameterSetName -eq 'Help') { - Get-PSakeScriptTasks -buildFile $psakeFile | Format-Table -Property Name, Description -} else { - Set-BuildEnvironment -Force - Invoke-psake -buildFile $psakeFile -taskList $Task -Verbose:$VerbosePreference - exit ([int](-not $psake.build_success)) -} -``` - -### requirements.psd1 - -```powershell -@{ - PSDependOptions = @{ Target = 'CurrentUser' } - psake = '4.9.0' - PowerShellBuild = 'latest' - Pester = @{ - MinimumVersion = '5.6.1' - Parameters = @{ SkipPublisherCheck = $true } - } - PSScriptAnalyzer = '1.24.0' - platyPS = '0.14.2' -} -``` - -### psakeFile.ps1 (full) - -```powershell -properties { - $PSBPreference.Build.CompileModule = $true - $PSBPreference.Build.CompileDirectories = @('Enum', 'Classes', 'Private', 'Public') - $PSBPreference.Test.ScriptAnalysis.Enabled = $true - $PSBPreference.Test.CodeCoverage.Enabled = $true - $PSBPreference.Test.CodeCoverage.Threshold = 0.80 - $PSBPreference.Publish.PSRepositoryApiKey = $env:PSGALLERY_API_KEY -} - -task default -depends Test - -task Clean -FromModule PowerShellBuild -task Build -FromModule PowerShellBuild -task Analyze -FromModule PowerShellBuild -task Pester -FromModule PowerShellBuild -task Test -FromModule PowerShellBuild -task Publish -FromModule PowerShellBuild -``` - ## Invoke-Build Alternative ```powershell @@ -228,37 +152,6 @@ $PSBPreference.Test.CodeCoverage.Threshold = 0.75 task . Build ``` -## CI/CD Integration - -### GitHub Actions - -```yaml -name: CI - -on: [push, pull_request] - -jobs: - test: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - name: Test - shell: pwsh - run: ./build.ps1 -Task Test -Bootstrap - - publish: - needs: test - runs-on: windows-latest - if: github.ref == 'refs/heads/main' - steps: - - uses: actions/checkout@v4 - - name: Publish - shell: pwsh - run: ./build.ps1 -Task Publish -Bootstrap - env: - PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} -``` - ## Troubleshooting | Problem | Solution | @@ -270,3 +163,8 @@ jobs: | ScriptAnalyzer fails build | Fix violations or set `FailBuildOnSeverityLevel = 'Warning'` | | Code coverage below threshold | Raise `CodeCoverage.Threshold` or add tests | | Publish fails | Verify `PSGALLERY_API_KEY` env var is set | + +## References + +- **`references/complete-example.md`** - Full project scaffold: build.ps1, requirements.psd1, psakeFile.ps1 with all tasks +- **`references/ci-cd.md`** - GitHub Actions workflow for test and publish pipelines diff --git a/plugins/powershellbuild/skills/powershellbuild/references/ci-cd.md b/plugins/powershellbuild/skills/powershellbuild/references/ci-cd.md new file mode 100644 index 0000000..2cdb6e3 --- /dev/null +++ b/plugins/powershellbuild/skills/powershellbuild/references/ci-cd.md @@ -0,0 +1,37 @@ +# CI/CD Integration with PowerShellBuild + +## GitHub Actions + +```yaml +name: CI + +on: [push, pull_request] + +jobs: + test: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Test + shell: pwsh + run: ./build.ps1 -Task Test -Bootstrap + + publish: + needs: test + runs-on: windows-latest + if: github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + - name: Publish + shell: pwsh + run: ./build.ps1 -Task Publish -Bootstrap + env: + PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} +``` + +### Key Points + +- Always use `./build.ps1` as the entry point, not `Invoke-psake` directly — `build.ps1` handles bootstrapping dependencies and setting up the build environment. +- Use `-Bootstrap` on the first run (or always in CI) to install dependencies from `requirements.psd1`. +- Pass secrets as environment variables, not as parameters. +- Publish job should depend on the test job (`needs: test`) and only run on main branch. diff --git a/plugins/powershellbuild/skills/powershellbuild/references/complete-example.md b/plugins/powershellbuild/skills/powershellbuild/references/complete-example.md new file mode 100644 index 0000000..2711100 --- /dev/null +++ b/plugins/powershellbuild/skills/powershellbuild/references/complete-example.md @@ -0,0 +1,94 @@ +# Complete PowerShellBuild Example + +## Project Structure + +``` +MyModule/ +├── src/ +│ ├── MyModule.psd1 +│ ├── MyModule.psm1 +│ ├── Private/ +│ │ └── HelperFunction.ps1 +│ └── Public/ +│ └── Get-Something.ps1 +├── tests/ +│ └── MyModule.Tests.ps1 +├── docs/ +├── build.ps1 +├── psakeFile.ps1 +└── requirements.psd1 +``` + +## build.ps1 + +```powershell +[cmdletbinding(DefaultParameterSetName = 'Task')] +param( + [parameter(ParameterSetName = 'Task', position = 0)] + [string[]]$Task = 'default', + + [switch]$Bootstrap, + + [parameter(ParameterSetName = 'Help')] + [switch]$Help +) + +$ErrorActionPreference = 'Stop' + +if ($Bootstrap.IsPresent) { + Get-PackageProvider -Name Nuget -ForceBootstrap | Out-Null + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + if (-not (Get-Module -Name PSDepend -ListAvailable)) { + Install-Module -Name PSDepend -Repository PSGallery -Scope CurrentUser + } + Import-Module -Name PSDepend -Verbose:$false + Invoke-PSDepend -Path './requirements.psd1' -Install -Import -Force -WarningAction SilentlyContinue +} + +$psakeFile = './psakeFile.ps1' +if ($PSCmdlet.ParameterSetName -eq 'Help') { + Get-PSakeScriptTasks -buildFile $psakeFile | Format-Table -Property Name, Description +} else { + Set-BuildEnvironment -Force + Invoke-psake -buildFile $psakeFile -taskList $Task -Verbose:$VerbosePreference + exit ([int](-not $psake.build_success)) +} +``` + +## requirements.psd1 + +```powershell +@{ + PSDependOptions = @{ Target = 'CurrentUser' } + psake = '4.9.0' + PowerShellBuild = 'latest' + Pester = @{ + MinimumVersion = '5.6.1' + Parameters = @{ SkipPublisherCheck = $true } + } + PSScriptAnalyzer = '1.24.0' + platyPS = '0.14.2' +} +``` + +## psakeFile.ps1 (full) + +```powershell +properties { + $PSBPreference.Build.CompileModule = $true + $PSBPreference.Build.CompileDirectories = @('Enum', 'Classes', 'Private', 'Public') + $PSBPreference.Test.ScriptAnalysis.Enabled = $true + $PSBPreference.Test.CodeCoverage.Enabled = $true + $PSBPreference.Test.CodeCoverage.Threshold = 0.80 + $PSBPreference.Publish.PSRepositoryApiKey = $env:PSGALLERY_API_KEY +} + +task default -depends Test + +task Clean -FromModule PowerShellBuild +task Build -FromModule PowerShellBuild +task Analyze -FromModule PowerShellBuild +task Pester -FromModule PowerShellBuild +task Test -FromModule PowerShellBuild +task Publish -FromModule PowerShellBuild +```