From 28ad67219d437f97eedb25a950665840ff3a998e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Sjo=CC=88berg?= Date: Wed, 5 Mar 2025 16:32:45 +0100 Subject: [PATCH 1/4] feat: .node-version support --- crates/volta-core/src/project/mod.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/volta-core/src/project/mod.rs b/crates/volta-core/src/project/mod.rs index 1e9d7379b..9ef69d53f 100644 --- a/crates/volta-core/src/project/mod.rs +++ b/crates/volta-core/src/project/mod.rs @@ -109,7 +109,10 @@ impl Project { extends = manifest.extends; } - let platform = platform.map(TryInto::try_into).transpose()?; + let platform = match platform.map(TryInto::try_into).transpose()? { + Some(platform) => Some(platform), + None => Self::platform_from_node_version(&manifest_file), + }; Ok(Project { manifest_file, @@ -119,6 +122,28 @@ impl Project { }) } + /// Returns a Node.js version from .node_version_file + fn platform_from_node_version(manifest_file: &Path) -> Option { + // project path without package.json + let project_path = manifest_file.parent()?; + let version_file_path = project_path.join(".node-version"); + let version_content = std::fs::read_to_string(version_file_path).ok()?; + let version = version_content + .trim() + .strip_prefix('v') + .unwrap_or(version_content.trim()); + + match Version::parse(version) { + Ok(node) => Some(PlatformSpec { + node, + yarn: None, + npm: None, + pnpm: None, + }), + Err(_) => None, + } + } + /// Returns a reference to the manifest file for the current project pub fn manifest_file(&self) -> &Path { &self.manifest_file From 8d3182f9e8228605f451b15ba73d2be8efdda672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Sjo=CC=88berg?= Date: Wed, 5 Mar 2025 21:01:01 +0100 Subject: [PATCH 2/4] fix: Tests and error handling --- .../empty-dot-node-version/.node-version | 0 .../empty-dot-node-version/package.json | 6 ++ .../dot-node-version/has-both/.node-version | 1 + .../dot-node-version/has-both/package.json | 9 +++ .../dot-node-version/has-none/package.json | 6 ++ .../malformed-dot-node-version/.node-version | 1 + .../malformed-dot-node-version/package.json | 6 ++ .../.node-version | 1 + .../only-dot-node-version-with-v/package.json | 6 ++ .../only-dot-node-version/.node-version | 1 + .../only-dot-node-version/package.json | 6 ++ crates/volta-core/src/error/kind.rs | 10 ++- crates/volta-core/src/project/mod.rs | 34 +++++++-- crates/volta-core/src/project/tests.rs | 72 +++++++++++++++++++ 14 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 crates/volta-core/fixtures/dot-node-version/empty-dot-node-version/.node-version create mode 100644 crates/volta-core/fixtures/dot-node-version/empty-dot-node-version/package.json create mode 100644 crates/volta-core/fixtures/dot-node-version/has-both/.node-version create mode 100644 crates/volta-core/fixtures/dot-node-version/has-both/package.json create mode 100644 crates/volta-core/fixtures/dot-node-version/has-none/package.json create mode 100644 crates/volta-core/fixtures/dot-node-version/malformed-dot-node-version/.node-version create mode 100644 crates/volta-core/fixtures/dot-node-version/malformed-dot-node-version/package.json create mode 100644 crates/volta-core/fixtures/dot-node-version/only-dot-node-version-with-v/.node-version create mode 100644 crates/volta-core/fixtures/dot-node-version/only-dot-node-version-with-v/package.json create mode 100644 crates/volta-core/fixtures/dot-node-version/only-dot-node-version/.node-version create mode 100644 crates/volta-core/fixtures/dot-node-version/only-dot-node-version/package.json diff --git a/crates/volta-core/fixtures/dot-node-version/empty-dot-node-version/.node-version b/crates/volta-core/fixtures/dot-node-version/empty-dot-node-version/.node-version new file mode 100644 index 000000000..e69de29bb diff --git a/crates/volta-core/fixtures/dot-node-version/empty-dot-node-version/package.json b/crates/volta-core/fixtures/dot-node-version/empty-dot-node-version/package.json new file mode 100644 index 000000000..59de32abd --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/empty-dot-node-version/package.json @@ -0,0 +1,6 @@ +{ + "name": "dot-node-version", + "version": "0.0.1", + "description": "Testing .node-version file", + "license": "To Kill" +} diff --git a/crates/volta-core/fixtures/dot-node-version/has-both/.node-version b/crates/volta-core/fixtures/dot-node-version/has-both/.node-version new file mode 100644 index 000000000..ceda194c2 --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/has-both/.node-version @@ -0,0 +1 @@ +6.11.0 \ No newline at end of file diff --git a/crates/volta-core/fixtures/dot-node-version/has-both/package.json b/crates/volta-core/fixtures/dot-node-version/has-both/package.json new file mode 100644 index 000000000..82fc2d78c --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/has-both/package.json @@ -0,0 +1,9 @@ +{ + "name": "dot-node-version", + "version": "0.0.1", + "description": "Testing .node-version file", + "license": "To Kill", + "volta": { + "node": "6.11.1" + } +} diff --git a/crates/volta-core/fixtures/dot-node-version/has-none/package.json b/crates/volta-core/fixtures/dot-node-version/has-none/package.json new file mode 100644 index 000000000..59de32abd --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/has-none/package.json @@ -0,0 +1,6 @@ +{ + "name": "dot-node-version", + "version": "0.0.1", + "description": "Testing .node-version file", + "license": "To Kill" +} diff --git a/crates/volta-core/fixtures/dot-node-version/malformed-dot-node-version/.node-version b/crates/volta-core/fixtures/dot-node-version/malformed-dot-node-version/.node-version new file mode 100644 index 000000000..82e397876 --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/malformed-dot-node-version/.node-version @@ -0,0 +1 @@ +v6 \ No newline at end of file diff --git a/crates/volta-core/fixtures/dot-node-version/malformed-dot-node-version/package.json b/crates/volta-core/fixtures/dot-node-version/malformed-dot-node-version/package.json new file mode 100644 index 000000000..59de32abd --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/malformed-dot-node-version/package.json @@ -0,0 +1,6 @@ +{ + "name": "dot-node-version", + "version": "0.0.1", + "description": "Testing .node-version file", + "license": "To Kill" +} diff --git a/crates/volta-core/fixtures/dot-node-version/only-dot-node-version-with-v/.node-version b/crates/volta-core/fixtures/dot-node-version/only-dot-node-version-with-v/.node-version new file mode 100644 index 000000000..536f023d0 --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/only-dot-node-version-with-v/.node-version @@ -0,0 +1 @@ +v6.11.0 \ No newline at end of file diff --git a/crates/volta-core/fixtures/dot-node-version/only-dot-node-version-with-v/package.json b/crates/volta-core/fixtures/dot-node-version/only-dot-node-version-with-v/package.json new file mode 100644 index 000000000..59de32abd --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/only-dot-node-version-with-v/package.json @@ -0,0 +1,6 @@ +{ + "name": "dot-node-version", + "version": "0.0.1", + "description": "Testing .node-version file", + "license": "To Kill" +} diff --git a/crates/volta-core/fixtures/dot-node-version/only-dot-node-version/.node-version b/crates/volta-core/fixtures/dot-node-version/only-dot-node-version/.node-version new file mode 100644 index 000000000..ceda194c2 --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/only-dot-node-version/.node-version @@ -0,0 +1 @@ +6.11.0 \ No newline at end of file diff --git a/crates/volta-core/fixtures/dot-node-version/only-dot-node-version/package.json b/crates/volta-core/fixtures/dot-node-version/only-dot-node-version/package.json new file mode 100644 index 000000000..59de32abd --- /dev/null +++ b/crates/volta-core/fixtures/dot-node-version/only-dot-node-version/package.json @@ -0,0 +1,6 @@ +{ + "name": "dot-node-version", + "version": "0.0.1", + "description": "Testing .node-version file", + "license": "To Kill" +} diff --git a/crates/volta-core/src/error/kind.rs b/crates/volta-core/src/error/kind.rs index 6add88efa..2ab8c3e6f 100644 --- a/crates/volta-core/src/error/kind.rs +++ b/crates/volta-core/src/error/kind.rs @@ -106,6 +106,10 @@ pub enum ErrorKind { advice: String, }, + DotNodeVersionMalformed { + file: PathBuf, + }, + DownloadToolNetworkError { tool: tool::Spec, from_url: String, @@ -679,7 +683,10 @@ at {} ), ErrorKind::DeprecatedCommandError { command, advice } => { write!(f, "The subcommand `{}` is deprecated.\n{}", command, advice) - } + }, + ErrorKind::DotNodeVersionMalformed { file } => { + write!(f, "The .node-version file at {} is malformed", file.display()) + }, ErrorKind::DownloadToolNetworkError { tool, from_url } => write!( f, "Could not download {} @@ -1479,6 +1486,7 @@ impl ErrorKind { ErrorKind::DeleteDirectoryError { .. } => ExitCode::FileSystemError, ErrorKind::DeleteFileError { .. } => ExitCode::FileSystemError, ErrorKind::DeprecatedCommandError { .. } => ExitCode::InvalidArguments, + ErrorKind::DotNodeVersionMalformed { .. } => ExitCode::ConfigurationError, ErrorKind::DownloadToolNetworkError { .. } => ExitCode::NetworkError, ErrorKind::ExecuteHookError { .. } => ExitCode::ExecutionFailure, ErrorKind::ExtensionCycleError { .. } => ExitCode::ConfigurationError, diff --git a/crates/volta-core/src/project/mod.rs b/crates/volta-core/src/project/mod.rs index 9ef69d53f..5b38d6f0e 100644 --- a/crates/volta-core/src/project/mod.rs +++ b/crates/volta-core/src/project/mod.rs @@ -111,7 +111,10 @@ impl Project { let platform = match platform.map(TryInto::try_into).transpose()? { Some(platform) => Some(platform), - None => Self::platform_from_node_version(&manifest_file), + None => match Self::platform_from_node_version(&manifest_file)? { + Some(platform) => Some(platform), + None => None, + }, }; Ok(Project { @@ -123,24 +126,41 @@ impl Project { } /// Returns a Node.js version from .node_version_file - fn platform_from_node_version(manifest_file: &Path) -> Option { + fn platform_from_node_version( + manifest_file: &Path, + ) -> Result, VoltaError> { // project path without package.json - let project_path = manifest_file.parent()?; + let project_path = match manifest_file.parent() { + Some(path) => path, + None => return Ok(None), + }; let version_file_path = project_path.join(".node-version"); - let version_content = std::fs::read_to_string(version_file_path).ok()?; + + if !version_file_path.exists() { + return Ok(None); + } + + let version_content = std::fs::read_to_string(&version_file_path).map_err(|_| { + ErrorKind::DotNodeVersionMalformed { + file: version_file_path.clone(), + } + })?; let version = version_content .trim() .strip_prefix('v') .unwrap_or(version_content.trim()); match Version::parse(version) { - Ok(node) => Some(PlatformSpec { + Ok(node) => Ok(Some(PlatformSpec { node, yarn: None, npm: None, pnpm: None, - }), - Err(_) => None, + })), + Err(_) => Err(ErrorKind::DotNodeVersionMalformed { + file: version_file_path.clone(), + } + .into()), } } diff --git a/crates/volta-core/src/project/tests.rs b/crates/volta-core/src/project/tests.rs index a5f774727..107729916 100644 --- a/crates/volta-core/src/project/tests.rs +++ b/crates/volta-core/src/project/tests.rs @@ -80,6 +80,78 @@ mod project { assert_eq!(platform.yarn, Some("1.2.0".parse().unwrap())); } + #[test] + fn platform_dot_node_version() { + let project_path = fixture_path(&["dot-node-version", "only-dot-node-version"]); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let platform = test_project.platform().unwrap(); + + assert_eq!(platform.node, "6.11.0".parse().unwrap()); + } + + #[test] + fn platform_dot_node_version_and_package_json() { + let project_path = fixture_path(&["dot-node-version", "has-both"]); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let platform = test_project.platform().unwrap(); + + assert_eq!(platform.node, "6.11.1".parse().unwrap()); + } + + #[test] + fn platform_dot_node_version_with_v() { + let project_path = fixture_path(&["dot-node-version", "only-dot-node-version-with-v"]); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let platform = test_project.platform().unwrap(); + + assert_eq!(platform.node, "6.11.0".parse().unwrap()); + } + + #[test] + fn platform_dot_node_version_no_node() { + let project_path = fixture_path(&["dot-node-version", "has-none"]); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let platform = test_project.platform(); + + assert!(platform.is_none()); + } + + #[test] + fn platform_dot_node_version_malformed() { + let project_path = fixture_path(&["dot-node-version", "malformed-dot-node-version"]); + let project_error = Project::for_dir(project_path).unwrap_err(); + + match project_error.kind() { + ErrorKind::DotNodeVersionMalformed { file } => { + let expected_path = fixture_path(&[ + "dot-node-version", + "malformed-dot-node-version", + ".node-version", + ]); + assert_eq!(&expected_path, file); + } + kind => panic!("Wrong error kind: {:?}", kind), + } + } + + #[test] + fn platform_dot_node_version_empty() { + let project_path = fixture_path(&["dot-node-version", "empty-dot-node-version"]); + let project_error = Project::for_dir(project_path).unwrap_err(); + + match project_error.kind() { + ErrorKind::DotNodeVersionMalformed { file } => { + let expected_path = fixture_path(&[ + "dot-node-version", + "empty-dot-node-version", + ".node-version", + ]); + assert_eq!(&expected_path, file); + } + kind => panic!("Wrong error kind: {:?}", kind), + } + } + #[test] fn platform_workspace() { let project_path = fixture_path(&["nested", "subproject", "inner_project"]); From 441fa97bf055e381c9e0bbf3e53b7c89173de59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Sjo=CC=88berg?= Date: Thu, 6 Mar 2025 08:08:14 +0100 Subject: [PATCH 3/4] fix: Fix linting problems --- crates/volta-core/src/project/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/volta-core/src/project/mod.rs b/crates/volta-core/src/project/mod.rs index 5b38d6f0e..e906be0f0 100644 --- a/crates/volta-core/src/project/mod.rs +++ b/crates/volta-core/src/project/mod.rs @@ -111,10 +111,7 @@ impl Project { let platform = match platform.map(TryInto::try_into).transpose()? { Some(platform) => Some(platform), - None => match Self::platform_from_node_version(&manifest_file)? { - Some(platform) => Some(platform), - None => None, - }, + None => Self::platform_from_node_version(&manifest_file)?, }; Ok(Project { From 25e7c594b7d530b523d6657bfebcd69929ed7e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Sjo=CC=88berg?= Date: Thu, 6 Mar 2025 08:16:33 +0100 Subject: [PATCH 4/4] fix: Minor comment changes --- crates/volta-core/src/project/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/volta-core/src/project/mod.rs b/crates/volta-core/src/project/mod.rs index e906be0f0..cd6349359 100644 --- a/crates/volta-core/src/project/mod.rs +++ b/crates/volta-core/src/project/mod.rs @@ -122,11 +122,11 @@ impl Project { }) } - /// Returns a Node.js version from .node_version_file + /// Returns a Node.js version from .node-version file fn platform_from_node_version( manifest_file: &Path, ) -> Result, VoltaError> { - // project path without package.json + // Project path (without package.json) let project_path = match manifest_file.parent() { Some(path) => path, None => return Ok(None),