@@ -15,6 +15,8 @@ var semver = require('semver')
15
15
var color = require ( 'ansicolors' )
16
16
var npa = require ( 'npm-package-arg' )
17
17
var iferr = require ( 'iferr' )
18
+ var sortedObject = require ( 'sorted-object' )
19
+ var extend = Object . assign || require ( 'util' ) . _extend
18
20
var npm = require ( './npm.js' )
19
21
var mutateIntoLogicalTree = require ( './install/mutate-into-logical-tree.js' )
20
22
var recalculateMetadata = require ( './install/deps.js' ) . recalculateMetadata
@@ -76,8 +78,8 @@ var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) {
76
78
77
79
pruneNestedExtraneous ( data )
78
80
filterByEnv ( data )
79
- var bfs = filterFound ( bfsify ( data ) , args )
80
- var lite = getLite ( bfs )
81
+ var unlooped = filterFound ( unloop ( data ) , args )
82
+ var lite = getLite ( unlooped )
81
83
82
84
if ( silent ) return cb ( null , data , lite )
83
85
@@ -86,7 +88,7 @@ var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) {
86
88
var out
87
89
if ( json ) {
88
90
var seen = [ ]
89
- var d = long ?bfs :lite
91
+ var d = long ?unlooped :lite
90
92
// the raw data can be circular
91
93
out = JSON . stringify ( d , function ( k , o ) {
92
94
if ( typeof o === 'object' ) {
@@ -96,9 +98,9 @@ var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) {
96
98
return o
97
99
} , 2 )
98
100
} else if ( npm . config . get ( 'parseable' ) ) {
99
- out = makeParseable ( bfs , long , dir )
101
+ out = makeParseable ( unlooped , long , dir )
100
102
} else if ( data ) {
101
- out = makeArchy ( bfs , long , dir )
103
+ out = makeArchy ( unlooped , long , dir )
102
104
}
103
105
output ( out )
104
106
@@ -247,36 +249,25 @@ function getLite (data, noname, depth) {
247
249
return lite
248
250
}
249
251
250
- function bfsify ( root ) {
251
- // walk over the data, and turn it from this:
252
- // +-- a
253
- // | `-- b
254
- // | `-- a (truncated)
255
- // `--b (truncated)
256
- // into this:
257
- // +-- a
258
- // `-- b
259
- // which looks nicer
252
+ function unloop ( root ) {
260
253
var queue = [ root ]
261
- var seen = [ root ]
254
+ var seen = { }
255
+ seen [ root . path ] = true
262
256
263
257
while ( queue . length ) {
264
258
var current = queue . shift ( )
265
259
var deps = current . dependencies = current . dependencies || { }
266
260
Object . keys ( deps ) . forEach ( function ( d ) {
267
261
var dep = deps [ d ]
268
262
if ( dep . missing ) return
269
- if ( inList ( seen , dep ) ) {
270
- if ( npm . config . get ( 'parseable' ) || ! npm . config . get ( 'long' ) ) {
271
- delete deps [ d ]
272
- return
273
- } else {
274
- dep = deps [ d ] = Object . create ( dep )
275
- dep . dependencies = { }
276
- }
263
+ if ( dep . path && seen [ dep . path ] ) {
264
+ dep = deps [ d ] = extend ( { } , dep )
265
+ dep . dependencies = { }
266
+ dep . _deduped = path . relative ( root . path , dep . path ) . replace ( / n o d e _ m o d u l e s \/ / g, '' )
267
+ return
277
268
}
269
+ seen [ dep . path ] = true
278
270
queue . push ( dep )
279
- seen . push ( dep )
280
271
} )
281
272
}
282
273
@@ -285,18 +276,23 @@ function bfsify (root) {
285
276
286
277
function filterFound ( root , args ) {
287
278
if ( ! args . length ) return root
288
- var deps = root . dependencies
289
- if ( deps ) {
290
- Object . keys ( deps ) . forEach ( function ( depName ) {
291
- var dep = filterFound ( deps [ depName ] , args )
279
+ if ( ! root . dependencies ) return root
280
+
281
+ // Mark all deps
282
+ var toMark = [ root ]
283
+ while ( toMark . length ) {
284
+ var markPkg = toMark . shift ( )
285
+ var markDeps = markPkg . dependencies
286
+ if ( ! markDeps ) continue
287
+ Object . keys ( markDeps ) . forEach ( function ( depName ) {
288
+ var dep = markDeps [ depName ]
292
289
if ( dep . peerMissing ) return
293
-
294
- // see if this one itself matches
295
- var found = false
296
- for ( var ii = 0 ; ! found && ii < args . length ; ii ++ ) {
290
+ dep . _parent = markPkg
291
+ for ( var ii = 0 ; ii < args . length ; ii ++ ) {
297
292
var argName = args [ ii ] [ 0 ]
298
293
var argVersion = args [ ii ] [ 1 ]
299
294
var argRaw = args [ ii ] [ 2 ]
295
+ var found
300
296
if ( depName === argName && argVersion ) {
301
297
found = semver . satisfies ( dep . version , argVersion , true )
302
298
} else if ( depName === argName ) {
@@ -305,16 +301,33 @@ function filterFound (root, args) {
305
301
} else if ( dep . path === argRaw ) {
306
302
found = true
307
303
}
304
+ if ( found ) {
305
+ dep . _found = 'explicit'
306
+ var parent = dep . _parent
307
+ while ( parent && ! parent . _found && ! parent . _deduped ) {
308
+ parent . _found = 'implicit'
309
+ parent = parent . _parent
310
+ }
311
+ break
312
+ }
308
313
}
309
- // included explicitly
310
- if ( found ) dep . _found = true
311
- // included because a child was included
312
- if ( dep . _found && ! root . _found ) root . _found = 1
313
- // not included
314
- if ( ! dep . _found ) delete deps [ depName ]
314
+ toMark . push ( dep )
315
+ } )
316
+ }
317
+ var toTrim = [ root ]
318
+ while ( toTrim . length ) {
319
+ var trimPkg = toTrim . shift ( )
320
+ var trimDeps = trimPkg . dependencies
321
+ if ( ! trimDeps ) continue
322
+ trimPkg . dependencies = { }
323
+ Object . keys ( trimDeps ) . forEach ( function ( name ) {
324
+ var dep = trimDeps [ name ]
325
+ if ( ! dep . _found ) return
326
+ if ( dep . _found === 'implicit' && dep . _deduped ) return
327
+ trimPkg . dependencies [ name ] = dep
328
+ toTrim . push ( dep )
315
329
} )
316
330
}
317
- if ( ! root . _found ) root . _found = false
318
331
return root
319
332
}
320
333
@@ -345,7 +358,7 @@ function makeArchy_ (data, long, dir, depth, parent, d) {
345
358
var out = { }
346
359
// the top level is a bit special.
347
360
out . label = data . _id || ''
348
- if ( data . _found === true && data . _id ) {
361
+ if ( data . _found === 'explicit' && data . _id ) {
349
362
if ( npm . color ) {
350
363
out . label = color . bgBlack ( color . yellow ( out . label . trim ( ) ) ) + ' '
351
364
} else {
@@ -354,6 +367,14 @@ function makeArchy_ (data, long, dir, depth, parent, d) {
354
367
}
355
368
if ( data . link ) out . label += ' -> ' + data . link
356
369
370
+ if ( data . _deduped ) {
371
+ if ( npm . color ) {
372
+ out . label += ' ' + color . brightBlack ( 'deduped' )
373
+ } else {
374
+ out . label += ' deduped'
375
+ }
376
+ }
377
+
357
378
if ( data . invalid ) {
358
379
if ( data . realName !== data . name ) out . label += ' (' + data . realName + ')'
359
380
var invalid = 'invalid'
@@ -369,6 +390,7 @@ function makeArchy_ (data, long, dir, depth, parent, d) {
369
390
370
391
if ( data . peerMissing ) {
371
392
var peerMissing = 'UNMET PEER DEPENDENCY'
393
+
372
394
if ( npm . color ) peerMissing = color . bgBlack ( color . red ( peerMissing ) )
373
395
out . label = peerMissing + ' ' + out . label
374
396
}
@@ -415,7 +437,7 @@ function makeArchy_ (data, long, dir, depth, parent, d) {
415
437
. sort ( alphasort ) . filter ( function ( d ) {
416
438
return ! isCruft ( data . dependencies [ d ] )
417
439
} ) . map ( function ( d ) {
418
- return makeArchy_ ( data . dependencies [ d ] , long , dir , depth + 1 , data , d )
440
+ return makeArchy_ ( sortedObject ( data . dependencies [ d ] ) , long , dir , depth + 1 , data , d )
419
441
} )
420
442
}
421
443
@@ -444,19 +466,20 @@ function getExtras (data) {
444
466
}
445
467
446
468
function makeParseable ( data , long , dir , depth , parent , d ) {
469
+ if ( data . _deduped ) return [ ]
447
470
depth = depth || 0
448
471
if ( depth > npm . config . get ( 'depth' ) ) return [ makeParseable_ ( data , long , dir , depth , parent , d ) ]
449
472
return [ makeParseable_ ( data , long , dir , depth , parent , d ) ]
450
- . concat ( Object . keys ( data . dependencies || { } )
451
- . sort ( alphasort ) . map ( function ( d ) {
452
- return makeParseable ( data . dependencies [ d ] , long , dir , depth + 1 , data , d )
453
- } ) )
454
- . filter ( function ( x ) { return x } )
455
- . join ( '\n' )
473
+ . concat ( Object . keys ( data . dependencies || { } )
474
+ . sort ( alphasort ) . map ( function ( d ) {
475
+ return makeParseable ( data . dependencies [ d ] , long , dir , depth + 1 , data , d )
476
+ } ) )
477
+ . filter ( function ( x ) { return x } )
478
+ . join ( '\n' )
456
479
}
457
480
458
481
function makeParseable_ ( data , long , dir , depth , parent , d ) {
459
- if ( data . hasOwnProperty ( '_found' ) && data . _found !== true ) return ''
482
+ if ( data . hasOwnProperty ( '_found' ) && data . _found !== 'explicit' ) return ''
460
483
461
484
if ( data . missing ) {
462
485
if ( depth < npm . config . get ( 'depth' ) ) {