From 525412f566119abfff167e2382570e06ccb32f41 Mon Sep 17 00:00:00 2001 From: rendianmeng Date: Fri, 31 Jul 2026 18:53:37 +0800 Subject: [PATCH] feat: win bl update exe file test --- .github/workflows/publish.yml | 4 +- INSTALL.md | 8 ++- docs/agents/publish.md | 28 +++++++---- packages/core/src/install/cdn.ts | 9 +++- tools/release/lib/binary-build.mjs | 22 +++++---- tools/release/lib/binary-options.mjs | 50 +++++++++++++++++-- tools/release/lib/binary-options.test.mjs | 40 +++++++++++++++ tools/release/lib/binary-release.mjs | 60 +++++++++++++---------- tools/release/lib/oss-direct-upload.mjs | 7 ++- tools/release/publish-channel.mjs | 16 ++++-- 10 files changed, 177 insertions(+), 67 deletions(-) create mode 100644 tools/release/lib/binary-options.test.mjs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index df2f559..844f8b0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ on: - channel - stable channel: - description: "Required when mode=channel. dist-tag / rolling manifest name (lowercase, digits, dashes). bailian-cli binary install verify: sync-release; knowledge-studio-cli / other tags: mcp, plugin, etc." + description: "Required when mode=channel. npm dist-tag only (lowercase, digits, dashes), e.g. mcp / plugin / sync-release. bailian-cli binary CDN always overwrites sync-release.json; knowledge-studio-cli is npm-only." required: false type: string @@ -93,7 +93,7 @@ jobs: - name: Require channel input if: ${{ inputs.channel == '' }} run: | - echo "::error::mode=channel requires the workflow input \"channel\" (bailian-cli install verify: sync-release; knowledge-studio-cli / others: e.g. mcp, plugin). Leave mode=stable if you do not need a dist-tag." + echo "::error::mode=channel requires the workflow input \"channel\" (npm dist-tag, e.g. mcp / plugin / sync-release). Leave mode=stable if you do not need a dist-tag." exit 1 - uses: actions/checkout@v6 diff --git a/INSTALL.md b/INSTALL.md index 4f07a8c..991db4c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -8,7 +8,7 @@ curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash ``` -可选:正式安装读 CDN 上的 `manifest.json`。预发 / channel 验证示例: +可选:正式安装读 CDN 上的 `manifest.json`。预发 / channel 验证一律读 `sync-release.json`(bailian-cli 的 channel 发版都会覆盖它): ```bash curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash -s -- --channel sync-release @@ -19,10 +19,14 @@ curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash -s -- --channel sync Windows PowerShell: ```powershell +# 正式(manifest.json) irm https://bailian.aliyun.com/cli/install.ps1 | iex + +# channel / 预发验证(sync-release.json) +$env:BAILIAN_CHANNEL = 'sync-release'; irm 'https://bailian.aliyun.com/cli/install.ps1' | iex ``` -带参时先落盘再执行(`irm | iex` 不便传参),或使用仓外静态资源文档中的预发入口。 +带其它参时先落盘再执行(`irm | iex` 不便传参),或使用仓外静态资源文档中的预发入口。 二进制安装布局为 `versions//` + `current` 指针;`bl update` 只切换指针并清理旧版本(保留当前与上一版)。更新进程退出后,下次执行 `bl` 即使用新版本(无需「重启应用」)。 diff --git a/docs/agents/publish.md b/docs/agents/publish.md index f476d1e..f29467b 100644 --- a/docs/agents/publish.md +++ b/docs/agents/publish.md @@ -26,20 +26,28 @@ publish-stable.mjs / publish-channel.mjs ← 唯一发版入口 两种模式: -| 模式 | 用途 | 触发方式 | -| ------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| channel | npm dist-tag + GitHub prerelease + 滚动 `channel-` | 选 mode=channel,填 channel 名。**仅 `bailian-cli` 二进制安装验证**用 `sync-release`;`knowledge-studio-cli` 与 mcp/plugin 等仍按各自 dist-tag 名填写 | -| stable | npm latest + GitHub Release `v`(含 install 脚本) | 选 mode=stable,需 production environment 审批 | +| 模式 | 用途 | 触发方式 | +| ------- | --------------------------------------------------------------------------------------- | -------------------------------------------- | +| channel | npm dist-tag +(仅 bailian-cli)二进制 + CDN **一律**覆盖 `sync-release.json` | mode=channel,channel 填 **npm dist-tag** 名 | +| stable | npm latest + GitHub Release `v` + CDN **`manifest.json`**(及 `latest.json` 别名) | mode=stable,需 production environment 审批 | 可选 flag:`--skip-binary`(仅发 npm,紧急逃生)。 +### CDN 滚动指针(bailian-cli) + +| 发布模式 | CDN 指针 | 本机安装 / 更新 | +| -------- | ---------------------------------- | ----------------------------------------------------------------- | +| channel | 始终覆盖 `sync-release.json` | `BAILIAN_CHANNEL=sync-release` / `install --channel sync-release` | +| stable | `manifest.json`(+ `latest.json`) | 默认安装 / `bl update`(无 channel) | + +workflow 的 `channel` 输入**只决定 npm dist-tag**(如 `mcp` / `plugin` / `sync-release`),**不再**生成 `release-test.json` 这类旁路文件。 + ### channel 发布 -1. 在 GitHub 触发 Publish workflow,package 选目标包,mode 选 `channel`,channel 填名称: - - **`bailian-cli` 二进制安装验证**:填 `sync-release`(更新 CDN `sync-release.json`;本机 `BAILIAN_CHANNEL=sync-release` / `install.sh --channel sync-release`)。填什么就写什么 `{channel}.json`;发 `release-test` **不会**更新 `sync-release.json` - - **`knowledge-studio-cli`**:不要用 `sync-release`(那是 `bl` 安装脚本/CDN 约定);按 npm dist-tag 需要填 `mcp` / `plugin` 等即可,行为与改文档前一致 - - 其它 npm dist-tag(任一 package):填 `mcp` / `plugin` 等 -2. CI 自动:生成 `0.0.0-beta--` → 临时 bump → 自检 → **npm 发到 dist-tag** → **Bun 编二进制并创建 GitHub prerelease + 滚动 `{channel}.json`** → 还原 package.json +1. 在 GitHub 触发 Publish workflow,mode 选 `channel`,channel 填 npm dist-tag 名: + - **`bailian-cli`**:npm 发到该 tag;二进制同时刷新 CDN `sync-release.json`(与 tag 名无关)。本机验证:`BAILIAN_CHANNEL=sync-release` + - **`knowledge-studio-cli`**:仅 npm(自动跳过 binary,不碰 `sync-release.json`) +2. CI 自动:生成 `0.0.0-beta--` → 临时 bump → 自检 → **npm 发到 dist-tag** →(bailian-cli)**Bun 编二进制 + GH prerelease + 覆盖 `sync-release.json`** → 还原 package.json 3. 对应脚本:`tools/release/publish-channel.mjs` ### stable 发布 @@ -47,7 +55,7 @@ publish-stable.mjs / publish-channel.mjs ← 唯一发版入口 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** → 完成 +4. CI 自动:自检 → **npm 发到 latest** → **推送 git tag `v`** → **Bun 编二进制并创建/更新 GitHub Release** →(bailian-cli)维护 CDN **`manifest.json`** → 完成 5. 如果所选发布集合的当前版本已全部存在于 npm,stable 发布会失败并提示先升级版本号;如果只有部分包已发布,CI 会继续补发缺失包 6. 对应脚本:`tools/release/publish-stable.mjs` diff --git a/packages/core/src/install/cdn.ts b/packages/core/src/install/cdn.ts index 7cfc4c6..491b168 100644 --- a/packages/core/src/install/cdn.ts +++ b/packages/core/src/install/cdn.ts @@ -7,7 +7,11 @@ * v/.zip —— immutable per-version binaries + SHA256SUMS * manifest.json —— stable install/update pointer (rolling-manifest shape) * latest.json —— stable alias; same body as manifest.json - * .json —— per-channel rolling manifests (betas / local verify) + * sync-release.json —— official channel/verify rolling pointer (all bailian-cli + * channel publishes overwrite this; npm dist-tag is separate) + * + * Legacy `{name}.json` files may still exist on CDN; install may resolve them, but + * release tooling no longer creates per-dist-tag manifests. * * Override with `BAILIAN_CLI_CDN`. */ @@ -29,7 +33,8 @@ export function getCliCdnBase(): string { /** * Rolling manifest URL at the CDN base root. * Stable (`latest` / `stable` / empty) → `manifest.json`. - * Named channel → `{channel}.json` (e.g. sync-release for local verify). + * Official verify line → `sync-release.json` (`channel=sync-release`). + * Other names still map to `{channel}.json` for backward compatibility only. * All share the same rolling-manifest shape from binary-build. */ export function channelManifestUrl(channel = "latest"): string { diff --git a/tools/release/lib/binary-build.mjs b/tools/release/lib/binary-build.mjs index d92b545..ea0108c 100644 --- a/tools/release/lib/binary-build.mjs +++ b/tools/release/lib/binary-build.mjs @@ -7,9 +7,8 @@ * node tools/release/lib/binary-build.mjs --mode stable --host * * Manifests: - * --mode stable → writes latest.json (rolling manifest of the implicit - * "latest" channel; OSS prefix root only, not a GH asset) - * --mode channel → writes .json + * --mode stable → writes latest.json (fed into OSS manifest.json + latest.json) + * --mode channel → always writes sync-release.json (npm --channel is dist-tag only) */ import { chmodSync, mkdirSync, writeFileSync } from "node:fs"; import { join, resolve } from "node:path"; @@ -17,7 +16,11 @@ import { fileURLToPath } from "node:url"; import { spawnSync } from "node:child_process"; import { parseArgs } from "node:util"; import { ROOT, readPackageJson, PACKAGES } from "./packages.mjs"; -import { channelManifestFileName, normalizeModeChannel } from "./binary-options.mjs"; +import { + normalizeModeChannel, + rollingManifestChannelId, + rollingManifestFileName, +} from "./binary-options.mjs"; import { ensureZip, zipOne } from "./binary-zip.mjs"; const BINARY_COMPILE = fileURLToPath(new URL("./binary-compile.mjs", import.meta.url)); @@ -157,8 +160,9 @@ function writeChecksums(outdir, artifacts) { writeFileSync(join(outdir, "SHA256SUMS"), `${lines.join("\n")}\n`); } -/** Write the rolling channel manifest (`latest.json` / `.json`) with per-platform zip file + sha256. */ -function writeChannelManifest(outdir, version, artifacts, channel) { +/** Write the rolling channel manifest (`latest.json` / `sync-release.json`) with per-platform zip + sha256. */ +function writeChannelManifest(outdir, version, artifacts, mode) { + const channel = rollingManifestChannelId(mode); const assets = Object.fromEntries( artifacts.map((item) => [ `${item.os}-${item.arch}`, @@ -176,7 +180,7 @@ function writeChannelManifest(outdir, version, artifacts, channel) { releasedAt: new Date().toISOString(), assets, }; - const name = channelManifestFileName(channel); + const name = rollingManifestFileName(mode); writeJson(join(outdir, name), manifest); return name; } @@ -222,9 +226,7 @@ export function buildBinaryArtifacts(rawOptions = {}) { writeChecksums(outdir, artifacts); const extras = ["SHA256SUMS"]; - extras.push( - writeChannelManifest(outdir, version, artifacts, mode === "channel" ? channel : "latest"), - ); + extras.push(writeChannelManifest(outdir, version, artifacts, mode)); log(`\nBuilt ${artifacts.length} zip(s):`); for (const item of artifacts) { diff --git a/tools/release/lib/binary-options.mjs b/tools/release/lib/binary-options.mjs index 184a554..ec0ff63 100644 --- a/tools/release/lib/binary-options.mjs +++ b/tools/release/lib/binary-options.mjs @@ -1,13 +1,21 @@ /** * Shared mode / channel / manifest naming for binary-build and binary-release. * - * Every mode writes a rolling channel manifest: stable writes `latest.json` - * (uploaded to the OSS prefix root only, never attached to the GitHub Release), - * channel writes `.json` (attached to the rolling `channel-` - * GitHub Release and uploaded to the OSS prefix root). + * Rolling CDN pointers (bailian-cli binary): + * - stable → build writes `latest.json`; OSS maintains `manifest.json` (+ `latest.json`) + * - channel → always `sync-release.json` on OSS / GH rolling release `channel-sync-release` + * + * The workflow `--channel` value remains the npm dist-tag (and versioned GH release notes). + * It does not choose the CDN rolling filename. */ import { assertChannel } from "./validate.mjs"; +/** Official bailian-cli channel/verify rolling manifest name on CDN. */ +export const SYNC_RELEASE_CHANNEL = "sync-release"; + +/** Stable build-local rolling manifest (fed into maintainReleaseManifest). */ +export const STABLE_ROLLING_CHANNEL = "latest"; + /** * @param {string} mode * @param {string | null | undefined} channel @@ -28,8 +36,40 @@ export function normalizeModeChannel(mode = "stable", channel = null) { return { mode: "stable", channel: null }; } -/** Rolling-manifest basename: `latest.json` for stable, `.json` otherwise. */ +/** + * Rolling-manifest basename for a logical channel id inside a build artifact. + * Prefer {@link rollingManifestFileName} for mode-aware naming. + */ export function channelManifestFileName(channel) { if (!channel) throw new Error("channelManifestFileName requires a channel name"); return `${channel}.json`; } + +/** + * Rolling manifest basename written by binary-build / uploaded to OSS root. + * - stable → `latest.json` + * - channel → always `sync-release.json` (ignores npm dist-tag name) + * + * @param {"stable" | "channel"} mode + * @returns {string} + */ +export function rollingManifestFileName(mode) { + if (mode === "stable") return channelManifestFileName(STABLE_ROLLING_CHANNEL); + if (mode === "channel") return channelManifestFileName(SYNC_RELEASE_CHANNEL); + throw new Error(`rollingManifestFileName: unknown mode ${mode}`); +} + +/** Channel id embedded in the rolling manifest JSON body. */ +export function rollingManifestChannelId(mode) { + if (mode === "stable") return STABLE_ROLLING_CHANNEL; + if (mode === "channel") return SYNC_RELEASE_CHANNEL; + throw new Error(`rollingManifestChannelId: unknown mode ${mode}`); +} + +/** GitHub rolling prerelease tag that holds only the CDN channel pointer. */ +export function rollingChannelReleaseTag(mode) { + if (mode !== "channel") { + throw new Error("rollingChannelReleaseTag is only valid for mode=channel"); + } + return `channel-${SYNC_RELEASE_CHANNEL}`; +} diff --git a/tools/release/lib/binary-options.test.mjs b/tools/release/lib/binary-options.test.mjs new file mode 100644 index 0000000..9c75a26 --- /dev/null +++ b/tools/release/lib/binary-options.test.mjs @@ -0,0 +1,40 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { + channelManifestFileName, + normalizeModeChannel, + rollingChannelReleaseTag, + rollingManifestChannelId, + rollingManifestFileName, + STABLE_ROLLING_CHANNEL, + SYNC_RELEASE_CHANNEL, +} from "./binary-options.mjs"; + +describe("rolling CDN manifest naming", () => { + it("keeps sync-release and latest constants", () => { + assert.equal(SYNC_RELEASE_CHANNEL, "sync-release"); + assert.equal(STABLE_ROLLING_CHANNEL, "latest"); + }); + + it("channel mode always rolls sync-release.json regardless of npm dist-tag", () => { + const { mode, channel } = normalizeModeChannel("channel", "mcp"); + assert.equal(mode, "channel"); + assert.equal(channel, "mcp"); + assert.equal(rollingManifestFileName(mode), "sync-release.json"); + assert.equal(rollingManifestChannelId(mode), "sync-release"); + assert.equal(rollingChannelReleaseTag(mode), "channel-sync-release"); + }); + + it("stable mode rolls latest.json for maintainReleaseManifest input", () => { + const { mode, channel } = normalizeModeChannel("stable", null); + assert.equal(mode, "stable"); + assert.equal(channel, null); + assert.equal(rollingManifestFileName(mode), "latest.json"); + assert.equal(rollingManifestChannelId(mode), "latest"); + }); + + it("channelManifestFileName still formats arbitrary names for helpers", () => { + assert.equal(channelManifestFileName("release-test"), "release-test.json"); + assert.equal(channelManifestFileName(SYNC_RELEASE_CHANNEL), "sync-release.json"); + }); +}); diff --git a/tools/release/lib/binary-release.mjs b/tools/release/lib/binary-release.mjs index 7143db8..0a8e0b8 100644 --- a/tools/release/lib/binary-release.mjs +++ b/tools/release/lib/binary-release.mjs @@ -2,18 +2,19 @@ * Publish bailian-cli binary assets to GitHub Releases. * * stable: release `v` (tag must already be on origin; --verify-tag) - * assets: bl-*.zip, SHA256SUMS (no latest.json) + * assets: bl-*.zip, SHA256SUMS (no latest.json on the GH release) + * OSS: rewrite release/manifest.json + latest.json from build latest.json * channel: versioned prerelease `v` (assets: bl-*.zip, SHA256SUMS) - * + rolling prerelease tag `channel-` holding only `.json` + * + rolling prerelease `channel-sync-release` holding only sync-release.json + * OSS: always overwrite prefix-root sync-release.json * - * Same commit/day channel publishes share one `v` Release (identical - * binaries); only the rolling `channel-` manifest differs per channel. + * Workflow `--channel` is the npm dist-tag (and versioned release notes); it does + * not choose the CDN rolling filename. Same commit/day channel publishes share one + * `v` Release (identical binaries). * * Re-runs are idempotent via `gh release upload --clobber` (see gh-release.mjs). * After the GitHub upload the same assets are pushed straight to OSS from the - * runner, HEAD-reconciled, and (stable only) release/manifest.json + latest.json - * are rewritten with the SAME rolling-manifest body as channel `.json` — - * all in-process, no external FC (see oss-direct-upload.mjs). + * runner and HEAD-reconciled — all in-process, no external FC (see oss-direct-upload.mjs). * * Called by publish-stable.mjs / publish-channel.mjs. * Debug: @@ -26,7 +27,12 @@ import { fileURLToPath } from "node:url"; import { parseArgs as parseCliArgs } from "node:util"; import { ROOT, readPackageJson, PACKAGES } from "./packages.mjs"; import { buildBinaryArtifacts, matrixAssetNames } from "./binary-build.mjs"; -import { channelManifestFileName, normalizeModeChannel } from "./binary-options.mjs"; +import { + normalizeModeChannel, + rollingChannelReleaseTag, + rollingManifestFileName, + SYNC_RELEASE_CHANNEL, +} from "./binary-options.mjs"; import { ensureGh, GITHUB_REPOSITORY, upsertRelease } from "./gh-release.mjs"; import { maintainReleaseManifest, @@ -79,22 +85,23 @@ function uploadStable({ dir, version, files, dryRun }) { } function uploadChannel({ dir, version, channel, files, dryRun }) { - // Versioned tag is shared across channels built from the same beta version string. + // Versioned tag is shared across npm dist-tags built from the same beta version. upsertRelease({ tag: `v${version}`, title: `v${version}`, prerelease: true, - notes: `Beta build for the \`${channel}\` channel.`, + notes: `Beta build (npm dist-tag \`${channel}\`). CDN rolling pointer: ${SYNC_RELEASE_CHANNEL}.json.`, assets: versionBinaryAssets(dir, version, files), dryRun, }); + const rollingTag = rollingChannelReleaseTag("channel"); upsertRelease({ - tag: `channel-${channel}`, - title: `channel: ${channel}`, + tag: rollingTag, + title: `channel: ${SYNC_RELEASE_CHANNEL}`, prerelease: true, - notes: `Rolling manifest for the \`${channel}\` channel. Latest beta: ${version}.`, - assets: [join(dir, channelManifestFileName(channel))], + notes: `Rolling CDN manifest (${SYNC_RELEASE_CHANNEL}.json). Latest beta: ${version} (npm dist-tag \`${channel}\`).`, + assets: [join(dir, rollingManifestFileName("channel"))], dryRun, }); } @@ -117,16 +124,16 @@ function planDryRunWithoutArtifacts({ version, mode, channel }) { tag: `v${version}`, title: `v${version}`, prerelease: true, - notes: `Beta build for the \`${channel}\` channel.`, + notes: `Beta build (npm dist-tag \`${channel}\`). CDN rolling pointer: ${SYNC_RELEASE_CHANNEL}.json.`, assets: [...matrix, "SHA256SUMS"], dryRun: true, }); upsertRelease({ - tag: `channel-${channel}`, - title: `channel: ${channel}`, + tag: rollingChannelReleaseTag("channel"), + title: `channel: ${SYNC_RELEASE_CHANNEL}`, prerelease: true, - notes: `Rolling manifest for the \`${channel}\` channel. Latest beta: ${version}.`, - assets: [channelManifestFileName(channel)], + notes: `Rolling CDN manifest (${SYNC_RELEASE_CHANNEL}.json). Latest beta: ${version} (npm dist-tag \`${channel}\`).`, + assets: [rollingManifestFileName("channel")], dryRun: true, }); } @@ -136,15 +143,14 @@ function planDryRunWithoutArtifacts({ version, mode, channel }) { * upload. `files == null` means dry-run planning without artifacts on disk, * so bare basenames stand in for real paths. */ -function ossMirrorPlans({ dir, version, mode, channel, files }) { +function ossMirrorPlans({ dir, version, mode, files }) { const paths = files ? versionBinaryAssets(dir, version, files) : [...matrixAssetNames(version), "SHA256SUMS"]; const plans = [{ tag: `v${version}`, paths }]; if (mode === "channel") { - const manifest = channelManifestFileName(channel); - // Rolling channel manifest lives at the OSS prefix root, next to - // manifest.json / latest.json — one flat json per channel. + const manifest = rollingManifestFileName("channel"); + // Always sync-release.json at the OSS prefix root (next to manifest.json). plans.push({ tag: "", paths: [files ? join(dir, manifest) : manifest] }); } return plans; @@ -183,7 +189,7 @@ export async function releaseBinaryArtifacts(rawOptions = {}) { if (dryRun && !existsSync(dir)) { process.stdout.write(`[dry-run] ${dir} missing; planning expected assets\n`); planDryRunWithoutArtifacts({ version, mode, channel }); - const plans = ossMirrorPlans({ dir, version, mode, channel, files: null }); + const plans = ossMirrorPlans({ dir, version, mode, files: null }); await syncStaticFilesToOss({ filePaths: [join(ROOT, "CHANGELOG.md"), join(ROOT, "CHANGELOG.zh.md")], dryRun: true, @@ -209,10 +215,10 @@ export async function releaseBinaryArtifacts(rawOptions = {}) { if (!files.includes("SHA256SUMS")) { throw new Error(`Missing SHA256SUMS in ${dir}`); } - const rollingManifest = channelManifestFileName(mode === "channel" ? channel : "latest"); + const rollingManifest = rollingManifestFileName(mode); if (!files.includes(rollingManifest)) { throw new Error( - `Missing ${rollingManifest} in ${dir}. Rebuild with matching --mode/--channel (found: ${files.join(", ") || "(empty)"}).`, + `Missing ${rollingManifest} in ${dir}. Rebuild with matching --mode (found: ${files.join(", ") || "(empty)"}).`, ); } @@ -242,7 +248,7 @@ export async function releaseBinaryArtifacts(rawOptions = {}) { // Push the exact Release assets straight to OSS from the runner, then // HEAD-reconcile. Stable releases additionally maintain release/manifest.json // (newer-version guard). Throws on failure — CI is the only OSS writer. - const plans = ossMirrorPlans({ dir, version, mode, channel, files }); + const plans = ossMirrorPlans({ dir, version, mode, files }); const mirror = await mirrorReleaseAssetsToOss({ plans, dryRun }); if (mode === "stable" && !mirror.skipped) { await maintainReleaseManifest({ diff --git a/tools/release/lib/oss-direct-upload.mjs b/tools/release/lib/oss-direct-upload.mjs index ae142d0..6e3caf9 100644 --- a/tools/release/lib/oss-direct-upload.mjs +++ b/tools/release/lib/oss-direct-upload.mjs @@ -4,15 +4,14 @@ * No external FC is involved anymore; CI is the single writer. * * Flow: - * - Every mode uploads its assets to `//`; rolling - * channel manifests (`.json`) go to the prefix root (empty tag). + * - Every mode uploads its assets to `//`; channel + * mode also uploads `sync-release.json` to the prefix root (empty tag). * - After upload, every object is HEAD-verified against the local byte size * (reconciliation — the runner has the ground-truth artifacts on disk). * - Stable only: when the tag is a NEWER version than the current manifest * (compareVersions), rewrite `/manifest.json` and the rolling * `/latest.json` — both carry the SAME rolling-manifest body - * written by binary-build.mjs, so manifest.json shares the channel - * `.json` shape. Channel/prerelease never touches either. + * written by binary-build.mjs. Channel mode never touches those two files. * * Zero-dependency: OSS V1 header signature (HMAC-SHA1) over plain fetch. * diff --git a/tools/release/publish-channel.mjs b/tools/release/publish-channel.mjs index a5e62c6..3bc0bdb 100644 --- a/tools/release/publish-channel.mjs +++ b/tools/release/publish-channel.mjs @@ -35,7 +35,9 @@ const { values } = parseArgs({ const channel = values.channel; const dryRun = values["dry-run"]; const knowledge = values.knowledge; -const skipBinary = values["skip-binary"]; +// knowledge-studio-cli channel publishes are npm-only: binary artifacts are `bl` +// and must not overwrite CDN sync-release.json used by bailian-cli install verify. +const skipBinary = values["skip-binary"] || knowledge; const packages = knowledge ? ALL_PACKAGES : PACKAGES; assertChannel(channel); @@ -89,18 +91,22 @@ try { } } - // 2) binary GitHub Release — must run before finally restores package.json versions + // 2) binary GitHub Release — must run before finally restores package.json versions. + // Channel binary always refreshes OSS sync-release.json (npm tag is independent). if (skipBinary) { - log("\n[skip-binary] skipping binary GitHub Release"); + const reason = knowledge + ? "[knowledge] skipping binary (npm-only; does not touch sync-release.json)" + : "[skip-binary] skipping binary GitHub Release"; + log(`\n${reason}`); } else { step( - `publish binary GitHub Release (mode=channel, channel=${channel}, version=${betaVersion})`, + `publish binary GitHub Release (mode=channel, npm-tag=${channel}, CDN=sync-release, version=${betaVersion})`, ); await releaseBinaryArtifacts({ mode: "channel", channel, dryRun }); } const parts = ["npm"]; - if (!skipBinary) parts.push("binary"); + if (!skipBinary) parts.push("binary/sync-release"); log(`\nchannel release complete: ${channel}@${betaVersion} (${parts.join(" + ")})`); } catch (error) { process.stderr.write(`\nrelease publish-channel failed: ${error.message}\n`);