Skip to content

Commit a2e8a0e

Browse files
committed
Fix #39 by adding documentation
To make -Target Clean safe, change output calculation and add ModuleName as suggested in #65
1 parent 32ea855 commit a2e8a0e

File tree

7 files changed

+260
-142
lines changed

7 files changed

+260
-142
lines changed
Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,79 @@
11
function ResolveOutputFolder {
22
[CmdletBinding()]
33
param(
4+
# The name of the module to build
5+
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
6+
[Alias("Name")]
7+
[string]$ModuleName,
8+
9+
# Where to resolve the $OutputDirectory from when relative
10+
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
11+
[Alias("ModuleBase")]
12+
[string]$Source,
13+
414
# Where to build the module.
515
# Defaults to an \output folder, adjacent to the "SourcePath" folder
616
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
717
[string]$OutputDirectory,
818

9-
# If set (true) adds a folder named after the version number to the OutputDirectory
10-
[Parameter(ValueFromPipelineByPropertyName)]
11-
[switch]$VersionedOutputDirectory,
12-
1319
# specifies the module version for use in the output path if -VersionedOutputDirectory is true
14-
[Parameter(ValueFromPipelineByPropertyName)]
20+
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
1521
[Alias("ModuleVersion")]
1622
[string]$Version,
1723

18-
# Where to resolve the $OutputDirectory from when relative
24+
# If set (true) adds a folder named after the version number to the OutputDirectory
25+
[Parameter(ValueFromPipelineByPropertyName)]
26+
[Alias("Force")]
27+
[switch]$VersionedOutputDirectory,
28+
29+
# Controls whether or not there is a build or cleanup performed
1930
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
20-
[string]$ModuleBase
31+
[ValidateSet("Clean", "Build", "CleanBuild")]
32+
[string]$Target = "CleanBuild"
2133
)
2234
process {
2335
Write-Verbose "Resolve OutputDirectory path: $OutputDirectory"
2436

2537
# Ensure the OutputDirectory makes sense (it's never blank anymore)
2638
if (!(Split-Path -IsAbsolute $OutputDirectory)) {
2739
# Relative paths are relative to the ModuleBase
28-
$OutputDirectory = Join-Path $ModuleBase $OutputDirectory
40+
$OutputDirectory = Join-Path $Source $OutputDirectory
41+
}
42+
# If they passed in a path with ModuleName\Version on the end...
43+
if ((Split-Path $OutputDirectory -Leaf).EndsWith($Version) -and (Split-Path (Split-Path $OutputDirectory) -Leaf) -eq $ModuleName) {
44+
# strip the version (so we can add it back)
45+
$VersionedOutputDirectory = $true
46+
$OutputDirectory = Split-Path $OutputDirectory
47+
}
48+
# Ensure the OutputDirectory is named "ModuleName"
49+
if ((Split-Path $OutputDirectory -Leaf) -ne $ModuleName) {
50+
# If it wasn't, add a "ModuleName"
51+
$OutputDirectory = Join-Path $OutputDirectory $ModuleName
52+
}
53+
# Ensure the OutputDirectory is not a parent of the SourceDirectory
54+
if (-not [io.path]::GetRelativePath($OutputDirectory, $Source).StartsWith("..")) {
55+
Write-Verbose "Added Version to OutputDirectory path: $OutputDirectory"
56+
$OutputDirectory = Join-Path $OutputDirectory $Version
57+
}
58+
# Ensure the version number is on the OutputDirectory if it's supposed to be
59+
if ($VersionedOutputDirectory -and -not (Split-Path $OutputDirectory -Leaf).EndsWith($Version)) {
60+
Write-Verbose "Added Version to OutputDirectory path: $OutputDirectory"
61+
$OutputDirectory = Join-Path $OutputDirectory $Version
2962
}
3063

31-
# Make sure the OutputDirectory exists (relative to ModuleBase or absolute)
32-
$OutputDirectory = New-Item $OutputDirectory -ItemType Directory -Force | Convert-Path
33-
if ($VersionedOutputDirectory -and $OutputDirectory.TrimEnd("/\") -notmatch "\d+\.\d+\.\d+$") {
34-
$OutputDirectory = New-Item (Join-Path $OutputDirectory $Version) -ItemType Directory -Force | Convert-Path
35-
Write-Verbose "Added ModuleVersion to OutputDirectory path: $OutputDirectory"
64+
if (Test-Path $OutputDirectory -PathType Leaf) {
65+
throw "Unable to build. There is a file in the way at $OutputDirectory"
66+
}
67+
68+
if ($Target -match "Clean") {
69+
Write-Verbose "Cleaning $OutputDirectory"
70+
if (Test-Path $OutputDirectory -PathType Container) {
71+
Remove-Item $OutputDirectory -Recurse -Force
72+
}
73+
}
74+
if ($Target -match "Build") {
75+
# Make sure the OutputDirectory exists (relative to ModuleBase or absolute)
76+
New-Item $OutputDirectory -ItemType Directory -Force | Convert-Path
3677
}
37-
$OutputDirectory
3878
}
39-
}
79+
}

Source/Public/Build-Module.ps1

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,12 @@ function Build-Module {
5353
[Alias("ModuleManifest", "Path")]
5454
[string]$SourcePath = $(Get-Location -PSProvider FileSystem),
5555

56-
# Where to build the module.
57-
# Defaults to an ..\output folder (adjacent to the "SourcePath" folder)
56+
# Where to build the module. Defaults to "..\Output" adjacent to the "SourcePath" folder.
57+
# The actual OutputDirectory may be a subfolder of this, because the path always ends with \ModuleName or \ModuleName\Version
58+
# -OutputDirectory ..\Output, the actual path is ..\Output\ModuleName or ..\Output\ModuleName\1.2.3
59+
# -OutputDirectory ..\, the actual path is ..\Output\ModuleName or ..\Output\ModuleName\1.2.3
60+
# -OutputDirectory ..\b\10.2, the actual path is ..\b\10.2\ModuleName\1.2.3
61+
# -OutputDirectory ..\b\ModuleName\1.2.3, the actual path is ..\b\ModuleName\1.2.3
5862
[Alias("Destination")]
5963
[string]$OutputDirectory = "..\Output",
6064

@@ -122,7 +126,14 @@ function Build-Module {
122126
[Alias("ExportModuleMember","Postfix")]
123127
[string]$Suffix,
124128

125-
# Controls whether or not there is a build or cleanup performed
129+
# Controls whether we delete the output folder and whether we build the output
130+
# There are three options:
131+
# - Clean deletes the build output folder
132+
# - Build builds the module output
133+
# - CleanBuild first deletes the build output folder and then builds the module back into it
134+
# Note that if you specify -VersionedOutputDirectory, the folder to be deleted is the version folder
135+
# For -OutputDirectory ..\Output -SemVer 1.2.3 -Versioned the path to clean is ..\Output\ModuleName\1.2.3
136+
# For -OutputDirectory ..\, the path to clean is ..\Output\ModuleName (the ..\Output path wouldn't be removed)
126137
[ValidateSet("Clean", "Build", "CleanBuild")]
127138
[string]$Target = "CleanBuild",
128139

@@ -154,41 +165,29 @@ function Build-Module {
154165
Write-Progress "Building $($ModuleInfo.Name)" -Status "Use -Verbose for more information"
155166
Write-Verbose "Building $($ModuleInfo.Name)"
156167

157-
# Output file names
168+
# Ensure the OutputDirectory (exists for build, or is cleaned otherwise)
158169
$OutputDirectory = $ModuleInfo | ResolveOutputFolder
170+
if ($Target -notmatch "Build") {
171+
return
172+
}
159173
$RootModule = Join-Path $OutputDirectory "$($ModuleInfo.Name).psm1"
160174
$OutputManifest = Join-Path $OutputDirectory "$($ModuleInfo.Name).psd1"
161175
Write-Verbose "Output to: $OutputDirectory"
162176

163-
if ($Target -match "Clean") {
164-
Write-Verbose "Cleaning $OutputDirectory"
165-
if (Test-Path $OutputDirectory -PathType Leaf) {
166-
throw "Unable to build. There is a file in the way at $OutputDirectory"
167-
}
168-
if (Test-Path $OutputDirectory -PathType Container) {
169-
if (Get-ChildItem $OutputDirectory\*) {
170-
Remove-Item $OutputDirectory\* -Recurse -Force
171-
}
172-
}
173-
if ($Target -notmatch "Build") {
174-
return # No build, just cleaning
175-
}
176-
} else {
177-
# If we're not cleaning, skip the build if it's up to date already
178-
Write-Verbose "Target $Target"
179-
$NewestBuild = (Get-Item $RootModule -ErrorAction SilentlyContinue).LastWriteTime
180-
$IsNew = Get-ChildItem $ModuleInfo.ModuleBase -Recurse |
181-
Where-Object LastWriteTime -gt $NewestBuild |
182-
Select-Object -First 1 -ExpandProperty LastWriteTime
183-
if ($null -eq $IsNew) {
184-
# This is mostly for testing ...
185-
if ($Passthru) {
186-
Get-Module $OutputManifest -ListAvailable
187-
}
188-
return # Skip the build
177+
# Skip the build if it's up to date already
178+
Write-Verbose "Target $Target"
179+
$NewestBuild = (Get-Item $RootModule -ErrorAction SilentlyContinue).LastWriteTime
180+
$IsNew = Get-ChildItem $ModuleInfo.ModuleBase -Recurse |
181+
Where-Object LastWriteTime -gt $NewestBuild |
182+
Select-Object -First 1 -ExpandProperty LastWriteTime
183+
184+
if ($null -eq $IsNew) {
185+
# This is mostly for testing ...
186+
if ($Passthru) {
187+
Get-Module $OutputManifest -ListAvailable
189188
}
189+
return # Skip the build
190190
}
191-
$null = New-Item -ItemType Directory -Path $OutputDirectory -Force
192191

193192
# Note that the module manifest parent folder is the "root" of the source directories
194193
Push-Location $ModuleInfo.ModuleBase -StackName Build-Module

Source/Public/Convert-CodeCoverage.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ function Convert-CodeCoverage {
22
<#
33
.SYNOPSIS
44
Convert the file name and line numbers from Pester code coverage of "optimized" modules to the source
5+
.DESCRIPTION
6+
Converts the code coverage line numbers from Pester to the source file paths.
7+
The returned file name is always the relative path stored in the module.
58
.EXAMPLE
69
Invoke-Pester .\Tests -CodeCoverage (Get-ChildItem .\Output -Filter *.psm1).FullName -PassThru |
710
Convert-CodeCoverage -SourceRoot .\Source -Relative
Lines changed: 105 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,128 @@
11
#requires -Module ModuleBuilder
22
Describe "ResolveOutputFolder" {
3-
4-
Context "Given an OutputDirectory and ModuleBase" {
5-
6-
$Result = InModuleScope -ModuleName ModuleBuilder {
7-
ResolveOutputFolder -OutputDirectory TestDrive:\Output -ModuleBase TestDrive:\Source
3+
$CommandInTest = InModuleScope ModuleBuilder { Get-Command ResolveOutputFolder }
4+
filter ToTestDrive { "$_".Replace($TestDrive, "TestDrive:") }
5+
6+
$TestCases = [Hashtable[]]@(
7+
@{ Source = "Source"
8+
Output = "Output"
9+
Result = "Output\ModuleName"
10+
Forced = "Output\ModuleName\1.2.3"
811
}
9-
10-
It "Creates the Output directory" {
11-
$Result | Should -Be (Convert-Path "TestDrive:\Output")
12-
$Result | Should -Exist
12+
@{ Output = "ModuleName\Output"
13+
Source = "ModuleName\Source"
14+
Result = "ModuleName\Output\ModuleName"
15+
Forced = "ModuleName\Output\ModuleName\1.2.3"
1316
}
14-
}
15-
16-
Context "Given an OutputDirectory, ModuleBase and ModuleVersion but no switch" {
17-
18-
$Result = InModuleScope -ModuleName ModuleBuilder {
19-
ResolveOutputFolder -OutputDirectory TestDrive:\Output -ModuleVersion "1.0.0" -ModuleBase TestDrive:\Source
17+
@{ # Be like Jaykul
18+
Source = "ModuleName\Source"
19+
Output = "ModuleName"
20+
Result = "ModuleName\1.2.3"
21+
Forced = "ModuleName\1.2.3"
2022
}
21-
22-
It "Creates the Output directory" {
23-
"TestDrive:\Output" | Should -Exist
23+
@{ # Be like azure
24+
Source = "1\s"
25+
Output = "1\b"
26+
Result = "1\b\ModuleName"
27+
Forced = "1\b\ModuleName\1.2.3"
2428
}
25-
26-
It "Returns the Output directory" {
27-
$Result | Should -Be (Convert-Path "TestDrive:\Output")
29+
@{ # An edge case, build straight to a modules folder
30+
Source = "ModuleName\Source"
31+
Output = "Modules"
32+
Result = "Modules\ModuleName"
33+
Forced = "Modules\ModuleName\1.2.3"
2834
}
29-
30-
It "Does not creates children in Output" {
31-
Get-ChildItem $Result | Should -BeNullOrEmpty
35+
@{ # What if they pass in the correct path ahead of time?
36+
Source = "1\s"
37+
Output = "1\b\ModuleName"
38+
Result = "1\b\ModuleName"
39+
Forced = "1\b\ModuleName\1.2.3"
3240
}
33-
}
34-
35-
Context "Given an OutputDirectory, ModuleBase, ModuleVersion and switch" {
36-
37-
$Result = InModuleScope -ModuleName ModuleBuilder {
38-
ResolveOutputFolder -OutputDirectory TestDrive:\Output -ModuleVersion "1.0.0" -VersionedOutput -ModuleBase TestDrive:\Source
41+
@{ # What if they pass in the correct path ahead of time?
42+
Source = "1\s"
43+
Output = "1\b\ModuleName\1.2.3"
44+
Result = "1\b\ModuleName\1.2.3"
45+
Forced = "1\b\ModuleName\1.2.3"
3946
}
40-
41-
It "Creates the Output directory" {
42-
"TestDrive:\Output" | Should -Exist
47+
@{ # Super edge case: what if they pass in an incorrectly versioned output path?
48+
Source = "1\s"
49+
Output = "1\b\ModuleName\4.5.6"
50+
Result = "1\b\ModuleName\4.5.6\ModuleName"
51+
Forced = "1\b\ModuleName\4.5.6\ModuleName\1.2.3"
4352
}
44-
45-
It "Creates the version directory" {
46-
"TestDrive:\Output\1.0.0" | Should -Exist
53+
)
54+
Context "Build ModuleName" {
55+
It "From '<Source>' to '<Output>' creates '<Result>'" -TestCases $TestCases {
56+
param($Source, $Output, $Result)
57+
58+
$Parameters = @{
59+
Source = "TestDrive:\$Source"
60+
Output = "TestDrive:\$Output"
61+
}
62+
63+
$Actual = &$CommandInTest @Parameters -Name ModuleName -Target Build -Version 1.2.3 | ToTestDrive
64+
$Actual | Should -Exist
65+
$Actual | Should -Be "TestDrive:\$Result"
4766
}
4867

49-
It "Returns the Output directory" {
50-
$Result | Should -Be (Convert-Path "TestDrive:\Output\1.0.0")
68+
It "From '<Source>' to '<Output>' -ForceVersion creates '<Forced>'" -TestCases $TestCases {
69+
param($Source, $Output, $Forced)
70+
71+
$Parameters = @{
72+
Source = "TestDrive:\$Source"
73+
Output = "TestDrive:\$Output"
74+
}
75+
76+
$Actual = &$CommandInTest @Parameters -Name ModuleName -Target Build -Version 1.2.3 -Force | ToTestDrive
77+
$Actual | Should -Exist
78+
$Actual | Should -Be "TestDrive:\$Forced"
5179
}
5280
}
81+
Context "Cleaned ModuleName" {
82+
It "From '<Source>' to '<Output>' deletes '<Result>'" -TestCases $TestCases {
83+
param($Source, $Output, $Result)
84+
85+
$Parameters = @{
86+
Source = "TestDrive:\$Source"
87+
Output = "TestDrive:\$Output"
88+
}
89+
90+
$null = mkdir "TestDrive:\$Result" -Force
91+
92+
# There's no output when we're cleaning
93+
&$CommandInTest @Parameters -Name ModuleName -Version 1.2.3 -Target Clean | Should -BeNullOrEmpty
94+
"TestDrive:\$Result" | Should -Not -Exist
95+
# NOTE: This is only true because we made it above
96+
"TestDrive:\$Result" | Split-Path | Should -Exist
97+
}
5398

54-
Context "Given a relative OutputDirectory, the Folder is created relative to ModuleBase" {
99+
It "From '<Source>' to '<Output>' -ForceVersion deletes '<Forced>'" -TestCases $TestCases {
100+
param($Source, $Output, $Forced)
55101

102+
$Parameters = @{
103+
Source = "TestDrive:\$Source"
104+
Output = "TestDrive:\$Output"
105+
}
106+
$null = mkdir "TestDrive:\$Forced" -Force
56107

57-
$Result = InModuleScope -ModuleName ModuleBuilder {
58-
ResolveOutputFolder -OutputDirectory '..\Output' -ModuleBase TestDrive:\Source
108+
# There's no output when we're cleaning
109+
&$CommandInTest @Parameters -Name ModuleName -Version 1.2.3 -Force -Target Clean | Should -BeNullOrEmpty
110+
"TestDrive:\$Forced" | Should -Not -Exist
111+
# NOTE: This is only true because we made it above
112+
"TestDrive:\$Forced" | Split-Path | Should -Exist
59113
}
114+
}
115+
Context "Error Cases" {
116+
It "Won't remove a file that blocks the folder path" {
117+
$null = mkdir TestDrive:\ModuleName\Source -force
118+
New-Item TestDrive:\ModuleName\1.2.3 -ItemType File -Value "Hello World"
60119

61-
It "Returns an absolute FileSystem path" {
62-
{ [io.path]::IsPathRooted($Result) } | Should -Not -Throw
63-
}
120+
$Parameters = @{
121+
Source = "TestDrive:\ModuleName\Source"
122+
Output = "TestDrive:\ModuleName\"
123+
}
64124

65-
It "has created the Folder" {
66-
(Test-Path $Result) | Should -Be $True
125+
{ &$CommandInTest @Parameters -Name ModuleName -Target Build -Version 1.2.3 -Force } | Should -throw "There is a file in the way"
67126
}
68-
69127
}
70128
}

0 commit comments

Comments
 (0)