从零开发一个Coding Agent:monorepo项目搭建

本篇文章是《从零开发一个Coding Agent》系列第二篇,主要介绍如何使用npm搭建一个monorepo项目,以及如何管理多个子项目。

什么是monorepo项目

monorepo项目是指在一个代码仓库中管理多个子项目的项目。每个子项目都是一个独立的npm包,它们之间通过npm的依赖管理机制进行协调。npm workspaces 中,本地包之间仍然通过 package.json 声明依赖;区别是安装时 npm 会把依赖链接到本地 workspace,,而不是从 npm registry 下载。 举个例子: agent包 需要引用 ai 包。

根目录已经声明:

json 复制代码
{
  "workspaces": ["packages/*"]
}

packages/ai/package.json:

json 复制代码
{
  "name": "@di-code/ai",
  "version": "0.0.0"
}

在 packages/agent/package.json 中声明:

json 复制代码
{
  "name": "@di-code/agent",
  "version": "0.0.0",
  "dependencies": {
    "@di-code/ai": "0.0.0"
  }
}

然后从根目录安装: npm install

npm 会建立类似下面的本地链接:

text 复制代码
node_modules/@di-code/ai
    -> D:\pi\di-code\packages\ai

Windows 上实际可能是 junction(目录联接),但效果与符号链接一致,不会复制一份源码。 于是 agent 中可以像使用普通 npm 包一样导入:

javascript 复制代码
import type { Message } from "@di-code/ai";

不要使用跨目录相对导入:

javascript 复制代码
import type { Message } from "../../ai/src/types.js";

环境和目录

接下来我们就进入了项目的真正开发了,首先需要检查自己的环境是否符合要求。node版本至少为22.19.0。

新建项目目录,我这里选择名称为di-code,并初始化根 package.json:

