Skip to content

Commit de9e3fa

Browse files
vaindclaude
andauthored
feat: Support GitHub release title pattern matching (#117)
* feat: add gh-title-pattern input for release channel filtering Implements Issue #85 by adding a new `gh-title-pattern` input parameter that allows filtering releases by GitHub release titles using regex patterns. Features: - Filter releases by title patterns (e.g., '\(Stable\)$' for stable releases) - Uses GitHub API to fetch release metadata - Fully backward compatible when parameter is not specified - Comprehensive test coverage with error handling Usage example: ```yaml uses: getsentry/github-workflows/updater@v3 with: path: modules/sentry-cocoa name: Cocoa SDK (Stable) gh-title-pattern: '\(Stable\)$' ``` 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: remove --paginate flag from GitHub API call The --paginate flag returns separate pages, not combined results. Using the default API call (first 30 releases) is sufficient for most repositories when filtering by release title patterns. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: clean up GitHub release filtering code Simplify the implementation with: - Consolidated URL validation with single regex match - Cleaner variable assignment using tuple unpacking - Simplified array handling by wrapping API result in @() - Removed unnecessary null/single object checks - More concise comments and clearer logic flow 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: streamline conditional checks and improve code readability in update-dependency.ps1 * refactor: remove unnecessary comment in update-dependency.Tests.ps1 * test: add deterministic test case for specific version matching - Add test that matches exact release version (2.11.1) by title pattern - This provides a deterministic test case that verifies exact behavior - Fix error handling to ensure proper error message when no releases match - All 4 gh-title-pattern tests now pass 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: add changelog entry for GitHub release title pattern filtering Documents the new gh-title-pattern feature that allows users to filter releases by their GitHub release titles using regex patterns. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: set GH_TOKEN env var for GitHub CLI in CI Ensures that gh api commands work properly in CI environments by setting the GH_TOKEN environment variable to the provided api-token input. This fixes the issue where GitHub release title filtering would fail silently in CI due to lack of authentication. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: pass GH_TOKEN environment variable to scripts for authentication * docs: clarify changelog entry for GitHub release title pattern filtering * fix: set GH_TOKEN environment variable for Invoke-Pester step in CI * fix: enhance error handling for GitHub releases fetching in update-dependency script --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1dbbc41 commit de9e3fa

File tree

6 files changed

+166
-79
lines changed

6 files changed

+166
-79
lines changed

.github/workflows/script-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
- run: Invoke-Pester
2626
working-directory: updater
2727
shell: pwsh
28+
env:
29+
GH_TOKEN: ${{ github.token }}
2830

2931
danger:
3032
name: Danger JS Tests

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ To update your existing Danger workflows:
4444
4545
### Features
4646
47+
- 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))
4748
- Updater now supports dependencies without changelog files by falling back to git commit messages ([#116](https://github.com/getsentry/github-workflows/pull/116))
4849
- Danger - Improve conventional commit scope handling, and non-conventional PR title support ([#105](https://github.com/getsentry/github-workflows/pull/105))
4950
- Add Proguard artifact endpoint for Android builds in sentry-server ([#100](https://github.com/getsentry/github-workflows/pull/100))

updater/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ jobs:
3232
pattern: '^1\.' # Limit to major version '1'
3333
api-token: ${{ secrets.CI_DEPLOY_KEY }}
3434

35+
# Update to stable releases only by filtering GitHub release titles
36+
cocoa-stable:
37+
runs-on: ubuntu-latest
38+
steps:
39+
- uses: getsentry/github-workflows/updater@v3
40+
with:
41+
path: modules/sentry-cocoa
42+
name: Cocoa SDK (Stable)
43+
gh-title-pattern: '\(Stable\)$' # Only releases with "(Stable)" suffix
44+
api-token: ${{ secrets.CI_DEPLOY_KEY }}
45+
3546
# Update a properties file
3647
cli:
3748
runs-on: ubuntu-latest
@@ -91,6 +102,10 @@ jobs:
91102
* type: string
92103
* required: false
93104
* default: ''
105+
* `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).
106+
* type: string
107+
* required: false
108+
* default: ''
94109
* `changelog-entry`: Whether to add a changelog entry for the update.
95110
* type: boolean
96111
* required: false

updater/action.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ inputs:
1313
description: 'RegEx pattern that will be matched against available versions when picking the latest one.'
1414
required: false
1515
default: ''
16+
gh-title-pattern:
17+
description: 'RegEx pattern to match against GitHub release titles. Only releases with matching titles will be considered.'
18+
required: false
19+
default: ''
1620
changelog-entry:
1721
description: 'Whether to add a changelog entry for the update.'
1822
required: false
@@ -107,7 +111,9 @@ runs:
107111
env:
108112
DEPENDENCY_PATH: ${{ inputs.path }}
109113
DEPENDENCY_PATTERN: ${{ inputs.pattern }}
110-
run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Pattern $env:DEPENDENCY_PATTERN
114+
GH_TITLE_PATTERN: ${{ inputs.gh-title-pattern }}
115+
GH_TOKEN: ${{ inputs.api-token }}
116+
run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Pattern $env:DEPENDENCY_PATTERN -GhTitlePattern $env:GH_TITLE_PATTERN
111117

112118
- name: Get the base repo info
113119
if: steps.target.outputs.latestTag != steps.target.outputs.originalTag
@@ -164,6 +170,8 @@ runs:
164170
- name: Get target changelog
165171
if: ${{ ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.root.outputs.changed == 'false') }}
166172
shell: pwsh
173+
env:
174+
GH_TOKEN: ${{ inputs.api-token }}
167175
run: |
168176
$changelog = ${{ github.action_path }}/scripts/get-changelog.ps1 `
169177
-RepoUrl '${{ steps.target.outputs.url }}' `
@@ -223,6 +231,7 @@ runs:
223231
shell: pwsh
224232
env:
225233
DEPENDENCY_PATH: ${{ inputs.path }}
234+
GH_TOKEN: ${{ inputs.api-token }}
226235
run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Tag '${{ steps.target.outputs.latestTag }}'
227236

228237
- name: Update Changelog
@@ -231,6 +240,7 @@ runs:
231240
env:
232241
DEPENDENCY_NAME: ${{ inputs.name }}
233242
CHANGELOG_SECTION: ${{ inputs.changelog-section }}
243+
GH_TOKEN: ${{ inputs.api-token }}
234244
run: |
235245
${{ github.action_path }}/scripts/update-changelog.ps1 `
236246
-Name $env:DEPENDENCY_NAME `

updater/scripts/update-dependency.ps1

Lines changed: 73 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ param(
1212
[Parameter(Mandatory = $true)][string] $Path,
1313
# RegEx pattern that will be matched against available versions when picking the latest one
1414
[string] $Pattern = '',
15+
# RegEx pattern to match against GitHub release titles. Only releases with matching titles will be considered
16+
[string] $GhTitlePattern = '',
1517
# Specific version - if passed, no discovery is performed and the version is set directly
1618
[string] $Tag = ''
1719
)
1820

21+
$ErrorActionPreference = 'Stop'
1922
Set-StrictMode -Version latest
2023
. "$PSScriptRoot/common.ps1"
2124

@@ -37,31 +40,24 @@ if ($Path -match '^(.+\.cmake)(#(.+))?$') {
3740
$isCMakeFile = $false
3841
}
3942

40-
if (-not (Test-Path $Path ))
41-
{
43+
if (-not (Test-Path $Path )) {
4244
throw "Dependency $Path doesn't exit";
4345
}
4446

4547
# If it's a directory, we consider it a submodule dependendency. Otherwise, it must a properties-style file or a script.
4648
$isSubmodule = (Test-Path $Path -PathType Container)
4749

48-
function SetOutput([string] $name, $value)
49-
{
50-
if (Test-Path env:GITHUB_OUTPUT)
51-
{
50+
function SetOutput([string] $name, $value) {
51+
if (Test-Path env:GITHUB_OUTPUT) {
5252
"$name=$value" | Tee-Object $env:GITHUB_OUTPUT -Append
53-
}
54-
else
55-
{
53+
} else {
5654
"$name=$value"
5755
}
5856
}
5957

60-
if (-not $isSubmodule)
61-
{
58+
if (-not $isSubmodule) {
6259
$isScript = $Path -match '\.(ps1|sh)$'
63-
function DependencyConfig ([Parameter(Mandatory = $true)][string] $action, [string] $value = $null)
64-
{
60+
function DependencyConfig ([Parameter(Mandatory = $true)][string] $action, [string] $value = $null) {
6561
if ($isCMakeFile) {
6662
# CMake file handling
6763
switch ($action) {
@@ -82,64 +78,48 @@ if (-not $isSubmodule)
8278
'set-version' {
8379
Update-CMakeFile $Path $cmakeDep $value
8480
}
85-
Default {
81+
default {
8682
throw "Unknown action $action"
8783
}
8884
}
89-
}
90-
elseif ($isScript)
91-
{
92-
if (Get-Command 'chmod' -ErrorAction SilentlyContinue)
93-
{
85+
} elseif ($isScript) {
86+
if (Get-Command 'chmod' -ErrorAction SilentlyContinue) {
9487
chmod +x $Path
95-
if ($LastExitCode -ne 0)
96-
{
88+
if ($LastExitCode -ne 0) {
9789
throw 'chmod failed';
9890
}
9991
}
100-
try
101-
{
92+
try {
10293
$result = & $Path $action $value
10394
$failed = -not $?
104-
}
105-
catch
106-
{
95+
} catch {
10796
$result = $_
10897
$failed = $true
10998
}
110-
if ($failed)
111-
{
99+
if ($failed) {
112100
throw "Script execution failed: $Path $action $value | output: $result"
113101
}
114102
return $result
115-
}
116-
else
117-
{
118-
switch ($action)
119-
{
120-
'get-version'
121-
{
103+
} else {
104+
switch ($action) {
105+
'get-version' {
122106
return (Get-Content $Path -Raw | ConvertFrom-StringData).version
123107
}
124-
'get-repo'
125-
{
108+
'get-repo' {
126109
return (Get-Content $Path -Raw | ConvertFrom-StringData).repo
127110
}
128-
'set-version'
129-
{
111+
'set-version' {
130112
$content = Get-Content $Path
131113
$content = $content -replace '^(?<prop>version *= *).*$', "`${prop}$value"
132114
$content | Out-File $Path
133115

134116
$readVersion = (Get-Content $Path -Raw | ConvertFrom-StringData).version
135117

136-
if ("$readVersion" -ne "$value")
137-
{
118+
if ("$readVersion" -ne "$value") {
138119
throw "Update failed - read-after-write yielded '$readVersion' instead of expected '$value'"
139120
}
140121
}
141-
Default
142-
{
122+
default {
143123
throw "Unknown action $action"
144124
}
145125
}
@@ -150,27 +130,20 @@ if (-not $isSubmodule)
150130
. "$PSScriptRoot/cmake-functions.ps1"
151131
}
152132

153-
if ("$Tag" -eq '')
154-
{
155-
if ($isSubmodule)
156-
{
133+
if ("$Tag" -eq '') {
134+
if ($isSubmodule) {
157135
git submodule update --init --no-fetch --single-branch $Path
158136
Push-Location $Path
159-
try
160-
{
137+
try {
161138
$originalTag = $(git describe --tags)
162139
git fetch --tags
163140
[string[]]$tags = $(git tag --list)
164141
$url = $(git remote get-url origin)
165142
$mainBranch = $(git remote show origin | Select-String 'HEAD branch: (.*)').Matches[0].Groups[1].Value
166-
}
167-
finally
168-
{
143+
} finally {
169144
Pop-Location
170145
}
171-
}
172-
else
173-
{
146+
} else {
174147
$originalTag = DependencyConfig 'get-version'
175148
$url = DependencyConfig 'get-repo'
176149

@@ -179,26 +152,58 @@ if ("$Tag" -eq '')
179152
$tags = $tags | ForEach-Object { ($_ -split '\s+')[1] -replace '^refs/tags/', '' }
180153

181154
$headRef = ($(git ls-remote $url HEAD) -split '\s+')[0]
182-
if ("$headRef" -eq '')
183-
{
155+
if ("$headRef" -eq '') {
184156
throw "Couldn't determine repository head (no ref returned by ls-remote HEAD"
185157
}
186158
$mainBranch = (git ls-remote --heads $url | Where-Object { $_.StartsWith($headRef) }) -replace '.*\srefs/heads/', ''
187159
}
188160

189161
$url = $url -replace '\.git$', ''
190162

191-
if ("$Pattern" -eq '')
192-
{
163+
# Filter by GitHub release titles if pattern is provided
164+
if ("$GhTitlePattern" -ne '') {
165+
Write-Host "Filtering tags by GitHub release title pattern '$GhTitlePattern'"
166+
167+
# Parse GitHub repo owner/name from URL
168+
if ($url -notmatch 'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$') {
169+
throw "Could not parse GitHub owner/repo from URL: $url"
170+
}
171+
172+
$owner, $repo = $Matches[1], $Matches[2]
173+
174+
# Fetch releases from GitHub API
175+
$releases = @(gh api "repos/$owner/$repo/releases" --paginate --jq '.[] | {tag_name: .tag_name, name: .name}' | ConvertFrom-Json)
176+
if ($LASTEXITCODE -ne 0) {
177+
throw "Failed to fetch GitHub releases from $owner/$repo (exit code: $LASTEXITCODE)"
178+
}
179+
180+
# Find tags that have matching release titles
181+
$validTags = @{}
182+
foreach ($release in $releases) {
183+
if ($release.name -match $GhTitlePattern) {
184+
$validTags[$release.tag_name] = $true
185+
}
186+
}
187+
188+
# Filter tags to only include those with matching release titles
189+
$originalTagCount = $tags.Length
190+
$tags = @($tags | Where-Object { $validTags.ContainsKey($_) })
191+
Write-Host "GitHub release title filtering: $originalTagCount -> $($tags.Count) tags"
192+
193+
if ($tags.Count -eq 0) {
194+
throw "Found no tags with GitHub releases matching title pattern '$GhTitlePattern'"
195+
}
196+
}
197+
198+
if ("$Pattern" -eq '') {
193199
# Use a default pattern that excludes pre-releases
194200
$Pattern = '^v?([0-9.]+)$'
195201
}
196202

197203
Write-Host "Filtering tags with pattern '$Pattern'"
198204
$tags = $tags -match $Pattern
199205

200-
if ($tags.Length -le 0)
201-
{
206+
if ($tags.Length -le 0) {
202207
throw "Found no tags matching pattern '$Pattern'"
203208
}
204209

@@ -207,14 +212,11 @@ if ("$Tag" -eq '')
207212
Write-Host "Sorted tags: $tags"
208213
$latestTag = $tags[-1]
209214

210-
if (("$originalTag" -ne '') -and ("$latestTag" -ne '') -and ("$latestTag" -ne "$originalTag"))
211-
{
212-
do
213-
{
215+
if (("$originalTag" -ne '') -and ("$latestTag" -ne '') -and ("$latestTag" -ne "$originalTag")) {
216+
do {
214217
# It's possible that the dependency was updated to a pre-release version manually in which case we don't want to
215218
# roll back, even though it's not the latest version matching the configured pattern.
216-
if ((GetComparableVersion $originalTag) -ge (GetComparableVersion $latestTag))
217-
{
219+
if ((GetComparableVersion $originalTag) -ge (GetComparableVersion $latestTag)) {
218220
Write-Host "SemVer represented by the original tag '$originalTag' is newer than the latest tag '$latestTag'. Skipping update."
219221
$latestTag = $originalTag
220222
break
@@ -224,8 +226,7 @@ if ("$Tag" -eq '')
224226
$refs = $(git ls-remote --tags $url)
225227
$refOriginal = (($refs -match "refs/tags/$originalTag" ) -split '[ \t]') | Select-Object -First 1
226228
$refLatest = (($refs -match "refs/tags/$latestTag" ) -split '[ \t]') | Select-Object -First 1
227-
if ($refOriginal -eq $refLatest)
228-
{
229+
if ($refOriginal -eq $refLatest) {
229230
Write-Host "Latest tag '$latestTag' points to the same commit as the original tag '$originalTag'. Skipping update."
230231
$latestTag = $originalTag
231232
break
@@ -241,23 +242,19 @@ if ("$Tag" -eq '')
241242
SetOutput 'url' $url
242243
SetOutput 'mainBranch' $mainBranch
243244

244-
if ("$originalTag" -eq "$latestTag")
245-
{
245+
if ("$originalTag" -eq "$latestTag") {
246246
return
247247
}
248248

249249
$Tag = $latestTag
250250
}
251251

252-
if ($isSubmodule)
253-
{
252+
if ($isSubmodule) {
254253
Write-Host "Updating submodule $Path to $Tag"
255254
Push-Location $Path
256255
git checkout $Tag
257256
Pop-Location
258-
}
259-
else
260-
{
257+
} else {
261258
Write-Host "Updating 'version' in $Path to $Tag"
262259
DependencyConfig 'set-version' $tag
263260
}

0 commit comments

Comments
 (0)