Project Icon

multi-semantic-release

多包仓库自动化语义发布工具

multi-semantic-release 是专为 monorepo 项目设计的语义化发布工具。它基于 semantic-release,实现自动版本管理和跨包版本更新。支持 npm、yarn、pnpm 和 bolt 等包管理器,提供 alpha 和 beta 分支发布流程。通过 CLI 或 JS API 使用,配置灵活,简化了复杂项目的发布流程,提高版本管理效率。

multi-semantic-release

CI semantic-release Conventional Commits Prettier npm

用于单仓库的黑客式语义发布

概述

这是一个概念验证项目,它包装了semantic-release以适用于单仓库

这个包应该能够正常工作,但可能不够稳定,不适合用于重要的生产环境,因为它高度依赖于semantic-release的工作方式(所以在semantic-release的未来版本中可能会出现问题或过时)。

semantic-release最好的特点之一是可以忘记版本号。但在单仓库中,对于本地依赖(在同一个单仓库中被引用为dependenciesdevDependenciespeerDependencies的包)仍然需要大量的版本号管理。然而,在multi-semantic-release中,本地依赖的版本号会在发布时被写入package.json。这意味着不再需要硬编码版本号(我们建议在您的仓库代码中直接使用*星号)。

主要特性

  • 命令行界面和JavaScript API
  • 自动化且可配置的跨包版本升级
  • 提供alpha和beta分支发布流程
  • 支持npm(v7+)、yarn、pnpm(有限制)、基于bolt的单仓库
  • 可选择忽略某些包
  • 支持Linux/MacOS/Windows

目录

安装

yarn add multi-semantic-release --dev
npm i multi-semantic-release -D

要求

使用方法

multi-semantic-release [选项]
npx multi-semantic-release [选项]

配置

发布的配置与semantic-release配置相同,即在package.jsonrelease键下或任何类型的.releaserc文件中设置,如.yaml.json

但在multi-semantic-release中,这种配置可以在全局(在您的顶级目录中)或每个包(在该单独包的目录中)中完成。如果您同时设置了两者,则每个包的设置将覆盖全局设置。

multi-semantic-release不支持任何命令行参数(这是不可能的,除非复制semantic-release的文件,而我一直在尽量避免这样做)。

multi-semantic-release自动检测以下包管理器的工作空间中的包:

yarn / npm (v7+)

确保在您的package.json项目文件中有一个workspaces属性。在那里,您可以设置一个包列表,这些包可能会在msr过程中被处理,也可以忽略其他包。例如,假设您的项目有4个包(即a、b、c和d),您只想处理a和d(忽略b和c)。您可以在package.json文件中设置以下结构:

{
  "name": "msr-test-yarn",
  "author": "Dave Houlbrooke <dave@shax.com",
  "version": "0.0.0-semantically-released",
  "private": true,
  "license": "0BSD",
  "engines": {
    "node": ">=8.3"
  },
  "workspaces": [
    "packages/*",
    "!packages/b/**",
    "!packages/c/**"
  ],
  "release": {
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator"
    ],
    "noCi": true
  }
}

pnpm

确保在项目根目录的pnpm-workspace.yaml中有一个packages属性。在那里,您可以设置一个包列表,这些包可能会在msr过程中被处理,也可以忽略其他包。例如,假设您的项目有4个包(即a、b、c和d),您只想处理a和d(忽略b和c)。您可以在pnpm-workspace.yaml文件中设置以下结构:

packages:
  - 'packages/**'
  - '!packages/b/**'
  - '!packages/c/**'

注意,包版本中的workspace:前缀目前还不支持。issues/85

bolt

确保在您的package.json项目文件中有一个bolt.workspaces属性。在那里,您可以设置一个包列表,这些包可能会在msr过程中被处理,也可以忽略其他包。例如,假设您的项目有4个包(即a、b、c和d),您只想处理a和d(忽略b和c)。您可以在package.json文件中设置以下结构:

