@@ -1038,7 +1038,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1038
1038
if ( ! options ) {
1039
1039
options = { } ;
1040
1040
}
1041
-
1041
+ const pkgSpecVersionCache = { } ;
1042
1042
if ( ! existsSync ( pkgLockFile ) ) {
1043
1043
return {
1044
1044
pkgList,
@@ -1051,6 +1051,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1051
1051
rootNode ,
1052
1052
parentRef = null ,
1053
1053
visited = new Set ( ) ,
1054
+ pkgSpecVersionCache = { } ,
1054
1055
options = { } ,
1055
1056
) => {
1056
1057
if ( visited . has ( node ) ) {
@@ -1119,6 +1120,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1119
1120
author : authorString ,
1120
1121
scope : scope ,
1121
1122
_integrity : integrity ,
1123
+ externalReferences : [ ] ,
1122
1124
properties : [
1123
1125
{
1124
1126
name : "SrcFile" ,
@@ -1155,11 +1157,88 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1155
1157
value : node . location ,
1156
1158
} ) ;
1157
1159
}
1160
+ if ( node ?. installLinks ) {
1161
+ pkg . properties . push ( {
1162
+ name : "cdx:npm:installLinks" ,
1163
+ value : "true" ,
1164
+ } ) ;
1165
+ }
1166
+ if ( node ?. binPaths ?. length ) {
1167
+ pkg . properties . push ( {
1168
+ name : "cdx:npm:binPaths" ,
1169
+ value : node . binPaths . join ( ", " ) ,
1170
+ } ) ;
1171
+ }
1172
+ if ( node ?. hasInstallScript ) {
1173
+ pkg . properties . push ( {
1174
+ name : "cdx:npm:hasInstallScript" ,
1175
+ value : "true" ,
1176
+ } ) ;
1177
+ }
1178
+ if ( node ?. isLink ) {
1179
+ pkg . properties . push ( {
1180
+ name : "cdx:npm:isLink" ,
1181
+ value : "true" ,
1182
+ } ) ;
1183
+ }
1184
+ // This getter method could fail with errors at times.
1185
+ // Example Error: Invalid tag name "^>=6.0.0" of package "^>=6.0.0": Tags may not have any characters that encodeURIComponent encodes.
1186
+ try {
1187
+ if ( ! node ?. isRegistryDependency ) {
1188
+ pkg . properties . push ( {
1189
+ name : "cdx:npm:isRegistryDependency" ,
1190
+ value : "false" ,
1191
+ } ) ;
1192
+ }
1193
+ } catch ( err ) {
1194
+ // ignore
1195
+ }
1196
+ if ( node ?. isWorkspace ) {
1197
+ pkg . properties . push ( {
1198
+ name : "cdx:npm:isWorkspace" ,
1199
+ value : "true" ,
1200
+ } ) ;
1201
+ }
1202
+ if ( node ?. inBundle ) {
1203
+ pkg . properties . push ( {
1204
+ name : "cdx:npm:inBundle" ,
1205
+ value : "true" ,
1206
+ } ) ;
1207
+ }
1208
+ if ( node ?. inDepBundle ) {
1209
+ pkg . properties . push ( {
1210
+ name : "cdx:npm:inDepBundle" ,
1211
+ value : "true" ,
1212
+ } ) ;
1213
+ }
1214
+ if ( node . package ?. repository ?. url ) {
1215
+ pkg . externalReferences . push ( {
1216
+ type : "vcs" ,
1217
+ url : node . package . repository . url ,
1218
+ } ) ;
1219
+ }
1220
+ if ( node . package ?. bugs ?. url ) {
1221
+ pkg . externalReferences . push ( {
1222
+ type : "issue-tracker" ,
1223
+ url : node . package . bugs . url ,
1224
+ } ) ;
1225
+ }
1226
+ if ( node ?. package ?. keywords ?. length ) {
1227
+ pkg . tags = Array . isArray ( node . package . keywords )
1228
+ ? node . package . keywords . sort ( )
1229
+ : node . package . keywords . split ( "," ) ;
1230
+ }
1158
1231
}
1159
- const packageLicense = node . package . license ;
1160
- if ( packageLicense ) {
1232
+ if ( node . package ?. license ) {
1161
1233
// License will be overridden if shouldFetchLicense() is enabled
1162
- pkg . license = packageLicense ;
1234
+ pkg . license = node . package . license ;
1235
+ }
1236
+ const deprecatedMessage = node . package ?. deprecated ;
1237
+ if ( deprecatedMessage ) {
1238
+ pkg . properties . push ( {
1239
+ name : "cdx:npm:deprecated" ,
1240
+ value : deprecatedMessage ,
1241
+ } ) ;
1163
1242
}
1164
1243
pkgList . push ( pkg ) ;
1165
1244
@@ -1170,7 +1249,14 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1170
1249
const {
1171
1250
pkgList : childPkgList ,
1172
1251
dependenciesList : childDependenciesList ,
1173
- } = parseArboristNode ( workspaceNode , rootNode , purlString , visited ) ;
1252
+ } = parseArboristNode (
1253
+ workspaceNode ,
1254
+ rootNode ,
1255
+ purlString ,
1256
+ visited ,
1257
+ pkgSpecVersionCache ,
1258
+ options ,
1259
+ ) ;
1174
1260
pkgList = pkgList . concat ( childPkgList ) ;
1175
1261
dependenciesList = dependenciesList . concat ( childDependenciesList ) ;
1176
1262
const depWorkspacePurlString = decodeURIComponent (
@@ -1193,8 +1279,9 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1193
1279
1194
1280
// this handles the case when a node has ["dependencies"] key in a package-lock.json
1195
1281
// for a node. We exclude the root node because it's already been handled
1282
+ // If the node has "requires", we don't have to track the "dependencies"
1196
1283
const childrenDependsOn = [ ] ;
1197
- if ( node !== rootNode ) {
1284
+ if ( node !== rootNode && ! node . edgesOut . size ) {
1198
1285
for ( const child of node . children ) {
1199
1286
const childNode = child [ 1 ] ;
1200
1287
const {
@@ -1205,6 +1292,8 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1205
1292
rootNode ,
1206
1293
decodeURIComponent ( purlString ) ,
1207
1294
visited ,
1295
+ pkgSpecVersionCache ,
1296
+ options ,
1208
1297
) ;
1209
1298
pkgList = pkgList . concat ( childPkgList ) ;
1210
1299
dependenciesList = dependenciesList . concat ( childDependenciesList ) ;
@@ -1232,6 +1321,10 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1232
1321
let targetVersion ;
1233
1322
let targetName ;
1234
1323
let foundMatch = false ;
1324
+ // This cache is required to help us down the line.
1325
+ if ( edge ?. to ?. version && edge ?. spec ) {
1326
+ pkgSpecVersionCache [ `${ edge . name } -${ edge . spec } ` ] = edge . to . version ;
1327
+ }
1235
1328
// if the edge doesn't have an integrity, it's likely a peer dependency
1236
1329
// which isn't installed
1237
1330
// Bug #795. At times, npm loses the integrity node completely and such packages are getting missed out
@@ -1285,11 +1378,43 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1285
1378
break ;
1286
1379
}
1287
1380
}
1381
+ if ( ! targetVersion || ! targetName ) {
1382
+ if ( pkgSpecVersionCache [ `${ edge . name } -${ edge . spec } ` ] ) {
1383
+ targetVersion = pkgSpecVersionCache [ `${ edge . name } -${ edge . spec } ` ] ;
1384
+ targetName = edge . name ;
1385
+ }
1386
+ }
1288
1387
}
1289
1388
1290
1389
// if we can't find the version of the edge, continue
1291
1390
// it may be an optional peer dependency
1292
1391
if ( ! targetVersion || ! targetName ) {
1392
+ if (
1393
+ DEBUG_MODE &&
1394
+ ! options . deep &&
1395
+ ! [ "optional" , "peer" , "peerOptional" ] . includes ( edge ?. type )
1396
+ ) {
1397
+ if ( ! targetVersion ) {
1398
+ console . log (
1399
+ `Unable to determine the version for the dependency ${ edge . name } from the path ${ edge ?. from ?. path } . This is likely an edge case that is not handled.` ,
1400
+ edge ,
1401
+ ) ;
1402
+ } else if ( ! targetName ) {
1403
+ console . log (
1404
+ `Unable to determine the name for the dependency from the edge from the path ${ edge ?. from ?. path } . This is likely an edge case that is not handled.` ,
1405
+ edge ,
1406
+ ) ;
1407
+ }
1408
+ }
1409
+ // juice-shop
1410
+ // Lock files created with --legacy-peer-deps will have certain peer dependencies missing
1411
+ // This flags any non-missing peers
1412
+ if ( DEBUG_MODE && edge ?. type === "peer" && edge ?. error !== "MISSING" ) {
1413
+ console . log (
1414
+ `Unable to determine the version for the dependency ${ edge . name } from the path ${ edge ?. from ?. path } . This is likely an edge case that is not handled.` ,
1415
+ edge ,
1416
+ ) ;
1417
+ }
1293
1418
continue ;
1294
1419
}
1295
1420
const depPurlString = decodeURIComponent (
@@ -1309,6 +1434,8 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1309
1434
rootNode ,
1310
1435
decodeURIComponent ( purlString ) ,
1311
1436
visited ,
1437
+ pkgSpecVersionCache ,
1438
+ options ,
1312
1439
) ;
1313
1440
pkgList = pkgList . concat ( childPkgList ) ;
1314
1441
dependenciesList = dependenciesList . concat ( childDependenciesList ) ;
@@ -1332,7 +1459,24 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1332
1459
} ) ;
1333
1460
let tree = undefined ;
1334
1461
try {
1335
- tree = await arb . loadVirtual ( ) ;
1462
+ const rootNodeModulesDir = join ( path . dirname ( pkgLockFile ) , "node_modules" ) ;
1463
+ if ( existsSync ( rootNodeModulesDir ) ) {
1464
+ if ( options . deep ) {
1465
+ console . log (
1466
+ `Constructing the actual dependency hierarchy from ${ rootNodeModulesDir } .` ,
1467
+ ) ;
1468
+ tree = await arb . loadActual ( ) ;
1469
+ } else {
1470
+ if ( DEBUG_MODE ) {
1471
+ console . log (
1472
+ "Constructing virtual dependency tree based on the lock file. Pass --deep argument to construct the actual dependency tree from disk." ,
1473
+ ) ;
1474
+ }
1475
+ tree = await arb . loadVirtual ( ) ;
1476
+ }
1477
+ } else {
1478
+ tree = await arb . loadVirtual ( ) ;
1479
+ }
1336
1480
} catch ( e ) {
1337
1481
console . log (
1338
1482
`Unable to parse ${ pkgLockFile } without legacy peer dependencies. Retrying ...` ,
@@ -1364,6 +1508,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1364
1508
tree ,
1365
1509
null ,
1366
1510
new Set ( ) ,
1511
+ pkgSpecVersionCache ,
1367
1512
options ,
1368
1513
) ) ;
1369
1514
0 commit comments