ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

changesets publish 命令深度解析:发布流程、返回值与 2FA 处理机制

changesets publish 命令深度解析:发布流程、返回值与 2FA 处理机制 changesets publish 命令深度解析发布流程、返回值与 2FA 处理机制【免费下载链接】changesets A tool to manage versioning and changelogs with a focus on monorepos项目地址: https://gitcode.com/gh_mirrors/ch/changesets导读changeset publish是 changesets 发布流水线的最后一环负责把changeset version生成的版本变更真正发布到 npm 等 registry并为发布成功的包创建 git tag。本文以 publish 命令的官方说明 为骨架结合 命令实现源码 与对应测试完整讲解{PM}.publish()的四种返回值、基于拓扑分块topological chunk的五步发布流程以及 TTY 环境下 OTP/2FA 的顺序发布与批量发布切换机制。读完本文你将能理解 changesets 在何种场景下顺序发布、何时切回批量模式、如何处理failed:needs-2fa与failed:already-published并能在 CI 与非交互环境中正确配置发布。命令概览publish 是什么命令形态与适用时机changeset publish的经典调用方式为changeset publish [--otp{token}]按照 docs/command-line-options.md 的说明该命令会进入每个包检查其package.json中的版本是否已发布到 npm若未发布则执行npm publish。如果项目使用 pnpmchangesets 会自动检测并改用pnpm publish使用 Yarn Berry 时则调用yarn npm publish。两条重要的使用约束假设最后一次提交就是发布提交version与publish之间不应再有新的提交二者拆分为两个命令正是为了让用户在真正发布前有机会检查变更是否正确。发布后需要手动推送 tagpublish 会在本地生成 git tag但不会自动推送远端官方推荐发布完成后执行git push --follow-tags。命令本身还支持两个常用参数--otp{token}直接提供 npm 一次性密码one-time password。未提供时 CLI 会提示输入。--tag TAGNAME为发布的包指定 npm dist-tag 而非默认的latest适合测试/验证用途常与 snapshot 发布配合使用。更多细节可参考 docs/snapshot-releases.md。发布前的准备在调用publish之前需要先经过changeset add收集变更集与changeset version更新版本号与 CHANGELOG两个阶段并将版本提交合入主干分支。publish只负责把已经定好版本的包发出去它本身不修改任何版本文件。{PM}.publish()的四种返回值publish 命令 README 首先定义了发布动作的返回值契约。这里的{PM}是 PackageManager 的抽象——在源码中对应 PublishTool 接口其publish方法统一返回PublishResult。四种取值分别是返回值含义附加字段published发布成功版本已推送到 registry无failed通用失败如 E403 权限拒绝、E404 包不存在等code?、message?failed:already-published该版本已存在于 registry视为已发布而非错误code?failed:needs-2fa需要双因素认证OTP/交互式登录才能继续authUrl?、doneUrl?从类型定义看PublishResult是一个判别联合discriminated union基类固定携带name、version、result三个字段types.ts。其中failed:needs-2fa是唯一带结构化恢复信息的失败类型authUrl与doneUrl由 npm v11 及以上版本在 EOTP 错误中返回用于描述在何处完成认证以及认证完成后跳转到哪里npm.ts。三个包管理器对返回值的归一化细节略有不同npm通过npm publish --json解析输出识别EOTP需要 2FA、ENEEDAUTH未认证以及cannot publish over the previously published已发布等错误码npm.ts。pnpm识别ERR_PNPM_OTP_NON_INTERACTIVE非交互式 OTP 缺失与E403/E404且 pnpm 10 及以下版本会把 registry 错误委托给 npm 的错误处理逻辑pnpm.ts。yarn通过 Yarn Berry 的 reporter 输出流解析错误YN0033或消息中包含 otp/authentication 关键字时归类为failed:needs-2fayarn.ts。export type PublishResult | PublishResultSuccess // { result: published } | PublishResultFailed // { result: failed, code?, message? } | PublishResultAlreadyPublished // { result: failed:already-published, code? } | PublishResultFailedNeeds2fa // { result: failed:needs-2fa, code?, message?, authUrl?, doneUrl? }这段类型声明lib/types.ts是整个发布状态机的数据基础主流程对每个结果做isPublishSuccessful/isPublishFailure判断lib/common.tsisPublishFailure的实现是result.result.startsWith(failed)因此后三种失败值都会被纳入统一的失败统计。Publish Flow五步发布流程README 将发布流程归纳为五个步骤源码中的publish函数index.ts逐条落实了这五步。先看整体骨架export async function publish(options?: PublishOptions) { // 1. 解析参数、读取包信息与配置 const packages await getPackages(cwd); const packagesByName new Map(packages.packages.map((pkg) [pkg.packageJson.name, pkg])); const publishTool await getPublishTool(packages); // 按包管理器选择 npm/pnpm/yarn const config await readConfig(packages); // 2. 生成发布计划按依赖图拓扑分块 const plan artifactDir ? await readPlanFile(path.join(artifactDir, publish-plan.json)) : await getPublishPlan(packages.rootDir, config, { tag: releaseTag }); // 3. 逐 chunk 发布顺序发布 批量发布 2FA 恢复 publishChunks: for (const chunk of plan) { // ... sequential / bulk 双模式 } // 4. 汇总结果、输出报告、创建 git tag if (successfulNpmPublishes.length ! 0) { /* 成功列表 */ } if (unsuccessfulNpmPublishes.length ! 0) { log.error(/* 失败列表 */); throw new ExitError(1); } }下面按 README 的五步逐一展开。第一步按拓扑分块chunk处理发布计划发布计划由 getPublishPlan.ts 生成本质是一个二维数组外层是发布顺序内层是同一批次chunk内可以并发的包。export type PublishPlan ReadonlyArray ReadonlyArrayPublishReleaseEntry | TagReleaseEntry ;分块算法sortReleases见 getPublishPlan.ts基于依赖图getDependentsGraph构建发布图再用graphSequencer求出拓扑序依赖者dependent必须在被依赖者之后发布例如pkg-a依赖pkg-b则pkg-b所在的 chunk 先于pkg-a所在 chunk。图中存在环时不会直接报错而是打印循环依赖警告后继续getPublishPlan.ts第 262-268 行。计划中的条目分为两类publish真正要发到 registry 的包与tag-only仅打 git tag 的私有包由config.privatePackages.tag开启后纳入计划。测试 publishes release chunks sequentially 验证了这一点pkg-a依赖pkg-b时发布调用顺序必须是pkg-b先、pkg-a后且打 tag 的顺序与之对应。注意发布计划基于注册表查询结果npm info/pnpm info/yarn npm info计算哪些包尚未发布。源码注释明确指出由于 registry 的具体解析被委托给各包管理器 CLI计划本身不记录 registry 地址因此publish-plan与publish两阶段依赖配置的一致性getPublishPlan.ts。第二步TTY 下先顺序发布直到一次非交互发布成功这一步的关键判断在 index.tslet otpCode publishTool.getOtpCode(options?.otp); // in TTY mode the first publish checks if the publish process requires interactive auth or not // on CI everything has to be configured in a way that allows automation so we can go straight to bulk publishing // similarly, when OTP is provided we can go straight to bulk publishing as well let sequential process.stdin.isTTY otpCode null;即只有同时满足标准输入是 TTY且没有预先提供 OTP两个条件时才会进入顺序发布模式。这是整个发布策略的出发点在 TTY 下第一个包会以**非交互--json**方式尝试发布以此探测当前环境是否需要交互式认证。若探测成功published说明当前会话具备自动化发布能力立即切换到批量模式见第三步。若探测返回failed:needs-2fa则进入交互式重试丢弃可能已失效的 OTP用stdio: inherit重新发布把终端交还给用户完成 2FA 认证。关于 OTP 的生命周期源码有两处关键规则对应 README 第 2 步的注释已提供的 OTP 在其有效期内会被复用otpCode会透传给后续每次发布调用--otp参数因此一次认证可覆盖多个包的发布。交互式重试前必须丢弃 OTP见 index.ts——进入while (result.result failed:needs-2fa)循环后首先执行otpCode null避免把已拒绝的 OTP 传给后续发布。源码中还贴心地做了交互提示当总发布数 ≥ 2 且触发 2FA 时会建议用户勾选 npm 的skip 2fa for 5 minutes选项避免每个包都重复认证index.ts。此外README 第 2 步还规定了两种继续顺序发布的情形交互式发布成功说明当前会话只能通过交互方式认证继续逐包顺序发布每包都会占用终端。failed:already-published该版本已存在既不算成功也不算硬失败跳过该包继续下一个。第三步批量发布当前 chunk 的其余包当顺序探测成功或提供了 OTP、或非 TTY后sequential置为false流程进入批量发布分支bulkPublishPackagesindex.tsconst publishPromises publishQueue.map(async (item) { const pkg packagesByName.get(item.release.name)!; const result await npmPublishQueue.add(() publishTool.publish({ pkg, release: item.release, tarballPath: artifactDir ? resolve(artifactDir, item.release.tarball!.path) : null, interactive: false, // 批量发布一律非交互 otpCode, // 复用探测阶段证明有效的 OTP }), ); onResult?.(result); return { release: item.release, result }; }); return Promise.all(publishPromises);批量发布的要点同一 chunk 内的包并发发布但受npmPublishQueue限制。该队列的并发上限定义在 lib/common.tsNPM_PUBLISH_CONCURRENCY_LIMIT 10即发布操作最多同时进行 10 个而npm info之类的 registry 查询走另一个队列NPM_REQUEST_CONCURRENCY_LIMIT 40。这样既保证吞吐又避免瞬间打爆 registry。成功与已发布的包直接收尾published计入successfulNpmPublishesfailed:already-published不再处理。通用失败会阻断后续 chunk批量结果中若出现failed硬失败会把失败包计入unsuccessfulNpmPublishes并break publishChunks整个发布流程立即终止。测试 stops publishing after a failed chunk 验证了这一点——一个 chunk 失败后后续 chunk 不再执行。2FA 失败的恢复条件只有当同一批中没有硬失败时failed:needs-2fa的包才会被重新放回队列、切回顺序模式逐个交互恢复index.ts。混合出现通用失败与 2FA 失败时全部按失败上报并停止发布——源码注释解释了这个设计与硬失败混合时再做恢复会使流程复杂化因此只允许全部失败都可恢复时恢复。对应测试见 does not recover 2FA failures when the same bulk publish has a hard failure。恢复队列还有一个细节重新进入顺序模式后otpCode会被置为nullindex.ts因为批量发布已证明当前 OTP 缺失或失效。测试 returns to sequential publishing when an OTP becomes invalid during bulk publishing 完整覆盖了这条链路前 5 次调用携带--otp expired第 6 次交互恢复不带 OTP 且stdio: inherit之后的批量调用不再带 OTP。第四步非 TTYCI下直接进入批量模式README 第 4 步说明在非 TTY 环境中直接从批量模式开始并将 2FA 失败当作普通失败上报。这对应sequential初始值计算中的process.stdin.isTTY判断——CI 中 stdin 不是 TTY也没有人工交互的可能因此所有包一次性进入bulkPublishPackages并发发布。failed:needs-2fa无法触发交互恢复按不可恢复失败处理与硬失败一样终止发布。即便 chunk 中已有失败CI 模式下仍会尝试该 chunk 内的所有包而不是像 TTY 顺序模式那样遇硬失败立即中断测试 attempts every package in a failing non-TTY chunk 验证了该行为。这解释了为什么 CI 上的变化集发布通常要求配置好 registry token 或 OTP 环境变量——一切认证都必须预先配置为可自动化。第五步汇总结果并创建 git tag发布循环结束后进入收尾阶段index.ts输出成功列表若successfulNpmPublishes非空按包名排序展示nameversion进度条停止。输出失败列表若unsuccessfulNpmPublishes非空用红色展示每个失败包的code与message如E403: failed。创建 git tag为两类条目打 tag——发布成功的包pkg-a1.0.0形式计划中的tag-only条目私有包。关键点在于即使是发生失败、中断发布的 chunk其中已成功的发布和 tag-only 条目也会被打 tagREADME 第 5 步最后一句。测试 tags tag-only releases from a failing chunk 验证了这一点——pkg-a发布失败后私有包pkg-b1.0.0的 tag 依然被创建。但失败 chunk 之后依赖它的tag-only 条目不会被标记测试 does not tag tag-only releases after a failing chunk。失败即退出非零存在任何失败时throw new ExitError(1)供 CI 捕获。git tag 的创建由 git-tag/utils.ts 的createGitTags完成已存在的 tag 会被跳过测试 renders existing tags for successful publishes。tag 事件也可以通过--output以 NDJSON 流式写出每行形如{type:git-tag,tag:pkg-a1.0.0,packageName:pkg-a}。PublishOptions 完整参数表publish函数接受的选项定义在 index.ts参数类型默认值作用cwdstringprocess.cwd()仓库根目录所有包发现与配置读取的基准otpstring无npm 一次性密码优先级低于环境变量读取逻辑getOtpCode会依次检查参数、NPM_CONFIG_OTP、npm_config_otptagstringlatest发布使用的 npm dist-tagfromPackDirstring无从changeset pack生成的产物目录发布artifact 模式outputstring无将 git-tag 等事件以 NDJSON 写入指定文件gitTagbooleantrue是否创建 git tag可关闭参数间的相互约束源码中有两处显式校验artifact 模式与自定义 tag 互斥index.tsfromPackDir与tag同时提供时报错Releasing under custom tag is not allowed in artifact mode.。pre 模式与自定义 tag 互斥index.ts处于 pre-release 模式时指定tag会报错提示先执行changeset pre exit。对应测试见 in pre state should report error if the tag option is used in pre release。另外在 pre 模式或自定义 tag 场景下publish 会打印非 latest tag 警告showNonLatestTagWarningpre 模式中从未正式发布过的包会发布到latest其余发布到preState.tagindex.ts。这一only-pre判定逻辑位于 getPublishPlan.ts当已发布版本全部是 pre tag 且dist-tags.latest存在时判定为only-pre首次正式发布会落到latest而非 pre tag。发布计划生成谁进入计划谁被跳过为了让第五步的分块与tag-only概念落地有必要看一下发布计划的来源。getPublishPlan的完整流程getPublishPlan.ts是发现未发布包getUnpublishedPackages遍历所有非私有包通过npm info name --json无 latest dist-tag 时回退查询精确版本判断本地版本是否已在 registry。本地版本不在已发布版本列表中的包进入发布列表同时打印These packages will be published预览与已发布计数。收集 tag-only 私有包getUntaggedPrivatePackages当配置privatePackages.tag: true时将未被 git tag 覆盖的私有包作为tag-only条目加入计划。过滤规则config.ignore中列出的包shouldSkipPackage不进入计划私有包private: true默认不发布。测试 does not tag ignored private packages 验证了 ignore 生效时连 tag 查询都不会执行。拓扑排序分块合并两类条目后交给sortReleases生成最终计划。如果计划为空没有任何未发布包也没有待打 tag 的私有包publish 会打印No unpublished projects to publish.并直接返回index.ts。关键实现细节与底层原理为什么 cwd 如此重要npm 适配层在发布前解析.npmrc时对工作目录有明确讲究npm.tsnpm 9 之前调用 npm 必须从仓库根目录并以嵌套包为目标才能正确解析根.npmrcnpm 9 之后.npmrc查找已具备 workspace 感知能力因此现在直接从包目录本身调用 npm publish。同时 npm 会从环境变量中剥离NPM_CONFIG_OTP/npm_config_otpsanitizeEnv防止遗留 OTP 污染发布npm.tspnpm 适配层还会额外剥离PNPM_CONFIG_OTPpnpm.ts。publishConfig 支持publishConfig.registrynpm 侧会为 scoped 包生成--scope:registry与--registry覆盖参数保证npm info查询走与发布一致的 registrynpm.ts。publishConfig.directorynpm 原生不支持该字段changesets 继承自 Lerna 的做法是手动解析——将目标目录作为位置参数传给npm publish但使用它时必须预先构建好该目录因为 changesets 只是委托包管理器发布不会重新实现 packpublishnpm.ts。pnpm 原生支持该字段pnpm.tsYarn 则明确不支持遇到时直接返回失败yarn.ts。2FA 的进程内处理预留源码中有一段被注释掉的逻辑index.ts当 npm v11 返回authUrl/doneUrl时理论上可以在进程内完成 2FA 引导TODO 标注为后续 PR 实现目前实际走的是以stdio: inherit重新发布、把终端交给用户的交互式回退路径。进度条与输出发布过程使用clack/prompts的progress组件显示Publishing packages (N/M)进度2FA 交互前会暂停进度条恢复后重新启动index.ts。最终结果通过formatPackageList按包名排序展示失败条目附上code与message。测试验证矩阵publish 命令测试共 742 行覆盖了本文涉及的核心行为可作为理解发布状态机的对照测试用例验证点publishes release chunks sequentially依赖顺序pkg-b先于pkg-a发布并打 tagstops publishing after a failed chunk通用失败立即终止后续 chunk 不再发布attempts every package in a failing non-TTY chunk非 TTY 下 chunk 内全部尝试失败照常上报does not recover 2FA failures when the same bulk publish has a hard failure混合失败不恢复直接停止returns to sequential publishing when an OTP becomes invalid during bulk publishingOTP 失效 → 顺序交互恢复 → 恢复批量且不再复用旧 OTPtags tag-only releases from a failing chunk失败 chunk 中已成功/私有包仍打 tagdoes not tag tag-only releases after a failing chunk失败 chunk 之后的 tag-only 条目不打 tagdoes not tag ignored private packagesignore 配置生效时连 tag 都不打rejects custom tags when publishing from a pack directoryartifact 模式与自定义 tag 互斥总结与实战要点把 README 的发布流程与源码对照后可以提炼出以下可直接用于实践的要点TTY 本地发布未提供 OTP 时第一个包先非交互探测成功后自动切批量并发 ≤ 10遇到 2FA 则交互认证认证通过后继续批量。CI 发布务必预先配置 registry token / OTP环境变量2FA 失败会被当作普通失败上报并终止包管理器可被自动检测npm/pnpm/yarnYarn Classic 不支持。失败语义failed:already-published不是错误是幂等已达成只有failed才是硬失败failed:needs-2fa在 TTY 下可恢复。tag 语义git tag 为每个成功发布包与 tag-only 私有包创建失败 chunk 内的已完成条目仍会打 tag发布后记得git push --follow-tags。约束记住三条version与publish之间不要提交pre 模式与 artifact 模式下禁止自定义 tagpublishConfig.directory需要预先构建产物。如需进一步了解发布在多包仓库中的注意事项、pre-release 与 snapshot 模式可继续阅读仓库中的 problems-publishing-in-monorepos.md、prereleases.md 与 snapshot-releases.md。【免费下载链接】changesets A tool to manage versioning and changelogs with a focus on monorepos项目地址: https://gitcode.com/gh_mirrors/ch/changesets创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表