feat: win bl update exe file test

This commit is contained in:
rendianmeng
2026-07-31 18:53:37 +08:00
parent 75b056ba64
commit 525412f566
10 changed files with 177 additions and 67 deletions
+2 -2
View File
@@ -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
+6 -2
View File
@@ -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/<ver>/` + `current` 指针;`bl update` 只切换指针并清理旧版本(保留当前与上一版)。更新进程退出后,下次执行 `bl` 即使用新版本(无需「重启应用」)。
+18 -10
View File
@@ -26,20 +26,28 @@ publish-stable.mjs / publish-channel.mjs ← 唯一发版入口
两种模式:
| 模式 | 用途 | 触发方式 |
| ------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| channel | npm dist-tag + GitHub prerelease + 滚动 `channel-<name>` | 选 mode=channelchannel 名。**仅 `bailian-cli` 二进制安装验证**用 `sync-release``knowledge-studio-cli` 与 mcp/plugin 等仍按各自 dist-tag 名填写 |
| stable | npm latest + GitHub Release `v<ver>`(含 install 脚本) | 选 mode=stable需 production environment 审批 |
| 模式 | 用途 | 触发方式 |
| ------- | --------------------------------------------------------------------------------------- | -------------------------------------------- |
| channel | npm dist-tag +(仅 bailian-cli二进制 + CDN **一律**覆盖 `sync-release.json` | mode=channelchannel 填 **npm dist-tag** 名 |
| stable | npm latest + GitHub Release `v<ver>` + 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 workflowpackage 选目标包,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-<sha7>-<date>` → 临时 bump → 自检 → **npm 发到 dist-tag****Bun 编二进制并创建 GitHub prerelease + 滚动 `{channel}.json`** → 还原 package.json
1. 在 GitHub 触发 Publish workflowmode 选 `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-<sha7>-<date>` → 临时 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 workflowpackage 选目标包集合mode 选 `stable`
3. 需要 production environment 审批人批准
4. CI 自动:自检 → **npm 发到 latest****推送 git tag `v<ver>`****Bun 编二进制并创建/更新 GitHub Release** → 完成
4. CI 自动:自检 → **npm 发到 latest****推送 git tag `v<ver>`****Bun 编二进制并创建/更新 GitHub Release**bailian-cli维护 CDN **`manifest.json`** → 完成
5. 如果所选发布集合的当前版本已全部存在于 npmstable 发布会失败并提示先升级版本号如果只有部分包已发布CI 会继续补发缺失包
6. 对应脚本:`tools/release/publish-stable.mjs`
+7 -2
View File
@@ -7,7 +7,11 @@
* v<version>/<asset>.zip —— immutable per-version binaries + SHA256SUMS
* manifest.json —— stable install/update pointer (rolling-manifest shape)
* latest.json —— stable alias; same body as manifest.json
* <channel>.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 {
+12 -10
View File
@@ -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 <channel>.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` / `<channel>.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) {
+45 -5
View File
@@ -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 `<channel>.json` (attached to the rolling `channel-<name>`
* 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, `<channel>.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}`;
}
+40
View File
@@ -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");
});
});
+33 -27
View File
@@ -2,18 +2,19 @@
* Publish bailian-cli binary assets to GitHub Releases.
*
* stable: release `v<version>` (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<betaVersion>` (assets: bl-*.zip, SHA256SUMS)
* + rolling prerelease tag `channel-<name>` holding only `<name>.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<betaVersion>` Release (identical
* binaries); only the rolling `channel-<name>` 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<betaVersion>` 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 `<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({
+3 -4
View File
@@ -4,15 +4,14 @@
* No external FC is involved anymore; CI is the single writer.
*
* Flow:
* - Every mode uploads its assets to `<prefix>/<tag>/<basename>`; rolling
* channel manifests (`<channel>.json`) go to the prefix root (empty tag).
* - Every mode uploads its assets to `<prefix>/<tag>/<basename>`; 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 `<prefix>/manifest.json` and the rolling
* `<prefix>/latest.json` both carry the SAME rolling-manifest body
* written by binary-build.mjs, so manifest.json shares the channel
* `<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.
*
+11 -5
View File
@@ -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`);