6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
import { logging } from '@angular-devkit/core' ;
9
- import { exec } from 'child_process' ;
10
9
import { readFileSync } from 'fs' ;
11
- import { Observable , ReplaySubject , concat , of } from 'rxjs' ;
12
- import {
13
- catchError ,
14
- concatMap ,
15
- defaultIfEmpty ,
16
- filter ,
17
- first ,
18
- map ,
19
- shareReplay ,
20
- toArray ,
21
- } from 'rxjs/operators' ;
22
- import * as url from 'url' ;
10
+ import { Observable , from } from 'rxjs' ;
11
+ import { shareReplay } from 'rxjs/operators' ;
23
12
import { NpmRepositoryPackageJson } from './npm-package-json' ;
24
13
25
- const RegistryClient = require ( 'npm-registry-client ' ) ;
14
+ const pacote = require ( 'pacote ' ) ;
26
15
27
16
const npmPackageJsonCache = new Map < string , Observable < NpmRepositoryPackageJson > > ( ) ;
28
- const npmConfigOptionCache = new Map < string , Observable < string | undefined > > ( ) ;
29
17
30
-
31
- function _readNpmRc ( ) : Observable < { [ key : string ] : string } > {
32
- return new Observable < { [ key : string ] : string } > ( subject => {
33
- // TODO: have a way to read options without using fs directly.
34
- const path = require ( 'path' ) ;
35
- const fs = require ( 'fs' ) ;
36
- const perProjectNpmrc = path . resolve ( '.npmrc' ) ;
37
-
38
- let npmrc = '' ;
39
-
40
- if ( fs . existsSync ( perProjectNpmrc ) ) {
41
- npmrc = fs . readFileSync ( perProjectNpmrc ) . toString ( 'utf-8' ) ;
42
- } else {
43
- if ( process . platform === 'win32' ) {
44
- if ( process . env . LOCALAPPDATA ) {
45
- npmrc = fs . readFileSync ( path . join ( process . env . LOCALAPPDATA , '.npmrc' ) ) . toString ( 'utf-8' ) ;
46
- }
47
- } else {
48
- if ( process . env . HOME ) {
49
- npmrc = fs . readFileSync ( path . join ( process . env . HOME , '.npmrc' ) ) . toString ( 'utf-8' ) ;
50
- }
51
- }
52
- }
53
-
54
- const allOptionsArr = npmrc . split ( / \r ? \n / ) . map ( x => x . trim ( ) ) ;
55
- const allOptions : { [ key : string ] : string } = { } ;
56
-
57
- allOptionsArr . forEach ( x => {
58
- const [ key , ...value ] = x . split ( '=' ) ;
59
- allOptions [ key . trim ( ) ] = value . join ( '=' ) . trim ( ) ;
60
- } ) ;
61
-
62
- subject . next ( allOptions ) ;
63
- subject . complete ( ) ;
64
- } ) . pipe (
65
- catchError ( ( ) => of ( { } ) ) ,
66
- shareReplay ( ) ,
67
- ) ;
18
+ let npmrc : { [ key : string ] : string } ;
19
+ try {
20
+ npmrc = _readNpmRc ( ) ;
21
+ } catch {
22
+ npmrc = { } ;
68
23
}
69
24
70
25
71
- function getOptionFromNpmRc ( option : string ) : Observable < string | undefined > {
72
- return _readNpmRc ( ) . pipe (
73
- map ( options => options [ option ] ) ,
74
- ) ;
75
- }
26
+ function _readNpmRc ( ) : { [ key : string ] : string } {
27
+ // TODO: have a way to read options without using fs directly.
28
+ const path = require ( 'path' ) ;
29
+ const fs = require ( 'fs' ) ;
30
+ const perProjectNpmrc = path . resolve ( '.npmrc' ) ;
76
31
77
- function getOptionFromNpmCli ( option : string ) : Observable < string | undefined > {
78
- return new Observable < string | undefined > ( subject => {
79
- exec ( `npm get ${ option } ` , ( error , data ) => {
80
- if ( error ) {
81
- throw error ;
82
- } else {
83
- data = data . trim ( ) ;
84
- if ( ! data || data === 'undefined' || data === 'null' ) {
85
- subject . next ( ) ;
86
- } else {
87
- subject . next ( data ) ;
88
- }
89
- }
32
+ const configs : string [ ] = [ ] ;
90
33
91
- subject . complete ( ) ;
92
- } ) ;
93
- } ) . pipe (
94
- catchError ( ( ) => of ( undefined ) ) ,
95
- shareReplay ( ) ,
96
- ) ;
97
- }
98
-
99
- function getNpmConfigOption (
100
- option : string ,
101
- scope ?: string ,
102
- tryWithoutScope ?: boolean ,
103
- ) : Observable < string | undefined > {
104
- if ( scope && tryWithoutScope ) {
105
- return concat (
106
- getNpmConfigOption ( option , scope ) ,
107
- getNpmConfigOption ( option ) ,
108
- ) . pipe (
109
- filter ( result => ! ! result ) ,
110
- defaultIfEmpty ( ) ,
111
- first ( ) ,
112
- ) ;
34
+ if ( process . platform === 'win32' ) {
35
+ if ( process . env . LOCALAPPDATA ) {
36
+ configs . push ( fs . readFileSync ( path . join ( process . env . LOCALAPPDATA , '.npmrc' ) , 'utf8' ) ) ;
37
+ }
38
+ } else {
39
+ if ( process . env . HOME ) {
40
+ configs . push ( fs . readFileSync ( path . join ( process . env . HOME , '.npmrc' ) , 'utf8' ) ) ;
41
+ }
113
42
}
114
43
115
- const fullOption = `${ scope ? scope + ':' : '' } ${ option } ` ;
116
-
117
- let value = npmConfigOptionCache . get ( fullOption ) ;
118
- if ( value ) {
119
- return value ;
44
+ if ( fs . existsSync ( perProjectNpmrc ) ) {
45
+ configs . push ( fs . readFileSync ( perProjectNpmrc , 'utf8' ) ) ;
120
46
}
121
47
122
- value = option . startsWith ( '_' )
123
- ? getOptionFromNpmRc ( fullOption )
124
- : getOptionFromNpmCli ( fullOption ) ;
125
-
126
- npmConfigOptionCache . set ( fullOption , value ) ;
127
-
128
- return value ;
129
- }
130
-
131
- function getNpmClientSslOptions ( strictSsl ?: string , cafile ?: string ) {
132
- const sslOptions : { strict ?: boolean , ca ?: Buffer } = { } ;
48
+ const allOptions : { [ key : string ] : string } = { } ;
49
+ for ( const config of configs ) {
50
+ const allOptionsArr = config . split ( / \r ? \n / ) . map ( x => x . trim ( ) ) ;
133
51
134
- if ( strictSsl === 'false' ) {
135
- sslOptions . strict = false ;
136
- } else if ( strictSsl === 'true' ) {
137
- sslOptions . strict = true ;
138
- }
52
+ allOptionsArr . forEach ( x => {
53
+ const [ key , ...value ] = x . split ( '=' ) ;
54
+ const fullValue = value . join ( '=' ) . trim ( ) ;
55
+ if ( key && fullValue && fullValue !== 'null' ) {
56
+ allOptions [ key . trim ( ) ] = fullValue ;
57
+ }
58
+ } ) ;
139
59
140
- if ( cafile ) {
141
- sslOptions . ca = readFileSync ( cafile ) ;
60
+ if ( allOptions . cafile ) {
61
+ const cafile = allOptions . cafile ;
62
+ delete allOptions . cafile ;
63
+ try {
64
+ allOptions . ca = readFileSync ( cafile , 'utf8' ) ;
65
+ allOptions . ca = allOptions . ca . replace ( / \r ? \n / , '\\n' ) ;
66
+ } catch { }
67
+ }
142
68
}
143
69
144
- return sslOptions ;
70
+ return allOptions ;
145
71
}
146
72
147
73
/**
@@ -155,144 +81,24 @@ function getNpmClientSslOptions(strictSsl?: string, cafile?: string) {
155
81
export function getNpmPackageJson (
156
82
packageName : string ,
157
83
registryUrl : string | undefined ,
158
- logger : logging . LoggerApi ,
84
+ _logger : logging . LoggerApi ,
159
85
) : Observable < Partial < NpmRepositoryPackageJson > > {
160
- const scope = packageName . startsWith ( '@' ) ? packageName . split ( '/' ) [ 0 ] : undefined ;
161
-
162
- return (
163
- registryUrl ? of ( registryUrl ) : getNpmConfigOption ( 'registry' , scope , true )
164
- ) . pipe (
165
- map ( partialUrl => {
166
- if ( ! partialUrl ) {
167
- partialUrl = 'https://registry.npmjs.org/' ;
168
- }
169
- const partial = url . parse ( partialUrl ) ;
170
- let fullUrl = new url . URL ( `http://${ partial . host } /${ packageName . replace ( / \/ / g, '%2F' ) } ` ) ;
171
- try {
172
- const registry = new url . URL ( partialUrl ) ;
173
- registry . pathname = ( registry . pathname || '' )
174
- . replace ( / \/ ? $ / , '/' + packageName . replace ( / \/ / g, '%2F' ) ) ;
175
- fullUrl = new url . URL ( url . format ( registry ) ) ;
176
- } catch { }
177
-
178
- logger . debug (
179
- `Getting package.json from '${ packageName } ' (url: ${ JSON . stringify ( fullUrl ) } )...` ,
180
- ) ;
181
-
182
- return fullUrl ;
183
- } ) ,
184
- concatMap ( fullUrl => {
185
- let maybeRequest = npmPackageJsonCache . get ( fullUrl . toString ( ) ) ;
186
- if ( maybeRequest ) {
187
- return maybeRequest ;
188
- }
189
-
190
- const registryKey = `//${ fullUrl . host } /` ;
191
-
192
- return concat (
193
- getNpmConfigOption ( 'proxy' ) ,
194
- getNpmConfigOption ( 'https-proxy' ) ,
195
- getNpmConfigOption ( 'strict-ssl' ) ,
196
- getNpmConfigOption ( 'cafile' ) ,
197
- getNpmConfigOption ( '_auth' ) ,
198
- getNpmConfigOption ( 'user-agent' ) ,
199
- getNpmConfigOption ( '_authToken' , registryKey ) ,
200
- getNpmConfigOption ( 'username' , registryKey , true ) ,
201
- getNpmConfigOption ( 'password' , registryKey , true ) ,
202
- getNpmConfigOption ( 'email' , registryKey , true ) ,
203
- getNpmConfigOption ( 'always-auth' , registryKey , true ) ,
204
- ) . pipe (
205
- toArray ( ) ,
206
- concatMap ( options => {
207
- const [
208
- http ,
209
- https ,
210
- strictSsl ,
211
- cafile ,
212
- token ,
213
- userAgent ,
214
- authToken ,
215
- username ,
216
- password ,
217
- email ,
218
- alwaysAuth ,
219
- ] = options ;
220
-
221
- const subject = new ReplaySubject < NpmRepositoryPackageJson > ( 1 ) ;
222
-
223
- const sslOptions = getNpmClientSslOptions ( strictSsl , cafile ) ;
224
-
225
- const auth : {
226
- token ?: string ,
227
- alwaysAuth ?: boolean ;
228
- username ?: string ;
229
- password ?: string ;
230
- email ?: string ;
231
- } = { } ;
232
-
233
- if ( alwaysAuth !== undefined ) {
234
- auth . alwaysAuth = alwaysAuth === 'false' ? false : ! ! alwaysAuth ;
235
- }
236
-
237
- if ( email ) {
238
- auth . email = email ;
239
- }
240
-
241
- if ( authToken ) {
242
- auth . token = authToken ;
243
- } else if ( token ) {
244
- try {
245
- // attempt to parse "username:password" from base64 token
246
- // to enable Artifactory / Nexus-like repositories support
247
- const delimiter = ':' ;
248
- const parsedToken = Buffer . from ( token , 'base64' ) . toString ( 'ascii' ) ;
249
- const [ extractedUsername , ...passwordArr ] = parsedToken . split ( delimiter ) ;
250
- const extractedPassword = passwordArr . join ( delimiter ) ;
251
-
252
- if ( extractedUsername && extractedPassword ) {
253
- auth . username = extractedUsername ;
254
- auth . password = extractedPassword ;
255
- } else {
256
- throw new Error ( 'Unable to extract username and password from _auth token' ) ;
257
- }
258
- } catch ( ex ) {
259
- auth . token = token ;
260
- }
261
- } else if ( username ) {
262
- auth . username = username ;
263
- auth . password = password ;
264
- }
265
-
266
- const client = new RegistryClient ( {
267
- proxy : { http, https } ,
268
- ssl : sslOptions ,
269
- ...( userAgent && { userAgent : userAgent } ) ,
270
- } ) ;
271
- client . log . level = 'silent' ;
272
- const params = {
273
- timeout : 30000 ,
274
- auth,
275
- } ;
276
-
277
- client . get (
278
- fullUrl . toString ( ) ,
279
- params ,
280
- ( error : object , data : NpmRepositoryPackageJson ) => {
281
- if ( error ) {
282
- subject . error ( error ) ;
283
- }
284
-
285
- subject . next ( data ) ;
286
- subject . complete ( ) ;
287
- } ) ;
288
-
289
- maybeRequest = subject . asObservable ( ) ;
290
- npmPackageJsonCache . set ( fullUrl . toString ( ) , maybeRequest ) ;
86
+ const cachedResponse = npmPackageJsonCache . get ( packageName ) ;
87
+ if ( cachedResponse ) {
88
+ return cachedResponse ;
89
+ }
291
90
292
- return maybeRequest ;
293
- } ) ,
294
- ) ;
295
- } ) ,
91
+ const resultPromise = pacote . packument (
92
+ packageName ,
93
+ {
94
+ 'full-metadata' : true ,
95
+ ...npmrc ,
96
+ registry : registryUrl ,
97
+ } ,
296
98
) ;
297
99
100
+ const response = from < NpmRepositoryPackageJson > ( resultPromise ) . pipe ( shareReplay ( ) ) ;
101
+ npmPackageJsonCache . set ( packageName , response ) ;
102
+
103
+ return response ;
298
104
}
0 commit comments