atharvalade commented on code in PR #2990:
URL: https://github.com/apache/iggy/pull/2990#discussion_r2991220516


##########
scripts/bump-version.sh:
##########
@@ -0,0 +1,605 @@
+#!/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)
+  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
+
+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}"
+
+# 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"
+            ;;
+        *)
+            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
+        exit 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
+            exit 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
+            exit 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
+            exit 1 ;;
+    esac
+}
+
+# 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)
+            sed -i "1,/^version = \".*\"/s/^version = \".*\"/version = 
\"${translated}\"/" "$abs_file" ;;
+        cargo-ws-dep)
+            local pkg="${format#cargo-ws-dep:}"
+            sed -i "s/^\(${pkg} = .*version = \"\)[^\"]*/\1${translated}/" 
"$abs_file" ;;
+        cargo-dep)
+            local pkg="${format#cargo-dep:}"
+            sed -i "s/^\(${pkg} = .*version = \"\)[^\"]*/\1${translated}/" 
"$abs_file" ;;
+        python-cargo)
+            sed -i "1,/^version = \".*\"/s/^version = \".*\"/version = 
\"${translated}\"/" "$abs_file" ;;
+        pyproject)
+            sed -i '/^\[project\]/,/^\[/{ s/^version = ".*"/version = 
"'"${translated}"'"/ }' "$abs_file" ;;
+        json)
+            sed -i "s/\"version\": *\"[^\"]*\"/\"version\": 
\"${translated}\"/" "$abs_file" ;;
+        csproj)
+            sed -i 
"s/<Version>[^<]*<\/Version>/<Version>${translated}<\/Version>/" "$abs_file" ;;
+        gradle)
+            sed -i "s/^version=.*/version=${translated}/" "$abs_file" ;;
+        go)
+            sed -i "s/const Version = \"[^\"]*\"/const Version = 
\"${translated}\"/" "$abs_file" ;;
+    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 -C "$REPO_ROOT" diff --quiet -- "$file" 2>/dev/null; then
+            dirty_files+=("$file")
+        fi
+        if ! git -C "$REPO_ROOT" diff --cached --quiet -- "$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|--dry) 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: edge on already-SNAPSHOT is a no-op
+    if [[ "$component" == "sdk-java" && "$keyword" == "edge" ]]; 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
+        echo ""
+        echo "To commit:"
+        local git_add="  git add"
+        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
+}
+
+# --- 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.
+cmd_bump_multi() {
+    local components="$1"
+    shift
+    local failed=0
+    for comp in $components; do
+        echo -e "${CYAN}--- ${comp} ---${NC}"
+        if cmd_bump "$comp" "$@"; then
+            :
+        else
+            failed=$((failed + 1))
+            echo -e "${YELLOW}Skipped ${comp} (transition not valid)${NC}"
+        fi
+        echo ""
+    done
+    if [[ $failed -gt 0 ]]; then
+        echo -e "${YELLOW}${failed} component(s) skipped due to invalid 
transitions.${NC}"
+        echo -e "Use ${CYAN}--set${NC} to handle them individually."
+    fi
+}

Review Comment:
   `md_bump_multi` counts failures in failed but never returns a non-zero 
status, so the function always completes “successfully” from the shell’s point 
of view even when one or more `cmd_bump` calls failed



##########
scripts/bump-version.sh:
##########
@@ -0,0 +1,605 @@
+#!/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)
+  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
+
+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}"
+
+# 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"
+            ;;
+        *)
+            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
+        exit 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
+            exit 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
+            exit 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
+            exit 1 ;;
+    esac
+}
+
+# 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)
+            sed -i "1,/^version = \".*\"/s/^version = \".*\"/version = 
\"${translated}\"/" "$abs_file" ;;
+        cargo-ws-dep)
+            local pkg="${format#cargo-ws-dep:}"
+            sed -i "s/^\(${pkg} = .*version = \"\)[^\"]*/\1${translated}/" 
"$abs_file" ;;
+        cargo-dep)
+            local pkg="${format#cargo-dep:}"
+            sed -i "s/^\(${pkg} = .*version = \"\)[^\"]*/\1${translated}/" 
"$abs_file" ;;
+        python-cargo)
+            sed -i "1,/^version = \".*\"/s/^version = \".*\"/version = 
\"${translated}\"/" "$abs_file" ;;
+        pyproject)
+            sed -i '/^\[project\]/,/^\[/{ s/^version = ".*"/version = 
"'"${translated}"'"/ }' "$abs_file" ;;
+        json)
+            sed -i "s/\"version\": *\"[^\"]*\"/\"version\": 
\"${translated}\"/" "$abs_file" ;;
+        csproj)
+            sed -i 
"s/<Version>[^<]*<\/Version>/<Version>${translated}<\/Version>/" "$abs_file" ;;
+        gradle)
+            sed -i "s/^version=.*/version=${translated}/" "$abs_file" ;;
+        go)
+            sed -i "s/const Version = \"[^\"]*\"/const Version = 
\"${translated}\"/" "$abs_file" ;;
+    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 -C "$REPO_ROOT" diff --quiet -- "$file" 2>/dev/null; then
+            dirty_files+=("$file")
+        fi
+        if ! git -C "$REPO_ROOT" diff --cached --quiet -- "$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|--dry) 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: edge on already-SNAPSHOT is a no-op
+    if [[ "$component" == "sdk-java" && "$keyword" == "edge" ]]; 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
+        echo ""
+        echo "To commit:"
+        local git_add="  git add"
+        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
+}
+
+# --- 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.
+cmd_bump_multi() {
+    local components="$1"
+    shift
+    local failed=0
+    for comp in $components; do
+        echo -e "${CYAN}--- ${comp} ---${NC}"
+        if cmd_bump "$comp" "$@"; then
+            :
+        else
+            failed=$((failed + 1))
+            echo -e "${YELLOW}Skipped ${comp} (transition not valid)${NC}"
+        fi
+        echo ""
+    done
+    if [[ $failed -gt 0 ]]; then
+        echo -e "${YELLOW}${failed} component(s) skipped due to invalid 
transitions.${NC}"
+        echo -e "Use ${CYAN}--set${NC} to handle them individually."
+    fi
+}
+
+# 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" "$@"
+        exit 0 ;;
+    rust-all)
+        shift
+        cmd_bump_multi "$RUST_COMPONENTS" "$@"
+        exit 0 ;;

Review Comment:
   The `--all` and `rust-all `branches always run exit 0 after 
`cmd_bump_multi`, so the process exit code is 0 even when `failed > 0`. That 
makes `rust-all / --all` unsafe for release automation or any script that 
relies on `$? `(partial bumps look like full success). Suggested direction: 
have `cmd_bump_multi` return non-zero when failed > 0, and replace the 
unconditional exit 0 with propagating that status (e.g. `cmd_bump_multi` `...; 
exit $?` or `exit 1` when any component failed).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to