# Astro check 遇到 TypeScript 7：别删类型检查，先修复依赖

> Canonical: https://vxi.me/posts/astro-check-typescript-7-compatibility/
> Locale: zh-cn
> Published: 2026-10-11
> Updated: 2026-10-11
> Markdown: https://vxi.me/posts/astro-check-typescript-7-compatibility.md

一个依赖升级 PR，把 TypeScript 从 6.0.3 升到了 7.0.2。看版本号，似乎只是一次常规的大版本更新；对使用 Astro 的项目来说，`astro check` 却可能因此跑不下去。

我在一份公开的 [Astro / Starlight 项目记录](https://github.com/sivadotblog/vishwakarma/issues/8)里看到两条很不一样的修复路线。一条是删掉 `astro check`，让 CI 不再执行出问题的检查；另一条是把 TypeScript 退回 6，保留原来的检查，并暂时让 Dependabot 别再自动提出 7.x 升级。最后合并的是后者。

这件事的关键不只是某个包的 peer dependency 没跟上。TypeScript 7.0 换成了原生编译器，一部分依赖旧 TypeScript API 的语言工具还需要时间适配。微软在 [TypeScript 7.0 正式发布说明](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/)中也明确提醒：Astro、Vue、Svelte、MDX 这类嵌入式语言工作流，暂时应继续使用 TypeScript 6。

先交代证据范围：我核对了固定版本的依赖文件、两份修复 PR 和原项目 CI；另外用 Node.js 标准库独立运行了 5 个合成测试，验证 `exports` 如何把“包已经安装”误判成“包不存在”。**我没有在这个项目里运行 `npm install` 或 `astro check`，因此后面的 CI 成功属于原项目的运行结果，不是我的 Astro 复现。**

## 两种修法：删掉检查，还是恢复兼容版本

这个案例来自 [vishwakarma Issue #8](https://github.com/sivadotblog/vishwakarma/issues/8)。前面的 [Dependabot PR #3](https://github.com/sivadotblog/vishwakarma/pull/3)把 TypeScript 升到了 7.0.2；维护记录报告升级后 `astro check` 出现兼容问题。

[PR #9](https://github.com/sivadotblog/vishwakarma/pull/9) 曾提出一种简单处理：从 `apps/web/package.json` 移除 `check` 命令与 `@astrojs/check` 依赖，同时删去 Makefile 中的 `typecheck-web`，让 `check-web` 只剩格式检查和构建。这个 PR 后来关闭，**没有合并**。

光看构建有没有成功，很容易觉得这样就够了。但 `astro build` 能成功，并不能替代 `astro check` 对 `.astro` 和 TypeScript 代码的类型诊断。把失败的检查从流水线删掉，失去的是原本能拦住问题的那道检查。

后来合并的 [PR #10](https://github.com/sivadotblog/vishwakarma/pull/10)没有拆掉检查，而是把 `apps/web` 的 TypeScript 依赖改回去：

```diff
- "typescript": "^7.0.2"
+ "typescript": "^6.0.3"
```

我读了这次修复后的[固定 commit](https://github.com/sivadotblog/vishwakarma/tree/9837d456647d948207941c86244e253594aef9ae)。项目使用 Astro `^7.2.10`、`@astrojs/check` `^0.9.10`；而同一个 [`package-lock.json`](https://github.com/sivadotblog/vishwakarma/blob/9837d456647d948207941c86244e253594aef9ae/package-lock.json) 锁定的 `@astrojs/check@0.9.10` 明确声明：

```json
"peerDependencies": {
  "typescript": "^5.0.0 || ^6.0.0"
}
```

这里引用的是**锁文件里的依赖声明**，不是我本地安装后得到的报错日志。它至少说明，这个具体的检查器版本并未把 TypeScript 7 纳入支持范围。

原项目修复 PR 的 [GitHub Actions #38103905437](https://github.com/sivadotblog/vishwakarma/actions/runs/38103905437) 也值得看。它运行在 PR head `5d828e8d55c5db833f5cca17f28da3b6aba05a99` 上，Web job 的 `make setup-web`、`make check-web`，以及 Python job 都成功完成。这给“保留检查器并退回兼容版本”的**修复后状态**提供了第三方 CI 证据；它没有证明 TypeScript 7 的失败路径已被我独立复现。

## 为什么有时明明安装了 TypeScript，却被提示没安装？

兼容性其实有两层。

更深的一层来自编译器架构。[TypeScript 7.0](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/) 使用 Go 实现的原生工具链，`tsc` 命令本身能够工作，但 7.0 尚未提供 TypeScript 6 那套稳定的程序化 API。像 Astro 这种要把 TypeScript 嵌入到自己的语言服务、分析 `.astro` 文件的工具，不能因为命令行 `tsc` 已经升级，就自动拥有可用的集成接口。

另一层更容易误导排障方向。Astro 上游的 [Issue #17268](https://github.com/withastro/astro/issues/17268) 记录过：TypeScript 7 的包导出方式变化后，旧检测逻辑中的 `require.resolve('typescript')` 可能遇到 `ERR_PACKAGE_PATH_NOT_EXPORTED`。如果工具把所有解析错误都归成“没安装”，就可能给用户显示错误的修复提示。

我想把这个细节弄清楚，所以没有去下载完整 Astro 或 TypeScript，而是用 Node.js v22.16.0 的标准库造了两个临时假包。一个模拟旧式的可解析入口；另一个在 `package.json` 里只公开 `./package.json` 这个子路径，不导出包根路径。下面是测试中实际使用的关键配置摘录，做了排版整理：

```js
{
  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 错误：

```text
# 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](https://github.com/withastro/astro/pull/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 的真实修改记录](https://github.com/sivadotblog/vishwakarma/pull/10)；我没有在原仓库替读者执行这些命令：

```bash
# 在仓库根目录，先看当前依赖
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`](https://github.com/sivadotblog/vishwakarma/blob/9837d456647d948207941c86244e253594aef9ae/.github/dependabot.yml) 使用的是：

```yaml
- 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](https://github.com/withastro/astro/issues/17268) 已关闭，报错指导的 [#18053](https://github.com/withastro/astro/pull/18053) 已合并；涉及将 `.astro` 交给 TypeScript 7 Content Mapper 的 [#17852](https://github.com/withastro/astro/pull/17852) 仍处于开放状态。这些是特定时间点的公开项目状态，后续新版本发布后要重新确认，不宜用一条旧 Issue 决定未来所有项目的升级路线。

对已经遇到 `astro check` 失败的人，眼下比较实际的处理顺序是：核对错误与依赖契约，保留检查，退回兼容组合，验证锁文件和 CI，再单独规划下一次升级。这样解决的是当前工程能否继续受检查，而不是只让一条红色流水线安静下来。

如果你还在考虑个人技术博客采用哪套内容架构，可以再看[我之前对 Astro、Markdown 和 GitHub Actions 的取舍](https://aitokenhacks.com/posts/astro-vs-wordpress-markdown-github-actions-blog/)；而这里要处理的，是选定 Astro 之后，怎样不被下一次自动依赖更新悄悄削掉原有的校验能力。
