Skip to content

deps: any root pnpm add silently downgrades TypeScript 6.0.3 → 5.9.3 #451

Description

@IgorShevchik

Environment

main. Reproduced on a clean clone.

Description

Running pnpm add -D <anything> at the workspace root re-resolves the root importer's peers from scratch and moves typescript from 6.0.3 to 5.9.3, churning ~320 lockfile lines along the way. The package being added is irrelevant — it is a property of the current dependency graph.

$ pnpm add -D @conventional-commits/parser@0.4.1
$ git diff --stat pnpm-lock.yaml
 pnpm-lock.yaml | 321 ++++++++++++++++++++++-----------------------
 -        version: 6.0.3
 +        version: 5.9.3

Cause

Two facts combine:

  1. package.json declares typescript as a non-optional peerDependency (^5.6.3 || ^6.0.0 || ^7.0.0) and does not list it in devDependencies, so pnpm auto-installs the peer and is free to choose within that range.
  2. @nuxt/module-builder@1.0.3, a root devDependency, declares peer typescript: ^5.9.3 — which excludes 6.x. It is the only typescript peer range in the tree that does (pnpm-lock.yaml:1833).

pnpm install alone is stable: pnpm install --lockfile-only with no changes leaves the lockfile byte-identical, and --frozen-lockfile passes. Only pnpm add forces the re-resolve, and the intersection lands on 5.9.3.

Why it matters

It is silent and it is attached to the most ordinary action there is. Anyone adding a dependency gets an unrelated TypeScript downgrade in their diff, and the plausible reactions are all bad: not noticing, noticing and reverting the lockfile by hand, or concluding the package they added is at fault. The last one already happened — PR #449 avoided a devDependency entirely on that basis, until review established the real cause.

Suggested fix

Either:

  • pin it in devDependencies — add "typescript": "^6.0.3" to root devDependencies, so the resolution is stated rather than derived; or
  • add an overridetypescript: ^6.0.3 in pnpm-workspace.yaml, alongside the overrides already there for vite and rolldown and for the same reason: a peer range elsewhere pulling the tree somewhere the project does not want.

The second matches the file's existing pattern, and those overrides already carry comments explaining exactly this kind of trap.

Adding the devDependency the manual way — appending to package.json and running pnpm install — produces a clean 33-line pure-addition lockfile diff with typescript untouched, which is what #449 ended up doing. That is a workaround for one PR, not a fix for the next person.

Additional context

Found by an independent review pass over #449.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions