Skip to content

fix(cli): load driver-sql's schema-work classifier lazily (#5726) - #5789

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-5726-cli-lazy-driver-sql-import
Aug 6, 2026
Merged

fix(cli): load driver-sql's schema-work classifier lazily (#5726)#5789
baozhoutao merged 1 commit into
mainfrom
claude/issue-5726-cli-lazy-driver-sql-import

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5726

问题

packages/cli/src/utils/schema-migrate.ts:17 有一处顶层 value import:

import { isInPlaceSchemaWork } from '@objectstack/driver-sql';

这一行的代价不由「需要 driver 的那条命令」承担。oclif 的 findCommand每次 CLI 调用时都会遍历命令表并 import() 每个命令模块,而这个文件被 9 条命令共用(meta:resyncmigrate,以及 7 条 migrate:*)。所以只要 packages/drivers/driver-sql/dist 没构建:

  1. 任何命令(包括 os dev)都会为这 9 条各打一段 MODULE_NOT_FOUND,刷在你真正要的输出前面(dev 还 fork 子进程,于是翻倍);
  2. 更严重的是,这 9 条命令直接从命令表里消失 —— os migrate plan 回答 Command migrate:plan not found.

issue 记录时是 6 条命令 / 12 段;在当前 origin/main 上已经涨到 9 条(migrate:metamigrate:recorded-bymigrate:resume 是后来加的)。这个放大器会随 migrate 子命令增加而继续变大。

改法(方向 1)

把那一处 value import 改成在真正用到的地方惰性加载:

async function loadIsInPlaceSchemaWork() {
  const { isInPlaceSchemaWork } = await import('@objectstack/driver-sql');
  return isInPlaceSchemaWork;
}

renderPendingSchemaWork / summarizePendingSchemaWork 随之变成 async,migrate plan / migrate apply 里的 5 处调用点加 await。第 16 行的 import type 不动 —— 纯类型导入会被完全擦除,不产生运行时边。

