mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
🔧 fix(release/version): 对齐测试版号并移除Mac交互打包
- build-release 优先读取 GONAVI_VERSION 与 version/dev-version.txt - 新增共享测试版号文件,统一开发态与发布脚本版本来源 - internal/app 版本解析增加 dev-version 回退与回归测试 - macOS 发布改为 ZIP 归档,不再触发 create-dmg 与 Finder 排版 - 补充发布脚本调整的需求追踪文档
This commit is contained in:
357
build-release.sh
357
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}"
|
||||
|
||||
71
docs/需求追踪/需求进度追踪-发布脚本测试版号与Mac打包无交互-20260424.md
Normal file
71
docs/需求追踪/需求进度追踪-发布脚本测试版号与Mac打包无交互-20260424.md
Normal file
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
67
internal/app/version_test.go
Normal file
67
internal/app/version_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
1
version/dev-version.txt
Normal file
1
version/dev-version.txt
Normal file
@@ -0,0 +1 @@
|
||||
0.0.1-test
|
||||
Reference in New Issue
Block a user