{
  "name": "msr-test-bolt",
  "author": "Dave Houlbrooke <dave@shax.com",
  "version": "0.0.0-semantically-released",
  "private": true,
  "license": "0BSD",
  "engines": {
    "node": ">=8.3"
  },
  "bolt": {
    "workspaces": [
      "packages/*",
      "!packages/b/**",
      "!packages/c/**"
    ]
  },
  "release": {
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator"
    ],
    "noCi": true
  }
}

命令行界面

有几个调整可以使msr适应一些特殊情况:

标志类型描述默认值
--sequential-init布尔值避免假设的并发初始化冲突false
--debug布尔值输出调试信息false
--first-parent布尔值仅对当前分支应用提交过滤false
--deps.bump字符串定义依赖版本更新规则。
  • override — 用下一个版本替换任何先前版本
  • satisfy — 检查下一个包版本是否符合其当前引用。如果匹配(*匹配任何版本,1.1.0匹配1.1.x1.5.0匹配^1.0.0等),则不会触发发布;如果不匹配,则应用override策略;inherit将尝试遵循当前声明的版本/范围。~1.0.0 + minor变为~1.1.01.x + major变为2.x,但1.x + minor仍为1.x,因此不会发布,等等。
  • ignore 阻止MSR更新依赖
实验性功能
override
--deps.release字符串如果任何依赖项发生更改,定义依赖包的发布类型。
  • patchminormajor — 严格声明更新任何依赖时发生的发布类型;
  • inherit — 将更新的依赖中"最高"的发布类型应用于包。
    例如,如果任何依赖有破坏性变更,major发布将应用于链上的所有依赖项。
实验性功能
patch
--deps.prefix字符串如果--deps.bump设置为override,可选择附加到下一个版本的前缀。支持的值:^ | ~ | ''(空字符串)''(空字符串)
--dry-run布尔值演练模式false
--ignore-packages字符串在升级过程中要忽略的包列表(附加到package.json工作区中已存在的包)null
--ignore-private-packages布尔值忽略私有包false

示例:

