diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2012c430..d4e54523 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -94,6 +94,8 @@ jobs: # Bucket / prefix / allowlists live in the FC function. FC_TRIGGER_URL: ${{ vars.FC_TRIGGER_URL }} FC_RELEASE_AUDIENCE: ${{ vars.FC_RELEASE_AUDIENCE }} + # Script order: git tag, then binary GitHub Release + OSS, then npm. + # OSS upload can fail on a network blip; npm must not succeed first. run: node tools/release/publish-stable.mjs ${{ inputs.package == 'knowledge-studio-cli' && '--knowledge' || '' }} publish-channel: @@ -162,6 +164,8 @@ jobs: # credentials in this repo (see publish-stable env notes). FC_TRIGGER_URL: ${{ vars.FC_TRIGGER_URL }} FC_RELEASE_AUDIENCE: ${{ vars.FC_RELEASE_AUDIENCE }} + # Script order: binary GitHub Release + OSS, then npm. + # OSS upload can fail on a network blip; npm must not succeed first. run: node tools/release/publish-channel.mjs ${{ inputs.package == 'knowledge-studio-cli' && '--knowledge' || '' }} --channel "${{ inputs.channel }}" # bailian-kb-dsh is the dsh plugin (downstream host adapter): independent version, diff --git a/docs/agents/publish.md b/docs/agents/publish.md index f53e63e2..a57fc121 100644 --- a/docs/agents/publish.md +++ b/docs/agents/publish.md @@ -15,13 +15,14 @@ ```text publish-stable.mjs / publish-channel.mjs ← 唯一发版入口 - ├─ npm(pnpm publish) - └─ binary(lib/binary-release - → binary-build - → gh-release - → oss-direct-upload → FC release 通道) + ├─ binary 先走(lib/binary-release + │ → binary-build → gh-release + │ → oss-direct-upload → FC release 通道) + └─ OSS 成功后再 npm(pnpm publish) ``` +bailian-cli 的二进制(GitHub Release + OSS)在 npm 之前。OSS 上传受网络波动可能失败;若 npm 先成功,会出现 latest 已发出但 CDN 没有对应二进制。stable 仍要先推 `v` tag,因为 GitHub Release 首次创建带 `--verify-tag`。`knowledge-studio-cli` / `--skip-binary` 没有二进制,仍只发 npm。 + `tools/release/lib/binary-release.mjs` 等是实现,一般不要单独当发版入口(调试可用)。 ### OSS 通道:FC 预签名上传(仓库不持有任何 OSS 凭据) @@ -73,7 +74,7 @@ workflow 的 `channel` 输入**只决定 npm dist-tag**(如 `mcp` / `plugin` / 1. 在 GitHub 触发 Publish workflow,mode 选 `channel`,channel 填 npm dist-tag 名: - **`bailian-cli`**:npm 发到该 tag;二进制同时刷新 CDN `sync-release.json`(与 tag 名无关)。本机验证:`BAILIAN_CHANNEL=sync-release`。**先发二进制(zip+tar.gz 上齐)再发静态仓 `install.sh`**,避免新脚本去拉还不存在的 `.tar.gz`。 - **`knowledge-studio-cli`**:仅 npm(自动跳过 binary,不碰 `sync-release.json`) -2. CI 自动:生成 `0.0.0-beta--`(UTC 到分钟;同 commit 同分钟重跑会覆盖同号)→ 临时 bump → 自检 → **npm 发到 dist-tag** →(bailian-cli)**Bun 编二进制 + GH prerelease + 覆盖 `sync-release.json`** → 还原 package.json +2. CI 自动:生成 `0.0.0-beta--`(UTC 到分钟;同 commit 同分钟重跑会覆盖同号)→ 临时 bump → 自检 →(bailian-cli)**Bun 编二进制 + GH prerelease + 覆盖 `sync-release.json`** → **npm 发到 dist-tag** → 还原 package.json 3. 对应脚本:`tools/release/publish-channel.mjs` ### stable 发布 @@ -81,7 +82,7 @@ workflow 的 `channel` 输入**只决定 npm dist-tag**(如 `mcp` / `plugin` / 1. 确保当前 release tooling 覆盖的包(`tools/release/lib/packages.mjs`)已升到目标版本且一致;当前基础集合为 `packages/core` / `packages/runtime` / `packages/commands` / `packages/cli`,`knowledge-studio-cli` 发布会额外包含 `packages/kscli` 2. 在 GitHub 触发 Publish workflow,package 选目标包集合,mode 选 `stable` 3. 需要 production environment 审批人批准 -4. CI 自动:自检 → **npm 发到 latest** → **推送 git tag `v`** → **Bun 编二进制并创建/更新 GitHub Release**(每平台 `.zip`,darwin/linux 额外 `.tar.gz`)→(bailian-cli)维护 CDN **`manifest.json`**(unix 资产含 `tar` / `tarSha256`,`file` 仍为 zip)→ 完成 +4. CI 自动:自检 → **推送 git tag `v`** → **Bun 编二进制并创建/更新 GitHub Release**(每平台 `.zip`,darwin/linux 额外 `.tar.gz`)→(bailian-cli)维护 CDN **`manifest.json`**(unix 资产含 `tar` / `tarSha256`,`file` 仍为 zip)→ **npm 发到 latest** → 完成 5. 如果所选发布集合的当前版本已全部存在于 npm,stable 发布会失败并提示先升级版本号;如果只有部分包已发布,CI 会继续补发缺失包 6. 对应脚本:`tools/release/publish-stable.mjs` @@ -162,4 +163,5 @@ node tools/release/publish-channel.mjs --channel test --knowledge --dry-run | stable 发布前没有升级版本号 | 所选发布集合的版本已全部存在于 npm,CI 明确报错并要求先升级版本号 | | channel job 缺少 `contents: write` | `gh release create` 失败 | | darwin 二进制编完后没有 ad-hoc 重签 | Apple Silicon / macOS 27 对失效的 linker 签名直接 SIGKILL(`Killed: 9`) | +| npm 先于二进制 / OSS 成功 | latest 已发出,CDN 上没有对应 zip/tar.gz;OSS 网络失败时无法回滚 npm | | stable 未先推 tag 就建 Release | `--verify-tag` 失败 | diff --git a/tools/release/publish-channel.mjs b/tools/release/publish-channel.mjs index 6f2963fa..116ee6bb 100644 --- a/tools/release/publish-channel.mjs +++ b/tools/release/publish-channel.mjs @@ -80,19 +80,8 @@ try { published.set(pkg.key, exists); log(`${pkg.name}@${betaVersion}: ${exists ? "already published" : "to publish"}`); } - if (packages.every((pkg) => published.get(pkg.key))) { - log("\nall packages already published; nothing to do for npm."); - } else { - // 1) npm (dependency order: core → runtime → commands → cli [→ kscli]) - for (const pkg of packages) { - if (published.get(pkg.key)) continue; - step(`publish ${pkg.name}@${betaVersion} (tag=${channel}, provenance)`); - pnpmPublish(pkg, { tag: channel, provenance: true, dryRun }); - } - } - - // 2) binary GitHub Release — must run before finally restores package.json versions. - // Channel binary always refreshes OSS sync-release.json (npm tag is independent). + // 1) binary GitHub Release before npm, and before finally restores package.json. + // OSS sync-release.json can fail on a network blip; npm must not land first. if (skipBinary) { const reason = knowledge ? "[knowledge] skipping binary (npm-only; does not touch sync-release.json)" @@ -105,6 +94,17 @@ try { await releaseBinaryArtifacts({ mode: "channel", channel, dryRun }); } + // 2) npm (dependency order: core → runtime → commands → cli [→ kscli]) + if (packages.every((pkg) => published.get(pkg.key))) { + log("\nall packages already published; nothing to do for npm."); + } else { + for (const pkg of packages) { + if (published.get(pkg.key)) continue; + step(`publish ${pkg.name}@${betaVersion} (tag=${channel}, provenance)`); + pnpmPublish(pkg, { tag: channel, provenance: true, dryRun }); + } + } + const parts = ["npm"]; if (!skipBinary) parts.push("binary/sync-release"); log(`\nchannel release complete: ${channel}@${betaVersion} (${parts.join(" + ")})`); diff --git a/tools/release/publish-stable.mjs b/tools/release/publish-stable.mjs index 66e85445..626b6cc9 100644 --- a/tools/release/publish-stable.mjs +++ b/tools/release/publish-stable.mjs @@ -62,14 +62,7 @@ try { ); } - // 1) npm (dependency order: core → runtime → commands → cli [→ kscli]) - for (const pkg of packages) { - if (published.get(pkg.key)) continue; - step(`publish ${pkg.name}@${version} (tag=latest, provenance)`); - pnpmPublish(pkg, { tag: "latest", provenance: true, dryRun }); - } - - // 2) git tag — must be on origin before the GitHub Release step (--verify-tag) + // 1) git tag — must be on origin before the GitHub Release step (--verify-tag) const tag = `v${version}`; if (dryRun) { log("\n[dry-run] skipping git tag"); @@ -81,7 +74,8 @@ try { pushTag(tag); } - // 3) binary GitHub Release (same version; orchestrated here, not a separate release entry) + // 2) binary GitHub Release + OSS before npm. The OSS upload can fail on a + // network blip; publishing npm first would leave latest without binaries. if (skipBinary) { log("\n[skip-binary] skipping binary GitHub Release"); } else { @@ -89,6 +83,13 @@ try { await releaseBinaryArtifacts({ mode: "stable", dryRun }); } + // 3) npm (dependency order: core → runtime → commands → cli [→ kscli]) + for (const pkg of packages) { + if (published.get(pkg.key)) continue; + step(`publish ${pkg.name}@${version} (tag=latest, provenance)`); + pnpmPublish(pkg, { tag: "latest", provenance: true, dryRun }); + } + const parts = ["npm"]; if (!skipBinary) parts.push("binary"); log(`\nstable release complete (${parts.join(" + ")}).`);