Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 45 additions & 26 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,48 +70,67 @@ jobs:
fail-fast: false
matrix:
os: [ windows-latest, ubuntu-latest, macos-latest ]
shell: [ pwsh ]
include:
- os: windows-latest
shell: powershell
steps:
- name: Download build.requires.psd1
uses: actions/download-artifact@v8
with:
name: build.requires.psd1
- name: Download Build Output
uses: actions/download-artifact@v8
with:
name: ModuleBuilder
path: output/ModuleBuilder # /home/runner/work/ModuleBuilder/ModuleBuilder/output/ModuleBuilder
- name: Download Pester Tests
uses: actions/download-artifact@v8
with:
name: PesterTests
path: PesterTests
- name: Install Output Modules
# Avoid installing ModuleBuilder with ModuleFast, so there's only one copy
- name: Remove ModuleBuilder from build.requires
shell: pwsh
run: | # PowerShell
# https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-powershell#powershell-module-locations
$ModuleDestination = if ($IsWindows) {
Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'PowerShell/Modules'
} else {
Join-Path $HOME '.local/share/powershell/Modules'
}

Get-ChildItem -Directory output -OutVariable Modules
| Move-Item -Destination { Join-Path $ModuleDestination $_.Name } -Force

Write-Host "Installing $($Modules -join ', ') to $ModuleDestination"
Get-ChildItem -Directory $ModuleDestination
Write-Host "PSModulePath:"
$Env:PSModulePath -split ([IO.Path]::PathSeparator) | Out-Host

# Avoid installing ModuleBuilder with ModuleFast, so there's only one copy
@(Get-Content build.requires.psd1)
| Where { $_ -notmatch "ModuleBuilder"}
| Set-Content build.requires.psd1
@(Get-Content build.requires.psd1).Where({ $_ -notmatch "ModuleBuilder"}) | Set-Content build.requires.psd1
- name: ⚡ Install Required Modules
uses: JustinGrote/ModuleFast-action@v0.0.1
uses: JustinGrote/ModuleFast-action@v1.0.1
env:
MODULEFAST_DESTINATION: ${{ github.workspace }}/Modules
# Copy over the build output AFTER Install-ModuleFast, because it's caching my build output :(
- name: Download Build Output
uses: actions/download-artifact@v8
with:
name: ModuleBuilder
path: Modules/ModuleBuilder # /home/runner/work/ModuleBuilder/ModuleBuilder/output/ModuleBuilder
- name: Invoke-Pester
if: matrix.shell == 'powershell'
shell: powershell
env:
MODULEFAST_DESTINATION: ${{ github.workspace }}/Modules
run: | # PowerShell
$Env:PSModulePath = $Env:MODULEFAST_DESTINATION + [IO.Path]::PathSeparator + $Env:PSModulePath

# For the cross-platform matrix we don't need to do coverage or anything complicated
$Result = Invoke-Pester . -PassThru
@(
"## Pester Tests for ${{ matrix.os }}"
""
$Result.Duration.ToString()
"| Total | Passed | Failed |"
"|------:|-------:|-------:|"
"| $($Result.TotalCount) | $($Result.PassedCount) | $($Result.FailedCount) |"
""
"| Duration | Total | Passed | Failed | Skipped | Name |"
"|---------:|------:|-------:|-------:|--------:|:-----|"
@($Result.Containers).ForEach{
"| $($_.Duration) | $($_.TotalCount) | $($_.PassedCount) | $($_.FailedCount) | $($_.SkippedCount) | $($_.Name) |"
}
) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY
- name: Invoke-Pester
if: matrix.shell == 'pwsh'
shell: pwsh
env:
MODULEFAST_DESTINATION: ${{ github.workspace }}/Modules
run: | # PowerShell
$Env:PSModulePath = $Env:MODULEFAST_DESTINATION + [IO.Path]::PathSeparator + $Env:PSModulePath

