diff --git a/.changeset/README.md b/.changeset/README.md index e49d978..d54a06a 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -10,3 +10,22 @@ Select the affected packages, choose the semantic version bump, and describe the tests, CI, and internal refactors that do not change a published package may omit a changeset. The three public packages are kept in a fixed group and release with the same version. + +## Manual Version PR + +GitHub Actions is intentionally not allowed to create or approve pull requests. A maintainer prepares each Version PR: + +```sh +git switch main +git pull --ff-only origin main +git switch -c release/version-packages +pnpm version-packages +pnpm check +git add .changeset package.json pnpm-lock.yaml packages +git commit -m "chore: release packages" +git push -u origin release/version-packages +``` + +Open a pull request, review the generated versions and changelogs, and merge it with rebase. Only after that Version PR +is merged should `NPM_RELEASE_ENABLED` be set to `true` and the Release workflow dispatched. Restore the variable to +`false` after registry verification. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7dab31..dc3e809 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,6 @@ jobs: environment: npm permissions: contents: write - pull-requests: write id-token: write steps: - name: Check out repository @@ -53,14 +52,21 @@ jobs: - name: Run quality gate run: pnpm check - - name: Create version pull request or publish - id: changesets - uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 - with: - publish: pnpm release - version: pnpm version-packages - title: "chore: release packages" - commit: "chore: release packages" - commitMode: github-api - env: - GITHUB_TOKEN: ${{ github.token }} + - name: Detect pending Changesets + id: release-state + shell: bash + run: | + if find .changeset -maxdepth 1 -type f -name '*.md' ! -name README.md | grep -q .; then + echo "publish=false" >> "${GITHUB_OUTPUT}" + echo "::notice::Pending Changesets found. Create and review a manual Version PR before publishing." + else + echo "publish=true" >> "${GITHUB_OUTPUT}" + fi + + - name: Publish packages with npm OIDC + if: steps.release-state.outputs.publish == 'true' + run: pnpm release + + - name: Push release tags + if: steps.release-state.outputs.publish == 'true' + run: git push origin --tags diff --git a/specs/20260729-public-monorepo-release-governance/TECH.md b/specs/20260729-public-monorepo-release-governance/TECH.md index d67c819..e8ba3b2 100644 --- a/specs/20260729-public-monorepo-release-governance/TECH.md +++ b/specs/20260729-public-monorepo-release-governance/TECH.md @@ -254,8 +254,7 @@ checkout 必须使用完整 history。Version PR branch `changeset-release/main` #### Actions - Default `GITHUB_TOKEN` permissions:read-only -- 允许 GitHub Actions 创建 pull requests,以便 Changesets 创建 Version PR -- 不允许 workflow 使用 `GITHUB_TOKEN` 自动提交 approving review +- 禁止 GitHub Actions 创建或批准 pull requests;Version PR 由 maintainer 本地生成并人工创建 - public fork workflow policy:所有 external contributors 都需要 maintainer approval - 只允许 GitHub-owned actions 和明确 allowlisted third-party actions - policy 支持时,要求所有 `uses:` 使用完整 40-character commit SHA @@ -364,11 +363,11 @@ SHA 更新 PR,维护者 review upstream diff 后合并。 ### 8. Release workflow -新增 `.github/workflows/release.yml`,保留 Changesets 的单 workflow state machine: +新增 `.github/workflows/release.yml`,使用人工 Version PR 与自动 OIDC publish 分离的 state machine: -1. `main` 上仍有未消费 Changesets 时,创建或更新 `chore: release packages` Version PR。 -2. Version PR 合并后,package versions 已更新且 Changesets 已消费;下一次 run 执行正式 npm publish、tags 和 - GitHub Releases。 +1. Maintainer 从最新 `main` 创建 release branch,运行 `pnpm version-packages`,提交人工 Version PR。 +2. Version PR 经人工 review 并合并后,package versions 已更新且 Changesets 已消费。 +3. Maintainer 打开 release window 并运行 workflow;workflow 执行正式 npm publish 和 tags。 Triggers: @@ -383,42 +382,26 @@ Controls: - job 绑定 protected Environment `npm`; - permissions 只包含: - `contents: write` - - `pull-requests: write` - `id-token: write` - install 使用 frozen lockfile; - release build 禁用 package-manager cache; - 先执行完整 `pnpm check`; -- `changesets/action` 使用已验证的完整 SHA; -- action 使用 GitHub API commit mode,使 Version PR commits 和 release tags 由 GitHub App 签名; -- action 的 `publish` command 为 `pnpm release`; -- action 的 `version` command 为 `pnpm version-packages`; -- 只向 action 提供 GitHub 自动生成的 `GITHUB_TOKEN`; +- 检测到未消费 Changesets 时 fail closed,不执行 publish; +- 没有未消费 Changesets 时执行 `pnpm release`,再 push Changesets 创建的 release tags; +- workflow 不拥有 `pull-requests: write`,也不使用 `GITHUB_TOKEN` 创建或批准 PR; - 不设置 `NPM_TOKEN`、`NODE_AUTH_TOKEN` 或其他 npm write credential。 -`npm` Environment settings: - -- required reviewer:至少一名独立 maintainer; -- prevent self-review:启用; -- deployment branches:只允许 protected `main`; -- disallow administrators bypass:启用; -- Environment name 必须与 npm Trusted Publisher 中的 `Environment name` 精确一致。 - -因为 Version PR creation 和 publish 复用一个 protected job,一个 release window 通常出现两次 Environment -approval: - -1. 第一次批准运行后,Changesets 创建或更新 Version PR;不会 publish。 -2. Version PR 被 review 并 merge 后,第二次批准运行才会 publish。 - -这是有意的双重人工边界,不应通过注入长期 Token 或移除 Environment 来简化。 +`npm` Environment 只允许 protected branches,且 Environment name 必须与 npm Trusted Publisher 中的 +`Environment name` 精确一致。有第二名 maintainer 后,应增加 required reviewer 并启用 prevent self-review;在此之前, +人工 Version PR 是发布前的人类检查边界。 `NPM_RELEASE_ENABLED` 默认和日常值为 `false`。打开 release window 的顺序: 1. 确认 Trusted Publishers、Environment reviewers 和 `main` ruleset; -2. 设置为 `true`; -3. 手动运行 Release 或等待 `main` push; -4. review 并 merge Version PR; -5. 批准正式 publish deployment; -6. registry 验证成功后立即恢复为 `false`。 +2. 从最新 `main` 运行 `pnpm version-packages`,创建、review 并 merge Version PR; +3. 设置为 `true`; +4. 手动运行 Release; +5. registry 验证成功后立即恢复为 `false`。 ### 9. npm Trusted Publishing @@ -633,7 +616,7 @@ docs/release/npm-release.md - 若需要,执行一次性 `0.0.0` bootstrap。 - 配置 Trusted Publishers。 - 删除并撤销 bootstrap credentials。 -- 通过 Version PR 和 OIDC 发布 `0.1.0`。 +- 通过人工 Version PR 和 OIDC 发布 `0.1.0`。 - 验证 provenance、tags、GitHub Releases 和 registry consumer。 - 关闭 release window,并把 package publishing access 改为 disallow tokens。