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:?}");