Skip to content

Commit 7b949d3

Browse files
committed
fix(release): fix binary CI publish and clarify release modules
Stabilize Bun compile on 1.2.19, align manifests with OSS consumers, and split gh / webhook / mode helpers out of binary-release.
1 parent 168e2b5 commit 7b949d3

10 files changed

Lines changed: 372 additions & 246 deletions

docs/agents/binary-distribution.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ Publish workflow
3131
### A. 本仓库构建 / Release
3232

3333
- [ ] `node tools/release/lib/binary-build.mjs --mode stable --host`
34-
- [ ] `dist-bin/` 含矩阵二进制、`SHA256SUMS``latest.json`(channel 为 `<name>.json`
35-
- [ ] dry-run:`node tools/release/lib/binary-release.mjs --mode stable --skip-build --dry-run`
34+
- [ ] `dist-bin/`**完整**矩阵二进制、`SHA256SUMS``latest.json`(channel 为 `<name>.json`
35+
- [ ] manifest asset 只有 `file` + `sha256`(无硬编码 `url`;客户端按 OSS `{base}/releases/{version}/{file}` 拼)
36+
- [ ] dry-run:`node tools/release/lib/binary-release.mjs --mode stable --dry-run`(不编译)
3637
- [ ] Release **不含** 生产 install 脚本
3738

3839
### B. 仓外(联调时确认)
@@ -53,19 +54,31 @@ node tools/release/lib/binary-release.mjs --mode stable --skip-build --dry-run
5354
vp check
5455
```
5556

57+
实现分层(均在 `tools/release/lib/`):
58+
59+
- `binary-build.mjs` / `binary-compile.mjs` — 编译 + manifest
60+
- `binary-options.mjs` — 共享 `--mode` / `--channel` 校验
61+
- `binary-release.mjs` — 编排(stable/channel 上传哪些资产)
62+
- `gh-release.mjs``gh release` create / clobber / verify
63+
- `oss-sync-webhook.mjs` — 可选 FC 通知
64+
5665
## 常见漏点
5766

58-
| 漏点 | 后果 |
59-
| ---------------------- | ------------------------- |
60-
| 只发 npm、未建 Release | FC 无源可同步 |
61-
| FC 未跑完用户就 curl | OSS 404 / 半包 |
62-
| 矩阵变更未通知脚本方 | 装错 arch / 永久失败 |
63-
| webhook 配错当发版失败 | 不应;webhook 失败只 warn |
64-
|`Bun.build({ compile })` 代替 CLI | Bun ≤1.2.19 可能 exit 0 但不写 outfile → `sha256` ENOENT |
65-
| 编译后未 `chmod` windows `.exe` | Bun 1.2.19 在 Unix 上写出 mode `000``sha256` / upload `EACCES` |
67+
| 漏点 | 后果 |
68+
| ------------------------------------ | ----------------------------------------------------------------- |
69+
| 只发 npm、未建 Release | FC 无源可同步 |
70+
| FC 未跑完用户就 curl | OSS 404 / 半包 |
71+
| 矩阵变更未通知脚本方 | 装错 arch / 永久失败 |
72+
| webhook 配错当发版失败 | 不应;webhook 失败只 warn |
73+
|`Bun.build({ compile })` 代替 CLI | Bun ≤1.2.19 可能 exit 0 但不写 outfile → `sha256` ENOENT |
74+
| 编译后未 `chmod` windows `.exe` | Bun 1.2.19 在 Unix 上写出 mode `000``sha256` / upload `EACCES` |
75+
| manifest 写死 GitHub `url` | FC 同步后 `bl update` 仍打 GitHub,绕开 OSS |
76+
| `--dry-run` 仍全量 compile | 本地验证极慢;dry-run 应只规划 gh / webhook |
77+
|`--host` 产物去 upload | 半包上架;release 路径会校验完整矩阵 |
6678

6779
## 编译实现注意
6880

6981
- `binary-compile.mjs` 必须走 **`bun build --compile --outfile …`**,不要用 `Bun.build({ compile })`(CI 钉 `1.2.19` 时 API 会假成功)。
7082
- 编译后校验 outfile 存在再算 SHA256。
7183
- 每个产物在哈希前 `chmod 0755`(规避 Bun 1.2.19 windows cross-compile 无权限,见 oven-sh/bun#21308)。
84+
- channel 同日同 commit 共用一个 `v0.0.0-beta-…` Release;滚动 tag `channel-<name>` 只挂 `<name>.json`

docs/agents/publish.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
```text
1717
publish-stable.mjs / publish-channel.mjs ← 唯一发版入口
1818
├─ npm(pnpm publish)
19-
└─ binary(lib/binary-release → lib/binary-build + gh release)
19+
└─ binary(lib/binary-release
20+
→ binary-build
21+
→ gh-release
22+
→ oss-sync-webhook)
2023
```
2124

22-
`tools/release/lib/binary-release.mjs` / `binary-build.mjs` 是实现,一般不要单独当发版入口(调试可用)。详细约定见 [binary-distribution 方案](../proposals/binary-distribution.md)
25+
`tools/release/lib/binary-release.mjs` 等是实现,一般不要单独当发版入口(调试可用)。详细约定见 [binary-distribution](binary-distribution.md)
2326

2427
两种模式:
2528

tools/release/lib/binary-build.mjs

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@ import { fileURLToPath } from "node:url";
1616
import { spawnSync } from "node:child_process";
1717
import { parseArgs } from "node:util";
1818
import { ROOT, readPackageJson, PACKAGES } from "./packages.mjs";
19-
import { assertChannel } from "./validate.mjs";
19+
import { manifestFileName, normalizeModeChannel } from "./binary-options.mjs";
2020

2121
const BINARY_COMPILE = fileURLToPath(new URL("./binary-compile.mjs", import.meta.url));
2222
const CLI_ENTRY = join(ROOT, "packages/cli/src/main.ts");
2323
const DEFAULT_OUTDIR = join(ROOT, "dist-bin");
24-
const DEFAULT_CDN = "https://github.com/modelstudioai/cli/releases";
2524
const USAGE =
2625
"Usage: node tools/release/lib/binary-build.mjs [--mode stable|channel] [--channel <name>] [--host] [--target <bun-target>] [--outdir <dir>]\n";
2726

@@ -33,6 +32,16 @@ export const BINARY_TARGETS = [
3332
{ bunTarget: "bun-windows-x64", os: "windows", arch: "x64", exe: true },
3433
];
3534

35+
/** Asset basename for a matrix row: `bl-<ver>-<os>-<arch>[.exe]`. */
36+
export function binaryAssetName(version, { os, arch, exe }) {
37+
return `bl-${version}-${os}-${arch}${exe ? ".exe" : ""}`;
38+
}
39+
40+
/** Full matrix basenames for a version (order matches BINARY_TARGETS). */
41+
export function matrixAssetNames(version) {
42+
return BINARY_TARGETS.map((target) => binaryAssetName(version, target));
43+
}
44+
3645
function log(message = "") {
3746
process.stdout.write(`${message}\n`);
3847
}
@@ -41,10 +50,6 @@ function writeJson(path, value) {
4150
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
4251
}
4352

44-
function cdnBase() {
45-
return (process.env.BAILIAN_CLI_CDN || DEFAULT_CDN).replace(/\/$/, "");
46-
}
47-
4853
function parseCliArgs(argv) {
4954
const { values } = parseArgs({
5055
args: argv,
@@ -78,22 +83,12 @@ function normalizeBuildOptions({
7883
mode = "stable",
7984
channel = null,
8085
}) {
81-
if (mode !== "stable" && mode !== "channel") {
82-
throw new Error(`--mode must be stable or channel, got: ${mode}`);
83-
}
84-
if (mode === "channel") {
85-
if (!channel) throw new Error("--mode channel requires --channel <name>");
86-
assertChannel(channel);
87-
if (channel === "stable") {
88-
throw new Error(`--channel cannot be "stable"; use --mode stable`);
89-
}
90-
}
86+
const modeChannel = normalizeModeChannel(mode, channel);
9187
return {
9288
outdir: outdir ?? DEFAULT_OUTDIR,
9389
onlyTarget,
9490
hostOnly: Boolean(hostOnly),
95-
mode,
96-
channel: mode === "channel" ? channel : null,
91+
...modeChannel,
9792
};
9893
}
9994

@@ -133,16 +128,12 @@ function sha256File(path) {
133128
return createHash("sha256").update(readFileSync(path)).digest("hex");
134129
}
135130

136-
function assetFileName(version, os, arch, exe) {
137-
return `bl-${version}-${os}-${arch}${exe ? ".exe" : ""}`;
138-
}
139-
140131
function compileOne({ bunTarget, os, arch, exe }, version, outdir, entry) {
141-
const fileName = assetFileName(version, os, arch, exe);
132+
const fileName = binaryAssetName(version, { os, arch, exe });
142133
const outfile = join(outdir, fileName);
143134
log(`compile ${bunTarget}${fileName}`);
144135

145-
// Bun.build() lives in binary-compile.mjs (must run under Bun); this file stays Node.
136+
// binary-compile.mjs shells out to `bun build --compile` (CLI); this file stays Node.
146137
const result = spawnSync(
147138
"bun",
148139
[BINARY_COMPILE, "--entry", entry, "--outfile", outfile, "--target", bunTarget],
@@ -165,15 +156,19 @@ function writeChecksums(outdir, artifacts) {
165156
writeFileSync(join(outdir, "SHA256SUMS"), `${lines.join("\n")}\n`);
166157
}
167158

168-
function writeChannelManifest(outdir, version, artifacts, mode, channel) {
169-
const base = cdnBase();
159+
/**
160+
* Write latest.json (stable) or <channel>.json (channel).
161+
* Asset entries carry file + sha256 only — no baked download URL.
162+
* Consumers (bl update / install scripts) resolve via BAILIAN_CLI_CDN +
163+
* `{base}/releases/{version}/{file}` (see packages/core releaseAssetUrl).
164+
*/
165+
function writeManifest(outdir, version, artifacts, mode, channel) {
170166
const assets = Object.fromEntries(
171167
artifacts.map((item) => [
172168
`${item.os}-${item.arch}`,
173169
{
174170
file: item.fileName,
175171
sha256: item.sha256,
176-
url: `${base}/download/v${version}/${item.fileName}`,
177172
},
178173
]),
179174
);
@@ -184,9 +179,9 @@ function writeChannelManifest(outdir, version, artifacts, mode, channel) {
184179
releasedAt: new Date().toISOString(),
185180
assets,
186181
};
187-
const names = mode === "stable" ? ["latest.json"] : [`${channel}.json`];
188-
for (const name of names) writeJson(join(outdir, name), manifest);
189-
return names;
182+
const name = manifestFileName(mode, channel);
183+
writeJson(join(outdir, name), manifest);
184+
return [name];
190185
}
191186

192187
function cliVersion() {
@@ -207,7 +202,7 @@ function smokeTestHostBinary(artifacts, outdir) {
207202
}
208203
}
209204

210-
/** Compile binaries into `outdir` and write checksums + channel manifest. */
205+
/** Compile binaries into `outdir` and write checksums + manifest. */
211206
export function buildBinaryArtifacts(rawOptions = {}) {
212207
const options = normalizeBuildOptions(rawOptions);
213208
const { outdir, mode, channel } = options;
@@ -223,7 +218,7 @@ export function buildBinaryArtifacts(rawOptions = {}) {
223218

224219
const artifacts = targets.map((target) => compileOne(target, version, outdir, CLI_ENTRY));
225220
writeChecksums(outdir, artifacts);
226-
const manifests = writeChannelManifest(outdir, version, artifacts, mode, channel);
221+
const manifests = writeManifest(outdir, version, artifacts, mode, channel);
227222
smokeTestHostBinary(artifacts, outdir);
228223

229224
log(`\nBuilt ${artifacts.length} binary(ies):`);

tools/release/lib/binary-compile.mjs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
* Single-target Bun compile helper. Must be run with Bun on PATH:
33
* bun tools/release/lib/binary-compile.mjs --entry <path> --outfile <path> --target <bun-target>
44
*
5-
* Uses `bun build --compile` (CLI), not `Bun.build({ compile })`.
6-
* Bun ≤1.2.19's Build API can report success without writing `compile.outfile`
7-
* (API support landed properly around 1.2.21). CLI works on the pinned CI version.
5+
* Uses `bun build --compile` (CLI). The Bun.build({ compile }) API on ≤1.2.19
6+
* can exit 0 without writing outfile; CI pins 1.2.19 so we stay on the CLI.
87
*
98
* Called by binary-build.mjs (Node orchestration stays on Node).
109
*/
@@ -61,9 +60,6 @@ if (result.status !== 0) {
6160
}
6261

6362
if (!existsSync(outfile)) {
64-
console.error(
65-
`bun build --compile exited 0 but outfile missing: ${outfile}\n` +
66-
`(Bun Build API compile.outfile is unreliable on some versions; this helper uses the CLI.)`,
67-
);
63+
console.error(`bun build --compile exited 0 but outfile missing: ${outfile}`);
6864
process.exit(1);
6965
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Shared mode / channel / manifest naming for binary-build and binary-release.
3+
*/
4+
import { assertChannel } from "./validate.mjs";
5+
6+
/**
7+
* @param {string} mode
8+
* @param {string | null | undefined} channel
9+
* @returns {{ mode: "stable" | "channel", channel: string | null }}
10+
*/
11+
export function normalizeModeChannel(mode = "stable", channel = null) {
12+
if (mode !== "stable" && mode !== "channel") {
13+
throw new Error(`--mode must be stable or channel, got: ${mode}`);
14+
}
15+
if (mode === "channel") {
16+
if (!channel) throw new Error("--mode channel requires --channel <name>");
17+
assertChannel(channel);
18+
if (channel === "stable") {
19+
throw new Error(`--channel cannot be "stable"; use --mode stable`);
20+
}
21+
return { mode, channel };
22+
}
23+
return { mode: "stable", channel: null };
24+
}
25+
26+
/** Manifest basename written to dist-bin / uploaded to Releases. */
27+
export function manifestFileName(mode, channel) {
28+
return mode === "stable" ? "latest.json" : `${channel}.json`;
29+
}

0 commit comments

Comments
 (0)