@@ -5,32 +5,9 @@ import { CannedMetricsModule } from './canned-metrics';
55import { CDK_CORE , CONSTRUCTS , ModuleImportLocations } from './cdk' ;
66import { SelectiveImport } from './relationship-decider' ;
77import { ResourceClass } from './resource-class' ;
8+ import { FilePatternValues , TsFileWriter , substituteFilePattern } from '../util' ;
89
9- /**
10- * A module containing a single resource
11- */
12- export class ResourceModule extends Module {
13- public constructor ( public readonly service : string , public readonly resource : string ) {
14- super ( `@aws-cdk/${ service } /${ resource } -l1` ) ;
15- }
16- }
17-
18- /**
19- * A module containing a service
20- */
21- export class ServiceModule extends Module {
22- public constructor ( public readonly service : string , public readonly shortName : string ) {
23- super ( `@aws-cdk/${ service } ` ) ;
24- }
25- }
26-
27- export interface AstBuilderProps {
28- readonly db : SpecDatabase ;
29- /**
30- * Override the locations modules are imported from
31- */
32- readonly importLocations ?: ModuleImportLocations ;
33-
10+ export interface AddServiceProps {
3411 /**
3512 * Append a suffix at the end of generated names.
3613 */
@@ -42,82 +19,126 @@ export interface AstBuilderProps {
4219 * @default - not deprecated
4320 */
4421 readonly deprecated ?: string ;
45- }
4622
47- export class AstBuilder < T extends Module > {
4823 /**
49- * Build a module for all resources in a service
24+ * The target resource module we want to generate these resources into
25+ *
26+ * (Practically, only used to render CloudFormation resources into the `core` module.)
5027 */
51- public static forService ( service : Service , props : AstBuilderProps ) : AstBuilder < ServiceModule > {
52- const scope = new ServiceModule ( service . name , service . shortName ) ;
53- const aug = new AugmentationsModule ( props . db , service . name , props . importLocations ?. cloudwatch ) ;
54- const metrics = CannedMetricsModule . forService ( props . db , service ) ;
28+ readonly destinationModule ?: string ;
5529
56- const ast = new AstBuilder ( scope , props , aug , metrics ) ;
30+ /**
31+ * Override the locations modules are imported from
32+ */
33+ readonly importLocations ?: ModuleImportLocations ;
34+ }
5735
58- const resources = props . db . follow ( 'hasResource' , service ) ;
36+ export interface AstBuilderProps {
37+ readonly db : SpecDatabase ;
5938
60- for ( const link of resources ) {
61- ast . addResource ( link . entity ) ;
62- }
63- ast . renderImports ( ) ;
64- return ast ;
65- }
39+ readonly modulesRoot ?: string ;
40+ }
6641
42+ export interface GenerateFilePatterns {
6743 /**
68- * Build an module for a single resource
44+ * The pattern used to name resource files.
45+ * @default "%serviceName%/%serviceShortName%.generated.ts"
6946 */
70- public static forResource ( resource : Resource , props : AstBuilderProps ) : AstBuilder < ResourceModule > {
71- const parts = resource . cloudFormationType . toLowerCase ( ) . split ( '::' ) ;
72- const scope = new ResourceModule ( parts [ 1 ] , parts [ 2 ] ) ;
73- const aug = new AugmentationsModule ( props . db , parts [ 1 ] , props . importLocations ?. cloudwatch ) ;
74- const metrics = CannedMetricsModule . forResource ( props . db , resource ) ;
75-
76- const ast = new AstBuilder ( scope , props , aug , metrics ) ;
77- ast . addResource ( resource ) ;
78- ast . renderImports ( ) ;
47+ readonly resources : string ;
7948
80- return ast ;
81- }
49+ /**
50+ * The pattern used to name augmentations.
51+ * @default "%serviceName%/%serviceShortName%-augmentations.generated.ts"
52+ */
53+ readonly augmentations : string ;
8254
83- public readonly db : SpecDatabase ;
8455 /**
85- * Map of CloudFormation resource name to generated class name
56+ * The pattern used to name canned metrics.
57+ * @default "%serviceName%/%serviceShortName%-canned-metrics.generated.ts"
8658 */
87- public readonly resources : Record < string , string > = { } ;
88- private nameSuffix ?: string ;
89- private deprecated ?: string ;
59+ readonly cannedMetrics : string ;
60+ }
61+
62+ export const DEFAULT_FILE_PATTERNS : GenerateFilePatterns = {
63+ resources : '%serviceName%/%serviceShortName%.generated.ts' ,
64+ augmentations : '%serviceName%/%serviceShortName%-augmentations.generated.ts' ,
65+ cannedMetrics : '%serviceName%/%serviceShortName%-canned-metrics.generated.ts' ,
66+ } ;
67+
68+ export class AstBuilder {
69+ public readonly db : SpecDatabase ;
70+
9071 public readonly selectiveImports = new Array < SelectiveImport > ( ) ;
72+
73+ public readonly serviceModules = new Map < string , SubmoduleInfo > ( ) ;
9174 private readonly modulesRootLocation : string ;
9275
93- protected constructor (
94- public readonly module : T ,
76+ constructor (
9577 props : AstBuilderProps ,
96- public readonly augmentations ?: AugmentationsModule ,
97- public readonly cannedMetrics ?: CannedMetricsModule ,
9878 ) {
9979 this . db = props . db ;
100- this . nameSuffix = props . nameSuffix ;
101- this . deprecated = props . deprecated ;
102- this . modulesRootLocation = props . importLocations ?. modulesRoot ?? '../..' ;
103-
104- CDK_CORE . import ( this . module , 'cdk' , { fromLocation : props . importLocations ?. core } ) ;
105- CONSTRUCTS . import ( this . module , 'constructs' ) ;
106- CDK_CORE . helpers . import ( this . module , 'cfn_parse' , { fromLocation : props . importLocations ?. coreHelpers } ) ;
107- CDK_CORE . errors . import ( this . module , 'cdk_errors' , { fromLocation : props . importLocations ?. coreErrors } ) ;
80+ this . modulesRootLocation = props . modulesRoot ?? '../..' ;
10881 }
10982
110- public addResource ( resource : Resource ) {
111- const resourceClass = new ResourceClass ( this . module , this . db , resource , {
112- suffix : this . nameSuffix ,
113- deprecated : this . deprecated ,
83+ /**
84+ * Add all resources in a service
85+ */
86+ public addService ( service : Service , props ?: AddServiceProps ) {
87+ const resources = this . db . follow ( 'hasResource' , service ) ;
88+ const submod = this . createSubmodule ( service , props ?. destinationModule , props ?. importLocations ) ;
89+
90+ for ( const { entity : resource } of resources ) {
91+ this . addResourceToSubmodule ( submod , resource , props ) ;
92+ }
93+
94+ this . renderImports ( submod ) ;
95+ return submod ;
96+ }
97+
98+ /**
99+ * Build an module for a single resource (only used for testing)
100+ */
101+ public addResource ( resource : Resource , props ?: AddServiceProps ) {
102+ const service = this . db . incoming ( 'hasResource' , resource ) . only ( ) . entity ;
103+ const submod = this . createSubmodule ( service , props ?. destinationModule , props ?. importLocations ) ;
104+
105+ this . addResourceToSubmodule ( submod , resource , props ) ;
106+
107+ this . renderImports ( submod ) ;
108+ return submod ;
109+ }
110+
111+ public writeAll ( writer : TsFileWriter , filePatterns : GenerateFilePatterns ) {
112+ for ( const mods of this . serviceModules . values ( ) ) {
113+ const pattern : FilePatternValues = {
114+ serviceName : mods . service . name ,
115+ serviceShortName : mods . service . shortName ,
116+ moduleName : mods . moduleName ,
117+ } ;
118+
119+ writer . write ( mods . resourceModule , substituteFilePattern ( filePatterns . resources , pattern ) ) ;
120+
121+ if ( mods . augmentationsModule . hasAugmentations ) {
122+ writer . write ( mods . augmentationsModule , substituteFilePattern ( filePatterns . augmentations , pattern ) ) ;
123+ }
124+
125+ if ( mods . cannedMetricsModule . hasCannedMetrics ) {
126+ writer . write ( mods . cannedMetricsModule , substituteFilePattern ( filePatterns . cannedMetrics , pattern ) ) ;
127+ }
128+ }
129+ }
130+
131+ private addResourceToSubmodule ( submodule : SubmoduleInfo , resource : Resource , props ?: AddServiceProps ) {
132+ const resourceClass = new ResourceClass ( submodule . resourceModule , this . db , resource , {
133+ suffix : props ?. nameSuffix ,
134+ deprecated : props ?. deprecated ,
114135 } ) ;
115- this . resources [ resource . cloudFormationType ] = resourceClass . spec . name ;
136+ submodule . resources [ resource . cloudFormationType ] = resourceClass . spec . name ;
116137
117138 resourceClass . build ( ) ;
118139
119140 this . addImports ( resourceClass ) ;
120- this . augmentations ? .augmentResource ( resource , resourceClass ) ;
141+ submodule . augmentationsModule . augmentResource ( resource , resourceClass ) ;
121142 }
122143
123144 private addImports ( resourceClass : ResourceClass ) {
@@ -140,11 +161,61 @@ export class AstBuilder<T extends Module> {
140161 }
141162 }
142163
143- public renderImports ( ) {
164+ private renderImports ( serviceModules : SubmoduleInfo ) {
144165 const sortedImports = this . selectiveImports . sort ( ( a , b ) => a . moduleName . localeCompare ( b . moduleName ) ) ;
145166 for ( const selectiveImport of sortedImports ) {
146167 const sourceModule = new Module ( selectiveImport . moduleName ) ;
147- sourceModule . importSelective ( this . module , selectiveImport . types . map ( ( t ) => `${ t . originalType } as ${ t . aliasedType } ` ) , { fromLocation : `${ this . modulesRootLocation } /${ sourceModule . name } ` } ) ;
168+ sourceModule . importSelective ( serviceModules . resourceModule , selectiveImport . types . map ( ( t ) => `${ t . originalType } as ${ t . aliasedType } ` ) , {
169+ fromLocation : `${ this . modulesRootLocation } /${ sourceModule . name } ` ,
170+ } ) ;
148171 }
149172 }
173+
174+ private createSubmodule ( service : Service , targetServiceModule ?: string , importLocations ?: ModuleImportLocations ) : SubmoduleInfo {
175+ const moduleName = targetServiceModule ?? service . name ;
176+
177+ const mods = this . serviceModules . get ( moduleName ) ;
178+ if ( mods ) {
179+ // eslint-disable-next-line @cdklabs/no-throw-default-error
180+ throw new Error ( `A submodule named ${ moduleName } was already created` ) ;
181+ }
182+
183+ const resourceModule = new Module ( `@aws-cdk/${ moduleName } ` ) ;
184+ CDK_CORE . import ( resourceModule , 'cdk' , { fromLocation : importLocations ?. core } ) ;
185+ CONSTRUCTS . import ( resourceModule , 'constructs' ) ;
186+ CDK_CORE . helpers . import ( resourceModule , 'cfn_parse' , { fromLocation : importLocations ?. coreHelpers } ) ;
187+ CDK_CORE . errors . import ( resourceModule , 'cdk_errors' , { fromLocation : importLocations ?. coreErrors } ) ;
188+
189+ const augmentationsModule = new AugmentationsModule ( this . db , service . shortName , importLocations ?. cloudwatch ) ;
190+ const cannedMetricsModule = CannedMetricsModule . forService ( this . db , service ) ;
191+
192+ const ret : SubmoduleInfo = {
193+ service,
194+ moduleName,
195+ resourceModule,
196+ augmentationsModule,
197+ cannedMetricsModule,
198+ resources : { } ,
199+ } ;
200+ this . serviceModules . set ( moduleName , ret ) ;
201+ return ret ;
202+ }
203+ }
204+
205+ export interface SubmoduleInfo {
206+ readonly service : Service ;
207+
208+ /**
209+ * The name of the submodule of aws-cdk-lib
210+ */
211+ readonly moduleName : string ;
212+
213+ readonly resourceModule : Module ;
214+ readonly augmentationsModule : AugmentationsModule ;
215+ readonly cannedMetricsModule : CannedMetricsModule ;
216+
217+ /**
218+ * Map of CloudFormation resource name to generated class name
219+ */
220+ readonly resources : Record < string , string > ;
150221}
0 commit comments