Skip to content

Commit

Permalink
feat: pass args to scripts (#49)
Browse files Browse the repository at this point in the history
Pass any additional arguments passed after script name in command
invocation to the script.

Resolves #48

---------

Co-authored-by: khai96_ <[email protected]>
  • Loading branch information
Yakiyo and KSXGitHub authored Aug 5, 2023
1 parent 8ed5aba commit 0bd5a5c
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 4 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ lets_find_up = "0.0.3"
itertools = "0.10.5"
dunce = "1.0.4"
phf = { version = "0.11.2", features = ["macros" ]}
os_display = { version = "0.1.3", features = ["unix", "windows"] }
format-buf = "1.0.0"

[dev-dependencies]
assert_cmd = "2.0.5"
Expand Down
3 changes: 1 addition & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use clap::*;
use std::ffi::OsString;

#[derive(Debug, Parser)]
#[clap(author, version, about, rename_all = "kebab-case")]
Expand Down Expand Up @@ -31,5 +30,5 @@ pub struct RunArgs {
pub script: Option<String>, // Not OsString because it would be compared against package.json#scripts

/// Arguments to pass to the package script.
pub args: Vec<OsString>,
pub args: Vec<String>,
}
45 changes: 43 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use clap::Parser;
use cli::Cli;
use error::{MainError, PnError};
use format_buf::format as format_buf;
use indexmap::IndexMap;
use itertools::Itertools;
use os_display::Quoted;
use pipe_trait::Pipe;
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
env,
ffi::OsString,
fs::File,
Expand Down Expand Up @@ -78,7 +81,8 @@ fn run() -> Result<(), MainError> {
let (cwd, manifest) = cwd_and_manifest()?;
if let Some(name) = args.script {
if let Some(command) = manifest.scripts.get(&name) {
print_and_run_script(&manifest, &name, command, &cwd)
let command = append_args(command, &args.args);
print_and_run_script(&manifest, &name, &command, &cwd)
} else {
PnError::MissingScript { name }
.pipe(MainError::Pn)
Expand All @@ -96,11 +100,13 @@ fn run() -> Result<(), MainError> {
cli::Command::Other(args) => {
let (cwd, manifest) = cwd_and_manifest()?;
if let Some(name) = args.first() {
let name = name.as_str();
if passed_through::PASSED_THROUGH_COMMANDS.contains(name) {
return pass_to_pnpm(&args); // args already contain name, no need to prepend
}
if let Some(command) = manifest.scripts.get(name) {
return print_and_run_script(&manifest, name, command, &cwd);
let command = append_args(command, &args[1..]);
return print_and_run_script(&manifest, name, &command, &cwd);
}
}
pass_to_sub(args.join(" "))
Expand Down Expand Up @@ -138,6 +144,18 @@ fn run_script(name: &str, command: &str, cwd: &Path) -> Result<(), MainError> {
.pipe(Err)
}

fn append_args<'a>(command: &'a str, args: &[String]) -> Cow<'a, str> {
if args.is_empty() {
return Cow::Borrowed(command);
}
let mut command = command.to_string();
for arg in args {
let quoted = Quoted::unix(arg); // because pn uses `sh -c` even on Windows
format_buf!(command, " {quoted}");
}
Cow::Owned(command)
}

fn list_scripts(
mut stdout: impl Write,
script_map: impl IntoIterator<Item = (String, String)>,
Expand Down Expand Up @@ -232,6 +250,29 @@ mod tests {
use pretty_assertions::assert_eq;
use serde_json::json;

#[test]
fn test_append_args_empty() {
let command = append_args("echo hello world", &[]);
dbg!(&command);
assert!(matches!(&command, Cow::Borrowed(_)));
}

#[test]
fn test_append_args_non_empty() {
let command = append_args(
"echo hello world",
&[
"abc".to_string(),
"def".to_string(),
"ghi jkl".to_string(),
"\"".to_string(),
],
);
dbg!(&command);
assert!(matches!(&command, Cow::Owned(_)));
assert_eq!(command, r#"echo hello world 'abc' 'def' 'ghi jkl' '"'"#);
}

#[test]
fn test_list_scripts() {
let script_map = [
Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/append-script-args/list-args.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
set -o errexit -o nounset
for arg in "$@"; do
echo "$arg"
done
5 changes: 5 additions & 0 deletions tests/fixtures/append-script-args/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"scripts": {
"list-args": "sh list-args.sh"
}
}
5 changes: 5 additions & 0 deletions tests/fixtures/append-script-args/stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
foo
bar
hello world
! !@
abc def ghi
36 changes: 36 additions & 0 deletions tests/test_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,42 @@ fn run_script() {
"\n> [email protected] {}\n> echo hello world\n\n",
temp_dir.path().pipe(dunce::canonicalize).unwrap().display(),
));

Command::cargo_bin("pn")
.unwrap()
.current_dir(&temp_dir)
.args(["test", "\"me\""])
.assert()
.success()
.stdout("hello world \"me\"\n")
.stderr(format!(
"\n> [email protected] {}\n> echo hello world '\"me\"'\n\n",
temp_dir.path().pipe(dunce::canonicalize).unwrap().display(),
));
}

#[test]
fn append_script_args() {
let temp_dir = tempdir().unwrap();
let tree = MergeableFileSystemTree::<&str, &str>::from(dir! {
"package.json" => file!(include_str!("fixtures/append-script-args/package.json")),
"list-args.sh" => file!(include_str!("fixtures/append-script-args/list-args.sh")),
});
tree.build(&temp_dir).unwrap();

Command::cargo_bin("pn")
.unwrap()
.current_dir(&temp_dir)
.arg("run")
.arg("list-args")
.arg("foo")
.arg("bar")
.arg("hello world")
.arg("! !@")
.arg("abc def ghi")
.assert()
.success()
.stdout(include_str!("fixtures/append-script-args/stdout.txt").replace("\r\n", "\n"));
}

#[test]
Expand Down

0 comments on commit 0bd5a5c

Please sign in to comment.