From 9aedd1d49608d2088f205c6146a3c32bb84fc347 Mon Sep 17 00:00:00 2001 From: SkyfallWasTaken Date: Wed, 10 Jul 2024 18:07:42 +0100 Subject: [PATCH] Add run functionality --- .gitignore | 3 ++- Cargo.lock | 10 ++++++++++ crates/dinopkg-cli/Cargo.toml | 6 +++++- crates/dinopkg-cli/src/command/run.rs | 26 ++++++++++++++++++++++++-- crates/dinopkg-cli/src/main.rs | 3 ++- crates/dinopkg-cli/src/run_script.rs | 25 +++++++++++++++++++++++++ crates/dinopkg-package-json/src/lib.rs | 7 ++++--- 7 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 crates/dinopkg-cli/src/run_script.rs diff --git a/.gitignore b/.gitignore index 187b4c2..4b075ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target package.json package-lock.json -bun.lockb \ No newline at end of file +bun.lockb +test.js \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8edf73f..51213ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1075,6 +1075,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -1225,6 +1234,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", diff --git a/crates/dinopkg-cli/Cargo.toml b/crates/dinopkg-cli/Cargo.toml index 6e6012d..2ab23ba 100644 --- a/crates/dinopkg-cli/Cargo.toml +++ b/crates/dinopkg-cli/Cargo.toml @@ -8,7 +8,11 @@ clap = { version = "4.5.9", features = ["derive"] } color-eyre = "0.6.3" owo-colors = "4.0.0" reqwest = "0.12.5" -tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.38.0", features = [ + "macros", + "rt-multi-thread", + "process", +] } dinopkg-package-json = { path = "../dinopkg-package-json", features = [ "tokio", ] } diff --git a/crates/dinopkg-cli/src/command/run.rs b/crates/dinopkg-cli/src/command/run.rs index 0207ac5..dcea191 100644 --- a/crates/dinopkg-cli/src/command/run.rs +++ b/crates/dinopkg-cli/src/command/run.rs @@ -2,8 +2,11 @@ use color_eyre::{eyre::eyre, Result}; use dinopkg_package_json::PackageJson; use owo_colors::OwoColorize; +use crate::run_script::{run_script, DEFAULT_SHELL, DEFAULT_SHELL_EXEC_ARG}; + pub async fn run(script_name: Option) -> Result<()> { - let package_json = PackageJson::from_file(10).await?; + let (package_json, package_json_path) = PackageJson::from_file(10).await?; + let root_path = package_json_path.parent().unwrap(); // Should never happen, `package.json` should always be there match script_name { Some(script_name) => { @@ -12,7 +15,26 @@ pub async fn run(script_name: Option) -> Result<()> { }; match scripts.get(&script_name) { Some(script) => { - log::info!("{script_name}: {script}") + println!("> {}", script.bold()); + + let status = + run_script(DEFAULT_SHELL, DEFAULT_SHELL_EXEC_ARG, &script, root_path) + .await?; + + if cfg!(unix) { + use std::os::unix::process::ExitStatusExt; + + if let Some(signal) = status.signal() { + return Err(eyre!(format!("process terminated by signal {signal}"))); + } + } + + // The only time the exit code isn't there is if the process was terminated by a signal. + // We check for that above (and on non-Unix systems, there will always be an exit code.) + let exit_code = status.code().unwrap(); + if exit_code != exitcode::OK { + return Err(eyre!(format!("process exited with code {exit_code}"))); + } } _ => return Err(eyre!(format!("script `{script_name}` not found"))), } diff --git a/crates/dinopkg-cli/src/main.rs b/crates/dinopkg-cli/src/main.rs index cd6bd10..e53c48f 100644 --- a/crates/dinopkg-cli/src/main.rs +++ b/crates/dinopkg-cli/src/main.rs @@ -1,8 +1,9 @@ use clap::Parser; +use color_eyre::Result; use env_logger::Env; mod command; -use color_eyre::Result; +mod run_script; use command::{Cli, Command}; #[tokio::main] diff --git a/crates/dinopkg-cli/src/run_script.rs b/crates/dinopkg-cli/src/run_script.rs new file mode 100644 index 0000000..7fa98df --- /dev/null +++ b/crates/dinopkg-cli/src/run_script.rs @@ -0,0 +1,25 @@ +use std::{path::Path, process::ExitStatus}; + +use color_eyre::eyre::Result; +use tokio::process::Command; + +pub const DEFAULT_SHELL: &str = if cfg!(windows) { "cmd.exe" } else { "/bin/sh" }; +pub const DEFAULT_SHELL_EXEC_ARG: &str = if cfg!(windows) { "/c" } else { "-c" }; + +pub async fn run_script( + shell: &str, + shell_exec_arg: &str, + command: &str, + cwd_path: &Path, +) -> Result { + // Scripts are run from the root of the package folder, regardless of what + // the current working directory is when npm run is called. As such, we + // do this too for compatibility (and also because it's ten times less annoying!) + let mut tokio_command = Command::new(shell); + debug_assert!(cwd_path.is_dir()); + tokio_command + .arg(shell_exec_arg) + .arg(command) + .current_dir(cwd_path); + Ok(tokio_command.status().await?) +} diff --git a/crates/dinopkg-package-json/src/lib.rs b/crates/dinopkg-package-json/src/lib.rs index 32cd056..da4f6f0 100644 --- a/crates/dinopkg-package-json/src/lib.rs +++ b/crates/dinopkg-package-json/src/lib.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::path::PathBuf; use serde::{Deserialize, Serialize}; @@ -43,13 +44,13 @@ impl PackageJson { } #[cfg(feature = "tokio")] - pub async fn from_file(max_attempts: usize) -> Result { + pub async fn from_file(max_attempts: usize) -> Result<(Self, PathBuf), Error> { let path = util::find_package_json(max_attempts).await?; let Some(path) = path else { return Err(Error::NotFound); }; - let file = tokio::fs::read(path).await?; + let file = tokio::fs::read(path.clone()).await?; let file = String::from_utf8(file)?; - Self::parse(&file) + Ok((Self::parse(&file)?, path)) } }