Pular para o conteúdo
Sunstone Apps
← Voltar para o blog
Astro e pnpm

Quando astro check diz que falta dependência, mas o bug real é um override do pnpm

O erro parecia dependência ausente. O Astro pedia para instalar @astrojs/check e TypeScript, ambos já declarados no pacote do site. A correção não foi instalar de novo. Foi remover um override global do pnpm que fazia o language server do Astro carregar uma versão incompatível de vscode-languageserver-protocol.

Publicado: 8 min read
Imagem de prévia OpenGraph deste artigo. Quando astro check diz que falta dependência, mas o bug real é um override do pnpm

A parte enganosa desse bug era o prompt.

Rodar pnpm --filter @sites/sunstoneapps.com typecheck deveria ser rotina. O script só executa astro check para o site Astro. Em vez disso, o Astro parava e pedia para instalar dependências:

To continue, Astro requires the following dependency to be installed: @astrojs/check.

Astro will run the following command:
pnpm add @astrojs/check typescript

Essa mensagem fez os primeiros minutos parecerem óbvios. Talvez o pacote do site declarasse as dependências, mas o node_modules estivesse incompleto. Talvez pnpm install tivesse rodado em modo produção. Talvez o linker hoisted não tivesse criado os links locais. Cada hipótese era plausível o bastante para gastar tempo.

Todas estavam erradas.

sites/sunstoneapps.com/package.json já tinha os dois pacotes em devDependencies:

{
  "devDependencies": {
    "@astrojs/check": "^0.9.9",
    "typescript": "~6.0.3"
  }
}

O lockfile também conhecia esses pacotes. pnpm --filter @sites/sunstoneapps.com list @astrojs/check typescript --depth 0 mostrava as duas dependências. Rodar pnpm install de novo não mudava o prompt. Adicionar as mesmas dependências outra vez também não mudava nada.

Nesse ponto, o prompt deixou de ser evidência. Ele era só a embalagem do erro real.

Rode o binário por baixo

O movimento útil foi chamar o checker diretamente:

pnpm --filter @sites/sunstoneapps.com exec astro-check --version

Isso pulou o prompt amigável do Astro e mostrou o crash real:

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './' is not defined by "exports" in .../node_modules/vscode-languageserver-protocol/package.json

Agora o problema tinha forma. Não era o Astro deixando de encontrar @astrojs/check. Era o @astrojs/check iniciando, carregando sua pilha de language server e quebrando dentro da família vscode-languageserver.

Essa diferença importa. Instalar @astrojs/check de novo não corrige incompatibilidade de export em dependência transitiva.

O stack apontava para vscode-languageserver-protocol, então a próxima pergunta era simples: qual versão está instalada, e quem pediu essa versão?

O pacote instalado era [email protected]. O package.json dele tinha um mapa de exports assim:

{
  "exports": {
    ".": {
      "types": "./lib/common/api.d.ts",
      "default": "./lib/common/api.js"
    },
    "./node": {
      "types": "./lib/node/main.d.ts",
      "node": "./lib/node/main.js"
    },
    "./browser": {
      "types": "./lib/browser/main.d.ts",
      "browser": "./lib/browser/main.js"
    }
  }
}

O import que quebrava não era vscode-languageserver-protocol. Era vscode-languageserver-protocol/, com barra no final. Esse subpath não é exportado pela versão 3.18.1, então o Node rejeita.

O override era o bug

O próximo arquivo a inspecionar era o próprio vscode-languageserver. O @astrojs/check traz @astrojs/language-server, que usa [email protected]. Esse pacote declara:

{
  "dependencies": {
    "vscode-languageserver-protocol": "3.17.5"
  }
}

Mas o workspace tinha este override em pnpm-workspace.yaml:

overrides:
  vscode-languageserver-protocol: 3.18.1

Isso forçava [email protected] a usar uma versão do pacote de protocolo que ele não declarou. Na maior parte do tempo, overrides parecem inofensivos quando só sobem uma dependência transitiva. Este mudava uma fronteira de export. O language server ainda continha código que fazia:

require("vscode-languageserver-protocol/");

e o pacote 3.18.1 recusava esse subpath.

A correção certa não era patchar o Astro, nem fixar Node, nem adicionar outra dependência. A correção certa era remover o override e deixar [email protected] usar a versão que ele pediu.

Depois de remover o override e rodar pnpm install, o lockfile trouxe [email protected] de volta para a parte do grafo usada pelo language server. [email protected] ainda pode existir em outro ponto se algum pacote realmente precisar dele. O ponto importante é que o checker do Astro deixou de forçar a versão errada dentro da dependência do language server.

A falsa pista do Node.js

Uma teoria errada tentadora foi a versão do Node.js.

O runtime local era Node v25.6.1, e o stack trace vinha do resolvedor de package exports do Node. O Astro suporta >=22.12.0, e Node 25 é mais novo que o piso suportado. Isso tornava fácil suspeitar de runtime não suportado.

O erro direto provou o contrário. O Node estava fazendo a coisa certa: lendo o mapa de exports do pacote e rejeitando um subpath com barra final que não estava exportado. O grafo de pacotes estava errado. O Node só deixou o grafo errado visível.

A lição prática é: quando um erro fala de package exports, inspecione versões de dependências antes de culpar o runtime. Versões novas do Node podem expor esses problemas com mais clareza, mas a correção muitas vezes está no grafo.

