From e19f840fdc8c71d0e7e5929c699309ac800caade Mon Sep 17 00:00:00 2001 From: yr <194215355+Ychris12138@noreply.github.com> Date: Sat, 12 Sep 2026 11:18:42 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20macOS=20support=20(v0.1.0)=20?= =?UTF-8?q?=E2=80=94=20platform=20split,=20opener,=20PATH=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Platform adaptation only; no new agent features (docs/release.md §3/§7): - tauri config split per platform (auto-merged at build): common (window/CSP/icons incl. icon.icns), tauri.windows.conf.json (NSIS + WebView2Loader + install notes), tauri.macos.conf.json (app+dmg, minimumSystemVersion 12.0) - openInFileManager now uses plugin-opener openPath (cross-platform); hardcoded explorer spawn removed — verified at runtime on Windows (opener opens Explorer at the data dir) - macOS PATH fix (P0): fix_path_env::fix() at startup — Finder/Dock launches do not inherit shell PATH; acceptance is git/claude/codex usable from the in-app terminal after Finder launch - shell capabilities split by platform: shell-macos.json carries only git/claude/codex/npm/node; cmd/powershell/explorer are Windows-only (explorer dropped entirely) — first pass of the allowlist tightening tracked in Roadmap issue #2 - make-release.mjs is platform-aware: win32 collects the NSIS exe, darwin collects the newest DMG (universal preferred), README/verify steps adapt (Get-FileHash vs shasum, SmartScreen vs Gatekeeper) - main.tsx boot-failure text now built via textContent (renderer security red line), not innerHTML - docs: release.md gains macOS build env, universal DMG flow, two-phase signing policy, mac test matrix in the release gate; README states Windows + macOS targets; seed manual gains a macOS appendix Windows regression: 63 vitest green, tsc+vite build, NSIS rebuilds from split config, dev + release smoke verified (CSP intact, opener click opens Explorer at data dir), release/ regenerated. --- AGENTS.md | 3 +- README.md | 4 +- docs/release.md | 58 ++++++++++-- docs/seed-manual.md | 8 ++ package-lock.json | 10 ++ package.json | 1 + scripts/make-release.mjs | 107 ++++++++++++++++------ src-tauri/Cargo.lock | 61 ++++++++++++ src-tauri/Cargo.toml | 3 + src-tauri/capabilities/common.json | 39 ++++++++ src-tauri/capabilities/default.json | 65 ------------- src-tauri/capabilities/shell-macos.json | 31 +++++++ src-tauri/capabilities/shell-windows.json | 35 +++++++ src-tauri/src/lib.rs | 9 ++ src-tauri/tauri.conf.json | 10 +- src-tauri/tauri.macos.conf.json | 9 ++ src-tauri/tauri.windows.conf.json | 14 +++ src/main.tsx | 21 +++-- src/storage/git.ts | 6 +- 19 files changed, 372 insertions(+), 122 deletions(-) create mode 100644 src-tauri/capabilities/common.json delete mode 100644 src-tauri/capabilities/default.json create mode 100644 src-tauri/capabilities/shell-macos.json create mode 100644 src-tauri/capabilities/shell-windows.json create mode 100644 src-tauri/tauri.macos.conf.json create mode 100644 src-tauri/tauri.windows.conf.json diff --git a/AGENTS.md b/AGENTS.md index a29b349..0c9da5c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,8 @@ ResearchThread 是一个**本地优先**的桌面工具(Tauri 2,macOS + Wind - **周报正确性**:任务定位主键改完整相对路径(修跨项目同名任务归错),活跃工作线 ☆ 置顶稳定排序 - **watcher 路径归一化**:Windows 反斜杠事件先归一再剥数据目录前缀(此前 `.git` / `.activity` 过滤失配、活动账记绝对路径);`.rt-bak` 一并过滤 - **发布物料**:关于页读真实版本(`getVersion()`)标「种子测试版」;[docs/seed-manual.md](docs/seed-manual.md) 种子手册——隐私边界(**明确撤回「打包数据目录反馈」**,改为脱敏反馈模板)、SmartScreen 安装步骤、卸载数据保留说明、外部编辑并发规则 - - **发布包与安装提示**:`npm run make-release`([scripts/make-release.mjs](scripts/make-release.mjs))把 NSIS 安装包、SHA256SUMS、README、种子手册归拢到 `release/`(gitignore,脚本可重复生成);安装器中文化(`bundle.windows.nsis.languages` 简中+英文+语言选择器)并以 `bundle.license`(`src-tauri/INSTALL-NOTES.txt`)在安装前展示中文安装须知;应用首启弹欢迎卡(数据位置/快照警告含义/脱敏反馈,`settings.introSeen` 持久化开关) + - **发布包与安装提示**:`npm run make-release`([scripts/make-release.mjs](scripts/make-release.mjs))平台感知(win 收 NSIS exe / darwin 收 DMG),归拢安装包、SHA256SUMS、README、种子手册到 `release/`(gitignore,脚本可重复生成);安装器中文化(`bundle.windows.nsis.languages` 简中+英文+语言选择器)并以 `bundle.license`(`src-tauri/INSTALL-NOTES.txt`)在安装前展示中文安装须知;应用首启弹欢迎卡(数据位置/快照警告含义/脱敏反馈,`settings.introSeen` 持久化开关) +- **macOS 适配(v0.1.0,PR feat/v0.1-macos-support)**:Tauri 配置拆三文件(`tauri.conf.json` 公共 / `tauri.windows.conf.json` NSIS+WebView2Loader / `tauri.macos.conf.json` app+dmg+minimumSystemVersion 12.0,构建时自动合并);文件管理器改 `plugin-opener`(`openPath`,替代硬编码 explorer);**Finder/Dock 启动 PATH 修复**(`fix-path-env` crate,lib.rs setup 里 `fix()`,mac 验收 = Finder 双击启动后终端 git/claude/codex 可用);shell capability 拆平台(`shell-windows.json` / `shell-macos.json`——mac 只留 git/claude/codex/npm/node,explorer/cmd/powershell 不进 mac 白名单);universal 构建命令与 mac 测试矩阵见 docs/release.md §0/§3/§4-6。**签名两阶段**:内测不签名(右键→打开过 Gatekeeper),面向陌生用户前必须 Developer ID + 公证 - 下一步:干净 Windows 账户实测(首装 / 无 git / 无 CLI / 双开 / 升级 / 卸载重装 / 快照恢复演练)→ 全部过门后打 release tag 发 **Windows x64 受控种子版**;随后回到 **第二步阶梯 2a**(AgentRunner CLI 后端,见下条与 PART2.md) - 第二步技术报告见 [PART2.md](PART2.md):CLI 后端选型(claude 首选/codex 坑清单)、MCP 工具面与 will_write 归属协议、2c 解析器选型、风险登记册与排期——**实施 2a–2d 前必读** - **Windows 构建环境(本机)**:Rust stable-msvc 装了但**缺 Windows SDK**(提权安装 SDK 未获批准);当前用 `stable-x86_64-pc-windows-gnu` + `rust-lld` + Strawberry Perl 的 dlltool 编译通过。**启动命令**: diff --git a/README.md b/README.md index 72d9027..649ca49 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ResearchThread -本地优先的科研/工作流程管理桌面应用(Tauri 2)。当前种子构建:**Windows x64**;macOS 为计划目标、尚未构建验证。把手写「思路整理」工作流数字化:**空间 → 项目 → 任务**三级层级 + 双层上下文(项目整体上下文 / 任务上下文)+ 周文档。 +本地优先的科研/工作流程管理桌面应用(Tauri 2)。发布目标:**Windows x64 + macOS 12+(universal:Apple Silicon + Intel,DMG)**——macOS 适配代码已入库,DMG 需在 macOS 机构建(流程见 [docs/release.md](docs/release.md) §3)。把手写「思路整理」工作流数字化:**空间 → 项目 → 任务**三级层级 + 双层上下文(项目整体上下文 / 任务上下文)+ 周文档。 第二步(已规划、未开始):**人只说自然语言,agent 维护整个项目地图**——口述新任务/新项目/任务转折即可,agent 经 MCP 工具接口执行并定时产出日报/周报。 @@ -22,7 +22,7 @@ ## 技术栈 - React 19 + Vite + Tailwind v4(SPA;按 [UI.md](UI.md) 契约集成外部交付的 UI,剥离其 SSR/登录/数据库) -- Tauri 2 桌面壳(plugin-fs / plugin-shell / single-instance / global-shortcut;生产线启用 CSP,见 [docs/release.md](docs/release.md) §5) +- Tauri 2 桌面壳(plugin-fs / plugin-shell / plugin-opener / single-instance / global-shortcut;生产线启用 CSP,见 [docs/release.md](docs/release.md) §5) - 数据访问统一走 `src/dataAccess.ts` 接口([UI.md](UI.md) 第 2 节契约):浏览器 dev → MockDataAccess;Tauri 桌面 → StorageDataAccess(Markdown 文件树落盘) - 数据层 `src/storage/`:纯核心 `core.ts`(无 Tauri API)+ IO 适配器 `fsAdapter.ts`(plugin-fs)+ 事务式写入 `atomicWrite.ts` + 每日快照健康 `snapshot.ts`;同一核心配 `node:fs` 即成 2b 阶段 MCP server(`tests/storageNode.test.ts` 的适配器即雏形) - Vitest 守护写回契约与数据安全(宽松读/规范写/保留未知字段/外部冲突防护/软删除/中断恢复);CI 见 `.github/workflows/ci.yml` diff --git a/docs/release.md b/docs/release.md index 056c0ea..432349a 100644 --- a/docs/release.md +++ b/docs/release.md @@ -6,26 +6,37 @@ ## 0. 一句话流程 ```bash +# —— Windows 构建机(本机 GNU 工具链)—— export PATH="/d/perl/c/x86_64-w64-mingw32/bin:/d/perl/c/bin:$HOME/.cargo/bin:$PATH" export RUSTUP_TOOLCHAIN=stable-x86_64-pc-windows-gnu npx vitest run # ① 测试全绿 npm run build # ② tsc + vite 构建通过 npm run tauri build # ③ NSIS 安装包(同上环境) npm run make-release # ④ 归拢 release/(含门槛自检) + +# —— macOS 构建机(Apple Silicon 亦出 universal)—— +npx vitest run +npm run build +npm run tauri build -- --target universal-apple-darwin --bundles app,dmg # ③ DMG +npm run make-release # ④ 同一脚本,darwin 下收 DMG ``` +两台机器各自生成 `release/`,发布前把 Windows 的 exe 与 macOS 的 dmg **汇总到同一份发布物**(SHA256SUMS 合并核对)。 + ## 1. 位置与产物(不变量) - `release/` 在仓库根、**gitignore**,内容只能由 [scripts/make-release.mjs](../scripts/make-release.mjs) 生成;脚本是入库的,产物不入库。 -- 产物固定四件套,缺一不可: +- 产物固定集合,缺一不可: | 文件 | 内容 | 源头 | |---|---|---| - | `ResearchThread__x64-setup.exe` | NSIS 安装包 | `npm run tauri build` | - | `SHA256SUMS.txt` | 安装包与手册的 SHA-256 | 脚本计算 | + | `ResearchThread__x64-setup.exe` | Windows NSIS 安装包 | Windows 机 `npm run tauri build` | + | `ResearchThread__universal.dmg` | macOS 安装映像(Apple Silicon + Intel) | macOS 机 `tauri build --target universal-apple-darwin` | + | `SHA256SUMS.txt` | 安装包与手册的 SHA-256 | 脚本计算(双平台合并后核对齐全) | | `README.md` | 下载/校验/安装说明 + 构建提交记录 | 脚本模板 | | `种子测试手册.md` | 种子用户手册副本 | `docs/seed-manual.md`(只改源头,不改副本) | - 每次生成**整体覆盖**:`release/` 只保留最新一个版本,不堆历史;历史版本靠 git tag 重新构建复现,发布后的安装包应转存到发布渠道/网盘归档。 - 手工改 `release/` 里任何文件 = 无效发布,下次生成会被覆盖。 +- Tauri 配置按平台拆分(构建时自动合并):`tauri.conf.json`(公共:窗口/CSP/图标)+ `tauri.windows.conf.json`(NSIS/WebView2Loader/须知页)+ `tauri.macos.conf.json`(app+dmg/最低 macOS 12)。**不要把平台特有配置写回公共文件。** ## 2. 版本规则 @@ -33,14 +44,25 @@ npm run make-release # ④ 归拢 release/(含门槛自检) - 发版必须先在 git 打 tag `vX.Y.Z`,tag 指向的提交 = 构建提交;从脏工作区构建会被脚本警告。 - 0.x 为种子期:功能新增升 minor、修复升 patch;数据格式破坏性变更(见 §5)即便 0.x 也需用户确认。 -## 3. 打包环境(本机现状) +## 3. 打包环境 + +### Windows(本机现状) - 机器无 Windows SDK,用 GNU 工具链:`stable-x86_64-pc-windows-gnu` + rust-lld(`src-tauri/.cargo/config.toml` 固定),启动命令见 §0。 - `src-tauri/WebView2Loader.dll` **勿删**:GNU 构建动态链接它,已用 `bundle.resources` 打进安装器;升级 `webview2-com-sys` 后必须同步替换该 dll 并重测安装包启动。 -- 改过 `src-tauri/capabilities/*.json` 后需 `touch src-tauri/build.rs` 强制重嵌(dev watch 不监听 capabilities)。 -- 发布前 `package-lock.json` 与 `src-tauri/Cargo.lock` 必须已提交(可复现构建的前提)。 - 安装器形态固定:**per-user NSIS、免管理员、未签名**;SmartScreen 警告由安装须知页(`src-tauri/INSTALL-NOTES.txt`)解释,**不得**要求用户关闭 SmartScreen。 +### macOS(v0.1.0 起) + +- 前置只需 Xcode Command Line Tools(`xcode-select --install`)+ rustup + node;第一轮先 native ARM(`npm run tauri dev`),通过后再 universal。 +- universal 二进制:`rustup target add aarch64-apple-darwin x86_64-apple-darwin` 后 `npm run tauri build -- --target universal-apple-darwin --bundles app,dmg`;产物在 `src-tauri/target/universal-apple-darwin/release/bundle/dmg/`。 +- **PATH 修复是硬依赖**:Finder/Dock 启动的 GUI 不继承 shell PATH,`src-tauri/src/lib.rs` 里的 `fix_path_env::fix()` 不可移除;验收标准是 **Finder 双击启动**后 git/claude/codex 在内置终端可用(Terminal 启动可用不算过)。 +- 文件管理器走 `plugin-opener`(`openPath`),不进 shell 白名单;shell capability 按平台拆分(`capabilities/shell-macos.json` 只含 git/claude/codex/node/npm)。 +- 签名分两阶段:内测期不签名(用户走「右键 → 打开」过 Gatekeeper,手册已写);**面向陌生用户分发前必须 Developer ID 签名 + 公证**(Tauri 支持 signing identity 自动公证),届时更新手册与 `tauri.macos.conf.json` 的签名配置。 +- `bundle.icon` 已含 `icons/icon.icns`;`minimumSystemVersion: 12.0`。 + +通用:改过 `src-tauri/capabilities/*.json` 后需 `touch src-tauri/build.rs` 强制重嵌(dev watch 不监听 capabilities);发布前 `package-lock.json` 与 `src-tauri/Cargo.lock` 必须已提交。 + ## 4. 每次发布的门槛(顺序执行,全过才发) 1. `npx vitest run` 全绿(当前基线 60 个)。 @@ -48,7 +70,9 @@ npm run make-release # ④ 归拢 release/(含门槛自检) 3. `npm run tauri dev` 冒烟:应用可启动、数据目录正常读取。 4. `npm run tauri build` 成功出包。 5. `npm run make-release`:脚本自检通过(版本三处一致;安装包不旧于源码;脏工作区警告)。**记住 SHA-256,发布渠道必须原文公布。** -6. 干净(或隔离)Windows 环境验证——没有做过这步的包不发给用户: +6. 干净(或隔离)环境验证——没有做过这步的包不发给用户: + + **Windows:** - [ ] 全新安装 → 首启欢迎卡出现,数据目录创建; - [ ] 无 git 的机器:左下出现「快照保护未生效」持续警告(装 git 后重试可恢复); - [ ] 双击两次启动:第二实例只聚焦第一个窗口(单实例); @@ -56,6 +80,21 @@ npm run make-release # ④ 归拢 release/(含门槛自检) - [ ] 覆盖安装(升级)后数据完好; - [ ] 卸载 → 数据目录**仍在** → 重装后数据回来; - [ ] 快照恢复演练:改错/删错任务后用 `git checkout` 找回。 + + **macOS(v0.1.0 起,第一轮 native + 第二轮 universal 各过一遍核心项):** + - [ ] DMG → 应用程序安装,Finder 双击启动(**不**从 Terminal 启动); + - [ ] Finder/Dock 启动后,内置终端 `git --version` / `claude --version` / `codex --version` 可用(PATH 修复验收); + - [ ] 数据目录 `~/ResearchThread` 创建、读写、重启仍在; + - [ ] 创建 Space/Project/Task、写 context、重启不丢; + - [ ] 外部编辑(Obsidian/文本编辑器)→ 冲突提示出现; + - [ ] Force Quit 后重启:中断写入被恢复(`.rt-bak` 机制); + - [ ] 连续双击两次启动:单实例聚焦; + - [ ] `⌘⇧Space` 全局快捷键唤起收件箱; + - [ ] 「打开数据目录」在 Finder 中打开(opener); + - [ ] 删除任务 → `.trash/` 可找回; + - [ ] 无 git 的 Mac:快照警告出现;装 git 后重试恢复; + - [ ] 旧 DMG → 新 DMG 覆盖升级数据完好;删 .app 卸载不删数据; + - [ ] 未签名 DMG 下载后:右键 → 打开可过 Gatekeeper。 7. git commit 并打 tag `vX.Y.Z`;tag message 记录版本与 SHA-256。 8. 发布渠道:安装包 + 公布的 SHA-256 + `release/README.md` 内容 + 种子手册。 @@ -84,8 +123,9 @@ npm run make-release # ④ 归拢 release/(含门槛自检) ## 7. 范围边界(防蔓延,发布不夹带) -- 只发 **Windows x64 NSIS**。macOS、安装包签名、自动更新是后续里程碑,不 sneak 进普通发版。 -- 安装器保持现有形态(中文界面 + 安装须知页 + 语言选择器);不加自定义安装页面、不捆绑其他软件。 +- 发布目标:**Windows x64 NSIS + macOS 12+ universal DMG(未签名内测期)**。Mac App Store、自动更新是后续里程碑,不 sneak 进普通发版。 +- 安装器形态固定:Windows 中文 NSIS + 安装须知页;macOS 走标准 DMG 拖入 Applications。不加自定义安装页面、不捆绑其他软件。 +- 面向陌生用户的 macOS 公开分发前,**必须** Developer ID 签名 + 公证(熟人内测可用「右键 → 打开」过渡,手册已写)。 - 反目标照旧(AGENTS.md):不做项目管理软件、不做笔记软件、不做 chat-first。 ## 8. 发布记录 diff --git a/docs/seed-manual.md b/docs/seed-manual.md index 1b22ebe..9f55523 100644 --- a/docs/seed-manual.md +++ b/docs/seed-manual.md @@ -80,3 +80,11 @@ Windows 版本:如 Win10 22H2 / Win11 23H2 - 图谱视图与整体目标冲突,保持禁用;Agent 面板是占位,尚未接真实 CLI。 遇到任何「数据看起来不对」的情况,请先复制整个数据目录留证,再联系我们。谢谢! + +## 8. macOS 附录(Mac 种子用户) + +- **安装**:双击 DMG,把 ResearchThread 拖进「应用程序」。安装包未签名,首次启动若提示「无法验证开发者」:在「应用程序」里**右键 ResearchThread → 打开 → 再点「打开」**(只需一次),不要去系统设置里关 Gatekeeper。 +- **数据**:与 Windows 完全一致,存放在 `/Users/<你>/ResearchThread/`(Finder 里按 `⌘⇧G` 输入 `~/ResearchThread` 可直达)。删除应用不会删除这个目录。 +- **快照**:需要本机安装 git(`xcode-select --install` 装的 Command Line Tools 就自带)。Finder 双击启动的应用拿不到终端里的 PATH——应用已内置修复;若左下角仍出现「快照保护未生效」,装好 git 后在「设置 → 数据 → 重试」。 +- **并发规则、隐私说明、反馈模板**:与上文 Windows 部分完全相同。 +- 测试重点(Mac 版特有):Finder 双击启动后在内置终端跑 `git --version` 应该成功;「打开数据目录」应该唤起 Finder。 diff --git a/package-lock.json b/package-lock.json index 75b78e4..82724c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@tauri-apps/api": "^2.11.1", "@tauri-apps/plugin-fs": "^2.5.2", "@tauri-apps/plugin-global-shortcut": "^2.3.2", + "@tauri-apps/plugin-opener": "^2.5.5", "@tauri-apps/plugin-shell": "^2.3.6", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", @@ -1199,6 +1200,15 @@ "@tauri-apps/api": "^2.11.0" } }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.5", + "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-opener/-/plugin-opener-2.5.5.tgz", + "integrity": "sha512-xvzGai5aQds8j8R8RsUK/lW6pGG50YgOYIPLzvkqmkwAj7dfySOD7sGtejRzvVdMmv1EQfKVEFh1MvmDp8QR0g==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + } + }, "node_modules/@tauri-apps/plugin-shell": { "version": "2.3.6", "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-shell/-/plugin-shell-2.3.6.tgz", diff --git a/package.json b/package.json index 3aec4f1..c0a09bc 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@tauri-apps/api": "^2.11.1", "@tauri-apps/plugin-fs": "^2.5.2", "@tauri-apps/plugin-global-shortcut": "^2.3.2", + "@tauri-apps/plugin-opener": "^2.5.5", "@tauri-apps/plugin-shell": "^2.3.6", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", diff --git a/scripts/make-release.mjs b/scripts/make-release.mjs index 665ce2a..16b5c35 100644 --- a/scripts/make-release.mjs +++ b/scripts/make-release.mjs @@ -1,21 +1,21 @@ -// scripts/make-release.mjs — 把发布产物归拢到 release/(可重复执行)。 -// 输入:src-tauri/target/release/bundle/nsis/ 下的安装包 + docs/seed-manual.md -// 输出:release/ResearchThread__x64-setup.exe、SHA256SUMS.txt、README.md、种子测试手册.md +// scripts/make-release.mjs — 把发布产物归拢到 release/(可重复执行,平台感知)。 +// Windows(win32):收 NSIS 安装包(ResearchThread__x64-setup.exe) +// macOS(darwin):收 DMG(native 或 universal-apple-darwin 构建产物,取最新) +// 共同输出:release/<安装包>、SHA256SUMS.txt、README.md、种子测试手册.md // 用法:先 npm run tauri build,再 npm run make-release。 // 发版要求与红线见 docs/release.md;本脚本强制:版本三处一致,其余为警告。 import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; -import { copyFile, mkdir, readFile, stat, writeFile } from "node:fs/promises"; +import { copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const isMac = process.platform === "darwin"; const conf = JSON.parse(await readFile(join(root, "src-tauri/tauri.conf.json"), "utf8")); const version = conf.version; -const installerName = `ResearchThread_${version}_x64-setup.exe`; -const installerSrc = join(root, "src-tauri/target/release/bundle/nsis", installerName); const sha256 = async (path) => { const buf = await readFile(path); @@ -43,11 +43,42 @@ if (pkg.version !== version || cargoVersion !== version) { process.exit(1); } -// 2) 安装包存在 -try { - await stat(installerSrc); -} catch { - console.error(`找不到安装包 ${installerSrc}——先运行 npm run tauri build`); +// 2) 定位本平台的安装包 +async function findInstaller() { + if (!isMac) { + const name = `ResearchThread_${version}_x64-setup.exe`; + return { src: join(root, "src-tauri/target/release/bundle/nsis", name), name }; + } + // native 构建在 target/release/,universal 在 target/universal-apple-darwin/release/ + const bundleRoots = [ + join(root, "src-tauri/target/universal-apple-darwin/release/bundle/dmg"), + join(root, "src-tauri/target/release/bundle/dmg"), + ]; + const found = []; + for (const dir of bundleRoots) { + try { + for (const f of await readdir(dir)) { + if (f.endsWith(".dmg")) { + const full = join(dir, f); + found.push({ src: full, name: f, mtime: (await stat(full)).mtimeMs }); + } + } + } catch { + // 目录不存在(未做该形态构建) + } + } + if (found.length === 0) return null; + found.sort((a, b) => b.mtime - a.mtime); // 取最新(universal 优先构建时通常也更新) + return found[0]; +} + +const installer = await findInstaller(); +if (!installer) { + console.error( + isMac + ? "找不到 DMG 产物——先运行 npm run tauri build -- --bundles app,dmg" + : `找不到安装包 ResearchThread_${version}_x64-setup.exe——先运行 npm run tauri build`, + ); process.exit(1); } @@ -57,10 +88,9 @@ const gitDirty = (git("status", "--porcelain") ?? "").length > 0; if (gitDirty) console.warn("⚠ 工作区有未提交改动——发布应从干净的提交 / tag 构建(docs/release.md §2)"); // 4) 安装包是否比源码旧(过期构建警告) -const installerMtime = (await stat(installerSrc)).mtimeMs; +const installerMtime = (await stat(installer.src)).mtimeMs; const staleSources = []; const scan = async (dir) => { - const { readdir } = await import("node:fs/promises"); for (const e of await readdir(dir, { withFileTypes: true })) { const full = join(dir, e.name); if (e.isDirectory()) await scan(full); @@ -70,8 +100,17 @@ const scan = async (dir) => { await scan(join(root, "src")); await scan(join(root, "src-tauri/src")); await scan(join(root, "src-tauri/capabilities")); -for (const f of ["src-tauri/tauri.conf.json", "src-tauri/INSTALL-NOTES.txt"]) { - if ((await stat(join(root, f))).mtimeMs > installerMtime) staleSources.push(f); +for (const f of [ + "src-tauri/tauri.conf.json", + "src-tauri/tauri.windows.conf.json", + "src-tauri/tauri.macos.conf.json", + "src-tauri/INSTALL-NOTES.txt", +]) { + try { + if ((await stat(join(root, f))).mtimeMs > installerMtime) staleSources.push(f); + } catch { + // 平台配置文件可以不存在 + } } if (staleSources.length > 0) { console.warn(`⚠ 安装包早于 ${staleSources.length} 个较新的源文件(如 ${staleSources[0]})——确认已重新 npm run tauri build`); @@ -82,8 +121,8 @@ if (staleSources.length > 0) { const outDir = join(root, "release"); await mkdir(outDir, { recursive: true }); -const installerDest = join(outDir, installerName); -await copyFile(installerSrc, installerDest); +const installerDest = join(outDir, installer.name); +await copyFile(installer.src, installerDest); const installerHash = await sha256(installerDest); await copyFile(join(root, "docs/seed-manual.md"), join(outDir, "种子测试手册.md")); @@ -92,47 +131,61 @@ const manualHash = await sha256(join(outDir, "种子测试手册.md")); await writeFile( join(outDir, "SHA256SUMS.txt"), - `${installerHash} ${installerName}\n${manualHash} 种子测试手册.md\n`, + `${installerHash} ${installer.name}\n${manualHash} 种子测试手册.md\n`, "utf8", ); const buildDate = new Date().toISOString().slice(0, 10); +const platformLabel = isMac ? "macOS(universal:Apple Silicon + Intel)" : "Windows x64"; +const verifyCmd = isMac + ? `shasum -a 256 ./${installer.name}` + : `Get-FileHash .\\${installer.name}`; +const installSteps = isMac + ? `1. 核对校验值:终端执行 \`${verifyCmd}\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 +2. 双击 DMG,把 ResearchThread 拖入「应用程序」。 +3. 首次启动(未签名构建):在「应用程序」里**右键 → 打开 → 打开**,之后正常启动。 + 应用未来做 Developer ID 签名 + 公证后此步可免(docs/release.md §7)。` + : `1. 核对校验值:PowerShell 执行 \`${verifyCmd}\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 +2. 双击安装包。未签名会过 SmartScreen:**「更多信息」→「仍要运行」**,不需要关闭 SmartScreen。 +3. 首次启动应用会弹欢迎提示(数据位置 / 快照警告含义 / 反馈方式),照读一遍即可。`; +const uninstallNote = isMac + ? `卸载 = 把「应用程序」里的 ResearchThread 删除;\`~/ResearchThread\` 数据目录**不会**被删除。` + : `卸载不会删除数据目录;程序装在 \`%LOCALAPPDATA%\\ResearchThread\`(约 10 MB)。`; + await writeFile( join(outDir, "README.md"), - `# ResearchThread ${version} 种子发布包(Windows x64) + `# ResearchThread ${version} 种子发布包(${platformLabel}) 本目录为受控种子测试版发布物(scripts/make-release.mjs 生成,可重复执行)。 发版要求与红线见仓库 docs/release.md。 | 文件 | 说明 | |---|---| -| \`${installerName}\` | NSIS 安装包(per-user,免管理员;安装器有中文界面与安装须知页) | +| \`${installer.name}\` | 安装包${isMac ? "(DMG,universal 二进制)" : "(NSIS,per-user,免管理员;中文界面与安装须知页)"} | | \`SHA256SUMS.txt\` | 校验值(发布渠道需原文公布) | | \`种子测试手册.md\` | 种子用户手册:数据/快照/并发/隐私与脱敏反馈模板 | ## 安装步骤 -1. 核对校验值:PowerShell 执行 \`Get-FileHash .\\${installerName}\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 -2. 双击安装包。未签名会过 SmartScreen:**「更多信息」→「仍要运行」**,不需要关闭 SmartScreen。 -3. 首次启动应用会弹欢迎提示(数据位置 / 快照警告含义 / 反馈方式),照读一遍即可。 +${installSteps} ## 快速核对 -- 程序装在 \`%LOCALAPPDATA%\\ResearchThread\`(约 10 MB)。 -- 数据目录 \`~/ResearchThread\` 由应用首次启动创建,卸载**不会**删除。 +- 数据目录 \`~/ResearchThread\` 由应用首次启动创建,卸载**不会**删除。${uninstallNote} - 每天首次启动自动 git 快照;无 git 机器会显示「快照保护未生效」持续警告(数据仍正常保存)。 ## 构建信息 - 版本:${version}(tauri.conf.json = package.json = Cargo.toml) +- 平台:${platformLabel} - 构建提交:\`${gitHead}\`${gitDirty ? "(⚠ 工作区不干净)" : ""} - 构建日期:${buildDate} -- ${installerName} +- ${installer.name} - SHA-256:\`${installerHash}\` `, "utf8", ); console.log(`release/ 已生成(构建提交 ${gitHead}${gitDirty ? ",工作区不干净" : ""}):`); -console.log(` ${installerName} (SHA-256 ${installerHash})`); +console.log(` ${installer.name} (SHA-256 ${installerHash})`); console.log(` SHA256SUMS.txt / README.md / 种子测试手册.md`); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 69a5283..44f2398 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1026,6 +1026,16 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +[[package]] +name = "fix-path-env" +version = "0.0.0" +source = "git+https://github.com/tauri-apps/fix-path-env-rs#c4c45d503ea115a839aae718d02f79e7c7f0f673" +dependencies = [ + "home", + "strip-ansi-escapes", + "thiserror 1.0.69", +] + [[package]] name = "flate2" version = "1.1.10" @@ -1533,6 +1543,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "html5ever" version = "0.38.0" @@ -3010,12 +3029,14 @@ dependencies = [ name = "researchthread" version = "0.1.0" dependencies = [ + "fix-path-env", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-fs", "tauri-plugin-global-shortcut", + "tauri-plugin-opener", "tauri-plugin-shell", "tauri-plugin-single-instance", ] @@ -3485,6 +3506,15 @@ dependencies = [ "quote", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.11.1" @@ -3794,6 +3824,28 @@ dependencies = [ "thiserror 2.0.20", ] +[[package]] +name = "tauri-plugin-opener" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d60366174b745b4ef5824b8bbc1c457fd08f0ce101ff643c0a49181a9f4e91" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.20", + "url", + "windows", + "zbus", +] + [[package]] name = "tauri-plugin-shell" version = "2.3.6" @@ -4450,6 +4502,15 @@ dependencies = [ "libc", ] +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ca79eee..6949f3f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,8 +16,11 @@ tauri-build = { version = "2", features = [] } tauri = { version = "2", features = [] } tauri-plugin-fs = { version = "2", features = ["watch"] } tauri-plugin-shell = "2" +tauri-plugin-opener = "2" tauri-plugin-single-instance = "2" tauri-plugin-global-shortcut = "2" +# Finder/Dock 启动的 GUI 不继承 shell PATH(Tauri 官方修复,见 docs/release.md mac 章节) +fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src-tauri/capabilities/common.json b/src-tauri/capabilities/common.json new file mode 100644 index 0000000..ee1d703 --- /dev/null +++ b/src-tauri/capabilities/common.json @@ -0,0 +1,39 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "common", + "description": "跨平台能力:数据目录文件 IO 与监听、目录打开(opener)、全局快捷键", + "windows": ["main"], + "permissions": [ + "core:default", + "core:window:allow-show", + "core:window:allow-hide", + "core:window:allow-set-focus", + "core:window:allow-is-visible", + "core:window:allow-close", + "core:window:allow-destroy", + "fs:default", + "fs:allow-read-text-file", + "fs:allow-write-text-file", + "fs:allow-read-dir", + "fs:allow-mkdir", + "fs:allow-exists", + "fs:allow-remove", + "fs:allow-rename", + "fs:allow-stat", + "fs:allow-watch", + { + "identifier": "fs:scope", + "allow": [ + { "path": "$HOME/ResearchThread" }, + { "path": "$HOME/ResearchThread/**" } + ], + "requireLiteralLeadingDot": false + }, + "opener:default", + "opener:allow-open-path", + "global-shortcut:allow-register", + "global-shortcut:allow-unregister", + "global-shortcut:allow-unregister-all", + "global-shortcut:allow-is-registered" + ] +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json deleted file mode 100644 index 5bde162..0000000 --- a/src-tauri/capabilities/default.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "ResearchThread 主窗口能力:数据目录文件 IO 与监听、终端 shell、全局快捷键", - "windows": ["main"], - "permissions": [ - "core:default", - "core:window:allow-show", - "core:window:allow-hide", - "core:window:allow-set-focus", - "core:window:allow-is-visible", - "core:window:allow-close", - "core:window:allow-destroy", - "fs:default", - "fs:allow-read-text-file", - "fs:allow-write-text-file", - "fs:allow-read-dir", - "fs:allow-mkdir", - "fs:allow-exists", - "fs:allow-remove", - "fs:allow-rename", - "fs:allow-stat", - "fs:allow-watch", - { - "identifier": "fs:scope", - "allow": [ - { "path": "$HOME/ResearchThread" }, - { "path": "$HOME/ResearchThread/**" } - ], - "requireLiteralLeadingDot": false - }, - { - "identifier": "shell:allow-execute", - "allow": [ - { "name": "git", "cmd": "git", "args": true }, - { "name": "claude", "cmd": "claude", "args": true }, - { "name": "codex", "cmd": "codex", "args": true }, - { "name": "cmd", "cmd": "cmd", "args": true }, - { "name": "powershell", "cmd": "powershell", "args": true }, - { "name": "explorer", "cmd": "explorer", "args": true }, - { "name": "npm", "cmd": "npm", "args": true }, - { "name": "node", "cmd": "node", "args": true } - ] - }, - { - "identifier": "shell:allow-spawn", - "allow": [ - { "name": "git", "cmd": "git", "args": true }, - { "name": "claude", "cmd": "claude", "args": true }, - { "name": "codex", "cmd": "codex", "args": true }, - { "name": "cmd", "cmd": "cmd", "args": true }, - { "name": "powershell", "cmd": "powershell", "args": true }, - { "name": "explorer", "cmd": "explorer", "args": true }, - { "name": "npm", "cmd": "npm", "args": true }, - { "name": "node", "cmd": "node", "args": true } - ] - }, - "shell:allow-kill", - "shell:allow-stdin-write", - "global-shortcut:allow-register", - "global-shortcut:allow-unregister", - "global-shortcut:allow-unregister-all", - "global-shortcut:allow-is-registered" - ] -} diff --git a/src-tauri/capabilities/shell-macos.json b/src-tauri/capabilities/shell-macos.json new file mode 100644 index 0000000..b3db5ba --- /dev/null +++ b/src-tauri/capabilities/shell-macos.json @@ -0,0 +1,31 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "shell-macos", + "description": "macOS 终端/快照 shell 白名单(高权限边界,见 docs/release.md §5-0):git/claude/codex/node/npm;文件管理器走 opener,不需要 shell", + "windows": ["main"], + "platforms": ["macOS"], + "permissions": [ + { + "identifier": "shell:allow-execute", + "allow": [ + { "name": "git", "cmd": "git", "args": true }, + { "name": "claude", "cmd": "claude", "args": true }, + { "name": "codex", "cmd": "codex", "args": true }, + { "name": "npm", "cmd": "npm", "args": true }, + { "name": "node", "cmd": "node", "args": true } + ] + }, + { + "identifier": "shell:allow-spawn", + "allow": [ + { "name": "git", "cmd": "git", "args": true }, + { "name": "claude", "cmd": "claude", "args": true }, + { "name": "codex", "cmd": "codex", "args": true }, + { "name": "npm", "cmd": "npm", "args": true }, + { "name": "node", "cmd": "node", "args": true } + ] + }, + "shell:allow-kill", + "shell:allow-stdin-write" + ] +} diff --git a/src-tauri/capabilities/shell-windows.json b/src-tauri/capabilities/shell-windows.json new file mode 100644 index 0000000..919db3a --- /dev/null +++ b/src-tauri/capabilities/shell-windows.json @@ -0,0 +1,35 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "shell-windows", + "description": "Windows 终端/快照 shell 白名单(高权限边界,见 docs/release.md §5-0):git/claude/codex/node/npm + cmd/powershell;explorer 已由 opener 取代", + "windows": ["main"], + "platforms": ["windows"], + "permissions": [ + { + "identifier": "shell:allow-execute", + "allow": [ + { "name": "git", "cmd": "git", "args": true }, + { "name": "claude", "cmd": "claude", "args": true }, + { "name": "codex", "cmd": "codex", "args": true }, + { "name": "cmd", "cmd": "cmd", "args": true }, + { "name": "powershell", "cmd": "powershell", "args": true }, + { "name": "npm", "cmd": "npm", "args": true }, + { "name": "node", "cmd": "node", "args": true } + ] + }, + { + "identifier": "shell:allow-spawn", + "allow": [ + { "name": "git", "cmd": "git", "args": true }, + { "name": "claude", "cmd": "claude", "args": true }, + { "name": "codex", "cmd": "codex", "args": true }, + { "name": "cmd", "cmd": "cmd", "args": true }, + { "name": "powershell", "cmd": "powershell", "args": true }, + { "name": "npm", "cmd": "npm", "args": true }, + { "name": "node", "cmd": "node", "args": true } + ] + }, + "shell:allow-kill", + "shell:allow-stdin-write" + ] +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5a661db..93cbdb0 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -14,7 +14,16 @@ pub fn run() { })) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_global_shortcut::Builder::new().build()) + .setup(|_app| { + // macOS:Finder/Dock 启动不继承 .zshrc 的 PATH,git/claude/codex 会找不到; + // 官方 fix-path-env 恢复登录 shell 的 PATH(其他平台为安全 no-op) + if let Err(e) = fix_path_env::fix() { + eprintln!("fix_path_env failed: {e}"); + } + Ok(()) + }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 39571f5..1a74222 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -26,19 +26,11 @@ }, "bundle": { "active": true, - "targets": ["nsis"], - "resources": ["WebView2Loader.dll"], - "license": "INSTALL-NOTES.txt", - "windows": { - "nsis": { - "languages": ["SimpChinese", "English"], - "displayLanguageSelector": true - } - }, "icon": [ "icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", + "icons/icon.icns", "icons/icon.ico" ] } diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json new file mode 100644 index 0000000..8173e9c --- /dev/null +++ b/src-tauri/tauri.macos.conf.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "bundle": { + "targets": ["app", "dmg"], + "macOS": { + "minimumSystemVersion": "12.0" + } + } +} diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json new file mode 100644 index 0000000..a89ece3 --- /dev/null +++ b/src-tauri/tauri.windows.conf.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "bundle": { + "targets": ["nsis"], + "resources": ["WebView2Loader.dll"], + "license": "INSTALL-NOTES.txt", + "windows": { + "nsis": { + "languages": ["SimpChinese", "English"], + "displayLanguageSelector": true + } + } + } +} diff --git a/src/main.tsx b/src/main.tsx index fa22d99..c022092 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -44,12 +44,21 @@ async function boot(): Promise { initialTask = null; } } catch (e) { - root.innerHTML = ` -
-

