11 KiB
Proposal M5: landing/ 与 desktop app 拆分(pnpm workspace 或独立仓)
来源:问题.md M5(行 248-252)、附录 F 第 8 条(行 541-542) 状态:proposed 提出:Roundtable 2026-04-20(Linus + Karpathy + Steve Jobs) Owner: TBD 估计工时:1 周(workspace 方案)或 2 周(独立仓方案) Blocking dependencies:无
问题陈述
landing/ 目录是独立的 marketing site(Next.js + Cloudflare Functions),自带:
- 独立
package.json - 独立
node_modules - 独立 build 流程
- 独立 deploy(Cloudflare Pages)
- 独立的 SQL migration(
landing/migrations/)
和 desktop app(Electron + React)混在同一个 git repo 里。scripts/generate-landing-release-links.mjs 做跨项目协调,暗示两个产品要手动同步某些 artifact。
问题表现:
- 贡献者 onboarding 费劲:clone 后不知道要在根目录还是 landing/ 跑 pnpm install;CI 要跑哪个
- release 耦合:desktop app 发版和 landing 发版必须同时推到同一个 commit——其中一个有问题就拖另一个
- 依赖污染:根
node_modules和landing/node_modules可能安装同一个包的不同版本 - CI 资源浪费:改一个 landing 文件触发整个 desktop 的 typecheck/test
- 安全面混淆:landing 的 Cloudflare token、数据库 URL 和 desktop 的签名证书放在同一个仓库,权限边界模糊
根因分析
Linus 视角:一个 repo 两个产品 = release 耦合。
Monorepo 本身不是错——Google / Facebook 用 monorepo。但 monorepo 需要工具层面的约束:
- 统一 task runner(nx / turborepo / bazel)
- 明确的 workspace 划分
- 共享依赖 hoisting 策略
- CI 按 changed-files 触发
当前项目有 pnpm 但没 workspace 配置,landing 实际上就是"目录塞在这里的另一个项目",没有 monorepo 的任何好处——只有成本。
Karpathy 视角补充:AI coding agent 在这种结构下很容易在 desktop 任务里改到 landing 文件,或反之。隔离不清楚的 repo 让 agent 的 blast radius 变大。
设计方案
Option A:pnpm workspace(推荐,低成本)
目标结构:
AgentCompany/
├── package.json # root (workspace root, 无 runtime 代码)
├── pnpm-workspace.yaml # 新增
├── packages/
│ ├── desktop/ # 当前的根内容移到这里
│ │ ├── package.json
│ │ ├── src/
│ │ ├── tests/
│ │ └── ...
│ └── landing/ # 当前的 landing/ 移到这里
│ ├── package.json
│ ├── app/
│ └── ...
├── pi-mono-reference/ # 保留在 root(现状)
└── docs/
pnpm-workspace.yaml:
packages:
- "packages/*"
Root package.json(变简):
{
"name": "agentcompany-root",
"private": true,
"scripts": {
"desktop:dev": "pnpm --filter desktop dev",
"desktop:build": "pnpm --filter desktop build",
"landing:dev": "pnpm --filter landing dev",
"landing:build": "pnpm --filter landing build",
"typecheck": "pnpm -r typecheck",
"lint": "pnpm -r lint"
}
}
CI 按 package 分跑:desktop PR 只跑 desktop typecheck/test,landing PR 只跑 landing。
好处:
- 两个项目独立
package.json、独立版本 - 根目录干净
- 共享开发工具(eslint / tsconfig base)可提到 root
- pnpm 自动 hoist 共享依赖,磁盘占用下降
成本:
- git history 移动(git mv 保留 blame)
- CI 脚本改写(
scripts/要调整路径引用) - 发布流程(electron-builder / cloudflare deploy)适配新路径
Option B:独立仓(彻底隔离)
把 landing/ 推到独立 repo(例如 github.com/badlogic/agentcompany-landing),现 repo 只保留 desktop。
好处:
- 完全隔离 CI / secrets / 权限
- landing 团队可独立迭代不看 desktop 代码
- desktop 的 code review 范围缩小
成本:
- 跨 repo 协调(release link 生成脚本要走 GitHub API)
- Onboarding 要 clone 两个 repo
- git history 完整迁移工作量
推荐 A
- 当前团队规模不需要完全隔离
- landing 改动相对少,一个 repo 能保持 cross-cutting 改动(例如品牌一致性)的原子性
- workspace 成本最低,收益立刻兑现
Migration 计划(Option A)
- Phase 1(1 天):新建
packages/desktop/空目录 +packages/landing/;pnpm-workspace.yaml - Phase 2(1 天):
git mv把当前根代码移到packages/desktop/,landing/ 移到packages/landing/- 根
package.json拆成 root + desktop - 根配置(
tsconfig.base.json/eslint.config.mjs)保留 root
- 根
- Phase 3(1 天):修
scripts/*里的硬编码路径(scripts/release-*.mjs、scripts/build-*.mjs) - Phase 4(1 天):CI 配置分包跑
- Phase 5(1 天):文档同步——README 更新;
CLAUDE.md/AGENTS.md说明 workspace 结构 - Phase 6(持续):观察 2 周,处理零星问题
风险与回滚
| 风险 | 触发条件 | 回滚方法 |
|---|---|---|
| git history 丢失 | git mv 不规范 | 用 git mv 保留 rename detection;必要时 git log --follow 验证 |
| electron-builder 找不到 artifact | 打包路径变化 | Phase 3 脚本改完后跑一次完整 build 验证 |
| Cloudflare deploy 路径改 | landing build output 位置变 | wrangler.toml 更新;deploy 前本地 dry-run |
| renderer import path 断 | 代码中的硬路径 | codemod 扫 require("../../landing") 类的跨 package 引用(理论上没有) |
回滚路径:phase 2 是单个 commit(git mv),任何后续阶段出问题直接 revert。
验证标准
- 独立 build 成功:
pnpm --filter desktop build和pnpm --filter landing build分别跑通 - 依赖完整:
pnpm install根目录跑完pnpm -r typecheck全绿 - CI 提速:改 landing 文件的 PR 不触发 desktop test
- Release 脚本:
pnpm run release:local/pnpm release:boss/pnpm release:cloudflare:public都能跑 - Git blame 保留:
git log --follow packages/desktop/src/main/service-graph.ts能追到原始 commit
不在范围
- 不引入 turborepo / nx:pnpm workspace 已足够,加 task runner 是过度工程
- 不改代码模块边界:desktop 内部代码结构本 proposal 不动
- 不做跨 package 共享代码:landing 目前不需要 desktop 的类型;如果未来需要再加
packages/shared/ - 不动 pi-mono-reference/:那是第三方 reference 副本,保留在 root
- 不改 git branching strategy:单仓单分支 vs 多分支不是本 proposal 问题
参考文件清单
landing/package.json(独立 package 定义)landing/migrations/(独立 SQL migration)scripts/generate-landing-release-links.mjs(跨项目脚本)scripts/release-public-cloudflare.mjs(landing 发布)scripts/release-boss.mjs(desktop 发布)- 根
package.json:1-86(会被拆分) - 根
pnpm-lock.yaml(workspace 化后会更长但更清晰)
附:为什么现在修
延迟成本:landing 每加一个 dependency 就让根 node_modules 更大;desktop 的 CI 时间持续上升。现在拆 1 天,拖到 landing 翻倍大小就是 3 天+。
对 AI coding agent 来说,workspace 化后"这个改动属于哪个 package"一目了然——agent 不再会在 desktop 任务里 accidentally 修 landing 文件。这是隔离 blast radius 的最低成本方式。
次要触发:M5 和 M4(scripts 目录混乱)相关——scripts 里的 release-public-cloudflare.mjs / release-boss.mjs / release-local.mjs 混在一起部分就是因为 desktop/landing 不分。拆了 workspace,scripts 也可以按 package 分。
附:gating matrix
| Gate | 条件 | 放行判断人 |
|---|---|---|
| G1 | pnpm-workspace.yaml 存在,pnpm install 在根目录一次跑通 |
Owner |
| G2 | pnpm --filter desktop build 产出可签名的 .app bundle |
Owner |
| G3 | pnpm --filter landing build 产出可部署的 Cloudflare Pages 资源 |
Owner |
| G4 | CI 按 changed files 触发相应 package 测试 | Owner |
| G5 | git log --follow 在两个 package 内都能追到原始 commit | Linus |
附:迁移后的目录结构草图
AgentCompany/
├── package.json # workspace root
├── pnpm-workspace.yaml
├── pnpm-lock.yaml # hoisted
├── tsconfig.base.json # shared
├── eslint.config.mjs # shared
├── .gitignore
├── CLAUDE.md / AGENTS.md
├── docs/
├── pi-mono-reference/ # 保留根(第三方 reference)
├── scripts/
│ ├── desktop/ # electron-builder / signing / release
│ └── landing/ # cloudflare deploy / release-links
└── packages/
├── desktop/
│ ├── package.json
│ ├── src/
│ │ ├── main/
│ │ ├── preload/
│ │ ├── renderer/
│ │ └── shared/
│ ├── tests/
│ ├── builtin-skills/
│ ├── electron-vite.config.ts
│ └── tsconfig.json # extends ../../tsconfig.base.json
└── landing/
├── package.json
├── app/
├── migrations/
├── next.config.js
└── tsconfig.json
附:为什么不选其他方案
方案 A(当前 proposal):pnpm workspace,一个仓两个 package。
方案 B(独立仓):对当前团队规模不经济;跨仓协调成本高。
方案 C(保持现状 + 更多 gitignore 隔离):治标不治本;CI 仍跑全量。
方案 A 成本 1 周,收益持续。
附:release 脚本改造示意
// packages/desktop/package.json scripts 片段
{
"scripts": {
"build": "electron-vite build && node ../../scripts/desktop/build-desktop.mjs",
"release:local": "node ../../scripts/desktop/release-local.mjs",
"release:boss": "node ../../scripts/desktop/release-boss.mjs"
}
}
// packages/landing/package.json scripts 片段
{
"scripts": {
"build": "next build",
"release": "node ../../scripts/landing/release-cloudflare.mjs"
}
}
// root package.json
{
"scripts": {
"release:all": "pnpm --filter desktop release:boss && pnpm --filter landing release"
}
}
附:CI 按 changed files 触发(示意)
# .github/workflows/desktop-ci.yml
on:
pull_request:
paths:
- "packages/desktop/**"
- "pnpm-lock.yaml"
- "tsconfig.base.json"
# .github/workflows/landing-ci.yml
on:
pull_request:
paths:
- "packages/landing/**"
desktop PR 不触发 landing CI,反之亦然。