@cdktn/provider-generator 0.24.0-pre.5 → 0.24.0-pre.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.spec.swcrc +22 -0
  2. package/LICENSE +355 -0
  3. package/README.md +1 -1
  4. package/build/__tests__/edge-provider-schema/cli.js +8 -3
  5. package/build/get/__tests__/generator/import-style.test.d.ts +2 -0
  6. package/build/get/__tests__/generator/import-style.test.js +101 -0
  7. package/build/get/__tests__/generator/module-generator.test.js +12 -12
  8. package/build/get/constructs-maker.d.ts +5 -1
  9. package/build/get/constructs-maker.js +14 -6
  10. package/build/get/generator/emitter/struct-emitter.d.ts +2 -1
  11. package/build/get/generator/emitter/struct-emitter.js +14 -11
  12. package/build/get/generator/module-generator.js +3 -3
  13. package/build/get/generator/provider-generator.d.ts +9 -1
  14. package/build/get/generator/provider-generator.js +9 -6
  15. package/jest.config.js +16 -9
  16. package/package.json +24 -23
  17. package/package.sh +1 -1
  18. package/src/__tests__/__snapshots__/edge-provider-schema.test.ts.snap +8 -8
  19. package/src/__tests__/__snapshots__/provider.test.ts.snap +2951 -2951
  20. package/src/__tests__/edge-provider-schema/builder.ts +185 -0
  21. package/src/__tests__/edge-provider-schema/cli.ts +51 -0
  22. package/src/__tests__/edge-provider-schema/index.ts +161 -0
  23. package/src/__tests__/edge-provider-schema.test.ts +24 -0
  24. package/src/__tests__/provider.test.ts +180 -0
  25. package/src/get/__tests__/constructs-maker.test.ts +118 -0
  26. package/src/get/__tests__/generator/__snapshots__/complex-computed-types.test.ts.snap +5 -5
  27. package/src/get/__tests__/generator/__snapshots__/export-sharding.test.ts.snap +3310 -3310
  28. package/src/get/__tests__/generator/__snapshots__/module-generator.test.ts.snap +355 -355
  29. package/src/get/__tests__/generator/__snapshots__/nested-types.test.ts.snap +8 -8
  30. package/src/get/__tests__/generator/__snapshots__/provider.test.ts.snap +8 -8
  31. package/src/get/__tests__/generator/__snapshots__/resource-types.test.ts.snap +126 -126
  32. package/src/get/__tests__/generator/__snapshots__/skipped-attributes.test.ts.snap +17 -17
  33. package/src/get/__tests__/generator/__snapshots__/types.test.ts.snap +65 -65
  34. package/src/get/__tests__/generator/complex-computed-types.test.ts +28 -0
  35. package/src/get/__tests__/generator/deep-nested-attributes.test.ts +56 -0
  36. package/src/get/__tests__/generator/description-escaping.test.ts +84 -0
  37. package/src/get/__tests__/generator/empty-provider-resources.test.ts +26 -0
  38. package/src/get/__tests__/generator/export-sharding.test.ts +169 -0
  39. package/src/get/__tests__/generator/import-style.test.ts +129 -0
  40. package/src/get/__tests__/generator/module-generator.test.ts +528 -0
  41. package/src/get/__tests__/generator/nested-types.test.ts +54 -0
  42. package/src/get/__tests__/generator/provider.test.ts +51 -0
  43. package/src/get/__tests__/generator/resource-types.test.ts +118 -0
  44. package/src/get/__tests__/generator/skipped-attributes.test.ts +72 -0
  45. package/src/get/__tests__/generator/types.test.ts +611 -0
  46. package/src/get/__tests__/generator/versions-file.test.ts +72 -0
  47. package/src/get/__tests__/util.ts +75 -0
  48. package/src/get/constructs-maker.ts +822 -0
  49. package/src/get/generator/custom-defaults.ts +493 -0
  50. package/src/get/generator/emitter/attributes-emitter.ts +225 -0
  51. package/src/get/generator/emitter/index.ts +5 -0
  52. package/src/get/generator/emitter/resource-emitter.ts +226 -0
  53. package/src/get/generator/emitter/struct-emitter.ts +683 -0
  54. package/src/get/generator/loop-detection.ts +81 -0
  55. package/src/get/generator/models/attribute-model.ts +216 -0
  56. package/src/get/generator/models/attribute-type-model.ts +448 -0
  57. package/src/get/generator/models/index.ts +7 -0
  58. package/src/get/generator/models/resource-model.ts +161 -0
  59. package/src/get/generator/models/scope.ts +54 -0
  60. package/src/get/generator/models/struct.ts +116 -0
  61. package/src/get/generator/module-generator.ts +234 -0
  62. package/src/get/generator/provider-generator.ts +355 -0
  63. package/src/get/generator/resource-parser.ts +762 -0
  64. package/src/get/generator/sanitized-comments.ts +49 -0
  65. package/src/get/generator/skipped-attributes.ts +27 -0
  66. package/src/index.ts +40 -0
  67. package/src/util.ts +26 -0
