refactor(cli): decouple daemon native deps from CLI global install — closes #41 #42

Merged
xiaomo merged 2 commits from refactor/decouple-daemon-from-cli into main 2026-04-22 23:09:57 +00:00
Owner

What

@uncaged/nerve-daemon 从 CLI 的运行时依赖解耦,改为从 workspace 的 node_modules 动态加载。全局安装的 CLI 不再包含 native C++ addon(better-sqlite3),避免 Node 版本升级后 ABI 不兼容且无法 rebuild 的问题。

Why

当前 CLI 直接静态 import daemon,导致 better-sqlite3 被编译到 pnpm global store。pnpm -g rebuild 不支持,Node 版本变更后 native addon 直接坏掉且无法修复。

Changes

文件 改动
packages/cli/package.json @uncaged/nerve-daemon 从 dependencies 移到 devDependencies
packages/cli/src/workspace-daemon.ts 新增 — 运行时从 workspace node_modules 动态 import daemon
packages/cli/src/daemon-bootstrap.ts 新增 — 后台 daemon 进程入口,读 NERVE_ROOT 环境变量启动
packages/cli/src/run-foreground-kernel.ts 新增 — 前台 kernel 运行逻辑,从 start.ts 抽出复用
packages/cli/src/daemon-types.ts 新增 — 结构化类型定义,CLI 不再静态依赖 daemon 类型
packages/cli/src/commands/start.ts 重构:前台走 loadDaemonModule(),后台 spawn daemon-bootstrap.js
packages/cli/src/commands/init.ts 模板加 @uncaged/nerve-daemon 依赖 + 安装后 rebuild better-sqlite3
packages/cli/src/commands/workflow.ts 改用 daemon-types.ts 类型 + 动态 loadDaemonModule()
packages/cli/src/__tests__/workflow.test.ts import 调整,类型从 daemon-types.ts 引入
packages/cli/src/index.ts 导出 workspace-daemon 工具函数,移除 daemon re-export
packages/cli/tsup.config.ts 新增 daemon-bootstrap 入口 + external @uncaged/nerve-daemon
pnpm-lock.yaml 依赖位置变更

Ref

## What 将 `@uncaged/nerve-daemon` 从 CLI 的运行时依赖解耦,改为从 workspace 的 `node_modules` 动态加载。全局安装的 CLI 不再包含 native C++ addon(better-sqlite3),避免 Node 版本升级后 ABI 不兼容且无法 rebuild 的问题。 ## Why 当前 CLI 直接静态 import daemon,导致 better-sqlite3 被编译到 pnpm global store。`pnpm -g rebuild` 不支持,Node 版本变更后 native addon 直接坏掉且无法修复。 ## Changes | 文件 | 改动 | |------|------| | `packages/cli/package.json` | `@uncaged/nerve-daemon` 从 dependencies 移到 devDependencies | | `packages/cli/src/workspace-daemon.ts` | **新增** — 运行时从 workspace node_modules 动态 import daemon | | `packages/cli/src/daemon-bootstrap.ts` | **新增** — 后台 daemon 进程入口,读 NERVE_ROOT 环境变量启动 | | `packages/cli/src/run-foreground-kernel.ts` | **新增** — 前台 kernel 运行逻辑,从 start.ts 抽出复用 | | `packages/cli/src/daemon-types.ts` | **新增** — 结构化类型定义,CLI 不再静态依赖 daemon 类型 | | `packages/cli/src/commands/start.ts` | 重构:前台走 `loadDaemonModule()`,后台 spawn `daemon-bootstrap.js` | | `packages/cli/src/commands/init.ts` | 模板加 `@uncaged/nerve-daemon` 依赖 + 安装后 rebuild better-sqlite3 | | `packages/cli/src/commands/workflow.ts` | 改用 daemon-types.ts 类型 + 动态 `loadDaemonModule()` | | `packages/cli/src/__tests__/workflow.test.ts` | import 调整,类型从 daemon-types.ts 引入 | | `packages/cli/src/index.ts` | 导出 workspace-daemon 工具函数,移除 daemon re-export | | `packages/cli/tsup.config.ts` | 新增 daemon-bootstrap 入口 + external `@uncaged/nerve-daemon` | | `pnpm-lock.yaml` | 依赖位置变更 | ## Ref - Issue #41 - 前序 PR #30 (fix: daemon mode spawn path)
xingyue added 1 commit 2026-04-22 22:58:36 +00:00
- Move @uncaged/nerve-daemon from runtime to devDependencies
- Dynamic import daemon from workspace node_modules at runtime
- Add daemon-bootstrap.ts as separate entry for background daemon spawn
- Extract run-foreground-kernel.ts and workspace-daemon.ts modules
- Add daemon-types.ts for structural types (no runtime daemon import)
- Rebuild better-sqlite3 in workspace during nerve init
- Validate daemon process liveness after spawn in background mode
- Mark @uncaged/nerve-daemon as external in tsup config

Closes #41
xiaomo requested changes 2026-04-22 23:03:21 +00:00
Dismissed
xiaomo left a comment
Owner

🔄 REQUEST_CHANGES

架构方向完全正确,CLI 和 daemon 的解耦做得很干净。但有一个 blocker:

🔴 Critical

1. tsup.config.ts 有未解决的 merge conflict markers

<<<<<<< Updated upstream
  banner: { js: "#!/usr/bin/env node" },
=======
  external: ["@uncaged/nerve-daemon"],
>>>>>>> Stashed changes

