const util = require ( 'util' ) ; import { readdirSync , unlinkSync } from 'fs' ; import { each , endsWith , every , find , first , forEach , has , indexOf , isArray , isEmpty , isNil , kebabCase , keys , snakeCase , some , uniq , uniqBy , upperFirst } from 'lodash' ; import { join , normalize } from 'path' ; import { GeneratorOptions } from '../bootstrap/options' ; import { Schema , Swagger , SwaggerPropertyDefinition , SwaggerSchema , SwaggerSchemaProperties } from '../bootstrap/swagger' ; import { INamespaceGroups , IType , ITypeProperty , Type , TypeProperty } from './types' ; import { convertNamespaceToPath , ensureFolder , getDirectories , getPathToRoot , getSortedObjectProperties , isInTypesToFilter , log , logError , readAndCompileTemplateFile , removeExtension , removeFolder , writeFileIfContentsIsChanged } from './utils' ; const TS_SUFFIX = '.ts' ; const MODEL_SUFFIX = '.model' ; const MODEL_FILE_SUFFIX = `${ MODEL_SUFFIX }${ TS_SUFFIX }` ; const ROOT_NAMESPACE = 'root' ; export function generateModelTSFiles ( swagger , options ) { log ( 'generating models...' ) ; const folder = normalize ( options . modelFolder ) ; if ( options . generateValidatorFile ) { generateTSValidations ( folder , options ) ; } generateTSBaseModel ( folder , options ) ; const typeCollection = getTypeDefinitions ( swagger , options , MODEL_SUFFIX , MODEL_FILE_SUFFIX ) ; const namespaceGroups = getNamespaceGroups ( typeCollection , options ) ; generateTSModels ( namespaceGroups , folder , options ) ; generateSubTypeFactory ( namespaceGroups , folder , options ) ; } function generateTSValidations ( folder , options ) { if ( ! options . generateClasses ) return ; const outputFileName = join ( folder , options . validatorsFileName ) ; const data = { } ; const template = readAndCompileTemplateFile ( options . templates . validators ) ; const result = template ( data ) ; ensureFolder ( folder ) ; const isChanged = writeFileIfContentsIsChanged ( outputFileName , result ) ; if ( isChanged ) { log ( `generated ${ outputFileName }` ) ; } } function generateTSBaseModel ( folder , options ) { if ( ! options . generateClasses ) return ; const outputFileName = join ( folder , options . baseModelFileName ) ; const data = { subTypePropertyName : options . subTypePropertyName } ; const template = readAndCompileTemplateFile ( options . templates . baseModel ) ; const result = template ( data ) ; ensureFolder ( folder ) ; const isChanged = writeFileIfContentsIsChanged ( outputFileName , result ) ; if ( isChanged ) { log ( `generated ${ outputFileName }` ) ; } } function getTypeDefinitions ( swagger , options , suffix , fileSuffix ) { let typeCollection = new Array ( ) ; forEach ( swagger . components . schemas , ( item , key ) => { if ( ! isInTypesToFilter ( item , key , options ) ) { let type = getTypeDefinition ( swagger , typeCollection , item , key , options , suffix , fileSuffix ) ; if ( type ) { typeCollection . push ( type ) ; } } } ) ; fillMissingBaseTypes ( swagger , typeCollection , options , suffix , fileSuffix ) ; fillPropertyTypes ( swagger , typeCollection , options , suffix , fileSuffix ) ; typeCollection = uniqBy ( typeCollection , 'typeName' ) ; return typeCollection ; } function getTypeDefinition ( swagger , typeCollection , item , key , options , suffix , fileSuffix ) { let isEnumType = getIsEnumType ( item ) ; if ( isEnumType ) { return undefined ; } let required = ( item . required as string [ ] ) || [ ] ; let namespace = getNamespace ( key , options , true ) ; let fullNamespace = getNamespace ( key , options , false ) ; let typeName = getTypeName ( key , options ) ; if ( getIsGenericType ( typeName ) ) { typeName = convertGenericToGenericT ( typeName ) ; } let fullTypeName = fullNamespace ? `${ fullNamespace }.${ typeName }` : typeName ; let pathToRoot = getPathToRoot ( namespace ) ; let importFile = getImportFile ( typeName , namespace , pathToRoot , suffix ) ; let properties = options . sortModelProperties ? ( getSortedObjectProperties ( item . properties ) as SwaggerSchemaProperties ) : item . properties ; let baseType = undefined ; let baseImportFile = undefined ; let isSubType = getIsSubType ( item ) ; let hasSubTypeProperty = isSubType || getHasSubTypeProperty ( properties , options ) ; if ( isSubType ) { baseType = getBaseType ( typeName , typeCollection , item , options , false ) ; if ( baseType ) { baseType . isBaseType = true ; baseImportFile = getImportFile ( baseType . typeName , baseType . namespace , pathToRoot , suffix ) ; required = getSubTypeRequired ( item ) ; properties = getSubTypeProperties ( item , baseType ) ; } } let type = new Type ( { fileName : getFileName ( key , options , fileSuffix ) , typeName : typeName , namespace : namespace , fullNamespace : fullNamespace , fullTypeName : fullTypeName , isSubType : isSubType , hasSubTypeProperty : hasSubTypeProperty , importFile : importFile , isBaseType : false , baseType : baseType , baseImportFile : baseImportFile , path : convertNamespaceToPath ( namespace ) , pathToRoot : pathToRoot , properties : [ ] } ) ; fillTypeProperties ( swagger , type , baseType , required , properties , item , key , options , suffix , fileSuffix ) ; return type ; } function fillMissingBaseTypes ( swagger , typeCollection , options , suffix , fileSuffix ) { forEach ( swagger . components . schemas , ( item , key ) => { let isSubType = getIsSubType ( item ) ; let type = findTypeInTypeCollection ( typeCollection , key ) ; if ( isSubType && ! type . baseType ) { let namespace = getNamespace ( key , options , true ) ; let pathToRoot = getPathToRoot ( namespace ) ; let baseType = getBaseType ( key , typeCollection , item , options , true ) ; let baseImportFile ; if ( baseType ) { baseType . isBaseType = true ; baseImportFile = getImportFile ( baseType . typeName , baseType . namespace , pathToRoot , suffix ) ; } let required = getSubTypeRequired ( item ) ; let properties = getSubTypeProperties ( item , baseType ) ; type . baseType = baseType ; type . baseImportFile = baseImportFile ; fillTypeProperties ( swagger , type , baseType , required , properties , item , key , options , suffix , fileSuffix ) ; } } ) ; } function fillPropertyTypes ( swagger , typeCollection , options , suffix , fileSuffix ) { const missingTypes = new Array < string > ( ) ; forEach ( typeCollection , ( type , typeKey ) => { forEach ( type . properties , ( property , propertyKey ) => { const propertyType = findTypesInTypeCollection ( typeCollection , property . typeName , property . isUnionType , property . unionTypeNames ) ; property . type = propertyType ; const hasMissingTypes = isNil ( propertyType ) || ( isArray ( propertyType ) && some ( propertyType , item => isNil ( item ) ) ) ; if ( hasMissingTypes && ! property . isEnum && property . isComplexType ) { let isMissingType = true ; let missingTypeName = property . typeName ; if ( property . isArrayComplexType ) { const arrayPropertyType = findTypesInTypeCollection ( typeCollection , property . arrayTypeName , property . isArrayUnionType , property . unionTypeNames ) ; isMissingType = isNil ( arrayPropertyType ) || ( isArray ( arrayPropertyType ) && some ( arrayPropertyType , item => isNil ( item ) ) ) ; missingTypeName = property . arrayTypeName ; } else if ( property . isArray ) { isMissingType = false ; } if ( isMissingType ) { missingTypes . push ( missingTypeName ) ; } } } ) ; } ) ; for ( const missingType of uniq ( missingTypes ) ) { logError ( `type ${ missingType } not found` ) ; } } function fillTypeProperties ( swagger , type , baseType , required , properties , item , key , options , suffix , fileSuffix ) { type . properties = [ ] ; forEach ( properties , ( item , key ) => { let property = getTypePropertyDefinition ( swagger , type , baseType , required , item , key , options , suffix , fileSuffix ) ; type . properties . push ( property ) ; } ) ; } function getTypePropertyDefinition ( swagger , type , baseType , required , item , key , options , suffix , fileSuffix ) { const staticFieldName = `${ snakeCase ( key ) . toUpperCase ( ) }_FIELD_NAME` ; let isRefType = ! ! item . $ref || ! ! item . oneOf ; let isArray = item . type == 'array' ; let isEnum = ( item . type == 'string' && ! ! item . enum ) || ( isArray && item . items && item . items . type == 'string' && ! ! item . items . enum ) || ( ( isRefType || isArray ) && getIsEnumRefType ( swagger , item , isArray ) ) ; if ( isEnum ) { isRefType = false ; } let propertyType = getPropertyType ( item , key , options , isEnum ) ; let isComplexType = isRefType || isArray ; let hasImportTypes = isRefType || ( isArray && ( item . items . $ref || item . items . oneOf ) && ! isEnum ) ; let importTypes = hasImportTypes ? getImportTypes ( propertyType . typeName , propertyType . isUnionType , propertyType . unionTypeNames , isArray ) : undefined ; let importFiles = hasImportTypes ? getImportFiles ( importTypes , propertyType . namespace , type . pathToRoot , suffix ) : undefined ; let importTypeIsPropertyType = first ( importTypes ) === type . typeName ; let hasUniqueImportTypes = hasImportTypes && ! importTypeIsPropertyType && getHasUniqueImportTypes ( importTypes , baseType , type . properties ) ; let validators = getTypePropertyValidatorDefinitions ( required , item , key , propertyType . typeName , isEnum ) ; let hasValidation = keys ( validators . validation ) . length > 0 ; let importEnumType = isEnum ? removeGenericArray ( propertyType . typeName , isArray ) : undefined ; let isUniqueImportEnumType = isEnum && getIsUniqueImportEnumType ( importEnumType , type . properties ) ; let property = new TypeProperty ( { name : key , staticFieldName : staticFieldName , type : null , typeName : propertyType . typeName , interfaceTypeName : propertyType . interfaceTypeName , isUnionType : propertyType . isUnionType , unionTypeNames : propertyType . unionTypeNames , unionInterfaceTypeNames : propertyType . unionInterfaceTypeNames , namespace : propertyType . namespace , description : item . description , hasValidation , isComplexType , hasImportTypes , hasUniqueImportTypes , importTypes , importFiles , isEnum , enum : item . enum , isUniqueImportEnumType , importEnumType , isArray , isArrayComplexType : propertyType . isArrayComplexType , arrayTypeName : propertyType . arrayTypeName , arrayInterfaceTypeName : propertyType . arrayInterfaceTypeName , validators } ) ; return property ; } function getTypePropertyValidatorDefinitions ( required , item , key , typeName , isEnum ) { let isRequired = indexOf ( required , key ) !== - 1 ; let validators = { validation : { } as any , validatorArray : [ ] } ; if ( isRequired ) { validators . validation . required = isRequired ; validators . validatorArray . push ( 'Validators.required' ) ; } if ( has ( item , 'minimum' ) ) { validators . validation . minimum = item . minimum ; validators . validatorArray . push ( `minValueValidator(${ item . minimum })` ) ; } if ( has ( item , 'maximum' ) ) { validators . validation . maximum = item . maximum ; validators . validatorArray . push ( `maxValueValidator(${ item . maximum })` ) ; } if ( isEnum ) { validators . validation . enum = `'${ item . enum }'` ; validators . validatorArray . push ( `enumValidator(${ typeName })` ) ; } if ( has ( item , 'minLength' ) ) { validators . validation . minLength = item . minLength ; validators . validatorArray . push ( `Validators.minLength(${ item . minLength })` ) ; } if ( has ( item , 'maxLength' ) ) { validators . validation . maxLength = item . maxLength ; validators . validatorArray . push ( `Validators.maxLength(${ item . maxLength })` ) ; } if ( has ( item , 'pattern' ) ) { validators . validation . pattern = `'${ item . pattern }'` ; validators . validatorArray . push ( `Validators.pattern('${ item . pattern }')` ) ; } return validators ; } function getHasUniqueImportTypes ( importTypes , baseType , typeProperties ) { let baseTypeName = baseType ? baseType . typeName : undefined ; if ( every ( importTypes , item => item === baseTypeName ) ) { return false ; } return true ; } function getIsUniqueImportEnumType ( currentTypeName , typeProperties ) { return ! some ( typeProperties , property => { return property . importEnumType === currentTypeName ; } ) ; } function getTypeNameWithoutNamespacePrefixesToRemove ( key , options ) { if ( ! options . namespacePrefixesToRemove ) { return key ; } let namespaces = options . namespacePrefixesToRemove ; namespaces . forEach ( item => { key = key . replace ( item , '' ) ; if ( key [ 0 ] === '.' ) { key = key . substring ( 1 ) ; } } ) ; return key ; } function getPropertyType ( item , name , options , isEnum ) { let result = { typeName : '' , interfaceTypeName : '' , isUnionType : false , unionTypeNames : undefined , unionInterfaceTypeNames : undefined , namespace : '' , fullNamespace : undefined , isArrayComplexType : false , arrayTypeName : undefined , arrayInterfaceTypeName : undefined } ; if ( item . type ) { result . typeName = item . type ; if ( item . type == 'integer' ) { result . typeName = 'number' ; } if ( item . type == 'string' && item . format == 'date' ) { result . typeName = 'Date' ; } if ( item . type == 'string' && item . format == 'date-time' ) { result . typeName = 'Date' ; } if ( item . type == 'string' && item . enum ) { result . typeName = `${ name }` ; } result . interfaceTypeName = result . typeName ; if ( item . type == 'array' && item . items ) { let arrayPropType = getPropertyType ( item . items , name , options , isEnum ) ; result . typeName = `Array<${ getTypeNameOrUnionTypeName ( arrayPropType . isUnionType , arrayPropType . unionTypeNames , arrayPropType . typeName ) }>` ; result . interfaceTypeName = `Array<${ getTypeNameOrUnionTypeName ( arrayPropType . isUnionType , arrayPropType . unionInterfaceTypeNames , arrayPropType . interfaceTypeName ) }>` ; result . namespace = arrayPropType . namespace ; result . isArrayComplexType = ! isEnum ? ! ! item . items . $ref || ! ! item . items . oneOf : false ; result . arrayTypeName = arrayPropType . typeName ; result . arrayInterfaceTypeName = arrayPropType . interfaceTypeName ; result . isUnionType = arrayPropType . isUnionType ; result . unionTypeNames = arrayPropType . unionTypeNames ; result . unionInterfaceTypeNames = arrayPropType . unionInterfaceTypeNames ; } return result ; } if ( item . $ref ) { const { typeName , interfaceTypeName , namespace , fullNamespace } = getRefTypeProperties ( item . $ref , options , isEnum ) ; result = { ... result , typeName , interfaceTypeName , namespace , fullNamespace } ; return result ; } if ( item . oneOf ) { const unionTypeNames = [ ] ; const unionInterfaceTypeNames = [ ] ; let namespace ; let fullNamespace ; item . oneOf . map ( oneOfItem => { const { typeName , interfaceTypeName , namespace , fullNamespace } = getRefTypeProperties ( oneOfItem . $ref , options , isEnum ) ; unionTypeNames . push ( typeName ) ; unionInterfaceTypeNames . push ( interfaceTypeName ) ; } ) ; result = { ... result , typeName : getTypeNameOrUnionTypeName ( true , unionTypeNames , null ) , interfaceTypeName : getTypeNameOrUnionTypeName ( true , unionInterfaceTypeNames , null ) , isUnionType : true , unionTypeNames , unionInterfaceTypeNames , namespace , fullNamespace } ; return result ; } } function getRefTypeProperties ( ref , options , isEnum ) { let type = removeDefinitionsRef ( ref ) ; let typeName = getTypeName ( type , options ) ; const interfaceTypeName = isEnum ? typeName : `I${ typeName }` ; const namespace = getNamespace ( type , options , true ) ; const fullNamespace = getNamespace ( type , options , false ) ; if ( getIsGenericType ( typeName ) ) { let genericTType = getGenericTType ( typeName ) ; if ( genericTType && genericTType === 'System.DateTime' ) { typeName = typeName . replace ( genericTType , 'Date' ) ; } } return { typeName , interfaceTypeName , namespace , fullNamespace } ; } function removeDefinitionsRef ( value ) { let result = value . replace ( '#/components/schemas/' , '' ) ; return result ; } function getTypeNameOrUnionTypeName ( isUnionType , unionTypeNames , typeName ) { return isUnionType ? unionTypeNames . join ( ' | ' ) : typeName ; } function getFileName ( type , options , fileSuffix ) { let typeName = removeGenericTType ( getTypeName ( type , options ) ) ; return `${ kebabCase ( typeName ) }${ fileSuffix }` ; } function getTypeName ( type , options ) { let typeName ; if ( getIsGenericType ( type ) ) { let startGenericT = type . indexOf ( '[' ) ; let startGenericType = type . lastIndexOf ( '.' , startGenericT ) + 1 ; typeName = type . substring ( startGenericType ) ; typeName = convertGenericTypeName ( typeName ) ; typeName = getTypeNameWithoutSuffixesToRemove ( typeName , options ) ; } else { typeName = type . split ( '.' ) . pop ( ) ; typeName = getTypeNameWithoutSuffixesToRemove ( typeName , options ) ; if ( typeName === 'Object' ) { typeName = 'SystemObject' ; } } return upperFirst ( typeName ) ; } function getTypeNameWithoutSuffixesToRemove ( typeName , options ) { if ( ! options . typeNameSuffixesToRemove ) { return typeName ; } let typeNameSuffixesToRemove = options . typeNameSuffixesToRemove ; typeNameSuffixesToRemove . forEach ( item => { if ( endsWith ( typeName , item ) ) { typeName = typeName . slice ( 0 , - item . length ) ; } } ) ; return typeName ; } function getIsGenericType ( type ) { return type . indexOf ( '[' ) !== - 1 || type . indexOf ( '<' ) !== - 1 ; } /**
 * NullableOrEmpty<System.Date> -> System.Data
 */ function getGenericTType ( type ) { if ( getIsGenericType ( type ) ) { return /\<(.*)\>/ . exec ( type ) [ 1 ] ; } return undefined ; } /**
 * NullableOrEmpty<System.Date> -> NullableOrEmpty
 */ function removeGenericTType ( type ) { if ( getIsGenericType ( type ) ) { type = type . substring ( 0 , type . indexOf ( '<' ) ) ; } return type ; } /**
 * NullableOrEmpty[System.Date] -> NullableOrEmpty<System.Date>
 */ function convertGenericTypeName ( typeName ) { return typeName . replace ( '[' , '<' ) . replace ( ']' , '>' ) ; } /**
 * NullableOrEmpty<System.Date> -> NullableOrEmpty<T>
 */ function convertGenericToGenericT ( typeName ) { return typeName . replace ( /\<.*\>/ , '<T>' ) ; } function getIsSubType ( item ) { return item . allOf !== undefined ; } function getHasSubTypeProperty ( properties , options ) { return has ( properties , options . subTypePropertyName ) ; } function getBaseType ( subTypeName , typeCollection , item , options , logErrorForMissingBaseType ) { let type = removeDefinitionsRef ( item . allOf [ 0 ] . $ref ) ; let typeName = getTypeName ( type , options ) ; let baseType = findTypeInTypeCollection ( typeCollection , typeName ) ; if ( ! baseType && logErrorForMissingBaseType ) { logError ( `baseType ${ typeName } of ${ subTypeName } not found` ) ; } return baseType ; } function findTypesInTypeCollection ( typeCollection , typeName , isUnionType , unionTypeNames ) { if ( isUnionType ) { return unionTypeNames . map ( item => findTypeInTypeCollection ( typeCollection , item ) ) ; } return findTypeInTypeCollection ( typeCollection , typeName ) ; } function findTypeInTypeCollection ( typeCollection , typeName ) { let result = find ( typeCollection , type => { return type . typeName === typeName ; } ) ; return result ; } function getSubTypeProperties ( item , baseType ) { if ( ! isEmpty ( item . allOf ) && item . allOf . length > 1 ) { return item . allOf [ 1 ] . properties ; } return { } ; } function getSubTypeRequired ( item ) { if ( ! isEmpty ( item . allOf ) && item . allOf . length > 1 ) { return ( item . allOf [ 1 ] . required as string [ ] ) || [ ] ; } return [ ] ; } function getIsEnumType ( item ) { return ! ! ( item && item . enum ) ; } function getIsEnumRefType ( swagger , item , isArray ) { let refItemName ; if ( isArray ) { if ( item . items . $ref ) { refItemName = removeDefinitionsRef ( item . items . $ref ) ; } } else if ( item . oneOf ) { const firstOneOf = first ( item . oneOf ) as Schema ; refItemName = removeDefinitionsRef ( firstOneOf . $ref ) ; } else { refItemName = removeDefinitionsRef ( item . $ref ) ; } let refItem = swagger . components . schemas [ refItemName ] ; return getIsEnumType ( refItem ) ; } function getNamespace ( type , options , removePrefix ) { let typeName = removePrefix ? getTypeNameWithoutNamespacePrefixesToRemove ( type , options ) : type ; if ( getIsGenericType ( typeName ) ) { let first = typeName . substring ( 0 , typeName . indexOf ( '[' ) ) ; typeName = first + first . substring ( typeName . indexOf ( ']' ) ) ; } let parts = typeName . split ( '.' ) ; parts . pop ( ) ; return parts . join ( '.' ) ; } function getImportTypes ( type , isUnionType , unionTypeNames , isArray ) { if ( isUnionType ) { return unionTypeNames ; } if ( isArray ) { let result = removeGenericArray ( type , isArray ) ; return [ result ] ; } type = removeGenericTType ( type ) ; return [ type ] ; } function removeGenericArray ( type , isArray ) { if ( isArray ) { let result = type . replace ( 'Array<' , '' ) . replace ( '>' , '' ) ; return result ; } return type ; } function getImportFiles ( importTypes , namespace , pathToRoot , suffix ) { return importTypes . map ( item => getImportFile ( item , namespace , pathToRoot , suffix ) ) ; } function getImportFile ( importType , namespace , pathToRoot , suffix ) { let importPath = `${ kebabCase ( importType ) }${ suffix }` ; if ( namespace ) { let namespacePath = convertNamespaceToPath ( namespace ) ; importPath = `${ namespacePath }/${ importPath }` ; } return ( pathToRoot + importPath ) . toLocaleLowerCase ( ) ; } function getNamespaceGroups ( typeCollection , options ) { let namespaces = { [ ROOT_NAMESPACE ] : [ ] } ; for ( let i = 0 ; i < typeCollection . length ; ++ i ) { let type = typeCollection [ i ] ; let namespace = type . namespace || ROOT_NAMESPACE ; if ( excludeNamespace ( namespace , options . exclude ) ) { continue ; } if ( ! namespaces [ namespace ] ) { namespaces [ namespace ] = [ ] ; } namespaces [ namespace ] . push ( type ) ; } return namespaces ; } function excludeNamespace ( namespace , excludeOptions ) { if ( ! excludeOptions || ! excludeOptions . length ) { return false ; } for ( const excludeCheck of excludeOptions ) { if ( ( excludeCheck instanceof RegExp && excludeCheck . test ( namespace ) ) || ~ namespace . indexOf ( < string > excludeCheck ) ) { return true ; } } return false ; } function generateTSModels ( namespaceGroups , folder , options ) { let data = { generateClasses : options . generateClasses , hasComplexType : false , validatorFileName : removeExtension ( options . validatorsFileName ) , baseModelFileName : removeExtension ( options . baseModelFileName ) , subTypeFactoryFileName : removeExtension ( options . subTypeFactoryFileName ) , moduleName : options . modelModuleName , enumModuleName : options . enumModuleName , enumRef : options . enumRef , subTypePropertyName : options . subTypePropertyName , subTypePropertyConstantName : snakeCase ( options . subTypePropertyName ) . toUpperCase ( ) , type : undefined } ; let template = readAndCompileTemplateFile ( options . templates . models ) ; ensureFolder ( folder ) ; for ( let namespace in namespaceGroups ) { let typeCol = namespaceGroups [ namespace ] ; let firstType = typeCol [ 0 ] || < Type > { namespace : '' } ; let namespacePath = convertNamespaceToPath ( firstType . namespace ) ; let typeFolder = `${ folder }${ namespacePath }` ; let folderParts = namespacePath . split ( '/' ) ; let prevParts = folder ; folderParts . forEach ( part => { prevParts += part + '/' ; ensureFolder ( prevParts ) ; } ) ; let nrGeneratedFiles = 0 ; each ( typeCol , type => { let outputFileName = join ( typeFolder , type . fileName ) ; data . type = type ; data . hasComplexType = type . properties . some ( property => property . isComplexType ) ; let result = template ( data , { allowProtoPropertiesByDefault : true } ) ; let isChanged = writeFileIfContentsIsChanged ( outputFileName , result ) ; if ( isChanged ) { nrGeneratedFiles ++ ; } } ) ; log ( `generated ${ nrGeneratedFiles } type${ nrGeneratedFiles === 1 ? '' : 's' } in ${ typeFolder }` ) ; removeFilesOfNonExistingTypes ( typeCol , typeFolder , options , MODEL_FILE_SUFFIX ) ; } let namespacePaths = Object . keys ( namespaceGroups ) . map ( namespace => { return join ( folder , convertNamespaceToPath ( namespace ) ) ; } ) ; cleanFoldersForObsoleteFiles ( folder , namespacePaths ) ; } function cleanFoldersForObsoleteFiles ( folder , namespacePaths ) { getDirectories ( folder ) . forEach ( name => { let folderPath = join ( folder , name ) ; let namespacePath = find ( namespacePaths , path => { return path . startsWith ( folderPath ) ; } ) ; if ( ! namespacePath ) { removeFolder ( folderPath ) ; log ( `removed obsolete folder ${ name } in ${ folder }` ) ; } else { cleanFoldersForObsoleteFiles ( folderPath , namespacePaths ) ; } } ) ; } function generateSubTypeFactory ( namespaceGroups , folder , options ) { let data = { subTypes : undefined , subTypePropertyName : options . subTypePropertyName } ; let template = readAndCompileTemplateFile ( options . templates . subTypeFactory ) ; for ( let key in namespaceGroups ) { data . subTypes = namespaceGroups [ key ] . filter ( type => { return type . hasSubTypeProperty ; } ) ; let namespacePath = namespaceGroups [ key ] [ 0 ] ? namespaceGroups [ key ] [ 0 ] . path : '' ; let outputFileName = join ( folder , options . subTypeFactoryFileName ) ; let result = template ( data ) ; let isChanged = writeFileIfContentsIsChanged ( outputFileName , result ) ; if ( isChanged ) { log ( `generated ${ outputFileName }` ) ; } } } function addRootFixedFileNames ( fileNames , options ) { let enumOutputFileName = normalize ( options . enumTSFile . split ( '/' ) . pop ( ) ) ; fileNames . splice ( 0 , 0 , removeExtension ( enumOutputFileName ) ) ; if ( options . generateClasses ) { let validatorsOutputFileName = normalize ( options . validatorsFileName ) ; fileNames . splice ( 0 , 0 , removeExtension ( validatorsOutputFileName ) ) ; } } function removeFilesOfNonExistingTypes ( typeCollection , folder , options , suffix ) { let counter = 0 ; let files = readdirSync ( folder ) ; each ( files , file => { if ( endsWith ( file , suffix ) && ! find ( typeCollection , type => { return type . fileName == file ; } ) ) { counter ++ ; unlinkSync ( join ( folder , file ) ) ; log ( `removed ${ file } in ${ folder }` ) ; } } ) ; if ( counter > 0 ) { log ( `removed ${ counter } types in ${ folder }` ) ; } }