ADR-0006 Runtime & toolchain
Company standard — Node LTS runtime, pnpm package manager, Vite+ (vp) unified toolchain.
- Decision
- Node LTS + pnpm + Vite+Ship & run
- Status
- SupersededSuperseded by ADR-0035
- Date
- Scope
- Company-wide default for all apps and tooling
Connections
Builds on
Cited by
Context
Section titled “Context”Every app needs a JavaScript runtime to execute server code, a package manager to install dependencies, and a dev toolchain (dev server, build, test, lint, format). Most teams inherit a split toolchain: Node here, pnpm or npm there, plus separate Vite/Vitest/ESLint/Prettier configs that drift apart over time.
We want one coherent toolchain across dev, CI, and production — the same runtime and the same entry point locally, in the pipeline, and inside the production Docker container (ADR-0003).
Options considered
Section titled “Options considered”1. Node + npm
Section titled “1. Node + npm”The default.
Pros
- The reference runtime; everything is tested against it first
- npm ships with Node — zero setup
Cons
- npm is the slowest, weakest package manager of the candidates (lockfile churn, audit noise)
- No toolchain story — dev/build/test/lint remain separate tools with separate configs
2. Node LTS + pnpm + Vite+
Section titled “2. Node LTS + pnpm + Vite+”Node LTS as the runtime, pnpm as the package manager, and Vite+ (vp, from VoidZero) as the unified toolchain: one entry point combining Vite (dev), Rolldown (build), Vitest (test), Oxlint + Oxfmt (lint/format), and a cached task runner — all configured from a single vite.config.ts. vp env pins the Node version and package manager per project.
Pros
- Reference-runtime compatibility — Node LTS is what the ecosystem (TanStack Start, Vite, Hono, Drizzle) tests against first; zero compat babysitting
- pnpm: fast, disk-efficient, strict resolution that catches phantom dependencies
- Vite+ collapses the toolchain into one dependency and one config file — dev, build, test, lint, format, tasks
vp envpins Node LTS + pnpm per project → no “works on my machine” drift between dev and CI (the Docker image pins its own matching tag)- Eject path is cheap: Vite+ wraps tools we would use anyway (Vite, Vitest, pnpm), so dropping it later is a config change, not a rewrite
Cons
- Vite+ is young (1.x, MIT-licensed) — we accept faster churn than the tools it wraps in exchange for the consolidation; mitigated by the cheap eject path
- Three versioned components (Node, pnpm, vp) instead of one binary — mitigated by
vp envmanaging them
3. Bun
Section titled “3. Bun”All-in-one toolkit: runtime, package manager, bundler, test runner.
Pros
- One binary for everything; very fast installs and startup
- Node compatibility covers most of the npm ecosystem
Cons
- Not the reference runtime: occasional Node-compat edge cases in exotic packages become our problem
- Its all-in-one advantage is matched by Vite+ while keeping Node’s compatibility and pnpm’s strictness
- Vite/TanStack tooling targets Node first
4. Deno
Section titled “4. Deno”Secure-by-default runtime with web-standard APIs.
Pros
- Excellent security model; built-in TS, formatter, linter
Cons
- Node/npm compatibility is bumpier than advertised for our stack
- Smallest ecosystem fit for Vite/TanStack tooling
Decision
Section titled “Decision”Node LTS (runtime) + pnpm (package manager) + Vite+ (toolchain) is the company standard — in local dev, in CI, and inside the production Docker image (ADR-0003).
Concretely:
- Node LTS — LTS lines only, never Current. New apps start on Active LTS: 24.x until 26.x enters Active LTS (2026-10-28), then 26.x. Upgrades scheduled before EOL. From Node 27, every major becomes LTS (one release per year), so “even-numbered” no longer means anything
- pnpm for dependencies —
pnpm-lock.yamlis the only lockfile; never mix npm/yarn/bun lockfiles in a repo - Vite+ (
vp) as the single entry point:vp dev,vp build,vp check(lint + format + types),vp test— configured from onevite.config.ts - Versions pinned per project in
.node-versionandpackageManager(vp env pin);vp envresolves them on laptops and CI. The Docker base image tag is pinned separately and must match - Lint/format (Oxlint/Oxfmt) is settled here as part of the toolchain; testing uses the bundled Vitest (ADR-0014)
The deciding factors: reference-runtime compatibility (zero babysitting), pnpm’s strictness and speed, and Vite+ giving us Bun-style toolchain consolidation on top of boring, dependable Node — with a cheap eject path if Vite+ itself disappoints.
Consequences
Section titled “Consequences”Easier:
- One config file governs dev, build, test, lint, and format — no more config drift between five tools
.node-version+vp envkills “works on my machine” between laptops and CI; the Dockerfile tag covers containers- Fast, strict CI installs with pnpm’s content-addressable store
- Onboarding: install
vp, runvp env, done
Harder:
- We track a young tool (Vite+ 1.x) — expect upgrade churn; we pin versions and upgrade deliberately
- Node upgrades are a scheduled, company-wide event (LTS cadence), not an afterthought
Given up: Bun’s single-binary simplicity and raw install speed. Accepted — Vite+ gets us the same consolidation on the reference runtime, with pnpm’s strictness as a bonus.

