Skip to content

fix: Stop compile mode sweeping the working directory and skipping about help - #213

Merged
tablackburn merged 1 commit into
mainfrom
fix/206-207-compile-defaults
Aug 28, 2026
Merged

fix: Stop compile mode sweeping the working directory and skipping about help#213
tablackburn merged 1 commit into
mainfrom
fix/206-207-compile-defaults

Conversation

@tablackburn

Copy link
Copy Markdown
Contributor

Closes #206 · Closes #207

Both are silent defects in Build-PSBuildModule's staging that produce a plausible artifact from a green build. The option each issue listed was chosen after researching consumer usage and peer tools — that research changed the answer for #206.

#206 — compile mode swept the working directory

CompileDirectories defaulted to @(), and PowerShell treats an empty -Path as not supplied, falling back to the current location. So -Compile without the parameter concatenated every .ps1 beneath wherever the build ran into the root module. Found when a test compiled this repository's own Meta.tests.ps1 into a fixture module — the built module then failed to import while the build reported success.

The fix is a default and a guard, because they close different halves. I originally planned only the default; the research corrected that:

Both psakeFile.ps1:88 and IB.tasks.ps1:23 forward CompileDirectories unguarded. A consumer setting $PSBPreference.Build.CompileDirectories = @() binds an explicit empty array, the default never applies, and the sweep happens through the supported task path.

So the claim in #206 that task consumers can't hit this holds only for consumers who leave the setting alone.

Options rejected, with reasons:

  • [ValidateNotNullOrEmpty()] — tested, and validation attributes are not applied to default values. It leaves the omitted case broken while breaking the explicit one. Exactly backwards.
  • Throwing — compiling purely to wrap an existing .psm1 in CompileHeader/CompileFooter is a coherent request, and it's what the parameter literally says.
  • Falling back to $Path — makes an empty list mean "everything", which is how the two staging mechanisms got tangled in the first place.

