Skip to content

Commit 3e331ba

Browse files
authored
Merge pull request #7 from chef/cw/version-bumper
Version Bumper (replacement for expeditor built_in:bump_version)
2 parents 8f6f486 + 2371861 commit 3e331ba

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
name: Version Bumper
2+
3+
description: |
4+
A reusable GitHub Action to automatically bump the version of a project based on pull request labels.
5+
It uses the `bump` gem to handle versioning and tagging. Though implemented in Ruby, it can be used
6+
with any language or project type.
7+
8+
This action is triggered when called by another workflow after a pull request is merged into the main branch.
9+
It determines the version bump type (major, minor, patch) based on the labels applied to the pull request.
10+
The action will automatically commit the version bump and create a new tag for the release.
11+
12+
By default, it will bump the patch version unless specified otherwise. Use Labels on your PR
13+
to control behavior. Define the 'Version Bump: Major', 'Version Bump: Minor',
14+
and "Version Bump: Skip" labels in your repository to use this action effectively.
15+
16+
The action updates version strings in the files specified by the `version_file_patterns` input parameter.
17+
You may use glob patterns. Files that do not exist will be ignored with a warning. The file VERSION
18+
is detected automatically.
19+
20+
Most of the complexity of this action is in the file processing and label-reading logic; the actual version
21+
bumping is entriely handled by the `bump` gem.
22+
23+
Example usage:
24+
```yaml
25+
uses: ./.github/workflows/version-bumper.yml
26+
with:
27+
version_file_patterns: |
28+
lib/*/version.rb
29+
package.json
30+
inspec.yml
31+
```
32+
33+
on:
34+
workflow_call:
35+
inputs:
36+
version_file_patterns:
37+
description: 'List of file patterns to search for version strings (one pattern per line). VERSION is detected automatically.'
38+
required: false
39+
type: string
40+
default: 'lib/*/version.rb'
41+
target_branch:
42+
description: 'The branch to push version changes to'
43+
required: false
44+
default: 'main'
45+
type: string
46+
git_username:
47+
description: 'Git username for commits'
48+
required: false
49+
default: 'Progress CI Automation'
50+
type: string
51+
git_email:
52+
description: 'Git email for commits'
53+
required: false
54+
default: 'ci@progress.com'
55+
type: string
56+
outputs:
57+
bump_level:
58+
description: 'The level that was bumped (major, minor, patch, or skipped)'
59+
value: ${{ jobs.bump-version.outputs.bump_level }}
60+
was_skipped:
61+
description: 'Whether the version bump was skipped'
62+
value: ${{ jobs.bump-version.outputs.was_skipped }}
63+
new_version:
64+
description: 'The new version after bumping'
65+
value: ${{ jobs.bump-version.outputs.new_version }}
66+
pr_number:
67+
description: 'The PR number that was processed'
68+
value: ${{ jobs.bump-version.outputs.pr_number }}
69+
70+
jobs:
71+
bump-version:
72+
runs-on: ubuntu-latest
73+
outputs:
74+
bump_level: ${{ steps.bump-type.outputs.level || 'skipped' }}
75+
was_skipped: ${{ steps.bump-type.outputs.skip }}
76+
new_version: ${{ steps.run-bump.outputs.new_version || '' }}
77+
pr_number: ${{ steps.get-pr-number.outputs.pr_number }}
78+
permissions:
79+
contents: write
80+
pull-requests: read
81+
82+
steps:
83+
- name: Checkout code
84+
uses: actions/checkout@v4
85+
with:
86+
fetch-depth: 0
87+
88+
- name: Set up Git
89+
run: |
90+
git config --global user.name "${{ inputs.git_username }}"
91+
git config --global user.email "${{ inputs.git_email }}"
92+
93+
- name: Set up Ruby
94+
uses: ruby/setup-ruby@v1
95+
with:
96+
ruby-version: 'ruby'
97+
# Do not run bundler - we just want the bump gem
98+
99+
- name: Install bump gem
100+
run: gem install bump
101+
102+
- name: Get PR number from context
103+
id: get-pr-number
104+
uses: actions/github-script@v7
105+
with:
106+
script: |
107+
// Check if we're running in the context of a PR event
108+
if (!context.payload.pull_request) {
109+
core.setFailed('This workflow must be run in a PR context');
110+
throw new Error('Not running in PR context');
111+
}
112+
113+
const prNumber = context.payload.pull_request.number;
114+
console.log(`Detected PR number from event context: ${prNumber}`);
115+
116+
if (!prNumber) {
117+
core.setFailed('Could not detect PR number from context');
118+
throw new Error('Failed to detect PR number');
119+
}
120+
121+
core.setOutput('pr_number', prNumber.toString());
122+
return prNumber.toString();
123+
result-encoding: string
124+
125+
- name: Fetch PR labels
126+
id: fetch-labels
127+
uses: actions/github-script@v7
128+
with:
129+
script: |
130+
const prNumber = parseInt(${{ steps.get-pr-number.outputs.pr_number }});
131+
132+
try {
133+
const { data: pr } = await github.rest.pulls.get({
134+
owner: context.repo.owner,
135+
repo: context.repo.repo,
136+
pull_number: prNumber
137+
});
138+
139+
const labelNames = pr.labels.map(label => label.name);
140+
console.log(`PR #${prNumber} has labels:`, labelNames);
141+
return JSON.stringify(labelNames);
142+
} catch (error) {
143+
console.log(`Error fetching PR #${prNumber}: ${error.message}`);
144+
core.setFailed(`Failed to fetch PR #${prNumber}: ${error.message}`);
145+
throw new Error(`Failed to fetch PR #${prNumber}`);
146+
}
147+
result-encoding: string
148+
149+
- name: Determine bump type from labels
150+
id: bump-type
151+
run: |
152+
# Store the labels in a variable and properly parse JSON format
153+
LABELS='${{ steps.fetch-labels.outputs.result }}'
154+
155+
# First check if we should skip the version bump entirely
156+
if echo "$LABELS" | grep -q "Version Bump: Skip"; then
157+
echo "skip=true" >> $GITHUB_OUTPUT
158+
echo "Found label: Version Bump: Skip - will skip version bump"
159+
exit 0
160+
fi
161+
162+
# Check for other version bump labels
163+
if echo "$LABELS" | grep -q "Version Bump: Major"; then
164+
echo "level=major" >> $GITHUB_OUTPUT
165+
echo "Found label: Version Bump: Major"
166+
elif echo "$LABELS" | grep -q "Version Bump: Minor"; then
167+
echo "level=minor" >> $GITHUB_OUTPUT
168+
echo "Found label: Version Bump: Minor"
169+
else
170+
echo "level=patch" >> $GITHUB_OUTPUT
171+
echo "No specific version bump label found, defaulting to patch"
172+
fi
173+
174+
echo "skip=false" >> $GITHUB_OUTPUT
175+
echo "Determined bump level: $(cat $GITHUB_OUTPUT | grep level | cut -d= -f2)"
176+
177+
- name: Build list of files to update
178+
id: file-list
179+
if: steps.bump-type.outputs.skip != 'true'
180+
run: |
181+
# Process file patterns line by line
182+
BUMP_FILE_ARGS=""
183+
184+
# Save the patterns to a variable first
185+
PATTERNS="${{ inputs.version_file_patterns }}"
186+
187+
echo "Processing file patterns from input"
188+
# Use a for loop to process each pattern
189+
for file_pattern in $PATTERNS; do
190+
# Skip empty patterns
191+
if [ -z "$file_pattern" ]; then
192+
continue
193+
fi
194+
195+
echo " - Processing pattern: $file_pattern"
196+
197+
# Check if the file exists directly
198+
if [ -f "$file_pattern" ]; then
199+
echo " - Found file: $file_pattern"
200+
BUMP_FILE_ARGS="$BUMP_FILE_ARGS --replace-in $file_pattern"
201+
else
202+
# Check for glob matches
203+
for matched_file in $file_pattern; do
204+
if [ -f "$matched_file" ]; then
205+
echo " - Found file: $matched_file"
206+
BUMP_FILE_ARGS="$BUMP_FILE_ARGS --replace-in $matched_file"
207+
fi
208+
done
209+
fi
210+
done
211+
212+
# Note that bump auto-detects VERSION, and mentioning it again is an error.
213+
214+
echo "Bump file arguments: $BUMP_FILE_ARGS"
215+
echo "file_args=$BUMP_FILE_ARGS" >> $GITHUB_OUTPUT
216+
217+
- name: Run version bump with custom files
218+
id: run-bump
219+
if: steps.bump-type.outputs.skip != 'true'
220+
run: |
221+
BUMP_LEVEL="${{ steps.bump-type.outputs.level }}"
222+
FILE_ARGS="${{ steps.file-list.outputs.file_args }}"
223+
224+
# Build the bump command with the level and any file arguments
225+
BUMP_COMMAND="bump $BUMP_LEVEL --tag $FILE_ARGS"
226+
227+
echo "Executing: $BUMP_COMMAND"
228+
# Execute the bump command (bump will handle committing and tagging)
229+
OUTPUT=$(eval $BUMP_COMMAND)
230+
echo "$OUTPUT"
231+
232+
# Extract the new version number from the output
233+
NEW_VERSION=$(echo "$OUTPUT" | grep -o "to [0-9]\+\.[0-9]\+\.[0-9]\+" | cut -d' ' -f2)
234+
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
235+
echo "Bumped to version: $NEW_VERSION"
236+
env:
237+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
238+
239+
- name: Push changes to target branch
240+
if: steps.bump-type.outputs.skip != 'true'
241+
run: |
242+
# Bump has already committed and tagged, we just need to push
243+
git push origin HEAD:${{ inputs.target_branch }}
244+
git push origin --tags
245+
env:
246+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
247+
248+
- name: Log skipped version bump
249+
if: steps.bump-type.outputs.skip == 'true'
250+
run: |
251+
echo "Version bump was skipped due to the 'Version Bump: Skip' label"

0 commit comments

Comments
 (0)