Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 100 additions & 6 deletions crates/socket-patch-core/src/crawlers/pkg_managers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,16 @@ pub(crate) struct EnvFiles {

impl YarnEnv {
pub(crate) fn current(scope: crate::utils::repo_root::ReadScope) -> Self {
let vars = yarn_process_vars();
Self {
// Yarn takes the last `yarn_node_linker` spelling in the
// environment (each one overwrites the setting) but the first
// `yarn_rc_filename` (`getRcFilename` returns on a match).
node_linker: yarn_env_key(std::env::vars_os(), "YARN_NODE_LINKER", true),
pnpm_store_folder: yarn_env_key(std::env::vars_os(), "YARN_PNPM_STORE_FOLDER", true),
rc_filename: yarn_env_key(std::env::vars_os(), "YARN_RC_FILENAME", false)
node_linker: yarn_env_key(vars.iter().cloned(), "YARN_NODE_LINKER", true),
pnpm_store_folder: yarn_env_key(vars.iter().cloned(), "YARN_PNPM_STORE_FOLDER", true),
rc_filename: yarn_env_key(vars.iter().cloned(), "YARN_RC_FILENAME", false)
.unwrap_or_else(|| ".yarnrc.yml".to_string()),
home: crate::utils::fs::home_dir(),
home: yarn_home(),
vars: None,
env_files: Default::default(),
scope,
Expand All @@ -240,7 +241,11 @@ impl YarnEnv {
scope: crate::utils::repo_root::ReadScope,
project_root: &Path,
) -> Self {
let env_list = yarn_env_key(std::env::vars_os(), "YARN_INJECT_ENVIRONMENT_FILES", true);
let env_list = yarn_env_key(
yarn_process_vars().into_iter(),
"YARN_INJECT_ENVIRONMENT_FILES",
true,
);
Self::current(scope).with_env_files(project_root, env_list.as_deref())
}

Expand Down Expand Up @@ -331,7 +336,7 @@ impl YarnEnv {
fn base_var(&self, name: &str) -> Option<String> {
match &self.vars {
Some(vars) => vars.get(name).cloned(),
None => std::env::var(name).ok(),
None => yarn_process_var(name),
}
}

Expand Down Expand Up @@ -360,6 +365,56 @@ impl YarnEnv {
}
}

/// The home folder whose rc file [`YarnEnv::current`] reads last.
#[cfg(not(test))]
fn yarn_home() -> Option<PathBuf> {
crate::utils::fs::home_dir()
}

/// In unit tests, only a `HOME`/`USERPROFILE` a live `EnvGuard` sets: a
/// developer's `~/.yarnrc.yml` can't change what the tests see (#1919).
#[cfg(test)]
fn yarn_home() -> Option<PathBuf> {
crate::utils::fs::home_from_env(|key| crate::test_env::guarded_var(key).map(Into::into))
}

/// The process environment the `YARN_*` lookups of [`YarnEnv::current`]
/// and [`YarnEnv::for_project`] read.
#[cfg(not(test))]
fn yarn_process_vars() -> Vec<(std::ffi::OsString, std::ffi::OsString)> {
std::env::vars_os().collect()
}

/// In unit tests, only the variables a live `EnvGuard` holds: a
/// developer's `YARN_NODE_LINKER` (or `YARN_PNPM_STORE_FOLDER`,
/// `YARN_RC_FILENAME`, `YARN_INJECT_ENVIRONMENT_FILES`) can't change what
/// the tests see (#1919), as `utils::go_env` does for Go. Windows keeps a
/// key's existing spelling when a guard sets it in another case, so there
/// the upper-case spelling a guard uses counts too.
#[cfg(test)]
fn yarn_process_vars() -> Vec<(std::ffi::OsString, std::ffi::OsString)> {
let held = |key: &str| {
crate::test_env::guarded_var(key).is_some()
|| (cfg!(windows) && crate::test_env::guarded_var(&key.to_ascii_uppercase()).is_some())
};
std::env::vars_os()
.filter(|(key, _)| key.to_str().is_some_and(held))
.collect()
}

/// The variable a `${NAME}` reference in a yarn rc or env file expands
/// from when [`YarnEnv::vars`] is `None`.
#[cfg(not(test))]
fn yarn_process_var(name: &str) -> Option<String> {
std::env::var(name).ok()
}

/// In unit tests, only a variable a live `EnvGuard` sets (#1919).
#[cfg(test)]
fn yarn_process_var(name: &str) -> Option<String> {
crate::test_env::guarded_var(name)
}
Comment thread
mikolalysenko marked this conversation as resolved.
Resolved

/// The yarn environment setting `key` (upper case) in `vars`: yarn
/// lower-cases every environment key before matching it, so
/// `yarn_node_linker` sets the linker as `YARN_NODE_LINKER` does (#1100;
Expand Down Expand Up @@ -704,6 +759,45 @@ impl YarnPnpLoader {
mod tests {
use super::*;

/// The unit tests read a `YARN_*` variable or the home folder only
/// while a guard sets it, so a developer's `YARN_NODE_LINKER` or
/// `~/.yarnrc.yml` can't flip a test (#1919), while a test that sets
/// one still sees it.
#[test]
#[serial_test::serial]
fn yarn_env_sees_only_guarded_variables() {
use crate::utils::repo_root::ReadScope;
let home = tempfile::tempdir().unwrap();
{
let _linker = crate::test_env::EnvGuard::many([
(
"YARN_NODE_LINKER",
Some(std::ffi::OsStr::new("node-modules")),
),
(
"YARN_PNPM_STORE_FOLDER",
Some(std::ffi::OsStr::new("store")),
),
("YARN_RC_FILENAME", Some(std::ffi::OsStr::new(".alt.yml"))),
("SOCKET_YARN_TEST_VAR", Some(std::ffi::OsStr::new("x"))),
("HOME", Some(home.path().as_os_str())),
]);
let env = YarnEnv::current(ReadScope::Workspace);
assert_eq!(env.node_linker.as_deref(), Some("node-modules"));
assert_eq!(env.pnpm_store_folder.as_deref(), Some("store"));
assert_eq!(env.rc_filename, ".alt.yml");
assert_eq!(env.home.as_deref(), Some(home.path()));
assert_eq!(env.var("SOCKET_YARN_TEST_VAR").as_deref(), Some("x"));
}
let env = YarnEnv::current(ReadScope::Workspace);
assert_eq!(env.node_linker, None);
assert_eq!(env.home, None);
assert_eq!(env.pnpm_store_folder, None);
assert_eq!(env.rc_filename, ".yarnrc.yml");
assert_eq!(env.var("SOCKET_YARN_TEST_VAR"), None);
assert_eq!(env.var("PATH"), None, "an unguarded host variable");
}

/// The public entry points only add the host's yarn env, which is
/// asked for a loader file alone: without one they agree with the
/// hermetic variants the other tests use.
Expand Down
Loading