| #!/bin/bash |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| set -euo pipefail |
| |
| REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
| |
| RED='\033[0;31m' |
| GREEN='\033[0;32m' |
| YELLOW='\033[1;33m' |
| CYAN='\033[0;36m' |
| NC='\033[0m' |
| |
| usage() { |
| cat <<'EOF' |
| Usage: |
| bump-version.sh <component> <flags> |
| bump-version.sh --status [<component>] |
| |
| Components: |
| rust-sdk core/sdk + workspace dep + python iggy dep |
| rust-common core/common + workspace dep |
| rust-binary-protocol core/binary_protocol + workspace dep |
| rust-server core/server |
| rust-cli core/cli + workspace dep |
| rust-connector-sdk core/connectors/sdk + workspace dep |
| rust-all All Rust crates at once |
| --all All components (Rust + SDKs + web-ui) |
| sdk-python foreign/python/Cargo.toml + foreign/python/pyproject.toml |
| sdk-node foreign/node/package.json |
| sdk-go foreign/go/contracts/version.go |
| sdk-csharp foreign/csharp/Iggy_SDK/Iggy_SDK.csproj |
| sdk-java foreign/java/gradle.properties |
| web-ui web/package.json |
| |
| Version flags (exactly one required): |
| --patch Bump patch version (0.9.3 -> 0.9.4) |
| --minor Bump minor version (0.9.3 -> 0.10.0) |
| --major Bump major version (0.9.3 -> 1.0.0) |
| --edge Increment edge counter (0.9.3-edge.1 -> 0.9.3-edge.2) |
| Or add -edge.1 when combined with --patch/--minor/--major |
| --strip-edge Remove edge suffix (0.9.3-edge.1 -> 0.9.3) |
| --set V Set explicit version V (use --force to bypass validation) |
| |
| Modifiers: |
| --dry-run Preview changes without writing (default: writes immediately) |
| --force Bypass validation (only with --set) |
| |
| Examples: |
| bump-version.sh rust-all --patch --edge # 0.9.3 -> 0.9.4-edge.1 |
| bump-version.sh rust-all --edge # 0.9.3-edge.1 -> 0.9.3-edge.2 |
| bump-version.sh rust-all --strip-edge # 0.9.3-edge.1 -> 0.9.3 |
| bump-version.sh rust-all --minor --edge --dry-run |
| bump-version.sh --all --strip-edge |
| bump-version.sh rust-sdk --patch |
| bump-version.sh sdk-python --set 0.8.0 |
| bump-version.sh --status |
| EOF |
| } |
| |
| RUST_COMPONENTS="rust-sdk rust-common rust-binary-protocol rust-server rust-cli rust-connector-sdk" |
| SDK_COMPONENTS="sdk-python sdk-node sdk-go sdk-csharp sdk-java" |
| ALL_COMPONENTS="${RUST_COMPONENTS} ${SDK_COMPONENTS} web-ui" |
| |
| # Returns "file:format" lines per component. |
| # Format keys: cargo, cargo-ws-dep:PKG, cargo-dep:PKG, python-cargo, pyproject, json, csproj, gradle, go |
| get_version_files() { |
| local component="$1" |
| case "$component" in |
| rust-sdk) |
| echo "core/sdk/Cargo.toml:cargo" |
| echo "Cargo.toml:cargo-ws-dep:iggy" |
| echo "foreign/python/Cargo.toml:cargo-dep:iggy" |
| ;; |
| rust-common) |
| echo "core/common/Cargo.toml:cargo" |
| echo "Cargo.toml:cargo-ws-dep:iggy_common" |
| ;; |
| rust-binary-protocol) |
| echo "core/binary_protocol/Cargo.toml:cargo" |
| echo "Cargo.toml:cargo-ws-dep:iggy_binary_protocol" |
| ;; |
| rust-server) |
| echo "core/server/Cargo.toml:cargo" |
| ;; |
| rust-cli) |
| echo "core/cli/Cargo.toml:cargo" |
| echo "Cargo.toml:cargo-ws-dep:iggy-cli" |
| ;; |
| rust-connector-sdk) |
| echo "core/connectors/sdk/Cargo.toml:cargo" |
| echo "Cargo.toml:cargo-ws-dep:iggy_connector_sdk" |
| ;; |
| sdk-python) |
| echo "foreign/python/Cargo.toml:python-cargo" |
| echo "foreign/python/pyproject.toml:pyproject" |
| ;; |
| sdk-node) |
| echo "foreign/node/package.json:json" |
| ;; |
| sdk-go) |
| echo "foreign/go/contracts/version.go:go" |
| ;; |
| sdk-csharp) |
| echo "foreign/csharp/Iggy_SDK/Iggy_SDK.csproj:csproj" |
| ;; |
| sdk-java) |
| echo "foreign/java/gradle.properties:gradle" |
| ;; |
| web-ui) |
| echo "web/package.json:json" |
| ;; |
| *) |
| echo -e "${RED}Unknown component: ${component}${NC}" >&2 |
| echo "Valid: rust-all ${ALL_COMPONENTS}" >&2 |
| return 1 |
| ;; |
| esac |
| } |
| |
| # Parse canonical semver into "base pre_type pre_num". |
| # "0.9.3-edge.1" -> "0.9.3 edge 1", "0.9.3" -> "0.9.3 stable 0" |
| parse_version() { |
| local ver="$1" |
| if [[ "$ver" =~ ^([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then |
| echo "$ver stable 0" |
| elif [[ "$ver" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-edge\.([0-9]+)$ ]]; then |
| echo "${BASH_REMATCH[1]} edge ${BASH_REMATCH[2]}" |
| else |
| echo -e "${RED}Cannot parse version: ${ver}${NC}" >&2 |
| return 1 |
| fi |
| } |
| |
| # Compute next version from current + keyword + flags. |
| # Args: current keyword add_edge(0|1) strip_edge(0|1) |
| compute_next_version() { |
| local current="$1" keyword="$2" add_edge="${3:-0}" strip_edge="${4:-0}" |
| local parsed base pre_type pre_num |
| parsed=$(parse_version "$current") || return 1 |
| read -r base pre_type pre_num <<< "$parsed" |
| |
| local major minor patch |
| IFS='.' read -r major minor patch <<< "$base" |
| |
| # --strip-edge: remove edge suffix |
| if [[ $strip_edge -eq 1 ]]; then |
| if [[ "$pre_type" != "edge" ]]; then |
| echo -e "${RED}Cannot --strip-edge: ${current} has no edge suffix${NC}" >&2 |
| return 1 |
| fi |
| echo "$base" |
| return 0 |
| fi |
| |
| # edge keyword: increment edge counter |
| if [[ "$keyword" == "edge" ]]; then |
| if [[ "$pre_type" != "edge" ]]; then |
| echo -e "${RED}Cannot 'edge' from stable version ${current}${NC}" >&2 |
| echo -e " Use ${GREEN}patch --edge${NC} or ${GREEN}minor --edge${NC} instead" >&2 |
| return 1 |
| fi |
| echo "${base}-edge.$((pre_num + 1))" |
| return 0 |
| fi |
| |
| # patch/minor/major: bump base version |
| local new_base |
| case "$keyword" in |
| patch) new_base="${major}.${minor}.$((patch + 1))" ;; |
| minor) new_base="${major}.$((minor + 1)).0" ;; |
| major) new_base="$((major + 1)).0.0" ;; |
| *) echo -e "${RED}Invalid keyword: ${keyword}${NC}" >&2; return 1 ;; |
| esac |
| |
| if [[ $add_edge -eq 1 ]]; then |
| echo "${new_base}-edge.1" |
| else |
| echo "${new_base}" |
| fi |
| } |
| |
| # Convert canonical semver to ecosystem-specific format. |
| translate_version() { |
| local canonical="$1" format="$2" |
| local parsed base pre_type pre_num |
| parsed=$(parse_version "$canonical" 2>/dev/null) || { echo "$canonical"; return 0; } |
| read -r base pre_type pre_num <<< "$parsed" |
| |
| case "$format" in |
| cargo|cargo-ws-dep:*|cargo-dep:*|json|csproj|go) |
| echo "$canonical" ;; |
| python-cargo) |
| case "$pre_type" in |
| edge) echo "${base}-dev${pre_num}" ;; |
| stable) echo "$base" ;; |
| esac ;; |
| pyproject) |
| case "$pre_type" in |
| edge) echo "${base}.dev${pre_num}" ;; |
| stable) echo "$base" ;; |
| esac ;; |
| gradle) |
| case "$pre_type" in |
| edge) echo "${base}-SNAPSHOT" ;; |
| stable) echo "$base" ;; |
| esac ;; |
| *) |
| echo -e "${RED}Unknown format: ${format}${NC}" >&2 |
| return 1 ;; |
| esac |
| } |
| |
| # Reverse: read ecosystem format, return canonical semver. |
| canonicalize_version() { |
| local raw="$1" format="$2" |
| case "$format" in |
| cargo|cargo-ws-dep:*|cargo-dep:*|json|csproj|go) |
| echo "$raw" ;; |
| python-cargo) |
| # Handle both old (0.7.2-dev.1) and new (0.7.2-dev1) formats |
| if [[ "$raw" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-dev\.?([0-9]+)$ ]]; then |
| echo "${BASH_REMATCH[1]}-edge.${BASH_REMATCH[2]}" |
| else |
| echo "$raw" |
| fi ;; |
| pyproject) |
| # Handle both old (0.7.2.dev.1) and new (0.7.2.dev1) formats |
| if [[ "$raw" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.dev\.?([0-9]+)$ ]]; then |
| echo "${BASH_REMATCH[1]}-edge.${BASH_REMATCH[2]}" |
| else |
| echo "$raw" |
| fi ;; |
| gradle) |
| if [[ "$raw" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-SNAPSHOT$ ]]; then |
| echo "${BASH_REMATCH[1]}-edge.0" |
| else |
| echo "$raw" |
| fi ;; |
| *) |
| echo -e "${RED}Unknown format: ${format}${NC}" >&2 |
| return 1 ;; |
| esac |
| } |
| |
| # Extract version string from file based on format. |
| read_current_version() { |
| local file="$1" format="$2" |
| local abs_file="${REPO_ROOT}/${file}" |
| local fmt_base="${format%%:*}" |
| |
| case "$fmt_base" in |
| cargo) |
| grep '^version = ' "$abs_file" | head -1 | sed 's/version = "\(.*\)"/\1/' ;; |
| cargo-ws-dep) |
| local pkg="${format#cargo-ws-dep:}" |
| grep "^${pkg} = " "$abs_file" | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/' ;; |
| cargo-dep) |
| local pkg="${format#cargo-dep:}" |
| grep "^${pkg} = " "$abs_file" | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/' ;; |
| python-cargo) |
| grep '^version = ' "$abs_file" | head -1 | sed 's/version = "\(.*\)"/\1/' ;; |
| pyproject) |
| grep '^version = ' "$abs_file" | head -1 | sed 's/version = "\(.*\)"/\1/' ;; |
| json) |
| grep '"version"' "$abs_file" | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/' ;; |
| csproj) |
| grep '<Version>' "$abs_file" | head -1 | sed 's/.*<Version>\(.*\)<\/Version>.*/\1/' | tr -d '[:space:]' ;; |
| gradle) |
| grep '^version=' "$abs_file" | head -1 | sed 's/version=//' ;; |
| go) |
| grep 'const Version' "$abs_file" | head -1 | sed 's/.*const Version = "\([^"]*\)".*/\1/' ;; |
| *) |
| echo -e "${RED}Unknown format: ${format}${NC}" >&2 |
| return 1 ;; |
| esac |
| } |
| |
| # Portable sed -i wrapper (works on both GNU and BSD/macOS sed). |
| sedi() { |
| if sed --version 2>/dev/null | grep -q 'GNU'; then |
| sed -i "$@" |
| else |
| sed -i '' "$@" |
| fi |
| } |
| |
| # Write translated version into file using sed. |
| write_version() { |
| local file="$1" format="$2" new_canonical="$3" |
| local abs_file="${REPO_ROOT}/${file}" |
| local fmt_base="${format%%:*}" |
| local translated |
| translated=$(translate_version "$new_canonical" "$format") |
| |
| case "$fmt_base" in |
| cargo) |
| sedi "1,/^version = \".*\"/s/^version = \".*\"/version = \"${translated}\"/" "$abs_file" ;; |
| cargo-ws-dep) |
| local pkg="${format#cargo-ws-dep:}" |
| sedi "s/^\(${pkg} = .*version = \"\)[^\"]*/\1${translated}/" "$abs_file" ;; |
| cargo-dep) |
| local pkg="${format#cargo-dep:}" |
| sedi "s/^\(${pkg} = .*version = \"\)[^\"]*/\1${translated}/" "$abs_file" ;; |
| python-cargo) |
| sedi "1,/^version = \".*\"/s/^version = \".*\"/version = \"${translated}\"/" "$abs_file" ;; |
| pyproject) |
| sedi '/^\[project\]/,/^\[/{s/^version = ".*"/version = "'"${translated}"'"/;}' "$abs_file" ;; |
| json) |
| sedi "1,/\"version\": *\"[^\"]*\"/{s/\"version\": *\"[^\"]*\"/\"version\": \"${translated}\"/;}" "$abs_file" ;; |
| csproj) |
| sedi "1,/<Version>[^<]*<\/Version>/{s/<Version>[^<]*<\/Version>/<Version>${translated}<\/Version>/;}" "$abs_file" ;; |
| gradle) |
| sedi "s/^version=.*/version=${translated}/" "$abs_file" ;; |
| go) |
| sedi "1,/const Version = \"[^\"]*\"/{s/const Version = \"[^\"]*\"/const Version = \"${translated}\"/;}" "$abs_file" ;; |
| *) |
| echo -e "${RED}Unknown format: ${format}${NC}" >&2 |
| return 1 ;; |
| esac |
| } |
| |
| # --status: print current versions for all or one component. |
| cmd_status() { |
| local filter="${1:-}" |
| local components |
| if [[ "$filter" == "rust-all" ]]; then |
| components="$RUST_COMPONENTS" |
| elif [[ -n "$filter" ]]; then |
| components="$filter" |
| else |
| components="$ALL_COMPONENTS" |
| fi |
| |
| for comp in $components; do |
| echo -e "${CYAN}${comp}${NC}" |
| local first_canonical="" |
| while IFS= read -r entry; do |
| local file="${entry%%:*}" |
| local format="${entry#*:}" |
| local raw canonical |
| raw=$(read_current_version "$file" "$format") |
| canonical=$(canonicalize_version "$raw" "$format") |
| printf " %-55s %s" "$file" "$raw" |
| if [[ "$raw" != "$canonical" ]]; then |
| printf " (canonical: %s)" "$canonical" |
| fi |
| printf "\n" |
| if [[ -z "$first_canonical" ]]; then |
| first_canonical="$canonical" |
| fi |
| done < <(get_version_files "$comp") |
| echo "" |
| done |
| } |
| |
| # Collect unique files from version entries for dirty check. |
| collect_files() { |
| local component="$1" |
| get_version_files "$component" | while IFS= read -r entry; do |
| echo "${entry%%:*}" |
| done | sort -u |
| } |
| |
| # Pre-flight: check for uncommitted changes in version files. |
| preflight_dirty_check() { |
| local component="$1" |
| local dirty_files=() |
| while IFS= read -r file; do |
| if ! git diff --quiet -- "$REPO_ROOT/$file" 2>/dev/null; then |
| dirty_files+=("$file") |
| fi |
| if ! git diff --cached --quiet -- "$REPO_ROOT/$file" 2>/dev/null; then |
| dirty_files+=("$file (staged)") |
| fi |
| done < <(collect_files "$component") |
| |
| if [[ ${#dirty_files[@]} -gt 0 ]]; then |
| echo -e "${YELLOW}Warning: uncommitted changes in version files:${NC}" >&2 |
| for f in "${dirty_files[@]}"; do |
| echo -e " ${f}" >&2 |
| done |
| fi |
| } |
| |
| # Pre-flight: verify grouped components have consistent canonical versions. |
| # Returns the canonical version (or exits on inconsistency). |
| preflight_consistency_check() { |
| local component="$1" |
| local first_canonical="" first_file="" |
| local inconsistent=0 |
| |
| while IFS= read -r entry; do |
| local file="${entry%%:*}" |
| local format="${entry#*:}" |
| local raw canonical |
| raw=$(read_current_version "$file" "$format") |
| canonical=$(canonicalize_version "$raw" "$format") |
| |
| if [[ -z "$first_canonical" ]]; then |
| first_canonical="$canonical" |
| first_file="$file" |
| elif [[ "$canonical" != "$first_canonical" ]]; then |
| if [[ $inconsistent -eq 0 ]]; then |
| echo -e "${RED}Inconsistent versions in ${component}:${NC}" >&2 |
| echo -e " ${first_file}: ${first_canonical}" >&2 |
| inconsistent=1 |
| fi |
| echo -e " ${file}: ${canonical}" >&2 |
| fi |
| done < <(get_version_files "$component") |
| |
| if [[ $inconsistent -ne 0 ]]; then |
| echo -e "${RED}Fix version inconsistencies before bumping, or use --set --force.${NC}" >&2 |
| return 1 |
| fi |
| |
| echo "$first_canonical" |
| } |
| |
| # Main bump logic. |
| cmd_bump() { |
| local component="$1" |
| shift |
| local apply=1 set_ver="" force=0 has_edge=0 strip_edge=0 keyword="" |
| |
| while [[ $# -gt 0 ]]; do |
| case "$1" in |
| --patch) |
| [[ -n "$keyword" ]] && { echo -e "${RED}Conflicting flags: --${keyword} and --patch${NC}" >&2; return 1; } |
| keyword="patch" ;; |
| --minor) |
| [[ -n "$keyword" ]] && { echo -e "${RED}Conflicting flags: --${keyword} and --minor${NC}" >&2; return 1; } |
| keyword="minor" ;; |
| --major) |
| [[ -n "$keyword" ]] && { echo -e "${RED}Conflicting flags: --${keyword} and --major${NC}" >&2; return 1; } |
| keyword="major" ;; |
| --edge) has_edge=1 ;; |
| --strip-edge) strip_edge=1 ;; |
| --dry-run) apply=0 ;; |
| --set) |
| [[ $# -lt 2 ]] && { echo -e "${RED}--set requires a version argument${NC}" >&2; return 1; } |
| set_ver="$2"; shift ;; |
| --force) force=1 ;; |
| *) echo -e "${RED}Unknown flag: ${1}${NC}" >&2; return 1 ;; |
| esac |
| shift |
| done |
| |
| # Resolve --edge: standalone = increment counter, combined with bump = add suffix |
| local add_edge=0 |
| if [[ $has_edge -eq 1 ]]; then |
| if [[ -n "$keyword" ]]; then |
| add_edge=1 |
| else |
| keyword="edge" |
| fi |
| fi |
| |
| # Validate: need exactly one action |
| if [[ -z "$set_ver" && -z "$keyword" && $strip_edge -eq 0 ]]; then |
| echo -e "${RED}Missing version flag. Use --patch, --minor, --major, --edge, --strip-edge, or --set${NC}" >&2 |
| return 1 |
| fi |
| |
| preflight_dirty_check "$component" |
| local current |
| current=$(preflight_consistency_check "$component") |
| |
| local new_ver |
| if [[ -n "$set_ver" ]]; then |
| if [[ $force -eq 0 ]]; then |
| if ! parse_version "$set_ver" > /dev/null 2>&1; then |
| echo -e "${RED}Invalid version format: ${set_ver}${NC}" >&2 |
| echo "Expected: X.Y.Z or X.Y.Z-edge.N (use --force to bypass)" >&2 |
| return 1 |
| fi |
| fi |
| if [[ $force -eq 0 && "$set_ver" == "$current" ]]; then |
| echo -e "${YELLOW}Already at ${set_ver}, nothing to do.${NC}" |
| return 0 |
| fi |
| new_ver="$set_ver" |
| else |
| new_ver=$(compute_next_version "$current" "${keyword:-_none}" "$add_edge" "$strip_edge") || return 1 |
| fi |
| |
| # Java SNAPSHOT idempotency: any edge operation on already-SNAPSHOT is a no-op |
| # (SNAPSHOT has no counter, so --edge and --patch --edge both produce SNAPSHOT) |
| if [[ "$component" == "sdk-java" ]] && [[ "$keyword" == "edge" || $add_edge -eq 1 ]]; then |
| local raw_java |
| raw_java=$(read_current_version "foreign/java/gradle.properties" "gradle") |
| if [[ "$raw_java" == *-SNAPSHOT ]]; then |
| echo -e "${GREEN}sdk-java already at SNAPSHOT (${raw_java}), nothing to do.${NC}" |
| return 0 |
| fi |
| fi |
| |
| echo -e "${CYAN}Component:${NC} ${component}" |
| echo -e "${CYAN}Current:${NC} ${current}" |
| echo -e "${CYAN}New:${NC} ${new_ver}" |
| echo "" |
| |
| local changed_files=() |
| while IFS= read -r entry; do |
| local file="${entry%%:*}" |
| local format="${entry#*:}" |
| local raw_old translated_new |
| raw_old=$(read_current_version "$file" "$format") |
| translated_new=$(translate_version "$new_ver" "$format") |
| |
| if [[ "$raw_old" == "$translated_new" ]]; then |
| printf " %-55s %s (unchanged)\n" "$file" "$raw_old" |
| else |
| printf " %-55s %s -> ${GREEN}%s${NC}\n" "$file" "$raw_old" "$translated_new" |
| changed_files+=("$file") |
| fi |
| |
| if [[ $apply -eq 1 ]]; then |
| write_version "$file" "$format" "$new_ver" |
| local verify |
| verify=$(read_current_version "$file" "$format") |
| if [[ "$verify" != "$translated_new" ]]; then |
| echo -e "${RED}FAILED to write ${file}: expected '${translated_new}', got '${verify}'${NC}" >&2 |
| return 1 |
| fi |
| fi |
| done < <(get_version_files "$component") |
| |
| echo "" |
| if [[ $apply -eq 1 ]]; then |
| echo -e "${GREEN}Applied.${NC}" |
| else |
| echo -e "${YELLOW}Dry run. Remove --dry-run to write changes.${NC}" |
| fi |
| |
| if [[ ${#changed_files[@]} -gt 0 ]]; then |
| local unique_files |
| IFS=$'\n' read -r -d '' -a unique_files < <(printf '%s\n' "${changed_files[@]}" | sort -u && printf '\0') || true |
| |
| if [[ "${BUMP_MULTI:-0}" -eq 1 ]]; then |
| # In multi-component mode, accumulate files for a single commit suggestion |
| for f in "${unique_files[@]}"; do |
| BUMP_MULTI_FILES+=("$f") |
| done |
| else |
| echo "" |
| echo "Next steps:" |
| echo " cargo generate-lockfile" |
| local git_add=" git add Cargo.lock" |
| for f in "${unique_files[@]}"; do |
| git_add+=" ${f}" |
| done |
| echo "$git_add" |
| echo " git commit -m \"chore(release): bump ${component} to ${new_ver}\"" |
| fi |
| fi |
| } |
| |
| # --- Argument parsing --- |
| |
| if [[ $# -eq 0 ]]; then |
| usage |
| exit 0 |
| fi |
| |
| case "$1" in |
| -h|--help) |
| usage |
| exit 0 ;; |
| --status) |
| cmd_status "${2:-}" |
| exit 0 ;; |
| esac |
| |
| # Bump multiple components with the same flags. |
| # Collects all changed files and prints a single commit suggestion at the end. |
| cmd_bump_multi() { |
| local components="$1" |
| shift |
| local failed=0 |
| BUMP_MULTI=1 |
| BUMP_MULTI_FILES=() |
| |
| for comp in $components; do |
| echo -e "${CYAN}--- ${comp} ---${NC}" |
| if cmd_bump "$comp" "$@"; then |
| : |
| else |
| failed=$((failed + 1)) |
| echo -e "${RED}FAILED ${comp}${NC}" |
| fi |
| echo "" |
| done |
| |
| if [[ $failed -gt 0 ]]; then |
| echo -e "${RED}${failed} component(s) failed.${NC}" |
| echo -e "Use ${CYAN}--set${NC} to handle them individually." |
| echo -e "Run ${CYAN}git checkout${NC} on affected files to revert partial changes." |
| BUMP_MULTI=0 |
| return 1 |
| fi |
| |
| if [[ ${#BUMP_MULTI_FILES[@]} -gt 0 ]]; then |
| local unique_files |
| IFS=$'\n' read -r -d '' -a unique_files < <(printf '%s\n' "${BUMP_MULTI_FILES[@]}" | sort -u && printf '\0') || true |
| echo "Next steps:" |
| echo " cargo generate-lockfile" |
| local git_add=" git add Cargo.lock" |
| for f in "${unique_files[@]}"; do |
| git_add+=" ${f}" |
| done |
| echo "$git_add" |
| echo " git commit -m \"chore(release): bump all components\"" |
| fi |
| |
| BUMP_MULTI=0 |
| } |
| |
| # Resolve component target: --all, rust-all, or named component. |
| # Remaining args are flags passed to cmd_bump. |
| case "$1" in |
| --all) |
| shift |
| cmd_bump_multi "$ALL_COMPONENTS" "$@" ;; |
| rust-all) |
| shift |
| cmd_bump_multi "$RUST_COMPONENTS" "$@" ;; |
| --*) |
| echo -e "${RED}Expected component name, got flag: ${1}${NC}" >&2 |
| echo "Use: bump-version.sh <component> <flags>" >&2 |
| exit 1 ;; |
| *) |
| COMPONENT="$1" |
| shift |
| cmd_bump "$COMPONENT" "$@" ;; |
| esac |