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 1e9d7379b..cd6349359 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,45 @@ impl Project { }) } + /// Returns a Node.js version from .node-version file + fn platform_from_node_version( + manifest_file: &Path, + ) -> Result, VoltaError> { + // Project path (without package.json) + let project_path = match manifest_file.parent() { + Some(path) => path, + None => return Ok(None), + }; + let version_file_path = project_path.join(".node-version"); + + 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) => Ok(Some(PlatformSpec { + node, + yarn: None, + npm: None, + pnpm: None, + })), + Err(_) => Err(ErrorKind::DotNodeVersionMalformed { + file: version_file_path.clone(), + } + .into()), + } + } + /// Returns a reference to the manifest file for the current project pub fn manifest_file(&self) -> &Path { &self.manifest_file 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"]);