Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/agents/telemetry-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ source-config 只用于百炼 / DashScope API 侧消费,不发送到通用网
1. 本地 `~/.bailian/telemetry.jsonl`:权限 `0600`,超过 5 MB 后重建;每行附带 `uid`(只读已有文件,不创建)
2. AEM:`pid=bailian-cli-node`,源码运行自动使用 `env=dev`,npm 安装或编译二进制使用 `env=prod`

编译二进制的 `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` 匹配不到。

旁路状态:`~/.bailian/telemetry-uid`(遵循 `BAILIAN_CONFIG_DIR`)由 `ensureUid` 在首次远端上报时生成,不是事件落盘目标。

底层 Node tracker 还会附加公共设备字段:OS 类型/版本、Node 应用名与版本、平台,以及内置的 `device_id`(本机网卡 MAC 的 MD5,保持不变)。稳定匿名 install id 写入 AEM 的 `uid`:来自 `~/.bailian/telemetry-uid`,首次远端上报时生成随机 UUID v4,以 0600 权限原子发布;生成失败时不上报 `uid`。遥测关闭时不生成该文件。本地 `telemetry.jsonl` 每行附带同名 `uid`。注意:这不是账号用户 ID,只是上报到 AEM `uid` 字段的匿名标识;与 Tracker 内置 `device_id` 无关。
Expand Down Expand Up @@ -208,3 +210,4 @@ vp test packages/core/tests/telemetry-uid.test.ts packages/core/tests/telemetry-
- ✗ 新增 bypass `fetch` 后漏掉应由网关消费的 source-config,或把它发给 OSS / npm / 第三方下载地址
- ✗ 只改 `bl` 入口,导致 `kscli` 的产品名或版本标签错误
- ✗ 把帮助、版本或参数校验失败算进“全部命令”;这些路径当前没有进入 telemetry middleware
- ✗ 把 `NODE_ENV` 判断放在 `BAILIAN_COMPILED` 前面。Bun 编译会把 `process.env.NODE_ENV` 折成 `"development"`,二进制流量全部进 `env=dev`,生产看板为空
20 changes: 12 additions & 8 deletions packages/core/src/telemetry/env.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
/**
* 判断当前运行环境。任一条件为真即视为 dev,默认 prod。
* 判断当前运行环境。默认 prod。
*
* 1. NODE_ENV=development — Node 圈通用约定,测试同学/CI 可显式声明
* 2. Bun 编译二进制(BAILIAN_COMPILED=1)— 一律 prod
* 1. Bun 编译二进制(BAILIAN_COMPILED=1)— 一律 prod。必须先于 NODE_ENV:
* `bun build --compile` 会把 `process.env.NODE_ENV` 内联成构建机的值,
* 未设置时是 `"development"`。放在前面会让二进制流量全部记成 dev。
* release 编译同时 `--define process.env.NODE_ENV="production"`。
* 2. NODE_ENV=development — 仅非二进制。测试同学/CI 可显式声明
* 3. 当前模块文件路径不在 node_modules 里 — 自动识别从源码运行(pnpm dev /
* npm link / 直接 pnpm -F bailian-cli exec tsx src/main.ts),避免开发者忘记设环境变量
* 时仍把数据打到 prod
*
* 缓存结果,模块加载期算一次就行。
* 这两处必须直接读取 `process.env.*`。包一层再读,Bun 的 `--define` 匹配不到,
* 编译进二进制的标记会失效。缓存结果,模块加载期算一次就行。
*/
let cachedEnv: "dev" | "prod" | undefined;

export function detectEnv(): "dev" | "prod" {
if (cachedEnv) return cachedEnv;
if (process.env.NODE_ENV === "development") {
cachedEnv = "dev";
return cachedEnv;
}
if (process.env.BAILIAN_COMPILED === "1") {
cachedEnv = "prod";
return cachedEnv;
}
if (process.env.NODE_ENV === "development") {
cachedEnv = "dev";
return cachedEnv;
}
cachedEnv = import.meta.url.includes("/node_modules/") ? "prod" : "dev";
return cachedEnv;
}
44 changes: 44 additions & 0 deletions packages/core/tests/telemetry-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { afterEach, expect, test, vi } from "vite-plus/test";

afterEach(() => {
vi.unstubAllEnvs();
vi.resetModules();
});

async function loadDetectEnv(): Promise<() => "dev" | "prod"> {
vi.resetModules();
const telemetryEnv = await import("../src/telemetry/env.ts");
return telemetryEnv.detectEnv;
}

test("compiled binary reports prod even when NODE_ENV is development", async () => {
vi.stubEnv("BAILIAN_COMPILED", "1");
vi.stubEnv("NODE_ENV", "development");
const detectEnv = await loadDetectEnv();
expect(detectEnv()).toBe("prod");
});

test("compiled binary reports prod when NODE_ENV is unset", async () => {
vi.stubEnv("BAILIAN_COMPILED", "1");
vi.stubEnv("NODE_ENV", undefined);
const detectEnv = await loadDetectEnv();
expect(detectEnv()).toBe("prod");
});

test("NODE_ENV=development reports dev outside a compiled binary", async () => {
vi.stubEnv("BAILIAN_COMPILED", undefined);
vi.stubEnv("NODE_ENV", "development");
const detectEnv = await loadDetectEnv();
expect(detectEnv()).toBe("dev");
});

test("source tree reports dev when NODE_ENV is production or test", async () => {
vi.stubEnv("BAILIAN_COMPILED", undefined);
vi.stubEnv("NODE_ENV", "production");
const productionDetectEnv = await loadDetectEnv();
expect(productionDetectEnv()).toBe("dev");

vi.stubEnv("NODE_ENV", "test");
const testDetectEnv = await loadDetectEnv();
expect(testDetectEnv()).toBe("dev");
});
47 changes: 32 additions & 15 deletions tools/release/lib/binary-compile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
*/
import { existsSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";

function parseArgs(argv) {
let entry = null;
Expand All @@ -34,11 +36,14 @@ function parseArgs(argv) {
return { entry, outfile, target };
}

const { entry, outfile, target } = parseArgs(process.argv.slice(2));

const result = spawnSync(
"bun",
[
/**
* Bun ≤1.3.14 inlines `process.env.NODE_ENV` during `bun build --compile`.
* Unset on the build machine becomes the string `"development"`, which
* `detectEnv()` would report as dev. Pin production here, and keep the
* compiled-marker define so the runtime check still wins if that default changes.
*/
export function bunCompileArguments(entry, outfile, target) {
return [
"build",
entry,
"--compile",
Expand All @@ -48,18 +53,30 @@ const result = spawnSync(
target,
"--define",
'process.env.BAILIAN_COMPILED="1"',
],
{ encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] },
);
"--define",
'process.env.NODE_ENV="production"',
];
}

if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
function compileBinary({ entry, outfile, target }) {
const result = spawnSync("bun", bunCompileArguments(entry, outfile, target), {
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
});

if (result.status !== 0) {
process.exit(result.status ?? 1);
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);

if (result.status !== 0) {
process.exit(result.status ?? 1);
}

if (!existsSync(outfile)) {
console.error(`bun build --compile exited 0 but outfile missing: ${outfile}`);
process.exit(1);
}
}

if (!existsSync(outfile)) {
console.error(`bun build --compile exited 0 but outfile missing: ${outfile}`);
process.exit(1);
if (resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) {
compileBinary(parseArgs(process.argv.slice(2)));
}
18 changes: 18 additions & 0 deletions tools/release/lib/binary-compile.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expect, test } from "vite-plus/test";
import { bunCompileArguments } from "./binary-compile.mjs";

test("release compile pins the compiled marker and production NODE_ENV", () => {
expect(bunCompileArguments("entry.ts", "out/bl", "bun-linux-x64")).toEqual([
"build",
"entry.ts",
"--compile",
"--outfile",
"out/bl",
"--target",
"bun-linux-x64",
"--define",
'process.env.BAILIAN_COMPILED="1"',
"--define",
'process.env.NODE_ENV="production"',
]);
});
Loading