Trovella Wiki

Version Strategy

Version range conventions, pnpm overrides, intentional pins, and the monthly deep-check process.

Range Convention

All catalog entries and most direct dependencies use caret ranges (^):

^4.3.6  =  >=4.3.6  <5.0.0

Caret ranges allow Renovate to propose patch and minor updates without requiring a catalog change. When a new minor version is published, Renovate updates the lockfile. When a new major version is published, it opens a PR that bumps the catalog entry itself (which requires manual review).

The exception is packages pinned to an exact version. Currently there are none in the catalog, but the convention is to pin only when:

  • A specific version has a known regression in a newer release
  • The package has unstable minor releases (rare in the ecosystem today)
  • An integration test has proven incompatibility with a specific version range

Intentional Version Constraints

TypeScript: Pinned to 5.x

# pnpm-workspace.yaml
typescript: ^5.9.3

TypeScript is constrained to the 5.x line because typescript-eslint does not yet support TypeScript 6.x. The comment in pnpm-workspace.yaml documents this:

# TypeScript (pinned to 5.x -- typescript-eslint doesn't support 6.x yet)
typescript: ^5.9.3

When typescript-eslint adds 6.x support, remove the constraint and let Renovate propose the major bump.

Lockstep Groups

Some packages must update together. The catalog enforces this by listing them at the same version:

GroupPackagesShared Version
tRPC@trpc/client, @trpc/react-query, @trpc/server^11.15.0
Reactreact, react-dom^19.2.4
Tailwindtailwindcss, @tailwindcss/postcss^4.2.2
Vitestvitest, @vitest/coverage-v8^4.1.2
Fumadocsfumadocs-core, fumadocs-ui^16.7.10

The catalog makes this explicit. If someone bumps @trpc/client but forgets @trpc/server, the version mismatch is visible in a single file (pnpm-workspace.yaml), not scattered across five package.json files.

Renovate reinforces this through package groups that update all related packages in one PR.

pnpm Overrides

The root package.json defines pnpm.overrides for transitive dependency fixes:

{
  "pnpm": {
    "overrides": {
      "vite": "^8.0.5",
      "defu": ">=6.1.5",
      "minimatch@<4>brace-expansion": "1.1.13",
    },
  },
}

When to use overrides

Overrides force a specific version of a transitive dependency (one you do not directly import but that appears in your dependency tree). Use them when:

  • Security vulnerability -- a transitive dependency has a CVE and the direct dependency has not released a fix yet.
  • Compatibility fix -- a transitive dependency version causes build or runtime errors. The vite override exists because a newer Vite is needed for compatibility with the current build toolchain.
  • Sub-dependency pin -- the minimatch@<4>brace-expansion override pins a specific sub-dependency of an older minimatch version to avoid a known issue.

Override hygiene

Overrides are technical debt. Each one should have a comment explaining why it exists and under what condition it can be removed. Review overrides during the monthly deep-check and remove any that are no longer needed (i.e., the upstream package has released a fix).

onlyBuiltDependencies

The root package.json also lists packages in pnpm.onlyBuiltDependencies:

{
  "pnpm": {
    "onlyBuiltDependencies": [
      "@sentry/cli",
      "@vvago/vale",
      "esbuild",
      "protobufjs",
      "sharp",
      "unrs-resolver",
    ],
  },
}

This restricts which packages are allowed to run install scripts (postinstall, preinstall). Packages not in this list have their install scripts skipped, which improves install speed and reduces supply chain risk. When adding a dependency that requires a build step during install (native binaries, WASM compilation), add it to this list.

Monthly Deep-Check

Renovate handles the steady stream of individual package updates. The monthly deep-check catches cross-dependency interaction issues that individual PRs miss.

Process

# Install ncu globally (one-time setup)
pnpm add -g npm-check-updates

# Run the doctor check -- updates one package at a time, tests after each
ncu --doctor -u

# If updates were applied, verify the full suite
pnpm install
pnpm ci:full    # ci:check + build

# Commit if everything passes
git add package.json pnpm-lock.yaml **/package.json
git commit -m "chore: monthly dependency update via ncu --doctor"

What ncu --doctor does

  1. Updates a single dependency to its latest version.
  2. Runs the test suite (pnpm test).
  3. If tests pass, keeps the update and moves to the next dependency.
  4. If tests fail, reverts the update and reports it as incompatible.
  5. At the end, you have a clean set of safe updates applied.

When to run

Pick a consistent day each month (e.g., first Saturday). This complements Renovate -- Renovate handles the steady stream, ncu --doctor catches anything that fell through the cracks or that only breaks when combined with other updates.

Review overrides during deep-check

While running the monthly check, also review each pnpm.overrides entry:

  1. Check if the upstream dependency has released a version that fixes the issue the override addresses.
  2. If fixed, remove the override, run pnpm install, and verify with pnpm ci:full.
  3. If still needed, leave a comment with the current status and expected resolution timeline.

On this page