From f85f5120344bbab70d9a02b6a5ccc7640b85964d Mon Sep 17 00:00:00 2001 From: Trent Blackburn Date: Thu, 28 May 2026 21:09:45 -0400 Subject: [PATCH 1/2] feat: make Help tests runnable standalone via Set-BuildEnvironment tests/Help.tests.ps1 bootstraps the build via Invoke-psake when $Env:BHBuildOutput is unset, but build.psake.ps1's properties block needs BuildHelpers vars (BHProjectName, BHPSModuleManifest) that are only populated by ./build.ps1 before psake runs. Running the Help tests in isolation (e.g. Invoke-Pester tests/Help.tests.ps1 from an editor) bypasses that, leaving the vars empty and the standalone build broken. Call Set-BuildEnvironment inside the existing bootstrap guard in both BeforeDiscovery and BeforeAll so the vars are populated before psake is invoked. The guard only fires when BHBuildOutput is unset, so there is no effect when tests run via ./build.ps1 or in CI; Set-BuildEnvironment -Force is idempotent. Surfaced by a cross-repo audit against a consumer module (tablackburn/PlexAutomationToolkit) that already carried this fix locally. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + tests/Help.tests.ps1 | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ca8e39..6553a6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ own `CHANGELOG.md` (generated from `CHANGELOG.template.md` during init). - "Repository secrets" section in `README.md` documenting the GitHub Actions secrets the bundled workflows expect (`PSGALLERY_API_KEY`, `CODECOV_TOKEN`, `GITGUARDIAN_API_KEY`) — required vs. optional, source, and failure mode when missing. - `Initialize-Template.ps1` now mentions configuring GitHub repository secrets in its post-init "Next steps" output, between the build-test step and the first push. +- `tests/Help.tests.ps1` now calls `Set-BuildEnvironment` inside its build-bootstrap guard (both `BeforeDiscovery` and `BeforeAll`) so the Help tests can run standalone — e.g. `Invoke-Pester tests/Help.tests.ps1` directly from an editor — without first running `./build.ps1`. The call is skipped entirely when tests run through the build pipeline (the guard only fires when `BHBuildOutput` is unset), and `Set-BuildEnvironment -Force` is idempotent, so there is no effect on CI or `./build.ps1` runs. ### Changed diff --git a/tests/Help.tests.ps1 b/tests/Help.tests.ps1 index f61606d..4409335 100644 --- a/tests/Help.tests.ps1 +++ b/tests/Help.tests.ps1 @@ -48,6 +48,12 @@ BeforeDiscovery { # Check if the BHBuildOutput environment variable exists to determine if this test is running in a psake # build or not. If it does not exist, it is not running in a psake build, so build the module. if ($null -eq $Env:BHBuildOutput) { + # Populate BuildHelpers env vars so build.psake.ps1's properties block has + # the values it needs (BHPSModuleManifest, BHProjectName). When running via + # ./build.ps1 this happens before psake; running tests in isolation bypasses + # that, so we do it here. Set-BuildEnvironment is idempotent and -Force keeps + # it from erroring if the vars are already set. + Set-BuildEnvironment -Path (Split-Path -Path $PSScriptRoot -Parent) -Force $buildFilePath = Join-Path -Path $PSScriptRoot -ChildPath '..\build.psake.ps1' $invokePsakeParameters = @{ TaskList = 'Build' @@ -99,6 +105,12 @@ BeforeAll { # Check if the BHBuildOutput environment variable exists to determine if this test is running in a psake # build or not. If it does not exist, it is not running in a psake build, so build the module. if ($null -eq $Env:BHBuildOutput) { + # Populate BuildHelpers env vars so build.psake.ps1's properties block has + # the values it needs (BHPSModuleManifest, BHProjectName). When running via + # ./build.ps1 this happens before psake; running tests in isolation bypasses + # that, so we do it here. Set-BuildEnvironment is idempotent and -Force keeps + # it from erroring if the vars are already set. + Set-BuildEnvironment -Path (Split-Path -Path $PSScriptRoot -Parent) -Force $buildFilePath = Join-Path -Path $PSScriptRoot -ChildPath '..\build.psake.ps1' $invokePsakeParameters = @{ TaskList = 'Build' From 06f2771b5805906cf51b87dbb7f0f7a92372588c Mon Sep 17 00:00:00 2001 From: Trent Blackburn Date: Thu, 28 May 2026 22:08:57 -0400 Subject: [PATCH 2/2] refactor(tests): bootstrap standalone Help tests via build.ps1 Replace the inline Set-BuildEnvironment + Invoke-psake bootstrap in Help.tests.ps1 (both BeforeDiscovery and BeforeAll) with a single call to build.ps1 -- the canonical entry point -- so dependency bootstrap, BuildHelpers environment setup, and module staging all run through the real build path instead of a partial reimplementation that could drift. build.ps1 is invoked with the call operator (&), not dot-sourced, so its terminating exit is contained to the script boundary and does not end the Pester run (verified on Windows PowerShell 5.1 and PowerShell 7). Path is built with a single two-argument Join-Path over Split-Path -Parent so it stays valid on PowerShell 5.1 (which lacks Join-Path -AdditionalChildPath). Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- tests/Help.tests.ps1 | 42 ++++++++++++++++++------------------------ 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6553a6f..7a7748d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ own `CHANGELOG.md` (generated from `CHANGELOG.template.md` during init). - "Repository secrets" section in `README.md` documenting the GitHub Actions secrets the bundled workflows expect (`PSGALLERY_API_KEY`, `CODECOV_TOKEN`, `GITGUARDIAN_API_KEY`) — required vs. optional, source, and failure mode when missing. - `Initialize-Template.ps1` now mentions configuring GitHub repository secrets in its post-init "Next steps" output, between the build-test step and the first push. -- `tests/Help.tests.ps1` now calls `Set-BuildEnvironment` inside its build-bootstrap guard (both `BeforeDiscovery` and `BeforeAll`) so the Help tests can run standalone — e.g. `Invoke-Pester tests/Help.tests.ps1` directly from an editor — without first running `./build.ps1`. The call is skipped entirely when tests run through the build pipeline (the guard only fires when `BHBuildOutput` is unset), and `Set-BuildEnvironment -Force` is idempotent, so there is no effect on CI or `./build.ps1` runs. +- `tests/Help.tests.ps1` can now run standalone — e.g. `Invoke-Pester tests/Help.tests.ps1` directly from an editor (or an agent running a single test) — without first running `./build.ps1`. Its build-bootstrap guard (in both `BeforeDiscovery` and `BeforeAll`) now delegates to `build.ps1 -Task 'Build' -Bootstrap`, the canonical entry point, so dependency bootstrap, BuildHelpers environment setup, and module staging all happen through the real build path instead of a partial reimplementation. The guard only fires when `BHBuildOutput` is unset, so there is no effect on CI or `./build.ps1` runs. `build.ps1` is invoked with the call operator (`&`), not dot-sourced, so its terminating `exit` is contained to the script boundary and does not end the Pester run. ### Changed diff --git a/tests/Help.tests.ps1 b/tests/Help.tests.ps1 index 4409335..97f62a8 100644 --- a/tests/Help.tests.ps1 +++ b/tests/Help.tests.ps1 @@ -48,18 +48,15 @@ BeforeDiscovery { # Check if the BHBuildOutput environment variable exists to determine if this test is running in a psake # build or not. If it does not exist, it is not running in a psake build, so build the module. if ($null -eq $Env:BHBuildOutput) { - # Populate BuildHelpers env vars so build.psake.ps1's properties block has - # the values it needs (BHPSModuleManifest, BHProjectName). When running via - # ./build.ps1 this happens before psake; running tests in isolation bypasses - # that, so we do it here. Set-BuildEnvironment is idempotent and -Force keeps - # it from erroring if the vars are already set. - Set-BuildEnvironment -Path (Split-Path -Path $PSScriptRoot -Parent) -Force - $buildFilePath = Join-Path -Path $PSScriptRoot -ChildPath '..\build.psake.ps1' - $invokePsakeParameters = @{ - TaskList = 'Build' - BuildFile = $buildFilePath - } - Invoke-psake @invokePsakeParameters + # Standalone run (e.g. Invoke-Pester on this file directly, or an agent + # running one test): the module isn't built and the BuildHelpers env vars + # aren't set. Defer to build.ps1 -- the canonical entry point -- to bootstrap + # dependencies, set the BuildHelpers environment, and stage the module. + # Invoke with & (not dot-sourcing): build.ps1 ends in an exit statement, and + # the call operator contains it to the script boundary instead of ending the + # whole Pester run. + $buildScript = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'build.ps1' + & $buildScript -Task 'Build' -Bootstrap } # PowerShellBuild outputs to Output///, override BHBuildOutput @@ -105,18 +102,15 @@ BeforeAll { # Check if the BHBuildOutput environment variable exists to determine if this test is running in a psake # build or not. If it does not exist, it is not running in a psake build, so build the module. if ($null -eq $Env:BHBuildOutput) { - # Populate BuildHelpers env vars so build.psake.ps1's properties block has - # the values it needs (BHPSModuleManifest, BHProjectName). When running via - # ./build.ps1 this happens before psake; running tests in isolation bypasses - # that, so we do it here. Set-BuildEnvironment is idempotent and -Force keeps - # it from erroring if the vars are already set. - Set-BuildEnvironment -Path (Split-Path -Path $PSScriptRoot -Parent) -Force - $buildFilePath = Join-Path -Path $PSScriptRoot -ChildPath '..\build.psake.ps1' - $invokePsakeParameters = @{ - TaskList = 'Build' - BuildFile = $buildFilePath - } - Invoke-psake @invokePsakeParameters + # Standalone run (e.g. Invoke-Pester on this file directly, or an agent + # running one test): the module isn't built and the BuildHelpers env vars + # aren't set. Defer to build.ps1 -- the canonical entry point -- to bootstrap + # dependencies, set the BuildHelpers environment, and stage the module. + # Invoke with & (not dot-sourcing): build.ps1 ends in an exit statement, and + # the call operator contains it to the script boundary instead of ending the + # whole Pester run. + $buildScript = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'build.ps1' + & $buildScript -Task 'Build' -Bootstrap } }