@@ -1310,6 +1310,56 @@ tap.test('failing optional peer deps are not installed', async t => {
13101310 t . notOk ( setupRequire ( dir ) ( 'bar' , 'which' ) , 'Failing optional peer deps should not be installed' )
13111311} )
13121312
1313+ tap . test ( 'optional peer declared only in peerDependenciesMeta is materialized when provided' , async t => {
1314+ // Regression for npm/cli#9460.
1315+ // `bar` declares `which` as an optional peer via peerDependenciesMeta only, with no peerDependencies entry, so no edge is created for it.
1316+ // The workspace provides `which`, so under the linked strategy `which` should be linked into `bar`'s store node_modules (matching pnpm).
1317+ // `which` is not a root dependency, so it is not hoisted to the top-level node_modules where parent-dir lookup would mask the result.
1318+ const graph = {
1319+ registry : [
1320+ { name : 'which' , version : '1.0.0' } ,
1321+ { name : 'bar' , version : '1.0.0' , peerDependenciesMeta : { which : { optional : true } } } ,
1322+ ] ,
1323+ root : { name : 'foo' , version : '1.2.3' } ,
1324+ workspaces : [
1325+ { name : 'app' , version : '1.0.0' , dependencies : { bar : '*' , which : '1.0.0' } } ,
1326+ ] ,
1327+ }
1328+
1329+ const { dir, registry } = await getRepo ( graph )
1330+
1331+ // Note that we override this cache to prevent interference from other tests
1332+ const cache = fs . mkdtempSync ( `${ getTempDir ( ) } /test-` )
1333+ const arborist = new Arborist ( { path : dir , registry, packumentCache : new Map ( ) , cache } )
1334+ await arborist . reify ( { installStrategy : 'linked' } )
1335+
1336+ t . ok ( setupRequire ( path . join ( dir , 'packages' , 'app' ) ) ( 'bar' , 'which' ) ,
1337+ 'optional peer provided by the workspace is materialized into bar store node_modules' )
1338+ } )
1339+
1340+ tap . test ( 'optional peer declared only in peerDependenciesMeta is omitted when not provided' , async t => {
1341+ // Counterpart to the regression above: when nobody provides the optional peer it must stay omitted, preserving "optional" semantics.
1342+ const graph = {
1343+ registry : [
1344+ { name : 'bar' , version : '1.0.0' , peerDependenciesMeta : { which : { optional : true } } } ,
1345+ ] ,
1346+ root : { name : 'foo' , version : '1.2.3' } ,
1347+ workspaces : [
1348+ { name : 'app' , version : '1.0.0' , dependencies : { bar : '*' } } ,
1349+ ] ,
1350+ }
1351+
1352+ const { dir, registry } = await getRepo ( graph )
1353+
1354+ // Note that we override this cache to prevent interference from other tests
1355+ const cache = fs . mkdtempSync ( `${ getTempDir ( ) } /test-` )
1356+ const arborist = new Arborist ( { path : dir , registry, packumentCache : new Map ( ) , cache } )
1357+ await arborist . reify ( { installStrategy : 'linked' } )
1358+
1359+ t . notOk ( setupRequire ( path . join ( dir , 'packages' , 'app' ) ) ( 'bar' , 'which' ) ,
1360+ 'optional peer that nobody provides is not materialized' )
1361+ } )
1362+
13131363// Virtual packages are 2 packages that have the same version but are
13141364// duplicated on disk to solve peer-dependency conflict.
13151365tap . test ( 'virtual packages' , async t => {
0 commit comments