Skip to content

Instantly share code, notes, and snippets.

@RandyMcMillan
Forked from rust-play/playground.rs
Created March 31, 2025 11:35
Show Gist options
  • Save RandyMcMillan/5ca601ce02ff19905ad1497c9d5acc08 to your computer and use it in GitHub Desktop.
Save RandyMcMillan/5ca601ce02ff19905ad1497c9d5acc08 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
use std::env;
//use std::env::consts::OS;
use std::fs;
use std::path::Path;
use std::process::{Command, Stdio};
use std::str;
fn nvm_has(command: &str) -> bool {
Command::new("type")
.arg(command)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map_or(false, |status| status.success())
}
fn nvm_echo(message: &str) {
Command::new("printf")
.arg(message)
.stdout(Stdio::inherit())
.stderr(Stdio::null())
.status()
.ok();
}
fn nvm_grep(args: &[&str]) -> Result<String, String> {
let output = Command::new("grep")
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.map_err(|e| format!("Failed to execute grep: {}", e))?;
if output.status.success() {
String::from_utf8(output.stdout).map_err(|e| format!("Invalid UTF-8: {}", e))
} else {
String::from_utf8(output.stderr).map_err(|e| format!("Grep failed: {}", e))
}
}
fn nvm_default_install_dir() -> String {
env::var("XDG_CONFIG_HOME").map_or_else(
|_| format!("{}/.nvm", env::var("HOME").unwrap_or_default()),
|xdg_config_home| format!("{}/nvm", xdg_config_home),
)
}
fn nvm_install_dir() -> String {
env::var("NVM_DIR").unwrap_or_else(|_| nvm_default_install_dir())
}
fn nvm_latest_version() -> String {
"v0.40.2".to_string()
}
fn nvm_profile_is_bash_or_zsh(profile: &str) -> bool {
profile.contains("/.bashrc")
|| profile.contains("/.bash_profile")
|| profile.contains("/.zshrc")
|| profile.contains("/.zprofile")
}
fn nvm_source(method: &str) -> Result<String, String> {
let nvm_github_repo = env::var("NVM_INSTALL_GITHUB_REPO").unwrap_or_else(|_| "nvm-sh/nvm".to_string());
if nvm_github_repo != "nvm-sh/nvm" {
eprintln!(
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n\
@ WARNING: REMOTE REPO IDENTIFICATION HAS CHANGED! @\n\
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n\
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n\n\
The default repository for this install is `nvm-sh/nvm`,\n\
but the environment variables `$NVM_INSTALL_GITHUB_REPO` is\n\
currently set to `{}`.\n\n\
If this is not intentional, interrupt this installation and\n\
verify your environment variables.",
nvm_github_repo
);
}
let nvm_version = env::var("NVM_INSTALL_VERSION").unwrap_or_else(|_| nvm_latest_version());
let nvm_source_url = match method {
"script-nvm-exec" => format!(
"https://raw.githubusercontent.com/{}/{}/nvm-exec",
nvm_github_repo, nvm_version
),
"script-nvm-bash-completion" => format!(
"https://raw.githubusercontent.com/{}/{}/bash_completion",
nvm_github_repo, nvm_version
),
"script" => format!(
"https://raw.githubusercontent.com/{}/{}/nvm.sh",
nvm_github_repo, nvm_version
),
"git" | "" => format!("https://github.com/{}.git", nvm_github_repo),
_ => return Err(format!("Unexpected value \"{}\" for $NVM_METHOD", method)),
};
Ok(nvm_source_url)
}
fn nvm_node_version() -> String {
env::var("NODE_VERSION").unwrap_or_default()
}
fn nvm_download(args: &[String]) -> Result<(), String> {
if nvm_has("curl") {
let mut command = Command::new("curl");
command.args(args);
let output = command
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map_err(|e| format!("Failed to execute curl: {}", e))?;
if !output.success(){
return Err("Curl failed".to_string());
}
} else if nvm_has("wget") {
let sed_args: Vec<String> = args
.iter()
.map(|arg| {
arg.replace("--progress-bar", "--progress=bar")
.replace("--compressed", "")
.replace("--fail", "")
.replace("-L", "")
.replace("-I", "--server-response")
.replace("-s ", "-q ")
.replace("-sS", "-nv")
.replace("-o", "-O")
.replace("-C -", "-c")
})
.collect();
let mut command = Command::new("wget");
command.args(sed_args);
let output = command
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map_err(|e| format!("Failed to execute wget: {}", e))?;
if !output.success(){
return Err("Wget failed".to_string());
}
} else {
return Err("Neither curl nor wget is available".to_string());
}
Ok(())
}
fn install_nvm_from_git() -> Result<(), String> {
let install_dir = nvm_install_dir();
let nvm_version = env::var("NVM_INSTALL_VERSION").unwrap_or_else(|_| nvm_latest_version());
if !nvm_version.is_empty() {
let _remote_ref = nvm_source("git")?;
let grep_output = nvm_grep(&["-q", &nvm_version])?;
if !grep_output.contains(&nvm_version) {
if nvm_download(&["-o".to_string(), "/dev/null".to_string(), nvm_source("script-nvm-exec")?])
.is_err()
{
return Err(format!("Failed to find '{}' version.", nvm_version));
}
}
}
if Path::new(&format!("{}/.git", install_dir)).exists() {
nvm_echo(&format!(
"=> nvm is already installed in {}, trying to update using git",
install_dir
));
nvm_echo("\r=> ");
} else {
nvm_echo(&format!("=> Downloading nvm from git to '{}'", install_dir));
nvm_echo("\r=> ");
fs::create_dir_all(&install_dir).map_err(|e| format!("Failed to create directory: {}", e))?;
if fs::read_dir(&install_dir)
.map_err(|e| format!("Failed to read directory: {}", e))?
.next()
.is_some()
{
Command::new("git")
.arg("init")
.arg(&install_dir)
.status()
.map_err(|e| format!("Failed to initialize git: {}", e))?;
Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("remote")
.arg("add")
.arg("origin")
.arg(nvm_source("")?)
.stderr(Stdio::null())
.status()
.or_else(|_| {
Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("remote")
.arg("set-url")
.arg("origin")
.arg(nvm_source("").expect(""))
.status()
})
.map_err(|e| format!("Failed to add remote: {}", e)).expect("");
} else {
Command::new("git")
.arg("clone")
.arg(nvm_source("")?)
.arg(format!("--depth=1"))
.arg(&install_dir)
.status()
.map_err(|e| format!("Failed to clone git: {}", e))?;
}
}
Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("fetch")
.arg("origin")
.arg("tag")
.arg(&nvm_version)
.arg("--depth=1")
.stderr(Stdio::null())
.status()
.or_else(|_| {
Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("fetch")
.arg("origin")
.arg(&nvm_version)
.arg("--depth=1")
.status()
})
.map_err(|e| format!("Failed to fetch: {}", e))?;
Command::new("git")
.arg("-c")
.arg("advice.detachedHead=false")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("checkout")
.arg("-f")
.arg("--quiet")
.arg("FETCH_HEAD")
.status()
.map_err(|e| format!("Failed to checkout: {}", e))?;
if Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("show-ref")
.arg("refs/heads/master")
.status()
.map_or(false, |status| status.success())
{
if Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("branch")
.arg("--quiet")
.stderr(Stdio::null())
.status()
.map_or(false, |status| status.success())
{
Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("branch")
.arg("-D")
.arg("master")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok();
} else {
eprintln!("Your version of git is out of date. Please update it!");
Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("branch")
.arg("-D")
.arg("master")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok();
}
}
nvm_echo("=> Compressing and cleaning up git repository");
if Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("reflog")
.arg("expire")
.arg("--expire=now")
.arg("--all")
.status()
.map_or(false, |status| status.success())
{
} else {
eprintln!("Your version of git is out of date. Please update it!");
}
if Command::new("git")
.arg(format!("--git-dir={}/.git", install_dir))
.arg(format!("--work-tree={}", install_dir))
.arg("gc")
.arg("--auto")
.arg("--aggressive")
.arg("--prune=now")
.status()
.map_or(false, |status| status.success())
{
} else {
eprintln!("Your version of git is out of date. Please update it!");
}
Ok(())
}
fn nvm_install_node() -> Result<(), String> {
let node_version = nvm_node_version();
if node_version.is_empty() {
return Ok(());
}
nvm_echo(&format!("=> Installing Node.js version {}", node_version));
Command::new("nvm")
.arg("install")
.arg(&node_version)
.status()
.map_err(|e| format!("Failed to install node: {}", e))?;
let current_node = Command::new("nvm")
.arg("current")
.output()
.map_err(|e| format!("Failed to get current node version: {}", e))?;
let current_node_str = str::from_utf8(&current_node.stdout)
.map_err(|e| format!("Invalid UTF-8: {}", e))?
.trim();
let installed_node = Command::new("nvm")
.arg("version")
.arg(&node_version)
.output()
.map_err(|e| format!("failed to get installed node version: {}",e))?;
let installed_node_str = str::from_utf8(&installed_node.stdout).map_err(|e| format!("Invalid UTF-8: {}", e))?.trim();
if installed_node_str == current_node_str {
nvm_echo(&format!(
"=> Node.js version {} has been successfully installed",
node_version
));
} else {
eprintln!("Failed to install Node.js {}", node_version);
}
Ok(())
}
fn install_nvm_as_script() -> Result<(), String> {
let install_dir = nvm_install_dir();
let nvm_source_script = nvm_source("script")?;
let nvm_exec_source = nvm_source("script-nvm-exec")?;
let nvm_bash_completion_source = nvm_source("script-nvm-bash-completion")?;
fs::create_dir_all(&install_dir).map_err(|e| format!("Failed to create directory: {}", e))?;
if Path::new(&format!("{}/nvm.sh", install_dir)).exists() {
nvm_echo(&format!(
"=> nvm is already installed in {}, trying to update the script",
install_dir
));
} else {
nvm_echo(&format!("=> Downloading nvm as script to '{}'", install_dir));
}
nvm_download(&["-s".to_string(), nvm_source_script, "-o".to_string(), format!("{}/nvm.sh", install_dir)])?;
nvm_download(&["-s".to_string(), nvm_exec_source, "-o".to_string(), format!("{}/nvm-exec", install_dir)])?;
nvm_download(&["-s".to_string(), nvm_bash_completion_source, "-o".to_string(), format!("{}/bash_completion", install_dir)])?;
Command::new("chmod")
.arg("a+x")
.arg(format!("{}/nvm-exec", install_dir))
.status()
.map_err(|e| format!("Failed to make nvm-exec executable: {}", e))?;
Ok(())
}
fn nvm_try_profile(profile: &str) -> Option<String> {
if !Path::new(profile).exists() {
return None;
}
Some(profile.to_string())
}
fn nvm_detect_profile() -> Option<String> {
if env::var("PROFILE").as_deref() == Ok("/dev/null") {
return None;
}
if let Ok(profile) = env::var("PROFILE") {
if Path::new(&profile).exists() {
return Some(profile);
}
}
if env::var("SHELL").unwrap_or_default().contains("bash") {
if Path::new(&format!("{}/.bashrc", env::var("HOME").unwrap_or_default())).exists() {
return Some(format!("{}/.bashrc", env::var("HOME").unwrap_or_default()));
} else if Path::new(&format!("{}/.bash_profile", env::var("HOME").unwrap_or_default())).exists() {
return Some(format!("{}/.bash_profile", env::var("HOME").unwrap_or_default()));
}
} else if env::var("SHELL").unwrap_or_default().contains("zsh") {
let zdotdir = env::var("ZDOTDIR").unwrap_or_else(|_| env::var("HOME").unwrap_or_default());
if Path::new(&format!("{}/.zshrc", zdotdir)).exists() {
return Some(format!("{}/.zshrc", zdotdir));
} else if Path::new(&format!("{}/.zprofile", zdotdir)).exists() {
return Some(format!("{}/.zprofile", zdotdir));
}
}
for profile in [".profile", ".bashrc", ".bash_profile", ".zprofile", ".zshrc"] {
if let Some(profile_path) = nvm_try_profile(&format!("{}/{}", env::var("HOME").unwrap_or_default(), profile)) {
return Some(profile_path);
}
}
None
}
fn nvm_check_global_modules() -> Result<(), String> {
let npm_command = Command::new("command")
.arg("-v")
.arg("npm")
.stderr(Stdio::null())
.output()
.map_or(None, |output| {
if output.status.success() {
String::from_utf8(output.stdout).ok()
} else {
None
}
});
if npm_command.is_none() {
return Ok(());
}
if env::var("NVM_DIR").is_ok() {
if npm_command.clone().unwrap().contains(&env::var("NVM_DIR").unwrap()) {
return Ok(());
}
}
let npm_version = Command::new("npm")
.arg("--version")
.output()
.map_or_else(|_| "-1".to_string(), |output| {
String::from_utf8(output.stdout).unwrap_or_default()
});
let npm_version_num = npm_version.split(|c: char| !c.is_ascii_digit()).next().unwrap_or("-1").parse::<i32>().unwrap_or(-1);
if npm_version_num <= 0 {
return Ok(());
}
let npm_global_modules = Command::new("npm")
.arg("list")
.arg("-g")
.arg("--depth=0")
.output()
.map_or_else(|_| "".to_string(), |output| {
String::from_utf8(output.stdout).unwrap_or_default()
});
let module_count = npm_global_modules
.lines()
.skip(1)
.filter(|line| !line.contains("npm@") && !line.contains("(empty)"))
.count();
if module_count != 0 {
eprintln!("=> You currently have modules installed globally with `npm`. These will no");
eprintln!("=> longer be linked to the active version of Node when you install a new node");
eprintln!("=> with `nvm`; and they may (depending on how you construct your `$PATH`)");
eprintln!("=> override the binaries of modules installed with `nvm`:");
eprintln!();
npm_global_modules.lines().skip(1).filter(|line| !line.contains("npm@") && !line.contains("(empty)")).for_each(|line| eprintln!("{}", line));
eprintln!("=> If you wish to uninstall them at a later point (or re-install them under your");
eprintln!("=> `nvm` node installs), you can remove them from the system Node as follows:");
eprintln!();
eprintln!(" $ nvm use system");
eprintln!(" $ npm uninstall -g a_module");
eprintln!();
}
Ok(())
}
fn nvm_do_install() -> Result<(), String> {
let nvm_dir = env::var("NVM_DIR").ok();
if let Some(dir) = nvm_dir {
if !Path::new(&dir).exists() {
if Path::new(&dir).exists() {
return Err(format!("File \"{}\" has the same name as installation directory.", dir));
}
if dir == nvm_default_install_dir() {
fs::create_dir_all(&dir).map_err(|e| format!("Failed to create directory: {}", e))?;
} else {
return Err(format!("You have $NVM_DIR set to \"{}\", but that directory does not exist. Check your profile files and environment.", dir));
}
}
}
if nvm_has("xcode-select") {
if Command::new("xcode-select")
.arg("-p")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map_or(false, |s| s.code() == Some(2))
{
if nvm_has("git") && Command::new("which").arg("git").output().map_or(false, |o| String::from_utf8_lossy(&o.stdout).contains("/usr/bin/git")) && nvm_has("curl") && Command::new("which").arg("curl").output().map_or(false, |o| String::from_utf8_lossy(&o.stdout).contains("/usr/bin/curl")) {
eprintln!("You may be on a Mac, and need to install the Xcode Command Line Developer Tools.");
eprintln!("If so, run `xcode-select --install` and try again. If not, please report this!");
return Err("Xcode Command Line Tools missing".to_string());
}
}
}
let method = env::var("METHOD").unwrap_or_default();
if method.is_empty() {
if nvm_has("git") {
install_nvm_from_git()?;
} else if nvm_has("curl") || nvm_has("wget") {
install_nvm_as_script()?;
} else {
return Err("You need git, curl, or wget to install nvm".to_string());
}
} else if method == "git" {
if !nvm_has("git") {
return Err("You need git to install nvm".to_string());
}
install_nvm_from_git()?;
} else if method == "script" {
if !nvm_has("curl") && !nvm_has("wget") {
return Err("You need curl or wget to install nvm".to_string());
}
install_nvm_as_script()?;
} else {
return Err(format!(
"The environment variable $METHOD is set to \"{}\", which is not recognized as a valid installation method.",
method
));
}
nvm_echo("");
let nvm_profile = nvm_detect_profile();
let profile_install_dir = nvm_install_dir().replace(&env::var("HOME").unwrap_or_default(), "$HOME");
let source_str = format!(
"\\nexport NVM_DIR=\"{}\"\\n[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\" # This loads nvm\\n",
profile_install_dir
);
let completion_str = format!(
"[ -s \"$NVM_DIR/bash_completion\" ] && \\. \"$NVM_DIR/bash_completion\" # This loads nvm bash_completion\\n"
);
let bash_or_zsh = nvm_profile.as_ref().map(|profile| nvm_profile_is_bash_or_zsh(profile)).unwrap_or(false);
if nvm_profile.is_none() {
let tried_profile = env::var("PROFILE").map(|p| format!("{} (as defined in $PROFILE), ", p)).unwrap_or_default();
nvm_echo(&format!(
"=> Profile not found. Tried {}~/.bashrc, ~/.bash_profile, ~/.zprofile, ~/.zshrc, and ~/.profile.",
tried_profile
));
nvm_echo("=> Create one of them and run this script again");
nvm_echo(" OR");
nvm_echo("=> Append the following lines to the correct file yourself:");
print!("{}", source_str);
nvm_echo("");
} else {
if !Command::new("grep").arg("-qc").arg("/nvm.sh").arg(nvm_profile.as_ref().unwrap()).status().map_or(false, |s| s.success()) {
nvm_echo(&format!("=> Appending nvm source string to {}", nvm_profile.as_ref().unwrap()));
//fs::OpenOptions::new().append(true).open(nvm_profile.as_ref().unwrap()).map_err(|e| format!("Failed to open file: {}",e))?.write(source_str.as_bytes()).map_err(|e| format!("failed to write to file: {}",e))?;
} else {
nvm_echo(&format!("=> nvm source string already in {}", nvm_profile.as_ref().unwrap()));
}
if bash_or_zsh && !Command::new("grep").arg("-qc").arg("$NVM_DIR/bash_completion").arg(nvm_profile.as_ref().unwrap()).status().map_or(false, |s| s.success()) {
nvm_echo(&format!("=> Appending bash_completion source string to {}", nvm_profile.as_ref().unwrap()));
//fs::OpenOptions::new().append(true).open(nvm_profile.as_ref().unwrap()).map_err(|e| format!("Failed to open file: {}",e))?.write(completion_str.as_bytes()).map_err(|e| format!("failed to write to file: {}",e))?;
} else {
nvm_echo(&format!("=> bash_completion source string already in {}", nvm_profile.as_ref().unwrap()));
}
}
if bash_or_zsh && nvm_profile.is_none() {
nvm_echo("=> Please also append the following lines to the if you are using bash/zsh shell:");
print!("{}", completion_str);
}
Command::new(format!("{}/nvm.sh", nvm_install_dir()))
.arg("reset")
.status()
.map_err(|e| format!("Failed to reset nvm: {}", e))?;
nvm_check_global_modules()?;
nvm_install_node()?;
nvm_echo("=> Close and reopen your terminal to start using nvm or run the following to use it now:");
print!("{}", source_str);
if bash_or_zsh {
print!("{}", completion_str);
}
Ok(())
}
fn main() {
if env::var("NVM_ENV").as_deref() != Ok("testing") {
if let Err(err) = nvm_do_install() {
eprintln!("Error: {}", err);
std::process::exit(1);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment