Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ Examples:

Manage:
vp env pin lts # Pin to latest LTS version
vp env install # Install version from .node-version / package.json
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "command-env-install-nvmrc"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[[case]]
name = "command_env_install_nvmrc"
vp = "global"
skip-platforms = ["windows"]
seed-runtime = false
steps = [
{ argv = ["vp", "env", "install"], comment = "Install version from .nvmrc (22.x)", continue-on-failure = true },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# command_env_install_nvmrc

## `vp env install`

Install version from .nvmrc (22.x)

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

Installing Node.js <version>...
Installed Node.js <version>
```
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Usage: vp env use [OPTIONS] [VERSION]
Use a specific Node.js version for this shell session

Arguments:
[VERSION] Version to use (e.g., "20", "20.18.0", "lts", "latest"). If omitted, reads from .node-version or package.json
[VERSION] Version to use (e.g., "20", "20.18.0", "lts", "latest"). If omitted, reads from .node-version, package.json, or .nvmrc

Options:
--unset Remove session override (revert to file-based resolution)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.18.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[[case]]
name = "command_env_which_nvmrc"
vp = "global"
local-registry = true
skip-platforms = ["windows"]
steps = [
{ argv = ["vp", "env", "exec", "node", "--version"], comment = "Ensure Node.js is installed first", continue-on-failure = true },
{ argv = ["vp", "env", "which", "node"], comment = "Core tool - shows resolved Node.js binary path from .nvmrc", continue-on-failure = true },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# command_env_which_nvmrc

## `vp env exec node --version`

Ensure Node.js is installed first

```
<version>
```

## `vp env which node`

Core tool - shows resolved Node.js binary path from .nvmrc

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

<home>/.vite-plus/js_runtime/node/<version>/bin/node
Version: 20.18.0
Source: <workspace>/.nvmrc
```
4 changes: 2 additions & 2 deletions crates/vite_global_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ Examples:
#[command(visible_alias = "i")]
Install {
/// Version to install (e.g., "20", "20.18.0", "lts", "latest")
/// If not provided, installs the version from .node-version or package.json
/// If not provided, installs the version from .node-version, package.json, or .nvmrc
Comment thread
TheAlexLichter marked this conversation as resolved.
version: Option<String>,
},

Expand All @@ -430,7 +430,7 @@ Examples:
vp env use --unset # Clear the session override")]
Use {
/// Version to use (e.g., "20", "20.18.0", "lts", "latest").
/// If omitted, reads from .node-version or package.json.
/// If omitted, reads from .node-version, package.json, or .nvmrc.
version: Option<String>,

/// Remove session override (revert to file-based resolution)
Expand Down
93 changes: 79 additions & 14 deletions crates/vite_global_cli/src/commands/env/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

use serde::{Deserialize, Serialize};
use vite_js_runtime::{
NodeProvider, VersionSource, is_valid_version, normalize_version, read_package_json,
resolve_node_version,
NodeProvider, VersionSource, is_valid_version, normalize_version, read_nvmrc_file,
read_package_json, resolve_node_version,
};
use vite_path::{AbsolutePath, AbsolutePathBuf};

Expand Down Expand Up @@ -198,8 +198,9 @@ pub async fn delete_session_version() -> Result<(), Error> {
/// 2. `.node-version` file in current or parent directories
/// 3. `package.json#devEngines.runtime` in current or parent directories
/// 4. `package.json#engines.node` in current or parent directories
/// 5. User default from config.json
/// 6. Latest LTS version
/// 5. `.nvmrc` file in current or parent directories
/// 6. User default from config.json
/// 7. Latest LTS version
pub async fn resolve_version(cwd: &AbsolutePath) -> Result<VersionResolution, Error> {
// Session override via environment variable (set by `vp env use`)
if let Some(env_version) = vite_shared::EnvConfig::get().node_version {
Expand Down Expand Up @@ -270,24 +271,20 @@ pub(crate) async fn resolve_project_version_source(

// Invalid version from a project source: try lower-priority sources in the same directory.
// This mirrors the fallback logic in download_runtime_for_project().
if !matches!(
resolution.source,
VersionSource::NodeVersionFile | VersionSource::DevEnginesRuntime
) {
if matches!(resolution.source, VersionSource::NvmrcFile) {
return Ok(None);
}

let Some(project_root) = resolution.project_root else {
return Ok(None);
};
let package_json_path = project_root.join("package.json");
let Ok(Some(pkg)) = read_package_json(&package_json_path).await else {
return Ok(None);
};
let pkg = read_package_json(&package_json_path).await.ok().flatten();

if matches!(resolution.source, VersionSource::NodeVersionFile)
&& let Some(version) = pkg
.dev_engines_runtime("node")
.as_ref()
.and_then(|pkg| pkg.dev_engines_runtime("node"))
.and_then(|r| r.version.clone())
.and_then(|v| validate_version_spec(&v, "devEngines.runtime", warn_invalid))
{
Expand All @@ -299,9 +296,12 @@ pub(crate) async fn resolve_project_version_source(
}));
}

if let Some(version) = pkg
.engines
if matches!(
resolution.source,
VersionSource::NodeVersionFile | VersionSource::DevEnginesRuntime
) && let Some(version) = pkg
.as_ref()
.and_then(|pkg| pkg.engines.as_ref())
.and_then(|e| e.node.clone())
.and_then(|v| validate_version_spec(&v, "engines.node", warn_invalid))
{
Expand All @@ -313,6 +313,18 @@ pub(crate) async fn resolve_project_version_source(
}));
}

if let Some(version) = read_nvmrc_file(&project_root)
.await
.and_then(|v| validate_version_spec(&v, ".nvmrc", warn_invalid))
{
return Ok(Some(ProjectVersionSource {
version,
source: ".nvmrc".into(),
source_path: project_root.join(".nvmrc"),
project_root,
}));
}

Ok(None)
}

Expand Down Expand Up @@ -619,6 +631,59 @@ mod tests {
assert_eq!(resolution.source, VersionSource::DevEnginesRuntime);
}

#[tokio::test]
async fn test_resolve_version_from_nvmrc() {
let temp_dir = TempDir::new().unwrap();
let temp_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();

tokio::fs::write(temp_path.join(".nvmrc"), "22.22.0\n").await.unwrap();

let source = resolve_project_version_source(&temp_path, false).await.unwrap().unwrap();
assert_eq!(source.version, "22.22.0");
assert_eq!(source.source, ".nvmrc");
assert_eq!(source.source_path, temp_path.join(".nvmrc"));
}

#[tokio::test]
async fn test_node_version_takes_priority_over_nvmrc() {
let temp_dir = TempDir::new().unwrap();
let temp_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();

tokio::fs::write(temp_path.join(".node-version"), "24.0.0\n").await.unwrap();
tokio::fs::write(temp_path.join(".nvmrc"), "22.22.0\n").await.unwrap();

let source = resolve_project_version_source(&temp_path, false).await.unwrap().unwrap();
assert_eq!(source.version, "24.0.0");
assert_eq!(source.source, ".node-version");
assert_eq!(source.source_path, temp_path.join(".node-version"));
}

#[tokio::test]
async fn test_project_source_inherits_parent_nvmrc() {
let temp_dir = TempDir::new().unwrap();
let parent = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
let child = parent.join("child");
tokio::fs::create_dir(&child).await.unwrap();
tokio::fs::write(parent.join(".nvmrc"), "22.22.0\n").await.unwrap();

let source = resolve_project_version_source(&child, false).await.unwrap().unwrap();
assert_eq!(source.version, "22.22.0");
assert_eq!(source.source, ".nvmrc");
assert_eq!(source.source_path, parent.join(".nvmrc"));
}

#[tokio::test]
async fn test_project_source_falls_back_from_invalid_node_version_to_nvmrc() {
let temp_dir = TempDir::new().unwrap();
let temp_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
tokio::fs::write(temp_path.join(".node-version"), "not-a-version\n").await.unwrap();
tokio::fs::write(temp_path.join(".nvmrc"), "22.22.0\n").await.unwrap();

let source = resolve_project_version_source(&temp_path, false).await.unwrap().unwrap();
assert_eq!(source.version, "22.22.0");
assert_eq!(source.source, ".nvmrc");
}

#[tokio::test]
async fn test_resolve_version_node_version_takes_priority() {
let temp_dir = TempDir::new().unwrap();
Expand Down
Loading
Loading