@@ -22,6 +22,8 @@ const DATASOURCE = 'datasources';
2222const SERVICE = 'services' ;
2323const g = require ( '../../lib/globalize' ) ;
2424const json5 = require ( 'json5' ) ;
25+ const fs = require ( 'fs' ) ;
26+ const { Project, SyntaxKind} = require ( 'ts-morph' ) ;
2527
2628const isWindows = process . platform === 'win32' ;
2729
@@ -87,11 +89,30 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
8789 type : Boolean ,
8890 } ) ;
8991
92+ this . option ( 'outDir' , {
93+ description : g . f ( 'Custom output directory for generated files.' ) ,
94+ required : false ,
95+ default : 'src' ,
96+ type : String ,
97+ } ) ;
98+
9099 return super . _setupGenerator ( ) ;
91100 }
92101
93102 setOptions ( ) {
94- return super . setOptions ( ) ;
103+ const result = super . setOptions ( ) ;
104+ if ( this . options . config ) {
105+ const config =
106+ typeof this . options . config === 'string'
107+ ? JSON . parse ( this . options . config )
108+ : this . options . config ;
109+ if ( config . outDir ) this . options . outDir = config . outDir ;
110+ if ( config . url ) this . options . url = config . url ;
111+ if ( config . prefix ) this . options . prefix = config . prefix ;
112+ if ( config . client !== undefined ) this . options . client = config . client ;
113+ if ( config . server !== undefined ) this . options . server = config . server ;
114+ }
115+ return result ;
95116 }
96117
97118 checkLoopBackProject ( ) {
@@ -241,6 +262,11 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
241262 log : this . log ,
242263 validate : this . options . validate ,
243264 promoteAnonymousSchemas : this . options [ 'promote-anonymous-schemas' ] ,
265+ prefix :
266+ this . options . outDir && this . options . outDir !== 'src'
267+ ? ''
268+ : this . options . prefix ,
269+ previousPrefix : this . options . previousPrefix || '' ,
244270 } ) ;
245271 debugJson ( 'OpenAPI spec' , result . apiSpec ) ;
246272 Object . assign ( this , result ) ;
@@ -251,6 +277,13 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
251277
252278 async selectControllers ( ) {
253279 if ( this . shouldExit ( ) ) return ;
280+ this . controllerSpecs = this . controllerSpecs . map ( c => {
281+ if ( this . options . prefix && c . tag && c . tag . includes ( this . options . prefix ) ) {
282+ const splited = c . tag . split ( this . options . prefix ) ;
283+ c . tag = splited . join ( '' ) ;
284+ }
285+ return c ;
286+ } ) ;
254287 const choices = this . controllerSpecs . map ( c => {
255288 const names = [ ] ;
256289 if ( this . options . server !== false ) {
@@ -287,8 +320,14 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
287320 ) ;
288321 this . selectedServices = this . selectedControllers ;
289322 this . selectedControllers . forEach ( c => {
290- c . fileName = getControllerFileName ( c . tag || c . className ) ;
291- c . serviceFileName = getServiceFileName ( c . tag || c . serviceClassName ) ;
323+ const originalClassName = c . className ;
324+ const originalServiceClassName = c . serviceClassName ;
325+
326+ c . fileName = getControllerFileName ( c . tag || originalClassName ) ;
327+ if ( this . options . prefix ) {
328+ c . fileName = this . options . prefix . toLowerCase ( ) + '.' + c . fileName ;
329+ }
330+ c . serviceFileName = getServiceFileName ( c . tag || originalServiceClassName ) ;
292331 } ) ;
293332 }
294333
@@ -302,7 +341,10 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
302341 if ( debug . enabled ) {
303342 debug ( `Artifact output filename set to: ${ controllerFile } ` ) ;
304343 }
305- const dest = this . destinationPath ( `src/controllers/${ controllerFile } ` ) ;
344+ const outDir = this . options . outDir || 'src' ;
345+ const dest = this . destinationPath (
346+ `${ outDir } /controllers/${ controllerFile } ` ,
347+ ) ;
306348 if ( debug . enabled ) {
307349 debug ( 'Copying artifact to: %s' , dest ) ;
308350 }
@@ -349,7 +391,10 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
349391 if ( debug . enabled ) {
350392 debug ( `Artifact output filename set to: ${ dataSourceFile } ` ) ;
351393 }
352- const dest = this . destinationPath ( `src/datasources/${ dataSourceFile } ` ) ;
394+ const outDir = this . options . outDir || 'src' ;
395+ const dest = this . destinationPath (
396+ `${ outDir } /datasources/${ dataSourceFile } ` ,
397+ ) ;
353398 if ( debug . enabled ) {
354399 debug ( 'Copying artifact to: %s' , dest ) ;
355400 }
@@ -372,7 +417,8 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
372417 if ( debug . enabled ) {
373418 debug ( `Artifact output filename set to: ${ file } ` ) ;
374419 }
375- const dest = this . destinationPath ( `src/services/${ file } ` ) ;
420+ const outDir = this . options . outDir || 'src' ;
421+ const dest = this . destinationPath ( `${ outDir } /services/${ file } ` ) ;
376422 if ( debug . enabled ) {
377423 debug ( 'Copying artifact to: %s' , dest ) ;
378424 }
@@ -396,7 +442,8 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
396442 if ( debug . enabled ) {
397443 debug ( `Artifact output filename set to: ${ modelFile } ` ) ;
398444 }
399- const dest = this . destinationPath ( `src/models/${ modelFile } ` ) ;
445+ const outDir = this . options . outDir || 'src' ;
446+ const dest = this . destinationPath ( `${ outDir } /models/${ modelFile } ` ) ;
400447 if ( debug . enabled ) {
401448 debug ( 'Copying artifact to: %s' , dest ) ;
402449 }
@@ -408,7 +455,8 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
408455 // update index file for models and controllers
409456 async _updateIndex ( dir ) {
410457 const update = async files => {
411- const targetDir = this . destinationPath ( `src/${ dir } ` ) ;
458+ const outDir = this . options . outDir || 'src' ;
459+ const targetDir = this . destinationPath ( `${ outDir } /${ dir } ` ) ;
412460 for ( const f of files ) {
413461 // Check all files being generated to ensure they succeeded
414462 const status = this . conflicter . generationStatus [ f ] ;
@@ -490,6 +538,9 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
490538 this . _generateServiceProxies ( ) ;
491539 await this . _updateIndex ( SERVICE ) ;
492540 }
541+ if ( this . options . outDir && this . options . outDir !== 'src' ) {
542+ await this . _updateBootOptions ( ) ;
543+ }
493544 }
494545
495546 install ( ) {
@@ -525,6 +576,83 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
525576 } ) ;
526577 }
527578
579+ async _updateBootOptions ( ) {
580+ const invokedFrom = this . destinationRoot ( ) ;
581+ const applicationPath = path . join ( invokedFrom , 'src' , 'application.ts' ) ;
582+ if ( ! fs . existsSync ( applicationPath ) ) return ;
583+
584+ const relDir = ( this . options . outDir || 'src' ) . replace ( / ^ s r c [ \\ \\ / ] / , '' ) ;
585+ const artifactTypes = [
586+ {
587+ name : 'controllers' ,
588+ dir : `${ relDir } /controllers` ,
589+ extension : '.controller.js' ,
590+ } ,
591+ {
592+ name : 'datasources' ,
593+ dir : `${ relDir } /datasources` ,
594+ extension : '.datasource.js' ,
595+ } ,
596+ { name : 'models' , dir : `${ relDir } /models` , extension : '.model.js' } ,
597+ { name : 'services' , dir : `${ relDir } /services` , extension : '.service.js' } ,
598+ ] ;
599+
600+ const project = new Project ( { } ) ;
601+ project . addSourceFilesAtPaths ( `${ invokedFrom } /src/**/*.ts` ) ;
602+ const applicationFile = project . getSourceFileOrThrow ( applicationPath ) ;
603+ const constructor = applicationFile . getClasses ( ) [ 0 ] . getConstructors ( ) [ 0 ] ;
604+ const bootOptionsObject = constructor
605+ . getDescendantsOfKind ( SyntaxKind . BinaryExpression )
606+ . find ( expr => expr . getLeft ( ) . getText ( ) === 'this.bootOptions' )
607+ ?. getRight ( )
608+ . asKindOrThrow ( SyntaxKind . ObjectLiteralExpression ) ;
609+ this . log ( 'bootOptions found: ' + ! ! bootOptionsObject ) ;
610+
611+ if ( bootOptionsObject ) {
612+ for ( const artifact of artifactTypes ) {
613+ let artifactProperty = bootOptionsObject . getProperty ( artifact . name ) ;
614+ if ( ! artifactProperty ) {
615+ bootOptionsObject . addPropertyAssignment ( {
616+ name : artifact . name ,
617+ initializer : `{
618+ dirs: ['${ artifact . name } '],
619+ extensions: ['${ artifact . extension } '],
620+ nested: true,
621+ }` ,
622+ } ) ;
623+ artifactProperty = bootOptionsObject . getProperty ( artifact . name ) ;
624+ }
625+ if ( ! artifactProperty ) continue ;
626+
627+ const artifactObject = artifactProperty . getInitializerIfKindOrThrow (
628+ SyntaxKind . ObjectLiteralExpression ,
629+ ) ;
630+ let dirsProperty = artifactObject . getProperty ( 'dirs' ) ;
631+ if ( ! dirsProperty ) {
632+ artifactObject . addPropertyAssignment ( {
633+ name : 'dirs' ,
634+ initializer : `['${ artifact . name } ']` ,
635+ } ) ;
636+ dirsProperty = artifactObject . getProperty ( 'dirs' ) ;
637+ }
638+ if ( ! dirsProperty ) continue ;
639+
640+ const dirsArray = dirsProperty . getInitializerIfKindOrThrow (
641+ SyntaxKind . ArrayLiteralExpression ,
642+ ) ;
643+ const exists = dirsArray . getElements ( ) . some ( el => {
644+ const literal = el . asKind ( SyntaxKind . StringLiteral ) ;
645+ return literal ?. getLiteralValue ( ) === artifact . dir ;
646+ } ) ;
647+ if ( ! exists ) {
648+ dirsArray . addElement ( `'${ artifact . dir } '` ) ;
649+ }
650+ }
651+ applicationFile . formatText ( ) ;
652+ applicationFile . saveSync ( ) ;
653+ }
654+ }
655+
528656 async end ( ) {
529657 await super . end ( ) ;
530658 if ( this . shouldExit ( ) ) return ;
0 commit comments