Skip to content

Commit bb4e39f

Browse files
committed
feat: extract version bump logic into reusable script
- Add .github/scripts/version-bump.sh with extracted logic from PR #137 - Add version-check.yaml workflow to verify module versions are updated - Add version-bump.yaml workflow that uses the script for label-triggered bumps - Update CONTRIBUTING.md with instructions on using the version bump script - Script supports patch/minor/major bumps and validates semantic versioning - CI will now check that module versions are properly updated in PRs
1 parent e987004 commit bb4e39f

File tree

4 files changed

+420
-2
lines changed

4 files changed

+420
-2
lines changed

.github/scripts/version-bump.sh

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#!/bin/bash
2+
3+
# Version Bump Script
4+
# Extracts version bump logic from GitHub Actions workflow
5+
# Usage: ./version-bump.sh <bump_type> [base_ref]
6+
# bump_type: patch, minor, or major
7+
# base_ref: base reference for diff (default: origin/main)
8+
9+
set -euo pipefail
10+
11+
# Function to print usage
12+
usage() {
13+
echo "Usage: $0 <bump_type> [base_ref]"
14+
echo " bump_type: patch, minor, or major"
15+
echo " base_ref: base reference for diff (default: origin/main)"
16+
exit 1
17+
}
18+
19+
# Function to validate version format
20+
validate_version() {
21+
local version="$1"
22+
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
23+
echo "❌ Invalid version format: '$version'. Expected X.Y.Z format." >&2
24+
return 1
25+
fi
26+
return 0
27+
}
28+
29+
# Function to bump version
30+
bump_version() {
31+
local current_version="$1"
32+
local bump_type="$2"
33+
34+
IFS='.' read -r major minor patch <<< "$current_version"
35+
36+
# Validate that components are numeric
37+
if ! [[ "$major" =~ ^[0-9]+$ ]] || ! [[ "$minor" =~ ^[0-9]+$ ]] || ! [[ "$patch" =~ ^[0-9]+$ ]]; then
38+
echo "❌ Version components must be numeric: major='$major' minor='$minor' patch='$patch'" >&2
39+
return 1
40+
fi
41+
42+
case "$bump_type" in
43+
"patch")
44+
echo "$major.$minor.$((patch + 1))"
45+
;;
46+
"minor")
47+
echo "$major.$((minor + 1)).0"
48+
;;
49+
"major")
50+
echo "$((major + 1)).0.0"
51+
;;
52+
*)
53+
echo "❌ Invalid bump type: '$bump_type'. Expected patch, minor, or major." >&2
54+
return 1
55+
;;
56+
esac
57+
}
58+
59+
# Function to update README version
60+
update_readme_version() {
61+
local readme_path="$1"
62+
local namespace="$2"
63+
local module_name="$3"
64+
local new_version="$4"
65+
66+
if [ ! -f "$readme_path" ]; then
67+
return 1
68+
fi
69+
70+
# Check if README contains version references for this specific module
71+
local module_source="registry.coder.com/${namespace}/${module_name}/coder"
72+
if grep -q "source.*${module_source}" "$readme_path"; then
73+
echo "Updating version references for $namespace/$module_name in $readme_path"
74+
# Use awk to only update versions that follow the specific module source
75+
awk -v module_source="$module_source" -v new_version="$new_version" '
76+
/source.*=.*/ {
77+
if ($0 ~ module_source) {
78+
in_target_module = 1
79+
} else {
80+
in_target_module = 0
81+
}
82+
}
83+
/version.*=.*"/ {
84+
if (in_target_module) {
85+
gsub(/version[[:space:]]*=[[:space:]]*"[^"]*"/, "version = \"" new_version "\"")
86+
in_target_module = 0
87+
}
88+
}
89+
{ print }
90+
' "$readme_path" > "${readme_path}.tmp" && mv "${readme_path}.tmp" "$readme_path"
91+
return 0
92+
elif grep -q 'version\s*=\s*"' "$readme_path"; then
93+
echo "⚠️ Found version references but no module source match for $namespace/$module_name"
94+
return 1
95+
fi
96+
97+
return 1
98+
}
99+
100+
# Main function
101+
main() {
102+
# Parse arguments
103+
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
104+
usage
105+
fi
106+
107+
local bump_type="$1"
108+
local base_ref="${2:-origin/main}"
109+
110+
# Validate bump type
111+
case "$bump_type" in
112+
"patch"|"minor"|"major")
113+
;;
114+
*)
115+
echo "❌ Invalid bump type: '$bump_type'. Expected patch, minor, or major." >&2
116+
exit 1
117+
;;
118+
esac
119+
120+
echo "🔍 Detecting modified modules..."
121+
122+
# Detect modified modules
123+
local changed_files
124+
changed_files=$(git diff --name-only "${base_ref}"...HEAD)
125+
local modules
126+
modules=$(echo "$changed_files" | grep -E '^registry/[^/]+/modules/[^/]+/' | cut -d'/' -f1-4 | sort -u)
127+
128+
if [ -z "$modules" ]; then
129+
echo "❌ No modules detected in changes"
130+
exit 1
131+
fi
132+
133+
echo "Found modules:"
134+
echo "$modules"
135+
echo ""
136+
137+
# Initialize tracking variables
138+
local bumped_modules=""
139+
local updated_readmes=""
140+
local untagged_modules=""
141+
local has_changes=false
142+
143+
# Process each module
144+
while IFS= read -r module_path; do
145+
if [ -z "$module_path" ]; then continue; fi
146+
147+
local namespace
148+
namespace=$(echo "$module_path" | cut -d'/' -f2)
149+
local module_name
150+
module_name=$(echo "$module_path" | cut -d'/' -f4)
151+
152+
echo "📦 Processing: $namespace/$module_name"
153+
154+
# Find latest tag
155+
local latest_tag
156+
latest_tag=$(git tag -l "release/${namespace}/${module_name}/v*" | sort -V | tail -1)
157+
local readme_path="$module_path/README.md"
158+
local current_version
159+
160+
if [ -z "$latest_tag" ]; then
161+
# No tag found, check if README has version references
162+
if [ -f "$readme_path" ] && grep -q 'version\s*=\s*"' "$readme_path"; then
163+
# Extract version from README
164+
local readme_version
165+
readme_version=$(grep 'version\s*=\s*"' "$readme_path" | head -1 | sed 's/.*version\s*=\s*"\([^"]*\)".*/\1/')
166+
echo "No git tag found, but README shows version: $readme_version"
167+
168+
# Validate extracted version format
169+
if ! validate_version "$readme_version"; then
170+
echo "Starting from v1.0.0 instead"
171+
current_version="1.0.0"
172+
else
173+
current_version="$readme_version"
174+
untagged_modules="$untagged_modules\n- $namespace/$module_name (README: v$readme_version)"
175+
fi
176+
else
177+
echo "No existing tags or version references found for $namespace/$module_name, starting from v1.0.0"
178+
current_version="1.0.0"
179+
fi
180+
else
181+
current_version=$(echo "$latest_tag" | sed 's/.*\/v//')
182+
echo "Found git tag: $latest_tag (v$current_version)"
183+
fi
184+
185+
echo "Current version: $current_version"
186+
187+
# Validate current version format
188+
if ! validate_version "$current_version"; then
189+
exit 1
190+
fi
191+
192+
# Calculate new version
193+
local new_version
194+
new_version=$(bump_version "$current_version" "$bump_type")
195+
196+
echo "New version: $new_version"
197+
198+
# Update README if applicable
199+
if update_readme_version "$readme_path" "$namespace" "$module_name" "$new_version"; then
200+
updated_readmes="$updated_readmes\n- $namespace/$module_name"
201+
has_changes=true
202+
fi
203+
204+
bumped_modules="$bumped_modules\n- $namespace/$module_name: v$current_version → v$new_version"
205+
echo ""
206+
207+
done <<< "$modules"
208+
209+
# Output results
210+
echo "📋 Summary:"
211+
echo "Bump Type: $bump_type"
212+
echo ""
213+
echo "Modules Updated:"
214+
echo -e "$bumped_modules"
215+
echo ""
216+
217+
if [ -n "$updated_readmes" ]; then
218+
echo "READMEs Updated:"
219+
echo -e "$updated_readmes"
220+
echo ""
221+
fi
222+
223+
if [ -n "$untagged_modules" ]; then
224+
echo "⚠️ Modules Without Git Tags:"
225+
echo -e "$untagged_modules"
226+
echo "These modules were versioned based on README content. Consider creating proper release tags after merging."
227+
echo ""
228+
fi
229+
230+
# Check for changes and provide guidance
231+
if [ "$has_changes" = true ]; then
232+
echo "✅ Version bump completed successfully!"
233+
echo "📝 README files have been updated with new versions."
234+
echo ""
235+
echo "Next steps:"
236+
echo "1. Review the changes: git diff"
237+
echo "2. Commit the changes: git add . && git commit -m 'chore: bump module versions ($bump_type)'"
238+
echo "3. Push the changes: git push"
239+
exit 0
240+
else
241+
echo "ℹ️ No README files were updated (no version references found matching module sources)."
242+
echo "Version calculations completed, but no files were modified."
243+
exit 0
244+
fi
245+
}
246+
247+
# Run main function with all arguments
248+
main "$@"

