ci: Install the built module from a local repository - #230
Conversation
CI has built this module on every push and never installed it, so nothing observes an install-time break: a RequiredModules entry naming a version that does not exist, or a dependency dropped from the manifest that is still needed. Several breaks this cycle are exactly that shape. Add a repository-local workflow that publishes the built module into a temporary file-share repository alongside mirrored copies of its required modules, then saves and imports it in a process whose PSModulePath holds only an empty directory and a tools directory. It runs twice on windows-latest, once with a PowerShell 7 probe and once with a Windows PowerShell 5.1 probe. The test logic lives in tests/InstallTime/ as PowerShell rather than inline YAML, so it can be run locally and reviewed as code. A negative control republishes the module with an unsatisfiable dependency and requires the save to fail with nothing on disk, so a green run is evidence rather than an assumption. Refs #229 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WTjuRainebRsnmcg8fRCKB
There was a problem hiding this comment.
🟡 Changes recommended
It leaves a persistent local side effect (PSGallery trust policy) and contains an incomplete documentation sentence that should be corrected before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a repository-local CI workflow that actually installs the freshly built PowerShellBuild module from a temporary local repository (including mirrored RequiredModules) to catch install-time manifest/dependency breakages on both PowerShell 7 and Windows PowerShell 5.1.
Changes:
- Adds a new GitHub Actions workflow to run the install-time probe on
windows-latestfor both PowerShell editions. - Introduces
tests/InstallTime/PowerShell scripts to stage tools, publish to a temp file-share repo, and validateSave-Module+ import behavior (including a negative control). - Updates repo documentation and spelling dictionary entries to reflect the new CI coverage.
File summaries
| File | Description |
|---|---|
tests/InstallTime/Test-InstallTimeDependency.ps1 |
Isolated probe that validates Save-Module resolves dependencies and the module imports/exports correctly. |
tests/InstallTime/Invoke-LocalRepositoryInstallTest.ps1 |
Driver that builds, stages packaging tools, mirrors dependencies, publishes to a local repo, runs positive + negative probes. |
.github/workflows/install-test.yml |
New CI workflow that runs the install-time test matrix (PowerShell 7 + Windows PowerShell 5.1). |
instructions/repository-specific.instructions.md |
Documents the new install-test workflow and its intent/limits. |
cspell.json |
Adds terminology used by the new scripts/docs. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| PackageManagement\Get-PackageProvider -Name 'NuGet' -ForceBootstrap | Out-Null | ||
| Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | ||
| Save-Module -Name 'PowerShellGet' -RequiredVersion $powerShellGetVersion -Repository 'PSGallery' -Path $paths.Tools -Force |
| } finally { | ||
| if (Get-PSRepository -Name $RepositoryName -ErrorAction SilentlyContinue) { | ||
| Unregister-PSRepository -Name $RepositoryName | ||
| Write-Host "Unregistered $RepositoryName." | ||
| } | ||
| } |
| - Runs `tests/InstallTime/Invoke-LocalRepositoryInstallTest.ps1`, which publishes the built | ||
| module into a temporary file-share repository alongside mirrored copies of its | ||
| `RequiredModules`, then saves and imports it in a process whose `PSModulePath` holds only an | ||
| empty directory. A build cannot observe an install-time break; this can |
The first CI run failed on PowerShell 7 while Windows PowerShell 5.1 passed. Dropping the engine's own module directory from the isolated PSModulePath also drops CimCmdlets, so Pester 6.0.0 cannot resolve Get-CimInstance and falls through to looking for a `uname` application. A GitHub Windows runner has uname.exe from Git for Windows on PATH, and Pester then throws "SafeCommands entry for uname does not hold a reference to the proper command." A developer machine without Git's usr\bin on PATH only warns, which is why this never appeared locally. That is an artifact of the isolation, not something a consumer could hit, so keep the engine directory on the PSModulePath for PowerShell 7. It ships with the engine and holds no dependency of the module under test. Windows PowerShell 5.1 keeps the two-entry path it already had: it does not need the engine directory, and adding it there makes the engine restore the machine's other default module paths, which puts the Program Files copy of Pester back in view. The isolation is guaranteed by the explicit visibility assertions either way, not by the contents of that list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WTjuRainebRsnmcg8fRCKB
Pushed only to show this workflow going red in CI on a real install-time failure. Reverted immediately. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o succeed" This reverts commit 3b38059.
CI evidenceGreen, both editions — run 33916994194: Windows PowerShell 5.1 produced the identical Red on demand — 3b38059 temporarily dropped 4d1dfd1 reverts it, and the tree at the branch tip is byte-identical to the verified-green 62d6dbd. Those two commits are the evidence and disappear on squash merge. One thing the prototype did not surviveThe first CI run (33916171119) failed on PowerShell 7 while 5.1 passed — a failure that cannot be reproduced on a normal developer machine. Emptying 62d6dbd keeps the engine's own module directory on the path for PowerShell 7 only. Windows PowerShell 5.1 must not have it: adding it there makes the engine restore the machine's other default module paths, which puts the Program Files copy of Pester back in view. Either way the isolation is held by the explicit visibility assertions, which still pass on both editions — see the 🤖 Generated with Claude Code |
Closes #229.
CI has built this module on every push and never installed it. A build cannot observe an
install-time break — a
RequiredModulesentry naming a version that does not exist, or adependency dropped from the manifest that is still needed — and several breaks this cycle are
exactly that shape.
What this adds
A repository-local workflow,
.github/workflows/install-test.yml, running on push and pullrequest alongside the existing shared
ModuleCI.ymlcall. Two jobs, both onwindows-latestsothat a real Windows PowerShell 5.1 engine is available: one probes with PowerShell 7, one with
Windows PowerShell 5.1.
The logic is PowerShell under
tests/InstallTime/rather than inline YAML, so it can be runlocally and reviewed as code:
Invoke-LocalRepositoryInstallTest.ps1— builds the module, stages a tools module path holdingonly PowerShellGet and PackageManagement, registers a temporary file-share repository, mirrors
every
RequiredModulesentry into it from the gallery, publishes the built module, runs theprobe, runs the negative control, and unregisters the repository however the run ends.
Test-InstallTimeDependency.ps1— the isolated half. Runs in a fresh process whosePSModulePathholds only an empty directory and the tools path, asserts nothing under test isvisible before the save, saves the module, asserts every dependency resolved at its declared
version, re-asserts the isolated path, imports by explicit manifest path, and asserts the
required modules loaded from the isolated path and that the declared functions and the
PowerShellBuild.IB.Tasksalias are exported.Everything is read from the built manifest rather than hardcoded, so the test keeps testing the
manifest instead of a stale copy of it.
Negative control
A test that cannot fail is not evidence. The negative control republishes a scratch copy of the
module whose manifest demands a dependency version that does not exist, and requires the save to
fail with nothing on disk. It also asserts that
Publish-Moduleitself refuses the brokenpackage (
UnableToResolveModuleDependency) — the same gate runs against the PowerShell Gallery,so an unsatisfiable dependency fails a release rather than reaching consumers.
Shown red then green in CI on this branch, not just locally: a temporary commit that dropped
-ExpectSaveToFailfrom the negative probe turned both jobs red on the real failure(
ProviderFailToDownloadFile), and reverting it turned them green. Run links are in the reviewthread.
What it deliberately does not cover
Documented in the comment-based help at the top of the driver so nobody reads a green run as more
than it is: the live dependency graph (the real gallery picks the newest package satisfying each
range, not the mirrored floor), transitive dependency drift, gallery ingestion, and real network
transport. Also noted there: against a file-share repository PowerShellGet 2.x gets prerelease
gating wrong, so this test must not be extended to prerelease behavior.
Notes for review
CHANGELOG.mdentry: this is CI only, not a user-facing change to the shipped module.cspell.jsongainscoreclr,fullclr, andLASTEXITCODE.(PowerShellGet 2.2.5 plus PackageManagement 1.4.8.1) rather than copied off the running
machine. The gallery package carries both
coreclrandfullclrbinaries, so the same stagingworks on both editions and does not depend on what the runner image happens to have installed.
PSAvoidUsingWriteHost, which thisrepository already excludes.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WTjuRainebRsnmcg8fRCKB