使用 npm Trusted Publishing 通过 GitHub Actions 自动发布 npm 包
npm Trusted Publishing 允许 GitHub Actions 通过 OIDC 身份发布 npm 包,不再需要长期保存 NPM_TOKEN、NODE_AUTH_TOKEN 这类发布 token。
官方文档:
https://docs.npmjs.com/trusted-publishers/它解决的核心问题很直接:
让 npm 只信任指定仓库、指定 workflow 发起的发布动作,而不是依赖一个长期有效的发布 token。
适用场景
Trusted Publishing 适合这些项目:
- npm 包由 GitHub Actions 自动发布。
- 不想在 GitHub Secrets 里保存长期 npm 发布 token。
- 发布动作只应该由指定仓库、指定 workflow 执行。
- 希望发布过程带有 npm provenance / OIDC 信任链。
它不适合这些场景:
- 使用 self-hosted runner 发布。npm Trusted Publishing 当前主要支持 GitHub-hosted runner。
- 发布时需要访问私有 npm 依赖,但没有额外配置只读 token。
- 想在本地完整模拟 OIDC 发布。OIDC 鉴权只能在真实 CI 环境里完整验证。
基本原理
Trusted Publishing 的流程可以理解成五步:
- 在 npm 包设置里声明“这个 GitHub 仓库的某个 workflow 可以发布这个包”。
- GitHub Actions workflow 开启
id-token: write权限。 npm publish在 CI 中自动检测 OIDC 环境。- npm 校验 workflow 身份是否和包设置里的 Trusted Publisher 匹配。
- 校验通过后发布包,不需要
NPM_TOKEN。
也就是说,真正的发布凭据不再是你手动生成的长期 token,而是 CI 运行时临时生成的 OIDC 身份。
前置要求
按照 npm 官方文档,Trusted Publishing 当前要求:
- Node.js
22.14.0或更高。 - npm CLI
11.5.1或更高。 - workflow 运行在支持的 CI 环境中,例如 GitHub-hosted runner。
- npm 包页面已经配置 Trusted Publisher。
为了省事,GitHub Actions 里可以直接使用 Node 24:
- name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org"第一步:在 npm 包里配置 Trusted Publisher
进入 npm 包页面:
https://www.npmjs.com/package/<你的包名>/access找到 Trusted Publisher,选择 GitHub Actions,然后填写:
Organization or user: GitHub 用户名或组织名Repository: 仓库名Workflow filename: publish.ymlEnvironment name: 留空,除非 workflow 使用了 GitHub EnvironmentAllowed actions: npm publish注意:
Workflow filename只填文件名,例如publish.yml。- 不要填
.github/workflows/publish.yml。 - workflow 文件必须真实存在于
.github/workflows/目录下。 - monorepo 中有多个 npm 包时,每个要发布的包都需要单独配置 Trusted Publisher。
如果这些字段和真实 workflow 不一致,通常要等到发布时才会暴露问题。npm 不会在保存 Trusted Publisher 配置时替你完整验证仓库和 workflow 是否真的匹配。
第二步:配置 GitHub Actions workflow
单包项目可以使用下面这个最小版本:
name: Publish
on: push: tags: - "v*"
permissions: id-token: write contents: read
jobs: publish: runs-on: ubuntu-latest
steps: - name: Checkout uses: actions/checkout@v6
- name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" package-manager-cache: false
- name: Install dependencies run: npm ci
- name: Build run: npm run build --if-present
- name: Test run: npm test --if-present
- name: Publish to npm run: npm publish --access public关键点是:
permissions: id-token: write contents: read以及发布时使用:
npm publish --access public不要再传:
env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}Trusted Publishing 要的不是长期 token,而是 GitHub Actions 在运行时签发的 OIDC 身份。
Yarn / pnpm 项目怎么处理
即使项目使用 Yarn 或 pnpm,也建议发布步骤使用 npm publish。
例如 Yarn 项目:
- name: Enable Corepack run: corepack enable
- name: Install dependencies run: yarn install --immutable
- name: Build run: yarn build
- name: Publish to npm run: npm publish --access publicpnpm 项目同理:
- name: Enable Corepack run: corepack enable
- name: Install dependencies run: pnpm install --frozen-lockfile
- name: Build run: pnpm build
- name: Publish to npm run: npm publish --access public原因是 Trusted Publishing 是 npm CLI 的 OIDC 发布能力。安装和构建可以继续沿用项目自己的包管理器,但发布动作建议交给 npm CLI。
Monorepo 示例
如果一个仓库里有多个包,可以用 tag 决定发布哪个 workspace。
例如 tag 格式:
workflow 示例:
name: Publish
on: push: tags: - "**"
permissions: id-token: write contents: read
jobs: publish: runs-on: ubuntu-latest
steps: - name: Parse tag id: parse run: | TAG="${GITHUB_REF#refs/tags/}" PACKAGE_NAME="${TAG%@*}" VERSION="${TAG##*@}" echo "package_name=$PACKAGE_NAME" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Checkout uses: actions/checkout@v6
- name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" package-manager-cache: false
- name: Enable Corepack run: corepack enable
- name: Install dependencies run: yarn install --immutable
- name: Find workspace id: find run: | PACKAGE_DIR=$(yarn workspaces list --json \ | jq -r --arg name "${{ steps.parse.outputs.package_name }}" \ 'select(.name == $name) | .location')
if [ -z "$PACKAGE_DIR" ]; then echo "::error::Package not found: ${{ steps.parse.outputs.package_name }}" exit 1 fi
echo "dir=$PACKAGE_DIR" >> "$GITHUB_OUTPUT"
- name: Verify version working-directory: ${{ steps.find.outputs.dir }} run: | PKG_VERSION=$(jq -r '.version' package.json) TAG_VERSION="${{ steps.parse.outputs.version }}"
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then echo "::error::Version mismatch: package.json=$PKG_VERSION tag=$TAG_VERSION" exit 1 fi
- name: Build run: yarn workspaces foreach -R --from "${{ steps.parse.outputs.package_name }}" --topological-dev run build
- name: Test working-directory: ${{ steps.find.outputs.dir }} run: | if jq -e ".scripts.test" package.json > /dev/null; then yarn test else echo "No test script defined" fi
- name: Publish to npm working-directory: ${{ steps.find.outputs.dir }} run: npm publish --access publicmonorepo 里最容易出错的是 npm 包设置。每个 workspace 发布到 npm 后,都要在各自的 npm 包页面单独配置 Trusted Publisher。
预发布版本 dist-tag
如果希望 1.0.0-beta.1 自动发布到 beta tag,可以在发布步骤里根据版本号决定 dist-tag:
- name: Publish to npm run: | VERSION=$(jq -r ".version" package.json)
if echo "$VERSION" | grep -qE "(alpha|beta|rc|next)"; then DIST_TAG=$(echo "$VERSION" | sed -E "s/.*-(alpha|beta|rc|next).*/\1/") npm publish --access public --tag "$DIST_TAG" else npm publish --access public fi这样:
1.0.0-beta.1 -> beta1.0.0-rc.1 -> rc1.0.0 -> latest如何测试但不发布
可以先检查包内容:
npm pack --dry-run这会展示最终 tarball 会包含哪些文件,但不会发布。
也可以测试发布命令的大部分流程:
npm publish --dry-run --access public注意:
npm publish --dry-run不会真正发布。- 如果当前版本已经发布过,它可能仍然报错:
You cannot publish over the previously published versions。 - 这不代表包不能打包,只代表这个版本号已经存在。
- OIDC / Trusted Publisher 鉴权无法在本地完整验证。
- 最终鉴权闭环只能通过 GitHub Actions 中的一次真实新版本发布验证。
常见问题
workflow 报没有权限获取 OIDC token
检查 workflow 是否包含:
permissions: id-token: write contents: readid-token: write 是 GitHub Actions 生成 OIDC token 的关键权限。
npm 提示 Trusted Publisher 不匹配
检查 npm 包设置里的这些字段是否完全匹配:
Organization or userRepositoryWorkflow filenameEnvironment nameAllowed actions尤其注意:
Workflow filename: publish.yml不要写成:
.github/workflows/publish.yml如果使用了 GitHub Environment,Environment name 也必须和 workflow 里的环境名一致。
还需要 NPM_TOKEN 吗
发布公开包通常不需要。
应该删除:
env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}如果项目安装私有依赖,可能仍然需要只读 token 来安装依赖,但发布本身不应该再依赖长期发布 token。
可以继续用 Yarn 或 pnpm 构建吗
可以。
推荐模式是:
Yarn / pnpm / npm installYarn / pnpm / npm buildnpm publish也就是安装和构建沿用项目自己的包管理器,发布统一用 npm CLI。
要不要提交构建产物
两种方式都可以:
- 如果 CI 里会构建,就不需要提交
lib/dist。 - 如果 workflow 不构建,就必须确保发布产物已经提交进仓库。
更推荐 CI 中构建,避免本地忘记更新产物。
为什么 npm provenance 没显示
Trusted Publishing 从 GitHub Actions 或 GitLab CI/CD 发布公开包时,npm 会自动生成 provenance。这个前提通常包括:
- 使用 Trusted Publishing / OIDC 发布。
- 仓库是公开仓库。
- 包是公开包。
如果仓库是私有仓库,即使包本身是公开包,也可能不会生成 provenance。
推荐发布流程
- 修改代码。
- 更新
package.json版本号。 - 本地运行构建和测试。
- 本地检查包内容:
npm pack --dry-run- 提交代码。
- 打 tag:
git tag v1.2.3git push origin v1.2.3monorepo 可以使用包名加版本号:
- GitHub Actions 自动发布。
- 到 npm 包页面确认新版本已发布。
总结
Trusted Publishing 的关键不是复杂脚本,而是三件事:
- npm 包页面配置 Trusted Publisher。
- GitHub Actions workflow 开启
id-token: write。 - CI 中使用
npm publish发布。
这样可以去掉长期 npm 发布 token,减少 secret 泄露风险,也让发布来源更清晰。