文章目录
- 前言
-
- [📦 **Monorepo 结构详解**](#📦 Monorepo 结构详解)
-
- [**1. 根目录 package.json 的角色**](#1. 根目录 package.json 的角色)
- [**2. 子包 package.json 的角色**](#2. 子包 package.json 的角色)
-
- [**A. gitnexus/ (CLI + MCP 服务器) --- 唯一发布到 npm 的包**](#A. gitnexus/ (CLI + MCP 服务器) — 唯一发布到 npm 的包)
- [**B. gitnexus-web/ (React UI) --- 也是私有包**](#B. gitnexus-web/ (React UI) — 也是私有包)
- [**C. gitnexus-shared/ (共享类型库)**](#C. gitnexus-shared/ (共享类型库))
- [**D. eval/ (Python 评估框架)**](#D. eval/ (Python 评估框架))
- [🔗 **依赖链接方式**](#🔗 依赖链接方式)
-
- [**"file:" 协议 (本地文件链接)**](#"file:" 协议 (本地文件链接))
- [📊 **完整依赖图**](#📊 完整依赖图)
- [🛠️ **日常开发工作流**](#🛠️ 日常开发工作流)
-
- [**场景 1:修改 gitnexus-shared 中的类型**](#场景 1:修改 gitnexus-shared 中的类型)
- [**场景 2:本地运行 CLI**](#场景 2:本地运行 CLI)
- [**场景 3:本地运行 Web UI**](#场景 3:本地运行 Web UI)
- [**场景 4:发布新版本到 npm**](#场景 4:发布新版本到 npm)
- [📋 **Monorepo 和 npm workspaces 的区别**](#📋 Monorepo 和 npm workspaces 的区别)
-
- [**GitNexus 的方式(手动链接)**](#GitNexus 的方式(手动链接))
- [**如果使用 npm workspaces**](#如果使用 npm workspaces)
- [❓ **常见问题**](#❓ 常见问题)
-
- [**Q: 为什么 gitnexus-web 也叫 "gitnexus"?**](#Q: 为什么 gitnexus-web 也叫 "gitnexus"?)
- [**Q: npm install 时发生了什么?**](#Q: npm install 时发生了什么?)
- [**Q: 为什么不用 Lerna 或 pnpm?**](#Q: 为什么不用 Lerna 或 pnpm?)
- [🎯 **总结:这个项目的 Monorepo 策略**](#🎯 总结:这个项目的 Monorepo 策略)
前言
简短回答
不使用 Lerna / pnpm workspace / yarn workspace
而是使用 npm 原生的 workspaces 特性 + 手动依赖链接 (file:../)
📦 Monorepo 结构详解
1. 根目录 package.json 的角色
json
{
"name": "gitnexus-monorepo", // 标识这是一个 monorepo
"private": true, // 🔑 重要:不发布根包到 npm
"scripts": {
"format": "prettier --write .", // 根级全局命令
"lint": "eslint .",
"gitnexus:refresh": "gitnexus analyze --embeddings --skills"
},
"devDependencies": {
// 🔑 关键:所有包共享这些开发工具
"@typescript-eslint/eslint-plugin": "^8.57.2",
"eslint": "^9.39.4",
"prettier": "^3.8.0",
"husky": "^9.1.7",
"lint-staged": "^15.5.0"
}
}
作用:
- ✅ 定义整个 monorepo 的 lint/format/prepare 规则
- ✅ 所有子包都继承这些 devDependencies
- ✅
private: true确保根包不会被发布到 npm
2. 子包 package.json 的角色
A. gitnexus/ (CLI + MCP 服务器) --- 唯一发布到 npm 的包
json
{
"name": "gitnexus", // 🔑 这个名字会发布到 npm
"version": "1.6.5", // 当前版本
"private": false, // ❌ 不设置,默认公开
"type": "module", // ESM 模块格式
"bin": {
"gitnexus": "dist/cli/index.js" // 🔑 CLI 命令入口
},
"files": [
"dist", // 只包含编译后的文件
"hooks",
"scripts",
"skills",
"vendor",
"web"
],
"dependencies": {
// 运行时依赖 (用户安装时会下载)
"@huggingface/transformers": "^4.1.0",
"@ladybugdb/core": "^0.16.1",
"tree-sitter": "^0.21.1",
"express": "^5.2.1",
// ... 很多其他包
},
"devDependencies": {
"gitnexus-shared": "file:../gitnexus-shared", // 🔑 本地链接
"typescript": "^5.4.5"
}
}
关键特性:
- ✅ 发布到 npm 的唯一包
- ✅ 包含
bin字段,定义 CLI 命令 - ✅
"gitnexus-shared": "file:../gitnexus-shared"表示 本地依赖链接
B. gitnexus-web/ (React UI) --- 也是私有包
json
{
"name": "gitnexus", // 🔑 注意:和 CLI 同名!
"private": true, // 不发布到 npm
"version": "0.0.0", // 版本为 0.0.0(不重要)
"type": "module",
"dependencies": {
"react": "^19.2.5",
"vite": "^8.0.11",
"gitnexus-shared": "file:../gitnexus-shared", // 🔑 本地链接
"sigma": "^3.0.2", // 图可视化库
"@langchain/anthropic": "^1.3.29"
}
}
关键特性:
- ✅ 也依赖
gitnexus-shared(共享类型) - ✅ 有自己的构建脚本(Vite)
- ✅ 私有,不发布
C. gitnexus-shared/ (共享类型库)
json
{
"name": "gitnexus-shared",
"version": "1.0.0",
"private": true, // 私有包
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts", // 🔑 TypeScript 类型声明入口
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./test-helpers": { // 支持 `import from "gitnexus-shared/test-helpers"`
"types": "./dist/test-helpers.d.ts",
"default": "./dist/test-helpers.js"
}
},
"files": [
"dist",
"src"
],
"devDependencies": {
"typescript": "^6.0.3"
}
}
关键特性:
- ✅ 只包含 TypeScript 类型定义和常量
- ✅ 导出子路径支持 (exports 字段)
- ✅ 被 CLI 和 Web 都导入
D. eval/ (Python 评估框架)
js
[project]
name = "gitnexus-swebench-eval"
version = "0.1.0"
requires-python = ">=3.11"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
关键特性:
- ✅ 完全独立的 Python 项目 (pyproject.toml)
- ✅ 不被 npm 管理
- ✅ 有自己的 Python 依赖
🔗 依赖链接方式
"file:" 协议 (本地文件链接)
这是 npm 的原生特性,不需要任何工具(如 lerna、pnpm):
json
// gitnexus/package.json 和 gitnexus-web/package.json 都这样写:
"devDependencies": {
"gitnexus-shared": "file:../gitnexus-shared"
^^^^^^^^^^^^^^^^^^^^^^
相对路径指向本地包
}
工作原理:
bash
npm install
↓
npm 看到 "file:../gitnexus-shared"
↓
在本地找到 gitnexus-shared/ 目录
↓
符号链接 (symlink) 到 node_modules/
↓
导入时:import from "gitnexus-shared" → 使用本地版本
验证:
bash
cd gitnexus
ls -la node_modules/gitnexus-shared
# lrwxr-xr-x ... gitnexus-shared -> ../../../gitnexus-shared
↑
是符号链接!
📊 完整依赖图
js
┌──────────────────────────────────────────────────────────┐
│ package.json (根 - 私有) │
│ • 共享: prettier, eslint, husky, lint-staged │
│ • 统一: npm install, npm run format, npm run lint │
└──────────────────────────────────────────────────────────┘
↓ ↓ ↓
┌─────────────────┐ ┌──────────────────┐ ┌────────────────┐
│ gitnexus/ │ │ gitnexus-web/ │ │ gitnexus- │
│ (CLI 包) │ │ (Web UI) │ │ shared/ (types)│
│ │ │ │ │ │
│ private: false │ │ private: true │ │ private: true │
│ 发布到 npm ✅ │ │ 内部用 ❌ │ │ 内部用 ❌ │
│ │ │ │ │ │
│ depends on: │ │ depends on: │ │ exports: │
│ gitnexus-shared │ │ gitnexus-shared │ │ • index │
│ (file:../...) │ │ (file:../...) │ │ • test-helpers│
└─────────────────┘ └──────────────────┘ └────────────────┘
↓ ↓ ↑
└────────────────────────┴────────────────────┘
(都用 file: 引用)
┌──────────────────────┐
│ eval/ (Python) │
│ pyproject.toml │
│ 独立包 │
│ 不被 npm 管理 │
└──────────────────────┘
🛠️ 日常开发工作流
场景 1:修改 gitnexus-shared 中的类型
bash
# 1. 编辑文件
cd gitnexus-shared
vim src/index.ts
# 2. 构建
npm run build # 运行 tsc,生成 dist/
# 3. gitnexus 和 gitnexus-web 立即看到新类型
# 因为它们通过 file: 链接指向本地 dist/
# TypeScript 会自动重新编译
# 4. 不需要 npm publish!
场景 2:本地运行 CLI
bash
cd gitnexus
# 开发模式 (tsx watch)
npm run dev
# gitnexus 命令 → src/cli/index.ts
# 或直接运行
npm run build
gitnexus analyze /path/to/repo
场景 3:本地运行 Web UI
bash
cd gitnexus-web
# 开发模式 (Vite)
npm run dev
# 打开 http://localhost:5173
# Web UI 通过 axios 调用 gitnexus serve 的 HTTP API
# gitnexus serve 地址: http://localhost:4747
场景 4:发布新版本到 npm
bash
# 只有 gitnexus/ 被发布
cd gitnexus
# 1. 更新版本
npm version minor # 1.6.5 → 1.7.0
# 2. npm 自动构建
npm run prepare # 因为 package.json 中有 "prepare": "node scripts/build.js"
# 3. 发布
npm publish
# gitnexus-shared 和 gitnexus-web 不发布
# eval/ 是 Python 包,单独管理
📋 Monorepo 和 npm workspaces 的区别
GitNexus 的方式(手动链接)
json
// package.json
{
"devDependencies": {
"gitnexus-shared": "file:../gitnexus-shared"
}
}
优点:
- ✅ 简单,无需额外工具
- ✅ 完全控制发布策略
- ✅ 子包可以完全独立
缺点:
- ❌ 手动管理依赖版本
- ❌ 复杂的 monorepo 可能难以维护
如果使用 npm workspaces
json
// package.json (假设)
{
"workspaces": [
"gitnexus",
"gitnexus-web",
"gitnexus-shared"
]
}
优点:
- ✅ 自动符号链接
- ✅ 统一版本管理
- ✅ 统一 node_modules
缺点:
- ❌ 更复杂的配置
- ❌ 子包紧耦合
❓ 常见问题
Q: 为什么 gitnexus-web 也叫 "gitnexus"?
json
// gitnexus/package.json
{ "name": "gitnexus" } // 发布到 npm
// gitnexus-web/package.json
{ "name": "gitnexus", "private": true } // 内部用,不冲突
答 :private: true 使其不会发布,所以不会冲突。这样内部引用时可以用同一个名字。
Q: npm install 时发生了什么?
bash
npm install
↓
npm 看到 root package.json
↓
安装 root 的 devDependencies (prettier, eslint 等)
↓
找到所有子包的 package.json
↓
对每个子包,处理 file: 链接 → symlink 到 node_modules/
↓
安装每个子包的 dependencies + devDependencies
Q: 为什么不用 Lerna 或 pnpm?
答:因为:
- GitNexus 只有 3-4 个 npm 包,不复杂
- 只有 1 个包发布到 npm (gitnexus/)
- 手动
file:链接足够 - 无需额外工具的复杂性
🎯 总结:这个项目的 Monorepo 策略
| 特性 | 实现 |
|---|---|
| 包数量 | 4 个 (gitnexus, gitnexus-web, gitnexus-shared, eval) |
| 共享工具 | 根 package.json 的 devDependencies |
| 依赖链接 | "file:../package-name" 协议 |
| 发布策略 | 只有 gitnexus/ 发布到 npm |
| 工具选择 | npm 原生,无 lerna/pnpm/yarn |
| git 策略 | 单一代码库,原子提交 |
| 版本号 | gitnexus/ 同步,其他包独立或不发布 |
这种设计轻量级但有效,适合中等规模的多包项目!