diff --git a/cli/flags.rs b/cli/flags.rs index 5f9740852c..772c719a88 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -53,7 +53,7 @@ pub enum DenoSubcommand { file: Option, }, Install { - dir: Option, + root: Option, exe_name: String, module_url: String, args: Vec, @@ -181,10 +181,13 @@ impl Flags { } static ENV_VARIABLES_HELP: &str = "ENVIRONMENT VARIABLES: - DENO_DIR Set deno's base directory - NO_COLOR Set to disable color - HTTP_PROXY Proxy address for HTTP requests (module downloads, fetch) - HTTPS_PROXY Same but for HTTPS"; + DENO_DIR Set deno's base directory (defaults to $HOME/.deno) + DENO_INSTALL_ROOT Set deno install's output directory + (defaults to $HOME/.deno/bin) + NO_COLOR Set to disable color + HTTP_PROXY Proxy address for HTTP requests + (module downloads, fetch) + HTTPS_PROXY Same but for HTTPS"; static DENO_HELP: &str = "A secure JavaScript and TypeScript runtime @@ -344,9 +347,9 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { permission_args_parse(flags, matches); ca_file_arg_parse(flags, matches); - let dir = if matches.is_present("dir") { - let install_dir = matches.value_of("dir").unwrap(); - Some(PathBuf::from(install_dir)) + let root = if matches.is_present("root") { + let install_root = matches.value_of("root").unwrap(); + Some(PathBuf::from(install_root)) } else { None }; @@ -364,7 +367,7 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { let args = cmd_args[1..].to_vec(); flags.subcommand = DenoSubcommand::Install { - dir, + root, exe_name, module_url, args, @@ -624,10 +627,9 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> { permission_args(SubCommand::with_name("install")) .setting(AppSettings::TrailingVarArg) .arg( - Arg::with_name("dir") - .long("dir") - .short("d") - .help("Installation directory (defaults to $HOME/.deno/bin)") + Arg::with_name("root") + .long("root") + .help("Installation root") .takes_value(true) .multiple(false)) .arg( @@ -647,15 +649,21 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> { .allow_hyphen_values(true) ) .arg(ca_file_arg()) - .about("Install script as executable") + .about("Install script as an executable") .long_about( -"Installs a script as executable. The default installation directory is -$HOME/.deno/bin and it must be added to the path manually. +"Installs a script as an executable in the installation root's bin directory. deno install --allow-net --allow-read file_server https://deno.land/std/http/file_server.ts deno install colors https://deno.land/std/examples/colors.ts -To change installation directory use -d/--dir flag: - deno install --allow-net --allow-read -d /usr/local/bin file_server https://deno.land/std/http/file_server.ts") +To change the installation root, use --root: + deno install --allow-net --allow-read --root /usr/local file_server https://deno.land/std/http/file_server.ts + +The installation root is determined, in order of precedence: + - --root option + - DENO_INSTALL_ROOT environment variable + - $HOME/.deno + +These must be added to the path manually if required.") } fn bundle_subcommand<'a, 'b>() -> App<'a, 'b> { @@ -2029,7 +2037,7 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Install { - dir: None, + root: None, exe_name: "deno_colors".to_string(), module_url: "https://deno.land/std/examples/colors.ts".to_string(), args: vec![], @@ -2054,7 +2062,7 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Install { - dir: None, + root: None, exe_name: "file_server".to_string(), module_url: "https://deno.land/std/http/file_server.ts".to_string(), args: vec![], @@ -2072,8 +2080,8 @@ mod tests { let r = flags_from_vec_safe(svec![ "deno", "install", - "-d", - "/usr/local/bin", + "--root", + "/usr/local", "-f", "--allow-net", "--allow-read", @@ -2086,7 +2094,7 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Install { - dir: Some(PathBuf::from("/usr/local/bin")), + root: Some(PathBuf::from("/usr/local")), exe_name: "file_server".to_string(), module_url: "https://deno.land/std/http/file_server.ts".to_string(), args: svec!["arg1", "arg2"], @@ -2478,7 +2486,7 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Install { - dir: None, + root: None, exe_name: "deno_colors".to_string(), module_url: "https://deno.land/std/examples/colors.ts".to_string(), args: vec![], diff --git a/cli/installer.rs b/cli/installer.rs index ff795f2dc0..9abc8cd31e 100644 --- a/cli/installer.rs +++ b/cli/installer.rs @@ -78,7 +78,10 @@ deno {} "$@" Ok(()) } -fn get_installer_dir() -> Result { +fn get_installer_root() -> Result { + if let Ok(env_dir) = env::var("DENO_INSTALL_ROOT").map(PathBuf::from) { + return env_dir.canonicalize(); + } // In Windows's Powershell $HOME environmental variable maybe null // if so use $USERPROFILE instead. let home = env::var("HOME") @@ -96,23 +99,23 @@ fn get_installer_dir() -> Result { let mut home_path = PathBuf::from(home_path); home_path.push(".deno"); - home_path.push("bin"); Ok(home_path) } pub fn install( flags: Flags, - installation_dir: Option, + root: Option, exec_name: &str, module_url: &str, args: Vec, force: bool, ) -> Result<(), Error> { - let installation_dir = if let Some(dir) = installation_dir { - dir.canonicalize()? + let root = if let Some(root) = root { + root.canonicalize()? } else { - get_installer_dir()? + get_installer_root()? }; + let installation_dir = root.join("bin"); // ensure directory exists if let Ok(metadata) = fs::metadata(&installation_dir) { @@ -268,8 +271,11 @@ mod tests { } #[test] - fn install_custom_dir() { + 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(), Some(temp_dir.path().to_path_buf()), @@ -280,7 +286,35 @@ mod tests { ) .expect("Install failed"); - let mut file_path = temp_dir.path().join("echo_test"); + 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(); + assert!(content + .contains(r#""run" "http://localhost:4545/cli/tests/echo_server.ts""#)); + } + + #[test] + fn install_custom_dir_env_var() { + let temp_dir = TempDir::new().expect("tempdir fail"); + let bin_dir = temp_dir.path().join("bin"); + std::fs::create_dir(&bin_dir).unwrap(); + env::set_var("DENO_INSTALL_ROOT", temp_dir.path().to_path_buf()); + + install( + Flags::default(), + None, + "echo_test", + "http://localhost:4545/cli/tests/echo_server.ts", + vec![], + false, + ) + .expect("Install failed"); + + let mut file_path = bin_dir.join("echo_test"); if cfg!(windows) { file_path = file_path.with_extension("cmd"); } @@ -294,6 +328,8 @@ mod tests { #[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 { @@ -310,7 +346,7 @@ mod tests { ) .expect("Install failed"); - let mut file_path = temp_dir.path().join("echo_test"); + let mut file_path = bin_dir.join("echo_test"); if cfg!(windows) { file_path = file_path.with_extension("cmd"); } @@ -323,6 +359,8 @@ mod tests { #[test] fn install_local_module() { let temp_dir = TempDir::new().expect("tempdir fail"); + let bin_dir = temp_dir.path().join("bin"); + std::fs::create_dir(&bin_dir).unwrap(); let local_module = env::current_dir().unwrap().join("echo_server.ts"); let local_module_url = Url::from_file_path(&local_module).unwrap(); let local_module_str = local_module.to_string_lossy(); @@ -337,7 +375,7 @@ mod tests { ) .expect("Install failed"); - let mut file_path = temp_dir.path().join("echo_test"); + let mut file_path = bin_dir.join("echo_test"); if cfg!(windows) { file_path = file_path.with_extension("cmd"); } @@ -350,6 +388,8 @@ mod tests { #[test] fn install_force() { 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(), @@ -361,7 +401,7 @@ mod tests { ) .expect("Install failed"); - let mut file_path = temp_dir.path().join("echo_test"); + let mut file_path = bin_dir.join("echo_test"); if cfg!(windows) { file_path = file_path.with_extension("cmd"); } diff --git a/cli/lib.rs b/cli/lib.rs index 79fe312a18..467c05708f 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -279,7 +279,7 @@ async fn info_command( async fn install_command( flags: Flags, - dir: Option, + root: Option, exe_name: String, module_url: String, args: Vec, @@ -292,7 +292,7 @@ async fn install_command( let main_module = ModuleSpecifier::resolve_url_or_path(&module_url)?; let mut worker = create_main_worker(global_state, main_module.clone())?; worker.preload_module(&main_module).await?; - installer::install(flags, dir, &exe_name, &module_url, args, force) + installer::install(flags, root, &exe_name, &module_url, args, force) .map_err(ErrBox::from) } @@ -570,12 +570,12 @@ pub fn main() { } DenoSubcommand::Info { file } => info_command(flags, file).boxed_local(), DenoSubcommand::Install { - dir, + root, exe_name, module_url, args, force, - } => install_command(flags, dir, exe_name, module_url, args, force) + } => install_command(flags, root, exe_name, module_url, args, force) .boxed_local(), DenoSubcommand::Repl => run_repl(flags).boxed_local(), DenoSubcommand::Run { script } => run_command(flags, script).boxed_local(), diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 905870c296..819ccda728 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -197,6 +197,8 @@ fn upgrade_in_tmpdir() { #[test] fn installer_test_local_module_run() { let temp_dir = TempDir::new().expect("tempdir fail"); + let bin_dir = temp_dir.path().join("bin"); + std::fs::create_dir(&bin_dir).unwrap(); let local_module = std::env::current_dir().unwrap().join("tests/echo.ts"); let local_module_str = local_module.to_string_lossy(); deno::installer::install( @@ -208,7 +210,7 @@ fn installer_test_local_module_run() { false, ) .expect("Failed to install"); - let mut file_path = temp_dir.path().join("echo_test"); + let mut file_path = bin_dir.join("echo_test"); if cfg!(windows) { file_path = file_path.with_extension("cmd"); } @@ -235,6 +237,8 @@ fn installer_test_local_module_run() { fn installer_test_remote_module_run() { let g = util::http_server(); let temp_dir = TempDir::new().expect("tempdir fail"); + let bin_dir = temp_dir.path().join("bin"); + std::fs::create_dir(&bin_dir).unwrap(); deno::installer::install( deno::flags::Flags::default(), Some(temp_dir.path().to_path_buf()), @@ -244,7 +248,7 @@ fn installer_test_remote_module_run() { false, ) .expect("Failed to install"); - let mut file_path = temp_dir.path().join("echo_test"); + let mut file_path = bin_dir.join("echo_test"); if cfg!(windows) { file_path = file_path.with_extension("cmd"); } @@ -1654,6 +1658,8 @@ fn cafile_install_remote_module() { let g = util::http_server(); let temp_dir = TempDir::new().expect("tempdir fail"); + let bin_dir = temp_dir.path().join("bin"); + std::fs::create_dir(&bin_dir).unwrap(); let deno_dir = TempDir::new().expect("tempdir fail"); let cafile = util::root_path().join("cli/tests/tls/RootCA.pem"); @@ -1663,7 +1669,7 @@ fn cafile_install_remote_module() { .arg("install") .arg("--cert") .arg(cafile) - .arg("--dir") + .arg("--root") .arg(temp_dir.path()) .arg("echo_test") .arg("https://localhost:5545/cli/tests/echo.ts") @@ -1671,7 +1677,7 @@ fn cafile_install_remote_module() { .expect("Failed to spawn script"); assert!(install_output.status.success()); - let mut echo_test_path = temp_dir.path().join("echo_test"); + let mut echo_test_path = bin_dir.join("echo_test"); if cfg!(windows) { echo_test_path = echo_test_path.with_extension("cmd"); } diff --git a/std/manual.md b/std/manual.md index 0381aaf2a9..e80a568724 100644 --- a/std/manual.md +++ b/std/manual.md @@ -874,14 +874,14 @@ Or you could import it into another ES module to consume: ### Installing executable scripts -Deno provides ability to easily install and distribute executable code via -`deno install` command. +Deno provides `deno install` to easily install and distribute executable code. `deno install [FLAGS...] [EXE_NAME] [URL] [SCRIPT_ARGS...]` will install the script available at `URL` under the name `EXE_NAME`. -This command is a thin wrapper that creates executable shell scripts which -invoke `deno` with specified permissions and CLI flags. +This command creates a thin, executable shell script which invokes `deno` using +the specified CLI flags and main module. It is place in the installation root's +`bin` directory. Example: @@ -893,36 +893,37 @@ $ deno install --allow-net --allow-read file_server https://deno.land/std/http/f /Users/deno/.deno/bin/file_server ``` -By default scripts are installed at `$HOME/.deno/bin` or -`$USERPROFILE/.deno/bin` and one of that directories must be added to the path -manually. +To change the installation root, use `--root`: + +```shell +$ deno install --allow-net --allow-read --root /usr/local file_server https://deno.land/std/http/file_server.ts +``` + +The installation root is determined, in order of precedence: + +- `--root` option +- `DENO_INSTALL_ROOT` environment variable +- `$HOME/.deno` + +These must be added to the path manually if required. ```shell $ echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc ``` -Installation directory can be changed using `-d/--dir` flag: - -```shell -$ deno install --allow-net --allow-read --dir /usr/local/bin file_server https://deno.land/std/http/file_server.ts -``` - -When installing a script you can specify permissions that will be used to run -the script. - -Example: +You must specify permissions that will be used to run the script at installation +time. ```shell $ deno install --allow-net --allow-read file_server https://deno.land/std/http/file_server.ts 8080 ``` -Above command creates an executable called `file_server` that runs with write -and read permissions and binds to port 8080. +The above command creates an executable called `file_server` that runs with +write and read permissions and binds to port 8080. -It is a good practice to use `import.meta.main` idiom for an entry point for -executable file. See -[Testing if current file is the main program](#testing-if-current-file-is-the-main-program) -section. +For good practice, use the +[`import.meta.main`](#testing-if-current-file-is-the-main-program) idiom to +specify the entry point in an executable script. Example: