bump-version.sh 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. #!/usr/bin/env bash
  2. # CoinVision 版本号升级脚本
  3. #
  4. # 同步更新两处版本号:
  5. # 1) pubspec.yaml → version: X.Y.Z+CODE
  6. # 2) lib/core/config/app_config.dart → static const String appVersion = 'X.Y.Z';
  7. #
  8. # 用法:
  9. # ./scripts/bump-version.sh patch # 4.0.3 → 4.0.4 (修订号+1)
  10. # ./scripts/bump-version.sh minor # 4.0.3 → 4.1.0 (次版本+1,修订号归零)
  11. # ./scripts/bump-version.sh major # 4.0.3 → 5.0.0 (主版本+1,次/修订归零)
  12. # ./scripts/bump-version.sh 4.2.1 # 手动指定版本号
  13. # ./scripts/bump-version.sh --show # 只显示当前版本,不修改
  14. # ./scripts/bump-version.sh --dry-run patch # 预览改动,不写入文件
  15. #
  16. # versionCode 规则: major × 10000 + minor × 100 + patch
  17. # 例: 4.0.3 → 40003 ; 4.1.0 → 40100 ; 5.0.0 → 50000
  18. #
  19. # 安全检查:
  20. # - 新 versionCode 必须严格大于当前 versionCode,否则拒绝更新
  21. # - pubspec.yaml 的 x.y.z 必须和 app_config.dart 的 appVersion 一致
  22. set -euo pipefail
  23. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  24. PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
  25. cd "$PROJECT_ROOT"
  26. PUBSPEC="pubspec.yaml"
  27. APP_CONFIG="lib/core/config/app_config.dart"
  28. if [ -t 1 ]; then
  29. RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
  30. BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
  31. else
  32. RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; NC=''
  33. fi
  34. log_info() { printf "${BLUE}==>${NC} %s\n" "$*"; }
  35. log_ok() { printf "${GREEN}✓${NC} %s\n" "$*"; }
  36. log_warn() { printf "${YELLOW}!${NC} %s\n" "$*"; }
  37. log_err() { printf "${RED}✗${NC} %s\n" "$*" >&2; }
  38. usage() {
  39. cat <<EOF
  40. CoinVision 版本号升级脚本
  41. 用法:
  42. ./scripts/bump-version.sh <模式> [选项]
  43. 模式:
  44. patch 修订号 +1 (4.0.3 → 4.0.4)
  45. minor 次版本 +1,修订号归零 (4.0.3 → 4.1.0)
  46. major 主版本 +1,次/修订归零 (4.0.3 → 5.0.0)
  47. X.Y.Z 手动指定版本号 (如 4.2.1)
  48. --show 仅显示当前版本,不修改
  49. 选项:
  50. --dry-run 预览改动,不写入文件
  51. -h, --help 显示此帮助
  52. 示例:
  53. ./scripts/bump-version.sh --show
  54. ./scripts/bump-version.sh patch
  55. ./scripts/bump-version.sh minor --dry-run
  56. ./scripts/bump-version.sh 4.2.1
  57. versionCode = major × 10000 + minor × 100 + patch
  58. EOF
  59. }
  60. if [ ! -f "$PUBSPEC" ]; then
  61. log_err "找不到 $PUBSPEC"
  62. exit 1
  63. fi
  64. if [ ! -f "$APP_CONFIG" ]; then
  65. log_err "找不到 $APP_CONFIG"
  66. exit 1
  67. fi
  68. # ── 解析当前版本 ─────────────────────────────────────────────
  69. CURRENT_LINE=$(grep -E "^version:" "$PUBSPEC" | head -1)
  70. if [ -z "$CURRENT_LINE" ]; then
  71. log_err "$PUBSPEC 中未找到 version: 行"
  72. exit 1
  73. fi
  74. CURRENT_VERSION_FULL=$(echo "$CURRENT_LINE" | awk '{print $2}' | tr -d '[:space:]')
  75. CURRENT_SEMVER="${CURRENT_VERSION_FULL%%+*}"
  76. CURRENT_CODE="${CURRENT_VERSION_FULL##*+}"
  77. if ! echo "$CURRENT_SEMVER" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
  78. log_err "当前 pubspec.yaml 版本号格式非法: $CURRENT_SEMVER (应为 x.y.z)"
  79. exit 1
  80. fi
  81. if ! echo "$CURRENT_CODE" | grep -qE '^[0-9]+$'; then
  82. log_err "当前 pubspec.yaml versionCode 格式非法: $CURRENT_CODE"
  83. exit 1
  84. fi
  85. CURRENT_MAJOR="${CURRENT_SEMVER%%.*}"
  86. CURRENT_REST="${CURRENT_SEMVER#*.}"
  87. CURRENT_MINOR="${CURRENT_REST%%.*}"
  88. CURRENT_PATCH="${CURRENT_REST##*.}"
  89. # ── 解析 app_config.dart 中的 appVersion ────────────────────
  90. APP_CONFIG_VERSION=$(grep -E "static const String appVersion" "$APP_CONFIG" | sed -E "s/.*'([^']+)'.*/\1/")
  91. if [ -z "$APP_CONFIG_VERSION" ]; then
  92. log_err "$APP_CONFIG 中未找到 static const String appVersion"
  93. exit 1
  94. fi
  95. # ── 参数解析 ─────────────────────────────────────────────────
  96. MODE=""
  97. DRY_RUN=false
  98. while [ $# -gt 0 ]; do
  99. case "$1" in
  100. -h|--help) usage; exit 0 ;;
  101. --show)
  102. printf "${CYAN}${BOLD}当前版本${NC}\n"
  103. printf " pubspec.yaml : %s (semver=%s, code=%s)\n" "$CURRENT_VERSION_FULL" "$CURRENT_SEMVER" "$CURRENT_CODE"
  104. printf " app_config.dart : %s\n" "$APP_CONFIG_VERSION"
  105. if [ "$CURRENT_SEMVER" != "$APP_CONFIG_VERSION" ]; then
  106. log_warn "两处版本号不一致!"
  107. exit 1
  108. fi
  109. exit 0
  110. ;;
  111. --dry-run) DRY_RUN=true ;;
  112. patch|minor|major)
  113. MODE="$1"
  114. ;;
  115. [0-9]*.[0-9]*.[0-9]*)
  116. MODE="manual"
  117. MANUAL_VERSION="$1"
  118. ;;
  119. *) log_err "未知参数: $1"; echo; usage; exit 1 ;;
  120. esac
  121. shift
  122. done
  123. if [ -z "$MODE" ]; then
  124. log_err "未指定模式"
  125. echo
  126. usage
  127. exit 1
  128. fi
  129. # ── 一致性校验 ───────────────────────────────────────────────
  130. if [ "$CURRENT_SEMVER" != "$APP_CONFIG_VERSION" ]; then
  131. log_warn "pubspec.yaml ($CURRENT_SEMVER) 与 app_config.dart ($APP_CONFIG_VERSION) 不一致"
  132. log_warn "将以 pubspec.yaml 为基准计算新版本"
  133. fi
  134. # ── 计算新版本 ───────────────────────────────────────────────
  135. case "$MODE" in
  136. patch)
  137. NEW_MAJOR="$CURRENT_MAJOR"
  138. NEW_MINOR="$CURRENT_MINOR"
  139. NEW_PATCH=$((CURRENT_PATCH + 1))
  140. ;;
  141. minor)
  142. NEW_MAJOR="$CURRENT_MAJOR"
  143. NEW_MINOR=$((CURRENT_MINOR + 1))
  144. NEW_PATCH=0
  145. ;;
  146. major)
  147. NEW_MAJOR=$((CURRENT_MAJOR + 1))
  148. NEW_MINOR=0
  149. NEW_PATCH=0
  150. ;;
  151. manual)
  152. if ! echo "$MANUAL_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
  153. log_err "手动版本号格式非法: $MANUAL_VERSION (应为 x.y.z)"
  154. exit 1
  155. fi
  156. NEW_MAJOR="${MANUAL_VERSION%%.*}"
  157. _REST="${MANUAL_VERSION#*.}"
  158. NEW_MINOR="${_REST%%.*}"
  159. NEW_PATCH="${_REST##*.}"
  160. ;;
  161. esac
  162. # 修订号不得超过 99,否则和下一个次版本的 versionCode 冲突
  163. if [ "$NEW_PATCH" -gt 99 ]; then
  164. log_err "修订号 $NEW_PATCH 超过 99,会和下一个次版本 versionCode 冲突"
  165. log_err "建议升级 minor 版本: ./scripts/bump-version.sh minor"
  166. exit 1
  167. fi
  168. if [ "$NEW_MINOR" -gt 99 ]; then
  169. log_err "次版本号 $NEW_MINOR 超过 99,会和下一个主版本 versionCode 冲突"
  170. log_err "建议升级 major 版本: ./scripts/bump-version.sh major"
  171. exit 1
  172. fi
  173. NEW_SEMVER="${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}"
  174. NEW_CODE=$((NEW_MAJOR * 10000 + NEW_MINOR * 100 + NEW_PATCH))
  175. NEW_VERSION_FULL="${NEW_SEMVER}+${NEW_CODE}"
  176. # ── 安全检查: versionCode 必须严格递增 ──────────────────────
  177. if [ "$NEW_CODE" -le "$CURRENT_CODE" ]; then
  178. log_err "新 versionCode ($NEW_CODE) 不大于当前 versionCode ($CURRENT_CODE)"
  179. log_err "Android 拒绝安装 versionCode 更低或相同的包"
  180. exit 1
  181. fi
  182. # ── 展示变化 ─────────────────────────────────────────────────
  183. printf "\n${CYAN}${BOLD}版本号变更预览${NC}\n"
  184. printf " 版本号 : ${YELLOW}%s${NC} → ${GREEN}%s${NC}\n" "$CURRENT_SEMVER" "$NEW_SEMVER"
  185. printf " versionCode : ${YELLOW}%s${NC} → ${GREEN}%s${NC}\n" "$CURRENT_CODE" "$NEW_CODE"
  186. printf " 完整写入 : ${GREEN}%s${NC}\n" "$NEW_VERSION_FULL"
  187. printf "\n${CYAN}${BOLD}将更新文件${NC}\n"
  188. printf " %s (line: version: %s)\n" "$PUBSPEC" "$NEW_VERSION_FULL"
  189. printf " %s (line: static const String appVersion = '%s';)\n" "$APP_CONFIG" "$NEW_SEMVER"
  190. if [ "$DRY_RUN" = true ]; then
  191. echo
  192. log_info "dry-run 模式,未写入文件"
  193. exit 0
  194. fi
  195. # ── 写入文件 (macOS/BSD sed 和 GNU sed 兼容) ─────────────────
  196. sed_inplace() {
  197. local pattern="$1"
  198. local file="$2"
  199. # macOS BSD sed 要求 -i '' ; GNU sed 不能加 ''
  200. if sed --version >/dev/null 2>&1; then
  201. sed -i "$pattern" "$file"
  202. else
  203. sed -i '' "$pattern" "$file"
  204. fi
  205. }
  206. # pubspec.yaml: 替换第一个 ^version: 开头的行
  207. sed_inplace "s|^version:.*|version: ${NEW_VERSION_FULL}|" "$PUBSPEC"
  208. # app_config.dart: 替换 appVersion 常量
  209. sed_inplace "s|static const String appVersion = '[^']*';|static const String appVersion = '${NEW_SEMVER}';|" "$APP_CONFIG"
  210. # ── 写入验证 ─────────────────────────────────────────────────
  211. WRITTEN_PUBSPEC=$(grep -E "^version:" "$PUBSPEC" | awk '{print $2}' | tr -d '[:space:]')
  212. WRITTEN_CONFIG=$(grep -E "static const String appVersion" "$APP_CONFIG" | sed -E "s/.*'([^']+)'.*/\1/")
  213. if [ "$WRITTEN_PUBSPEC" != "$NEW_VERSION_FULL" ]; then
  214. log_err "写入 $PUBSPEC 失败,实际: $WRITTEN_PUBSPEC"
  215. exit 1
  216. fi
  217. if [ "$WRITTEN_CONFIG" != "$NEW_SEMVER" ]; then
  218. log_err "写入 $APP_CONFIG 失败,实际: $WRITTEN_CONFIG"
  219. exit 1
  220. fi
  221. echo
  222. log_ok "$PUBSPEC → version: $WRITTEN_PUBSPEC"
  223. log_ok "$APP_CONFIG → appVersion = '$WRITTEN_CONFIG'"
  224. # ── 下一步提示 ───────────────────────────────────────────────
  225. echo
  226. printf "${CYAN}${BOLD}下一步${NC}\n"
  227. printf " 1. 检查改动: ${BLUE}git diff $PUBSPEC $APP_CONFIG${NC}\n"
  228. printf " 2. 构建 Release: ${BLUE}./scripts/build.sh${NC}\n"
  229. printf " 3. 提交 + 打 tag:\n"
  230. printf " ${BLUE}git add $PUBSPEC $APP_CONFIG${NC}\n"
  231. printf " ${BLUE}git commit -m 'chore(release): bump version to $NEW_SEMVER'${NC}\n"
  232. printf " ${BLUE}git tag v$NEW_SEMVER${NC}\n"