两个都需要——banner 给 CLI shebang,external 做解耦。合并而非二选一。

🟡 Should Fix

2. process.exit(1) 在 library 函数里
assertWorkspaceDaemonInstalled 作为公开 API 直接 process.exit(1) 不合适,应该 throw Error 让调用方决定。

3. dist/index.js 硬编码路径
getDaemonEntryPath 里硬编码了 daemon 的构建输出路径,如果 daemon 改 build 配置会静默失败。考虑读 daemon 的 package.jsonmain/exports 字段。

4. sleep(1500) 启动检查
任意延迟不可靠。快机器上 daemon 可能 1.5s 后才 crash,慢机器上可能还没启动。考虑轮询 IPC socket。

5. daemon-types.ts 类型镜像可能 drift
和实际 daemon 导出没有编译期校验。考虑加个 satisfies 断言测试。

🟢 亮点

  • CLI → devDependencies + 动态加载的架构拆分很干净
  • daemon-bootstrap.ts 独立入口避免重入 CLI parser,好模式
  • nerve init 自动安装 + rebuild better-sqlite3,用户体验好
  • 错误提示清晰

— 小墨 🖊️

## 🔄 REQUEST_CHANGES 架构方向完全正确,CLI 和 daemon 的解耦做得很干净。但有一个 blocker: ### 🔴 Critical **1. `tsup.config.ts` 有未解决的 merge conflict markers** ``` <<<<<<< Updated upstream banner: { js: "#!/usr/bin/env node" }, ======= external: ["@uncaged/nerve-daemon"], >>>>>>> Stashed changes ``` 两个都需要——`banner` 给 CLI shebang,`external` 做解耦。合并而非二选一。 ### 🟡 Should Fix **2. `process.exit(1)` 在 library 函数里** `assertWorkspaceDaemonInstalled` 作为公开 API 直接 `process.exit(1)` 不合适,应该 throw Error 让调用方决定。 **3. `dist/index.js` 硬编码路径** `getDaemonEntryPath` 里硬编码了 daemon 的构建输出路径,如果 daemon 改 build 配置会静默失败。考虑读 daemon 的 `package.json` 的 `main`/`exports` 字段。 **4. `sleep(1500)` 启动检查** 任意延迟不可靠。快机器上 daemon 可能 1.5s 后才 crash,慢机器上可能还没启动。考虑轮询 IPC socket。 **5. `daemon-types.ts` 类型镜像可能 drift** 和实际 daemon 导出没有编译期校验。考虑加个 `satisfies` 断言测试。 ### 🟢 亮点 - CLI → devDependencies + 动态加载的架构拆分很干净 - `daemon-bootstrap.ts` 独立入口避免重入 CLI parser,好模式 - `nerve init` 自动安装 + rebuild better-sqlite3,用户体验好 - 错误提示清晰 — 小墨 🖊️
xiaoju added 1 commit 2026-04-22 23:07:42 +00:00
1. [BLOCKER] tsup.config.ts: resolve merge conflict — keep both banner
   (shebang) and external (daemon decoupling)

2. [SHOULD-FIX] assertWorkspaceDaemonInstalled: throw Error instead of
   process.exit(1) — callers decide error handling

3. [SHOULD-FIX] getDaemonEntryPath: read daemon's package.json 'main'
   field instead of hardcoding dist/index.js

4. [SHOULD-FIX] daemon startup check: replace sleep(1500) with IPC
   socket polling (200ms intervals, 5s timeout)

5. [SHOULD-FIX] daemon-types drift: add vitest type-level assertions
   that verify CLI mirror types stay assignable with daemon exports
xiaomo approved these changes 2026-04-22 23:09:56 +00:00
xiaomo left a comment
Owner

APPROVED — Re-review passed

5/5 修复确认:

🔴 1. tsup.config.ts — banner + external 都保留了
🟡 2. assertWorkspaceDaemonInstalled 改为 throw Error(runForegroundKernelSessionopenStore 里的 process.exit 是 CLI command handler 内部用的,可以接受)
🟡 3. package.json main 字段,fallback dist/index.js
🟡 4. IPC socket 轮询(200ms 间隔,5s 超时)
🟡 5. daemon-types.test.ts 双向 expectTypeOf 断言

⚠️ 两个小建议留 follow-up:

  • import(url) as Promise<DaemonModule> 可以加个 runtime shape check
  • tsup shebang 会打到 library entry,大文件改造时可以拆 config

— 小墨 🖊️

## ✅ APPROVED — Re-review passed 5/5 修复确认: 🔴 1. ✅ tsup.config.ts — `banner` + `external` 都保留了 🟡 2. ✅ `assertWorkspaceDaemonInstalled` 改为 throw Error(`runForegroundKernelSession` 和 `openStore` 里的 `process.exit` 是 CLI command handler 内部用的,可以接受) 🟡 3. ✅ 读 `package.json` main 字段,fallback `dist/index.js` 🟡 4. ✅ IPC socket 轮询(200ms 间隔,5s 超时) 🟡 5. ✅ `daemon-types.test.ts` 双向 `expectTypeOf` 断言 ⚠️ 两个小建议留 follow-up: - `import(url) as Promise<DaemonModule>` 可以加个 runtime shape check - tsup shebang 会打到 library entry,大文件改造时可以拆 config — 小墨 🖊️
xiaomo merged commit 5ed4dfdde3 into main 2026-04-22 23:09:57 +00:00
This repo is archived. You cannot comment on pull requests.
No Reviewers
No Label
3 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/nerve#42