From 4488d2c6d45e49b5c875c32cd10756f88548746e Mon Sep 17 00:00:00 2001 From: imeepos Date: Wed, 17 Jun 2026 06:51:37 -0700 Subject: [PATCH] docs: plan explicit logger env migration --- .../plans/2026-06-17-explicit-env-logger.md | 871 ++++++++++++++++++ 1 file changed, 871 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-17-explicit-env-logger.md diff --git a/docs/superpowers/plans/2026-06-17-explicit-env-logger.md b/docs/superpowers/plans/2026-06-17-explicit-env-logger.md new file mode 100644 index 000000000..fbee5a1f4 --- /dev/null +++ b/docs/superpowers/plans/2026-06-17-explicit-env-logger.md @@ -0,0 +1,871 @@ +# Logger Env 显式传递实现计划 + +> **给 agent 执行者:** 必选子技能:使用 `superpowers:subagent-driven-development`(推荐)或 `superpowers:executing-plans` 按任务逐项实现本计划。步骤统一使用 checkbox(`- [ ]`)语法跟踪。 + +**目标:** 移除 `server/src` 中 logger 对空对象 `Env` 和模块级全局 `logger` 的依赖,强制 logger 初始化从入口真实 `env` 显式传递。 + +**方案概览:** 先扩展单元扫描测试,让默认 `Env`、`initLogger({}, {})`、`export const logger` 和从本项目 `logtailUtils` 导入全局 `logger` 都变成可见失败。再收紧 `initLogger` / `logtailUtils` 签名,删除 import-time logger,并按已知调用点把 `env` 或 `ctx.logger` 逐层传递。最后运行类型检查、单元测试和静态搜索,确认没有回归。 + +**技术栈:** TypeScript、Bun test、Hono、pino、Cloudflare Workers bindings、tsgo + +--- + +## 文件结构 + +- 修改:`server/tests/unit/logging/init-logger-env.test.ts` + 职责:测试 `initLogger` 使用显式 `Env`,并扫描 `server/src` 禁止默认 env 和全局 logger 模式。 +- 修改:`server/src/utils/logging/initLogger.ts` + 职责:将 `env` 参数从 `Partial` 收紧为 `Env`。 +- 修改:`server/src/external/logtail/logtailUtils.ts` + 职责:删除模块级 logger,只保留 `createLogger(env)`、`createDualLogger(env)` 和 `Logger` 类型。 +- 修改:`server/src/external/aws/eventbridge/eventBridgeUtils.ts` + 职责:用函数参数 `env: Env` 创建局部 logger,不导入全局 `logger`。 +- 修改:`server/src/internal/balances/utils/lock/cancelLockExpiry.ts` + 职责:把 Worker bindings `env: Env` 显式传给 `deleteSchedule`,业务环境重命名为 `appEnv`。 +- 修改:`server/src/internal/balances/finalizeLock/runFinalizeLock.ts` + 职责:调用 `cancelLockExpiry` 时传入 `workerEnv: ctx.bindings` 或等价显式 Worker `Env`。如果当前 `RequestContext` 没有 bindings,按本任务步骤新增窄字段。 +- 修改:`server/src/internal/balances/finalizeLock/runFinalizeLockV2.ts` + 职责:调用 `cancelLockExpiry` 时传入显式 Worker `Env` 和业务 `AppEnv`,保持 V2 finalize lock 路径与 V1 一致。 +- 修改:`server/src/honoUtils/HonoEnv.ts` + 职责:如果 finalize lock 调用链暂时没有 Worker `Env`,为 `RequestContext` 增加明确字段 `bindings: Env`,只用于 Worker bindings,不替代业务 `ctx.env: AppEnv`。 +- 修改:`server/src/honoMiddlewares/baseMiddleware.ts` + 职责:如果新增 `RequestContext.bindings`,在 Hono 边界把 `c.env` 写入上下文。 +- 修改:`server/src/queue/processMessage.ts` + 职责:`processMessage` 接收 `env: Env`,局部创建 logger,并用 `env.NODE_ENV` 替代 `runtimeEnv.NODE_ENV`。 +- 修改:`server/src/queue/initWorkers.ts` + 职责:调用 `processMessage` 时传入入口 env。 +- 修改:`server/src/internal/customers/cusUtils/fullCustomerCacheUtils/testFullCustomerCacheGuard.ts` + 职责:用 `ctx.logger` 记录错误,删除全局 `logger` 导入。 +- 检查并按需修改:`server/src/external/aws/eventbridge/initEventBridge.ts`、`server/src/queue/initSqs.ts` + 职责:确保 EventBridge/SQS client 初始化没有新增空 env fallback。 + +--- + +### 任务 1:扩展扫描测试,先暴露 logger env 违规 + +**文件:** +- 修改:`server/tests/unit/logging/init-logger-env.test.ts` + +- [ ] **步骤 1:替换测试文件内容** + +将 `server/tests/unit/logging/init-logger-env.test.ts` 改为: + +```ts +import { afterEach, describe, expect, test } from "bun:test"; +import { readdirSync, readFileSync } from "node:fs"; +import { join, relative } from "node:path"; +import { initLogger } from "@/utils/logging/initLogger.js"; + +const makeEnv = (overrides: Partial): Env => overrides as Env; + +const sourceRoot = join(process.cwd(), "src"); + +const forbiddenSourcePatterns = [ + { + name: "Env parameter default", + pattern: /\benv\s*:\s*Env\s*=/, + }, + { + name: "optional Env parameter", + pattern: /\benv\?\s*:\s*Env\b/, + }, + { + name: "empty Env parameter fallback", + pattern: /\benv\s*=\s*\{\}\s+as\s+Env\b/, + }, + { + name: "empty Env nullish fallback", + pattern: /\benv\s*\?\?\s*\(?\s*\{\}\s+as\s+Env\b/, + }, + { + name: "initLogger empty Env", + pattern: /\binitLogger\(\s*\{\s*\}\s*,\s*\{\s*\}\s*\)/, + }, + { + name: "createLoggerStructure empty Env", + pattern: /\bcreateLoggerStructure\([^;]*,\s*\{\s*\}\s*\)/, + }, + { + name: "global logtail logger export", + pattern: /\bexport\s+const\s+logger\b/, + }, + { + name: "global logtail logger import", + pattern: + /import\s+\{[^}]*\blogger\b[^}]*\}\s+from\s+["'](?:@\/external\/logtail\/logtailUtils(?:\.js)?|[^"']*external\/logtail\/logtailUtils(?:\.js)?)["']/, + }, + { + name: "logger Env typed as Partial", + pattern: /\benv\s*:\s*Partial\b/, + files: new Set([ + "src/utils/logging/initLogger.ts", + "src/external/logtail/logtailUtils.ts", + ]), + }, +]; + +const collectTypeScriptFiles = (dir: string): string[] => + readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const path = join(dir, entry.name); + if (entry.isDirectory()) return collectTypeScriptFiles(path); + if (!entry.isFile()) return []; + return /\.(ts|tsx)$/.test(entry.name) ? [path] : []; + }); + +const findForbiddenSourcePatterns = () => + collectTypeScriptFiles(sourceRoot).flatMap((file) => { + const relativePath = relative(process.cwd(), file); + const contents = readFileSync(file, "utf8"); + + return forbiddenSourcePatterns + .filter((rule) => !rule.files || rule.files.has(relativePath)) + .filter((rule) => rule.pattern.test(contents)) + .map((rule) => `${relativePath}: ${rule.name}`); + }); + +describe("initLogger env configuration", () => { + const originalNodeEnv = process.env.NODE_ENV; + + afterEach(() => { + if (originalNodeEnv === undefined) { + delete (process.env as Record).NODE_ENV; + } else { + process.env.NODE_ENV = originalNodeEnv; + } + }); + + test("uses the passed Env instead of process.env to resolve dev mode", () => { + process.env.NODE_ENV = "production"; + + const logger = initLogger( + {}, + makeEnv({ + NODE_ENV: "development", + AXIOM_TOKEN: "", + }), + ); + + expect(logger.level).toBe("debug"); + }); + + test("does not allow default Env or global logtail logger usage", () => { + expect(findForbiddenSourcePatterns()).toEqual([]); + }); +}); +``` + +- [ ] **步骤 2:运行测试,确认它先失败** + +运行: + +```bash +cd server && bun test --isolate tests/unit/logging/init-logger-env.test.ts +``` + +预期:FAIL。失败列表至少包含: + +```text +src/external/logtail/logtailUtils.ts: initLogger empty Env +src/external/logtail/logtailUtils.ts: createLoggerStructure empty Env +src/external/logtail/logtailUtils.ts: global logtail logger export +src/external/logtail/logtailUtils.ts: logger Env typed as Partial +``` + +如果同时列出 `src/queue/processMessage.ts`、`src/external/aws/eventbridge/eventBridgeUtils.ts` 或 `src/internal/customers/cusUtils/fullCustomerCacheUtils/testFullCustomerCacheGuard.ts` 的全局 logger import,这是符合预期的。 + +- [ ] **步骤 3:提交失败测试** + +```bash +git add server/tests/unit/logging/init-logger-env.test.ts +git commit -m "test: forbid implicit logger env" +``` + +--- + +### 任务 2:收紧 logger factory,删除模块级 logger + +**文件:** +- 修改:`server/src/utils/logging/initLogger.ts` +- 修改:`server/src/external/logtail/logtailUtils.ts` +- 测试:`server/tests/unit/logging/init-logger-env.test.ts` + +- [ ] **步骤 1:收紧 `initLogger` 签名** + +在 `server/src/utils/logging/initLogger.ts` 中,把函数签名从: + +```ts +export const initLogger = ( + options: InitLoggerOptions, + env: Partial, +) => { +``` + +改为: + +```ts +export const initLogger = (options: InitLoggerOptions, env: Env) => { +``` + +函数体不需要改变,继续读取 `env.NODE_ENV` 和 `env.AXIOM_TOKEN`。 + +- [ ] **步骤 2:删除 `logtailUtils.ts` 的 import-time logger** + +在 `server/src/external/logtail/logtailUtils.ts` 中删除这两段: + +```ts +const pinoLogger = initLogger({}, {}); +``` + +```ts +export const logger = createLoggerStructure(pinoLogger, {}); +``` + +- [ ] **步骤 3:收紧 `createLoggerStructure` 的 env 类型** + +在 `server/src/external/logtail/logtailUtils.ts` 中,把: + +```ts +const createLoggerStructure = ( + basePinoLogger: pino.Logger, + env: Partial, +): Logger => ({ +``` + +改为: + +```ts +const createLoggerStructure = ( + basePinoLogger: pino.Logger, + env: Env, +): Logger => ({ +``` + +- [ ] **步骤 4:确认 factory 出口仍然显式接收 Env** + +确认 `server/src/external/logtail/logtailUtils.ts` 中保留以下代码: + +```ts +export const createLogger = (env: Env) => + createLoggerStructure(initLogger({}, env), env); + +/** + * Lazy dual-output logger (stdout JSON + axiom). Used only by long-running + * trigger.dev tasks so their lines surface in both the trigger run UI and + * our axiom store. Default `logger` / `createLogger` are unaffected. + */ +export const createDualLogger = (env: Env) => + createLoggerStructure(initLogger({ mode: "dual" }, env), env); +``` + +如果注释中的 `Default logger` 文案不再准确,把注释中的这一句改为: + +```ts + * `createLogger` is unaffected. +``` + +- [ ] **步骤 5:运行测试,确认剩余失败只来自调用点** + +运行: + +```bash +cd server && bun test --isolate tests/unit/logging/init-logger-env.test.ts +``` + +预期:仍 FAIL,但不再包含: + +```text +src/external/logtail/logtailUtils.ts: initLogger empty Env +src/external/logtail/logtailUtils.ts: createLoggerStructure empty Env +src/external/logtail/logtailUtils.ts: global logtail logger export +src/external/logtail/logtailUtils.ts: logger Env typed as Partial +``` + +如果仍失败,应只剩其他文件从 `logtailUtils` 导入全局 `logger`。 + +- [ ] **步骤 6:提交 logger factory 改动** + +```bash +git add server/src/utils/logging/initLogger.ts server/src/external/logtail/logtailUtils.ts +git commit -m "refactor: require env for logger factories" +``` + +--- + +### 任务 3:迁移 EventBridge logger,并补齐取消 schedule 的 Worker Env + +**文件:** +- 修改:`server/src/external/aws/eventbridge/eventBridgeUtils.ts` +- 修改:`server/src/internal/balances/utils/lock/cancelLockExpiry.ts` +- 修改:`server/src/internal/balances/finalizeLock/runFinalizeLock.ts` +- 修改:`server/src/internal/balances/finalizeLock/runFinalizeLockV2.ts` +- 按需修改:`server/src/honoUtils/HonoEnv.ts` +- 按需修改:`server/src/honoMiddlewares/baseMiddleware.ts` +- 测试:`server/tests/unit/logging/init-logger-env.test.ts` + +- [ ] **步骤 1:把 EventBridge 全局 logger 改成 factory** + +在 `server/src/external/aws/eventbridge/eventBridgeUtils.ts` 中,把导入: + +```ts +import { logger } from "@/external/logtail/logtailUtils.js"; +``` + +改为: + +```ts +import { createLogger } from "@/external/logtail/logtailUtils.js"; +``` + +在 `createSchedule` 函数体开始处加入: + +```ts + const logger = createLogger(env); +``` + +完整目标片段: + +```ts +export const createSchedule = async ({ + env, + scheduleName, + scheduleAt, + sqsMessageBody, + messageGroupId, +}: { + env: Env; + scheduleName: string; + scheduleAt: Date; + sqsMessageBody: string; + messageGroupId: string; +}) => { + const logger = createLogger(env); + + if (isLocalQueue(env)) { + logger.debug( + "[EventBridge] createSchedule skipped (local SQS queue — no EventBridge in dev)", + ); + return; + } +``` + +在 `deleteSchedule` 函数体开始处加入: + +```ts + const logger = createLogger(env); +``` + +完整目标片段: + +```ts +export const deleteSchedule = async ({ + env, + scheduleName, +}: { + env: Env; + scheduleName: string; +}) => { + const logger = createLogger(env); + + if (isLocalQueue(env)) { + logger.debug( + "[EventBridge] deleteSchedule skipped (local SQS queue — no EventBridge in dev)", + ); + return; + } +``` + +- [ ] **步骤 2:让 `cancelLockExpiry` 同时接收 Worker Env 和业务 AppEnv** + +把 `server/src/internal/balances/utils/lock/cancelLockExpiry.ts` 改为: + +```ts +import { deleteSchedule } from "@/external/aws/eventbridge/eventBridgeUtils.js"; +import { buildLockScheduleName } from "./buildLockScheduleName.js"; + +/** Cancels the EventBridge expiry schedule for a lock receipt. Safe to call even if no schedule exists. */ +export const cancelLockExpiry = async ({ + workerEnv, + orgId, + appEnv, + hashedKey, +}: { + workerEnv: Env; + orgId: string; + appEnv: string; + hashedKey: string; +}) => { + const scheduleName = buildLockScheduleName({ + orgId, + env: appEnv, + hashedKey, + }); + await deleteSchedule({ env: workerEnv, scheduleName }); +}; +``` + +- [ ] **步骤 3:如果 `RequestContext` 还没有 Worker bindings,新增窄字段** + +检查 `server/src/honoUtils/HonoEnv.ts`。如果 `RequestContext` 中没有 Worker bindings 字段,在 `logger: Logger;` 后加入: + +```ts + bindings: Env; +``` + +目标片段: + +```ts + db: DrizzleCli; + dbGeneral: DrizzleCli; + logger: Logger; + bindings: Env; + /** V2 Redis instance for this request. Populated by every ctx-building +``` + +然后在 `server/src/honoMiddlewares/baseMiddleware.ts` 的 `c.set("ctx", { ... })` 对象里,紧跟 `logger,` 加入: + +```ts + bindings: c.env, +``` + +目标片段: + +```ts + db, + dbGeneral: db, + logger, + bindings: c.env, + redisV2, +``` + +如果已有等价字段,例如 `workerEnv: Env`,不要新增第二个字段;后续步骤统一使用已有字段名。 + +- [ ] **步骤 4:更新 finalize lock 调用点** + +在 `server/src/internal/balances/finalizeLock/runFinalizeLock.ts` 中,把: + +```ts +await cancelLockExpiry({ + orgId: ctx.org.id, + env: ctx.env, + hashedKey: Bun.hash(params.lock_id).toString(), +}); +``` + +改为: + +```ts +await cancelLockExpiry({ + workerEnv: ctx.bindings, + orgId: ctx.org.id, + appEnv: ctx.env, + hashedKey: Bun.hash(params.lock_id).toString(), +}); +``` + +在 `server/src/internal/balances/finalizeLock/runFinalizeLockV2.ts` 中做同样替换。 + +- [ ] **步骤 5:运行局部静态检查** + +运行: + +```bash +rg -n "import \\{[^}]*logger[^}]*\\} from [\"']@/external/logtail/logtailUtils|deleteSchedule\\(\\{ scheduleName|cancelLockExpiry\\(\\{" server/src/external/aws/eventbridge server/src/internal/balances +``` + +预期:不再有 `logger` 从本项目 `logtailUtils` 导入;不再有 `deleteSchedule({ scheduleName })`。`cancelLockExpiry({` 的调用点必须都包含 `workerEnv` 和 `appEnv`。 + +- [ ] **步骤 6:运行 logger 扫描测试** + +运行: + +```bash +cd server && bun test --isolate tests/unit/logging/init-logger-env.test.ts +``` + +预期:如果还有失败,应来自其他全局 logger 调用点,不来自 `eventBridgeUtils.ts`。 + +- [ ] **步骤 7:提交 EventBridge 改动** + +```bash +git add server/src/external/aws/eventbridge/eventBridgeUtils.ts server/src/internal/balances/utils/lock/cancelLockExpiry.ts server/src/internal/balances/finalizeLock/runFinalizeLock.ts server/src/internal/balances/finalizeLock/runFinalizeLockV2.ts server/src/honoUtils/HonoEnv.ts server/src/honoMiddlewares/baseMiddleware.ts +git commit -m "refactor: pass env into eventbridge logging" +``` + +--- + +### 任务 4:迁移 SQS message 处理 logger 和 runtimeEnv 读取 + +**文件:** +- 修改:`server/src/queue/processMessage.ts` +- 修改:`server/src/queue/initWorkers.ts` +- 测试:`server/tests/unit/logging/init-logger-env.test.ts` +- 测试:`server/tests/unit/runtime-env-imports.test.ts` + +- [ ] **步骤 1:替换 `processMessage.ts` 的导入** + +在 `server/src/queue/processMessage.ts` 中删除: + +```ts +import { runtimeEnv } from "@/utils/envUtils.js"; +``` + +把: + +```ts +import { logger } from "@/external/logtail/logtailUtils.js"; +``` + +改为: + +```ts +import { createLogger } from "@/external/logtail/logtailUtils.js"; +``` + +- [ ] **步骤 2:让 `processMessage` 显式接收 Worker Env** + +把函数签名从: + +```ts +export const processMessage = async ({ + message, + db, +}: { + message: Message; + db: DrizzleCli; +}) => { +``` + +改为: + +```ts +export const processMessage = async ({ + env, + message, + db, +}: { + env: Env; + message: Message; + db: DrizzleCli; +}) => { +``` + +- [ ] **步骤 3:用入口 env 创建 worker logger** + +把: + +```ts +const workerLogger = addWorkflowToLogs({ + logger: logger, + workflowContext: { + id: workflowId, + name: job.name, + payload: job.data, + }, +}); +``` + +改为: + +```ts +const workerLogger = addWorkflowToLogs({ + logger: createLogger(env), + workflowContext: { + id: workflowId, + name: job.name, + payload: job.data, + }, +}); +``` + +- [ ] **步骤 4:用显式 env 替代 runtimeEnv** + +把: + +```ts +if (runtimeEnv.NODE_ENV === "development") { +``` + +改为: + +```ts +if (env.NODE_ENV === "development") { +``` + +- [ ] **步骤 5:更新 `initWorkers.ts` 调用点** + +在 `server/src/queue/initWorkers.ts` 中,把两个调用点: + +```ts +await processMessage({ message, db }); +``` + +和: + +```ts +fn: () => processMessage({ message, db }), +``` + +改为: + +```ts +await processMessage({ env, message, db }); +``` + +和: + +```ts +fn: () => processMessage({ env, message, db }), +``` + +这里的 `env` 必须来自 `initWorkers(env: Env)` 或等价入口参数;不要从模块级变量读取。 + +- [ ] **步骤 6:运行局部静态检查** + +运行: + +```bash +rg -n "runtimeEnv|import \\{[^}]*logger[^}]*\\} from [\"']@/external/logtail/logtailUtils|processMessage\\(\\{ message, db \\}\\)" server/src/queue +``` + +预期:无输出。 + +- [ ] **步骤 7:运行相关扫描测试** + +运行: + +```bash +cd server && bun test --isolate tests/unit/runtime-env-imports.test.ts tests/unit/logging/init-logger-env.test.ts +``` + +预期:如果仍失败,不应再包含 `src/queue/processMessage.ts`。 + +- [ ] **步骤 8:提交队列改动** + +```bash +git add server/src/queue/processMessage.ts server/src/queue/initWorkers.ts +git commit -m "refactor: pass env into sqs message logging" +``` + +--- + +### 任务 5:迁移 cache guard 的全局 logger + +**文件:** +- 修改:`server/src/internal/customers/cusUtils/fullCustomerCacheUtils/testFullCustomerCacheGuard.ts` +- 测试:`server/tests/unit/logging/init-logger-env.test.ts` + +- [ ] **步骤 1:删除全局 logger 导入** + +在 `server/src/internal/customers/cusUtils/fullCustomerCacheUtils/testFullCustomerCacheGuard.ts` 中删除: + +```ts +import { logger } from "@/external/logtail/logtailUtils.js"; +``` + +- [ ] **步骤 2:使用 `ctx.logger` 记录 set 失败** + +把: + +```ts +logger.error(`Failed to set test fullCustomer cache guard: ${error}`); +``` + +改为: + +```ts +ctx.logger.error(`Failed to set test fullCustomer cache guard: ${error}`); +``` + +- [ ] **步骤 3:使用 `ctx.logger` 记录 remove 失败** + +把: + +```ts +logger.error(`Failed to remove test fullCustomer cache guard: ${error}`); +``` + +改为: + +```ts +ctx.logger.error(`Failed to remove test fullCustomer cache guard: ${error}`); +``` + +- [ ] **步骤 4:运行 logger 扫描测试** + +运行: + +```bash +cd server && bun test --isolate tests/unit/logging/init-logger-env.test.ts +``` + +预期:如果任务 1-5 全部完成,测试 PASS,或者失败列表指向新的全局 logger import 文件。 + +- [ ] **步骤 5:如果扫描发现新的全局 logger import,逐个迁移** + +运行: + +```bash +rg -n "import \\{[^}]*logger[^}]*\\} from [\"'](?:@/external/logtail/logtailUtils(?:\\.js)?|[^\"']*external/logtail/logtailUtils(?:\\.js)?)[\"']" server/src +``` + +对每个命中文件使用同一规则迁移: + +```ts +// 如果函数已有 ctx +ctx.logger.error("message"); + +// 如果函数已有 env: Env +const logger = createLogger(env); +logger.error("message"); +``` + +迁移后再次运行同一个 `rg` 命令,预期无输出。不要修改 `better-auth` 的 `logger` 导入。 + +- [ ] **步骤 6:提交 cache guard 改动** + +```bash +git add server/src/internal/customers/cusUtils/fullCustomerCacheUtils/testFullCustomerCacheGuard.ts +git commit -m "refactor: use context logger for cache guard" +``` + +如果步骤 5 迁移了额外文件,把这些文件加入同一个提交,并在提交信息中使用: + +```bash +git commit -m "refactor: remove global logtail logger imports" +``` + +--- + +### 任务 6:全量静态搜索和类型检查修复 + +**文件:** +- 按类型检查输出修改相关 `server/src/**` 文件 +- 测试:`server/tests/unit/logging/init-logger-env.test.ts` +- 测试:`server/tests/unit/runtime-env-imports.test.ts` + +- [ ] **步骤 1:运行最终静态搜索** + +运行: + +```bash +rg -n "initLogger\\(\\s*\\{\\s*\\}\\s*,\\s*\\{\\s*\\}\\s*\\)|createLoggerStructure\\([^;]*,\\s*\\{\\s*\\}\\s*\\)|export const logger\\b|import \\{[^}]*logger[^}]*\\} from [\"'](?:@/external/logtail/logtailUtils(?:\\.js)?|[^\"']*external/logtail/logtailUtils(?:\\.js)?)[\"']|env\\?\\s*:\\s*Env\\b|env\\s*:\\s*Env\\s*=|env\\s*=\\s*\\{\\}\\s+as\\s+Env" server/src +``` + +预期:无输出。 + +- [ ] **步骤 2:运行 runtime env 和 logger 单元扫描测试** + +运行: + +```bash +cd server && bun test --isolate tests/unit/runtime-env-imports.test.ts tests/unit/logging/init-logger-env.test.ts +``` + +预期:PASS。 + +- [ ] **步骤 3:运行类型检查** + +运行: + +```bash +cd server && bun ts +``` + +预期:PASS。 + +如果失败,按错误文件修复。常见修复: + +```ts +// 缺 env 参数 +await processMessage({ env, message, db }); + +// 业务 AppEnv 和 Worker Env 混用 +await cancelLockExpiry({ + workerEnv: ctx.bindings, + orgId: ctx.org.id, + appEnv: ctx.env, + hashedKey, +}); + +// logger singleton 删除后改用上下文 logger +ctx.logger.warn("message"); +``` + +每次修复后重新运行: + +```bash +cd server && bun ts +``` + +直到类型检查 PASS。 + +- [ ] **步骤 4:提交类型修复** + +如果步骤 3 修改了文件: + +```bash +git add server/src server/tests/unit/logging/init-logger-env.test.ts +git commit -m "fix: thread env through logger call sites" +``` + +如果步骤 3 没有源码改动,不创建提交。 + +--- + +### 任务 7:运行单元测试并提交最终验证状态 + +**文件:** +- 检查:`server/src/**` +- 检查:`server/tests/unit/**` + +- [ ] **步骤 1:运行完整单元测试** + +运行: + +```bash +cd server && bun test:unit +``` + +预期:PASS。 + +如果失败且是本次 logger/env 改动引起,修复后重跑: + +```bash +cd server && bun test:unit +``` + +如果失败来自外部服务、缺少本地环境或已存在不相关失败,记录失败命令和首个错误摘要,不把结果报告为通过。 + +- [ ] **步骤 2:检查工作区只包含本次相关改动** + +运行: + +```bash +git status --short +``` + +预期:至少本轮提交都已完成;如果仍有未提交改动,只能包含本计划相关文件。不要回滚用户已有改动。 + +- [ ] **步骤 3:如果还有本次相关未提交改动,提交它们** + +运行: + +```bash +git add server/src server/tests/unit/logging/init-logger-env.test.ts +git commit -m "test: verify explicit logger env" +``` + +如果没有未提交的本次相关改动,不创建提交。 + +- [ ] **步骤 4:汇总验证结果** + +最终回复中列出: + +```text +类型检查:cd server && bun ts -> PASS +单元测试:cd server && bun test:unit -> PASS +静态搜索:无隐式 logger env 或全局 logtail logger +提交:列出本轮提交 hash 和 message +``` + +如果任何命令未通过,使用实际结果替换 `PASS`,并说明第一个阻塞错误。