diff --git a/packages/turbo-workspaces/__tests__/utils.test.ts b/packages/turbo-workspaces/__tests__/utils.test.ts index e870bb01e10d6..2a99dc19e0dec 100644 --- a/packages/turbo-workspaces/__tests__/utils.test.ts +++ b/packages/turbo-workspaces/__tests__/utils.test.ts @@ -64,7 +64,14 @@ describe("utils", () => { ["pnpm", "9.12.3"], ["yarn", "4.5.0+sha224.abc"], ["bun", "1.1.0"], - ["nub", "0.1.0"] + ["nub", "0.1.0"], + ["pnpm", "^9.0.0"], + ["pnpm", "9"], + ["pnpm", ">=9.0.0 <10.0.0"], + ["pnpm", ">=9.0.0 <10.0.0 || >=9.5.0 <10.0.0"], + ["pnpm", "^6.35.1"], + ["yarn", "^4.0.0"], + ["yarn", "^1.22.0"] ] as const)("reads %s from devEngines.packageManager", (name, version) => { const workspaceRoot = makeWorkspace({ devEngines: { @@ -164,34 +171,69 @@ describe("utils", () => { "version` must not contain" ], [ - { devEngines: { packageManager: { name: "pnpm", version: "^9.0.0" } } }, - "exact semantic version" + { + devEngines: { + packageManager: { + name: "pnpm", + version: "https://registry.npmjs.org/pnpm/-/pnpm-9.12.3.tgz" + } + } + }, + "valid semantic version range" ], [ { devEngines: { packageManager: { name: "pnpm", - version: "https://registry.npmjs.org/pnpm/-/pnpm-9.12.3.tgz" + version: "9.12.3+sha512.Purxi/Zex==" } } }, - "exact semantic version" + "valid semantic version range" ], [ - { devEngines: { packageManager: { name: "pnpm", version: "9" } } }, - "exact semantic version" + { + devEngines: { + packageManager: { name: "pnpm", version: ">=1.0.0 <1.0.0" } + } + }, + "at least one version" ], [ { devEngines: { packageManager: { name: "pnpm", - version: "9.12.3+sha512.Purxi/Zex==" + version: "9 || >=1.0.0 <1.0.0" } } }, - "exact semantic version" + "at least one version" + ], + [ + { devEngines: { packageManager: { name: "pnpm", version: "*" } } }, + "one major version" + ], + [ + { + devEngines: { packageManager: { name: "pnpm", version: ">=9.0.0" } } + }, + "one major version" + ], + [ + { + devEngines: { + packageManager: { name: "pnpm", version: ">=6.0.0 <10.0.0" } + } + }, + "one major version" + ], + [ + { + devEngines: { packageManager: { name: "pnpm", version: "9 || 10" } } + }, + "one major version" ] ])( "rejects invalid devEngines.packageManager %#", diff --git a/packages/turbo-workspaces/src/utils.ts b/packages/turbo-workspaces/src/utils.ts index 8b35f5f0a37dd..53974287c7490 100644 --- a/packages/turbo-workspaces/src/utils.ts +++ b/packages/turbo-workspaces/src/utils.ts @@ -49,8 +49,6 @@ const SUPPORTED_PACKAGE_MANAGERS = new Set([ "nub", "aube" ]); -const DEV_ENGINES_VERSION_REGEX = - /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/; function getPackageJson({ workspaceRoot @@ -189,15 +187,37 @@ function getWorkspacePackageManager({ ); } - if ( - !DEV_ENGINES_VERSION_REGEX.test(version) || - semver.valid(version) === null - ) { + if (semver.validRange(version) === null) { throw invalidDevEnginesPackageManager( - "`devEngines.packageManager.version` must be an exact semantic version" + "`devEngines.packageManager.version` must be a valid semantic version range" ); } + const minVersion = semver.minVersion(version); + if (minVersion === null) { + throw invalidDevEnginesPackageManager( + "`devEngines.packageManager.version` must admit at least one version" + ); + } + + for (const disjunct of version.split("||")) { + const disjunctMinVersion = semver.minVersion(disjunct.trim()); + if (disjunctMinVersion === null) { + throw invalidDevEnginesPackageManager( + "`devEngines.packageManager.version` must admit at least one version" + ); + } + + if ( + disjunctMinVersion.major !== minVersion.major || + semver.satisfies(`${disjunctMinVersion.major + 1}.0.0`, disjunct.trim()) + ) { + throw invalidDevEnginesPackageManager( + "`devEngines.packageManager.version` must only allow versions within one major version" + ); + } + } + return name; }