From 9da922f66283273056ef35a602b559f577d91c63 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Mar 2026 12:00:10 +0000 Subject: [PATCH 1/4] chore: set version to 0.9.0 (beta) Not a fully tested release. Marking as beta until all services are verified and edge cases are resolved. https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn --- manifest.firefox.json | 2 +- manifest.json | 2 +- package.json | 2 +- src/options/options.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.firefox.json b/manifest.firefox.json index 06b89a3..2ebfcf0 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "2.0.14", + "version": "0.9.0", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "browser_specific_settings": { "gecko": { diff --git a/manifest.json b/manifest.json index 69b4644..4ac9c90 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "2.0.14", + "version": "0.9.0", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "permissions": [ "storage", diff --git a/package.json b/package.json index 2dcb4a7..97a2290 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silent-send", - "version": "2.0.14", + "version": "0.9.0", "private": true, "license": "BSL-1.1", "description": "Browser extension that substitutes personal data before sending to AI services", diff --git a/src/options/options.html b/src/options/options.html index 5d53b66..c49293b 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -591,7 +591,7 @@ From e8746babe4e83bb253bbbd575790bf2442ee67c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Mar 2026 12:23:31 +0000 Subject: [PATCH 2/4] feat: add version bump script (bump-version.sh) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agnostic version bump tool that finds all version strings in the project and updates them together. Usage: ./bump-version.sh # interactive menu ./bump-version.sh patch # 0.9.0 → 0.9.1 ./bump-version.sh minor # 0.9.0 → 0.10.0 ./bump-version.sh major # 0.9.0 → 1.0.0 ./bump-version.sh 1.2.3 # set to specific version Finds versions in JSON files ("version": "X.Y.Z") and HTML/JS files (vX.Y.Z). Excludes node_modules and dist directories. https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn --- bump-version.sh | 193 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100755 bump-version.sh diff --git a/bump-version.sh b/bump-version.sh new file mode 100755 index 0000000..64e730d --- /dev/null +++ b/bump-version.sh @@ -0,0 +1,193 @@ +#!/bin/bash +# +# Version Bump Script +# +# Finds all version numbers in the project, shows them, and lets you +# bump the patch/minor/major or enter a custom version. +# +# Works with any project that has version strings in JSON files, +# HTML files, or other text files. +# +# Usage: +# ./bump-version.sh # interactive — shows versions, asks what to do +# ./bump-version.sh 1.2.3 # set all versions to 1.2.3 +# ./bump-version.sh patch # bump patch: 0.9.0 → 0.9.1 +# ./bump-version.sh minor # bump minor: 0.9.0 → 0.10.0 +# ./bump-version.sh major # bump major: 0.9.0 → 1.0.0 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Find all files containing version-like strings +# Looks for patterns like "version": "X.Y.Z" or "v X.Y.Z" or "Version X.Y.Z" +find_version_files() { + # JSON files with "version": "X.Y.Z" + grep -rlE '"version":\s*"[0-9]+\.[0-9]+\.[0-9]+"' --include='*.json' . 2>/dev/null | grep -v node_modules | grep -v dist || true + # HTML/JS files with version display strings like "v1.2.3" or "v 1.2.3" + grep -rlE ' v[0-9]+\.[0-9]+\.[0-9]+' --include='*.html' --include='*.js' . 2>/dev/null | grep -v node_modules | grep -v dist || true +} + +# Extract version from a file +get_version_from_file() { + local file="$1" + # Try JSON "version": "X.Y.Z" first + local ver=$(grep -oP '"version":\s*"\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + if [ -n "$ver" ]; then + echo "$ver" + return + fi + # Try "vX.Y.Z" or "Version X.Y.Z" pattern + ver=$(grep -oP '[vV]ersion[" ]*\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + if [ -n "$ver" ]; then + echo "$ver" + return + fi + # Try " vX.Y.Z" (e.g. "Silent Send v0.9.0") + ver=$(grep -oP ' v\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + if [ -n "$ver" ]; then + echo "$ver" + return + fi +} + +# Get the most common version across all files (the "current" version) +get_current_version() { + local versions="" + while IFS= read -r file; do + [ -z "$file" ] && continue + local ver=$(get_version_from_file "$file") + if [ -n "$ver" ]; then + versions="$versions $ver" + fi + done <<< "$(find_version_files)" + + # Return the most common version + echo "$versions" | tr ' ' '\n' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' +} + +# Show all version occurrences +show_versions() { + echo -e "${CYAN}Files with version strings:${NC}" + echo "" + while IFS= read -r file; do + [ -z "$file" ] && continue + local ver=$(get_version_from_file "$file") + if [ -n "$ver" ]; then + local rel="${file#./}" + echo -e " ${GREEN}$ver${NC} $rel" + fi + done <<< "$(find_version_files)" + echo "" +} + +# Replace version in all files +replace_version() { + local old="$1" + local new="$2" + + echo -e "${YELLOW}Replacing $old → $new${NC}" + echo "" + + while IFS= read -r file; do + [ -z "$file" ] && continue + local ver=$(get_version_from_file "$file") + if [ "$ver" = "$old" ]; then + # Replace in JSON files: "version": "X.Y.Z" + sed -i "s/\"version\": \"$old\"/\"version\": \"$new\"/" "$file" 2>/dev/null + # Replace display strings: vX.Y.Z or Version X.Y.Z + sed -i "s/\(ersion[\" ]*\)$old/\1$new/g" "$file" 2>/dev/null + # Replace vX.Y.Z standalone + sed -i "s/v$old/v$new/g" "$file" 2>/dev/null + + local rel="${file#./}" + echo -e " ${GREEN}✓${NC} $rel" + fi + done <<< "$(find_version_files)" + + echo "" + echo -e "${GREEN}Done! All versions set to $new${NC}" +} + +# Bump a version component +bump_version() { + local ver="$1" + local part="$2" + + IFS='.' read -r major minor patch <<< "$ver" + + case "$part" in + major) echo "$((major + 1)).0.0" ;; + minor) echo "$major.$((minor + 1)).0" ;; + patch) echo "$major.$minor.$((patch + 1))" ;; + esac +} + +# --- Main --- + +CURRENT=$(get_current_version) + +if [ -z "$CURRENT" ]; then + echo -e "${RED}No version strings found in the project.${NC}" + exit 1 +fi + +# Handle command-line argument +if [ -n "$1" ]; then + case "$1" in + patch|minor|major) + NEW=$(bump_version "$CURRENT" "$1") + show_versions + replace_version "$CURRENT" "$NEW" + exit 0 + ;; + [0-9]*) + show_versions + replace_version "$CURRENT" "$1" + exit 0 + ;; + *) + echo "Usage: $0 [patch|minor|major|X.Y.Z]" + exit 1 + ;; + esac +fi + +# Interactive mode +show_versions + +echo -e "Current version: ${GREEN}$CURRENT${NC}" +echo "" +echo " [1] Bump patch: $CURRENT → $(bump_version "$CURRENT" patch)" +echo " [2] Bump minor: $CURRENT → $(bump_version "$CURRENT" minor)" +echo " [3] Bump major: $CURRENT → $(bump_version "$CURRENT" major)" +echo " [4] Enter custom version" +echo " [q] Quit" +echo "" +read -p "Choice: " choice + +case "$choice" in + 1) NEW=$(bump_version "$CURRENT" patch) ;; + 2) NEW=$(bump_version "$CURRENT" minor) ;; + 3) NEW=$(bump_version "$CURRENT" major) ;; + 4) + read -p "Enter version: " NEW + if ! [[ "$NEW" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo -e "${RED}Invalid version format. Use X.Y.Z${NC}" + exit 1 + fi + ;; + q|Q|"") echo "Cancelled."; exit 0 ;; + *) echo -e "${RED}Invalid choice.${NC}"; exit 1 ;; +esac + +replace_version "$CURRENT" "$NEW" From 863e6376422d1bf652f85ec1383bae9e2490bc21 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Mar 2026 12:30:13 +0000 Subject: [PATCH 3/4] feat: make bump-version.sh truly project-agnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now supports all common version patterns: - JSON ("version": "X.Y.Z") — Node, extensions, composer - TOML (version = "X.Y.Z") — Rust, Python pyproject.toml - YAML (version: X.Y.Z) — Helm, GitHub Actions - Python (__version__ = "X.Y.Z") — packages - PHP (Version: X.Y.Z) — WordPress plugins/themes - Shell (VERSION="X.Y.Z") — scripts - HTML (vX.Y.Z) — display footers - XML — .csproj, plist Skips node_modules, dist, build, .git, __pycache__, venv, vendor. Skips itself to avoid matching usage examples. Skips files over 500KB to avoid scanning binaries. https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn --- bump-version.sh | 141 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 47 deletions(-) diff --git a/bump-version.sh b/bump-version.sh index 64e730d..4f7e4c3 100755 --- a/bump-version.sh +++ b/bump-version.sh @@ -1,16 +1,22 @@ #!/bin/bash # -# Version Bump Script +# Version Bump Script — works with any project # -# Finds all version numbers in the project, shows them, and lets you -# bump the patch/minor/major or enter a custom version. +# Finds version strings in any file format and updates them together. # -# Works with any project that has version strings in JSON files, -# HTML files, or other text files. +# Supported patterns: +# JSON: "version": "1.2.3" +# TOML: version = "1.2.3" +# YAML: version: 1.2.3 +# Python: __version__ = "1.2.3" or version = "1.2.3" +# PHP: Version: 1.2.3 (WordPress plugin/theme headers) +# Shell: VERSION="1.2.3" +# HTML/JS: v1.2.3 +# setup.cfg: version = 1.2.3 # # Usage: -# ./bump-version.sh # interactive — shows versions, asks what to do -# ./bump-version.sh 1.2.3 # set all versions to 1.2.3 +# ./bump-version.sh # interactive +# ./bump-version.sh 1.2.3 # set all to 1.2.3 # ./bump-version.sh patch # bump patch: 0.9.0 → 0.9.1 # ./bump-version.sh minor # bump minor: 0.9.0 → 0.10.0 # ./bump-version.sh major # bump major: 0.9.0 → 1.0.0 @@ -26,53 +32,88 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' RED='\033[0;31m' -NC='\033[0m' # No Color +DIM='\033[2m' +NC='\033[0m' -# Find all files containing version-like strings -# Looks for patterns like "version": "X.Y.Z" or "v X.Y.Z" or "Version X.Y.Z" +# Directories to skip +SKIP_DIRS="node_modules|dist|build|\.git|__pycache__|\.venv|venv|\.egg-info|vendor" + +# File extensions to search +SEARCH_EXTS="json|toml|yaml|yml|py|php|sh|bash|html|js|css|cfg|ini|txt|md|xml|plist|gradle|gemspec|podspec|csproj|props" + +# Find all files that might contain version strings find_version_files() { - # JSON files with "version": "X.Y.Z" - grep -rlE '"version":\s*"[0-9]+\.[0-9]+\.[0-9]+"' --include='*.json' . 2>/dev/null | grep -v node_modules | grep -v dist || true - # HTML/JS files with version display strings like "v1.2.3" or "v 1.2.3" - grep -rlE ' v[0-9]+\.[0-9]+\.[0-9]+' --include='*.html' --include='*.js' . 2>/dev/null | grep -v node_modules | grep -v dist || true + local files="" + + # Use find + grep for maximum compatibility + while IFS= read -r file; do + [ -z "$file" ] && continue + # Skip binary files and large files + [ ! -f "$file" ] && continue + local size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0) + [ "$size" -gt 500000 ] && continue + + files="$files +$file" + done < <(find . -type f \( \ + -name "*.json" -o -name "*.toml" -o -name "*.yaml" -o -name "*.yml" \ + -o -name "*.py" -o -name "*.php" -o -name "*.sh" -o -name "*.bash" \ + -o -name "*.html" -o -name "*.js" -o -name "*.css" -o -name "*.cfg" \ + -o -name "*.ini" -o -name "*.txt" -o -name "*.xml" -o -name "*.plist" \ + -o -name "*.gradle" -o -name "*.gemspec" -o -name "*.podspec" \ + -o -name "*.csproj" -o -name "*.props" -o -name "*.md" \ + -o -name "Makefile" -o -name "Dockerfile" -o -name "Cargo.toml" \ + -o -name "setup.py" -o -name "setup.cfg" -o -name "pyproject.toml" \ + -o -name "package.json" -o -name "composer.json" -o -name "Gemfile" \ + \) 2>/dev/null | grep -Ev "$SKIP_DIRS" | grep -v "bump-version") + + echo "$files" } -# Extract version from a file +# Extract version from a file — tries all known patterns get_version_from_file() { local file="$1" - # Try JSON "version": "X.Y.Z" first - local ver=$(grep -oP '"version":\s*"\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) - if [ -n "$ver" ]; then - echo "$ver" - return - fi - # Try "vX.Y.Z" or "Version X.Y.Z" pattern - ver=$(grep -oP '[vV]ersion[" ]*\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) - if [ -n "$ver" ]; then - echo "$ver" - return - fi - # Try " vX.Y.Z" (e.g. "Silent Send v0.9.0") - ver=$(grep -oP ' v\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) - if [ -n "$ver" ]; then - echo "$ver" - return - fi + local ver="" + + # JSON: "version": "X.Y.Z" + ver=$(grep -oP '"version"\s*:\s*"\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + [ -n "$ver" ] && echo "$ver" && return + + # TOML/Python: version = "X.Y.Z" or __version__ = "X.Y.Z" + ver=$(grep -oP '(?:__)?version\s*=\s*["\x27]\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + [ -n "$ver" ] && echo "$ver" && return + + # YAML: version: X.Y.Z or version: "X.Y.Z" + ver=$(grep -oP '^version:\s*["\x27]?\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + [ -n "$ver" ] && echo "$ver" && return + + # PHP/WordPress: Version: X.Y.Z (in comment header) + ver=$(grep -oP 'Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + [ -n "$ver" ] && echo "$ver" && return + + # Shell: VERSION="X.Y.Z" or VERSION='X.Y.Z' + ver=$(grep -oP 'VERSION\s*=\s*["\x27]?\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + [ -n "$ver" ] && echo "$ver" && return + + # HTML/display: " vX.Y.Z" or ">vX.Y.Z" + ver=$(grep -oP '[ >]v\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) + [ -n "$ver" ] && echo "$ver" && return + + # XML/plist: X.Y.Z near version key + ver=$(grep -A1 -i 'version' "$file" 2>/dev/null | grep -oP '[0-9]+\.[0-9]+\.[0-9]+' | head -1) + [ -n "$ver" ] && echo "$ver" && return } -# Get the most common version across all files (the "current" version) +# Get the most common version (the "current" version) get_current_version() { local versions="" while IFS= read -r file; do [ -z "$file" ] && continue local ver=$(get_version_from_file "$file") - if [ -n "$ver" ]; then - versions="$versions $ver" - fi + [ -n "$ver" ] && versions="$versions $ver" done <<< "$(find_version_files)" - # Return the most common version - echo "$versions" | tr ' ' '\n' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' + echo "$versions" | tr ' ' '\n' | grep -v '^$' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' } # Show all version occurrences @@ -94,6 +135,7 @@ show_versions() { replace_version() { local old="$1" local new="$2" + local escaped_old=$(echo "$old" | sed 's/\./\\./g') echo -e "${YELLOW}Replacing $old → $new${NC}" echo "" @@ -102,12 +144,19 @@ replace_version() { [ -z "$file" ] && continue local ver=$(get_version_from_file "$file") if [ "$ver" = "$old" ]; then - # Replace in JSON files: "version": "X.Y.Z" - sed -i "s/\"version\": \"$old\"/\"version\": \"$new\"/" "$file" 2>/dev/null - # Replace display strings: vX.Y.Z or Version X.Y.Z - sed -i "s/\(ersion[\" ]*\)$old/\1$new/g" "$file" 2>/dev/null - # Replace vX.Y.Z standalone - sed -i "s/v$old/v$new/g" "$file" 2>/dev/null + # Replace all known version patterns + # JSON: "version": "X.Y.Z" + sed -i "s/\(\"version\"\s*:\s*\"\)$escaped_old\"/\1$new\"/g" "$file" 2>/dev/null + # TOML/Python: version = "X.Y.Z" or __version__ = "X.Y.Z" + sed -i "s/\(\(__\)\?version\s*=\s*[\"']\)$escaped_old/\1$new/g" "$file" 2>/dev/null + # YAML: version: X.Y.Z + sed -i "s/\(^version:\s*[\"']\?\)$escaped_old/\1$new/g" "$file" 2>/dev/null + # PHP/WordPress: Version: X.Y.Z + sed -i "s/\(Version:\s*\)$escaped_old/\1$new/g" "$file" 2>/dev/null + # Shell: VERSION="X.Y.Z" + sed -i "s/\(VERSION\s*=\s*[\"']\?\)$escaped_old/\1$new/g" "$file" 2>/dev/null + # HTML display: vX.Y.Z + sed -i "s/\([ >]v\)$escaped_old/\1$new/g" "$file" 2>/dev/null local rel="${file#./}" echo -e " ${GREEN}✓${NC} $rel" @@ -122,9 +171,7 @@ replace_version() { bump_version() { local ver="$1" local part="$2" - IFS='.' read -r major minor patch <<< "$ver" - case "$part" in major) echo "$((major + 1)).0.0" ;; minor) echo "$major.$((minor + 1)).0" ;; From eb2e946673ba2c4b9f501248fcbb8727eeba6984 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Mar 2026 12:43:32 +0000 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20bump-version.sh=20v2=20=E2=80=94=20?= =?UTF-8?q?preview,=20confirm,=20undo,=20profiles,=20help?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete rewrite with safety features: - Version profiles: groups files by version, only updates the current version (ignores unrelated X.Y.Z strings in the project) - Preview: shows exact line-by-line diff before applying changes - Confirmation: asks y/N before modifying files (--yes to skip) - Undo: saves rollback info, ./bump-version.sh undo to revert - --help: full usage documentation with examples - Only matches version DECLARATIONS (requires keyword context like "version":, version=, VERSION=, v prefix) — won't match random numbers like dates, IPs, or port numbers https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn --- bump-version.sh | 304 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 252 insertions(+), 52 deletions(-) diff --git a/bump-version.sh b/bump-version.sh index 4f7e4c3..59a1f69 100755 --- a/bump-version.sh +++ b/bump-version.sh @@ -1,8 +1,9 @@ #!/bin/bash # -# Version Bump Script — works with any project +# bump-version.sh — Universal version bump tool # -# Finds version strings in any file format and updates them together. +# Finds version strings in any project, shows what will change, +# asks for confirmation, and updates them. Supports undo. # # Supported patterns: # JSON: "version": "1.2.3" @@ -15,11 +16,13 @@ # setup.cfg: version = 1.2.3 # # Usage: -# ./bump-version.sh # interactive -# ./bump-version.sh 1.2.3 # set all to 1.2.3 -# ./bump-version.sh patch # bump patch: 0.9.0 → 0.9.1 -# ./bump-version.sh minor # bump minor: 0.9.0 → 0.10.0 -# ./bump-version.sh major # bump major: 0.9.0 → 1.0.0 +# ./bump-version.sh # interactive +# ./bump-version.sh patch # bump patch: 0.9.0 → 0.9.1 +# ./bump-version.sh minor # bump minor: 0.9.0 → 0.10.0 +# ./bump-version.sh major # bump major: 0.9.0 → 1.0.0 +# ./bump-version.sh 1.2.3 # set all to 1.2.3 +# ./bump-version.sh undo # revert last bump +# ./bump-version.sh --help # show help # set -e @@ -27,34 +30,76 @@ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$SCRIPT_DIR" +UNDO_FILE="$SCRIPT_DIR/.version-bump-undo" + # Colors GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' RED='\033[0;31m' DIM='\033[2m' +BOLD='\033[1m' NC='\033[0m' # Directories to skip SKIP_DIRS="node_modules|dist|build|\.git|__pycache__|\.venv|venv|\.egg-info|vendor" -# File extensions to search -SEARCH_EXTS="json|toml|yaml|yml|py|php|sh|bash|html|js|css|cfg|ini|txt|md|xml|plist|gradle|gemspec|podspec|csproj|props" +# --- Help --- +show_help() { + cat << 'HELPEOF' +bump-version.sh — Universal version bump tool -# Find all files that might contain version strings +USAGE: + ./bump-version.sh Interactive mode + ./bump-version.sh patch Bump patch: 0.9.0 → 0.9.1 + ./bump-version.sh minor Bump minor: 0.9.0 → 0.10.0 + ./bump-version.sh major Bump major: 0.9.0 → 1.0.0 + ./bump-version.sh 1.2.3 Set to exact version + ./bump-version.sh undo Revert last version bump + ./bump-version.sh --help Show this help + +HOW IT WORKS: + 1. Scans all text files for version-like patterns + 2. Groups files by version number (the "version profile") + 3. Only updates files matching the most common version + 4. Shows a preview of all changes before applying + 5. Asks for confirmation (unless --yes flag is used) + 6. Saves undo information for easy rollback + +SUPPORTED PATTERNS: + "version": "1.2.3" JSON (package.json, manifest.json, etc.) + version = "1.2.3" TOML, Python (pyproject.toml, setup.py) + version: 1.2.3 YAML (helm charts, GitHub Actions) + __version__ = "1.2.3" Python packages + Version: 1.2.3 PHP/WordPress plugin/theme headers + VERSION="1.2.3" Shell scripts + v1.2.3 HTML footers, display strings + +SAFETY: + - Only updates files matching the current version (ignores unrelated X.Y.Z) + - Shows all changes before applying and asks for confirmation + - Saves undo info so you can revert with ./bump-version.sh undo + - Skips node_modules, dist, build, .git, __pycache__, venv, vendor + - Skips binary files and files over 500KB + - Skips itself (won't match examples in this help text) + +EXAMPLES: + ./bump-version.sh # shows versions, pick what to do + ./bump-version.sh patch # quick patch bump, still confirms + ./bump-version.sh 1.0.0 --yes # set to 1.0.0 without confirmation + ./bump-version.sh undo # oops, go back +HELPEOF + exit 0 +} + +# --- Find version files --- find_version_files() { - local files="" - - # Use find + grep for maximum compatibility while IFS= read -r file; do [ -z "$file" ] && continue - # Skip binary files and large files [ ! -f "$file" ] && continue local size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0) [ "$size" -gt 500000 ] && continue - - files="$files -$file" + echo "$file" done < <(find . -type f \( \ -name "*.json" -o -name "*.toml" -o -name "*.yaml" -o -name "*.yml" \ -o -name "*.py" -o -name "*.php" -o -name "*.sh" -o -name "*.bash" \ @@ -66,11 +111,11 @@ $file" -o -name "setup.py" -o -name "setup.cfg" -o -name "pyproject.toml" \ -o -name "package.json" -o -name "composer.json" -o -name "Gemfile" \ \) 2>/dev/null | grep -Ev "$SKIP_DIRS" | grep -v "bump-version") - - echo "$files" } -# Extract version from a file — tries all known patterns +# --- Extract version from file --- +# Only matches version DECLARATIONS, not arbitrary X.Y.Z in text. +# Each pattern requires a keyword context (version, VERSION, __version__, etc.) get_version_from_file() { local file="$1" local ver="" @@ -83,28 +128,24 @@ get_version_from_file() { ver=$(grep -oP '(?:__)?version\s*=\s*["\x27]\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) [ -n "$ver" ] && echo "$ver" && return - # YAML: version: X.Y.Z or version: "X.Y.Z" + # YAML: version: X.Y.Z (must be at start of line) ver=$(grep -oP '^version:\s*["\x27]?\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) [ -n "$ver" ] && echo "$ver" && return - # PHP/WordPress: Version: X.Y.Z (in comment header) + # PHP/WordPress: Version: X.Y.Z ver=$(grep -oP 'Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) [ -n "$ver" ] && echo "$ver" && return - # Shell: VERSION="X.Y.Z" or VERSION='X.Y.Z' + # Shell: VERSION="X.Y.Z" ver=$(grep -oP 'VERSION\s*=\s*["\x27]?\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) [ -n "$ver" ] && echo "$ver" && return - # HTML/display: " vX.Y.Z" or ">vX.Y.Z" + # HTML display: " vX.Y.Z" or ">vX.Y.Z" (requires v prefix — not bare numbers) ver=$(grep -oP '[ >]v\K[0-9]+\.[0-9]+\.[0-9]+' "$file" 2>/dev/null | head -1) [ -n "$ver" ] && echo "$ver" && return - - # XML/plist: X.Y.Z near version key - ver=$(grep -A1 -i 'version' "$file" 2>/dev/null | grep -oP '[0-9]+\.[0-9]+\.[0-9]+' | head -1) - [ -n "$ver" ] && echo "$ver" && return } -# Get the most common version (the "current" version) +# --- Get current version (most common across files) --- get_current_version() { local versions="" while IFS= read -r file; do @@ -116,35 +157,93 @@ get_current_version() { echo "$versions" | tr ' ' '\n' | grep -v '^$' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' } -# Show all version occurrences +# --- Show all version occurrences grouped by version --- show_versions() { - echo -e "${CYAN}Files with version strings:${NC}" + local current="$1" + local files_list="" + + echo -e "${CYAN}Version profile:${NC}" echo "" + + # Collect all versions + declare -A version_files while IFS= read -r file; do [ -z "$file" ] && continue local ver=$(get_version_from_file "$file") if [ -n "$ver" ]; then local rel="${file#./}" - echo -e " ${GREEN}$ver${NC} $rel" + if [ -z "${version_files[$ver]+x}" ]; then + version_files[$ver]="$rel" + else + version_files[$ver]="${version_files[$ver]}|$rel" + fi fi done <<< "$(find_version_files)" + + # Show grouped by version + for ver in $(echo "${!version_files[@]}" | tr ' ' '\n' | sort -V); do + if [ "$ver" = "$current" ]; then + echo -e " ${GREEN}$ver${NC} ${BOLD}(current — will be updated)${NC}" + else + echo -e " ${DIM}$ver (different — will NOT be changed)${NC}" + fi + IFS='|' read -ra files <<< "${version_files[$ver]}" + for f in "${files[@]}"; do + if [ "$ver" = "$current" ]; then + echo -e " ${GREEN}✓${NC} $f" + else + echo -e " ${DIM}· $f${NC}" + fi + done + done echo "" } -# Replace version in all files +# --- Preview changes --- +preview_changes() { + local old="$1" + local new="$2" + + echo -e "${YELLOW}Changes to be made:${NC}" + echo "" + + local count=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + local ver=$(get_version_from_file "$file") + if [ "$ver" = "$old" ]; then + local rel="${file#./}" + # Show the actual line that will change + local line=$(grep -n "$old" "$file" 2>/dev/null | head -1) + local linenum=$(echo "$line" | cut -d: -f1) + local content=$(echo "$line" | cut -d: -f2-) + local newcontent=$(echo "$content" | sed "s/$old/$new/g") + echo -e " ${BOLD}$rel${NC} (line $linenum)" + echo -e " ${RED}- $content${NC}" + echo -e " ${GREEN}+ $newcontent${NC}" + echo "" + count=$((count + 1)) + fi + done <<< "$(find_version_files)" + + echo -e " ${CYAN}$count file(s) will be modified${NC}" + echo "" +} + +# --- Apply changes --- replace_version() { local old="$1" local new="$2" local escaped_old=$(echo "$old" | sed 's/\./\\./g') - echo -e "${YELLOW}Replacing $old → $new${NC}" - echo "" + # Save undo info + echo "$new $old" > "$UNDO_FILE" + local undo_files="" while IFS= read -r file; do [ -z "$file" ] && continue local ver=$(get_version_from_file "$file") if [ "$ver" = "$old" ]; then - # Replace all known version patterns # JSON: "version": "X.Y.Z" sed -i "s/\(\"version\"\s*:\s*\"\)$escaped_old\"/\1$new\"/g" "$file" 2>/dev/null # TOML/Python: version = "X.Y.Z" or __version__ = "X.Y.Z" @@ -160,14 +259,57 @@ replace_version() { local rel="${file#./}" echo -e " ${GREEN}✓${NC} $rel" + undo_files="$undo_files $rel" fi done <<< "$(find_version_files)" + # Append file list to undo + echo "$undo_files" >> "$UNDO_FILE" + echo "" - echo -e "${GREEN}Done! All versions set to $new${NC}" + echo -e "${GREEN}Done! $old → $new${NC}" + echo -e "${DIM}Run ./bump-version.sh undo to revert${NC}" } -# Bump a version component +# --- Undo --- +do_undo() { + if [ ! -f "$UNDO_FILE" ]; then + echo -e "${RED}No undo information found. Nothing to revert.${NC}" + exit 1 + fi + + local versions=$(head -1 "$UNDO_FILE") + local current=$(echo "$versions" | awk '{print $1}') + local previous=$(echo "$versions" | awk '{print $2}') + + echo -e "${YELLOW}Undo: $current → $previous${NC}" + echo "" + + # Just do a version replace in the other direction + local escaped_current=$(echo "$current" | sed 's/\./\\./g') + + while IFS= read -r file; do + [ -z "$file" ] && continue + local ver=$(get_version_from_file "$file") + if [ "$ver" = "$current" ]; then + sed -i "s/\(\"version\"\s*:\s*\"\)$escaped_current\"/\1$previous\"/g" "$file" 2>/dev/null + sed -i "s/\(\(__\)\?version\s*=\s*[\"']\)$escaped_current/\1$previous/g" "$file" 2>/dev/null + sed -i "s/\(^version:\s*[\"']\?\)$escaped_current/\1$previous/g" "$file" 2>/dev/null + sed -i "s/\(Version:\s*\)$escaped_current/\1$previous/g" "$file" 2>/dev/null + sed -i "s/\(VERSION\s*=\s*[\"']\?\)$escaped_current/\1$previous/g" "$file" 2>/dev/null + sed -i "s/\([ >]v\)$escaped_current/\1$previous/g" "$file" 2>/dev/null + + local rel="${file#./}" + echo -e " ${GREEN}✓${NC} $rel" + fi + done <<< "$(find_version_files)" + + rm -f "$UNDO_FILE" + echo "" + echo -e "${GREEN}Reverted to $previous${NC}" +} + +# --- Bump version component --- bump_version() { local ver="$1" local part="$2" @@ -179,45 +321,94 @@ bump_version() { esac } +# --- Confirm --- +confirm() { + local msg="$1" + read -p "$msg [y/N] " answer + case "$answer" in + y|Y|yes|YES) return 0 ;; + *) return 1 ;; + esac +} + # --- Main --- +# Parse flags +YES_FLAG=false +for arg in "$@"; do + case "$arg" in + --help|-h) show_help ;; + --yes|-y) YES_FLAG=true ;; + undo) do_undo; exit 0 ;; + esac +done + +# Get first non-flag argument +ACTION="" +for arg in "$@"; do + case "$arg" in + --*|-*) continue ;; + undo) continue ;; + *) ACTION="$arg"; break ;; + esac +done + CURRENT=$(get_current_version) if [ -z "$CURRENT" ]; then - echo -e "${RED}No version strings found in the project.${NC}" + echo -e "${RED}No version declarations found in the project.${NC}" + echo -e "${DIM}Looking for patterns like \"version\": \"X.Y.Z\" or VERSION=\"X.Y.Z\"${NC}" + echo -e "${DIM}Run ./bump-version.sh --help for supported formats${NC}" exit 1 fi # Handle command-line argument -if [ -n "$1" ]; then - case "$1" in +if [ -n "$ACTION" ]; then + case "$ACTION" in patch|minor|major) - NEW=$(bump_version "$CURRENT" "$1") - show_versions - replace_version "$CURRENT" "$NEW" - exit 0 + NEW=$(bump_version "$CURRENT" "$ACTION") ;; [0-9]*) - show_versions - replace_version "$CURRENT" "$1" - exit 0 + if ! [[ "$ACTION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo -e "${RED}Invalid version format. Use X.Y.Z (e.g. 1.0.0)${NC}" + exit 1 + fi + NEW="$ACTION" ;; *) - echo "Usage: $0 [patch|minor|major|X.Y.Z]" + echo -e "${RED}Unknown action: $ACTION${NC}" + echo "Usage: $0 [patch|minor|major|X.Y.Z|undo|--help]" exit 1 ;; esac + + show_versions "$CURRENT" + preview_changes "$CURRENT" "$NEW" + + if [ "$YES_FLAG" = false ]; then + if ! confirm "Apply these changes?"; then + echo "Cancelled." + exit 0 + fi + fi + + replace_version "$CURRENT" "$NEW" + exit 0 fi # Interactive mode -show_versions +show_versions "$CURRENT" -echo -e "Current version: ${GREEN}$CURRENT${NC}" +echo -e "Current version: ${GREEN}${BOLD}$CURRENT${NC}" echo "" echo " [1] Bump patch: $CURRENT → $(bump_version "$CURRENT" patch)" echo " [2] Bump minor: $CURRENT → $(bump_version "$CURRENT" minor)" echo " [3] Bump major: $CURRENT → $(bump_version "$CURRENT" major)" echo " [4] Enter custom version" +if [ -f "$UNDO_FILE" ]; then + prev=$(head -1 "$UNDO_FILE" | awk '{print $2}') + echo " [u] Undo last bump (revert to $prev)" +fi echo " [q] Quit" echo "" read -p "Choice: " choice @@ -227,14 +418,23 @@ case "$choice" in 2) NEW=$(bump_version "$CURRENT" minor) ;; 3) NEW=$(bump_version "$CURRENT" major) ;; 4) - read -p "Enter version: " NEW + read -p "Enter version (X.Y.Z): " NEW if ! [[ "$NEW" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo -e "${RED}Invalid version format. Use X.Y.Z${NC}" exit 1 fi ;; + u|U) do_undo; exit 0 ;; q|Q|"") echo "Cancelled."; exit 0 ;; *) echo -e "${RED}Invalid choice.${NC}"; exit 1 ;; esac +echo "" +preview_changes "$CURRENT" "$NEW" + +if ! confirm "Apply these changes?"; then + echo "Cancelled." + exit 0 +fi + replace_version "$CURRENT" "$NEW"