Bruno 单体仓库依赖边界:严格的 @usebruno 依赖 DAG 与五条包所有权守护规则
【免费下载链接】brunoOpensource IDE For Exploring and Testing API's (lightweight alternative to Postman/Insomnia)项目地址: https://gitcode.com/GitHub_Trending/br/bruno
Bruno 是一个以 npm workspaces 组织的 monorepo,其内部包(@usebruno/*)之间的依赖方向被.claude/rules/architecture.md明确规定为一个严格有向无环图(DAG)。本文完整继承该规则文档的核心内容——三级依赖分层、五条守护护栏、声明依赖与真实导入一致性要求——并结合当前仓库中各包package.json的实际声明逐条佐证,帮助你在为 Bruno 新增代码或做跨包改动前,准确判断「这个 import 该放哪个包、这个依赖该不该声明」,避免引入循环依赖或向上依赖这类架构级缺陷。
一、核心规则:内部依赖必须是严格 DAG
规则文档的开篇即给出总纲:内部@usebruno/*依赖(即每个包package.json中的声明)构成一个严格 DAG,新代码必须遵守它——循环依赖或向上依赖是架构 bug,而不是图省事的便利。
文档同时指明了一份按需查阅的参考文档:整个 monorepo 的完整地图(构建工具、请求管线、沙箱、文件格式、核心数据模型类型、依赖版本)位于 .claude/reference/architecture.md,并要求在进行任何非平凡的跨包或架构性工作之前先阅读它。也就是说,.claude/rules/architecture.md定义的是「不可破坏的不变量」(invariants),而 reference 文档提供的是「地图」——两者配合使用:规则文档常驻生效,参考文档在跨包工作时按需展开。
当前仓库共有 16 个 workspace 包,见根目录 package.json 的workspaces字段(packages/bruno-app、bruno-electron、bruno-cli、bruno-common、bruno-converters、bruno-schema、bruno-schema-types、bruno-query、bruno-js、bruno-lang、bruno-tests、bruno-toml、bruno-graphql-docs、bruno-requests、bruno-filestore、bruno-sqlite)。理解依赖方向,正是从这个清单出发。
二、依赖方向与所有权边界:三级分层
规则文档将全部内部包按依赖位置划分为三层。以下完整继承原文档的划分,并在后文用各包package.json的实际声明逐层验证:
1. 叶子库(Leaf libs)——零内部依赖
bruno-common、bruno-lang、bruno-query、bruno-requests、bruno-graphql-docs、bruno-schema、bruno-schema-types、bruno-toml被列为零内部@usebruno/*依赖的叶子库。
从源码结构看,packages/bruno-common/package.json 的dependencies字段就是一个空对象{},是这一层级的最严格实例。需要指出一个与当前代码的细微出入:packages/bruno-requests/package.json 的dependencies中实际声明了"@usebruno/common": "0.1.0",即 bruno-requests 目前指向基础叶子。可以推断规则文档中「零内部依赖」的枚举对 bruno-requests 的描述偏保守,实际它是仅依赖bruno-common的准叶子——方向依然向下,不破坏 DAG,但新代码不应在这条边之外再增加任何内部依赖。
2. 中间消费者(Mid consumers)
bruno-js→ (bruno-common,bruno-query)bruno-converters→ (bruno-common,bruno-schema;bruno-schema-types作为 devDependency)bruno-filestore→ (bruno-common,bruno-lang;bruno-schema-types作为 devDependency)
与当前package.json逐一比对:
| 包 | 规则文档声明的内部依赖 | 当前 package.json 实际声明 |
|---|---|---|
| bruno-js | common、query | "@usebruno/common": "0.1.0"、"@usebruno/query": "0.1.0" |
| bruno-converters | common、schema(运行时);schema-types(dev) | dependencies:"@usebruno/common": "^0.1.0"、"@usebruno/schema": "^0.7.0";devDependencies:"@usebruno/schema-types": "0.0.1" |
| bruno-filestore | common、lang(运行时);schema-types(dev) | dependencies:"@usebruno/common": "0.1.0"、"@usebruno/lang": "0.12.0";devDependencies:"@usebruno/schema-types": "0.0.1" |
三者的运行时/编译期划分与规则文档完全一致:bruno-schema-types只出现在devDependencies中,这正呼应了后文守护规则第 4 条「types-only」。
3. 顶层消费者(Top consumers)——依赖只进不出
「things flowintothem, never out」:
bruno-cli→ (bruno-common,bruno-converters,bruno-filestore,bruno-js,bruno-lang,bruno-requests)bruno-electron→ (bruno-common,bruno-converters,bruno-filestore,bruno-js,bruno-lang,bruno-requests,bruno-schema)bruno-app→ (bruno-common,bruno-converters,bruno-graphql-docs,bruno-schema)
实际package.json验证结果:
| 包 | 当前实际声明的内部运行时依赖 |
|---|---|
| bruno-cli | common、converters、filestore、js、lang、requests —— 与文档完全一致 |
| bruno-electron | common、converters、filestore、js、lang、requests、schema,另加"@usebruno/sqlite": "0.1.0" |
| bruno-app | common、graphql-docs、schema,另加"@usebruno/sqlite": "0.1.0" |
两个值得注意的补充事实:其一,bruno-electron的包名不带作用域("name": "bruno"),它是唯一以非@usebruno/*命名对外分发的包,因此「依赖只进不出」对它的约束更为关键;其二,当前 workspace 已新增packages/bruno-sqlite,并被bruno-app与bruno-electron以@usebruno/sqlite 0.1.0声明,规则文档的顶层枚举尚未覆盖这一新增包——它的依赖方向同样是「流入顶层消费者」,符合 DAG,但做架构判断时应把它一并纳入心智模型。
三、五条守护护栏(Guardrails)
规则文档列出了 DAG 所强制的五条具体护栏,下面逐条完整说明并给出仓库证据。
护栏 1:bruno-common 是「浏览器安全」的基础叶子
它运行在 Web 渲染进程(bruno-app)中,而不仅是 Node 环境,因此必须保持平台中立:
- 禁止使用 Node 内建模块(
fs、path、os、crypto、child_process、node:*); - 禁止依赖任何自身会拉入 Node 的第三方包;
- 当前保持零运行时依赖——
packages/bruno-common/package.json中"dependencies": {}印证了这一点,文档要求「保持这个状态」; - 不依赖任何其他
@usebruno/*包。需要 Node 能力或需要其他 bruno 包的工具函数,必须放到别的包里。
护栏 2:共享库/库包禁止 import bruno-app 或 bruno-electron
渲染进程专属或 Electron 专属的代码,不允许为了「让上层可导入」而下推(push down)到某个库中。任何在packages/bruno-common、bruno-js等库包里出现require('electron')或从bruno-app源码导入的行为,都是对所有权边界的违反。
护栏 3:bruno-js 必须保持 Electron-free
bruno-js同时在两个宿主中运行:Electron 主进程(bruno-electron → js)与命令行(bruno-cli → js,见 bruno-cli/package.json 中的"@usebruno/js": "0.12.0")。一旦在 bruno-js 中加入electron/IPC 导入,CLI 路径即被破坏。规则文档给出的分工是:沙箱逻辑(sandbox logic)属于 bruno-js,宿主接线(host wiring)属于 bruno-electron。bruno-js 的 package.json 中确实没有任何 electron 相关依赖,其main直接指向src/index.js(无构建产物,从源码直接消费)。
护栏 4:bruno-schema-types 是 types-only 包
它是 converters/filestore 的devDependency,用纯tsc构建(packages/bruno-schema-types/package.json 的"build": "tsc -p tsconfig.json",devDependencies中仅有typescript,无 rollup 等运行时构建工具)。规则要求:只从它导入类型,永远不要往里面加运行时代码,也不要出现对它的运行时 import。这与第二层级中bruno-converters、bruno-filestore把它放在devDependencies的声明方式互为印证。
护栏 5:bruno-schema(Yup)与 bruno-schema-types(TS 类型)是两个独立且都存活的包
bruno-app与bruno-converters使用@usebruno/schema做运行时校验(两者dependencies中均有"@usebruno/schema": "^0.7.0"/"0.7.0");bruno-filestore与bruno-converters使用@usebruno/schema-types做编译期类型(两者devDependencies中均有"@usebruno/schema-types": "0.0.1");- 数据模型的任何改动通常同时触及两者——这是跨包改动时最容易漏掉的一条。
四、声明的依赖必须匹配真实的导入
规则文档最后一节强调:package.json就是契约,而 workspace 的依赖提升(hoisting)会掩盖对契约的违背。具体执行要点有三:
- 每个 import 都必须在导入方自己的 manifest 里声明。某个模块仅因为兄弟包或根目录恰好拉了它才解析成功,属于「潜伏的断裂」——一旦工作区结构变化就会炸掉。
- 测试与构建专用包必须放进
devDependencies。dependencies里的东西会连同其传递依赖树一起发布给用户;各叶子库prepack脚本(如npm run test && npm run build)配合files字段(dist、src、package.json)决定了最终发布内容,dependencies的膨胀会直接放大用户安装的体积。 - 当某次改动移除了某个声明的最后一个消费者时,要同步删掉这条声明,保持声明与实际导入严格同步。
与契约配套的还有一层根级版本钉死。当前根 package.json 的overrides字段硬钉了"axios": "1.18.0"、"rollup": "3.30.0"等关键版本(还有tar、pbkdf2、electron-store/conf等)。这意味着:在某个叶子包里单独升级 axios 或 rollup 是无效的——真正生效的是根级 override,升级必须改根package.json。各叶子包package.json中的"overrides": { "rollup": "3.30.0" }(如 bruno-common、bruno-requests、bruno-filestore)也保持了与根级一致。
五、实践自检清单:新增代码前如何验证合规
综合规则文档与各包package.json的证据,跨包改动前可以按以下清单自检:
- 依赖方向:新 import 是否指向 DAG 的下方(叶子 → 中间消费者 → 顶层消费者)?若
bruno-app/bruno-electron/bruno-cli之外的包需要引用它们,即为向上依赖,属于架构 bug。 - 平台约束:改动落在
bruno-common中时,是否引入了 Node 内建模块或会拉入 Node 的依赖?落在bruno-js中时,是否引入了electron或 IPC 调用? - schema 双改:涉及集合/请求数据模型时,是否同时检查了
@usebruno/schema(运行时 Yup 校验)与@usebruno/schema-types(编译期 TS 类型)? - manifest 一致性:新增的 import 是否写入了导入方自己的
dependencies/devDependencies?测试/构建专用依赖是否误入dependencies?被移除的最后一个 import 是否连带删除了声明? - 版本升级:要升 axios/rollup 时,改的是否是根 package.json 的
overrides,而不是叶子包里的版本号? - 架构级工作前置:非平凡的跨包改动,是否已先通读 .claude/reference/architecture.md 获取完整 monorepo 地图(构建工具分布、请求管线、沙箱模式、文件格式、关键依赖主版本)?
小结
.claude/rules/architecture.md用不到 50 行定义了一套可执行的架构不变量:以严格 DAG 约束@usebruno/*内部依赖方向,以五条护栏锁定关键包(bruno-common浏览器安全、bruno-js双宿主运行、bruno-schema-types纯类型)的所有权边界,再以「声明必须匹配真实导入」堵住 workspace hoisting 带来的隐性违约。本文所有佐证均来自当前仓库的实际package.json声明;若未来仓库演进(例如bruno-requests收敛掉对bruno-common的运行时依赖,或bruno-sqlite被补入规则文档的枚举),应以仓库实际内容为准重新核对本文表格。
【免费下载链接】brunoOpensource IDE For Exploring and Testing API's (lightweight alternative to Postman/Insomnia)项目地址: https://gitcode.com/GitHub_Trending/br/bruno
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考