Skip to content

Implements switchVersionRequirement#302

Merged
arcanis merged 5 commits into
mainfrom
mael/switch-requirements
May 21, 2026
Merged

Implements switchVersionRequirement#302
arcanis merged 5 commits into
mainfrom
mael/switch-requirements

Conversation

@arcanis
Copy link
Copy Markdown
Member

@arcanis arcanis commented May 19, 2026

This PR makes it possible to restrict the versions that Yarn Switch will allow to run based on the home folder configuration. Note that unlike other settings switchVersionRequirement can only be set in the home folder, since its purpose is to enhance security by disabling packageManager versions the user doesn't want to run.


Note

Medium Risk
Adds a new pre-execution version gate for Yarn Switch based on parsing the user’s home .yarnrc.yml, which can block command execution/daemon startup if misconfigured or if YAML parsing fails.

Overview
Adds support for a new switchVersionRequirement setting (schema + merge support) to constrain which Yarn versions Yarn Switch will run.

zpm-switch now reads switchVersionRequirement from the home .yarnrc.yml (via serde_yaml) and validates resolved Yarn versions before running commands, generating completions, or starting a daemon; failures produce a new SwitchVersionMismatch error. Acceptance tests cover success paths (missing/empty config, matching range) and failure paths (mismatching range, daemon start).

Reviewed by Cursor Bugbot for commit 54cf9ae. Bugbot is set up for automated code reviews on this repo. Configure here.

fn load_switch_version_requirement() -> Result<Option<Range>, Error> {
let home_dir
= Path::home_dir()?
.ok_or(Error::MissingHomeFolder)?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing home directory causes unnecessary validation failure

Medium Severity

When no home directory exists (common in Docker containers, some CI environments), load_switch_version_requirement returns Error::MissingHomeFolder instead of Ok(None). Since switchVersionRequirement can only be set in the home folder, the absence of a home folder inherently means no restriction is configured — it's logically equivalent to "file not found" and the function could safely return Ok(None). Instead, it breaks all yarn commands in these environments.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d79eeee. Configure here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 19, 2026

⏱️ Benchmark Results

gatsby install-full-cold

Metric Base Head Difference
Mean 4.291s 4.355s +1.49% ⚠️
Median 4.307s 4.325s +0.42% ⚠️
Min 4.154s 4.174s
Max 4.374s 4.915s
Std Dev 0.052s 0.157s
📊 Raw benchmark data (gatsby install-full-cold)

Base times: 4.307s, 4.353s, 4.293s, 4.316s, 4.309s, 4.239s, 4.324s, 4.237s, 4.199s, 4.314s, 4.281s, 4.270s, 4.322s, 4.308s, 4.154s, 4.239s, 4.250s, 4.367s, 4.374s, 4.190s, 4.313s, 4.343s, 4.294s, 4.308s, 4.246s, 4.307s, 4.356s, 4.304s, 4.325s, 4.286s

Head times: 4.904s, 4.915s, 4.305s, 4.338s, 4.296s, 4.189s, 4.267s, 4.174s, 4.306s, 4.313s, 4.359s, 4.347s, 4.346s, 4.329s, 4.322s, 4.351s, 4.349s, 4.322s, 4.324s, 4.327s, 4.372s, 4.335s, 4.300s, 4.313s, 4.338s, 4.327s, 4.356s, 4.316s, 4.305s, 4.298s


gatsby install-cache-only

Metric Base Head Difference
Mean 1.262s 1.272s +0.73% ⚠️
Median 1.263s 1.271s +0.65% ⚠️
Min 1.243s 1.246s
Max 1.287s 1.297s
Std Dev 0.011s 0.011s
📊 Raw benchmark data (gatsby install-cache-only)

Base times: 1.264s, 1.265s, 1.262s, 1.250s, 1.260s, 1.253s, 1.258s, 1.264s, 1.259s, 1.266s, 1.271s, 1.259s, 1.267s, 1.250s, 1.245s, 1.275s, 1.271s, 1.254s, 1.246s, 1.243s, 1.287s, 1.273s, 1.249s, 1.275s, 1.254s, 1.275s, 1.262s, 1.281s, 1.265s, 1.265s

Head times: 1.253s, 1.263s, 1.277s, 1.264s, 1.265s, 1.275s, 1.280s, 1.278s, 1.288s, 1.290s, 1.273s, 1.282s, 1.270s, 1.277s, 1.280s, 1.278s, 1.278s, 1.270s, 1.260s, 1.268s, 1.268s, 1.260s, 1.246s, 1.267s, 1.272s, 1.270s, 1.264s, 1.274s, 1.297s, 1.258s


gatsby install-cache-and-lock (warm, with lockfile)

Metric Base Head Difference
Mean 0.336s 0.339s +0.82% ⚠️
Median 0.336s 0.339s +0.65% ⚠️
Min 0.328s 0.334s
Max 0.345s 0.347s
Std Dev 0.004s 0.003s
📊 Raw benchmark data (gatsby install-cache-and-lock (warm, with lockfile))

Base times: 0.341s, 0.335s, 0.336s, 0.345s, 0.340s, 0.342s, 0.334s, 0.340s, 0.337s, 0.332s, 0.339s, 0.342s, 0.333s, 0.335s, 0.332s, 0.337s, 0.345s, 0.333s, 0.333s, 0.331s, 0.328s, 0.333s, 0.332s, 0.336s, 0.340s, 0.337s, 0.337s, 0.339s, 0.334s, 0.332s

Head times: 0.340s, 0.339s, 0.340s, 0.338s, 0.342s, 0.339s, 0.341s, 0.338s, 0.346s, 0.341s, 0.342s, 0.347s, 0.338s, 0.342s, 0.341s, 0.338s, 0.336s, 0.336s, 0.334s, 0.335s, 0.337s, 0.336s, 0.335s, 0.342s, 0.339s, 0.338s, 0.342s, 0.338s, 0.338s, 0.334s

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Empty YAML file causes parsing error instead of succeeding
    • Added an early return for empty config file contents so no YAML parse is attempted.

Create PR

Or push these changes by commenting:

@cursor push 27d2e1622a
Preview (27d2e1622a)
diff --git a/packages/zpm-switch/src/config.rs b/packages/zpm-switch/src/config.rs
--- a/packages/zpm-switch/src/config.rs
+++ b/packages/zpm-switch/src/config.rs
@@ -31,6 +31,10 @@
         return Ok(None);
     };
 
+    if text.is_empty() {
+        return Ok(None);
+    }
+
     let partial: PartialRcSettings
         = serde_yaml::from_str(&text)?;

You can send follow-ups to the cloud agent here.

Comment thread packages/zpm-switch/src/config.rs
@arcanis
Copy link
Copy Markdown
Member Author

arcanis commented May 21, 2026

@cursor push 27d2e16

Applied via @cursor push command
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Version validation bypassed via daemon command path
    • Daemon startup now validates version references against switchVersionRequirement before installing or spawning Yarn, with an integration test covering the blocked daemon path.

Create PR

Or push these changes by commenting:

