From 14c651083571e56e0680c000c4f16f4765d800f3 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 24 Apr 2026 16:48:09 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20fix(release/version):=20?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E6=B5=8B=E8=AF=95=E7=89=88=E5=8F=B7=E5=B9=B6?= =?UTF-8?q?=E7=A7=BB=E9=99=A4Mac=E4=BA=A4=E4=BA=92=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build-release 优先读取 GONAVI_VERSION 与 version/dev-version.txt - 新增共享测试版号文件,统一开发态与发布脚本版本来源 - internal/app 版本解析增加 dev-version 回退与回归测试 - macOS 发布改为 ZIP 归档,不再触发 create-dmg 与 Finder 排版 - 补充发布脚本调整的需求追踪文档 --- build-release.sh | 357 +++++------------- ...追踪-发布脚本测试版号与Mac打包无交互-20260424.md | 71 ++++ internal/app/version.go | 44 ++- internal/app/version_test.go | 67 ++++ version/dev-version.txt | 1 + 5 files changed, 273 insertions(+), 267 deletions(-) create mode 100644 docs/需求追踪/需求进度追踪-发布脚本测试版号与Mac打包无交互-20260424.md create mode 100644 internal/app/version_test.go create mode 100644 version/dev-version.txt diff --git a/build-release.sh b/build-release.sh index d2bc228..edb9fe3 100755 --- a/build-release.sh +++ b/build-release.sh @@ -1,16 +1,42 @@ #!/bin/bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + # 配置 APP_NAME="GoNavi" DIST_DIR="dist" BUILD_BIN_DIR="build/bin" DEFAULT_BINARY_NAME="GoNavi" # 对应 wails.json 中的 outputfilename +DEV_VERSION_FILE="version/dev-version.txt" +DEFAULT_DEV_VERSION="0.0.1-test" -# 提取版本号 -VERSION=$(grep '"version":' frontend/package.json | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]') -if [ -z "$VERSION" ]; then - VERSION="0.0.0" -fi +resolve_build_version() { + if [ -n "${GONAVI_VERSION:-}" ]; then + printf '%s\n' "${GONAVI_VERSION}" + return + fi + + if [ -f "$DEV_VERSION_FILE" ]; then + local dev_version + dev_version=$(head -n 1 "$DEV_VERSION_FILE" | tr -d '\r' | tr -d '[:space:]') + if [ -n "$dev_version" ]; then + printf '%s\n' "$dev_version" + return + fi + fi + + local package_version + package_version=$(grep '"version":' frontend/package.json | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[:space:]') + if [ -n "$package_version" ]; then + printf '%s\n' "$package_version" + return + fi + + printf '%s\n' "$DEFAULT_DEV_VERSION" +} + +VERSION="$(resolve_build_version)" echo "ℹ️ 检测到版本号: $VERSION" LDFLAGS="-s -w -X GoNavi-Wails/internal/app.AppVersion=$VERSION" @@ -94,280 +120,79 @@ clear_macos_bundle_xattrs() { fi } -verify_macos_dmg_bundle_signature() { - local dmg_path="$1" - local mount_dir="" - local app_path="" +package_macos_bundle_zip() { + local app_path="$1" + local archive_path="$2" + local archive_abs - if [ -z "$dmg_path" ] || [ ! -f "$dmg_path" ]; then - echo -e "${RED} ❌ DMG 文件不存在,无法校验签名:$dmg_path${NC}" - return 1 - fi - if ! command -v hdiutil >/dev/null 2>&1 || ! command -v codesign >/dev/null 2>&1; then - echo -e "${YELLOW} ⚠️ 当前环境缺少 hdiutil 或 codesign,跳过 DMG 内应用签名校验。${NC}" - return 0 + if [ ! -d "$app_path" ]; then + echo -e "${RED} ❌ 未找到 macOS 应用包:$app_path${NC}" + exit 1 fi - mount_dir=$(mktemp -d "${TMPDIR:-/tmp}/gonavi-dmg-verify.XXXXXX") - if [ -z "$mount_dir" ] || [ ! -d "$mount_dir" ]; then - echo -e "${RED} ❌ 创建 DMG 校验挂载目录失败。${NC}" - return 1 + archive_abs="$(cd "$(dirname "$archive_path")" && pwd)/$(basename "$archive_path")" + rm -f "$archive_path" + if command -v ditto >/dev/null 2>&1; then + ditto -c -k --sequesterRsrc --keepParent "$app_path" "$archive_abs" + elif command -v zip >/dev/null 2>&1; then + ( + cd "$(dirname "$app_path")" && \ + zip -qry "$archive_abs" "$(basename "$app_path")" + ) + else + echo -e "${RED} ❌ 未找到 ditto/zip,无法打包 macOS 应用。${NC}" + exit 1 fi - if ! hdiutil attach -nobrowse -readonly -mountpoint "$mount_dir" "$dmg_path" >/dev/null 2>&1; then - rmdir "$mount_dir" >/dev/null 2>&1 || true - echo -e "${RED} ❌ 挂载 DMG 失败,无法校验签名。${NC}" - return 1 + if [ ! -f "$archive_abs" ]; then + echo -e "${RED} ❌ macOS 应用归档失败:$archive_abs${NC}" + exit 1 fi - - app_path=$(find "$mount_dir" -maxdepth 1 -name "*.app" -print -quit) - if [ -z "$app_path" ] || [ ! -d "$app_path" ]; then - hdiutil detach "$mount_dir" -quiet >/dev/null 2>&1 || true - rmdir "$mount_dir" >/dev/null 2>&1 || true - echo -e "${RED} ❌ DMG 内未找到 .app 应用包。${NC}" - return 1 - fi - - if ! codesign --verify --deep --strict --verbose=4 "$app_path" >/dev/null 2>&1; then - echo -e "${RED} ❌ DMG 内 .app 签名校验失败:$(basename "$app_path")${NC}" - codesign --verify --deep --strict --verbose=4 "$app_path" 2>&1 | sed 's/^/ /' - hdiutil detach "$mount_dir" -quiet >/dev/null 2>&1 || true - rmdir "$mount_dir" >/dev/null 2>&1 || true - return 1 - fi - - hdiutil detach "$mount_dir" -quiet >/dev/null 2>&1 || true - rmdir "$mount_dir" >/dev/null 2>&1 || true - return 0 } -MAC_VOLICON_PATH="build/darwin/icon.icns" -if [ ! -f "$MAC_VOLICON_PATH" ]; then - MAC_VOLICON_PATH="" -fi +package_macos_release() { + local platform="$1" + local archive_suffix="$2" + + echo -e "${GREEN}🍎 正在构建 macOS (${platform})...${NC}" + wails build -platform "darwin/${platform}" -clean -ldflags "$LDFLAGS" + if [ $? -ne 0 ]; then + echo -e "${RED} ❌ macOS ${platform} 构建失败。${NC}" + return + fi + + local app_src="$BUILD_BIN_DIR/$DEFAULT_BINARY_NAME.app" + local app_dest_name="${APP_NAME}-${VERSION}-${archive_suffix}.app" + local zip_name="${APP_NAME}-${VERSION}-${archive_suffix}.zip" + + mv "$app_src" "$DIST_DIR/$app_dest_name" + + local app_bin_path + app_bin_path=$(find "$DIST_DIR/$app_dest_name/Contents/MacOS" -maxdepth 1 -type f -print -quit) + if [ -z "$app_bin_path" ] || [ ! -f "$app_bin_path" ]; then + echo -e "${RED} ❌ 未找到 macOS ${platform} 主程序文件。${NC}" + exit 1 + fi + + echo -e "${YELLOW} ⚠️ macOS ${platform} 改为无交互 ZIP 打包,不再生成 DMG。${NC}" + echo " 🔏 正在对 .app 进行 ad-hoc 签名 (${platform})..." + clear_macos_bundle_xattrs "$DIST_DIR/$app_dest_name" + codesign --force --deep --sign - "$DIST_DIR/$app_dest_name" + + echo " 📦 正在打包 macOS 应用归档 (${platform})..." + package_macos_bundle_zip "$DIST_DIR/$app_dest_name" "$DIST_DIR/$zip_name" + rm -rf "$DIST_DIR/$app_dest_name" + echo " ✅ 已生成 $zip_name" +} echo -e "${GREEN}🚀 开始构建 $APP_NAME $VERSION...${NC}" # 清理并创建输出目录 -rm -rf $DIST_DIR -mkdir -p $DIST_DIR +rm -rf "$DIST_DIR" +mkdir -p "$DIST_DIR" -# --- macOS ARM64 构建 --- -echo -e "${GREEN}🍎 正在构建 macOS (arm64)...${NC}" -wails build -platform darwin/arm64 -clean -ldflags "$LDFLAGS" -if [ $? -eq 0 ]; then - APP_SRC="$BUILD_BIN_DIR/$DEFAULT_BINARY_NAME.app" - APP_DEST_NAME="${APP_NAME}-${VERSION}-mac-arm64.app" - DMG_NAME="${APP_NAME}-${VERSION}-mac-arm64.dmg" - - # 移动 .app 到 dist - mv "$APP_SRC" "$DIST_DIR/$APP_DEST_NAME" - - APP_BIN_PATH=$(find "$DIST_DIR/$APP_DEST_NAME/Contents/MacOS" -maxdepth 1 -type f -print -quit) - if [ -n "$APP_BIN_PATH" ] && [ -f "$APP_BIN_PATH" ]; then - echo -e "${YELLOW} ⚠️ macOS arm64 不再执行 UPX 压缩,保留原始主程序。${NC}" - else - echo -e "${RED} ❌ 未找到 macOS arm64 主程序文件。${NC}" - exit 1 - fi - - # Ad-hoc 代码签名(无 Apple Developer 账号时防止 Gatekeeper 报已损坏) - echo " 🔏 正在对 .app 进行 ad-hoc 签名 (arm64)..." - clear_macos_bundle_xattrs "$DIST_DIR/$APP_DEST_NAME" - codesign --force --deep --sign - "$DIST_DIR/$APP_DEST_NAME" - - # 创建 DMG - if command -v create-dmg &> /dev/null; then - echo " 📦 正在打包 DMG (arm64)..." - # 移除已存在的 DMG (以防万一) - rm -f "$DIST_DIR/$DMG_NAME" - # create-dmg 的 source 需要是“包含 .app 的目录”,不能直接传 .app 路径。 - STAGE_DIR=$(mktemp -d "$DIST_DIR/.dmg-stage-${APP_NAME}-${VERSION}-arm64.XXXXXX") - if [ -z "$STAGE_DIR" ] || [ ! -d "$STAGE_DIR" ]; then - echo -e "${RED} ❌ 创建 DMG 临时目录失败,跳过 DMG 打包。${NC}" - else - if command -v ditto &> /dev/null; then - ditto "$DIST_DIR/$APP_DEST_NAME" "$STAGE_DIR/$APP_DEST_NAME" - else - cp -R "$DIST_DIR/$APP_DEST_NAME" "$STAGE_DIR/$APP_DEST_NAME" - fi - - # 注意:本地验证表明 `--sandbox-safe` 与“目录作为 source”组合会污染 DMG 内 .app 的扩展属性, - # 导致签名校验失败,因此这里显式禁用该参数,优先保证产物可打开。 - CREATE_DMG_ARGS=(--volname "${APP_NAME} ${VERSION}" --format UDZO) - if [ -n "$MAC_VOLICON_PATH" ]; then - CREATE_DMG_ARGS+=(--volicon "$MAC_VOLICON_PATH") - else - echo -e "${YELLOW} ⚠️ 未找到 macOS 卷图标 (build/darwin/icon.icns),跳过 --volicon。${NC}" - fi - - create-dmg "${CREATE_DMG_ARGS[@]}" \ - --window-pos 200 120 \ - --window-size 800 400 \ - --icon-size 100 \ - --icon "$APP_DEST_NAME" 200 190 \ - --hide-extension "$APP_DEST_NAME" \ - --app-drop-link 600 185 \ - "$DIST_DIR/$DMG_NAME" \ - "$STAGE_DIR" - - CREATE_DMG_EXIT_CODE=$? - rm -rf "$STAGE_DIR" - - if [ $CREATE_DMG_EXIT_CODE -ne 0 ]; then - echo -e "${RED} ❌ create-dmg 执行失败 (exit=$CREATE_DMG_EXIT_CODE),保留 .app 以便排查。${NC}" - else - # create-dmg 可能会在失败时遗留 rw.*.dmg 中间产物;不要直接当作最终产物使用 - if [ ! -f "$DIST_DIR/$DMG_NAME" ]; then - RW_FILE=$(find "$DIST_DIR" -maxdepth 1 -name "rw.*.dmg" -print -quit) - if [ -n "$RW_FILE" ]; then - echo -e "${YELLOW} ⚠️ 检测到 create-dmg 中间产物: $(basename "$RW_FILE"),正在转换为可分发 DMG...${NC}" - hdiutil convert "$RW_FILE" -format UDZO -o "$DIST_DIR/$DMG_NAME" >/dev/null 2>&1 - rm -f "$RW_FILE" - fi - fi - - # 防御性:即使生成了目标文件,也要确保不是 UDRW(UDRW 在 Finder 下可能表现为“已损坏/无法打开”) - if [ -f "$DIST_DIR/$DMG_NAME" ] && command -v hdiutil &> /dev/null; then - DMG_FORMAT=$(hdiutil imageinfo "$DIST_DIR/$DMG_NAME" 2>/dev/null | awk -F': ' '/^Format:/{print $2; exit}') - if [ "$DMG_FORMAT" = "UDRW" ]; then - echo -e "${YELLOW} ⚠️ 检测到 UDRW(可写原始映像),正在转换为 UDZO...${NC}" - TMP_UDZO="$DIST_DIR/.tmp.$DMG_NAME" - rm -f "$TMP_UDZO" - hdiutil convert "$DIST_DIR/$DMG_NAME" -format UDZO -o "$TMP_UDZO" >/dev/null 2>&1 && mv "$TMP_UDZO" "$DIST_DIR/$DMG_NAME" - fi - fi - - if [ -f "$DIST_DIR/$DMG_NAME" ] && command -v hdiutil &> /dev/null; then - hdiutil verify "$DIST_DIR/$DMG_NAME" >/dev/null 2>&1 - if [ $? -ne 0 ]; then - echo -e "${RED} ❌ DMG 校验失败,保留 .app 以便排查。${NC}" - elif ! verify_macos_dmg_bundle_signature "$DIST_DIR/$DMG_NAME"; then - echo -e "${RED} ❌ DMG 内应用签名校验失败,保留 .app 与 .dmg 以便排查。${NC}" - else - # 删除中间的 .app 文件,保持目录整洁 - rm -rf "$DIST_DIR/$APP_DEST_NAME" - echo " ✅ 已生成 $DMG_NAME" - fi - fi - fi - - if [ ! -f "$DIST_DIR/$DMG_NAME" ]; then - echo -e "${RED} ❌ DMG 生成失败,请检查 create-dmg 输出。${NC}" - fi - fi - else - echo -e "${YELLOW} ⚠️ 未找到 create-dmg 工具,跳过 DMG 打包,仅保留 .app。${NC}" - echo " 安装命令: brew install create-dmg" - fi - else - echo -e "${RED} ❌ macOS arm64 构建失败。${NC}" -fi - -# --- macOS AMD64 构建 --- -echo -e "${GREEN}🍎 正在构建 macOS (amd64)...${NC}" -wails build -platform darwin/amd64 -clean -ldflags "$LDFLAGS" -if [ $? -eq 0 ]; then - APP_SRC="$BUILD_BIN_DIR/$DEFAULT_BINARY_NAME.app" - APP_DEST_NAME="${APP_NAME}-${VERSION}-mac-amd64.app" - DMG_NAME="${APP_NAME}-${VERSION}-mac-amd64.dmg" - - mv "$APP_SRC" "$DIST_DIR/$APP_DEST_NAME" - - APP_BIN_PATH=$(find "$DIST_DIR/$APP_DEST_NAME/Contents/MacOS" -maxdepth 1 -type f -print -quit) - if [ -n "$APP_BIN_PATH" ] && [ -f "$APP_BIN_PATH" ]; then - echo -e "${YELLOW} ⚠️ macOS amd64 不再执行 UPX 压缩,保留原始主程序。${NC}" - else - echo -e "${RED} ❌ 未找到 macOS amd64 主程序文件。${NC}" - exit 1 - fi - - # Ad-hoc 代码签名 - echo " 🔏 正在对 .app 进行 ad-hoc 签名 (amd64)..." - clear_macos_bundle_xattrs "$DIST_DIR/$APP_DEST_NAME" - codesign --force --deep --sign - "$DIST_DIR/$APP_DEST_NAME" - - if command -v create-dmg &> /dev/null; then - echo " 📦 正在打包 DMG (amd64)..." - rm -f "$DIST_DIR/$DMG_NAME" - # create-dmg 的 source 需要是“包含 .app 的目录”,不能直接传 .app 路径。 - STAGE_DIR=$(mktemp -d "$DIST_DIR/.dmg-stage-${APP_NAME}-${VERSION}-amd64.XXXXXX") - if [ -z "$STAGE_DIR" ] || [ ! -d "$STAGE_DIR" ]; then - echo -e "${RED} ❌ 创建 DMG 临时目录失败,跳过 DMG 打包。${NC}" - else - if command -v ditto &> /dev/null; then - ditto "$DIST_DIR/$APP_DEST_NAME" "$STAGE_DIR/$APP_DEST_NAME" - else - cp -R "$DIST_DIR/$APP_DEST_NAME" "$STAGE_DIR/$APP_DEST_NAME" - fi - - # 注意:本地验证表明 `--sandbox-safe` 与“目录作为 source”组合会污染 DMG 内 .app 的扩展属性, - # 导致签名校验失败,因此这里显式禁用该参数,优先保证产物可打开。 - CREATE_DMG_ARGS=(--volname "${APP_NAME} ${VERSION}" --format UDZO) - if [ -n "$MAC_VOLICON_PATH" ]; then - CREATE_DMG_ARGS+=(--volicon "$MAC_VOLICON_PATH") - else - echo -e "${YELLOW} ⚠️ 未找到 macOS 卷图标 (build/darwin/icon.icns),跳过 --volicon。${NC}" - fi - - create-dmg "${CREATE_DMG_ARGS[@]}" \ - --window-pos 200 120 \ - --window-size 800 400 \ - --icon-size 100 \ - --icon "$APP_DEST_NAME" 200 190 \ - --hide-extension "$APP_DEST_NAME" \ - --app-drop-link 600 185 \ - "$DIST_DIR/$DMG_NAME" \ - "$STAGE_DIR" - - CREATE_DMG_EXIT_CODE=$? - rm -rf "$STAGE_DIR" - - if [ $CREATE_DMG_EXIT_CODE -ne 0 ]; then - echo -e "${RED} ❌ create-dmg 执行失败 (exit=$CREATE_DMG_EXIT_CODE),保留 .app 以便排查。${NC}" - else - if [ ! -f "$DIST_DIR/$DMG_NAME" ]; then - RW_FILE=$(find "$DIST_DIR" -maxdepth 1 -name "rw.*.dmg" -print -quit) - if [ -n "$RW_FILE" ]; then - echo -e "${YELLOW} ⚠️ 检测到 create-dmg 中间产物: $(basename "$RW_FILE"),正在转换为可分发 DMG...${NC}" - hdiutil convert "$RW_FILE" -format UDZO -o "$DIST_DIR/$DMG_NAME" >/dev/null 2>&1 - rm -f "$RW_FILE" - fi - fi - - if [ -f "$DIST_DIR/$DMG_NAME" ] && command -v hdiutil &> /dev/null; then - DMG_FORMAT=$(hdiutil imageinfo "$DIST_DIR/$DMG_NAME" 2>/dev/null | awk -F': ' '/^Format:/{print $2; exit}') - if [ "$DMG_FORMAT" = "UDRW" ]; then - echo -e "${YELLOW} ⚠️ 检测到 UDRW(可写原始映像),正在转换为 UDZO...${NC}" - TMP_UDZO="$DIST_DIR/.tmp.$DMG_NAME" - rm -f "$TMP_UDZO" - hdiutil convert "$DIST_DIR/$DMG_NAME" -format UDZO -o "$TMP_UDZO" >/dev/null 2>&1 && mv "$TMP_UDZO" "$DIST_DIR/$DMG_NAME" - fi - fi - - if [ -f "$DIST_DIR/$DMG_NAME" ] && command -v hdiutil &> /dev/null; then - hdiutil verify "$DIST_DIR/$DMG_NAME" >/dev/null 2>&1 - if [ $? -ne 0 ]; then - echo -e "${RED} ❌ DMG 校验失败,保留 .app 以便排查。${NC}" - elif ! verify_macos_dmg_bundle_signature "$DIST_DIR/$DMG_NAME"; then - echo -e "${RED} ❌ DMG 内应用签名校验失败,保留 .app 与 .dmg 以便排查。${NC}" - else - rm -rf "$DIST_DIR/$APP_DEST_NAME" - echo " ✅ 已生成 $DMG_NAME" - fi - fi - fi - - if [ ! -f "$DIST_DIR/$DMG_NAME" ]; then - echo -e "${RED} ❌ DMG 生成失败。${NC}" - fi - fi - else - echo -e "${YELLOW} ⚠️ 未找到 create-dmg 工具。${NC}" - fi - else - echo -e "${RED} ❌ macOS amd64 构建失败。${NC}" -fi +package_macos_release "arm64" "mac-arm64" +package_macos_release "amd64" "mac-amd64" # --- Windows AMD64 构建 --- echo -e "${GREEN}🪟 正在构建 Windows (amd64)...${NC}" diff --git a/docs/需求追踪/需求进度追踪-发布脚本测试版号与Mac打包无交互-20260424.md b/docs/需求追踪/需求进度追踪-发布脚本测试版号与Mac打包无交互-20260424.md new file mode 100644 index 0000000..51d555c --- /dev/null +++ b/docs/需求追踪/需求进度追踪-发布脚本测试版号与Mac打包无交互-20260424.md @@ -0,0 +1,71 @@ +# 需求进度追踪 - 发布脚本测试版号与 Mac 打包无交互 + +## 1. 需求摘要 +- 需求名称:发布脚本测试版号与 Mac 打包无交互 +- 提出日期:2026-04-24 +- 负责人:Codex +- 目标: + - `build-release.sh` 不再触发 macOS DMG/Finder 排版交互。 + - `build-release.sh` 与开发态应用内版本号统一使用测试版号来源。 +- 非目标: + - 不调整 GitHub Release 工作流。 + - 不修改正式发布 tag 版本策略。 + +## 2. 范围与验收 +- 范围: + - 发布脚本 `build-release.sh` + - 版本解析逻辑 `internal/app/version.go` + - 共享测试版号文件 +- 验收标准: + - `bash build-release.sh` 的 macOS 打包不再调用 `create-dmg` 或触发 Finder 排版。 + - 本地开发态版本显示与发布脚本默认版本号一致。 + - 保留环境变量覆盖版本号能力。 +- 依赖与约束: + - 维持现有 Windows/Linux 构建逻辑不变。 + +## 3. 里程碑与进度 +- [x] 阶段 1(需求澄清):确认去掉 DMG 排版,统一测试版号来源 +- [x] 阶段 2(影响分析):锁定 `build-release.sh` 与 `internal/app/version.go` +- [x] 阶段 3(方案设计):共享 `version/dev-version.txt`,macOS 改 ZIP 打包 +- [x] 阶段 4(实施计划):先补版本回归测试,再改实现 +- [ ] 阶段 5(实现与自检): +- [ ] 阶段 6(评审与交付): +- [ ] 阶段 7(发布与观察): + +## 4. 变更清单 +- 已完成: + - 新增共享测试版号文件。 + - 新增版本回归测试。 + - 改造发布脚本 macOS 打包为无交互 ZIP。 +- 进行中: + - 自检验证。 +- 待处理: + - 无。 + +## 5. 风险与阻塞 +- 风险: + - 正式发版若未覆盖 `GONAVI_VERSION`,默认会使用测试版号。 +- 阻塞: + - 无。 +- 缓解措施: + - 允许通过 `GONAVI_VERSION` 环境变量显式覆盖。 + +## 6. 决策记录 +- 决策 1:以 `version/dev-version.txt` 作为本地开发/测试共享版本号来源。 +- 决策 2:发布脚本的 macOS 产物改为 ZIP,避免 `create-dmg` 的 Finder 交互。 + +## 7. 验证记录 +- 验证项: + - 版本回归测试 + - 发布脚本语法检查 + - 发布脚本运行输出 +- 结果: + - 进行中 +- 证据(日志/截图/链接): + - 待补充 + +## 8. 下一步 +- 下一步行动: + - 跑通回归测试和脚本验证,确认输出产物与版本号 +- 负责人: + - Codex diff --git a/internal/app/version.go b/internal/app/version.go index 4871a6e..a4748a0 100644 --- a/internal/app/version.go +++ b/internal/app/version.go @@ -9,12 +9,16 @@ import ( var AppVersion = "0.0.0" var AppBuildTime = "" +var developmentVersionPathResolver = defaultDevelopmentVersionPaths +var packageVersionPathResolver = defaultPackageVersionPaths func getCurrentVersion() string { version := strings.TrimSpace(AppVersion) if version == "" || version == "0.0.0" { if env := strings.TrimSpace(os.Getenv("GONAVI_VERSION")); env != "" { version = env + } else if devVersion, err := readDevelopmentVersion(); err == nil && devVersion != "" { + version = devVersion } else if pkgVersion, err := readPackageVersion(); err == nil && pkgVersion != "" { version = pkgVersion } @@ -22,7 +26,20 @@ func getCurrentVersion() string { return normalizeVersion(version) } -func readPackageVersion() (string, error) { +func defaultDevelopmentVersionPaths() []string { + paths := []string{ + filepath.Join("version", "dev-version.txt"), + } + exe, err := os.Executable() + if err == nil { + base := filepath.Dir(exe) + paths = append(paths, filepath.Join(base, "version", "dev-version.txt")) + paths = append(paths, filepath.Join(base, "..", "version", "dev-version.txt")) + } + return paths +} + +func defaultPackageVersionPaths() []string { paths := []string{ filepath.Join("frontend", "package.json"), } @@ -32,7 +49,32 @@ func readPackageVersion() (string, error) { paths = append(paths, filepath.Join(base, "frontend", "package.json")) paths = append(paths, filepath.Join(base, "..", "frontend", "package.json")) } + return paths +} +func readDevelopmentVersion() (string, error) { + return readPlainVersionFromPaths(developmentVersionPathResolver()) +} + +func readPackageVersion() (string, error) { + return readJSONVersionFromPaths(packageVersionPathResolver()) +} + +func readPlainVersionFromPaths(paths []string) (string, error) { + for _, p := range paths { + data, err := os.ReadFile(p) + if err != nil { + continue + } + if version := strings.TrimSpace(string(data)); version != "" { + return version, nil + } + } + + return "", os.ErrNotExist +} + +func readJSONVersionFromPaths(paths []string) (string, error) { for _, p := range paths { data, err := os.ReadFile(p) if err != nil { diff --git a/internal/app/version_test.go b/internal/app/version_test.go new file mode 100644 index 0000000..fa7a388 --- /dev/null +++ b/internal/app/version_test.go @@ -0,0 +1,67 @@ +package app + +import ( + "os" + "path/filepath" + "testing" +) + +func TestGetCurrentVersionUsesDevelopmentVersionFileWhenUnset(t *testing.T) { + tempDir := t.TempDir() + devVersionPath := filepath.Join(tempDir, "dev-version.txt") + if err := os.WriteFile(devVersionPath, []byte("0.0.1-test\n"), 0o644); err != nil { + t.Fatalf("WriteFile returned error: %v", err) + } + + originalAppVersion := AppVersion + originalDevResolver := developmentVersionPathResolver + originalPackageResolver := packageVersionPathResolver + AppVersion = "0.0.0" + developmentVersionPathResolver = func() []string { + return []string{devVersionPath} + } + packageVersionPathResolver = func() []string { + return nil + } + t.Setenv("GONAVI_VERSION", "") + defer func() { + AppVersion = originalAppVersion + developmentVersionPathResolver = originalDevResolver + packageVersionPathResolver = originalPackageResolver + }() + + got := getCurrentVersion() + if got != "0.0.1-test" { + t.Fatalf("expected development version file fallback, got %q", got) + } +} + +func TestGetCurrentVersionPrefersEnvOverDevelopmentVersionFile(t *testing.T) { + tempDir := t.TempDir() + devVersionPath := filepath.Join(tempDir, "dev-version.txt") + if err := os.WriteFile(devVersionPath, []byte("0.0.1-test\n"), 0o644); err != nil { + t.Fatalf("WriteFile returned error: %v", err) + } + + originalAppVersion := AppVersion + originalDevResolver := developmentVersionPathResolver + originalPackageResolver := packageVersionPathResolver + AppVersion = "0.0.0" + developmentVersionPathResolver = func() []string { + return []string{devVersionPath} + } + packageVersionPathResolver = func() []string { + return nil + } + t.Setenv("GONAVI_VERSION", "dev-override") + defer func() { + AppVersion = originalAppVersion + developmentVersionPathResolver = originalDevResolver + packageVersionPathResolver = originalPackageResolver + }() + + got := getCurrentVersion() + if got != "dev-override" { + t.Fatalf("expected env override, got %q", got) + } +} diff --git a/version/dev-version.txt b/version/dev-version.txt new file mode 100644 index 0000000..d1de8ba --- /dev/null +++ b/version/dev-version.txt @@ -0,0 +1 @@ +0.0.1-test