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 " $@ "
0 commit comments