diff --git a/Cargo.lock b/Cargo.lock index 6851327..9098dcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1221,7 +1221,7 @@ dependencies = [ [[package]] name = "runrs" -version = "1.1.7" +version = "1.1.8" dependencies = [ "argh", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 28ed9e8..f886ff2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "runrs" -version = "1.1.7" +version = "1.1.8" edition = "2018" license = "MIT/Apache-2.0" -description = "A Tool for Run Rust Scripts" +description = "A Tool for Run Rust/TypeScript Scripts" readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index f563392..9a1d1da 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,13 @@ Runnable binaries: * ~/.cargo/bin/runrs * ~/.cargo/bin/rust-script -Simply call `rust-script` (https://github.com/fornwall/rust-script). \ No newline at end of file +Simply call `rust-script` (https://github.com/fornwall/rust-script). + +
+ +Run `*.ts` from URL: +```shell +runts 'https://script.hatter.ink/@0/cal-bun.ts' + +runts 'https://script.hatter.ink/@1/cal.ts' +``` \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b41be23..0f45810 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ mod run_ts; mod template; mod util; mod verify; +mod resolver; #[derive(FromArgs, PartialEq, Debug)] /// Run script diff --git a/src/resolver.rs b/src/resolver.rs new file mode 100644 index 0000000..dfbbf1d --- /dev/null +++ b/src/resolver.rs @@ -0,0 +1,97 @@ +use crate::util; +use rust_util::XResult; +use std::fs; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; + +// download from internet if starts with https:// +pub fn resolve_file(script_file: &str) -> XResult { + if script_file.starts_with("http://") { + return simple_error!("Insecure script file: {}", script_file); + } + if !script_file.starts_with("https://") { + return Ok(script_file.to_string()); + } + let file_sha256 = sha256::digest(script_file); + let file_name = get_file_name(script_file)?; + let cache_file_path = format!( + "{}/.cache/runrs/{}/{}", + util::get_user_home_or_die(), + file_sha256, + file_name + ); + debugging!("Cache file: {}", cache_file_path); + if let Ok(metadata) = fs::metadata(&cache_file_path) { + if metadata.is_file() { + debugging!("Found cache file: {}", cache_file_path); + return Ok(cache_file_path); + } + return simple_error!("Cache file is not a file: {}", cache_file_path); + } + + // create cache file parent path if not exists + let cache_file_path_path = Path::new(&cache_file_path); + match cache_file_path_path.parent() { + None => return simple_error!("Cache file has no parent: {}", cache_file_path), + Some(cache_file_path_path_parent) => { + if let Err(_) = fs::metadata(cache_file_path_path_parent) { + debugging!( + "Create cache file parent path: {:?}", + cache_file_path_path_parent + ); + if let Err(e) = fs::create_dir_all(cache_file_path_path_parent) { + return simple_error!( + "Create cache file parent dir: {:?}, failed: {}", + cache_file_path_path_parent, + e + ); + } + } + } + } + + // download script file + debugging!("Try get script: {}", script_file); + let get_script_response_result = reqwest::blocking::get(script_file); + debugging!("Get script response: {:#?}", &get_script_response_result); + let get_script_response = match get_script_response_result { + Err(e) => return simple_error!("Get script failed: {}", e), + Ok(response) => response, + }; + let get_script_response_status = get_script_response.status().as_u16(); + if get_script_response_status == 404 { + return simple_error!("Script not found!"); + } else if get_script_response_status != 200 { + return simple_error!("Get script failed: {}", get_script_response_status); + } + let remote_script_content = match get_script_response.text() { + Err(e) => { + return simple_error!("Get script: {} failed: {}", &script_file, e); + } + Ok(remote_script_content) => remote_script_content, + }; + // write script file to cache + if let Err(e) = fs::write(&cache_file_path, remote_script_content) { + return simple_error!("Write script: {} failed: {}", cache_file_path, e); + } + if let Err(_) = fs::write(&format!("{}.url", cache_file_path), script_file) { + // JUST IGNORE + } + // change script file permission + debugging!("Write file: {} success", cache_file_path); + match fs::set_permissions(&cache_file_path, PermissionsExt::from_mode(0o755)) { + Err(e) => { + return simple_error!("Chmod script: {} permission failed: {}", cache_file_path, e) + } + Ok(_) => debugging!("Chmod script: {} permission succeed", cache_file_path), + } + + Ok(cache_file_path) +} + +fn get_file_name(script_file: &str) -> XResult { + match script_file.split("/").last() { + None => simple_error!("Invalid script file: {}", script_file), + Some(file_name) => Ok(file_name.to_string()), + } +} diff --git a/src/run_rs.rs b/src/run_rs.rs index 5728895..d40c550 100644 --- a/src/run_rs.rs +++ b/src/run_rs.rs @@ -1,3 +1,4 @@ +use crate::resolver::resolve_file; use crate::{util, verify, RunScriptArgs}; use rust_util::util_env::is_env_on; use rust_util::util_os::get_user_home; @@ -7,6 +8,10 @@ pub fn do_run_script(args: &RunScriptArgs) { failure_and_exit!("Must assign a script file name"); } let script_file = &args.arguments[0]; + let script_file = resolve_file(script_file) + .unwrap_or_else(|e| failure_and_exit!("Failed to resolve script: {}", e)); + let script_file = &script_file; + verify::verify_script(script_file, is_env_on("RUNRS_SKIP_VERIFY")); let (_, script_sha256) = util::read_file_and_digest(script_file); diff --git a/src/run_ts.rs b/src/run_ts.rs index 28cc82c..4c8d3e2 100644 --- a/src/run_ts.rs +++ b/src/run_ts.rs @@ -1,3 +1,4 @@ +use crate::resolver::resolve_file; use crate::{verify, RunScriptArgs}; use rust_util::util_cmd; use rust_util::util_env::is_env_on; @@ -14,7 +15,7 @@ pub fn do_run_script(args: &RunScriptArgs) { debugging!("Run ts args: {:?}", args.arguments); let mut is_runtime_deno = true; - let (script_file, first_arg) = (|| { + let (raw_script_file, first_arg) = (|| { for (i, arg) in args.arguments.iter().enumerate() { if arg == RUNTIME_DENO || arg == RUNTIME_BUN { is_runtime_deno = arg == RUNTIME_DENO; @@ -25,6 +26,9 @@ pub fn do_run_script(args: &RunScriptArgs) { } (&args.arguments[args.arguments.len() - 1], false) })(); + let script_file = resolve_file(raw_script_file) + .unwrap_or_else(|e| failure_and_exit!("Failed to resolve script: {}", e)); + let script_file = &script_file; verify::verify_script(script_file, is_env_on("RUNTS_SKIP_VERIFY")); let mut cmd = Command::new("/usr/bin/env"); @@ -40,7 +44,11 @@ pub fn do_run_script(args: &RunScriptArgs) { .filter(|arg| { if *arg == RUNTIME_DENO || *arg == RUNTIME_BUN { is_runtime_deno = *arg == RUNTIME_DENO; - debugging!("Runts runtime arg: {}, is runtime deno: {}", *arg, is_runtime_deno); + debugging!( + "Runts runtime arg: {}, is runtime deno: {}", + *arg, + is_runtime_deno + ); return false; } true @@ -55,7 +63,11 @@ pub fn do_run_script(args: &RunScriptArgs) { if arg == RUNTIME_DENO || arg == RUNTIME_BUN { continue; } - cmd.arg(arg); + if arg == raw_script_file { // replace remote file with local file + cmd.arg(script_file); + } else { + cmd.arg(arg); + } } debugging!("Run command: {cmd:?}");