Lerna version 命令完全指南:多包仓库的版本号管理与发布流程
【免费下载链接】lernaLerna is a fast, modern build system for managing and publishing multiple JavaScript/TypeScript packages from the same repository.项目地址: https://gitcode.com/gh_mirrors/le/lerna
lerna version是 Lerna 中负责"为自上次发布以来发生变更的包提升版本号"的核心命令,它串联起变更检测、版本选择、manifest 修改、changelog 生成、git 提交打标签与推送的完整发布链路。本文以本仓库中 libs/commands/version/README.md 为主体,结合 命令入口实现 与 核心执行逻辑 的源码细节,系统讲解该命令的用法、全部配置项、prerelease 策略与生命周期脚本执行顺序,帮助你掌握一套可落地的多包版本管理方案。
一、命令概览:一次运行完成五件事
lerna version的运行流程可以概括为以下五个步骤(也是该命令的官方定义):
- 识别变更包:找出自上一次 tagged release 以来发生更新的包;
- 提示选择新版本:交互式询问每个变更包的新版本号;
- 修改包元数据:更新
package.json中的版本字段,并在根目录和各个包内执行相应的生命周期脚本; - 提交并打标签:将这些变更提交到 git 并打上版本标签;
- 推送到远端:将提交与标签推送到 git remote。
在 libs/commands/version/src/index.ts 的execute()方法中,可以清楚看到这四个执行阶段被编排为串行任务队列:updatePackageVersions()(更新各包版本、依赖与 lockfile)→commitAndTagUpdates()(提交并打标签)→gitPushToRemote()(推送)→createRelease()(可选,创建 GitHub/GitLab Release)。
二、基本用法与位置参数bump
命令的三种典型调用方式:
lerna version 1.0.1 # 显式指定版本号 lerna version patch # semver 关键字 lerna version # 从交互式提示中选择semver bump 位置参数
lerna version [major | minor | patch | premajor | preminor | prepatch | prerelease] # 直接使用下一个语义化版本号,跳过 "Select a new version for..." 提示当传入该位置参数时,lerna version会跳过版本选择提示,直接按该关键字递增版本号(底层使用semver.inc()进行递增,见 getVersionsForUpdates 实现)。但注意:你仍然需要--yes标志来跳过全部确认提示。
从源码角度,位置参数的校验逻辑在 libs/commands/version/src/command.ts 的addBumpPositional()中实现:yargs 会将输入值coerce校验,只有满足semver.valid()或属于七个关键字之一才会通过,否则抛出bump must be an explicit version string _or_ one of: ...错误。随后在index.ts中,合法版本号走repoVersion分支(全局统一版本),合法关键字走increment分支(按major/minor/...递增)。
固定模式与独立模式的区别
版本号的选择策略取决于lerna.json中的version配置:
- 固定模式(fixed,默认):所有包共享同一个版本号,运行时一次提示、全局统一;源码中通过
makeGlobalVersionPredicate与setGlobalVersionFloor/Ceiling保证各包版本被收敛到全局版本(含 breaking change 时强制所有包同步 major,见 setUpdatesForVersions); - 独立模式(independent):
"version": "independent",每个包分别提示并拥有独立版本号与独立 tag。
三、Prerelease 预发布版本管理
如果你的仓库中存在携带预发布版本号的包(如2.0.0-beta.3),并且执行非预发布 bump(major、minor、patch),Lerna 会将这些"先前已预发布"的包与自上次发布以来变更的包一并发布。
对于使用 conventional commits 的项目,预发布管理依赖以下三个标志:
--conventional-prerelease:将当前变更以预发布版本形式发布;--conventional-graduate:将预发布版本的包"毕业"到稳定版本;- 只运行
lerna version --conventional-commits(不带上述标志)时,仅当版本本身已经是预发布版本时,当前变更才会以预发布形式发布。
四、核心配置项:变更检测与版本选择
--allow-branch <glob>
对允许执行lerna version的 git 分支做 glob 白名单过滤。推荐配置在lerna.json中:
{ "command": { "version": { "allowBranch": "main" } } }上述配置下,在main之外的任何分支运行lerna version都会直接失败。将版本发布限制在主分支是公认的最佳实践。也支持分支数组:
{ "command": { "version": { "allowBranch": ["main", "feature/*"] } } }上面的配置允许所有feature/前缀分支执行版本命令。但请务必注意:在 feature 分支上生成 git tag 隐患很大——当这些分支被合并进主分支后,若 tag 因 squash 合并或冲突解决而"脱离"了原始上下文,后续lerna version将难以正确计算"自上次发布以来的 diff"。
持久化配置总可以被命令行覆盖,请谨慎使用:
lerna version --allow-branch hotfix/oops-fix-the-thing在源码中,该选项通过minimatch对当前分支名做匹配,不匹配时抛出ENOTALLOWED校验错误(见 index.ts initialize 阶段)。
--amend
lerna version --amend # 保留原有提交信息,并跳过 git push使用该标志后,lerna version将在当前提交上直接修改,而不是新增一个提交。这对 CI 场景非常有用,可以减少项目历史中的提交数量。为防止意外覆盖,该命令会跳过git push(即隐式开启--no-push)。源码中pushToRemote = gitTagVersion && amend !== true && push直接体现了这一约束(index.ts),git 提交时也通过--amend --no-edit保留原提交信息(见 git-commit.ts)。
--build-metadata <buildMetadata>
lerna version --build-metadata 001构建元数据必须符合 SemVer 规范。提供后它将应用于所有更新的包,无论项目使用独立还是固定版本模式。如果选择交互式提示,可以通过请求自定义版本为特定包修改或移除构建元数据。源码中通过applyBuildMetadata()在固定、独立与推荐版本各分支统一注入(index.ts)。
--ci-behind-behavior <behavior>
lerna version --ci-behind-behavior skip控制 CI 环境下当前分支落后于其上游 remote 时lerna version的行为:
- 默认值
error:抛出EBEHIND错误并以非零退出码失败; skip:记录EBEHIND警告并以成功状态退出,不执行版本化或发布。
非 CI 的交互式执行中,只要当前分支落后于上游就始终失败。该选项同样可写入lerna.json:
{ "command": { "version": { "ciBehindBehavior": "skip" } } }在 command.ts 中该参数被限定为choices: ["error", "skip"];行为逻辑见 index.ts,底层通过git remote update与git rev-list --left-right --count判断落后提交数(is-behind-upstream.ts)。
--ignore-changes
检测变更包时忽略匹配 glob(s) 的文件。该选项最适合配置在根目录lerna.json,既避免 shell 过早展开 glob,也便于与lerna diff、lerna changed共享配置:
{ "ignoreChanges": ["**/__fixtures__/**", "**/__tests__/**", "**/*.md"] }命令行方式:
lerna version --ignore-changes '**/*.md' '**/__tests__/**'传--no-ignore-changes可禁用任何已有的持久化配置。文档还特别提醒:以下两种情况下包一定会被发布,不受本选项约束:
- 该包最新 release 是 prerelease 版本(如
1.0.0-alpha、1.0.0–0.3.7等); - 该包的一个或多个本地关联依赖发生了变化。
--include-merged-tags
lerna version --include-merged-tags检测变更包时,包含来自已合并分支的 tag。
--exact
lerna version --exact指定更新包中的关联依赖时使用精确版本号(不带标点),而不是 semver 兼容范围(带^)。对应源码中的savePrefix = this.options.exact ? "" : "^"(index.ts),该前缀随后用于updateDependencies()中改写本地依赖声明(index.ts)。
--force-publish
lerna version --force-publish=package-2,package-4 # 强制所有包都进行版本化 lerna version --force-publish强制发布指定包(逗号分隔)或所有包(*)。这会跳过lerna changed的变更检查,强制更新一个没有git diff变更的包。注意:与--conventional-commits和--conventional-graduate组合使用时该选项会被忽略。
--preid
lerna version prerelease # 使用下一个语义化预发布版本,例如 # 1.0.0 => 1.0.1-alpha.0 lerna version prepatch --preid next # 使用带特定预发布标识符的下一个版本,例如 # 1.0.0 => 1.0.1-next.0该标志指定premajor、preminor、prepatch或prereleasebump 使用的预发布标识符。默认标识符为alpha(见 command.ts 的defaultDescription: "alpha",以及 index.ts 中resolvePrereleaseId的preid || existingPreid || "alpha"优先级链)。
--premajor-version-bump
控制当检测到非破坏性变更时,Lerna 如何处理premajor版本(尚未进行过 major 发布的包,如"version": "0.2.4")的 bump。注意:premajor 包中的破坏性变更始终触发minorbump。
lerna version --conventional-commits # 或者显式使用默认值 lerna version --conventional-commits --premajor-version-bump default # 所有非破坏性变更按配置的 conventional commits preset 触发 bump # 对于默认 preset,非破坏性 feat 是 minor bump # 0.1.0 --> 0.2.0 lerna version --conventional-commits --premajor-version-bump force-patch # 确保所有非破坏性 premajor bump 都按 patch 处理 # 此时非破坏性 feat 永远是 patch bump # 0.1.0 --> 0.1.1该参数在 command.ts 中被限定为choices: ["default", "force-patch"],并在recommendVersions()中传给recommendVersion()参与计算(index.ts)。
--json
lerna version --json以 JSON 格式输出更新的包。示例输出:
[ { "name": "footer", "version": "0.0.4", "private": false, "location": "/home/ammo/git/lerna-getting-started-example/packages/footer", "newVersion": "0.0.5" }, { "name": "header", "version": "0.0.4", "private": false, "location": "/home/ammo/git/lerna-getting-started-example/packages/header", "newVersion": "0.0.5" }, { "name": "remixapp", "version": "0.0.4", "private": true, "location": "/home/ammo/git/lerna-getting-started-example/packages/remixapp", "newVersion": "0.0.5" } ]五、Conventional Commits 相关选项
--conventional-commits
lerna version --conventional-commits使用conventional-changelog包来确定版本 bump 并生成CHANGELOG.md文件。若不额外指定--changelog-preset,将使用conventional-changelog的默认配置,即当前的 angular 规范。如果想要按其他规范(如支持在 header 中使用!表示破坏性变更的 Conventional Commits 规范)进行版本递增,需要传入规范名,例如--changelog-preset conventionalcommits。配合--no-changelog可禁用CHANGELOG.md的生成(或更新)。
--changelog-preset
lerna version --conventional-commits --changelog-preset angular-bitbucket默认 changelog preset 为angular。Preset 可以是内置或可安装的 conventional changelog 配置包的名称,既可以用完整包名,也可以用自动展开的后缀(如angular会展开为conventional-changelog-angular)。该选项也可写在lerna.json:
{ "changelogPreset": "angular" }如果 preset 导出的是 builder 函数(如conventional-changelog-conventionalcommits),还可以同时指定 preset 配置:
{ "changelogPreset": { "name": "conventionalcommits", "issueUrlFormat": "{{host}}/{{owner}}/{{repository}}/issues/{{id}}" } }若使用默认 preset 之外的任何 preset,还应在 devDependencies 中安装对应的conventional-changelog包。该参数同时参与每个包版本 bump 的计算(传入recommendVersion)。
--changelog-entry-additional-markdown
lerna version --conventional-commits --changelog-entry-additional-markdown "### Some title\n\nSome *paragraph*"允许为当前 release 的新增 changelog 条目追加额外的 markdown 内容,例如补充文档或链接到参考站点、视频。如果文本是静态的、每次发布不变,可以固化在lerna.json中:
{ "command": { "version": { "changelogEntryAdditionalMarkdown": "### Some title\n\nSome *paragraph*" } } }--conventional-graduate
lerna version --conventional-commits --conventional-graduate=package-2,package-4 # 强制所有 prerelease 包毕业 lerna version --conventional-commits --conventional-graduate将指定包(逗号分隔)或所有包(*)"毕业"(graduate)为稳定版本。与--force-publish类似,它不要求当前 HEAD 已发布;区别在于非 prerelease 的包会被忽略。若存在未指定包(指定包列表时)或非 prerelease 包的变更,这些包会按--conventional-commits的正常逻辑版本化。
"毕业"意味着将 prerelease 版本提升为对应的非 prerelease 变体,例如:package-1@1.0.0-alpha.0 => package-1@1.0.0。
注意:指定包时,被指定包的依赖方会被发布,但不会被毕业。
--force-conventional-graduate
lerna version --conventional-commits --conventional-graduate=package-2,package-4 --force-conventional-graduate # 强制所有 prerelease 包毕业,且非 prerelease 或无变更的包也一并更新 lerna version --conventional-commits --conventional-graduate --force-conventional-graduate强制毕业所有由--conventional-graduate指定的包,此时非 prerelease 包不会像不加该标志时那样被忽略。在单版本(fixed)模式下,可以用来强制所有指定包统一更新到同一版本,即使它们没有变更或不是 prerelease。它类似于--force-publish,但在启用了--conventional-commits与--conventional-graduate时不会被忽略。该标志仅在设置了--conventional-graduate时生效,否则被忽略。
--conventional-prerelease
lerna version --conventional-commits --conventional-prerelease=package-2,package-4 # 强制所有变更包以 prerelease 形式发布 lerna version --conventional-commits --conventional-prerelease将指定包(逗号分隔)或所有包(*)以 prerelease 版本发布。实现方式是为conventional-commits推荐的版本加pre前缀:例如当前变更包含一个 feature commit 时,推荐 bump 为minor,该标志将产生preminor发布。若存在未指定包(指定包列表时)或已是 prerelease 的包的变更,这些包会按--conventional-commits的正常逻辑版本化。
--conventional-bump-prerelease
lerna version --conventional-commits --conventional-prerelease --conventional-bump-prerelease即使已发布的包已经是 prerelease,也强制以递增后的 prerelease 版本发布。与--conventional-prerelease一样,通过对conventional-commits推荐版本加pre前缀实现;不同之处在于:不加该标志时,对已是 prerelease 的包只应用 prerelease bump。开启后效果示例:
Changes: - major: 1.0.0-alpha.0 => 2.0.0-alpha.0 - minor: 1.0.0-alpha.0 => 1.1.0-alpha.0 - patch: 1.0.0-alpha.0 => 1.0.1-alpha.0另外需要留意:--conventional-prerelease与--conventional-graduate不能同时使用,源码中对此组合抛出ENOTALLOWED错误(index.ts)。
六、git 交互与提交推送选项
--message <msg>(别名-m)
该选项为-m起别名以对齐git commit的习惯:
lerna version -m "chore(release): publish %s" # commit message = "chore(release): publish v1.0.0" lerna version -m "chore(release): publish %v" # commit message = "chore(release): publish 1.0.0" # 独立版本模式下不替换任何占位符 lerna version -m "chore(release): publish" # commit message = "chore(release): publish # # - package-1@3.0.1 # - package-2@1.5.4"- 若消息包含
%s,会被替换为带v前缀的全局版本号; - 若消息包含
%v,会被替换为不带v前缀的全局版本号; - 占位符替换仅适用于默认的"固定"版本模式——独立版本模式没有"全局"版本可供替换,此时每个更新包的标签会以
@分隔符追加到消息主体之后(源码见 gitCommitAndTagVersionForUpdates)。
该选项同样可配置在lerna.json:
{ "command": { "version": { "message": "chore(release): publish %s" } } }这对于集成 commitizen、semantic-release 等期望特定提交消息规范的项目很有用。固定模式下的占位符替换实现位于 gitCommitAndTagVersion:message.replace(/%s/g, tag).replace(/%v/g, version)。
--git-tag-command <cmd>
允许指定自定义命令来应用 git tag,例如为 CI/CD 管道中没有直接写权限的包装命令提供支持:
lerna version --git-tag-command "git gh-tag %s -m %s"也可配置在lerna.json:
{ "command": { "version": { "gitTagCommand": "git gh-tag %s -m %s" } } }默认命令是git tag %s -m %s。源码中 git-tag.ts 会将命令按空格拆分,并把%s替换为 tag 名;同时--force-git-tag会追加--force参数、--sign-git-tag会追加--sign参数。
--git-remote <name>
lerna version --git-remote upstream将 git 变更推送到指定 remote 而不是默认的origin。默认值为origin(command.ts)。对应的推送实现见 git-push.ts,其中使用git push --follow-tags --no-verify --atomic推送,并在--atomic不被旧版 git 支持时自动降级为重试非原子推送。
--no-commit-hooks
默认情况下lerna version在提交版本变更时会允许 git commit hooks 运行。传--no-commit-hooks禁用此行为(对应 npm version 的--commit-hooks选项,语义反转)。源码中 git-commit.ts 会在commitHooks === false时追加--no-verify。
--no-git-tag-version
默认lerna version会提交 package.json 变更并打标签。传--no-git-tag-version禁用此行为(对应 npm version 的--git-tag-version选项,语义反转)。禁用后源码会输出Skipping git tag/commit日志。
--no-granular-pathspec
默认情况下lerna version只git add版本化过程中变更过的叶子包 manifest(以及可能的 changelog),效果等价于git add -- packages/*/package.json,但精确到实际变更的文件。若你确定需要不同行为,才应传--no-granular-pathspec,它会让 git 命令字面意义上变成git add -- .。选择这种 pathspec 后,必须确保所有密钥和构建产物已被正确忽略,否则它们将被提交并推送。
该选项最适合配置在lerna.json中,因为真的不建议搞错:
{ "version": "independent", "granularPathspec": false }根级配置是刻意的,这样还能同时覆盖lerna publish中同名的选项。源码实现见 git-add.ts:粒度模式收集相对路径的文件列表,非粒度模式则使用"."全局添加。
--no-private
默认情况下lerna version在选择版本、提交和打标签时会包含 private 包。传--no-private禁用此行为。注意该选项不排除私有 scoped 包(如@scope/pkg),只排除package.json中带"private": true字段的包。在 index.ts 中,--no-private会在收集更新包时完全移除 private 包,同时未设置版本的包只有在其为 private 时才会被跳过(否则抛出ENOVERSION)。
--no-push
默认lerna version会将提交和标签推送到配置的 git remote。传--no-push禁用推送。
--sign-git-commit/--signoff-git-commit/--sign-git-tag/--force-git-tag
--sign-git-commit:提交时传递--gpg-sign标志(对应 npm version 同名选项);--signoff-git-commit:提交时追加--signoff标志。注意它与--sign-git-commit不同,后者是 GPG 签名;--sign-git-tag:打标签时传递--sign标志(对应 npm version 同名选项);--force-git-tag:替换任何已有标签而不是报错(对应 git tag 的--force)。
上述标志在 git-commit.ts 与 git-tag.ts 中均有直接对应实现。
--tag-version-prefix
提供自定义的 tag 前缀,默认是v。注意:目前需要为version命令和publish命令各指定一次:
# 本地 lerna version --tag-version-prefix='' # CI 上 lerna publish from-git --tag-version-prefix=''--sync-dist-version
更新 contents 目录(dist 等发布产物目录)中package.json的版本号。对应pkg.syncDistVersion(syncDistVersion)调用(index.ts)。
--amend的补充说明
与--no-git-tag-version、--no-push组合使用时,--amend会跳过工作区校验(因为 amend 提交通常意味着工作区是脏的),此时日志会警告Skipping working tree validation, proceed at your own risk(index.ts)。
七、锁文件与 npm 客户端相关选项
--npm-client-args
该选项允许向lerna version为更新 lockfile 而执行的npm install传递参数。例如:
lerna version 3.3.3 --npm-client-args=--legacy-peer-deps lerna version 3.3.3 --npm-client-args="--legacy-peer-deps,--force" lerna version 3.3.3 --npm-client-args="--legacy-peer-deps --force"也可设置在lerna.json中:
{ "npmClientArgs": ["--legacy-peer-deps", "--production"] }或仅针对 version 命令:
{ "command": { "version": { "npmClientArgs": ["--legacy-peer-deps", "--production"] } } }源码中参数数组会按空白或逗号展开后拼接进 install 命令(index.ts)。
--run-scripts-on-lockfile-update
默认情况下,lerna version在版本 bump 后同步 package-lock 文件时会跳过所有生命周期脚本。开启该选项后会运行prepare、postinstall等脚本。对应实现中,未开启时会给 install 追加--ignore-scripts(index.ts)。
各包管理器 lockfile 的同步逻辑
源码 updatePackageVersions 按npmClient分支处理:
- pnpm:执行
pnpm install --lockfile-only,更新根目录pnpm-lock.yaml; - bun:通过
updateBunLockfile()更新 bun.lock(见 update-bun-lockfile.ts); - yarn:检测 yarn 版本,
>=2.0.0时执行yarn install --mode update-lockfile并设置YARN_ENABLE_SCRIPTS=false,更新根目录yarn.lock; - npm(或未指定):仅当根目录存在
package-lock.json时执行npm install --package-lock-only,更新根目录锁文件。
八、Release 发布与其余选项
--create-release <type>
lerna version --conventional-commits --create-release github lerna version --conventional-commits --create-release gitlab基于变更的包创建正式的 GitHub 或 GitLab Release。要求必须同时传--conventional-commits(否则源码抛出ERELEASE:To create a release, you must enable --conventional-commits)。
GitHub 认证环境变量:
GH_TOKEN(必需):GitHub 认证 token(Settings > Developer settings > Personal access tokens),创建时请授予repo:public_reposcope;GHE_API_URL:使用 GitHub Enterprise 时,API 的绝对 URL;GHE_VERSION:使用 GitHub Enterprise 时,当前安装的 GHE 版本。
GitLab 认证环境变量:
GL_TOKEN(必需):GitLab 认证 token(User Settings > Access Tokens);GL_API_URL:包含版本号的 API 绝对 URL(默认https://gitlab.com/api/v4)。
注意:使用该选项时不能同时传
--no-changelog(源码中同样以ERELEASE校验,见 index.ts)。
Release 的创建实现位于 create-release.ts,会基于本次生成的 tag 与各包 changelog 条目(releaseNotes)构建 release 内容。
--no-changelog
lerna version --conventional-commits --no-changelog使用conventional-commits时不生成任何CHANGELOG.md文件。
注意:使用该选项时不能同时传
--create-release。
--ignore-scripts
传入后,lerna version期间将禁用生命周期脚本的运行。
--yes
lerna version --yes # 跳过确认提示跳过所有确认提示。在 CI 中自动应答发布确认提示非常有用。对应源码confirmVersions()中this.options.yes分支(index.ts),交互式提示消息在独立执行时为Are you sure you want to create these versions?,被lerna publish组合调用时为Are you sure you want to publish these packages?。
交互式版本选择器的工作方式
不带bump位置参数且不使用--conventional-commits时,会触发交互式提示。从 prompt-version.ts 的实现看,提示器会根据当前版本预计算 6 个候选(Patch / Minor / Major / Prepatch / Preminor / Premajor),并提供Custom Prerelease(自定义 prerelease 标识符)与Custom Version(自定义完整版本,需通过semver.valid校验)两个入口,所有候选都会应用--build-metadata。
九、弃用选项(Deprecated Options)
以下旧选项已被移除,运行时会被拦截并提示改用新写法:
--cd-version:请改用bump位置参数传 semver 关键字;--repo-version:请改用bump位置参数传显式版本号;--skip-git:请改用--no-git-tag-version和--no-push。
注意:
--skip-git不会限制所有 git 命令的执行——lerna version仍然依赖git。
另外源码中还拦截了更早的--ignore(改名为--ignore-changes)与--github-release(改为--create-release=github)两个旧选项,并统一建议运行lerna repair来同步lerna.json(command.ts)。
十、实战技巧:生成历史 Changelog
如果在 monorepo 活跃一段时间之后才启用--conventional-commits,仍可通过conventional-changelog-cli配合lerna exec为之前的 release 生成 changelog:
# Lerna 实际并不使用 conventional-changelog-cli,需要临时安装 npm i -D conventional-changelog-cli # 文档:npx conventional-changelog --help # 固定版本模式(默认) # 先在根目录运行,再在叶子包中运行 npx conventional-changelog --preset angular --release-count 0 --outfile ./CHANGELOG.md --verbose npx lerna exec --concurrency 1 --stream -- 'conventional-changelog --preset angular --release-count 0 --commit-path $PWD --pkg $PWD/package.json --outfile $PWD/CHANGELOG.md --verbose' # 独立版本模式 # (不生成根目录 changelog) npx lerna exec --concurrency 1 --stream -- 'conventional-changelog --preset angular --release-count 0 --commit-path $PWD --pkg $PWD/package.json --outfile $PWD/CHANGELOG.md --verbose --lerna-package $LERNA_PACKAGE_NAME'如果使用了自定义--changelog-preset,请将示例中的--preset值对应修改。
十一、生命周期脚本执行顺序
Lerna 在lerna version期间会按以下语义运行 npm 生命周期脚本:
// preversion: Run BEFORE bumping the package version. // version: Run AFTER bumping the package version, but BEFORE commit. // postversion: Run AFTER bumping the package version, and AFTER commit.完整执行顺序:
- 检测变更包,选择版本 bump;
- 在根目录运行
preversion生命周期; - 对每个变更包,按拓扑顺序(所有依赖先于依赖方):
- 运行该包的
preversion生命周期; - 更新 package.json 中的版本号;
- 运行该包的
version生命周期;
- 运行该包的
- 在根目录运行
version生命周期; - 将变更文件加入 git index(若未禁用
--no-git-tag-version); - 创建提交和 tag(若未禁用
--no-git-tag-version); - 对每个变更包,按字典顺序(按目录结构的字母序):
- 运行该包的
postversion生命周期;
- 运行该包的
- 在根目录运行
postversion生命周期; - 将提交和 tag 推送到远端(若未禁用
--no-push); - 创建 Release(若启用
--create-release)。
源码层面的佐证:updatePackageVersions()中根目录preversion最先执行(且存在一个防递归保护——如果npm_lifecycle_event本身就是(pre|post)?version,会跳过根生命周期避免递归,见 index.ts);包级动作链依次为preversion→ 刷新 manifest → 写入新版本与依赖 → 序列化/更新锁文件 →version→(可选)更新 changelog,并通过runProjectsTopologically保证拓扑顺序;commitAndTagUpdates()在提交打标签后通过pMap执行postversion。另外,包级更新动作之间会重新refresh()manifest,因为"任何前一个生命周期都可能修改 manifest"。git add之前还会检测工作区是否安装了 prettier,若有则对变更文件自动格式化(见 git-add.ts)。
十二、执行前的 git 前置校验
除了上述配置,lerna version在正式运行前还会做一系列 git 校验(index.ts initialize),理解这些校验有助于排查 CI 中常见的失败:
- 仓库必须有提交:无任何提交时抛出
ENOCOMMIT; - 必须处于分支而非 detached HEAD:
getCurrentBranch返回HEAD时抛出ENOGIT; - 分支必须存在于远端:需要推送时远端不存在当前分支会抛出
ENOREMOTEBRANCH; - 分支受
allowBranch约束:不匹配时抛出ENOTALLOWED; - 本地分支不得落后于上游:否则抛出
EBEHIND(交互模式始终失败,CI 下由--ci-behind-behavior决定成败); - 工作区必须干净:默认执行
checkWorkingTree,而--force-publish或conventional-commits + conventional-graduate组合时降级为仅检查未提交内容(throwIfUncommitted)。
满足这些前置条件后,lerna version会依次完成版本计算、确认、manifest 更新、changelog 生成、提交打标签与推送,最终输出更新包清单与各自的新版本号,完成一次可靠的多包发布流程。
【免费下载链接】lernaLerna is a fast, modern build system for managing and publishing multiple JavaScript/TypeScript packages from the same repository.项目地址: https://gitcode.com/gh_mirrors/le/lerna
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考