.github/workflows/version-bump.yaml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
name: Version Bump
2+
3+
on:
4+
pull_request:
5+
types: [labeled]
6+
7+
concurrency:
8+
group: ${{ github.workflow }}-${{ github.ref }}
9+
cancel-in-progress: true
10+
11+
jobs:
12+
version-bump:
13+
if: github.event.label.name == 'version:patch' || github.event.label.name == 'version:minor' || github.event.label.name == 'version:major'
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: write
17+
pull-requests: write
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
token: ${{ secrets.GITHUB_TOKEN }}
24+
25+
- name: Setup Git
26+
run: |
27+
git config user.name "github-actions[bot]"
28+
git config user.email "github-actions[bot]@users.noreply.github.com"
29+
30+
- name: Extract bump type from label
31+
id: bump-type
32+
run: |
33+
case "${{ github.event.label.name }}" in
34+
"version:patch")
35+
echo "type=patch" >> $GITHUB_OUTPUT
36+
;;
37+
"version:minor")
38+
echo "type=minor" >> $GITHUB_OUTPUT
39+
;;
40+
"version:major")
41+
echo "type=major" >> $GITHUB_OUTPUT
42+
;;
43+
*)
44+
echo "Invalid version label: ${{ github.event.label.name }}"
45+
exit 1
46+
;;
47+
esac
48+
49+
- name: Run version bump script
50+
id: version-bump
51+
run: |
52+
# Capture script output for later use in PR comment
53+
output_file=$(mktemp)
54+
if ./.github/scripts/version-bump.sh "${{ steps.bump-type.outputs.type }}" origin/main > "$output_file" 2>&1; then
55+
echo "success=true" >> $GITHUB_OUTPUT
56+
echo "Script completed successfully"
57+
else
58+
echo "success=false" >> $GITHUB_OUTPUT
59+
echo "Script failed"
60+
cat "$output_file"
61+
exit 1
62+
fi
63+
64+
# Store output for PR comment
65+
{
66+
echo "output<<EOF"
67+
cat "$output_file"
68+
echo "EOF"
69+
} >> $GITHUB_OUTPUT
70+
71+
# Show output
72+
cat "$output_file"
73+
74+
- name: Commit changes
75+
if: steps.version-bump.outputs.success == 'true'
76+
run: |
77+
# Check if any README files were modified
78+
if git diff --quiet 'registry/*/modules/*/README.md'; then
79+
echo "No README changes to commit"
80+
else
81+
echo "Committing README changes..."
82+
git diff --name-only 'registry/*/modules/*/README.md'
83+
git add registry/*/modules/*/README.md
84+
git commit -m "chore: bump module versions (${{ steps.bump-type.outputs.type }})"
85+
git push
86+
fi
87+
88+
- name: Comment on PR
89+
if: steps.version-bump.outputs.success == 'true'
90+
uses: actions/github-script@v7
91+
with:
92+
script: |
93+
const output = `${{ steps.version-bump.outputs.output }}`;
94+
const bumpType = `${{ steps.bump-type.outputs.type }}`;
95+
96+
let comment = `## 🚀 Version Bump Summary\n\n`;
97+
comment += `**Bump Type:** \`${bumpType}\`\n\n`;
98+
comment += `### Script Output:\n\`\`\`\n${output}\n\`\`\`\n\n`;
99+
comment += `> Versions have been automatically updated using the version bump script.`;
100+
101+
github.rest.issues.createComment({
102+
issue_number: context.issue.number,
103+
owner: context.repo.owner,
104+
repo: context.repo.repo,
105+
body: comment
106+
});

.github/workflows/version-check.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Version Check
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'registry/**/modules/**'
7+
8+
jobs:
9+
version-check:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v4
14+
with:
15+
fetch-depth: 0
16+
17+
- name: Check if module versions need updating
18+
run: |
19+
echo "🔍 Checking if module versions need to be updated..."
20+
21+
# Run the version bump script in dry-run mode to see what would change
22+
if ./.github/scripts/version-bump.sh patch origin/main; then
23+
echo "✅ Module versions are up to date or no modules were modified"
24+
else
25+
echo "❌ Module versions may need to be updated"
26+
echo ""
27+
echo "To update module versions, run one of:"
28+
echo " ./.github/scripts/version-bump.sh patch"
29+
echo " ./.github/scripts/version-bump.sh minor"
30+
echo " ./.github/scripts/version-bump.sh major"
31+
echo ""
32+
echo "Or add a version label to this PR: version:patch, version:minor, or version:major"
33+
exit 1
34+
fi

0 commit comments

Comments
 (0)