summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/flags.rs317
-rw-r--r--cli/installer.rs490
-rw-r--r--cli/lib.rs28
-rw-r--r--std/examples/README.md8
-rw-r--r--std/examples/catj.ts2
-rwxr-xr-xstd/http/file_server.ts2
-rw-r--r--std/installer/README.md90
-rw-r--r--std/manual.md20
8 files changed, 721 insertions, 236 deletions
diff --git a/cli/flags.rs b/cli/flags.rs
index d7a95d79e..1eda8765e 100644
--- a/cli/flags.rs
+++ b/cli/flags.rs
@@ -28,8 +28,6 @@ macro_rules! std_url {
};
}
-/// Used for `deno install...` subcommand
-const INSTALLER_URL: &str = std_url!("installer/mod.ts");
/// Used for `deno test...` subcommand
const TEST_RUNNER_URL: &str = std_url!("testing/runner.ts");
@@ -45,7 +43,12 @@ pub enum DenoSubcommand {
},
Help,
Info,
- Install,
+ Install {
+ dir: Option<String>,
+ exe_name: String,
+ module_url: String,
+ args: Vec<String>,
+ },
Repl,
Run,
Types,
@@ -92,6 +95,63 @@ pub struct DenoFlags {
pub lock_write: bool,
}
+impl DenoFlags {
+ /// Return list of permission arguments that are equivalent
+ /// to the ones used to create `self`.
+ pub fn to_permission_args(&self) -> Vec<String> {
+ let mut args = vec![];
+
+ if !self.read_whitelist.is_empty() {
+ let s = format!("--allow-read={}", self.read_whitelist.join(","));
+ args.push(s);
+ }
+
+ if self.allow_read {
+ args.push("--allow-read".to_string());
+ }
+
+ if !self.write_whitelist.is_empty() {
+ let s = format!("--allow-write={}", self.write_whitelist.join(","));
+ args.push(s);
+ }
+
+ if self.allow_write {
+ args.push("--allow-write".to_string());
+ }
+
+ if !self.net_whitelist.is_empty() {
+ let s = format!("--allow-net={}", self.net_whitelist.join(","));
+ args.push(s);
+ }
+
+ if self.allow_net {
+ args.push("--allow-net".to_string());
+ }
+
+ if self.allow_env {
+ args.push("--allow-env".to_string());
+ }
+
+ if self.allow_run {
+ args.push("--allow-run".to_string());
+ }
+
+ if self.allow_plugin {
+ args.push("--allow-plugin".to_string());
+ }
+
+ if self.allow_hrtime {
+ args.push("--allow-hrtime".to_string());
+ }
+
+ if self.allow_env {
+ args.push("--allow-env".to_string());
+ }
+
+ args
+ }
+}
+
static ENV_VARIABLES_HELP: &str = "ENVIRONMENT VARIABLES:
DENO_DIR Set deno's base directory
NO_COLOR Set to disable color
@@ -247,30 +307,32 @@ fn fmt_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
}
fn install_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
- flags.subcommand = DenoSubcommand::Run;
- flags.allow_read = true;
- flags.allow_write = true;
- flags.allow_net = true;
- flags.allow_env = true;
- flags.allow_run = true;
- flags.argv.push(INSTALLER_URL.to_string());
+ permission_args_parse(flags, matches);
- if matches.is_present("dir") {
+ let dir = if matches.is_present("dir") {
let install_dir = matches.value_of("dir").unwrap();
- flags.argv.push("--dir".to_string());
- println!("dir {}", install_dir);
- flags.argv.push(install_dir.to_string());
+ Some(install_dir.to_string())
} else {
- println!("no dir");
- }
+ None
+ };
- let exe_name = matches.value_of("exe_name").unwrap();
- flags.argv.push(String::from(exe_name));
+ let exe_name = matches.value_of("exe_name").unwrap().to_string();
+ let cmd_values = matches.values_of("cmd").unwrap();
+ let mut cmd_args = vec![];
- let cmd = matches.values_of("cmd").unwrap();
- for arg in cmd {
- flags.argv.push(String::from(arg));
+ for value in cmd_values {
+ cmd_args.push(value.to_string());
}
+
+ let module_url = cmd_args[0].to_string();
+ let args = cmd_args[1..].to_vec();
+
+ flags.subcommand = DenoSubcommand::Install {
+ dir,
+ exe_name,
+ module_url,
+ args,
+ };
}
fn bundle_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
@@ -375,63 +437,8 @@ fn run_test_args_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
config_arg_parse(flags, matches);
v8_flags_arg_parse(flags, matches);
no_remote_arg_parse(flags, matches);
+ permission_args_parse(flags, matches);
- if matches.is_present("allow-read") {
- if matches.value_of("allow-read").is_some() {
- let read_wl = matches.values_of("allow-read").unwrap();
- let raw_read_whitelist: Vec<String> =
- read_wl.map(std::string::ToString::to_string).collect();
- flags.read_whitelist = resolve_fs_whitelist(&raw_read_whitelist);
- debug!("read whitelist: {:#?}", &flags.read_whitelist);
- } else {
- flags.allow_read = true;
- }
- }
- if matches.is_present("allow-write") {
- if matches.value_of("allow-write").is_some() {
- let write_wl = matches.values_of("allow-write").unwrap();
- let raw_write_whitelist: Vec<String> =
- write_wl.map(std::string::ToString::to_string).collect();
- flags.write_whitelist =
- resolve_fs_whitelist(raw_write_whitelist.as_slice());
- debug!("write whitelist: {:#?}", &flags.write_whitelist);
- } else {
- flags.allow_write = true;
- }
- }
- if matches.is_present("allow-net") {
- if matches.value_of("allow-net").is_some() {
- let net_wl = matches.values_of("allow-net").unwrap();
- let raw_net_whitelist =
- net_wl.map(std::string::ToString::to_string).collect();
- flags.net_whitelist = resolve_hosts(raw_net_whitelist);
- debug!("net whitelist: {:#?}", &flags.net_whitelist);
- } else {
- flags.allow_net = true;
- }
- }
- if matches.is_present("allow-env") {
- flags.allow_env = true;
- }
- if matches.is_present("allow-run") {
- flags.allow_run = true;
- }
- if matches.is_present("allow-plugin") {
- flags.allow_plugin = true;
- }
- if matches.is_present("allow-hrtime") {
- flags.allow_hrtime = true;
- }
- if matches.is_present("allow-all") {
- flags.allow_read = true;
- flags.allow_env = true;
- flags.allow_net = true;
- flags.allow_run = true;
- flags.allow_read = true;
- flags.allow_write = true;
- flags.allow_plugin = true;
- flags.allow_hrtime = true;
- }
if matches.is_present("cached-only") {
flags.cached_only = true;
}
@@ -542,7 +549,7 @@ fn repl_subcommand<'a, 'b>() -> App<'a, 'b> {
}
fn install_subcommand<'a, 'b>() -> App<'a, 'b> {
- SubCommand::with_name("install")
+ permission_args(SubCommand::with_name("install"))
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name("dir")
@@ -566,13 +573,13 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> {
"Installs a script as executable. The default installation directory is
$HOME/.deno/bin and it must be added to the path manually.
- deno install file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
+ 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 -d /usr/local/bin file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read")
+ deno install --allow-net --allow-read -d /usr/local/bin file_server https://deno.land/std/http/file_server.ts")
}
fn bundle_subcommand<'a, 'b>() -> App<'a, 'b> {
@@ -681,15 +688,8 @@ Once cached, static imports no longer send network requests
)
}
-fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
+fn permission_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
app
- .arg(importmap_arg())
- .arg(reload_arg())
- .arg(config_arg())
- .arg(lock_arg())
- .arg(lock_write_arg())
- .arg(no_remote_arg())
- .arg(v8_flags_arg())
.arg(
Arg::with_name("allow-read")
.long("allow-read")
@@ -743,6 +743,17 @@ fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
.long("allow-all")
.help("Allow all permissions"),
)
+}
+
+fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
+ permission_args(app)
+ .arg(importmap_arg())
+ .arg(reload_arg())
+ .arg(config_arg())
+ .arg(lock_arg())
+ .arg(lock_write_arg())
+ .arg(no_remote_arg())
+ .arg(v8_flags_arg())
.arg(
Arg::with_name("cached-only")
.long("cached-only")
@@ -952,6 +963,65 @@ fn no_remote_arg_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
}
}
+fn permission_args_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
+ if matches.is_present("allow-read") {
+ if matches.value_of("allow-read").is_some() {
+ let read_wl = matches.values_of("allow-read").unwrap();
+ let raw_read_whitelist: Vec<String> =
+ read_wl.map(std::string::ToString::to_string).collect();
+ flags.read_whitelist = resolve_fs_whitelist(&raw_read_whitelist);
+ debug!("read whitelist: {:#?}", &flags.read_whitelist);
+ } else {
+ flags.allow_read = true;
+ }
+ }
+ if matches.is_present("allow-write") {
+ if matches.value_of("allow-write").is_some() {
+ let write_wl = matches.values_of("allow-write").unwrap();
+ let raw_write_whitelist: Vec<String> =
+ write_wl.map(std::string::ToString::to_string).collect();
+ flags.write_whitelist =
+ resolve_fs_whitelist(raw_write_whitelist.as_slice());
+ debug!("write whitelist: {:#?}", &flags.write_whitelist);
+ } else {
+ flags.allow_write = true;
+ }
+ }
+ if matches.is_present("allow-net") {
+ if matches.value_of("allow-net").is_some() {
+ let net_wl = matches.values_of("allow-net").unwrap();
+ let raw_net_whitelist =
+ net_wl.map(std::string::ToString::to_string).collect();
+ flags.net_whitelist = resolve_hosts(raw_net_whitelist);
+ debug!("net whitelist: {:#?}", &flags.net_whitelist);
+ } else {
+ flags.allow_net = true;
+ }
+ }
+ if matches.is_present("allow-env") {
+ flags.allow_env = true;
+ }
+ if matches.is_present("allow-run") {
+ flags.allow_run = true;
+ }
+ if matches.is_present("allow-plugin") {
+ flags.allow_plugin = true;
+ }
+ if matches.is_present("allow-hrtime") {
+ flags.allow_hrtime = true;
+ }
+ if matches.is_present("allow-all") {
+ flags.allow_read = true;
+ flags.allow_env = true;
+ flags.allow_net = true;
+ flags.allow_run = true;
+ flags.allow_read = true;
+ flags.allow_write = true;
+ flags.allow_plugin = true;
+ flags.allow_hrtime = true;
+ }
+}
+
// TODO(ry) move this to utility module and add test.
/// Strips fragment part of URL. Panics on bad URL.
pub fn resolve_urls(urls: Vec<String>) -> Vec<String> {
@@ -1662,18 +1732,13 @@ mod tests {
assert_eq!(
r.unwrap(),
DenoFlags {
- subcommand: DenoSubcommand::Run,
- argv: svec![
- "deno",
- INSTALLER_URL,
- "deno_colors",
- "https://deno.land/std/examples/colors.ts"
- ],
- allow_write: true,
- allow_net: true,
- allow_read: true,
- allow_env: true,
- allow_run: true,
+ subcommand: DenoSubcommand::Install {
+ dir: None,
+ exe_name: "deno_colors".to_string(),
+ module_url: "https://deno.land/std/examples/colors.ts".to_string(),
+ args: vec![],
+ },
+ argv: svec!["deno"],
..DenoFlags::default()
}
);
@@ -1684,28 +1749,23 @@ mod tests {
let r = flags_from_vec_safe(svec![
"deno",
"install",
- "file_server",
- "https://deno.land/std/http/file_server.ts",
"--allow-net",
- "--allow-read"
+ "--allow-read",
+ "file_server",
+ "https://deno.land/std/http/file_server.ts"
]);
assert_eq!(
r.unwrap(),
DenoFlags {
- subcommand: DenoSubcommand::Run,
- argv: svec![
- "deno",
- INSTALLER_URL,
- "file_server",
- "https://deno.land/std/http/file_server.ts",
- "--allow-net",
- "--allow-read"
- ],
- allow_write: true,
+ subcommand: DenoSubcommand::Install {
+ dir: None,
+ exe_name: "file_server".to_string(),
+ module_url: "https://deno.land/std/http/file_server.ts".to_string(),
+ args: vec![],
+ },
+ argv: svec!["deno"],
allow_net: true,
allow_read: true,
- allow_env: true,
- allow_run: true,
..DenoFlags::default()
}
);
@@ -1718,30 +1778,25 @@ mod tests {
"install",
"-d",
"/usr/local/bin",
+ "--allow-net",
+ "--allow-read",
"file_server",
"https://deno.land/std/http/file_server.ts",
- "--allow-net",
- "--allow-read"
+ "arg1",
+ "arg2"
]);
assert_eq!(
r.unwrap(),
DenoFlags {
- subcommand: DenoSubcommand::Run,
- argv: svec![
- "deno",
- INSTALLER_URL,
- "--dir",
- "/usr/local/bin",
- "file_server",
- "https://deno.land/std/http/file_server.ts",
- "--allow-net",
- "--allow-read"
- ],
- allow_write: true,
+ subcommand: DenoSubcommand::Install {
+ dir: Some("/usr/local/bin".to_string()),
+ exe_name: "file_server".to_string(),
+ module_url: "https://deno.land/std/http/file_server.ts".to_string(),
+ args: svec!["arg1", "arg2"],
+ },
+ argv: svec!["deno"],
allow_net: true,
allow_read: true,
- allow_env: true,
- allow_run: true,
..DenoFlags::default()
}
);
diff --git a/cli/installer.rs b/cli/installer.rs
new file mode 100644
index 000000000..2d47f6a04
--- /dev/null
+++ b/cli/installer.rs
@@ -0,0 +1,490 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use crate::flags::DenoFlags;
+use regex::{Regex, RegexBuilder};
+use std::env;
+use std::fs;
+use std::fs::File;
+use std::io;
+use std::io::Error;
+use std::io::ErrorKind;
+use std::io::Write;
+#[cfg(not(windows))]
+use std::os::unix::fs::PermissionsExt;
+use std::path::PathBuf;
+use url::Url;
+
+lazy_static! {
+ static ref EXEC_NAME_RE: Regex = RegexBuilder::new(
+ r"^[a-z][\w-]*$"
+ ).case_insensitive(true).build().unwrap();
+ // Regular expression to test disk driver letter. eg "C:\\User\username\path\to"
+ static ref DRIVE_LETTER_REG: Regex = RegexBuilder::new(
+ r"^[c-z]:"
+ ).case_insensitive(true).build().unwrap();
+}
+
+fn yes_no_prompt(msg: &str) -> bool {
+ println!("{} [yN]", msg);
+ let mut buffer = String::new();
+ io::stdin()
+ .read_line(&mut buffer)
+ .expect("Couldn't read from stdin");
+ buffer.starts_with('y') | buffer.starts_with('Y')
+}
+
+fn is_remote_url(module_url: &str) -> bool {
+ module_url.starts_with("http://") || module_url.starts_with("https://")
+}
+
+fn validate_exec_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),
+ ))
+ }
+}
+
+#[cfg(windows)]
+fn generate_executable_file(
+ file_path: PathBuf,
+ commands: Vec<String>,
+) -> Result<(), Error> {
+ // On Windows if user is using Powershell .cmd extension is need to run the
+ // installed module.
+ // Generate batch script to satisfy that.
+ let template_header = "This executable is generated by Deno. Please don't modify it unless you know what it means.";
+ let commands: Vec<String> =
+ commands.iter().map(|c| format!("\"{}\"", c)).collect();
+ // TODO: remove conditionals in generated scripts
+ let template = format!(
+ r#"% {} %
+@IF EXIST "%~dp0\deno.exe" (
+ "%~dp0\deno.exe" {} %*
+) ELSE (
+ @SETLOCAL
+ @SET PATHEXT=%PATHEXT:;.TS;=;%
+ {} %*
+)
+"#,
+ template_header,
+ commands[1..].join(" "),
+ commands.join(" ")
+ );
+ let file_path = file_path.with_extension(".cmd");
+ let mut file = File::create(&file_path)?;
+ file.write_all(template.as_bytes())?;
+ Ok(())
+}
+
+#[cfg(not(windows))]
+fn generate_executable_file(
+ file_path: PathBuf,
+ commands: Vec<String>,
+) -> Result<(), Error> {
+ // On Windows if user is using Powershell .cmd extension is need to run the
+ // installed module.
+ // Generate batch script to satisfy that.
+ let template_header = "This executable is generated by Deno. Please don't modify it unless you know what it means.";
+ let commands: Vec<String> =
+ commands.iter().map(|c| format!("\"{}\"", c)).collect();
+
+ // TODO: remove conditionals in generated scripts
+ let template = format!(
+ r#"#!/bin/sh
+# {}
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
+
+case \`uname\` in
+ *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
+esac
+
+if [ -x "$basedir/deno" ]; then
+ "$basedir/deno" {} "$@"
+ ret=$?
+else
+ {} "$@"
+ ret=$?
+fi
+exit $ret
+"#,
+ template_header,
+ commands[1..].join(" "),
+ commands.join(" ")
+ );
+ let mut file = File::create(&file_path)?;
+ file.write_all(template.as_bytes())?;
+ let _metadata = fs::metadata(&file_path)?;
+ let mut permissions = _metadata.permissions();
+ permissions.set_mode(0o755);
+ fs::set_permissions(&file_path, permissions)?;
+ Ok(())
+}
+
+fn get_installer_dir() -> Result<PathBuf, Error> {
+ // In Windows's Powershell $HOME environmental variable maybe null
+ // if so use $USERPROFILE instead.
+ let home = env::var("HOME")
+ .map(String::into)
+ .unwrap_or_else(|_| "".to_string());
+ let user_profile = env::var("USERPROFILE")
+ .map(String::into)
+ .unwrap_or_else(|_| "".to_string());
+
+ if home.is_empty() && user_profile.is_empty() {
+ return Err(Error::new(ErrorKind::Other, "$HOME is not defined"));
+ }
+
+ let home_path = if !home.is_empty() { home } else { user_profile };
+
+ let mut home_path = PathBuf::from(home_path);
+ home_path.push(".deno");
+ home_path.push("bin");
+ Ok(home_path)
+}
+
+pub fn install(
+ flags: DenoFlags,
+ installation_dir: Option<String>,
+ exec_name: &str,
+ module_url: &str,
+ args: Vec<String>,
+) -> Result<(), Error> {
+ let installation_dir = if let Some(dir) = installation_dir {
+ PathBuf::from(dir).canonicalize()?
+ } else {
+ get_installer_dir()?
+ };
+
+ // ensure directory exists
+ if let Ok(metadata) = fs::metadata(&installation_dir) {
+ if !metadata.is_dir() {
+ return Err(Error::new(
+ ErrorKind::Other,
+ "Insallation path is not a directory",
+ ));
+ }
+ } else {
+ fs::create_dir_all(&installation_dir)?;
+ };
+
+ // Check if module_url is remote
+ let module_url = if is_remote_url(module_url) {
+ Url::parse(module_url).expect("Should be valid url")
+ } else {
+ let module_path = PathBuf::from(module_url);
+ let module_path = if module_path.is_absolute() {
+ module_path
+ } else {
+ let cwd = env::current_dir().unwrap();
+ cwd.join(module_path)
+ };
+ Url::from_file_path(module_path).expect("Path should be absolute")
+ };
+
+ validate_exec_name(exec_name)?;
+ let file_path = installation_dir.join(exec_name);
+
+ if file_path.exists() {
+ let msg = format!(
+ "⚠️ {} is already installed, do you want to overwrite it?",
+ exec_name
+ );
+ if !yes_no_prompt(&msg) {
+ return Ok(());
+ };
+ };
+
+ let mut executable_args = vec!["deno".to_string(), "run".to_string()];
+ executable_args.extend_from_slice(&flags.to_permission_args());
+ executable_args.push(module_url.to_string());
+ executable_args.extend_from_slice(&args);
+
+ generate_executable_file(file_path.to_owned(), executable_args)?;
+
+ println!("✅ Successfully installed {}", exec_name);
+ println!("{}", 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(())
+}
+
+fn is_in_path(dir: &PathBuf) -> bool {
+ if let Some(paths) = env::var_os("PATH") {
+ for p in env::split_paths(&paths) {
+ if *dir == p {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use tempfile::TempDir;
+
+ #[test]
+ fn test_is_remote_url() {
+ assert!(is_remote_url("https://deno.land/std/http/file_server.ts"));
+ assert!(is_remote_url("http://deno.land/std/http/file_server.ts"));
+ assert!(!is_remote_url("file:///dev/deno_std/http/file_server.ts"));
+ assert!(!is_remote_url("./dev/deno_std/http/file_server.ts"));
+ }
+
+ #[test]
+ fn install_basic() {
+ let temp_dir = TempDir::new().expect("tempdir fail");
+ let temp_dir_str = temp_dir.path().to_string_lossy().to_string();
+ let original_home = env::var_os("HOME");
+ let original_user_profile = env::var_os("HOME");
+ env::set_var("HOME", &temp_dir_str);
+ env::set_var("USERPROFILE", &temp_dir_str);
+
+ install(
+ DenoFlags::default(),
+ None,
+ "echo_test",
+ "http://localhost:4545/tests/echo_server.ts",
+ vec![],
+ )
+ .expect("Install failed");
+
+ let mut file_path = temp_dir.path().join(".deno/bin/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();
+
+ let expected_content = if cfg!(windows) {
+ r#"% This executable is generated by Deno. Please don't modify it unless you know what it means. %
+@IF EXIST "%~dp0\deno.exe" (
+ "%~dp0\deno.exe" "run" "http://localhost:4545/tests/echo_server.ts" %*
+) ELSE (
+ @SETLOCAL
+ @SET PATHEXT=%PATHEXT:;.TS;=;%
+ "deno" "run" "http://localhost:4545/tests/echo_server.ts" %*
+)
+"#
+ } else {
+ r#"#!/bin/sh
+# This executable is generated by Deno. Please don't modify it unless you know what it means.
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
+
+case \`uname\` in
+ *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
+esac
+
+if [ -x "$basedir/deno" ]; then
+ "$basedir/deno" "run" "http://localhost:4545/tests/echo_server.ts" "$@"
+ ret=$?
+else
+ "deno" "run" "http://localhost:4545/tests/echo_server.ts" "$@"
+ ret=$?
+fi
+exit $ret
+"#
+ };
+
+ assert_eq!(content, expected_content.to_string());
+
+ 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);
+ }
+ }
+
+ #[test]
+ fn install_custom_dir() {
+ let temp_dir = TempDir::new().expect("tempdir fail");
+
+ install(
+ DenoFlags::default(),
+ Some(temp_dir.path().to_string_lossy().to_string()),
+ "echo_test",
+ "http://localhost:4545/tests/echo_server.ts",
+ vec![],
+ )
+ .expect("Install failed");
+
+ let mut file_path = temp_dir.path().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();
+
+ let expected_content = if cfg!(windows) {
+ r#"% This executable is generated by Deno. Please don't modify it unless you know what it means. %
+@IF EXIST "%~dp0\deno.exe" (
+ "%~dp0\deno.exe" "run" "http://localhost:4545/tests/echo_server.ts" %*
+) ELSE (
+ @SETLOCAL
+ @SET PATHEXT=%PATHEXT:;.TS;=;%
+ "deno" "run" "http://localhost:4545/tests/echo_server.ts" %*
+)
+"#
+ } else {
+ r#"#!/bin/sh
+# This executable is generated by Deno. Please don't modify it unless you know what it means.
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
+
+case \`uname\` in
+ *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
+esac
+
+if [ -x "$basedir/deno" ]; then
+ "$basedir/deno" "run" "http://localhost:4545/tests/echo_server.ts" "$@"
+ ret=$?
+else
+ "deno" "run" "http://localhost:4545/tests/echo_server.ts" "$@"
+ ret=$?
+fi
+exit $ret
+"#
+ };
+
+ assert_eq!(content, expected_content.to_string());
+ }
+
+ #[test]
+ fn install_with_flags() {
+ let temp_dir = TempDir::new().expect("tempdir fail");
+
+ install(
+ DenoFlags {
+ allow_net: true,
+ allow_read: true,
+ ..DenoFlags::default()
+ },
+ Some(temp_dir.path().to_string_lossy().to_string()),
+ "echo_test",
+ "http://localhost:4545/tests/echo_server.ts",
+ vec!["--foobar".to_string()],
+ )
+ .expect("Install failed");
+
+ let mut file_path = temp_dir.path().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();
+
+ let expected_content = if cfg!(windows) {
+ r#"% This executable is generated by Deno. Please don't modify it unless you know what it means. %
+@IF EXIST "%~dp0\deno.exe" (
+ "%~dp0\deno.exe" "run" "--allow-read" "--allow-net" "http://localhost:4545/tests/echo_server.ts" "--foobar" %*
+) ELSE (
+ @SETLOCAL
+ @SET PATHEXT=%PATHEXT:;.TS;=;%
+ "deno" "run" "--allow-read" "--allow-net" "http://localhost:4545/tests/echo_server.ts" "--foobar" %*
+)
+"#
+ } else {
+ r#"#!/bin/sh
+# This executable is generated by Deno. Please don't modify it unless you know what it means.
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
+
+case \`uname\` in
+ *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
+esac
+
+if [ -x "$basedir/deno" ]; then
+ "$basedir/deno" "run" "--allow-read" "--allow-net" "http://localhost:4545/tests/echo_server.ts" "--foobar" "$@"
+ ret=$?
+else
+ "deno" "run" "--allow-read" "--allow-net" "http://localhost:4545/tests/echo_server.ts" "--foobar" "$@"
+ ret=$?
+fi
+exit $ret
+"#
+ };
+
+ assert_eq!(content, expected_content.to_string());
+ }
+
+ #[test]
+ fn install_local_module() {
+ let temp_dir = TempDir::new().expect("tempdir fail");
+ 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();
+
+ install(
+ DenoFlags::default(),
+ Some(temp_dir.path().to_string_lossy().to_string()),
+ "echo_test",
+ &local_module_str,
+ vec![],
+ )
+ .expect("Install failed");
+
+ let mut file_path = temp_dir.path().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();
+
+ let expected_content = if cfg!(windows) {
+ format!(
+ r#"% This executable is generated by Deno. Please don't modify it unless you know what it means. %
+@IF EXIST "%~dp0\deno.exe" (
+ "%~dp0\deno.exe" "run" "{}" %*
+) ELSE (
+ @SETLOCAL
+ @SET PATHEXT=%PATHEXT:;.TS;=;%
+ "deno" "run" "{}" %*
+)
+"#,
+ local_module_url.to_string(),
+ local_module_url.to_string()
+ )
+ } else {
+ format!(
+ r#"#!/bin/sh
+# This executable is generated by Deno. Please don't modify it unless you know what it means.
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
+
+case \`uname\` in
+ *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
+esac
+
+if [ -x "$basedir/deno" ]; then
+ "$basedir/deno" "run" "{}" "$@"
+ ret=$?
+else
+ "deno" "run" "{}" "$@"
+ ret=$?
+fi
+exit $ret
+"#,
+ local_module_url.to_string(),
+ local_module_url.to_string()
+ )
+ };
+
+ assert_eq!(content, expected_content);
+ }
+}
diff --git a/cli/lib.rs b/cli/lib.rs
index 655bd2413..3a894e0da 100644
--- a/cli/lib.rs
+++ b/cli/lib.rs
@@ -35,6 +35,7 @@ mod global_state;
mod global_timer;
mod http_util;
mod import_map;
+mod installer;
mod js;
mod lockfile;
mod metrics;
@@ -280,6 +281,27 @@ async fn info_command(flags: DenoFlags) {
js_check(result);
}
+async fn install_command(
+ flags: DenoFlags,
+ dir: Option<String>,
+ exe_name: String,
+ module_url: String,
+ args: Vec<String>,
+) {
+ // Firstly fetch and compile module, this
+ // ensures the module exists.
+ let mut fetch_flags = flags.clone();
+ fetch_flags.argv.push(module_url.to_string());
+ fetch_flags.reload = true;
+ fetch_command(fetch_flags).await;
+
+ let install_result =
+ installer::install(flags, dir, &exe_name, &module_url, args);
+ if let Err(e) = install_result {
+ print_msg_and_exit(&e.to_string());
+ }
+}
+
async fn fetch_command(flags: DenoFlags) {
let (mut worker, state) = create_worker_and_state(flags);
@@ -428,6 +450,12 @@ pub async fn main() {
DenoSubcommand::Fetch => fetch_command(flags).await,
DenoSubcommand::Format { check, files } => fmt_command(files, check).await,
DenoSubcommand::Info => info_command(flags).await,
+ DenoSubcommand::Install {
+ dir,
+ exe_name,
+ module_url,
+ args,
+ } => install_command(flags, dir, exe_name, module_url, args).await,
DenoSubcommand::Repl => run_repl(flags).await,
DenoSubcommand::Run => run_script(flags).await,
DenoSubcommand::Types => types_command(),
diff --git a/std/examples/README.md b/std/examples/README.md
index e2f6b2a08..351c4a6b4 100644
--- a/std/examples/README.md
+++ b/std/examples/README.md
@@ -16,13 +16,13 @@ deno https://deno.land/std/examples/echo_server.ts --allow-net
Or
```shell
-deno install echo_server https://deno.land/std/examples/echo_server.ts --allow-net
+deno install --allow-net echo_server https://deno.land/std/examples/echo_server.ts
```
### cat - print file to standard output
```shell
-deno install deno_cat https://deno.land/std/examples/cat.ts --allow-read
+deno install --allow-read 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 catj https://deno.land/std/examples/catj.ts --allow-read
+deno install --allow-read catj 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://den
```
export GIST_TOKEN=ABC # Generate at https://github.com/settings/tokens
-deno install gist https://deno.land/std/examples/gist.ts --allow-net --allow-env
+deno install --allow-net --allow-env gist 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 ef6fdb02f..7951eeeb0 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 catj https://deno.land/std/examples/catj.ts --allow-read
+// $ deno install --allow-read catj 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 4510023f7..6ec10cd65 100755
--- a/std/http/file_server.ts
+++ b/std/http/file_server.ts
@@ -48,7 +48,7 @@ if (serverArgs.h || serverArgs.help) {
Serves a local directory in HTTP.
INSTALL:
- deno install file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
+ deno install --allow-net --allow-read file_server https://deno.land/std/http/file_server.ts
USAGE:
file_server [path] [options]
diff --git a/std/installer/README.md b/std/installer/README.md
index f78c86e91..2fc7654ed 100644
--- a/std/installer/README.md
+++ b/std/installer/README.md
@@ -1,89 +1 @@
-# deno_installer
-
-Install remote or local script as executables.
-
-## Installation
-
-`installer` can be installed using itself:
-
-```sh
-deno -A https://deno.land/std/installer/mod.ts deno_installer https://deno.land/std/installer/mod.ts -A
-```
-
-## Usage
-
-Install script
-
-```sh
-# remote script
-$ deno_installer file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
-> [1/1] Compiling https://deno.land/std/http/file_server.ts
->
-> ✅ Successfully installed file_server.
-> ~/.deno/bin/file_server
-
-# local script
-$ deno_installer file_server ./deno_std/http/file_server.ts --allow-net --allow-read
-> [1/1] Compiling file:///dev/deno_std/http/file_server.ts
->
-> ✅ Successfully installed file_server.
-> ~/.deno/bin/file_server
-```
-
-Run installed script:
-
-```sh
-$ file_server
-HTTP server listening on http://0.0.0.0:4500/
-```
-
-## Custom installation directory
-
-By default installer uses `~/.deno/bin` to store installed scripts so make sure
-it's in your `$PATH`.
-
-```
-echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc # change this to your shell
-```
-
-If you prefer to change installation directory use `-d` or `--dir` flag.
-
-```
-$ deno_installer --dir /usr/local/bin file_server ./deno_std/http/file_server.ts --allow-net --allow-read
-> [1/1] Compiling file:///dev/deno_std/http/file_server.ts
->
-> ✅ Successfully installed file_server.
-> /usr/local/bin/file_server
-```
-
-## Update installed script
-
-```sh
-$ deno_installer file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
-> ⚠️ file_server is already installed, do you want to overwrite it? [yN]
-> y
->
-> [1/1] Compiling file:///dev/deno_std/http/file_server.ts
->
-> ✅ Successfully installed file_server.
-```
-
-Show help
-
-```sh
-$ deno_installer --help
-> deno installer
- Install remote or local script as executables.
-
-USAGE:
- deno -A https://deno.land/std/installer/mod.ts [OPTIONS] EXE_NAME SCRIPT_URL [FLAGS...]
-
-ARGS:
- EXE_NAME Name for executable
- SCRIPT_URL Local or remote URL of script to install
- [FLAGS...] List of flags for script, both Deno permission and script specific
- flag can be used.
-
-OPTIONS:
- -d, --dir <PATH> Installation directory path (defaults to ~/.deno/bin)
-```
+WARNING: This code is deprecated and std/installer will be removed soon.
diff --git a/std/manual.md b/std/manual.md
index d97e42705..9ccd0a4fe 100644
--- a/std/manual.md
+++ b/std/manual.md
@@ -285,7 +285,7 @@ await Deno.remove("request.log");
This one serves a local directory in HTTP.
```bash
-deno install file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
+deno install --allow-net --allow-read file_server https://deno.land/std/http/file_server.ts
```
Run it:
@@ -821,8 +821,8 @@ Or you could import it into another ES module to consume:
Deno provides ability to easily install and distribute executable code via
`deno install` command.
-`deno install [EXE_NAME] [URL] [FLAGS...]` will install script available at
-`URL` with name `EXE_NAME`.
+`deno install [FLAGS...] [EXE_NAME] [URL] [SCRIPT_ARGS...]` will install script
+available at `URL` with name `EXE_NAME`.
This command is a thin wrapper that creates executable shell scripts which
invoke `deno` with specified permissions and CLI flags.
@@ -830,15 +830,16 @@ invoke `deno` with specified permissions and CLI flags.
Example:
```shell
-$ deno install file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
+$ deno install --allow-net --allow-read file_server 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
```
-By default scripts are installed at `$HOME/.deno/bin` and that directory must be
-added to the path manually.
+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.
```shell
$ echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
@@ -847,17 +848,16 @@ $ echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
Installation directory can be changed using `-d/--dir` flag:
```shell
-$ deno install --dir /usr/local/bin file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
+$ 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. They are placed after the script URL and can be mixed with any
-additional CLI flags you want to pass to the script.
+the script.
Example:
```shell
-$ deno install file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read 8080
+$ 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