diff --git a/sources/Engine.ts b/sources/Engine.ts index 208d5a87c..d93501596 100644 --- a/sources/Engine.ts +++ b/sources/Engine.ts @@ -244,7 +244,7 @@ export class Engine { * project using the default package managers, and configure it so that we * don't need to ask again in the future. */ - async findProjectSpec(initialCwd: string, locator: Locator | LazyLocator, {transparent = false}: {transparent?: boolean} = {}): Promise { + async findProjectSpec(initialCwd: string, locator: Locator | LazyLocator, {transparent = false, binaryVersion}: {transparent?: boolean, binaryVersion?: string | null} = {}): Promise { // A locator is a valid descriptor (but not the other way around) const fallbackDescriptor = {name: locator.name, range: `${locator.reference}`}; @@ -293,7 +293,7 @@ export class Engine { } case `Found`: { - const spec = result.getSpec(); + const spec = result.getSpec({enforceExactVersion: !binaryVersion}); if (spec.name !== locator.name) { if (transparent) { if (typeof locator.reference === `function`) @@ -344,7 +344,7 @@ export class Engine { }; } - const descriptor = await this.findProjectSpec(cwd, fallbackLocator, {transparent: isTransparentCommand}); + const descriptor = await this.findProjectSpec(cwd, fallbackLocator, {transparent: isTransparentCommand, binaryVersion}); if (binaryVersion) descriptor.range = binaryVersion; diff --git a/sources/specUtils.ts b/sources/specUtils.ts index edd5c7e91..8b73aacd2 100644 --- a/sources/specUtils.ts +++ b/sources/specUtils.ts @@ -150,7 +150,7 @@ export async function setLocalPackageManager(cwd: string, info: PreparedPackageM interface FoundSpecResult { type: `Found`; target: string; - getSpec: () => Descriptor; + getSpec: (options?: {enforceExactVersion?: boolean}) => Descriptor; range?: Descriptor & {onFail?: DevEngineDependency[`onFail`]}; envFilePath?: string; } @@ -249,6 +249,6 @@ export async function loadSpec(initialCwd: string): Promise { onFail: selection.data.devEngines.packageManager.onFail, }, // Lazy-loading it so we do not throw errors on commands that do not need valid spec. - getSpec: () => parseSpec(rawPmSpec, path.relative(initialCwd, selection.manifestPath)), + getSpec: ({enforceExactVersion = true} = {}) => parseSpec(rawPmSpec, path.relative(initialCwd, selection.manifestPath), {enforceExactVersion}), }; } diff --git a/tests/main.test.ts b/tests/main.test.ts index 80f117673..61c1933eb 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -1709,3 +1709,51 @@ describe(`handle integrity checks`, () => { }); }); }); + +describe(`allow range versions in devEngines.packageManager.version when user specifies exact version`, () => { + for (const {name, versionRange, userProvidedVersion} of [ + {name: `npm`, versionRange: `^10.7.0`, userProvidedVersion: `6.14.2`}, + {name: `yarn`, versionRange: `^2.2.0`, userProvidedVersion: `2.2.2`}, + {name: `pnpm`, versionRange: `^5.8.0`, userProvidedVersion: `5.8.0`}, + ]) { + it(`should work with ${name}`, async () => { + await xfs.mktempPromise(async cwd => { + await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { + devEngines: { + packageManager: { + name, + version: versionRange, + }, + }, + }); + + await expect(runCli(cwd, [`${name}@${userProvidedVersion}`, `--version`])).resolves.toMatchObject({ + exitCode: 0, + stderr: ``, + stdout: `${userProvidedVersion}\n`, + }); + }); + }); + } +}); + +it(`should still validate devEngines.packageManager.version format when no user version specified`, async () => { + await xfs.mktempPromise(async cwd => { + // When no user version is specified, range versions in devEngines should still cause error + await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { + devEngines: { + packageManager: { + name: `npm`, + version: `^6.14.2`, + }, + }, + }); + + // Without user-specified version, should still fail due to range version in devEngines + await expect(runCli(cwd, [`npm`, `--version`])).resolves.toMatchObject({ + exitCode: 1, + stderr: expect.stringContaining(`Invalid package manager specification in package.json (npm@^6.14.2); expected a semver version`), + stdout: ``, + }); + }); +});