diff --git a/.github/linters/.powershell-psscriptanalyzer.psd1 b/.github/linters/.powershell-psscriptanalyzer.psd1 index 09cc3d0..f1b546d 100644 --- a/.github/linters/.powershell-psscriptanalyzer.psd1 +++ b/.github/linters/.powershell-psscriptanalyzer.psd1 @@ -50,6 +50,7 @@ } } ExcludeRules = @( + 'PSAvoidUsingWriteHost', # Write-Host is acceptable in GitHub Actions runners. 'PSMissingModuleManifestField', # This rule is not applicable until the module is built. 'PSUseToExportFieldsInManifest' ) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 97129a3..71fc1d7 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -1,6 +1,6 @@ name: Action-Test -run-name: "Action-Test - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" +run-name: 'Action-Test - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}' on: workflow_dispatch: @@ -17,17 +17,438 @@ permissions: pull-requests: read jobs: - ActionTestBasic: - name: Action-Test - [Basic] + ActionTest-PatchBump: + name: Action-Test - [Patch bump] runs-on: ubuntu-latest steps: - # Need to check out as part of the test, as its a local action - name: Checkout repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Action-Test + - name: Create fake PR event + shell: pwsh + run: | + $event = @{ + pull_request = @{ + head = @{ ref = 'feat/test-patch' } + labels = @( + @{ name = 'patch' } + ) + } + } | ConvertTo-Json -Depth 5 + $event | Set-Content -Path "$env:RUNNER_TEMP/event.json" + "GITHUB_EVENT_PATH=$env:RUNNER_TEMP/event.json" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Resolve-PSModuleVersion + id: resolve + uses: ./ + env: + GH_TOKEN: ${{ github.token }} + with: + Settings: | + { + "Publish": { + "Module": { + "ReleaseType": "Release", + "AutoPatching": false, + "VersionPrefix": "v", + "MajorLabels": "major", + "MinorLabels": "minor", + "PatchLabels": "patch", + "IgnoreLabels": "" + } + } + } + + - name: Verify - Patch bump + shell: pwsh + env: + RESOLVE_CREATE_RELEASE: ${{ steps.resolve.outputs.CreateRelease }} + RESOLVE_VERSION: ${{ steps.resolve.outputs.Version }} + RESOLVE_RELEASE_TYPE: ${{ steps.resolve.outputs.ReleaseType }} + RESOLVE_FULL_VERSION: ${{ steps.resolve.outputs.FullVersion }} + run: | + $createRelease = $env:RESOLVE_CREATE_RELEASE + $version = $env:RESOLVE_VERSION + $releaseType = $env:RESOLVE_RELEASE_TYPE + $fullVersion = $env:RESOLVE_FULL_VERSION + + if ($createRelease -ne 'true') { + Write-Error "Expected CreateRelease='true', got '$createRelease'" + exit 1 + } + if ([string]::IsNullOrEmpty($version)) { + Write-Error 'Expected a non-empty Version' + exit 1 + } + if ($releaseType -ne 'Release') { + Write-Error "Expected ReleaseType='Release', got '$releaseType'" + exit 1 + } + + Write-Host "Version: $version" + Write-Host "FullVersion: $fullVersion" + Write-Host "ReleaseType: $releaseType" + Write-Host "CreateRelease: $createRelease" + + ActionTest-MinorBump: + name: Action-Test - [Minor bump] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Create fake PR event + shell: pwsh + run: | + $event = @{ + pull_request = @{ + head = @{ ref = 'feat/test-minor' } + labels = @( + @{ name = 'minor' } + ) + } + } | ConvertTo-Json -Depth 5 + $event | Set-Content -Path "$env:RUNNER_TEMP/event.json" + "GITHUB_EVENT_PATH=$env:RUNNER_TEMP/event.json" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Resolve-PSModuleVersion + id: resolve + uses: ./ + env: + GH_TOKEN: ${{ github.token }} + with: + Settings: | + { + "Publish": { + "Module": { + "ReleaseType": "Release", + "AutoPatching": false, + "VersionPrefix": "v", + "MajorLabels": "major", + "MinorLabels": "minor", + "PatchLabels": "patch", + "IgnoreLabels": "" + } + } + } + + - name: Verify - Minor bump + shell: pwsh + env: + RESOLVE_CREATE_RELEASE: ${{ steps.resolve.outputs.CreateRelease }} + RESOLVE_VERSION: ${{ steps.resolve.outputs.Version }} + RESOLVE_RELEASE_TYPE: ${{ steps.resolve.outputs.ReleaseType }} + RESOLVE_FULL_VERSION: ${{ steps.resolve.outputs.FullVersion }} + run: | + $createRelease = $env:RESOLVE_CREATE_RELEASE + $version = $env:RESOLVE_VERSION + $releaseType = $env:RESOLVE_RELEASE_TYPE + $fullVersion = $env:RESOLVE_FULL_VERSION + + if ($createRelease -ne 'true') { + Write-Error "Expected CreateRelease='true', got '$createRelease'" + exit 1 + } + if ([string]::IsNullOrEmpty($version)) { + Write-Error 'Expected a non-empty Version' + exit 1 + } + if ($releaseType -ne 'Release') { + Write-Error "Expected ReleaseType='Release', got '$releaseType'" + exit 1 + } + + Write-Host "Version: $version" + Write-Host "FullVersion: $fullVersion" + Write-Host "ReleaseType: $releaseType" + Write-Host "CreateRelease: $createRelease" + + ActionTest-MajorBump: + name: Action-Test - [Major bump] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Create fake PR event + shell: pwsh + run: | + $event = @{ + pull_request = @{ + head = @{ ref = 'feat/test-major' } + labels = @( + @{ name = 'major' } + ) + } + } | ConvertTo-Json -Depth 5 + $event | Set-Content -Path "$env:RUNNER_TEMP/event.json" + "GITHUB_EVENT_PATH=$env:RUNNER_TEMP/event.json" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Resolve-PSModuleVersion + id: resolve + uses: ./ + env: + GH_TOKEN: ${{ github.token }} + with: + Settings: | + { + "Publish": { + "Module": { + "ReleaseType": "Release", + "AutoPatching": false, + "VersionPrefix": "v", + "MajorLabels": "major", + "MinorLabels": "minor", + "PatchLabels": "patch", + "IgnoreLabels": "" + } + } + } + + - name: Verify - Major bump + shell: pwsh + env: + RESOLVE_CREATE_RELEASE: ${{ steps.resolve.outputs.CreateRelease }} + RESOLVE_VERSION: ${{ steps.resolve.outputs.Version }} + RESOLVE_RELEASE_TYPE: ${{ steps.resolve.outputs.ReleaseType }} + RESOLVE_FULL_VERSION: ${{ steps.resolve.outputs.FullVersion }} + run: | + $createRelease = $env:RESOLVE_CREATE_RELEASE + $version = $env:RESOLVE_VERSION + $releaseType = $env:RESOLVE_RELEASE_TYPE + $fullVersion = $env:RESOLVE_FULL_VERSION + + if ($createRelease -ne 'true') { + Write-Error "Expected CreateRelease='true', got '$createRelease'" + exit 1 + } + if ([string]::IsNullOrEmpty($version)) { + Write-Error 'Expected a non-empty Version' + exit 1 + } + if ($releaseType -ne 'Release') { + Write-Error "Expected ReleaseType='Release', got '$releaseType'" + exit 1 + } + + Write-Host "Version: $version" + Write-Host "FullVersion: $fullVersion" + Write-Host "ReleaseType: $releaseType" + Write-Host "CreateRelease: $createRelease" + + ActionTest-AutoPatch: + name: Action-Test - [Auto-patch] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Create fake PR event + shell: pwsh + run: | + $event = @{ + pull_request = @{ + head = @{ ref = 'feat/test-autopatch' } + labels = @() + } + } | ConvertTo-Json -Depth 5 + $event | Set-Content -Path "$env:RUNNER_TEMP/event.json" + "GITHUB_EVENT_PATH=$env:RUNNER_TEMP/event.json" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Resolve-PSModuleVersion + id: resolve uses: ./ + env: + GH_TOKEN: ${{ github.token }} with: - Subject: PSModule + Settings: | + { + "Publish": { + "Module": { + "ReleaseType": "Release", + "AutoPatching": true, + "VersionPrefix": "v", + "MajorLabels": "major", + "MinorLabels": "minor", + "PatchLabels": "patch", + "IgnoreLabels": "" + } + } + } + + - name: Verify - Auto-patch + shell: pwsh + env: + RESOLVE_CREATE_RELEASE: ${{ steps.resolve.outputs.CreateRelease }} + RESOLVE_VERSION: ${{ steps.resolve.outputs.Version }} + RESOLVE_RELEASE_TYPE: ${{ steps.resolve.outputs.ReleaseType }} + RESOLVE_FULL_VERSION: ${{ steps.resolve.outputs.FullVersion }} + run: | + $createRelease = $env:RESOLVE_CREATE_RELEASE + $version = $env:RESOLVE_VERSION + $releaseType = $env:RESOLVE_RELEASE_TYPE + $fullVersion = $env:RESOLVE_FULL_VERSION + + if ($createRelease -ne 'true') { + Write-Error "Expected CreateRelease='true', got '$createRelease'" + exit 1 + } + if ([string]::IsNullOrEmpty($version)) { + Write-Error 'Expected a non-empty Version' + exit 1 + } + if ($releaseType -ne 'Release') { + Write-Error "Expected ReleaseType='Release', got '$releaseType'" + exit 1 + } + + Write-Host "Version: $version" + Write-Host "FullVersion: $fullVersion" + Write-Host "ReleaseType: $releaseType" + Write-Host "CreateRelease: $createRelease" + + ActionTest-IgnoreLabel: + name: Action-Test - [Ignore label] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Create fake PR event + shell: pwsh + run: | + $event = @{ + pull_request = @{ + head = @{ ref = 'feat/test-ignore' } + labels = @( + @{ name = 'patch' } + @{ name = 'skip-release' } + ) + } + } | ConvertTo-Json -Depth 5 + $event | Set-Content -Path "$env:RUNNER_TEMP/fake-event.json" + "GITHUB_EVENT_PATH=$env:RUNNER_TEMP/fake-event.json" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Resolve-PSModuleVersion + id: resolve + uses: ./ + env: + GH_TOKEN: ${{ github.token }} + with: + Settings: | + { + "Publish": { + "Module": { + "ReleaseType": "Release", + "AutoPatching": false, + "VersionPrefix": "v", + "MajorLabels": "major", + "MinorLabels": "minor", + "PatchLabels": "patch", + "IgnoreLabels": "skip-release" + } + } + } + EventPath: ${{ runner.temp }}/fake-event.json + EventJson: '{"pull_request":{"head":{"ref":"feat/test-ignore"},"labels":[{"name":"patch"},{"name":"skip-release"}]}}' + + - name: Verify - Ignore label + shell: pwsh + env: + RESOLVE_CREATE_RELEASE: ${{ steps.resolve.outputs.CreateRelease }} + RESOLVE_RELEASE_TYPE: ${{ steps.resolve.outputs.ReleaseType }} + run: | + $createRelease = $env:RESOLVE_CREATE_RELEASE + $releaseType = $env:RESOLVE_RELEASE_TYPE + + if ($createRelease -ne 'false') { + Write-Error "Expected CreateRelease='false', got '$createRelease'" + exit 1 + } + if ($releaseType -ne 'None') { + Write-Error "Expected ReleaseType='None', got '$releaseType'" + exit 1 + } + + Write-Host "ReleaseType: $releaseType" + Write-Host "CreateRelease: $createRelease" + + ActionTest-None: + name: Action-Test - [None release type] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Create fake PR event + shell: pwsh + run: | + $event = @{ + pull_request = @{ + head = @{ ref = 'feat/test-none' } + labels = @( + @{ name = 'patch' } + ) + } + } | ConvertTo-Json -Depth 5 + $event | Set-Content -Path "$env:RUNNER_TEMP/event.json" + "GITHUB_EVENT_PATH=$env:RUNNER_TEMP/event.json" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Resolve-PSModuleVersion + id: resolve + uses: ./ + with: + Settings: | + { + "Publish": { + "Module": { + "ReleaseType": "None", + "AutoPatching": false, + "VersionPrefix": "v", + "MajorLabels": "major", + "MinorLabels": "minor", + "PatchLabels": "patch", + "IgnoreLabels": "" + } + } + } + + - name: Verify - None release type + shell: pwsh + env: + RESOLVE_CREATE_RELEASE: ${{ steps.resolve.outputs.CreateRelease }} + RESOLVE_VERSION: ${{ steps.resolve.outputs.Version }} + RESOLVE_RELEASE_TYPE: ${{ steps.resolve.outputs.ReleaseType }} + run: | + $createRelease = $env:RESOLVE_CREATE_RELEASE + $version = $env:RESOLVE_VERSION + $releaseType = $env:RESOLVE_RELEASE_TYPE + + if ($createRelease -ne 'false') { + Write-Error "Expected CreateRelease='false', got '$createRelease'" + exit 1 + } + if (-not [string]::IsNullOrEmpty($version)) { + Write-Error "Expected empty Version, got '$version'" + exit 1 + } + if ($releaseType -ne 'None') { + Write-Error "Expected ReleaseType='None', got '$releaseType'" + exit 1 + } + + Write-Host "Version: $version" + Write-Host "ReleaseType: $releaseType" + Write-Host "CreateRelease: $createRelease" diff --git a/README.md b/README.md index d560186..82dc431 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,3 @@ -# Template-Action +# Resolve-PSModuleVersion -A template repository for GitHub Actions - -## Usage - -### Inputs - -### Secrets - -### Outputs - -### Example - -```yaml -Example here -``` +This GitHub Action is a part of the [PSModule framework](https://github.com/PSModule). diff --git a/action.yml b/action.yml index fb42d0b..c2843ea 100644 --- a/action.yml +++ b/action.yml @@ -1,15 +1,21 @@ -name: Template-Action -description: A template action for GitHub Actions using PowerShell +name: Resolve-PSModuleVersion +description: Resolves the next module version from settings, PR labels, GitHub releases, and the PowerShell Gallery. author: PSModule branding: - icon: upload-cloud + icon: tag color: white inputs: - Subject: - description: The subject to greet + Settings: + description: The complete settings object as a JSON string (output of Get-PSModuleSettings). + required: true + Name: + description: Name of the module. When empty, the repository name is used. required: false - default: World + WorkingDirectory: + description: The working directory where the script will run from. + required: false + default: '.' Debug: description: Enable debug output. required: false @@ -18,30 +24,52 @@ inputs: description: Enable verbose output. required: false default: 'false' - Version: - description: Specifies the version of the GitHub module to be installed. The value must be an exact version. + EventPath: + description: Path to the GitHub event JSON file. Defaults to the standard GitHub event path. Override for testing. required: false - Prerelease: - description: Allow prerelease versions if available. - required: false - default: 'false' - WorkingDirectory: - description: The working directory where the script will run from. + default: '' + EventJson: + description: GitHub event payload as a JSON string. When set, overrides reading from the event file. Use for testing. required: false - default: ${{ github.workspace }} + default: '' + +outputs: + Version: + description: The Major.Minor.Patch portion of the resolved version. + value: ${{ steps.resolve.outputs.Version }} + Prerelease: + description: The prerelease tag, empty when not a prerelease. + value: ${{ steps.resolve.outputs.Prerelease }} + FullVersion: + description: The full version string including version prefix and prerelease tag (for example v1.4.0-alpha001). + value: ${{ steps.resolve.outputs.FullVersion }} + ReleaseType: + description: The resolved release type - Release, Prerelease, or None. + value: ${{ steps.resolve.outputs.ReleaseType }} + CreateRelease: + description: 'true when a release or prerelease should be created from this run.' + value: ${{ steps.resolve.outputs.CreateRelease }} runs: using: composite steps: - - name: Template-Action - uses: PSModule/GitHub-Script@0097f3bbe3f413f3b577b9bcc600727b0ca3201a # v1.7.10 + - name: Install-PSModuleHelpers + uses: PSModule/Install-PSModuleHelpers@ed79b6e3aa8c9cd3d30ab2bf02ea6bd4687b9c74 # v1.0.7 + + - name: Install PSSemVer + shell: pwsh + run: | + Install-PSResource -Name PSSemVer -Repository PSGallery -TrustRepository + + - name: Resolve module version + id: resolve + shell: pwsh + working-directory: ${{ inputs.WorkingDirectory }} env: - PSMOUDLE_TEMPLATE_ACTION_INPUT_Subject: ${{ inputs.Subject }} - with: - Name: Template-Action - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} - Script: ${{ github.action_path }}/scripts/main.ps1 + PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Settings: ${{ inputs.Settings }} + PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Name: ${{ inputs.Name }} + PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_EventJson: ${{ inputs.EventJson }} + PSMODULE_ACTION_PATH: ${{ github.action_path }} + GITHUB_EVENT_PATH: ${{ inputs.EventPath || github.event_path }} + run: | + & "$env:PSMODULE_ACTION_PATH/scripts/main.ps1" diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 480b20a..60f0e18 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -1,24 +1,293 @@ -#Requires -Modules GitHub - +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', 'prereleaseName', + Justification = 'Variable is used in script blocks.' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', 'publishModule', + Justification = 'Variable is used in script blocks.' +)] [CmdletBinding()] -param( - [Parameter()] - [string] $Subject = $env:PSMOUDLE_TEMPLATE_ACTION_INPUT_Subject -) - -begin { - $scriptName = $MyInvocation.MyCommand.Name - Write-Debug "[$scriptName] - Start" +param() + +$PSStyle.OutputRendering = 'Ansi' + +Import-Module -Name 'Helpers' -Force + +LogGroup 'Load inputs' { + $env:GITHUB_REPOSITORY_NAME = $env:GITHUB_REPOSITORY -replace '.+/' + + $name = if ([string]::IsNullOrEmpty($env:PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Name)) { + $env:GITHUB_REPOSITORY_NAME + } else { + $env:PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Name + } + Write-Host "Module name: [$name]" + + $settingsJson = $env:PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Settings + if ([string]::IsNullOrWhiteSpace($settingsJson)) { + Write-Error 'Settings input is required.' + exit 1 + } + $settings = $settingsJson | ConvertFrom-Json + $publishModule = $settings.Publish.Module +} + +LogGroup 'Resolve configuration' { + $autoPatching = [bool]$publishModule.AutoPatching + $incrementalPrerelease = [bool]$publishModule.IncrementalPrerelease + $datePrereleaseFormat = [string]$publishModule.DatePrereleaseFormat + $versionPrefix = [string]$publishModule.VersionPrefix + $releaseType = [string]$publishModule.ReleaseType + $ignoreLabels = (([string]$publishModule.IgnoreLabels) -split ',') | ForEach-Object { $_.Trim() } | Where-Object { $_ } + $majorLabels = (([string]$publishModule.MajorLabels) -split ',') | ForEach-Object { $_.Trim() } | Where-Object { $_ } + $minorLabels = (([string]$publishModule.MinorLabels) -split ',') | ForEach-Object { $_.Trim() } | Where-Object { $_ } + $patchLabels = (([string]$publishModule.PatchLabels) -split ',') | ForEach-Object { $_.Trim() } | Where-Object { $_ } + + Write-Host '-------------------------------------------------' + [pscustomobject]@{ + AutoPatching = $autoPatching + IncrementalPrerelease = $incrementalPrerelease + DatePrereleaseFormat = $datePrereleaseFormat + VersionPrefix = $versionPrefix + ReleaseType = $releaseType + IgnoreLabels = $ignoreLabels -join ', ' + MajorLabels = $majorLabels -join ', ' + MinorLabels = $minorLabels -join ', ' + PatchLabels = $patchLabels -join ', ' + } | Format-List | Out-String + Write-Host '-------------------------------------------------' } -process { - try { - Write-Output "Hello, $Subject!" - } catch { - throw $_ +LogGroup 'Event information' { + $eventJsonInput = $env:PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_EventJson + if (-not [string]::IsNullOrWhiteSpace($eventJsonInput)) { + $githubEvent = $eventJsonInput | ConvertFrom-Json + } else { + $githubEventJson = Get-Content $env:GITHUB_EVENT_PATH + $githubEvent = $githubEventJson | ConvertFrom-Json + } + $pull_request = $githubEvent.pull_request + if (-not $pull_request) { + throw 'GitHub event does not contain pull_request data. This action must be run from a pull_request event.' } + $prHeadRef = $pull_request.head.ref + $labels = @() + $labels += $pull_request.labels.name + + Write-Host '-------------------------------------------------' + [PSCustomObject]@{ + PRHeadRef = $prHeadRef + Labels = $labels -join ', ' + } | Format-List | Out-String + Write-Host '-------------------------------------------------' } -end { - Write-Debug "[$scriptName] - End" +LogGroup 'Determine release configuration' { + $prereleaseName = $prHeadRef -replace '[^a-zA-Z0-9]' + + $validReleaseTypes = @('Release', 'Prerelease', 'None') + if ([string]::IsNullOrWhiteSpace($releaseType)) { + Write-Error "Settings.Publish.Module.ReleaseType is required. Valid values are: $($validReleaseTypes -join ', ')" + exit 1 + } + if ($releaseType -notin $validReleaseTypes) { + Write-Error "Invalid ReleaseType: [$releaseType]. Valid values are: $($validReleaseTypes -join ', ')" + exit 1 + } + + $createRelease = $releaseType -eq 'Release' + $createPrerelease = $releaseType -eq 'Prerelease' + $shouldPublish = $createRelease -or $createPrerelease + + $ignoreRelease = ($labels | Where-Object { $ignoreLabels -contains $_ }).Count -gt 0 + if ($ignoreRelease -and $shouldPublish) { + Write-Host 'Ignoring release creation due to ignore label.' + $shouldPublish = $false + } + + $majorRelease = $false + $minorRelease = $false + $patchRelease = $false + $hasVersionBump = $false + + if ($shouldPublish) { + $majorRelease = ($labels | Where-Object { $majorLabels -contains $_ }).Count -gt 0 + $minorRelease = ($labels | Where-Object { $minorLabels -contains $_ }).Count -gt 0 -and -not $majorRelease + $patchRelease = ( + (($labels | Where-Object { $patchLabels -contains $_ }).Count -gt 0) -or $autoPatching + ) -and -not $majorRelease -and -not $minorRelease + + $hasVersionBump = $majorRelease -or $minorRelease -or $patchRelease + if (-not $hasVersionBump) { + Write-Host 'No version bump label found and AutoPatching is disabled. No release will be created.' + $shouldPublish = $false + } + } + + Write-Host '-------------------------------------------------' + [PSCustomObject]@{ + ReleaseType = $releaseType + ShouldPublish = $shouldPublish + CreateRelease = $createRelease + CreatePrerelease = $createPrerelease + Major = $majorRelease + Minor = $minorRelease + Patch = $patchRelease + } | Format-List | Out-String + Write-Host '-------------------------------------------------' +} + +$newVersion = $null +$releases = @() + +if ($shouldPublish) { + LogGroup 'Get releases - GitHub' { + $releases = gh release list --json 'createdAt,isDraft,isLatest,isPrerelease,name,publishedAt,tagName' | ConvertFrom-Json + if ($LASTEXITCODE -ne 0) { + Write-Error 'Failed to list releases for the repo.' + exit $LASTEXITCODE + } + Write-Host '-------------------------------------------------' + $releases | Select-Object -Property name, isPrerelease, isLatest, publishedAt | Format-Table | Out-String + Write-Host '-------------------------------------------------' + } + + LogGroup 'Get latest version - GitHub' { + $latestRelease = $releases | Where-Object { $_.isLatest -eq $true } + $ghReleaseVersionString = $latestRelease.tagName + if (-not [string]::IsNullOrEmpty($ghReleaseVersionString)) { + $ghReleaseVersion = New-PSSemVer -Version $ghReleaseVersionString + } else { + Write-Warning "Could not find the latest GitHub release. Using '0.0.0'." + $ghReleaseVersion = New-PSSemVer -Version '0.0.0' + } + Write-Host "GitHub version: [$($ghReleaseVersion.ToString())]" + } + + LogGroup 'Get latest version - PSGallery' { + $count = 5 + $delay = 10 + $latest = $null + for ($i = 1; $i -le $count; $i++) { + try { + Write-Host "Finding module [$name] in the PowerShell Gallery." + $latest = Find-PSResource -Name $name -Repository PSGallery -Verbose:$false + Write-Host "$($latest | Format-Table | Out-String)" + break + } catch { + if ($i -eq $count) { + Write-Warning "Failed to find the module [$name] in the PowerShell Gallery." + Write-Warning $_.Exception.Message + } + Start-Sleep -Seconds $delay + } + } + if ($latest.Version) { + $psGalleryVersion = New-PSSemVer -Version ($latest.Version).ToString() + } else { + Write-Warning "Could not find module online. Using '0.0.0'." + $psGalleryVersion = New-PSSemVer -Version '0.0.0' + } + Write-Host "PSGallery version: [$($psGalleryVersion.ToString())]" + } + + LogGroup 'Latest version' { + $latestVersion = New-PSSemVer -Version ($psGalleryVersion, $ghReleaseVersion | Sort-Object -Descending | Select-Object -First 1) + Write-Host "Latest version: [$($latestVersion.ToString())]" + } + + LogGroup 'Calculate new version' { + $newVersion = New-PSSemVer -Version $latestVersion + $newVersion.Prefix = $versionPrefix + if ($majorRelease) { + Write-Host 'Incrementing major version.' + $newVersion.BumpMajor() + } elseif ($minorRelease) { + Write-Host 'Incrementing minor version.' + $newVersion.BumpMinor() + } elseif ($patchRelease) { + Write-Host 'Incrementing patch version.' + $newVersion.BumpPatch() + } + Write-Host "Partial new version: [$newVersion]" + + if ($createPrerelease -and $hasVersionBump) { + Write-Host "Adding a prerelease tag using the branch name [$prereleaseName]." + $newVersion.Prerelease = $prereleaseName + + if (-not [string]::IsNullOrEmpty($datePrereleaseFormat)) { + Write-Host "Using date-based prerelease format: [$datePrereleaseFormat]." + $newVersion.Prerelease += "$(Get-Date -Format $datePrereleaseFormat)" + } + + if ($incrementalPrerelease) { + $newVersionString = "$($newVersion.Major).$($newVersion.Minor).$($newVersion.Patch)" + $params = @{ + Name = $name + Version = '*' + Prerelease = $true + Repository = 'PSGallery' + Verbose = $false + ErrorAction = 'SilentlyContinue' + } + $psGalleryPrereleases = Find-PSResource @params | + Where-Object { "$($_.Version.Major).$($_.Version.Minor).$($_.Version.Build)" -eq $newVersionString } | + Where-Object { $_.Prerelease -like "$prereleaseName*" } + $latestPSGalleryPrerelease = $psGalleryPrereleases.Prerelease | ForEach-Object { + [long]($_ -replace $prereleaseName) + } | Sort-Object | Select-Object -Last 1 + Write-Host "PSGallery prerelease: [$latestPSGalleryPrerelease]" + + $ghPrereleases = $releases | + Where-Object { $_.tagName -like "*$newVersionString*" } | + Where-Object { $_.tagName -like "*$prereleaseName*" } + $latestGHPrereleases = $ghPrereleases.tagName | ForEach-Object { + $number = $_ -replace '\.' + $number = ($number -split $prereleaseName, 2)[-1] + [long]$number + } | Sort-Object | Select-Object -Last 1 + Write-Host "GitHub prerelease: [$latestGHPrereleases]" + + if ($null -eq $latestPSGalleryPrerelease) { $latestPSGalleryPrerelease = 0 } + if ($null -eq $latestGHPrereleases) { $latestGHPrereleases = 0 } + + $latestPrereleaseNumber = [Math]::Max($latestPSGalleryPrerelease, $latestGHPrereleases) + 1 + $latestPrereleaseNumber = ([string]$latestPrereleaseNumber).PadLeft(3, '0') + $newVersion.Prerelease += $latestPrereleaseNumber + } + } + + Write-Host "New version: [$($newVersion.ToString())]" + } +} + +LogGroup 'Emit outputs' { + $versionString = '' + $prereleaseString = '' + $fullVersionString = '' + $resolvedReleaseType = $releaseType + if ($newVersion) { + $versionString = "$($newVersion.Major).$($newVersion.Minor).$($newVersion.Patch)" + $prereleaseString = [string]$newVersion.Prerelease + $fullVersionString = $newVersion.ToString() + } + if (-not $shouldPublish) { + $resolvedReleaseType = 'None' + } + + Add-Content -Path $env:GITHUB_OUTPUT -Value "Version=$versionString" + Add-Content -Path $env:GITHUB_OUTPUT -Value "Prerelease=$prereleaseString" + Add-Content -Path $env:GITHUB_OUTPUT -Value "FullVersion=$fullVersionString" + Add-Content -Path $env:GITHUB_OUTPUT -Value "ReleaseType=$resolvedReleaseType" + Add-Content -Path $env:GITHUB_OUTPUT -Value "CreateRelease=$($shouldPublish.ToString().ToLower())" + + Write-Host '-------------------------------------------------' + [PSCustomObject]@{ + Version = $versionString + Prerelease = $prereleaseString + FullVersion = $fullVersionString + ReleaseType = $resolvedReleaseType + CreateRelease = $shouldPublish + } | Format-List | Out-String + Write-Host '-------------------------------------------------' }