Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 1 addition & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,6 @@ jobs:
BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

maintainer-evidence:
name: Maintainer evidence
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.5"
- name: Require evidence only for high-risk changes
env:
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
PR_BODY: ${{ github.event.pull_request.body }}
run: bun scripts/pr-evidence.ts

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
runs-on: ubuntu-latest
Expand All @@ -91,14 +74,13 @@ jobs:
gate:
name: Gate
runs-on: ubuntu-latest
needs: [prepare-verification, verify, maintainer-evidence, package-compatibility]
needs: [prepare-verification, verify, package-compatibility]
if: always()
steps:
- name: Check results
run: |
if [ "${{ needs.prepare-verification.result }}" != "success" ] || \
[ "${{ needs.verify.result }}" != "success" ] || \
[ "${{ needs.maintainer-evidence.result }}" != "success" ] || \
[ "${{ needs.package-compatibility.result }}" != "success" ]; then
echo "One or more required checks failed."
exit 1
Expand Down
89 changes: 0 additions & 89 deletions .github/workflows/prepare-beta.yml

This file was deleted.

21 changes: 18 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: Publish npm

on:
push:
branches: [main]
workflow_dispatch:
inputs:
channel:
Expand All @@ -11,7 +13,7 @@ on:
- beta
- stable
confirm:
description: Type PUBLISH to confirm
description: Type PUBLISH to confirm a manual beta or stable retry
required: true
type: string

Expand All @@ -23,7 +25,10 @@ concurrency:

jobs:
preflight:
if: vars.NPM_RELEASE_ENABLED == 'true' && inputs.confirm == 'PUBLISH'
if: >-
vars.NPM_RELEASE_ENABLED == 'true' &&
((github.event_name == 'push' && contains(github.event.head_commit.message, 'chore: release packages')) ||
(github.event_name == 'workflow_dispatch' && inputs.confirm == 'PUBLISH'))
runs-on: ubuntu-latest
outputs:
channel: ${{ steps.release.outputs.channel }}
Expand Down Expand Up @@ -53,11 +58,18 @@ jobs:
- name: Install dependencies
run: bun install --frozen-lockfile

- name: Prepare immutable beta snapshot version
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'beta'
run: >-
bun run scripts/release/snapshot.ts apply
--run-id "${{ github.run_id }}"
--sha "${{ github.sha }}"

- name: Validate channel, branch, and package versions
id: release
run: >-
bun run scripts/release/channel.ts validate
--channel "${{ inputs.channel }}"
--channel "${{ github.event_name == 'push' && 'stable' || inputs.channel }}"
--ref "${{ github.ref_name }}"
--output "$GITHUB_OUTPUT"

Expand Down Expand Up @@ -94,6 +106,9 @@ jobs:
- name: Build publishable packages
run: |
bun install --frozen-lockfile
if [ "${{ needs.preflight.outputs.channel }}" = beta ]; then
bun run scripts/release/snapshot.ts apply --version "${{ needs.preflight.outputs.version }}"
fi
bun run build:packages

