@@ -10,6 +10,8 @@ import {
1010 validRange ,
1111 parse ,
1212 satisfies ,
13+ gte ,
14+ minVersion ,
1315} from 'semver' ;
1416import { SpawnOptions } from 'child_process' ;
1517import { deprecate } from 'util' ;
@@ -59,6 +61,11 @@ export interface ScanParentDirsResult {
5961 * or `undefined` if not found.
6062 */
6163 lockfileVersion ?: number ;
64+ /**
65+ * Whether Turborepo supports the `COREPACK_HOME` environment variable.
66+ * `undefined` if not a Turborepo project.
67+ */
68+ turboSupportsCorepackHome ?: boolean ;
6269}
6370
6471export interface TraverseUpDirectoriesProps {
@@ -173,6 +180,31 @@ export function* traverseUpDirectories({
173180 }
174181}
175182
183+ async function readProjectRootInfo ( {
184+ start,
185+ base,
186+ } : TraverseUpDirectoriesProps ) : Promise <
187+ | {
188+ packageJson : PackageJson ;
189+ rootDir : string ;
190+ }
191+ | undefined
192+ > {
193+ let curRootPackageJsonPath : string | undefined ;
194+ for ( const dir of traverseUpDirectories ( { start, base } ) ) {
195+ const packageJsonPath = path . join ( dir , 'package.json' ) ;
196+ if ( await fs . pathExists ( packageJsonPath ) ) {
197+ curRootPackageJsonPath = packageJsonPath ;
198+ }
199+ }
200+ return curRootPackageJsonPath
201+ ? {
202+ packageJson : await fs . readJson ( curRootPackageJsonPath ) ,
203+ rootDir : path . dirname ( curRootPackageJsonPath ) ,
204+ }
205+ : undefined ;
206+ }
207+
176208/**
177209 * @deprecated Use `getNodeBinPaths()` instead.
178210 */
@@ -343,6 +375,21 @@ export async function scanParentDirs(
343375 bunLockPath ? fs . readFile ( bunLockPath , 'utf8' ) : null ,
344376 ] ) ;
345377
378+ const rootProjectInfo = readPackageJson
379+ ? await readProjectRootInfo ( {
380+ base,
381+ start : destPath ,
382+ } )
383+ : undefined ;
384+ const turboVersionRange =
385+ rootProjectInfo ?. packageJson ?. devDependencies ?. turbo ;
386+ const turboSupportsCorepackHome = turboVersionRange
387+ ? await checkTurboSupportsCorepack (
388+ turboVersionRange ,
389+ rootProjectInfo ?. rootDir
390+ )
391+ : undefined ;
392+
346393 // Priority order is bun with yarn lock > yarn > pnpm > npm > bun
347394 if ( bunLockBin && hasYarnLock ) {
348395 cliType = 'bun' ;
@@ -366,9 +413,13 @@ export async function scanParentDirs(
366413 // TODO: read "bun-lockfile-format-v0"
367414 lockfileVersion = 0 ;
368415 } else {
369- cliType = packageJson
370- ? detectPackageManagerNameWithoutLockfile ( packageJson )
371- : 'npm' ;
416+ cliType =
417+ packageJson && rootProjectInfo
418+ ? detectPackageManagerNameWithoutLockfile (
419+ packageJson ,
420+ turboSupportsCorepackHome
421+ )
422+ : 'npm' ;
372423 }
373424
374425 const packageJsonPath = pkgJsonPath || undefined ;
@@ -378,12 +429,50 @@ export async function scanParentDirs(
378429 lockfilePath,
379430 lockfileVersion,
380431 packageJsonPath,
432+ turboSupportsCorepackHome,
381433 } ;
382434}
383435
384- function detectPackageManagerNameWithoutLockfile ( packageJson : PackageJson ) {
436+ type TurboJson = {
437+ globalPassThroughEnv ?: string [ ] ;
438+ } ;
439+
440+ async function checkTurboSupportsCorepack (
441+ turboVersionRange : string ,
442+ rootDir : string
443+ ) {
444+ if ( turboRangeSupportsCorepack ( turboVersionRange ) ) {
445+ return true ;
446+ }
447+ const turboJsonPath = path . join ( rootDir , 'turbo.json' ) ;
448+ const turboJsonExists = await fs . pathExists ( turboJsonPath ) ;
449+ const turboJson = turboJsonExists
450+ ? ( JSON . parse ( await fs . readFile ( turboJsonPath , 'utf8' ) ) as TurboJson )
451+ : undefined ;
452+ return turboJson ?. globalPassThroughEnv ?. includes ( 'COREPACK_HOME' ) || false ;
453+ }
454+
455+ function turboRangeSupportsCorepack ( turboVersionRange : string ) {
456+ const versionSupportingCorepack = '2.1.3' ;
457+ const minTurboBeingUsed = minVersion ( turboVersionRange ) ;
458+ if ( ! minTurboBeingUsed ) {
459+ return false ;
460+ }
461+ return gte ( minTurboBeingUsed , versionSupportingCorepack ) ;
462+ }
463+
464+ function detectPackageManagerNameWithoutLockfile (
465+ packageJson : PackageJson ,
466+ turboSupportsCorepackHome : boolean | undefined
467+ ) {
385468 const packageJsonPackageManager = packageJson . packageManager ;
386- if ( usingCorepack ( process . env , packageJsonPackageManager ) ) {
469+ if (
470+ usingCorepack (
471+ process . env ,
472+ packageJsonPackageManager ,
473+ turboSupportsCorepackHome
474+ )
475+ ) {
387476 const corepackPackageManager = validateVersionSpecifier (
388477 packageJsonPackageManager
389478 ) ;
@@ -404,12 +493,24 @@ function detectPackageManagerNameWithoutLockfile(packageJson: PackageJson) {
404493 return 'npm' ;
405494}
406495
407- function usingCorepack (
496+ export function usingCorepack (
408497 env : { [ x : string ] : string | undefined } ,
409- packageJsonPackageManager : string | undefined
498+ packageJsonPackageManager : string | undefined ,
499+ turboSupportsCorepackHome : boolean | undefined
410500) {
411- const corepackFlagged = env . ENABLE_EXPERIMENTAL_COREPACK === '1' ;
412- return corepackFlagged && Boolean ( packageJsonPackageManager ) ;
501+ if (
502+ env . ENABLE_EXPERIMENTAL_COREPACK !== '1' ||
503+ packageJsonPackageManager === undefined
504+ ) {
505+ return false ;
506+ }
507+ if ( turboSupportsCorepackHome === false ) {
508+ console . warn (
509+ 'Warning: Disabling corepack because it may break your project. To use corepack, either upgrade to `[email protected] +` or include `COREPACK_HOME` in `turbo.json#globalPassThroughEnv`.' 510+ ) ;
511+ return false ;
512+ }
513+ return true ;
413514}
414515
415516export async function walkParentDirs ( {
@@ -472,8 +573,13 @@ export async function runNpmInstall(
472573
473574 try {
474575 await runNpmInstallSema . acquire ( ) ;
475- const { cliType, packageJsonPath, packageJson, lockfileVersion } =
476- await scanParentDirs ( destPath , true ) ;
576+ const {
577+ cliType,
578+ packageJsonPath,
579+ packageJson,
580+ lockfileVersion,
581+ turboSupportsCorepackHome,
582+ } = await scanParentDirs ( destPath , true ) ;
477583
478584 if ( ! packageJsonPath ) {
479585 debug (
@@ -512,6 +618,7 @@ export async function runNpmInstall(
512618 nodeVersion,
513619 env,
514620 packageJsonEngines : packageJson ?. engines ,
621+ turboSupportsCorepackHome,
515622 } ) ;
516623 let commandArgs : string [ ] ;
517624 const isPotentiallyBrokenNpm =
@@ -598,15 +705,21 @@ export function getEnvForPackageManager({
598705 nodeVersion,
599706 env,
600707 packageJsonEngines,
708+ turboSupportsCorepackHome,
601709} : {
602710 cliType : CliType ;
603711 lockfileVersion : number | undefined ;
604712 packageJsonPackageManager ?: string | undefined ;
605713 nodeVersion : NodeVersion | undefined ;
606714 env : { [ x : string ] : string | undefined } ;
607715 packageJsonEngines ?: PackageJson . Engines ;
716+ turboSupportsCorepackHome ?: boolean | undefined ;
608717} ) {
609- const corepackEnabled = usingCorepack ( env , packageJsonPackageManager ) ;
718+ const corepackEnabled = usingCorepack (
719+ env ,
720+ packageJsonPackageManager ,
721+ turboSupportsCorepackHome
722+ ) ;
610723
611724 const {
612725 detectedLockfile,
@@ -1041,17 +1154,16 @@ export async function runCustomInstallCommand({
10411154 spawnOpts ?: SpawnOptions ;
10421155} ) {
10431156 console . log ( `Running "install" command: \`${ installCommand } \`...` ) ;
1044- const { cliType, lockfileVersion, packageJson } = await scanParentDirs (
1045- destPath ,
1046- true
1047- ) ;
1157+ const { cliType, lockfileVersion, packageJson, turboSupportsCorepackHome } =
1158+ await scanParentDirs ( destPath , true ) ;
10481159 const env = getEnvForPackageManager ( {
10491160 cliType,
10501161 lockfileVersion,
10511162 packageJsonPackageManager : packageJson ?. packageManager ,
10521163 nodeVersion,
10531164 env : spawnOpts ?. env || { } ,
10541165 packageJsonEngines : packageJson ?. engines ,
1166+ turboSupportsCorepackHome,
10551167 } ) ;
10561168 debug ( `Running with $PATH:` , env ?. PATH || '' ) ;
10571169 await execCommand ( installCommand , {
@@ -1068,10 +1180,8 @@ export async function runPackageJsonScript(
10681180) {
10691181 assert ( path . isAbsolute ( destPath ) ) ;
10701182
1071- const { packageJson, cliType, lockfileVersion } = await scanParentDirs (
1072- destPath ,
1073- true
1074- ) ;
1183+ const { packageJson, cliType, lockfileVersion, turboSupportsCorepackHome } =
1184+ await scanParentDirs ( destPath , true ) ;
10751185 const scriptName = getScriptName (
10761186 packageJson ,
10771187 typeof scriptNames === 'string' ? [ scriptNames ] : scriptNames
@@ -1091,6 +1201,7 @@ export async function runPackageJsonScript(
10911201 nodeVersion : undefined ,
10921202 env : cloneEnv ( process . env , spawnOpts ?. env ) ,
10931203 packageJsonEngines : packageJson ?. engines ,
1204+ turboSupportsCorepackHome,
10941205 } ) ,
10951206 } ;
10961207
0 commit comments