diff --git a/bump-version.sh b/bump-version.sh new file mode 100755 index 0000000..59a1f69 --- /dev/null +++ b/bump-version.sh @@ -0,0 +1,440 @@ +#!/bin/bash +# +# bump-version.sh — Universal version bump tool +# +# 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" +# 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 +# ./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 + +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" + +# --- Help --- +show_help() { + cat << 'HELPEOF' +bump-version.sh — Universal version bump tool + +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() { + while IFS= read -r file; do + [ -z "$file" ] && continue + [ ! -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 + 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" \ + -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") +} + +# --- 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="" + + # 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 (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 + 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" + 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" (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 +} + +# --- Get current version (most common across files) --- +get_current_version() { + local versions="" + while IFS= read -r file; do + [ -z "$file" ] && continue + local ver=$(get_version_from_file "$file") + [ -n "$ver" ] && versions="$versions $ver" + done <<< "$(find_version_files)" + + echo "$versions" | tr ' ' '\n' | grep -v '^$' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' +} + +# --- Show all version occurrences grouped by version --- +show_versions() { + 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#./}" + 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 "" +} + +# --- 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') + + # 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 + # 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" + 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! $old → $new${NC}" + echo -e "${DIM}Run ./bump-version.sh undo to revert${NC}" +} + +# --- 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" + 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 +} + +# --- 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 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 "$ACTION" ]; then + case "$ACTION" in + patch|minor|major) + NEW=$(bump_version "$CURRENT" "$ACTION") + ;; + [0-9]*) + 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 -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 "$CURRENT" + +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 + +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 (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" diff --git a/manifest.firefox.json b/manifest.firefox.json index 10640e0..912e723 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 6383ca1..2324715 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 3083ebf..5511747 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -610,7 +610,7 @@