bundlekit 用 Changesets + GitHub Actions 实现自动化 npm publish。本文档记录从零到首发到迭代的完整步骤。
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 开发者 CI / GitHub Actions │
│ ───────── ───────────────────────── │
│ │
│ 1. 改代码 │
│ 2. pnpm changeset │
│ (选受影响包 + bump 类型 + 写说明) │
│ 3. git commit + push │
│ 4. 提 PR 到 master │
│ │
│ └─ PR 合并到 master │
│ │ │
│ ▼ │
│ 5. publish-npm.yml 触发 │
│ 6. install + build + test │
│ 7. changesets/action 检测: │
│ - 有未发布的 changeset │
│ → 创建 / 更新 "Version Packages" PR │
│ - 无未发布 changeset │
│ → 不动 │
│ │
│ 8. 维护者 review "Version Packages" PR │
│ (检查 version bump + CHANGELOG.md) │
│ 9. 合并 PR │
│ │ │
│ ▼ │
│ 10. publish-npm.yml 再次触发 │
│ 11. changesets/action 检测无 pending: │
│ → pnpm changeset publish │
│ → 推 tarball 到 npm registry │
│ → 创建 git tag + GitHub release │
│ │
└─────────────────────────────────────────────────────────────────────┘
核心机制:两次 push 触发,第一次开 Version Packages PR,第二次自动 publish。
npm view @bundlekit/cli 2>&1 | head -3
# 期望:404 Not Found(说明 scope 没被占)如果 @bundlekit scope 已被别人占,需要把所有 @bundlekit/* 包改名(例如改成 @bundlekit/* 或 @<你的用户名>/bundlekit-*)。涉及修改:
- 各
packages/*/package.json的name packages/bundlekit-shared-utils/lib/types/cli-init/index.ts的BUNDLER_PACKAGE_MAPpackages/bundlekit-plugin-*/templates/*/package.json.ejs中的 dep 名- 全部文档(docs / README)中的 import 示例
访问 https://www.npmjs.com/org/create,新建 @bundlekit org(如果是个人 scope 直接用账号 scope 即可)。
-
登录 https://www.npmjs.com → 头像菜单 → Access Tokens
-
Generate New Token → Granular Access Token(推荐,更安全)
-
配置:
字段 值 Token name bundlekit-ciExpiration 365 天(到期前要轮换) Packages and scopes 勾选 @bundlekitscope 下全部包Permissions Read and write -
点 Generate
-
⚠️ 立即复制 token,离开页面后无法再查看
💡 Classic Token 也行(选 Automation 类型),但 Granular 更精确控权限。
-
打开仓库 https://github.com/Harhao/bundlekit → Settings 标签
-
左侧 Secrets and variables → Actions
-
点 New repository secret
-
配置:
字段 值 Name NPM_TOKENSecret 粘贴步骤 3 复制的 token -
点 Add secret
💡
GITHUB_TOKEN不需要手工配置,GitHub Actions 自动注入。
仓库 Settings → Actions → General → 拖到底部 Workflow permissions:
- ✅ Read and write permissions(默认是 read-only,必须改)
- ✅ Allow GitHub Actions to create and approve pull requests
否则 changesets/action 无法创建 "Version Packages" PR。
新建 .github/PULL_REQUEST_TEMPLATE.md:
## Summary
<!-- 描述这个 PR 做了什么 -->
## Checklist
- [ ] 跑 `pnpm changeset` 写了 changeset
- [ ] `pnpm test` 通过
- [ ] `pnpm test:integration` 通过
- [ ] 相关文档更新bundlekit 当前所有包是 0.0.1,首发推荐 bump 到 0.1.0(pre-1.0 不稳定语义,给 BREAKING 留余地)。
cd /path/to/bundlekit
pnpm changeset交互式问答:
? Which packages would you like to include? ...
◯ @bundlekit/cli
◯ @bundlekit/service
◯ @bundlekit/shared-utils
◯ @bundlekit/bundler-vite
...
按空格全选所有 @bundlekit/*(首发就是全部要发)。
? Which packages should have a major bump? ...
回车跳过(首发不用 major)。
? Which packages should have a minor bump? ...
按空格选所有要 bump 的包(应该是除了已选 major 之外的全部)。
? Please enter a summary for this change ...
填:"Initial public release"
完成后会生成 .changeset/<random-name>.md,里面是受影响包列表 + summary。
# 预览 version bump 效果(不实际写盘)
pnpm changeset status输出示例:
🦋 Releases:
🦋 @bundlekit/cli@0.1.0 (minor)
🦋 @bundlekit/service@0.1.0 (minor)
🦋 ...
git add .changeset/
git add . # 其他改动一起
git commit -m "chore: prepare initial 0.1.0 release"
git push origin master打开 https://github.com/Harhao/bundlekit/actions 看 publish-npm workflow:
-
第一波(推 master 触发):
✓ checkout ✓ setup pnpm ✓ setup node ✓ pnpm install --frozen-lockfile ✓ pnpm build:all ✓ pnpm test ✓ pnpm test:integration ✓ changesets/action → 创建 "Version Packages" PR
PR 名字是 chore: version packages,机器人提交。
打开 PR,确认:
- ✅ 每个
@bundlekit/*包的package.json.version从0.0.1改为0.1.0 - ✅ 根
CHANGELOG.md/ 各包CHANGELOG.md内容正确 - ✅ pnpm-lock.yaml 同步更新
- ✅ workspace:^ 协议在生产 build 中被 pnpm 自动转换
如果有问题,不要直接关 PR,回到本地改 changeset:
git checkout changeset-release/master
# 改 .changeset/ 或代码
git push # PR 会自动更新review 通过 → 点 Squash and merge(或 Merge commit)。
PR merge 到 master 触发新一轮 workflow:
-
第二波:
✓ checkout ✓ setup pnpm ✓ setup node ✓ pnpm install --frozen-lockfile ✓ pnpm build:all ✓ pnpm test ✓ pnpm test:integration ✓ changesets/action → 检测无 pending changeset → pnpm changeset publish → 推 tarball 到 npm → 创建 git tag @bundlekit/cli@0.1.0 等 → 创建 GitHub release
# 任意目录
npm view @bundlekit/cli
# 期望看到 "version": "0.1.0"
npm view @bundlekit/service
npm view @bundlekit/bundler-vite
npm view @bundlekit/plugin-react
# 全部都应该是 0.1.0也可以打开 https://www.npmjs.com/package/@bundlekit/cli 看 release。
# 全新目录
cd /tmp/bundlekit-test
npm install -g @bundlekit/cli@0.1.0
# 现在外部用户能直接用
dc create my-app -t react-ts -b vite
cd my-app && pnpm install
pnpm dev# 1. 在 feature 分支改代码
git checkout -b fix/some-bug
# ... 改代码 + 加测试 ...
# 2. 跑测试确认
pnpm test
pnpm test:integration
# 3. 写 changeset
pnpm changeset
# 选受影响的包 + 选 patch(bug fix)/ minor(新功能)
# 写 summary
# 4. commit + push
git add .
git commit -m "fix: some bug"
git push origin fix/some-bug
# 5. 在 GitHub 提 PR 到 master
# 6. PR review + merge → workflow 自动:
# - 第一波:更新 "Version Packages" PR(如果已存在)
# 或创建新的(如果是第一个 pending changeset)
# - "Version Packages" PR merge → 第二波自动 publish 新版本bundlekit 用 lockstep 发版(所有 @bundlekit/* 同步 bump),原因:
- cli 创建项目时模板用
workspace:^协议 - pnpm publish 自动把
workspace:^转换为依赖包的实际版本 - 假设所有
@bundlekit/*版本同步,避免 service@0.1.0 拿到 plugin-react@0.0.5 这种错配
✓ 正确 → 所有 @bundlekit/* 同时 minor bump(0.1.0 → 0.2.0 全套)
✗ 错误 → 单独把 @bundlekit/plugin-react bump 到 0.5.0
如果某个包真有独立大版本号需求(如 plugin-react 跟随 react 主版本),用 linked 配置:
但目前 linked: [],全部 lockstep。
{
"$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": [
"bundlekit-cli-docs"
]
}| 字段 | 含义 |
|---|---|
access: "public" |
@bundlekit/* scope 包默认 npm 私有,必须显式开 public |
baseBranch: "master" |
与 GitHub Actions on.push.branches 必须一致 |
updateInternalDependencies: "patch" |
A 包升级时依赖它的 B 包自动 bump patch |
ignore: ["bundlekit-cli-docs"] |
docs 站点包不参与发版 |
name: publish-npm
on:
push:
branches:
- master
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write # 创建 commit / tag / release
pull-requests: write # 创建 / 更新 Version Packages PR
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # ⚠ 必须:changeset 需完整 git history
- uses: pnpm/action-setup@v4
with:
version: 8.15.9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build:all
- run: pnpm test
- run: pnpm test:integration
- name: Create Release Pull Request or Publish
uses: changesets/action@v1
with:
title: 'chore: version packages'
commit: 'chore: version packages [skip ci]'
publish: pnpm changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}NPM_TOKEN 错或权限不够。检查:
- Token 是否在 npm 上仍有效(去 npmjs.com/settings//tokens 查)
- Token 是否对
@bundlekitscope 有 read+write 权限 - GitHub secret name 是否正好是
NPM_TOKEN
可能原因:
- 没 changeset:
.changeset/下没有 markdown 文件(除了config.json和README.md),workflow 不动 - 权限不够:仓库 Settings → Actions → General → "Allow GitHub Actions to create and approve pull requests" 没勾
- baseBranch 不匹配:
.changeset/config.json的baseBranch与 workflowon.push.branches不一致
打开 Actions 看新触发的 workflow:
- 如果 workflow 没触发:检查 master 是否真的有新 commit(PR merge 可能是 rebase 模式,commit 已存在)
- 如果 changesets/action 报错:看具体错误,通常是 NPM_TOKEN 问题
- 如果 changesets/action 跑完但没 publish:检查
.changeset/是否还有 markdown 文件(merge 后应该自动删除)
# npm 不允许覆盖发布同一版本,但允许 deprecate
npm deprecate @bundlekit/cli@0.1.0 "Bad release, use 0.1.1"
# 然后立刻发新版修复
pnpm changeset # 选 patch + 写 "Hotfix for 0.1.0"
git push正常。pnpm publish 自动转换:
- "@bundlekit/service": "workspace:^"
+ "@bundlekit/service": "^0.1.0"转换基于 monorepo 里 @bundlekit/service 当前版本号。
不推荐,但应急可用:
# 切到 master
git checkout master && git pull
# 应用所有 pending changeset
pnpm changeset version
# review 改动
git diff
# commit + push
git add . && git commit -m "chore: version packages"
git push
# 手工 publish
NPM_TOKEN=<your_npm_token> pnpm changeset publish仍会推 git tag 但不会自动建 GitHub release。
用 verdaccio 起本地 registry:
npm install -g verdaccio
verdaccio # 默认 http://localhost:4873
# 在仓库根目录配置临时 .npmrc
echo "registry=http://localhost:4873/" > .npmrc
# publish 到 verdaccio
NPM_TOKEN=fake-token pnpm changeset publish
# 测完删掉 .npmrc
rm .npmrcpnpm changeset publish 是按依赖顺序串行发,失败时已发的不会回滚。处理:
- 看哪些包成功发了(
npm view @bundlekit/<pkg>验) - 把失败的包手工发:
cd packages/bundlekit-<failed> NPM_TOKEN=<token> npm publish --access public
- 或者把所有版本号回退一个 patch,重新跑 publish
不建议,但临时可这样改 .github/workflows/publish-npm.yml:
- run: pnpm test || echo "tests skipped"或者注释掉 test step,merge 后再恢复。
| 工具 | 选用了吗 | 理由 |
|---|---|---|
| changeset | ✅ | monorepo 版本管理事实标准 |
| changesets/action | ✅ | 与 GitHub Actions 集成最深 |
| pnpm publish | ✅ | 自动转换 workspace:^ 协议 |
| semantic-release | ❌ | 过度黑盒,与 monorepo 配合复杂 |
| lerna | ❌ | 现代仓库改用 pnpm+changeset |
| nx release | ❌ | 主要为 nx 仓库设计 |
| release-it | ❌ | 单仓库友好,monorepo 不行 |
- npm scope 占领(
@bundlekit或备选) - npm Automation Token 生成
- GitHub Secrets 配置
NPM_TOKEN - Workflow permissions 设 read+write + 允许 PR
-
.changeset/config.jsonbaseBranch 正确 -
.github/workflows/publish-npm.yml跑通本地 dry run - 所有
@bundlekit/*包版本号统一 - 所有
@bundlekit/*包publishConfig.registry正确 - 所有
@bundlekit/*包files字段不含源码
- 写 changeset 描述变更
- 本地
pnpm test+pnpm test:integration通过 - commit + push 到 master
- Review "Version Packages" PR
- Merge PR
- 等 workflow publish 完成
-
npm view验证新版本可拉
- 贡献指南 — 完整 contributor 流程
- 发版流程文档站 — 同内容的文档站版本
- Changeset 官方文档
- pnpm publish
- GitHub Actions 文档