在 CI/CD 环境中用 release-it 自动化版本发布:GitHub Actions、GitLab CI、Travis 与 Circle CI 配置实战

原创2026-09-24 19:32:031,963 阅读
文章标签:开发工具DevOps

在 CI/CD 环境中用 release-it 自动化版本发布:GitHub Actions、GitLab CI、Travis 与 Circle CI 配置实战

导读

release-it 是一款自动化版本号递增与包发布的工具(仓库根目录 README.md 中描述为 "Automate versioning and package publishing")。当它被接入 Travis、Circle CI、GitHub Actions、GitLab CI 等持续集成/持续交付环境时,需要额外解决三类问题:Git 远端认证(推送 release commit 与 tag)、npm 注册表认证(发布包)、GitHub/GitLab Release 的 token 注入。本文以 docs/ci.md 为主体骨架,结合仓库源码(lib/config.js、lib/args.js、lib/plugin/npm/npm.js、lib/plugin/git/Git.js)与默认配置(config/release-it.json),给出可直接复制运行的 CI 配置片段,并深入解释 --ci 模式、npm 前置检查、tag already exists 等 CI 高频问题的底层原理。

CI 环境中 release-it 的行为基础:--ci 模式

在 CI 环境中运行 release-it 时,最核心的行为开关是 --ci:关闭所有交互式提示,全程无人工干预。lib/cli.js 的帮助文本对此的描述是:

--ci — No prompts, no user interaction; activated automatically in CI environments

也就是说,--ci 并不一定需要手动传入——lib/config.js 的 loadOptions 使用 ci-info 检测运行环境,并将 ci: isCI 合并进最终配置(lib/config.js):

const merged = defu(
  {},
  constructorConfig,
  {
    ci: isCI   // 由 ci-info 自动检测(Travis、Circle、GitHub Actions、GitLab CI 等均会被识别)
  },
  localConfig,
  defaultConfig
);

从配置对象的 isCI getter(lib/config.js)可以看到,isCI 为真还会被 --release-version、--changelog 这类非交互模式触发:

get isCI() {
  return Boolean(this.options.ci) || this.isReleaseVersion || this.isChangelog;
}

--ci 在 lib/args.js 中被声明为布尔选项,也支持 --no-ci 覆盖。CI 模式下,prompt 会被替换为 spinner/直接执行(见 lib/plugin/Plugin.js 的 isCI && !isException ? spinner : showPrompt 分支),npm 发布的 OTP 回调也会被禁用(见下文)。

Git:让 CI 环境具备推回远端的能力

release-it 默认执行"提交 release commit + 打 tag + 推送"三步(默认配置 config/release-it.json 中 git.commit、git.tag、git.push 均为 true)。要把这些变更推回远端仓库,CI 环境必须持有对远端主机(如 GitHub)的认证凭证。

SSH(推荐)

如果仓库使用 SSH 形式的 URL(如 git@github.com:user/repo.git),只需将部署公钥添加到 CI/CD 环境即可,无需处理 token 拼接问题。GitLab CI 场景同样推荐 SSH(详见下文 GitLab CI 小节)。

HTTPS:将 GITHUB_TOKEN 注入 origin URL