ResearchThread 数据层初始化失败

-

${String(e)}

-

请检查数据目录(~/ResearchThread)权限后重启应用。

-
`; + // renderer 安全红线:错误文案一律 textContent,不经 innerHTML(docs/release.md §5-0) + const box = document.createElement("div"); + box.style.cssText = + "font-family:system-ui,sans-serif;padding:48px;max-width:560px;margin:0 auto;color:#1c1917"; + const h1 = document.createElement("h1"); + h1.style.cssText = "font-size:18px;margin-bottom:12px"; + h1.textContent = "ResearchThread 数据层初始化失败"; + const msg = document.createElement("p"); + msg.style.cssText = "color:#666;line-height:1.6;white-space:pre-wrap"; + msg.textContent = e instanceof Error ? e.message : String(e); + const hint = document.createElement("p"); + hint.style.cssText = "color:#999;margin-top:16px"; + hint.textContent = "请检查数据目录(~/ResearchThread)权限后重启应用。"; + box.append(h1, msg, hint); + root.replaceChildren(box); return; } diff --git a/src/storage/git.ts b/src/storage/git.ts index fca5f36..f5df95d 100644 --- a/src/storage/git.ts +++ b/src/storage/git.ts @@ -48,8 +48,8 @@ export async function gitSnapshot(dataDir: string, label?: string): Promise { - const cmd = Command.create("explorer", [dir]); - await cmd.spawn(); + const { openPath } = await import("@tauri-apps/plugin-opener"); + await openPath(dir); } From 3e1ab0bbfb6e648e023bb6d42f5c7d1ff000c7cb Mon Sep 17 00:00:00 2001 From: yr <194215355+Ychris12138@noreply.github.com> Date: Sat, 12 Sep 2026 14:49:30 +0800 Subject: [PATCH 2/6] fix(macos): allow dotfile paths in fs scope via plugin config tauri-plugin-fs reads require_literal_leading_dot only from the plugins.fs section of tauri.conf.json; the same key inside a capability fs:scope entry is silently ignored. Unset, it defaults to true on Unix (dotfiles never match **), so data-layer init failed on macOS with 'forbidden path: ~/ResearchThread/.activity' while Windows (default false) worked. Set plugins.fs.requireLiteralLeadingDot = false (no-op on Windows) and drop the dead capability-level key. --- src-tauri/capabilities/common.json | 3 +-- src-tauri/tauri.conf.json | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src-tauri/capabilities/common.json b/src-tauri/capabilities/common.json index ee1d703..512d6ef 100644 --- a/src-tauri/capabilities/common.json +++ b/src-tauri/capabilities/common.json @@ -26,8 +26,7 @@ "allow": [ { "path": "$HOME/ResearchThread" }, { "path": "$HOME/ResearchThread/**" } - ], - "requireLiteralLeadingDot": false + ] }, "opener:default", "opener:allow-open-path", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 1a74222..e2749d0 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -24,6 +24,11 @@ "devCsp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src ipc: http://ipc.localhost ws://localhost:1420 http://localhost:1420" } }, + "plugins": { + "fs": { + "requireLiteralLeadingDot": false + } + }, "bundle": { "active": true, "icon": [ From 51c45a81b5a4a4c6f2f680f189f528d7ddb4d9b8 Mon Sep 17 00:00:00 2001 From: yr <194215355+Ychris12138@noreply.github.com> Date: Sat, 12 Sep 2026 15:12:57 +0800 Subject: [PATCH 3/6] =?UTF-8?q?docs(macos):=20Gatekeeper=20guidance=20?= =?UTF-8?q?=E2=80=94=20right-click=20open=20removed=20on=20macOS=2015+?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real-download verification on M5 / macOS 26.6: a quarantined unsigned app shows the hard 'move to trash / done' dialog even via right-click open; the bypass now lives in System Settings > Privacy & Security > 'Open Anyway'. Split the guidance by OS version in the seed manual, release playbook (§3/§4-6/§7), make-release README template, and AGENTS.md status. --- AGENTS.md | 2 +- docs/release.md | 6 +++--- docs/seed-manual.md | 4 +++- scripts/make-release.mjs | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0c9da5c..3915950 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ ResearchThread 是一个**本地优先**的桌面工具(Tauri 2,macOS + Wind - **watcher 路径归一化**:Windows 反斜杠事件先归一再剥数据目录前缀(此前 `.git` / `.activity` 过滤失配、活动账记绝对路径);`.rt-bak` 一并过滤 - **发布物料**:关于页读真实版本(`getVersion()`)标「种子测试版」;[docs/seed-manual.md](docs/seed-manual.md) 种子手册——隐私边界(**明确撤回「打包数据目录反馈」**,改为脱敏反馈模板)、SmartScreen 安装步骤、卸载数据保留说明、外部编辑并发规则 - **发布包与安装提示**:`npm run make-release`([scripts/make-release.mjs](scripts/make-release.mjs))平台感知(win 收 NSIS exe / darwin 收 DMG),归拢安装包、SHA256SUMS、README、种子手册到 `release/`(gitignore,脚本可重复生成);安装器中文化(`bundle.windows.nsis.languages` 简中+英文+语言选择器)并以 `bundle.license`(`src-tauri/INSTALL-NOTES.txt`)在安装前展示中文安装须知;应用首启弹欢迎卡(数据位置/快照警告含义/脱敏反馈,`settings.introSeen` 持久化开关) -- **macOS 适配(v0.1.0,PR feat/v0.1-macos-support)**:Tauri 配置拆三文件(`tauri.conf.json` 公共 / `tauri.windows.conf.json` NSIS+WebView2Loader / `tauri.macos.conf.json` app+dmg+minimumSystemVersion 12.0,构建时自动合并);文件管理器改 `plugin-opener`(`openPath`,替代硬编码 explorer);**Finder/Dock 启动 PATH 修复**(`fix-path-env` crate,lib.rs setup 里 `fix()`,mac 验收 = Finder 双击启动后终端 git/claude/codex 可用);shell capability 拆平台(`shell-windows.json` / `shell-macos.json`——mac 只留 git/claude/codex/npm/node,explorer/cmd/powershell 不进 mac 白名单);universal 构建命令与 mac 测试矩阵见 docs/release.md §0/§3/§4-6。**签名两阶段**:内测不签名(右键→打开过 Gatekeeper),面向陌生用户前必须 Developer ID + 公证 +- **macOS 适配(v0.1.0,PR feat/v0.1-macos-support)**:Tauri 配置拆三文件(`tauri.conf.json` 公共 / `tauri.windows.conf.json` NSIS+WebView2Loader / `tauri.macos.conf.json` app+dmg+minimumSystemVersion 12.0,构建时自动合并);文件管理器改 `plugin-opener`(`openPath`,替代硬编码 explorer);**Finder/Dock 启动 PATH 修复**(`fix-path-env` crate,lib.rs setup 里 `fix()`,mac 验收 = Finder 双击启动后终端 git/claude/codex 可用);shell capability 拆平台(`shell-windows.json` / `shell-macos.json`——mac 只留 git/claude/codex/npm/node,explorer/cmd/powershell 不进 mac 白名单);universal 构建命令与 mac 测试矩阵见 docs/release.md §0/§3/§4-6。**签名两阶段**:内测不签名(**macOS 15+ 无「右键→打开」通道**,被拦后走「系统设置→隐私与安全性→仍要打开」,实机 2026-09-12 确认;macOS 12–14 才是右键→打开),面向陌生用户前必须 Developer ID + 公证 - 下一步:干净 Windows 账户实测(首装 / 无 git / 无 CLI / 双开 / 升级 / 卸载重装 / 快照恢复演练)→ 全部过门后打 release tag 发 **Windows x64 受控种子版**;随后回到 **第二步阶梯 2a**(AgentRunner CLI 后端,见下条与 PART2.md) - 第二步技术报告见 [PART2.md](PART2.md):CLI 后端选型(claude 首选/codex 坑清单)、MCP 工具面与 will_write 归属协议、2c 解析器选型、风险登记册与排期——**实施 2a–2d 前必读** - **Windows 构建环境(本机)**:Rust stable-msvc 装了但**缺 Windows SDK**(提权安装 SDK 未获批准);当前用 `stable-x86_64-pc-windows-gnu` + `rust-lld` + Strawberry Perl 的 dlltool 编译通过。**启动命令**: diff --git a/docs/release.md b/docs/release.md index 432349a..d5ea047 100644 --- a/docs/release.md +++ b/docs/release.md @@ -58,7 +58,7 @@ npm run make-release # ④ 同一脚本,darwin 下收 DMG - universal 二进制:`rustup target add aarch64-apple-darwin x86_64-apple-darwin` 后 `npm run tauri build -- --target universal-apple-darwin --bundles app,dmg`;产物在 `src-tauri/target/universal-apple-darwin/release/bundle/dmg/`。 - **PATH 修复是硬依赖**:Finder/Dock 启动的 GUI 不继承 shell PATH,`src-tauri/src/lib.rs` 里的 `fix_path_env::fix()` 不可移除;验收标准是 **Finder 双击启动**后 git/claude/codex 在内置终端可用(Terminal 启动可用不算过)。 - 文件管理器走 `plugin-opener`(`openPath`),不进 shell 白名单;shell capability 按平台拆分(`capabilities/shell-macos.json` 只含 git/claude/codex/node/npm)。 -- 签名分两阶段:内测期不签名(用户走「右键 → 打开」过 Gatekeeper,手册已写);**面向陌生用户分发前必须 Developer ID 签名 + 公证**(Tauri 支持 signing identity 自动公证),届时更新手册与 `tauri.macos.conf.json` 的签名配置。 +- 签名分两阶段:内测期不签名(未签名包被 Gatekeeper 拦截后,macOS 15+ 走「系统设置 → 隐私与安全性 → 仍要打开」放行;macOS 12–14 才有「右键 → 打开」——**「右键打开」通道在 macOS 15 Sequoia 起已被移除**,2026-09-12 M5/macOS 26.6 实测确认,手册已按系统版本分开写);**面向陌生用户分发前必须 Developer ID 签名 + 公证**(Tauri 支持 signing identity 自动公证),届时更新手册与 `tauri.macos.conf.json` 的签名配置。 - `bundle.icon` 已含 `icons/icon.icns`;`minimumSystemVersion: 12.0`。 通用:改过 `src-tauri/capabilities/*.json` 后需 `touch src-tauri/build.rs` 强制重嵌(dev watch 不监听 capabilities);发布前 `package-lock.json` 与 `src-tauri/Cargo.lock` 必须已提交。 @@ -94,7 +94,7 @@ npm run make-release # ④ 同一脚本,darwin 下收 DMG - [ ] 删除任务 → `.trash/` 可找回; - [ ] 无 git 的 Mac:快照警告出现;装 git 后重试恢复; - [ ] 旧 DMG → 新 DMG 覆盖升级数据完好;删 .app 卸载不删数据; - - [ ] 未签名 DMG 下载后:右键 → 打开可过 Gatekeeper。 + - [ ] 未签名 DMG 下载后(真实浏览器下载带 quarantine):macOS 15+ 弹「移到废纸篓/完成」→ 系统设置 → 隐私与安全性 →「仍要打开」可放行(macOS 12–14:右键 → 打开)。 7. git commit 并打 tag `vX.Y.Z`;tag message 记录版本与 SHA-256。 8. 发布渠道:安装包 + 公布的 SHA-256 + `release/README.md` 内容 + 种子手册。 @@ -125,7 +125,7 @@ npm run make-release # ④ 同一脚本,darwin 下收 DMG - 发布目标:**Windows x64 NSIS + macOS 12+ universal DMG(未签名内测期)**。Mac App Store、自动更新是后续里程碑,不 sneak 进普通发版。 - 安装器形态固定:Windows 中文 NSIS + 安装须知页;macOS 走标准 DMG 拖入 Applications。不加自定义安装页面、不捆绑其他软件。 -- 面向陌生用户的 macOS 公开分发前,**必须** Developer ID 签名 + 公证(熟人内测可用「右键 → 打开」过渡,手册已写)。 +- 面向陌生用户的 macOS 公开分发前,**必须** Developer ID 签名 + 公证(熟人内测可用「系统设置 → 隐私与安全性 → 仍要打开」过渡,macOS 15+ 无「右键 → 打开」通道,手册已写)。 - 反目标照旧(AGENTS.md):不做项目管理软件、不做笔记软件、不做 chat-first。 ## 8. 发布记录 diff --git a/docs/seed-manual.md b/docs/seed-manual.md index 9f55523..0c22f06 100644 --- a/docs/seed-manual.md +++ b/docs/seed-manual.md @@ -83,7 +83,9 @@ Windows 版本:如 Win10 22H2 / Win11 23H2 ## 8. macOS 附录(Mac 种子用户) -- **安装**:双击 DMG,把 ResearchThread 拖进「应用程序」。安装包未签名,首次启动若提示「无法验证开发者」:在「应用程序」里**右键 ResearchThread → 打开 → 再点「打开」**(只需一次),不要去系统设置里关 Gatekeeper。 +- **安装**:双击 DMG,把 ResearchThread 拖进「应用程序」。安装包未签名,首次启动会被 macOS 拦截,放行方式(只需一次,**不要**去系统设置里关 Gatekeeper): + - **macOS 15(Sequoia)及更高**:双击/右键打开都会弹「移到废纸篓 / 完成」——点「**完成**」关掉它,然后打开 **系统设置 → 隐私与安全性**,滚到页面底部,点「ResearchThread 已被阻止」旁的「**仍要打开**」,Touch ID/密码确认即可。 + - **macOS 12–14**:在「应用程序」里**右键 ResearchThread → 打开 → 再点「打开」**即可。 - **数据**:与 Windows 完全一致,存放在 `/Users/<你>/ResearchThread/`(Finder 里按 `⌘⇧G` 输入 `~/ResearchThread` 可直达)。删除应用不会删除这个目录。 - **快照**:需要本机安装 git(`xcode-select --install` 装的 Command Line Tools 就自带)。Finder 双击启动的应用拿不到终端里的 PATH——应用已内置修复;若左下角仍出现「快照保护未生效」,装好 git 后在「设置 → 数据 → 重试」。 - **并发规则、隐私说明、反馈模板**:与上文 Windows 部分完全相同。 diff --git a/scripts/make-release.mjs b/scripts/make-release.mjs index 16b5c35..69efb5c 100644 --- a/scripts/make-release.mjs +++ b/scripts/make-release.mjs @@ -143,7 +143,9 @@ const verifyCmd = isMac const installSteps = isMac ? `1. 核对校验值:终端执行 \`${verifyCmd}\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 2. 双击 DMG,把 ResearchThread 拖入「应用程序」。 -3. 首次启动(未签名构建):在「应用程序」里**右键 → 打开 → 打开**,之后正常启动。 +3. 首次启动(未签名构建)会被拦截,按系统版本放行(只需一次): + - macOS 15 及更高:弹「移到废纸篓 / 完成」时点「**完成**」,再打开 **系统设置 → 隐私与安全性**,底部点「**仍要打开**」并确认。 + - macOS 12–14:在「应用程序」里**右键 → 打开 → 打开**。 应用未来做 Developer ID 签名 + 公证后此步可免(docs/release.md §7)。` : `1. 核对校验值:PowerShell 执行 \`${verifyCmd}\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 2. 双击安装包。未签名会过 SmartScreen:**「更多信息」→「仍要运行」**,不需要关闭 SmartScreen。 From 114f4b43d8baf082cd17cdb596dac73ed0a87048 Mon Sep 17 00:00:00 2001 From: yr <194215355+Ychris12138@noreply.github.com> Date: Sat, 12 Sep 2026 15:25:10 +0800 Subject: [PATCH 4/6] docs: record macOS verification completion in status docs --- AGENTS.md | 7 ++++--- README.md | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3915950..d99cbd0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,8 +22,8 @@ ResearchThread 是一个**本地优先**的桌面工具(Tauri 2,macOS + Wind ## 当前状态 -- 更新日期:2026-09-11 -- 阶段:**M3 完成 + 种子发布加固**(M1/M2/M3 全部落地,实机验证)——发布前审阅确认的九项门槛已全部代码落地: +- 更新日期:2026-09-12 +- 阶段:**M3 完成 + 种子发布加固 + macOS 实机验证通过**(M1/M2/M3 全部落地,实机验证)——发布前审阅确认的九项门槛已全部代码落地: - **事务式写入**(`storage/atomicWrite.ts`):写 `.rt-tmp` → 原文件让位 `.rt-bak` → 新内容就位 → 清备份;任何一步失败或进程中断后,**原文件或备份必有一个可恢复**;启动时 `recoverInterruptedWrites()` 扫描恢复并记账。测试注入 rename 失败/持续故障/崩溃残留全覆盖 - **单实例**(`tauri-plugin-single-instance`):第二实例启动即聚焦已有主窗口后退出,杜绝双 watcher + 同名 tmp/bak 互踩 - **外部编辑并发策略**:storage 保存前比对磁盘内容与应用最后已知内容(`seen` 缓存,读/写后更新),不一致抛 `ExternalConflictError`,UI 弹「覆盖保存(acknowledge 后重试)/ 重新加载 / 取消」。`updateTaskMeta` / `appendEntry` / `updateEntry` / `updateProjectDescription` / `saveWeekly` **五路全部受保护**(appendEntry 同样检查冲突后整文件重写,比纯追加更安全) @@ -37,7 +37,8 @@ ResearchThread 是一个**本地优先**的桌面工具(Tauri 2,macOS + Wind - **发布物料**:关于页读真实版本(`getVersion()`)标「种子测试版」;[docs/seed-manual.md](docs/seed-manual.md) 种子手册——隐私边界(**明确撤回「打包数据目录反馈」**,改为脱敏反馈模板)、SmartScreen 安装步骤、卸载数据保留说明、外部编辑并发规则 - **发布包与安装提示**:`npm run make-release`([scripts/make-release.mjs](scripts/make-release.mjs))平台感知(win 收 NSIS exe / darwin 收 DMG),归拢安装包、SHA256SUMS、README、种子手册到 `release/`(gitignore,脚本可重复生成);安装器中文化(`bundle.windows.nsis.languages` 简中+英文+语言选择器)并以 `bundle.license`(`src-tauri/INSTALL-NOTES.txt`)在安装前展示中文安装须知;应用首启弹欢迎卡(数据位置/快照警告含义/脱敏反馈,`settings.introSeen` 持久化开关) - **macOS 适配(v0.1.0,PR feat/v0.1-macos-support)**:Tauri 配置拆三文件(`tauri.conf.json` 公共 / `tauri.windows.conf.json` NSIS+WebView2Loader / `tauri.macos.conf.json` app+dmg+minimumSystemVersion 12.0,构建时自动合并);文件管理器改 `plugin-opener`(`openPath`,替代硬编码 explorer);**Finder/Dock 启动 PATH 修复**(`fix-path-env` crate,lib.rs setup 里 `fix()`,mac 验收 = Finder 双击启动后终端 git/claude/codex 可用);shell capability 拆平台(`shell-windows.json` / `shell-macos.json`——mac 只留 git/claude/codex/npm/node,explorer/cmd/powershell 不进 mac 白名单);universal 构建命令与 mac 测试矩阵见 docs/release.md §0/§3/§4-6。**签名两阶段**:内测不签名(**macOS 15+ 无「右键→打开」通道**,被拦后走「系统设置→隐私与安全性→仍要打开」,实机 2026-09-12 确认;macOS 12–14 才是右键→打开),面向陌生用户前必须 Developer ID + 公证 -- 下一步:干净 Windows 账户实测(首装 / 无 git / 无 CLI / 双开 / 升级 / 卸载重装 / 快照恢复演练)→ 全部过门后打 release tag 发 **Windows x64 受控种子版**;随后回到 **第二步阶梯 2a**(AgentRunner CLI 后端,见下条与 PART2.md) +- **macOS 实机验证(2026-09-12,issue #4 已回报)**:universal DMG(lipo x86_64+arm64)、Finder 启动 PATH 三件套(git/claude/codex 全通)、数据持久、快照、.trash、冲突链路、单实例、⌘⇧Space、DMG 安装/重装/卸载保留全过;**修复 P1**:tauri-plugin-fs 点文件开关只认 `plugins.fs` 插件配置(capabilities 同名字段被忽略),Unix 默认拒绝点路径导致 macOS 数据层初始化必失败(`3e1ab0b`);**确认 macOS 15+ 无「右键打开」Gatekeeper 通道**(真实下载实测),指引按系统版本分流(`51c45a8`)。待办 issue:DMG 安装须知文件、首启 quarantine 检测提示卡、Developer ID 签名+公证 +- 下一步:Windows 侧拉取分支回归(fs 修复对 Windows no-op,跑 vitest + NSIS 重归拢)→ 审阅合并 PR #3 → 干净 Windows 账户实测(首装 / 无 git / 无 CLI / 双开 / 升级 / 卸载重装 / 快照恢复演练)→ 全部过门后打 release tag 发 **Windows x64 受控种子版 + macOS universal DMG**;随后回到 **第二步阶梯 2a**(AgentRunner CLI 后端,见下条与 PART2.md) - 第二步技术报告见 [PART2.md](PART2.md):CLI 后端选型(claude 首选/codex 坑清单)、MCP 工具面与 will_write 归属协议、2c 解析器选型、风险登记册与排期——**实施 2a–2d 前必读** - **Windows 构建环境(本机)**:Rust stable-msvc 装了但**缺 Windows SDK**(提权安装 SDK 未获批准);当前用 `stable-x86_64-pc-windows-gnu` + `rust-lld` + Strawberry Perl 的 dlltool 编译通过。**启动命令**: ```bash diff --git a/README.md b/README.md index 649ca49..ab579e0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## 状态 -**M3 完成 + 种子发布加固**(2026-09-11,M1–M3 全部落地并实机验证):数据落盘(Markdown 文件树)、活动日志、文件监听、收件箱、今日视角、终端面板、数据目录初始化、周文档一键生成、全文搜索、项目归档、markdown 预览之上,按发布前审阅补齐数据安全九项:事务式可恢复写入、单实例、外部编辑冲突防护、每日快照健康(git 缺失界面可见)、未保存草稿退出保护、周文档覆盖确认、周报正确性修正、watcher 路径归一化、种子手册(隐私边界 + 脱敏反馈模板)。下一步:干净 Windows 环境实测 → release tag → 受控种子版;随后第二步阶梯 2a。计划见 [PLAN.md](PLAN.md)。 +**M3 完成 + 种子发布加固**(2026-09-11,M1–M3 全部落地并实机验证):数据落盘(Markdown 文件树)、活动日志、文件监听、收件箱、今日视角、终端面板、数据目录初始化、周文档一键生成、全文搜索、项目归档、markdown 预览之上,按发布前审阅补齐数据安全九项:事务式可恢复写入、单实例、外部编辑冲突防护、每日快照健康(git 缺失界面可见)、未保存草稿退出保护、周文档覆盖确认、周报正确性修正、watcher 路径归一化、种子手册(隐私边界 + 脱敏反馈模板)。**macOS 实机验证完成**(2026-09-12,PR #3):universal DMG(x86_64+arm64)、Finder 启动 PATH、应用内终端三件套、冲突/快照/.trash/单实例全矩阵通过;修复 macOS 数据层初始化 bug(fs 插件点文件作用域);确认 macOS 15+ 无「右键打开」Gatekeeper 通道,安装指引已按系统版本分流。下一步:干净 Windows 环境实测 → release tag → 受控种子版;随后第二步阶梯 2a。计划见 [PLAN.md](PLAN.md)。 ## 文档导航 From b192899671719c41865acbf390d455c126af9334 Mon Sep 17 00:00:00 2001 From: yr <194215355+Ychris12138@noreply.github.com> Date: Sat, 12 Sep 2026 16:13:47 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20PR=20#3=20closeout=20=E2=80=94=20str?= =?UTF-8?q?ict=20universal=20release,=20unified=20sums,=20least=20privileg?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review closeout (5 items), no new features: - make-release.mjs: darwin now strictly requires the universal DMG at src-tauri/target/universal-apple-darwin/... with productName+version filename match -- native/ARM-only DMGs can never ship; release/ now ACCUMULATES current-version installers across platforms (old-version artifacts auto-cleaned) and SHA256SUMS.txt/README.md are recomputed over whatever is present, so copying the other platform's installer and re-running yields one unified checksum file (resolves the overwrite-vs-merge contradiction); README template lists both installers with the Intel-untested wording - capabilities/common.json: opener:default dropped; allow-open-path now carries an explicit path scope limited to the data dir. Note: a bare allow-open-path has an empty scope and the command is silently denied -- verified at runtime (open failed silently without scope, works with scope); least privilege AND functional - seed manual: title/known-limits updated to Windows+macOS reality, Intel-not-separately-tested caveat added - release.md: assembly semantics documented (accumulate + recompute), universal-only rule, Intel release-note wording requirement - AGENTS.md: capabilities/default.json references replaced with the platform-split files; stale 'data dir changeable in settings' claim removed Windows regression on latest head: 63 vitest green, tsc+vite build, NSIS rebuilt, aggregation semantics exercised (fake 0.0.9 dmg cleaned, 0.1.0 dmg from 'other platform' merged into unified SHA256SUMS), opener click opens Explorer at the data dir (verified via Shell.Application). --- AGENTS.md | 4 +- docs/release.md | 12 +-- docs/seed-manual.md | 4 +- scripts/make-release.mjs | 137 +++++++++++++++++------------ src-tauri/capabilities/common.json | 9 +- 5 files changed, 97 insertions(+), 69 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d99cbd0..dbb3702 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,11 +48,11 @@ ResearchThread 是一个**本地优先**的桌面工具(Tauri 2,macOS + Wind ``` `src-tauri/.cargo/config.toml` 固定了 rust-lld 链接器;`Cargo.toml` 的 lib crate-type 只保留 `["lib"]`(cdylib 会撞 65535 导出上限)。改 `capabilities/*.json` 后需 `touch build.rs && cargo build` 强制重嵌(dev watch 不监听 capabilities)。 - **打包安装器**:`npm run tauri build`(同上环境)→ NSIS 安装包在 `src-tauri/target/release/bundle/nsis/`,per-user 安装免管理员;随后 `npm run make-release` 归拢发布包到 `release/`(安装包 + SHA256SUMS + README + 种子手册)。**坑**:GNU 构建动态链接 `WebView2Loader.dll`(dev 时由构建目录提供,安装后缺失会导致启动即退 EXIT=127)——已通过 `bundle.resources` 打进安装器(`src-tauri/WebView2Loader.dll`,勿删);升级 webview2-com-sys 后需同步更新该 dll。安装器未签名,首次安装会过 SmartScreen 警告(安装须知页已说明「更多信息→仍要运行」)。 -- 终端面板 shell 白名单:`capabilities/default.json` 中 `shell:allow-spawn`/`shell:allow-execute` 的 allow 条目(name/cmd/args:true),新增可执行程序需同步加两处 +- 终端面板 shell 白名单:按平台拆分在 `capabilities/shell-windows.json` / `capabilities/shell-macos.json` 的 `shell:allow-spawn`/`shell:allow-execute` allow 条目(name/cmd/args:true),新增可执行程序需同步加两处(其余公共能力在 `capabilities/common.json`) ## 关键决策(改动前必须与用户确认) -1. **存储 = Markdown 文件树 + YAML frontmatter,不用数据库**。理由:第二步 agent 经 MCP 工具读写(零迁移);Obsidian 可打开同一目录;git 提供版本历史。数据目录(默认 `~/ResearchThread/`,可在设置中修改)与代码仓库分离。**文件树是项目地图的唯一事实源**,一切地图视图都是 `get_project_map()` 的派生渲染,绝不持久化为第二事实源。 +1. **存储 = Markdown 文件树 + YAML frontmatter,不用数据库**。理由:第二步 agent 经 MCP 工具读写(零迁移);Obsidian 可打开同一目录;git 提供版本历史。数据目录固定 `~/ResearchThread/`(当前不提供设置项修改)与代码仓库分离。**文件树是项目地图的唯一事实源**,一切地图视图都是 `get_project_map()` 的派生渲染,绝不持久化为第二事实源。 2. **storage 拆分为纯核心 + IO 适配器**(`core.ts` 无 Tauri API,`fsAdapter.ts` 注入 `tauri-plugin-fs`)。目的:2b 阶段同一核心配 `node:fs` 即可跑成 MCP server。**storage 写回契约**(宽松读 / 规范写 / 字段级合并保留未知字段 / 原子写 / 容忍 CRLF)见 PLAN.md 第三节,是数据完整性的关键,**违反契约的改动必须有测试覆盖**。 3. **起步零自定义 Rust**:文件 IO 走官方 `tauri-plugin-fs` 的 JS API。极简是第一阶段的起步策略而非长期约束——重大引入(如真 PTY)前与用户确认即可;**PTY 已降级为远期可选**(对话面板出现后终端退化为工具抽屉)。 4. **前端 = React 19 + Vite + Tailwind v4(2026-09-10 定)**:用户采纳外部按 UI.md 交付的 UI,并明确「可以扩大规模,只要匹配需求」。集成时剥离其 SSR(TanStack Start/Nitro)、登录(better-auth)、数据库(pglite/pg/kysely)——本地优先单机桌面应用不需要这些。**`DataAccess` 接口(UI.md 第 2 节)是 UI 与 storage 的唯一边界**,UI 组件不得绕过它触文件或自建数据。原「M1 末评估框架去留」的决策点提前完成。交付中超出 M1 范围的视图(搜索/今日/周报/图谱/agent 对话)按插件门控保留、默认关闭,不投入打磨(图谱与反目标冲突,保持禁用)。 diff --git a/docs/release.md b/docs/release.md index d5ea047..fce144b 100644 --- a/docs/release.md +++ b/docs/release.md @@ -21,7 +21,7 @@ npm run tauri build -- --target universal-apple-darwin --bundles app,dmg # ③ npm run make-release # ④ 同一脚本,darwin 下收 DMG ``` -两台机器各自生成 `release/`,发布前把 Windows 的 exe 与 macOS 的 dmg **汇总到同一份发布物**(SHA256SUMS 合并核对)。 +两台机器各自运行 `make-release` 生成自家产物;**汇总**:把对侧安装包拷进本机 `release/` 后重跑一次 `npm run make-release`——脚本按目录内实际存在的当前版本安装包全量重算 `SHA256SUMS.txt` 与 `README.md`,得到覆盖双平台的统一发布物。 ## 1. 位置与产物(不变量) @@ -30,11 +30,13 @@ npm run make-release # ④ 同一脚本,darwin 下收 DMG | 文件 | 内容 | 源头 | |---|---|---| | `ResearchThread__x64-setup.exe` | Windows NSIS 安装包 | Windows 机 `npm run tauri build` | - | `ResearchThread__universal.dmg` | macOS 安装映像(Apple Silicon + Intel) | macOS 机 `tauri build --target universal-apple-darwin` | - | `SHA256SUMS.txt` | 安装包与手册的 SHA-256 | 脚本计算(双平台合并后核对齐全) | + | `ResearchThread__universal.dmg` | macOS 安装映像(Apple Silicon + Intel 切片;**Intel 硬件未单独实测**,发布页须按此口径说明) | macOS 机 `tauri build --target universal-apple-darwin` | + | `SHA256SUMS.txt` | 安装包与手册的 SHA-256 | 脚本按 release/ 内实际安装包**全量重算**(跨平台统一,见下方汇总语义) | | `README.md` | 下载/校验/安装说明 + 构建提交记录 | 脚本模板 | | `种子测试手册.md` | 种子用户手册副本 | `docs/seed-manual.md`(只改源头,不改副本) | -- 每次生成**整体覆盖**:`release/` 只保留最新一个版本,不堆历史;历史版本靠 git tag 重新构建复现,发布后的安装包应转存到发布渠道/网盘归档。 +- **汇总语义(消解双平台覆盖矛盾)**:脚本只清理「旧版本」安装包;**当前版本**的安装包跨平台累积——Windows 跑完把 dmg 拷进来重跑、或在 macOS 侧反向操作,`SHA256SUMS.txt` 都会自动覆盖双平台,无需手工拼校验和文件。 +- macOS 侧**只认 universal DMG**(`target/universal-apple-darwin/...`),严格匹配 `productName + version` 文件名;native/ARM-only DMG 不是发布物,脚本直接失败、不 fallback。 +- 历史(旧版本)不堆在 `release/`:发布后的安装包转存到发布渠道/网盘归档,靠 git tag 重新构建复现。 - 手工改 `release/` 里任何文件 = 无效发布,下次生成会被覆盖。 - Tauri 配置按平台拆分(构建时自动合并):`tauri.conf.json`(公共:窗口/CSP/图标)+ `tauri.windows.conf.json`(NSIS/WebView2Loader/须知页)+ `tauri.macos.conf.json`(app+dmg/最低 macOS 12)。**不要把平台特有配置写回公共文件。** @@ -55,7 +57,7 @@ npm run make-release # ④ 同一脚本,darwin 下收 DMG ### macOS(v0.1.0 起) - 前置只需 Xcode Command Line Tools(`xcode-select --install`)+ rustup + node;第一轮先 native ARM(`npm run tauri dev`),通过后再 universal。 -- universal 二进制:`rustup target add aarch64-apple-darwin x86_64-apple-darwin` 后 `npm run tauri build -- --target universal-apple-darwin --bundles app,dmg`;产物在 `src-tauri/target/universal-apple-darwin/release/bundle/dmg/`。 +- universal 二进制:`rustup target add aarch64-apple-darwin x86_64-apple-darwin` 后 `npm run tauri build -- --target universal-apple-darwin --bundles app,dmg`;产物在 `src-tauri/target/universal-apple-darwin/release/bundle/dmg/`。**发布口径**:universal 含 Intel 切片(`lipo` 可验证),但实际硬件验证只在 Apple Silicon 上做过——release note 必须写「Tested on Apple Silicon; Intel hardware has not yet been separately validated」,不得声称 Intel 已全面验证。 - **PATH 修复是硬依赖**:Finder/Dock 启动的 GUI 不继承 shell PATH,`src-tauri/src/lib.rs` 里的 `fix_path_env::fix()` 不可移除;验收标准是 **Finder 双击启动**后 git/claude/codex 在内置终端可用(Terminal 启动可用不算过)。 - 文件管理器走 `plugin-opener`(`openPath`),不进 shell 白名单;shell capability 按平台拆分(`capabilities/shell-macos.json` 只含 git/claude/codex/node/npm)。 - 签名分两阶段:内测期不签名(未签名包被 Gatekeeper 拦截后,macOS 15+ 走「系统设置 → 隐私与安全性 → 仍要打开」放行;macOS 12–14 才有「右键 → 打开」——**「右键打开」通道在 macOS 15 Sequoia 起已被移除**,2026-09-12 M5/macOS 26.6 实测确认,手册已按系统版本分开写);**面向陌生用户分发前必须 Developer ID 签名 + 公证**(Tauri 支持 signing identity 自动公证),届时更新手册与 `tauri.macos.conf.json` 的签名配置。 diff --git a/docs/seed-manual.md b/docs/seed-manual.md index 0c22f06..4aa4202 100644 --- a/docs/seed-manual.md +++ b/docs/seed-manual.md @@ -1,4 +1,4 @@ -# ResearchThread 种子测试手册(Windows x64 · 0.1.0 Seed) +# ResearchThread 种子测试手册(Windows x64 + macOS · 0.1.0 Seed) 感谢参与种子测试。这份手册说明怎么装、数据放在哪、怎么保护数据、以及反馈时我们收集什么、**不**收集什么。 @@ -75,7 +75,7 @@ Windows 版本:如 Win10 22H2 / Win11 23H2 ## 7. 已知限制(种子版) -- 仅 Windows x64;macOS、自动更新、安装包签名在后续版本。 +- Windows x64 + macOS(universal:Apple Silicon + Intel 切片;实际硬件验证在 Apple Silicon 完成,Intel 未单独实测);自动更新、安装包签名在后续版本。 - 周文档为实验功能:同一周「重新生成」会覆盖手工修改(有二次确认,git 历史可找回)。 - 图谱视图与整体目标冲突,保持禁用;Agent 面板是占位,尚未接真实 CLI。 diff --git a/scripts/make-release.mjs b/scripts/make-release.mjs index 69efb5c..7acc10c 100644 --- a/scripts/make-release.mjs +++ b/scripts/make-release.mjs @@ -1,13 +1,16 @@ -// scripts/make-release.mjs — 把发布产物归拢到 release/(可重复执行,平台感知)。 -// Windows(win32):收 NSIS 安装包(ResearchThread__x64-setup.exe) -// macOS(darwin):收 DMG(native 或 universal-apple-darwin 构建产物,取最新) -// 共同输出:release/<安装包>、SHA256SUMS.txt、README.md、种子测试手册.md +// scripts/make-release.mjs — 把发布产物归拢到 release/(可重复执行,平台感知,跨平台可汇总)。 +// Windows(win32):收 NSIS 安装包 ResearchThread__x64-setup.exe +// macOS(darwin):只收 universal DMG(src-tauri/target/universal-apple-darwin/...), +// 严格匹配 productName+version,没有就失败——native/ARM-only DMG 不允许当发布物。 +// 汇总语义:release/ 内「当前版本」的安装包跨平台累积(本脚本只清理旧版本产物), +// SHA256SUMS.txt / README.md 每次按 release/ 内实际存在的安装包全量重算—— +// 把对侧安装包拷进 release/ 后重跑本脚本,即得覆盖双平台的统一校验和。 // 用法:先 npm run tauri build,再 npm run make-release。 // 发版要求与红线见 docs/release.md;本脚本强制:版本三处一致,其余为警告。 import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; -import { copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises"; +import { copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -16,6 +19,7 @@ const isMac = process.platform === "darwin"; const conf = JSON.parse(await readFile(join(root, "src-tauri/tauri.conf.json"), "utf8")); const version = conf.version; +const productName = conf.productName; const sha256 = async (path) => { const buf = await readFile(path); @@ -43,32 +47,28 @@ if (pkg.version !== version || cargoVersion !== version) { process.exit(1); } -// 2) 定位本平台的安装包 +// 2) 定位本平台安装包(严格版本匹配;macOS 只认 universal) +const installerRe = new RegExp(`^${productName}_${version}_.+\\.(exe|dmg)$`); async function findInstaller() { if (!isMac) { - const name = `ResearchThread_${version}_x64-setup.exe`; + const name = `${productName}_${version}_x64-setup.exe`; return { src: join(root, "src-tauri/target/release/bundle/nsis", name), name }; } - // native 构建在 target/release/,universal 在 target/universal-apple-darwin/release/ - const bundleRoots = [ - join(root, "src-tauri/target/universal-apple-darwin/release/bundle/dmg"), - join(root, "src-tauri/target/release/bundle/dmg"), - ]; + // 只扫 universal 目录:native/ARM-only DMG 不是发布物,绝不 fallback + const dmgDir = join(root, "src-tauri/target/universal-apple-darwin/release/bundle/dmg"); const found = []; - for (const dir of bundleRoots) { - try { - for (const f of await readdir(dir)) { - if (f.endsWith(".dmg")) { - const full = join(dir, f); - found.push({ src: full, name: f, mtime: (await stat(full)).mtimeMs }); - } + try { + for (const f of await readdir(dmgDir)) { + if (installerRe.test(f)) { + const full = join(dmgDir, f); + found.push({ src: full, name: f, mtime: (await stat(full)).mtimeMs }); } - } catch { - // 目录不存在(未做该形态构建) } + } catch { + // 目录不存在(未做 universal 构建) } if (found.length === 0) return null; - found.sort((a, b) => b.mtime - a.mtime); // 取最新(universal 优先构建时通常也更新) + found.sort((a, b) => b.mtime - a.mtime); return found[0]; } @@ -76,8 +76,8 @@ const installer = await findInstaller(); if (!installer) { console.error( isMac - ? "找不到 DMG 产物——先运行 npm run tauri build -- --bundles app,dmg" - : `找不到安装包 ResearchThread_${version}_x64-setup.exe——先运行 npm run tauri build`, + ? `找不到 universal DMG(${productName}_${version}_*.dmg @ src-tauri/target/universal-apple-darwin/release/bundle/dmg/)——先运行 npm run tauri build -- --target universal-apple-darwin --bundles app,dmg。native/ARM-only DMG 不能作为发布物。` + : `找不到安装包 ${productName}_${version}_x64-setup.exe——先运行 npm run tauri build`, ); process.exit(1); } @@ -116,78 +116,99 @@ if (staleSources.length > 0) { console.warn(`⚠ 安装包早于 ${staleSources.length} 个较新的源文件(如 ${staleSources[0]})——确认已重新 npm run tauri build`); } -// —— 生成 release/(整体覆盖,只保留最新版本) —— +// —— 生成 release/ —— const outDir = join(root, "release"); await mkdir(outDir, { recursive: true }); +// 清理旧版本产物(当前版本的安装包跨平台累积,不删对侧文件) +const oldArtifactRe = new RegExp(`^${productName}_\\d+\\.\\d+\\.\\d+_.+\\.(exe|dmg)$`); +for (const f of await readdir(outDir)) { + if (oldArtifactRe.test(f) && !installerRe.test(f)) { + await rm(join(outDir, f), { force: true }); + console.log(`已清理旧版本产物:${f}`); + } +} + const installerDest = join(outDir, installer.name); await copyFile(installer.src, installerDest); -const installerHash = await sha256(installerDest); -await copyFile(join(root, "docs/seed-manual.md"), join(outDir, "种子测试手册.md")); - -const manualHash = await sha256(join(outDir, "种子测试手册.md")); +// 收集 release/ 内当前版本的全部安装包(含对侧拷入的),统一重算校验和 +const bundled = (await readdir(outDir)) + .filter((f) => installerRe.test(f)) + .sort(); +const manualName = "种子测试手册.md"; +await copyFile(join(root, "docs/seed-manual.md"), join(outDir, manualName)); +const hashes = {}; +for (const f of [...bundled, manualName]) { + hashes[f] = await sha256(join(outDir, f)); +} await writeFile( join(outDir, "SHA256SUMS.txt"), - `${installerHash} ${installer.name}\n${manualHash} 种子测试手册.md\n`, + [...bundled, manualName].map((f) => `${hashes[f]} ${f}`).join("\n") + "\n", "utf8", ); const buildDate = new Date().toISOString().slice(0, 10); -const platformLabel = isMac ? "macOS(universal:Apple Silicon + Intel)" : "Windows x64"; -const verifyCmd = isMac - ? `shasum -a 256 ./${installer.name}` - : `Get-FileHash .\\${installer.name}`; -const installSteps = isMac - ? `1. 核对校验值:终端执行 \`${verifyCmd}\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 -2. 双击 DMG,把 ResearchThread 拖入「应用程序」。 -3. 首次启动(未签名构建)会被拦截,按系统版本放行(只需一次): - - macOS 15 及更高:弹「移到废纸篓 / 完成」时点「**完成**」,再打开 **系统设置 → 隐私与安全性**,底部点「**仍要打开**」并确认。 - - macOS 12–14:在「应用程序」里**右键 → 打开 → 打开**。 - 应用未来做 Developer ID 签名 + 公证后此步可免(docs/release.md §7)。` - : `1. 核对校验值:PowerShell 执行 \`${verifyCmd}\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 -2. 双击安装包。未签名会过 SmartScreen:**「更多信息」→「仍要运行」**,不需要关闭 SmartScreen。 -3. 首次启动应用会弹欢迎提示(数据位置 / 快照警告含义 / 反馈方式),照读一遍即可。`; -const uninstallNote = isMac - ? `卸载 = 把「应用程序」里的 ResearchThread 删除;\`~/ResearchThread\` 数据目录**不会**被删除。` - : `卸载不会删除数据目录;程序装在 \`%LOCALAPPDATA%\\ResearchThread\`(约 10 MB)。`; +const platformRows = bundled + .map((f) => + f.endsWith(".exe") + ? `| \`${f}\` | Windows x64 安装包(NSIS,per-user,免管理员;中文界面与安装须知页) |` + : `| \`${f}\` | macOS 安装映像(DMG,universal:Apple Silicon + Intel 切片;Intel 硬件未单独实测) |`, + ) + .join("\n"); +const verifyLines = bundled + .map((f) => `- \`${f}\`:\`${hashes[f]}\``) + .join("\n"); await writeFile( join(outDir, "README.md"), - `# ResearchThread ${version} 种子发布包(${platformLabel}) + `# ResearchThread ${version} 种子发布包 本目录为受控种子测试版发布物(scripts/make-release.mjs 生成,可重复执行)。 发版要求与红线见仓库 docs/release.md。 | 文件 | 说明 | |---|---| -| \`${installer.name}\` | 安装包${isMac ? "(DMG,universal 二进制)" : "(NSIS,per-user,免管理员;中文界面与安装须知页)"} | -| \`SHA256SUMS.txt\` | 校验值(发布渠道需原文公布) | -| \`种子测试手册.md\` | 种子用户手册:数据/快照/并发/隐私与脱敏反馈模板 | +${platformRows} +| \`SHA256SUMS.txt\` | 校验值(发布渠道需原文公布,覆盖下方全部安装包) | +| \`${manualName}\` | 种子用户手册:数据/快照/并发/隐私与脱敏反馈模板 | + +## 校验值(SHA-256) + +${verifyLines} ## 安装步骤 -${installSteps} +**Windows:** +1. 核对校验值:PowerShell 执行 \`Get-FileHash .\\<安装包>\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 +2. 双击安装包。未签名会过 SmartScreen:**「更多信息」→「仍要运行」**,不需要关闭 SmartScreen。 +3. 首次启动应用会弹欢迎提示(数据位置 / 快照警告含义 / 反馈方式),照读一遍即可。 + +**macOS:** +1. 核对校验值:终端执行 \`shasum -a 256 ./\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 +2. 双击 DMG,把 ResearchThread 拖入「应用程序」。 +3. 首次启动(未签名构建):先试右键 ResearchThread → 打开;macOS 15+ 已移除右键打开,改在终端执行 \`xattr -cr /Applications/ResearchThread.app\` 后正常启动。 +4. 应用为 universal 二进制(Apple Silicon + Intel 切片),实际硬件验证在 Apple Silicon 上完成,Intel 未单独实测——发布页按此口径说明。 ## 快速核对 -- 数据目录 \`~/ResearchThread\` 由应用首次启动创建,卸载**不会**删除。${uninstallNote} +- 数据目录 \`~/ResearchThread\` 由应用首次启动创建,卸载**不会**删除(Windows 卸载只删程序本体;macOS 删 .app 同理)。 - 每天首次启动自动 git 快照;无 git 机器会显示「快照保护未生效」持续警告(数据仍正常保存)。 ## 构建信息 - 版本:${version}(tauri.conf.json = package.json = Cargo.toml) -- 平台:${platformLabel} +- 本次生成平台:${isMac ? "macOS" : "Windows"}(另一平台安装包由对侧构建后拷入本目录重跑本脚本汇总) - 构建提交:\`${gitHead}\`${gitDirty ? "(⚠ 工作区不干净)" : ""} - 构建日期:${buildDate} -- ${installer.name} -- SHA-256:\`${installerHash}\` +- 本次归拢:${installer.name} `, "utf8", ); console.log(`release/ 已生成(构建提交 ${gitHead}${gitDirty ? ",工作区不干净" : ""}):`); -console.log(` ${installer.name} (SHA-256 ${installerHash})`); -console.log(` SHA256SUMS.txt / README.md / 种子测试手册.md`); +console.log(` 本次归拢 ${installer.name} (SHA-256 ${hashes[installer.name]})`); +console.log(` 覆盖安装包:${bundled.join(", ") || "(无)"}`); +console.log(` SHA256SUMS.txt / README.md / ${manualName}`); diff --git a/src-tauri/capabilities/common.json b/src-tauri/capabilities/common.json index 512d6ef..dc53722 100644 --- a/src-tauri/capabilities/common.json +++ b/src-tauri/capabilities/common.json @@ -28,8 +28,13 @@ { "path": "$HOME/ResearchThread/**" } ] }, - "opener:default", - "opener:allow-open-path", + { + "identifier": "opener:allow-open-path", + "allow": [ + { "path": "$HOME/ResearchThread" }, + { "path": "$HOME/ResearchThread/**" } + ] + }, "global-shortcut:allow-register", "global-shortcut:allow-unregister", "global-shortcut:allow-unregister-all", From aa9183e55d168af63add73826865e90bab224830 Mon Sep 17 00:00:00 2001 From: yr <194215355+Ychris12138@noreply.github.com> Date: Sat, 12 Sep 2026 16:20:57 +0800 Subject: [PATCH 6/6] =?UTF-8?q?docs:=20final=20review=20fixes=20=E2=80=94?= =?UTF-8?q?=20Gatekeeper=20flow=20in=20release=20README,=20release/=20copy?= =?UTF-8?q?-in=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - make-release README template: macOS 15+ first-launch guidance now matches the verified manual flow (System Settings -> Privacy & Security -> 'Open Anyway'), not xattr quarantine clearing - release.md: rule restated precisely -- platform installers may be copied into release/ as assembly input (then make-release MUST be re-run so SHA256SUMS covers the final artifact set); generated metadata (README/SHA256SUMS/manual copy) is never hand-edited --- docs/release.md | 6 +++--- scripts/make-release.mjs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/release.md b/docs/release.md index fce144b..0903a67 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,7 +1,7 @@ # 发布流程与边界(Release Playbook) > **每次打包发版前必读。** 本文件是发版的唯一规范;与代码冲突时以本文件为准修复代码。 -> `release/`(仓库根)是**唯一**发布产物位置,由 `npm run make-release` 生成——本目录不手工编辑、不手工投放文件。 +> `release/`(仓库根)是**唯一**发布产物位置,由 `npm run make-release` 生成——**生成型文件(README.md / SHA256SUMS.txt / 种子手册副本)绝不手工编辑**;对侧平台的安装包可以作为汇总输入拷入 `release/`,但拷入后**必须重跑** `npm run make-release` 重新生成校验和(见 §1 汇总语义)。 ## 0. 一句话流程 @@ -34,10 +34,10 @@ npm run make-release # ④ 同一脚本,darwin 下收 DMG | `SHA256SUMS.txt` | 安装包与手册的 SHA-256 | 脚本按 release/ 内实际安装包**全量重算**(跨平台统一,见下方汇总语义) | | `README.md` | 下载/校验/安装说明 + 构建提交记录 | 脚本模板 | | `种子测试手册.md` | 种子用户手册副本 | `docs/seed-manual.md`(只改源头,不改副本) | -- **汇总语义(消解双平台覆盖矛盾)**:脚本只清理「旧版本」安装包;**当前版本**的安装包跨平台累积——Windows 跑完把 dmg 拷进来重跑、或在 macOS 侧反向操作,`SHA256SUMS.txt` 都会自动覆盖双平台,无需手工拼校验和文件。 +- **汇总语义(消解双平台覆盖矛盾)**:脚本只清理「旧版本」安装包;**当前版本**的安装包跨平台累积——Windows 跑完把 dmg 拷进来重跑、或在 macOS 侧反向操作,`SHA256SUMS.txt` 都会自动覆盖双平台,无需手工拼校验和文件。**规则**:安装包(exe/dmg)可以作为汇总输入拷入 `release/`;除此之外的一切(README.md、SHA256SUMS.txt、手册副本)只能由脚本生成,手改 = 无效发布;任何拷入之后都必须重跑 `npm run make-release`,让校验和覆盖最终产物集合。 - macOS 侧**只认 universal DMG**(`target/universal-apple-darwin/...`),严格匹配 `productName + version` 文件名;native/ARM-only DMG 不是发布物,脚本直接失败、不 fallback。 - 历史(旧版本)不堆在 `release/`:发布后的安装包转存到发布渠道/网盘归档,靠 git tag 重新构建复现。 -- 手工改 `release/` 里任何文件 = 无效发布,下次生成会被覆盖。 +- 生成型 metadata(README / SHA256SUMS / 手册副本)手改 = 无效发布,下次生成会被覆盖;安装包拷入见「汇总语义」。 - Tauri 配置按平台拆分(构建时自动合并):`tauri.conf.json`(公共:窗口/CSP/图标)+ `tauri.windows.conf.json`(NSIS/WebView2Loader/须知页)+ `tauri.macos.conf.json`(app+dmg/最低 macOS 12)。**不要把平台特有配置写回公共文件。** ## 2. 版本规则 diff --git a/scripts/make-release.mjs b/scripts/make-release.mjs index 7acc10c..3af63b0 100644 --- a/scripts/make-release.mjs +++ b/scripts/make-release.mjs @@ -189,7 +189,7 @@ ${verifyLines} **macOS:** 1. 核对校验值:终端执行 \`shasum -a 256 ./\`,与 \`SHA256SUMS.txt\` 或发布页公布的值一致。 2. 双击 DMG,把 ResearchThread 拖入「应用程序」。 -3. 首次启动(未签名构建):先试右键 ResearchThread → 打开;macOS 15+ 已移除右键打开,改在终端执行 \`xattr -cr /Applications/ResearchThread.app\` 后正常启动。 +3. 首次启动(未签名构建):macOS 15+ 会弹「移到废纸篓 / 完成」——点「完成」关掉,再打开 **系统设置 → 隐私与安全性**,页面底部点「ResearchThread 已被阻止」旁的「**仍要打开**」确认;macOS 12–14:右键 ResearchThread → 打开 → 打开。不要关闭 Gatekeeper。 4. 应用为 universal 二进制(Apple Silicon + Intel 切片),实际硬件验证在 Apple Silicon 上完成,Intel 未单独实测——发布页按此口径说明。 ## 快速核对