json 复制代码
{
	"name": "di-code",
	"version": "0.0.0",
	"private": true,
	"type": "module",
	"workspaces": ["packages/*"],
	"scripts": {
		"build": "npm run build --workspace @di-code/tui && npm run build --workspace @di-code/ai && npm run build --workspace @di-code/agent && npm run build --workspace @di-code/coding-agent",
		"check": "biome check . && tsc --noEmit -p tsconfig.json",
		"test": "npm run test --workspaces --if-present"
	},
	"devDependencies": {
		"@biomejs/biome": "2.3.5",
		"@types/node": "22.19.19",
		"typescript": "5.9.3",
		"vitest": "4.1.9"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

关键字段说明:

  • private: true 防止对根聚合包执行意外发布。
  • type: module 确立根脚本的 ESM 语义。
  • workspaces 只匹配四个直接子包,不把未来示例或临时目录意外变成 workspace。
  • build 的显式顺序对应未来依赖方向。
  • check 先运行 Biome;只有 Biome 成功才运行 TypeScript。
  • test 将测试命令分发到所有定义了 test script 的 workspace。
  • 所有 devDependency 都使用精确版本,不使用 ^ 或 ~。

然后新建packages目录,用于存放所有的子项目。在该目录下创建ai, agent, coding-agent, tui四个子目录。 每个子目录下创建src目录,用于存放源代码。

TypeScript配置

在 根目录创建一个tsconfig.base.json文件:

json 复制代码
{
	"compilerOptions": {
		"target": "ES2022",
		"lib": ["ES2022"],
		"module": "NodeNext",
		"moduleResolution": "NodeNext",
		"strict": true,
		"erasableSyntaxOnly": true,
		"verbatimModuleSyntax": true,
		"noEmitOnError": true,
		"declaration": true,
		"declarationMap": true,
		"sourceMap": true,
		"inlineSources": true,
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"skipLibCheck": true,
		"resolveJsonModule": true,
		"types": ["node"]
	}
}

部分选项说明:

  • target 决定输出 JavaScript 的语法基线;ES2022 在最低 Node 22 运行时上可用。
  • module 与 moduleResolution 同时使用 NodeNext,让 TypeScript 按现代 Node.js 的 ESM/CJS 规则判断包边界。不要把一个改为 NodeNext、另一个留成旧的 node。
  • strict 是项目约束,后续不得为了消除错误而关闭。
  • verbatimModuleSyntax 要求 import/export 写法与最终模块语义一致,并促使类型导入使用 import type。
  • noEmitOnError 防止类型错误时留下看似可用的构建产物。
  • declaration 和 declarationMap 为 workspace 公共类型生成 .d.ts 与映射。
  • sourceMap 和 inlineSources 为后续调试保留源码定位。
  • skipLibCheck 只跳过第三方声明文件内部的检查,不跳过本项目源码检查。
  • types: ["node"] 明确引入 Node.js 全局类型,避免环境中其他 @types/* 包意外污染全局命名空间。

然后在根目录写入TS配置文件tsconfig.json:

json 复制代码
{
	"extends": "./tsconfig.base.json",
	"compilerOptions": {
		"noEmit": true
	},
	"include": ["packages/*/src/**/*.ts", "packages/*/test/**/*.ts"],
	"exclude": ["**/node_modules/**", "**/dist/**"]
}

根配置的职责是全仓检查,不是构建:

  • include 同时覆盖源码和未来测试。
  • noEmit 保证 npm run check 不修改工作区。
  • dist 被排除,避免检查生成文件并重复报告问题。

编写四个 workspace package.json

在四个目录 ai, agent, coding-agent, tui 下创建 package.json 文件。它们的脚本和导出形状保持一致,名称和描述不同。

packages/ai/package.json

json 复制代码
{
	"name": "@di-code/ai",
	"version": "0.0.0",
	"description": "Provider-neutral AI contracts and adapters for di-code",
	"private": true,
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsc -p tsconfig.build.json",
		"test": "vitest run --passWithNoTests"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

packages/agent/package.json

json 复制代码
{
	"name": "@di-code/agent",
	"version": "0.0.0",
	"description": "Core agent state and loop for di-code",
	"private": true,
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsc -p tsconfig.build.json",
		"test": "vitest run --passWithNoTests"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

packages/coding-agent/package.json

json 复制代码
{
	"name": "@di-code/coding-agent",
	"version": "0.0.0",
	"description": "Coding agent product layer for di-code",
	"private": true,
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsc -p tsconfig.build.json",
		"test": "vitest run --passWithNoTests"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

packages/tui/package.json

json 复制代码
{
	"name": "@di-code/tui",
	"version": "0.0.0",
	"description": "Reusable terminal UI library for di-code",
	"private": true,
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsc -p tsconfig.build.json",
		"test": "vitest run --passWithNoTests"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}
  • main 指向运行时 ESM 入口。
  • types 指向 TypeScript 声明入口。
  • exports 限制包的公开入口为根入口,防止后续调用者依赖内部文件路径。

编写 workspace 构建配置

在以下四个位置创建相同内容的 tsconfig.build.json:

text 复制代码
packages\ai\tsconfig.build.json
packages\agent\tsconfig.build.json
packages\coding-agent\tsconfig.build.json
packages\tui\tsconfig.build.json

内容均为:

json 复制代码
{
	"extends": "../../tsconfig.base.json",
	"compilerOptions": {
		"rootDir": "./src",
		"outDir": "./dist"
	},
	"include": ["src/**/*.ts"],
	"exclude": ["node_modules", "dist", "test"]
}
  • extends 让四个包继承完全相同的严格模式和 ESM 语义。
  • rootDir 告诉编译器源码相对根为 src。
  • outDir 使输出留在各自包内,不把不同包混到根 dist。
  • build 只包含 src,测试不进入发布产物。

入口创建

在以下四个文件中各写一行:

text 复制代码
packages\ai\src\index.ts
packages\agent\src\index.ts
packages\coding-agent\src\index.ts
packages\tui\src\index.ts

内容:

ts 复制代码
export {};

配置 Biome 格式化工具

Biome 是面向 JavaScript、TypeScript、JSON 等文件的代码质量工具,简单来说就是ESLint + Prettier。

在 di-code\biome.json 写入:

json 复制代码
{
	"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
	"files": {
		"includes": [
			"package.json",
			"biome.json",
			"tsconfig*.json",
			"packages/*/package.json",
			"packages/*/tsconfig*.json",
			"packages/*/src/**/*.ts",
			"packages/*/test/**/*.ts"
		]
	},
	"formatter": {
		"enabled": true,
		"formatWithErrors": false,
		"indentStyle": "tab",
		"lineWidth": 120
	},
	"linter": {
		"enabled": true,
		"rules": {
			"recommended": true
		}
	}
}

编写 .gitignore

在根目录下创建 .gitignore 写入:

gitignore 复制代码
# Dependencies
node_modules/

# Build and test output
dist/
coverage/
*.tsbuildinfo

# Logs
*.log
npm-debug.log*

# Environment and credentials
.env
.env.*
!.env.example

# Editors and operating systems
.vscode/
.idea/
.DS_Store
Thumbs.db

这些都不需要提交代码仓库

安装依赖与构建

到这里我们基本已经完成了基础的 monorepo 配置。 接下来,我们可以开始安装依赖并构建项目。

bash 复制代码
npm install
npm run build

构建完成之后我们就能看到每个包下面都有一个 dist 目录,里面包含了编译后的代码。

最后执行一下检查,确保代码质量。

bash 复制代码
npm run check

如果 Biome 只报告格式差异,可以执行

powershell 复制代码
npx biome check --write package.json tsconfig.base.json tsconfig.json biome.json packages

进行修复,然后再执行 npm run check 确保格式符合要求。

到这里我们已经完成了基础的 monorepo 配置。本章节git分支Monorepo 初始化:

相关推荐
IT_陈寒43 分钟前
Java中equals方法比了个寂寞?原来这才是正确的重写姿势
前端·人工智能·后端
Thneonl43 分钟前
Celery 生产踩坑:1000 任务积压与 acks_late 双重执行
后端·python
默_笙43 分钟前
🚓 分诊台与拆题术:让 RAG 学会判断和规划
前端·javascript
卷福同学43 分钟前
第一次当面试官有感
后端·面试
苏三说技术43 分钟前
为什么越来越多人用 OnlyOffice?
后端
知守观43 分钟前
@Transactional 事务失效排查,try-catch 吞异常导致回滚失败(附源码分析)
后端·spring
羑悻43 分钟前
Codex + Seed-2.1-pro 实测:多模态理解 + Coding Agent 能扛住真实仓库吗?
后端
CopyCode43 分钟前
用 AI 迁项目有多爽?我把 Webpack 迁 Vite 的全过程记下来了
前端·架构
去伪存真44 分钟前
Electron 自动化发布指南:GitHub Actions 跨平台打包全纪录
前端·electron
计算机魔术师44 分钟前
OpenAI智能体失控闯进美国政府网站,53张用户图片外泄背后
前端