@@ -0,0 +1,116 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { AttributeModel } from "./attribute-model";
4
+ import { downcaseFirst } from "../../../util";
5
+ export class Struct {
6
+ constructor(
7
+ public readonly name: string,
8
+ public readonly attributes: AttributeModel[],
9
+ public readonly isClass = false,
10
+ public readonly isAnonymous = false,
11
+ public isSingleItem = false,
12
+ public readonly nestingMode: string = "",
13
+ ) {}
14
+
15
+ public get assignableAttributes(): AttributeModel[] {
16
+ return this.attributes.filter((attribute) => attribute.isAssignable);
17
+ }
18
+
19
+ public get optionalAttributes(): AttributeModel[] {
20
+ return this.attributes.filter((attribute) => attribute.optional);
21
+ }
22
+
23
+ public get allOptional(): boolean {
24
+ return (
25
+ this.attributes.filter(
26
+ (attribute) => !attribute.optional && !attribute.computed,
27
+ ).length == 0
28
+ );
29
+ }
30
+
31
+ public get attributeType() {
32
+ return `${this.name}${this.allOptional ? " = {}" : ""}`;
33
+ }
34
+
35
+ public get assignable() {
36
+ return !this.isClass || this.assignableAttributes.length > 0;
37
+ }
38
+
39
+ public get extends(): string {
40
+ return "";
41
+ }
42
+
43
+ public get mapperName(): string {
44
+ return `${downcaseFirst(this.name)}ToTerraform`;
45
+ }
46
+
47
+ public get hclMapperName(): string {
48
+ return `${downcaseFirst(this.name)}ToHclTerraform`;
49
+ }
50
+
51
+ public get outputReferenceName(): string {
52
+ return `${this.name}OutputReference`;
53
+ }
54
+
55
+ public get listName(): string {
56
+ return `${this.name}List`;
57
+ }
58
+
59
+ public get mapName(): string {
60
+ return `${this.name}Map`;
61
+ }
62
+
63
+ public get mapListName(): string {
64
+ return `${this.name}MapList`;
65
+ }
66
+
67
+ public get listMapName(): string {
68
+ return `${this.name}ListMap`;
69
+ }
70
+
71
+ public get listListName(): string {
72
+ return `${this.name}ListList`;
73
+ }
74
+
75
+ public get isProvider(): boolean {
76
+ return this.attributes.some((att) => att.isProvider);
77
+ }
78
+
79
+ public get referencedTypes(): string[] {
80
+ const types: string[] = [];
81
+
82
+ this.attributes.forEach((att) => {
83
+ const attReferences = att.getReferencedTypes(false); // This may be a config struct, but still need the references in this context
84
+ if (attReferences) {
85
+ types.push(...attReferences);
86
+ }
87
+ });
88
+
89
+ return types;
90
+ }
91
+
92
+ public get exportCount(): number {
93
+ let count = 1; // self
94
+ count += 1; // toTerraform function
95
+
96
+ if (
97
+ this.nestingMode === "list" ||
98
+ this.nestingMode === "set" ||
99
+ this.nestingMode === "map"
100
+ ) {
101
+ count += 1; // output reference
102
+
103
+ if (!this.isSingleItem) {
104
+ count += 1; // complex collection
105
+ }
106
+ }
107
+
108
+ return count;
109
+ }
110
+ }
111
+
112
+ export class ConfigStruct extends Struct {
113
+ public get extends(): string {
114
+ return ` extends cdktn.TerraformMetaArguments`;
115
+ }
116
+ }
@@ -0,0 +1,234 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { CodeMaker, toCamelCase } from "codemaker";
4
+ import { AttributeModel } from "./models";
5
+ import { sanitizedComment } from "./sanitized-comments";
6
+ import { ConstructsMakerModuleTarget } from "@cdktn/commons";
7
+
8
+ export class ModuleGenerator {
9
+ constructor(
10
+ private readonly code: CodeMaker,
11
+ private readonly targets: ConstructsMakerModuleTarget[],
12
+ ) {
13
+ this.code.indentation = 2;
14
+
15
+ for (const target of this.targets) {
16
+ this.emitSubmodule(target);
17
+ }
18
+ }
19
+
20
+ private emitSubmodule(target: ConstructsMakerModuleTarget) {
21
+ const spec = target.spec;
22
+
23
+ if (!spec) {
24
+ throw new Error(`missing spec for ${target.fqn}`);
25
+ }
26
+
27
+ this.code.openFile(target.fileName);
28
+
29
+ this.code.line(`// generated by cdktn get`);
30
+ this.code.line(`// ${target.source}`);
31
+
32
+ this.code.line(
33
+ `import { TerraformModule, type TerraformModuleUserConfig } from 'cdktn';`,
34
+ );
35
+ this.code.line(`import { Construct } from 'constructs';`);
36
+
37
+ const baseName = this.code.toPascalCase(target.name.replace(/[-/.]/g, "_"));
38
+ const configType = `${baseName}Config`;
39
+
40
+ this.code.openBlock(
41
+ `export interface ${configType} extends TerraformModuleUserConfig`,
42
+ );
43
+ for (const input of spec.inputs) {
44
+ const optional = input.required && input.default === undefined ? "" : "?";
45
+
46
+ const comment = sanitizedComment(this.code);
47
+ if (input.description) {
48
+ comment.line(input.description);
49
+ }
50
+ if (input.default) {
51
+ comment.line(input.default);
52
+ }
53
+ if (input.type.includes("map(")) {
54
+ comment.line(
55
+ `The property type contains a map, they have special handling, please see {@link cdk.tf/module-map-inputs the docs}`,
56
+ );
57
+ }
58
+ comment.end();
59
+
60
+ this.code.line(
61
+ `readonly ${AttributeModel.escapeName(
62
+ toCamelCase(input.name),
63
+ )}${optional}: ${parseType(input.type)};`,
64
+ );
65
+ }
66
+ this.code.closeBlock();
67
+
68
+ // Add a link to the Terraform Registry if it is sourced from there
69
+ // https://developer.hashicorp.com/terraform/language/modules/sources
70
+ // Registry modules are referred to as <NAMESPACE>/<NAME>/<PROVIDER>
71
+ // Other sources contain dots, e.g.
72
+ // app.terraform.io/example-corp/k8s-cluster/azurerm (private registries)
73
+ // github.com/hashicorp/example (Github)
74
+ // git@github.com:hashicorp/example.git
75
+ // bitbucket.org/hashicorp/terraform-consul-aws (Bitbucket)
76
+ // ... and more
77
+ // ../module and ./module (local paths)
78
+ const isNonRegistryModule = target.source.includes(".");
79
+ let registryPath;
80
+ // Submodules also exist (e.g. terraform-aws-modules/vpc/aws//modules/vpc-endpoints)
81
+ // And linking directly to them in the Registry requires including the version
82
+ // like e.g. https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest/submodules/vpc-endpoints
83
+ if (target.source.includes("//")) {
84
+ registryPath = target.source.replace(
85
+ "//modules",
86
+ `/${target.version || "latest"}/submodules`,
87
+ );
88
+ // terraform-aws-modules/vpc/aws//modules/vpc-endpoints
89
+ // ->
90
+ // terraform-aws-modules/vpc/aws/latest/submodules/vpc-endpoints
91
+ } else {
92
+ // not submodule specified, just append the version
93
+ registryPath = `${target.source}/${target.version || "latest"}`;
94
+ }
95
+
96
+ const comment = sanitizedComment(this.code);
97
+ comment.line(`Defines an ${baseName} based on a Terraform module`);
98
+ comment.line(``);
99
+ comment.line(
100
+ isNonRegistryModule
101
+ ? `Source at ${target.source}`
102
+ : `Docs at Terraform Registry: {@link https://registry.terraform.io/modules/${registryPath} ${target.source}}`,
103
+ );
104
+ comment.end();
105
+ this.code.openBlock(`export class ${baseName} extends TerraformModule`);
106
+
107
+ this.code.line(`private readonly inputs: { [name: string]: any } = { };`);
108
+
109
+ const allOptional = spec.inputs.find((x) => x.required) ? "" : " = {}";
110
+
111
+ this.code.open(
112
+ `public constructor(scope: Construct, id: string, config: ${configType}${allOptional}) {`,
113
+ );
114
+ this.code.open(`super(scope, id, {`);
115
+ this.code.line("...config,");
116
+ this.code.line(`source: '${target.source}',`);
117
+ if (target.version) {
118
+ this.code.line(`version: '${target.version}',`);
119
+ }
120
+ this.code.close(`});`);
121
+
122
+ for (const input of spec.inputs) {
123
+ const inputName = AttributeModel.escapeName(toCamelCase(input.name));
124
+ this.code.line(`this.${inputName} = config.${inputName};`);
125
+ }
126
+
127
+ this.code.close(`}`); // ctor
128
+
129
+ for (const input of spec.inputs) {
130
+ const inputName = AttributeModel.escapeName(toCamelCase(input.name));
131
+ const inputType =
132
+ parseType(input.type) +
133
+ (input.required && input.default === undefined ? "" : " | undefined");
134
+ this.code.openBlock(`public get ${inputName}(): ${inputType}`);
135
+ this.code.line(`return this.inputs['${input.name}'] as ${inputType};`);
136
+ this.code.closeBlock();
137
+
138
+ this.code.openBlock(`public set ${inputName}(value: ${inputType})`);
139
+ this.code.line(`this.inputs['${input.name}'] = value;`);
140
+ this.code.closeBlock();
141
+ }
142
+
143
+ for (const output of spec.outputs) {
144
+ const outputName = toCamelCase(output.name);
145
+ this.code.openBlock(`public get ${outputName}Output()`);
146
+ this.code.line(`return this.getString('${output.name}');`);
147
+ this.code.closeBlock();
148
+ }
149
+
150
+ this.code.openBlock(`protected synthesizeAttributes()`);
151
+ this.code.line(`return this.inputs;`);
152
+ this.code.closeBlock();
153
+
154
+ this.code.openBlock(
155
+ `protected synthesizeHclAttributes(): { [name: string]: any }`,
156
+ );
157
+ this.code.line(`return Object.fromEntries(`);
158
+ this.code.line(` Object.entries(this.inputs)`);
159
+ this.code.line(` .filter(([, val]) => val !== undefined)`);
160
+ this.code.line(` .map(([key, val]) => {`);
161
+ this.code.line(` return [`);
162
+ this.code.line(` key,`);
163
+ this.code.line(` {`);
164
+ this.code.line(` value: val,`);
165
+ this.code.line(` type: "any",`);
166
+ this.code.line(` },`);
167
+ this.code.line(` ];`);
168
+ this.code.line(` })`);
169
+ this.code.line(`);`);
170
+ this.code.closeBlock();
171
+
172
+ this.code.closeBlock(); // class
173
+ this.code.closeFile(target.fileName);
174
+ }
175
+ }
176
+
177
+ function parseType(type: string) {
178
+ if (type === "string") {
179
+ return "string";
180
+ }
181
+ if (type === "number") {
182
+ return "number";
183
+ }
184
+ if (type === "bool") {
185
+ return "boolean";
186
+ }
187
+ if (type === "list") {
188
+ return "string[]";
189
+ }
190
+ if (type === "map") {
191
+ return "{ [key: string]: string }";
192
+ }
193
+ if (type === "tuple") {
194
+ return "string[]";
195
+ }
196
+ if (type === "any") {
197
+ return "any";
198
+ }
199
+
200
+ const complexType = parseComplexType(type);
201
+ if (complexType) {
202
+ return complexType;
203
+ }
204
+
205
+ throw new Error(`unknown type ${type}`);
206
+ }
207
+
208
+ function parseComplexType(type: string): string | undefined {
209
+ const complex = /^(object|list|map|set|tuple)\(([\s\S]+)\)/;
210
+ const match = complex.exec(type);
211
+ if (!match) {
212
+ return undefined;
213
+ }
214
+
215
+ const [, kind, innerType] = match;
216
+
217
+ if (kind === "object") {
218
+ return `any`;
219
+ }
220
+
221
+ if (kind === "list" || kind === "set") {
222
+ return `${parseType(innerType)}[]`;
223
+ }
224
+
225
+ if (kind === "tuple") {
226
+ return `any[]`;
227
+ }
228
+
229
+ if (kind === "map") {
230
+ return `{ [key: string]: ${parseType(innerType)} }`;
231
+ }
232
+
233
+ throw new Error(`unexpected kind ${kind}`);
234
+ }
@@ -0,0 +1,355 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { CodeMaker, toCamelCase } from "codemaker";
4
+ import {
5
+ ConstructsMakerProviderTarget,
6
+ ConstructsMakerTarget,
7
+ LANGUAGES,
8
+ logger,
9
+ ProviderSchema,
10
+ TerraformProviderConstraint,
11
+ } from "@cdktn/commons";
12
+ import { FQPN, parseFQPN, ProviderName } from "@cdktn/provider-schema";
13
+ import { ResourceModel } from "./models";
14
+ import { ResourceParser } from "./resource-parser";
15
+ import { ResourceEmitter, StructEmitter } from "./emitter";
16
+
17
+ export interface TerraformProviderGeneratorOptions {
18
+ /**
19
+ * File extension to write on emitted relative imports.
20
+ * Defaults to "" (CommonJS).
21
+ */
22
+ readonly importExtension?: string;
23
+ }
24
+
25
+ interface ProviderData {
26
+ name: string;
27
+ source: string;
28
+ version: string;
29
+ }
30
+
31
+ const isMatching = (
32
+ target: ConstructsMakerTarget,
33
+ terraformSchemaName: string,
34
+ ): boolean => {
35
+ if (target.isModule) return false;
36
+
37
+ const elements = terraformSchemaName.split("/");
38
+
39
+ if (elements.length === 1) {
40
+ return target.source === terraformSchemaName;
41
+ } else {
42
+ const [hostname, namespace, provider] = elements;
43
+
44
+ if (!hostname || !namespace || !provider) {
45
+ throw new Error(`can't handle ${terraformSchemaName}`);
46
+ }
47
+
48
+ // If target.source is set, try to match it
49
+ if (target.source) {
50
+ const targetSource = {
51
+ // defaults
52
+ hostname: "registry.terraform.io",
53
+ namespace: "hashicorp",
54
+ name: "",
55
+ };
56
+ const targetElements = target.source.split("/");
57
+ switch (targetElements.length) {
58
+ case 1: // only name set
59
+ targetSource.name = targetElements[0].toLowerCase();
60
+ break;
61
+ case 2: // namespace and name set
62
+ targetSource.namespace = targetElements[0].toLowerCase();
63
+ targetSource.name = targetElements[1].toLowerCase();
64
+ break;
65
+ case 3: // hostname, namespace and name set
66
+ targetSource.hostname = targetElements[0].toLowerCase();
67
+ targetSource.namespace = targetElements[1].toLowerCase();
68
+ targetSource.name = targetElements[2].toLowerCase();
69
+ break;
70
+ default:
71
+ throw new Error(
72
+ `can't handle ${target.source}. Expected string with 1, 2 or 3 elements separated by '/'`,
73
+ );
74
+ }
75
+
76
+ return (
77
+ targetSource.hostname === hostname &&
78
+ targetSource.namespace === namespace &&
79
+ targetSource.name === provider
80
+ );
81
+ }
82
+
83
+ // Else, try to match target.name to the provider name
84
+
85
+ return target.name === provider;
86
+ }
87
+ };
88
+
89
+ export interface ProviderConstraints {
90
+ [fqn: string]: ProviderData;
91
+ }
92
+
93
+ export class TerraformProviderGenerator {
94
+ private resourceParser = new ResourceParser();
95
+ private resourceEmitter: ResourceEmitter;
96
+ private structEmitter: StructEmitter;
97
+ private readonly importExtension: string;
98
+ public versions: { [fqpn: string]: string | undefined } = {};
99
+
100
+ constructor(
101
+ private readonly code: CodeMaker,
102
+ private readonly schema: ProviderSchema,
103
+ options: TerraformProviderGeneratorOptions = {},
104
+ ) {
105
+ this.code.indentation = 2;
106
+ this.importExtension = options.importExtension ?? "";
107
+ this.resourceEmitter = new ResourceEmitter(this.code);
108
+ this.structEmitter = new StructEmitter(this.code, this.importExtension);
109
+ }
110
+
111
+ private getProviderByConstraint(
112
+ providerConstraint: ConstructsMakerTarget,
113
+ ): FQPN | undefined {
114
+ return Object.keys(this.schema.provider_schemas || {}).find((fqpn) =>
115
+ isMatching(providerConstraint, fqpn),
116
+ ) as FQPN | undefined;
117
+ }
118
+
119
+ public generate(providerConstraint: ConstructsMakerTarget) {
120
+ const fqpn = this.getProviderByConstraint(providerConstraint);
121
+ if (!fqpn) {
122
+ logger.debug(
123
+ `Could not find provider constraint for ${providerConstraint} in schema: ${JSON.stringify(
124
+ this.schema,
125
+ null,
126
+ 2,
127
+ )}`,
128
+ );
129
+ throw new Error(
130
+ `Could not find provider with constraint ${JSON.stringify(
131
+ providerConstraint,
132
+ )}`,
133
+ );
134
+ }
135
+
136
+ const providerVersion = this.schema.provider_versions?.[fqpn];
137
+ this.emitProvider(fqpn, providerVersion, providerConstraint);
138
+ this.versions[fqpn] = providerVersion;
139
+ }
140
+
141
+ public generateAll() {
142
+ for (const fqpn of Object.keys(this.schema.provider_schemas || {})) {
143
+ this.generate(
144
+ new ConstructsMakerProviderTarget(
145
+ new TerraformProviderConstraint(fqpn),
146
+ LANGUAGES[0],
147
+ ),
148
+ );
149
+ }
150
+ }
151
+
152
+ public async save(outdir: string) {
153
+ await this.code.save(outdir);
154
+ }
155
+
156
+ public buildResourceModels(
157
+ fqpn: FQPN,
158
+ constraint?: ConstructsMakerTarget,
159
+ ): ResourceModel[] {
160
+ const provider = this.schema.provider_schemas?.[fqpn];
161
+ if (!provider) {
162
+ throw new Error(`Can not find provider '${fqpn}' in schema`);
163
+ }
164
+
165
+ const resources = Object.entries(provider.resource_schemas || {}).map(
166
+ ([type, resource]) =>
167
+ this.resourceParser.parse(fqpn, type, resource, "resource", constraint),
168
+ );
169
+
170
+ const dataSources = Object.entries(provider.data_source_schemas || {}).map(
171
+ ([type, resource]) =>
172
+ this.resourceParser.parse(
173
+ fqpn,
174
+ `data_${type}`,
175
+ resource,
176
+ "data_source",
177
+ constraint,
178
+ ),
179
+ );
180
+
181
+ return ([] as ResourceModel[]).concat(...resources, ...dataSources);
182
+ }
183
+
184
+ public getClassNameForResource(terraformType: string) {
185
+ return this.resourceParser.getClassNameForResource(terraformType);
186
+ }
187
+
188
+ public getNamespaceNameForResource(terraformType: string) {
189
+ return this.resourceParser.getNamespaceNameForResource(terraformType);
190
+ }
191
+
192
+ private emitProvider(
193
+ fqpn: FQPN,
194
+ providerVersion?: string,
195
+ constraint?: ConstructsMakerTarget,
196
+ ) {
197
+ const name = constraint?.name
198
+ ? (constraint.name as ProviderName)
199
+ : parseFQPN(fqpn).name;
200
+ const provider = this.schema.provider_schemas?.[fqpn];
201
+ if (!provider) {
202
+ throw new Error(`Can not find provider '${fqpn}' in schema`);
203
+ }
204
+
205
+ const files: string[] = [];
206
+ this.buildResourceModels(fqpn, constraint).forEach((resourceModel) => {
207
+ if (constraint) {
208
+ resourceModel.providerVersionConstraint = constraint.version;
209
+ resourceModel.terraformProviderSource = constraint.source;
210
+ }
211
+ resourceModel.providerVersion = providerVersion;
212
+
213
+ if (resourceModel.structsRequireSharding) {
214
+ files.push(this.emitResourceWithComplexStruct(resourceModel));
215
+ } else {
216
+ files.push(this.emitResource(resourceModel));
217
+ }
218
+ this.emitResourceReadme(resourceModel);
219
+ });
220
+
221
+ if (provider.provider) {
222
+ const providerResource = this.resourceParser.parse(
223
+ fqpn,
224
+ `provider`,
225
+ provider.provider,
226
+ "provider",
227
+ constraint,
228
+ );
229
+ if (constraint) {
230
+ providerResource.providerVersionConstraint = constraint.version;
231
+ providerResource.terraformProviderSource = constraint.source;
232
+ providerResource.terraformProviderName = constraint.name;
233
+ }
234
+ providerResource.providerVersion = providerVersion;
235
+ files.push(this.emitResource(providerResource));
236
+ this.emitResourceReadme(providerResource);
237
+ }
238
+
239
+ this.emitIndexFile(name, files);
240
+ this.emitLazyIndexFile(name, files);
241
+ }
242
+
243
+ private emitResourceReadme(resource: ResourceModel): void {
244
+ const filePath = `${resource.namespaceFolderPath}/README.md`;
245
+ this.code.openFile(filePath);
246
+ this.code.line(`# \`${resource.terraformType}\``);
247
+ this.code.line();
248
+ const type = resource.isProvider
249
+ ? resource.provider
250
+ : resource.terraformType;
251
+ this.code.line(
252
+ `Refer to the Terraform Registry for docs: [\`${type}\`](${resource.linkToDocs}).`,
253
+ );
254
+ this.code.closeFile(filePath);
255
+ }
256
+
257
+ private emitIndexFile(provider: ProviderName, files: string[]): void {
258
+ const folder = `providers/${provider}`;
259
+ const filePath = `${folder}/index.ts`;
260
+ this.code.openFile(filePath);
261
+ this.code.line("// generated by cdktn get");
262
+ for (const file of files) {
263
+ const dirName = file.replace(`${folder}/`, "").replace("/index.ts", "");
264
+ this.code.line(
265
+ `export * as ${toCamelCase(dirName)} from './${dirName}/index${this.importExtension}';`,
266
+ );
267
+ }
268
+ this.code.line();
269
+ this.code.closeFile(filePath);
270
+ }
271
+
272
+ private emitLazyIndexFile(provider: ProviderName, files: string[]): void {
273
+ const folder = `providers/${provider}`;
274
+ const filePath = `${folder}/lazy-index.ts`;
275
+ this.code.openFile(filePath);
276
+ this.code.line("// generated by cdktn get");
277
+ for (const file of files) {
278
+ const dirName = file.replace(`${folder}/`, "").replace("/index.ts", "");
279
+ this.code.line(
280
+ `Object.defineProperty(exports, '${toCamelCase(
281
+ dirName,
282
+ )}', { get: function () { return require('./${dirName}'); } });`,
283
+ );
284
+ }
285
+ this.code.line();
286
+ this.code.closeFile(filePath);
287
+ }
288
+
289
+ private emitResource(resource: ResourceModel): string {
290
+ this.code.openFile(resource.filePath);
291
+ this.emitFileHeader(resource);
292
+ this.structEmitter.emit(resource);
293
+ this.resourceEmitter.emit(resource);
294
+ this.code.closeFile(resource.filePath);
295
+
296
+ return resource.filePath;
297
+ }
298
+
299
+ private emitResourceWithComplexStruct(resource: ResourceModel) {
300
+ const generatedFiles = [];
301
+
302
+ // drop the last segment of the filepath
303
+ const filePath = resource.filePath;
304
+ this.code.openFile(filePath);
305
+ this.code.line(`// generated from terraform resource schema`);
306
+ this.code.line();
307
+
308
+ if (resource.structsRequireSharding) {
309
+ const structsSpecifier = `./${resource.structsFolderName}/index${this.importExtension}`;
310
+ if (resource.referencedTypes.length > 0) {
311
+ this.code.line(
312
+ `import { ${resource.referencedTypes.join(
313
+ ", \n",
314
+ )}} from '${structsSpecifier}';`,
315
+ );
316
+ }
317
+
318
+ this.code.line(`export * from '${structsSpecifier}';`);
319
+
320
+ resource.importStatements.forEach((statement) =>
321
+ this.code.line(statement),
322
+ );
323
+
324
+ this.structEmitter.emitInterface(resource, resource.configStruct);
325
+ this.resourceEmitter.emit(resource);
326
+
327
+ this.code.closeFile(filePath);
328
+
329
+ this.structEmitter.emit(resource);
330
+ generatedFiles.push(resource.fileName);
331
+ generatedFiles.push(resource.structsFolderName);
332
+ } else {
333
+ resource.importStatements.forEach((statement) =>
334
+ this.code.line(statement),
335
+ );
336
+
337
+ this.structEmitter.emit(resource);
338
+ this.resourceEmitter.emit(resource);
339
+ this.code.closeFile(filePath);
340
+ generatedFiles.push(resource.fileName);
341
+ }
342
+
343
+ return filePath;
344
+ }
345
+
346
+ private emitFileHeader(resource: ResourceModel) {
347
+ this.code.line(`// ${resource.linkToDocs}`);
348
+ this.code.line(`// generated from terraform resource schema`);
349
+ this.code.line();
350
+ resource.importStatements.forEach((statement) => this.code.line(statement));
351
+ this.code.line();
352
+ this.code.line("// Configuration");
353
+ this.code.line();
354
+ }
355
+ }