Skip to content

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

Closed

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. added
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    and removed on Aug 6, 2026
  2. 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

  3. self-assigned this
    on Aug 6, 2026
  4. 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

  5. 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

Labels

domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpm:dispatchedpm:queue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions