Merge pull request #105 from blindzero/release/v0.8.0 #17
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Artifact tag name (e.g. v0.7.0-test). This does NOT need to exist as a git tag.' | |
| required: false | |
| type: string | |
| publish_release: | |
| description: 'If true, create a GitHub Release and upload the ZIP as a release asset.' | |
| required: false | |
| default: false | |
| type: boolean | |
| publish_psgallery: | |
| description: 'If true, publish the module to PowerShell Gallery (requires explicit intent).' | |
| required: false | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: write | |
| actions: read | |
| jobs: | |
| release: | |
| name: Build artifact (and optionally publish release) | |
| runs-on: ubuntu-latest | |
| outputs: | |
| tag: ${{ steps.tag.outputs.value }} | |
| is_prerelease: ${{ steps.semver.outputs.is_prerelease }} | |
| is_stable: ${{ steps.semver.outputs.is_stable }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.ref }} | |
| - name: Show PowerShell version | |
| shell: pwsh | |
| run: | | |
| $PSVersionTable.PSVersion | |
| pwsh -v | |
| - name: Determine artifact tag | |
| id: tag | |
| shell: pwsh | |
| run: | | |
| $inputTag = '${{ inputs.tag }}' | |
| if (-not [string]::IsNullOrWhiteSpace($inputTag)) { | |
| "value=$inputTag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| exit 0 | |
| } | |
| $refName = '${{ github.ref_name }}' | |
| if ([string]::IsNullOrWhiteSpace($refName)) { | |
| throw "github.ref_name is empty. Provide inputs.tag when using workflow_dispatch." | |
| } | |
| "value=$refName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Classify tag (stable vs prerelease) | |
| id: semver | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ steps.tag.outputs.value }}' | |
| $isStable = $tag -match '^v\d+\.\d+\.\d+$' | |
| $isPrerelease = $tag -match '^v\d+\.\d+\.\d+-[0-9A-Za-z][0-9A-Za-z\.\-]*$' | |
| "is_stable=$($isStable.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "is_prerelease=$($isPrerelease.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Require tag to point to main HEAD | |
| if: ${{ startsWith(github.ref, 'refs/tags/v') }} | |
| shell: pwsh | |
| run: | | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| git fetch origin main --depth 1 | Out-Null | |
| $tagSha = (git rev-parse '${{ github.ref }}^{}').Trim() | |
| $mainSha = (git rev-parse 'origin/main').Trim() | |
| if ($tagSha -ne $mainSha) { | |
| throw "Tag does not point to origin/main HEAD. Tag SHA=$tagSha, main SHA=$mainSha" | |
| } | |
| Write-Host "Tag points to origin/main HEAD ($mainSha)." | |
| - name: Require green CI on the target commit | |
| if: ${{ inputs.publish_release == true || inputs.publish_psgallery == true || startsWith(github.ref, 'refs/tags/v') }} | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| $repo = '${{ github.repository }}' | |
| $sha = '${{ github.sha }}' | |
| # NOTE: This must match the workflow filename for CI. | |
| # In this repository it is: .github/workflows/ci.yml | |
| $uri = "https://api.github.com/repos/$repo/actions/workflows/ci.yml/runs?per_page=100&head_sha=$sha" | |
| $headers = @{ | |
| Authorization = "Bearer $env:GH_TOKEN" | |
| Accept = "application/vnd.github+json" | |
| "X-GitHub-Api-Version" = "2022-11-28" | |
| } | |
| $resp = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers | |
| $run = $resp.workflow_runs | Sort-Object run_number -Descending | Select-Object -First 1 | |
| if (-not $run) { | |
| throw "No completed CI workflow run found for SHA $sha. If CI is currently running for this commit, wait for it to complete successfully on main before releasing." | |
| } | |
| if ($run.status -ne 'completed' -or $run.conclusion -ne 'success') { | |
| throw "CI is not green for SHA $sha. Status=$($run.status), Conclusion=$($run.conclusion)." | |
| } | |
| Write-Host "CI is green for SHA $sha (run #$($run.run_number))." | |
| - name: Validate tag version matches module manifest version(s) | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ steps.tag.outputs.value }}' | |
| if ($tag -notmatch '^v(\d+\.\d+\.\d+)(?:-[0-9A-Za-z][0-9A-Za-z\.\-]*)?$') { | |
| Write-Host "Tag '$tag' is not SemVer-like. Skipping module version validation." | |
| exit 0 | |
| } | |
| $expectedVersion = $Matches[1] | |
| Write-Host "Expected module version from tag: $expectedVersion" | |
| $manifestPaths = @( | |
| 'src/IdLE/IdLE.psd1', | |
| 'src/IdLE.Core/IdLE.Core.psd1', | |
| 'src/IdLE.Steps.Common/IdLE.Steps.Common.psd1', | |
| 'src/IdLE.Provider.Mock/IdLE.Provider.Mock.psd1' | |
| ) | |
| $mismatches = @() | |
| foreach ($path in $manifestPaths) { | |
| if (-not (Test-Path -LiteralPath $path)) { | |
| throw "Module manifest not found: $path" | |
| } | |
| $data = Import-PowerShellDataFile -LiteralPath $path | |
| $actualVersion = [string]$data.ModuleVersion | |
| if ($actualVersion -ne $expectedVersion) { | |
| $mismatches += [pscustomobject]@{ | |
| Path = $path | |
| Expected = $expectedVersion | |
| ModuleVersion = $actualVersion | |
| } | |
| } | |
| } | |
| if ($mismatches.Count -gt 0) { | |
| $mismatches | Format-Table -AutoSize | Out-String | Write-Host | |
| throw "Tag '$tag' does not match module manifest version(s). Run the version bump before tagging." | |
| } | |
| Write-Host "Manifest versions match tag version ($expectedVersion)." | |
| - name: Build release artifact | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ steps.tag.outputs.value }}' | |
| ./tools/New-IdleReleaseArtifact.ps1 -Tag $tag | |
| - name: Verify artifact exists | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ steps.tag.outputs.value }}' | |
| $path = "artifacts/IdLE-$tag.zip" | |
| if (-not (Test-Path -LiteralPath $path)) { | |
| throw "Expected artifact not found: $path" | |
| } | |
| Get-Item -LiteralPath $path | Format-List FullName, Length, LastWriteTimeUtc | |
| - name: Expand artifact for dry-run upload | |
| if: ${{ inputs.publish_release != true && !startsWith(github.ref, 'refs/tags/v') }} | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ steps.tag.outputs.value }}' | |
| $zipPath = "artifacts/IdLE-$tag.zip" | |
| $staging = "artifacts/staging-$tag" | |
| if (Test-Path -LiteralPath $staging) { | |
| Remove-Item -LiteralPath $staging -Recurse -Force | |
| } | |
| New-Item -ItemType Directory -Path $staging -Force | Out-Null | |
| Expand-Archive -Path $zipPath -DestinationPath $staging -Force | |
| Get-ChildItem -LiteralPath $staging -Recurse | Select-Object FullName | Format-List | |
| - name: Upload workflow artifact (expanded folder) | |
| if: ${{ inputs.publish_release != true && !startsWith(github.ref, 'refs/tags/v') }} | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: IdLE-${{ steps.tag.outputs.value }}-expanded | |
| path: artifacts/staging-${{ steps.tag.outputs.value }} | |
| if-no-files-found: error | |
| - name: Upload workflow artifact (zip) | |
| if: ${{ inputs.publish_release == true || startsWith(github.ref, 'refs/tags/v') }} | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: IdLE-${{ steps.tag.outputs.value }}-zip | |
| path: artifacts/IdLE-${{ steps.tag.outputs.value }}.zip | |
| if-no-files-found: error | |
| - name: Create GitHub Release (optional) | |
| if: ${{ inputs.publish_release == true || startsWith(github.ref, 'refs/tags/v') }} | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.tag.outputs.value }} | |
| name: ${{ steps.tag.outputs.value }} | |
| generate_release_notes: true | |
| prerelease: ${{ steps.semver.outputs.is_prerelease == 'true' }} | |
| files: | | |
| artifacts/IdLE-${{ steps.tag.outputs.value }}.zip | |
| psgallery_local_test: | |
| name: Test module publish (local repository) | |
| runs-on: ubuntu-latest | |
| needs: release | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.ref }} | |
| - name: Show PowerShell version | |
| shell: pwsh | |
| run: | | |
| $PSVersionTable.PSVersion | |
| pwsh -v | |
| - name: Install PowerShellGet | |
| shell: pwsh | |
| run: | | |
| Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | |
| Install-Module -Name PowerShellGet -Scope CurrentUser -Force -AllowClobber | |
| Import-Module PowerShellGet -Force | |
| Get-Module PowerShellGet | Select-Object Name, Version, Path | Format-List | |
| - name: Build publishable module package | |
| shell: pwsh | |
| run: | | |
| ./tools/New-IdleModulePackage.ps1 -Clean | Format-List FullName | |
| - name: Publish module to local PSRepository and verify install/import | |
| shell: pwsh | |
| run: | | |
| $modulePath = Join-Path $env:GITHUB_WORKSPACE 'artifacts/IdLE' | |
| if (-not (Test-Path -LiteralPath $modulePath)) { | |
| throw "Staged module path not found: $modulePath" | |
| } | |
| $repoPath = Join-Path $env:RUNNER_TEMP 'psrepo' | |
| New-Item -ItemType Directory -Path $repoPath -Force | Out-Null | |
| Register-PSRepository -Name 'LocalRepo' -SourceLocation $repoPath -PublishLocation $repoPath -InstallationPolicy Trusted | |
| Publish-Module -Path $modulePath -Repository 'LocalRepo' -ErrorAction Stop | |
| $published = Find-Module -Name 'IdLE' -Repository 'LocalRepo' -ErrorAction Stop | |
| Write-Host "Published to LocalRepo: $($published.Name) $($published.Version)" | |
| Install-Module -Name 'IdLE' -Repository 'LocalRepo' -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop | |
| Import-Module IdLE -Force -ErrorAction Stop | |
| $m = Get-Module IdLE | |
| if (-not $m) { throw 'IdLE did not import successfully.' } | |
| Write-Host "Imported IdLE: $($m.Name) $($m.Version)" | |
| Unregister-PSRepository -Name 'LocalRepo' -ErrorAction SilentlyContinue | |
| psgallery: | |
| name: Publish to PowerShell Gallery | |
| runs-on: ubuntu-latest | |
| needs: [release, psgallery_local_test] | |
| environment: psgallery-prod | |
| if: >- | |
| ${{ | |
| needs.release.outputs.is_stable == 'true' | |
| && ( | |
| startsWith(github.ref, 'refs/tags/v') | |
| || (github.event_name == 'workflow_dispatch' && inputs.publish_psgallery == true && inputs.tag != '') | |
| ) | |
| }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.ref }} | |
| - name: Show PowerShell version | |
| shell: pwsh | |
| run: | | |
| $PSVersionTable.PSVersion | |
| pwsh -v | |
| - name: Install PowerShellGet | |
| shell: pwsh | |
| run: | | |
| Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | |
| Install-Module -Name PowerShellGet -Scope CurrentUser -Force -AllowClobber | |
| Import-Module PowerShellGet -Force | |
| Get-Module PowerShellGet | Select-Object Name, Version, Path | Format-List | |
| - name: Build publishable module package | |
| shell: pwsh | |
| run: | | |
| ./tools/New-IdleModulePackage.ps1 -Clean | Format-List FullName | |
| - name: Publish module to PSGallery | |
| shell: pwsh | |
| env: | |
| PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} | |
| run: | | |
| if ([string]::IsNullOrWhiteSpace($env:PSGALLERY_API_KEY)) { | |
| throw "Missing secret PSGALLERY_API_KEY." | |
| } | |
| $modulePath = Join-Path $env:GITHUB_WORKSPACE 'artifacts/IdLE' | |
| if (-not (Test-Path -LiteralPath $modulePath)) { | |
| throw "Staged module path not found: $modulePath" | |
| } | |
| $manifest = Join-Path $modulePath 'IdLE.psd1' | |
| if (-not (Test-Path -LiteralPath $manifest)) { | |
| throw "Staged module manifest not found: $manifest" | |
| } | |
| $data = Import-PowerShellDataFile -LiteralPath $manifest | |
| Write-Host "Publishing IdLE Version: $($data.ModuleVersion)" | |
| Publish-Module -Path $modulePath -NuGetApiKey $env:PSGALLERY_API_KEY -Repository PSGallery -ErrorAction Stop |