手动发 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 包并不复杂,核心只有三件事:
- 用 Trusted Publishing 建立 npm 和 GitHub Actions 的信任关系;
- 在 tag workflow 中校验版本、跑测试、发布;
- 用
npm version patch和git push --follow-tags完成一次发版。
配置一次之后,发版就从“登录、测试、发布、打 tag”变成一条命令。对需要频繁更新版本的项目来说,这种一致性比单纯省几分钟更有价值。
参考资料: