feat(sdk): 版本注入与事件归属骨架(0.2.0/build_id/schema_version)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-18 12:22:05 +08:00
parent 0cb8c6fb0c
commit 77ca0ea110
6 changed files with 47 additions and 6 deletions
+13 -2
View File
@@ -1,6 +1,17 @@
import { build } from "esbuild"; import { build } from "esbuild";
import { execSync } from "node:child_process";
let buildId;
try {
buildId = execSync("git log -1 --format=%h-%cd --date=format:%Y%m%d%H%M").toString().trim();
} catch {
buildId = "dev-" + Date.now();
}
await build({ await build({
entryPoints: ["src/index.ts"], entryPoints: ["src/index.ts"], bundle: true, minify: true, format: "iife",
bundle: true, minify: true, format: "iife",
outfile: "dist/v1.js", target: ["es2020"], // 代码依赖 globalThis(es2018 目标名不副实) outfile: "dist/v1.js", target: ["es2020"], // 代码依赖 globalThis(es2018 目标名不副实)
// define 键必须用点链:esbuild 只做精确表达式匹配,裸键不会替换
// globalThis.__BUILD_ID__ 成员访问(实测 0.24.2),裸键将恒为 "unknown"
define: { "globalThis.__BUILD_ID__": JSON.stringify(buildId) },
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ctk-sdk", "name": "ctk-sdk",
"version": "0.1.0", "version": "0.2.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -1
View File
@@ -3,6 +3,7 @@ import { mergeConfig } from "./config";
import { Transport } from "./transport"; import { Transport } from "./transport";
import { startPageTracking } from "./events"; import { startPageTracking } from "./events";
import { buildEnvContext, collectHighEntropyHints, pageContext } from "./context"; import { buildEnvContext, collectHighEntropyHints, pageContext } from "./context";
import { SDK_VERSION, BUILD_ID, SCHEMA_VERSION, CONFIG_VERSION } from "./types";
import type { CtkConfig } from "./config"; import type { CtkConfig } from "./config";
// IIFE 打包入口(build.mjs):模块加载即接管全局 ctk 命令队列并重放。 // IIFE 打包入口(build.mjs):模块加载即接管全局 ctk 命令队列并重放。
@@ -49,7 +50,7 @@ function dispatch(cmd: string, arg?: unknown) {
occurred_at: new Date().toISOString(), occurred_at: new Date().toISOString(),
path: pc.path, path: pc.path,
title: pc.title, title: pc.title,
properties: { sdk_version: "0.1.0" }, properties: { sdk_version: SDK_VERSION, build_id: BUILD_ID, schema_version: SCHEMA_VERSION, config_version: CONFIG_VERSION },
}); });
startPageTracking(transport); startPageTracking(transport);
} else if (cmd === "track" && transport) { } else if (cmd === "track" && transport) {
+23
View File
@@ -0,0 +1,23 @@
/** 事件归属四元组:入队时冻结,flush 不再重读(spec §1.1) */
export interface Attribution {
site_id: string;
visitor_id: string;
session_id: string;
page_id: string;
seq: number;
purpose?: null; // V1.1 预留
epoch?: null; // V1.1 预留
grant_ref?: null; // V1.1 预留
}
export const SDK_VERSION = "0.2.0"; // 与 package.json 同步手维护
export const SCHEMA_VERSION = 2;
export const CONFIG_VERSION = "1";
// build_id 由 esbuild/vitest define 注入的全局常量(ambient 声明,
// 不能用模块级 export declare——esbuild 打包时会被擦除导致引用悬空)
declare global {
// eslint-disable-next-line no-var
var __BUILD_ID__: string | undefined;
}
/** 运行时兜底读取(build 注入缺失时降级 "unknown",绝不抛) */
export const BUILD_ID: string = globalThis.__BUILD_ID__ ?? "unknown";
+6 -2
View File
@@ -54,7 +54,7 @@ describe("index 入口", () => {
delete (document as { referrer?: string }).referrer; delete (document as { referrer?: string }).referrer;
}); });
it("init 后 sdk_loaded 入队(含 sdk_version)", async () => { it("init 后 sdk_loaded 入队(含版本组:sdk_version/build_id/schema_version/config_version)", async () => {
await loadSdk([["init", { siteId: "site-1", endpoint: "/e", flushMs: 60000 }]]); await loadSdk([["init", { siteId: "site-1", endpoint: "/e", flushMs: 60000 }]]);
g.ctk!("flush"); g.ctk!("flush");
@@ -62,7 +62,11 @@ describe("index 入口", () => {
expect(bodies).toHaveLength(1); expect(bodies).toHaveLength(1);
const loaded = bodies[0].events.find((e: { event_name: string }) => e.event_name === "sdk_loaded"); const loaded = bodies[0].events.find((e: { event_name: string }) => e.event_name === "sdk_loaded");
expect(loaded).toBeTruthy(); expect(loaded).toBeTruthy();
expect(loaded.properties.sdk_version).toBe("0.1.0"); expect(loaded.properties.sdk_version).toBe("0.2.0");
// build_id 由 vitest.config define 注入 "vitest";无 define 环境兜底 "unknown"
expect(loaded.properties.build_id).toBe("vitest");
expect(loaded.properties.schema_version).toBe(2);
expect(loaded.properties.config_version).toBe("1");
expect(loaded.event_id).toMatch(UUID_RE); expect(loaded.event_id).toMatch(UUID_RE);
}); });
+2
View File
@@ -1,4 +1,6 @@
import { defineConfig } from "vitest/config"; import { defineConfig } from "vitest/config";
export default defineConfig({ export default defineConfig({
test: { environment: "happy-dom", setupFiles: [] }, test: { environment: "happy-dom", setupFiles: [] },
// 与 build.mjs 同构的 define 注入(点链键):测试内 BUILD_ID 恒为 "vitest"
define: { "globalThis.__BUILD_ID__": JSON.stringify("vitest") },
}); });