GitHub Actions 自动发布 npm 包:从手动发版到一条命令

 21小时前     13  
GitHub Actions 自动发布 npm 包:从手动发版到一条命令

文章目录

手动发 npm 包,流程通常是:改版本号、跑测试、登录 npm、执行 npm publish、再创建 Git tag 和 GitHub Release。

流程不算复杂,但每次都要重复,而且很容易出现几个问题:

  • 忘记跑测试,或者测试没通过就发布了;
  • tag 和 package.json 版本不一致;
  • 本地 npm 登录过期;
  • 默认 registry 指向了镜像,发布到了错误的位置;
  • 发布完成后才想起忘记打 tag。

更稳妥的方式,是把“测试、校验版本、发布 npm、创建 GitHub Release”交给 GitHub Actions。配置好之后,每次发版只需要提交代码、打一个 tag,剩下的自动完成。

本文整理一套可复用的做法,使用 npm 的 Trusted Publishing(OIDC),不需要在 GitHub 里保存长期有效的 npm token。

最终效果

发版流程变成:

更新 CHANGELOG
    ↓
npm version patch
    ↓
git push --follow-tags
    ↓
GitHub Actions 自动测试
    ↓
校验 tag 与 package.json 版本
    ↓
发布到 npm
    ↓
创建 GitHub Release

整个过程不需要再手动执行 npm login 和 npm publish。

一、先确定认证方式

GitHub Actions 发布 npm 包,常见有两种方式。

方式一:NPM_TOKEN

在 npm 创建一个 granular access token,保存到 GitHub 仓库的 Secrets,然后在 workflow 中通过 NODE_AUTH_TOKEN 使用。

优点是配置简单,几乎适用于所有 npm 账号和仓库。

缺点是 token 是长期凭证,需要定期轮换;一旦泄露,攻击者可以发布包。

方式二:Trusted Publishing(推荐)

npm 通过 GitHub Actions 的 OIDC 身份验证发布者,不需要保存 npm token。

workflow 运行时,GitHub 生成一个短期身份令牌,npm 验证这个令牌来自指定的仓库和 workflow,然后允许发布。Trusted Publishing 还会自动生成发布证明(provenance),让用户能够验证包的来源。

这种方式更适合公开仓库和公开 npm 包,也是本文采用的方式。

二、准备工作

需要满足以下条件:

  • GitHub 仓库公开;
  • npm 包已经存在(第一次发布可以先手动完成,或者用 token 发布首个版本);
  • workflow 使用 GitHub 托管的 runner;
  • npm CLI 版本不低于 11.5.1;
  • 本地 Node.js 版本与 CI 保持一致,例如 Node 22。

如果包是第一次发布,建议先手动发布一个初始版本。Trusted Publishing 需要先在 npm 包设置中建立信任关系,而包不存在时无法配置。

三、一次性配置 Trusted Publisher

打开 npm 包页面,进入包的 Settings → Publishing access → Trusted publishing,添加一个 GitHub Actions 发布者。

需要填写:

  • Owner:GitHub 用户名或组织名;
  • Repository:仓库名;
  • Workflow filename:workflow 文件名,例如 publish-npm.yml;
  • Environment:如果 workflow 使用了 environment 就填写对应名称,否则留空。

在 Allowed actions 中:

  • 勾选 Allow npm publish,允许 workflow 直接执行 npm publish;
  • Allow npm dist-tag 按需勾选。普通发布不需要它,除非 workflow 会主动执行 npm dist-tag;
  • Stage publish 通常始终允许,不影响直接发布。

这里最容易踩的坑是:只配置了 stage publish,没有允许直接 publish。这样 CI 只会把版本提交到 staging,需要人工去 npm 页面批准,无法做到全自动。

如果 npm 包设置里启用了 staged publishing,也需要确认发布策略:要么在 npm 页面批准 stage,要么关闭 staged publishing,改成直接发布。

