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