@ -10,6 +10,7 @@ var iferr = require('iferr')
var npa = require ( 'npm-package-arg' )
var npa = require ( 'npm-package-arg' )
var validate = require ( 'aproba' )
var validate = require ( 'aproba' )
var realizePackageSpecifier = require ( 'realize-package-specifier' )
var realizePackageSpecifier = require ( 'realize-package-specifier' )
var asap = require ( 'asap' )
var dezalgo = require ( 'dezalgo' )
var dezalgo = require ( 'dezalgo' )
var fetchPackageMetadata = require ( '../fetch-package-metadata.js' )
var fetchPackageMetadata = require ( '../fetch-package-metadata.js' )
var andAddParentToErrors = require ( './and-add-parent-to-errors.js' )
var andAddParentToErrors = require ( './and-add-parent-to-errors.js' )
@ -78,15 +79,17 @@ function doesChildVersionMatch (child, requested, requestor) {
return semver . satisfies ( child . package . version , requested . spec )
return semver . satisfies ( child . package . version , requested . spec )
}
}
// TODO: Rename to maybe computeMetadata or computeRelationships
exports . recalculateMetadata = function ( tree , log , next ) {
exports . recalculateMetadata = function ( tree , log , next ) {
recalculateMetadata ( tree , log , { } , next )
recalculateMetadata ( tree , log , { } , next )
}
}
exports . _ childDependencySpecifier = childDependencySpecifier
function childDependencySpecifier ( tree , name , spec , cb ) {
function childDependencySpecifier ( tree , name , spec , cb ) {
if ( ! tree . resolved ) tree . resolved = { }
if ( ! tree . resolved ) tree . resolved = { }
if ( ! tree . resolved [ name ] ) tree . resolved [ name ] = { }
if ( ! tree . resolved [ name ] ) tree . resolved [ name ] = { }
if ( tree . resolved [ name ] [ spec ] ) {
if ( tree . resolved [ name ] [ spec ] ) {
return process . nextTick ( function ( ) {
return asap ( function ( ) {
cb ( null , tree . resolved [ name ] [ spec ] )
cb ( null , tree . resolved [ name ] [ spec ] )
} )
} )
}
}
@ -101,19 +104,24 @@ function recalculateMetadata (tree, log, seen, next) {
validate ( 'OOOF' , arguments )
validate ( 'OOOF' , arguments )
if ( seen [ tree . path ] ) return next ( )
if ( seen [ tree . path ] ) return next ( )
seen [ tree . path ] = true
seen [ tree . path ] = true
if ( tree . parent == null ) resetMetadata ( tree )
if ( tree . parent == null ) {
function markDeps ( spec , done ) {
resetMetadata ( tree )
validate ( 'SF' , arguments )
tree . isTop = true
var matched = spec . match ( /^(@?[^@]+)@(.*)$/ )
}
childDependencySpecifier ( tree , matched [ 1 ] , matched [ 2 ] , function ( er , req ) {
function markDeps ( toMark , done ) {
var name = toMark . name
var spec = toMark . spec
var kind = toMark . kind
childDependencySpecifier ( tree , name , spec , function ( er , req ) {
if ( er || ! req . name ) return done ( )
if ( er || ! req . name ) return done ( )
var child = findRequirement ( tree , req . name , req )
var child = findRequirement ( tree , req . name , req )
if ( child ) {
if ( child ) {
resolveWithExistingModule ( child , tree , log , andIgnoreErrors ( done ) )
resolveWithExistingModule ( child , tree , log , andIgnoreErrors ( done ) )
} else if ( tree . package . dependencies [ req . name ] != null ) {
} else if ( kind === 'dep' ) {
tree . missingDeps [ req . name ] = req . rawSpec
tree . missingDeps [ req . name ] = req . rawSpec
done ( )
done ( )
} else if ( tree . package . devDependencies [ req . name ] != null ) {
} else if ( kind === 'dev' ) {
tree . missingDevDeps [ req . name ] = req . rawSpec
tree . missingDevDeps [ req . name ] = req . rawSpec
done ( )
done ( )
} else {
} else {
@ -121,15 +129,16 @@ function recalculateMetadata (tree, log, seen, next) {
}
}
} )
} )
}
}
function specs ( deps ) {
return Object . keys ( deps ) . map ( function ( depname ) { return depname + '@' + deps [ depname ] } )
function makeMarkable ( deps , kind ) {
if ( ! deps ) return [ ]
return Object . keys ( deps ) . map ( function ( depname ) { return { name : depname , spec : deps [ depname ] , kind : kind } } )
}
}
// Ensure dependencies and dev dependencies are marked as required
// Ensure dependencies and dev dependencies are marked as required
var tomark = specs ( tree . package . dependencies )
var tomark = makeMarkable ( tree . package . dependencies , 'dep' )
if ( ! tree . parent && ( npm . config . get ( 'dev' ) || ! npm . config . get ( 'production' ) ) ) {
if ( tree . isTop ) tomark = union ( tomark , makeMarkable ( tree . package . devDependencies , 'dev' ) )
tomark = union ( tomark , specs ( tree . package . devDependencies ) )
}
// Ensure any children ONLY from a shrinkwrap are also included
// Ensure any children ONLY from a shrinkwrap are also included
var childrenOnlyInShrinkwrap = tree . children . filter ( function ( child ) {
var childrenOnlyInShrinkwrap = tree . children . filter ( function ( child ) {
return child . fromShrinkwrap &&
return child . fromShrinkwrap &&
@ -137,7 +146,13 @@ function recalculateMetadata (tree, log, seen, next) {
! tree . package . devDependencies [ child . package . name ]
! tree . package . devDependencies [ child . package . name ]
} )
} )
var tomarkOnlyInShrinkwrap = childrenOnlyInShrinkwrap . map ( function ( child ) {
var tomarkOnlyInShrinkwrap = childrenOnlyInShrinkwrap . map ( function ( child ) {
return child . package . _ spec
var name = child . package . name
var matched = child . package . _ spec . match ( /^@?[^@]+@(.*)$/ )
var spec = matched ? matched [ 1 ] : child . package . _ spec
var kind = tree . package . dependencies [ name ] ? 'dep'
: tree . package . devDependencies [ name ] ? 'dev'
: 'dep'
return { name : name , spec : spec , kind : kind }
} )
} )
tomark = union ( tomark , tomarkOnlyInShrinkwrap )
tomark = union ( tomark , tomarkOnlyInShrinkwrap )
@ -148,9 +163,7 @@ function recalculateMetadata (tree, log, seen, next) {
[ asyncMap , tomark , markDeps ] ,
[ asyncMap , tomark , markDeps ] ,
[ asyncMap , tree . children , function ( child , done ) { recalculateMetadata ( child , log , seen , done ) } ]
[ asyncMap , tree . children , function ( child , done ) { recalculateMetadata ( child , log , seen , done ) } ]
] , function ( ) {
] , function ( ) {
tree . userRequired = tree . package . _ requiredBy . some ( function ( req ) { return req === '#USER' } )
tree . location = flatNameFromTree ( tree )
tree . existing = tree . package . _ requiredBy . some ( function ( req ) { return req === '#EXISTING' } )
tree . package . _ location = flatNameFromTree ( tree )
next ( null , tree )
next ( null , tree )
} )
} )
}
}
@ -158,18 +171,23 @@ function recalculateMetadata (tree, log, seen, next) {
function addRequiredDep ( tree , child , cb ) {
function addRequiredDep ( tree , child , cb ) {
isDep ( tree , child , function ( childIsDep , childIsProdDep , childIsDevDep ) {
isDep ( tree , child , function ( childIsDep , childIsProdDep , childIsDevDep ) {
if ( ! childIsDep ) return cb ( false )
if ( ! childIsDep ) return cb ( false )
var name = childIsProdDep ? flatNameFromTree ( tree ) : '#DEV:' + flatNameFromTree ( tree )
replaceModuleByPath ( child , 'requiredBy' , tree )
replaceModuleName ( child . package , '_requiredBy' , name )
replaceModuleBy Name ( tree , 'requires' , child )
replaceModule ( child , 'requiredBy' , tree )
if ( childIsProdDep && tree . missingDeps ) delete tree . missingDeps [ moduleName ( child ) ]
replaceModule ( tree , 'requires' , child )
if ( childIsDevDep && tree . missingDevDeps ) delete tree . missingDevDeps [ moduleName ( child ) ]
cb ( true )
cb ( true )
} )
} )
}
}
exports . _ removeObsoleteDep = removeObsoleteDep
exports . removeObsoleteDep = removeObsoleteDep
function removeObsoleteDep ( child ) {
function removeObsoleteDep ( child ) {
if ( child . removed ) return
if ( child . removed ) return
child . removed = true
child . removed = true
// remove from physical tree
if ( child . parent ) {
child . parent . children = child . parent . children . filter ( function ( pchild ) { return pchild !== child } )
}
// remove from logical tree
var requires = child . requires || [ ]
var requires = child . requires || [ ]
requires . forEach ( function ( requirement ) {
requires . forEach ( function ( requirement ) {
requirement . requiredBy = requirement . requiredBy . filter ( function ( reqBy ) { return reqBy !== child } )
requirement . requiredBy = requirement . requiredBy . filter ( function ( reqBy ) { return reqBy !== child } )
@ -243,9 +261,7 @@ exports.loadRequestedDeps = function (args, tree, saveToDependencies, log, next)
// won't be when we're done), flag it as "depending" on the user
// won't be when we're done), flag it as "depending" on the user
// themselves, so we don't remove it as a dep that no longer exists
// themselves, so we don't remove it as a dep that no longer exists
addRequiredDep ( tree , child , function ( childIsDep ) {
addRequiredDep ( tree , child , function ( childIsDep ) {
if ( ! childIsDep ) {
if ( ! childIsDep ) child . userRequired = true
replaceModuleName ( child . package , '_requiredBy' , '#USER' )
}
depLoaded ( null , child , tracker )
depLoaded ( null , child , tracker )
} )
} )
} ) )
} ) )
@ -266,13 +282,13 @@ exports.removeDeps = function (args, tree, saveToDependencies, log, next) {
validate ( 'AOOF' , [ args , tree , log , next ] )
validate ( 'AOOF' , [ args , tree , log , next ] )
args . forEach ( function ( pkg ) {
args . forEach ( function ( pkg ) {
var pkgName = moduleName ( pkg )
var pkgName = moduleName ( pkg )
var toRemove = tree . children . filter ( moduleNameMatches ( pkgName ) )
var pkgToRemove = toRemove [ 0 ] || createChild ( { package : { name : pkgName } } )
if ( saveToDependencies ) {
if ( saveToDependencies ) {
var toRemove = tree . children . filter ( moduleNameMatches ( pkgName ) )
replaceModuleByPath ( tree , 'removed' , pkgToRemove )
var pkgToRemove = toRemove [ 0 ] || createChild ( { package : { name : pkgName } } )
replaceModule ( tree , 'removed' , pkgToRemove )
pkgToRemove . save = saveToDependencies
pkgToRemove . save = saveToDependencies
}
}
tree . children = tree . children . filter ( noModuleNameMatches ( pkgName ) )
removeObsoleteDep ( pkgToRemove )
} )
} )
log . finish ( )
log . finish ( )
next ( )
next ( )
@ -313,7 +329,6 @@ var failedDependency = exports.failedDependency = function (tree, name_pkg) {
pkg = name_pkg
pkg = name_pkg
name = moduleName ( pkg )
name = moduleName ( pkg )
}
}
tree . children = tree . children . filter ( noModuleNameMatches ( name ) )
tree . children = tree . children . filter ( noModuleNameMatches ( name ) )
if ( isDepOptional ( tree , name ) ) {
if ( isDepOptional ( tree , name ) ) {
@ -322,10 +337,14 @@ var failedDependency = exports.failedDependency = function (tree, name_pkg) {
tree . failed = true
tree . failed = true
if ( ! tree . parent ) return true
if ( tree . isTo p) return true
if ( tree . userRequired ) return true
if ( tree . userRequired ) return true
removeObsoleteDep ( tree )
if ( ! tree . requiredBy ) return false
for ( var ii = 0 ; ii < tree . requiredBy . length ; ++ ii ) {
for ( var ii = 0 ; ii < tree . requiredBy . length ; ++ ii ) {
var requireParent = tree . requiredBy [ ii ]
var requireParent = tree . requiredBy [ ii ]
if ( failedDependency ( requireParent , tree . package ) ) {
if ( failedDependency ( requireParent , tree . package ) ) {
@ -367,7 +386,7 @@ function andHandleOptionalErrors (log, tree, name, done) {
exports . loadDeps = loadDeps
exports . loadDeps = loadDeps
function loadDeps ( tree , log , next ) {
function loadDeps ( tree , log , next ) {
validate ( 'OOF' , arguments )
validate ( 'OOF' , arguments )
if ( tree . loaded || ( tree . parent && tree . parent . failed ) ) return andFinishTracker . now ( log , next )
if ( tree . loaded || ( tree . parent && tree . parent . failed ) || tree . removed ) return andFinishTracker . now ( log , next )
if ( tree . parent ) tree . loaded = true
if ( tree . parent ) tree . loaded = true
if ( ! tree . package . dependencies ) tree . package . dependencies = { }
if ( ! tree . package . dependencies ) tree . package . dependencies = { }
asyncMap ( Object . keys ( tree . package . dependencies ) , function ( dep , done ) {
asyncMap ( Object . keys ( tree . package . dependencies ) , function ( dep , done ) {
@ -446,38 +465,44 @@ function resolveWithExistingModule (child, tree, log, next) {
var updatePhantomChildren = exports . updatePhantomChildren = function ( current , child ) {
var updatePhantomChildren = exports . updatePhantomChildren = function ( current , child ) {
validate ( 'OO' , arguments )
validate ( 'OO' , arguments )
while ( current && current !== child . parent ) {
while ( current && current !== child . parent ) {
// FIXME: phantomChildren doesn't actually belong in the package.json
if ( ! current . phantomChildren ) current . phantomChildren = { }
if ( ! current . package . _ phantomChildren ) current . package . _ phantomChildren = { }
current . phantomChildren [ moduleName ( child ) ] = child
current . package . _ phantomChildren [ moduleName ( child ) ] = child . package . version
current = current . parent
current = current . parent
}
}
}
}
function flatNameFromTree ( tree ) {
function flatNameFromTree ( tree ) {
validate ( 'O' , arguments )
validate ( 'O' , arguments )
if ( ! tree . parent ) return '/'
if ( tree . isTo p) return '/'
var path = flatNameFromTree ( tree . parent )
var path = flatNameFromTree ( tree . parent )
if ( path !== '/' ) path += '/'
if ( path !== '/' ) path += '/'
return flatName ( path , tree )
return flatName ( path , tree )
}
}
exports . _ replaceModuleName = replaceModuleName
exports . _ replaceModuleByPath = replaceModuleByPath
function replaceModuleName ( obj , key , name ) {
function replaceModuleByPath ( obj , key , child ) {
validate ( 'OSS' , arguments )
return replaceModule ( obj , key , child , function ( replacing , child ) {
obj [ key ] = union ( obj [ key ] || [ ] , [ name ] )
return replacing . path === child . path
} )
}
exports . _ replaceModuleByName = replaceModuleByName
function replaceModuleByName ( obj , key , child ) {
var childName = moduleName ( child )
return replaceModule ( obj , key , child , function ( replacing , child ) {
return moduleName ( replacing ) === childName
} )
}
}
exports . _ replaceModule = replaceModule
function replaceModule ( obj , key , child , matchBy ) {
function replaceModule ( obj , key , child ) {
validate ( 'OSOF' , arguments )
validate ( 'OSO' , arguments )
if ( ! obj [ key ] ) obj [ key ] = [ ]
if ( ! obj [ key ] ) obj [ key ] = [ ]
// we replace children with a new array object instead of mutating it
// we replace children with a new array object instead of mutating it
// because mutating it results in weird failure states.
// because mutating it results in weird failure states.
// I would very much like to know _why_ this is. =/
// I would very much like to know _why_ this is. =/
var children = [ ] . concat ( obj [ key ] )
var children = [ ] . concat ( obj [ key ] )
var childName = moduleName ( child )
for ( var replaceAt = 0 ; replaceAt < children . length ; ++ replaceAt ) {
for ( var replaceAt = 0 ; replaceAt < children . length ; ++ replaceAt ) {
if ( moduleName ( children [ replaceAt ] ) === childName ) break
if ( matchBy ( children [ replaceAt ] , child ) ) break
}
}
var replacing = children . splice ( replaceAt , 1 , child )
var replacing = children . splice ( replaceAt , 1 , child )
obj [ key ] = children
obj [ key ] = children
@ -514,15 +539,17 @@ function resolveWithNewModule (pkg, tree, log, next) {
children : pkg . _ bundled || [ ] ,
children : pkg . _ bundled || [ ] ,
isLink : tree . isLink
isLink : tree . isLink
} )
} )
delete pkg . _ bundled
var hasBundled = child . children . length
var replaced = replaceModule ( parent , 'children' , child )
var replaced = replaceModuleByName ( parent , 'children' , child )
if ( replaced ) removeObsoleteDep ( replaced )
if ( replaced ) removeObsoleteDep ( replaced )
addRequiredDep ( tree , child , function ( ) {
addRequiredDep ( tree , child , function ( ) {
pkg . _ location = flatNameFromTree ( child )
child . location = flatNameFromTree ( child )
if ( tree . parent && parent !== tree ) updatePhantomChildren ( tree . parent , child )
if ( tree . parent && parent !== tree ) updatePhantomChildren ( tree . parent , child )
if ( pkg . _ b undled) {
if ( hasB undled) {
inflateBundled ( child , child . children )
inflateBundled ( child , child . children )
}
}
@ -584,7 +611,7 @@ var findRequirement = exports.findRequirement = function (tree, name, requested,
if ( matches . length ) return matches [ 0 ]
if ( matches . length ) return matches [ 0 ]
return null
return null
}
}
if ( ! tree . parent ) return null
if ( tree . isTo p) return null
return findRequirement ( tree . parent , name , requested , requestor )
return findRequirement ( tree . parent , name , requested , requestor )
}
}
@ -618,13 +645,12 @@ var earliestInstallable = exports.earliestInstallable = function (requiredBy, tr
return null
return null
}
}
// FIXME: phantomChildren doesn't actually belong in the package.json
if ( tree . phantomChildren && tree . phantomChildren [ pkg . name ] ) return null
if ( tree . package . _ phantomChildren && tree . package . _ phantomChildren [ pkg . name ] ) return null
if ( ! tree . parent ) return tree
if ( tree . isTo p) return tree
if ( tree . isGlobal ) return tree
if ( tree . isGlobal ) return tree
if ( npm . config . get ( 'global-style' ) && ! tree . parent . parent ) return tree
if ( npm . config . get ( 'global-style' ) && tree . parent . isTo p) return tree
if ( npm . config . get ( 'legacy-bundling' ) ) return tree
if ( npm . config . get ( 'legacy-bundling' ) ) return tree
return ( earliestInstallable ( requiredBy , tree . parent , pkg ) || tree )
return ( earliestInstallable ( requiredBy , tree . parent , pkg ) || tree )