- name: Publish packages with npm Trusted Publishing
Expand Down
26 changes: 12 additions & 14 deletions docs/contributing/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,25 @@ Add a changeset whenever a user-visible package change is made:
bun run changeset
```

Choose the SemVer impact and describe the change for the generated changelog. Once merged, the **Release PR** workflow creates or updates a stable version PR on `main`. It never publishes npm packages.
Choose the SemVer impact and describe the change for the generated changelog. Once merged, the **Release PR** workflow creates or updates a stable version PR on `main`. Only merging that Release PR starts stable publishing, which still waits for approval on the `npm-release` Environment.

## Publish a beta

1. In GitHub, open **Actions → Prepare Beta Release → Run workflow**.
2. Keep the workflow branch set to `main`, enter the target stable series (for example `0.1.0`), and run it.
3. The workflow creates or updates `release/0.1.0-beta`, consumes the available changesets, and commits the next version such as `0.1.0-beta.0`.
4. Open **Actions → Publish npm → Run workflow**.
5. Select `release/0.1.0-beta` in the workflow branch dropdown, choose `beta`, type `PUBLISH`, and run it.
6. Approve the `npm-release` Environment deployment after reviewing the commit and job summary.
1. In GitHub, open **Actions → Publish npm → Run workflow**.
2. Keep the workflow branch set to `main`, choose `beta`, type `PUBLISH`, and run it.
3. Approve the `npm-release` Environment deployment after reviewing the commit, generated version, and job summary.

The publish workflow validates that the selected branch, package version, and channel agree. It publishes with the npm `beta` dist-tag and creates the immutable `v0.1.0-beta.N` tag. It then installs that exact version from the public npm registry on Linux, Windows, and macOS under Node.js 22 and 24. The GitHub prerelease is created only after all six consumer jobs pass.
The workflow derives an immutable version from the GitHub Actions run ID and current `main` commit without changing Git history, for example `0.0.0-beta.run-123456789.sha-a1b2c3d`. It publishes with the npm `beta` dist-tag and creates the matching immutable Git tag. It then installs that exact version from the public npm registry on Linux, Windows, and macOS under Node.js 22 and 24. The GitHub prerelease is created only after all six consumer jobs pass.

For another beta, merge fixes and their changesets into `main`, then rerun **Prepare Beta Release** for the same series. The workflow merges `main` into the beta branch and calculates the next beta. Never merge the beta branch back into `main`; delete it after the stable release.
For another beta, merge fixes into `main` and run **Publish npm** again. Every run gets a new Actions-run-and-commit-derived version; no beta branch is created and changesets are not consumed.

## Publish a stable release

1. Review and merge the automated `chore: release packages` PR.
2. Open **Actions → Publish npm → Run workflow**.
3. Select `main`, choose `stable`, type `PUBLISH`, and run it.
4. Approve the `npm-release` Environment deployment after reviewing the exact package version.
2. **Publish npm** starts automatically when the release commit reaches `main`.
3. Approve the `npm-release` Environment deployment after reviewing the exact package version.

If publishing fails partway through, manually rerun it from **Actions → Publish npm → Run workflow** with branch `main`, channel `stable`, and confirmation `PUBLISH`. Exact package versions already published are skipped.

Only a clean `X.Y.Z` version on `main` can publish to npm's `latest` tag. Publishing creates the matching immutable Git tag, waits for all three packages to become visible in the public registry, and runs the six-job consumer matrix. The GitHub Release is the final certification step and is created only after that matrix passes. Publishing is idempotent: packages whose exact version already exists are skipped, so a partially failed publish can be retried from the same commit.

Expand Down Expand Up @@ -93,7 +91,7 @@ npm install --global @openagentpack/cli
npm install --global @openagentpack/cli@beta

# Pin or test an exact version without a global install
npx @openagentpack/cli@0.1.0-beta.0 --version
npx @openagentpack/cli@0.0.0-beta.run-123456789.sha-a1b2c3d --version

# SDK
npm install @openagentpack/sdk
Expand All @@ -108,4 +106,4 @@ After installing the CLI, run `agents --help`. A beta user returns to stable wit
- If all packages published but a post-release consumer job fails, keep the immutable tag, do not unpublish or move the tag, and do not create the GitHub Release. Fix the compatibility issue and publish a new patch version; npm package versions cannot be overwritten.
- Registry visibility is retried for five minutes before it is classified as a release failure. Retry the same workflow only when npm propagation, rather than package compatibility, was the cause.
- If a version tag already points at another commit, stop. Tags are immutable; investigate the repository history instead of moving or deleting the tag.
- If **Prepare Beta Release** reports no unreleased changesets, add a changeset on `main` before preparing another beta.
- Beta publishing must be manually dispatched from `main`; the release identity check rejects other branches.
28 changes: 13 additions & 15 deletions docs/contributing/release.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,25 @@ OpenAgentPack 将 `@openagentpack/sdk`、`@openagentpack/playground` 和 `@opena
bun run changeset
```

选择 SemVer 影响范围并填写 changelog。PR 合并后,**Release PR** 工作流会在 `main` 上创建或更新稳定版版本 PR,但绝不会直接发布 npm
选择 SemVer 影响范围并填写 changelog。PR 合并后,**Release PR** 工作流会在 `main` 上创建或更新稳定版版本 PR。只有合并该 Release PR 才会自动启动正式发布,并且发布仍需 `npm-release` Environment 审批

## 发布 Beta

1. 打开 GitHub 的 **Actions → Prepare Beta Release → Run workflow**。
2. workflow branch 保持 `main`,输入目标稳定版系列,例如 `0.1.0`,然后运行。
3. 工作流会创建或更新 `release/0.1.0-beta`,消费现有 changeset,并提交下一个版本,例如 `0.1.0-beta.0`。
4. 打开 **Actions → Publish npm → Run workflow**。
5. 在 workflow branch 下拉框选择 `release/0.1.0-beta`,channel 选择 `beta`,输入 `PUBLISH`,然后运行。
6. 检查提交和 job 信息后,批准 `npm-release` Environment deployment。
1. 打开 GitHub 的 **Actions → Publish npm → Run workflow**。
2. workflow branch 保持 `main`,channel 选择 `beta`,输入 `PUBLISH`,然后运行。
3. 检查提交、自动生成的精确版本和 job 信息后,批准 `npm-release` Environment deployment。

发布工作流会同时校验分支、包版本和 channel。通过后使用 npm 的 `beta` dist-tag 发布,并创建不可变的 `v0.1.0-beta.N` Git tag。随后,它会在 Linux、Windows、macOS 的 Node.js 22 和 24 环境中,从公共 npm registry 安装该精确版本。只有六个消费者 job 全部通过,才创建 GitHub prerelease。
工作流根据 GitHub Actions run ID 和 `main` 当前提交生成不修改 Git 历史的版本,例如 `0.0.0-beta.run-123456789.sha-a1b2c3d`。通过后使用 npm 的 `beta` dist-tag 发布,并创建对应的不可变 Git tag。随后,它会在 Linux、Windows、macOS 的 Node.js 22 和 24 环境中,从公共 npm registry 安装该精确版本。只有六个消费者 job 全部通过,才创建 GitHub prerelease。

需要下一个 Beta 时,把修复及其 changeset 合并到 `main`,再对同一个版本系列运行 **Prepare Beta Release**。工作流会把 `main` 合入 Beta 分支并计算下一个 Beta。不要把 Beta 分支反向合并到 `main`;稳定版发布后删除它
需要下一个 Beta 时,把修复合并到 `main` 后再次运行 **Publish npm**。每次运行都会根据 Actions run ID 和提交生成新的不可变版本,不需要 Beta 分支,也不会消费 changeset

## 发布稳定版

1. 审核并合并自动生成的 `chore: release packages` PR。
2. 打开 **Actions → Publish npm → Run workflow**。
3. 选择 `main`,channel 选择 `stable`,输入 `PUBLISH`,然后运行。
4. 检查准确版本后,批准 `npm-release` Environment deployment。
2. 合并提交到达 `main` 后,**Publish npm** 会自动启动。
3. 检查准确版本后,批准 `npm-release` Environment deployment。

如果发布中途失败,可在 **Actions → Publish npm → Run workflow** 中选择 `main`、channel `stable`,输入 `PUBLISH` 手动重跑。已经成功发布的精确包版本会被跳过。

只有 `main` 上不含预发布后缀的 `X.Y.Z` 才能发布到 npm 的 `latest`。发布后会创建对应的不可变 Git tag,等待三个包在公共 registry 中全部可见,再执行六个消费者 job。正式 GitHub Release 是最后的发布认证,只有矩阵全部通过后才创建。发布过程可安全重试:已经存在的精确包版本会被跳过。

Expand Down Expand Up @@ -92,8 +90,8 @@ npm install --global @openagentpack/cli
# Beta CLI(npm beta)
npm install --global @openagentpack/cli@beta

# 固定或临时体验某个精确版本
npx @openagentpack/cli@0.1.0-beta.0 --version
# 固定或临时体验某个精确 Beta 版本
npx @openagentpack/cli@0.0.0-beta.run-123456789.sha-a1b2c3d --version

# SDK
npm install @openagentpack/sdk
Expand All @@ -108,4 +106,4 @@ npm install @openagentpack/sdk
- 所有包都已发布,但发布后消费者 job 失败:保留不可变 tag,不执行 unpublish、不移动 tag,也不创建 GitHub Release。修复兼容性问题后发布新的 patch 版本;npm 上的版本不能被覆盖。
- registry 可见性会重试五分钟,之后才判定 release 失败。只有确认失败原因是 npm 同步延迟而不是包兼容性时,才从同一个提交重试。
- 版本 tag 已指向其他提交:立即停止。tag 必须保持不可变,应排查历史,不能移动或删除 tag。
- **Prepare Beta Release** 提示没有未发布 changeset:先在 `main` 添加 changeset,再准备下一个 Beta
- Beta 发布必须从 `main` 手动触发;其他分支会被发布身份检查拒绝
5 changes: 3 additions & 2 deletions scripts/open-source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ describe("open-source repository invariants", () => {
expect(unpinned).toEqual([]);
});

test("npm publishing is manual, approval-gated, and cannot be cancelled mid-publish", () => {
test("npm publishing auto-starts stable releases, remains approval-gated, and cannot be cancelled", () => {
const workflow = readFileSync(resolve(root, ".github/workflows/release.yml"), "utf8");
expect(workflow).not.toMatch(/\npush:/);
expect(workflow).toMatch(/\n {2}push:\n {4}branches: \[main\]/);
expect(workflow).toContain("contains(github.event.head_commit.message, 'chore: release packages')");
expect(workflow).toContain("environment: npm-release");
expect(workflow).toContain("vars.NPM_RELEASE_ENABLED == 'true'");
expect(workflow).toContain("inputs.confirm == 'PUBLISH'");
Expand Down
13 changes: 6 additions & 7 deletions scripts/release/channel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ describe("release channel guard", () => {
expect(() => validateReleaseIdentity("stable", "main", "1.2.3-beta.0")).toThrow("X.Y.Z");
});

test("binds beta versions to their isolated release branch", () => {
expect(validateReleaseIdentity("beta", "release/1.2.3-beta", "1.2.3-beta.4", "1.2.3")).toEqual({
test("accepts deterministic beta snapshots only on main", () => {
expect(validateReleaseIdentity("beta", "main", "0.0.0-beta.run-123456789.sha-a1b2c3d")).toEqual({
channel: "beta",
version: "1.2.3-beta.4",
version: "0.0.0-beta.run-123456789.sha-a1b2c3d",
distTag: "beta",
});
expect(() => validateReleaseIdentity("beta", "main", "1.2.3-beta.0")).toThrow("release/X.Y.Z-beta");
expect(() => validateReleaseIdentity("beta", "release/1.2.3-beta", "1.2.4-beta.0")).toThrow("1.2.3-beta.N");
expect(() => validateReleaseIdentity("beta", "release/1.2.3-beta", "1.2.3-beta.0", "2.0.0")).toThrow(
"does not match",
expect(() => validateReleaseIdentity("beta", "feature/test", "0.0.0-beta.run-123456789.sha-a1b2c3d")).toThrow(
"main",
);
expect(() => validateReleaseIdentity("beta", "main", "1.2.3-beta.0")).toThrow("unexpected format");
});

test("requires all fixed-group package versions to match", () => {
Expand Down
Loading