1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

refactor(installer): refactor installer code to be more testable (#13374)

This commit is contained in:
David Sherret 2022-01-14 13:23:47 -05:00 committed by GitHub
parent dc58063d00
commit 903cb48fe9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -46,11 +46,12 @@ fn validate_name(exec_name: &str) -> Result<(), AnyError> {
/// One compatible with cmd & powershell with a .cmd extension
/// A second compatible with git bash / MINGW64
/// Generate batch script to satisfy that.
fn generate_executable_file(
mut file_path: PathBuf,
args: Vec<String>,
) -> Result<(), AnyError> {
let args: Vec<String> = args.iter().map(|c| format!("\"{}\"", c)).collect();
fn generate_executable_file(shim_data: &ShimData) -> Result<(), AnyError> {
let args: Vec<String> = shim_data
.args
.iter()
.map(|c| format!("\"{}\"", c))
.collect();
let template = format!(
"% generated by deno install %\n@deno {} %*\n",
args
@ -59,12 +60,11 @@ fn generate_executable_file(
.collect::<Vec<_>>()
.join(" ")
);
let mut file = File::create(&file_path)?;
let mut file = File::create(&shim_data.file_path)?;
file.write_all(template.as_bytes())?;
// write file for bash
// create filepath without extensions
file_path.set_extension("");
let template = format!(
r#"#!/bin/sh
# generated by deno install
@ -72,19 +72,17 @@ deno {} "$@"
"#,
args.join(" "),
);
let mut file = File::create(&file_path)?;
let mut file = File::create(&shim_data.file_path.with_extension(""))?;
file.write_all(template.as_bytes())?;
Ok(())
}
#[cfg(not(windows))]
fn generate_executable_file(
file_path: PathBuf,
args: Vec<String>,
) -> Result<(), AnyError> {
fn generate_executable_file(shim_data: &ShimData) -> Result<(), AnyError> {
use shell_escape::escape;
let args: Vec<String> = args
.into_iter()
let args: Vec<String> = shim_data
.args
.iter()
.map(|c| escape(c.into()).into_owned())
.collect();
let template = format!(
@ -94,12 +92,12 @@ exec deno {} "$@"
"#,
args.join(" "),
);
let mut file = File::create(&file_path)?;
let mut file = File::create(&shim_data.file_path)?;
file.write_all(template.as_bytes())?;
let _metadata = fs::metadata(&file_path)?;
let _metadata = fs::metadata(&shim_data.file_path)?;
let mut permissions = _metadata.permissions();
permissions.set_mode(0o755);
fs::set_permissions(&file_path, permissions)?;
fs::set_permissions(&shim_data.file_path, permissions)?;
Ok(())
}
@ -195,27 +193,73 @@ pub fn install(
flags: Flags,
install_flags: InstallFlags,
) -> Result<(), AnyError> {
let root = if let Some(root) = install_flags.root {
canonicalize_path(&root)?
} else {
get_installer_root()?
};
let installation_dir = root.join("bin");
let shim_data = resolve_shim_data(&flags, &install_flags)?;
// ensure directory exists
if let Ok(metadata) = fs::metadata(&installation_dir) {
if let Ok(metadata) = fs::metadata(&shim_data.installation_dir) {
if !metadata.is_dir() {
return Err(generic_error("Installation path is not a directory"));
}
} else {
fs::create_dir_all(&installation_dir)?;
fs::create_dir_all(&shim_data.installation_dir)?;
};
if shim_data.file_path.exists() && !install_flags.force {
return Err(generic_error(
"Existing installation found. Aborting (Use -f to overwrite).",
));
};
generate_executable_file(&shim_data)?;
for (path, contents) in shim_data.extra_files {
fs::write(path, contents)?;
}
println!("✅ Successfully installed {}", shim_data.name);
println!("{}", shim_data.file_path.display());
if cfg!(windows) {
let display_path = shim_data.file_path.with_extension("");
println!("{} (shell)", display_path.display());
}
let installation_dir_str = shim_data.installation_dir.to_string_lossy();
if !is_in_path(&shim_data.installation_dir) {
println!(" Add {} to PATH", installation_dir_str);
if cfg!(windows) {
println!(" set PATH=%PATH%;{}", installation_dir_str);
} else {
println!(" export PATH=\"{}:$PATH\"", installation_dir_str);
}
}
Ok(())
}
struct ShimData {
name: String,
installation_dir: PathBuf,
file_path: PathBuf,
args: Vec<String>,
extra_files: Vec<(PathBuf, String)>,
}
fn resolve_shim_data(
flags: &Flags,
install_flags: &InstallFlags,
) -> Result<ShimData, AnyError> {
let root = if let Some(root) = &install_flags.root {
canonicalize_path(root)?
} else {
get_installer_root()?
};
let installation_dir = root.join("bin");
// Check if module_url is remote
let module_url = resolve_url_or_path(&install_flags.module_url)?;
let name = install_flags
.name
.clone()
.or_else(|| infer_name_from_url(&module_url));
let name = match name {
@ -232,12 +276,6 @@ pub fn install(
file_path = file_path.with_extension("cmd");
}
if file_path.exists() && !install_flags.force {
return Err(generic_error(
"Existing installation found. Aborting (Use -f to overwrite).",
));
};
let mut extra_files: Vec<(PathBuf, String)> = vec![];
let mut executable_args = vec!["run".to_string()];
@ -246,9 +284,9 @@ pub fn install(
executable_args.push("--location".to_string());
executable_args.push(url.to_string());
}
if let Some(ca_file) = flags.ca_file {
if let Some(ca_file) = &flags.ca_file {
executable_args.push("--cert".to_string());
executable_args.push(ca_file)
executable_args.push(ca_file.to_owned())
}
if let Some(log_level) = flags.log_level {
if log_level == Level::Error {
@ -311,13 +349,13 @@ pub fn install(
executable_args.push(format!("--inspect-brk={}", inspect_brk.to_string()));
}
if let Some(import_map_path) = flags.import_map_path {
let import_map_url = resolve_url_or_path(&import_map_path)?;
if let Some(import_map_path) = &flags.import_map_path {
let import_map_url = resolve_url_or_path(import_map_path)?;
executable_args.push("--import-map".to_string());
executable_args.push(import_map_url.to_string());
}
if let Some(config_path) = flags.config_path {
if let Some(config_path) = &flags.config_path {
let mut copy_path = file_path.clone();
copy_path.set_extension("tsconfig.json");
executable_args.push("--config".to_string());
@ -325,7 +363,7 @@ pub fn install(
extra_files.push((copy_path, fs::read_to_string(config_path)?));
}
if let Some(lock_path) = flags.lock {
if let Some(lock_path) = &flags.lock {
let mut copy_path = file_path.clone();
copy_path.set_extension("lock.json");
executable_args.push("--lock".to_string());
@ -336,29 +374,13 @@ pub fn install(
executable_args.push(module_url.to_string());
executable_args.extend_from_slice(&install_flags.args);
generate_executable_file(file_path.to_owned(), executable_args)?;
for (path, contents) in extra_files {
fs::write(path, contents)?;
}
println!("✅ Successfully installed {}", name);
println!("{}", file_path.to_string_lossy());
if cfg!(windows) {
file_path.set_extension("");
println!("{} (shell)", file_path.to_string_lossy());
}
let installation_dir_str = installation_dir.to_string_lossy();
if !is_in_path(&installation_dir) {
println!(" Add {} to PATH", installation_dir_str);
if cfg!(windows) {
println!(" set PATH=%PATH%;{}", installation_dir_str);
} else {
println!(" export PATH=\"{}:$PATH\"", installation_dir_str);
}
}
Ok(())
Ok(ShimData {
name,
installation_dir,
file_path,
args: executable_args,
extra_files,
})
}
fn is_in_path(dir: &Path) -> bool {
@ -484,6 +506,16 @@ mod tests {
)
.expect("Install failed");
if let Some(home) = original_home {
env::set_var("HOME", home);
}
if let Some(user_profile) = original_user_profile {
env::set_var("USERPROFILE", user_profile);
}
if let Some(install_root) = original_install_root {
env::set_var("DENO_INSTALL_ROOT", install_root);
}
let mut file_path = temp_dir.path().join(".deno/bin/echo_test");
assert!(file_path.exists());
@ -502,15 +534,6 @@ mod tests {
} else {
assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#));
}
if let Some(home) = original_home {
env::set_var("HOME", home);
}
if let Some(user_profile) = original_user_profile {
env::set_var("USERPROFILE", user_profile);
}
if let Some(install_root) = original_install_root {
env::set_var("DENO_INSTALL_ROOT", install_root);
}
}
#[test]
@ -554,142 +577,66 @@ mod tests {
}
#[test]
fn install_prompt() {
let temp_dir = TempDir::new().expect("tempdir fail");
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags {
prompt: true,
..Flags::default()
},
InstallFlags {
fn install_inferred_name() {
let shim_data = resolve_shim_data(
&Flags::default(),
&InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
root: Some(temp_dir.path().to_path_buf()),
name: None,
root: Some(env::temp_dir()),
force: false,
},
)
.unwrap();
let mut file_path = bin_dir.join("echo_test");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(content.contains(
r#""run" "--prompt" "http://localhost:4545/echo_server.ts""#
));
} else {
assert!(content
.contains(r#"run --prompt 'http://localhost:4545/echo_server.ts'"#));
}
}
#[test]
fn install_inferred_name() {
let temp_dir = TempDir::new().expect("tempdir fail");
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags::default(),
InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: None,
root: Some(temp_dir.path().to_path_buf()),
force: false,
},
)
.expect("Install failed");
let mut file_path = bin_dir.join("echo_server");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(
content.contains(r#""run" "http://localhost:4545/echo_server.ts""#)
);
} else {
assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#));
}
assert_eq!(shim_data.name, "echo_server");
assert_eq!(
shim_data.args,
vec!["run", "http://localhost:4545/echo_server.ts",]
);
}
#[test]
fn install_inferred_name_from_parent() {
let temp_dir = TempDir::new().expect("tempdir fail");
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags::default(),
InstallFlags {
let shim_data = resolve_shim_data(
&Flags::default(),
&InstallFlags {
module_url: "http://localhost:4545/subdir/main.ts".to_string(),
args: vec![],
name: None,
root: Some(temp_dir.path().to_path_buf()),
root: Some(env::temp_dir()),
force: false,
},
)
.expect("Install failed");
.unwrap();
let mut file_path = bin_dir.join("subdir");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(
content.contains(r#""run" "http://localhost:4545/subdir/main.ts""#)
);
} else {
assert!(content.contains(r#"run 'http://localhost:4545/subdir/main.ts'"#));
}
assert_eq!(shim_data.name, "subdir");
assert_eq!(
shim_data.args,
vec!["run", "http://localhost:4545/subdir/main.ts",]
);
}
#[test]
fn install_custom_dir_option() {
let temp_dir = TempDir::new().expect("tempdir fail");
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags::default(),
InstallFlags {
let shim_data = resolve_shim_data(
&Flags::default(),
&InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
root: Some(temp_dir.path().to_path_buf()),
root: Some(env::temp_dir()),
force: false,
},
)
.expect("Install failed");
.unwrap();
let mut file_path = bin_dir.join("echo_test");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(
content.contains(r#""run" "http://localhost:4545/echo_server.ts""#)
);
} else {
assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#));
}
assert_eq!(shim_data.name, "echo_test");
assert_eq!(
shim_data.args,
vec!["run", "http://localhost:4545/echo_server.ts",]
);
}
#[test]
@ -701,9 +648,9 @@ mod tests {
let original_install_root = env::var_os("DENO_INSTALL_ROOT");
env::set_var("DENO_INSTALL_ROOT", temp_dir.path().to_path_buf());
install(
Flags::default(),
InstallFlags {
let shim_data = resolve_shim_data(
&Flags::default(),
&InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
@ -711,100 +658,102 @@ mod tests {
force: false,
},
)
.expect("Install failed");
.unwrap();
let mut file_path = bin_dir.join("echo_test");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(
content.contains(r#""run" "http://localhost:4545/echo_server.ts""#)
);
} else {
assert!(content.contains(r#"run 'http://localhost:4545/echo_server.ts'"#));
}
if let Some(install_root) = original_install_root {
env::set_var("DENO_INSTALL_ROOT", install_root);
}
assert_eq!(
fs::canonicalize(shim_data.installation_dir).unwrap(),
fs::canonicalize(bin_dir).unwrap()
);
assert_eq!(shim_data.name, "echo_test");
assert_eq!(
shim_data.args,
vec!["run", "http://localhost:4545/echo_server.ts",]
);
}
#[test]
fn install_with_flags() {
let temp_dir = TempDir::new().expect("tempdir fail");
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags {
let shim_data = resolve_shim_data(
&Flags {
allow_net: Some(vec![]),
allow_read: Some(vec![]),
check: CheckFlag::None,
log_level: Some(Level::Error),
..Flags::default()
},
InstallFlags {
&InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec!["--foobar".to_string()],
name: Some("echo_test".to_string()),
root: Some(temp_dir.path().to_path_buf()),
force: false,
},
)
.expect("Install failed");
let mut file_path = bin_dir.join("echo_test");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert!(file_path.exists());
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(content.contains(r#""run" "--allow-read" "--allow-net" "--quiet" "--no-check" "http://localhost:4545/echo_server.ts" "--foobar""#));
} else {
assert!(content.contains(r#"run --allow-read --allow-net --quiet --no-check 'http://localhost:4545/echo_server.ts' --foobar"#));
}
}
#[test]
fn install_allow_all() {
let temp_dir = TempDir::new().expect("tempdir fail");
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
install(
Flags {
allow_all: true,
..Flags::default()
},
InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
root: Some(temp_dir.path().to_path_buf()),
root: Some(env::temp_dir()),
force: false,
},
)
.unwrap();
let mut file_path = bin_dir.join("echo_test");
if cfg!(windows) {
file_path = file_path.with_extension("cmd");
}
assert_eq!(shim_data.name, "echo_test");
assert_eq!(
shim_data.args,
vec![
"run",
"--allow-read",
"--allow-net",
"--quiet",
"--no-check",
"http://localhost:4545/echo_server.ts",
"--foobar",
]
);
}
let content = fs::read_to_string(file_path).unwrap();
if cfg!(windows) {
assert!(content.contains(
r#""run" "--allow-all" "http://localhost:4545/echo_server.ts""#
));
} else {
assert!(content
.contains(r#"run --allow-all 'http://localhost:4545/echo_server.ts'"#));
}
#[test]
fn install_prompt() {
let shim_data = resolve_shim_data(
&Flags {
prompt: true,
..Flags::default()
},
&InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
root: Some(env::temp_dir()),
force: false,
},
)
.unwrap();
assert_eq!(
shim_data.args,
vec!["run", "--prompt", "http://localhost:4545/echo_server.ts",]
);
}
#[test]
fn install_allow_all() {
let shim_data = resolve_shim_data(
&Flags {
allow_all: true,
..Flags::default()
},
&InstallFlags {
module_url: "http://localhost:4545/echo_server.ts".to_string(),
args: vec![],
name: Some("echo_test".to_string()),
root: Some(env::temp_dir()),
force: false,
},
)
.unwrap();
assert_eq!(
shim_data.args,
vec!["run", "--allow-all", "http://localhost:4545/echo_server.ts",]
);
}
#[test]