From 84984c0da8b4228a91a799e4ca60172e68d1cead Mon Sep 17 00:00:00 2001
From: Thiago Barbosa
Date: Mon, 27 Jul 2026 17:50:19 +0200
Subject: [PATCH] fix: Error when upgrading pnpm to latest in
`devEngines.packageManager`
Signed-off-by: Thiago Barbosa
---
sources/specUtils.ts | 18 ++++++++++++------
tests/Use.test.ts | 31 +++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 6 deletions(-)
diff --git a/sources/specUtils.ts b/sources/specUtils.ts
index 8b73aacd2..c35e82806 100644
--- a/sources/specUtils.ts
+++ b/sources/specUtils.ts
@@ -124,11 +124,6 @@ export async function setLocalPackageManager(cwd: string, info: PreparedPackageM
const lookup = await loadSpec(cwd);
const range = `range` in lookup && lookup.range;
- if (range) {
- if (info.locator.name !== range.name || !semverSatisfies(info.locator.reference, range.range)) {
- warnOrThrow(`The requested version of ${info.locator.name}@${info.locator.reference} does not match the devEngines specification (${range.name}@${range.range})`, range.onFail);
- }
- }
const content = lookup.type !== `NoProject`
? await fs.promises.readFile(lookup.target, `utf8`)
@@ -137,7 +132,18 @@ export async function setLocalPackageManager(cwd: string, info: PreparedPackageM
const {data, indent} = nodeUtils.readPackageJson(content);
const previousPackageManager = data.packageManager ?? (range ? `${range.name}@${range.range}` : `unknown`);
- data.packageManager = `${info.locator.name}@${info.locator.reference}`;
+
+ // When the project relies solely on an exact `devEngines.packageManager`
+ // version (i.e. there's no `packageManager` field), update that field in
+ // place rather than adding a conflicting `packageManager` entry (see #874).
+ if (range && data.packageManager == null && range.name === info.locator.name && semverValid(range.range)) {
+ data.devEngines.packageManager.version = info.locator.reference;
+ } else {
+ if (range && (info.locator.name !== range.name || !semverSatisfies(info.locator.reference, range.range)))
+ warnOrThrow(`The requested version of ${info.locator.name}@${info.locator.reference} does not match the devEngines specification (${range.name}@${range.range})`, range.onFail);
+
+ data.packageManager = `${info.locator.name}@${info.locator.reference}`;
+ }
const newContent = nodeUtils.normalizeLineEndings(content, `${JSON.stringify(data, null, indent)}\n`);
await fs.promises.writeFile(lookup.target, newContent, `utf8`);
diff --git a/tests/Use.test.ts b/tests/Use.test.ts
index 978c2e3a8..4579571e7 100644
--- a/tests/Use.test.ts
+++ b/tests/Use.test.ts
@@ -77,6 +77,37 @@ describe(`UseCommand`, () => {
});
});
+ it(`with an exact version in 'devEngines.packageManager' and no 'packageManager' field`, async () => {
+ await xfs.mktempPromise(async cwd => {
+ process.env.NO_COLOR = `1`;
+ await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
+ devEngines: {packageManager: {name: `yarn`, version: `2.1.0`}},
+ license: `MIT`,
+ });
+
+ // Should update the pinned devEngines version rather than error out
+ // when the requested version doesn't match the current one (#874):
+ await expect(runCli(cwd, [`use`, `yarn@2.4.3`])).resolves.toMatchObject({
+ exitCode: 0,
+ stderr: ``,
+ stdout: expect.stringMatching(/^Installing yarn@2\.4\.3 in the project\.\.\.\n\n/),
+ });
+
+ const data = await xfs.readJsonPromise(ppath.join(cwd, `package.json`));
+ expect(data).toMatchObject({
+ devEngines: {packageManager: {name: `yarn`, version: `2.4.3+sha512.8dd9fedc5451829619e526c56f42609ad88ae4776d9d3f9456d578ac085115c0c2f0fb02bb7d57fd2e1b6e1ac96efba35e80a20a056668f61c96934f67694fd0`}},
+ });
+ // The `packageManager` field should not be added:
+ expect(data).not.toHaveProperty(`packageManager`);
+
+ await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
+ exitCode: 0,
+ stdout: `2.4.3\n`,
+ stderr: ``,
+ });
+ });
+ });
+
it(`with 'devEngines.packageManager' and 'packageManager' fields`, async () => {
await xfs.mktempPromise(async cwd => {
process.env.NO_COLOR = `1`;