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
23 changes: 22 additions & 1 deletion .github/workflows/BuildModule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,33 @@ jobs:
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'NuGet.config', '**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-

- name: Verify net472 compatibility
shell: pwsh
timeout-minutes: 10
run: |
dotnet build .\PowerForge\PowerForge.csproj -c Release -f net472 --nologo
dotnet build .\PowerForge.PowerShell\PowerForge.PowerShell.csproj -c Release -f net472 --nologo
dotnet build .\PSPublishModule\PSPublishModule.csproj -c Release -f net472 --nologo

- name: Run net472 C# smoke tests
shell: pwsh
timeout-minutes: 10
run: dotnet test .\PowerForge.Net472SmokeTests\PowerForge.Net472SmokeTests.csproj -c Release -f net472 --nologo

- name: Build Module
shell: pwsh
timeout-minutes: 10
run: .\Build\Build-Module.ps1

- name: Test with Pester
- name: Test with Windows PowerShell 5.1
shell: pwsh
timeout-minutes: 10
run: |
& "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File .\PSPublishModule.Tests.ps1
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Test with PowerShell 7
shell: pwsh
timeout-minutes: 10
run: .\PSPublishModule.Tests.ps1
Expand Down
27 changes: 24 additions & 3 deletions Module/Build/Build-Module.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,30 @@ Get-Module -Name 'PSPublishModule' -All -ErrorAction SilentlyContinue | Remove-M

$importPath = $null

