包概述
这个 monorepo 包含两个主要包:@google/gemini-cli
和 @google/gemini-cli-core
。
@google/gemini-cli
这是 Gemini CLI 的主要包。它负责用户界面、命令解析和所有其他面向用户的功能。
当此包发布时,它会被打包成一个单独的可执行文件。此捆绑包包含包的所有依赖项,包括 @google/gemini-cli-core
。这意味着无论用户使用 npm install -g @google/gemini-cli
安装包还是直接使用 npx @google/gemini-cli
运行它,他们都在使用这个单一的、自包含的可执行文件。
@google/gemini-cli-core
此包包含与 Gemini API 交互的核心逻辑。它负责发出 API 请求、处理身份验证和管理本地缓存。
此包不会被打包。当它发布时,它作为标准 Node.js 包与其自己的依赖项一起发布。这允许它在需要时作为独立包在其他项目中使用。dist
文件夹中的所有转译 js 代码都包含在包中。
发布流程
此项目遵循结构化的发布流程,以确保所有包都被正确地版本化和发布。该流程被设计为尽可能自动化。
当前理论
对于大多数更改,简单地修补次要版本是可以接受的。我们可以且应该频繁发布;我们发布得越频繁,就越容易判断是什么更改破坏了某些东西。鼓励开发者在他们的分支合并后按照下面描述的方式推送发布。我也认为我愿意将发布发布步骤作为现有 PR 的一部分来做,尽管如果其他人也在发布且版本号频繁更改,这可能会有更多的变动。
如何发布
发布新版本就像创建和推送新的 Git 标签一样简单。标签必须遵循语义版本控制并以 v
为前缀,例如 v0.2.0
或 v1.0.0-alpha.1
。从您要发布的分支运行以下命令:
# 创建新标签(例如,v0.2.0)
# 可选使用 git log 查找要标记的较旧提交 sha
git tag v0.2.0 <可选 sha>
# 将标签推送到远程存储库以触发发布
git push origin v0.2.0
夜间发布
除了手动发布外,此项目还有一个自动化的夜间发布流程,以提供最新的"前沿"版本用于测试和开发。
流程
每天午夜 UTC 时间,计划夜间发布工作流会自动运行。它执行以下步骤:
- 从
main
分支检出最新代码。 - 安装所有依赖项。
- 运行完整的
preflight
检查套件(代码检查、类型检查等)。 - 运行集成测试,包括有和没有 Docker 的情况。测试会自动重试最多三次以处理任何不稳定性。
- 如果所有检查和测试都成功,它会运行
npm run tag:release:nightly
脚本。此脚本创建并推送格式为v<version>+nightly.<ddmmyy>.<sha>
的新注释 Git 标签。 - 推送此标签会触发主要的发布工作流,该工作流将包发布到 npm,标签为
nightly
。
故障处理
如果夜间工作流中的任何步骤失败,它将自动在存储库中创建一个带有 bug
和 nightly-failure
标签的新问题。该问题将包含指向失败工作流运行的链接,以便于调试。
如何使用夜间构建
要安装最新的夜间构建,请使用 @nightly
标签:
npm install -g @google/gemini-cli@nightly
高级流程是:
- 确保您的本地分支
main
或release-xxx
(如果修复之前版本的热修复)与远程存储库保持最新。 - 根据自上次发布以来的更改决定新的版本号。
- 可选 使用
git log
查找您要推送的提交的 sha(如果不是最新的) - 可选 在本地运行集成测试以增加对发布的信心。
- 使用所需版本号创建新的 Git 标签。
- 将标签推送到
google-gemini/gemini-cli
存储库。 - 推送将触发发布工作流,该工作流自动化其余流程。
- 工作流完成后,它将创建一个带有版本更新的
release/vX.Y.Z
分支。从此分支创建拉取请求,将版本更改合并回main
。
推送新标签将触发发布工作流,该工作流将自动:
- 构建包并将其发布到 npm 注册表。
- 创建带有生成的发布说明的新 GitHub 发布。
- 创建包含
package.json
文件中版本更新的新分支release/vX.Y.Z
。
我们还运行一个名为 release-docker.yml 的 Google cloud build。它发布沙盒 docker 以匹配您的发布。一旦服务帐户权限得到解决,这也将移至 GH 并与主发布文件合并。
2. 监控发布工作流
您可以在 GitHub Actions 选项卡中监控发布工作流的进度。如果工作流失败,您需要调查失败原因,修复问题,然后创建新标签以触发新发布。
发布后
工作流成功完成后,您应该:
- 转到存储库的拉取请求页面。
- 从
release/vX.Y.Z
分支到main
创建新的拉取请求。 - 审查拉取请求(它应该只包含
package.json
文件中的版本更新)并合并它。这保持main
中的版本是最新的。
发布验证
推送新发布后,应该执行冒烟测试以确保包按预期工作。这可以通过在本地安装包并运行一组测试来确保它们正常运行。
npx -y @google/gemini-cli@latest --version
验证推送是否按预期工作(如果您没有执行 rc 或 dev 标签)npx -y @google/gemini-cli@<release tag> --version
验证标签是否适当推送- 这在本地是破坏性的
npm uninstall @google/gemini-cli && npm uninstall -g @google/gemini-cli && npm cache clean --force && npm install @google/gemini-cli@<version>
- 建议对一些 llm 命令和工具进行基本运行的冒烟测试,以确保包按预期工作。我们将来会更多地编码化这一点。
何时合并版本更改,或不合并?
上述从当前或较旧提交创建补丁或热修复发布的模式使存储库处于以下状态:
- 标签(
vX.Y.Z-patch.1
):此标签正确指向 main 上包含您打算发布的稳定代码的原始提交。这很关键。任何检出此标签的人都会获得已发布的确切代码。 - 分支(
release-vX.Y.Z-patch.1
):此分支在标记的提交之上包含一个新提交。该新提交仅包含 package.json(和其他相关文件如 package-lock.json)中的版本号更改。
这种分离是好的。它保持您的主分支历史记录干净,不包含特定于发布的版本更新,直到您决定合并它们。
这是关键决定,完全取决于发布的性质。
为稳定补丁和热修复合并回来
您几乎总是希望将 release-<tag>
分支合并回 main
,用于任何稳定补丁或热修复发布。
- 为什么?主要原因是更新 main 的 package.json 中的版本。如果您从较旧的提交发布 v1.2.1 但从不将版本更新合并回来,您的 main 分支的 package.json 仍将显示 "version": "1.2.0"。下一个开始为下一个功能发布(v1.3.0)工作的开发者将从具有不正确的较旧版本号的代码库分支。这会导致混乱并需要稍后手动版本更新。
- 流程:在创建 release-v1.2.1 分支并成功发布包后,您应该打开拉取请求将 release-v1.2.1 合并到 main。此 PR 将只包含一个提交:"chore: bump version to v1.2.1"。这是一个干净、简单的集成,使您的主分支与最新发布的版本保持同步。
不要为预发布(RC、Beta、Dev)合并回来
您通常不会将预发布的发布分支合并回 main
。
- 为什么?预发布版本(例如,v1.3.0-rc.1、v1.3.0-rc.2)根据定义是不稳定的和临时的。您不希望用一系列发布候选版本的版本更新污染您的主分支历史记录。main 中的 package.json 应该反映最新的稳定发布版本,而不是 RC。
- 流程:创建 release-v1.3.0-rc.1 分支,执行 npm publish --tag rc,然后...分支已经完成了它的目的。您可以简单地删除它。RC 的代码已经在 main(或功能分支)上,因此不会丢失功能代码。发布分支只是版本号的临时载体。
本地测试和验证:打包和发布流程的更改
如果您需要测试发布流程而不实际发布到 NPM 或创建公共 GitHub 发布,您可以从 GitHub UI 手动触发工作流。
- 转到存储库的 Actions 选项卡。
- 点击"Run workflow"下拉菜单。
- 保持
dry_run
选项选中(true
)。 - 点击"Run workflow"按钮。
这将运行整个发布流程,但会跳过 npm publish
和 gh release create
步骤。您可以检查工作流日志以确保一切按预期工作。
在提交对打包和发布流程的任何更改之前,在本地测试它们是至关重要的。这确保包将被正确发布,并且在用户安装时它们将按预期工作。
要验证您的更改,您可以执行发布流程的试运行。这将模拟发布流程而不实际将包发布到 npm 注册表。
npm_package_version=9.9.9 SANDBOX_IMAGE_REGISTRY="registry" SANDBOX_IMAGE_NAME="thename" npm run publish:npm --dry-run
此命令将执行以下操作:
- 构建所有包。
- 运行所有预发布脚本。
- 创建将发布到 npm 的包 tarball。
- 打印将发布的包的摘要。
然后,您可以检查生成的 tarball 以确保它们包含正确的文件,并且 package.json
文件已正确更新。tarball 将在每个包目录的根目录中创建(例如,packages/cli/google-gemini-cli-0.1.6.tgz
)。
通过执行试运行,您可以确信您对打包流程的更改是正确的,并且包将成功发布。
发布深度解析
发布流程的主要目标是从 packages/ 目录中获取源代码,构建它,并在项目根目录的临时 bundle
目录中组装一个干净、自包含的包。这个 bundle
目录是实际发布到 NPM 的内容。
以下是关键阶段:
阶段 1:发布前健全性检查和版本控制
- 发生什么:在移动任何文件之前,流程确保项目处于良好状态。这涉及运行测试、代码检查和类型检查(npm run preflight)。根 package.json 和 packages/cli/package.json 中的版本号更新为新的发布版本。
- 为什么:这保证只有高质量、工作的代码被发布。版本控制是表示新发布的第一步。
阶段 2:构建源代码
- 发生什么:packages/core/src 和 packages/cli/src 中的 TypeScript 源代码被编译成 JavaScript。
- 文件移动:
- packages/core/src/*/.ts -> 编译到 -> packages/core/dist/
- packages/cli/src/*/.ts -> 编译到 -> packages/cli/dist/
- 为什么:开发期间编写的 TypeScript 代码需要转换为可以由 Node.js 运行的纯 JavaScript。核心包首先构建,因为 cli 包依赖于它。
阶段 3:组装最终可发布包
这是最关键的阶段,文件被移动和转换为发布的最终状态。在项目根目录创建临时 bundle
文件夹来容纳最终包内容。
package.json
被转换:- 发生什么:从 packages/cli/ 读取 package.json,修改并写入根
bundle
/ 目录。脚本 scripts/prepare-cli-packagejson.js 负责此操作。 - 文件移动:packages/cli/package.json -> (内存转换)->
bundle
/package.json - 为什么:最终的 package.json 必须与开发中使用的不同。关键更改包括:
- 删除 devDependencies。
- 删除工作区特定的 "dependencies": { "@gemini-cli/core": "workspace:*" } 并确保核心代码直接打包到最终 JavaScript 文件中。
- 确保 bin、main 和 files 字段指向最终包结构中的正确位置。
- 发生什么:从 packages/cli/ 读取 package.json,修改并写入根
创建 JavaScript 捆绑包:
- 发生什么:来自 packages/core/dist 和 packages/cli/dist 的构建 JavaScript 被打包成单个可执行 JavaScript 文件。
- 文件移动:packages/cli/dist/index.js + packages/core/dist/index.js -> (由 esbuild 打包)->
bundle
/gemini.js(或类似名称)。 - 为什么:这创建了一个包含所有必要应用程序代码的单个优化文件。它通过消除核心包作为 NPM 上单独依赖项的需要来简化包,因为其代码现在直接包含在内。
复制静态和支持文件:
- 发生什么:不是源代码的一部分但对包正确工作或良好描述所需的基本文件被复制到
bundle
目录中。 - 文件移动:
- README.md ->
bundle
/README.md - LICENSE ->
bundle
/LICENSE - packages/cli/src/utils/*.sb(沙盒配置文件)->
bundle
/
- README.md ->
- 为什么:
- README.md 和 LICENSE 是任何 NPM 包中应该包含的标准文件。
- 沙盒配置文件(.sb 文件)是 CLI 沙盒功能正常运行所需的关键运行时资产。它们必须位于最终可执行文件旁边。
- 发生什么:不是源代码的一部分但对包正确工作或良好描述所需的基本文件被复制到
阶段 4:发布到 NPM
- 发生什么:从根
bundle
目录内运行 npm publish 命令。 - 为什么:通过从
bundle
目录内运行 npm publish,只有我们在阶段 3 中仔细组装的文件被上传到 NPM 注册表。这防止任何源代码、测试文件或开发配置被意外发布,为用户提供干净和最小的包。
文件流摘要
graph TD
subgraph "源文件"
A["packages/core/src/*.ts<br/>packages/cli/src/*.ts"]
B["packages/cli/package.json"]
C["README.md<br/>LICENSE<br/>packages/cli/src/utils/*.sb"]
end
subgraph "流程"
D(构建)
E(转换)
F(组装)
G(发布)
end
subgraph "产物"
H["打包的 JS"]
I["最终 package.json"]
J["bundle/"]
end
subgraph "目标"
K["NPM 注册表"]
end
A --> D --> H
B --> E --> I
C --> F
H --> F
I --> F
F --> J
J --> G --> K
此流程确保最终发布的产物是项目的专门构建、干净和高效的表示,而不是开发工作区的直接副本。
NPM 工作区
此项目使用 NPM 工作区来管理此 monorepo 中的包。这通过允许我们从项目根目录管理依赖项并跨多个包运行脚本来简化开发。
工作原理
根 package.json
文件定义了此项目的工作区:
{
"workspaces": ["packages/*"]
}
这告诉 NPM,packages
目录内的任何文件夹都是应该作为工作区的一部分管理的单独包。
工作区的好处
- 简化依赖管理:从项目根目录运行
npm install
将为工作区中的所有包安装所有依赖项并将它们链接在一起。这意味着您不需要在每个包的目录中运行npm install
。 - 自动链接:工作区内的包可以相互依赖。当您运行
npm install
时,NPM 将自动在包之间创建符号链接。这意味着当您对一个包进行更改时,这些更改立即可用于依赖它的其他包。 - 简化脚本执行:您可以使用
--workspace
标志从项目根目录在任何包中运行脚本。例如,要在cli
包中运行build
脚本,您可以运行npm run build --workspace @google/gemini-cli
。