四、准备 GitHub Actions workflow

在仓库中创建 .github/workflows/publish-npm.yml。下面是一个通用模板,把包名替换成实际名称即可。

name: Publish to npm

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:

permissions:
  contents: read
  id-token: write

concurrency:
  group: npm-publish-${{ github.ref }}
  cancel-in-progress: false

jobs:
  publish:
    name: Publish package
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org/

      # Trusted Publishing 要求 npm >= 11.5.1
      - name: Upgrade npm
        run: npm install -g npm@11.5.1

      - name: Install dependencies
        run: npm ci --ignore-scripts

      - name: Verify tag matches package version
        if: github.ref_type == 'tag'
        run: |
          VERSION=$(node -p "require('./package.json').version")
          TAG="${GITHUB_REF_NAME#v}"
          if [ "$VERSION" != "$TAG" ]; then
            echo "Tag $GITHUB_REF_NAME does not match package.json version $VERSION."
            exit 1
          fi

      - name: Check whether version already exists
        id: registry
        run: |
          VERSION=$(node -p "require('./package.json').version")
          if npm view "your-package@$VERSION" version \
            --registry=https://registry.npmjs.org/ \
            --prefer-online >/dev/null 2>&1; then
            echo "published=true" >> "$GITHUB_OUTPUT"
            echo "your-package@$VERSION already exists; skipping publish."
          else
            echo "published=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Run checks
        run: npm run check

      - name: Publish
        if: steps.registry.outputs.published != 'true'
        run: npm publish --access public

模板中有几个关键点。

1. id-token: write 不能少

Trusted Publishing 依赖 GitHub OIDC token,因此 workflow 必须声明:

permissions:
  id-token: write

不需要配置 NODE_AUTH_TOKEN,也不需要把 npm token 放进 Secrets。

2. 显式锁定 npm 版本

GitHub runner 自带的 npm 版本不一定支持 Trusted Publishing。把版本固定下来,可以避免 runner 镜像更新导致发布突然失败。

npm install -g npm@11.5.1

3. 校验 tag 与版本

workflow 在 tag 上触发时,使用:

TAG="${GITHUB_REF_NAME#v}"

去掉可能存在的 v 前缀,再与 package.json 的 version 比较。

版本不一致时直接失败,避免出现“代码是 1.2.0,npm 发布成了 1.1.0”这种问题。

4. 已发布版本自动跳过

发布前先查询 registry:

npm view "your-package@$VERSION" version

如果该版本已经存在,就跳过 npm publish。这样 workflow 可以安全重跑,不会因为重复发布而报错。

5. 测试放在发布之前

模板中的 npm run check 可以替换成项目实际使用的命令,例如:

npm run lint
npm run test
npm run build

如果项目有 prepublishOnly,npm publish 还会自动执行一次发布前检查。测试失败时,npm 不会收到新版本。

6. scoped 包要调整包名

如果包名带 scope,例如 @your-scope/your-package,需要把模板中的包名替换成完整名称:

npm view "@your-scope/your-package@$VERSION" version
npm publish --access public

公开的 scoped 包需要 --access public;普通的 unscoped 包通常不需要,但显式写明也不会出错。

五、发版流程

配置完成后,从干净的 main 分支开始:

# 1. 更新 CHANGELOG 和必要的文档
# 2. 升级版本,并自动创建 commit 和 tag
npm version patch

# 3. 推送 commit 和 tag
git push --follow-tags

npm version patch 会:

  • 修改 package.json 和 lockfile 中的版本;
  • 创建一个版本提交;
  • 创建一个形如 v1.2.3 的 Git tag。

推送 tag 后,GitHub Actions 会自动运行。

如果项目使用 minor 或 major,把 patch 换成对应命令即可。也可以手动修改版本、提交并打 tag,只要 tag 和 package.json 一致。

六、发布后怎么确认

