Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
04ed700
feat(env)!: manage package manager environments
liangmiQwQ Aug 12, 2026
01e90c9
fix(env): restore node lifecycle compatibility
liangmiQwQ Aug 12, 2026
39facbf
fix(env): respect doctor component scope
liangmiQwQ Aug 12, 2026
2f9c2a9
fix(env): restore informative node output
liangmiQwQ Aug 12, 2026
25fe26d
fix(env): preserve scoped package manager state
liangmiQwQ Aug 12, 2026
6186f0c
test(env): enable session fallback in hash snapshot
liangmiQwQ Aug 12, 2026
984f313
fix(env): separate doctor labels from values
liangmiQwQ Aug 12, 2026
c9ff3ca
fix(env): present doctor modes as values
liangmiQwQ Aug 12, 2026
2df8606
test(env): remove platform-specific doctor snapshot
liangmiQwQ Aug 13, 2026
f293ff5
fix(env): preserve main compatibility
liangmiQwQ Aug 20, 2026
c3a4b94
test(env): refresh component management snapshots
liangmiQwQ Aug 20, 2026
8b41c8b
test(env): refresh Windows shell snapshots
liangmiQwQ Aug 20, 2026
b20cf03
wip
liangmiQwQ Aug 21, 2026
3b3cf01
wip
liangmiQwQ Aug 21, 2026
db1f4f5
test(env): align list formatting snapshot
liangmiQwQ Aug 28, 2026
b8cf946
support pm famaliy
liangmiQwQ Aug 30, 2026
2881870
feat(env): add package manager shim preferences
liangmiQwQ Aug 30, 2026
2704c6e
fix(installer): support older Vite+ payloads
liangmiQwQ Aug 30, 2026
a2ec9ba
fix(installer): clear handled preference errors
liangmiQwQ Aug 30, 2026
770152f
fix(installer): reset global payload exit code
liangmiQwQ Aug 30, 2026
c9b5e6a
fix(installer): enable the complete managed environment
liangmiQwQ Aug 30, 2026
c623983
fix(env): address package manager review regressions
liangmiQwQ Aug 30, 2026
67b4cfb
fix(env): address follow-up review regressions
liangmiQwQ Aug 30, 2026
6fe1499
fix(env): address system mode review regressions
liangmiQwQ Aug 30, 2026
8e8fb9f
fix(env): address follow-up environment regressions
liangmiQwQ Aug 30, 2026
1788ae2
fix(env): align concrete component fallbacks
liangmiQwQ Aug 30, 2026
80e2956
fix(env): preserve component lifecycle contracts
liangmiQwQ Aug 30, 2026
9c79b9d
fix(env): align undecided manager behavior
liangmiQwQ Aug 30, 2026
d928446
fix(env): complete package manager fallback contracts
liangmiQwQ Aug 30, 2026
6b274f0
Merge remote-tracking branch 'upstream/main' into feat-breaking/exten…
liangmiQwQ Aug 30, 2026
f536a1e
fix(env): store per-manager default versions
liangmiQwQ Aug 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ _runtime and package management, create, dev, check, test, build, pack, and mono

