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。正式发布按这个顺序走:- 确认当前目录有可发布的
package.json,并且是 git 仓库根目录。 - 确认工作区干净,
origin指向 GitHub。不在main/master上只警告,不中断。 - 在
.github/workflows里找到执行npm publish、并声明id-token: write的工作流。存在publish.yml时优先使用它。 - 打印发布计划,输入
y后继续。--yes跳过确认。 - 确认
gh已登录,并提醒核对 npm 上的 Trusted Publisher。 - 运行项目里已经定义的
typecheck、build,以及test:unit或test,再执行npm pack --dry-run。 - 带
patch/minor/major时执行npm version,提交信息是chore: release v%s。不升版本时,推送当前package.json版本对应的标签。 - 先推送当前提交,再推送
vX.Y.Z标签。标签推送触发 Actions。 - 用
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 | git clone https://github.com/hanqunfeng/npm-publish-oidc.git ~/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外,命令先确认两件事,两项都检查完再退出,方便一次看到全部问题:- 当前目录可以发布到 npm。只看当前目录的
package.json,不向上查找。文件要能解析,包含可发布的name和语义化version,且private不为true。 - 当前目录是 git 仓库根目录。
- 当前目录可以发布到 npm。只看当前目录的
-
--check、--dry-run和正式发布还要求origin指向 GitHub,并且.github/workflows里有 OIDC 发布工作流。--dry-run和正式发布另外要求工作区干净。 -
用 HTTPS 和个人访问令牌推送
.github/workflows/时,令牌需要workflow权限。
初始化工作流
-
项目还没有
.github/workflows/publish.yml时:
1 | cd /path/to/your-package |
-
--init根据publish.yml.tmpl生成一份基础工作流。文件已存在时不覆盖。旁边已经有publish.yaml时会停下来,提示直接改那个文件,或改名为publish.yml。--init不提交、不推送、不发布,也不能和其他参数一起使用。 -
scope 包(名字以
@开头)且没有设置publishConfig.access时,发布命令写成npm publish --access public。非 scope 包,或者已经写了publishConfig.access,发布命令是npm publish,访问级别交给项目自己的配置。
1 | # 通过 npm Trusted Publishing(OIDC)发布 @hanqunfeng/oidc-example。 |
-
上面是步骤摘要。模板里还有这些步骤:检查 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
-
在 npm 包的 Trusted Publisher 里配置 GitHub Actions。原理和字段说明见 使用 GitHub Actions 和 OIDC 安全发布 npm 包。
-
四个字段按下面填写:
| 字段 | 值 |
|---|---|
| Organization or user | GitHub 仓库所有者 |
| Repository | 仓库名 |
| Workflow filename | publish.yml(只写文件名) |
| Environment name | 留空 |
-
仓库所有者、仓库名和工作流文件名必须与
origin、实际使用的工作流一致。正式发布前,命令会打印一行提醒:请确认 npm 已配置 Trusted Publisher: <owner>/<repo> / <workflow 文件名>。
发布
-
在项目根目录执行:
1 | npm-publish-oidc --check # 本地检查:已有的 typecheck、build、test,以及 npm pack --dry-run |
-
正式发布会先输出发布计划,输入
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」。两条命令都不推送。