npm release pipeline + bin rename (ccr collides with upstream CCR) - #1
Merged
Merged
Conversation
Tag-driven npm publish with provenance: push a v* tag and the full test suite, a tag/version agreement check, and a tarball content review run before anything ships. workflow_dispatch with dry_run: true rehearses the whole pipeline without publishing. Renames the main bin from ccr to ccr-toolkit — the upstream CCR CLI already owns ccr, so a global install of both packages collides with EEXIST, leaving whichever installed second broken. Adds the repository/bugs/homepage metadata that npm publish --provenance requires, documents the npm install path, and fixes the <you> placeholder left in the clone URL.
npm 正在淘汰长期发布 token:绕过 2FA 的 granular access token 已于 2026 年 8 月失去敏感账号/包管理操作权限,约 2027 年 1 月将失去直接 发布能力——发布面收缩为「读私有包 + 暂存发布,需人工 2FA 批准」。 原 workflow 用的正是这种 NPM_TOKEN,届时会失效。 改为 OIDC 优先:包存在后,npm 自动识别 GitHub Actions 的 OIDC 环境, 用短时、绑定 workflow 的 token 认证,无长期密钥可泄露或轮换。 但 OIDC 无法冷启动。读 npm 源码确认其实现是向 POST /-/npm/v1/oidc/token/exchange/package/<包名> 换取 token——交换以「包已存在且已配可信发布者」为前提,注册表没有 「用 OIDC 发布全新包」的端点。故首次发布仍走 token,之后自动切 OIDC。 token 传递方式:以 NPM_TOKEN 交给 shell(npm 不读此名),仅在非空时 export 成 NODE_AUTH_TOKEN。这样缺失 secret 时 npm 看到的是「未定义」 而非「已定义为空」,直接走 OIDC 分支,不依赖 npm 对空 token 的处理。 新增 11 项测试锁住这些在打 tag 时才暴露、且失败得很晚的契约 (id-token 权限、provenance、token 传法、dry_run 守卫、测试先于发布、 tag 与版本一致、显式指定公共 registry、repository 字段、冷启动说明)。 顺带修正 README 中过期的测试数(103 → 206,badge 同步),并把 harness 遥测目录加进 .gitignore。 测试 206 项全绿;npm pack 12 文件正常。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
.github/workflows/release.yml— push av*tag and it runs the full test suite, asserts the tag agrees withpackage.json, prints the packed tarball for review, then publishes with--provenance.workflow_dispatchwithdry_run: truerehearses everything short of publishing.bin rename:
ccr→ccr-toolkit— the upstream CCR CLI already owns the commandccr(@musistudio/claude-code-routerv3.1.1, verified on the registry). A global install of both packages collides with EEXIST, leaving whichever installed second broken. Unpublished, so the rename is free now and expensive after. The four hard-codedccrstrings incli.mjsoutput and the README sample are updated to match.npm metadata —
repository/bugs/homepageadded.npm publish --provenancerequires arepositoryfield pointing at the publishing repo; without it the publish step fails.README: npm install path documented; the
<you>placeholder left in the clone URL fixed; tests badge 185 → 195.Verification
npm test: 195/195 passingnpm pack --dry-run: 12 files, 40.7 kB — contents correctnode src/cli.mjs --list: allccr-toolkitreferences alignedBefore the first npm release (one-time)
NPM_TOKENrepository secret (Settings → Secrets → Actions).npm loginlocally, or create the token at npmjs.com.v0.2.0already exists (GitHub release) and npm has no 0.2.0 — either publish as 0.2.1 (recommended: tagv0.2.1, bump version first) or delete and re-push thev0.2.0tag to trigger the workflow.workflow_dispatch→dry_run: truebefore the first real tag.