use std::path::Path;
use std::process::Command;
use super::ScriptDirective;
pub(crate) fn detect(dir: &Path) -> bool {
dir.join("yarn.lock").exists()
}
pub(crate) fn run_cmd(task: &str, args: &[String]) -> Command {
let mut c = super::program::command("yarn");
c.arg(task).args(args);
c
}
pub(crate) fn install_cmd(dir: &Path, frozen: bool, scripts: ScriptDirective) -> Command {
let yarn_major = if frozen || scripts != ScriptDirective::Default {
detect_major_version(dir)
} else {
None
};
install_cmd_with_major(frozen, scripts, yarn_major)
}
fn install_cmd_with_major(
frozen: bool,
scripts: ScriptDirective,
yarn_major: Option<u32>,
) -> Command {
let is_berry = matches!(yarn_major, Some(major) if major >= 2);
let mut c = super::program::command("yarn");
c.arg("install");
if frozen {
c.arg(if is_berry {
"--immutable"
} else {
"--frozen-lockfile"
});
}
match scripts {
ScriptDirective::Deny => match yarn_major {
Some(major) if major >= 2 => {
c.env("YARN_ENABLE_SCRIPTS", "false");
}
Some(_) => {
c.arg("--ignore-scripts");
}
None => {
c.arg("--ignore-scripts");
c.env("YARN_ENABLE_SCRIPTS", "false");
}
},
ScriptDirective::ForceOn => {
if is_berry {
c.env("YARN_ENABLE_SCRIPTS", "true");
}
}
ScriptDirective::Default => {}
}
c
}
fn detect_major_version(dir: &Path) -> Option<u32> {
let output = super::program::command("yarn")
.arg("--version")
.current_dir(dir)
.output()
.ok()?;
if !output.status.success() {
return None;
}
parse_major_version(String::from_utf8_lossy(&output.stdout).trim())
}
fn parse_major_version(version: &str) -> Option<u32> {
version.split('.').next()?.parse().ok()
}
pub(crate) fn exec_cmd(dir: &Path, args: &[String]) -> Command {
let yarn_major = detect_major_version(dir);
exec_cmd_with_major(yarn_major, args)
}
fn exec_cmd_with_major(yarn_major: Option<u32>, args: &[String]) -> Command {
let mut c = super::program::command("yarn");
let subcommand = match yarn_major {
Some(major) if major >= 2 => "exec",
_ => "run",
};
c.arg(subcommand).args(args);
c
}
#[cfg(test)]
mod tests {
use super::{
ScriptDirective, exec_cmd_with_major, install_cmd_with_major, parse_major_version,
};
fn args_of(cmd: &std::process::Command) -> Vec<String> {
cmd.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect()
}
fn has_env(cmd: &std::process::Command, key: &str, value: &str) -> bool {
cmd.get_envs()
.any(|(k, v)| k == std::ffi::OsStr::new(key) && v == Some(std::ffi::OsStr::new(value)))
}
#[test]
fn frozen_install_uses_classic_flag_for_yarn_one() {
assert_eq!(
args_of(&install_cmd_with_major(
true,
ScriptDirective::Default,
Some(1)
)),
["install", "--frozen-lockfile"]
);
}
#[test]
fn frozen_install_uses_immutable_for_yarn_two_plus() {
assert_eq!(
args_of(&install_cmd_with_major(
true,
ScriptDirective::Default,
Some(4)
)),
["install", "--immutable"]
);
}
#[test]
fn frozen_install_falls_back_when_version_missing() {
assert_eq!(
args_of(&install_cmd_with_major(
true,
ScriptDirective::Default,
None
)),
["install", "--frozen-lockfile"]
);
}
#[test]
fn deny_scripts_uses_ignore_scripts_flag_on_classic() {
assert_eq!(
args_of(&install_cmd_with_major(
false,
ScriptDirective::Deny,
Some(1)
)),
["install", "--ignore-scripts"]
);
}
#[test]
fn deny_scripts_uses_env_not_flag_on_berry() {
let cmd = install_cmd_with_major(false, ScriptDirective::Deny, Some(4));
assert!(
has_env(&cmd, "YARN_ENABLE_SCRIPTS", "false"),
"Berry deny must set YARN_ENABLE_SCRIPTS=false",
);
assert_eq!(args_of(&cmd), ["install"]);
}
#[test]
fn deny_scripts_covers_both_mechanisms_when_version_missing() {
let cmd = install_cmd_with_major(false, ScriptDirective::Deny, None);
assert_eq!(args_of(&cmd), ["install", "--ignore-scripts"]);
assert!(
has_env(&cmd, "YARN_ENABLE_SCRIPTS", "false"),
"undetected deny must also set the Berry env so it can't fail open",
);
}
#[test]
fn force_on_sets_enable_scripts_env_on_berry() {
let cmd = install_cmd_with_major(false, ScriptDirective::ForceOn, Some(4));
assert!(
has_env(&cmd, "YARN_ENABLE_SCRIPTS", "true"),
"Berry force-on must set YARN_ENABLE_SCRIPTS=true",
);
assert_eq!(args_of(&cmd), ["install"]);
}
#[test]
fn force_on_is_noop_on_classic() {
let cmd = install_cmd_with_major(false, ScriptDirective::ForceOn, Some(1));
assert!(!has_env(&cmd, "YARN_ENABLE_SCRIPTS", "true"));
assert_eq!(args_of(&cmd), ["install"]);
}
#[test]
fn frozen_and_deny_scripts_combine_on_berry() {
let cmd = install_cmd_with_major(true, ScriptDirective::Deny, Some(4));
assert!(has_env(&cmd, "YARN_ENABLE_SCRIPTS", "false"));
assert_eq!(args_of(&cmd), ["install", "--immutable"]);
}
#[test]
fn parse_major_version_reads_first_segment() {
assert_eq!(parse_major_version("4.1.0"), Some(4));
}
#[test]
fn exec_uses_run_subcommand_on_yarn_one() {
let args = [String::from("eslint"), String::from("src")];
let built: Vec<_> = exec_cmd_with_major(Some(1), &args)
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
assert_eq!(built, ["run", "eslint", "src"]);
}
#[test]
fn exec_uses_exec_subcommand_on_yarn_berry() {
let args = [String::from("eslint"), String::from("src")];
let built: Vec<_> = exec_cmd_with_major(Some(4), &args)
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
assert_eq!(built, ["exec", "eslint", "src"]);
}
#[test]
fn exec_falls_back_to_run_when_version_missing() {
let args = [String::from("eslint")];
let built: Vec<_> = exec_cmd_with_major(None, &args)
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
assert_eq!(built, ["run", "eslint"]);
}
}