tfts 0.2.3 → 0.2.4
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.
- package/dist/src/cli/get.d.ts.map +1 -1
- package/dist/src/cli/get.js +34 -1
- package/dist/src/cli/get.js.map +1 -1
- package/dist/src/codegen/__tests__/fixtures.d.ts +5 -0
- package/dist/src/codegen/__tests__/fixtures.d.ts.map +1 -0
- package/dist/src/codegen/__tests__/fixtures.js +133 -0
- package/dist/src/codegen/__tests__/fixtures.js.map +1 -0
- package/dist/src/codegen/__tests__/generator.spec.js +223 -66
- package/dist/src/codegen/__tests__/generator.spec.js.map +1 -1
- package/dist/src/codegen/fn-generator.d.ts +22 -0
- package/dist/src/codegen/fn-generator.d.ts.map +1 -0
- package/dist/src/codegen/fn-generator.js +203 -0
- package/dist/src/codegen/fn-generator.js.map +1 -0
- package/dist/src/codegen/generator.d.ts +1 -5
- package/dist/src/codegen/generator.d.ts.map +1 -1
- package/dist/src/codegen/generator.js +365 -206
- package/dist/src/codegen/generator.js.map +1 -1
- package/dist/src/codegen/schema.d.ts +0 -2
- package/dist/src/codegen/schema.d.ts.map +1 -1
- package/dist/src/codegen/schema.js +0 -97
- package/dist/src/codegen/schema.js.map +1 -1
- package/dist/src/core/tokens.d.ts +8 -14
- package/dist/src/core/tokens.d.ts.map +1 -1
- package/dist/src/core/tokens.js +3 -3
- package/dist/src/core/tokens.js.map +1 -1
- package/dist/src/facade/datasource.d.ts +2 -2
- package/dist/src/facade/datasource.d.ts.map +1 -1
- package/dist/src/facade/datasource.js +2 -2
- package/dist/src/facade/datasource.js.map +1 -1
- package/dist/src/facade/resource.d.ts +2 -2
- package/dist/src/facade/resource.d.ts.map +1 -1
- package/dist/src/facade/resource.js +2 -2
- package/dist/src/facade/resource.js.map +1 -1
- package/dist/src/facade/stack.d.ts +1 -2
- package/dist/src/facade/stack.d.ts.map +1 -1
- package/dist/src/facade/stack.js +3 -1
- package/dist/src/facade/stack.js.map +1 -1
- package/dist/src/generated/fn.d.ts +599 -0
- package/dist/src/generated/fn.d.ts.map +1 -0
- package/dist/src/generated/fn.js +998 -0
- package/dist/src/generated/fn.js.map +1 -0
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -3
- package/dist/src/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/src/codegen/templates.d.ts +0 -14
- package/dist/src/codegen/templates.d.ts.map +0 -1
- package/dist/src/codegen/templates.js +0 -70
- package/dist/src/codegen/templates.js.map +0 -1
|
@@ -1,227 +1,386 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// --- Naming utilities ---
|
|
2
|
+
const snakeToCamelCase = (str) => str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
3
|
+
const snakeToPascalCase = (str) => {
|
|
4
|
+
const camel = snakeToCamelCase(str);
|
|
5
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
6
|
+
};
|
|
7
|
+
const snakeToKebabCase = (str) => str.replace(/_/g, "-");
|
|
8
|
+
const stripProviderPrefix = (name, providerName) => {
|
|
9
|
+
const prefix = `${providerName}_`;
|
|
10
|
+
return name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
11
|
+
};
|
|
12
|
+
// --- Type mapping ---
|
|
13
|
+
const mapSchemaTypeToTsConfig = (type) => {
|
|
14
|
+
if (typeof type === "string") {
|
|
15
|
+
switch (type) {
|
|
16
|
+
case "string":
|
|
17
|
+
return "TfString";
|
|
18
|
+
case "number":
|
|
19
|
+
return "TfNumber";
|
|
20
|
+
case "bool":
|
|
21
|
+
return "TfBoolean";
|
|
22
|
+
case "dynamic":
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(type)) {
|
|
27
|
+
const [container, inner] = type;
|
|
28
|
+
switch (container) {
|
|
29
|
+
case "list":
|
|
30
|
+
if (inner === "string")
|
|
31
|
+
return "TfStringList";
|
|
32
|
+
if (inner === "number")
|
|
33
|
+
return "TfNumberList";
|
|
34
|
+
return `readonly ${mapSchemaTypeToTsConfig(inner)}[]`;
|
|
35
|
+
case "set":
|
|
36
|
+
if (inner === "string")
|
|
37
|
+
return "TfStringList";
|
|
38
|
+
if (inner === "number")
|
|
39
|
+
return "TfNumberList";
|
|
40
|
+
return `readonly ${mapSchemaTypeToTsConfig(inner)}[]`;
|
|
41
|
+
case "map":
|
|
42
|
+
if (inner === "string")
|
|
43
|
+
return "TfStringMap";
|
|
44
|
+
return `Readonly<Record<string, ${mapSchemaTypeToTsConfig(inner)}>>`;
|
|
45
|
+
case "object":
|
|
46
|
+
return "unknown";
|
|
47
|
+
case "tuple":
|
|
48
|
+
return "unknown";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return "unknown";
|
|
52
|
+
};
|
|
53
|
+
const createTypeNameRegistry = () => ({
|
|
54
|
+
usedNames: new Set(),
|
|
55
|
+
blockTypeNames: new Map(),
|
|
56
|
+
});
|
|
57
|
+
const registerTypeName = (registry, name) => {
|
|
58
|
+
let finalName = name;
|
|
59
|
+
while (registry.usedNames.has(finalName)) {
|
|
60
|
+
finalName = `${finalName}A`;
|
|
61
|
+
}
|
|
62
|
+
registry.usedNames.add(finalName);
|
|
63
|
+
return finalName;
|
|
64
|
+
};
|
|
65
|
+
const registerBlockTypeName = (registry, baseName) => {
|
|
66
|
+
const actualName = registerTypeName(registry, baseName);
|
|
67
|
+
registry.blockTypeNames.set(baseName, actualName);
|
|
68
|
+
return actualName;
|
|
69
|
+
};
|
|
70
|
+
const getBlockTypeName = (registry, baseName) => registry.blockTypeNames.get(baseName) ?? baseName;
|
|
71
|
+
const isPropertyOptional = (attr) => attr.required !== true || attr.optional === true || attr.computed === true;
|
|
72
|
+
const isInputAttribute = (attr) => attr.required === true || attr.optional === true;
|
|
73
|
+
const isBlockOptional = (block) => block.min_items === undefined || block.min_items === 0;
|
|
74
|
+
const getBlockPropertyType = (block, typeName) => {
|
|
75
|
+
if (block.nesting_mode === "single") {
|
|
76
|
+
return typeName;
|
|
77
|
+
}
|
|
78
|
+
if ((block.nesting_mode === "list" || block.nesting_mode === "set") && block.max_items === 1) {
|
|
79
|
+
return `${typeName} | readonly ${typeName}[]`;
|
|
80
|
+
}
|
|
81
|
+
return `readonly ${typeName}[]`;
|
|
82
|
+
};
|
|
83
|
+
const getBlockConstructorValue = (block, tfName, tsName) => {
|
|
84
|
+
if ((block.nesting_mode === "list" || block.nesting_mode === "set") && block.max_items === 1) {
|
|
85
|
+
return `${tfName}: config.${tsName} !== undefined ? (Array.isArray(config.${tsName}) ? config.${tsName} : [config.${tsName}]) : undefined,`;
|
|
86
|
+
}
|
|
87
|
+
return `${tfName}: config.${tsName},`;
|
|
88
|
+
};
|
|
89
|
+
// Reserved property names that should not have getters generated
|
|
10
90
|
const RESERVED_NAMES = new Set([
|
|
11
91
|
"node",
|
|
12
|
-
"
|
|
13
|
-
"terraformResourceType",
|
|
14
|
-
"friendlyUniqueId",
|
|
15
|
-
"terraformGeneratorMetadata",
|
|
16
|
-
"connection",
|
|
17
|
-
"count",
|
|
92
|
+
"provider",
|
|
18
93
|
"dependsOn",
|
|
94
|
+
"count",
|
|
19
95
|
"forEach",
|
|
20
96
|
"lifecycle",
|
|
21
|
-
"provider",
|
|
22
|
-
"provisioners",
|
|
23
97
|
"fqn",
|
|
98
|
+
"terraformResourceType",
|
|
99
|
+
"terraformGeneratorMetadata",
|
|
100
|
+
"connection",
|
|
101
|
+
"provisioners",
|
|
24
102
|
]);
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
return
|
|
103
|
+
const generateBlockTypes = (block, className, registry) => {
|
|
104
|
+
const result = [];
|
|
105
|
+
if (!block.block_types) {
|
|
106
|
+
return result;
|
|
29
107
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Resource files (each in lib/ subdirectory for CDKTF compatibility)
|
|
45
|
-
for (const [resourceName, resourceSchema] of Object.entries(entry.resource_schemas ?? {})) {
|
|
46
|
-
const className = terraformNameToClassName(resourceName);
|
|
47
|
-
const dirName = terraformNameToFileName(resourceName);
|
|
48
|
-
const namespaceName = pascalToCamelCase(className);
|
|
49
|
-
const config = generateConfigWithNestedTypes(`${className}Config`, resourceSchema.block, "TerraformResourceConfig");
|
|
50
|
-
const resourceClass = resourceTemplate(className, resourceName, config.props, config.getters);
|
|
51
|
-
const content = [RESOURCE_IMPORTS, ...config.types, resourceClass].join("\n\n");
|
|
52
|
-
files.set(`lib/${dirName}/index.ts`, content);
|
|
53
|
-
namespaceExports.push(`export * as ${namespaceName} from "./lib/${dirName}/index.js";`);
|
|
54
|
-
}
|
|
55
|
-
// Data source files (each in lib/ subdirectory for CDKTF compatibility)
|
|
56
|
-
for (const [dataSourceName, dataSourceSchema] of Object.entries(entry.data_source_schemas ?? {})) {
|
|
57
|
-
const baseClassName = terraformNameToClassName(dataSourceName);
|
|
58
|
-
const className = `Data${baseClassName}`;
|
|
59
|
-
const dirName = `data-${terraformNameToFileName(dataSourceName)}`;
|
|
60
|
-
const namespaceName = `data${baseClassName}`;
|
|
61
|
-
const config = generateConfigWithNestedTypes(`${className}Config`, dataSourceSchema.block, "TerraformDataSourceConfig");
|
|
62
|
-
const dataSourceClass = dataSourceTemplate(className, dataSourceName, config.props, config.getters);
|
|
63
|
-
const content = [DATASOURCE_IMPORTS, ...config.types, dataSourceClass].join("\n\n");
|
|
64
|
-
files.set(`lib/${dirName}/index.ts`, content);
|
|
65
|
-
namespaceExports.push(`export * as ${namespaceName} from "./lib/${dirName}/index.js";`);
|
|
66
|
-
}
|
|
67
|
-
// Index file with namespace exports
|
|
68
|
-
files.set("index.ts", namespaceExports.join("\n") + "\n");
|
|
69
|
-
return files;
|
|
70
|
-
};
|
|
71
|
-
// Legacy single-file generator (kept for backward compatibility)
|
|
72
|
-
export const generateProvider = (name, schema) => {
|
|
73
|
-
const files = generateProviderFiles(name, schema);
|
|
74
|
-
// Combine all files except index.ts into one
|
|
75
|
-
const parts = [];
|
|
76
|
-
for (const [fileName, content] of files) {
|
|
77
|
-
if (fileName !== "index.ts") {
|
|
78
|
-
// Strip imports from non-first files to avoid duplicates
|
|
79
|
-
if (parts.length === 0) {
|
|
80
|
-
parts.push(content);
|
|
108
|
+
for (const [name, blockType] of Object.entries(block.block_types)) {
|
|
109
|
+
const baseName = `${className}${snakeToPascalCase(name)}`;
|
|
110
|
+
const blockTypeName = registerBlockTypeName(registry, baseName);
|
|
111
|
+
// Recursively generate nested block types
|
|
112
|
+
const nestedTypes = generateBlockTypes(blockType.block, blockTypeName, registry);
|
|
113
|
+
result.push(...nestedTypes);
|
|
114
|
+
// Generate the block type itself
|
|
115
|
+
const properties = [];
|
|
116
|
+
if (blockType.block.attributes) {
|
|
117
|
+
for (const [attrName, attr] of Object.entries(blockType.block.attributes)) {
|
|
118
|
+
const tsName = snakeToCamelCase(attrName);
|
|
119
|
+
const tsType = mapSchemaTypeToTsConfig(attr.type);
|
|
120
|
+
const optional = isPropertyOptional(attr) ? "?" : "";
|
|
121
|
+
properties.push(` readonly ${tsName}${optional}: ${tsType};`);
|
|
81
122
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
123
|
+
}
|
|
124
|
+
if (blockType.block.block_types) {
|
|
125
|
+
for (const [nestedName, nestedBlock] of Object.entries(blockType.block.block_types)) {
|
|
126
|
+
const tsName = snakeToCamelCase(nestedName);
|
|
127
|
+
const nestedTypeName = `${blockTypeName}${snakeToPascalCase(nestedName)}`;
|
|
128
|
+
const propType = getBlockPropertyType(nestedBlock, nestedTypeName);
|
|
129
|
+
const optional = isBlockOptional(nestedBlock) ? "?" : "";
|
|
130
|
+
properties.push(` readonly ${tsName}${optional}: ${propType};`);
|
|
86
131
|
}
|
|
87
132
|
}
|
|
133
|
+
const code = `export type ${blockTypeName} = {
|
|
134
|
+
${properties.join("\n")}
|
|
135
|
+
};`;
|
|
136
|
+
result.push({ typeName: blockTypeName, code });
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
140
|
+
const generateImports = (baseClass, baseConfig, block, includeProvider = false) => {
|
|
141
|
+
const types = new Set(["Construct", "TokenString", baseConfig]);
|
|
142
|
+
types.add(baseClass);
|
|
143
|
+
if (includeProvider) {
|
|
144
|
+
types.add("TerraformProvider");
|
|
88
145
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
export const generateConfig = (name, schema) => {
|
|
102
|
-
return generateConfigInterface(name, schema).code;
|
|
103
|
-
};
|
|
104
|
-
const generateConfigInterface = (name, block) => {
|
|
105
|
-
const attrEntries = Object.entries(block.attributes ?? {});
|
|
106
|
-
const blockEntries = Object.entries(block.block_types ?? {});
|
|
107
|
-
const attrProps = attrEntries.map(([attrName]) => ({
|
|
108
|
-
tfName: toTfName(attrName),
|
|
109
|
-
tsName: snakeToCamelCase(attrName),
|
|
110
|
-
}));
|
|
111
|
-
const attrLines = attrEntries.map(([attrName, attr]) => {
|
|
112
|
-
const tsType = parseSchemaType(attr.type);
|
|
113
|
-
const optional = attr.optional === true || attr.computed === true;
|
|
114
|
-
const propName = snakeToCamelCase(attrName);
|
|
115
|
-
return ` readonly ${propName}${optional ? "?" : ""}: ${tsType};`;
|
|
116
|
-
});
|
|
117
|
-
const blockProps = blockEntries.map(([blockName, blockType]) => {
|
|
118
|
-
const isList = blockType.nesting_mode === "list" || blockType.nesting_mode === "set";
|
|
119
|
-
const isSingleItem = isList && blockType.max_items === 1;
|
|
120
|
-
return {
|
|
121
|
-
tfName: toTfName(blockName),
|
|
122
|
-
tsName: snakeToCamelCase(blockName),
|
|
123
|
-
isListBlock: isSingleItem,
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
const blockLines = blockEntries.map(([blockName, blockType]) => {
|
|
127
|
-
const nestedName = `${name}${snakeToPascalCase(blockName)}`;
|
|
128
|
-
const propName = snakeToCamelCase(blockName);
|
|
129
|
-
const isList = blockType.nesting_mode === "list" || blockType.nesting_mode === "set";
|
|
130
|
-
const isSingleItem = isList && blockType.max_items === 1;
|
|
131
|
-
const isOptional = blockType.min_items === undefined || blockType.min_items === 0;
|
|
132
|
-
if (isSingleItem) {
|
|
133
|
-
// max_items=1: accept single object or array
|
|
134
|
-
return ` readonly ${propName}${isOptional ? "?" : ""}: ${nestedName} | readonly ${nestedName}[];`;
|
|
146
|
+
if (block.attributes) {
|
|
147
|
+
for (const attr of Object.values(block.attributes)) {
|
|
148
|
+
const tsType = mapSchemaTypeToTsConfig(attr.type);
|
|
149
|
+
if (tsType.startsWith("Tf") ||
|
|
150
|
+
tsType === "TfString" ||
|
|
151
|
+
tsType === "TfNumber" ||
|
|
152
|
+
tsType === "TfBoolean" ||
|
|
153
|
+
tsType === "TfStringList" ||
|
|
154
|
+
tsType === "TfNumberList" ||
|
|
155
|
+
tsType === "TfStringMap") {
|
|
156
|
+
types.add(tsType);
|
|
157
|
+
}
|
|
135
158
|
}
|
|
136
|
-
|
|
137
|
-
|
|
159
|
+
}
|
|
160
|
+
const collectBlockTypes = (b) => {
|
|
161
|
+
if (b.block_types) {
|
|
162
|
+
for (const blockType of Object.values(b.block_types)) {
|
|
163
|
+
if (blockType.block.attributes) {
|
|
164
|
+
for (const attr of Object.values(blockType.block.attributes)) {
|
|
165
|
+
const tsType = mapSchemaTypeToTsConfig(attr.type);
|
|
166
|
+
if (tsType.startsWith("Tf")) {
|
|
167
|
+
types.add(tsType);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
collectBlockTypes(blockType.block);
|
|
172
|
+
}
|
|
138
173
|
}
|
|
139
|
-
return ` readonly ${propName}${isOptional ? "?" : ""}: ${nestedName};`;
|
|
140
|
-
});
|
|
141
|
-
return {
|
|
142
|
-
code: configInterfaceTemplate(name, [...attrLines, ...blockLines]),
|
|
143
|
-
props: [...attrProps, ...blockProps],
|
|
144
174
|
};
|
|
175
|
+
collectBlockTypes(block);
|
|
176
|
+
const sortedTypes = Array.from(types).sort();
|
|
177
|
+
return `import type { ${sortedTypes.join(", ")} } from "tfts";
|
|
178
|
+
import { ${baseClass} } from "tfts";`;
|
|
145
179
|
};
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const tsType = parseSchemaType(attr.type);
|
|
155
|
-
const optional = attr.optional === true || attr.computed === true;
|
|
156
|
-
const propName = snakeToCamelCase(attrName);
|
|
157
|
-
return ` readonly ${propName}${optional ? "?" : ""}: ${tsType};`;
|
|
158
|
-
});
|
|
159
|
-
// Collect getters for all attributes (excluding reserved names)
|
|
160
|
-
const getters = attrEntries
|
|
161
|
-
.filter(([attrName]) => !RESERVED_NAMES.has(snakeToCamelCase(attrName)))
|
|
162
|
-
.map(([attrName]) => ({ tfName: toTfName(attrName), tsName: snakeToCamelCase(attrName) }));
|
|
163
|
-
const blockProps = blockEntries.map(([blockName, blockType]) => {
|
|
164
|
-
const isList = blockType.nesting_mode === "list" || blockType.nesting_mode === "set";
|
|
165
|
-
const isSingleItem = isList && blockType.max_items === 1;
|
|
166
|
-
return {
|
|
167
|
-
tfName: toTfName(blockName),
|
|
168
|
-
tsName: snakeToCamelCase(blockName),
|
|
169
|
-
isListBlock: isSingleItem,
|
|
170
|
-
};
|
|
171
|
-
});
|
|
172
|
-
const blockLines = blockEntries.map(([blockName, blockType]) => {
|
|
173
|
-
const nestedName = `${name}${snakeToPascalCase(blockName)}`;
|
|
174
|
-
const propName = snakeToCamelCase(blockName);
|
|
175
|
-
const isList = blockType.nesting_mode === "list" || blockType.nesting_mode === "set";
|
|
176
|
-
const isSingleItem = isList && blockType.max_items === 1;
|
|
177
|
-
const isOptional = blockType.min_items === undefined || blockType.min_items === 0;
|
|
178
|
-
if (isSingleItem) {
|
|
179
|
-
// max_items=1: accept single object or array
|
|
180
|
-
return ` readonly ${propName}${isOptional ? "?" : ""}: ${nestedName} | readonly ${nestedName}[];`;
|
|
180
|
+
const generateConfigTypeWithName = (configTypeName, className, block, baseConfig, registry) => {
|
|
181
|
+
const properties = [];
|
|
182
|
+
if (block.attributes) {
|
|
183
|
+
for (const [name, attr] of Object.entries(block.attributes)) {
|
|
184
|
+
const tsName = snakeToCamelCase(name);
|
|
185
|
+
const tsType = mapSchemaTypeToTsConfig(attr.type);
|
|
186
|
+
const optional = isPropertyOptional(attr) ? "?" : "";
|
|
187
|
+
properties.push(` readonly ${tsName}${optional}: ${tsType};`);
|
|
181
188
|
}
|
|
182
|
-
|
|
183
|
-
|
|
189
|
+
}
|
|
190
|
+
if (block.block_types) {
|
|
191
|
+
for (const [name, blockType] of Object.entries(block.block_types)) {
|
|
192
|
+
const tsName = snakeToCamelCase(name);
|
|
193
|
+
const baseName = `${className}${snakeToPascalCase(name)}`;
|
|
194
|
+
const actualBlockTypeName = getBlockTypeName(registry, baseName);
|
|
195
|
+
const propType = getBlockPropertyType(blockType, actualBlockTypeName);
|
|
196
|
+
const optional = isBlockOptional(blockType) ? "?" : "";
|
|
197
|
+
properties.push(` readonly ${tsName}${optional}: ${propType};`);
|
|
184
198
|
}
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
}
|
|
200
|
+
return `export type ${configTypeName} = {
|
|
201
|
+
${properties.join("\n")}
|
|
202
|
+
} & ${baseConfig};`;
|
|
203
|
+
};
|
|
204
|
+
const generateConfigType = (className, block, baseConfig, registry) => {
|
|
205
|
+
const configTypeName = registerTypeName(registry, `${className}Config`);
|
|
206
|
+
return generateConfigTypeWithName(configTypeName, className, block, baseConfig, registry);
|
|
207
|
+
};
|
|
208
|
+
const generateConstructorBody = (block) => {
|
|
209
|
+
const lines = [];
|
|
210
|
+
if (block.attributes) {
|
|
211
|
+
for (const name of Object.keys(block.attributes)) {
|
|
212
|
+
const tsName = snakeToCamelCase(name);
|
|
213
|
+
lines.push(` ${name}: config.${tsName},`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (block.block_types) {
|
|
217
|
+
for (const [name, blockType] of Object.entries(block.block_types)) {
|
|
218
|
+
const tsName = snakeToCamelCase(name);
|
|
219
|
+
lines.push(` ${getBlockConstructorValue(blockType, name, tsName)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return lines.join("\n");
|
|
223
|
+
};
|
|
224
|
+
const generateGetters = (block) => Object.keys(block.attributes ?? {})
|
|
225
|
+
.filter((name) => !RESERVED_NAMES.has(snakeToCamelCase(name)))
|
|
226
|
+
.map((name) => {
|
|
227
|
+
const tsName = snakeToCamelCase(name);
|
|
228
|
+
return ` get ${tsName}(): TokenString {
|
|
229
|
+
return this.getStringAttribute("${name}");
|
|
230
|
+
}`;
|
|
231
|
+
})
|
|
232
|
+
.join("\n\n");
|
|
233
|
+
const generateInputGetters = (block) => Object.entries(block.attributes ?? {})
|
|
234
|
+
.filter(([, attr]) => isInputAttribute(attr))
|
|
235
|
+
.map(([name, attr]) => {
|
|
236
|
+
const tsName = snakeToCamelCase(name);
|
|
237
|
+
const tsType = mapSchemaTypeToTsConfig(attr.type);
|
|
238
|
+
return ` get ${tsName}Input(): ${tsType} | undefined {
|
|
239
|
+
return this._config.${tsName};
|
|
240
|
+
}`;
|
|
241
|
+
})
|
|
242
|
+
.join("\n\n");
|
|
243
|
+
// --- Provider generation ---
|
|
244
|
+
const generateProviderClass = (providerName, source, block) => {
|
|
245
|
+
const className = `${snakeToPascalCase(providerName)}Provider`;
|
|
246
|
+
const registry = createTypeNameRegistry();
|
|
247
|
+
registry.usedNames.add(className);
|
|
248
|
+
const blockTypes = generateBlockTypes(block, className, registry);
|
|
249
|
+
const imports = generateImports("TerraformProvider", "TerraformProviderConfig", block);
|
|
250
|
+
const configType = generateConfigType(className, block, "TerraformProviderConfig", registry);
|
|
251
|
+
const constructorBody = generateConstructorBody(block);
|
|
252
|
+
const getters = generateGetters(block);
|
|
253
|
+
const blockTypeCode = blockTypes.length > 0 ? blockTypes.map((bt) => bt.code).join("\n\n") + "\n\n" : "";
|
|
254
|
+
const getterSection = getters ? `\n\n${getters}` : "";
|
|
255
|
+
return `${imports}
|
|
256
|
+
|
|
257
|
+
${blockTypeCode}${configType}
|
|
258
|
+
|
|
259
|
+
export class ${className} extends TerraformProvider {
|
|
260
|
+
constructor(scope: Construct, id: string, config: ${className}Config = {}) {
|
|
261
|
+
super(scope, id, "${source}", {
|
|
262
|
+
${constructorBody}
|
|
263
|
+
}, config);
|
|
264
|
+
}${getterSection}
|
|
265
|
+
}
|
|
266
|
+
`;
|
|
267
|
+
};
|
|
268
|
+
// --- Resource generation ---
|
|
269
|
+
const generateResourceClass = (resourceName, providerName, block) => {
|
|
270
|
+
const strippedName = stripProviderPrefix(resourceName, providerName);
|
|
271
|
+
const className = snakeToPascalCase(strippedName);
|
|
272
|
+
const registry = createTypeNameRegistry();
|
|
273
|
+
registry.usedNames.add(className);
|
|
274
|
+
// Reserve the config type name first so nested blocks get the A suffix if collision
|
|
275
|
+
registry.usedNames.add(`${className}Config`);
|
|
276
|
+
const blockTypes = generateBlockTypes(block, className, registry);
|
|
277
|
+
const imports = generateImports("TerraformResource", "TerraformResourceConfig", block, true);
|
|
278
|
+
const configType = generateConfigTypeWithName(`${className}Config`, className, block, "TerraformResourceConfig", registry);
|
|
279
|
+
const constructorBody = generateConstructorBody(block);
|
|
280
|
+
const getters = generateGetters(block);
|
|
281
|
+
const inputGetters = generateInputGetters(block);
|
|
282
|
+
const blockTypeCode = blockTypes.length > 0 ? blockTypes.map((bt) => bt.code).join("\n\n") + "\n\n" : "";
|
|
283
|
+
const allGetters = [getters, inputGetters].filter(Boolean).join("\n\n");
|
|
284
|
+
const getterSection = allGetters ? `\n\n${allGetters}` : "";
|
|
285
|
+
return `${imports}
|
|
286
|
+
|
|
287
|
+
${blockTypeCode}${configType}
|
|
288
|
+
|
|
289
|
+
export class ${className} extends TerraformResource {
|
|
290
|
+
private readonly _config: ${className}Config;
|
|
291
|
+
|
|
292
|
+
constructor(scope: Construct, id: string, config: ${className}Config) {
|
|
293
|
+
super(scope, id, "${resourceName}", {
|
|
294
|
+
${constructorBody}
|
|
295
|
+
}, config);
|
|
296
|
+
this._config = config;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
static importFrom(scope: Construct, id: string, resourceId: TfString, provider?: TerraformProvider): ${className} {
|
|
300
|
+
return new ${className}(scope, id, { lifecycle: { importId: resourceId }, provider } as ${className}Config);
|
|
301
|
+
}${getterSection}
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
196
304
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
305
|
+
// --- Data source generation ---
|
|
306
|
+
const generateDataSourceClass = (dataSourceName, providerName, block) => {
|
|
307
|
+
const strippedName = stripProviderPrefix(dataSourceName, providerName);
|
|
308
|
+
const className = `Data${snakeToPascalCase(strippedName)}`;
|
|
309
|
+
const registry = createTypeNameRegistry();
|
|
310
|
+
registry.usedNames.add(className);
|
|
311
|
+
const blockTypes = generateBlockTypes(block, className, registry);
|
|
312
|
+
const imports = generateImports("TerraformDataSource", "TerraformDataSourceConfig", block);
|
|
313
|
+
const configType = generateConfigType(className, block, "TerraformDataSourceConfig", registry);
|
|
314
|
+
const constructorBody = generateConstructorBody(block);
|
|
315
|
+
const getters = generateGetters(block);
|
|
316
|
+
const inputGetters = generateInputGetters(block);
|
|
317
|
+
const blockTypeCode = blockTypes.length > 0 ? blockTypes.map((bt) => bt.code).join("\n\n") + "\n\n" : "";
|
|
318
|
+
const allGetters = [getters, inputGetters].filter(Boolean).join("\n\n");
|
|
319
|
+
const getterSection = allGetters ? `\n\n${allGetters}` : "";
|
|
320
|
+
return `${imports}
|
|
321
|
+
|
|
322
|
+
${blockTypeCode}${configType}
|
|
323
|
+
|
|
324
|
+
export class ${className} extends TerraformDataSource {
|
|
325
|
+
private readonly _config: ${className}Config;
|
|
326
|
+
|
|
327
|
+
constructor(scope: Construct, id: string, config: ${className}Config) {
|
|
328
|
+
super(scope, id, "${dataSourceName}", {
|
|
329
|
+
${constructorBody}
|
|
330
|
+
}, config);
|
|
331
|
+
this._config = config;
|
|
332
|
+
}${getterSection}
|
|
333
|
+
}
|
|
334
|
+
`;
|
|
335
|
+
};
|
|
336
|
+
const generateIndexFile = (exports) => {
|
|
337
|
+
const lines = exports
|
|
338
|
+
.slice()
|
|
339
|
+
.sort((a, b) => a.namespace.localeCompare(b.namespace))
|
|
340
|
+
.map((e) => `export * as ${e.namespace} from "./${e.path}";`);
|
|
341
|
+
return lines.join("\n") + "\n";
|
|
342
|
+
};
|
|
343
|
+
// --- Main generator ---
|
|
344
|
+
export const generateProviderFiles = (name, schema) => {
|
|
345
|
+
const files = new Map();
|
|
346
|
+
const namespaceExports = [];
|
|
347
|
+
// Find the provider schema entry
|
|
348
|
+
const providerEntry = Object.entries(schema.provider_schemas).find(([source]) => source.includes(`/${name}`) || source.endsWith(`/${name}`));
|
|
349
|
+
if (!providerEntry) {
|
|
350
|
+
return files;
|
|
351
|
+
}
|
|
352
|
+
const [source, schemaEntry] = providerEntry;
|
|
353
|
+
// Generate provider
|
|
354
|
+
files.set("provider/index.ts", generateProviderClass(name, source, schemaEntry.provider));
|
|
355
|
+
namespaceExports.push({ namespace: "provider", path: "provider/index.js" });
|
|
356
|
+
// Generate resources
|
|
357
|
+
if (schemaEntry.resource_schemas) {
|
|
358
|
+
for (const [resourceName, resourceSchema] of Object.entries(schemaEntry.resource_schemas)) {
|
|
359
|
+
const strippedName = stripProviderPrefix(resourceName, name);
|
|
360
|
+
const kebabName = snakeToKebabCase(strippedName);
|
|
361
|
+
const path = `lib/${kebabName}/index.ts`;
|
|
362
|
+
files.set(path, generateResourceClass(resourceName, name, resourceSchema.block));
|
|
363
|
+
namespaceExports.push({
|
|
364
|
+
namespace: snakeToCamelCase(strippedName),
|
|
365
|
+
path: `lib/${kebabName}/index.js`,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Generate data sources
|
|
370
|
+
if (schemaEntry.data_source_schemas) {
|
|
371
|
+
for (const [dataSourceName, dataSourceSchema] of Object.entries(schemaEntry.data_source_schemas)) {
|
|
372
|
+
const strippedName = stripProviderPrefix(dataSourceName, name);
|
|
373
|
+
const kebabName = snakeToKebabCase(strippedName);
|
|
374
|
+
const path = `lib/data-${kebabName}/index.ts`;
|
|
375
|
+
files.set(path, generateDataSourceClass(dataSourceName, name, dataSourceSchema.block));
|
|
376
|
+
namespaceExports.push({
|
|
377
|
+
namespace: `data${snakeToPascalCase(strippedName)}`,
|
|
378
|
+
path: `lib/data-${kebabName}/index.js`,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Generate index file
|
|
383
|
+
files.set("index.ts", generateIndexFile(namespaceExports));
|
|
384
|
+
return files;
|
|
226
385
|
};
|
|
227
386
|
//# sourceMappingURL=generator.js.map
|