DSH / Atlas
2026-07-26implementedsimplificationarchived 2026-07-27

Consolidate gate scripts on already-present deps and builtins

把门禁脚本统一到已有依赖与内置模块上

The `scripts/` gates mostly used the right tools (`node:fs` `globSync` in 15+ gates, mdast/micromark in the markdown gates), but a handful of stragglers hand-rolled what a sibling gate already did with an existing dependency or builtin: - **Duplicated fence scanners.** `scripts/md-fences.ts` (~55 lines, consumed by `doc-typecheck.ts`) and `extractEquivBlocks` in `scripts/verify-type-equiv.ts` (~39 lines) were two cop

English

Problem

The scripts/ gates mostly used the right tools (node:fs globSync in 15+ gates, mdast/micromark in the markdown gates), but a handful of stragglers hand-rolled what a sibling gate already did with an existing dependency or builtin:

  • Duplicated fence scanners. scripts/md-fences.ts (~55 lines, consumed by doc-typecheck.ts) and extractEquivBlocks in scripts/verify-type-equiv.ts (~39 lines) were two copies of the same regex line-scanner for fenced code blocks, while scripts/verify-mermaid.ts already extracted fences by visiting mdast code nodes — and markdownProseLines in scripts/markdown.ts itself parsed to mdast but then hand-tracked fence state with a second regex. The regex scanners only recognized backtick fences at column 0, so they silently disagreed with the mdast-based gates on tilde and indented fences.
  • Hand-rolled argv parsing. parseOptions in scripts/publint-all.ts and its near-identical copy in scripts/verify-built-package-invariants.mjs (~26 lines) stepped argv indexes manually, while sibling scripts (verify-runtime-closure.ts, build-exe-for-python-sdk.ts, packages/sdk/scripts/src/args.ts) already used the node:util parseArgs builtin.
  • Hand-rolled directory walks. Five sites re-derived nested readdirSync walks that globSync covers: verify-runtime-closure.ts (packages + vendor manifests), dev-web.ts discoverPluginDirs, verify-package-paths.ts realPackageNames, verify-client-domain-graph.ts listSources, and publint-all.ts addPath (~55–65 lines total). scripts/package-invariants.ts shows the one-line globSync template.

No new dependency was needed anywhere; every replacement is an existing devDep or a Node builtin.

Decision

  • A shared mdast fence helper, markdownFences in scripts/markdown.ts, visits code nodes for the language, full info string, body, and 1-based opening-fence line; doc-typecheck.ts and verify-type-equiv.ts extract fences through it. md-fences.ts and the duplicated extractEquivBlocks scanner are deleted, and markdownProseLines derives fenced lines from the parsed code nodes' positions instead of a second regex.
  • Both CLIs parse argv via parseArgs; unknown options and missing values still fail loud, with parseArgs's own error text instead of the bespoke usage strings.
  • The five straggler walks use globSync. The walks in check-workspace-constraints.ts and clean.ts stay: they need dirent-level detail to diagnose malformed trees, which glob-by-pattern cannot report.

Alternatives considered

  • A new glob/walking dependency (tinyglobby, fdir). Rejected: the builtin already won repo-wide; these were stragglers, not a gap.
  • p-map for publint-all.ts's ~19-line ordered worker pool. Deliberately left out: one new devDep for one small deletion is at the edge of the dependency policy bar, and the pool's requirements (bounded workers, deterministic order, env override) are documented in the parallel-gates note. Fold it in only if p-map earns a second consumer.
  • Leaving the fence scanners. Rejected: two drifting copies of a parser beside a third correct implementation is exactly the duplication the shared markdown.ts helper exists to prevent, and the column-0-backtick-only limitation was a latent inconsistency between sibling gates.

Consequences

  • One fence parser: every markdown gate now classifies fences through mdast, so tilde, indented, and 4-backtick container fences behave identically everywhere. The docs tree contained no fence shape the regex scanners mishandled, so gate results are unchanged on the tree that landed the swap: pnpm run doc-sync and each rewritten gate ran before and after with byte-identical output (doc-typecheck block/opt-out counts, verify-type-equiv match counts, publint, verify-built-package-invariants, verify-runtime-closure, verify-package-paths, verify-client-domain-graph, and both package-README prose gates).
  • verify-type-equiv still rejects an unterminated type-equivalence fence: mdast silently closes an unterminated block at end-of-file (its comparisons could then pass), so the shared helper reports whether a closing delimiter exists and the gate errors on an unclosed block, preserving the removed scanner's rejection. The doc-typecheck scanner never had that error path.
  • parseArgs keeps the last value of a duplicated option instead of erroring — a dev-tool edge case the tests don't pin, accepted in exchange for deleting the two bespoke parsers. (Strict mode still rejects a ---prefixed token where a value is expected, matching the replaced parsers.)

中文

问题

