Skip to content

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

Description

@os-zhuang

观察类发现(finding)。起因是本地 pnpm dev 起不来、控制台刷满报错,排查后确认代码没有 bug,坏的只是「工作区未构建」这一个前置条件——但诊断输出把它呈现成了十几个互不相关的问题,且唯一可执行的那条指向错误的修法。与 #5217 同族(同样是「未构建的工作区被报成 N 个内容问题」),但站点不同,故单独记一笔。

现象

在一个依赖装了一半、且 packages/spec/dist 陈旧的 worktree 里跑 pnpm dev,控制台先刷 12 段 MODULE_NOT_FOUND:

(node:95916) [MODULE_NOT_FOUND] Warning: ModuleLoadError
module: @oclif/core@4.13.1
task: findCommand (meta:resync)
plugin: @objectstack/cli
code: MODULE_NOT_FOUND
message: [MODULE_NOT_FOUND] import() failed to load .../cli/dist/commands/meta/resync.js:
  Cannot find package '.../packages/cli/node_modules/@objectstack/driver-sql/index.js'
  imported from .../packages/cli/dist/utils/schema-migrate.js

同样的段落重复 6 条命令 —— meta:resync、migrate、migrate:apply、migrate:plan、migrate:value-shapes、migrate:files-to-references —— 一条都不是我调用的命令(我跑的是 dev)。dev 又 fork 了子进程,于是 6 × 2 = 12 段。

刷完之后,真正可执行的那条在最后:

✗ datasource 'default': connect failed — Cannot find package '.../@objectstack/driver-sql/index.js' ...
  (declared boot-critical by the host ... ⇒ fail-fast per ADR-0062 D5).
  Fix the datasource configuration, or set OS_ALLOW_DRIVER_CONNECT_FAILURE=1 to boot anyway
  and serve errors until it is reachable.

真实原因

只有一个:packages/drivers/* 的 dist 不在(本次的成因是这 5 个包连 node_modules 都没有 —— pnpm install 在该 worktree 里没装全 —— 外加 packages/spec/dist 陈旧)。

放大成 12 段噪音的是这一行:

packages/cli/src/utils/schema-migrate.ts:17

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

oclif 的 findCommand 在每次 CLI 调用时都会遍历命令表并 import() 命令模块,所以只要有任何一条命令的 import 链断了,跑任何命令都会为它打一段警告。schema-migrate.ts 被 6 条命令共用,driver-sql 的 dist 一缺,这 6 条就集体加载失败 —— 哪怕你跑的是 dev。

(同文件 16 行的 import type { ManagedDriftEntry, ... } 是纯类型导入,不产生运行时依赖,与本条无关。全 packages/cli/src 里 driver-sql 的生产代码 value import 只有这一处,isInPlaceSchemaWork 也只在本文件用了 3 次:schema-migrate.ts:334/335/379。)

为什么值得记一笔

  1. 12 段噪音没有一段提到真正的原因或修法。 它们指向 meta:resync / migrate:* 这些跟启动毫无关系的命令,读起来像是 CLI 的命令表坏了。

  2. 唯一可执行的那条给的是错误修法。 datasource 'default': connect failed 的两个建议 —— 「Fix the datasource configuration」和「设 OS_ALLOW_DRIVER_CONNECT_FAILURE=1」—— 对这个成因都是错的:datasource 配置是好的;而设了那个开关只会让一个构建不完整的工作区"启动成功",然后对所有请求报错。正确修法是 pnpm build,一句都没提。一次 30 秒的构建被包装成了一场追查。

  3. 它会主动把人带向不存在的 bug。 我在排查途中跑 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.
    

    这些全部是假象。 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 之后全仓 72/72 绿,pnpm dev 零报错启动。

    特意把这段写下来,是因为它极容易被下一个人当成 driver-sql / spec 的真实漂移去"修",从而在正确的代码上改出真的 bug。

复现

# 在一个装了依赖但未构建(或 spec/dist 陈旧)的 worktree 里
rm -rf packages/drivers/*/dist
pnpm dev

建议方向(实现者自选)

  1. 把那一处 value import 改成惰性的(首选,改动最小)。在真正需要的函数里 await import('@objectstack/driver-sql'),或者把 isInPlaceSchemaWork 这个纯谓词挪到不依赖 driver 的模块里。这样 driver 未构建时那 6 条命令仍能被 oclif 发现,12 段噪音直接消失,CLI 对"缺 driver"也更健壮。

  2. 让 datasource 的 fail-fast 认得这个成因。packages/services/service-datasource/src/datasource-connection-service.ts:687-701:当 connect 失败的底层原因是 ERR_MODULE_NOT_FOUND / Cannot find package '…/dist/…' 时,判定为「工作区未构建」并提示 pnpm build,而不是提示改 datasource 配置或设 OS_ALLOW_DRIVER_CONNECT_FAILURE=1 —— 后者对这个成因是有害建议。

