1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 23:34:47 -05:00

feat: deno install in Rust (#3806)

//std/installer couldn't be removed due to bug, but it's now deprecated.
This commit is contained in:
Bartek Iwańczuk 2020-01-31 00:42:39 +01:00 committed by GitHub
parent 25b13c8917
commit de5c099b47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 721 additions and 236 deletions

View file

@ -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().to_string();
let cmd_values = matches.values_of("cmd").unwrap();
let mut cmd_args = vec![];
for value in cmd_values {
cmd_args.push(value.to_string());
}
let exe_name = matches.value_of("exe_name").unwrap();
flags.argv.push(String::from(exe_name));
let module_url = cmd_args[0].to_string();
let args = cmd_args[1..].to_vec();
let cmd = matches.values_of("cmd").unwrap();
for arg in cmd {
flags.argv.push(String::from(arg));
}
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()
}
);

490
cli/installer.rs Normal file
View file

@ -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);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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