首页
/ Bruno 本地开发指南:从依赖安装到测试运行的完整工作流(npm workspaces + Electron)

Bruno 本地开发指南:从依赖安装到测试运行的完整工作流(npm workspaces + Electron)

2026-09-07 17:50:05作者:裴锟轩Denise

本篇指南基于 Bruno 官方贡献文档 contributing.md 展开,带你走通 Bruno(一个开源的 API 调试 IDE)在本地从零跑起来的完整流程:环境准备、monorepo 子包构建、双进程开发模式(React + Electron)、Electron userData 路径自定义、常见问题排查,以及测试运行与 PR 规范。读完并动手完成后,你将能够独立构建、调试 Bruno 的桌面端,并为项目提交符合规范的代码。

技术栈与项目结构

Bruno 由 ReactElectron 构成,前者负责界面(渲染进程),后者负责系统能力(主进程)。官方文档列出的核心依赖库如下:

用途
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-commonpackages/bruno-requestspackages/bruno-filestorepackages/bruno-converterspackages/bruno-querypackages/bruno-graphql-docspackages/bruno-schema-types —— 被上述两端共享的底层库;
  • packages/bruno-js(脚本沙箱)、packages/bruno-lang(bru 格式解析)、packages/bruno-cli(命令行工具)等。

重要前提:需要 Node v22.x 或最新 LTS 版本,项目使用 npm workspaces 管理依赖。仓库根目录提供了 .nvmrc,内容为 v22.12.0nvm 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.jsonscripts 中,本质都是 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 侧使用的 cryptofetch 等浏览器端 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,从源码看它按顺序完成四件事:

  1. 清理所有 node_modules(但会保留 tests/scripting/additional-context-roots/fixtures 下作为测试夹具提交的 node_modules,避免破坏依赖解析测试);
  2. npm i --legacy-peer-deps 安装依赖,随后按当前平台强制安装 @lydell/node-pty-<platform>-<arch>@1.1.0 这类硬钉版本的平台二进制包(用于终端/进程能力);
  3. 构建全部共享包——注意 setup 脚本在文档列出的 7 个包之外还会额外执行 npm run build:bruno-sqlite,即本地开发建议优先用 npm run setup,以免遗漏新增子包;
  4. 打包 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.jsonrsbuild devdev:electron 转发到 packages/bruno-electron/package.jsonelectron .。官方文档也注明前端使用 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_modulespackage-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.jsonpackages/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:e2etest:e2e:sanitytest: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),以仓库内脚本实现为准。

登录后查看全文
热门项目推荐
相关项目推荐