Skip to content

Commit 2765158

Browse files
committed
docs(rfcs): add package manager detection RFC
Document how vite-plus determines which package manager a project uses, including the priority-ordered detection algorithm, all supported detection signals, interactive selection behavior, and the --package-manager CLI flag for vp create.
1 parent 0fca323 commit 2765158

1 file changed

Lines changed: 223 additions & 0 deletions

File tree

rfcs/package-manager-detection.md

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
# RFC: Package Manager Detection
2+
3+
## Summary
4+
5+
Document how Vite+ determines which package manager (pnpm/yarn/npm/bun) a project uses. This detection runs automatically before any package management command (`vp install`, `vp add`, `vp remove`, etc.) and drives all PM-specific behavior including command translation, lockfile handling, and workspace configuration.
6+
7+
## Detection Algorithm
8+
9+
Vite+ uses a strict priority-ordered algorithm to detect the package manager. The first match wins.
10+
11+
### Priority 1: `packageManager` field in `package.json`
12+
13+
The highest-priority signal. If the root `package.json` contains a `packageManager` field, it is used unconditionally.
14+
15+
```json
16+
{
17+
"packageManager": "pnpm@10.19.0"
18+
}
19+
```
20+
21+
**Format**: `<name>@<semver>[+<hash>]`
22+
23+
- `name` must be one of: `pnpm`, `yarn`, `npm`, `bun`
24+
- `semver` must be valid (e.g., `10.19.0`, `4.0.0`)
25+
- Optional hash suffix: `pnpm@10.0.0+sha512.abc123...`
26+
27+
**Errors**:
28+
29+
- Invalid semver → `PackageManagerVersionInvalid` error
30+
- Unknown name → `UnsupportedPackageManager` error
31+
32+
**Reference**: [Node.js Corepack packageManager field](https://nodejs.org/api/packages.html#packagemanager)
33+
34+
### Priority 2: Lockfiles
35+
36+
If no `packageManager` field is found, Vite+ checks for lockfiles in the workspace root. Checked in this order:
37+
38+
| File | Detected PM | Notes |
39+
| --------------------- | ----------- | -------------------------------- |
40+
| `pnpm-workspace.yaml` | pnpm | Workspace definition file |
41+
| `pnpm-lock.yaml` | pnpm | Lockfile |
42+
| `yarn.lock` | yarn | Lockfile |
43+
| `.yarnrc.yml` | yarn | Yarn Berry (v2+) configuration |
44+
| `package-lock.json` | npm | Lockfile |
45+
| `bun.lock` | bun | Text-format lockfile (preferred) |
46+
| `bun.lockb` | bun | Binary-format lockfile (legacy) |
47+
48+
When detected from lockfiles, version is set to `"latest"` (resolved during download).
49+
50+
### Priority 3: Configuration files
51+
52+
Lower-priority config files that indicate a package manager:
53+
54+
| File | Detected PM | Notes |
55+
| ----------------- | ----------- | ------------------------------------------- |
56+
| `.pnpmfile.cjs` | pnpm | [pnpm hooks](https://pnpm.io/pnpmfile) |
57+
| `pnpmfile.cjs` | pnpm | Legacy format (pnpm v5.x) |
58+
| `bunfig.toml` | bun | [Bun configuration](https://bun.sh/docs/pm) |
59+
| `yarn.config.cjs` | yarn | Yarn Berry (v2+) configuration |
60+
61+
### Priority 4: Explicit default
62+
63+
If a caller provides a default package manager type (used internally by some code paths), that default is used with version `"latest"`.
64+
65+
### Priority 5: Interactive selection
66+
67+
If no signals are detected and no default is provided, the behavior depends on the environment:
68+
69+
#### CI environment
70+
71+
Checks for common CI environment variables:
72+
73+
- `CI`, `CONTINUOUS_INTEGRATION`, `GITHUB_ACTIONS`, `GITLAB_CI`, `CIRCLECI`, `TRAVIS`, `JENKINS_URL`, `BUILDKITE`, `DRONE`, `CODEBUILD_BUILD_ID` (AWS CodeBuild), `TF_BUILD` (Azure Pipelines)
74+
75+
**Result**: Auto-selects `pnpm` without prompting.
76+
77+
#### Non-interactive terminal
78+
79+
If stdin is not a TTY (piped input, non-interactive shell):
80+
81+
**Result**: Auto-selects `pnpm` without prompting.
82+
83+
#### Interactive terminal
84+
85+
Displays a keyboard-navigable menu:
86+
87+
```
88+
No package manager detected. Please select one:
89+
Use ↑↓ arrows to navigate, Enter to select, 1-4 for quick selection
90+
91+
▶ [1] pnpm (recommended) ←
92+
[2] npm
93+
[3] yarn
94+
[4] bun
95+
```
96+
97+
If the interactive menu fails (terminal compatibility issues), falls back to a simple text prompt:
98+
99+
```
100+
No package manager detected. Please select one:
101+
────────────────────────────────────────────────
102+
[1] pnpm (recommended)
103+
[2] npm
104+
[3] yarn
105+
[4] bun
106+
107+
Enter your choice (1-4) [default: 1]:
108+
```
109+
110+
## CLI Flag: `--package-manager`
111+
112+
The `vp create` command supports a `--package-manager` flag for explicitly specifying the package manager:
113+
114+
```bash
115+
vp create vite:monorepo --no-interactive --package-manager bun
116+
```
117+
118+
**Resolution priority for `vp create`**:
119+
120+
1. Detected workspace `packageManager` field (existing monorepo takes precedence)
121+
2. `--package-manager` CLI flag
122+
3. Interactive prompt / auto-default (pnpm)
123+
124+
This ensures monorepo consistency: if you run `vp create` inside an existing workspace that already has a `packageManager` field, the workspace setting wins over the CLI flag.
125+
126+
## Auto-Update Behavior
127+
128+
After detection and download, Vite+ automatically writes the resolved package manager version to the `packageManager` field in `package.json`. This ensures:
129+
130+
- Future runs use the exact version (Priority 1 match)
131+
- Team members get consistent versions
132+
- CI environments use deterministic versions
133+
134+
## Version Resolution
135+
136+
| Detection method | Version used |
137+
| ------------------------- | ---------------------------------------------------------------- |
138+
| `packageManager` field | Exact version from field (e.g., `10.19.0`) |
139+
| Lockfile/config detection | `"latest"` — resolved to latest stable version from npm registry |
140+
| Interactive selection | `"latest"` — resolved to latest stable version from npm registry |
141+
142+
**Special cases**:
143+
144+
- **yarn ≥ 2.0.0**: Downloads from `@yarnpkg/cli-dist` instead of `yarn` npm package
145+
- **bun**: Downloads platform-specific native binary from `@oven/bun-{os}-{arch}` (including musl variants for Alpine Linux)
146+
147+
## Workspace and Monorepo Detection
148+
149+
Workspace detection determines `is_monorepo` based on:
150+
151+
- `pnpm-workspace.yaml` → monorepo (pnpm)
152+
- `package.json` with `workspaces` field → monorepo (npm/yarn/bun)
153+
154+
The package manager type and monorepo status together drive:
155+
156+
- Which lockfile patterns to watch for cache invalidation
157+
- Whether catalog support is available (pnpm, yarn, bun — not npm)
158+
- How workspace filters (`--filter`) are translated
159+
160+
## Detection Signals Summary
161+
162+
### Per package manager
163+
164+
| Package Manager | Lockfiles | Config Files | Field |
165+
| --------------- | ----------------------- | ------------------------------------------------------ | ---------------- |
166+
| pnpm | `pnpm-lock.yaml` | `pnpm-workspace.yaml`, `.pnpmfile.cjs`, `pnpmfile.cjs` | `packageManager` |
167+
| yarn | `yarn.lock` | `.yarnrc.yml`, `.yarnrc`, `yarn.config.cjs` | `packageManager` |
168+
| npm | `package-lock.json` || `packageManager` |
169+
| bun | `bun.lock`, `bun.lockb` | `bunfig.toml` | `packageManager` |
170+
171+
### Cache invalidation (fingerprint ignores)
172+
173+
Each package manager has specific files that trigger cache invalidation when changed:
174+
175+
| Package Manager | Watched Files |
176+
| --------------- | ------------------------------------------------------------------------------------ |
177+
| pnpm | `pnpm-workspace.yaml`, `pnpm-lock.yaml`, `.pnpmfile.cjs`, `pnpmfile.cjs`, `.pnp.cjs` |
178+
| yarn | `.yarnrc`, `.yarnrc.yml`, `yarn.config.cjs`, `yarn.lock`, `.yarn/**/*`, `.pnp.cjs` |
179+
| npm | `package-lock.json`, `npm-shrinkwrap.json` |
180+
| bun | `bun.lock`, `bun.lockb`, `bunfig.toml` |
181+
| All | `**/package.json`, `.npmrc` |
182+
183+
## Implementation
184+
185+
### Rust (core detection)
186+
187+
- **File**: `crates/vite_install/src/package_manager.rs`
188+
- **Function**: `get_package_manager_type_and_version()` — priority-ordered detection
189+
- **Function**: `prompt_package_manager_selection()` — CI/TTY/interactive fallback
190+
- **Enum**: `PackageManagerType``Pnpm`, `Yarn`, `Npm`, `Bun`
191+
192+
### TypeScript (CLI integration)
193+
194+
- **File**: `packages/cli/src/utils/workspace.ts``detectWorkspace()` wraps NAPI binding
195+
- **File**: `packages/cli/src/utils/prompts.ts``selectPackageManager()` for non-interactive default
196+
- **File**: `packages/cli/src/create/bin.ts``--package-manager` flag handling
197+
198+
### NAPI binding (bridge)
199+
200+
- **File**: `packages/cli/binding/src/package_manager.rs``detectWorkspace()` exports to JS
201+
202+
## Future Enhancements
203+
204+
### `devEngines.packageManager` field
205+
206+
Support the [Node.js `devEngines` field](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#devengines) for package manager constraints:
207+
208+
```json
209+
{
210+
"devEngines": {
211+
"packageManager": {
212+
"name": "pnpm",
213+
"version": ">=10.0.0"
214+
}
215+
}
216+
}
217+
```
218+
219+
This would be checked between Priority 1 (`packageManager` field) and Priority 2 (lockfiles). It specifies a constraint rather than an exact version, so it would be combined with other signals.
220+
221+
### Multiple lockfile conflict resolution
222+
223+
Currently, if multiple lockfiles exist (e.g., both `pnpm-lock.yaml` and `package-lock.json`), the first one found in priority order wins silently. A future enhancement could warn about conflicting lockfiles and suggest cleanup.

0 commit comments

Comments
 (0)