fix(cli): load driver-sql's schema-work classifier lazily (#5726) - #5789
Merged
Conversation
`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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 6, 2026 05:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5726
问题
packages/cli/src/utils/schema-migrate.ts:17有一处顶层 value import:这一行的代价不由「需要 driver 的那条命令」承担。oclif 的
findCommand在每次 CLI 调用时都会遍历命令表并import()每个命令模块,而这个文件被 9 条命令共用(meta:resync、migrate,以及 7 条migrate:*)。所以只要packages/drivers/driver-sql/dist没构建:os dev)都会为这 9 条各打一段MODULE_NOT_FOUND,刷在你真正要的输出前面(dev还 fork 子进程,于是翻倍);os migrate plan回答Command migrate:plan not found.。issue 记录时是 6 条命令 / 12 段;在当前
origin/main上已经涨到 9 条(migrate:meta、migrate:recorded-by、migrate:resume是后来加的)。这个放大器会随 migrate 子命令增加而继续变大。改法(方向 1)
把那一处 value import 改成在真正用到的地方惰性加载:
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 --versionos migrate plan --helpCommand migrate:plan not found.(exit 2)构建产物侧:
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,三条源码级钉子:packages/cli/src的生产代码里不允许有任何@objectstack/driver-*的静态 value import(import type放行);schema-migrate.ts仍然通过await import()依赖 driver-sql —— 防止有人用「在 CLI 里重抄一份谓词」来满足第 1 条;await(丢 await 不是类型错误,只是输出和进程退出赛跑;仓库里已有formatOutput的同形 eslint 规则)。之所以是源码级断言:缺陷长在 import 图的形状上,这是在编写期决定的,任何「把函数调起来跑一遍」的测试都看不见 —— 本包所有行为测试都跑在一个 driver 恰好已构建的工作区里。
反向验证(方向:红,与事前预期一致) —— 把第 17 行的静态 import 放回去,钉子 1 立刻变红并点名:
钉子 2、3 保持绿。这是诚实的方向:放回静态 import 既不会删掉动态 import,也不会把调用点的 await 拿掉,所以只有守护「静态导入形状」的那一条应该动。
pnpm --filter @objectstack/cli typecheck通过;pnpm --filter @objectstack/cli test85 文件 / 836 用例全绿。排查这类问题时,你很可能会顺手跑
pnpm --filter @objectstack/driver-sql build,然后拿到 20+ 条看起来非常像真实类型契约漂移的错误:这些全部是假象。
isAppResolvedDefaultToken在packages/spec/src/data/default-value-tokens.ts:143好好地导出着;'default_mismatch'也在packages/spec/src/shared/external-errors.ts的SchemaDiffEntryKind联合里 —— 只是不在陈旧的packages/spec/dist里。pnpm install && pnpm build之后全部消失(issue 报告者实测 72/72 绿)。正确修法永远是
pnpm build,不是去改 driver-sql / spec 的源码。在正确的代码上「修」出真 bug,是这个 issue 最贵的失败模式 —— 这一节按分诊要求原样保留在这里当疫苗。范围
严格限于分诊裁定的方向 1。issue 里的另外两个方向均未触碰,留给各自车道立单:
packages/services/service-datasource):让 datasource 的 fail-fast 认出「工作区未构建」这个成因。现状未变 —— 它仍然建议「Fix the datasource configuration」或设OS_ALLOW_DRIVER_CONNECT_FAILURE=1,对这个成因两条都是有害建议(后者只会让一个构建不完整的工作区「启动成功」,然后对所有请求报错)。dev脚本):加一次廉价的前置构建判定。无用户可见行为变化:纯本地 / worktree 首启 DX;CI 里 build 永远在前,跑不出这个形态。changeset 为
@objectstack/clipatch 档。Generated by Claude Code