@@ -141,13 +141,19 @@ export class BlobsServer {
141
141
142
142
const { dataPath, key, metadataPath, rootPath } = this . getLocalPaths ( url )
143
143
144
- if ( ! dataPath || ! metadataPath ) {
144
+ // If there's no root path, the request is invalid.
145
+ if ( ! rootPath ) {
145
146
return this . sendResponse ( req , res , 400 )
146
147
}
147
148
148
- // If there is no key in the URL, it means a `list` operation.
149
+ // If there's no data or metadata paths, it means we're listing stores.
150
+ if ( ! dataPath || ! metadataPath ) {
151
+ return this . listStores ( req , res , rootPath , url . searchParams . get ( 'prefix' ) ?? '' )
152
+ }
153
+
154
+ // If there is no key in the URL, it means we're listing blobs.
149
155
if ( ! key ) {
150
- return this . list ( { dataPath, metadataPath, rootPath, req, res, url } )
156
+ return this . listBlobs ( { dataPath, metadataPath, rootPath, req, res, url } )
151
157
}
152
158
153
159
this . onRequest ( { type : Operation . GET } )
@@ -213,7 +219,7 @@ export class BlobsServer {
213
219
res . end ( )
214
220
}
215
221
216
- async list ( options : {
222
+ async listBlobs ( options : {
217
223
dataPath : string
218
224
metadataPath : string
219
225
rootPath : string
@@ -248,6 +254,22 @@ export class BlobsServer {
248
254
return this . sendResponse ( req , res , 200 , JSON . stringify ( result ) )
249
255
}
250
256
257
+ async listStores ( req : http . IncomingMessage , res : http . ServerResponse , rootPath : string , prefix : string ) {
258
+ try {
259
+ const allStores = await fs . readdir ( rootPath )
260
+ const filteredStores = allStores
261
+ // Store names are URI-encoded on Windows, so we must decode them first.
262
+ . map ( ( store ) => ( platform === 'win32' ? decodeURIComponent ( store ) : store ) )
263
+ . filter ( ( store ) => store . startsWith ( prefix ) )
264
+
265
+ return this . sendResponse ( req , res , 200 , JSON . stringify ( { stores : filteredStores } ) )
266
+ } catch ( error ) {
267
+ this . logDebug ( 'Could not list stores:' , error )
268
+
269
+ return this . sendResponse ( req , res , 500 )
270
+ }
271
+ }
272
+
251
273
async put ( req : http . IncomingMessage , res : http . ServerResponse ) {
252
274
const apiMatch = this . parseAPIRequest ( req )
253
275
@@ -304,18 +326,24 @@ export class BlobsServer {
304
326
305
327
const [ , siteID , rawStoreName , ...key ] = url . pathname . split ( '/' )
306
328
307
- if ( ! siteID || ! rawStoreName ) {
329
+ if ( ! siteID ) {
308
330
return { }
309
331
}
310
332
311
- // On Windows, file paths can't include the `:` character, which is used in
312
- // deploy-scoped stores.
333
+ const rootPath = resolve ( this . directory , 'entries' , siteID )
334
+
335
+ if ( ! rawStoreName ) {
336
+ return { rootPath }
337
+ }
338
+
339
+ // On Windows, file paths can't include the `:` character, so we URI-encode
340
+ // them.
313
341
const storeName = platform === 'win32' ? encodeURIComponent ( rawStoreName ) : rawStoreName
314
- const rootPath = resolve ( this . directory , 'entries' , siteID , storeName )
315
- const dataPath = resolve ( rootPath , ...key )
342
+ const storePath = resolve ( rootPath , storeName )
343
+ const dataPath = resolve ( storePath , ...key )
316
344
const metadataPath = resolve ( this . directory , 'metadata' , siteID , storeName , ...key )
317
345
318
- return { dataPath, key : key . join ( '/' ) , metadataPath, rootPath }
346
+ return { dataPath, key : key . join ( '/' ) , metadataPath, rootPath : storePath }
319
347
}
320
348
321
349
handleRequest ( req : http . IncomingMessage , res : http . ServerResponse ) {
0 commit comments