export = loadModules ; import * as depTypes from './dep-types' ; import * as fs from 'then-fs' ; import * as _get from 'lodash.get' ; import * as _set from 'lodash.set' ; import * as _clone from 'lodash.clone' ; import * as _assign from 'lodash.assign' ; import * as _flatten from 'lodash.flatten' ; import * as debugModule from 'debug' ; import * as path from 'path' ; import * as semver from 'semver' ; import * as resolve from 'snyk-resolve' ; import * as tryRequire from 'snyk-try-require' ; import { AbbreviatedVersion , PackageExpanded , PackageJsonEnriched } from './types' ; const debug = debugModule ( 'snyk:resolve:deps' ) ; function applyExtraFields ( src , dest , extraFields ) { if ( ! extraFields || ! extraFields . length ) { return ; } extraFields . forEach ( function applyExtraField ( field ) { _set ( dest , field , _get ( src , field ) || null ) ; } ) ; } async function loadModules ( root , depType , options ) { tryRequire . cache . reset ( ) ; let opt = _clone ( options || { } ) ; let pkgRoot = root ; if ( opt . file ) { const pathInfo = path . parse ( opt . file ) ; pkgRoot = path . resolve ( pkgRoot , pathInfo . dir ) ; opt . file = pathInfo . base ; } const tree = await loadModulesInternal ( pkgRoot , depType || null , null , opt ) ; let missing = [ ] ; if ( tree . __dependencies ) { Object . keys ( tree . __dependencies ) . forEach ( function ( name ) { if ( ! tree . dependencies [ name ] ) { missing . push ( resolve ( name , pkgRoot ) . then ( function ( dir ) { return loadModulesInternal ( dir , depTypes . PROD , { __from : [ tree . name + '@' + tree . version , name ] , } ) ; } ) . catch ( function ( e ) { if ( e . code === 'NO_PACKAGE_FOUND' ) { return false ; } } ) ) ; } } ) ; } if ( missing . length ) { return Promise . all ( missing ) . then ( function ( packages ) { packages . filter ( Boolean ) . forEach ( function ( pkg ) { pkg . dep = tree . __dependencies [ pkg . name ] ; tree . dependencies [ pkg . name ] = pkg ; } ) ; return tree ; } ) ; } return tree ; } function loadModulesInternal ( root , rootDepType , parent , options ? ) { options = options || { } ; if ( ! rootDepType ) { rootDepType = depTypes . EXTRANEOUS ; } if ( typeof root !== 'string' ) { return Promise . reject ( new Error ( 'module path must be a string' ) ) ; } let modules = { } as PackageExpanded ; let dir = path . resolve ( root , options . file || 'package.json' ) ; let promise = tryRequire ( dir ) . then ( function ( pkg ) { if ( pkg ) { let full = pkg . name + '@' + ( pkg . version || '0.0.0' ) ; modules = { } as PackageExpanded ; applyExtraFields ( pkg , modules , options . extraFields ) ; _assign ( modules , { name : pkg . name , version : pkg . version || null , license : pkg . license || 'none' , depType : rootDepType , hasDevDependencies : ! ! pkg . devDependencies , full : full , __from : ( parent || { __from : [ ] } ) . __from , __devDependencies : pkg . devDependencies , __dependencies : pkg . dependencies , __optionalDependencies : pkg . optionalDependencies , __bundleDependencies : pkg . bundleDependencies , __filename : pkg . __filename , } ) ; pkg . __from = modules . __from . concat ( full ) ; pkg . full = modules . full ; if ( ! pkg . shrinkwrap && parent && parent . shrinkwrap ) { pkg . shrinkwrap = parent . shrinkwrap ; } else if ( pkg . shrinkwrap ) { pkg . shrinkwrap = pkg . full ; } if ( modules . __from . length === 0 ) { modules . __from . push ( full ) ; } } else { throw new Error ( dir + ' is not a node project' ) ; } modules . dependencies = { } ; return fs . readdir ( path . resolve ( root , 'node_modules' ) ) . then ( function ( dirs ) { let res = dirs . map ( function ( directory ) { if ( [ '.bin' , '.DS_Store' , '~' ] . indexOf ( directory ) >= 0 ) { return null ; } if ( directory . indexOf ( '@' ) === 0 ) { debug ( 'scoped reset on %s' , directory ) ; directory = path . resolve ( root , 'node_modules' , directory ) ; return fs . readdir ( directory ) . then ( function ( directories ) { return Promise . all ( directories . map ( function ( scopedDir ) { return tryRequire ( path . resolve ( directory , scopedDir , 'package.json' ) ) ; } ) ) ; } ) ; } directory = path . resolve ( root , 'node_modules' , directory ) ; return fs . realpath ( directory ) . then ( function ( realDirectory ) { if ( realDirectory === root ) { return null ; } else { return tryRequire ( path . resolve ( directory , 'package.json' ) ) ; } } ) ; } ) ; return Promise . all ( res ) . then ( function ( response ) { response = _flatten ( response ) . filter ( Boolean ) ; response . reduce ( function ( acc , curr ) { let license ; let licenses = curr . license as any || curr . licenses as any ; if ( Array . isArray ( licenses ) ) { license = licenses . reduce ( function ( accumulator , current ) { accumulator . push ( ( current || { } ) . type || current ) ; return accumulator ; } , [ ] ) . join ( '/' ) ; } else { license = ( licenses || { } ) . type || licenses ; } let depInfo = depTypes ( curr . name ! , pkg ) ; let depType = depInfo . type || rootDepType ; let depFrom = depInfo . from ; let valid = false ; if ( depFrom ) { valid = semver . satisfies ( curr . version as string , depFrom ) ; } let full = curr . name + '@' + ( curr . version || '0.0.0' ) ; acc [ curr . name ! ] = { } as PackageExpanded ; applyExtraFields ( curr , acc [ curr . name ! ] , options . extraFields ) ; _assign ( acc [ curr . name ! ] , { name : curr . name , version : curr . version || null , full : full , valid : valid , depType : depType , snyk : curr . snyk , license : license || 'none' , dep : depFrom || null , __from : pkg . __from . concat ( full ) , __devDependencies : curr . devDependencies , __dependencies : curr . dependencies , __optionalDependencies : curr . optionalDependencies , __bundleDependencies : curr . bundleDependencies , __filename : curr . __filename , } ) ; if ( depInfo . bundled ) { acc [ curr . name ! ] . bundled = acc [ curr . name ! ] . __from . slice ( 0 ) ; } if ( pkg . shrinkwrap ) { acc [ curr . name ! ] . shrinkwrap = pkg . shrinkwrap ; } return acc ; } , modules . dependencies ) ; return modules ; } ) ; } ) . then ( function ( mods ) { let deps = Object . keys ( mods . dependencies ) ; let promises = deps . map ( function ( dep ) { let depType = mods . dependencies [ dep ] . depType ; let directory = path . dirname ( mods . dependencies [ dep ] . __filename ) ; return loadModulesInternal ( directory , depType , pkg ) ; } ) ; return Promise . all ( promises ) . then ( function ( res ) { res . forEach ( function ( mod ) { mods . dependencies [ mod . name ] . dependencies = mod . dependencies ; } ) ; return mods ; } ) ; } ) . catch ( function ( error ) { if ( error . code === 'ENOENT' ) { modules . dependencies = { } ; return modules ; } throw error ; } ) ; } ) ; return promise ; }