diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d346e6..136d5fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +### Features + +- Updater - Add `post-update-script` input parameter to run custom scripts after dependency updates ([#130](https://github.com/getsentry/github-workflows/pull/130)) + - Scripts receive original and new version as arguments + - Support both bash (`.sh`) and PowerShell (`.ps1`) scripts + - Enables workflows like updating lock files, running code generators, or modifying configuration files + ### Fixes - Updater - Fix boolean input handling for `changelog-entry` parameter and add input validation ([#127](https://github.com/getsentry/github-workflows/pull/127)) diff --git a/updater/README.md b/updater/README.md index 633af21..a25f4e5 100644 --- a/updater/README.md +++ b/updater/README.md @@ -95,6 +95,18 @@ jobs: target-branch: v7 pattern: '^1\.' # Limit to major version '1' api-token: ${{ secrets.CI_DEPLOY_KEY }} + + # Use a post-update script (sh or ps1) to make additional changes after dependency update + # The script receives two arguments: original version and new version + post-update-script: + runs-on: ubuntu-latest + steps: + - uses: getsentry/github-workflows/updater@v3 + with: + path: modules/sentry-cocoa + name: Cocoa SDK + post-update-script: scripts/post-update.sh # Receives args: $1=old version, $2=new version + api-token: ${{ secrets.CI_DEPLOY_KEY }} ``` ## Inputs @@ -135,12 +147,45 @@ jobs: * type: string * required: false * default: '' (uses repository default branch) +* `post-update-script`: Optional script to run after successful dependency update. Can be a bash script (`.sh`) or PowerShell script (`.ps1`). The script will be executed in the repository root directory before PR creation. The script receives two arguments: + * `$1` / `$args[0]` - The original version (version before update) + * `$2` / `$args[1]` - The new version (version after update) + * type: string + * required: false + * default: '' * `api-token`: Token for the repo. Can be passed in using `${{ secrets.GITHUB_TOKEN }}`. If you provide the usual `${{ github.token }}`, no followup CI will run on the created PR. If you want CI to run on the PRs created by the Updater, you need to provide custom user-specific auth token. * type: string * required: true +### Post-Update Script Example + +**Bash script** (`scripts/post-update.sh`): + +```bash +#!/usr/bin/env bash +set -euo pipefail + +ORIGINAL_VERSION="$1" +NEW_VERSION="$2" + +echo "Updated from $ORIGINAL_VERSION to $NEW_VERSION" +# Make additional changes to repository files here +``` + +**PowerShell script** (`scripts/post-update.ps1`): + +```powershell +param( + [Parameter(Mandatory = $true)][string] $OriginalVersion, + [Parameter(Mandatory = $true)][string] $NewVersion +) + +Write-Output "Updated from $OriginalVersion to $NewVersion" +# Make additional changes to repository files here +``` + ## Outputs * `prUrl`: The created/updated PR's URL. diff --git a/updater/action.yml b/updater/action.yml index a21da6c..5349ec9 100644 --- a/updater/action.yml +++ b/updater/action.yml @@ -36,6 +36,10 @@ inputs: api-token: description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}' required: true + post-update-script: + description: 'Optional script to run after successful dependency update. Can be a bash script (.sh) or PowerShell script (.ps1). The script will be executed in the caller-repo directory before PR creation.' + required: false + default: '' outputs: prUrl: @@ -102,6 +106,17 @@ runs: } Write-Output "✓ PR strategy value '${{ inputs.pr-strategy }}' is valid" + - name: Validate post-update-script + if: ${{ inputs.post-update-script != '' }} + shell: pwsh + run: | + # Validate that inputs.post-update-script contains only safe characters + if ('${{ inputs.post-update-script }}' -notmatch '^[a-zA-Z0-9_\./#\s-]+$') { + Write-Output "::error::Invalid post-update-script path: '${{ inputs.post-update-script }}'. Only alphanumeric characters, spaces, and _-./# are allowed." + exit 1 + } + Write-Output "✓ Post-update script path '${{ inputs.post-update-script }}' is valid" + # What we need to accomplish: # * update to the latest tag # * create a PR @@ -134,8 +149,9 @@ runs: DEPENDENCY_PATH: ${{ inputs.path }} DEPENDENCY_PATTERN: ${{ inputs.pattern }} GH_TITLE_PATTERN: ${{ inputs.gh-title-pattern }} + POST_UPDATE_SCRIPT: ${{ inputs.post-update-script }} 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 + run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Pattern $env:DEPENDENCY_PATTERN -GhTitlePattern $env:GH_TITLE_PATTERN -PostUpdateScript $env:POST_UPDATE_SCRIPT - name: Get the base repo info if: steps.target.outputs.latestTag != steps.target.outputs.originalTag @@ -270,8 +286,9 @@ runs: working-directory: caller-repo env: DEPENDENCY_PATH: ${{ inputs.path }} + POST_UPDATE_SCRIPT: ${{ inputs.post-update-script }} GH_TOKEN: ${{ inputs.api-token }} - run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Tag '${{ steps.target.outputs.latestTag }}' + run: ${{ github.action_path }}/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Tag '${{ steps.target.outputs.latestTag }}' -PostUpdateScript $env:POST_UPDATE_SCRIPT - name: Update Changelog if: ${{ inputs.changelog-entry == 'true' && ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.root.outputs.changed == 'false') }} diff --git a/updater/scripts/update-dependency.ps1 b/updater/scripts/update-dependency.ps1 index c825858..648a3a8 100644 --- a/updater/scripts/update-dependency.ps1 +++ b/updater/scripts/update-dependency.ps1 @@ -15,7 +15,10 @@ param( # 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 = '' + [string] $Tag = '', + # Optional post-update script to run after successful dependency update + # The script receives the original and new version as arguments + [string] $PostUpdateScript = '' ) $ErrorActionPreference = 'Stop' @@ -249,6 +252,9 @@ if ("$Tag" -eq '') { $Tag = $latestTag } +$originalTagForPostUpdate = if ($originalTag) { $originalTag } else { '' } +$newTagForPostUpdate = $Tag + if ($isSubmodule) { Write-Host "Updating submodule $Path to $Tag" Push-Location $Path @@ -258,3 +264,25 @@ if ($isSubmodule) { Write-Host "Updating 'version' in $Path to $Tag" DependencyConfig 'set-version' $tag } + +# Run post-update script if provided +if ("$PostUpdateScript" -ne '') { + Write-Host "Running post-update script: $PostUpdateScript" + if (-not (Test-Path $PostUpdateScript)) { + throw "Post-update script not found: $PostUpdateScript" + } + + if (Get-Command 'chmod' -ErrorAction SilentlyContinue) { + chmod +x $PostUpdateScript + if ($LastExitCode -ne 0) { + throw 'chmod failed'; + } + } + + & $PostUpdateScript "$originalTag" "$tag" + if ($LastExitCode -ne 0) { + throw "Post-update script failed with exit code $LastExitCode" + } + + Write-Host '✓ Post-update script completed successfully' +} diff --git a/updater/tests/update-dependency.Tests.ps1 b/updater/tests/update-dependency.Tests.ps1 index 6b77a42..0d92a02 100644 --- a/updater/tests/update-dependency.Tests.ps1 +++ b/updater/tests/update-dependency.Tests.ps1 @@ -1,9 +1,10 @@ BeforeAll { - function UpdateDependency([Parameter(Mandatory = $true)][string] $path, [string] $pattern = $null, [string] $ghTitlePattern = $null) + function UpdateDependency([Parameter(Mandatory = $true)][string] $path, [string] $pattern = $null, [string] $ghTitlePattern = $null, [string] $postUpdateScript = $null) { $params = @{ Path = $path } if ($pattern) { $params.Pattern = $pattern } if ($ghTitlePattern) { $params.GhTitlePattern = $ghTitlePattern } + if ($postUpdateScript) { $params.PostUpdateScript = $postUpdateScript } $result = & "$PSScriptRoot/../scripts/update-dependency.ps1" @params if (-not $?) @@ -488,4 +489,123 @@ FetchContent_Declare( $version | Should -Match '^8\.' } } + + Context 'post-update-script' { + It 'runs PowerShell post-update script with version arguments' { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/sentry-cli' + @("repo=$repo", 'version=0') | Out-File $testFile + + $postUpdateScript = "$testDir/post-update-test.ps1" + $markerFile = "$testDir/post-update-marker.txt" + @' +param([string] $originalVersion, [string] $newVersion) +"$originalVersion|$newVersion" | Out-File +'@ + " '$markerFile'" | Out-File $postUpdateScript + + UpdateDependency $testFile '^0\.' -postUpdateScript $postUpdateScript + + # Verify post-update script was executed + Test-Path $markerFile | Should -Be $true + $markerContent = Get-Content $markerFile + $markerContent | Should -Match '^0\|0\.28\.0$' + + # Clean up + Remove-Item $markerFile -ErrorAction SilentlyContinue + Remove-Item $postUpdateScript -ErrorAction SilentlyContinue + } + + It 'runs bash post-update script with version arguments' -Skip:$IsWindows { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/sentry-cli' + @("repo=$repo", 'version=0') | Out-File $testFile + + $postUpdateScript = "$testDir/post-update-test.sh" + $markerFile = "$testDir/post-update-marker.txt" + @" +#!/usr/bin/env bash +set -euo pipefail +echo "`$1|`$2" > '$markerFile' +"@ | Out-File $postUpdateScript + + UpdateDependency $testFile '^0\.' -postUpdateScript $postUpdateScript + + # Verify post-update script was executed + Test-Path $markerFile | Should -Be $true + $markerContent = Get-Content $markerFile + $markerContent | Should -Match '^0\|0\.28\.0$' + + # Clean up + Remove-Item $markerFile -ErrorAction SilentlyContinue + Remove-Item $postUpdateScript -ErrorAction SilentlyContinue + } + + It 'fails when post-update script does not exist' { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/sentry-cli' + @("repo=$repo", 'version=0') | Out-File $testFile + + $postUpdateScript = "$testDir/nonexistent-script.ps1" + + { UpdateDependency $testFile '^0\.' -postUpdateScript $postUpdateScript } | Should -Throw '*Post-update script not found*' + } + + It 'fails when PowerShell post-update script exits with error' { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/sentry-cli' + @("repo=$repo", 'version=0') | Out-File $testFile + + $postUpdateScript = "$testDir/failing-post-update.ps1" + @' +param([string] $originalVersion, [string] $newVersion) +throw "Post-update script failed intentionally" +'@ | Out-File $postUpdateScript + + { UpdateDependency $testFile '^0\.' -postUpdateScript $postUpdateScript } | Should -Throw '*Post-update script failed*' + + # Clean up + Remove-Item $postUpdateScript -ErrorAction SilentlyContinue + } + + It 'fails when bash post-update script exits with error' -Skip:$IsWindows { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/sentry-cli' + @("repo=$repo", 'version=0') | Out-File $testFile + + $postUpdateScript = "$testDir/failing-post-update.sh" + @' +#!/usr/bin/env bash +exit 1 +'@ | Out-File $postUpdateScript + + { UpdateDependency $testFile '^0\.' -postUpdateScript $postUpdateScript } | Should -Throw '*Post-update script failed*' + + # Clean up + Remove-Item $postUpdateScript -ErrorAction SilentlyContinue + } + + It 'receives empty string for original version when updating from scratch' { + $testFile = "$testDir/test.properties" + $repo = 'https://github.com/getsentry/sentry-cli' + @("repo=$repo", 'version=') | Out-File $testFile + + $postUpdateScript = "$testDir/post-update-empty-original.ps1" + $markerFile = "$testDir/post-update-marker-empty.txt" + @' +param([string] $originalVersion, [string] $newVersion) +"original=[$originalVersion]|new=[$newVersion]" | Out-File +'@ + " '$markerFile'" | Out-File $postUpdateScript + + UpdateDependency $testFile '^0\.' -postUpdateScript $postUpdateScript + + # Verify post-update script received empty original version + Test-Path $markerFile | Should -Be $true + $markerContent = Get-Content $markerFile + $markerContent | Should -Match 'original=\[\]\|new=\[0\.28\.0\]' + + # Clean up + Remove-Item $markerFile -ErrorAction SilentlyContinue + Remove-Item $postUpdateScript -ErrorAction SilentlyContinue + } + } }