# The source manifest bootstrap requires Module\Lib to exist. Self-builds generate the
# pipeline config before staged module libraries are present, so prefer the compiled DLL then.
if (Test-Path -LiteralPath $sourceLibRoot) {
# Json-only and no-dotnet-build flows should avoid importing the source manifest from Module\Lib.
# Once a PowerShell session loads those repo DLLs on Windows, later self-build attempts cannot
# refresh them in-place. Import the compiled binary module instead for config generation.
$preferBinaryImport = $JsonOnly -or $NoDotnetBuild

if ($preferBinaryImport) {
if (-not (Test-Path -LiteralPath $binaryModule)) {
if (Test-Path -LiteralPath $csproj) {
$i = [char]0x2139 # ℹ
Write-Host "$i Building PSPublishModule ($Configuration)" -ForegroundColor DarkGray
$buildOutput = & dotnet build $csproj -c $Configuration --nologo --verbosity quiet 2>&1
if ($LASTEXITCODE -ne 0) {
$buildOutput | Out-Host
throw "dotnet build failed (exit $LASTEXITCODE)."
}
}
}

if (Test-Path -LiteralPath $binaryModule) {
$importPath = $binaryModule
}
} elseif (Test-Path -LiteralPath $sourceLibRoot) {
# Keep the source-manifest path for regular repo builds where Module\Lib is intentionally populated.
# Build-ModuleSelf now prefers binary import to avoid in-place refreshes of loaded repo DLLs.
$importPath = $sourceManifest
} else {
if (-not (Test-Path -LiteralPath $binaryModule)) {
Expand Down
33 changes: 0 additions & 33 deletions Module/Build/Build-ModuleSelf.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,6 @@ $moduleProject = Join-Path -Path $repoRoot -ChildPath 'PSPublishModule\PSPublish
if (-not (Test-Path -LiteralPath $cliProject)) { throw "PowerForge.Cli project not found: $cliProject" }
if (-not (Test-Path -LiteralPath $moduleProject)) { throw "PSPublishModule project not found: $moduleProject" }

function Sync-LocalModuleLib {
param(
[Parameter(Mandatory)][string] $RepoRoot,
[Parameter(Mandatory)][string] $ConfigurationName,
[Parameter(Mandatory)][string] $PrimaryFramework
)

$moduleLibRoot = Join-Path -Path $RepoRoot -ChildPath 'Module\Lib'
New-Item -Path $moduleLibRoot -ItemType Directory -Force | Out-Null

$frameworkMappings = @(
@{ Output = $PrimaryFramework; Folder = 'Core' }
@{ Output = 'net472'; Folder = 'Default' }
)

foreach ($mapping in $frameworkMappings) {
$outputPath = Join-Path -Path $RepoRoot -ChildPath ("PSPublishModule\bin\{0}\{1}" -f $ConfigurationName, $mapping.Output)
if (-not (Test-Path -LiteralPath $outputPath)) {
continue
}

$targetPath = Join-Path -Path $moduleLibRoot -ChildPath $mapping.Folder
if (Test-Path -LiteralPath $targetPath) {
Remove-Item -LiteralPath $targetPath -Recurse -Force -ErrorAction SilentlyContinue
}

New-Item -Path $targetPath -ItemType Directory -Force | Out-Null
Copy-Item -Path (Join-Path -Path $outputPath -ChildPath '*') -Destination $targetPath -Recurse -Force
}
}

if ($Framework -eq 'auto') {
$runtimesText = (dotnet --list-runtimes 2>$null) -join "`n"
$Framework = if ($runtimesText -match '(?m)^Microsoft\\.NETCore\\.App\\s+10\\.') { 'net10.0' } else { 'net8.0' }
Expand Down Expand Up @@ -118,8 +87,6 @@ if (-not $NoBuild) {
if ($LASTEXITCODE -ne 0) { $moduleOutput | Out-Host; exit $LASTEXITCODE }
Write-Host "$ok Built PSPublishModule ($Framework, $Configuration)" -ForegroundColor Green
}

Sync-LocalModuleLib -RepoRoot $repoRoot -ConfigurationName $Configuration -PrimaryFramework $Framework
}

$cliDir = Join-Path -Path $repoRoot -ChildPath ("PowerForge.Cli\\bin\\{0}\\{1}" -f $Configuration, $Framework)
Expand Down
99 changes: 66 additions & 33 deletions Module/PSPublishModule.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
$script:SourceRoot = $PSScriptRoot
$script:CopiedSourceLib = $false

function Test-ModulePayloadUsableForCurrentHost {
param(
[Parameter(Mandatory)][string] $ModuleRoot
)

$libRoot = Join-Path $ModuleRoot 'Lib'
if (-not (Test-Path -LiteralPath $libRoot -PathType Container)) {
return $false
}

$directories = Get-ChildItem -Path $libRoot -Directory -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name
$hasCore = $directories -contains 'Core'
$hasDefault = $directories -contains 'Default'
$hasStandard = $directories -contains 'Standard'

if ($PSEdition -eq 'Core') {
return $hasCore -or $hasStandard
}

return $hasDefault -or $hasStandard
}

function Get-ModulePayloadRootForTests {
param(
Expand All @@ -11,49 +32,61 @@ function Get-ModulePayloadRootForTests {
throw "Path $SourceRoot doesn't contain a PSD1 file. Failing tests."
}

$sourceLibRoot = Join-Path $SourceRoot 'Lib'
$hasSourceLibraries = (Test-Path -LiteralPath $sourceLibRoot -PathType Container) -and
(Get-ChildItem -Path $sourceLibRoot -Directory -ErrorAction SilentlyContinue | Select-Object -First 1)
if ($hasSourceLibraries) {
return $SourceRoot
}

$moduleName = $sourceManifest.BaseName
$installedModule = Get-Module -ListAvailable -Name $moduleName |
Sort-Object Version -Descending |
Where-Object { $_.ModuleBase -and $_.ModuleBase -ne $SourceRoot } |
Select-Object -First 1
if ($installedModule) {
return $installedModule.ModuleBase
}

$artefactModule = Get-ChildItem -Path (Join-Path $SourceRoot 'Artefacts\Unpacked') -Filter '*.psd1' -File -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.BaseName -eq $moduleName } |
Sort-Object FullName -Descending |
Sort-Object -Property @(
@{ Expression = 'LastWriteTimeUtc'; Descending = $true },
@{ Expression = 'FullName'; Descending = $true }
) |
Select-Object -First 1
if ($artefactModule) {
return Split-Path -Path $artefactModule.FullName -Parent
$artefactRoot = Split-Path -Path $artefactModule.FullName -Parent
if (Test-ModulePayloadUsableForCurrentHost -ModuleRoot $artefactRoot) {
return $artefactRoot
}
}

return $SourceRoot
}
if (Test-ModulePayloadUsableForCurrentHost -ModuleRoot $SourceRoot) {
return $SourceRoot
}

$PayloadRoot = Get-ModulePayloadRootForTests -SourceRoot $script:SourceRoot
$sourceLibRoot = Join-Path $script:SourceRoot 'Lib'
$payloadLibRoot = Join-Path $PayloadRoot 'Lib'
if ($env:PSPUBLISHMODULE_TEST_ALLOW_INSTALLED_FALLBACK -eq '1') {
$installedModule = Get-Module -ListAvailable -Name $moduleName |
Sort-Object Version -Descending |
Where-Object { $_.ModuleBase -and $_.ModuleBase -ne $SourceRoot } |
Select-Object -First 1
if ($installedModule) {
Write-Warning "Falling back to installed module payload for tests: $($installedModule.ModuleBase)"
return $installedModule.ModuleBase
}
}

if (($PayloadRoot -ne $script:SourceRoot) -and (Test-Path -LiteralPath $payloadLibRoot -PathType Container) -and (-not (Test-Path -LiteralPath $sourceLibRoot))) {
Copy-Item -Path $payloadLibRoot -Destination $sourceLibRoot -Recurse -Force
$script:CopiedSourceLib = $true
throw "No usable module payload found for $moduleName on PowerShell edition '$PSEdition'. Build the module first so the host-compatible payload exists."
}

$ModuleRoot = $script:SourceRoot
$PrimaryModule = Get-ChildItem -Path $ModuleRoot -Filter '*.psd1' -File -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $PrimaryModule) {
throw "Path $ModuleRoot doesn't contain a PSD1 file. Failing tests."
function Get-ResolvedModuleManifestPath {
param(
[Parameter(Mandatory)][string] $ModuleRoot
)

$manifestPath = Get-ChildItem -Path $ModuleRoot -Filter '*.psd1' -File -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $manifestPath) {
throw "Path $ModuleRoot doesn't contain a PSD1 file. Failing tests."
}

return $manifestPath.FullName
}

$PayloadRoot = Get-ModulePayloadRootForTests -SourceRoot $script:SourceRoot
$ModuleRoot = $PayloadRoot
$PrimaryModulePath = Get-ResolvedModuleManifestPath -ModuleRoot $ModuleRoot
$PrimaryModule = Get-Item -LiteralPath $PrimaryModulePath

$ModuleName = $PrimaryModule.BaseName
$env:PSPUBLISHMODULE_TEST_MANIFEST_PATH = $PrimaryModule.FullName
$env:PSPUBLISHMODULE_TEST_MODULE_ROOT = $ModuleRoot
$env:PSPUBLISHMODULE_TEST_SOURCE_ROOT = $script:SourceRoot
$PSDInformation = Import-PowerShellDataFile -Path $PrimaryModule.FullName
$RequiredModules = @(
'Pester'
Expand Down Expand Up @@ -139,7 +172,7 @@ if ($result.FailedCount -gt 0) {
}
}
} finally {
if ($script:CopiedSourceLib -and (Test-Path -LiteralPath $sourceLibRoot)) {
Remove-Item -LiteralPath $sourceLibRoot -Recurse -Force -ErrorAction SilentlyContinue
}
Remove-Item Env:PSPUBLISHMODULE_TEST_MANIFEST_PATH -ErrorAction SilentlyContinue
Remove-Item Env:PSPUBLISHMODULE_TEST_MODULE_ROOT -ErrorAction SilentlyContinue
Remove-Item Env:PSPUBLISHMODULE_TEST_SOURCE_ROOT -ErrorAction SilentlyContinue
}
2 changes: 1 addition & 1 deletion Module/Tests/Build-Module.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Describe 'Build-Module' {
BeforeAll {
# Import the module to make sure all functions are available
$moduleManifest = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath 'PSPublishModule.psd1'
$moduleManifest = if ($env:PSPUBLISHMODULE_TEST_MANIFEST_PATH) { $env:PSPUBLISHMODULE_TEST_MANIFEST_PATH } else { Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath 'PSPublishModule.psd1' }
Import-Module $moduleManifest -Force

# Set up temp directory
Expand Down
2 changes: 1 addition & 1 deletion Module/Tests/Get-ModuleTestFailures.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Describe "Get-ModuleTestFailures Tests" {
BeforeAll {
# Import the local module from the repository (avoid using an installed copy).
$ModulePath = [IO.Path]::Combine($PSScriptRoot, '..', 'PSPublishModule.psd1')
$ModulePath = if ($env:PSPUBLISHMODULE_TEST_MANIFEST_PATH) { $env:PSPUBLISHMODULE_TEST_MANIFEST_PATH } else { [IO.Path]::Combine($PSScriptRoot, '..', 'PSPublishModule.psd1') }
Import-Module $ModulePath -Force
}

Expand Down
2 changes: 1 addition & 1 deletion Module/Tests/ModuleTestingFunctions.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Get-Module PSPublishModule | Remove-Module -Force -ErrorAction SilentlyContinue

# Import the module fresh
$moduleManifest = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath 'PSPublishModule.psd1'
$moduleManifest = if ($env:PSPUBLISHMODULE_TEST_MANIFEST_PATH) { $env:PSPUBLISHMODULE_TEST_MANIFEST_PATH } else { Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath 'PSPublishModule.psd1' }
Import-Module $moduleManifest -Force
}

Expand Down
8 changes: 1 addition & 7 deletions Module/Tests/PrivateGallery.Commands.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ Describe 'Private gallery command metadata' {
BeforeAll {
$existingCommand = Get-Command Connect-ModuleRepository -ErrorAction SilentlyContinue
$loadedModule = Get-Module PSPublishModule -ErrorAction SilentlyContinue
$installedModule = Get-Module -ListAvailable PSPublishModule |
Sort-Object Version -Descending |
Select-Object -First 1

$moduleManifest = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath 'PSPublishModule.psd1'
$moduleManifest = if ($env:PSPUBLISHMODULE_TEST_MANIFEST_PATH) { $env:PSPUBLISHMODULE_TEST_MANIFEST_PATH } else { Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath 'PSPublishModule.psd1' }
$runtimesText = (dotnet --list-runtimes 2>$null) -join "`n"
$tfm = if ($runtimesText -match '(?m)^Microsoft\.NETCore\.App\s+10\.') { 'net10.0' } else { 'net8.0' }
$binaryModule = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath "../../PSPublishModule/bin/Release/$tfm") -ChildPath 'PSPublishModule.dll'
Expand All @@ -15,8 +11,6 @@ Describe 'Private gallery command metadata' {
$script:PrivateGalleryTestModule = $existingCommand.Module
} elseif ($loadedModule) {
$script:PrivateGalleryTestModule = $loadedModule
} elseif ($installedModule) {
$script:PrivateGalleryTestModule = Import-Module $installedModule.Path -Force -PassThru -ErrorAction Stop
} elseif (Test-Path -LiteralPath $binaryModule) {
try {
$script:PrivateGalleryTestModule = Import-Module $binaryModule -Force -PassThru -ErrorAction Stop
Expand Down
6 changes: 3 additions & 3 deletions Module/Tests/Remove-Comments.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
}

# Always import the local module from the repository to avoid picking up an installed copy.
$ModuleToLoad = Join-Path -Path $PSScriptRoot -ChildPath '..' -AdditionalChildPath 'PSPublishModule.psd1'
Import-Module $ModuleToLoad -Force
}
$ModuleToLoad = if ($env:PSPUBLISHMODULE_TEST_MANIFEST_PATH) { $env:PSPUBLISHMODULE_TEST_MANIFEST_PATH } else { Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath 'PSPublishModule.psd1' }
Import-Module $ModuleToLoad -Force
}

It 'Save to variable' {
$FilePath = [io.path]::Combine($PSScriptRoot, 'Input', 'RemoveCommentsTests.ps1')
Expand Down
19 changes: 13 additions & 6 deletions Module/Tests/Step-Version.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
Describe 'Step-Version' {
Describe 'Step-Version' {
BeforeAll {
$script:ModuleToLoad = if ($env:PSPUBLISHMODULE_TEST_MANIFEST_PATH) {
$env:PSPUBLISHMODULE_TEST_MANIFEST_PATH
} else {
Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath 'PSPublishModule.psd1'
}

Import-Module $script:ModuleToLoad -Force | Out-Null
}

It 'Testing version 0.1.X' {
$ModuleToLoad = Join-Path -Path $PSScriptRoot -ChildPath '..' -AdditionalChildPath 'PSPublishModule.psd1'
Import-Module $ModuleToLoad -Force | Out-Null
$Output = Step-Version -Module 'PowerShellManager' -ExpectedVersion '0.1.X'
$Output | Should -Be "0.1.3"
}
It "Testing version 0.2.X" {
$ModuleToLoad = Join-Path -Path $PSScriptRoot -ChildPath '..' -AdditionalChildPath 'PSPublishModule.psd1'
Import-Module $ModuleToLoad -Force | Out-Null

It 'Testing version 0.2.X' {
$Output = Step-Version -Module 'PowerShellManager' -ExpectedVersion '0.2.X'
$Output | Should -Be "0.2.0"
}
Expand Down
14 changes: 14 additions & 0 deletions PSPublishModule.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerForgeStudio.Cli", "Pow
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerForge.PowerShell", "PowerForge.PowerShell\PowerForge.PowerShell.csproj", "{4BA6DB6C-56AF-4C4C-B4CD-506A25DD7A43}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerForge.Net472SmokeTests", "PowerForge.Net472SmokeTests\PowerForge.Net472SmokeTests.csproj", "{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -195,6 +197,18 @@ Global
{4BA6DB6C-56AF-4C4C-B4CD-506A25DD7A43}.Release|x64.Build.0 = Release|Any CPU
{4BA6DB6C-56AF-4C4C-B4CD-506A25DD7A43}.Release|x86.ActiveCfg = Release|Any CPU
{4BA6DB6C-56AF-4C4C-B4CD-506A25DD7A43}.Release|x86.Build.0 = Release|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Debug|x64.ActiveCfg = Debug|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Debug|x64.Build.0 = Debug|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Debug|x86.ActiveCfg = Debug|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Debug|x86.Build.0 = Debug|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Release|Any CPU.Build.0 = Release|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Release|x64.ActiveCfg = Release|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Release|x64.Build.0 = Release|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Release|x86.ActiveCfg = Release|Any CPU
{FD12E32E-6FCF-4D27-86C6-5F153C740F2C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using PowerForge;

namespace PowerForge.Net472SmokeTests;

public sealed class ArtefactConfigurationFactoryNet472SmokeTests
{
[Fact]
public void Create_NormalizesWindowsPathsUnderNet472()
{
var factory = new ArtefactConfigurationFactory(new NullLogger());

var artefact = factory.Create(new ArtefactConfigurationRequest {
Type = ArtefactType.Unpacked,
Path = "output/packages",
RequiredModulesPath = "dependencies/modules",
ModulesPath = "module/content",
CopyFiles = new[] {
new ArtefactCopyMapping {
Source = "src/file.txt",
Destination = "dest/file.txt"
}
}
});

Assert.Equal(@"output\packages", artefact.Configuration.Path);
Assert.Equal(@"dependencies\modules", artefact.Configuration.RequiredModules.Path);
Assert.Equal(@"module\content", artefact.Configuration.RequiredModules.ModulesPath);
Assert.NotNull(artefact.Configuration.FilesOutput);
Assert.Single(artefact.Configuration.FilesOutput!);
Assert.Equal(@"src\file.txt", artefact.Configuration.FilesOutput![0].Source);
Assert.Equal(@"dest\file.txt", artefact.Configuration.FilesOutput![0].Destination);
}
}
Loading
Loading