# For the cross-platform matrix we don't need to do coverage or anything complicated
$Result = Invoke-Pester . -PassThru
@(
Expand Down
21 changes: 15 additions & 6 deletions Source/Private/CompressToBase64.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,21 @@ function CompressToBase64 {
process {
foreach ($File in $Path | Convert-Path) {
$Source = [System.IO.MemoryStream][System.IO.File]::ReadAllBytes($File)
$OutputStream = [System.IO.Compression.DeflateStream]::new(
[System.IO.MemoryStream]::new(),
[System.IO.Compression.CompressionMode]::Compress)
$Source.CopyTo($OutputStream)
$OutputStream.Flush()
$ByteArray = $OutputStream.BaseStream.ToArray()
# Write-Debug "Read $($Source.Length) bytes from $File"

$MemoryStream = [System.IO.MemoryStream]::new()
$DeflateStream = [System.IO.Compression.DeflateStream]::new(
$MemoryStream,
[System.IO.Compression.CompressionMode]::Compress,
$true)
$Source.CopyTo($DeflateStream)
# Framework 4.x (Windows PS) doesn't flush until we close the DeflateStream
$DeflateStream.Dispose()
$ByteArray = $MemoryStream.ToArray()
$MemoryStream.Dispose()
$Source.Dispose()
# Write-Debug "Compressed to $($ByteArray.Length) bytes"

if (!$ExpandScript) {
[Convert]::ToBase64String($ByteArray)
} else {
Expand Down
7 changes: 4 additions & 3 deletions Source/Private/SetModuleContent.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function SetModuleContent {
<#
.SYNOPSIS
A wrapper for Set-Content that handles arrays of file paths
A wrapper for Set-Content that copies lists of files
.DESCRIPTION
The implementation here is strongly dependent on Build-Module doing the right thing
Build-Module can optionally pass a PREFIX or SUFFIX, but otherwise only passes files
Expand All @@ -15,7 +15,7 @@ function SetModuleContent {
[CmdletBinding()]
param(
# Where to write the joined output
[Parameter(Position=0, Mandatory)]
[Parameter(Position = 0, Mandatory)]
[string]$OutputPath,

# Input files, the scripts that will be copied to the output path
Expand All @@ -28,12 +28,13 @@ function SetModuleContent {
# The working directory (allows relative paths for other values)
[string]$WorkingDirectory = $pwd,

# The encoding defaults to UTF8 (or UTF8NoBom on Core)
# The encoding defaults to UTF8 (or UTF8Bom on Core)
[Parameter(DontShow)]
[string]$Encoding = $(if($IsCoreCLR) { "UTF8Bom" } else { "UTF8" })
)
begin {
Write-Debug "SetModuleContent WorkingDirectory $WorkingDirectory"
Write-Debug "Encoding $Encoding"
Push-Location $WorkingDirectory -StackName SetModuleContent
$ContentStarted = $false # There has been no content yet

Expand Down
2 changes: 1 addition & 1 deletion Source/Public/Build-Module.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ function Build-Module {
Join-Path -Path $ModuleInfo.ModuleBase -ChildPath $_ | Convert-Path -ErrorAction SilentlyContinue
}

$ModuleInfo.Generators | Invoke-ScriptGenerator -Path $RootModule -Overwrite
$ModuleInfo.Generators | Invoke-ScriptGenerator -Path $RootModule -Overwrite -Encoding $ModuleInfo.Encoding


# This is mostly for testing ...
Expand Down
11 changes: 9 additions & 2 deletions Source/Public/ConvertTo-Script.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ function ConvertTo-Script {
# This is used to find the module manifest,
# But the the script will be saved in the same location
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string]$Path
[string]$Path,

# File encoding for output RootModule (defaults to UTF8)
# Converted to System.Text.Encoding for PowerShell 6 (and something else for PowerShell 5)
[ValidateSet("UTF8", "UTF8Bom", "UTF8NoBom", "UTF7", "ASCII", "Unicode", "UTF32")]
[string]$Encoding = $(if ($IsCoreCLR) { "UTF8Bom" } else { "UTF8" })
)
begin {
Write-Debug " ENTER: ConvertTo-Script BEGIN $Path $FunctionName"
Expand Down Expand Up @@ -81,6 +86,8 @@ function ConvertTo-Script {
return [AstVisitAction]::Continue
}
}

$SetContentCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Set-Content', [System.Management.Automation.CommandTypes]::Cmdlet)
Write-Debug " EXIT: ConvertTo-Script BEGIN"
}
process {
Expand Down Expand Up @@ -131,7 +138,7 @@ function ConvertTo-Script {
Get-Item $Path
) | CompressToBase64 -ExpandScriptName ImportBase64Module
"$FunctionName @PSBoundParameters"
) | Set-Content "$FunctionName.ps1"
) | & $SetContentCmd -Path "$FunctionName.ps1" -Encoding $Encoding

Update-ScriptFileInfo "$FunctionName.ps1" -Version $Manifest.ModuleVersion -Author $Manifest.Author -CompanyName $Manifest.CompanyName -Copyright $Manifest.Copyright -Tags $Manifest.PrivateData.PSData.Tags -ProjectUri $Manifest.PrivateData.PSData.ProjectUri -LicenseUri $Manifest.PrivateData.PSData.LicenseUri -IconUri $Manifest.PrivateData.PSData.IconUri -ReleaseNotes $Manifest.PrivateData.PSData.ReleaseNotes

Expand Down
20 changes: 14 additions & 6 deletions Source/Public/Invoke-ScriptGenerator.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,21 @@ function Invoke-ScriptGenerator {

# If set, will overwrite the Source with the generated content.
# Use with care, as this will modify the source file!
[switch]$Overwrite
[switch]$Overwrite,

# The encoding defaults to UTF8 (or UTF8Bom on Core)
[Parameter()]
[string]$Encoding = $(if($IsCoreCLR) { "UTF8Bom" } else { "UTF8" })
)
begin {
$AstParam = @{} + $PSBoundParameters
$null = $AstParam.Remove("Overwrite")
$null = $AstParam.Remove("Generator")
$null = $AstParam.Remove("Parameters")
$null = $AstParam.Remove("Encoding")
$ParseResults = ConvertToAst @AstParam
[StringBuilder]$Builder = $ParseResults.Ast.Extent.Text
$SetContentCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Set-Content', [System.Management.Automation.CommandTypes]::Cmdlet)
}
process {
if (-not $PSBoundParameters.ContainsKey("Generator") -and $Parameters.ContainsKey("Generator")) {
Expand Down Expand Up @@ -114,9 +120,11 @@ function Invoke-ScriptGenerator {
}

# Find that generator...
$GeneratorCmd = Get-Command -Name ${Generator} -ParameterType Ast -ErrorAction Ignore <# -CommandType Function #>
| Where-Object { $_.OutputType.Name -eq "TextReplacement" -or ($_.CommandType -eq "Alias" -and $_.Definition -like "PesterMock*" ) }
| Select-Object -First 1
$GeneratorCmd = Get-Command -Name ${Generator} -ParameterType Ast -ErrorAction Ignore <# -CommandType Function #> |
Where-Object {
$_.OutputType.Name -eq "TextReplacement" -or ($_.CommandType -eq "Alias" -and $_.Definition -like "PesterMock*" )
} |
Select-Object -First 1

if (-not $GeneratorCmd) {
Write-Error "Generator missconfiguration. Unable to find Generator = '$Generator'"
Expand All @@ -135,14 +143,14 @@ function Invoke-ScriptGenerator {
$ParseResults = ConvertToAst -Code $Builder.ToString() -Path $ParseResults.Path
# In case a Generator tries to use the actual files, update the content
if ($Overwrite -and $ParseResults.Path -and $ParseResults.Path -ne "scriptblock") {
Set-Content $ParseResults.Path $Builder
& $SetContentCmd -Path $ParseResults.Path -Value $Builder -Encoding $Encoding
}
}
}
end {
Write-Debug "Overwrite: $Overwrite and it's a file: $(([bool]$ParseResults.Path) -and $ParseResults.Path -ne "scriptblock") (Content is $($Builder.Length) long)"
if ($Overwrite -and $ParseResults.Path -and $ParseResults.Path -ne "scriptblock") {
Set-Content $ParseResults.Path $Builder
& $SetContentCmd -Path $ParseResults.Path -Value $Builder -Encoding $Encoding
} else {
$Builder.ToString()
}
Expand Down
33 changes: 30 additions & 3 deletions Source/Public/Update-AliasesToExport.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ function Update-AliasesToExport {

# The path to the module manifest that should contain the aliases
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string]$ModuleManifest
[string]$ModuleManifest,

# Controls what to set AliasesToExport to when no aliases are found by static analysis.
# DoNotSet: (default) leave the manifest unchanged.
# Wildcard: set AliasesToExport = '*'.
# EmptyArray: set AliasesToExport = @().
[ValidateSet("DoNotSet", "Wildcard", "EmptyArray")]
[string]$WhenNoAliases = "DoNotSet"
)
begin {
# This is used only to parse the parameters to New|Set|Remove-Alias
Expand Down Expand Up @@ -141,9 +148,29 @@ function Update-AliasesToExport {
}
}
process {
$null = Get-Metadata -Path $ModuleManifest -PropertyName AliasesToExport -ErrorAction SilentlyContinue -ErrorVariable Failed
if ($Failed) {
Write-Warning "Can't update AliasesToExport in '$ModuleManifest' unless it's already set."
return
}

$Visitor = [AliasExportGenerator]::new()
$ScriptModule.Visit($Visitor)
Update-Metadata -Path $ModuleManifest -PropertyName AliasesToExport -Value $Visitor.Aliases -WhatIf:$WhatIfPreference -Confirm:($ConfirmPreference -eq 'Low')
if ($Visitor.Aliases.Count -gt 0) {
$newValue = $Visitor.Aliases
} else {
switch ($WhenNoAliases) {
"DoNotSet" {
return
}
"Wildcard" {
$newValue = '*'
}
"EmptyArray" {
$newValue = @()
}
}
}
Update-Metadata -Path $ModuleManifest -PropertyName AliasesToExport -Value $newValue -WhatIf:$WhatIfPreference -Confirm:($ConfirmPreference -eq 'Low')
}
}

11 changes: 5 additions & 6 deletions Tests/Integration/Parameters.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ Describe "Parameters" -Tag Integration {
New-Item $PSScriptRoot/Result3/Parameters/3.0.0/DeleteMe.md -ItemType File -Force

Write-Host "Module Under Test:"
Get-Command Build-Module
| Get-Module -Name { $_.Source }
| Get-Item
| Out-Host
Get-Command Build-Module |
Get-Module -Name { $_.Source } |
Get-Item |
Out-Host
}

It "Passthru is read from the build manifest" {
Build-Module (Convert-FolderSeparator "$PSScriptRoot/Parameters/build.psd1") -Verbose -OutVariable Output
| Out-Host
Build-Module (Convert-FolderSeparator "$PSScriptRoot/Parameters/build.psd1") -Verbose -OutVariable Output | Out-Host

$Output | Should -Not -BeNullOrEmpty
$Output.Path | Convert-FolderSeparator | Should -Be (Convert-FolderSeparator "$PSScriptRoot/Result3/Parameters/3.0.0/Parameters.psd1")
Expand Down
8 changes: 4 additions & 4 deletions Tests/Private/CompressToBase64.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Describe "CompressToBase64" {
Context "It compresses and encodes a file for embedding into a script" {
BeforeAll {
$Base64 = InModuleScope ModuleBuilder {
CompressToBase64 $PSCommandPath
CompressToBase64 (Join-Path $PSScriptRoot CompressToBase64.Tests.ps1)
}
}

Expand All @@ -22,14 +22,14 @@ Describe "CompressToBase64" {
$OutputStream.Seek(0, "Begin")
$Source = [System.IO.StreamReader]::new($OutputStream, $true).ReadToEnd()

$Source | Should -Be (Get-Content $PSCommandPath -Raw)
$Source | Should -Be (Get-Content (Join-Path $PSScriptRoot CompressToBase64.Tests.ps1) -Raw)
}
}

Context "It wraps the Base64 encoded content in the specified command" {
BeforeAll {
$Base64 = InModuleScope ModuleBuilder {
CompressToBase64 $PSCommandPath -ExpandScriptName ImportBase64Module
CompressToBase64 (Join-Path $PSScriptRoot CompressToBase64.Tests.ps1) -ExpandScriptName ImportBase64Module
}
}

Expand All @@ -49,7 +49,7 @@ Describe "CompressToBase64" {
Context "It wraps the Base64 encoded content in the specified scriptblock" {
BeforeAll {
$Base64 = InModuleScope ModuleBuilder {
Get-ChildItem $PSCommandPath | CompressToBase64 -ExpandScript { ImportBase64Module }
Get-ChildItem (Join-Path $PSScriptRoot CompressToBase64.Tests.ps1) | CompressToBase64 -ExpandScript { ImportBase64Module }
}
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/Private/ConvertToAst.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Describe "ConvertToAst" {
Context "It returns a ParseResult for file paths" {
BeforeAll {
$ParseResult = InModuleScope ModuleBuilder {
ConvertToAst -Code $PSCommandPath
ConvertToAst -Code (Join-Path $PSScriptRoot ConvertToAst.Tests.ps1)
}
}

Expand Down
8 changes: 4 additions & 4 deletions Tests/Private/InitializeBuild.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ Describe "InitializeBuild" {
$Result.Result.Name | Should -Be "MyModule"
$Result.Result.SourceDirectories | Should -Be @("Classes", "Private", "Public")

(Convert-FolderSeparator $Result.Result.ModuleBase)
| Should -Be (Convert-FolderSeparator "TestDrive:\Source")
(Convert-FolderSeparator $Result.Result.ModuleBase) |
Should -Be (Convert-FolderSeparator "TestDrive:\Source")

(Convert-FolderSeparator $Result.Result.SourcePath)
| Should -Be (Convert-FolderSeparator "TestDrive:\Source\MyModule.psd1")
(Convert-FolderSeparator $Result.Result.SourcePath) |
Should -Be (Convert-FolderSeparator "TestDrive:\Source\MyModule.psd1")
}

It "Returns default values from the Build Command" {
Expand Down
Loading
Loading