diff options
author | Nayeem Rahman <nayeemrmn99@gmail.com> | 2020-05-01 20:33:11 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-01 15:33:11 -0400 |
commit | 96fd0f4692126516239d61784caf6599aa884844 (patch) | |
tree | 8ebeafc5562297104390da4d0c159940006fc28f | |
parent | 6661e7e287aa595eccdc8d49940c40953b1f69bc (diff) |
BREAKING: feat(cli/installer): Support guessing the executable name (#5036)
-rw-r--r-- | cli/flags.rs | 77 | ||||
-rw-r--r-- | cli/installer.rs | 183 | ||||
-rw-r--r-- | cli/lib.rs | 15 | ||||
-rw-r--r-- | cli/tests/integration_tests.rs | 9 | ||||
-rw-r--r-- | cli/tests/subdir/main.ts | 3 | ||||
-rw-r--r-- | std/examples/README.md | 8 | ||||
-rw-r--r-- | std/examples/catj.ts | 2 | ||||
-rwxr-xr-x | std/http/file_server.ts | 2 | ||||
-rw-r--r-- | std/manual.md | 28 |
9 files changed, 247 insertions, 80 deletions
diff --git a/cli/flags.rs b/cli/flags.rs index c1cc2c443..866b36820 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -53,10 +53,10 @@ pub enum DenoSubcommand { file: Option<String>, }, Install { - root: Option<PathBuf>, - exe_name: String, module_url: String, args: Vec<String>, + name: Option<String>, + root: Option<PathBuf>, force: bool, }, Repl, @@ -358,22 +358,21 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { }; let force = matches.is_present("force"); - let exe_name = matches.value_of("exe_name").unwrap().to_string(); + let name = matches.value_of("name").map(|s| s.to_string()); let cmd_values = matches.values_of("cmd").unwrap(); - let mut cmd_args = vec![]; - + let mut cmd = vec![]; for value in cmd_values { - cmd_args.push(value.to_string()); + cmd.push(value.to_string()); } - let module_url = cmd_args[0].to_string(); - let args = cmd_args[1..].to_vec(); + let module_url = cmd[0].to_string(); + let args = cmd[1..].to_vec(); flags.subcommand = DenoSubcommand::Install { - root, - exe_name, + name, module_url, args, + root, force, }; } @@ -642,6 +641,18 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> { permission_args(SubCommand::with_name("install")) .setting(AppSettings::TrailingVarArg) .arg( + Arg::with_name("cmd") + .required(true) + .multiple(true) + .allow_hyphen_values(true)) + .arg( + Arg::with_name("name") + .long("name") + .short("n") + .help("Executable file name") + .takes_value(true) + .required(false)) + .arg( Arg::with_name("root") .long("root") .help("Installation root") @@ -653,26 +664,26 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> { .short("f") .help("Forcefully overwrite existing installation") .takes_value(false)) - .arg( - Arg::with_name("exe_name") - .required(true) - ) - .arg( - Arg::with_name("cmd") - .required(true) - .multiple(true) - .allow_hyphen_values(true) - ) .arg(ca_file_arg()) .arg(unstable_arg()) .about("Install script as an executable") .long_about( "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 + deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts + deno install https://deno.land/std/examples/colors.ts + +To change the executable name, use -n/--name: + deno install --allow-net --allow-read -n serve https://deno.land/std/http/file_server.ts + +The executable name is inferred by default: + - Attempt to take the file stem of the URL path. The above example would + become 'file_server'. + - If the file stem is something generic like 'main', 'mod', 'index' or 'cli', + and the path has no parent, take the file name of the parent path. Otherwise + settle with the generic name. 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 + deno install --allow-net --allow-read --root /usr/local https://deno.land/std/http/file_server.ts The installation root is determined, in order of precedence: - --root option @@ -2159,17 +2170,16 @@ mod tests { let r = flags_from_vec_safe(svec![ "deno", "install", - "deno_colors", "https://deno.land/std/examples/colors.ts" ]); assert_eq!( r.unwrap(), Flags { subcommand: DenoSubcommand::Install { - root: None, - exe_name: "deno_colors".to_string(), + name: None, module_url: "https://deno.land/std/examples/colors.ts".to_string(), args: vec![], + root: None, force: false, }, ..Flags::default() @@ -2184,6 +2194,7 @@ mod tests { "install", "--allow-net", "--allow-read", + "-n", "file_server", "https://deno.land/std/http/file_server.ts" ]); @@ -2191,10 +2202,10 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Install { - root: None, - exe_name: "file_server".to_string(), + name: Some("file_server".to_string()), module_url: "https://deno.land/std/http/file_server.ts".to_string(), args: vec![], + root: None, force: false, }, allow_net: true, @@ -2214,6 +2225,7 @@ mod tests { "-f", "--allow-net", "--allow-read", + "-n", "file_server", "https://deno.land/std/http/file_server.ts", "arg1", @@ -2223,10 +2235,10 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Install { - root: Some(PathBuf::from("/usr/local")), - exe_name: "file_server".to_string(), + name: Some("file_server".to_string()), module_url: "https://deno.land/std/http/file_server.ts".to_string(), args: svec!["arg1", "arg2"], + root: Some(PathBuf::from("/usr/local")), force: true, }, allow_net: true, @@ -2610,6 +2622,7 @@ mod tests { "install", "--cert", "example.crt", + "-n", "deno_colors", "https://deno.land/std/examples/colors.ts" ]); @@ -2617,10 +2630,10 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Install { - root: None, - exe_name: "deno_colors".to_string(), + name: Some("deno_colors".to_string()), module_url: "https://deno.land/std/examples/colors.ts".to_string(), args: vec![], + root: None, force: false, }, ca_file: Some("example.crt".to_owned()), diff --git a/cli/installer.rs b/cli/installer.rs index 472f9a3eb..e402b8d03 100644 --- a/cli/installer.rs +++ b/cli/installer.rs @@ -28,13 +28,13 @@ pub fn is_remote_url(module_url: &str) -> bool { lower.starts_with("http://") || lower.starts_with("https://") } -fn validate_exec_name(exec_name: &str) -> Result<(), Error> { +fn validate_name(exec_name: &str) -> Result<(), Error> { if EXEC_NAME_RE.is_match(exec_name) { Ok(()) } else { Err(Error::new( ErrorKind::Other, - format!("Invalid module name: {}", exec_name), + format!("Invalid executable name: {}", exec_name), )) } } @@ -103,12 +103,28 @@ fn get_installer_root() -> Result<PathBuf, Error> { Ok(home_path) } +fn infer_name_from_url(url: &Url) -> Option<String> { + let path = PathBuf::from(url.path()); + let stem = match path.file_stem() { + Some(stem) => stem.to_string_lossy().to_string(), + None => return None, + }; + if let Some(parent_path) = path.parent() { + if stem == "main" || stem == "mod" || stem == "index" || stem == "cli" { + if let Some(parent_name) = parent_path.file_name() { + return Some(parent_name.to_string_lossy().to_string()); + } + } + } + Some(stem) +} + pub fn install( flags: Flags, - root: Option<PathBuf>, - exec_name: &str, module_url: &str, args: Vec<String>, + name: Option<String>, + root: Option<PathBuf>, force: bool, ) -> Result<(), Error> { let root = if let Some(root) = root { @@ -144,8 +160,18 @@ pub fn install( Url::from_file_path(module_path).expect("Path should be absolute") }; - validate_exec_name(exec_name)?; - let mut file_path = installation_dir.join(exec_name); + let name = name.or_else(|| infer_name_from_url(&module_url)); + + let name = match name { + Some(name) => name, + None => return Err(Error::new( + ErrorKind::Other, + "An executable name was not provided. One could not be inferred from the URL. Aborting.", + )), + }; + + validate_name(name.as_str())?; + let mut file_path = installation_dir.join(&name); if cfg!(windows) { file_path = file_path.with_extension("cmd"); @@ -154,7 +180,7 @@ pub fn install( if file_path.exists() && !force { return Err(Error::new( ErrorKind::Other, - "Existing installation found. Aborting (Use -f to overwrite)", + "Existing installation found. Aborting (Use -f to overwrite).", )); }; @@ -187,7 +213,7 @@ pub fn install( generate_executable_file(file_path.to_owned(), executable_args)?; - println!("✅ Successfully installed {}", exec_name); + println!("✅ Successfully installed {}", name); println!("{}", file_path.to_string_lossy()); let installation_dir_str = installation_dir.to_string_lossy(); @@ -230,6 +256,61 @@ mod tests { } #[test] + fn install_infer_name_from_url() { + assert_eq!( + infer_name_from_url( + &Url::parse("https://example.com/abc/server.ts").unwrap() + ), + Some("server".to_string()) + ); + assert_eq!( + infer_name_from_url( + &Url::parse("https://example.com/abc/main.ts").unwrap() + ), + Some("abc".to_string()) + ); + assert_eq!( + infer_name_from_url( + &Url::parse("https://example.com/abc/mod.ts").unwrap() + ), + Some("abc".to_string()) + ); + assert_eq!( + infer_name_from_url( + &Url::parse("https://example.com/abc/index.ts").unwrap() + ), + Some("abc".to_string()) + ); + assert_eq!( + infer_name_from_url( + &Url::parse("https://example.com/abc/cli.ts").unwrap() + ), + Some("abc".to_string()) + ); + assert_eq!( + infer_name_from_url(&Url::parse("https://example.com/main.ts").unwrap()), + Some("main".to_string()) + ); + assert_eq!( + infer_name_from_url(&Url::parse("https://example.com").unwrap()), + None + ); + assert_eq!( + infer_name_from_url(&Url::parse("file:///abc/server.ts").unwrap()), + Some("server".to_string()) + ); + assert_eq!( + infer_name_from_url(&Url::parse("file:///abc/main.ts").unwrap()), + Some("abc".to_string()) + ); + assert_eq!( + infer_name_from_url(&Url::parse("file:///main.ts").unwrap()), + Some("main".to_string()) + ); + assert_eq!(infer_name_from_url(&Url::parse("file:///").unwrap()), None); + } + + #[test] fn install_basic() { let temp_dir = TempDir::new().expect("tempdir fail"); let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); @@ -244,10 +325,10 @@ mod tests { install( Flags::default(), - None, - "echo_test", "http://localhost:4545/cli/tests/echo_server.ts", vec![], + Some("echo_test".to_string()), + None, false, ) .expect("Install failed"); @@ -274,17 +355,71 @@ mod tests { } #[test] - fn install_custom_dir_option() { + 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(), + "http://localhost:4545/cli/tests/echo_server.ts", + vec![], + None, Some(temp_dir.path().to_path_buf()), - "echo_test", + 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(); + assert!(content + .contains(r#""run" "http://localhost:4545/cli/tests/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(), + "http://localhost:4545/cli/tests/subdir/main.ts", + vec![], + None, + Some(temp_dir.path().to_path_buf()), + false, + ) + .expect("Install failed"); + + 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(); + assert!(content + .contains(r#""run" "http://localhost:4545/cli/tests/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(), "http://localhost:4545/cli/tests/echo_server.ts", vec![], + Some("echo_test".to_string()), + Some(temp_dir.path().to_path_buf()), false, ) .expect("Install failed"); @@ -309,10 +444,10 @@ mod tests { install( Flags::default(), - None, - "echo_test", "http://localhost:4545/cli/tests/echo_server.ts", vec![], + Some("echo_test".to_string()), + None, false, ) .expect("Install failed"); @@ -341,10 +476,10 @@ mod tests { log_level: Some(Level::Error), ..Flags::default() }, - Some(temp_dir.path().to_path_buf()), - "echo_test", "http://localhost:4545/cli/tests/echo_server.ts", vec!["--foobar".to_string()], + Some("echo_test".to_string()), + Some(temp_dir.path().to_path_buf()), false, ) .expect("Install failed"); @@ -370,10 +505,10 @@ mod tests { install( Flags::default(), - Some(temp_dir.path().to_path_buf()), - "echo_test", &local_module_str, vec![], + Some("echo_test".to_string()), + Some(temp_dir.path().to_path_buf()), false, ) .expect("Install failed"); @@ -396,10 +531,10 @@ mod tests { install( Flags::default(), - Some(temp_dir.path().to_path_buf()), - "echo_test", "http://localhost:4545/cli/tests/echo_server.ts", vec![], + Some("echo_test".to_string()), + Some(temp_dir.path().to_path_buf()), false, ) .expect("Install failed"); @@ -413,10 +548,10 @@ mod tests { // No force. Install failed. let no_force_result = install( Flags::default(), - Some(temp_dir.path().to_path_buf()), - "echo_test", "http://localhost:4545/cli/tests/cat.ts", // using a different URL vec![], + Some("echo_test".to_string()), + Some(temp_dir.path().to_path_buf()), false, ); assert!(no_force_result.is_err()); @@ -431,10 +566,10 @@ mod tests { // Force. Install success. let force_result = install( Flags::default(), - Some(temp_dir.path().to_path_buf()), - "echo_test", "http://localhost:4545/cli/tests/cat.ts", // using a different URL vec![], + Some("echo_test".to_string()), + Some(temp_dir.path().to_path_buf()), true, ); assert!(force_result.is_ok()); diff --git a/cli/lib.rs b/cli/lib.rs index fc37ff2af..673340cb9 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -291,10 +291,10 @@ async fn info_command( async fn install_command( flags: Flags, - root: Option<PathBuf>, - exe_name: String, module_url: String, args: Vec<String>, + name: Option<String>, + root: Option<PathBuf>, force: bool, ) -> Result<(), ErrBox> { // Firstly fetch and compile module, this step ensures that module exists. @@ -304,7 +304,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, root, &exe_name, &module_url, args, force) + installer::install(flags, &module_url, args, name, root, force) .map_err(ErrBox::from) } @@ -583,13 +583,14 @@ pub fn main() { } DenoSubcommand::Info { file } => info_command(flags, file).boxed_local(), DenoSubcommand::Install { - root, - exe_name, module_url, args, + name, + root, force, - } => install_command(flags, root, exe_name, module_url, args, force) - .boxed_local(), + } => { + install_command(flags, module_url, args, name, root, force).boxed_local() + } DenoSubcommand::Repl => run_repl(flags).boxed_local(), DenoSubcommand::Run { script } => run_command(flags, script).boxed_local(), DenoSubcommand::Test { diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 66df5a70f..953f92c66 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -203,10 +203,10 @@ fn installer_test_local_module_run() { let local_module_str = local_module.to_string_lossy(); deno::installer::install( deno::flags::Flags::default(), - Some(temp_dir.path().to_path_buf()), - "echo_test", &local_module_str, vec!["hello".to_string()], + Some("echo_test".to_string()), + Some(temp_dir.path().to_path_buf()), false, ) .expect("Failed to install"); @@ -241,10 +241,10 @@ fn installer_test_remote_module_run() { std::fs::create_dir(&bin_dir).unwrap(); deno::installer::install( deno::flags::Flags::default(), - Some(temp_dir.path().to_path_buf()), - "echo_test", "http://localhost:4545/cli/tests/echo.ts", vec!["hello".to_string()], + Some("echo_test".to_string()), + Some(temp_dir.path().to_path_buf()), false, ) .expect("Failed to install"); @@ -1712,6 +1712,7 @@ fn cafile_install_remote_module() { .arg(cafile) .arg("--root") .arg(temp_dir.path()) + .arg("-n") .arg("echo_test") .arg("https://localhost:5545/cli/tests/echo.ts") .output() diff --git a/cli/tests/subdir/main.ts b/cli/tests/subdir/main.ts new file mode 100644 index 000000000..29acf42e0 --- /dev/null +++ b/cli/tests/subdir/main.ts @@ -0,0 +1,3 @@ +if (import.meta.main) { + console.log("Hello, world!"); +} diff --git a/std/examples/README.md b/std/examples/README.md index ea85da542..1c47d258e 100644 --- a/std/examples/README.md +++ b/std/examples/README.md @@ -16,13 +16,13 @@ deno --allow-net https://deno.land/std/examples/echo_server.ts Or ```shell -deno install --allow-net echo_server https://deno.land/std/examples/echo_server.ts +deno install --allow-net https://deno.land/std/examples/echo_server.ts ``` ### cat - print file to standard output ```shell -deno install --allow-read deno_cat https://deno.land/std/examples/cat.ts +deno install --allow-read -n deno_cat https://deno.land/std/examples/cat.ts deno_cat file.txt ``` @@ -31,7 +31,7 @@ deno_cat file.txt A very useful command by Soheil Rashidi ported to Deno. ```shell -deno install --allow-read catj https://deno.land/std/examples/catj.ts +deno install --allow-read https://deno.land/std/examples/catj.ts catj example.json catj file1.json file2.json echo example.json | catj - @@ -47,7 +47,7 @@ deno --allow-net=deno.land https://deno.land/std/examples/curl.ts https://deno.l ``` export GIST_TOKEN=ABC # Generate at https://github.com/settings/tokens -deno install --allow-net --allow-env gist https://deno.land/std/examples/gist.ts +deno install --allow-net --allow-env https://deno.land/std/examples/gist.ts gist --title "Example gist 1" script.ts gist --t "Example gist 2" script2.ts ``` diff --git a/std/examples/catj.ts b/std/examples/catj.ts index 3ef14ce0b..bb2e9051b 100644 --- a/std/examples/catj.ts +++ b/std/examples/catj.ts @@ -4,7 +4,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Install using `deno install` -// $ deno install --allow-read catj https://deno.land/std/examples/catj.ts +// $ deno install --allow-read https://deno.land/std/examples/catj.ts /* eslint-disable @typescript-eslint/no-use-before-define */ import { parse } from "../flags/mod.ts"; diff --git a/std/http/file_server.ts b/std/http/file_server.ts index cc92e0d47..ded930db5 100755 --- a/std/http/file_server.ts +++ b/std/http/file_server.ts @@ -63,7 +63,7 @@ if (serverArgs.h ?? serverArgs.help) { Serves a local directory in HTTP. INSTALL: - deno install --allow-net --allow-read file_server https://deno.land/std/http/file_server.ts + deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts USAGE: file_server [path] [options] diff --git a/std/manual.md b/std/manual.md index d1cd56e79..29abe2c66 100644 --- a/std/manual.md +++ b/std/manual.md @@ -289,7 +289,7 @@ await Deno.remove("request.log"); This one serves a local directory in HTTP. ```bash -deno install --allow-net --allow-read file_server https://deno.land/std/http/file_server.ts +deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts ``` Run it: @@ -876,8 +876,8 @@ Or you could import it into another ES module to consume: 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`. +`deno install [OPTIONS...] [URL] [SCRIPT_ARGS...]` will install the script +available at `URL` under the name `EXE_NAME`. 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 @@ -886,17 +886,31 @@ the specified CLI flags and main module. It is place in the installation root's Example: ```shell -$ deno install --allow-net --allow-read file_server https://deno.land/std/http/file_server.ts +$ deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts [1/1] Compiling https://deno.land/std/http/file_server.ts ✅ Successfully installed file_server. /Users/deno/.deno/bin/file_server ``` +To change the executable name, use `-n`/`--name`: + +```shell + deno install --allow-net --allow-read -n serve https://deno.land/std/http/file_server.ts +``` + +The executable name is inferred by default: + +- Attempt to take the file stem of the URL path. The above example would become + 'file_server'. +- If the file stem is something generic like 'main', 'mod', 'index' or 'cli', + and the path has no parent, take the file name of the parent path. Otherwise + settle with the generic name. + 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 +$ deno install --allow-net --allow-read --root /usr/local https://deno.land/std/http/file_server.ts ``` The installation root is determined, in order of precedence: @@ -915,7 +929,7 @@ 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 +$ deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts 8080 ``` The above command creates an executable called `file_server` that runs with @@ -944,7 +958,7 @@ example installation command to your repository: ```shell # Install using deno install -$ deno install awesome_cli https://example.com/awesome/cli.ts +$ deno install -n awesome_cli https://example.com/awesome/cli.ts ``` ## Proxies |