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