Bruno 本地开发指南:从依赖安装到测试运行的完整工作流(npm workspaces + Electron)
本篇指南基于 Bruno 官方贡献文档 contributing.md 展开,带你走通 Bruno(一个开源的 API 调试 IDE)在本地从零跑起来的完整流程:环境准备、monorepo 子包构建、双进程开发模式(React + Electron)、Electron userData 路径自定义、常见问题排查,以及测试运行与 PR 规范。读完并动手完成后,你将能够独立构建、调试 Bruno 的桌面端,并为项目提交符合规范的代码。
技术栈与项目结构
Bruno 由 React 和 Electron 构成,前者负责界面(渲染进程),后者负责系统能力(主进程)。官方文档列出的核心依赖库如下:
| 用途 | 库 |
|---|---|
| CSS | Tailwind |
| 代码编辑器 | Codemirror |
| 状态管理 | Redux |
| 图标 | Tabler Icons |
| 表单 | formik |
| Schema 校验 | Yup |
| 请求客户端 | axios |
| 文件系统监听 | chokidar |
| 国际化 | i18next |
从根目录 package.json 可以印证这一架构:整个仓库是一个 npm workspaces monorepo,聚合了 16 个子包,核心包括:
packages/bruno-app—— React 渲染端(界面层);packages/bruno-electron—— Electron 主进程(系统能力、IPC 等);packages/bruno-common、packages/bruno-requests、packages/bruno-filestore、packages/bruno-converters、packages/bruno-query、packages/bruno-graphql-docs、packages/bruno-schema-types—— 被上述两端共享的底层库;packages/bruno-js(脚本沙箱)、packages/bruno-lang(bru 格式解析)、packages/bruno-cli(命令行工具)等。
重要前提:需要 Node v22.x 或最新 LTS 版本,项目使用 npm workspaces 管理依赖。仓库根目录提供了 .nvmrc,内容为
v22.12.0,nvm use会自动切换到该版本。
安装依赖
# use nodejs 22 version
nvm use
# install deps
npm i --legacy-peer-deps
注意 --legacy-peer-deps 参数是必需的,这也是仓库内其他安装脚本(如 scripts/setup.js 中的 npm i --legacy-peer-deps)保持一致的约定。
构建子包(Build packages)
在运行桌面应用之前,需要先构建被共享依赖的子包。官方文档给出两种方式:
方式一:逐个构建
# build packages
npm run build:graphql-docs
npm run build:bruno-query
npm run build:bruno-common
npm run build:bruno-converters
npm run build:bruno-requests
npm run build:schema-types
npm run build:bruno-filestore
# bundle js sandbox libraries
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
这些命令都定义在根 package.json 的 scripts 中,本质都是 npm run build --workspace=packages/<包名> 的转发,用 rollup 将各 TS/ESM 子包打包为可供 bruno-app / bruno-electron 消费的产物。
其中 sandbox:bundle-libraries 值得特别关注:它由 packages/bruno-js/package.json 定义(node ./src/sandbox/bundle-libraries.js),负责把内置脚本沙箱(QuickJS 侧使用的 crypto、fetch 等浏览器端 rollup 产物)打成 bundle。从源码看,packages/bruno-electron/src/index.js 在开发模式下会显式检查该产物是否存在:
if (isDev) {
if (!fs.existsSync(path.join(__dirname, '../../bruno-js/src/sandbox/bundle-browser-rollup.js'))) {
console.log('JS Sandbox libraries have not been bundled yet');
console.log('Please run the below command \nnpm run sandbox:bundle-libraries --workspace=packages/bruno-js');
throw new Error('JS Sandbox libraries have not been bundled yet');
}
}
也就是说,漏掉这一步 Electron 会直接启动失败,并提示你补跑该命令。
方式二:一键 setup
# install dependencies and setup
npm run setup
setup 指向 scripts/setup.js,从源码看它按顺序完成四件事:
- 清理所有
node_modules(但会保留tests/scripting/additional-context-roots/fixtures下作为测试夹具提交的node_modules,避免破坏依赖解析测试); npm i --legacy-peer-deps安装依赖,随后按当前平台强制安装@lydell/node-pty-<platform>-<arch>@1.1.0这类硬钉版本的平台二进制包(用于终端/进程能力);- 构建全部共享包——注意 setup 脚本在文档列出的 7 个包之外还会额外执行
npm run build:bruno-sqlite,即本地开发建议优先用npm run setup,以免遗漏新增子包; - 打包 JS 沙箱库(等价于上面的
sandbox:bundle-libraries)。
因此,如果你按文档方式一手工操作,建议对照 scripts/setup.js 的构建清单,确认 bruno-sqlite 等新增包是否也需要构建。
运行应用
Bruno 是桌面应用,开发时需要两个进程:rsbuild 驱动的 React dev server + Electron 主进程。
方式一:双终端分别启动
# run react app (terminal 1)
npm run dev:web
# run electron app (terminal 2)
npm run dev:electron
从子包定义看:dev:web 转发到 packages/bruno-app/package.json 的 rsbuild dev;dev:electron 转发到 packages/bruno-electron/package.json 的 electron .。官方文档也注明前端使用 React、构建与 dev server 使用 rsbuild。
如果只跑方式一的命令,两个进程需要约定同一个端口:Electron 端在 packages/bruno-electron/src/index.js 中读取 BRUNO_DEV_PORT(默认 3000):
const devPort = process.env.BRUNO_DEV_PORT || 3000;
const url = isDev
? `http://localhost:${devPort}`
: /* 打包后加载 file:// 下的 web/index.html */;
方式二:并发自动启动(推荐)
# run electron and react app concurrently
npm run dev
dev 指向 scripts/dev.js,它解决了双终端方案中"端口如何对接"的手动问题:先 spawn rsbuild dev server,用正则 Local:\s+http:\/\/localhost:(\d+) 从 rsbuild 输出中实时解析出实际监听端口,然后以 BRUNO_DEV_PORT=<端口> 注入环境变量启动 Electron(见 scripts/dev.js),并在任一进程退出或收到 SIGINT/SIGTERM 时统一清理两个子进程。
自定义 Electron userData 路径
默认情况下 Electron 会把配置、缓存等用户数据写在系统约定的应用数据目录。为了方便多份开发环境并行调试而不互相污染,Bruno 提供了 ELECTRON_USER_DATA_PATH 环境变量:当该变量存在且处于开发模式时,userData 路径会被重定向到指定目录。
实现位于 packages/bruno-electron/src/index.js:
if (isDev && process.env.ELECTRON_USER_DATA_PATH) {
console.debug('`ELECTRON_USER_DATA_PATH` found, modifying `userData` path: ...');
app.setPath('userData', process.env.ELECTRON_USER_DATA_PATH);
}
两个要点都体现在这一行条件里:isDev 保证生产构建不受影响(路径重定向只在开发模式生效),console.debug 会打印原路径到新路径的映射,便于确认生效。
文档给出的示例:
ELECTRON_USER_DATA_PATH=$(realpath ~/Desktop/bruno-test) npm run dev:electron
这会在桌面创建 bruno-test 目录并将其作为 userData 路径使用。从源码结构看,仓库的 Playwright E2E 体系(playwright/index.ts)同样依赖该变量为每个测试实例注入独立的 userDataPath,所以这个机制也是本地跑 E2E 的配套基础。
故障排查(Troubleshooting)
运行 npm install 时可能遇到 Unsupported platform 错误(通常来自含平台二进制/可选依赖的包)。官方给出的修复方式是删除所有 node_modules 与 package-lock.json 后重新安装:
# Delete node_modules in sub-directories
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Delete package-lock in sub-directories
find . -type f -name "package-lock.json" -delete
删除完成后重新执行 npm i --legacy-peer-deps(或直接 npm run setup,它内部会做同样的清理与安装)。monorepo 中每个子目录的残留 node_modules 都可能持有旧平台的二进制产物,因此需要逐目录清除而不是只删根目录。
运行测试
各子包用 Jest 跑单元测试,测试命令都注册在对应包自己的 test 脚本里(例如 packages/bruno-common/package.json、packages/bruno-lang/package.json 均为 jest),通过 npm workspaces 参数化执行:
# run bruno-schema tests
npm run test --workspace=packages/bruno-schema
# run bruno-query tests
npm run test --workspace=packages/bruno-query
# run bruno-common tests
npm run test --workspace=packages/bruno-common
# run bruno-converters tests
npm run test --workspace=packages/bruno-converters
# run bruno-app tests
npm run test --workspace=packages/bruno-app
# run bruno-electron tests
npm run test --workspace=packages/bruno-electron
# run bruno-lang tests
npm run test --workspace=packages/bruno-lang
# run bruno-toml tests
npm run test --workspace=packages/bruno-toml
# run tests over all workspaces
npm test --workspaces --if-present
其中 --if-present 会跳过未定义 test 脚本的包,适合一次跑完整个 monorepo。此外,根 package.json 还定义了基于 Playwright 的 E2E 入口(如 test:e2e、test:e2e:sanity、test:benchmark 等),覆盖 tests/ 目录下按功能模块组织的桌面端 UI 测试。
代码规范与 Pull Request 约定
提交代码前建议对照仓库内的 CODING_STANDARDS.md,它规定了具体风格:2 空格缩进、字符串用单引号(JSX/TSX 属性用双引号)、语句加分号、无尾随逗号、箭头函数参数始终加括号等;测试方面要求为新功能补充行为驱动(而非实现驱动)的测试、控制 mock 范围、保证测试确定性。根 package.json 还配置了 husky + nano-staged 的 pre-commit 钩子,对 *.{js,ts,jsx} 自动执行 npm run lint:fix,即 lint 规则由 eslint.config.js 定义并会在提交前自动修复。
官方文档对 PR 的要求:
- 保持 PR 小而聚焦,一个 PR 只做一件事;
- 遵守分支命名规范:
feature/[feature name]—— 特定功能分支,例如feature/dark-mode;bugfix/[bug name]—— 仅包含特定 bug 的修复,例如bugfix/bug-1。
小结
Bruno 本地开发的标准路径可以概括为:nvm use 切到 Node 22 → npm i --legacy-peer-deps 装依赖 → npm run setup(或按清单手动构建 7+ 个子包并 sandbox:bundle-libraries)→ npm run dev 一条命令启动 rsbuild dev server 与 Electron(端口自动对接)→ 需要隔离数据时注入 ELECTRON_USER_DATA_PATH → 用 npm test --workspaces --if-present 验证改动 → 按 feature/、bugfix/ 分支规范提交小而聚焦的 PR。所有关键脚本均可在 scripts/ 目录与根 package.json 中逐行查看,遇到文档与仓库脚本不一致时(例如 setup 会额外构建 bruno-sqlite),以仓库内脚本实现为准。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00