feat: botmux update 支持本地 checkout 走 git pull + 构建 + 重启(CLI 与 Dashboard) - #930
Open
deepcoldy wants to merge 5 commits into
Open
feat: botmux update 支持本地 checkout 走 git pull + 构建 + 重启(CLI 与 Dashboard)#930deepcoldy wants to merge 5 commits into
deepcoldy wants to merge 5 commits into
Conversation
Owner
Author
复审修复已推(commit 072d5f5)按复审意见收敛 4 处边界:
已知语义(本 PR 不扩范围):restart intent 仍按 version 判 update/manual,同 tag 不同 SHA 会落 manual intent,只影响 owner「已更新到 vX」DM 的措辞,不影响更新/build/lease/实际重启。 测试: |
原来 `botmux update` 只认 npm/pnpm/bun 全局安装,跑在本地 checkout(有 .git/src) 时会报「无法安全识别安装方式」退出。现给 cmdUpgrade 补一条本地开发分支: - 定位运行目录:优先解析全局瘦 wrapper(~/.botmux/bin/botmux)里指向的 dist/cli.js,往上两级得 checkout 根;解析不到回退到本进程安装根,再校验确是 git 工作树。逻辑抽成纯函数 src/utils/local-dev-update.ts 便于单测。 - 更新四步(在该目录内):git status --porcelain 有未提交改动即 fail closed 中止并提示(不自动 stash);git pull --ff-only(分叉/冲突直接报错停下,不 自动 merge);pnpm build(dist/ 被 gitignore,只 pull 不 build 重启后仍跑旧 代码,故不可省);node <dir>/dist/cli.js restart 从本 checkout 重启。 影响面:纯 CLI 侧改动,只动 cmdUpgrade 的本地分支 + 新增一个 utils 纯函数; 不动 npm/pnpm/bun 线上升级路径,也不动 maintenance 定时器与任何 worker/后端 /PTY 共用代码。跨平台:git/pnpm 调用在 win32 走 shell:true 解析 .cmd。 测试: - 新增 test/local-dev-update.test.ts(7 例,wrapper 文本解析 + 目录推导) - 更新 test/cli-update-alias.test.ts:断言改为新的本地分支行为(横幅 + update==upgrade 别名契约不变) - 实测:脏工作区正确中止不 stash;隔离仓库验证 ff-only 快进;wrapper 定位 到正确 checkout - pnpm build 通过;相关单测全绿(其余环境类失败在 stash 后基线上同样存在,与本 改动无关)
延续 CLI cmdUpgrade 的本地开发更新能力,把同一套逻辑接到 Dashboard「版本与 更新」卡片:本地 checkout(git 工作树)现在也能一键 git pull --ff-only + 构建 + 重启,不再只是置灰提示「请手动 git pull」。 共享层(src/utils/local-dev-update.ts): - 把 checkout 定位(解析 ~/.botmux/bin/botmux wrapper → dist/cli.js → 上两级)、 git 干净检查、HEAD 读取、更新步骤定义抽成共享纯/薄函数,CLI 与 dashboard 共用,避免两边逻辑漂移。CLI cmdUpgradeLocalDev 改为复用这些函数。 Dashboard 后端(dashboard.ts): - POST /api/update/run:local-dev 分支改为在 install 锁内执行 git 干净检查 (脏工作区 fail closed,返回 dirty_worktree + 文件列表,不 stash)→ git pull --ff-only(分叉/冲突报错停下)→ pnpm build(300s 超时、捕获输出); 用 HEAD 前后 sha 判断 changed;版本走 resolveCurrentVersion(git describe)。 复用既有 updateInFlight / 跨进程锁 / restart-lease 门禁。 - GET /api/update/status:新增 localDevUpdatable(local-dev 且定位到 git 工作树)。 - POST /api/update/restart:local-dev 从 resolveLocalDevCheckoutDir 重启, 与 CLI 一致(不再只用 dashboard 进程自身 cli.js)。 - rollback 对 local-dev 仍拒绝(本地开发无回滚语义)。 Dashboard 前端(settings-page.tsx / i18n.ts): - localDevUpdatable 时启用按钮,文案「本地更新(git pull + 构建 + 重启)」, 走既有 run→restart 两步流;dirty_worktree 错误在 UI 回显改动文件列表。 - 新增 zh/en 文案:本地可更新提示、确认框、进行中提示、脏工作区提示等。 影响面:改动集中在版本更新链路(CLI cmdUpgrade + dashboard /api/update/* + 更新卡片 UI)。不动 npm/pnpm/bun 线上升级路径与 maintenance 定时器(后者对 local-dev 仍 skip);不涉及 worker/后端/PTY/IM 共用代码。跨平台:git/pnpm 子进程在 win32 走 shell:true 解析 .cmd。 测试: - test/local-dev-update.test.ts:纯函数 + 真实 git 仓库验证 isGitWorktree / gitPorcelainStatus(干净/脏)/ gitHeadSha(跨 commit 变化)/ 更新步骤定义,共 11 例 - test/cli-update-alias.test.ts:按新本地分支行为更新断言(update==upgrade 别名契约不变) - test/dashboard-update-action.test.ts 现有 8 例仍通过(响应字段形状未变) - pnpm build + tsc --noEmit 通过;新 i18n key 与后端路由字符串确认已进 dist bundle - CLI 本地分支实测:脏工作区正确中止、ff-only 快进(隔离仓库)、wrapper 定位正确
回应对本地 checkout 更新路径的复审,修 4 个边界问题: 1. wrapper 解析收紧,避免更新错仓库:parseWrapperCliEntry 只在 `exec … node …` 行上匹配以 `dist/cli.js`(`[\\/]` 兼容 Windows 分隔符)结尾的目标,注释里的 其它 `.js`(如 `# preload "…/hook.js"`)不再被当成 CLI。resolveLocalDevCheckoutDir 进一步用 isBotmuxCheckout 做 repo 身份校验(`.git` + package.json.name==="botmux"), 非 botmux 仓库回退到 running root。**不**要求 dist 已存在——use:here 允许先有 checkout 后 build,本地更新正是要产出那个 dist。 2. build 成功后始终重启:local-dev 只 build 不动 HEAD(checkout 已被手动 pull 过) 时,旧逻辑因 changed=false 跳过重启,导致新 dist 不生效、与按钮承诺不符。/api/update/run local-dev 分支恒返回 restartRequired:true;前端改用 updateResponseNeedsRestart (restartRequired || changed)决定是否重启,changed 仅用于展示。 3. restart 前校验目标 cli.js 存在:/api/update/restart 对 local-dev 用 checkout 目标前,先校验 botmuxCliEntryAt(target) 存在;否则回退 running root。避免 wrapper 指向尚未 build 的 checkout 时,重启驱动 spawn 出 pid 又立刻因模块缺失退出、 restart lease 得不到清理(UI 空等 90s 且后续重启被挡)。 4. 版本按实际更新的 checkout 读:新增 resolveCurrentVersionAt(dir),runLocalDevUpdate 返回该 checkout 的 old/new version,不再混用 running root 的版本(wrapper→B、 dashboard 跑 A 时会显示错版本)。 已知语义(本 PR 不扩范围):restart intent 仍按 version 判 update/manual,同 tag 不同 SHA 会落 manual intent,只影响 owner「已更新到 vX」DM 的措辞,不影响更新/ build/lease/实际重启。 测试: - local-dev-update.test.ts 补:注释假路径负测、非 dist/cli.js 目标负测、Windows 分隔符正测、isBotmuxCheckout(.git + name 双条件、无 .git 拒绝) - dashboard-update-action.test.ts 补 updateResponseNeedsRestart 三例,锁住 changed=false && restartRequired=true 仍重启的核心回归 - pnpm build + tsc --noEmit 通过;相关单测 47 例全过;CLI 本地分支实测仍正确 (定位到真实 checkout、脏工作区中止)
二轮复审指出上一版 parseWrapperCliEntry 只判断行内出现 exec/node,未锚定为该行 实际执行的命令:`# old: exec node ".../dist/cli.js" "$@"` 这类「保留旧命令作注释」 写法,以及 `echo exec node "..."`,仍会被选中——若旧路径也是 botmux checkout, 后续 repo 身份校验照样通过,最终更新/重启错 checkout。 改为按本仓库生成 wrapper 的固定形状精确匹配:行首(仅允许前导空白)`exec` → `node`/绝对 node 路径 → 引号包裹的以 `dist/cli.js`(`[\\/]` 兼容 Windows)结尾的 路径 → 结尾 `"$@"`。锚定行首 + 要求尾部 `"$@"` 直接排除注释行与 echo 行。 测试:补「注释掉的旧 exec 行在真 exec 前 → 返回真路径」「echo exec 行 → null」 「缺尾部 $@ → null」负测;原有正例(标准/绝对 node/Windows 分隔符)保留。 build + tsc 通过;解析相关 30 例全过;对线上实际 wrapper 解析定位仍正确。
上一版正则允许捕获相对路径(如 `exec node "nested/botmux/dist/cli.js" "$@"`)。 相对路径在 shell exec 时相对调用者 cwd 解析,而 Dashboard/CLI resolver 在这里对它 做 dirname/repo 身份校验时相对的是读取方进程的 cwd——两者 cwd 不同就会解释成不同 checkout,Dashboard cwd 下恰有同名 botmux repo 时仍可能更新错目标。函数注释与前序 commit 也都承诺只接受绝对路径。 parseWrapperCliEntry 捕获后加 `posix.isAbsolute(entry) || win32.isAbsolute(entry)` 校验,不绝对则跳过该行。跨平台:POSIX `/…` 与 Windows drive(`C:\…`)/UNC 前缀都算绝对。 测试:补相对路径(`nested/…` 与 `./dist/cli.js`)→ null 负测;原有绝对路径正例 (POSIX/绝对 node 路径/Windows 分隔符)保留。build + tsc 通过;解析相关单测全过。
deepcoldy
force-pushed
the
feat/local-dev-git-update
branch
from
August 18, 2026 16:45
514bb3c to
dec2745
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景 / 为什么
原来
botmux update(=upgrade别名)只认 npm/pnpm/bun 全局安装。跑在本地checkout(有
.git/src)时:pull + 构建后重启」。
本 PR 给「本地开发从源码运行」这种部署补上一键更新:定位 checkout → git pull
--ff-only → 构建 → 重启,CLI 与 Dashboard 都支持。
改了什么
共享层
src/utils/local-dev-update.ts把 checkout 定位、git 干净检查、HEAD 读取、更新步骤定义抽成共享纯/薄函数,CLI 与
Dashboard 共用,避免逻辑漂移:
~/.botmux/bin/botmux里指向的dist/cli.js→ 往上两级得 checkout 根(那才是用户实际敲
botmux跑的目录),拿不到回退到本进程安装根,再校验是 git 工作树;
localDevUpdateSteps():git pull --ff-only→pnpm build(重启单独由各调用方施加)。CLI(
src/cli.ts)cmdUpgrade检测到本地 checkout 走新分支cmdUpgradeLocalDev:git status --porcelain有未提交改动 → fail closed 中止并列出改动(不自动 stash);git pull --ff-only(分叉/冲突直接报错停下,不自动 merge);pnpm build(关键:dist/被 gitignore,只 pull 不 build 重启后跑的还是旧代码);node <checkout>/dist/cli.js restart从本 checkout 重启(不走 PATH,避免被更靠前的全局 botmux 抢先)。Dashboard 后端(
src/dashboard.ts)POST /api/update/run:local-dev 分支不再拒绝,改为在既有 install 锁内执行git 干净检查(脏工作区返回
dirty_worktree+ 文件列表,不 stash)→git pull --ff-only→pnpm build(300s 超时、捕获输出);用 HEAD 前后 sha 判断changed;版本走
resolveCurrentVersion(git describe)。复用既有updateInFlight/ 跨进程锁 / restart-lease 门禁;GET /api/update/status:新增localDevUpdatable(本地开发且定位到 git 工作树才为 true);POST /api/update/restart:local-dev 从resolveLocalDevCheckoutDir()重启,与 CLI 一致;rollback对 local-dev 仍拒绝(本地开发无回滚语义)。Dashboard 前端(
settings-page.tsx/i18n.ts)localDevUpdatable时按钮启用,文案「本地更新(git pull + 构建 + 重启)」,走既有run→restart 两步流;
dirty_worktree错误在 UI 回显改动文件列表;影响面
改动集中在版本更新链路:CLI
cmdUpgrade+ Dashboard/api/update/*+ 更新卡片 UI。git/pnpm子进程在 win32 走shell:true解析.cmdshim;daemon 实际跑在 Linux,路径/进程调用两边都已考虑。
测试
pnpm build+pnpm exec tsc --noEmit通过;test/local-dev-update.test.ts(11 例):wrapper 文本解析 + 目录推导 + 更新步骤定义,并用真实 git 仓库验证
isGitWorktree/gitPorcelainStatus(干净/脏)/gitHeadSha(跨 commit 变化);test/cli-update-alias.test.ts:按新本地分支行为更新断言(update==upgrade别名契约不变);test/dashboard-update-action.test.ts现有 8 例仍通过(响应字段形状未变);distbundle;git pull --ff-only快进(隔离仓库验证)、wrapper 定位到正确 checkout。
待 live 验证
Dashboard「本地更新」按钮的端到端点击需要一个「从本 checkout 跑起来的 dashboard」,
部署会重启 daemon,留到 review 后 live 验证并补 UI 截图。