name: ai-board-build description: AI Vibe Board 项目构建与编译技能。当用户需要构建、编译、打包项目时使用,包括开发构建、正式发布构建、Tauri 桌面应用构建、前端构建。触发关键词:编译、构建、build、打包、release、debug、tauri build、sidecar、发布。即使用户只说"编译一下"或"打包",也应该使用此技能。
AI Vibe Board 构建技能
本项目是 Rust 后端 + Vue 3 前端 + Tauri 桌面应用的全栈项目。
权限配置(首次使用必须完成)
所有构建和开发命令都不需要 sudo。但 macOS 访问 HID 设备需要系统权限。
配置步骤
- 打开 系统设置 → 隐私与安全性 → 输入监控
- 点击左下角 🔒 解锁
- 添加你的终端应用:
- Terminal.app →
/System/Applications/Utilities/ - iTerm →
/Applications/ - VS Code(如果在内建终端运行)
- Terminal.app →
- 重启终端应用(完全退出再打开)
- 运行
make check-permissions验证
Tauri 桌面应用 需要单独授权:首次启动后,系统会弹窗要求输入监控权限,点击「允许」即可。
不需要 sudo 的前提
| 场景 | 是否需要 sudo | 原因 |
|---|---|---|
make dev / make d |
❌ | Tauri app 走独立 TCC 条目 |
make ss / make s |
❌ | 终端已授权输入监控 |
make sm / make m |
❌ | 同上 |
所有 build-* 命令 |
❌ | 编译不需要设备访问 |
make check/test/lint |
❌ | 纯代码检查 |
如果之前用过 sudo
sudo 创建的文件属于 root,会导致普通用户写入失败。执行清理:
sudo chown -R $(whoami) ~/.reai-vibe-board/
sudo chown -R $(whoami) target/ # 如果 target/ 下有 root 文件
Tauri 桌面应用权限(.app 三项授权)
Tauri 生产 .app 需要三项 macOS 权限才能完整运行。应用会在启动时自动检测并弹窗引导。
| 权限 | 作用 | 自动重启 | Tauri 命令 |
|---|---|---|---|
| 辅助功能 (Accessibility) | 语音转录文本自动插入输入框 | ✅ 授权后自动重启 | open_accessibility_settings |
| 输入监听 (Input Monitoring) | 监听键盘按键(PTT 一键录音) | ✅ macOS 自动杀进程重启 | open_input_monitoring_settings |
| 麦克风 (Microphone) | 录音和语音识别 | ❌ 即时生效 | navigator.mediaDevices.getUserMedia |
权限检测 API
check_accessibility_granted—AXIsProcessTrusted()check_input_monitoring_granted—CGPreflightListenEventAccess()restart_app—open .app && app.exit(0),辅助功能授权后自动触发
文本插入机制
sidecar 无法获得独立 TCC 授权(ad-hoc signing),文本插入路由:
- sidecar 调用
.app/Contents/MacOS/tauri-app insert-text --stdin - tauri-app 在 .app TCC 下执行
AXIsProcessTrusted()→ CGEvent 注入 - 返回 JSON
{"kind":"inserted"}等(serde tag 必须与 sidecarInsertOutcome一致)
构建环境净化(macOS)
强制约束:所有构建入口必须在净化后的环境里跑 cargo / cc / pkg-config,否则用户 shell 里的 MacPorts (/opt/local) / Homebrew (/opt/homebrew, /usr/local) 会把 host 绝对路径写进 Mach-O 的 LC_LOAD_DYLIB,导致 .app 在没装这些工具链的 Mac 上启动期 dyld abort(典型报错:Library not loaded: /opt/local/lib/liblzma.5.dylib)。
scripts/build-env.sh 负责净化,被所有 make target 与 scripts/tauri-release.sh 自动 source。手动跑 cargo build / cargo tauri build 时也必须先:
source scripts/build-env.sh
清掉的变量:
- pkg-config:
PKG_CONFIG_PATHPKG_CONFIG_LIBDIRPKG_CONFIGHOST_PKG_CONFIGTARGET_PKG_CONFIG - 链接器/编译器搜索:
LIBRARY_PATHDYLD_LIBRARY_PATHDYLD_FALLBACK_LIBRARY_PATHDYLD_FRAMEWORK_PATHDYLD_FALLBACK_FRAMEWORK_PATHCPATHC_INCLUDE_PATHCPLUS_INCLUDE_PATH - 编译/链接 flag:
LDFLAGSCFLAGSCPPFLAGSCXXFLAGSBINDGEN_EXTRA_CLANG_ARGS - 工具链覆盖:
CCCXXLDARRANLIB及其HOST_*/TARGET_*变体 - 扫描 cc-rs / pkg-config-rs 的 target-scoped 变量(
CC_aarch64_apple_darwin、PKG_CONFIG_PATH_*等):值若含 host 路径 → 脚本 fail - 检查
RUSTFLAGS/CARGO_ENCODED_RUSTFLAGS/CARGO_TARGET_*_LINKER/CARGO_TARGET_*_RUSTFLAGS是否被污染(含 host 路径 → fail)
PATH 清洗:移除 /opt/local/bin /opt/local/sbin /opt/homebrew/bin /opt/homebrew/sbin /usr/local/bin /usr/local/sbin;prepend ~/.cargo/bin ~/.bun/bin ~/.local/bin(按存在性);兜底 /usr/bin:/bin:/usr/sbin:/sbin。
固定 MACOSX_DEPLOYMENT_TARGET=10.15(对齐 tauri.conf.json minimumSystemVersion),SDKROOT 走 xcrun --sdk macosx --show-sdk-path。
构建后 gate:scripts/verify-no-host-paths.sh "$APP" 扫描 .app 内所有 Mach-O 的 LC_LOAD_DYLIB/LC_LOAD_WEAK_DYLIB/LC_REEXPORT_DYLIB/LC_LOAD_UPWARD_DYLIB/LC_ID_DYLIB/LC_RPATH,任一指向 host 路径直接 fail。make tauri-build / make tauri-build-sherpa / make local-build / make release 末尾自动跑。
dry-run(不触发真实 Apple 公证上传,省掉等待与失败重来):
make tauri-build # 自动 source build-env + env -u 屏蔽 APPLE_* + 末尾 verify
make verify-self-test # 验证 verify 脚本本身的逻辑
make verify-app # 校验当前已构建的 .app
真正发布(全托管,推荐):
./scripts/release.sh # 默认 patch +1(= make publish)
./scripts/release.sh minor # 或 minor / major / 显式 0.20.0
./scripts/release.sh --dry-run # 只算版本 + 预览路径修正 + tag 碰撞检查,不构建不公证不写文件
release.sh 是发布编排器:版本 bump + 三处对齐(Cargo.toml / tauri.conf.json / web/package.json,取最大值为基准)→ 路径自愈(cd ../web→cd web)→ tag 碰撞守卫 → 调底层引擎 → 失败回滚版本文件 → 成功后打印好 git commit + git tag v$NEW 命令,停在 commit 前等你确认(不自动 commit、不自动 push)。
底层引擎 ./scripts/tauri-release.sh(= make release)只管「构建→签名→公证→验证」、不做版本 bump;想跳过版本管理直接重建当前版本时才单独用它。引擎顺序:.env.local 加载 + APPLE_* 校验 + export → source build-env.sh → 路径自愈预检 → cargo tauri build(内部完成签名 + .app 公证 + staple)→ verify-no-host-paths(gate)→ codesign --verify → stapler validate .app → DMG 硬 gate(缺失即 fail) → notarytool submit .dmg + staple + stapler validate .dmg → spctl -a Gatekeeper 硬 gate。任一 gate 失败即非零退出——所以 exit 0 可信。
gate 边界:因 cargo tauri build 把 .app 公证封装在构建里,后续 gate 失败只能阻止 DMG 公证,已经发生的那次 .app 公证上传无法撤回(公证免费、不按次扣费,但那次上传 + Apple 排队几分钟的时间收不回来)。所以日常先用 make tauri-build / ./scripts/release.sh --dry-run 预检,再走真发布。
快速决策
根据需求选择构建路径:
| 场景 | 命令 | 产出 |
|---|---|---|
| 开发调试(推荐) | make dev / make d |
Tauri 桌面窗口 + 热更新 |
| 后端 + 前端(Web 模式) | make ss |
HTTP 服务 :8067 + STT |
| 只编译后端 | make build-sherpa |
target/release/ai-vibe-board |
| 对外发布(全托管,推荐) | ./scripts/release.sh / make publish |
版本 bump + 签名 + 公证 + gate,停在 commit 前 |
| 重建当前版本(裸引擎,不 bump) | make release |
签名 + 公证当前版本,不改版本号 |
| 仅本机构建(不公证) | make tauri-build |
dry-run:屏蔽 Apple 凭据、不 notarize |
| 只编译前端 | cd web && bun run build |
web/dist/ |
构建命令
开发模式(日常推荐)
make dev # 或 make d(Tauri 桌面应用,自动编译后端 + 前端热更新)
完整流程:
build-all-stt→ 编译 Rust 后端(含全部 STT)- 复制 sidecar 到
src-tauri/binaries/ - 启动 Tauri 开发模式(前端热更新 + 后端自动管理)
Web 模式(不用 Tauri 桌面窗口):
make ss # 编译 + 启动 HTTP 服务(含 sherpa STT)
# 另开终端:cd web && bun run dev # 前端热更新 :1420
正式发布
对外发布首选 ./scripts/release.sh(或 make publish)——它自带版本 bump(默认 patch)+ 三处对齐,无需手动改版本号(见下方「版本管理」章节)。根据分发目标选择命令:
| 场景 | 命令 | 说明 |
|---|---|---|
| 对外分发(全托管,推荐) | ./scripts/release.sh / make publish |
版本 bump+对齐 → 路径自愈 → 签名 + Notarization + Stapling + gate → 停在 commit 前 |
| 重建当前版本(裸引擎,不 bump) | ./scripts/tauri-release.sh / make release |
不改版本号,直接签名 + 公证 + 装订当前版本,见下方「Apple 签名与公证」章节 |
| 仅编译 .app/.dmg,不公证 | make tauri-build |
直接走 cargo tauri build。有 Developer ID 证书会真签名,但不做 notarization,发到别人电脑会被 Gatekeeper 拦 |
如果只发布后端二进制(不用 Tauri 桌面壳):
make build-all-stt # 产出 target/release/ai-vibe-board
✅ 发布前自检(现已自动化,了解即可)
历史上这两个坑反复踩,每次白等一轮 Apple 公证(上传 + 排队几分钟,失败还得重来)。现在 release.sh 与引擎已自动处理这两条,不再需要手动 grep;保留说明便于理解:
beforeBuildCommand路径方向(已自动纠正)grep '"beforeBuildCommand"' src-tauri/tauri.conf.json正确:
"beforeBuildCommand": "cd web && bun run build"错误:"beforeBuildCommand": "cd ../web && bun run build"release.sh(版本快照前)与引擎构建前预检都会探测到cd ../web并就地改回cd web,即便复发也会被自动修掉(属持久根因修复,不被失败回滚抹掉)。真正的机制(tauri-cli
app_paths.rs,2026-06 源码确认):hook 的 CWD 是 tauri-cli 解析出的「前端目录」——若从src-tauri/启动构建(本仓库 Makefile / release.sh / CI 均如此),其下找不到package.json,确定性回退到src-tauri/的父目录即仓库根,故cd web正确。若从仓库根启动,tauri-cli 会全仓库搜第一个package.json来猜前端目录(backup/firmware/landing 都有),命中哪个看运气——历史上 CI 从仓库根启动时碰巧猜中 landing 级目录,cd ../web侥幸可用,于是出现「CI 要../web、本地要web」的假性平台冲突,这行被来回改了 6+ 次。结论:永远从src-tauri/启动构建,永远写cd web(本地与 CI 的 tauri-cli 均锁定 2.11.1;升级 tauri-cli 时需先复核该解析机制是否变化)。详见下方「常见问题」同名条目。退出码现已可信(旧 bug 已修)
引擎曾有「
beforeBuildCommand失败 / DMG 缺失 / spctl 失败仍exit 0」的假成功 bug。现已补齐硬 gate(DMG 必须存在 +stapler validate+spctl任一失败即非零退出),exit 0 ⇒ 产物已验真,不再需要手动跑外部 gate 二次确认。release.sh走到成功收尾即代表这些 gate 全过。
双平台发布同步(macOS + Windows)
macOS 与 Windows 安装包由不同流程发布(Mac 本地公证、Windows GitHub Actions),通过 GitHub Releases 作为唯一真相源统一版本号、把两平台产物归集到同一个 vX.Y.Z Release。
- macOS:发布并
git push origin vX.Y.Z后,跑make release-upload(=scripts/upload-release.sh)把已公证的.dmg同步到对应 Release(幂等:没有就建、有就追加)。 - Windows:push tag 自动触发
.github/workflows/build.yml,NSIS 安装包发到同一 Release(对已存在的 tag 可用 workflow_dispatch 补发)。 - 谁先发布谁定版本号、建 Release,另一端复用同一 tag;
release.sh的 tag 碰撞守卫防两边撞号。
完整约定(角色分工、谁先到谁建、补发流程、典型时序)见 docs/release-coordination.md。
Apple Developer ID 签名与公证
对外分发必须完整走完签名 + 公证 + 装订三步,否则 Gatekeeper 会阻止运行。
关于「公证成本」的正确认知:公证(Notarization)本身免费、不按次收费——它包含在 Apple Developer Program 年费里,没有「用完即锁」的次数额度池,也不是按 App 分配。真实开销只有两类:①每次都要把包上传给 Apple、排队等自动扫描(DMG 通常 3-5 分钟),失败得从头重来;②账号级有速率限流(社区经验值约 75 次/天/团队,非 Apple 正式承诺的硬配额),但单人手动发版基本撞不到。所以本文反复强调先 dry-run 预检——省的是时间和重试成本,不是会被扣空的配额。
凭据角色分工
| 凭据 | 存放位置 | 作用 | 每次发布需要 |
|---|---|---|---|
| Developer ID Application 证书 + 私钥 | macOS 钥匙串「登录」 | 代码签名(.app / sidecar / dylib) | ✅ 但钥匙串自动提供,无需输密码 |
APPLE_ID + APPLE_PASSWORD (app-specific) + APPLE_TEAM_ID |
.env.local(gitignore) |
Notarization 上传凭证 | ✅ 每次都要,因为要登录 Apple 服务器 |
首次配置(一次性)
申请 Developer ID Application 证书
- developer.apple.com → Certificates → Developer ID Application → 上传本机钥匙串生成的 CSR → 下载 .cer →
security import或双击 - 验证:
security find-identity -v -p codesigning | grep "Developer ID Application"
- developer.apple.com → Certificates → Developer ID Application → 上传本机钥匙串生成的 CSR → 下载 .cer →
生成 App-Specific Password(用于 notarytool)
- account.apple.com → Sign-In and Security → App-Specific Passwords → Generate → 标签如
tauri-notarize - 密码只显示一次,立即存入密码管理器
- account.apple.com → Sign-In and Security → App-Specific Passwords → Generate → 标签如
备份私钥为
.p12(防丢)- 钥匙串访问 → 我的证书 → 展开箭头同时选中证书+私钥 → 右键「导出 2 项」→
.p12+ 强密码 - 异地保存(U 盘 / 加密云盘),密码单独存密码管理器
- 钥匙串访问 → 我的证书 → 展开箭头同时选中证书+私钥 → 右键「导出 2 项」→
配置
tauri.conf.json"macOS": { "signingIdentity": "Developer ID Application: YOUR NAME (TEAMID)", "providerShortName": "TEAMID" }配置
Entitlements.plistHardened Runtime 必需键<key>com.apple.security.cs.disable-library-validation</key><true/> <key>com.apple.security.cs.allow-jit</key><true/> <key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>(whisper-rs/sherpa-onnx 等未签名 dylib 必需,否则运行时 dyld 拒绝)
创建
.env.local(已在 .gitignore)export APPLE_SIGNING_IDENTITY="Developer ID Application: YOUR NAME (TEAMID)" export APPLE_ID="your-apple-id@example.com" export APPLE_PASSWORD="xxxx-xxxx-xxxx-xxxx" export APPLE_TEAM_ID="TEAMID"chmod 600 .env.local
日常发布(配置完成后)
./scripts/release.sh # 全托管:版本 bump + 引擎 + gate(推荐)
./scripts/tauri-release.sh # 仅底层引擎:不 bump,重建当前版本
底层引擎 tauri-release.sh 自动完成:
- 加载
.env.local+ 校验环境变量 - 确保 PATH 含
~/.cargo/bin(避免旧 Rust 读不懂 Cargo.lock v4) cargo tauri build→ 编译 + 签名 .app / sidecar / dylib → tauri-cli 自带:提交 .app 公证 + staple- 轻量校验 .app 已被 stapled(信任 tauri-cli 的产物,不重复提交)
- 单独提交 .dmg 公证 + staple(Tauri v2 默认不做 DMG 公证,脚本补上)
spctl -a最终 Gatekeeper 验证
关键事实:当
tauri.conf.json的macOS.signingIdentity已配置、且环境里有APPLE_ID + APPLE_PASSWORD + APPLE_TEAM_ID时,Tauri v2 的cargo tauri build会自动完成 .app 公证 + staple(log 里会看到Notarizing ... Accepted+Stapling app...);本仓库的tauri-release.sh在 build 前会校验这三个变量,所以这条路径成立。脚本若再用notarytool submit "$APP_PATH"提交一次 .app,会被拒绝(notarytool 只收 zip/pkg/dmg,不收裸 .app),所以脚本职责只剩"补 DMG 公证"。反过来,如果只配 signingIdentity 但没配 Apple 凭据(比如直接跑
make tauri-build),tauri-cli 会打印skipping app notarization后继续构建,产出的 .app 只签名不公证——发到别人电脑会被 Gatekeeper 拦。需要对外分发就用./scripts/release.sh(全托管,推荐)或./scripts/tauri-release.sh(裸引擎,不 bump)。
产物:src-tauri/target/release/bundle/dmg/ReAI Vibe Board_X.Y.Z_aarch64.dmg
公证验证命令
# 检查产物是否含 host 路径(MacPorts / Homebrew / 开发机用户目录)
scripts/verify-no-host-paths.sh "path/to/AI Vibe Board.app"
# 检查签名
codesign --verify --deep --strict --verbose=2 "path/to/AI Vibe Board.app"
# 检查公证票据是否装订
xcrun stapler validate "path/to/AI Vibe Board.app"
xcrun stapler validate "path/to/*.dmg"
# Gatekeeper 最终放行检查(应显示 Notarized Developer ID / accepted)
spctl -a -vvv -t install "path/to/*.dmg"
版本管理(由 release.sh 自动处理)
./scripts/release.sh / make publish 自动完成版本 bump + 三处对齐,无需手动改版本号。
升级规则
| 类型 | 命令 | 示例 |
|---|---|---|
| patch(默认) | ./scripts/release.sh 或 release.sh patch |
0.9.0 → 0.9.1 |
| minor | ./scripts/release.sh minor |
0.9.1 → 0.10.0 |
| major | ./scripts/release.sh major |
0.10.0 → 1.0.0 |
| 显式 | ./scripts/release.sh 0.20.0 |
→ 0.20.0(必须 > 当前基准) |
release.sh 自动维护的版本文件(三处对齐)
| 文件 | 格式 |
|---|---|
src-tauri/Cargo.toml |
version = "X.Y.Z" |
src-tauri/tauri.conf.json |
"version": "X.Y.Z" |
web/package.json |
"version": "X.Y.Z" |
三处事先不一致时(本仓库出现过 web/package.json 落后),
release.sh自动取最大值为基准再递增并打印告警。Cargo.lock随cargo tauri build自动刷新本 crate 版本。
全托管发布流程(release.sh 内部,了解即可)
- 解析 bump 档(默认 patch);多个版本参数会报错(防歧义)。
- 读三处版本、校验
X.Y.Z格式、用自写 semver 比较取最大值为基准;按档算新版本。 - tag 碰撞守卫:本地 + 远端(尽力,网络不可达只告警)
v$NEW已存在则报错退出。 --dry-run在此之后、任何写动作之前分支退出(只预览版本/路径/碰撞)。- 前置脏检查 4 个版本文件(Cargo.toml / tauri.conf.json / web/package.json / Cargo.lock)无未提交改动,否则报错退出(避免回滚误伤)。
- 规范化
beforeBuildCommand路径(持久根因修复,在版本快照前做,不被回滚)。 - 备份 4 文件 → 写入新版本到三处 → 写后校验确实写成
$NEW。 - 调引擎
scripts/tauri-release.sh(构建+签名+公证+硬 gate)。引擎失败 → 自动从备份回滚 4 文件。 - 成功 → 打印
git add … && git commit && git tag v$NEW命令,停在 commit 前等用户确认(不自动 commit、不自动 push)。
退出码可信:引擎硬 gate 保证 exit 0 ⇒ 产物已验真。commit + tag 需用户确认后手动执行;push 始终手动。dry-run 预检:
./scripts/release.sh --dry-run。
快速验证(不生成二进制)
make check # cargo check --all-targets
make clippy # 静态分析
make lint # fmt-check + clippy
make test # 运行测试
Cargo Features(STT 后端)
| Feature | 引擎 | 编译时间 | 适用场景 |
|---|---|---|---|
| (default) | 无 | ~2min | 基础版 |
sherpa |
Sherpa-ONNX | ~5min | 日常开发推荐 |
whisper |
whisper-rs | ~5min | macOS Metal GPU |
sherpa,whisper |
全部 | ~8min | 正式发布 |
快捷命令速查
| 快捷命令 | 等同于 | 说明 |
|---|---|---|
make d |
make dev |
Tauri 开发模式 |
make ss |
make serve |
启动 HTTP 服务(sherpa STT) |
make sm |
make monitor |
启动 HID 监控 |
make b |
make build |
构建后端 |
make s |
make serve |
同 ss |
make m |
make monitor |
同 sm |
make sss |
make sudo-serve |
强制 sudo(一般不需要) |
make ssm |
make sudo-monitor |
强制 sudo(一般不需要) |
make publish |
./scripts/release.sh |
全托管发布:版本 bump + 签名 + 公证 + gate(推荐) |
make release |
./scripts/tauri-release.sh |
裸引擎发布:不 bump,签名 + 公证当前版本 |
常见问题
端口 8067 被占用
lsof -nP -iTCP:8067 -sTCP:LISTEN # 找到占用进程
kill -9 <PID>
编译太慢
- 开发时用
make build-sherpa而非build-all-stt - 用
make check只做类型检查
Sidecar 未更新
后端代码修改后需要重新构建 sidecar:
make b && make sidecar # 或直接 make dev(会自动重建)
~/.reai-vibe-board/ 下文件权限错误
如果之前用过 sudo 启动,文件会属于 root:
sudo chown -R $(whoami) ~/.reai-vibe-board/
Cargo.lock version 4 无法读取
本机 Rust 太旧(如 MacPorts 的 1.71),而 lock 文件由新 Rust 生成。修复:
# 确认已装 rustup + stable
rustup show
# 把 ~/.cargo/bin 放到 PATH 最前(~/.zshrc)
export PATH="$HOME/.cargo/bin:$PATH"
scripts/tauri-release.sh 已自动处理,但 make 直跑不会。
cargo tauri 找不到(no such command: tauri)
发布脚本需要 cargo-tauri binary。装:
# 必须用 rustup 的 cargo,不能用 MacPorts 的旧 cargo
PATH="$HOME/.cargo/bin:$PATH" cargo install tauri-cli --version "^2.0" --locked
装好后会生成 ~/.cargo/bin/cargo-tauri(约 30 MB)。一次性安装,后续不用重装。
如果直接 cargo install tauri-cli 失败、报 serde_spanned ... requires rustc 1.76+,说明 PATH 里 MacPorts cargo 在前。两个修法选一:临时 PATH="$HOME/.cargo/bin:$PATH" cargo install ...,或在 ~/.zshrc 把 ~/.cargo/bin 永久放到 PATH 最前。
beforeBuildCommand 失败:cd: ../web: No such file or directory(反复踩坑)
这个坑本仓库踩过至少两次(commit
c89a92a改对过一次,ee8359f又改反了一次)。历史问题说明——release.sh与引擎现已在构建前自动纠正这个路径(见上方「正式发布 → 发布前自检」第 1 条);若需手动排查可参考该节。
V2 重构后 src-tauri/tauri.conf.json 里如果写成 "beforeBuildCommand": "cd ../web && bun run build",发布构建一开局就会失败。
机制:Tauri v2 执行 beforeBuildCommand / beforeDevCommand hook 时把 CWD 设为 dirs.frontend。本仓库没有显式配 TAURI_FRONTEND_PATH,所以 dirs.frontend fallback 到仓库根(src-tauri/ 的父目录),而不是 src-tauri/ 本身。从仓库根看 ../web 指向仓库外,不存在。
正确写法:
"beforeDevCommand": "cd web && bun run dev",
"beforeBuildCommand": "cd web && bun run build",
判断方向的方法(不要凭直觉,凭事实):
- 出现
sh: line 0: cd: ../web: No such file or directory→ hook CWD 不是src-tauri/,是项目根 → 用cd web - 出现
sh: line 0: cd: web: No such file or directory→ hook CWD 是src-tauri/(罕见,需配了frontendDir)→ 用cd ../web - 见到 commit message 写"路径修复"不要假定方向正确,本仓库历史上两个方向都"修复"过,以本机 grep 当前值 + 实际报错为准
将来如果给 Tauri 显式配了 frontendDir 或 TAURI_FRONTEND_PATH,hook CWD 会变成那个目录,路径写法需要再调整——届时同样按"出错信息 + 实测"判定,不靠记忆。
脚本第三次重跑卡在 .app 公证那步
症状:log 里能看到 tauri-cli 自己已经 Notarizing ... Accepted + Stapling app...,但脚本后续又调用 xcrun notarytool submit "$APP_PATH" 时报:
Error: ReAI Vibe Board.app must be a zip archive (.zip), flat installer package (.pkg), or UDIF disk image (.dmg)
原因:Tauri v2 的 cargo tauri build 已经在内部完成了 .app 公证 + staple(zip 打包 → notarytool → staple 一条龙),所以脚本不能再次 notarytool submit 裸 .app。现在的 scripts/tauri-release.sh 已经改成只验证 xcrun stapler validate "$APP_PATH",不重复提交;如果在更早的脚本版本上跑遇到,直接更新脚本即可。
切分支后前端 TypeScript 找不到模块
如 Cannot find module '@tauri-apps/plugin-dialog'。说明 web/node_modules 和当前分支 web/bun.lock 不同步:
cd web && bun install
.app 在别人电脑启动崩溃,提示 liblzma.5.dylib not found(或类似 /opt/local/lib/...)
症状:开发机能跑,分发到没装 MacPorts/Homebrew 的 Mac 上启动期直接 abort,Crash Report 里:
Library not loaded: /opt/local/lib/liblzma.5.dylib
Reason: tried: '/opt/local/lib/liblzma.5.dylib' (no such file), ...
原因:构建时链接器命中开发机的 MacPorts / Homebrew 路径,把 host 绝对路径写进了 Mach-O 的 LC_LOAD_DYLIB。
修复:
- 用本仓库的
scripts/build-env.sh净化环境再构建(make/tauri-release.sh已自动 source) - 构建后跑
scripts/verify-no-host-paths.sh "$APP"确认无污染 - 任一 gate fail 时检查 shell 配置(
~/.zshrc/direnv/launchd plist),把所有/opt/local、/opt/homebrew、/usr/local相关的PKG_CONFIG_PATHLIBRARY_PATHLDFLAGSCFLAGSCCCXX等清掉
历史脏包:/Applications/ReAI Vibe Board.app 用 scripts/verify-no-host-paths.sh 跑一遍,如果输出违规列表,就是这个问题;删掉重装新版本。
Notarization 卡住几分钟
正常现象。xcrun notarytool ... --wait 会轮询 Apple 服务器,DMG 通常 3-5 分钟,.app 更快。看到 status: Accepted 就成功。切勿 Ctrl+C,否则要重新提交。
失败(status: Invalid)时看日志:
xcrun notarytool log <submission-id> \
--apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID"
常见原因:Entitlements 缺 Hardened Runtime 键、dylib 未签名、包含未 staple 的旧产物。
权限弹窗不显示
macOS 的辅助功能和输入监听授权弹窗在 Tauri 中可能不显示。应用已内置:
- 启动时自动检测权限状态,未授权则弹出引导弹窗
- 点击「去授权」打开系统设置并显示分步操作指引
- 辅助功能授权后 2 秒轮询检测,通过后自动重启
构建前检查清单
正式发布前运行:
make lint # 格式 + Clippy
make test # 测试
Release Profile
[profile.release]
opt-level = 3 # 最大优化
lto = true # 链接时优化
strip = true # 去除调试符号