1
- import Future = require( "fibers/future" ) ;
2
- import * as npm from "npm" ;
1
+ import * as path from "path" ;
3
2
4
3
interface INpmOpts {
5
4
config ?: any ;
@@ -8,139 +7,109 @@ interface INpmOpts {
8
7
}
9
8
10
9
export class NodePackageManager implements INodePackageManager {
11
- constructor ( private $childProcess : IChildProcess ,
10
+ constructor ( private $fs : IFileSystem ,
11
+ private $errors : IErrors ,
12
+ private $childProcess : IChildProcess ,
12
13
private $logger : ILogger ,
13
14
private $options : IOptions ) { }
14
15
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 ;
16
+ public install ( packageName : string , pathToSave : string , config ?: any ) : any {
17
+ if ( this . $options . disableNpmInstall ) {
18
+ return ;
37
19
}
38
- }
20
+ if ( this . $options . ignoreScripts ) {
21
+ config = config || { } ;
22
+ config [ "ignore-scripts" ] = true ;
23
+ }
24
+
25
+ try {
26
+ let subDirsBefore :Array < string > = this . $fs . exists ( path . join ( pathToSave , "node_modules" ) ) . wait ( ) ? this . $fs . readDirectory ( path . join ( pathToSave , "node_modules" ) ) . wait ( ) : [ ] ;
39
27
40
- public install ( packageName : string , pathToSave : string , config ?: any ) : IFuture < any > {
41
- return ( ( ) => {
42
- if ( this . $options . disableNpmInstall ) {
43
- return ;
28
+ let flags = this . getFlagsString ( config , true ) ;
29
+ let params = [ "install" , packageName ] ;
30
+ for ( let index in flags ) {
31
+ params . push ( flags [ index ] ) ;
44
32
}
45
- if ( this . $options . ignoreScripts ) {
46
- config = config || { } ;
47
- config [ "ignore-scripts" ] = true ;
33
+ let res = this . $childProcess . spawnFromEvent ( "npm" , params , "close" , { } , { throwError : false } ) . wait ( ) ;
34
+ if ( res . stderr ) {
35
+ throw { stderr : res . stderr } ;
48
36
}
49
37
50
- try {
51
- return this . loadAndExecute ( "install" , [ pathToSave , packageName ] , { config : config } ) . wait ( ) ;
52
- } catch ( err ) {
53
- if ( err . code === "EPEERINVALID" ) {
54
- // Not installed peer dependencies are treated by npm 2 as errors, but npm 3 treats them as warnings.
55
- // 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 /[email protected] 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 /[email protected] ': '2.1.0-beta.0',
63
- // '@angular/[email protected] ': '2.1.0-beta.0',
64
- // '@angular/[email protected] ': '2.1.0-beta.0',
65
- // '@angular/[email protected] ': '2.1.0-beta.0',
66
- // '@angular/[email protected] ': '2.1.0-beta.0',
67
- // '@angular/[email protected] ': '2.1.0-beta.0',
68
- // '@angular/[email protected] ': '2.1.0-beta.0',
69
- // '@angular/[email protected] ': '2.1.0-beta.0',
70
- // '@ngrx/[email protected] ': '^2.0.0',
71
- // '@ngrx/[email protected] ': '^2.0.0',
72
- // '[email protected] ': '~2.0.0' } }
73
- this . $logger . warn ( err . message ) ;
74
- this . $logger . trace ( "Required peerDependencies are: " , err . peersDepending ) ;
75
- } else {
76
- // All other errors should be handled by the caller code.
77
- throw err ;
78
- }
38
+ let subDirsAfter = this . $fs . readDirectory ( path . join ( pathToSave , "node_modules" ) ) . wait ( ) ;
39
+
40
+ /** This diff is done in case the installed pakcage is a URL address, a path to local directory or a .tgz file
41
+ * in these cases we don't have the package name and we can't rely on "npm install --json"" option
42
+ * 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)
43
+ * The solution is to traverse node_modules before and after install and get the name of the installed package,
44
+ * even if it's installed through local path or URL. If command installes more than one package, all package names are returned.
45
+ */
46
+ let diff = subDirsAfter . filter ( x => subDirsBefore . indexOf ( x ) === - 1 ) ;
47
+ if ( diff . length <= 0 && subDirsBefore . length !== subDirsAfter . length ) {
48
+ this . $errors . failWithoutHelp ( `Couldn't install package correctly` ) ;
49
+ }
50
+
51
+ return diff ;
52
+ } catch ( err ) {
53
+ if ( err . stderr && err . stderr . indexOf ( "EPEERINVALID" ) !== - 1 ) {
54
+ // Not installed peer dependencies are treated by npm 2 as errors, but npm 3 treats them as warnings.
55
+ // 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 /[email protected] 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 /[email protected] ': '2.1.0-beta.0',
63
+ // '@angular/[email protected] ': '2.1.0-beta.0',
64
+ // '@angular/[email protected] ': '2.1.0-beta.0',
65
+ // '@angular/[email protected] ': '2.1.0-beta.0',
66
+ // '@angular/[email protected] ': '2.1.0-beta.0',
67
+ // '@angular/[email protected] ': '2.1.0-beta.0',
68
+ // '@angular/[email protected] ': '2.1.0-beta.0',
69
+ // '@angular/[email protected] ': '2.1.0-beta.0',
70
+ // '@ngrx/[email protected] ': '^2.0.0',
71
+ // '@ngrx/[email protected] ': '^2.0.0',
72
+ // '[email protected] ': '~2.0.0' } }
73
+ this . $logger . warn ( err . stderr ) ;
74
+ } else {
75
+ // All other errors should be handled by the caller code.
76
+ throw err ;
79
77
}
80
- } ) . future < any > ( ) ( ) ;
78
+ }
81
79
}
82
80
83
81
public uninstall ( packageName : string , config ?: any , path ?: string ) : IFuture < any > {
84
- return this . loadAndExecute ( "uninstall" , [ [ packageName ] ] , { config, path } ) ;
82
+ let flags = this . getFlagsString ( config , false ) ;
83
+ return this . $childProcess . exec ( `npm uninstall ${ packageName } ${ flags } ` , { cwd : path } ) ;
85
84
}
86
85
87
86
public search ( filter : string [ ] , silent : boolean ) : IFuture < any > {
88
87
let args = ( < any [ ] > ( [ filter ] || [ ] ) ) . concat ( silent ) ;
89
- return this . loadAndExecute ( "search" , args ) ;
90
- }
91
-
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 } ) ;
88
+ return this . $childProcess . exec ( `npm search ${ args } ` ) ;
95
89
}
96
90
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" } ) ;
91
+ public view ( packageName : string , config : any ) : IFuture < any > {
92
+ let flags = this . getFlagsString ( config , false ) ;
93
+ let viewResult = this . $childProcess . exec ( `npm view ${ packageName } ${ flags } ` ) . wait ( ) ;
94
+ return JSON . parse ( viewResult ) ;
100
95
}
101
96
102
- public view ( packageName : string , propertyName : string ) : IFuture < any > {
103
- return this . loadAndExecute ( "view" , [ [ packageName , propertyName ] , [ false ] ] ) ;
104
- }
105
-
106
- public executeNpmCommand ( npmCommandName : string , currentWorkingDirectory : string ) : IFuture < any > {
107
- return this . $childProcess . exec ( npmCommandName , { cwd : currentWorkingDirectory } ) ;
108
- }
109
-
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 > ( ) ( ) ;
116
- }
117
-
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
- npm . prefix = oldNpmPath ;
124
- }
125
-
126
- if ( err ) {
127
- future . throw ( err ) ;
128
- } else {
129
- future . return ( data ) ;
97
+ private getFlagsString ( config : any , asArray : boolean ) : any {
98
+ let array :Array < string > = [ ] ;
99
+ for ( let flag in config ) {
100
+ if ( config [ flag ] ) {
101
+ if ( flag === "dist-tags" || flag === "versions" ) {
102
+ array . push ( ` ${ flag } ` )
103
+ continue ;
104
+ }
105
+ array . push ( `--${ flag } ` )
130
106
}
131
- } ;
132
- args . push ( callback ) ;
133
-
134
- if ( opts && opts . path ) {
135
- oldNpmPath = npm . prefix ;
136
- npm . prefix = opts . path ;
107
+ }
108
+ if ( asArray ) {
109
+ return array ;
137
110
}
138
111
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 ;
112
+ return array . join ( " " ) ;
144
113
}
145
114
}
146
115
$injector . register ( "npm" , NodePackageManager ) ;
0 commit comments