diff --git a/.changeset/tall-jeans-retire.md b/.changeset/tall-jeans-retire.md new file mode 100644 index 0000000..7b13a1a --- /dev/null +++ b/.changeset/tall-jeans-retire.md @@ -0,0 +1,5 @@ +--- +"ny": minor +--- + +Add support for Bun, Bun, Bun, Bun. diff --git a/src/bin/ny.rs b/src/bin/ny.rs index 372b40c..3919f6d 100644 --- a/src/bin/ny.rs +++ b/src/bin/ny.rs @@ -23,7 +23,6 @@ fn main() -> Result<()> { let executor = RealExecutor {}; let fs = RealFs {}; let http_client = RealHttpClient {}; - // @todo this could be omitted for "run" command let agent = Agent::recognize(&fs, &cwd) .ok_or_else(|| eyre!("Couldn't find any lockfile inside {cwd:?} or any of its parents."))?; @@ -32,7 +31,7 @@ fn main() -> Result<()> { Some(Commands::Run { task, extra_args }) => { let task = task.as_str(); let extra_args: Vec<&str> = extra_args.iter().map(String::as_str).collect(); - run(&executor, &fs, task, &cwd, Some(&extra_args)) + run(&executor, &fs, &agent, task, &cwd, Some(&extra_args)) } Some(Commands::Add { packages, diff --git a/src/common/agent.rs b/src/common/agent.rs index d6cb7e9..66c7520 100644 --- a/src/common/agent.rs +++ b/src/common/agent.rs @@ -7,6 +7,7 @@ pub enum Agent { Npm, Yarn, Pnpm, + Bun, } impl Agent { @@ -37,6 +38,7 @@ lazy_static! { ("npm-shrinkwrap.json", Agent::Npm), ("yarn.lock", Agent::Yarn), ("pnpm-lock.yaml", Agent::Pnpm), + ("bun.lockb", Agent::Bun), ]) }; } diff --git a/src/common/commands/add.rs b/src/common/commands/add.rs index 129ff34..29126c8 100644 --- a/src/common/commands/add.rs +++ b/src/common/commands/add.rs @@ -1,5 +1,5 @@ use crate::{agent::Agent, execute::Executor}; -use eyre::Result; +use eyre::{eyre, Result}; pub fn add( executor: &dyn Executor, @@ -67,6 +67,24 @@ pub fn add( true, silence_stdout, ), + Agent::Bun => { + if workspace_root { + return Err(eyre!("Bun doesn't support workspace_root flag")); + } + + executor.execute( + "bun", + &merge_and_clean_args( + "add", + if dev { Some("-D") } else { None }, + None, + packages_refs, + ), + None, + true, + silence_stdout, + ) + } } } @@ -220,4 +238,28 @@ mod tests { assert!(result.is_ok()); } + + #[test] + fn test_add_bun() { + let mut mock_executor = MockExecutor::new(); + expect_execute_once( + &mut mock_executor, + "bun", + vec_of_strings!("add", "-D", "packageA", "packageB"), + None, + true, + false, + ); + + let result = add( + &mock_executor, + &Agent::Bun, + true, + false, + &vec_of_strings!["packageA", "packageB"], + false, + ); + + assert!(result.is_ok()); + } } diff --git a/src/common/commands/install.rs b/src/common/commands/install.rs index 750a6c6..0505c56 100644 --- a/src/common/commands/install.rs +++ b/src/common/commands/install.rs @@ -7,6 +7,7 @@ pub fn install(executor: &dyn Executor, agent: &Agent) -> Result<()> { Agent::Npm => executor.execute("npm", &["install"], None, true, false), Agent::Yarn => executor.execute("yarn", &["install"], None, true, false), Agent::Pnpm => executor.execute("pnpm", &["install"], None, true, false), + Agent::Bun => executor.execute("bun", &["install"], None, true, false), } } @@ -67,4 +68,21 @@ mod tests { assert!(result.is_ok()); } + + #[test] + fn test_install_bun() { + let mut mock_executor = MockExecutor::new(); + expect_execute_once( + &mut mock_executor, + "bun", + vec_of_strings!("install"), + None, + true, + false, + ); + + let result = install(&mock_executor, &Agent::Bun); + + assert!(result.is_ok()); + } } diff --git a/src/common/commands/run.rs b/src/common/commands/run.rs index 4e067e7..3fed9c2 100644 --- a/src/common/commands/run.rs +++ b/src/common/commands/run.rs @@ -2,15 +2,28 @@ use colored::Colorize; use eyre::{ContextCompat, Result}; use std::path::Path; -use crate::{execute::Executor, fs::find_in_parents, fs::Filesystem}; +use crate::{agent::Agent, execute::Executor, fs::find_in_parents, fs::Filesystem}; pub fn run( executor: &dyn Executor, fs: &dyn Filesystem, + agent: &Agent, task: &str, cwd: &Path, extra_args: Option<&[&str]>, ) -> Result<()> { + // for alternative runtimes, just proxy the call + if agent == &Agent::Bun { + let mut bun_args = vec!["run", task]; + + // append extra args if any + if let Some(extra_args) = extra_args { + bun_args.extend_from_slice(extra_args); + } + + return executor.execute("bun", &bun_args, None, true, false); + } + let package_json_path = find_in_parents(fs, cwd, "package.json").with_context(|| { format!("Couldn't find package.json in the current directory: {cwd:?} or its parents.") })?; @@ -134,6 +147,7 @@ mod tests { let result = run( &mock_executor, &mock_fs, + &Agent::Npm, "test", Path::new("/project"), Some(&["--no-timeout", "--bail"]), @@ -163,6 +177,7 @@ mod tests { let result = run( &mock_executor, &mock_fs, + &Agent::Npm, "mocha", Path::new("/project"), Some(&["--help"]), @@ -170,4 +185,29 @@ mod tests { assert!(result.is_ok()); } + + #[test] + fn command_run_by_bun() { + let mut mock_executor = MockExecutor::new(); + expect_execute_once( + &mut mock_executor, + "bun", + vec_of_strings!("run", "test", "--no-timeout"), + None, + true, + false, + ); + let mock_fs = MockFilesystem::new(); + + let result = run( + &mock_executor, + &mock_fs, + &Agent::Bun, + "test", + Path::new("/project"), + Some(&["--no-timeout"]), + ); + + assert!(result.is_ok()); + } }