$ multi-semantic-release --debug
$ multi-semantic-release --deps.bump=satisfy --deps.release=patch
$ multi-semantic-release --ignore-packages=packages/a/**,packages/b/**

你还可以将CLI的--ignore-packages选项与package.jsonworkspaces属性中每个包内的!操作符结合使用。尽管你可以使用CLI忽略选项,但不能使用它来设置要发布的包 - 也就是说,你仍需要在package.json中设置workspaces属性。

⚠️ 请注意,allowUnknownFlags已启用,因此其余标志将作为options参数传递给所有包的内部semrel调用。

API

multi-semantic-release默认导出一个multirelease()方法,该方法接受以下参数:

  • packages 包含package.json文件字符串路径的数组
  • options 包含默认semantic-release配置选项的对象

multirelease()返回一个描述多重发布结果的对象数组(对应传入的packages数组)。

const multirelease = require("multi-semantic-release");

multirelease([
  `${__dirname}/packages/my-pkg-1/package.json`,
  `${__dirname}/packages/my-pkg-2/package.json`,
]);

CI/CD

Multi-semantic release似乎与许多CI/CD系统兼容。至少我们确定了三个,以下是配置示例:

故障排除

npm v8.5+: npm ERR! notarget 未找到匹配版本...

发布monorepo时,你可能会遇到npm ERR! code ETARGET错误。这是因为npm version在MSR尚未更新的未来依赖版本上创建了重新验证更新。

最简单的解决方法是在.npmrc中设置workspaces-update为false,或手动运行npm config set workspaces-update false

npm: 无效的npm令牌

发布monorepo时,你可能会遇到EINVALIDNPMTOKEN错误。包越多,出错的机会就越大,很遗憾。

INVALIDNPMTOKEN 无效的npm令牌。
在NPM_TOKEN环境变量中配置的npm令牌(https://github.com/semantic-release/npm/blob/master/README.md#npm-registry-authentication)必须是有效的令牌(https://docs.npmjs.com/getting-started/working_with_tokens),允许发布到注册表https://registry.npmjs.org/。

不要急于更改你的令牌。_也许_这与你的注册表上的npm whoami请求限流有关(仅是假设:https://github.com/semantic-release/npm/pull/416)。此时你可以:

  • 根据需要多次重新运行你的构建。你可能会在新的尝试中成功。
  • 使用semrel-extra/npm插件进行npm发布(推荐)。

git: 连接被对等方重置

这个错误似乎与并发的git调用有关(issues/24)。或者可能不是。 无论如何,我们添加了一个特殊的--sequental-init标志来对这些调用进行排队。

实现说明(和其他想法)

对monorepo的支持

只要按照支持的包管理器之一的工作区功能配置工作区,就会自动查找包。

我知道Lerna现在是最知名的工具,但未来似乎很明显它将被Yarn和NPM的功能直接替代。如果你现在(2019年1月)使用Yarn工作区,那么发布是Lerna_真正_需要的唯一剩余功能(尽管如果Yarn添加并行脚本执行会很好)。因此,使用multi-semantic-release意味着你可能可以完全从项目中移除Lerna。

迭代vs协调

其他支持monorepo的semantic-release包通过迭代进入每个包并运行semantic-release命令来工作。这在概念上很简单,但不幸的是不可行,因为:

  • 如果发布的包依赖于兄弟包中的次要更改,可能会导致非常微妙的错误(最糟糕的那种!)- 如果项目严格遵循semver,这应该永远不会发生,但最好消除错误的_可能性_
  • 依赖版本号需要反映发布时的_下一个_版本,因此包需要在正确发布之前知道_所有其他包_的状态 - 这种中央状态需要由某些东西来协调

本地依赖和版本号

一个关键要求是优雅地处理本地依赖版本号。multi-semantic-release执行以下操作:

  • 首先确定所有包的下一个版本号
  • 如果一个版本没有更改但有本地依赖已更改...对该包也进行patch升级
  • 在发布包之前(在semantic-release的准备步骤中),将_所有_本地依赖的正确当前/下一个版本号写入package.json文件(覆盖任何现有值)
  • 这确保了在发布时,包与monorepo中的所有其他包保持原子正确性。

上述意味着,可能如果有人在多重发布_期间_(在所有依赖都以其下一个版本发布之前)升级依赖并从NPM拉取包,那么他们的npm install将失败(如果他们几分钟后再次尝试,就会成功)。权衡之下,我认为保持原子正确性更重要(假设项目提交了它们的锁文件,这种情况应该相当罕见)。

与semantic-release的集成

这是multi-semantic-release最棘手的部分,也是最可能破坏依赖的部分。我预计这将在未来引起维护问题。在理想情况下,semantic-release将内置对monorepo的支持(使得这个包变得不必要)。

我最终集成的方式是为semantic-release创建一个自定义的"内联插件",并将其作为唯一的插件传递给semanticRelease()。然后,这个插件调用任何其他配置的插件来检索并可能修改响应。 该插件同时启动所有发布,然后在不同点暂停它们(使用 Promises),以允许多发布中的其他包赶上进度。这主要是为了在发布任何包之前确定所有包的版本号。这使我们能够对本地依赖项已升级的发布进行"补丁"升级,并在每个 package.json 中准确写入本地依赖项的版本。

内联插件执行以下操作:

  • verifyConditions: 未使用
  • analyzeCommits:
    • context.commits 替换为仅限于该文件夹的提交列表
    • 调用 plugins.analyzeCommits() 获取下一个发布类型(例如来自 @semantic-release/commit-analyzer)
    • 等待所有包赶上这一进度
    • 对于未升级的包,检查它是否有已升级的本地依赖项(或依赖项的依赖项),如果是则返回 patch
  • verifyRelease: 未使用
  • generateNotes:
    • 调用 plugins.generateNotes() 获取发布说明(例如来自 @semantic-release/release-notes-generator)
    • 附加一个列出任何本地依赖项升级的部分(例如 "my-pkg-2: 升级到 1.2.1")
  • prepare:
    • package.jsondependenciesdevDependenciespeerDependencies 中为本地依赖项写入正确的版本
    • 将发布序列化,使它们一次只发生一个(因为 semantic-release 异步调用 git push,多个同时发布会失败,因为 Git 引用未锁定 — semantic-release 应该使用 execa.sync() 使 Git 操作具有原子性)
  • publish: 未使用
  • success: 未使用
  • fail: 未使用

不完善之处

与 semantic release 的集成相当不完善 — 以下是这个包难以维护的原因简要总结:

  1. 必须在 @semantic-release/commit-analyzer 使用前过滤 context.commits 对象(使其只列出相应目录的提交)。
  • 实际的 Git 过滤非常简单:参见 getCommitsFiltered.js
  • 但覆盖 context.commits 非常困难!我最终通过创建一个内联插件并通过 options.plugins 将其传递给 semanticRelease() 来实现
  • 内联插件在 semantic release 和其他配置的插件之间进行代理。它执行所需操作,然后调用例如 plugins.analyzeCommits() 并覆盖 context.commits — 参见 createInlinePluginCreator.js
  • 我认为这很混乱 — 内联插件甚至没有文档 :(
  1. 需要在所有插件进入发布步骤之前运行提交分析步骤
  • 内联插件为每个包返回一个 Promise,然后等待所有包分析它们的提交后再逐一解析它们
  • 如果包有本地依赖项(例如 package.json 中的 dependencies 指向内部包),这一步还会在它们中任何一个升级时进行 patch 升级。
  • 这必须递归工作!参见 hasChangedDeep.js
  1. 配置可以分层(即全局 .releaserc 和每个目录的单个包覆盖)。
  • 不得不复制 semantic release 的内部 cosmiconfig 设置才能使其工作 :(
  1. 我发现 Git 会因为例如 git tag 异步执行而陷入奇怪的状态
  • 为了解决这个问题,我不得不错开包的发布,使它们一次只发布一个(这会降低速度)
  • 我认为 semantic release 中对 execa() 的调用应该替换为 execa.sync() 以确保 Git 的内部状态是原子的。
  • 幸运的是,已经实现了另一种解决方法。Synchronizer 是其中的精髓。它对于使标签和提交发布阶段严格按顺序进行至关重要。事件发射器允许
    • 同步所有包的发布阶段。
    • 确保检查的完整性和无冲突过程的条件充分性。

Git 标签

发布始终使用 my-pkg-1@1.0.1 格式的 tagFormat 作为 Git 标签,并始终覆盖 semantic-release 配置中设置的任何 gitTag

我个人可以看到这个选项在协调 semantic-release 方面的潜力(例如,使两个具有相同标签的包始终同时升级和发布)。不幸的是,由于 semantic-release 中可用的集成点,在发布时阻止第二个包创建重复标签(导致错误)实际上是不可能的。

要使 tagFormat 选项按预期工作,需要进行以下操作:

  • semantic-release 需要检查给定的标签是否已存在于给定的提交中,如果是则不创建/推送它
  • 多个包发布的发布说明需要合并,但 Github 发布只执行一次(通过在 semantic-release 级别合并说明但只发布一次,或让 Github 插件合并它们)
  • 在文档中明确说明默认标签 v1.0.0 将具有与 Lerna 的固定模式相同的效果(所有更改的 monorepo 包同时发布)

贡献

欢迎提出任何类型的问题:错误、功能请求或问题。 你随时可以提出 PR。只需 fork 这个仓库,编写一些代码,添加一些测试,然后推送你的更改。 欢迎任何反馈。

替代方案

许可证

0BSD

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

稿定AI

稿定设计 是一个多功能的在线设计和创意平台,提供广泛的设计工具和资源,以满足不同用户的需求。从专业的图形设计师到普通用户,无论是进行图片处理、智能抠图、H5页面制作还是视频剪辑,稿定设计都能提供简单、高效的解决方案。该平台以其用户友好的界面和强大的功能集合,帮助用户轻松实现创意设计。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号