@@ -23,18 +23,28 @@ const INSTALL_STATE_FILE = `.yarn-state.yml` as Filename;
2323const MTIME_ACCURANCY = 1000 ;
2424
2525type InstallState = { locatorMap : NodeModulesLocatorMap , locationTree : LocationTree , binSymlinks : BinSymlinkMap , nmMode : NodeModulesMode , mtimeMs : number } ;
26+ type InstallStateCacheEntry = { statKey : string | null , promise : Promise < InstallState | null > } ;
2627type BinSymlinkMap = Map < PortablePath , Map < Filename , PortablePath > > ;
2728type LoadManifest = ( locator : LocatorKey , installLocation : PortablePath ) => Promise < Pick < Manifest , 'bin' > > ;
2829
30+ const installStateCache : Map < string , InstallStateCacheEntry > = new Map ( ) ;
31+
32+ function getInstallStateCacheKey ( project : Project , { unrollAliases} : { unrollAliases : boolean } ) {
33+ return `${ project . cwd } \0${ unrollAliases ? `unroll` : `plain` } ` ;
34+ }
35+
36+ function clearInstallStateCache ( project : Project ) {
37+ installStateCache . delete ( getInstallStateCacheKey ( project , { unrollAliases : true } ) ) ;
38+ installStateCache . delete ( getInstallStateCacheKey ( project , { unrollAliases : false } ) ) ;
39+ }
40+
2941export enum NodeModulesMode {
3042 CLASSIC = `classic` ,
3143 HARDLINKS_LOCAL = `hardlinks-local` ,
3244 HARDLINKS_GLOBAL = `hardlinks-global` ,
3345}
3446
3547export class NodeModulesLinker implements Linker {
36- private installStateCache : Map < string , Promise < InstallState | null > > = new Map ( ) ;
37-
3848 getCustomDataKey ( ) {
3949 return JSON . stringify ( {
4050 name : `NodeModulesLinker` ,
@@ -54,9 +64,7 @@ export class NodeModulesLinker implements Linker {
5464 if ( workspace )
5565 return workspace . cwd ;
5666
57- const installState = await miscUtils . getFactoryWithDefault ( this . installStateCache , opts . project . cwd , async ( ) => {
58- return await findInstallState ( opts . project , { unrollAliases : true } ) ;
59- } ) ;
67+ const installState = await this . findInstallState ( opts . project , { unrollAliases : true } ) ;
6068
6169 if ( installState === null )
6270 throw new UsageError ( `Couldn't find the node_modules state file - running an install might help (findPackageLocation)` ) ;
@@ -79,9 +87,7 @@ export class NodeModulesLinker implements Linker {
7987 if ( ! this . isEnabled ( opts ) )
8088 return null ;
8189
82- const installState = await miscUtils . getFactoryWithDefault ( this . installStateCache , opts . project . cwd , async ( ) => {
83- return await findInstallState ( opts . project , { unrollAliases : true } ) ;
84- } ) ;
90+ const installState = await this . findInstallState ( opts . project , { unrollAliases : true } ) ;
8591
8692 if ( installState === null )
8793 return null ;
@@ -107,6 +113,26 @@ export class NodeModulesLinker implements Linker {
107113 return new NodeModulesInstaller ( opts ) ;
108114 }
109115
116+ private async findInstallState ( project : Project , { unrollAliases} : { unrollAliases : boolean } ) {
117+ const cacheKey = getInstallStateCacheKey ( project , { unrollAliases} ) ;
118+ const statKey = await getInstallStateStatKey ( project ) ;
119+ const cached = installStateCache . get ( cacheKey ) ;
120+
121+ if ( cached ?. statKey === statKey )
122+ return await cached . promise ;
123+
124+ let promise : Promise < InstallState | null > ;
125+ promise = findInstallState ( project , { unrollAliases} ) . catch ( error => {
126+ const cached = installStateCache . get ( cacheKey ) ;
127+ if ( cached ?. promise === promise )
128+ installStateCache . delete ( cacheKey ) ;
129+ throw error ;
130+ } ) ;
131+
132+ installStateCache . set ( cacheKey , { statKey, promise} ) ;
133+ return await promise ;
134+ }
135+
110136 private isEnabled ( opts : MinimalLinkOptions ) {
111137 return opts . project . configuration . get ( `nodeLinker` ) === `node-modules` ;
112138 }
@@ -447,13 +473,29 @@ async function writeInstallState(project: Project, locatorMap: NodeModulesLocato
447473 const rootPath = project . cwd ;
448474 const installStatePath = ppath . join ( rootPath , NODE_MODULES , INSTALL_STATE_FILE ) ;
449475
450- // Force install state file rewrite, so that it has mtime bigger than all node_modules subfolders
451- if ( installChangedByUser )
452- await xfs . removePromise ( installStatePath ) ;
476+ try {
477+ // Force install state file rewrite, so that it has mtime bigger than all node_modules subfolders
478+ if ( installChangedByUser )
479+ await xfs . removePromise ( installStatePath ) ;
453480
454- await xfs . changeFilePromise ( installStatePath , locatorState , {
455- automaticNewlines : true ,
456- } ) ;
481+ await xfs . changeFilePromise ( installStatePath , locatorState , {
482+ automaticNewlines : true ,
483+ } ) ;
484+ } finally {
485+ clearInstallStateCache ( project ) ;
486+ }
487+ }
488+
489+ async function getInstallStateStatKey ( project : Project ) {
490+ const rootPath = project . cwd ;
491+ const installStatePath = ppath . join ( rootPath , NODE_MODULES , INSTALL_STATE_FILE ) ;
492+
493+ try {
494+ const stats = await xfs . statPromise ( installStatePath ) ;
495+ return `${ stats . mtimeMs } :${ stats . size } ` ;
496+ } catch ( e ) {
497+ return null ;
498+ }
457499}
458500
459501async function findInstallState ( project : Project , { unrollAliases = false } : { unrollAliases ?: boolean } = { } ) : Promise < InstallState | null > {
0 commit comments