0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

BREAKING: feat(cli/installer): Support guessing the executable name (#5036)

This commit is contained in:
Nayeem Rahman 2020-05-01 20:33:11 +01:00 committed by GitHub
parent 6661e7e287
commit 96fd0f4692
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 247 additions and 80 deletions

View file

@ -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,
};
}
@ -641,6 +640,18 @@ fn repl_subcommand<'a, 'b>() -> App<'a, 'b> {
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")
@ -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()),

View file

@ -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();
@ -229,6 +255,61 @@ mod tests {
assert!(!is_remote_url("./dev/deno_std/http/file_server.ts"));
}
#[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");
@ -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");
@ -273,6 +354,60 @@ mod tests {
}
}
#[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(),
"http://localhost:4545/cli/tests/echo_server.ts",
vec![],
None,
Some(temp_dir.path().to_path_buf()),
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");
@ -281,10 +416,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");
@ -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());

View file

@ -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 {

View file

@ -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()

3
cli/tests/subdir/main.ts Normal file
View file

@ -0,0 +1,3 @@
if (import.meta.main) {
console.log("Hello, world!");
}

View file

@ -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
```

View file

@ -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";

View file

@ -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]

View file

@ -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