Ariver
2026-07-02 8c9ba89a748d4c99810b31f3b455d1ed43d9b61f
Prepare 2.1.40 multi-platform package build
5 files modified
1 files added
176 ■■■■■ changed files
CHANGELOG.md 16 ●●●●● patch | view | raw | blame | history
CODEGRAPH.md 16 ●●●●● patch | view | raw | blame | history
privatevoice.src/app.go 4 ●●●● patch | view | raw | blame | history
privatevoice.src/build/darwin/Info.plist 4 ●●●● patch | view | raw | blame | history
privatevoice.src/scripts/build-local-macos-universal.sh 132 ●●●●● patch | view | raw | blame | history
privatevoice.src/scripts/build-windows-preview.sh 4 ●●●● patch | view | raw | blame | history
CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
## v2.1.40 (2026-07-02)
### 多平台测试包整理
- **同步 macOS 与 App 版本元数据**:将 macOS `Info.plist` 与 App 内显示版本统一到 `2.1.40 (build 20260702.0143)`,避免 macOS 包继续显示旧 build。
- **新增 macOS universal 本地 DMG 构建入口**:可在同一 build 下生成 `arm64`、`x86_64` 和 `universal` 三种 macOS DMG,方便分别验证 Apple Silicon、Intel Mac 和双架构通用包。
- **继续提供 Windows x64 预览包**:保留 2.1.39 的 Windows 炫彩 overlay 预览能力,并统一升级到本次 build。
- **Release 目录收敛**:本版本作为整理后的最新版四包基线,旧产物统一归档到 `X-旧版build成果`。
### 构建
- build: `20260702.0143`
- 说明: 本版本为 macOS 三架构 DMG + Windows x64 预览 zip 的多平台测试包整理基线。
---
## v2.1.39 (2026-07-01)
### Windows 动态指示视觉升级
CODEGRAPH.md
@@ -3,7 +3,7 @@
## Project
- Product: Wails v3 / Go desktop dictation app.
- Current branch purpose: Windows technical preview based on `v2.1.38-build20260629.2014-diagnostic`.
- Current branch purpose: cross-platform local package handoff based on `v2.1.40-build20260702.0143`.
- Protected macOS behavior: existing macOS App Store / local DMG build lines are not changed by this branch.
## Core Entrypoints
@@ -47,6 +47,17 @@
  - `QA-WINDOWS-PREVIEW.ps1`
  - `README-WINDOWS-PREVIEW.txt`
## macOS Local Package Scope
- Local DMG script: `privatevoice.src/scripts/build-local-macos.sh`.
- Universal DMG script: `privatevoice.src/scripts/build-local-macos-universal.sh`.
- Local macOS package set for this branch:
  - `arm64` DMG for Apple Silicon.
  - `x86_64` DMG for Intel Mac.
  - `universal` DMG for Intel + Apple Silicon.
