70 %
Chris Biscardi

Checking for a minimum node version in Rust with Command

In Toast we do some Command usage that calls out to node and requires features only available in node v14. To check the available node version and provide a useful error message we use the following code to parse node -v and check the version using the Rust semver crate.

tldr: The Code

rust
use color_eyre::eyre::{eyre, Result, WrapErr};
use semver::Version;
use std::process::Command;
fn check_node_version() -> Result<()> {
let minimum_required_node_major_version = Version {
major: 14,
minor: 0,
patch: 0,
pre: vec![],
build: vec![],
};
let mut cmd = Command::new("node");
cmd.arg("-v");
let output = cmd
.output()
.wrap_err_with(|| "Failed to execute `node -v` Command and collect output")?;
let version_string = std::str::from_utf8(&output.stdout)
.wrap_err_with(|| "Failed to create utf8 string from node -v Command output")?;
let version_string_trimmed =
version_string.trim_start_matches("v");
let current_node_version_result =
Version::parse(version_string_trimmed);
match current_node_version_result {
Ok(current_node_version) => {
if current_node_version < minimum_required_node_major_version {
Err(eyre!(format!(
"node version {} doesn't meet the minimum required version {}",
current_node_version, minimum_required_node_major_version
)))
} else {
Ok(())
}
}
Err(e) => Err(eyre!(format!(
"Couldn't parse node version from trimmed version `{}`, original string is `{}`",
version_string_trimmed, version_string
))),
}
}

The Error

The error that shows when everything works is

Error:
0: node version 11.15.0 doesn't meet the minimum required version 14.0.0

we get similar output if other things go wrong as well.

Explanation

We build a Version struct manually that in this case points to 14.0.0.

rust
let minimum_required_node_major_version = Version {
major: 14,
minor: 0,
patch: 0,
pre: vec![],
build: vec![],
}

We set up a Command that runs node -v and try to capture the output. This could fail so we use eyre's tools to wrap the error with additional context if it does.

rust
let mut cmd = Command::new("node");
cmd.arg("-v");
let output = cmd
.output()
.wrap_err_with(|| "Failed to execute `node -v` Command and collect output")?;

The Command output brings stdout in as a Vec[u8] but we want a string so we run from_utf8 on it, which could fail.

rust
let version_string = std::str::from_utf8(&output.stdout)
.wrap_err_with(|| "Failed to create utf8 string from node -v Command output")?;

The trimmed version string is because Rust's semver package doesn't handle v in the semver string like node.js does, so we need to remove it. The Version::parse handles whitespace trimming for us though.

rust
let version_string_trimmed =
version_string.trim_start_matches("v");
let current_node_version_result =
Version::parse(version_string_trimmed);

Finally with all of the information we have we can do a comparison of the current node version against the minimum we support and relay relevant error messages regardless.

rust
match current_node_version_result {
Ok(current_node_version) => {
if current_node_version < minimum_required_node_major_version {
Err(eyre!(format!(
"node version {} doesn't meet the minimum required version {}",
current_node_version, minimum_required_node_major_version
)))
} else {
Ok(())
}
}
Err(e) => Err(eyre!(format!(
"Couldn't parse node version from trimmed version `{}`, original string is `{}`",
version_string_trimmed, version_string
))),
}