# TypeScript 7 업그레이드로 Astro check가 실패할 때: 타입 검사를 지우지 말고 의존성을 복구하자

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

Dependabot이 TypeScript를 6.0.3에서 7.0.2로 올리는 PR을 만들었다. 버전 번호만 보면 평범한 메이저 업데이트다. 하지만 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에 의존한다. Microsoft도 [TypeScript 7.0 공식 발표](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/)에서 Astro, Vue, Svelte, MDX와 같은 임베디드 언어 도구 체인은 마이그레이션이 끝나기 전까지 TypeScript 6을 유지하도록 권고했다.

증거의 범위부터 분명히 하자. 특정 커밋에 고정된 의존성 파일과 두 PR, 원본 프로젝트의 GitHub Actions를 확인했다. 별도로 Node.js 표준 라이브러리만 사용한 합성 테스트 5개를 직접 실행해, `exports` 설정 때문에 "설치된 패키지를 찾을 수 없다"고 오진하는 현상을 검증했다. **그러나 원본 Astro 프로젝트에서 `npm install`이나 `astro check`는 실행하지 않았다.** 아래에서 언급하는 성공적인 Astro CI는 원본 프로젝트의 실행 결과이며 내가 독립적으로 재현한 결과가 아니다.

## 검사 자체를 없앨까, 호환되는 버전으로 복원할까?

사건은 [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은 **병합되지 않고 닫혔다**.

실패하던 검사를 실행하지 않으면 CI가 녹색으로 바뀔 수는 있다. 하지만 `astro build`가 성공했다는 사실은 `astro check`가 제공하던 `.astro` 템플릿 및 TypeScript 타입 진단이 정상이라는 뜻이 아니다. 업그레이드가 어렵다는 이유로 기존의 품질 게이트를 없애는 건 문제를 해결한 것과 다르다.

최종 병합된 [PR #10](https://github.com/sivadotblog/vishwakarma/pull/10)은 검사기를 보존하면서 Web workspace의 TypeScript 버전을 원래 호환 조합으로 되돌렸다.

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

수정 이후의 [고정 커밋](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`이 선언한 TypeScript peer 범위는 다음과 같다.

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

이는 **실제 lockfile의 선언을 읽은 결과**다. 내가 npm을 실행해 수집한 오류 로그가 아니다. 적어도 이 버전의 검사기는 TypeScript 7을 지원한다고 선언하지 않았다.

복원한 구성이 실제로 CI를 통과했다는 제3자 증거도 있다. [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의 [상위 이슈 #17268](https://github.com/withastro/astro/issues/17268)에는 TypeScript 7 패키지의 `exports` 변경으로 이전 검사 로직인 `require.resolve('typescript')`가 `ERR_PACKAGE_PATH_NOT_EXPORTED`를 낼 수 있다고 기록돼 있다. 모든 모듈 해석 예외를 "TypeScript 미설치"로 처리하면 사용자는 엉뚱한 문제를 고치려고 시간을 쓰게 된다.

이 메커니즘만큼은 직접 확인하고 싶었다. Astro나 실제 TypeScript 7을 설치하지 않고 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 환경에서 npm 서드파티 패키지 없이 `node --test ./node-exports-resolution.test.cjs`를 실행했다. 아래는 **실제 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`을 대체 경로로 사용하는 것도 만능은 아니다. 실제 패키지가 해당 하위 경로를 export하지 않았다면 이 방법 역시 실패한다.

미설치 오진을 수정하는 것만으로 TypeScript 7.0에 없는 기존 프로그래밍 API가 생기는 것도 아니다. Astro의 [PR #18053](https://github.com/withastro/astro/pull/18053)은 2026년 9월 병합되어 TypeScript 7 관련 오류 안내를 개선했다. 해당 이슈가 닫혔다는 사실을 TypeScript 7.0의 Astro 타입 검사 완전 지원으로 해석해서는 안 된다.

## 영향받은 Astro 프로젝트의 CI를 복구하는 방법

우선 현재 설치된 TypeScript와 `@astrojs/check` 버전, lockfile의 peer 조건, 실제 검사 오류를 확인하는 편이 좋다. Dependabot이 메이저 업그레이드를 제안했다고 해서 프레임워크의 모든 언어 도구까지 준비됐다는 보장은 없다.

이 공개 프로젝트는 `apps/web` 아래 `web`이라는 npm workspace를 사용한다. 다음 명령은 [실제 PR #10](https://github.com/sivadotblog/vishwakarma/pull/10)을 참고해 정리한 **독자가 자신의 수정 브랜치에서 실행할 수 있는 절차**다. 원본 프로젝트에 대해 내가 실행한 명령은 아니다.

```bash
# 현재 의존성 확인
npm ls typescript @astrojs/check -w web

# 이 사례에서 선택한 호환 버전으로 복원
npm install --save-dev 'typescript@^6.0.3' -w web

# lockfile을 기준으로 재설치하고 검사·빌드 수행
npm ci
npm run check -w web
npm run build -w web
```

의존성 충돌이 있으면 `npm ls` 자체가 0이 아닌 종료 코드를 낼 수 있으며, 이것도 조사에 도움이 되는 신호다. 원래 커밋을 보존하거나 새 수정 브랜치를 만든 뒤 Node/npm 요구 버전과 실제 workspace 이름을 확인하자. 단일 패키지 프로젝트에서는 `-w web`이 필요 없다. `^6.0.3`은 **이 특정 버전 조합의 대응책**이지 모든 Astro 프로젝트의 영구 처방이 아니다.

동일한 자동 업데이트가 다시 들어오는 일도 막아야 한다. 고정 커밋의 [`.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`, 빌드가 포함된다. 수정 검증은 lockfile이 의도한 버전을 담았는지, `npm ci`가 재현 가능한 설치를 수행하는지, `astro check`가 실제로 실행되는지, 빌드도 정상인지 각각 확인해야 한다. 기존에 `.astro` 타입 오류를 감지하는 테스트가 있다면, 여전히 그 오류를 발견하는지도 확인하는 편이 더 확실하다.

## TypeScript 7.1 이후에는 버전 고정을 풀어도 될까?

버전을 잠시 낮췄다고 해서 새로운 컴파일러를 영원히 피해야 하는 것은 아니다. Microsoft는 TypeScript 7.0 발표 당시 새로운 프로그래밍 API와 `@typescript/typescript6`처럼 기존 도구와 최신 `tsc`를 공존시키는 방법을 설명했다. 다만 `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)는 열려 있었다. 이 내용은 특정 시점의 소스 상태이므로 향후 버전의 지원 상황까지 보증하지 않는다.

TypeScript 메이저 업데이트 뒤 `astro check`가 실패했다면 먼저 오류와 의존성 계약을 확인하고 **검사기를 유지한 채** 호환되는 조합으로 복원한 다음 lockfile과 CI를 검증하자. 새로운 방식으로의 마이그레이션은 따로 계획하면 된다. 중요한 것은 빨간 배지를 없애는 게 아니라, 원래 있던 타입 검사가 계속 제 역할을 하도록 만드는 일이다.

왜 개인 기술 블로그에 Astro를 선택했는지 궁금하다면 이전에 쓴 [Astro·Markdown·GitHub Actions 비교 글](https://aitokenhacks.com/posts/astro-vs-wordpress-markdown-github-actions-blog/)도 참고할 수 있다. 이번 사례는 Astro를 선택한 *후에* 자동 의존성 업데이트가 기존 품질 게이트를 조용히 없애지 않도록 하는 문제다.