Por que o Astro mostrou o prompt errado

O prompt do Astro não estava mentindo de propósito. Do ponto de vista do CLI externo, o caminho da dependência de check não completou com sucesso. A ferramenta caiu na explicação comum: o pacote de check está ausente.

Esse prompt é útil em um projeto Astro novo. Ele é menos útil em um monorepo onde a dependência existe, mas quebra durante o carregamento.

O padrão de debug que mantivemos ficou assim:

  1. Confirme que o pacote está declarado no package.json do site.
  2. Confirme que o pnpm enxerga o pacote com pnpm --filter <site> list <pkg> --depth 0.
  3. Rode o binário real com pnpm --filter <site> exec astro-check --version.
  4. Se o binário quebrar, investigue esse stack trace, não o prompt de instalação.
  5. Verifique pnpm-workspace.yaml e seus overrides antes de reinstalar pacotes repetidamente.

Esse último passo era o que faltava. Overrides são globais. Uma linha adicionada para uma dependência transitiva pode afetar tooling em outra parte do monorepo sem avisar.

A validação que importava

Depois de remover o override, os checks voltaram a ser tediosos:

pnpm --filter @sites/sunstoneapps.com typecheck
pnpm sites:typecheck
pnpm sites:build
pnpm run format:check

astro check ainda imprime hints de depreciação do TypeScript para z em content.config.ts. Isso não é falha de build. O resultado importante foi:

0 errors
0 warnings

Os três sites Astro passaram no typecheck. Os três sites fizeram build. A formatação continuou verde.

Esse escopo final importa porque o override era global. Corrigir apenas sunstoneapps.com não bastaria. Se o grafo de dependências mudou para todos os sites, todos os sites precisavam ser validados.

Como evitar perseguir a correção errada

A parte perigosa dessa falha é que várias correções parecem responsáveis à primeira vista.

Rodar pnpm install faz sentido uma vez. Rodar três vezes geralmente é sinal de que a hipótese parou de se sustentar. Adicionar @astrojs/check de novo faz sentido se a dependência não está no package.json. Vira ruído quando o pacote já está declarado, aparece no lockfile e é visível para pnpm list.

O mesmo vale para apagar pastas geradas. Já tínhamos visto cache do content layer do Astro ao renomear conteúdo legal de .mdx para .md, e astro sync --force era a resposta certa naquele caso. Esse histórico tornou cache uma suspeita tentadora aqui também. Mas esta falha acontecia antes de validação de conteúdo importar. O checker quebrava carregando o próprio grafo de dependências do language server. Limpar .astro não mudaria um mapa de exports incompatível em vscode-languageserver-protocol.

A separação que usamos agora é:

  • Se conteúdo gerado aponta para arquivos removidos, rode astro sync --force ou astro build --force.
  • Se astro check diz que falta dependência, confirme declaração e visibilidade pelo pnpm.
  • Se o binário direto astro-check quebra, investigue o stack trace como problema de grafo de pacotes.
  • Se o stack menciona exports, inspecione versões e overrides antes de mudar código fonte.

Essa ordem mantém cada correção ligada ao tipo de falha que ela consegue resolver.

Também há um ponto de manutenção. Overrides deveriam ter dono e prazo de validade na prática, mesmo que o formato do arquivo não guarde esses metadados. Quando um override existe só porque um dia resolveu um problema transitório, ele vira infraestrutura invisível. Meses depois, outra ferramenta pode herdar esse override e falhar em um lugar que nem menciona a linha problemática.

Neste caso, o override morava no workspace raiz, enquanto o sintoma aparecia dentro de um pacote de site. Essa distância tornava o prompt crível. Nada em sites/sunstoneapps.com/package.json parecia errado. A linha errada estava uma árvore acima, em um arquivo que afeta todos os projetos.

A prevenção barata é disciplina de review: todo override novo deveria responder três perguntas na mesma mudança que o introduz.

  • Qual pacote precisa deste override?
  • Qual comando prova que o override é seguro para o resto do workspace?
  • Qual comando deve falhar se o override for removido cedo demais?

Se essas perguntas não têm resposta, provavelmente o override é amplo demais.

A regra que adicionamos

A regra durável é estreita:

Não force vscode-languageserver-protocol via overrides em pnpm-workspace.yaml enquanto o repo usa @astrojs/check com vscode-languageserver@9.

Se um pacote futuro precisar de 3.18.x, ele deve entrar de um jeito que não substitua a dependência 3.17.5 declarada por vscode-languageserver@9. Caso contrário, astro check pode quebrar com:

ERR_PACKAGE_PATH_NOT_EXPORTED
require("vscode-languageserver-protocol/")

Essa não é uma regra teórica. Ela nasceu de uma falha real: o comando de typecheck parecia quebrado, o build ainda passava, e a correção foi deletar um override e regenerar o lockfile.

A parte desconfortável é que override global pode parecer manutenção quando, na prática, está mudando o contrato público de uma dependência de outra ferramenta. Gerenciadores de pacote tornam isso fácil. Language servers são especialmente sensíveis porque ficam atrás de prompts amigáveis de CLI e árvores grandes de dependências.

Quando uma ferramenta de typecheck diz que falta dependência, mas a dependência está claramente instalada, pare de reinstalar. Rode o binário, leia o stack e inspecione overrides. A correção mais curta talvez seja deletar a linha esperta.

Leia o checklist de indexação