@cdktn/provider-generator 0.24.0-pre.9 → 0.24.0-pre.91

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 (124) 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/builder.d.ts +9 -3
  5. package/build/__tests__/edge-provider-schema/builder.js +6 -3
  6. package/build/__tests__/edge-provider-schema/cli.js +8 -3
  7. package/build/__tests__/edge-provider-schema/index.js +119 -1
  8. package/build/__tests__/provider.test.js +4 -1
  9. package/build/get/__tests__/generator/ephemeral-resources.test.d.ts +2 -0
  10. package/build/get/__tests__/generator/ephemeral-resources.test.js +49 -0
  11. package/build/get/__tests__/generator/module-generator.test.js +12 -12
  12. package/build/get/__tests__/generator/provider-functions.test.d.ts +2 -0
  13. package/build/get/__tests__/generator/provider-functions.test.js +176 -0
  14. package/build/get/__tests__/generator/provider-source-matching.test.d.ts +2 -0
  15. package/build/get/__tests__/generator/provider-source-matching.test.js +45 -0
  16. package/build/get/__tests__/generator/provider.test.js +9 -1
  17. package/build/get/__tests__/generator/supported-stored-classes.test.d.ts +2 -0
  18. package/build/get/__tests__/generator/supported-stored-classes.test.js +137 -0
  19. package/build/get/__tests__/generator/types.test.js +44 -1
  20. package/build/get/__tests__/generator/write-only.test.d.ts +2 -0
  21. package/build/get/__tests__/generator/write-only.test.js +65 -0
  22. package/build/get/__tests__/target-versions.test.d.ts +2 -0
  23. package/build/get/__tests__/target-versions.test.js +171 -0
  24. package/build/get/constructs-maker.d.ts +8 -1
  25. package/build/get/constructs-maker.js +46 -10
  26. package/build/get/generator/emitter/attributes-emitter.d.ts +13 -2
  27. package/build/get/generator/emitter/attributes-emitter.js +71 -5
  28. package/build/get/generator/emitter/index.d.ts +1 -0
  29. package/build/get/generator/emitter/index.js +2 -1
  30. package/build/get/generator/emitter/provider-functions-emitter.d.ts +11 -0
  31. package/build/get/generator/emitter/provider-functions-emitter.js +94 -0
  32. package/build/get/generator/emitter/resource-emitter.d.ts +5 -1
  33. package/build/get/generator/emitter/resource-emitter.js +83 -7
  34. package/build/get/generator/emitter/struct-emitter.js +10 -10
  35. package/build/get/generator/models/attribute-model.d.ts +4 -0
  36. package/build/get/generator/models/attribute-model.js +50 -4
  37. package/build/get/generator/models/attribute-type-model.d.ts +24 -0
  38. package/build/get/generator/models/attribute-type-model.js +91 -5
  39. package/build/get/generator/models/index.d.ts +1 -0
  40. package/build/get/generator/models/index.js +2 -1
  41. package/build/get/generator/models/provider-function-model.d.ts +102 -0
  42. package/build/get/generator/models/provider-function-model.js +667 -0
  43. package/build/get/generator/models/resource-model.d.ts +3 -0
  44. package/build/get/generator/models/resource-model.js +13 -4
  45. package/build/get/generator/models/struct.d.ts +2 -0
  46. package/build/get/generator/models/struct.js +6 -2
  47. package/build/get/generator/models/supported-stored-classes.d.ts +5 -0
  48. package/build/get/generator/models/supported-stored-classes.js +86 -0
  49. package/build/get/generator/module-generator.js +3 -3
  50. package/build/get/generator/provider-generator.d.ts +2 -0
  51. package/build/get/generator/provider-generator.js +41 -6
  52. package/build/get/generator/resource-parser.js +3 -1
  53. package/jest.config.js +16 -9
  54. package/package.json +28 -27
  55. package/package.sh +1 -1
  56. package/src/__tests__/__snapshots__/edge-provider-schema.test.ts.snap +4 -0
  57. package/src/__tests__/__snapshots__/provider.test.ts.snap +2828 -2828
  58. package/src/__tests__/edge-provider-schema/builder.ts +201 -0
  59. package/src/__tests__/edge-provider-schema/cli.ts +51 -0
  60. package/src/__tests__/edge-provider-schema/index.ts +284 -0
  61. package/src/__tests__/edge-provider-schema.test.ts +24 -0
  62. package/src/__tests__/provider.test.ts +183 -0
  63. package/src/get/__tests__/constructs-maker.test.ts +118 -0
  64. package/src/get/__tests__/generator/__snapshots__/complex-computed-types.test.ts.snap +5 -5
  65. package/src/get/__tests__/generator/__snapshots__/ephemeral-resources.test.ts.snap +524 -0
  66. package/src/get/__tests__/generator/__snapshots__/export-sharding.test.ts.snap +3310 -3310
  67. package/src/get/__tests__/generator/__snapshots__/module-generator.test.ts.snap +355 -355
  68. package/src/get/__tests__/generator/__snapshots__/nested-types.test.ts.snap +8 -8
  69. package/src/get/__tests__/generator/__snapshots__/provider-functions.test.ts.snap +444 -0
  70. package/src/get/__tests__/generator/__snapshots__/provider.test.ts.snap +8 -8
  71. package/src/get/__tests__/generator/__snapshots__/resource-types.test.ts.snap +126 -126
  72. package/src/get/__tests__/generator/__snapshots__/skipped-attributes.test.ts.snap +17 -17
  73. package/src/get/__tests__/generator/__snapshots__/types.test.ts.snap +201 -52
  74. package/src/get/__tests__/generator/__snapshots__/write-only.test.ts.snap +262 -0
  75. package/src/get/__tests__/generator/complex-computed-types.test.ts +28 -0
  76. package/src/get/__tests__/generator/deep-nested-attributes.test.ts +56 -0
  77. package/src/get/__tests__/generator/description-escaping.test.ts +84 -0
  78. package/src/get/__tests__/generator/empty-provider-resources.test.ts +26 -0
  79. package/src/get/__tests__/generator/ephemeral-resources.test.ts +46 -0
  80. package/src/get/__tests__/generator/export-sharding.test.ts +169 -0
  81. package/src/get/__tests__/generator/fixtures/deeply-nested-collections.test.fixture.json +129 -0
  82. package/src/get/__tests__/generator/fixtures/ephemeral-resources.test.fixture.json +160 -0
  83. package/src/get/__tests__/generator/fixtures/provider-functions-collision.test.fixture.json +31 -0
  84. package/src/get/__tests__/generator/fixtures/provider-functions-synthetic.test.fixture.json +192 -0
  85. package/src/get/__tests__/generator/fixtures/provider-functions.test.fixture.json +170 -0
  86. package/src/get/__tests__/generator/fixtures/write-only.test.fixture.json +58 -0
  87. package/src/get/__tests__/generator/import-style.test.ts +129 -0
  88. package/src/get/__tests__/generator/module-generator.test.ts +528 -0
  89. package/src/get/__tests__/generator/nested-types.test.ts +54 -0
  90. package/src/get/__tests__/generator/provider-functions.test.ts +262 -0
  91. package/src/get/__tests__/generator/provider-source-matching.test.ts +84 -0
  92. package/src/get/__tests__/generator/provider.test.ts +59 -0
  93. package/src/get/__tests__/generator/resource-types.test.ts +118 -0
  94. package/src/get/__tests__/generator/skipped-attributes.test.ts +72 -0
  95. package/src/get/__tests__/generator/supported-stored-classes.test.ts +147 -0
  96. package/src/get/__tests__/generator/types.test.ts +686 -0
  97. package/src/get/__tests__/generator/versions-file.test.ts +72 -0
  98. package/src/get/__tests__/generator/write-only.test.ts +71 -0
  99. package/src/get/__tests__/target-versions.test.ts +206 -0
  100. package/src/get/__tests__/util.ts +75 -0
  101. package/src/get/constructs-maker.ts +885 -0
  102. package/src/get/generator/custom-defaults.ts +493 -0
  103. package/src/get/generator/emitter/attributes-emitter.ts +316 -0
  104. package/src/get/generator/emitter/index.ts +6 -0
  105. package/src/get/generator/emitter/provider-functions-emitter.ts +117 -0
  106. package/src/get/generator/emitter/resource-emitter.ts +330 -0
  107. package/src/get/generator/emitter/struct-emitter.ts +683 -0
  108. package/src/get/generator/loop-detection.ts +81 -0
  109. package/src/get/generator/models/attribute-model.ts +273 -0
  110. package/src/get/generator/models/attribute-type-model.ts +560 -0
  111. package/src/get/generator/models/index.ts +8 -0
  112. package/src/get/generator/models/provider-function-model.ts +872 -0
  113. package/src/get/generator/models/resource-model.ts +183 -0
  114. package/src/get/generator/models/scope.ts +54 -0
  115. package/src/get/generator/models/struct.ts +124 -0
  116. package/src/get/generator/models/supported-stored-classes.ts +88 -0
  117. package/src/get/generator/module-generator.ts +234 -0
  118. package/src/get/generator/provider-generator.ts +428 -0
  119. package/src/get/generator/resource-parser.ts +764 -0
  120. package/src/get/generator/sanitized-comments.ts +49 -0
  121. package/src/get/generator/skipped-attributes.ts +27 -0
  122. package/src/index.ts +40 -0
  123. package/src/util.ts +26 -0
  124. package/tsconfig.json +1 -2
