Skip to content

Commit 8e4cae8

Browse files
committed
fix: report release binary telemetry as production
Bun compile inlines NODE_ENV as development, so binary command events never reached the production dashboard.
1 parent bddf58d commit 8e4cae8

5 files changed

Lines changed: 109 additions & 23 deletions

File tree

‎docs/agents/telemetry-change.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ source-config 只用于百炼 / DashScope API 侧消费,不发送到通用网
121121
1. 本地 `~/.bailian/telemetry.jsonl`:权限 `0600`,超过 5 MB 后重建
122122
2. AEM:`pid=bailian-cli-node`,源码运行自动使用 `env=dev`,npm 安装或编译二进制使用 `env=prod`
123123

124+
编译二进制的 `env=prod` 由 `BAILIAN_COMPILED=1` 决定,并且必须先于 `NODE_ENV`。`bun build --compile`(当前 pin 1.3.14)会把点访问 `process.env.NODE_ENV` 内联成构建机的值,未设置时是 `"development"`;`tools/release/lib/binary-compile.mjs` 同时 `--define process.env.NODE_ENV="production"`。这两处读取必须写成 `process.env.*`,包进别的变量后 `--define` 匹配不到。
125+
124126
底层 Node tracker 还会附加公共设备字段:OS 类型/版本、Node 应用名与版本、平台,以及由本机网络标识计算的 MD5 `device_id`。
125127

126128
从 2.8.2 起,每条命令事件通过 `commandPrefix` 显式记录调用入口,三种入口仍共用 `pid=bailian-cli-node`:
@@ -202,3 +204,4 @@ vp test packages/core/tests packages/runtime/tests/telemetry-stage.test.ts packa
202204
- ✗ 新增 bypass `fetch` 后漏掉应由网关消费的 source-config,或把它发给 OSS / npm / 第三方下载地址
203205
- ✗ 只改 `bl` 入口,导致 `kscli` 的产品名或版本标签错误
204206
- ✗ 把帮助、版本或参数校验失败算进“全部命令”;这些路径当前没有进入 telemetry middleware
207+
- ✗ 把 `NODE_ENV` 判断放在 `BAILIAN_COMPILED` 前面。Bun 编译会把 `process.env.NODE_ENV` 折成 `"development"`,二进制流量全部进 `env=dev`,生产看板为空

‎packages/core/src/telemetry/env.ts‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
/**
2-
* 判断当前运行环境。任一条件为真即视为 dev,默认 prod。
2+
* 判断当前运行环境。默认 prod。
33
*
4-
* 1. NODE_ENV=development — Node 圈通用约定,测试同学/CI 可显式声明
5-
* 2. Bun 编译二进制(BAILIAN_COMPILED=1)— 一律 prod
4+
* 1. Bun 编译二进制(BAILIAN_COMPILED=1)— 一律 prod。必须先于 NODE_ENV:
5+
* `bun build --compile` 会把 `process.env.NODE_ENV` 内联成构建机的值,
6+
* 未设置时是 `"development"`。放在前面会让二进制流量全部记成 dev。
7+
* release 编译同时 `--define process.env.NODE_ENV="production"`。
8+
* 2. NODE_ENV=development — 仅非二进制。测试同学/CI 可显式声明
69
* 3. 当前模块文件路径不在 node_modules 里 — 自动识别从源码运行(pnpm dev /
710
* npm link / 直接 pnpm -F bailian-cli exec tsx src/main.ts),避免开发者忘记设环境变量
811
* 时仍把数据打到 prod
912
*
10-
* 缓存结果,模块加载期算一次就行。
13+
* 这两处必须直接读取 `process.env.*`。包一层再读,Bun 的 `--define` 匹配不到,
14+
* 编译进二进制的标记会失效。缓存结果,模块加载期算一次就行。
1115
*/
1216
let cachedEnv: "dev" | "prod" | undefined;
1317

