import * as NPM from "npm" ; import * as Path from "path" ; import Emitter from "../platform/cross/src/emitter" ; type TPackageJSON = { [ key ] : any ; } ; export default class NPMInstaller extends Emitter { public static Events = { error : Symbol ( ) , logs : Symbol ( ) , success : Symbol ( ) , } ; constructor ( ) { super ( ) ; this . _bindNPM ( ) ; } public destroy ( ) { this . _unbindNPM ( ) ; } public install ( folder , config = { } , packages = { } , node = false ) { return new Promise ( ( resolve , reject ) => { this . _getPackageFile ( Path . normalize ( folder ) ) . then ( ( description ) => { if ( typeof description . dependencies !== 'object' || description . dependencies === null || Object . keys ( description . dependencies ) . length === 0 ) { if ( ! node ) { this . emit ( NPMInstaller . Events . logs , `Nothing to install for "${ folder }".` ) ; return resolve ( ) ; } description . dependencies = { } ; } Object . keys ( packages ) . forEach ( ( name ) => { if ( description . dependencies [ name ] === void 0 ) { description . dependencies [ name ] = packages [ name ] ; } } ) ; const brokenDependencies = [ ] ; const dependencies = Object . keys ( description . dependencies ) . map ( ( name ) => { if ( typeof description . dependencies [ name ] !== 'string' || description . dependencies [ name ] . trim ( ) === '' ) { brokenDependencies . push ( name ) ; return '' ; } return `${ name }@${ description . dependencies [ name ] }` ; } ) ; if ( brokenDependencies . length > 0 ) { return this . _reject ( new Error ( `Fail to install because next dependencies are defined incorrectly: "${ brokenDependencies . join ( ', ' ) }"` ) , reject ) ; } const originalCWD = process . cwd ( ) ; process . env . PATH += `:${ Path . normalize ( folder + '/node_modules/.bin' ) }` ; this . emit ( NPMInstaller . Events . logs , `Change folder to project folder: "${ folder }".` ) ; process . chdir ( folder ) ; this . emit ( NPMInstaller . Events . logs , `Loading NPM.` ) ; NPM . load ( Object . assign ( config , this . _getNPMConfiguration ( folder ) ) , ( error ) => { if ( error instanceof Error ) { this . emit ( NPMInstaller . Events . logs , `Fail to load NPM due error: "${ error . message }".` ) ; return this . _reject ( error , reject ) ; } this . emit ( NPMInstaller . Events . logs , `NPM is load successfully.` ) ; const nodePackages = [ `node@${ process . versions . node }` , `node-gyp` , ] ; ( new Promise ( ( resolveNodeInstallation , rejectNodeInstallation ) => { if ( node ) { this . _installPackages ( nodePackages ) . then ( ( results ) => { this . emit ( NPMInstaller . Events . logs , `Installation of node is complited.` ) ; resolveNodeInstallation ( results ) ; } ) . catch ( rejectNodeInstallation ) ; } else { return resolveNodeInstallation ( ) ; } } ) ) . then ( ( ) => { if ( dependencies . length === 0 ) { this . emit ( NPMInstaller . Events . logs , `Installation of "${ folder }" is complited.` ) ; return resolve ( ) ; } this . _installPackages ( dependencies ) . then ( ( ) => { this . emit ( NPMInstaller . Events . logs , `Installation of "${ folder }" is complited.` ) ; resolve ( ) ; } ) . catch ( ( errorAppInstallation ) => { this . _reject ( errorAppInstallation , reject ) ; } ) ; } ) . catch ( ( nodeErrorInstallation ) => { this . emit ( NPMInstaller . Events . logs , `Fail to install packages (${ nodePackages . join ( ', ' ) }) due error: "${ nodeErrorInstallation . message }".` ) ; return this . _reject ( nodeErrorInstallation , reject ) ; } ) ; } ) ; } ) . catch ( ( error ) => { return reject ( error ) ; } ) ; } ) ; } private _installPackages ( packages ) { return new Promise ( ( resolve , reject ) => { NPM . commands . install ( packages , ( error , ... results ) => { if ( error instanceof Error ) { return reject ( error ) ; } resolve ( results ) ; } ) ; } ) ; } private _bindNPM ( ) { NPM . on ( 'log' , ( message ) => { this . emit ( NPMInstaller . Events . logs , message ) ; } ) ; NPM . on ( 'error' , ( error ) => { this . emit ( NPMInstaller . Events . error , error ) ; } ) ; } private _unbindNPM ( ) { NPM . removeAllListeners ( 'log' ) ; NPM . removeAllListeners ( 'error' ) ; } private _getNPMConfiguration ( projectFolder ) { return { 'bin-links' : true , 'prefix' : projectFolder , 'scripts-prepend-node-path' : true , 'verbose' : true , } ; } private _getPackageFile ( packageFile ) { return new Promise ( ( resolve , reject ) => { import ( Path . normalize ( packageFile + "/package.json" ) ) . then ( ( description ) => { if ( typeof description !== 'object' || description === null ) { return this . _reject ( new Error ( `Fail to find "package.json" file.` ) , reject ) ; } if ( typeof description . name !== 'string' ) { return this . _reject ( new Error ( `Incorrect format of "package.json" file.` ) , reject ) ; } resolve ( description ) ; } ) . catch ( ( error ) => { return this . _reject ( error , reject ) ; } ) ; } ) ; } private _reject ( error , reject ) { this . emit ( NPMInstaller . Events . logs , error . message ) ; this . emit ( NPMInstaller . Events . error , error ) ; return reject ( error ) ; } }