- Mainline macOS local packages keep `LSMinimumSystemVersion=14.0` and the default Qwen3-capable dependency line.
- Monterey-compatible packages remain a separate build line through `privatevoice.src/scripts/build-local-macos-monterey.sh`.
## Standard Verification Commands
Run from `privatevoice.src`:
@@ -56,6 +67,9 @@
go test ./...
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go test -exec=/usr/bin/true ./...
./scripts/build-windows-preview.sh
./scripts/build-local-macos.sh arm64
./scripts/build-local-macos.sh x86_64
./scripts/build-local-macos-universal.sh
./scripts/verify-windows-qa-evidence.sh /path/to/qa-evidence-YYYYMMDD-HHMMSS.zip
```
privatevoice.src/app.go
@@ -28,8 +28,8 @@
)
const (
    appVersion        = "2.1.39"
    appBuild          = "20260701.2356"
    appVersion        = "2.1.40"
    appBuild          = "20260702.0143"
    appDisplayVersion = appVersion + " (build " + appBuild + ")"
    appName           = "PrivateVoice Dictation"
privatevoice.src/build/darwin/Info.plist
@@ -17,9 +17,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>2.1.38</string>
    <string>2.1.40</string>
    <key>CFBundleVersion</key>
    <string>20260629.2014</string>
    <string>20260702.0143</string>
    <key>ITSAppUsesNonExemptEncryption</key>
    <false/>
    <key>LSApplicationCategoryType</key>
privatevoice.src/scripts/build-local-macos-universal.sh
New file
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
#
# build-local-macos-universal.sh - Build a locally signed universal macOS DMG.
#
# The script reuses the existing per-architecture local build script, then
# combines the app binary and bundled dylibs with lipo before re-signing.
#
set -euo pipefail
APP_NAME="PrivateVoice Dictation"
DEFAULT_LOCAL_IDENTITY="PrivateVoice Local Code Signing"
LEGACY_LOCAL_IDENTITY="VoiceSnap Local Code Signing"
LOCAL_IDENTITY="${PRIVATEVOICE_LOCAL_SIGN_IDENTITY:-${VOICESNAP_LOCAL_SIGN_IDENTITY:-}}"
KEYCHAIN="${PRIVATEVOICE_LOCAL_KEYCHAIN:-${VOICESNAP_LOCAL_KEYCHAIN:-$HOME/Library/Keychains/login.keychain-db}}"
LOCAL_BUILD_FLAVOR="${PRIVATEVOICE_LOCAL_BUILD_FLAVOR:-local}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
INFO_PLIST="$PROJECT_DIR/build/darwin/Info.plist"
ENTITLEMENTS="$PROJECT_DIR/build/darwin/entitlements.plist"
VERSION="$(defaults read "$INFO_PLIST" CFBundleShortVersionString 2>/dev/null || echo "2.1.1")"
BUILD_ID="$(defaults read "$INFO_PLIST" CFBundleVersion 2>/dev/null || date +%Y%m%d.%H%M)"
ARM_BUILD_DIR="$PROJECT_DIR/build/local/arm64"
X86_BUILD_DIR="$PROJECT_DIR/build/local/x86_64"
BUILD_DIR="$PROJECT_DIR/build/local/universal"
ARM_APP="$ARM_BUILD_DIR/$APP_NAME.app"
X86_APP="$X86_BUILD_DIR/$APP_NAME.app"
APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-build$BUILD_ID-universal-$LOCAL_BUILD_FLAVOR.dmg"
COMPAT_DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-universal-$LOCAL_BUILD_FLAVOR.dmg"
step() {
  echo ""
  echo "==> $1"
}
fail() {
  echo "ERROR: $1" >&2
  exit 1
}
resolve_local_identity() {
  if [[ -z "$LOCAL_IDENTITY" ]]; then
    if security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$DEFAULT_LOCAL_IDENTITY"; then
      LOCAL_IDENTITY="$DEFAULT_LOCAL_IDENTITY"
    elif security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$LEGACY_LOCAL_IDENTITY"; then
      LOCAL_IDENTITY="$LEGACY_LOCAL_IDENTITY"
    else
      fail "Local signing identity not found; run build-local-macos.sh first"
    fi
  fi
  if ! security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$LOCAL_IDENTITY"; then
    fail "Local signing identity not available to codesign: $LOCAL_IDENTITY"
  fi
}
sign_item() {
  local item="$1"
  codesign --force \
    --options runtime \
    --timestamp=none \
    --entitlements "$ENTITLEMENTS" \
    --sign "$LOCAL_IDENTITY" \
    "$item"
}
step "Building arm64 app"
"$SCRIPT_DIR/build-local-macos.sh" arm64
step "Building x86_64 app"
"$SCRIPT_DIR/build-local-macos.sh" x86_64
step "Preflight checks"
[[ -d "$ARM_APP" ]] || fail "arm64 app not found at $ARM_APP"
[[ -d "$X86_APP" ]] || fail "x86_64 app not found at $X86_APP"
[[ -f "$ENTITLEMENTS" ]] || fail "entitlements.plist not found at $ENTITLEMENTS"
resolve_local_identity
step "Assembling universal app"
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"
ditto "$ARM_APP" "$APP_BUNDLE"
lipo -create \
  "$ARM_APP/Contents/MacOS/$APP_NAME" \
  "$X86_APP/Contents/MacOS/$APP_NAME" \
  -output "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
for arm_dylib in "$ARM_APP"/Contents/Frameworks/*.dylib; do
  dylib_name="$(basename "$arm_dylib")"
  x86_dylib="$X86_APP/Contents/Frameworks/$dylib_name"
  out_dylib="$APP_BUNDLE/Contents/Frameworks/$dylib_name"
  [[ -f "$x86_dylib" ]] || fail "Matching x86_64 dylib not found: $x86_dylib"
  lipo -create "$arm_dylib" "$x86_dylib" -output "$out_dylib"
done
step "Code signing universal app"
for dylib in "$APP_BUNDLE"/Contents/Frameworks/*.dylib; do
  sign_item "$dylib"
done
sign_item "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
sign_item "$APP_BUNDLE"
step "Verifying universal app"
codesign -vvv --deep --strict "$APP_BUNDLE"
lipo -archs "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
step "Creating universal DMG"
DMG_TEMP="$BUILD_DIR/dmg-staging"
rm -rf "$DMG_TEMP" "$DMG_PATH" "$COMPAT_DMG_PATH"
mkdir -p "$DMG_TEMP"
cp -R "$APP_BUNDLE" "$DMG_TEMP/"
ln -s /Applications "$DMG_TEMP/Applications"
hdiutil create -volname "$APP_NAME" \
  -srcfolder "$DMG_TEMP" \
  -ov -format UDZO \
  "$DMG_PATH"
rm -rf "$DMG_TEMP"
step "Signing universal DMG"
codesign --force --timestamp=none --sign "$LOCAL_IDENTITY" "$DMG_PATH"
hdiutil verify "$DMG_PATH"
cp "$DMG_PATH" "$COMPAT_DMG_PATH"
step "Universal local build complete"
echo "  App: $APP_BUNDLE"
echo "  DMG: $DMG_PATH"
echo "  Compatibility DMG: $COMPAT_DMG_PATH"
echo "  Signing identity: $LOCAL_IDENTITY"
privatevoice.src/scripts/build-windows-preview.sh
@@ -4,8 +4,8 @@
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
APP_VERSION="2.1.39"
APP_BUILD="20260701.2356"
APP_VERSION="2.1.40"
APP_BUILD="20260702.0143"
ARCH="${1:-amd64}"
if [[ "$ARCH" != "amd64" ]]; then