@@ -141,13 +141,19 @@ export class BlobsServer {
141141
142142 const { dataPath, key, metadataPath, rootPath } = this . getLocalPaths ( url )
143143
144- if ( ! dataPath || ! metadataPath ) {
144+ // If there's no root path, the request is invalid.
145+ if ( ! rootPath ) {
145146 return this . sendResponse ( req , res , 400 )
146147 }
147148
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.
149155 if ( ! key ) {
150- return this . list ( { dataPath, metadataPath, rootPath, req, res, url } )
156+ return this . listBlobs ( { dataPath, metadataPath, rootPath, req, res, url } )
151157 }
152158
153159 this . onRequest ( { type : Operation . GET } )
@@ -213,7 +219,7 @@ export class BlobsServer {
213219 res . end ( )
214220 }
215221
216- async list ( options : {
222+ async listBlobs ( options : {
217223 dataPath : string
218224 metadataPath : string
219225 rootPath : string
@@ -248,6 +254,22 @@ export class BlobsServer {
248254 return this . sendResponse ( req , res , 200 , JSON . stringify ( result ) )
249255 }
250256
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+
251273 async put ( req : http . IncomingMessage , res : http . ServerResponse ) {
252274 const apiMatch = this . parseAPIRequest ( req )
253275
@@ -304,18 +326,24 @@ export class BlobsServer {
304326
305327 const [ , siteID , rawStoreName , ...key ] = url . pathname . split ( '/' )
306328
307- if ( ! siteID || ! rawStoreName ) {
329+ if ( ! siteID ) {
308330 return { }
309331 }
310332
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.
313341 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 )
316344 const metadataPath = resolve ( this . directory , 'metadata' , siteID , storeName , ...key )
317345
318- return { dataPath, key : key . join ( '/' ) , metadataPath, rootPath }
346+ return { dataPath, key : key . join ( '/' ) , metadataPath, rootPath : storePath }
319347 }
320348
321349 handleRequest ( req : http . IncomingMessage , res : http . ServerResponse ) {
0 commit comments