@cdktn/provider-generator 0.25.0-pre.2 → 0.25.0-pre.21
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/build/__tests__/edge-provider-schema/builder.js +3 -3
- package/build/__tests__/edge-provider-schema/cli.js +18 -8
- package/build/__tests__/edge-provider-schema.test.js +18 -8
- package/build/__tests__/provider.test.js +21 -9
- package/build/get/__tests__/constructs-maker.test.js +91 -8
- package/build/get/__tests__/generator/complex-computed-types.test.js +18 -8
- package/build/get/__tests__/generator/deep-nested-attributes.test.js +18 -8
- package/build/get/__tests__/generator/description-escaping.test.js +18 -8
- package/build/get/__tests__/generator/empty-provider-resources.test.js +18 -8
- package/build/get/__tests__/generator/ephemeral-resources.test.js +18 -8
- package/build/get/__tests__/generator/export-sharding.test.js +18 -8
- package/build/get/__tests__/generator/import-style.test.js +18 -8
- package/build/get/__tests__/generator/module-generator.test.js +18 -8
- package/build/get/__tests__/generator/nested-types.test.js +18 -8
- package/build/get/__tests__/generator/provider-functions-name-collision.test.d.ts +2 -0
- package/build/get/__tests__/generator/provider-functions-name-collision.test.js +89 -0
- package/build/get/__tests__/generator/provider-functions.test.js +18 -8
- package/build/get/__tests__/generator/provider.test.js +18 -8
- package/build/get/__tests__/generator/resource-types.test.js +18 -8
- package/build/get/__tests__/generator/skipped-attributes.test.js +18 -8
- package/build/get/__tests__/generator/types.test.js +18 -8
- package/build/get/__tests__/generator/versions-file.test.js +18 -8
- package/build/get/__tests__/generator/write-only.test.js +18 -8
- package/build/get/__tests__/target-versions.test.js +18 -8
- package/build/get/__tests__/util.js +26 -12
- package/build/get/constructs-maker.d.ts +17 -0
- package/build/get/constructs-maker.js +72 -12
- package/build/get/generator/emitter/struct-emitter.js +18 -8
- package/build/get/generator/loop-detection.js +2 -3
- package/build/get/generator/models/attribute-model.js +3 -3
- package/build/get/generator/models/attribute-type-model.js +3 -3
- package/build/get/generator/models/provider-function-model.js +3 -4
- package/build/get/generator/models/supported-stored-classes.js +2 -3
- package/build/get/generator/provider-generator.d.ts +12 -0
- package/build/get/generator/provider-generator.js +18 -3
- package/build/get/generator/resource-parser.js +10 -4
- package/build/get/generator/sanitized-comments.js +2 -3
- package/build/get/generator/skipped-attributes.js +2 -3
- package/build/index.js +3 -3
- package/build/util.js +3 -4
- package/package.json +11 -11
- package/src/__tests__/provider.test.ts +3 -1
- package/src/get/__tests__/constructs-maker.test.ts +125 -1
- package/src/get/__tests__/generator/fixtures/provider-functions-name-collision.test.fixture.json +42 -0
- package/src/get/__tests__/generator/provider-functions-name-collision.test.ts +84 -0
- package/src/get/__tests__/util.ts +6 -1
- package/src/get/constructs-maker.ts +71 -2
- package/src/get/generator/models/attribute-type-model.ts +1 -2
- package/src/get/generator/provider-generator.ts +21 -2
- package/src/get/generator/resource-parser.ts +11 -2
- package/tsconfig.json +4 -0
package/build/util.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Copyright (c) HashiCorp, Inc
|
|
3
3
|
// SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.
|
|
5
|
+
exports.downcaseFirst = downcaseFirst;
|
|
6
|
+
exports.uppercaseFirst = uppercaseFirst;
|
|
6
7
|
/**
|
|
7
8
|
* Downcase the first character in a string.
|
|
8
9
|
*
|
|
@@ -14,7 +15,6 @@ function downcaseFirst(str) {
|
|
|
14
15
|
}
|
|
15
16
|
return `${str[0].toLocaleLowerCase()}${str.slice(1)}`;
|
|
16
17
|
}
|
|
17
|
-
exports.downcaseFirst = downcaseFirst;
|
|
18
18
|
/**
|
|
19
19
|
* Uppercase the first character in a string.
|
|
20
20
|
*
|
|
@@ -26,5 +26,4 @@ function uppercaseFirst(str) {
|
|
|
26
26
|
}
|
|
27
27
|
return `${str[0].toLocaleUpperCase()}${str.slice(1)}`;
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQkFBK0I7QUFDL0IsbUNBQW1DOzs7QUFFbkM7Ozs7R0FJRztBQUNILFNBQWdCLGFBQWEsQ0FBQyxHQUFXO0lBQ3ZDLElBQUksR0FBRyxLQUFLLEVBQUUsRUFBRSxDQUFDO1FBQ2YsT0FBTyxHQUFHLENBQUM7SUFDYixDQUFDO0lBQ0QsT0FBTyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxpQkFBaUIsRUFBRSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUN4RCxDQUFDO0FBTEQsc0NBS0M7QUFFRDs7OztHQUlHO0FBQ0gsU0FBZ0IsY0FBYyxDQUFDLEdBQVc7SUFDeEMsSUFBSSxHQUFHLEtBQUssRUFBRSxFQUFFLENBQUM7UUFDZixPQUFPLEdBQUcsQ0FBQztJQUNiLENBQUM7SUFDRCxPQUFPLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLGlCQUFpQixFQUFFLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0FBQ3hELENBQUM7QUFMRCx3Q0FLQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAoYykgSGFzaGlDb3JwLCBJbmNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBNUEwtMi4wXG5cbi8qKlxuICogRG93bmNhc2UgdGhlIGZpcnN0IGNoYXJhY3RlciBpbiBhIHN0cmluZy5cbiAqXG4gKiBAcGFyYW0gc3RyIHRoZSBzdHJpbmcgdG8gYmUgcHJvY2Vzc2VkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZG93bmNhc2VGaXJzdChzdHI6IHN0cmluZyk6IHN0cmluZyB7XG4gIGlmIChzdHIgPT09IFwiXCIpIHtcbiAgICByZXR1cm4gc3RyO1xuICB9XG4gIHJldHVybiBgJHtzdHJbMF0udG9Mb2NhbGVMb3dlckNhc2UoKX0ke3N0ci5zbGljZSgxKX1gO1xufVxuXG4vKipcbiAqIFVwcGVyY2FzZSB0aGUgZmlyc3QgY2hhcmFjdGVyIGluIGEgc3RyaW5nLlxuICpcbiAqIEBwYXJhbSBzdHIgdGhlIHN0cmluZyB0byBiZSBwcm9jZXNzZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB1cHBlcmNhc2VGaXJzdChzdHI6IHN0cmluZyk6IHN0cmluZyB7XG4gIGlmIChzdHIgPT09IFwiXCIpIHtcbiAgICByZXR1cm4gc3RyO1xuICB9XG4gIHJldHVybiBgJHtzdHJbMF0udG9Mb2NhbGVVcHBlckNhc2UoKX0ke3N0ci5zbGljZSgxKX1gO1xufVxuIl19
|
|
29
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQkFBK0I7QUFDL0IsbUNBQW1DOztBQU9uQyxzQ0FLQztBQU9ELHdDQUtDO0FBdEJEOzs7O0dBSUc7QUFDSCxTQUFnQixhQUFhLENBQUMsR0FBVztJQUN2QyxJQUFJLEdBQUcsS0FBSyxFQUFFLEVBQUUsQ0FBQztRQUNmLE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUNELE9BQU8sR0FBRyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsaUJBQWlCLEVBQUUsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7QUFDeEQsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxTQUFnQixjQUFjLENBQUMsR0FBVztJQUN4QyxJQUFJLEdBQUcsS0FBSyxFQUFFLEVBQUUsQ0FBQztRQUNmLE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUNELE9BQU8sR0FBRyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsaUJBQWlCLEVBQUUsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7QUFDeEQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAoYykgSGFzaGlDb3JwLCBJbmNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBNUEwtMi4wXG5cbi8qKlxuICogRG93bmNhc2UgdGhlIGZpcnN0IGNoYXJhY3RlciBpbiBhIHN0cmluZy5cbiAqXG4gKiBAcGFyYW0gc3RyIHRoZSBzdHJpbmcgdG8gYmUgcHJvY2Vzc2VkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZG93bmNhc2VGaXJzdChzdHI6IHN0cmluZyk6IHN0cmluZyB7XG4gIGlmIChzdHIgPT09IFwiXCIpIHtcbiAgICByZXR1cm4gc3RyO1xuICB9XG4gIHJldHVybiBgJHtzdHJbMF0udG9Mb2NhbGVMb3dlckNhc2UoKX0ke3N0ci5zbGljZSgxKX1gO1xufVxuXG4vKipcbiAqIFVwcGVyY2FzZSB0aGUgZmlyc3QgY2hhcmFjdGVyIGluIGEgc3RyaW5nLlxuICpcbiAqIEBwYXJhbSBzdHIgdGhlIHN0cmluZyB0byBiZSBwcm9jZXNzZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB1cHBlcmNhc2VGaXJzdChzdHI6IHN0cmluZyk6IHN0cmluZyB7XG4gIGlmIChzdHIgPT09IFwiXCIpIHtcbiAgICByZXR1cm4gc3RyO1xuICB9XG4gIHJldHVybiBgJHtzdHJbMF0udG9Mb2NhbGVVcHBlckNhc2UoKX0ke3N0ci5zbGljZSgxKX1gO1xufVxuIl19
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdktn/provider-generator",
|
|
3
|
-
"version": "0.25.0-pre.
|
|
3
|
+
"version": "0.25.0-pre.21",
|
|
4
4
|
"description": "Exposes API to generate Terraform CDK provider bindings",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -29,20 +29,20 @@
|
|
|
29
29
|
],
|
|
30
30
|
"license": "MPL-2.0",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"codemaker": "1.
|
|
33
|
-
"constructs": "10.
|
|
32
|
+
"codemaker": "1.140.0",
|
|
33
|
+
"constructs": "10.8.1",
|
|
34
34
|
"fs-extra": "11.3.6",
|
|
35
35
|
"glob": "13.0.6",
|
|
36
|
-
"jsii": "~
|
|
37
|
-
"jsii-pacmak": "1.
|
|
38
|
-
"@cdktn/commons": "0.25.0-pre.
|
|
39
|
-
"@cdktn/provider-schema": "0.25.0-pre.
|
|
40
|
-
"cdktn": "0.25.0-pre.
|
|
36
|
+
"jsii": "~6.0.0",
|
|
37
|
+
"jsii-pacmak": "1.140.0",
|
|
38
|
+
"@cdktn/commons": "0.25.0-pre.21",
|
|
39
|
+
"@cdktn/provider-schema": "0.25.0-pre.21",
|
|
40
|
+
"cdktn": "0.25.0-pre.21"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/fs-extra": "11.0.4",
|
|
44
44
|
"@types/node": "22.20.1",
|
|
45
|
-
"typescript": "
|
|
45
|
+
"typescript": "~6.0.0"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"watch": "tsc -w",
|
|
51
51
|
"watch-preserve-output": "tsc -w --preserveWatchOutput",
|
|
52
52
|
"test:update": "jest -u",
|
|
53
|
-
"package": "
|
|
53
|
+
"package": "bash package.sh",
|
|
54
54
|
"dist-clean": "rm -rf dist",
|
|
55
|
-
"package:js": "
|
|
55
|
+
"package:js": "bash package.sh"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -39,7 +39,9 @@ function directorySnapshot(root: string) {
|
|
|
39
39
|
content = fs.readFileSync(filePath, "utf-8");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
// glob yields native separators on Windows; key the snapshot by a
|
|
43
|
+
// "/"-separated path so it is identical on every platform.
|
|
44
|
+
output[file.split(path.sep).join(path.posix.sep)] = content;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
return output;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
ConstructsMakerModuleTarget,
|
|
9
9
|
ConstructsMakerProviderTarget,
|
|
10
10
|
} from "@cdktn/commons";
|
|
11
|
-
import { determineGoModuleName } from "../constructs-maker";
|
|
11
|
+
import { ConstructsMaker, determineGoModuleName } from "../constructs-maker";
|
|
12
12
|
|
|
13
13
|
describe("constructsMaker", () => {
|
|
14
14
|
describe("determineGoModuleName", () => {
|
|
@@ -115,4 +115,128 @@ describe("constructsMaker", () => {
|
|
|
115
115
|
expect(target.srcMakName).toEqual("security_group");
|
|
116
116
|
});
|
|
117
117
|
});
|
|
118
|
+
|
|
119
|
+
describe("removeOrphanedResourceFolders", () => {
|
|
120
|
+
let tmpDir: string;
|
|
121
|
+
|
|
122
|
+
const providerDir = (provider: string) =>
|
|
123
|
+
path.join(tmpDir, "providers", provider);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Lay out a provider folder containing the given resource directories plus
|
|
127
|
+
* the provider-level index files a real generation run writes.
|
|
128
|
+
*/
|
|
129
|
+
const seedProvider = async (provider: string, resources: string[]) => {
|
|
130
|
+
for (const resource of resources) {
|
|
131
|
+
await fs.mkdirs(path.join(providerDir(provider), resource));
|
|
132
|
+
await fs.writeFile(
|
|
133
|
+
path.join(providerDir(provider), resource, "index.ts"),
|
|
134
|
+
"// generated\n",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
await fs.writeFile(
|
|
138
|
+
path.join(providerDir(provider), "index.ts"),
|
|
139
|
+
"// generated by cdktn get\n",
|
|
140
|
+
);
|
|
141
|
+
await fs.writeFile(
|
|
142
|
+
path.join(providerDir(provider), "lazy-index.ts"),
|
|
143
|
+
"// generated by cdktn get\n",
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const makeMaker = (targetLanguage: Language) =>
|
|
148
|
+
new ConstructsMaker({
|
|
149
|
+
codeMakerOutput: tmpDir,
|
|
150
|
+
targetLanguage,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
beforeEach(async () => {
|
|
154
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "cdktf.orphans"));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
afterEach(async () => {
|
|
158
|
+
await fs.remove(tmpDir);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("removes resource folders the current run did not emit", async () => {
|
|
162
|
+
// 'teams' and 'data-lake-pipeline' exist only in the previously generated
|
|
163
|
+
// provider version; the new schema no longer produces them.
|
|
164
|
+
await seedProvider("mongodbatlas", [
|
|
165
|
+
"cluster",
|
|
166
|
+
"teams",
|
|
167
|
+
"data-lake-pipeline",
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const maker = makeMaker(Language.TYPESCRIPT);
|
|
171
|
+
maker.emittedResourceFolders = { mongodbatlas: ["cluster"] };
|
|
172
|
+
await maker.removeOrphanedResourceFolders();
|
|
173
|
+
|
|
174
|
+
expect(
|
|
175
|
+
await fs.pathExists(path.join(providerDir("mongodbatlas"), "cluster")),
|
|
176
|
+
).toBe(true);
|
|
177
|
+
expect(
|
|
178
|
+
await fs.pathExists(path.join(providerDir("mongodbatlas"), "teams")),
|
|
179
|
+
).toBe(false);
|
|
180
|
+
expect(
|
|
181
|
+
await fs.pathExists(
|
|
182
|
+
path.join(providerDir("mongodbatlas"), "data-lake-pipeline"),
|
|
183
|
+
),
|
|
184
|
+
).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("keeps provider-level files", async () => {
|
|
188
|
+
await seedProvider("mongodbatlas", ["cluster", "teams"]);
|
|
189
|
+
|
|
190
|
+
const maker = makeMaker(Language.TYPESCRIPT);
|
|
191
|
+
maker.emittedResourceFolders = { mongodbatlas: ["cluster"] };
|
|
192
|
+
await maker.removeOrphanedResourceFolders();
|
|
193
|
+
|
|
194
|
+
expect(
|
|
195
|
+
await fs.pathExists(path.join(providerDir("mongodbatlas"), "index.ts")),
|
|
196
|
+
).toBe(true);
|
|
197
|
+
expect(
|
|
198
|
+
await fs.pathExists(
|
|
199
|
+
path.join(providerDir("mongodbatlas"), "lazy-index.ts"),
|
|
200
|
+
),
|
|
201
|
+
).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("leaves providers that were not regenerated untouched", async () => {
|
|
205
|
+
// filterAlreadyGenerated can skip a provider entirely, in which case it
|
|
206
|
+
// has no entry in emittedResourceFolders and must not be pruned.
|
|
207
|
+
await seedProvider("mongodbatlas", ["cluster"]);
|
|
208
|
+
await seedProvider("onepassword", ["item"]);
|
|
209
|
+
|
|
210
|
+
const maker = makeMaker(Language.TYPESCRIPT);
|
|
211
|
+
maker.emittedResourceFolders = { mongodbatlas: ["cluster"] };
|
|
212
|
+
await maker.removeOrphanedResourceFolders();
|
|
213
|
+
|
|
214
|
+
expect(
|
|
215
|
+
await fs.pathExists(path.join(providerDir("onepassword"), "item")),
|
|
216
|
+
).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("is a no-op for non-TypeScript targets", async () => {
|
|
220
|
+
// Other languages co-locate providers and modules, so a dropped resource
|
|
221
|
+
// is indistinguishable from a hand-written construct.
|
|
222
|
+
await seedProvider("mongodbatlas", ["cluster", "teams"]);
|
|
223
|
+
|
|
224
|
+
const maker = makeMaker(Language.PYTHON);
|
|
225
|
+
maker.emittedResourceFolders = { mongodbatlas: ["cluster"] };
|
|
226
|
+
await maker.removeOrphanedResourceFolders();
|
|
227
|
+
|
|
228
|
+
expect(
|
|
229
|
+
await fs.pathExists(path.join(providerDir("mongodbatlas"), "teams")),
|
|
230
|
+
).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("does not throw when the provider folder is missing", async () => {
|
|
234
|
+
const maker = makeMaker(Language.TYPESCRIPT);
|
|
235
|
+
maker.emittedResourceFolders = { mongodbatlas: ["cluster"] };
|
|
236
|
+
|
|
237
|
+
await expect(
|
|
238
|
+
maker.removeOrphanedResourceFolders(),
|
|
239
|
+
).resolves.not.toThrow();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
118
242
|
});
|
package/src/get/__tests__/generator/fixtures/provider-functions-name-collision.test.fixture.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"provider_schemas": {
|
|
3
|
+
"registry.terraform.io/hashicorp/example": {
|
|
4
|
+
"resource_schemas": {
|
|
5
|
+
"example_provider_functions": {
|
|
6
|
+
"version": 0,
|
|
7
|
+
"block": {
|
|
8
|
+
"attributes": {
|
|
9
|
+
"id": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description_kind": "plain",
|
|
12
|
+
"computed": true
|
|
13
|
+
},
|
|
14
|
+
"name": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description_kind": "plain",
|
|
17
|
+
"optional": true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"functions": {
|
|
24
|
+
"sum": {
|
|
25
|
+
"description": "Adds up two numbers.",
|
|
26
|
+
"summary": "Sum numbers",
|
|
27
|
+
"return_type": "number",
|
|
28
|
+
"parameters": [
|
|
29
|
+
{
|
|
30
|
+
"name": "a",
|
|
31
|
+
"type": "number"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "b",
|
|
35
|
+
"type": "number"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { CodeMaker } from "codemaker";
|
|
6
|
+
import { TerraformProviderGenerator } from "../../generator/provider-generator";
|
|
7
|
+
import { sanitizeClassOrNamespaceName } from "../../generator/resource-parser";
|
|
8
|
+
import { createTmpHelper } from "../util";
|
|
9
|
+
|
|
10
|
+
const tmp = createTmpHelper();
|
|
11
|
+
|
|
12
|
+
// A provider declaring provider-defined functions emits them to
|
|
13
|
+
// providers/<provider>/provider-functions/index.ts. A resource named
|
|
14
|
+
// <provider>_provider_functions maps to that same directory, so without a
|
|
15
|
+
// guard the resource overwrites the functions wrapper and the provider index
|
|
16
|
+
// exports the name twice.
|
|
17
|
+
describe("resource named like the provider functions submodule", () => {
|
|
18
|
+
let workdir: string;
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
const code = new CodeMaker();
|
|
22
|
+
workdir = tmp("provider-functions-name-collision.test");
|
|
23
|
+
const spec = JSON.parse(
|
|
24
|
+
fs.readFileSync(
|
|
25
|
+
path.join(
|
|
26
|
+
__dirname,
|
|
27
|
+
"fixtures",
|
|
28
|
+
"provider-functions-name-collision.test.fixture.json",
|
|
29
|
+
),
|
|
30
|
+
"utf-8",
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
new TerraformProviderGenerator(code, spec).generateAll();
|
|
34
|
+
await code.save(workdir);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const read = (relPath: string) =>
|
|
38
|
+
fs.readFileSync(path.join(workdir, relPath), "utf-8");
|
|
39
|
+
|
|
40
|
+
test("the functions submodule holds the functions wrapper, not the resource", () => {
|
|
41
|
+
const output = read("providers/example/provider-functions/index.ts");
|
|
42
|
+
expect(output).toContain("class ExampleProviderFunctions");
|
|
43
|
+
expect(output).not.toContain("cdktn.TerraformResource");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("the resource is emitted to its own directory", () => {
|
|
47
|
+
const output = read(
|
|
48
|
+
"providers/example/provider-functions-resource/index.ts",
|
|
49
|
+
);
|
|
50
|
+
expect(output).toContain("class ProviderFunctionsResource");
|
|
51
|
+
expect(output).toContain("cdktn.TerraformResource");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("the provider index exports both, without duplicate names", () => {
|
|
55
|
+
const index = read("providers/example/index.ts");
|
|
56
|
+
expect(index).toContain(
|
|
57
|
+
"export * as providerFunctions from './provider-functions/index';",
|
|
58
|
+
);
|
|
59
|
+
expect(index).toContain(
|
|
60
|
+
"export * as providerFunctionsResource from './provider-functions-resource/index';",
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const exportedNames = [...index.matchAll(/export \* as (\w+) from/g)].map(
|
|
64
|
+
(m) => m[1],
|
|
65
|
+
);
|
|
66
|
+
expect(new Set(exportedNames).size).toBe(exportedNames.length);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("sanitizeClassOrNamespaceName", () => {
|
|
71
|
+
test("suffixes a resource named provider_functions", () => {
|
|
72
|
+
expect(sanitizeClassOrNamespaceName("provider_functions")).toBe(
|
|
73
|
+
"provider_functions_resource",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("still suffixes a resource named provider", () => {
|
|
78
|
+
expect(sanitizeClassOrNamespaceName("provider")).toBe("provider_resource");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("leaves an unrelated resource name alone", () => {
|
|
82
|
+
expect(sanitizeClassOrNamespaceName("function_app")).toBe("function_app");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -36,7 +36,12 @@ export function expectModuleToMatchSnapshot(
|
|
|
36
36
|
fixtureNames: string[],
|
|
37
37
|
) {
|
|
38
38
|
test(testName, async () => {
|
|
39
|
-
|
|
39
|
+
// Test names become a directory name, so collapse anything that is not
|
|
40
|
+
// filename-safe. The previous /\s*/ matched the empty string at index 0
|
|
41
|
+
// (no global flag either), which only prepended a "-" and left spaces --
|
|
42
|
+
// and characters like "*" in a test title are illegal in Windows paths.
|
|
43
|
+
const safeName = testName.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
44
|
+
await withTempDir(`${safeName}.test`, async () => {
|
|
40
45
|
const curdir = process.cwd();
|
|
41
46
|
fs.mkdirSync("module");
|
|
42
47
|
|
|
@@ -38,9 +38,14 @@ async function resolvePublishableFiles(dir: string): Promise<string[]> {
|
|
|
38
38
|
const stdout = await exec("npm", ["pack", "--dry-run", "--json"], {
|
|
39
39
|
cwd: dir,
|
|
40
40
|
});
|
|
41
|
-
|
|
41
|
+
// npm <=11 reports an array of packed packages; npm >=12 reports an object
|
|
42
|
+
// keyed by package name. Accept either so the build is npm-version agnostic.
|
|
43
|
+
const parsed = JSON.parse(stdout) as
|
|
44
|
+
| Array<{ files: { path: string }[] }>
|
|
45
|
+
| Record<string, { files: { path: string }[] }>;
|
|
46
|
+
const packInfo = Array.isArray(parsed) ? parsed[0] : Object.values(parsed)[0];
|
|
42
47
|
// Drop `..`-escape paths npm emits when following pnpm's node_modules symlinks.
|
|
43
|
-
return packInfo
|
|
48
|
+
return packInfo.files.map((f) => f.path).filter((p) => !p.includes(".."));
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
export interface GenerateJSIIOptions {
|
|
@@ -310,6 +315,8 @@ export class ConstructsMaker {
|
|
|
310
315
|
private readonly codeMakerOutdir: string;
|
|
311
316
|
private readonly code: CodeMaker;
|
|
312
317
|
private versions: { [providerName: string]: string | undefined };
|
|
318
|
+
/** Resource directory names emitted per provider folder during this run */
|
|
319
|
+
public emittedResourceFolders: { [providerName: string]: string[] };
|
|
313
320
|
|
|
314
321
|
constructor(
|
|
315
322
|
private readonly options: GetOptions,
|
|
@@ -323,6 +330,7 @@ export class ConstructsMaker {
|
|
|
323
330
|
fs.mkdirpSync(this.codeMakerOutdir);
|
|
324
331
|
this.code = new CodeMaker();
|
|
325
332
|
this.versions = {};
|
|
333
|
+
this.emittedResourceFolders = {};
|
|
326
334
|
}
|
|
327
335
|
private async generateTypescriptProvider(
|
|
328
336
|
target: ConstructsMakerProviderTarget,
|
|
@@ -335,6 +343,10 @@ export class ConstructsMaker {
|
|
|
335
343
|
generator.generate(target);
|
|
336
344
|
|
|
337
345
|
this.versions = { ...this.versions, ...generator.versions };
|
|
346
|
+
this.emittedResourceFolders = {
|
|
347
|
+
...this.emittedResourceFolders,
|
|
348
|
+
...generator.emittedResourceFolders,
|
|
349
|
+
};
|
|
338
350
|
endTSTimer();
|
|
339
351
|
}
|
|
340
352
|
|
|
@@ -590,6 +602,62 @@ export class ConstructsMaker {
|
|
|
590
602
|
});
|
|
591
603
|
}
|
|
592
604
|
|
|
605
|
+
/**
|
|
606
|
+
* Delete resource directories under `providers/` that this run did not emit, so each provider folder holds
|
|
607
|
+
* exactly the resources in the version it was generated against.
|
|
608
|
+
*
|
|
609
|
+
* Walks only providers present in `emittedResourceFolders`, keeping any provider this run skipped. Within those,
|
|
610
|
+
* removes every subdirectory not emitted and leaves provider-level files (`index.ts`, `lazy-index.ts`) in place.
|
|
611
|
+
* Deletes files, so it must run after `save()` has written the current run's output.
|
|
612
|
+
*
|
|
613
|
+
* TypeScript only: `providers/<name>/<resource>/` maps one-to-one onto the emitted index exports. Other languages
|
|
614
|
+
* co-locate providers and modules in the same folders, which makes a dropped resource indistinguishable from a
|
|
615
|
+
* hand-written construct.
|
|
616
|
+
*/
|
|
617
|
+
public async removeOrphanedResourceFolders() {
|
|
618
|
+
if (!this.isJavascriptTarget) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
for (const [provider, emittedFolders] of Object.entries(
|
|
623
|
+
this.emittedResourceFolders,
|
|
624
|
+
)) {
|
|
625
|
+
const providerFolder = path.resolve(
|
|
626
|
+
this.codeMakerOutdir,
|
|
627
|
+
"providers",
|
|
628
|
+
provider,
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
let entries: string[] = [];
|
|
632
|
+
try {
|
|
633
|
+
entries = await fs.readdir(providerFolder);
|
|
634
|
+
} catch (e) {
|
|
635
|
+
logger.debug(
|
|
636
|
+
`Could not list resource folders in '${providerFolder}': ${e}`,
|
|
637
|
+
);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const expected = new Set(emittedFolders);
|
|
642
|
+
for (const entry of entries) {
|
|
643
|
+
if (expected.has(entry)) {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const entryPath = path.resolve(providerFolder, entry);
|
|
648
|
+
if (!fs.statSync(entryPath).isDirectory()) {
|
|
649
|
+
// Leave index.ts / lazy-index.ts and any other provider-level files alone.
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
logger.debug(
|
|
654
|
+
`Removing orphaned resource folder '${entry}' from provider '${provider}': it was not emitted for the current provider version`,
|
|
655
|
+
);
|
|
656
|
+
await fs.remove(entryPath);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
593
661
|
// emits a constraints.json file with a map of the used provider fqpns and version constraints
|
|
594
662
|
// this is used for caching purposes
|
|
595
663
|
private emitConstraintsFile(
|
|
@@ -752,6 +820,7 @@ a NODE_OPTIONS variable, we won't override it. Hence, the provider generation mi
|
|
|
752
820
|
|
|
753
821
|
if (this.isJavascriptTarget) {
|
|
754
822
|
await this.save();
|
|
823
|
+
await this.removeOrphanedResourceFolders();
|
|
755
824
|
}
|
|
756
825
|
|
|
757
826
|
if (!this.isJavascriptTarget || this.options.outputJsii) {
|
|
@@ -215,8 +215,7 @@ export interface CollectionAttributeTypeModel extends AttributeTypeModel {
|
|
|
215
215
|
// into the nearest typed "Any"-wrapper. undefined only defensively - see
|
|
216
216
|
// resolveStoredClassName in supported-stored-classes.ts.
|
|
217
217
|
readonly resolvedStoredClass:
|
|
218
|
-
|
|
219
|
-
| undefined;
|
|
218
|
+
{ name: string; collapsed: boolean } | undefined;
|
|
220
219
|
}
|
|
221
220
|
|
|
222
221
|
export class ListAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
@@ -113,6 +113,11 @@ export class TerraformProviderGenerator {
|
|
|
113
113
|
private providerFunctionsEmitter: ProviderFunctionsEmitter;
|
|
114
114
|
private readonly importExtension: string;
|
|
115
115
|
public versions: { [fqpn: string]: string | undefined } = {};
|
|
116
|
+
/**
|
|
117
|
+
* Resource directory names emitted per provider folder Used to prune directories
|
|
118
|
+
* left over from a previous provider version.
|
|
119
|
+
*/
|
|
120
|
+
public emittedResourceFolders: { [provider: string]: string[] } = {};
|
|
116
121
|
|
|
117
122
|
constructor(
|
|
118
123
|
private readonly code: CodeMaker,
|
|
@@ -298,6 +303,20 @@ export class TerraformProviderGenerator {
|
|
|
298
303
|
|
|
299
304
|
this.emitIndexFile(name, files);
|
|
300
305
|
this.emitLazyIndexFile(name, files);
|
|
306
|
+
|
|
307
|
+
// Record exactly what this run emitted, derived the same way the index
|
|
308
|
+
// files derive their exports, so pruning can never disagree with them.
|
|
309
|
+
this.emittedResourceFolders[name] = files.map((file) =>
|
|
310
|
+
this.resourceFolderName(name, file),
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Map an emitted file path to the resource directory name it lives in, e.g.
|
|
316
|
+
* `providers/mongodbatlas/teams/index.ts` -> `teams`.
|
|
317
|
+
*/
|
|
318
|
+
private resourceFolderName(provider: ProviderName, file: string): string {
|
|
319
|
+
return file.replace(`providers/${provider}/`, "").replace("/index.ts", "");
|
|
301
320
|
}
|
|
302
321
|
|
|
303
322
|
private emitProviderFunctions(
|
|
@@ -333,7 +352,7 @@ export class TerraformProviderGenerator {
|
|
|
333
352
|
this.code.openFile(filePath);
|
|
334
353
|
this.code.line("// generated by cdktn get");
|
|
335
354
|
for (const file of files) {
|
|
336
|
-
const dirName =
|
|
355
|
+
const dirName = this.resourceFolderName(provider, file);
|
|
337
356
|
this.code.line(
|
|
338
357
|
`export * as ${toCamelCase(dirName)} from './${dirName}/index${this.importExtension}';`,
|
|
339
358
|
);
|
|
@@ -348,7 +367,7 @@ export class TerraformProviderGenerator {
|
|
|
348
367
|
this.code.openFile(filePath);
|
|
349
368
|
this.code.line("// generated by cdktn get");
|
|
350
369
|
for (const file of files) {
|
|
351
|
-
const dirName =
|
|
370
|
+
const dirName = this.resourceFolderName(provider, file);
|
|
352
371
|
this.code.line(
|
|
353
372
|
`Object.defineProperty(exports, '${toCamelCase(
|
|
354
373
|
dirName,
|
|
@@ -82,8 +82,17 @@ export function sanitizeClassOrNamespaceName(
|
|
|
82
82
|
isProvider = false,
|
|
83
83
|
) {
|
|
84
84
|
const resourceIsNamedProvider = !isProvider && baseName === "provider";
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// A resource named `<provider>_provider_functions` maps to the same
|
|
86
|
+
// providers/<provider>/provider-functions/ directory that the provider's
|
|
87
|
+
// functions submodule is emitted to, and would silently overwrite it.
|
|
88
|
+
const resourceIsNamedProviderFunctions =
|
|
89
|
+
!isProvider && baseName === "provider_functions";
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
isReservedClassOrNamespaceName(baseName) ||
|
|
93
|
+
resourceIsNamedProvider ||
|
|
94
|
+
resourceIsNamedProviderFunctions
|
|
95
|
+
) {
|
|
87
96
|
return `${baseName}_resource`;
|
|
88
97
|
} else {
|
|
89
98
|
return baseName;
|