1418
export function detectEnv(): "dev" | "prod" {
1519
if (cachedEnv) return cachedEnv;
16-
if (process.env.NODE_ENV === "development") {
17-
cachedEnv = "dev";
18-
return cachedEnv;
19-
}
2020
if (process.env.BAILIAN_COMPILED === "1") {
2121
cachedEnv = "prod";
2222
return cachedEnv;
2323
}
24+
if (process.env.NODE_ENV === "development") {
25+
cachedEnv = "dev";
26+
return cachedEnv;
27+
}
2428
cachedEnv = import.meta.url.includes("/node_modules/") ? "prod" : "dev";
2529
return cachedEnv;
2630
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { afterEach, expect, test, vi } from "vite-plus/test";
2+
3+
afterEach(() => {
4+
vi.unstubAllEnvs();
5+
vi.resetModules();
6+
});
7+
8+
async function loadDetectEnv(): Promise<() => "dev" | "prod"> {
9+
vi.resetModules();
10+
const telemetryEnv = await import("../src/telemetry/env.ts");
11+
return telemetryEnv.detectEnv;
12+
}
13+
14+
test("compiled binary reports prod even when NODE_ENV is development", async () => {
15+
vi.stubEnv("BAILIAN_COMPILED", "1");
16+
vi.stubEnv("NODE_ENV", "development");
17+
const detectEnv = await loadDetectEnv();
18+
expect(detectEnv()).toBe("prod");
19+
});
20+
21+
test("compiled binary reports prod when NODE_ENV is unset", async () => {
22+
vi.stubEnv("BAILIAN_COMPILED", "1");
23+
vi.stubEnv("NODE_ENV", undefined);
24+
const detectEnv = await loadDetectEnv();
25+
expect(detectEnv()).toBe("prod");
26+
});
27+
28+
test("NODE_ENV=development reports dev outside a compiled binary", async () => {
29+
vi.stubEnv("BAILIAN_COMPILED", undefined);
30+
vi.stubEnv("NODE_ENV", "development");
31+
const detectEnv = await loadDetectEnv();
32+
expect(detectEnv()).toBe("dev");
33+
});
34+
35+
test("source tree reports dev when NODE_ENV is production or test", async () => {
36+
vi.stubEnv("BAILIAN_COMPILED", undefined);
37+
vi.stubEnv("NODE_ENV", "production");
38+
const productionDetectEnv = await loadDetectEnv();
39+
expect(productionDetectEnv()).toBe("dev");
40+
41+
vi.stubEnv("NODE_ENV", "test");
42+
const testDetectEnv = await loadDetectEnv();
43+
expect(testDetectEnv()).toBe("dev");
44+
});

‎tools/release/lib/binary-compile.mjs‎

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*/
1010
import { existsSync } from "node:fs";
1111
import { spawnSync } from "node:child_process";
12+
import { resolve } from "node:path";
13+
import { fileURLToPath } from "node:url";
1214

1315
function parseArgs(argv) {
1416
let entry = null;
@@ -34,11 +36,14 @@ function parseArgs(argv) {
3436
return { entry, outfile, target };
3537
}
3638

37-
const { entry, outfile, target } = parseArgs(process.argv.slice(2));
38-
39-
const result = spawnSync(
40-
"bun",
41-
[
39+
/**
40+
* Bun ≤1.3.14 inlines `process.env.NODE_ENV` during `bun build --compile`.
41+
* Unset on the build machine becomes the string `"development"`, which
42+
* `detectEnv()` would report as dev. Pin production here, and keep the
43+
* compiled-marker define so the runtime check still wins if that default changes.
44+
*/
45+
export function bunCompileArguments(entry, outfile, target) {
46+
return [
4247
"build",
4348
entry,
4449
"--compile",
@@ -48,18 +53,30 @@ const result = spawnSync(
4853
target,
4954
"--define",
5055
'process.env.BAILIAN_COMPILED="1"',
51-
],
52-
{ encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] },
53-
);
56+
"--define",
57+
'process.env.NODE_ENV="production"',
58+
];
59+
}
5460

55-
if (result.stdout) process.stdout.write(result.stdout);
56-
if (result.stderr) process.stderr.write(result.stderr);
61+
function compileBinary({ entry, outfile, target }) {
62+
const result = spawnSync("bun", bunCompileArguments(entry, outfile, target), {
63+
encoding: "utf-8",
64+
stdio: ["ignore", "pipe", "pipe"],
65+
});
5766

58-
if (result.status !== 0) {
59-
process.exit(result.status ?? 1);
67+
if (result.stdout) process.stdout.write(result.stdout);
68+
if (result.stderr) process.stderr.write(result.stderr);
69+
70+
if (result.status !== 0) {
71+
process.exit(result.status ?? 1);
72+
}
73+
74+
if (!existsSync(outfile)) {
75+
console.error(`bun build --compile exited 0 but outfile missing: ${outfile}`);
76+
process.exit(1);
77+
}
6078
}
6179

62-
if (!existsSync(outfile)) {
63-
console.error(`bun build --compile exited 0 but outfile missing: ${outfile}`);
64-
process.exit(1);
80+
if (resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) {
81+
compileBinary(parseArgs(process.argv.slice(2)));
6582
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { expect, test } from "vite-plus/test";
2+
import { bunCompileArguments } from "./binary-compile.mjs";
3+
4+
test("release compile pins the compiled marker and production NODE_ENV", () => {
5+
expect(bunCompileArguments("entry.ts", "out/bl", "bun-linux-x64")).toEqual([
6+
"build",
7+
"entry.ts",
8+
"--compile",
9+
"--outfile",
10+
"out/bl",
11+
"--target",
12+
"bun-linux-x64",
13+
"--define",
14+
'process.env.BAILIAN_COMPILED="1"',
15+
"--define",
16+
'process.env.NODE_ENV="production"',
17+
]);
18+
});

0 commit comments

Comments
 (0)