3.(可选)根 dev 脚本目前只有 pnpm check:console-sha 一道前置检查,没有任何一步确认工作区已构建。可以像 #5217 建议的那样加一次廉价前置判定,用一句话说清该干什么。

影响面

纯内部工具链 / 本地与 worktree 首启 DX,无用户可见行为变化。CI 不受影响(CI 里 build 永远在前,跑不出这个形态)—— 和 #5217 一样,只有在本地/worktree 里才撞得到,而本地正是你想复现问题的时候。

环境

  • commit e0075962d
  • pnpm 10.31.0(与 packageManager 一致)
  • Node v25.9.0 —— 满足 engines.node >=22.0.0,但 .nvmrc 写的是 22;如实记录以便复现,本条与 Node 版本无关(类型/解析错误在 pnpm install && pnpm build 后全部消失)。

附:修好之后的样子

pnpm install && pnpm build(72/72 绿)之后,pnpm dev 干净启动,只剩两条关于 packages/console/dist 的信息/警告 —— 那是 console 单独构建(pnpm objectui:build)的正常开发态,不在本条范围内。

Activity

  1. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    发现分诊(#4949 纪律):晋级,摘 finding 换 pm:queue;域标签收敛为 domain:cli(摘 domain:devx —— 一单一域,主落点是 packages/cli/src/utils/schema-migrate.ts:17 的顶层 value import,已核 origin/main 仍在)。

    一行理由:方向 1(惰性 import 或把 isInPlaceSchemaWork 纯谓词挪出 driver 依赖)是改动最小、无争议的具体修法,12 段噪音直接消失且 CLI 对缺 driver 更健壮 —— 具体缺陷 + 落点明确 = 可派。范围限方向 1:方向 2 落 packages/services/service-datasource(services 域)、方向 3 落根 dev 脚本(devx 域),若认领后认为值得做,各自独立立单,不作 rider。正文「假漂移」一节请 dev 保留在 PR 说明里 —— 它是防下一个人误修正确代码的疫苗。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 6, 2026
  3. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    认领:PM 循环第 2 轮(cli 车道)
    会话:session_01DWUR56YsttL5sTF72Q75TQ
    分支:claude/issue-5726-cli-lazy-driver-sql-import
    Worktree:objectstack-issue-5726
    域:domain:cli
    文件面:packages/cli/src/utils/schema-migrate.ts(+ 如需,packages/cli 内新增/调整测试)+ changeset(越界即停,报告说明)

    执行注记:①范围限方向 1(分诊裁定):惰性化 :17 的顶层 value import(await import('@objectstack/driver-sql') 于真正需要处,或把 isInPlaceSchemaWork 纯谓词挪到不依赖 driver 的模块——后者若跨包,停手报告);方向 2(service-datasource)/方向 3(根 dev 脚本)⛔ 不做、不作 rider,认为值得做则各自独立立单;②前提已核:origin/main 上 schema-migrate.ts:16-17 双 import 仍在,17 行 value import 即 12 段噪音的放大器,isInPlaceSchemaWork 仅本文件 3 处消费;③issue「假漂移」一节按分诊要求保留进 PR 正文(防后人误修);④前一单 #5713(PR #5771,serve.ts)已合入 main,与本文件不相交,基线取最新 origin/main(de770bf)。


    Generated by Claude Code

  4. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    验收:ACCEPT(cli 车道 PM,session_01DWUR56YsttL5sTF72Q75TQ,2026-08-06 05:5xZ)→ PR #5789

    • 前提成立且已放大:dev 实测噪音面从 issue 记录的 6 条命令涨到 9 条(migrate:meta/migrate:recorded-by/migrate:resume 后加),且危害不止噪音——driver dist 缺失时 9 条命令从 oclif 命令表整体消失(os migrate plan --help → exit 2)。
    • 修法干净:严格方向 1、全部落 packages/cli;loadIsInPlaceSchemaWork() 惰性加载,谓词保持 driver 单一定义(拒绝重抄的理由——os migrate plan omits the datetime storage-convergence work, so the plan understates what apply will do #3954 数据安全标题下的不一致风险——写进了注释与钉子测试);刻意不包 try 的响亮失败取向正确。
    • 证据:mv 掉 dist 的前后实证(9 段→0 段;exit 2→完整 help);3 条源码级钉子(禁静态 driver value import / 依赖必须以动态形式存续防重抄 / async 渲染必 await);反向验证只红该红的那条。
    • CI 亲核:23 检查 0 failure,ESLint conclusion=success;changeset patch 档合理。
    • 界外两方向按分诊指示由 PM 立单:方向 2(service-datasource 认出「工作区未构建」成因)与方向 3(根 dev 脚本前置构建判定)即刻立单转 services / devx 车道,dev 的 9 条实测数据一并转录。

    转 ready + 挂 auto-merge 入队;MERGED 后接续派 #5602(cli 包串行,B-lite 裁决原样派)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions