# Astro check 遇上 TypeScript 7：別刪掉型別檢查，先修好相依套件

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

一個 Dependabot 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 6 的程式化 API。微軟在 [TypeScript 7.0 正式發布文章](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/)中也直接建議：Astro、Vue、Svelte、MDX 等嵌入式語言工具鏈，在完成遷移前先維持 TypeScript 6。

先把證據邊界說清楚：我檢查了固定版本的相依檔案、兩份 PR 及原專案 GitHub Actions，也獨立執行了 5 個不依賴第三方套件的 Node.js 合成測試，確認 `exports` 如何造成「套件明明存在卻被判定未安裝」。**我沒有在該專案執行 `npm install` 或 `astro check`。** 下文的 Astro 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)選擇保留 `astro check`，並將 Web workspace 的 TypeScript 相依版本調回：

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

我讀取了修復後的[固定 commit](https://github.com/sivadotblog/vishwakarma/tree/9837d456647d948207941c86244e253594aef9ae)。其中 `apps/web/package.json` 指定 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` 的 peer 相依範圍：

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

這段是**從鎖定檔讀到的相依套件宣告**，不是我執行 npm 後截取的錯誤輸出。對這個明確版本的檢查器而言，TypeScript 7 不在宣告的支援範圍內。

修復後也有公開的執行證據：[GitHub Actions #38103905437](https://github.com/sivadotblog/vishwakarma/actions/runs/38103905437) 使用 PR head `5d828e8d55c5db833f5cca17f28da3b6aba05a99`。Web 工作中的 `make setup-web`、`make check-web`，以及 Python 工作皆成功完成。它支持的是**保留檢查器後的修復狀態**，不代表我親自驗證了 TypeScript 7 的失敗情境。

## 為什麼 TypeScript 明明已安裝，卻可能被誤判不存在？

這裡至少有兩個不同的相容性層次。

更根本的是編譯器 API。[TypeScript 7.0](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/) 改用 Go 實作的原生編譯器。`tsc` 命令列程式能跑，不表示 TypeScript 6 的穩定程式化 API 也完整存在。Astro 需要透過自己的語言服務分析 `.astro` 檔案中的 TypeScript，光升級命令列編譯器並不能自動完成這些整合。

另一層比較容易把人帶錯方向。Astro 上游的 [Issue #17268](https://github.com/withastro/astro/issues/17268) 記錄過，TypeScript 7 調整套件的 `exports` 後，舊程式的 `require.resolve('typescript')` 可能回傳 `ERR_PACKAGE_PATH_NOT_EXPORTED`。如果檢測程式把所有解析例外都歸類成「沒有安裝」，使用者得到的修復提示就會失真。

我不想只根據 Issue 猜這段機制，所以使用 Node.js v22.16.0 的標準函式庫，製作兩個暫時性的**模擬套件**：一個維持舊式可解析的入口，另一個只公開 `./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`。

我在隔離 Linux 環境，以 Node.js v22.16.0 執行 `node --test ./node-exports-resolution.test.cjs`，沒有下載 npm 第三方套件。以下是**真實 Node 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 套件解析機制**，並不代表已執行真正的 TypeScript 7、Astro 或 `astro check`。`package.json` 的回退方法也不是萬用解：如果實際套件沒有公開那個子路徑，同樣可能解析失敗。

就算修好了「是否已安裝」的判斷，也不能憑空補上 TypeScript 7.0 尚未提供的舊 API。Astro 的 [PR #18053](https://github.com/withastro/astro/pull/18053) 在 2026 年 9 月合併，改進了面對 TypeScript 7 時的錯誤說明；相關 Issue 結案**不能解讀成 TypeScript 7.0 已完整支援 Astro 的型別檢查**。

## 真的碰上這個問題，怎麼恢復 CI？

對版本組合相近的 Astro 專案，我會先看目前 TypeScript、`@astrojs/check` 的實際版本、鎖定檔 peer 範圍，以及原始的檢查錯誤。Dependabot 提議升級，不代表整條語言工具鏈都相容。

這個範例採用 npm workspace，名字是 `web`，對應 `apps/web`。下列指令是**讀者可以在自己的修復分支執行**的步驟，依照 [PR #10 的公開修改](https://github.com/sivadotblog/vishwakarma/pull/10)整理；我沒有在原專案執行這組指令：

```bash
# 在專案根目錄確認目前的版本
npm ls typescript @astrojs/check -w web

# 將此案例回復為 TypeScript 6，並更新鎖定檔
npm install --save-dev 'typescript@^6.0.3' -w web

# 按鎖定檔重裝，保留型別檢查與建置
npm ci
npm run check -w web
npm run build -w web
```

發生相依衝突時，`npm ls` 本身就可能以非零狀態結束，這也是有用的診斷線索。執行前請保留原始 commit，或先建立修復分支，並確認 Node/npm 的版本需求及真正的 workspace 名稱。一般單套件專案不需要 `-w web`。`^6.0.3` 只是這個固定案例的解法，**不是所有 Astro 專案都應該永遠固定的版本**。

接著，避免自動升級 PR 不斷把同一個問題帶回來。範例中固定版的 [`.github/dependabot.yml`](https://github.com/sivadotblog/vishwakarma/blob/9837d456647d948207941c86244e253594aef9ae/.github/dependabot.yml) 在 npm 設定內加入：

```yaml
- package-ecosystem: npm
  directory: /
  schedule:
    interval: weekly
  ignore:
    - dependency-name: typescript
      versions: [">=7.0.0"]
```

這條 `ignore` 應放在對應的 **npm** 更新項目底下，不要順便封鎖其他無關的相依套件。還要設定未來重新檢查的條件：等 Astro 的檢查器和語言服務真正支援新整合方式，並由**自己的 CI** 通過後，再移除規則。否則一個暫時性方案很容易變成永久鎖版。

原範例的 `check-web` 會執行格式檢查、`astro check` 和建置。因此，正確驗收至少包括：鎖定檔使用期望的版本、`npm ci` 能重建依賴、`astro check` 真的成功完成、建置也能通過。如果原本已有會觸發 `.astro` 型別錯誤的測試範例，也應確認檢查器仍能抓到它；不要只看整體 CI 顯示綠色。

## TypeScript 7.1 以後，是否還要留在 6？

暫時降版不需要成為永久方案。微軟在 TypeScript 7.0 公布時已提到新的程式化 API 工作，以及 `@typescript/typescript6` 等讓舊工具與新 `tsc` 共存的選擇。但「命令列編譯器可用」、「Astro 範本能分析」、「編輯器語言服務正常」與「CI 型別檢查通過」，是不同的驗收項目。

在這次資料查核時，Astro [#17268](https://github.com/withastro/astro/issues/17268) 已結案、錯誤說明改進的 [PR #18053](https://github.com/withastro/astro/pull/18053) 已合併，而將 `.astro` 交給 TypeScript 7 Content Mapper 的 [PR #17852](https://github.com/withastro/astro/pull/17852) 仍是開放狀態。這只是特定時間點的專案狀態，未來版本發布後需要重新查證。

遇到 `astro check` 被大版本升級打壞，較務實的順序是：先確認錯誤和相依契約，**保留檢查器**，回到相容組合，驗證鎖定檔與 CI，然後再安排下一次遷移。目標不是讓紅色流水線安靜，而是讓原本的型別檢查繼續有效。

如果你還在考慮個人技術部落格該用哪套架構，可以參考[我之前比較 Astro、Markdown 與 GitHub Actions 的文章](https://aitokenhacks.com/posts/astro-vs-wordpress-markdown-github-actions-blog/)；這次要處理的則是選好 Astro 之後，下一次自動依賴更新會不會悄悄拿掉原本的品質把關。
