package.sh 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. #!/bin/bash
  2. # Build ../dist/DoTouchAI.app + a drag-install DMG from the local electron
  3. # dist + app sources. (Manual bundle construction: deterministic, no network,
  4. # no @electron/get.)
  5. set -euo pipefail
  6. cd "$(dirname "$0")/.."
  7. SRC="node_modules/electron/dist/Electron.app"
  8. OUT="../dist"
  9. APP="$OUT/DoTouchAI.app"
  10. PLIST="$APP/Contents/Info.plist"
  11. VERSION="$(node -p "require('./package.json').version")"
  12. DMG="$OUT/DoTouchAI-${VERSION}-arm64.dmg"
  13. rm -rf "$APP"
  14. mkdir -p "$OUT" "$APP/Contents/Resources/app/assets"
  15. cp -R "$SRC/." "$APP/"
  16. rm -rf "$APP/Contents/Resources/default_app.asar"
  17. # App sources — this shell has zero runtime npm deps, so node_modules stays out.
  18. cp main.js env.js service.js updater.js plugins.js package.json "$APP/Contents/Resources/app/"
  19. cp assets/trayTemplate.png assets/trayTemplate@2x.png assets/icon.icns \
  20. "$APP/Contents/Resources/app/assets/"
  21. # Build metadata (shown in the About dialog so installs are distinguishable).
  22. node -e "require('fs').writeFileSync('$APP/Contents/Resources/app/build.json', JSON.stringify({version:'$VERSION', builtAt:new Date().toLocaleString('zh-CN',{hour12:false})}, null, 2) + '\n')"
  23. # Bundled dsh plugins (voice suite) — copied from DSWorkSpace, deps hoisted
  24. # flat next to the plugins (same layout the profile already uses), and
  25. # auto-installed into the web profile on first launch (see app/plugins.js).
  26. # Dep sources come from the live profile root (the real files — the
  27. # DSWorkSpace copies are pnpm symlinks).
  28. VOICE_SRC="/Users/eastudio/Documents/DSWorkSpace"
  29. DEP_SRC="$HOME/.dsh/profiles/web/node_modules"
  30. BUNDLED="$APP/Contents/Resources/app/plugins"
  31. mkdir -p "$BUNDLED"
  32. rm -rf "$BUNDLED"/*
  33. cp -R "$VOICE_SRC/dsh-voice-client" "$BUNDLED/dsh-voice-client"
  34. rm -rf "$BUNDLED/dsh-voice-client/node_modules"
  35. cp -R "$VOICE_SRC/dsh-voice-server" "$BUNDLED/dsh-voice-server"
  36. rm -rf "$BUNDLED/dsh-voice-server/node_modules"
  37. cp -R "$VOICE_SRC/dsh-send-mode" "$BUNDLED/dsh-send-mode"
  38. for dep in sherpa-onnx-node sherpa-onnx-darwin-arm64 ws; do
  39. cp -R "$DEP_SRC/$dep" "$BUNDLED/$dep"
  40. done
  41. node -e "require('fs').writeFileSync('$BUNDLED/manifest.json', JSON.stringify({
  42. 'dsh-voice-client': { id: 'dsh-voice-client', name: 'dsh-voice-client' },
  43. 'dsh-voice-server': { id: 'dsh-voice-server', name: 'dsh-voice-server/lib/index-v6.js' },
  44. 'dsh-send-mode': { id: 'dsh-send-mode', name: 'dsh-send-mode' },
  45. 'sherpa-onnx-node': { dep: true },
  46. 'sherpa-onnx-darwin-arm64': { dep: true },
  47. 'ws': { dep: true }
  48. }, null, 2) + '\n')"
  49. echo "bundled plugins: $(du -sh "$BUNDLED" | cut -f1) — $(ls "$BUNDLED" | tr '\n' ' ')"
  50. # Identity + icon.
  51. cp assets/icon.icns "$APP/Contents/Resources/electron.icns"
  52. plutil -replace CFBundleName -string "DoTouchAI" "$PLIST"
  53. plutil -replace CFBundleDisplayName -string "DoTouchAI" "$PLIST"
  54. plutil -replace CFBundleIdentifier -string "local.eastudio.dsh-desktop" "$PLIST"
  55. plutil -replace CFBundleShortVersionString -string "$VERSION" "$PLIST"
  56. # Ad-hoc signature with --deep: re-signs the top-level AND all nested
  57. # frameworks/helpers consistently (the Electron dist ships a linker-signed
  58. # framework whose seal is incomplete; --deep replaces it with a full ad-hoc
  59. # signature so `codesign --verify --deep --strict` passes). No Apple ID needed;
  60. # Gatekeeper still requires a one-time bypass on foreign Macs (see 安装说明.txt).
  61. codesign --deep --force --sign - "$APP" >/dev/null 2>&1 || true
  62. codesign --verify --deep --strict "$APP" 2>/dev/null || echo "warn: signature did not pass --deep --strict (app may be rejected by Gatekeeper on other Macs)"
  63. echo "built: $APP ($(du -sh "$APP" | cut -f1))"
  64. # Drag-install DMG: the app beside a symlink to /Applications, plus a
  65. # first-run readme (Gatekeeper unblock + Node/dsh install steps for new Macs).
  66. STAGING="../.dmg-staging"
  67. rm -rf "$STAGING"
  68. mkdir -p "$STAGING"
  69. cp -R "$APP" "$STAGING/"
  70. ln -sfn /Applications "$STAGING/Applications"
  71. cat > "$STAGING/安装说明.txt" << 'EOF'
  72. DoTouchAI 安装说明
  73. ====================
  74. 一、安装
  75. 把 DoTouchAI 拖到右侧 Applications 文件夹。
  76. 二、首次打开(重要)
  77. 本应用未经 Apple 开发者签名公证,拷到新 Mac 后首次打开会被拦截
  78. (提示"无法打开"或"已损坏"),属正常现象,任选其一解除:
  79. ▸ 方法 A:在应用程序文件夹里右键 DoTouchAI → 打开 → 再点"打开"(只需一次)
  80. ▸ 方法 B(推荐):终端执行,清除全部标记
  81. xattr -cr "/Applications/DoTouchAI.app"
  82. 注:macOS 14.4+/26 会同时打 com.apple.quarantine 和 com.apple.provenance
  83. 两个标记,只去 quarantine 不够("还是打不开"),必须 -cr 全清或分别去两个
  84. ▸ 方法 C:双击被拦后,系统设置 → 隐私与安全性 → 底部点"仍要打开"
  85. 三、新 Mac 需要先装 Node.js 与 dsh(一次性)
  86. 1. 安装 Node.js LTS:https://nodejs.org(或 brew install node)
  87. 2. 终端执行:
  88. npm config set prefix ~/.local
  89. npm install -g @deepseek-ai/dsh
  90. echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.zshrc
  91. 3. 启动 DoTouchAI:若弹出"未检测到 dsh"引导,点"复制安装命令"照做即可;
  92. 语音插件已内置,会自动安装,无需其他操作。
  93. 四、验证
  94. 托盘菜单 → 关于 DoTouchAI:能看到 dsh 版本号即安装成功。
  95. EOF
  96. rm -f "$DMG"
  97. hdiutil create -volname "DoTouchAI $VERSION" -srcfolder "$STAGING" -ov -format UDZO "$DMG" >/dev/null
  98. rm -rf "$STAGING"
  99. hdiutil verify "$DMG" >/dev/null && echo "built: $DMG ($(du -sh "$DMG" | cut -f1))"