@@ -36,13 +36,21 @@ export async function generateShrinkwrap(options: ShrinkwrapOptions): Promise<Pa
3636 const yarnLock : YarnLock = lockfile . parse ( await fs . readFile ( yarnLockLoc , { encoding : 'utf8' } ) ) ;
3737 const pkgJson = await loadPackageJson ( packageJsonFile ) ;
3838
39- let lock = await generateLockFile ( pkgJson , yarnLock , packageJsonDir ) ;
39+ // Load resolutions from root package.json (for monorepo support)
40+ const rootPkgJsonPath = path . join ( path . dirname ( yarnLockLoc ) , 'package.json' ) ;
41+ let resolutions : Record < string , string > = { } ;
42+ if ( await fileExists ( rootPkgJsonPath ) ) {
43+ const rootPkgJson = await loadPackageJson ( rootPkgJsonPath ) ;
44+ resolutions = rootPkgJson . resolutions || { } ;
45+ }
46+
47+ let lock = await generateLockFile ( pkgJson , yarnLock , packageJsonDir , resolutions ) ;
4048
4149 if ( options . hoist ?? true ) {
4250 lock = hoistDependencies ( lock ) ;
4351 }
4452
45- _validateTree ( lock ) ;
53+ _validateTree ( lock , resolutions ) ;
4654
4755 if ( options . outputFile ) {
4856 // Write the shrinkwrap file
@@ -52,7 +60,12 @@ export async function generateShrinkwrap(options: ShrinkwrapOptions): Promise<Pa
5260 return lock ;
5361}
5462
55- async function generateLockFile ( pkgJson : PackageJson , yarnLock : YarnLock , rootDir : string ) : Promise < PackageLockFile > {
63+ async function generateLockFile (
64+ pkgJson : PackageJson ,
65+ yarnLock : YarnLock ,
66+ rootDir : string ,
67+ resolutions : Record < string , string > = { } ,
68+ ) : Promise < PackageLockFile > {
5669 const builder = new PackageGraphBuilder ( yarnLock ) ;
5770 const rootKeys = await builder . buildGraph ( pkgJson . dependencies || { } , rootDir ) ;
5871
@@ -65,7 +78,7 @@ async function generateLockFile(pkgJson: PackageJson, yarnLock: YarnLock, rootDi
6578 } ;
6679
6780 try {
68- checkRequiredVersions ( lockFile ) ;
81+ checkRequiredVersions ( lockFile , resolutions ) ;
6982 } catch ( e : any ) {
7083 const tempFile = path . join ( os . tmpdir ( ) , 'npm-shrinkwrap.json' ) ;
7184 await fs . writeFile ( tempFile , JSON . stringify ( lockFile , undefined , 2 ) , 'utf-8' ) ;
@@ -366,7 +379,7 @@ async function findPackageDir(depName: string, rootDir: string) {
366379 * tell our future selves that is cannot and will not work, and we should find another
367380 * solution.
368381 */
369- export function checkRequiredVersions ( root : PackageLockFile ) {
382+ export function checkRequiredVersions ( root : PackageLockFile , resolutions : Record < string , string > = { } ) {
370383 recurse ( root , [ [ root . name , root ] ] ) ;
371384
372385 // rootPath does include 'entry'
@@ -386,8 +399,11 @@ export function checkRequiredVersions(root: PackageLockFile) {
386399 range = range . split ( '@' ) [ 1 ] ;
387400 }
388401
402+ // If there's a resolution for this package, use that instead of the required range
403+ const effectiveRange = resolutions [ name ] || range ;
404+
389405 const depPath = [ name , ...rootPath . map ( x => x [ 0 ] ) ] ;
390- if ( ! semver . satisfies ( resolvedPackage . version , range ) ) {
406+ if ( ! semver . satisfies ( resolvedPackage . version , effectiveRange ) ) {
391407 // Ruh-roh.
392408 throw new Error ( `Looks like we're trying to force '${ renderRootPath ( depPath ) } ' to version '${ resolvedPackage . version } ' (found at ${ resolvedPath } => ${ name } ), but `
393409 + `${ depPath [ depPath . length - 1 ] } specifies the dependency as '${ range } '. NPM will not respect this shrinkwrap file. Try vendoring a patched `
@@ -421,7 +437,7 @@ export function checkRequiredVersions(root: PackageLockFile) {
421437 * We have manipulated the tree a bunch. Do a sanity check to ensure that all declared
422438 * dependencies are satisfied.
423439 */
424- export function _validateTree ( lock : PackageLockTree ) {
440+ export function _validateTree ( lock : PackageLockTree , resolutions : Record < string , string > = { } ) {
425441 const errors = new Array < string > ( ) ;
426442 recurse ( lock , [ [ 'root' , lock ] ] , { } ) ;
427443 if ( errors . length > 0 ) {
@@ -452,13 +468,16 @@ export function _validateTree(lock: PackageLockTree) {
452468 declaredRange = declaredRange . split ( '@' ) [ 1 ] ;
453469 }
454470
471+ // If there's a resolution for this package, use that instead of the declared range
472+ const effectiveRange = resolutions [ name ] || declaredRange ;
473+
455474 const foundVersion = depsVersions [ name ] ;
456475 const newRootPath = [ name , ...rootPath . map ( x => x [ 0 ] ) ] ;
457476 if ( ! foundVersion ) {
458477 errors . push ( `Dependency on ${ renderRootPath ( newRootPath ) } not satisfied: not found` ) ;
459- } else if ( ! semver . satisfies ( foundVersion , declaredRange ) ) {
478+ } else if ( ! semver . satisfies ( foundVersion , effectiveRange ) ) {
460479 // eslint-disable-next-line no-console
461- errors . push ( `Dependency on ${ renderRootPath ( newRootPath ) } not satisfied: declared range '${ declaredRange } ', found '${ foundVersion } '` ) ;
480+ errors . push ( `Dependency on ${ renderRootPath ( newRootPath ) } not satisfied: declared range '${ effectiveRange } ', found '${ foundVersion } '` ) ;
462481 }
463482 }
464483}
0 commit comments