Skip to content

Commit 98dfa2c

Browse files
Merge pull request #1020 from bakkot/install-version-number
Add error for `volta install 10`
2 parents a6a275f + 588c79f commit 98dfa2c

File tree

3 files changed

+95
-16
lines changed

3 files changed

+95
-16
lines changed

Cargo.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/volta-core/src/error/kind.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ pub enum ErrorKind {
163163
version: String,
164164
},
165165

166+
/// Thrown when a user does e.g. `volta install 12` instead of
167+
/// `volta install node@12`.
168+
InvalidInvocationOfBareVersion {
169+
action: String,
170+
version: String,
171+
},
172+
166173
/// Thrown when a tool name is invalid per npm's rules.
167174
InvalidToolName {
168175
name: String,
@@ -770,6 +777,32 @@ To {action} the packages '{name}' and '{version}', please {action} them in separ
770777
write!(f, "{}\n\n{}", error, wrapped_cta)
771778
}
772779

780+
ErrorKind::InvalidInvocationOfBareVersion {
781+
action,
782+
version,
783+
} => {
784+
let error = format!(
785+
"`volta {action} {version}` is not supported.",
786+
action = action,
787+
version = version
788+
);
789+
790+
let call_to_action = format!(
791+
"To {action} node version '{version}', please run `volta {action} {formatted}`. \
792+
To {action} the package '{version}', please use an explicit version such as '{version}@latest'.",
793+
action=action,
794+
version=version,
795+
formatted=tool_version("node", version)
796+
);
797+
798+
let wrapped_cta = match text_width() {
799+
Some(width) => fill(&call_to_action, width),
800+
None => call_to_action,
801+
};
802+
803+
write!(f, "{}\n\n{}", error, wrapped_cta)
804+
}
805+
773806
ErrorKind::InvalidToolName { name, errors } => {
774807
let indentation = " ";
775808
let wrapped = match text_width() {
@@ -1383,6 +1416,7 @@ impl ErrorKind {
13831416
ErrorKind::InvalidHookCommand { .. } => ExitCode::ExecutableNotFound,
13841417
ErrorKind::InvalidHookOutput { .. } => ExitCode::ExecutionFailure,
13851418
ErrorKind::InvalidInvocation { .. } => ExitCode::InvalidArguments,
1419+
ErrorKind::InvalidInvocationOfBareVersion { .. } => ExitCode::InvalidArguments,
13861420
ErrorKind::InvalidToolName { .. } => ExitCode::InvalidArguments,
13871421
ErrorKind::LockAcquireError => ExitCode::FileSystemError,
13881422
ErrorKind::NoBundledNpm { .. } => ExitCode::ConfigurationError,

crates/volta-core/src/tool/serial.rs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,33 +83,50 @@ impl Spec {
8383
Ok(tools)
8484
}
8585

86-
/// Check the args for the bad pattern of `volta install <tool> <number>`.
86+
/// Check the args for the bad patterns of
87+
/// - `volta install <number>`
88+
/// - `volta install <tool> <number>`
8789
fn check_args<T>(args: &[T], action: &str) -> Fallible<()>
8890
where
8991
T: AsRef<str>,
9092
{
9193
let mut args = args.iter();
9294

93-
// The case we are concerned with is where we have `<tool> <number>`.
94-
// This is only interesting if there are exactly two args. Then we care
95-
// whether the two items are a bare name (with no `@version`), followed
96-
// by a valid version specifier (ignoring custom tags). That is:
97-
//
98-
// - `volta install node@lts latest` is allowed.
99-
// - `volta install node latest` is an error.
100-
// - `volta install node latest yarn` is allowed.
101-
if let (Some(name), Some(maybe_version), None) = (args.next(), args.next(), args.next()) {
102-
if !HAS_VERSION.is_match(name.as_ref()) && is_version_like(maybe_version.as_ref()) {
103-
return Err(ErrorKind::InvalidInvocation {
95+
match (args.next(), args.next(), args.next()) {
96+
// The case we are concerned with here is where we have `<number>`.
97+
// That is, exactly one argument, which is a valid version specifier.
98+
//
99+
// - `volta install node@12` is allowed.
100+
// - `volta install 12` is an error.
101+
// - `volta install lts` is an error.
102+
(Some(maybe_version), None, None) if is_version_like(maybe_version.as_ref()) => {
103+
Err(ErrorKind::InvalidInvocationOfBareVersion {
104+
action: action.to_string(),
105+
version: maybe_version.as_ref().to_string(),
106+
}
107+
.into())
108+
}
109+
// The case we are concerned with here is where we have `<tool> <number>`.
110+
// This is only interesting if there are exactly two args. Then we care
111+
// whether the two items are a bare name (with no `@version`), followed
112+
// by a valid version specifier (ignoring custom tags). That is:
113+
//
114+
// - `volta install node@lts latest` is allowed.
115+
// - `volta install node latest` is an error.
116+
// - `volta install node latest yarn` is allowed.
117+
(Some(name), Some(maybe_version), None)
118+
if !HAS_VERSION.is_match(name.as_ref())
119+
&& is_version_like(maybe_version.as_ref()) =>
120+
{
121+
Err(ErrorKind::InvalidInvocation {
104122
action: action.to_string(),
105123
name: name.as_ref().to_string(),
106124
version: maybe_version.as_ref().to_string(),
107125
}
108-
.into());
126+
.into())
109127
}
128+
_ => Ok(()),
110129
}
111-
112-
Ok(())
113130
}
114131

115132
/// Compare `Spec`s for sorting when converting from strings
@@ -358,6 +375,23 @@ mod tests {
358375

359376
static PIN: &str = "pin";
360377

378+
#[test]
379+
fn special_cases_just_number() {
380+
let version = "1.2.3";
381+
let args: Vec<String> = vec![version.into()];
382+
383+
let err = Spec::from_strings(&args, PIN).unwrap_err();
384+
385+
assert_eq!(
386+
err.kind(),
387+
&ErrorKind::InvalidInvocationOfBareVersion {
388+
action: PIN.into(),
389+
version: version.into()
390+
},
391+
"`volta <action> number` results in the correct error"
392+
);
393+
}
394+
361395
#[test]
362396
fn special_cases_tool_space_number() {
363397
let name = "potato";
@@ -393,6 +427,15 @@ mod tests {
393427
"when there is only one arg"
394428
);
395429

430+
let one_with_explicit_verson = ["10@latest".to_owned()];
431+
assert_eq!(
432+
Spec::from_strings(&one_with_explicit_verson, PIN)
433+
.expect("is ok")
434+
.len(),
435+
only_one.len(),
436+
"when the sole arg is version-like but has an explicit version"
437+
);
438+
396439
let two_but_unmistakable = ["12".to_owned(), "node".to_owned()];
397440
assert_eq!(
398441
Spec::from_strings(&two_but_unmistakable, PIN)

0 commit comments

Comments
 (0)