@cursor push 0da0de1fe8
Preview (0da0de1fe8)
diff --git a/packages/zpm-switch/src/commands/switch/daemon_open.rs b/packages/zpm-switch/src/commands/switch/daemon_open.rs
--- a/packages/zpm-switch/src/commands/switch/daemon_open.rs
+++ b/packages/zpm-switch/src/commands/switch/daemon_open.rs
@@ -5,6 +5,7 @@
 use zpm_utils::{Path, ToFileString};
 
 use crate::{
+    config::validate_yarn_version,
     cwd::get_final_cwd,
     daemons::{self, DaemonEntry},
     errors::Error,
@@ -77,6 +78,8 @@
 
         match &reference {
             PackageManagerReference::Version(version_ref) => {
+                validate_yarn_version(&version_ref.version)?;
+
                 let mut binary
                     = install_package_manager(version_ref).await?;
 

diff --git a/tests/acceptance-tests/pkg-tests-specs/sources/features/yarnSwitch.test.ts b/tests/acceptance-tests/pkg-tests-specs/sources/features/yarnSwitch.test.ts
--- a/tests/acceptance-tests/pkg-tests-specs/sources/features/yarnSwitch.test.ts
+++ b/tests/acceptance-tests/pkg-tests-specs/sources/features/yarnSwitch.test.ts
@@ -63,6 +63,24 @@
           });
         }),
       );
+
+      test(
+        `it should fail to start a daemon when the version does not match the requirement`,
+        makeTemporaryEnv({
+          packageManager: `yarn@6.0.0`,
+        }, async ({path, runSwitch}) => {
+          const homePath = ppath.dirname(path);
+
+          await xfs.writeJsonPromise(ppath.join(homePath, Filename.rc), {
+            switchVersionRequirement: `>=99.0.0`,
+          });
+
+          await expect(runSwitch(`switch`, `daemon`, `--start`)).rejects.toMatchObject({
+            code: 1,
+            stdout: expect.stringContaining(`does not satisfy the required range`),
+          });
+        }),
+      );
     });
   });
 });

You can send follow-ups to the cloud agent here.

Comment thread packages/zpm-switch/src/commands/switch/explicit.rs
@arcanis
Copy link
Copy Markdown
Member Author

arcanis commented May 21, 2026

@cursor push 0da0de1

Applied via @cursor push command
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Version validation missing in tab completion execution path
    • The proxy completion path now validates version references before installing or executing Yarn for completions.

Create PR

Or push these changes by commenting:

@cursor push 436412ea93
Preview (436412ea93)
diff --git a/packages/zpm-switch/src/commands/proxy.rs b/packages/zpm-switch/src/commands/proxy.rs
--- a/packages/zpm-switch/src/commands/proxy.rs
+++ b/packages/zpm-switch/src/commands/proxy.rs
@@ -4,7 +4,7 @@
 use clipanion::core::{Completion, CompletionContext};
 use zpm_utils::{DataType, Note, ToFileString};
 
-use crate::{cwd::{get_fake_cwd, get_final_cwd}, errors::Error, install::install_package_manager, ipc::YARNSW_PATH_ENV, links::{LinkTarget, get_link, unset_link}, manifest::{LocalPackageManagerReference, PackageManagerField, PackageManagerReference, find_closest_package_manager}, yarn::get_default_yarn_version, yarn_enums::ReleaseLine};
+use crate::{config::validate_yarn_version, cwd::{get_fake_cwd, get_final_cwd}, errors::Error, install::install_package_manager, ipc::YARNSW_PATH_ENV, links::{LinkTarget, get_link, unset_link}, manifest::{LocalPackageManagerReference, PackageManagerField, PackageManagerReference, find_closest_package_manager}, yarn::get_default_yarn_version, yarn_enums::ReleaseLine};
 
 use super::switch::explicit::ExplicitCommand;
 
@@ -74,6 +74,10 @@
 
     let mut binary = match &reference {
         PackageManagerReference::Version(params) => {
+            let Ok(()) = validate_yarn_version(&params.version) else {
+                return vec![];
+            };
+
             let Ok(cmd) = install_package_manager(params).await else {
                 return vec![];
             };

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit dd6fa44. Configure here.

Comment thread packages/zpm-switch/src/commands/switch/explicit.rs
@arcanis
Copy link
Copy Markdown
Member Author

arcanis commented May 21, 2026

@cursor push 436412e

@arcanis arcanis merged commit 42ecd76 into main May 21, 2026
20 checks passed
@arcanis arcanis deleted the mael/switch-requirements branch May 21, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants