npm -- 用 npm-publish-oidc 发布包

摘要

  • Trusted Publishing 的配置、Workflow 权限和 provenance 见 使用 GitHub Actions 和 OIDC 安全发布 npm 包。配好之后,每次发布仍然要在本地升版本、打 vX.Y.Z 标签、推上去,再等 GitHub Actions 跑完 npm publish。
  • npm-publish-oidc 把本地这一段收成项目根目录里的一条命令。它检查项目、按需提升版本、推送提交和标签。npm publish 由该项目的 GitHub Actions 通过 npm Trusted Publishing 完成。
  • 当前工作目录就是项目根目录。脚本不会切到自己所在的目录。

它做了什么

  • 本地不执行 npm publish。正式发布按这个顺序走:

    1. 确认当前目录有可发布的 package.json,并且是 git 仓库根目录。
    2. 确认工作区干净,origin 指向 GitHub。不在 main / master 上只警告,不中断。
    3. 在 .github/workflows 里找到执行 npm publish、并声明 id-token: write 的工作流。存在 publish.yml 时优先使用它。
    4. 打印发布计划,输入 y 后继续。--yes 跳过确认。
    5. 确认 gh 已登录,并提醒核对 npm 上的 Trusted Publisher。
    6. 运行项目里已经定义的 typecheck、build,以及 test:unit 或 test,再执行 npm pack --dry-run。
    7. 带 patch / minor / major 时执行 npm version,提交信息是 chore: release v%s。不升版本时,推送当前 package.json 版本对应的标签。
    8. 先推送当前提交,再推送 vX.Y.Z 标签。标签推送触发 Actions。
    9. 用 gh 等到工作流结束,再查询 npm 上的版本。--no-watch 推送后就返回。

安装

  • 依赖:bash、git、Node.js、npm。监控发布进度时还要已登录的 GitHub CLI(gh auth login)。

Homebrew

1
brew install hanqunfeng/tap/npm-publish-oidc
  • 安装后确认:

1
npm-publish-oidc --help
  • Homebrew 会把 npm-publish-oidc 和 publish.yml.tmpl 装到同一目录,再把命令链接到 PATH。Node.js、npm 和 GitHub CLI 需要另行安装。

从 GitHub 克隆

1
2
git clone https://github.com/hanqunfeng/npm-publish-oidc.git ~/npm-publish-oidc
chmod +x ~/npm-publish-oidc/npm-publish-oidc
  • 在 ~/.zshrc 里加上:

1
export PATH="$HOME/npm-publish-oidc:$PATH"
  • 新开一个终端后确认:

1
npm-publish-oidc --help --zh
  • --help 是英文说明。--help --zh 是中文说明。其他命令加上 --zh,运行时的提示也用中文。

  • publish.yml.tmpl 必须和 npm-publish-oidc 放在同一目录。--init 通过脚本的真实路径读取它,经符号链接调用也能找到。Homebrew 安装已经满足这一点。

项目要求

  • 除 --help 外,命令先确认两件事,两项都检查完再退出,方便一次看到全部问题:

    1. 当前目录可以发布到 npm。只看当前目录的 package.json,不向上查找。文件要能解析,包含可发布的 name 和语义化 version,且 private 不为 true。
    2. 当前目录是 git 仓库根目录。
  • --check、--dry-run 和正式发布还要求 origin 指向 GitHub,并且 .github/workflows 里有 OIDC 发布工作流。--dry-run 和正式发布另外要求工作区干净。

  • 用 HTTPS 和个人访问令牌推送 .github/workflows/ 时,令牌需要 workflow 权限。

初始化工作流

  • 项目还没有 .github/workflows/publish.yml 时:

1
2
cd /path/to/your-package
npm-publish-oidc --init
  • --init 根据 publish.yml.tmpl 生成一份基础工作流。文件已存在时不覆盖。旁边已经有 publish.yaml 时会停下来,提示直接改那个文件,或改名为 publish.yml。--init 不提交、不推送、不发布,也不能和其他参数一起使用。

  • scope 包(名字以 @ 开头)且没有设置 publishConfig.access 时,发布命令写成 npm publish --access public。非 scope 包,或者已经写了 publishConfig.access,发布命令是 npm publish,访问级别交给项目自己的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 通过 npm Trusted Publishing(OIDC)发布 @hanqunfeng/oidc-example。
# 前置条件:在 npmjs.com 为本包配置 Trusted Publisher:
# owner=hanqunfeng、repo=oidc-example、workflow=publish.yml。
name: Publish

on:
push:
tags:
- "v*"

permissions:
contents: write
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- 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: Test
run: npm test
- name: Preview package contents
run: npm pack --dry-run
- name: Publish to npm (OIDC)
run: npm publish --access public
  • 上面是步骤摘要。模板里还有这些步骤:检查 Actions 上的 npm 不低于 11.5.1,核对 tag 与 package.json 的 version 一致,发布后用 gh release create 建 GitHub Release,再轮询 npm view 等到注册表出现这个版本。id-token: write 是 OIDC 发布必需的权限。contents: write 用来创建 Release。

  • 生成结果只是基础模板,可以按项目修改构建、测试和安装步骤。模板中的 npm ci 需要 package-lock.json 或 npm-shrinkwrap.json。没有可用的 test 脚本时,删掉或改掉模板里的 npm test。npm init 自带的 echo "Error: no test specified" && exit 1 不算可用测试。

  • 已经有别的 OIDC 发布工作流时,--init 仍然可以创建 publish.yml,并提示之后发布会优先用这份新文件。

npm Trusted Publisher

字段 值
Organization or user GitHub 仓库所有者
Repository 仓库名
Workflow filename publish.yml(只写文件名)
Environment name 留空
  • 仓库所有者、仓库名和工作流文件名必须与 origin、实际使用的工作流一致。正式发布前,命令会打印一行提醒:请确认 npm 已配置 Trusted Publisher: <owner>/<repo> / <workflow 文件名>。

发布

  • 在项目根目录执行:

1
2
3
4
5
6
7
8
9
npm-publish-oidc --check          # 本地检查:已有的 typecheck、build、test,以及 npm pack --dry-run
npm-publish-oidc --dry-run # 检查并预览将推送的提交和标签
npm-publish-oidc patch # 0.1.0 → 0.1.1,推送后监控 Actions
npm-publish-oidc minor # 0.1.0 → 0.2.0
npm-publish-oidc major # 0.1.0 → 1.0.0
npm-publish-oidc # 不升版本,推送当前版本的 vX.Y.Z 标签
npm-publish-oidc --no-watch patch # 推送后不等待 Actions
npm-publish-oidc --yes patch # 展示计划后直接发布,不再询问
npm-publish-oidc --workflow release.yml patch
  • 正式发布会先输出发布计划,输入 y 后才继续。--yes 或 -y 跳过确认。--check、--dry-run 和 --init 不会询问。

  • patch、minor、major 可与 --dry-run、--no-watch、--yes、--workflow 组合,顺序不限。一次只能指定一个版本级别。

  • 工作流的选择规则:

    • 传了 --workflow 时,只用这个文件名。它必须在 .github/workflows/ 下,是 .yml 或 .yaml,并且同时包含 npm publish 和 id-token: write。
    • 没传时,有 publish.yml 或 publish.yaml 就用它。
    • 没有这两个文件、但只有一个 OIDC 发布工作流时,用那一个。
    • 有多个、其中又没有 publish.yml 时,必须用 --workflow 指定文件名。
  • 工作流里看不到 tags: 时只警告。这个流程靠推送 v* 标签触发 Actions,触发条件对不上时,标签推上去也不会启动发布。

  • 不升版本时,命令会先查 npm 上的 latest。版本已经存在就停止,提示先用 patch / minor / major。注册表返回 404 时视为首次发布。npm version 会同时改 package.json 和 lockfile,并创建 release 提交与本地标签。本地还没有这个标签时,命令补打一个轻量标签。远程已经有同名标签时,跳过标签推送。

  • 监控阶段用 gh run list 按仓库、工作流文件和标签找运行,再用 gh run watch 跟到结束。成功后轮询 npm view <包名> version。注册表还没同步时,包已经发出,可以稍后自己查。结束时会给出 npm 页面、GitHub Release 链接。package.json 里有 bin 时,安装命令是 npm install -g,否则是 npm install。

本地检查跑什么

  • 脚本不假设固定的检查命令,只跑 package.json 里已经写了的脚本:

    • 有 typecheck 就 npm run typecheck。
    • 有 build 就 npm run build。
    • 有可用的 test:unit 就跑它;否则跑可用的 test。占位的 no test specified 会跳过。
    • 最后一律执行 npm pack --dry-run,看将要打进包里的文件。
  • --check 做完这些就结束。--dry-run 做完这些之后,再打印将执行的 npm version、git push,以及「GitHub Actions 将发布到 npm 并创建 Release」。两条命令都不推送。

后记