刻意不在 CLI 里重写这个谓词。 加性 / 原地(additive / in-place)的划分是关于 PendingSchemaWorkKind 的事实,声明在 driver 里那个联合类型旁边;CLI 复制一份,就等于哪天 driver 加一个 kind 时它有权和 driver 不一致 —— 而它不一致的方式,恰恰是把「重写现有行」的工作列在那个承诺「绝不丢数据」的标题下面(#3954)。所以这是「一个定义,晚一点加载」,不是「各写各的」。

同理,loadIsInPlaceSchemaWork 刻意不包 try:渲染时调用方手里已经握着一个活的 SQL driver(要渲染的条目正是 previewDeferredSchemaWork() 返回的),模块必然已在 loader 缓存里;万一真加载失败,必须响亮地失败,而不是退回去猜哪些工作会重写数据。

实证(把 driver-sql 的 dist 临时 mv 走)

os --version os migrate plan --help
改前 9 段 MODULE_NOT_FOUND 9 段 + Command migrate:plan not found.(exit 2)
改后 0 段,干净输出 完整 help,exit 0

构建产物侧:packages/cli/dist/utils/schema-migrate.js 改前有静态的 import { isInPlaceSchemaWork } from '@objectstack/driver-sql';,改后只剩动态的 await import('@objectstack/driver-sql')

测试

新增 packages/cli/src/utils/schema-migrate.lazy-driver-import.test.ts,三条源码级钉子:

  1. packages/cli/src 的生产代码里不允许有任何 @objectstack/driver-* 的静态 value import(import type 放行);
  2. schema-migrate.ts 仍然通过 await import() 依赖 driver-sql —— 防止有人用「在 CLI 里重抄一份谓词」来满足第 1 条;
  3. 两个 async 渲染函数的每个调用点都必须 await(丢 await 不是类型错误,只是输出和进程退出赛跑;仓库里已有 formatOutput 的同形 eslint 规则)。

之所以是源码级断言:缺陷长在 import 图的形状上,这是在编写期决定的,任何「把函数调起来跑一遍」的测试都看不见 —— 本包所有行为测试都跑在一个 driver 恰好已构建的工作区里。

反向验证(方向:红,与事前预期一致) —— 把第 17 行的静态 import 放回去,钉子 1 立刻变红并点名:

- []
+ [ "utils/schema-migrate.ts: import { isInPlaceSchemaWork } from '@objectstack/driver-sql'" ]

钉子 2、3 保持绿。这是诚实的方向:放回静态 import 既不会删掉动态 import,也不会把调用点的 await 拿掉,所以只有守护「静态导入形状」的那一条应该动。

pnpm --filter @objectstack/cli typecheck 通过;pnpm --filter @objectstack/cli test 85 文件 / 836 用例全绿

⚠️ 给下一个人:issue 里那份「假漂移」清单是假的,别去修

排查这类问题时,你很可能会顺手跑 pnpm --filter @objectstack/driver-sql build,然后拿到 20+ 条看起来非常像真实类型契约漂移的错误:

src/schema-drift.ts(33,10): error TS2305: Module '"@objectstack/spec/data"' has no exported member 'isAppResolvedDefaultToken'.
src/schema-drift.ts(442,9): error TS2322: Type '"default_mismatch"' is not assignable to type 'SchemaDiffEntryKind'.
src/memory-driver.ts(174,12): error TS2416: Property 'supports' ... Type '{}' is missing ... create, read, update, delete, and 27 more.

这些全部是假象。 isAppResolvedDefaultTokenpackages/spec/src/data/default-value-tokens.ts:143 好好地导出着;'default_mismatch' 也在 packages/spec/src/shared/external-errors.tsSchemaDiffEntryKind 联合里 —— 只是不在陈旧的 packages/spec/distpnpm install && pnpm build 之后全部消失(issue 报告者实测 72/72 绿)。

正确修法永远是 pnpm build,不是去改 driver-sql / spec 的源码。在正确的代码上「修」出真 bug,是这个 issue 最贵的失败模式 —— 这一节按分诊要求原样保留在这里当疫苗。

范围

严格限于分诊裁定的方向 1。issue 里的另外两个方向均未触碰,留给各自车道立单:

  • 方向 2(packages/services/service-datasource):让 datasource 的 fail-fast 认出「工作区未构建」这个成因。现状未变 —— 它仍然建议「Fix the datasource configuration」或设 OS_ALLOW_DRIVER_CONNECT_FAILURE=1,对这个成因两条都是有害建议(后者只会让一个构建不完整的工作区「启动成功」,然后对所有请求报错)。
  • 方向 3(根 dev 脚本):加一次廉价的前置构建判定。

无用户可见行为变化:纯本地 / worktree 首启 DX;CI 里 build 永远在前,跑不出这个形态。changeset 为 @objectstack/cli patch 档。


Generated by Claude Code

`schema-migrate.ts` statically value-imported `isInPlaceSchemaWork` from
`@objectstack/driver-sql`. That import is not paid for by the command that
needs it: oclif's `findCommand` `import()`s every command module on every
CLI invocation, and nine commands reach this file (`meta:resync`, `migrate`
and seven `migrate:*`). An unbuilt `packages/drivers/driver-sql/dist`
therefore printed nine MODULE_NOT_FOUND blocks — naming nine commands the
operator never invoked — in front of whatever they actually ran, and dropped
all nine out of the command table (`os migrate plan` answered
`Command migrate:plan not found.`).

Measured on a worktree with driver-sql's dist moved aside:
  before  `os --version`         9 MODULE_NOT_FOUND blocks, exit 0
          `os migrate plan -h`   9 blocks + `Command migrate:plan not found.`
  after   `os --version`         0 blocks, clean version line
          `os migrate plan -h`   full help, exit 0

The classifier keeps its ONE definition in the driver — the additive/in-place
split is a fact about `PendingSchemaWorkKind`, and a copy in the CLI would be
free to disagree the day a kind is added, by listing a row rewrite under the
heading that promises the work is never data-losing (#3954). So this is a
lazy `await import()` at the point of use, not a re-derivation.
`renderPendingSchemaWork` / `summarizePendingSchemaWork` become async; their
five call sites in `migrate plan` / `migrate apply` await them.

A source-level pin test keeps the shape from growing back: no CLI production
module may statically value-import an `@objectstack/driver-*` package, the
dynamic import to driver-sql must survive, and every call of the two async
renderers must be awaited.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 5:33am

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli.

21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v17.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 6, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review August 6, 2026 05:48
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 0b720de Aug 6, 2026
24 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5726-cli-lazy-driver-sql-import branch August 6, 2026 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

objectstack dev 在工作区未构建时刷 12 段无关命令的 MODULE_NOT_FOUND,唯一可执行的那条却指向错误修法

2 participants