Astro check 遇到 TypeScript 7:别删类型检查,先修复依赖
一个依赖升级 PR,把 TypeScript 从 6.0.3 升到了 7.0.2。看版本号,似乎只是一次常规的大版本更新;对使用 Astro 的项目来说,astro check 却可能因此跑不下去。
我在一份公开的 Astro / Starlight 项目记录里看到两条很不一样的修复路线。一条是删掉 astro check,让 CI 不再执行出问题的检查;另一条是把 TypeScript 退回 6,保留原来的检查,并暂时让 Dependabot 别再自动提出 7.x 升级。最后合并的是后者。
这件事的关键不只是某个包的 peer dependency 没跟上。TypeScript 7.0 换成了原生编译器,一部分依赖旧 TypeScript API 的语言工具还需要时间适配。微软在 TypeScript 7.0 正式发布说明中也明确提醒:Astro、Vue、Svelte、MDX 这类嵌入式语言工作流,暂时应继续使用 TypeScript 6。
先交代证据范围:我核对了固定版本的依赖文件、两份修复 PR 和原项目 CI;另外用 Node.js 标准库独立运行了 5 个合成测试,验证 exports 如何把“包已经安装”误判成“包不存在”。我没有在这个项目里运行 npm install 或 astro check,因此后面的 CI 成功属于原项目的运行结果,不是我的 Astro 复现。
两种修法:删掉检查,还是恢复兼容版本
这个案例来自 vishwakarma Issue #8。前面的 Dependabot PR #3把 TypeScript 升到了 7.0.2;维护记录报告升级后 astro check 出现兼容问题。
PR #9 曾提出一种简单处理:从 apps/web/package.json 移除 check 命令与 @astrojs/check 依赖,同时删去 Makefile 中的 typecheck-web,让 check-web 只剩格式检查和构建。这个 PR 后来关闭,没有合并。
光看构建有没有成功,很容易觉得这样就够了。但 astro build 能成功,并不能替代 astro check 对 .astro 和 TypeScript 代码的类型诊断。把失败的检查从流水线删掉,失去的是原本能拦住问题的那道检查。
后来合并的 PR #10没有拆掉检查,而是把 apps/web 的 TypeScript 依赖改回去:
- "typescript": "^7.0.2"
+ "typescript": "^6.0.3"
我读了这次修复后的固定 commit。项目使用 Astro ^7.2.10、@astrojs/check ^0.9.10;而同一个 package-lock.json 锁定的 @astrojs/[email protected] 明确声明:
"peerDependencies": {
"typescript": "^5.0.0 || ^6.0.0"
}
这里引用的是锁文件里的依赖声明,不是我本地安装后得到的报错日志。它至少说明,这个具体的检查器版本并未把 TypeScript 7 纳入支持范围。
原项目修复 PR 的 GitHub Actions #38103905437 也值得看。它运行在 PR head 5d828e8d55c5db833f5cca17f28da3b6aba05a99 上,Web job 的 make setup-web、make check-web,以及 Python job 都成功完成。这给“保留检查器并退回兼容版本”的修复后状态提供了第三方 CI 证据;它没有证明 TypeScript 7 的失败路径已被我独立复现。
为什么有时明明安装了 TypeScript,却被提示没安装?
兼容性其实有两层。
更深的一层来自编译器架构。TypeScript 7.0 使用 Go 实现的原生工具链,tsc 命令本身能够工作,但 7.0 尚未提供 TypeScript 6 那套稳定的程序化 API。像 Astro 这种要把 TypeScript 嵌入到自己的语言服务、分析 .astro 文件的工具,不能因为命令行 tsc 已经升级,就自动拥有可用的集成接口。
另一层更容易误导排障方向。Astro 上游的 Issue #17268 记录过:TypeScript 7 的包导出方式变化后,旧检测逻辑中的 require.resolve('typescript') 可能遇到 ERR_PACKAGE_PATH_NOT_EXPORTED。如果工具把所有解析错误都归成“没安装”,就可能给用户显示错误的修复提示。
我想把这个细节弄清楚,所以没有去下载完整 Astro 或 TypeScript,而是用 Node.js v22.16.0 的标准库造了两个临时假包。一个模拟旧式的可解析入口;另一个在 package.json 里只公开 ./package.json 这个子路径,不导出包根路径。下面是测试中实际使用的关键配置摘录,做了排版整理:
{
name: 'native-ts-synthetic',
version: '7.0.0-fixture',
type: 'module',
exports: {
'./package.json': './package.json'
}
}
对这个人工生成的假包调用 require.resolve('native-ts-synthetic'),会得到 ERR_PACKAGE_PATH_NOT_EXPORTED;但文件明明存在。由于这个假包明确导出了 ./package.json,改查 native-ts-synthetic/package.json 就能定位到它;完全不存在的包则给出 MODULE_NOT_FOUND。
实际执行命令是 node --test ./node-exports-resolution.test.cjs,运行环境为隔离 Linux / Node.js v22.16.0,无 npm 第三方依赖。下面是原始 TAP 日志的精简摘录,不是伪造的 Astro 错误:
# OBSERVED: legacy package resolution = FOUND
# OBSERVED: package exists; require.resolve(root) = ERR_PACKAGE_PATH_NOT_EXPORTED
# OBSERVED: require.resolve(package.json) = FOUND
# OBSERVED: absent package = MODULE_NOT_FOUND in both paths
# OBSERVED: catch-all = false missing; code-aware fallback = detected installed
# tests 5
# pass 5
# fail 0
这 5 个测试全部通过,只能说明 Node.js 的包导出限制确实会让一个“已存在”的包无法从根入口解析,并验证了一个过度简化的“缺包检测”为什么会误判。package.json 这个回退路径也不是通用解法:若实际包没有导出它,同样可能失败。
更重要的是,修好“有没有安装”的检测,只解决提示信息这一层。它不会凭空补出 TypeScript 7.0 缺失的程序化 API。Astro 的 PR #18053 在 2026 年 9 月合并,改进了 astro check 对 TypeScript 7 的报错说明;相关 Issue 关闭不能据此理解成 7.0 版的完整兼容已解决。
真的遇到这个问题,可以怎样恢复 CI
如果你维护的是相同或相近版本的 Astro 项目,我会先确认当前项目到底装了哪个 TypeScript、哪个 @astrojs/check,以及锁文件里实际声明了什么;不要只根据 Dependabot 的“可以升级”就认定整条工具链都兼容。
在这个公开项目里,package.json 的 npm workspace 名字是 web,对应目录 apps/web。下面是一组供读者在自己的修复分支执行的操作,参考了 PR #10 的真实修改记录;我没有在原仓库替读者执行这些命令:
# 在仓库根目录,先看当前依赖
npm ls typescript @astrojs/check -w web
# 恢复这个案例的兼容版本,并让 npm 同步修改锁文件
npm install --save-dev 'typescript@^6.0.3' -w web
# 以锁文件重新安装,再分别检查 .astro 类型和构建
npm ci
npm run check -w web
npm run build -w web
npm ls 在依赖冲突时自身也可能返回非零,正好可作为排查线索。执行前先保留原始 commit 或单独开分支,确认 Node/npm 版本和应用的实际 workspace 名;普通单包项目不需要 -w web。^6.0.3 是这个固定样本的选择,不是给所有 Astro 项目的永久版本处方。
然后在 Dependabot 的 npm 更新配置里加一道临时限制。这个样本的 .github/dependabot.yml 使用的是:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
ignore:
- dependency-name: typescript
versions: [">=7.0.0"]
注意 ignore 放在 npm 的更新项下,别顺手屏蔽整个仓库其他依赖。修完之后也要给这条规则留下回访条件:当 Astro 及相关检查器明确支持新的 TypeScript 集成方式,并且自己的 CI 真正跑通了,再移除屏障。否则“临时规避”会慢慢变成永远不升级。
这个例子里,原项目的 Makefile 将格式检查、astro check 与构建放在 check-web 里。更稳妥的验收应该同时看:锁文件是否回到期望版本、npm ci 能否复现安装、astro check 是否真的执行成功、构建是否仍能完成。如果你原来有针对 .astro 的错误夹具,还应确认它仍然能被类型检查抓出来;只看一个绿色的总状态是不够的。
TypeScript 7.1 之后,还需要继续固定在 6 吗?
不需要把临时降级当成永久路线。微软在 TypeScript 7.0 发布时已经提到:会继续提供新的程序化 API,并通过 @typescript/typescript6 等方案支持旧工具和新 tsc 并行。只是“编译器能单独跑”与“Astro 的模板、编辑器、语言服务、CI 全部迁好”依然是不同的检查项。
截至这次资料核对,Astro 的 #17268 已关闭,报错指导的 #18053 已合并;涉及将 .astro 交给 TypeScript 7 Content Mapper 的 #17852 仍处于开放状态。这些是特定时间点的公开项目状态,后续新版本发布后要重新确认,不宜用一条旧 Issue 决定未来所有项目的升级路线。
对已经遇到 astro check 失败的人,眼下比较实际的处理顺序是:核对错误与依赖契约,保留检查,退回兼容组合,验证锁文件和 CI,再单独规划下一次升级。这样解决的是当前工程能否继续受检查,而不是只让一条红色流水线安静下来。
如果你还在考虑个人技术博客采用哪套内容架构,可以再看我之前对 Astro、Markdown 和 GitHub Actions 的取舍;而这里要处理的,是选定 Astro 之后,怎样不被下一次自动依赖更新悄悄削掉原有的校验能力。