scripts/ 下的门禁大多本已在用正确的工具(15 个以上的门禁使用 node:fsglobSync,markdown 门禁使用 mdast/micromark),但少数几个掉队的脚本曾手写同类门禁早已用既有依赖或内置模块完成的事情:

  • 重复的围栏扫描器。scripts/md-fences.ts(约 55 行,由 doc-typecheck.ts 消费)和 scripts/verify-type-equiv.ts 中的 extractEquivBlocks(约 39 行)曾是同一个围栏代码块正则行扫描器的两份拷贝,而 scripts/verify-mermaid.ts 早已通过访问 mdast code 节点来提取代码围栏;scripts/markdown.ts 自己的 markdownProseLines 也曾先解析成 mdast,再用第二个正则手工跟踪围栏状态。这两个正则扫描器只识别第 0 列的反引号围栏,因此在波浪线围栏和缩进围栏上与基于 mdast 的门禁悄悄不一致。
  • 手写的 argv 解析。scripts/publint-all.ts 中的 parseOptionsscripts/verify-built-package-invariants.mjs 中与之几乎相同的拷贝(约 26 行)曾手工推进 argv 下标,而同类脚本(verify-runtime-closure.tsbuild-exe-for-python-sdk.tspackages/sdk/scripts/src/args.ts)早已在使用 node:util 的内置 parseArgs
  • **手写的目录遍历。**五处代码曾各自重写 globSync 已覆盖的嵌套 readdirSync 遍历:verify-runtime-closure.ts 对 packages 与 vendor manifest(元数据清单)的扫描、dev-web.tsdiscoverPluginDirsverify-package-paths.tsrealPackageNamesverify-client-domain-graph.tslistSources,以及 publint-all.tsaddPath(合计约 55–65 行)。scripts/package-invariants.ts 展示了一行式的 globSync 模板。

所有替换都不需要引入新依赖;每一处替换用的都是既有的 devDependency 或 Node 内置模块。

决策

  • scripts/markdown.ts 中的共享 mdast 围栏辅助函数 markdownFences 访问 code 节点,读取语言、完整 info string、块体以及以 1 起始的开围栏行号;doc-typecheck.tsverify-type-equiv.ts 通过它提取代码围栏。md-fences.ts 和重复的 extractEquivBlocks 扫描器已删除,markdownProseLines 也改为从解析出的 code 节点位置推导围栏内的行,而不再用第二个正则。
  • 两个 CLI 都改用 parseArgs 解析 argv;未知选项和缺失取值仍然大声失败,只是错误文案换成了 parseArgs 自带的文本,而非原先手写的用法字符串。
  • 那五处掉队的目录遍历改用 globSynccheck-workspace-constraints.tsclean.ts 中的遍历保留:它们需要 dirent 级别的细节来诊断结构异常的目录树,按模式匹配的 glob 报告不了这些信息。

曾考虑的替代方案

  • **新的 glob/目录遍历依赖(tinyglobbyfdir)。**不予采纳:内置模块已在全仓库范围内胜出;这几处只是掉队者,不是能力缺口。
  • **用 p-map 替换 publint-all.ts 中约 19 行的有序 worker 池。**刻意未纳入:为一次小删除引入一个新 devDependency,正处在依赖策略门槛的边缘,而且该池的需求(worker 数量有界、确定性顺序、环境变量覆盖)已记录在并行 pre-push 门禁决策记录中。仅当 p-map 赢得第二个消费方时再顺带纳入。
  • **保留这两个围栏扫描器。**不予采纳:在第三个正确实现旁边放着两份逐渐漂移的解析器拷贝,正是共享的 markdown.ts 辅助函数要防止的那种重复;「只认第 0 列反引号」的限制也是同类门禁之间的潜在不一致。

后果

  • 只剩一个围栏解析器:所有 markdown 门禁现在都经由 mdast 归类代码围栏,因此波浪线围栏、缩进围栏和四反引号容器围栏在各处的行为完全一致。文档树中不存在正则扫描器处理有误的围栏形态,所以在落地这次替换的代码树上门禁结果不变:pnpm run doc-sync 及每个被改写的门禁在改动前后各跑一遍,输出逐字节相同(doc-typecheck 的块数/opt-out 计数、verify-type-equiv 的匹配计数、publintverify-built-package-invariantsverify-runtime-closureverify-package-pathsverify-client-domain-graph,以及两个包 README 散文门禁)。
  • verify-type-equiv 仍然拒绝未闭合的类型等价围栏:mdast 会在文件末尾静默闭合未闭合的代码块(其比较随后可能通过),因此共享辅助函数会报告闭合定界符是否存在,门禁在块未闭合时报错,保留了被删扫描器的这条拒绝路径。doc-typecheck 的扫描器本来就没有这条错误路径。
  • parseArgs 对重复出现的选项保留最后一个值而不报错——一个测试未固定的开发工具边缘用例,作为删除两份手写解析器的交换被接受。(严格模式下,需要取值处遇到以 -- 开头的 token 仍会拒绝,与被替换的解析器行为一致。)