1- import Future = require( "fibers/future" ) ;
2- import * as npm from "npm" ;
1+ import * as path from "path" ;
32
43interface INpmOpts {
54 config ?: any ;
@@ -8,35 +7,13 @@ interface INpmOpts {
87}
98
109export class NodePackageManager implements INodePackageManager {
11- constructor ( private $childProcess : IChildProcess ,
10+ constructor ( private $fs : IFileSystem ,
11+ private $hostInfo : IHostInfo ,
12+ private $errors : IErrors ,
13+ private $childProcess : IChildProcess ,
1214 private $logger : ILogger ,
1315 private $options : IOptions ) { }
1416
15- public getCache ( ) : string {
16- return npm . cache ;
17- }
18-
19- public load ( config ?: any ) : IFuture < void > {
20- if ( npm . config . loaded ) {
21- let data = npm . config . sources . cli . data ;
22- Object . keys ( data ) . forEach ( k => delete data [ k ] ) ;
23- if ( config ) {
24- _ . assign ( data , config ) ;
25- }
26- return Future . fromResult ( ) ;
27- } else {
28- let future = new Future < void > ( ) ;
29- npm . load ( config , ( err : Error ) => {
30- if ( err ) {
31- future . throw ( err ) ;
32- } else {
33- future . return ( ) ;
34- }
35- } ) ;
36- return future ;
37- }
38- }
39-
4017 public install ( packageName : string , pathToSave : string , config ?: any ) : IFuture < any > {
4118 return ( ( ) => {
4219 if ( this . $options . disableNpmInstall ) {
@@ -47,100 +24,105 @@ export class NodePackageManager implements INodePackageManager {
4724 config [ "ignore-scripts" ] = true ;
4825 }
4926
27+ let jsonContentBefore = this . $fs . readJson ( path . join ( pathToSave , "package.json" ) ) . wait ( ) ;
28+ // let dependenciesBefore: Array<string> = [];
29+ let dependenciesBefore = _ . keys ( jsonContentBefore . dependencies ) . concat ( _ . keys ( jsonContentBefore . devDependencies ) ) ;
30+
31+ let flags = this . getFlagsString ( config , true ) ;
32+ let params = [ "install" ] ;
33+ if ( packageName !== pathToSave ) {
34+ params . push ( packageName ) ; //because npm install ${pwd} on mac tries to install itself as a dependency (windows and linux have no such issues)
35+ }
36+ params = params . concat ( flags ) ;
5037 try {
51- return this . loadAndExecute ( "install" , [ pathToSave , packageName ] , { config : config } ) . wait ( ) ;
38+ this . $childProcess . spawnFromEvent ( this . getNpmExecutableName ( ) , params , "close" , { cwd : pathToSave } ) . wait ( ) ;
5239 } catch ( err ) {
53- if ( err . code === "EPEERINVALID" ) {
40+ if ( err . message && err . message . indexOf ( "EPEERINVALID" ) !== - 1 ) {
5441 // Not installed peer dependencies are treated by npm 2 as errors, but npm 3 treats them as warnings.
5542 // We'll show them as warnings and let the user install them in case they are needed.
56- // The strucutre of the error object in such case is:
57- // { [Error: The package @angular /core@2.1.0-beta.0 does not satisfy its siblings' peerDependencies requirements!]
58- // code: 'EPEERINVALID',
59- // packageName: '@angular/core',
60- // packageVersion: '2.1.0-beta.0',
61- // peersDepending:
62- // { '@angular /common@2.1.0-beta.0': '2.1.0-beta.0',
63- // '@angular/compiler@2.1.0-beta.0': '2.1.0-beta.0',
64- // '@angular/forms@2.1.0-beta.0': '2.1.0-beta.0',
65- // '@angular/http@2.1.0-beta.0': '2.1.0-beta.0',
66- // '@angular/platform-browser@2.1.0-beta.0': '2.1.0-beta.0',
67- // '@angular/platform-browser-dynamic@2.1.0-beta.0': '2.1.0-beta.0',
68- // '@angular/platform-server@2.1.0-beta.0': '2.1.0-beta.0',
69- // '@angular/router@3.1.0-beta.0': '2.1.0-beta.0',
70- // '@ngrx/effects@2.0.0': '^2.0.0',
71- // '@ngrx/store@2.2.1': '^2.0.0',
72- // 'ng2-translate@2.5.0': '~2.0.0' } }
7343 this . $logger . warn ( err . message ) ;
74- this . $logger . trace ( "Required peerDependencies are: " , err . peersDepending ) ;
7544 } else {
7645 // All other errors should be handled by the caller code.
7746 throw err ;
7847 }
7948 }
49+
50+ let jsonContentAfter = this . $fs . readJson ( path . join ( pathToSave , "package.json" ) ) . wait ( ) ;
51+ let dependenciesAfter = _ . keys ( jsonContentAfter . dependencies ) . concat ( _ . keys ( jsonContentAfter . devDependencies ) ) ;
52+
53+ /** This diff is done in case the installed pakcage is a URL address, a path to local directory or a .tgz file
54+ * in these cases we don't have the package name and we can't rely on "npm install --json"" option
55+ * to get the project name because we have to parse the output from the stdout and we have no controll over it (so other messages may be mangled in stdout)
56+ * The solution is to compare package.json project dependencies before and after install and get the name of the installed package,
57+ * even if it's installed through local path or URL. If command installes more than one package, only the package originally installed is returned.
58+ */
59+ let dependencyDiff = _ ( jsonContentAfter . dependencies )
60+ . omitBy ( ( val : string , key : string ) => jsonContentBefore && jsonContentBefore . dependencies && jsonContentBefore . dependencies [ key ] && jsonContentBefore . dependencies [ key ] === val )
61+ . keys ( )
62+ . value ( ) ;
63+
64+ let devDependencyDiff = _ ( jsonContentAfter . devDependencies )
65+ . omitBy ( ( val : string , key : string ) => jsonContentBefore && jsonContentBefore . devDependencies && jsonContentBefore . devDependencies [ key ] && jsonContentBefore . devDependencies [ key ] === val )
66+ . keys ( )
67+ . value ( ) ;
68+
69+ let diff = dependencyDiff . concat ( devDependencyDiff ) ;
70+
71+ if ( diff . length <= 0 && dependenciesBefore . length === dependenciesAfter . length && packageName !== pathToSave ) {
72+ this . $errors . failWithoutHelp ( `The plugin ${ packageName } is already installed` ) ;
73+ }
74+ if ( diff . length <= 0 && dependenciesBefore . length !== dependenciesAfter . length ) {
75+ this . $errors . failWithoutHelp ( `Couldn't install package correctly` ) ;
76+ }
77+
78+ return diff ;
8079 } ) . future < any > ( ) ( ) ;
8180 }
8281
8382 public uninstall ( packageName : string , config ?: any , path ?: string ) : IFuture < any > {
84- return this . loadAndExecute ( "uninstall" , [ [ packageName ] ] , { config, path } ) ;
83+ let flags = this . getFlagsString ( config , false ) ;
84+ return this . $childProcess . exec ( `npm uninstall ${ packageName } ${ flags } ` , { cwd : path } ) ;
8585 }
8686
87- public search ( filter : string [ ] , silent : boolean ) : IFuture < any > {
88- let args = ( < any [ ] > ( [ filter ] || [ ] ) ) . concat ( silent ) ;
89- return this . loadAndExecute ( " search" , args ) ;
87+ public search ( filter : string [ ] , config : any ) : IFuture < any > {
88+ let args = ( < any [ ] > ( [ filter ] || [ ] ) ) . concat ( config . silent ) ;
89+ return this . $childProcess . exec ( `npm search ${ args . join ( " " ) } ` ) ;
9090 }
9191
92- public cache ( packageName : string , version : string , config ?: any ) : IFuture < IDependencyData > {
93- // function cache (pkg, ver, where, scrub, cb)
94- return this . loadAndExecute ( "cache" , [ packageName , version , undefined , false ] , { subCommandName : "add" , config : config } ) ;
95- }
96-
97- public cacheUnpack ( packageName : string , version : string , unpackTarget ?: string ) : IFuture < void > {
98- // function unpack (pkg, ver, unpackTarget, dMode, fMode, uid, gid, cb)
99- return this . loadAndExecute ( "cache" , [ packageName , version , unpackTarget , null , null , null , null ] , { subCommandName : "unpack" } ) ;
92+ public view ( packageName : string , config : any ) : IFuture < any > {
93+ return ( ( ) => {
94+ let flags = this . getFlagsString ( config , false ) ;
95+ let viewResult = this . $childProcess . exec ( `npm view ${ packageName } ${ flags } ` ) . wait ( ) ;
96+ return JSON . parse ( viewResult ) ;
97+ } ) . future < any > ( ) ( ) ;
10098 }
10199
102- public view ( packageName : string , propertyName : string ) : IFuture < any > {
103- return this . loadAndExecute ( "view" , [ [ packageName , propertyName ] , [ false ] ] ) ;
104- }
100+ private getNpmExecutableName ( ) : string {
101+ let npmExecutableName = "npm" ;
105102
106- public executeNpmCommand ( npmCommandName : string , currentWorkingDirectory : string ) : IFuture < any > {
107- return this . $childProcess . exec ( npmCommandName , { cwd : currentWorkingDirectory } ) ;
108- }
103+ if ( this . $hostInfo . isWindows ) {
104+ npmExecutableName += ".cmd" ;
105+ }
109106
110- private loadAndExecute ( commandName : string , args : any [ ] , opts ?: INpmOpts ) : IFuture < any > {
111- return ( ( ) => {
112- opts = opts || { } ;
113- this . load ( opts . config ) . wait ( ) ;
114- return this . executeCore ( commandName , args , opts ) . wait ( ) ;
115- } ) . future < any > ( ) ( ) ;
107+ return npmExecutableName ;
116108 }
117109
118- private executeCore ( commandName : string , args : any [ ] , opts ?: INpmOpts ) : IFuture < any > {
119- let future = new Future < any > ( ) ;
120- let oldNpmPath : string = undefined ;
121- let callback = ( err : Error , data : any ) => {
122- if ( oldNpmPath ) {
123- ( < any > npm ) . prefix = oldNpmPath ;
124- }
125-
126- if ( err ) {
127- future . throw ( err ) ;
128- } else {
129- future . return ( data ) ;
110+ private getFlagsString ( config : any , asArray : boolean ) : any {
111+ let array :Array < string > = [ ] ;
112+ for ( let flag in config ) {
113+ if ( config [ flag ] ) {
114+ if ( flag === "dist-tags" || flag === "versions" ) {
115+ array . push ( ` ${ flag } ` ) ;
116+ continue ;
117+ }
118+ array . push ( `--${ flag } ` ) ;
130119 }
131- } ;
132- args . push ( callback ) ;
133-
134- if ( opts && opts . path ) {
135- oldNpmPath = npm . prefix ;
136- ( < any > npm ) . prefix = opts . path ;
120+ }
121+ if ( asArray ) {
122+ return array ;
137123 }
138124
139- let subCommandName : string = opts . subCommandName ;
140- let command = subCommandName ? npm . commands [ commandName ] [ subCommandName ] : npm . commands [ commandName ] ;
141- command . apply ( this , args ) ;
142-
143- return future ;
125+ return array . join ( " " ) ;
144126 }
145127}
146128$injector . register ( "npm" , NodePackageManager ) ;
0 commit comments