使用 HTTPS 形式的 URL(如 https://github.com/user/project.git)时略复杂:需要先在 CI 环境配置 GITHUB_TOKEN,然后在运行 release-it 之前,把该 token 作为密码写入 origin URL。文档给出的 .travis.yml 片段:

script:
  - git remote rm origin
  - git remote add origin https://[user]:${GITHUB_TOKEN}@github.com/[user]/[project].git
  - git symbolic-ref HEAD refs/heads/main

将 <a href="https://link.gitcode.com/i/4f9e7d7a1eda0f1307a499f7f123b7dd" target="_blank">user] 和 [project] 替换为实际值即可。关于 Git 插件在 CI 中如何校验仓库状态,可参考 [lib/plugin/git/Git.js:默认配置 git.requireCleanWorkingDir: true、git.requireUpstream: true 会分别在 CI 中强制要求工作区干净、当前分支已配置 upstream,否则直接抛出错误中止发布。

GitHub Actions:完整示例与 fetch-depth: 0

文档给出的 GitHub Actions job 片段,完成"配置 Git 用户 → 暴露 npm/GitHub token → 执行发布":

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: git config
        run: |
          git config user.name "${GITHUB_ACTOR}"
          git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
      - run: npm install
      - run: npm run release
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

要点说明:

  • git config user.name / user.email:release-it 提交 release commit 时使用该身份。使用 GITHUB_ACTOR 与 GitHub 的 noreply 邮箱可避免身份为空导致提交失败。
  • NPM_TOKEN:用于发布到 npm registry(见下文 npm 小节)。
  • GITHUB_TOKEN:用于创建 GitHub Release。config/release-it.json 中默认 github.tokenRef: "GITHUB_TOKEN"、gitlab.tokenRef: "GITLAB_TOKEN",即 release-it 默认从这两个环境变量名读取 token。
  • fetch-depth: 0:仅当需要完整 Git 历史时才必要,例如使用 @release-it/conventional-changelog 等需要依据历史提交生成 changelog 的插件。

如果发布流程只需要"打版本号"而不需要交互,建议在 npm script 中显式加 --ci(如 release-it --ci),确保任何环境下行为一致。

npm:在 CI 中发布包

要在 CI/CD 环境(Travis、Circle 等)中把包发布到 npm registry,核心是在 release-it 执行发布前,把 NPM_TOKEN 写入 .npmrc 文件:

//registry.npmjs.org/:_authToken=$NPM_TOKEN

两种常见写入方式:

npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN

或直接以 shell 重定向生成(见下文 Travis 示例)。注意:.npmrc 应加入 .gitignore,否则 release-it 的 git 插件会在提交 release commit 时把它一并提交进仓库,导致 token 泄露。

为什么 CI 中常需要 --npm.skipChecks

release-it 在正式发布前会执行 npm 前置检查(详见 docs/npm.md#prerequisite-checks)。从源码 lib/plugin/npm/npm.js 可以看到 init() 阶段的三个并发校验:

  • isRegistryUp():执行 npm ping(npm.js);
  • isAuthenticated():执行 npm whoami(npm.js);
  • getLatestRegistryVersion():执行 npm show <name>@<tag> version。

问题在于:npm whoami 并不一定尊重 .npmrc 中的 token(文档明确指出该检查"does not seem to respect the .npmrc file"),因此在 CI 中可能误报未认证。此时使用 --npm.skipChecks(即 npm.skipChecks: true)跳过这些检查即可。从源码看,skipChecks 置真后 init() 直接 return,跳过全部校验与超时等待(npm.js)。

补充说明(来自 docs/npm.md 与源码):

  • 部分私有注册表(如 Nexus)不支持 npm ping/npm whoami/npm access,若报错为 E400/E404,release-it 会给出 warning 并继续(isRegistryUp、isAuthenticated 中对 /code E40[04]/ 的兜底逻辑);
  • 每个校验默认超时 10 秒(DEFAULT_TIMEOUT = 10,npm.js),可通过 npm.timeout 调整;
  • 交互式环境(非 CI、非 dry-run、有 TTY)在认证失败时会自动运行 npm login 重试,但该行为在 CI 中被显式禁用——canLogin 条件包含 !this.config.isCI && !this.config.isDryRun && process.stdin.isTTY(npm.js);
  • 包为 "private": true 时,release-it 会自动跳过 npm 相关步骤(npm.js)。

Travis

文档给出的 .travis.yml 片段:

deploy:
  script:
    - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
    - npm run release

Circle CI

文档给出的 .circleci/config.yml 关键片段:

jobs:
  deploy:
    steps:
      - attach_workspace:
          at: ~/repo
      - run:
          name: Authenticate with registry
          command: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN

注意:发布过程中 release-it 会更新项目的 package.json 以递增版本号。若希望 CircleCI 能把该变更推回仓库,需要为其配置来自 GitHub 账号的非只读 SSH 密钥对。

依赖注入:npm 相关的 CI 组合配置

npm.skipChecks 仅是 npm 命名空间下的一个布尔选项(schema/npm.json 中默认 false),与 --ci 独立。CI 中常用的 npm 组合还包括:

  • --npm.skipChecks:跳过 registry 连通性与权限校验;
  • --npm.tag=beta:以指定 dist-tag 发布(预发布版本默认 tag 为 next,详见 docs/npm.md#tags);
  • --npm.otp=123456:提供一次性密码(CI 中不建议,2FA 意义会失效);
  • npm.publishPath:仅发布 dist 等指定目录;
  • npm.stage: true:使用 npm 的 staged publishing 提交到暂存队列,把 2FA 审批延后到 npm stage approve,从而支持 CI 非交互式发布(docs/npm.md#staged-publishing)。

GitHub & GitLab Releases:token 环境变量

要发布(或草稿)GitHub/GitLab Release,只需保证 CI/CD 环境设置了对应的 token 环境变量,这与本地机器上完全一致:

  • GitHub:GITHUB_TOKEN(对应默认 github.tokenRef);
  • GitLab:GITLAB_TOKEN(对应默认 gitlab.tokenRef)。

token 的注入与保护细节可参考 docs/environment-variables.md:不要将 token 提交进 Git 仓库,不要写入 release-it 配置,也不要提交 .env 文件;如需更换环境变量名,可使用 github.tokenRef="RELEASE_IT_GITHUB_TOKEN" 或 gitlab.tokenRef 覆盖。

GitLab CI

SSH(推荐)的前置条件

在 GitLab CI 中使用 release-it 时,文档列出的 SSH 要求:

  • job 中安装 git 与 ssh 包;
  • 预先执行 npm install;
  • 环境变量包含 GITLAB_TOKEN、SSH_PRIVATE_KEY、CI_EMAIL 和 CI_USER;
  • 仓库中添加拥有受保护分支写权限的用户或部署密钥(通过环境变量注入)。

Alpine 容器中的完整 pipeline

以下示例在 Alpine 中先安装 Git 与 OpenSSH,把 SSH 私钥加入 ssh-agent,配置 SSH,最后执行 release-it:

before_script:
  - apk add --no-cache git openssh
  - eval `ssh-agent -s`
  - echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add - > /dev/null # add ssh key
  - mkdir -p ~/.ssh
  - chmod 700 ~/.ssh
  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
  - git checkout $CI_COMMIT_REF_NAME
  - git remote set-url origin "git@gitlab.com:$CI_PROJECT_PATH.git"
  - git config --global user.name "${CI_USERNAME}"
  - git config --global user.email "${CI_EMAIL}"
  - npm install
script:
  - npx release-it --ci

细节说明:

  • echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add -:tr -d '\r' 用于剔除换行符的 CR 残留,这是 CI 环境变量常见陷阱;
  • git remote set-url 也可以不在 pipeline 里做,而是改用 release-it 配置中的 git.pushRepo 选项。该选项在 lib/plugin/git/Git.js 的 getUpstreamArgs 中被消费:当 pushRepo 配置且看起来像 URL 时,git push 直接以该 URL 为目标;否则视为远端名并配合 --set-upstream 建立跟踪。
  • npx release-it --ci:显式传入 --ci 确保全程无交互。

常见错误:fatal: tag vX.X.X already exists

GitLab CI 用户报告过该错误(社区 issue #573):CI 任务基于旧的 commit/tag 状态运行时,release-it 试图创建已存在的 Git tag。文档给出的缓解手段有两级:

  1. 先拉取远端分支再执行发布:
- git pull origin $CI_COMMIT_REF_NAME
- npm run release
  1. 在 release-it 的 before:init 钩子中,以 --prune-tags 方式 fetch,提前删除远端已消失的本地 tag:
{
  "hooks": {
    "before:init": "git fetch --prune --prune-tags origin"
  }
}

before:init 属于生命周期钩子,在插件 init 阶段之前执行(见 lib/index.js 的 runLifeCycleHook 实现中 runHook('before', name) 的调度)。若 tag 已存在且与最新 tag 相同,Git 插件会给出 warning 并继续(lib/plugin/git/Git.js 对 tag '.+' already exists 的容错),否则才抛错终止。

CI 模式下的源码级延伸:snapshot 与推送行为

从源码可以进一步推断 CI 场景下两个值得注意的联动行为:

  • snapshot 模式的 CI 预置:在 lib/config.js 的 expandPreReleaseShorthand 中,当使用 --snapshot 且为字符串形式时,release-it 会自动硬编码一组适合 CI 快照发布的选项:git.tagMatch = 0.0.0-<snapshot>.[0-9]*、git.getLatestTagFromAllRefs = true、git.requireBranch = '!main'(要求不在 main 分支)、git.requireUpstream = false、npm.ignoreVersion = true。这意味着 CI 中发布快照版本无需额外配置,release-it 会基于 Git tag 而非 package.json 计算版本。
  • 推送失败的回滚:Git 插件在 push 失败时,如果此前已打 tag 会执行 git push origin --delete <tag> 回滚远端 tag(lib/plugin/git/Git.js),并警告"package might have been released, but git push failed"——提醒 CI 用户在推送失败时注意 npm 侧可能已发布成功,需要人工核对。

常见问题与核查清单

场景 症状 解决方案
Git push 401/权限拒绝 HTTPS 方式未携带 token 按上文重新 git remote add origin https://[user]:${GITHUB_TOKEN}@...
npm whoami 失败但手动发布正常 registry 不支持该命令或 .npmrc 未生效 --npm.skipChecks;或确认 //registry.npmjs.org/:_authToken=$NPM_TOKEN 已写入 .npmrc
.npmrc 被提交进 release commit token 泄露风险 将 .npmrc 加入 .gitignore
tag already exists CI 基于过期状态运行 before:init 中执行 git fetch --prune --prune-tags origin
CI 中 npm 发布触发交互 2FA/登录提示卡住 使用 --ci + NPM_TOKEN;或启用 npm.stage(staged publishing)把审批延后
包为 private 却尝试发布 认证错误 在 package.json 中设 "private": true,release-it 会自动跳过 npm 步骤

总结

在 CI/CD 环境中运行 release-it,本质上是把三个认证链路打通:Git 远端(SSH 或注入 token 的 HTTPS URL)、npm 注册表(.npmrc + NPM_TOKEN)、GitHub/GitLab Release(GITHUB_TOKEN/GITLAB_TOKEN),并配合 --ci(或依赖 ci-info 自动检测)关闭交互。遇到 npm 认证误报时用 --npm.skipChecks,遇到 tag 冲突时用 git fetch --prune --prune-tags 前置清理,遇到 2FA 卡顿时用 npm.stage 延后审批。本文所有配置片段与默认值均可对照 config/release-it.json、schema/npm.json 及 docs/git.md、docs/npm.md 继续深入查阅。

登录后查看全文
release-it