Corroboration for the default value: PoshCode/ModuleBuilder's Build-Module.ps1:95-99 declares the same four names in the same order, in the function signature. PowerShellBuild was the only comparable tool whose real default lived solely in a config file while the signature declared something unusable. Upstream declined to change Get-ChildItem's null handling (PowerShell#17793, Resolution-Won't Fix), with the working group advising callers to validate — so the guard belongs here.

Compiling to an empty set now warns. That's the #201 precedent: the failure mode is a module with no functions and a green build, and a warning makes it visible without forbidding the legitimate case.

#207 — about help skipped when the culture directory existed

The Copy-Item writing about_<Module>.help.txt sat inside the Test-Path branch that creates the culture directory. CopyDirectories runs earlier, so naming the culture directory there suppressed the about help — and in compile mode that's the only way to ship a locale directory at all, so any compiled module with localized data silently suppressed its own about topic.

This isn't a behaviour decision. Copy-Item was already called with Force = $true; a misplaced closing brace made it unreachable. The author intended an unconditional overwrite. The guard now covers only the directory creation — matching Build-PSBuildMAMLHelp.ps1:59-62 character-for-character, and matching ModuleBuilder's CopyReadMe.ps1, from which this code appears to descend. The port collapsed two guards into one.

New-Item -Force (the issue's option 3) is factually sound — verified against the docs and both engines — but would make this the only directory creation in the module not using the house idiom.

Blast radius

The consumer census read 88 build files across 86 public repositories:

$PSBPreference.Build.CompileDirectories set:   0 of 86
set to @() deliberately:                       0 of 86
direct Build-PSBuildModule callers:            7  (all pass -CompileDirectories)
ConvertReadMeToAboutHelp set via $PSBPreference: 0 of 86
repos setting CopyDirectories to include en-US:  4  (none enables readme conversion)

Nobody is relying on either broken behaviour. The one theoretical regression for #206 — a direct caller who omits the parameter, runs from their source root, and keeps functions outside those four directory names — is why the warning is there.

Tests

Red-before-green on both, each failing only its own case:

The context that pinned #207's broken behaviour with Should -Not -Exist is inverted deliberately, not discovered as a failure; its comment now records what changed and why.

Verification

Full suite 591 passed, 0 failed, 3 skipped. All five touched files verified CRLF-only, no lone LF, no \r\r\n. Migration guide TOC re-verified: 18 headings, 18 links, nothing dangling.

Related, filed not fixed

The research surfaced three more defects in this same staging logic, all separate: #210 (a hand-written about topic is dropped when compiling — verified in three published packages), #211 (a culture directory's .psd1 is flattened into the output root), #212 (compile and non-compile disagree on whether the readme or a source about file wins — only visible once #207 is fixed).

🤖 Generated with Claude Code

https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE

…out help

Two defects in Build-PSBuildModule's staging, both silent, both producing a
plausible artifact from a build that reported success. Researched against
consumer usage and peer tools before choosing between the options each
issue listed.

#206 -- CompileDirectories defaulted to @(), and PowerShell treats an empty
-Path as "not supplied" and falls back to the current location. So -Compile
without the parameter concatenated every .ps1 beneath the working directory
into the root module. Found when a test compiled this repository's own test
files into a fixture module.

The fix is a default plus a guard, because they close different halves. The
default -- @('Enum', 'Classes', 'Private', 'Public') -- covers an omitted
argument and matches what the tasks already pass, what the README has always
documented, and what ModuleBuilder declares in the same position. The guard
covers an explicit empty array, which both task files forward unguarded when
a consumer sets $PSBPreference.Build.CompileDirectories = @(); that reached
the same sweep through supported configuration, so the default alone would
not have closed it.

ValidateNotNullOrEmpty was considered and rejected on evidence: validation
attributes are not applied to default values, so it leaves the omitted case
open while breaking the explicit one. Throwing was rejected because
compiling only to wrap an existing .psm1 in a header and footer is a
coherent request. Upstream declined to change Get-ChildItem's null handling
(PowerShell/PowerShell#17793, Won't Fix) with the working group advising
callers to validate, so the guard belongs here.

Compiling to an empty set now warns. That is the #201 precedent -- the
failure mode is a module with no functions and a green build, and a warning
makes it visible without forbidding the legitimate case.

#207 -- the Copy-Item writing about_<Module>.help.txt sat inside the
Test-Path branch that creates the culture directory, so an existing
directory meant no about help file at all. CopyDirectories runs earlier, and
in compile mode naming the culture directory there is the only way to ship a
locale directory, so any compiled module with localized data suppressed its
own about help.

The Force on that copy was already present and unreachable: the author
intended an unconditional overwrite and a misplaced brace prevented it. This
restores that. The shape now matches Build-PSBuildMAMLHelp and ModuleBuilder's
CopyReadMe, from which this code appears to descend -- the port collapsed two
guards into one.

The test pinning the broken behaviour is inverted deliberately, not
discovered as a failure. Its comment now records what was fixed.

Closes #206
Closes #207

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE
Copilot AI lite review requested due to automatic review settings August 28, 2026 20:18

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown

Test Results

    4 files  ± 0    889 suites  +8   2m 23s ⏱️ -15s
  596 tests + 6    593 ✅ + 6   3 💤 ±0  0 ❌ ±0 
2 367 runs  +24  2 295 ✅ +24  72 💤 ±0  0 ❌ ±0 

Results for commit ee449a9. ± Comparison against base commit b764fbb.

This pull request removes 1 and adds 7 tests. Note that renamed tests count towards both.
Build-PSBuildModule.Converting the readme when the culture directory already exists.Writes no about help file
Build-PSBuildModule.Compiling with an explicitly empty compile directory list.Compiles nothing from the current working directory
Build-PSBuildModule.Compiling with an explicitly empty compile directory list.Warns that the compiled module will contain no functions
Build-PSBuildModule.Compiling without naming the compile directories.Builds a module that exports its public functions
Build-PSBuildModule.Compiling without naming the compile directories.Compiles nothing from the current working directory
Build-PSBuildModule.Compiling without naming the compile directories.Compiles the source module functions
Build-PSBuildModule.Converting the readme when the culture directory already exists.Writes the about help file anyway
Build-PSBuildModule.Converting the readme when the culture directory already exists.Writes the readme content into it

@tablackburn
tablackburn merged commit a7741ba into main Aug 28, 2026
9 checks passed
@tablackburn
tablackburn deleted the fix/206-207-compile-defaults branch August 28, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants