#!/usr/bin/env bash # CoinVision 版本号升级脚本 # # 同步更新两处版本号: # 1) pubspec.yaml → version: X.Y.Z+CODE # 2) lib/core/config/app_config.dart → static const String appVersion = 'X.Y.Z'; # # 用法: # ./scripts/bump-version.sh patch # 4.0.3 → 4.0.4 (修订号+1) # ./scripts/bump-version.sh minor # 4.0.3 → 4.1.0 (次版本+1,修订号归零) # ./scripts/bump-version.sh major # 4.0.3 → 5.0.0 (主版本+1,次/修订归零) # ./scripts/bump-version.sh 4.2.1 # 手动指定版本号 # ./scripts/bump-version.sh --show # 只显示当前版本,不修改 # ./scripts/bump-version.sh --dry-run patch # 预览改动,不写入文件 # # versionCode 规则: major × 10000 + minor × 100 + patch # 例: 4.0.3 → 40003 ; 4.1.0 → 40100 ; 5.0.0 → 50000 # # 安全检查: # - 新 versionCode 必须严格大于当前 versionCode,否则拒绝更新 # - pubspec.yaml 的 x.y.z 必须和 app_config.dart 的 appVersion 一致 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$PROJECT_ROOT" PUBSPEC="pubspec.yaml" APP_CONFIG="lib/core/config/app_config.dart" if [ -t 1 ]; then RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' else RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; NC='' fi log_info() { printf "${BLUE}==>${NC} %s\n" "$*"; } log_ok() { printf "${GREEN}✓${NC} %s\n" "$*"; } log_warn() { printf "${YELLOW}!${NC} %s\n" "$*"; } log_err() { printf "${RED}✗${NC} %s\n" "$*" >&2; } usage() { cat < [选项] 模式: patch 修订号 +1 (4.0.3 → 4.0.4) minor 次版本 +1,修订号归零 (4.0.3 → 4.1.0) major 主版本 +1,次/修订归零 (4.0.3 → 5.0.0) X.Y.Z 手动指定版本号 (如 4.2.1) --show 仅显示当前版本,不修改 选项: --dry-run 预览改动,不写入文件 -h, --help 显示此帮助 示例: ./scripts/bump-version.sh --show ./scripts/bump-version.sh patch ./scripts/bump-version.sh minor --dry-run ./scripts/bump-version.sh 4.2.1 versionCode = major × 10000 + minor × 100 + patch EOF } if [ ! -f "$PUBSPEC" ]; then log_err "找不到 $PUBSPEC" exit 1 fi if [ ! -f "$APP_CONFIG" ]; then log_err "找不到 $APP_CONFIG" exit 1 fi # ── 解析当前版本 ───────────────────────────────────────────── CURRENT_LINE=$(grep -E "^version:" "$PUBSPEC" | head -1) if [ -z "$CURRENT_LINE" ]; then log_err "$PUBSPEC 中未找到 version: 行" exit 1 fi CURRENT_VERSION_FULL=$(echo "$CURRENT_LINE" | awk '{print $2}' | tr -d '[:space:]') CURRENT_SEMVER="${CURRENT_VERSION_FULL%%+*}" CURRENT_CODE="${CURRENT_VERSION_FULL##*+}" if ! echo "$CURRENT_SEMVER" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then log_err "当前 pubspec.yaml 版本号格式非法: $CURRENT_SEMVER (应为 x.y.z)" exit 1 fi if ! echo "$CURRENT_CODE" | grep -qE '^[0-9]+$'; then log_err "当前 pubspec.yaml versionCode 格式非法: $CURRENT_CODE" exit 1 fi CURRENT_MAJOR="${CURRENT_SEMVER%%.*}" CURRENT_REST="${CURRENT_SEMVER#*.}" CURRENT_MINOR="${CURRENT_REST%%.*}" CURRENT_PATCH="${CURRENT_REST##*.}" # ── 解析 app_config.dart 中的 appVersion ──────────────────── APP_CONFIG_VERSION=$(grep -E "static const String appVersion" "$APP_CONFIG" | sed -E "s/.*'([^']+)'.*/\1/") if [ -z "$APP_CONFIG_VERSION" ]; then log_err "$APP_CONFIG 中未找到 static const String appVersion" exit 1 fi # ── 参数解析 ───────────────────────────────────────────────── MODE="" DRY_RUN=false while [ $# -gt 0 ]; do case "$1" in -h|--help) usage; exit 0 ;; --show) printf "${CYAN}${BOLD}当前版本${NC}\n" printf " pubspec.yaml : %s (semver=%s, code=%s)\n" "$CURRENT_VERSION_FULL" "$CURRENT_SEMVER" "$CURRENT_CODE" printf " app_config.dart : %s\n" "$APP_CONFIG_VERSION" if [ "$CURRENT_SEMVER" != "$APP_CONFIG_VERSION" ]; then log_warn "两处版本号不一致!" exit 1 fi exit 0 ;; --dry-run) DRY_RUN=true ;; patch|minor|major) MODE="$1" ;; [0-9]*.[0-9]*.[0-9]*) MODE="manual" MANUAL_VERSION="$1" ;; *) log_err "未知参数: $1"; echo; usage; exit 1 ;; esac shift done if [ -z "$MODE" ]; then log_err "未指定模式" echo usage exit 1 fi # ── 一致性校验 ─────────────────────────────────────────────── if [ "$CURRENT_SEMVER" != "$APP_CONFIG_VERSION" ]; then log_warn "pubspec.yaml ($CURRENT_SEMVER) 与 app_config.dart ($APP_CONFIG_VERSION) 不一致" log_warn "将以 pubspec.yaml 为基准计算新版本" fi # ── 计算新版本 ─────────────────────────────────────────────── case "$MODE" in patch) NEW_MAJOR="$CURRENT_MAJOR" NEW_MINOR="$CURRENT_MINOR" NEW_PATCH=$((CURRENT_PATCH + 1)) ;; minor) NEW_MAJOR="$CURRENT_MAJOR" NEW_MINOR=$((CURRENT_MINOR + 1)) NEW_PATCH=0 ;; major) NEW_MAJOR=$((CURRENT_MAJOR + 1)) NEW_MINOR=0 NEW_PATCH=0 ;; manual) if ! echo "$MANUAL_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then log_err "手动版本号格式非法: $MANUAL_VERSION (应为 x.y.z)" exit 1 fi NEW_MAJOR="${MANUAL_VERSION%%.*}" _REST="${MANUAL_VERSION#*.}" NEW_MINOR="${_REST%%.*}" NEW_PATCH="${_REST##*.}" ;; esac # 修订号不得超过 99,否则和下一个次版本的 versionCode 冲突 if [ "$NEW_PATCH" -gt 99 ]; then log_err "修订号 $NEW_PATCH 超过 99,会和下一个次版本 versionCode 冲突" log_err "建议升级 minor 版本: ./scripts/bump-version.sh minor" exit 1 fi if [ "$NEW_MINOR" -gt 99 ]; then log_err "次版本号 $NEW_MINOR 超过 99,会和下一个主版本 versionCode 冲突" log_err "建议升级 major 版本: ./scripts/bump-version.sh major" exit 1 fi NEW_SEMVER="${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}" NEW_CODE=$((NEW_MAJOR * 10000 + NEW_MINOR * 100 + NEW_PATCH)) NEW_VERSION_FULL="${NEW_SEMVER}+${NEW_CODE}" # ── 安全检查: versionCode 必须严格递增 ────────────────────── if [ "$NEW_CODE" -le "$CURRENT_CODE" ]; then log_err "新 versionCode ($NEW_CODE) 不大于当前 versionCode ($CURRENT_CODE)" log_err "Android 拒绝安装 versionCode 更低或相同的包" exit 1 fi # ── 展示变化 ───────────────────────────────────────────────── printf "\n${CYAN}${BOLD}版本号变更预览${NC}\n" printf " 版本号 : ${YELLOW}%s${NC} → ${GREEN}%s${NC}\n" "$CURRENT_SEMVER" "$NEW_SEMVER" printf " versionCode : ${YELLOW}%s${NC} → ${GREEN}%s${NC}\n" "$CURRENT_CODE" "$NEW_CODE" printf " 完整写入 : ${GREEN}%s${NC}\n" "$NEW_VERSION_FULL" printf "\n${CYAN}${BOLD}将更新文件${NC}\n" printf " %s (line: version: %s)\n" "$PUBSPEC" "$NEW_VERSION_FULL" printf " %s (line: static const String appVersion = '%s';)\n" "$APP_CONFIG" "$NEW_SEMVER" if [ "$DRY_RUN" = true ]; then echo log_info "dry-run 模式,未写入文件" exit 0 fi # ── 写入文件 (macOS/BSD sed 和 GNU sed 兼容) ───────────────── sed_inplace() { local pattern="$1" local file="$2" # macOS BSD sed 要求 -i '' ; GNU sed 不能加 '' if sed --version >/dev/null 2>&1; then sed -i "$pattern" "$file" else sed -i '' "$pattern" "$file" fi } # pubspec.yaml: 替换第一个 ^version: 开头的行 sed_inplace "s|^version:.*|version: ${NEW_VERSION_FULL}|" "$PUBSPEC" # app_config.dart: 替换 appVersion 常量 sed_inplace "s|static const String appVersion = '[^']*';|static const String appVersion = '${NEW_SEMVER}';|" "$APP_CONFIG" # ── 写入验证 ───────────────────────────────────────────────── WRITTEN_PUBSPEC=$(grep -E "^version:" "$PUBSPEC" | awk '{print $2}' | tr -d '[:space:]') WRITTEN_CONFIG=$(grep -E "static const String appVersion" "$APP_CONFIG" | sed -E "s/.*'([^']+)'.*/\1/") if [ "$WRITTEN_PUBSPEC" != "$NEW_VERSION_FULL" ]; then log_err "写入 $PUBSPEC 失败,实际: $WRITTEN_PUBSPEC" exit 1 fi if [ "$WRITTEN_CONFIG" != "$NEW_SEMVER" ]; then log_err "写入 $APP_CONFIG 失败,实际: $WRITTEN_CONFIG" exit 1 fi echo log_ok "$PUBSPEC → version: $WRITTEN_PUBSPEC" log_ok "$APP_CONFIG → appVersion = '$WRITTEN_CONFIG'" # ── 下一步提示 ─────────────────────────────────────────────── echo printf "${CYAN}${BOLD}下一步${NC}\n" printf " 1. 检查改动: ${BLUE}git diff $PUBSPEC $APP_CONFIG${NC}\n" printf " 2. 构建 Release: ${BLUE}./scripts/build.sh${NC}\n" printf " 3. 提交 + 打 tag:\n" printf " ${BLUE}git add $PUBSPEC $APP_CONFIG${NC}\n" printf " ${BLUE}git commit -m 'chore(release): bump version to $NEW_SEMVER'${NC}\n" printf " ${BLUE}git tag v$NEW_SEMVER${NC}\n"