diff --git a/README.md b/README.md index 37dfb24..27f0017 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,12 @@ customize rule selection, severity filtering, and custom rule inclusion. | `SettingsFilePath` | The path to the settings file. | false | `.github/linters/.powershell-psscriptanalyzer.psd1` | | `Debug` | Enable debug output. | false | `'false'` | | `Verbose` | Enable verbose output. | false | `'false'` | -| `Version` | Specifies the exact version of the GitHub module to install. | false | | -| `Prerelease` | Allow prerelease versions if available. | false | `'false'` | +| `Version` | Specifies the version of the PSScriptAnalyzer module to install (NuGet range). | false | | +| `Prerelease` | Allow prerelease versions of the PSScriptAnalyzer module if available. | false | `'false'` | +| `PesterVersion` | Specifies the version of the Pester module to install (NuGet range). | false | | +| `PesterPrerelease` | Allow prerelease versions of the Pester module if available. | false | `'false'` | +| `GitHubVersion` | Specifies the version of the GitHub module to install (NuGet range). | false | | +| `GitHubPrerelease` | Allow prerelease versions of the GitHub module if available. | false | `'false'` | | `WorkingDirectory` | The working directory where the script runs. | false | `'.'` | | `ReportAsJson` | Output generated reports in JSON format in addition to the configured format. | false | `'true'` | | `Notice_Mode` | Controls when to show notices for test completion. | false | `'Failed'` | diff --git a/action.yml b/action.yml index 8759594..4efa4b1 100644 --- a/action.yml +++ b/action.yml @@ -22,10 +22,30 @@ inputs: required: false default: 'false' Version: - description: Specifies the version of the GitHub module to be installed. The value must be an exact version. + description: | + Specifies the version of the PSScriptAnalyzer module to install, using NuGet version-range syntax (for example '[1.0.0, 2.0.0)'). + When empty, the latest available version is installed. required: false Prerelease: - description: Allow prerelease versions if available. + description: Allow prerelease versions of the PSScriptAnalyzer module if available. + required: false + default: 'false' + PesterVersion: + description: | + Specifies the version of the Pester module to install, using NuGet version-range syntax (for example '[6.0.0,7.0.0)'). + When empty, the latest available version is installed. + required: false + PesterPrerelease: + description: Allow prerelease versions of the Pester module if available. + required: false + default: 'false' + GitHubVersion: + description: | + Specifies the version of the GitHub module to install, using NuGet version-range syntax (for example '[1.2.0, 2.0.0)'). + When empty, the latest available version is installed. + required: false + GitHubPrerelease: + description: Allow prerelease versions of the GitHub module if available. required: false default: 'false' WorkingDirectory: @@ -255,24 +275,29 @@ runs: PSMODULE_INVOKE_SCRIPTANALYZER_INPUT_SettingsFilePath: ${{ inputs.SettingsFilePath }} with: Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} + Prerelease: ${{ inputs.GitHubPrerelease }} Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} + Version: ${{ inputs.GitHubVersion }} WorkingDirectory: ${{ inputs.WorkingDirectory }} Script: ${{ github.action_path }}/src/main.ps1 - name: Invoke-Pester - uses: PSModule/Invoke-Pester@abddf7bef0d0614d7ca322036af6a06ee0fb4d44 # v4.2.4 + uses: PSModule/Invoke-Pester@4ff33199141fdf22568990b6107fe3148ae93a1c # v5.1.0 id: test env: SettingsFilePath: ${{ fromJson(steps.paths.outputs.result).SettingsFilePath }} + PSMODULE_INVOKE_SCRIPTANALYZER_INPUT_Version: ${{ inputs.Version }} + PSMODULE_INVOKE_SCRIPTANALYZER_INPUT_Prerelease: ${{ inputs.Prerelease }} with: Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} + GitHubPrerelease: ${{ inputs.GitHubPrerelease }} + GitHubVersion: ${{ inputs.GitHubVersion }} + Prerelease: ${{ inputs.PesterPrerelease }} Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} + Version: ${{ inputs.PesterVersion }} WorkingDirectory: ${{ inputs.WorkingDirectory }} TestResult_TestSuiteName: ${{ inputs.TestResult_TestSuiteName }} + Prescript: ${{ github.action_path }}/src/Install-PSScriptAnalyzer.ps1 Path: ${{ github.action_path }}/src/tests/PSScriptAnalyzer Run_Path: ${{ fromJson(steps.paths.outputs.result).CodePath }} ReportAsJson: ${{ inputs.ReportAsJson }} diff --git a/src/Install-PSScriptAnalyzer.ps1 b/src/Install-PSScriptAnalyzer.ps1 new file mode 100644 index 0000000..62b5afd --- /dev/null +++ b/src/Install-PSScriptAnalyzer.ps1 @@ -0,0 +1,75 @@ +<# + .SYNOPSIS + Ensures the requested PSScriptAnalyzer version is the only one installed and loaded. + + .DESCRIPTION + Runs as the Invoke-Pester prescript, in the same process as the analyzer test run. It installs the + PSScriptAnalyzer version selected through the action's Version/Prerelease inputs (retrying transient + PSGallery failures), removes any other PSScriptAnalyzer version from the session, and imports the chosen + version into the global session state. This guarantees the tests use the selected version instead of + PowerShell auto-loading whatever copy happens to be preinstalled on the runner. +#> + +[CmdletBinding()] +param() + +$moduleName = 'PSScriptAnalyzer' +$version = $env:PSMODULE_INVOKE_SCRIPTANALYZER_INPUT_Version +$prerelease = $env:PSMODULE_INVOKE_SCRIPTANALYZER_INPUT_Prerelease -eq 'true' + +$installParams = @{ + Name = $moduleName + Repository = 'PSGallery' + TrustRepository = $true + PassThru = $true + WarningAction = 'SilentlyContinue' +} +if (-not [string]::IsNullOrWhiteSpace($version)) { + $installParams['Version'] = $version +} +if ($prerelease) { + $installParams['Prerelease'] = $true +} + +$label = [string]::IsNullOrWhiteSpace($version) ? $moduleName : "$moduleName $version" +Write-Host "Installing module: $label" + +$installed = $null +$retryCount = 5 +$retryDelay = 10 +for ($i = 0; $i -lt $retryCount; $i++) { + try { + $installed = Install-PSResource @installParams + break + } catch { + Write-Warning "Installation of $moduleName failed with error: $_" + if ($i -eq $retryCount - 1) { + throw + } + Write-Warning "Retrying in $retryDelay seconds..." + Start-Sleep -Seconds $retryDelay + } +} + +# Resolve the exact version to load. Prefer what was just installed; if the resource was already +# present, Install-PSResource returns nothing, so fall back to the newest installed version that +# satisfies the requested constraint. +$resolved = $installed | Where-Object { $_.Name -eq $moduleName } | Sort-Object Version -Descending | Select-Object -First 1 +if (-not $resolved) { + $getParams = @{ Name = $moduleName; Verbose = $false; ErrorAction = 'SilentlyContinue' } + if (-not [string]::IsNullOrWhiteSpace($version)) { + $getParams['Version'] = $version + } + $resolved = Get-InstalledPSResource @getParams | Sort-Object Version -Descending | Select-Object -First 1 +} +if (-not $resolved) { + throw "No installed '$moduleName' version satisfies constraint '$version'." +} + +# Remove any already-loaded versions so only the chosen one remains, then import that exact version +# into the global session state used by the Pester run. +Write-Host "Removing any loaded '$moduleName' module from the session" +Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue + +Write-Host "Importing module: $moduleName $($resolved.Version)" +Import-Module -Name $moduleName -RequiredVersion $resolved.Version -Force -Global