统一 flag 解析器
packages/ttsc/src/flags/schema.ts(~520 LOC)是 ttsc/ttsx 命令行表面的唯一真相源:一个 flag 一条声明,被每个需要知道这个 flag 的层消费。本页讲 schema 驱动的设计、它生成什么、以及 parser.ts 引擎如何工作。
为什么需要单一 schema
ttsc 的 flag 要穿过多个层:JS launcher → runBuild → tsgo / 原生旁车(host、lint)。如果每层各自硬编码 flag 列表,一个新 flag 要在五个地方改、还容易漏。schema 把它收成一条声明,其余层从生成产物读。
schema.ts:1 注释列出消费者:
生成器从 pnpm format 跑(pnpm gen:flags),committed 产物就是 Go 侧读的 spec——与 gen_shims:hand-maintained 同样的模式。手改生成文件被 format 检查拒绝。
flag 声明模型
每个 flag 声明(FLAG_SCHEMA)带几个维度:
kind(schema.ts:70):boolean(--flag/--flag=true/false,不吃 value token)、value(--flag value或--flag=value,必需值)、valueOptional(未来用)。layer/consumer(schema.ts:53):flag 被哪些层消费——launcher、runBuild、tsgo、host、lint。一个 flag 必须声明至少一个 consumer。forwardTo:当消费层不吸收 flag 时它去哪(如 ttsc 拥有的 flag 被 JS launcher 消费再重新 emit 成不同的 tsgo flag)。terminal:print-and-exit 类 flag(--showConfig、--listFilesOnly、--help),ttsc 不能给它们加 build-only 守卫。internalShadow:ttsc 内部会加给 tsgo 的 flag(--listEmittedFiles、--noEmit、--pretty)。用户也转发同名时,post-processing 要保留用户可见效果而非当 ttsc-internal 噪音剥掉。
TERMINAL_FLAGS 与 INTERNAL_SHADOW_FLAGS(从 FLAG_SCHEMA[*].terminal/internalShadow 派生的集合)被 runBuild.ts 用来做 schema 驱动的判定(forwardsTerminalTsgoFlag、forwardsInternalShadowFlag),而非手维护的 if 列表。
subcommand 模型
TtscSubcommand(schema.ts:26):ttsc/build/check/fix/format/prepare/clean。裸 "ttsc" 覆盖默认 lane(无显式子命令,如 ttsc -p tsconfig.json)。TtsxSubcommand 只有 "ttsx",但 schema 镜像 ttsc 的形状让未来子命令无需第二引擎就能插进来(schema.ts:35 注释)。
每个 flag 声明它在哪些子命令可用,引擎据此决定某子命令下某 flag 是否被识别。
parser.ts 引擎
parseFlags(parser.ts)是运行时解析引擎,被 runTtsc.ts/runTtsx.ts 调。它接受 argv、errorPrefix、subcommand,以及行为开关:
forwardAfterFirstPositional:首个 positional(如 ttsx 的入口文件)之后的 token 进tail(用户程序 argv),不进 passthrough。honorDoubleDashSeparator:--后的 token 进 tail。
返回 { positional, passthrough, tail, values }。访问器 getString/getNumber/getBoolean(parser.ts)从 values 取规范化后的 flag 值。
关键设计:未识别的 flag 进 passthrough 而非报错。schema 不认识 --strict,于是它进 passthrough,最终被 createNativeTsgoArgs 包进 --tsgo-args 转发给 tsgo(见 transform 派发)。这让 ttsc 不必枚举 tsgo 的全部 flag。
几个 schema 驱动判定的实例
runBuild.ts 里多处用 schema 派生集合替代手列表:
forwardsTerminalTsgoFlag(runBuild.ts:321):用户转发了 print-and-exit flag 时,ttsc 不加 compile-only 守卫(命令不会编译)。forwardsInternalShadowFlag(runBuild.ts:339):用户也转发了--listEmittedFiles时,保留 tsgo 的 TSFILE 输出(不当 ttsc-internal 噪音剥掉);--pretty同理。createTsgoDiagnosticArgs(runBuild.ts:516):结构化诊断时加--pretty false,但用户显式转发--pretty时让用户赢。
这些注释都引 RC-2/RC-3/RC-4(issue #125 的 RCA)——它们是把"每个 shadow flag 一个 bespoke if"重构成"一次结构化查找"的成果。
不变量
- 一个 flag 必须声明至少一个 consumer。
- 生成文件(
flags_gen.go、flags.mdx)手改被 format 检查拒绝。 - 未识别 flag 进 passthrough,转发给 tsgo。
- terminal / internalShadow 行为从 schema 派生,不手维护。
维护者提示
- 加 flag:编辑
schema.ts一处,跑pnpm gen:flags(或pnpm format),生成的 Go allow-list 与文档表自动更新。别手改flags_gen.go。 filterHostArgs(cmd/ttsc/filter.go)在 Goflag.Parse前剥掉未知转发 flag,靠生成的 allow-list;新 flag 若该到达某子命令,确保它在 schema 里声明了该 consumer。check:flags(scripts/check-flags.cjs)验证生成产物与 schema 一致,CI 跑它。