@@ -0,0 +1,183 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { toSnakeCase } from "codemaker";
4
+ import path from "path";
5
+ import { FQPN, parseFQPN, ProviderName } from "@cdktn/provider-schema";
6
+ import { AttributeModel } from "./attribute-model";
7
+ import { Struct, ConfigStruct } from "./struct";
8
+ import { Schema } from "@cdktn/commons";
9
+ import { ProviderFunctionsModel } from "./provider-function-model";
10
+
11
+ // Limit is 1200 to prevent stack size error.
12
+ // Could increase now that calculation is more accurate;
13
+ // however, probably better to have smaller
14
+ export const STRUCT_SHARDING_THRESHOLD = 400;
15
+
16
+ interface ResourceModelOptions {
17
+ terraformType: string;
18
+ className: string;
19
+ baseName: string;
20
+ fileName: string;
21
+ filePath: string;
22
+ attributes: AttributeModel[];
23
+ structs: Struct[];
24
+ fqpn: FQPN;
25
+ schema: Schema;
26
+ terraformSchemaType: string;
27
+ configStructName: string;
28
+ }
29
+
30
+ type DefinitionPath = string;
31
+ export class ResourceModel {
32
+ public className: string;
33
+ public filePath: string;
34
+ public terraformType: string;
35
+ public baseName: string;
36
+ public provider: ProviderName;
37
+ public fqpn: FQPN;
38
+ public providerVersionConstraint?: string;
39
+ public providerVersion?: string;
40
+ public terraformProviderSource?: string;
41
+ /*
42
+ * usually same as provider, but can be overridden if user picks a different name
43
+ * to be able to use two providers with the same name
44
+ */
45
+ public terraformProviderName: string;
46
+ /*
47
+ * Only set (by provider-generator.ts) when isProvider is true and the
48
+ * provider schema declares provider-defined functions - drives whether
49
+ * ResourceEmitter emits the memoized `functions` getter and its
50
+ * cross-directory import of the sibling provider-functions/index.ts file.
51
+ */
52
+ public providerFunctionsModel?: ProviderFunctionsModel;
53
+ public fileName: string;
54
+ public attributes: AttributeModel[];
55
+ public schema: Schema;
56
+ private _structs: Struct[];
57
+ private dependencies: string[];
58
+ private terraformSchemaType: string;
59
+ private configStructName: string;
60
+ public readonly structsRequireSharding: boolean;
61
+ public recursiveAttributePaths: { [usagePath: string]: DefinitionPath } = {};
62
+
63
+ constructor(options: ResourceModelOptions) {
64
+ this.className = options.className;
65
+ this.filePath = options.filePath;
66
+ this.terraformType = options.terraformType;
67
+ this.baseName = options.baseName;
68
+ this.attributes = options.attributes;
69
+ this.schema = options.schema;
70
+ this.fqpn = options.fqpn;
71
+ this.provider = parseFQPN(options.fqpn).name;
72
+ this.terraformProviderName = this.provider;
73
+ this.fileName = options.fileName;
74
+ this._structs = options.structs;
75
+ this.terraformSchemaType = options.terraformSchemaType;
76
+ this.configStructName = options.configStructName;
77
+ this.dependencies = [
78
+ `import { Construct } from 'constructs';`,
79
+ `import * as cdktn from 'cdktn';`,
80
+ ];
81
+
82
+ this.structsRequireSharding =
83
+ this._structs.reduce((partialSum, s) => partialSum + s.exportCount, 0) >
84
+ STRUCT_SHARDING_THRESHOLD;
85
+ }
86
+
87
+ public get structs(): Struct[] {
88
+ return [this.configStruct, ...this._structs];
89
+ }
90
+
91
+ public get configStruct() {
92
+ return new ConfigStruct(
93
+ this.configStructName,
94
+ this.attributes,
95
+ this.isEphemeralResource ? "TerraformEphemeralMetaArguments" : undefined,
96
+ );
97
+ }
98
+
99
+ public get synthesizableAttributes(): AttributeModel[] {
100
+ return this.configStruct.assignableAttributes;
101
+ }
102
+
103
+ public get importStatements(): string[] {
104
+ return this.dependencies;
105
+ }
106
+
107
+ public get schemaAsJson(): string {
108
+ return this.escapeSchema(JSON.stringify(this.schema, undefined, 2));
109
+ }
110
+
111
+ public get linkToDocs(): string {
112
+ const { hostname, namespace, name } = parseFQPN(this.fqpn);
113
+ const version = this.providerVersion || "latest";
114
+ const base = `https://${hostname}/providers/${namespace}/${name}/${version}/docs`;
115
+ if (this.isProvider) return base;
116
+ if (this.isDataSource)
117
+ return `${base}/data-sources/${this.terraformDocName}`;
118
+ if (this.isEphemeralResource)
119
+ return `${base}/ephemeral-resources/${this.terraformDocName}`;
120
+ return `${base}/resources/${this.terraformDocName}`;
121
+ }
122
+
123
+ public get isProvider(): boolean {
124
+ return this.terraformSchemaType === "provider";
125
+ }
126
+
127
+ public get isDataSource(): boolean {
128
+ return this.terraformSchemaType === "data_source";
129
+ }
130
+
131
+ public get isEphemeralResource(): boolean {
132
+ return this.terraformSchemaType === "ephemeral_resource";
133
+ }
134
+
135
+ public get parentClassName(): string {
136
+ return this.isProvider
137
+ ? "TerraformProvider"
138
+ : this.isDataSource
139
+ ? "TerraformDataSource"
140
+ : this.isEphemeralResource
141
+ ? "TerraformEphemeralResource"
142
+ : "TerraformResource";
143
+ }
144
+
145
+ public get terraformResourceType(): string {
146
+ return this.isProvider
147
+ ? this.terraformProviderName
148
+ : this.isDataSource
149
+ ? this.terraformType.replace(/^data_/, "")
150
+ : this.isEphemeralResource
151
+ ? this.terraformType.replace(/^ephemeral_/, "")
152
+ : this.terraformType;
153
+ }
154
+
155
+ public get terraformDocName(): string {
156
+ return toSnakeCase(
157
+ this.terraformResourceType.replace(
158
+ new RegExp(`^${this.provider}_`, "i"),
159
+ "",
160
+ ),
161
+ );
162
+ }
163
+
164
+ public get referencedTypes(): string[] {
165
+ return this.configStruct.referencedTypes;
166
+ }
167
+
168
+ public get structsFolderName(): string {
169
+ return `${path.basename(this.fileName, ".ts")}-structs`;
170
+ }
171
+
172
+ public get structsFolderPath(): string {
173
+ return path.join(this.namespaceFolderPath, this.structsFolderName);
174
+ }
175
+
176
+ public get namespaceFolderPath(): string {
177
+ return this.filePath.split("/").slice(0, -1).join("/");
178
+ }
179
+
180
+ private escapeSchema(schema: string): string {
181
+ return schema.replace(/(\*\/)/gi, `*\\/`);
182
+ }
183
+ }
@@ -0,0 +1,54 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ export interface ScopeProps {
4
+ readonly name: string;
5
+ readonly isProvider: boolean;
6
+ readonly isComputed?: boolean;
7
+ readonly isOptional?: boolean;
8
+ readonly isRequired?: boolean;
9
+ readonly inBlockType?: boolean;
10
+ readonly isNestedType?: boolean;
11
+ readonly parent?: Scope;
12
+ }
13
+
14
+ export class Scope {
15
+ public readonly name: string;
16
+ public readonly provider?: string;
17
+ public readonly parent?: Scope;
18
+ public readonly isProvider: boolean;
19
+ public isComputed: boolean;
20
+ public isOptional: boolean;
21
+ public isRequired: boolean;
22
+ public inBlockType: boolean;
23
+ public isNestedType: boolean;
24
+
25
+ constructor(props: ScopeProps) {
26
+ this.name = props.name;
27
+ this.parent = props.parent;
28
+ this.isProvider = props.isProvider;
29
+ this.isComputed = props.isComputed ?? false;
30
+ this.isOptional = props.isOptional ?? true;
31
+ this.isRequired = props.isRequired ?? false;
32
+ this.inBlockType = props.inBlockType ?? false;
33
+ this.isNestedType = props.isNestedType ?? false;
34
+ }
35
+
36
+ public fullName(attributeName: string) {
37
+ return `${this.baseName}.${attributeName}`;
38
+ }
39
+
40
+ public get baseName() {
41
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
42
+ let scope: Scope | undefined = this;
43
+ let name = "";
44
+ while (scope) {
45
+ if (name === "") {
46
+ name = scope.name;
47
+ } else {
48
+ name = `${scope.name}.${name}`;
49
+ }
50
+ scope = scope.parent;
51
+ }
52
+ return name;
53
+ }
54
+ }
@@ -0,0 +1,124 @@
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
+ constructor(
114
+ name: string,
115
+ attributes: AttributeModel[],
116
+ private readonly extendsClass: string = "TerraformMetaArguments",
117
+ ) {
118
+ super(name, attributes);
119
+ }
120
+
121
+ public get extends(): string {
122
+ return ` extends cdktn.${this.extendsClass}`;
123
+ }
124
+ }
@@ -0,0 +1,88 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { uppercaseFirst } from "../../../util";
4
+
5
+ // The core cdktn package (packages/cdktn/src/complex-computed-list.ts) only hand-writes
6
+ // this fixed set of wrapper classes for computed, non-complex collection attributes.
7
+ // Arbitrarily deep nestings of list/set/map (e.g. map(map(list(string)))) compose a
8
+ // storedClassType (e.g. "stringListMapMap") that has no corresponding class. Rather than
9
+ // falling back straight to a plain, untyped getter, resolveStoredClassName below first
10
+ // looks for the nearest typed "Any"-wrapper that still exists in this set, collapsing the
11
+ // unsupported inner shape into an untyped boundary while preserving typed navigation for
12
+ // the outer layers (see https://github.com/open-constructs/cdk-terrain/issues/11).
13
+ const SUPPORTED_STORED_CLASSES = new Set([
14
+ "StringMap",
15
+ "NumberMap",
16
+ "BooleanMap",
17
+ "AnyMap",
18
+ "StringMapMap",
19
+ "NumberMapMap",
20
+ "BooleanMapMap",
21
+ "AnyMapMap",
22
+ "BooleanList",
23
+ "StringListList",
24
+ "NumberListList",
25
+ "BooleanListList",
26
+ "AnyListList",
27
+ "StringMapList",
28
+ "NumberMapList",
29
+ "BooleanMapList",
30
+ "AnyMapList",
31
+ "StringListMap",
32
+ "NumberListMap",
33
+ "BooleanListMap",
34
+ "AnyListMap",
35
+ ]);
36
+
37
+ // Resolves the composed stored class name to use for a collection attribute (list/set/map)
38
+ // whose element is itself a non-complex collection (or simple type).
39
+ //
40
+ // `ownStoredClassType` is this type's own composed storedClassType (e.g. "stringListMapMap"
41
+ // for map(map(list(string)))) and `elementTypeModelType` is the element's typeModelType
42
+ // ("list" | "set" | "map" | "simple" | ...).
43
+ //
44
+ // Resolution order:
45
+ // 1. The own composed name (e.g. "StringListMapMap") - used as-is when it refers to a
46
+ // class that is hand-written in the core package (not collapsed).
47
+ // 2. Otherwise, the nearest typed "Any"-wrapper that still encodes the outer two layers
48
+ // (this type's own layer and its element's layer), e.g. "AnyMapMap" - collapsing the
49
+ // unsupported inner shape into an untyped boundary while preserving typed navigation
50
+ // for the outer layers.
51
+ // 3. Otherwise (defensively; every depth should resolve via 1 or 2), undefined - the
52
+ // caller must fall back to a plain, untyped getter.
53
+ export function resolveStoredClassName(
54
+ ownStoredClassType: string,
55
+ elementTypeModelType: string,
56
+ ): { name: string; collapsed: boolean } | undefined {
57
+ const candidate1 = uppercaseFirst(ownStoredClassType);
58
+ if (SUPPORTED_STORED_CLASSES.has(candidate1)) {
59
+ return { name: candidate1, collapsed: false };
60
+ }
61
+
62
+ let ownLayer: "List" | "Map" | undefined;
63
+ if (ownStoredClassType.endsWith("List")) {
64
+ ownLayer = "List";
65
+ } else if (ownStoredClassType.endsWith("Map")) {
66
+ ownLayer = "Map";
67
+ }
68
+ if (!ownLayer) {
69
+ return undefined;
70
+ }
71
+
72
+ let innerLayer: "List" | "Map" | undefined;
73
+ if (elementTypeModelType === "list" || elementTypeModelType === "set") {
74
+ innerLayer = "List";
75
+ } else if (elementTypeModelType === "map") {
76
+ innerLayer = "Map";
77
+ }
78
+ if (!innerLayer) {
79
+ return undefined;
80
+ }
81
+
82
+ const candidate2 = `Any${innerLayer}${ownLayer}`;
83
+ if (SUPPORTED_STORED_CLASSES.has(candidate2)) {
84
+ return { name: candidate2, collapsed: true };
85
+ }
86
+
87
+ return undefined;
88
+ }
@@ -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
+ }