Vite+ is the unified entry point for local web development. It combines [Vite](https://vite.dev/), [Vitest](https://vitest.dev/), [Oxlint](https://oxc.rs/docs/guide/usage/linter.html), [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), [Rolldown](https://rolldown.rs/), [tsdown](https://tsdown.dev/), and [Vite Task](https://github.com/voidzero-dev/vite-task) into one zero-config toolchain that also manages runtime and package manager workflows:

- **`vp env`:** Manage Node.js globally and per project
- **`vp env`:** Manage Node.js and package managers globally and per project
- **`vp install`:** Install dependencies with automatic package manager detection
- **`vp dev`:** Run Vite's fast native ESM dev server with instant HMR
- **`vp check`:** Run formatting, linting, and type checks in one command
Expand Down Expand Up @@ -105,7 +105,7 @@ Use `vp migrate` to migrate to Vite+. It merges tool-specific config files such
- **hooks** - Manage the Git hook dispatcher
- **staged** - Run linters on staged files
- **install** (`i`) - Install dependencies
- **env** - Manage Node.js versions
- **env** - Manage Node.js and package managers

#### Develop

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Start:
hooks Manage the Git hook dispatcher
staged Run linters on staged files
install, i Install all dependencies, or add packages if package names are provided
env Manage Node.js versions
env Manage Node.js and package managers

Develop:
dev Run the development server
Expand Down Expand Up @@ -460,54 +460,58 @@ VITE+ - The Unified Toolchain for the Web

Usage: vp env [COMMAND]

Manage Node.js versions
Manage Node.js and package-manager environments

Setup:
setup Create or update shims in VP_HOME/bin
on Enable managed mode - shims always use vite-plus managed Node.js
off Enable system-first mode - shims prefer system Node.js, fallback to managed
print Print shell snippet to set environment for current session
on Enable managed mode for selected environment scopes
off Enable system-first mode for selected environment scopes
print Print PATH setup for the resolved environment

Manage:
default Set or show the global default Node.js version
pin Pin a Node.js version in the current directory
unpin Remove the Node.js pin from the current directory (alias for `pin --unpin`)
use Use a specific Node.js version for this shell session
install, i Install a Node.js version
uninstall, uni Uninstall a Node.js version
clean Remove unused managed runtimes and package manager caches
exec, run Execute a command with a specific Node.js version
default Set or show global environment defaults
pin Pin Node.js and package-manager versions in the project
unpin Remove project environment pins (alias for `pin --unpin`)
use Activate an environment for this shell session
install, i Install a resolved or explicit environment
uninstall, uni Uninstall explicit component versions
clean Remove unused runtimes and package managers
exec, run Execute a command in a resolved or explicit environment

Inspect:
current Show current environment information
doctor Run diagnostics and show environment status
which Show path to the tool that would be executed
list, ls List locally installed Node.js versions
list-remote, ls-remote List available Node.js versions from the registry
list, ls List locally installed environment components
list-remote, ls-remote List available versions from component registries

Examples:
Setup:
vp env setup # Create Node.js and package-manager shims
vp env on # Use vite-plus managed Node.js
vp env print # Print shell snippet for this session
vp env on # Manage Node.js and package managers
vp env off pm # Prefer system package managers only
vp env off pnpm # Prefer system pnpm only
vp env print # Print PATH setup for both components

Manage:
vp env pin lts # Pin to latest LTS version
vp env install # Install version from .node-version / package.json / .nvmrc
vp env use 20 # Use Node.js 20 for this shell session
vp env use --unset # Remove session override
vp env clean # Remove unused managed caches
vp env default 22.19.0 # Set the Node.js default
vp env default pnpm@12 # Set pnpm's default version
vp env pin 22.19.0 # Pin Node.js for this project
vp env use 22.19.0 # Use Node.js in this shell
vp env clean # Clean all unused managed versions

Inspect:
vp env current # Show current resolved environment
vp env current --json # JSON output for automation
vp env doctor # Check environment configuration
vp env which node # Show which node binary will be used
vp env list-remote --lts # List only LTS versions
vp env list node # List only Node.js installations
vp env list-remote --lts # List only Node.js LTS versions

Execute:
vp env exec --node lts npm i # Execute 'npm i' with latest LTS
vp env exec node -v # Shim mode (version auto-resolved)
vp env exec --node lts node -v # Override Node.js
vp env exec --package-manager pnpm@12 pnpm i # Override the package manager
vp env exec node -v # Resolve both components

Related Commands:
vp install -g <package> # Install a package globally
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { readdirSync } = require('node:fs')
const { join } = require('node:path')

const versions = readdirSync(join(process.env.VP_HOME, 'package_manager', 'pnpm'), { withFileTypes: true })
.filter(entry => entry.isDirectory())
.map(entry => entry.name)

if (versions.length !== 1 || versions[0] === '0.0.1') {
process.stderr.write(`unexpected pnpm installs: ${versions.join(', ')}\n`)
process.exit(1)
}

console.log('kept one concrete pnpm fallback')
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,19 @@ vp = "global"
seed-runtime = false
steps = [
{ argv = ["vp", "env", "clean"], comment = "Clean isolated Vite+ caches" },
{ argv = ["vp", "env", "clean", "node"], comment = "Clean only Node.js runtimes" },
{ argv = ["vp", "env", "clean", "pm"], comment = "Clean all package-manager families" },
{ argv = ["vp", "env", "clean", "pnpm"], comment = "Clean one package-manager family" },
]

[[case]]
name = "env_clean_preserves_concrete_package_manager_fallback"
vp = "global"
local-registry = true
seed-runtime = false
steps = [
{ argv = ["vp", "env", "print", "pnpm"], snapshot = false },
{ argv = ["vpt", "write-file", "$VP_HOME/package_manager/pnpm/0.0.1/pnpm/bin/pnpm", "#!/bin/sh\n"], snapshot = false },
{ argv = ["vp", "env", "clean", "pnpm"], comment = "cleanup removes stale installs but preserves the concrete family's current fallback" },
{ argv = ["node", "assert-one-pnpm-version.cjs"], comment = "the cached registry fallback remains available" },
]
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,33 @@ VITE+ - The Unified Toolchain for the Web
✓ Removed 0 Node.js runtimes
✓ Removed 0 package manager installs
```

## `vp env clean node`

Clean only Node.js runtimes

```
VITE+ - The Unified Toolchain for the Web

✓ Removed 0 Node.js runtimes
```

## `vp env clean pm`

Clean all package-manager families

```
VITE+ - The Unified Toolchain for the Web

✓ Removed 0 package manager installs
```

## `vp env clean pnpm`

Clean one package-manager family

```
VITE+ - The Unified Toolchain for the Web

✓ Removed 0 package manager installs
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# env_clean_preserves_concrete_package_manager_fallback

## `vp env print pnpm`


## `vpt write-file $VP_HOME/package_manager/pnpm/0.0.1/pnpm/bin/pnpm '#'\!'/bin/sh
'`


## `vp env clean pnpm`

cleanup removes stale installs but preserves the concrete family's current fallback

```
VITE+ - The Unified Toolchain for the Web

✓ Removed 1 package manager install
```

## `node assert-one-pnpm-version.cjs`

the cached registry fallback remains available

```
kept one concrete pnpm fallback
```
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ verifies vp-use.cmd explicit, unset, file-based, failure status, and session-onl

```
Using Node.js <version> (resolved from 20.18.0)
Reverted to file-based Node.js version resolution
Reverted selected components to project environment resolution
Using Node.js <version> (resolved from .node-version)
Command Prompt environment use checks passed
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ vp = "global"
skip-platforms = ["windows"]
steps = [
{ argv = ["node", "-e", "const {execFileSync}=require('node:child_process');const text=execFileSync('vp',['env','doctor'],{encoding:'utf8'}).replace(/\\u001b\\[[0-9;]*m/g,'');const lines=text.split('\\n');const start=lines.findIndex(l=>l.trim()==='devEngines');if(start===-1){console.error('devEngines section not found in doctor output');process.exit(1);}const out=[];for(let i=start;i<lines.length;i++){if(i>start&&lines[i].trim()===''){break;}out.push(lines[i].trimEnd());}console.log(out.join('\\n'));"], comment = "print only the deterministic devEngines section of vp env doctor (the other sections are environment-dependent)", continue-on-failure = true },
{ argv = ["node", "-e", "const {execFileSync}=require('node:child_process');const text=execFileSync('vp',['env','doctor','node'],{encoding:'utf8'}).replace(/\\u001b\\[[0-9;]*m/g,'');const lines=text.split('\\n');const start=lines.findIndex(l=>l.trim()==='devEngines');if(start===-1){console.error('devEngines section not found in doctor output');process.exit(1);}const out=[];for(let i=start;i<lines.length;i++){if(i>start&&lines[i].trim()===''){break;}out.push(lines[i].trimEnd());}console.log(out.join('\\n'));"], comment = "node-scoped doctor output excludes package-manager findings", continue-on-failure = true },
{ argv = ["node", "-e", "const {execFileSync}=require('node:child_process');const text=execFileSync('vp',['env','doctor','pm'],{encoding:'utf8'}).replace(/\\u001b\\[[0-9;]*m/g,'');const lines=text.split('\\n');const start=lines.findIndex(l=>l.trim()==='devEngines');if(start===-1){console.error('devEngines section not found in doctor output');process.exit(1);}const out=[];for(let i=start;i<lines.length;i++){if(i>start&&lines[i].trim()===''){break;}out.push(lines[i].trimEnd());}console.log(out.join('\\n'));"], comment = "package-manager-scoped doctor output excludes runtime findings", continue-on-failure = true },
]
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,22 @@ devEngines
⚠ PackageManager packageManager is "npm@10.5.0" but devEngines.packageManager requires "pnpm"
note: This will become an error in a future release.
```

## `node -e 'const {execFileSync}=require('\''node:child_process'\'');const text=execFileSync('\''vp'\'',['\''env'\'','\''doctor'\'','\''node'\''],{encoding:'\''utf8'\''}).replace(/\u001b\[[0-9;]*m/g,'\'''\'');const lines=text.split('\''\n'\'');const start=lines.findIndex(l=>l.trim()==='\''devEngines'\'');if(start===-1){console.error('\''devEngines section not found in doctor output'\'');process.exit(1);}const out=[];for(let i=start;i<lines.length;i++){if(i>start&&lines[i].trim()==='\'''\''){break;}out.push(lines[i].trimEnd());}console.log(out.join('\''\n'\''));'`

node-scoped doctor output excludes package-manager findings

```
devEngines
⚠ Runtime .node-version (20.18.0) does not satisfy devEngines.runtime "^24.0.0"
```

## `node -e 'const {execFileSync}=require('\''node:child_process'\'');const text=execFileSync('\''vp'\'',['\''env'\'','\''doctor'\'','\''pm'\''],{encoding:'\''utf8'\''}).replace(/\u001b\[[0-9;]*m/g,'\'''\'');const lines=text.split('\''\n'\'');const start=lines.findIndex(l=>l.trim()==='\''devEngines'\'');if(start===-1){console.error('\''devEngines section not found in doctor output'\'');process.exit(1);}const out=[];for(let i=start;i<lines.length;i++){if(i>start&&lines[i].trim()==='\'''\''){break;}out.push(lines[i].trimEnd());}console.log(out.join('\''\n'\''));'`

package-manager-scoped doctor output excludes runtime findings

```
devEngines
⚠ PackageManager packageManager is "npm@10.5.0" but devEngines.packageManager requires "pnpm"
note: This will become an error in a future release.
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ verifies the Fish wrapper help, explicit use, unset, file-based use, and failure

```
Using Node.js <version> (resolved from 20.18.0)
Reverted to file-based Node.js version resolution
Reverted selected components to project environment resolution
Using Node.js <version> (resolved from .node-version)
error: Unexpected argument '--invalid-option'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,18 @@ skip-platforms = ["windows"]
seed-runtime = false
steps = [
{ argv = ["vp", "env", "install"], comment = "Install version from .node-version (22.x)", continue-on-failure = true },
{ argv = ["vp", "env", "use", "22", "--no-install"], snapshot = false },
{ argv = ["vp", "env", "install"], comment = "An implicit install identifies an active session override", continue-on-failure = true },
{ argv = ["vp", "env", "uninstall", "22"], comment = "Node.js uninstall accepts the same major-version selectors as install", continue-on-failure = true },
]

[[case]]
name = "command_env_install_bundled_npm"
vp = "global"
skip-platforms = ["windows"]
seed-runtime = false
steps = [
{ argv = ["vpt", "write-file", ".node-version", "22.18.0\n"], snapshot = false },
{ argv = ["vp", "env", "install", "npm"], comment = "unselected npm installs the resolved Node.js runtime that bundles it" },
{ argv = ["vpt", "stat-file", "$VP_HOME/js_runtime/node/22.18.0/bin/npm", "--assert", "symlink"], comment = "the installed npm is the Node.js-bundled executable" },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# command_env_install_bundled_npm

## `vpt write-file .node-version '22.18.0
'`


## `vp env install npm`

unselected npm installs the resolved Node.js runtime that bundles it

```
VITE+ - The Unified Toolchain for the Web

Installing npm bundled with Node.js <version>...
Installed npm bundled with Node.js <version>
```

## `vpt stat-file $VP_HOME/js_runtime/node/22.18.0/bin/npm --assert symlink`

the installed npm is the Node.js-bundled executable

```
<home>/.vite-plus/js_runtime/node/<version>/bin/npm: symlink
```
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,29 @@ VITE+ - The Unified Toolchain for the Web
Installing Node.js <version>...
Installed Node.js <version>
```

## `vp env use 22 --no-install`


## `vp env install`

An implicit install identifies an active session override

```
VITE+ - The Unified Toolchain for the Web

Installing Node.js <version>...
Installed Node.js <version>
Note: Installed from session override.
Run `vp env use --unset` to revert to project version resolution.
```

## `vp env uninstall 22`

Node.js uninstall accepts the same major-version selectors as install

```
VITE+ - The Unified Toolchain for the Web

Uninstalled Node.js <version>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { spawnSync } = require('node:child_process')
const { writeFileSync } = require('node:fs')

writeFileSync('package.json', JSON.stringify({
name: 'command-env-install-no-node-with-package-manager',
private: true,
packageManager: 'pnpm@10.18.0',
}))

const result = spawnSync('vp', ['env', 'install'], { encoding: 'utf8' })
const output = `${result.stdout}${result.stderr}`
if (result.status !== 1 || !output.includes('Installed pnpm v10.18.0')) {
process.stderr.write(output)
process.exit(1)
}

console.log('installed the declared package manager after reporting the missing Node.js pin')
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ seed-runtime = false
steps = [
{ argv = ["vp", "env", "install"], comment = "No version config - should error", continue-on-failure = true },
]

[[case]]
name = "command_env_install_no_node_with_package_manager"
vp = "global"
skip-platforms = ["windows"]
seed-runtime = false
steps = [
{ argv = ["vpt", "write-file", "$VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpm", "#!/bin/sh\n"], snapshot = false },
{ argv = ["vpt", "write-file", "$VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpx", "#!/bin/sh\n"], snapshot = false },
{ argv = ["node", "assert-package-manager-installed.cjs"], comment = "a bare install still installs the declared package manager when Node.js is unpinned" },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# command_env_install_no_node_with_package_manager

## `vpt write-file $VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpm '#'\!'/bin/sh
'`


## `vpt write-file $VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpx '#'\!'/bin/sh
'`


## `node assert-package-manager-installed.cjs`

a bare install still installs the declared package manager when Node.js is unpinned

```
installed the declared package manager after reporting the missing Node.js pin
```
Loading
Loading