diff --git a/.github/workflows/script-tests.yml b/.github/workflows/script-tests.yml index 1c32d38..2b79699 100644 --- a/.github/workflows/script-tests.yml +++ b/.github/workflows/script-tests.yml @@ -25,6 +25,8 @@ jobs: - run: Invoke-Pester working-directory: updater shell: pwsh + env: + GH_TOKEN: ${{ github.token }} danger: name: Danger JS Tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b130e..01f9f75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ To update your existing Danger workflows: ### Features +- Updater now supports filtering releases by GitHub release title patterns, e.g. to support release channels ([#117](https://github.com/getsentry/github-workflows/pull/117)) - Updater now supports dependencies without changelog files by falling back to git commit messages ([#116](https://github.com/getsentry/github-workflows/pull/116)) - Danger - Improve conventional commit scope handling, and non-conventional PR title support ([#105](https://github.com/getsentry/github-workflows/pull/105)) - Add Proguard artifact endpoint for Android builds in sentry-server ([#100](https://github.com/getsentry/github-workflows/pull/100)) diff --git a/updater/README.md b/updater/README.md index ca5db31..9c517ea 100644 --- a/updater/README.md +++ b/updater/README.md @@ -32,6 +32,17 @@ jobs: pattern: '^1\.' # Limit to major version '1' api-token: ${{ secrets.CI_DEPLOY_KEY }} + # Update to stable releases only by filtering GitHub release titles + cocoa-stable: + runs-on: ubuntu-latest + steps: + - uses: getsentry/github-workflows/updater@v3 + with: + path: modules/sentry-cocoa + name: Cocoa SDK (Stable) + gh-title-pattern: '\(Stable\)$' # Only releases with "(Stable)" suffix + api-token: ${{ secrets.CI_DEPLOY_KEY }} + # Update a properties file cli: runs-on: ubuntu-latest @@ -91,6 +102,10 @@ jobs: * type: string * required: false * default: '' +* `gh-title-pattern`: RegEx pattern to match against GitHub release titles. Only releases with matching titles will be considered. Useful for filtering to specific release channels (e.g., stable releases). + * type: string + * required: false + * default: '' * `changelog-entry`: Whether to add a changelog entry for the update. * type: boolean * required: false diff --git a/updater/action.yml b/updater/action.yml index a40a9f9..b3f21ad 100644 --- a/updater/action.yml +++ b/updater/action.yml @@ -13,6 +13,10 @@ inputs: description: 'RegEx pattern that will be matched against available versions when picking the latest one.' required: false default: '' + gh-title-pattern: + description: 'RegEx pattern to match against GitHub release titles. Only releases with matching titles will be considered.' + required: false + default: '' changelog-entry: description: 'Whether to add a changelog entry for the update.' required: false @@ -107,7 +111,9 @@ runs: env: DEPENDENCY_PATH: ${{ inputs.path }} DEPENDENCY_PATTERN: ${{ inputs.pattern }} - run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Pattern $env:DEPENDENCY_PATTERN + GH_TITLE_PATTERN: ${{ inputs.gh-title-pattern }} + GH_TOKEN: ${{ inputs.api-token }} + run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Pattern $env:DEPENDENCY_PATTERN -GhTitlePattern $env:GH_TITLE_PATTERN - name: Get the base repo info if: steps.target.outputs.latestTag != steps.target.outputs.originalTag @@ -164,6 +170,8 @@ runs: - name: Get target changelog if: ${{ ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.root.outputs.changed == 'false') }} shell: pwsh + env: + GH_TOKEN: ${{ inputs.api-token }} run: | $changelog = ${{ github.action_path }}/scripts/get-changelog.ps1 ` -RepoUrl '${{ steps.target.outputs.url }}' ` @@ -223,6 +231,7 @@ runs: shell: pwsh env: DEPENDENCY_PATH: ${{ inputs.path }} + GH_TOKEN: ${{ inputs.api-token }} run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Tag '${{ steps.target.outputs.latestTag }}' - name: Update Changelog @@ -231,6 +240,7 @@ runs: env: DEPENDENCY_NAME: ${{ inputs.name }} CHANGELOG_SECTION: ${{ inputs.changelog-section }} + GH_TOKEN: ${{ inputs.api-token }} run: | ${{ github.action_path }}/scripts/update-changelog.ps1 ` -Name $env:DEPENDENCY_NAME ` diff --git a/updater/scripts/update-dependency.ps1 b/updater/scripts/update-dependency.ps1 index 89c9fc1..c825858 100644 --- a/updater/scripts/update-dependency.ps1 +++ b/updater/scripts/update-dependency.ps1 @@ -12,10 +12,13 @@ param( [Parameter(Mandatory = $true)][string] $Path, # RegEx pattern that will be matched against available versions when picking the latest one [string] $Pattern = '', + # RegEx pattern to match against GitHub release titles. Only releases with matching titles will be considered + [string] $GhTitlePattern = '', # Specific version - if passed, no discovery is performed and the version is set directly [string] $Tag = '' ) +$ErrorActionPreference = 'Stop' Set-StrictMode -Version latest . "$PSScriptRoot/common.ps1" @@ -37,31 +40,24 @@ if ($Path -match '^(.+\.cmake)(#(.+))?$') { $isCMakeFile = $false } -if (-not (Test-Path $Path )) -{ +if (-not (Test-Path $Path )) { throw "Dependency $Path doesn't exit"; } # If it's a directory, we consider it a submodule dependendency. Otherwise, it must a properties-style file or a script. $isSubmodule = (Test-Path $Path -PathType Container) -function SetOutput([string] $name, $value) -{ - if (Test-Path env:GITHUB_OUTPUT) - { +function SetOutput([string] $name, $value) { + if (Test-Path env:GITHUB_OUTPUT) { "$name=$value" | Tee-Object $env:GITHUB_OUTPUT -Append - } - else - { + } else { "$name=$value" } } -if (-not $isSubmodule) -{ +if (-not $isSubmodule) { $isScript = $Path -match '\.(ps1|sh)$' - function DependencyConfig ([Parameter(Mandatory = $true)][string] $action, [string] $value = $null) - { + function DependencyConfig ([Parameter(Mandatory = $true)][string] $action, [string] $value = $null) { if ($isCMakeFile) { # CMake file handling switch ($action) { @@ -82,64 +78,48 @@ if (-not $isSubmodule) 'set-version' { Update-CMakeFile $Path $cmakeDep $value } - Default { + default { throw "Unknown action $action" } } - } - elseif ($isScript) - { - if (Get-Command 'chmod' -ErrorAction SilentlyContinue) - { + } elseif ($isScript) { + if (Get-Command 'chmod' -ErrorAction SilentlyContinue) { chmod +x $Path - if ($LastExitCode -ne 0) - { + if ($LastExitCode -ne 0) { throw 'chmod failed'; } } - try - { + try { $result = & $Path $action $value $failed = -not $? - } - catch - { + } catch { $result = $_ $failed = $true } - if ($failed) - { + if ($failed) { throw "Script execution failed: $Path $action $value | output: $result" } return $result - } - else - { - switch ($action) - { - 'get-version' - { + } else { + switch ($action) { + 'get-version' { return (Get-Content $Path -Raw | ConvertFrom-StringData).version } - 'get-repo' - { + 'get-repo' { return (Get-Content $Path -Raw | ConvertFrom-StringData).repo } - 'set-version' - { + 'set-version' { $content = Get-Content $Path $content = $content -replace '^(?version *= *).*$', "`${prop}$value" $content | Out-File $Path $readVersion = (Get-Content $Path -Raw | ConvertFrom-StringData).version - if ("$readVersion" -ne "$value") - { + if ("$readVersion" -ne "$value") { throw "Update failed - read-after-write yielded '$readVersion' instead of expected '$value'" } } - Default - { + default { throw "Unknown action $action" } } @@ -150,27 +130,20 @@ if (-not $isSubmodule) . "$PSScriptRoot/cmake-functions.ps1" } -if ("$Tag" -eq '') -{ - if ($isSubmodule) - { +if ("$Tag" -eq '') { + if ($isSubmodule) { git submodule update --init --no-fetch --single-branch $Path Push-Location $Path - try - { + try { $originalTag = $(git describe --tags) git fetch --tags [string[]]$tags = $(git tag --list) $url = $(git remote get-url origin) $mainBranch = $(git remote show origin | Select-String 'HEAD branch: (.*)').Matches[0].Groups[1].Value - } - finally - { + } finally { Pop-Location } - } - else - { + } else { $originalTag = DependencyConfig 'get-version' $url = DependencyConfig 'get-repo' @@ -179,8 +152,7 @@ if ("$Tag" -eq '') $tags = $tags | ForEach-Object { ($_ -split '\s+')[1] -replace '^refs/tags/', '' } $headRef = ($(git ls-remote $url HEAD) -split '\s+')[0] - if ("$headRef" -eq '') - { + if ("$headRef" -eq '') { throw "Couldn't determine repository head (no ref returned by ls-remote HEAD" } $mainBranch = (git ls-remote --heads $url | Where-Object { $_.StartsWith($headRef) }) -replace '.*\srefs/heads/', '' @@ -188,8 +160,42 @@ if ("$Tag" -eq '') $url = $url -replace '\.git$', '' - if ("$Pattern" -eq '') - { + # Filter by GitHub release titles if pattern is provided + if ("$GhTitlePattern" -ne '') { + Write-Host "Filtering tags by GitHub release title pattern '$GhTitlePattern'" + + # Parse GitHub repo owner/name from URL + if ($url -notmatch 'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$') { + throw "Could not parse GitHub owner/repo from URL: $url" + } + + $owner, $repo = $Matches[1], $Matches[2] + + # Fetch releases from GitHub API + $releases = @(gh api "repos/$owner/$repo/releases" --paginate --jq '.[] | {tag_name: .tag_name, name: .name}' | ConvertFrom-Json) + if ($LASTEXITCODE -ne 0) { + throw "Failed to fetch GitHub releases from $owner/$repo (exit code: $LASTEXITCODE)" + } + + # Find tags that have matching release titles + $validTags = @{} + foreach ($release in $releases) { + if ($release.name -match $GhTitlePattern) { + $validTags[$release.tag_name] = $true + } + } + + # Filter tags to only include those with matching release titles + $originalTagCount = $tags.Length + $tags = @($tags | Where-Object { $validTags.ContainsKey($_) }) + Write-Host "GitHub release title filtering: $originalTagCount -> $($tags.Count) tags" + + if ($tags.Count -eq 0) { + throw "Found no tags with GitHub releases matching title pattern '$GhTitlePattern'" + } + } + + if ("$Pattern" -eq '') { # Use a default pattern that excludes pre-releases $Pattern = '^v?([0-9.]+)$' } @@ -197,8 +203,7 @@ if ("$Tag" -eq '') Write-Host "Filtering tags with pattern '$Pattern'" $tags = $tags -match $Pattern - if ($tags.Length -le 0) - { + if ($tags.Length -le 0) { throw "Found no tags matching pattern '$Pattern'" } @@ -207,14 +212,11 @@ if ("$Tag" -eq '') Write-Host "Sorted tags: $tags" $latestTag = $tags[-1] - if (("$originalTag" -ne '') -and ("$latestTag" -ne '') -and ("$latestTag" -ne "$originalTag")) - { - do - { + if (("$originalTag" -ne '') -and ("$latestTag" -ne '') -and ("$latestTag" -ne "$originalTag")) { + do { # It's possible that the dependency was updated to a pre-release version manually in which case we don't want to # roll back, even though it's not the latest version matching the configured pattern. - if ((GetComparableVersion $originalTag) -ge (GetComparableVersion $latestTag)) - { + if ((GetComparableVersion $originalTag) -ge (GetComparableVersion $latestTag)) { Write-Host "SemVer represented by the original tag '$originalTag' is newer than the latest tag '$latestTag'. Skipping update." $latestTag = $originalTag break @@ -224,8 +226,7 @@ if ("$Tag" -eq '') $refs = $(git ls-remote --tags $url) $refOriginal = (($refs -match "refs/tags/$originalTag" ) -split '[ \t]') | Select-Object -First 1 $refLatest = (($refs -match "refs/tags/$latestTag" ) -split '[ \t]') | Select-Object -First 1 - if ($refOriginal -eq $refLatest) - { + if ($refOriginal -eq $refLatest) { Write-Host "Latest tag '$latestTag' points to the same commit as the original tag '$originalTag'. Skipping update." $latestTag = $originalTag break @@ -241,23 +242,19 @@ if ("$Tag" -eq '') SetOutput 'url' $url SetOutput 'mainBranch' $mainBranch - if ("$originalTag" -eq "$latestTag") - { + if ("$originalTag" -eq "$latestTag") { return } $Tag = $latestTag } -if ($isSubmodule) -{ +if ($isSubmodule) { Write-Host "Updating submodule $Path to $Tag" Push-Location $Path git checkout $Tag Pop-Location -} -else -{ +} else { Write-Host "Updating 'version' in $Path to $Tag" DependencyConfig 'set-version' $tag } diff --git a/updater/tests/update-dependency.Tests.ps1 b/updater/tests/update-dependency.Tests.ps1 index 1437fba..6b77a42 100644 --- a/updater/tests/update-dependency.Tests.ps1 +++ b/updater/tests/update-dependency.Tests.ps1 @@ -1,7 +1,11 @@ BeforeAll { - function UpdateDependency([Parameter(Mandatory = $true)][string] $path, [string] $pattern = $null) + function UpdateDependency([Parameter(Mandatory = $true)][string] $path, [string] $pattern = $null, [string] $ghTitlePattern = $null) { - $result = & "$PSScriptRoot/../scripts/update-dependency.ps1" -Path $path -Pattern $pattern + $params = @{ Path = $path } + if ($pattern) { $params.Pattern = $pattern } + if ($ghTitlePattern) { $params.GhTitlePattern = $ghTitlePattern } + + $result = & "$PSScriptRoot/../scripts/update-dependency.ps1" @params if (-not $?) { throw $result @@ -426,4 +430,62 @@ FetchContent_Declare( { UpdateDependency "$testFile#nonexistent" } | Should -Throw "*FetchContent_Declare for 'nonexistent' not found*" } } + + Context 'gh-title-pattern' { + It 'filters by GitHub release title pattern' { + $testFile = "$testDir/test.properties" + # Use sentry-cocoa repo which has releases with "(Stable)" suffix + $repo = 'https://github.com/getsentry/sentry-cocoa' + @("repo=$repo", 'version=0') | Out-File $testFile + + # Test filtering for releases with "(Stable)" suffix + UpdateDependency $testFile '' '\(Stable\)$' + + $content = Get-Content $testFile + $version = ($content | Where-Object { $_ -match '^version\s*=\s*(.+)$' }) -replace '^version\s*=\s*', '' + + # Verify that a version was selected (should be a stable release) + $version | Should -Not -Be '0' + $version | Should -Match '^\d+\.\d+\.\d+$' + } + + It 'throws error when no releases match title pattern' { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/github-workflows' + @("repo=$repo", 'version=0') | Out-File $testFile + + # Use a pattern that should match no releases + { UpdateDependency $testFile '' 'NonExistentPattern' } | Should -Throw '*Found no tags with GitHub releases matching title pattern*' + } + + It 'matches specific release version by exact title pattern' { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/github-workflows' + @("repo=$repo", 'version=0') | Out-File $testFile + + # Target a specific known release by exact title match + UpdateDependency $testFile '' '^2\.11\.1$' + + $content = Get-Content $testFile + $version = ($content | Where-Object { $_ -match '^version\s*=\s*(.+)$' }) -replace '^version\s*=\s*', '' + + # Should get exactly version 2.11.1 (with or without 'v' prefix) + $version | Should -Match '^v?2\.11\.1$' + } + + It 'works without title pattern (backward compatibility)' { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/sentry-cocoa' + @("repo=$repo", 'version=0') | Out-File $testFile + + # Test without title pattern should work as before + UpdateDependency $testFile '^8\.' + + $content = Get-Content $testFile + $version = ($content | Where-Object { $_ -match '^version\s*=\s*(.+)$' }) -replace '^version\s*=\s*', '' + + # Should get a version starting with 8 + $version | Should -Match '^8\.' + } + } }