diff --git a/.changeset/tender-bags-tell.md b/.changeset/tender-bags-tell.md new file mode 100644 index 000000000..404d890ac --- /dev/null +++ b/.changeset/tender-bags-tell.md @@ -0,0 +1,6 @@ +--- +"@vercel/build-utils": patch +"vercel": patch +--- + +[built-utils] Handle case of not having lockfile when corepack is enabled diff --git a/packages/build-utils/src/fs/run-user-scripts.ts b/packages/build-utils/src/fs/run-user-scripts.ts index 2844fcc45..2bb2e8c69 100644 --- a/packages/build-utils/src/fs/run-user-scripts.ts +++ b/packages/build-utils/src/fs/run-user-scripts.ts @@ -353,7 +353,9 @@ export async function scanParentDirs( // TODO: read "bun-lockfile-format-v0" lockfileVersion = 0; } else { - cliType = 'npm'; + cliType = packageJson + ? detectPackageManagerNameWithoutLockfile(packageJson) + : 'npm'; } const packageJsonPath = pkgJsonPath || undefined; @@ -366,6 +368,37 @@ export async function scanParentDirs( }; } +function detectPackageManagerNameWithoutLockfile(packageJson: PackageJson) { + const packageJsonPackageManager = packageJson.packageManager; + if (usingCorepack(process.env, packageJsonPackageManager)) { + const corepackPackageManager = validateVersionSpecifier( + packageJsonPackageManager as string + ); + switch (corepackPackageManager?.packageName) { + case 'npm': + case 'pnpm': + case 'yarn': + case 'bun': + return corepackPackageManager.packageName; + case undefined: + return 'npm'; + default: + throw new Error( + `Unknown package manager "${corepackPackageManager?.packageName}". Change your package.json "packageManager" field to a known package manager.` + ); + } + } + return 'npm'; +} + +function usingCorepack( + env: { [x: string]: string | undefined }, + packageJsonPackageManager: string | undefined +) { + const corepackFlagged = env.ENABLE_EXPERIMENTAL_COREPACK === '1'; + return corepackFlagged && Boolean(packageJsonPackageManager); +} + export async function walkParentDirs({ base, start, @@ -557,8 +590,7 @@ export function getEnvForPackageManager({ nodeVersion: NodeVersion | undefined; env: { [x: string]: string | undefined }; }) { - const corepackFlagged = env.ENABLE_EXPERIMENTAL_COREPACK === '1'; - const corepackEnabled = corepackFlagged && Boolean(packageJsonPackageManager); + const corepackEnabled = usingCorepack(env, packageJsonPackageManager); const { detectedLockfile, @@ -753,6 +785,39 @@ export function getPathOverrideForPackageManager({ } } +function validateVersionSpecifier(version: string) { + if (!version) { + return undefined; + } + + const [before, after, ...extra] = version.split('@'); + + if (extra.length) { + // should not have more than one `@` + return undefined; + } + + if (!before) { + // should have a package before the `@` + return undefined; + } + + if (!after) { + // should have a version after the `@` + return undefined; + } + + if (!validRange(after)) { + // the version after the `@` should be a valid semver value + return undefined; + } + + return { + packageName: before, + packageVersionRange: after, + }; +} + /** * Helper to get the binary paths that link to the used package manager. * Note: Make sure it doesn't contain any `console.log` calls. diff --git a/packages/cli/test/integration-1.test.ts b/packages/cli/test/integration-1.test.ts index 27ad2b264..913117cef 100644 --- a/packages/cli/test/integration-1.test.ts +++ b/packages/cli/test/integration-1.test.ts @@ -263,6 +263,7 @@ test('[vc build] should build project with corepack and select pnpm@7.1.0', asyn path.join(directory, '.vercel/cache/corepack') ); expect(contents).toEqual(['home', 'shim']); + expect(output.stdout).toMatch(/Running "pnpm run build"/gm); } finally { delete process.env.ENABLE_EXPERIMENTAL_COREPACK; } @@ -291,6 +292,7 @@ test('[vc build] should build project with corepack and select yarn@2.4.3', asyn path.join(directory, '.vercel/cache/corepack') ); expect(contents).toEqual(['home', 'shim']); + expect(output.stdout).toMatch(/Running "yarn run build"/gm); } finally { delete process.env.ENABLE_EXPERIMENTAL_COREPACK; }