import { errors } from "../errors" ; import { RuntimeDirEntry } from "../runtimes" ; import { FileSystemHost } from "./FileSystemHost" ; import { FileUtils } from "./FileUtils" ; import { matchGlobs } from "./matchGlobs" ; import { StandardizedFilePath } from "./StandardizedFilePath" ; interface VirtualDirectory { path : StandardizedFilePath ; files : Map < StandardizedFilePath , string > ; } /** An implementation of a file system that exists in memory only. */ export class InMemoryFileSystemHost implements FileSystemHost { internal private readonly directories = new Map < StandardizedFilePath , VirtualDirectory > ( ) ; /**
     * Constructor.
     */ constructor ( ) { this . getOrCreateDir ( "/" as StandardizedFilePath ) ; } inheritdoc isCaseSensitive ( ) { return true ; } inheritdoc delete ( path ) { try { this . deleteSync ( path ) ; return Promise . resolve ( ) ; } catch ( err ) { return Promise . reject ( err ) ; } } inheritdoc deleteSync ( path ) { const standardizedPath = FileUtils . getStandardizedAbsolutePath ( this , path ) ; if ( this . directories . has ( standardizedPath ) ) { for ( const descendantDirPath of getDescendantDirectories ( this . directories . keys ( ) , standardizedPath ) ) this . directories . delete ( descendantDirPath ) ; this . directories . delete ( standardizedPath ) ; return ; } const parentDir = this . directories . get ( FileUtils . getDirPath ( standardizedPath ) ) ; if ( parentDir == null || ! parentDir . files . has ( standardizedPath ) ) throw new errors . FileNotFoundError ( standardizedPath ) ; parentDir . files . delete ( standardizedPath ) ; } inheritdoc readDirSync ( dirPath ) { const standardizedDirPath = FileUtils . getStandardizedAbsolutePath ( this , dirPath ) ; const dir = this . directories . get ( standardizedDirPath ) ; if ( dir == null ) throw new errors . DirectoryNotFoundError ( standardizedDirPath ) ; return [ ... getDirectories ( this . directories . keys ( ) ) , ... Array . from ( dir . files . keys ( ) ) . map ( name => ( { name , isDirectory : false , isFile : true , isSymlink : false , } ) ) , ] ; function * getDirectories ( dirPaths ) { for ( const path of dirPaths ) { const parentDir = FileUtils . getDirPath ( path ) ; if ( parentDir === standardizedDirPath && parentDir !== path ) { yield { name : path , isDirectory : true , isFile : false , isSymlink : false , } ; } } } } inheritdoc readFile ( filePath , encoding = "utf-8" ) { try { return Promise . resolve ( this . readFileSync ( filePath , encoding ) ) ; } catch ( err ) { return Promise . reject ( err ) ; } } inheritdoc readFileSync ( filePath , encoding = "utf-8" ) { const standardizedFilePath = FileUtils . getStandardizedAbsolutePath ( this , filePath ) ; const parentDir = this . directories . get ( FileUtils . getDirPath ( standardizedFilePath ) ) ; if ( parentDir == null ) throw new errors . FileNotFoundError ( standardizedFilePath ) ; const fileText = parentDir . files . get ( standardizedFilePath ) ; if ( fileText === undefined ) throw new errors . FileNotFoundError ( standardizedFilePath ) ; return fileText ; } inheritdoc writeFile ( filePath , fileText ) { this . writeFileSync ( filePath , fileText ) ; return Promise . resolve ( ) ; } inheritdoc writeFileSync ( filePath , fileText ) { this . _writeFileSync ( filePath , fileText ) ; } private _writeFileSync ( filePath , fileText ) { const standardizedFilePath = FileUtils . getStandardizedAbsolutePath ( this , filePath ) ; const dirPath = FileUtils . getDirPath ( standardizedFilePath ) ; this . getOrCreateDir ( dirPath ) . files . set ( standardizedFilePath , fileText ) ; } inheritdoc mkdir ( dirPath ) { this . mkdirSync ( dirPath ) ; return Promise . resolve ( ) ; } inheritdoc mkdirSync ( dirPath ) { this . getOrCreateDir ( FileUtils . getStandardizedAbsolutePath ( this , dirPath ) ) ; } inheritdoc move ( srcPath , destPath ) { this . moveSync ( srcPath , destPath ) ; return Promise . resolve ( ) ; } inheritdoc moveSync ( srcPath , destPath ) { const standardizedSrcPath = FileUtils . getStandardizedAbsolutePath ( this , srcPath ) ; const standardizedDestPath = FileUtils . getStandardizedAbsolutePath ( this , destPath ) ; if ( this . fileExistsSync ( standardizedSrcPath ) ) { const fileText = this . readFileSync ( standardizedSrcPath ) ; this . deleteSync ( standardizedSrcPath ) ; this . writeFileSync ( standardizedDestPath , fileText ) ; } else if ( this . directories . has ( standardizedSrcPath ) ) { const moveDirectory = ( from , to ) => { this . _copyDirInternal ( from , to ) ; this . directories . delete ( from ) ; } ; moveDirectory ( standardizedSrcPath , standardizedDestPath ) ; for ( const descendantDirPath of getDescendantDirectories ( this . directories . keys ( ) , standardizedSrcPath ) ) { const relativePath = FileUtils . getRelativePathTo ( standardizedSrcPath , descendantDirPath ) ; moveDirectory ( descendantDirPath , FileUtils . pathJoin ( standardizedDestPath , relativePath ) as StandardizedFilePath ) ; } } else { throw new errors . PathNotFoundError ( standardizedSrcPath ) ; } } inheritdoc copy ( srcPath , destPath ) { this . copySync ( srcPath , destPath ) ; return Promise . resolve ( ) ; } inheritdoc copySync ( srcPath , destPath ) { const standardizedSrcPath = FileUtils . getStandardizedAbsolutePath ( this , srcPath ) ; const standardizedDestPath = FileUtils . getStandardizedAbsolutePath ( this , destPath ) ; if ( this . fileExistsSync ( standardizedSrcPath ) ) this . writeFileSync ( standardizedDestPath , this . readFileSync ( standardizedSrcPath ) ) ; else if ( this . directories . has ( standardizedSrcPath ) ) { this . _copyDirInternal ( standardizedSrcPath , standardizedDestPath ) ; for ( const descendantDirPath of getDescendantDirectories ( this . directories . keys ( ) , standardizedSrcPath ) ) { const relativePath = FileUtils . getRelativePathTo ( standardizedSrcPath , descendantDirPath ) ; this . _copyDirInternal ( descendantDirPath , FileUtils . pathJoin ( standardizedDestPath , relativePath ) as StandardizedFilePath ) ; } } else { throw new errors . PathNotFoundError ( standardizedSrcPath ) ; } } internal private _copyDirInternal ( from , to ) { const dir = this . directories . get ( from ) ! ; const newDir = this . getOrCreateDir ( to ) ; for ( const [ filePath , text ] of dir . files . entries ( ) ) { const toDir = FileUtils . pathJoin ( to , FileUtils . getBaseName ( filePath ) ) as StandardizedFilePath ; newDir . files . set ( toDir , text ) ; } } inheritdoc fileExists ( filePath ) { return Promise . resolve < boolean > ( this . fileExistsSync ( filePath ) ) ; } inheritdoc fileExistsSync ( filePath ) { const standardizedFilePath = FileUtils . getStandardizedAbsolutePath ( this , filePath ) ; const dirPath = FileUtils . getDirPath ( standardizedFilePath ) ; const dir = this . directories . get ( dirPath ) ; if ( dir == null ) return false ; return dir . files . has ( standardizedFilePath ) ; } inheritdoc directoryExists ( dirPath ) { return Promise . resolve < boolean > ( this . directoryExistsSync ( dirPath ) ) ; } inheritdoc directoryExistsSync ( dirPath ) { return this . directories . has ( FileUtils . getStandardizedAbsolutePath ( this , dirPath ) ) ; } inheritdoc realpathSync ( path ) { return path ; } inheritdoc getCurrentDirectory ( ) { return "/" ; } inheritdoc glob ( patterns ) { try { return Promise . resolve ( this . globSync ( patterns ) ) ; } catch ( err ) { return Promise . reject ( err ) ; } } inheritdoc globSync ( patterns ) { const allFilePaths = Array . from ( getAllFilePaths ( this . directories . values ( ) ) ) ; return matchGlobs ( allFilePaths , patterns , this . getCurrentDirectory ( ) ) ; function * getAllFilePaths ( directories ) { for ( const dir of directories ) yield * dir . files . keys ( ) ; } } internal private getOrCreateDir ( dirPath ) { let dir = this . directories . get ( dirPath ) ; if ( dir == null ) { dir = { path : dirPath , files : new Map < StandardizedFilePath , string > ( ) } ; this . directories . set ( dirPath , dir ) ; const parentDirPath = FileUtils . getDirPath ( dirPath ) ; if ( parentDirPath !== dirPath ) this . getOrCreateDir ( parentDirPath ) ; } return dir ; } } function * getDescendantDirectories ( directoryPaths , dirPath ) { for ( const path of directoryPaths ) { if ( FileUtils . pathStartsWith ( path , dirPath ) ) yield path ; } }