先看 npm registry,不要只看网页:

npm view your-package version \
  --registry=https://registry.npmjs.org/ \
  --prefer-online

查看 dist-tags:

npm view your-package dist-tags --json \
  --registry=https://registry.npmjs.org/ \
  --prefer-online

正常情况下会看到:

{
  "latest": "1.2.3"
}

再打开 npm 页面:

https://www.npmjs.com/package/your-package

npm 页面有 CDN 和浏览器缓存,偶尔会晚几分钟显示新版本。只要 registry 中的 latest 已经更新,用户执行 npm install your-package 就会安装新版本。

如果页面一直显示旧版本,可以:

npm cache clean --force

然后强制刷新浏览器,或者用无痕窗口打开版本页:

https://www.npmjs.com/package/your-package/v/1.2.3

七、几个常见问题

1. 为什么会出现 0.0.0-stage

这是 npm staged publishing 生成的占位版本,不是 package.json 中的版本。

它可能短暂成为 latest,导致 npm view your-package version 返回 0.0.0-stage。真实版本发布后,latest 会指向真实版本。

如果只看到占位版本,检查 npm 包的 staged publishing 设置,确认是否需要在 npm 页面批准发布。

2. tag 是 v1.2.3,但 workflow 没触发

检查 workflow 的触发规则:

on:
  push:
    tags:
      - "v*"

如果 tag 不带 v,上面的规则不会匹配。要么调整 workflow,要么统一 tag 格式。

3. 报 E403 或 E409

  • E403:通常是没有发布权限、Trusted Publisher 配置不匹配,或者 npm 登录状态有问题;
  • E409:这个版本号已经存在,不能重复发布,需要升级版本;
  • 404:检查包名、scope 和 registry 是否正确。

使用 Trusted Publishing 时,重点检查:

  • workflow 文件名是否和 npm 页面配置一致;
  • owner 和 repository 是否完全一致;
  • environment 是否匹配;
  • 是否允许了 direct publish;
  • workflow 是否有 id-token: write。

4. 默认 registry 是镜像

很多机器会配置国内 npm 镜像。发布时必须确认目标 registry:

npm config get registry

发布步骤最好显式写出:

npm publish --registry=https://registry.npmjs.org/

或者在 package.json 中设置:

{
  "publishConfig": {
    "registry": "https://registry.npmjs.org/",
    "access": "public"
  }
}

注意:镜像通常只适合安装,不适合发布。

5. 不要直接发布本地 tgz

npm publish ./package.tgz 可以发布已有压缩包,但不会重新执行项目的发布前检查。更推荐从项目根目录执行 npm publish,让 npm 重新打包并运行 prepublishOnly。

八、安全配置建议

  • 优先使用 Trusted Publishing,避免保存长期 npm token;
  • 只授权指定仓库和指定 workflow;
  • 不需要 dist-tag 管理权限时,不要勾选 Allow npm dist-tag;
  • 不需要 staged publishing 时,改成直接发布,减少一道人工审批;
  • 保护版本 tag,避免未审核代码触发发布;
  • 定期检查 npm 包的发布历史和 provenance;
  • 不要把 npm token、GitHub token 或 OTP 写进仓库、日志或截图。

总结

自动发布 npm 包并不复杂,核心只有三件事:

  1. 用 Trusted Publishing 建立 npm 和 GitHub Actions 的信任关系;
  2. 在 tag workflow 中校验版本、跑测试、发布;
  3. 用 npm version patch 和 git push --follow-tags 完成一次发版。

配置一次之后,发版就从“登录、测试、发布、打 tag”变成一条命令。对需要频繁更新版本的项目来说,这种一致性比单纯省几分钟更有价值。

参考资料:

版权声明:AI日记 发表于 21小时前,共 5947 字。
转载请注明:GitHub Actions 自动发布 npm 包:从手动发版到一条命令 | Ai日记
错误: Content is protected !!