Skip to content

ci(scripts): 用独立 tsconfig.scripts.json 给 scripts/ 补上类型门 (#3494) - #3498

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3494-scripts-typecheck
Aug 6, 2026
Merged

ci(scripts): 用独立 tsconfig.scripts.json 给 scripts/ 补上类型门 (#3494)#3498
yinlianghui merged 1 commit into
mainfrom
claude/issue-3494-scripts-typecheck

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3494

前提核验(先于实现)

--listFilesOnly 扫遍仓库 76 个 tsconfig,确认 scripts/__tests__/** 不在任何一个 program 里。

但 issue 的表述需要收窄一格 —— 「scripts/ 在零 tsconfig 覆盖内」并不完全成立:

文件 origin/main 上是否被编译
scripts/vite-crypto-stub.ts ✅ 是 —— apps/console/tsconfig.node.json(#3305 接进了 console 的 type-check)
scripts/vite-maplibre-worker.ts ✅ 同上
scripts/__tests__/*.ts(10 个门禁 pin 测试) 零覆盖

也就是说:两个非测试源文件其实有门,10 个 pin 测试一个都没有。issue 的实质结论(门禁 pin 测试自身无类型门)成立,落点不变;这里如实记下差异,避免下一个人按「整个 scripts/ 都没编译」去推断。

错误数:实测 8 条,不是 3 条

#3384 当时测得 3 条。此后 check-control-bytes.test.ts / docs-links-workflow.test.ts / shadcn-local-patches.test.ts 等陆续落地,现况:

lint-workflow.test.ts(57,37):          TS7016  ../../eslint.config.js 无声明
shadcn-local-patches.test.ts(6,1):     TS2578  Unused '@ts-expect-error'
shadcn-local-patches.test.ts(13,8):    TS7016  ../shadcn-local-patches.mjs 无声明
shadcn-local-patches.test.ts(69,68):   TS2345  it.each 回调签名(any[] 溢出)
shadcn-local-patches.test.ts(168,71):  TS2345  同上
shadcn-local-patches.test.ts(184,79):  TS2345  同上
vitest-invocation-guard.test.ts(6,1):  TS2578  Unused '@ts-expect-error'
vitest-invocation-guard.test.ts(11,8): TS7016  ../vitest-invocation-guard.mjs 无声明

顺带查清了那两条 TS2578 的成因:多行 import { … } from '…mjs' 里,TS 把「缺声明」报在说明符所在行(13 / 11 行),而 @ts-expect-error 挂在 import { 上一行 —— 这两个指令从来就没生效过,只是没人编译过所以没人知道。

allowJs 的取舍:两个方向都真跑了

issue 提示「allowJs/checkJs 会翻转既有 @ts-expect-error 的成立性」。对同一批文件实测:

报错数 要付的代价
allowJs: false 8 得给 5 个 .mjs 手写 .d.mts第二份事实来源,可以无声漂移 —— 正是 check-spec-symbol-derivation.mjs 存在的理由。而且 any 会外溢:3 条 TS2345 并非真缺陷,只是 any 的连带
allowJs: true 5 全部是「删掉一行已经变假的注释」。类型从 helper 源码本身推断,构造上无法漂移;patchedComponents() 真的是 string[],3 条 TS2345 自动消失

allowJs: true + checkJs: false。后者是刻意的边界:本项目消费 helper 的推断类型,不接管 8 个纯 JS 门禁脚本内部的类型整洁 —— 那是另一件大得多的事。

副作用是这条现在更强了:改动门禁 helper 的导出签名,它的 pin 测试会红

tsconfig.jsonallowJs: false 不受影响;vitest.config.mts 上同类的 @ts-expect-error 在那里依然正确,未动。

落点

  • tsconfig.scripts.json(新增) —— 独立,刻意不 extends tsconfig.base.json:那是包构建配置,其 exclude 列了 test glob,继承过来会一个测试文件都编不到,空转通过,正是 check-type-check-coverage.mjs 5b 段在上一层要抓的形态。
    • 按 glob 覆盖整个目录,不排除已被 console 覆盖的两个 vite-*.ts:排除清单是第二件要维护的事,而且 console 哪天不再 import 就会无声掉出所有 program。重叠的代价用对齐 console 那份的选项集付掉(strict / ESNext / bundler,以及不开 noImplicitReturns),这样共享文件不会一个项目绿另一个红。
    • 注释用 // 而非 /* */:glob 里的双星紧跟斜杠会提前闭合块注释,而解析失败的 tsconfig 不会响亮报错 —— 它退回默认值,tsc -p 转头去编译整个仓库。这不是假设,是写这个文件时真踩到的,已由 pin 测试钉住。
  • .github/workflows/ci.yml —— type-check job 加一步 pnpm type-check:scripts,放在 install 之后、Turbo 之前:program 里没有任何 @object-ui/* import,不需要 ^build,便宜且快速失败。
  • package.json —— 加 type-check:scripts 根脚本。pnpm type-checkturbo run type-check,结构上够不到无 package.json 的目录;没有这个命名脚本,这道门就只能在 CI 上跑,而本地复现不了的门,人会学会忽略它。(与 type-check:coverage / check:spec-symbols / check:control-bytes 同一惯例。)
  • scripts/__tests__/scripts-type-check.test.ts(新增) —— 断言的是行为不是拼写:配置能否解析、磁盘上每个 scripts/**/*.ts 是否真落进 program、CI 是否真跑、是否排在 install 之后,以及「不需要 workspace 构建」这个放置前提。
  • content/docs/guide/ci-cd-pipeline.md —— job 表里 type-check逐项列出了它跑什么。不更新就会复刻 ci-cd-pipeline.md 的 ci.yml job 表格漂移:写「Seven jobs」并列了一个不存在的 dev-server job(实际 6 个) #3451 那种「页面少报一道门」的漂移,而钉住这张表的 pin 测试恰好就在本 PR 新覆盖的目录里。

验证(方向先声明,再执行)

1. 主方向 —— 预测:改前 RED、改后 GREEN。

改前:tsc -p tsconfig.scripts.json  ->  8 errors(上表)
改后:pnpm type-check:scripts       ->  exit 0

2. 反向核验 A —— 把删掉的某个 @ts-expect-error 装回去。预测 RED,且原因必须是 TS2578(证明这些指令本就已失效,不是我删掉了一道有效的抑制):

render-budget-comment.test.ts(9,1): error TS2578: Unused '@ts-expect-error' directive.

3. 反向核验 B(#3181 绊线) —— 往 ci-cd-pipeline-doc.test.ts 追加一个必假的类型层断言(Assert< Equal< 1, 2 > >)。这里两个方向都要看,因为它们方向相反:

新门:  ci-cd-pipeline-doc.test.ts(381,28): error TS2344: Type 'false' does not satisfy 'true'   <- RED
vitest: Test Files 1 passed (1) / Tests 13 passed (13)                                          <- 依旧全绿

vitest 那一侧的绿才是要点:类型断言在运行时被擦除,跑测试永远看不见它。「pin 测试全绿」+「没有任何 program 读它」= 一个可以钉住已失效契约还照样报绿的测试。

4. pin 测试自身的空转核验 —— 把 include 改窄成 scripts/vite-*.ts。预测 RED 并点名漏掉的文件(而不是空转变绿):

× resolves every TypeScript source under scripts/, with none left out
× really does cover the gate pin tests, by name
  - scripts/__tests__/ci-cd-pipeline-doc.test.ts

5. 回归与门禁

pnpm exec vitest run scripts/__tests__ eslint-rules  ->  15 files / 196 tests passed
ci.yml YAML 解析                                      ->  jobs 键不变,type-check 步序:… install(5) → spec-symbols(6) → Type-check scripts/(7) → Turbo(8) → type-check(9)
node scripts/check-control-bytes.mjs                 ->  OK(3674 文件)
node scripts/check-type-check-coverage.mjs           ->  OK
node scripts/check-lint-coverage.mjs                 ->  OK
node scripts/check-doc-links.mjs                     ->  Docs links are valid
eslint(改动文件)                                     ->  exit 0
控制字符自扫(改动文件,超出 gate 扫描面)             ->  clean

⚠️ 合并顺序:与 #3496 冲突(不是文件冲突,是门禁冲突)

#3496 新增的 scripts/__tests__/shadcn-sync-fetch-cache.test.ts 带着这一行:

// @ts-expect-error — plain-JS CI helper, intentionally untyped
import { fetchUrl, … } from '../shadcn-sync.js';

本 PR 之后,该目录下的 .mjs/.js import 会由 allowJs 推断出真类型,于是这个指令变成 TS2578(Unused),新加的 CI 步骤会红。两个 PR 各自都绿,合并后才红 —— git 不会报冲突。

后合的一方删掉那一行注释即可(与本 PR 对另外 5 处的处理一致)。#3497 只动 shadcn-check.yml,与本 PR 无交集。

无 changeset

纯 CI/工具链改动,不影响任何已发布包;根 package.json 是 private,新增的是根脚本而非依赖(先例 #3437)。


Generated by Claude Code

…on (#3494)

`scripts/` is not a workspace package, so `pnpm type-check` (turbo, which walks
package.json `scripts`) structurally cannot reach it, and
check-type-check-coverage.mjs decides coverage per PACKAGE so it could not see
the gap either. Every file in `scripts/__tests__/` was therefore compiled by
nothing at all - ten pin tests holding ci.yml, docs-links.yml, lint.yml, the
changeset guard, the control-byte scanner and the shadcn local patches in place.

A pin test the compiler never reads can assert a contract that no longer
type-checks and still print green. Measured here: a provably-false type-level
assertion appended to ci-cd-pipeline-doc.test.ts left `vitest run` at 13 passed,
because type assertions are erased at runtime.

- tsconfig.scripts.json: standalone (NOT extending tsconfig.base.json, whose
  `exclude` lists the test globs and would have made the project vacuous),
  strict, noEmit, covering `scripts/**/*.ts` by glob.
- allowJs:true / checkJs:false, chosen by measurement rather than assumption:
  allowJs:false left 8 errors needing hand-written .d.mts files (a second source
  of truth, free to drift); allowJs:true left 5, each a now-false
  `@ts-expect-error` comment, and gives the pin tests types inferred from the
  helper itself. Comments updated accordingly.
- ci.yml `type-check` job runs `pnpm type-check:scripts` after the install; it
  needs no workspace build, so it stays in the cheap, fail-fast half.
- scripts/__tests__/scripts-type-check.test.ts pins the coverage (every .ts on
  disk under scripts/ resolves into the program), that the config parses at all,
  and that CI actually runs it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 6, 2026 1:35pm

Request Review

yinlianghui pushed a commit that referenced this pull request Aug 6, 2026
…ype gate

objectui#3494 (PR #3498) adds `tsconfig.scripts.json` with `allowJs: true`,
whose `include` glob covers this branch's new test file too. Neither PR can
see the other, and the two land green individually while the merge is red —
no git conflict, so nothing warns.

Two separate problems, both found by running #3498's compilerOptions against
this branch:

- the `@ts-expect-error` above the `../shadcn-sync.js` import becomes TS2578
  once the import has an inferred type. Removed (the sibling test files get
  the same treatment inside #3498).
- `fetchRegistry`'s `get` option was the one destructured option without a
  default, so it is absent from the function's inferred signature and passing
  it from a `.ts` caller is TS2353. Fixed at the producer by giving it its
  real default (`https.get`), which also drops the `get ? { get } : undefined`
  dance at the call site — not by suppressing it at the consumer.

Behaviour is unchanged: production still resolves `https.get`, the tests still
inject `http.get`. Verified against a local copy of #3498's config — zero
errors in either file; the only remaining diagnostics are the five stale
directives that #3498 itself removes.

Refs objectstack-ai/objectstack#5803

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@yinlianghui
yinlianghui marked this pull request as ready for review August 6, 2026 13:51
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit f995a45 Aug 6, 2026
17 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3494-scripts-typecheck branch August 6, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ci] scripts/ 在零 tsconfig 覆盖内:turbo type-check 从不检查 scripts/__tests__/*.ts——一批门禁 pin 测试自身无类型门

2 participants