import { deepStrictEqual , strictEqual } from 'assert' ; import { copyFileSync , existsSync , mkdirSync , readdirSync , readFileSync , rmdirSync , statSync , unlinkSync , writeFileSync } from 'fs' ; import { dirname , join , parse } from 'path' ; import { cyan , green } from 'colors/safe' ; function rmDirAndFiles ( path ) { const files = readdirSync ( path ) ; for ( const file of files ) { const stats = statSync ( join ( path , file ) ) ; if ( stats . isDirectory ( ) ) { rmDirAndFiles ( join ( path , file ) ) ; } else { unlinkSync ( join ( path , file ) ) ; } } rmdirSync ( path ) ; } export class extends Error export class ClientError extends Error { readonly name = 'ClientError' ; } export class export class FileSystem { currentDir = '' ; private readonly testDir = 'test-generators/subdir' ; private logs = true ; returns { this } memberof hideLogs ( ) { this . logs = false ; return this ; } param { string } path returns { this } memberof cd ( path ) { this . currentDir = join ( this . currentDir , path ) ; return this ; } returns { this } memberof cdProjectRootDir ( ) { const root = parse ( process . cwd ( ) ) . root ; while ( ! this . exists ( 'package.json' ) ) { if ( join ( process . cwd ( ) , this . parse ( '.' ) ) === root ) { throw new ClientError ( 'This project is not a FoalTS project. No package.json found.' ) ; } this . cd ( '..' ) ; } const content = readFileSync ( this . parse ( 'package.json' ) , 'utf8' ) ; let pkg ; try { pkg = JSON . parse ( content ) ; } catch ( error ) { throw new ClientError ( `The file package.json is not a valid JSON. ${ error . message }` ) ; } if ( ! pkg . dependencies || ! pkg . dependencies [ '@foal/core' ] ) { throw new ClientError ( 'This project is not a FoalTS project. The dependency @foal/core is missing in package.json.' ) ; } return this ; } param { string } path returns { boolean } memberof exists ( path ) { return existsSync ( this . parse ( path ) ) ; } param { string } path returns { this } memberof ensureDir ( path ) { const dir = dirname ( path ) ; if ( dir !== '.' ) { this . ensureDir ( dir ) ; } if ( ! existsSync ( this . parse ( path ) ) ) { mkdirSync ( this . parse ( path ) ) ; } return this ; } param { boolean } condition param { string } path returns { this } memberof ensureDirOnlyIf ( condition , path ) { if ( condition ) { this . ensureDir ( path ) ; } return this ; } param { string } path returns { this } memberof ensureFile ( path ) { if ( ! existsSync ( this . parse ( path ) ) ) { this . logCreate ( path ) ; writeFileSync ( this . parse ( path ) , '' , 'utf8' ) ; } return this ; } param { string } src param { string } dest returns { this } memberof copy ( src , dest ) { const templatePath = join ( __dirname , 'templates' , src ) ; if ( ! existsSync ( templatePath ) ) { throw new Error ( `The template "${ src }" does not exist.` ) ; } this . logCreate ( dest ) ; copyFileSync ( templatePath , this . parse ( dest ) ) ; return this ; } param { boolean } condition param { string } src param { string } dest returns { this } memberof copyOnlyIf ( condition , src , dest ) { if ( condition ) { this . copy ( src , dest ) ; } return this ; } param { string } src param { string } dest param { * } locals returns { this } memberof render ( src , dest , locals ) { const templatePath = join ( __dirname , 'templates' , src ) ; if ( ! existsSync ( templatePath ) ) { throw new Error ( `The template "${ src }" does not exist.` ) ; } let content = readFileSync ( templatePath , 'utf8' ) ; for ( const key in locals ) { content = content . split ( `/* ${ key } */` ) . join ( locals [ key ] ) ; } this . logCreate ( dest ) ; writeFileSync ( this . parse ( dest ) , content , 'utf8' ) ; return this ; } param { boolean } condition param { string } src param { string } dest param { * } locals returns { this } memberof renderOnlyIf ( condition , src , dest , locals ) { if ( condition ) { this . render ( src , dest , locals ) ; } return this ; } param { string } path param { ( content : string ) => string } callback returns { this } memberof modify ( path , callback ) { if ( ! existsSync ( this . parse ( path ) ) ) { throw new ClientError ( `Impossible to modify "${ path }": the file does not exist.` ) ; } const content = readFileSync ( this . parse ( path ) , 'utf8' ) ; this . logUpdate ( path ) ; writeFileSync ( this . parse ( path ) , callback ( content ) , 'utf8' ) ; return this ; } param { boolean } condition param { string } path param { ( content : string ) => string } callback returns { this } memberof modifyOnlyfIf ( condition , path , callback ) { if ( condition ) { this . modify ( path , callback ) ; } return this ; } param { string } path param { string } specifier param { string } source returns { this } memberof addNamedExportIn ( path , specifier , source ) { this . modify ( path , content => `${ content }export { ${ specifier } } from '${ source }';\n` ) ; return this ; } param { string } path param { string } specifier param { string } source returns { this } memberof addOrExtendNamedImportIn ( path , specifier , source , options ? ) { const initialLogs = this . logs ; if ( options ) { this . logs = options . logs ; } this . modify ( path , content => { const regex = /import (.*) from '(.*)';/g ; let endPos = 0 ; let specifierAlreadyExists = false ; const replacedContent = content . replace ( regex , ( match , p1 , p2 , offset ) => { endPos = offset + match . length ; const namedImportRegex = new RegExp ( `import {(.*)} from \'(.*)\';` ) ; return match . replace ( namedImportRegex , ( subString , specifiersStr , path ) => { if ( path !== source ) { return subString ; } const specifiers = specifiersStr . split ( ',' ) . map ( imp => imp . trim ( ) ) ; if ( specifiers . includes ( specifier ) ) { specifierAlreadyExists = true ; return subString ; } const newSpecifiers = specifiers . concat ( specifier ) . sort ( ( a , b ) => a . localeCompare ( b ) ) . join ( ', ' ) ; return `import { ${ newSpecifiers } } from '${ source }';` ; } ) ; } ) ; if ( specifierAlreadyExists ) { return content ; } if ( replacedContent !== content ) { return replacedContent ; } const newImport = `import { ${ specifier } } from '${ source }';` ; if ( endPos === 0 ) { return `${ newImport }\n\n${ content }` ; } return content . substr ( 0 , endPos ) + '\n' + newImport + content . substr ( endPos ) ; } ) ; this . logs = initialLogs ; return this ; } param { string } path param { string } className param { string } propertyName param { string } element returns { this } memberof addOrExtendClassArrayPropertyIn ( path , propertyName , element , options ? ) { const initialLogs = this . logs ; if ( options ) { this . logs = options . logs ; } this . modify ( path , content => content . replace ( new RegExp ( `class (\\w*|\\w* implements \\w*) {(.*)}` , 's' ) , ( match , className , p2 ) => { if ( /^(\s)*$/ . test ( p2 ) ) { return `class ${ className } {\n  ${ propertyName } = [\n    ${ element }\n  ];\n}` ; } const replacedMatch = match . replace ( new RegExp ( `( *)${ propertyName } = \\[(.*)\\];` , 's' ) , ( _ , spaces , content ) => { const items = content . replace ( /,\n/g , '\n' ) . split ( '\n' ) . map ( e => e . trim ( ) ) . concat ( element ) . map ( e => `${ spaces }${ spaces }${ e }` ) ; const cleanItems = [ ] ; for ( const item of items ) { if ( item . trim ( ) !== '' ) { cleanItems . push ( item ) ; } } return `${ spaces }${ propertyName } = [\n${ cleanItems . join ( ',\n' ) }\n${ spaces }];` ; } ) ; if ( replacedMatch !== match ) { return replacedMatch ; } return `class ${ className } {\n  ${ propertyName } = [\n    ${ element }\n  ];\n${ p2 }}` ; } ) ) ; this . logs = initialLogs ; return this ; } param { string } name returns { boolean } memberof projectHasDependency ( name ) { const initialCurrentDir = this . currentDir ; this . cdProjectRootDir ( ) ; const pkg = JSON . parse ( readFileSync ( this . parse ( 'package.json' ) , 'utf8' ) ) ; this . currentDir = initialCurrentDir ; return pkg . dependencies . hasOwnProperty ( name ) ; } /************************
        Testing Methods
    ************************/ memberof setUp ( ) { const [ firstDir ] = this . testDir . split ( '/' ) ; mkdirSync ( firstDir ) ; mkdirSync ( this . testDir ) ; this . currentDir = '' ; } memberof tearDown ( ) { const [ firstDir ] = this . testDir . split ( '/' ) ; rmDirAndFiles ( firstDir ) ; this . currentDir = '' ; } param { string } path returns { this } memberof assertExists ( path ) { if ( ! existsSync ( this . parse ( path ) ) ) { throw new Error ( `The file "${ path }" does not exist.` ) ; } return this ; } param { string } path returns { this } memberof assertNotExists ( path ) { if ( existsSync ( this . parse ( path ) ) ) { throw new Error ( `The file "${ path }" should not exist.` ) ; } return this ; } param { string } path memberof assertEmptyDir ( path ) { if ( readdirSync ( this . parse ( path ) ) . length > 0 ) { throw new Error ( `The directory "${ path }" should be empty.` ) ; } } param { string } actual param { string } expected param { { binary : boolean } } returns { this } memberof assertEqual ( actual , expected , { binary } = { binary : false } ) { const specPath = join ( __dirname , 'specs' , expected ) ; if ( ! existsSync ( specPath ) ) { throw new Error ( `The spec file "${ expected }" does not exist.` ) ; } if ( binary ) { deepStrictEqual ( readFileSync ( this . parse ( actual ) ) , readFileSync ( specPath ) ) ; } else { strictEqual ( readFileSync ( this . parse ( actual ) , 'utf8' ) , readFileSync ( specPath , 'utf8' ) ) ; } return this ; } param { string } src param { string } dest returns { this } memberof copyFixture ( src , dest ) { const fixturePath = join ( __dirname , 'fixtures' , src ) ; if ( ! existsSync ( fixturePath ) ) { throw new Error ( `The fixture file "${ src }" does not exist.` ) ; } copyFileSync ( fixturePath , this . parse ( dest ) ) ; return this ; } param { string } path memberof rmfile ( path ) { unlinkSync ( this . parse ( path ) ) ; } private isTestingEnvironment ( ) { return process . env . P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST === 'true' ; } private parse ( path ) { if ( this . isTestingEnvironment ( ) ) { return join ( this . testDir , this . currentDir , path ) ; } return join ( this . currentDir , path ) ; } private logCreate ( path ) { path = join ( this . currentDir , path ) ; if ( ! this . isTestingEnvironment ( ) && this . logs ) { console . log ( `${ green ( 'CREATE' ) } ${ path }` ) ; } } private logUpdate ( path ) { path = join ( this . currentDir , path ) ; if ( ! this . isTestingEnvironment ( ) && this . logs ) { console . log ( `${ cyan ( 'UPDATE' ) } ${ path }` ) ; } } }