Skip to content

ci: Install the built module from a local repository - #230

Open
tablackburn wants to merge 4 commits into
mainfrom
ci/229-local-repository-install-test
Open

ci: Install the built module from a local repository#230
tablackburn wants to merge 4 commits into
mainfrom
ci/229-local-repository-install-test

Conversation

@tablackburn

Copy link
Copy Markdown
Contributor

Closes #229.

CI has built this module on every push and never installed it. A build cannot observe 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 — 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 pull
request alongside the existing shared ModuleCI.yml call. Two jobs, both on windows-latest so
that 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 run
locally and reviewed as code:

  • Invoke-LocalRepositoryInstallTest.ps1 — builds the module, stages a tools module path holding
    only PowerShellGet and PackageManagement, registers a temporary file-share repository, mirrors
    every RequiredModules entry into it from the gallery, publishes the built module, runs the
    probe, runs the negative control, and unregisters the repository however the run ends.
  • Test-InstallTimeDependency.ps1 — the isolated half. Runs in a fresh process whose
    PSModulePath holds only an empty directory and the tools path, asserts nothing under test is
    visible 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.Tasks alias 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-Module itself refuses the broken
package (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
-ExpectSaveToFail from the negative probe turned both jobs red on the real failure
(ProviderFailToDownloadFile), and reverting it turned them green. Run links are in the review
thread.

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

  • No CHANGELOG.md entry: this is CI only, not a user-facing change to the shipped module.
  • cspell.json gains coreclr, fullclr, and LASTEXITCODE.
  • One deviation from the prototype in CI: Install the built module from a local repository to verify install-time dependency behaviour #229: the tools module path is staged from the gallery
    (PowerShellGet 2.2.5 plus PackageManagement 1.4.8.1) rather than copied off the running
    machine. The gallery package carries both coreclr and fullclr binaries, so the same staging
    works on both editions and does not depend on what the runner image happens to have installed.
  • Both scripts are clean under PSScriptAnalyzer apart from PSAvoidUsingWriteHost, which this
    repository already excludes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WTjuRainebRsnmcg8fRCKB

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
Copilot AI lite review requested due to automatic review settings September 4, 2026 20:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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-latest for both PowerShell editions.
  • Introduces tests/InstallTime/ PowerShell scripts to stage tools, publish to a temp file-share repo, and validate Save-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.

Comment on lines +387 to +389
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
Comment on lines +516 to +521
} finally {
if (Get-PSRepository -Name $RepositoryName -ErrorAction SilentlyContinue) {
Unregister-PSRepository -Name $RepositoryName
Write-Host "Unregistered $RepositoryName."
}
}
Comment on lines +299 to +302
- 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
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Test Results

    4 files  ±0    889 suites  ±0   2m 30s ⏱️ +3s
  596 tests ±0    593 ✅ ±0   3 💤 ±0  0 ❌ ±0 
2 367 runs  ±0  2 295 ✅ ±0  72 💤 ±0  0 ❌ ±0 

Results for commit 4d1dfd1. ± Comparison against base commit 3ea9e82.

♻️ This comment has been updated with latest results.

tablackburn and others added 3 commits September 4, 2026 16:30
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>
@tablackburn

Copy link
Copy Markdown
Contributor Author

CI evidence

Green, both editions — run 33916994194: Local repository install (PowerShell 7) and Local repository install (Windows PowerShell 5.1) both pass. Negative control on the PowerShell 7 job:

--- 7a. Publish-Module must refuse the broken package ---
Publish-Module refused it: UnableToResolveModuleDependency,Publish-PSArtifactUtility

--- 7b. Stage the broken package behind a throwaway stub ---
Removed the stub package BuildHelpers 99.0.0 from the local repository.

--- 7c. Save-Module must now fail on PowerShell7 ---
--- Save-Module PowerShellBuild 999.0.0 -Repository PowerShellBuildInstallTest ---
WARNING: Unable to find dependent module(s) (BuildHelpers)
WARNING: Package 'PowerShellBuild' failed to install.
Save-Module failed.
  FullyQualifiedErrorId: ProviderFailToDownloadFile,Microsoft.PowerShell.PackageManagement.Cmdlets.SavePackage
  Message: Unable to save the module 'PowerShellBuild'.
What landed in the isolated path:
  (nothing)

--- Negative control assertions ---
  PASS: Save-Module refused a package whose dependency version does not exist
  PASS: Nothing was left in the isolated path by the failed save

Windows PowerShell 5.1 produced the identical ProviderFailToDownloadFile result.

Red on demand3b38059 temporarily dropped -ExpectSaveToFail from the negative probe so the broken package was expected to install. Run 33916794776: both jobs failed with

RESULT: the isolated probe failed - ASSERTION FAILED: Save-Module resolved PowerShellBuild 999.0.0 from the local repository
...
The negative control failed on PowerShell7 with exit code 1; this test cannot be trusted to fail when the manifest is broken.

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 survive

The first CI run (33916171119) failed on PowerShell 7 while 5.1 passed — a failure that cannot be reproduced on a normal developer machine. Emptying PSModulePath also removes CimCmdlets, so Pester 6.0.0 cannot resolve Get-CimInstance, falls through to looking for a uname application, finds uname.exe from Git for Windows (on PATH on hosted Windows runners, not usually on a developer box), and throws SafeCommands entry for uname does not hold a reference to the proper command. Locally it just warns OS Information retrieval is not possible and carries on.

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 PASS: Pester is not visible on the isolated PSModulePath lines in both jobs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WTjuRainebRsnmcg8fRCKB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI: Install the built module from a local repository to verify install-time dependency behaviour

2 participants