#!/bin/bash # Version Bump Script # Usage: ./version-bump.sh [--ci] [base_ref] # --ci: CI mode - run bump, check for changes, exit 1 if changes needed # bump_type: patch, minor, or major # base_ref: base reference for diff (default: origin/main) set -euo pipefail CI_MODE=false usage() { echo "Usage: $0 [--ci] [base_ref]" echo " --ci: CI mode - validates versions are already bumped (exits 1 if not)" echo " bump_type: patch, minor, or major" echo " base_ref: base reference for diff (default: origin/main)" echo "" echo "Examples:" echo " $0 patch # Update versions with patch bump" echo " $0 minor # Update versions with minor bump" echo " $0 major # Update versions with major bump" echo " $0 --ci patch # CI check: verify patch bump has been applied" exit 1 } validate_version() { local version="$1" if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "❌ Invalid version format: '$version'. Expected X.Y.Z format." >&2 return 1 fi return 0 } bump_version() { local current_version="$1" local bump_type="$2" IFS='.' read -r major minor patch <<< "$current_version" if ! [[ "$major" =~ ^[0-9]+$ ]] || ! [[ "$minor" =~ ^[0-9]+$ ]] || ! [[ "$patch" =~ ^[0-9]+$ ]]; then echo "❌ Version components must be numeric: major='$major' minor='$minor' patch='$patch'" >&2 return 1 fi case "$bump_type" in "patch") echo "$major.$minor.$((patch + 1))" ;; "minor") echo "$major.$((minor + 1)).0" ;; "major") echo "$((major + 1)).0.0" ;; *) echo "❌ Invalid bump type: '$bump_type'. Expected patch, minor, or major." >&2 return 1 ;; esac } update_readme_version() { local readme_path="$1" local namespace="$2" local module_name="$3" local new_version="$4" if [ ! -f "$readme_path" ]; then return 1 fi local module_source="registry.coder.com/${namespace}/${module_name}/coder" if grep -q "source.*${module_source}" "$readme_path"; then echo "Updating version references for $namespace/$module_name in $readme_path" awk -v module_source="$module_source" -v new_version="$new_version" ' /^[[:space:]]*module[[:space:]]/ { in_module_block = 1 module_content = $0 "\n" module_has_target_source = 0 next } in_module_block { module_content = module_content $0 "\n" if ($0 ~ /source.*=/ && $0 ~ module_source) { module_has_target_source = 1 } if ($0 ~ /^[[:space:]]*}[[:space:]]*$/) { in_module_block = 0 if (module_has_target_source) { num_lines = split(module_content, lines, "\n") for (i = 1; i < num_lines; i++) { line = lines[i] if (line ~ /^[[:space:]]*version[[:space:]]*=/) { match(line, /^[[:space:]]*/) indent = substr(line, 1, RLENGTH) printf "%sversion = \"%s\"\n", indent, new_version } else { print line } } } else { printf "%s", module_content } module_content = "" next } next } { print } ' "$readme_path" > "${readme_path}.tmp" && mv "${readme_path}.tmp" "$readme_path" return 0 elif grep -q '^[[:space:]]*version[[:space:]]*=' "$readme_path"; then echo "⚠️ Found version references but no module source match for $namespace/$module_name" return 1 fi return 1 } main() { if [ "${1:-}" = "--ci" ]; then CI_MODE=true shift fi if [ $# -lt 1 ] || [ $# -gt 2 ]; then usage fi local bump_type="$1" local base_ref="${2:-origin/main}" case "$bump_type" in "patch" | "minor" | "major") ;; *) echo "❌ Invalid bump type: '$bump_type'. Expected patch, minor, or major." >&2 exit 1 ;; esac echo "🔍 Detecting modified modules..." local changed_files changed_files=$(git diff --name-only "${base_ref}"...HEAD) local modules modules=$(echo "$changed_files" | grep -E '^registry/[^/]+/modules/[^/]+/' | cut -d'/' -f1-4 | sort -u) if [ -z "$modules" ]; then echo "❌ No modules detected in changes" exit 1 fi echo "Found modules:" echo "$modules" echo "" local bumped_modules="" local updated_readmes="" local untagged_modules="" local has_changes=false declare -a modified_readme_files=() while IFS= read -r module_path; do if [ -z "$module_path" ]; then continue; fi local namespace namespace=$(echo "$module_path" | cut -d'/' -f2) local module_name module_name=$(echo "$module_path" | cut -d'/' -f4) echo "📦 Processing: $namespace/$module_name" local latest_tag latest_tag=$(git tag -l "release/${namespace}/${module_name}/v*" | sort -V | tail -1) local readme_path="$module_path/README.md" local current_version if [ -z "$latest_tag" ]; then if [ -f "$readme_path" ] && grep -q '^[[:space:]]*version[[:space:]]*=' "$readme_path"; then local readme_version readme_version=$(awk '/^[[:space:]]*version[[:space:]]*=/ { match($0, /"[^"]*"/); print substr($0, RSTART+1, RLENGTH-2); exit }' "$readme_path") echo "No git tag found, but README shows version: $readme_version" if ! validate_version "$readme_version"; then echo "Starting from v1.0.0 instead" current_version="1.0.0" else current_version="$readme_version" untagged_modules="$untagged_modules\n- $namespace/$module_name (README: v$readme_version)" fi else echo "No existing tags or version references found for $namespace/$module_name, starting from v1.0.0" current_version="1.0.0" fi else current_version=$(echo "$latest_tag" | sed 's/.*\/v//') echo "Found git tag: $latest_tag (v$current_version)" fi echo "Current version: $current_version" if ! validate_version "$current_version"; then exit 1 fi local new_version new_version=$(bump_version "$current_version" "$bump_type") echo "New version: $new_version" if update_readme_version "$readme_path" "$namespace" "$module_name" "$new_version"; then updated_readmes="$updated_readmes\n- $namespace/$module_name" modified_readme_files+=("$readme_path") has_changes=true fi bumped_modules="$bumped_modules\n- $namespace/$module_name: v$current_version → v$new_version" echo "" done <<< "$modules" if [ ${#modified_readme_files[@]} -gt 0 ]; then echo "🔧 Formatting modified README files..." if command -v bun > /dev/null 2>&1; then for readme_file in "${modified_readme_files[@]}"; do bun run prettier --write "$readme_file" 2> /dev/null || true done else echo "⚠️ Warning: bun not found, skipping formatting" fi echo "" fi echo "📋 Summary:" echo "Bump Type: $bump_type" echo "" echo "Modules Processed:" echo -e "$bumped_modules" echo "" if [ -n "$updated_readmes" ]; then echo "READMEs Updated:" echo -e "$updated_readmes" echo "" fi if [ -n "$untagged_modules" ]; then echo "⚠️ Modules Without Git Tags:" echo -e "$untagged_modules" echo "These modules were versioned based on README content. Consider creating proper release tags after merging." echo "" fi if [ "$CI_MODE" = true ]; then echo "🔍 Comparing files to committed versions..." if git diff --quiet; then echo "✅ PASS: All versions match - no changes needed" exit 0 else echo "❌ FAIL: Module versions need to be updated" echo "" echo "Run './.github/scripts/version-bump.sh $bump_type' locally and commit the changes" exit 1 fi fi if [ "$has_changes" = true ]; then echo "✅ Version bump completed successfully!" echo "📝 README files have been updated with new versions." echo "" echo "Next steps:" echo "1. Review the changes: git diff" echo "2. Commit the changes: git add . && git commit -m 'chore: bump module versions ($bump_type)'" echo "3. Push the changes: git push" exit 0 else echo "ℹ️ No README files were updated (no version references found matching module sources)." echo "Version calculations completed, but no files were modified." exit 0 fi } main "$@"