1
1
import "../common/logger" ;
2
2
import fs from "fs/promises" ;
3
3
import { spawn } from "child_process" ;
4
- import { response , Request as ServerRequest , Response as ServerResponse } from "express" ;
4
+ import { Request as ServerRequest , Response as ServerResponse } from "express" ;
5
5
import { NpmRegistryService , NpmRegistryConfigEntry , NpmRegistryConfig } from "../services/npmRegistry" ;
6
6
7
7
8
+ type RequestConfig = {
9
+ workspaceId : string ;
10
+ npmRegistryConfig : NpmRegistryConfig ;
11
+ }
12
+
13
+ type InnerRequestConfig = {
14
+ workspaceId ?: string ;
15
+ registry : NpmRegistryConfigEntry ;
16
+ }
17
+
8
18
type PackagesVersionInfo = {
9
19
"dist-tags" : {
10
20
latest : string
@@ -23,24 +33,24 @@ class PackageProcessingQueue {
23
33
public static readonly promiseRegistry : { [ packageId : string ] : Promise < void > } = { } ;
24
34
public static readonly resolveRegistry : { [ packageId : string ] :( ) => void } = { } ;
25
35
26
- public static add ( packageId : string ) {
36
+ public static add ( packageId : string ) : void {
27
37
PackageProcessingQueue . promiseRegistry [ packageId ] = new Promise < void > ( ( resolve ) => {
28
38
PackageProcessingQueue . resolveRegistry [ packageId ] = resolve ;
29
39
} ) ;
30
40
}
31
41
32
- public static has ( packageId : string ) {
42
+ public static has ( packageId : string ) : boolean {
33
43
return ! ! PackageProcessingQueue . promiseRegistry [ packageId ] ;
34
44
}
35
45
36
- public static wait ( packageId : string ) {
46
+ public static wait ( packageId : string ) : Promise < void > {
37
47
if ( ! PackageProcessingQueue . has ( packageId ) ) {
38
48
return Promise . resolve ( ) ;
39
49
}
40
50
return PackageProcessingQueue . promiseRegistry [ packageId ] ;
41
51
}
42
52
43
- public static resolve ( packageId : string ) {
53
+ public static resolve ( packageId : string ) : void {
44
54
if ( ! PackageProcessingQueue . has ( packageId ) ) {
45
55
return ;
46
56
}
@@ -78,10 +88,18 @@ export async function fetchRegistryWithConfig(request: ServerRequest, response:
78
88
return response . status ( 400 ) . send ( `Invalid package path: ${ path } ` ) ;
79
89
}
80
90
81
- const registryConfig : NpmRegistryConfig = request . body ;
82
- const config = NpmRegistryService . getRegistryEntryForPackageWithConfig ( pathPackageInfo . packageId , registryConfig ) ;
91
+ if ( ! request . body . workspaceId && ! request . body . npmRegistryConfig ) {
92
+ return response . status ( 400 ) . send ( "Missing workspaceId and/or npmRegistryConfig" ) ;
93
+ }
94
+
95
+ const { npmRegistryConfig} : RequestConfig = request . body ;
96
+
97
+ const registry = NpmRegistryService . getRegistryEntryForPackageWithConfig ( pathPackageInfo . packageId , npmRegistryConfig ) ;
83
98
84
- const registryResponse = await fetchFromRegistry ( path , config ) ;
99
+ const registryResponse = await fetchFromRegistry ( path , registry ) ;
100
+ if ( ! registryResponse . ok ) {
101
+ return response . status ( registryResponse . status ) . send ( await registryResponse . text ( ) ) ;
102
+ }
85
103
response . json ( await registryResponse . json ( ) ) ;
86
104
} catch ( error ) {
87
105
logger . error ( "Error fetching registry" , error ) ;
@@ -99,8 +117,11 @@ export async function fetchRegistry(request: ServerRequest, response: ServerResp
99
117
return response . status ( 400 ) . send ( `Invalid package path: ${ path } ` ) ;
100
118
}
101
119
102
- const config = NpmRegistryService . getInstance ( ) . getRegistryEntryForPackage ( pathPackageInfo . packageId ) ;
103
- const registryResponse = await fetchFromRegistry ( path , config ) ;
120
+ const registry = NpmRegistryService . getInstance ( ) . getRegistryEntryForPackage ( pathPackageInfo . packageId ) ;
121
+ const registryResponse = await fetchFromRegistry ( path , registry ) ;
122
+ if ( ! registryResponse . ok ) {
123
+ return response . status ( registryResponse . status ) . send ( await registryResponse . text ( ) ) ;
124
+ }
104
125
response . json ( await registryResponse . json ( ) ) ;
105
126
} catch ( error ) {
106
127
logger . error ( "Error fetching registry" , error ) ;
@@ -124,10 +145,15 @@ export async function fetchPackageFileWithConfig(request: ServerRequest, respons
124
145
return response . status ( 400 ) . send ( `Invalid package path: ${ path } ` ) ;
125
146
}
126
147
127
- const registryConfig : NpmRegistryConfig = request . body ;
128
- const config = NpmRegistryService . getRegistryEntryForPackageWithConfig ( pathPackageInfo . packageId , registryConfig ) ;
148
+ if ( ! request . body . workspaceId && ! request . body . npmRegistryConfig ) {
149
+ return response . status ( 400 ) . send ( "Missing workspaceId and/or npmRegistryConfig" ) ;
150
+ }
129
151
130
- fetchPackageFileInner ( request , response , config ) ;
152
+ const { workspaceId, npmRegistryConfig} : RequestConfig = request . body ;
153
+ const registryConfig : NpmRegistryConfig = npmRegistryConfig ;
154
+ const registry = NpmRegistryService . getRegistryEntryForPackageWithConfig ( pathPackageInfo . packageId , registryConfig ) ;
155
+
156
+ fetchPackageFileInner ( request , response , { workspaceId, registry} ) ;
131
157
}
132
158
133
159
export async function fetchPackageFile ( request : ServerRequest , response : ServerResponse ) {
@@ -139,12 +165,14 @@ export async function fetchPackageFile(request: ServerRequest, response: ServerR
139
165
return response . status ( 400 ) . send ( `Invalid package path: ${ path } ` ) ;
140
166
}
141
167
142
- const config = NpmRegistryService . getInstance ( ) . getRegistryEntryForPackage ( pathPackageInfo . packageId ) ;
143
- fetchPackageFileInner ( request , response , config ) ;
168
+ const registry = NpmRegistryService . getInstance ( ) . getRegistryEntryForPackage ( pathPackageInfo . packageId ) ;
169
+ fetchPackageFileInner ( request , response , { registry } ) ;
144
170
}
145
171
146
- async function fetchPackageFileInner ( request : ServerRequest , response : ServerResponse , config : NpmRegistryConfigEntry ) {
172
+ async function fetchPackageFileInner ( request : ServerRequest , response : ServerResponse , config : InnerRequestConfig ) {
147
173
try {
174
+ const { workspaceId, registry} = config
175
+ logger . info ( `Fetch file for workspaceId: ${ workspaceId } ` ) ;
148
176
const path = request . path . replace ( fetchPackageFileBasePath , "" ) ;
149
177
const pathPackageInfo = parsePackageInfoFromPath ( path ) ;
150
178
if ( ! pathPackageInfo ) {
@@ -157,7 +185,7 @@ async function fetchPackageFileInner(request: ServerRequest, response: ServerRes
157
185
158
186
let packageInfo : PackagesVersionInfo | null = null ;
159
187
if ( version === "latest" ) {
160
- const packageInfo : PackagesVersionInfo | null = await fetchPackageInfo ( packageId , config ) ;
188
+ const packageInfo : PackagesVersionInfo | null = await fetchPackageInfo ( packageId , registry ) ;
161
189
if ( packageInfo === null ) {
162
190
return response . status ( 404 ) . send ( "Not found" ) ;
163
191
}
@@ -170,14 +198,15 @@ async function fetchPackageFileInner(request: ServerRequest, response: ServerRes
170
198
await PackageProcessingQueue . wait ( packageId ) ;
171
199
}
172
200
173
- const packageBaseDir = `${ CACHE_DIR } /${ packageId } /${ packageVersion } /package` ;
201
+ const baseDir = `${ CACHE_DIR } /${ workspaceId ?? "default" } ` ;
202
+ const packageBaseDir = `${ baseDir } /${ packageId } /${ packageVersion } /package` ;
174
203
const packageExists = await fileExists ( `${ packageBaseDir } /package.json` )
175
204
if ( ! packageExists ) {
176
205
try {
177
206
logger . info ( `Package does not exist, fetch from registy: ${ packageId } @${ packageVersion } ` ) ;
178
207
PackageProcessingQueue . add ( packageId ) ;
179
208
if ( ! packageInfo ) {
180
- packageInfo = await fetchPackageInfo ( packageId , config ) ;
209
+ packageInfo = await fetchPackageInfo ( packageId , registry ) ;
181
210
}
182
211
183
212
if ( ! packageInfo || ! packageInfo . versions || ! packageInfo . versions [ packageVersion ] ) {
@@ -186,9 +215,9 @@ async function fetchPackageFileInner(request: ServerRequest, response: ServerRes
186
215
187
216
const tarball = packageInfo . versions [ packageVersion ] . dist . tarball ;
188
217
logger . info ( `Fetching tarball: ${ tarball } ` ) ;
189
- await fetchAndUnpackTarball ( tarball , packageId , packageVersion , config ) ;
218
+ await fetchAndUnpackTarball ( tarball , packageId , packageVersion , registry , baseDir ) ;
190
219
} catch ( error ) {
191
- logger . error ( " Error fetching package tarball" , error ) ;
220
+ logger . error ( ` Error fetching package: ${ error } ${ ( error as { stack : string } ) . stack } ` ) ;
192
221
return response . status ( 500 ) . send ( "Internal server error" ) ;
193
222
} finally {
194
223
PackageProcessingQueue . resolve ( packageId ) ;
@@ -224,6 +253,7 @@ function parsePackageInfoFromPath(path: string): {packageId: string, organizatio
224
253
}
225
254
226
255
let { packageId, organization, name, version, file} = matches . groups ;
256
+ // also test for alpha and beta versions like 0.0.1-beta1
227
257
version = / ^ \d + \. \d + \. \d + ( - [ \w \d ] + ) ? / . test ( version ) ? version : "latest" ;
228
258
229
259
return { packageId, organization, name, version, file} ;
@@ -265,25 +295,28 @@ function fetchPackageInfo(packageName: string, config: NpmRegistryConfigEntry):
265
295
} ) ;
266
296
}
267
297
268
- async function fetchAndUnpackTarball ( url : string , packageId : string , packageVersion : string , config : NpmRegistryConfigEntry ) {
298
+ async function fetchAndUnpackTarball ( url : string , packageId : string , packageVersion : string , config : NpmRegistryConfigEntry , baseDir : string ) {
299
+ if ( ! await fileExists ( baseDir ) ) {
300
+ await fs . mkdir ( baseDir , { recursive : true } ) ;
301
+ }
302
+
303
+ // Fetch tarball
269
304
const response : Response = await fetchFromRegistry ( url , config ) ;
270
305
const arrayBuffer = await response . arrayBuffer ( ) ;
271
306
const buffer = Buffer . from ( arrayBuffer ) ;
272
- const path = `${ CACHE_DIR } /${ url . split ( "/" ) . pop ( ) } ` ;
307
+ const path = `${ baseDir } /${ url . split ( "/" ) . pop ( ) } ` ;
273
308
await fs . writeFile ( path , buffer ) ;
274
- await unpackTarball ( path , packageId , packageVersion ) ;
275
- await fs . unlink ( path ) ;
276
- }
277
-
278
- async function unpackTarball ( path : string , packageId : string , packageVersion : string ) {
279
- const destinationPath = `${ CACHE_DIR } /${ packageId } /${ packageVersion } ` ;
309
+
310
+ // Unpack tarball
311
+ const destinationPath = `${ baseDir } /${ packageId } /${ packageVersion } ` ;
280
312
await fs . mkdir ( destinationPath , { recursive : true } ) ;
281
313
await new Promise < void > ( ( resolve , reject ) => {
282
314
const tar = spawn ( "tar" , [ "-xvf" , path , "-C" , destinationPath ] ) ;
283
- tar . on ( "close" , ( code ) => {
284
- code === 0 ? resolve ( ) : reject ( ) ;
285
- } ) ;
315
+ tar . on ( "close" , ( code ) => code === 0 ? resolve ( ) : reject ( ) ) ;
286
316
} ) ;
317
+
318
+ // Cleanup
319
+ await fs . unlink ( path ) ;
287
320
}
288
321
289
322
async function fileExists ( filePath : string ) : Promise < boolean > {
0 commit comments