@cdktn/provider-generator 0.24.0-pre.87 → 0.24.0-pre.89
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.d.ts +9 -3
- package/build/__tests__/edge-provider-schema/builder.js +6 -3
- package/build/__tests__/edge-provider-schema/index.js +119 -1
- package/build/__tests__/provider.test.js +4 -1
- package/build/get/__tests__/generator/ephemeral-resources.test.d.ts +2 -0
- package/build/get/__tests__/generator/ephemeral-resources.test.js +49 -0
- package/build/get/__tests__/generator/provider-functions.test.d.ts +2 -0
- package/build/get/__tests__/generator/provider-functions.test.js +176 -0
- package/build/get/__tests__/generator/provider.test.js +9 -1
- package/build/get/__tests__/generator/write-only.test.d.ts +2 -0
- package/build/get/__tests__/generator/write-only.test.js +65 -0
- package/build/get/__tests__/target-versions.test.d.ts +2 -0
- package/build/get/__tests__/target-versions.test.js +171 -0
- package/build/get/constructs-maker.d.ts +8 -1
- package/build/get/constructs-maker.js +13 -4
- package/build/get/generator/emitter/attributes-emitter.d.ts +12 -2
- package/build/get/generator/emitter/attributes-emitter.js +47 -5
- package/build/get/generator/emitter/index.d.ts +1 -0
- package/build/get/generator/emitter/index.js +2 -1
- package/build/get/generator/emitter/provider-functions-emitter.d.ts +11 -0
- package/build/get/generator/emitter/provider-functions-emitter.js +94 -0
- package/build/get/generator/emitter/resource-emitter.d.ts +5 -1
- package/build/get/generator/emitter/resource-emitter.js +83 -7
- package/build/get/generator/models/attribute-model.d.ts +2 -0
- package/build/get/generator/models/attribute-model.js +2 -1
- package/build/get/generator/models/index.d.ts +1 -0
- package/build/get/generator/models/index.js +2 -1
- package/build/get/generator/models/provider-function-model.d.ts +102 -0
- package/build/get/generator/models/provider-function-model.js +667 -0
- package/build/get/generator/models/resource-model.d.ts +3 -0
- package/build/get/generator/models/resource-model.js +13 -4
- package/build/get/generator/models/struct.d.ts +2 -0
- package/build/get/generator/models/struct.js +6 -2
- package/build/get/generator/provider-generator.d.ts +2 -0
- package/build/get/generator/provider-generator.js +31 -3
- package/build/get/generator/resource-parser.js +3 -1
- package/package.json +5 -5
- package/src/__tests__/__snapshots__/edge-provider-schema.test.ts.snap +4 -0
- package/src/__tests__/edge-provider-schema/builder.ts +17 -1
- package/src/__tests__/edge-provider-schema/index.ts +124 -1
- package/src/__tests__/provider.test.ts +3 -0
- package/src/get/__tests__/generator/__snapshots__/ephemeral-resources.test.ts.snap +524 -0
- package/src/get/__tests__/generator/__snapshots__/provider-functions.test.ts.snap +444 -0
- package/src/get/__tests__/generator/__snapshots__/write-only.test.ts.snap +262 -0
- package/src/get/__tests__/generator/ephemeral-resources.test.ts +46 -0
- package/src/get/__tests__/generator/fixtures/ephemeral-resources.test.fixture.json +160 -0
- package/src/get/__tests__/generator/fixtures/provider-functions-collision.test.fixture.json +31 -0
- package/src/get/__tests__/generator/fixtures/provider-functions-synthetic.test.fixture.json +192 -0
- package/src/get/__tests__/generator/fixtures/provider-functions.test.fixture.json +170 -0
- package/src/get/__tests__/generator/fixtures/write-only.test.fixture.json +58 -0
- package/src/get/__tests__/generator/provider-functions.test.ts +262 -0
- package/src/get/__tests__/generator/provider.test.ts +8 -0
- package/src/get/__tests__/generator/write-only.test.ts +71 -0
- package/src/get/__tests__/target-versions.test.ts +206 -0
- package/src/get/constructs-maker.ts +43 -3
- package/src/get/generator/emitter/attributes-emitter.ts +61 -6
- package/src/get/generator/emitter/index.ts +1 -0
- package/src/get/generator/emitter/provider-functions-emitter.ts +117 -0
- package/src/get/generator/emitter/resource-emitter.ts +110 -6
- package/src/get/generator/models/attribute-model.ts +3 -0
- package/src/get/generator/models/index.ts +1 -0
- package/src/get/generator/models/provider-function-model.ts +872 -0
- package/src/get/generator/models/resource-model.ts +25 -3
- package/src/get/generator/models/struct.ts +9 -1
- package/src/get/generator/provider-generator.ts +70 -4
- package/src/get/generator/resource-parser.ts +2 -0
|
@@ -0,0 +1,262 @@
|
|
|
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 { TerraformProviderGenerator } from "../../generator/provider-generator";
|
|
6
|
+
import {
|
|
7
|
+
assertNoFunctionsGetterCollision,
|
|
8
|
+
buildProviderFunctionsModel,
|
|
9
|
+
} from "../../generator/models/provider-function-model";
|
|
10
|
+
import { CodeMaker } from "codemaker";
|
|
11
|
+
import { FunctionSignature } from "@cdktn/commons";
|
|
12
|
+
import { createTmpHelper } from "../util";
|
|
13
|
+
|
|
14
|
+
const tmp = createTmpHelper();
|
|
15
|
+
|
|
16
|
+
test("generate provider functions for the time provider (real terraform 1.15.6 schema fragment)", async () => {
|
|
17
|
+
const code = new CodeMaker();
|
|
18
|
+
const workdir = tmp("provider-functions.test");
|
|
19
|
+
const spec = JSON.parse(
|
|
20
|
+
fs.readFileSync(
|
|
21
|
+
path.join(__dirname, "fixtures", "provider-functions.test.fixture.json"),
|
|
22
|
+
"utf-8",
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
new TerraformProviderGenerator(code, spec).generateAll();
|
|
26
|
+
await code.save(workdir);
|
|
27
|
+
|
|
28
|
+
const providerFunctionsOutput = fs.readFileSync(
|
|
29
|
+
path.join(workdir, "providers/time/provider-functions/index.ts"),
|
|
30
|
+
"utf-8",
|
|
31
|
+
);
|
|
32
|
+
expect(providerFunctionsOutput).toMatchSnapshot("time-provider-functions");
|
|
33
|
+
|
|
34
|
+
const providerOutput = fs.readFileSync(
|
|
35
|
+
path.join(workdir, "providers/time/provider/index.ts"),
|
|
36
|
+
"utf-8",
|
|
37
|
+
);
|
|
38
|
+
expect(providerOutput).toMatchSnapshot("time-provider");
|
|
39
|
+
|
|
40
|
+
const providerIndex = fs.readFileSync(
|
|
41
|
+
path.join(workdir, "providers/time/index.ts"),
|
|
42
|
+
"utf-8",
|
|
43
|
+
);
|
|
44
|
+
expect(providerIndex).toMatchSnapshot("provider-index");
|
|
45
|
+
|
|
46
|
+
const providerLazyIndex = fs.readFileSync(
|
|
47
|
+
path.join(workdir, "providers/time/lazy-index.ts"),
|
|
48
|
+
"utf-8",
|
|
49
|
+
);
|
|
50
|
+
expect(providerLazyIndex).toMatchSnapshot("provider-lazy-index");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("generate provider functions covering variadic parameters, primitive/list returns, and a 'default' parameter name", () => {
|
|
54
|
+
let providerFunctionsOutput: string;
|
|
55
|
+
let providerIndex: string;
|
|
56
|
+
let providerLazyIndex: string;
|
|
57
|
+
|
|
58
|
+
beforeAll(async () => {
|
|
59
|
+
const code = new CodeMaker();
|
|
60
|
+
const workdir = tmp("provider-functions-synthetic.test");
|
|
61
|
+
const spec = JSON.parse(
|
|
62
|
+
fs.readFileSync(
|
|
63
|
+
path.join(
|
|
64
|
+
__dirname,
|
|
65
|
+
"fixtures",
|
|
66
|
+
"provider-functions-synthetic.test.fixture.json",
|
|
67
|
+
),
|
|
68
|
+
"utf-8",
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
new TerraformProviderGenerator(code, spec).generateAll();
|
|
72
|
+
await code.save(workdir);
|
|
73
|
+
|
|
74
|
+
providerFunctionsOutput = fs.readFileSync(
|
|
75
|
+
path.join(workdir, "providers/example/provider-functions/index.ts"),
|
|
76
|
+
"utf-8",
|
|
77
|
+
);
|
|
78
|
+
providerIndex = fs.readFileSync(
|
|
79
|
+
path.join(workdir, "providers/example/index.ts"),
|
|
80
|
+
"utf-8",
|
|
81
|
+
);
|
|
82
|
+
providerLazyIndex = fs.readFileSync(
|
|
83
|
+
path.join(workdir, "providers/example/lazy-index.ts"),
|
|
84
|
+
"utf-8",
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("matches the snapshot", () => {
|
|
89
|
+
expect(providerFunctionsOutput).toMatchSnapshot(
|
|
90
|
+
"example-provider-functions",
|
|
91
|
+
);
|
|
92
|
+
expect(providerIndex).toMatchSnapshot("provider-index");
|
|
93
|
+
expect(providerLazyIndex).toMatchSnapshot("provider-lazy-index");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("nested collection docstrings use the compact recursive Array/Set notation", () => {
|
|
97
|
+
// list(set(string)): notation carries the structure; a plain Terraform
|
|
98
|
+
// list needs no prose note.
|
|
99
|
+
expect(providerFunctionsOutput).toContain(
|
|
100
|
+
"@param {Array<Set<string>>} nameGroups",
|
|
101
|
+
);
|
|
102
|
+
expect(providerFunctionsOutput).toContain("@returns {Array<Set<string>>}");
|
|
103
|
+
// set(list(string)): Set<...> notation plus the set-semantics prose note
|
|
104
|
+
// (the jsii type is still an array - the note keeps a reader from
|
|
105
|
+
// literally passing a JS Set).
|
|
106
|
+
expect(providerFunctionsOutput).toContain(
|
|
107
|
+
"@param {Set<Array<string>>} nameLists - Terraform set; ordering is not guaranteed and duplicate values are removed.",
|
|
108
|
+
);
|
|
109
|
+
expect(providerFunctionsOutput).toContain(
|
|
110
|
+
"@returns {Set<Array<string>>} Terraform set; ordering is not guaranteed and duplicate values are removed.",
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// This package has no TypeScript-compiling harness
|
|
115
|
+
// for generated output (no fixture/test anywhere generates a snippet and
|
|
116
|
+
// feeds it through `tsc`/`ts.transpileModule`, and `cdktn` itself isn't a
|
|
117
|
+
// dependency of this package to typecheck against), so these are
|
|
118
|
+
// structural assertions on the emitted declaration text rather than an
|
|
119
|
+
// actual compile - see also the runtime-level proof in
|
|
120
|
+
// packages/cdktn/test/provider-functions.test.ts, which exercises the
|
|
121
|
+
// same union/token mechanics these signatures rely on.
|
|
122
|
+
describe("composition: a function's return type is directly assignable to another function's parameter of the 'same' Terraform type", () => {
|
|
123
|
+
test("flagsForSeed()'s set(bool) return type (cdktn.IResolvable) is one of the exact union members flagSet()'s set(bool) parameter declares", () => {
|
|
124
|
+
expect(providerFunctionsOutput).toContain(
|
|
125
|
+
"public flagsForSeed(seed: string): cdktn.IResolvable {",
|
|
126
|
+
);
|
|
127
|
+
expect(providerFunctionsOutput).toContain(
|
|
128
|
+
"public flagSet(flags: Array<boolean | cdktn.IResolvable> | cdktn.IResolvable): cdktn.IResolvable {",
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("defaultTags()'s map(string) return type is textually identical to tagMap()'s map(string) parameter type", () => {
|
|
133
|
+
expect(providerFunctionsOutput).toContain(
|
|
134
|
+
"public defaultTags(prefix: string): { [key: string]: string } {",
|
|
135
|
+
);
|
|
136
|
+
expect(providerFunctionsOutput).toContain(
|
|
137
|
+
"public tagMap(tags: { [key: string]: string }): string {",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("a list(bool) parameter (not just set(bool)) also accepts a whole-collection token", () => {
|
|
143
|
+
expect(providerFunctionsOutput).toContain(
|
|
144
|
+
"public flagsOrdered(flags: Array<boolean | cdktn.IResolvable> | cdktn.IResolvable): cdktn.IResolvable {",
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a list(object) parameter widens to any[] | cdktn.IResolvable (whole-collection token accepted)", () => {
|
|
149
|
+
expect(providerFunctionsOutput).toContain(
|
|
150
|
+
"public describeConfigs(configs: any[] | cdktn.IResolvable): string {",
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("buildProviderFunctionsModel throws when two function names collapse to the same generated method name", () => {
|
|
156
|
+
expect(() =>
|
|
157
|
+
buildProviderFunctionsModel("example", {
|
|
158
|
+
foo_bar: { return_type: "string", parameters: [] },
|
|
159
|
+
foo__bar: { return_type: "string", parameters: [] },
|
|
160
|
+
}),
|
|
161
|
+
).toThrow(/foo_bar/);
|
|
162
|
+
expect(() =>
|
|
163
|
+
buildProviderFunctionsModel("example", {
|
|
164
|
+
foo_bar: { return_type: "string", parameters: [] },
|
|
165
|
+
foo__bar: { return_type: "string", parameters: [] },
|
|
166
|
+
}),
|
|
167
|
+
).toThrow(/foo__bar/);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("buildProviderFunctionsModel throws when two parameter names collapse within one function", () => {
|
|
171
|
+
expect(() =>
|
|
172
|
+
buildProviderFunctionsModel("example", {
|
|
173
|
+
my_function: {
|
|
174
|
+
return_type: "string",
|
|
175
|
+
parameters: [
|
|
176
|
+
{ name: "some_value", type: "string" },
|
|
177
|
+
{ name: "some__value", type: "string" },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
).toThrow(/some_value/);
|
|
182
|
+
expect(() =>
|
|
183
|
+
buildProviderFunctionsModel("example", {
|
|
184
|
+
my_function: {
|
|
185
|
+
return_type: "string",
|
|
186
|
+
parameters: [
|
|
187
|
+
{ name: "some_value", type: "string" },
|
|
188
|
+
{ name: "some__value", type: "string" },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
).toThrow(/some__value/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Both of these assert the model-level throw happens before any file is
|
|
196
|
+
// generated: buildProviderFunctionsModel is called directly, with no
|
|
197
|
+
// CodeMaker/generator involved, so there is no emission step to reach.
|
|
198
|
+
test("buildProviderFunctionsModel throws when a function name sanitizes to 'constructor'", () => {
|
|
199
|
+
// The nested signature literal is cast to `FunctionSignature` (rather than
|
|
200
|
+
// relying on contextual typing from the surrounding index signature)
|
|
201
|
+
// because TypeScript special-cases a property literally named
|
|
202
|
+
// "constructor" - contextually typing it against `Function` (from
|
|
203
|
+
// `Object.prototype`) instead of the index signature's `FunctionSignature`,
|
|
204
|
+
// and spuriously flagging `return_type: "string"` as an error. See
|
|
205
|
+
// https://github.com/microsoft/TypeScript/issues/40776.
|
|
206
|
+
const signature: FunctionSignature = {
|
|
207
|
+
return_type: "string",
|
|
208
|
+
parameters: [],
|
|
209
|
+
};
|
|
210
|
+
expect(() =>
|
|
211
|
+
buildProviderFunctionsModel("example", {
|
|
212
|
+
constructor: signature,
|
|
213
|
+
}),
|
|
214
|
+
).toThrow(/constructor/);
|
|
215
|
+
expect(() =>
|
|
216
|
+
buildProviderFunctionsModel("example", {
|
|
217
|
+
constructor: signature,
|
|
218
|
+
}),
|
|
219
|
+
).toThrow(/example/);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("buildProviderFunctionsModel throws when a function name sanitizes to 'providerLocalName'", () => {
|
|
223
|
+
expect(() =>
|
|
224
|
+
buildProviderFunctionsModel("example", {
|
|
225
|
+
provider_local_name: { return_type: "string", parameters: [] },
|
|
226
|
+
}),
|
|
227
|
+
).toThrow(/provider_local_name/);
|
|
228
|
+
expect(() =>
|
|
229
|
+
buildProviderFunctionsModel("example", {
|
|
230
|
+
provider_local_name: { return_type: "string", parameters: [] },
|
|
231
|
+
}),
|
|
232
|
+
).toThrow(/providerLocalName/);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("assertNoFunctionsGetterCollision throws when the provider's own config schema would generate a 'functions' property", () => {
|
|
236
|
+
expect(() =>
|
|
237
|
+
assertNoFunctionsGetterCollision("example", ["alias", "functions"]),
|
|
238
|
+
).toThrow(/"functions"/);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("assertNoFunctionsGetterCollision does not throw when there is no colliding attribute", () => {
|
|
242
|
+
expect(() =>
|
|
243
|
+
assertNoFunctionsGetterCollision("example", ["alias"]),
|
|
244
|
+
).not.toThrow();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("generation throws when a provider's config attribute collides with the generated 'functions' getter", async () => {
|
|
248
|
+
const code = new CodeMaker();
|
|
249
|
+
const spec = JSON.parse(
|
|
250
|
+
fs.readFileSync(
|
|
251
|
+
path.join(
|
|
252
|
+
__dirname,
|
|
253
|
+
"fixtures",
|
|
254
|
+
"provider-functions-collision.test.fixture.json",
|
|
255
|
+
),
|
|
256
|
+
"utf-8",
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
expect(() =>
|
|
260
|
+
new TerraformProviderGenerator(code, spec).generateAll(),
|
|
261
|
+
).toThrow(/"functions"/);
|
|
262
|
+
});
|
|
@@ -25,6 +25,10 @@ test("generate provider", async () => {
|
|
|
25
25
|
"utf-8",
|
|
26
26
|
);
|
|
27
27
|
expect(output).toMatchSnapshot();
|
|
28
|
+
// aws has no provider-defined functions in this fixture - no getter, no
|
|
29
|
+
// cross-directory import should be emitted.
|
|
30
|
+
expect(output).not.toContain("public get functions()");
|
|
31
|
+
expect(output).not.toContain("provider-functions");
|
|
28
32
|
});
|
|
29
33
|
|
|
30
34
|
test("generate provider with only block_types", async () => {
|
|
@@ -48,4 +52,8 @@ test("generate provider with only block_types", async () => {
|
|
|
48
52
|
"utf-8",
|
|
49
53
|
);
|
|
50
54
|
expect(output).toMatchSnapshot();
|
|
55
|
+
// elasticstack has no provider-defined functions in this fixture - no
|
|
56
|
+
// getter, no cross-directory import should be emitted.
|
|
57
|
+
expect(output).not.toContain("public get functions()");
|
|
58
|
+
expect(output).not.toContain("provider-functions");
|
|
51
59
|
});
|
|
@@ -0,0 +1,71 @@
|
|
|
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 { TerraformProviderGenerator } from "../../generator/provider-generator";
|
|
6
|
+
import { CodeMaker } from "codemaker";
|
|
7
|
+
import { createTmpHelper } from "../util";
|
|
8
|
+
|
|
9
|
+
const tmp = createTmpHelper();
|
|
10
|
+
|
|
11
|
+
test("generate a vault_alicloud_secret_backend resource with a write-only attribute", async () => {
|
|
12
|
+
const code = new CodeMaker();
|
|
13
|
+
const workdir = tmp("write-only.test");
|
|
14
|
+
const spec = JSON.parse(
|
|
15
|
+
fs.readFileSync(
|
|
16
|
+
path.join(__dirname, "fixtures", "write-only.test.fixture.json"),
|
|
17
|
+
"utf-8",
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
new TerraformProviderGenerator(code, spec).generateAll();
|
|
21
|
+
await code.save(workdir);
|
|
22
|
+
|
|
23
|
+
const output = fs.readFileSync(
|
|
24
|
+
path.join(workdir, "providers/vault/alicloud-secret-backend/index.ts"),
|
|
25
|
+
"utf-8",
|
|
26
|
+
);
|
|
27
|
+
expect(output).toMatchSnapshot();
|
|
28
|
+
|
|
29
|
+
// The write-only attribute's getter is deprecated ...
|
|
30
|
+
expect(output).toMatch(
|
|
31
|
+
/@deprecated Write-only: the provider never returns this value[\s\S]*?public get secretKeyWo\(\)/,
|
|
32
|
+
);
|
|
33
|
+
// ... its setter is a plain assignment ...
|
|
34
|
+
expect(output).toMatch(
|
|
35
|
+
/public set secretKeyWo\(value: string\) \{\n\s*this\._secretKeyWo = value;\n\s*\}/,
|
|
36
|
+
);
|
|
37
|
+
// ... and usage registration happens at resolve time, from inside
|
|
38
|
+
// synthesizeAttributes()/synthesizeHclAttributes(), by wrapping the
|
|
39
|
+
// mapped value in markWriteOnlyAttribute() ...
|
|
40
|
+
expect(output).toMatch(
|
|
41
|
+
/secret_key_wo: this\.markWriteOnlyAttribute\(cdktn\.stringToTerraform\(this\._secretKeyWo\)\),/,
|
|
42
|
+
);
|
|
43
|
+
expect(output).toMatch(
|
|
44
|
+
/value: this\.markWriteOnlyAttribute\(cdktn\.stringToHclTerraform\(this\._secretKeyWo\)\),/,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// The required write-only attribute gets no reset method (required
|
|
48
|
+
// attributes never do) ...
|
|
49
|
+
expect(output).not.toMatch(/resetSecretKeyWo\b/);
|
|
50
|
+
// ... but the OPTIONAL write-only sibling does, exercising the reset ->
|
|
51
|
+
// clear -> synth-passes path end to end at the generator level.
|
|
52
|
+
expect(output).toMatch(
|
|
53
|
+
/public resetSecondarySecretKeyWo\(\) \{\n\s*this\._secondarySecretKeyWo = undefined;\n\s*\}/,
|
|
54
|
+
);
|
|
55
|
+
expect(output).toMatch(
|
|
56
|
+
/secondary_secret_key_wo: this\.markWriteOnlyAttribute\(cdktn\.stringToTerraform\(this\._secondarySecretKeyWo\)\),/,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// The non-write-only sibling attribute is untouched: its getter is
|
|
60
|
+
// emitted directly (no @deprecated JSDoc immediately above), its setter
|
|
61
|
+
// has no registration call, and its synthesis isn't wrapped.
|
|
62
|
+
expect(output).toMatch(
|
|
63
|
+
/private _secretKeyWoVersion\?: number; \n\s*public get secretKeyWoVersion\(\)/,
|
|
64
|
+
);
|
|
65
|
+
expect(output).toMatch(
|
|
66
|
+
/public set secretKeyWoVersion\(value: number\) \{\n\s*this\._secretKeyWoVersion = value;\n\s*\}/,
|
|
67
|
+
);
|
|
68
|
+
expect(output).toMatch(
|
|
69
|
+
/secret_key_wo_version: cdktn\.numberToTerraform\(this\._secretKeyWoVersion\),/,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import * as fs from "fs-extra";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import {
|
|
6
|
+
Language,
|
|
7
|
+
TerraformDependencyConstraint,
|
|
8
|
+
TerraformTargetVersions,
|
|
9
|
+
} from "@cdktn/commons";
|
|
10
|
+
import { createTmpHelper } from "./util";
|
|
11
|
+
|
|
12
|
+
const mockReadSchema = jest.fn();
|
|
13
|
+
jest.mock("@cdktn/provider-schema", () => ({
|
|
14
|
+
readSchema: (...args: unknown[]) => mockReadSchema(...args),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Imported after the mock so ConstructsMaker picks up the mocked readSchema.
|
|
18
|
+
import { ConstructsMaker, GetOptions } from "../constructs-maker";
|
|
19
|
+
|
|
20
|
+
const tmp = createTmpHelper();
|
|
21
|
+
|
|
22
|
+
const constraint: TerraformDependencyConstraint = {
|
|
23
|
+
fqn: "hashicorp/random",
|
|
24
|
+
name: "random",
|
|
25
|
+
source: "hashicorp/random",
|
|
26
|
+
version: "3.1.3",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Minimal schema - no resources/data sources - so generation stays within
|
|
30
|
+
// pure TypeScript emission (no jsii-pacmak invocation), mirroring the
|
|
31
|
+
// "empty-provider-resources" fixture used elsewhere in this package.
|
|
32
|
+
const fakeSchema = {
|
|
33
|
+
providerSchema: {
|
|
34
|
+
format_version: "0.2" as const,
|
|
35
|
+
provider_schemas: {
|
|
36
|
+
"registry.terraform.io/hashicorp/random": {
|
|
37
|
+
resource_schemas: null,
|
|
38
|
+
data_source_schemas: null,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
provider_versions: {
|
|
42
|
+
"registry.terraform.io/hashicorp/random": "3.1.3",
|
|
43
|
+
},
|
|
44
|
+
cli_name: "terraform",
|
|
45
|
+
cli_version: "1.7.5",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe("targetVersions threading through 'cdktn get'", () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
mockReadSchema.mockReset();
|
|
52
|
+
mockReadSchema.mockResolvedValue(fakeSchema);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("passes GetOptions.targetVersions through to readSchema", async () => {
|
|
56
|
+
const workdir = tmp("target-versions-thread.test");
|
|
57
|
+
const targetVersions: TerraformTargetVersions = { terraform: ">=1.5.7" };
|
|
58
|
+
const options: GetOptions = {
|
|
59
|
+
codeMakerOutput: workdir,
|
|
60
|
+
targetLanguage: Language.TYPESCRIPT,
|
|
61
|
+
targetVersions,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const maker = new ConstructsMaker(options, "/some/cache/path");
|
|
65
|
+
await maker.generate([constraint]);
|
|
66
|
+
|
|
67
|
+
expect(mockReadSchema).toHaveBeenCalledWith(
|
|
68
|
+
[constraint],
|
|
69
|
+
"/some/cache/path",
|
|
70
|
+
targetVersions,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("omits targetVersions from the readSchema call when not configured", async () => {
|
|
75
|
+
const workdir = tmp("target-versions-absent.test");
|
|
76
|
+
const options: GetOptions = {
|
|
77
|
+
codeMakerOutput: workdir,
|
|
78
|
+
targetLanguage: Language.TYPESCRIPT,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const maker = new ConstructsMaker(options);
|
|
82
|
+
await maker.generate([constraint]);
|
|
83
|
+
|
|
84
|
+
expect(mockReadSchema).toHaveBeenCalledWith(
|
|
85
|
+
[constraint],
|
|
86
|
+
undefined,
|
|
87
|
+
undefined,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("stamps constraints.json with targetVersions + cli identity, and older constraints.json files without those fields are still treated as fresh (no forced regeneration)", async () => {
|
|
92
|
+
const workdir = tmp("target-versions-stamp.test");
|
|
93
|
+
const targetVersions: TerraformTargetVersions = { terraform: ">=1.5.7" };
|
|
94
|
+
const options: GetOptions = {
|
|
95
|
+
codeMakerOutput: workdir,
|
|
96
|
+
targetLanguage: Language.TYPESCRIPT,
|
|
97
|
+
targetVersions,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const maker = new ConstructsMaker(options);
|
|
101
|
+
await maker.generate([constraint]);
|
|
102
|
+
|
|
103
|
+
const constraintsFile = JSON.parse(
|
|
104
|
+
await fs.readFile(path.join(workdir, "constraints.json"), "utf8"),
|
|
105
|
+
);
|
|
106
|
+
expect(constraintsFile.targetVersions).toEqual(targetVersions);
|
|
107
|
+
expect(constraintsFile.cli).toEqual({
|
|
108
|
+
name: "terraform",
|
|
109
|
+
version: "1.7.5",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Rewrite constraints.json as it would have looked before this feature
|
|
113
|
+
// landed (no targetVersions/cli fields), keeping the rest identical -
|
|
114
|
+
// this simulates re-running `cdktn get` against an output directory
|
|
115
|
+
// generated by an older cdktn version.
|
|
116
|
+
const {
|
|
117
|
+
targetVersions: _tv,
|
|
118
|
+
cli: _cli,
|
|
119
|
+
...legacyConstraints
|
|
120
|
+
} = constraintsFile;
|
|
121
|
+
await fs.writeFile(
|
|
122
|
+
path.join(workdir, "constraints.json"),
|
|
123
|
+
JSON.stringify(legacyConstraints, null, 2),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const maker2 = new ConstructsMaker(options);
|
|
127
|
+
const toGenerate = await maker2.filterAlreadyGenerated([constraint]);
|
|
128
|
+
expect(toGenerate).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("threads a partial targetVersions (single product) through unchanged and stamps it verbatim", async () => {
|
|
132
|
+
const workdir = tmp("target-versions-partial.test");
|
|
133
|
+
const targetVersions: TerraformTargetVersions = { opentofu: ">=1.11.0" };
|
|
134
|
+
const options: GetOptions = {
|
|
135
|
+
codeMakerOutput: workdir,
|
|
136
|
+
targetLanguage: Language.TYPESCRIPT,
|
|
137
|
+
targetVersions,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const maker = new ConstructsMaker(options);
|
|
141
|
+
await maker.generate([constraint]);
|
|
142
|
+
|
|
143
|
+
expect(mockReadSchema).toHaveBeenCalledWith(
|
|
144
|
+
[constraint],
|
|
145
|
+
undefined,
|
|
146
|
+
targetVersions,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// The stamp records exactly what the project declared - no terraform
|
|
150
|
+
// default is injected at get-time (defaulting for the undeclared
|
|
151
|
+
// product happens only at synth-time, in resolveTargetVersions).
|
|
152
|
+
const constraintsFile = JSON.parse(
|
|
153
|
+
await fs.readFile(path.join(workdir, "constraints.json"), "utf8"),
|
|
154
|
+
);
|
|
155
|
+
expect(constraintsFile.targetVersions).toEqual({ opentofu: ">=1.11.0" });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("omits the cli stamp when the fetched schema carries no CLI version metadata", async () => {
|
|
159
|
+
const workdir = tmp("target-versions-no-cli.test");
|
|
160
|
+
const {
|
|
161
|
+
cli_name: _cliName,
|
|
162
|
+
cli_version: _cliVersion,
|
|
163
|
+
...schemaWithoutCli
|
|
164
|
+
} = fakeSchema.providerSchema;
|
|
165
|
+
mockReadSchema.mockResolvedValue({ providerSchema: schemaWithoutCli });
|
|
166
|
+
|
|
167
|
+
const targetVersions: TerraformTargetVersions = { terraform: ">=1.5.7" };
|
|
168
|
+
const options: GetOptions = {
|
|
169
|
+
codeMakerOutput: workdir,
|
|
170
|
+
targetLanguage: Language.TYPESCRIPT,
|
|
171
|
+
targetVersions,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const maker = new ConstructsMaker(options);
|
|
175
|
+
await maker.generate([constraint]);
|
|
176
|
+
|
|
177
|
+
const constraintsFile = JSON.parse(
|
|
178
|
+
await fs.readFile(path.join(workdir, "constraints.json"), "utf8"),
|
|
179
|
+
);
|
|
180
|
+
expect(constraintsFile.cli).toBeUndefined();
|
|
181
|
+
expect(constraintsFile.targetVersions).toEqual(targetVersions);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("does not force regeneration when targetVersions change between runs", async () => {
|
|
185
|
+
// The stamp is diagnostic-only: per the GetOptions.targetVersions doc,
|
|
186
|
+
// targetVersions never filters codegen (the full schema surface is
|
|
187
|
+
// always generated), so freshness only compares the providers map and
|
|
188
|
+
// the cdktf version.
|
|
189
|
+
const workdir = tmp("target-versions-change.test");
|
|
190
|
+
const options: GetOptions = {
|
|
191
|
+
codeMakerOutput: workdir,
|
|
192
|
+
targetLanguage: Language.TYPESCRIPT,
|
|
193
|
+
targetVersions: { terraform: ">=1.5.7" },
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const maker = new ConstructsMaker(options);
|
|
197
|
+
await maker.generate([constraint]);
|
|
198
|
+
|
|
199
|
+
const maker2 = new ConstructsMaker({
|
|
200
|
+
...options,
|
|
201
|
+
targetVersions: { terraform: ">=1.11.0" },
|
|
202
|
+
});
|
|
203
|
+
const toGenerate = await maker2.filterAlreadyGenerated([constraint]);
|
|
204
|
+
expect(toGenerate).toEqual([]);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ModuleSchema,
|
|
16
16
|
Errors,
|
|
17
17
|
type LanguageOptions,
|
|
18
|
+
type TerraformTargetVersions,
|
|
18
19
|
} from "@cdktn/commons";
|
|
19
20
|
import { DISPLAY_VERSION, Language } from "@cdktn/commons";
|
|
20
21
|
import { TerraformProviderGenerator } from "./generator/provider-generator";
|
|
@@ -265,7 +266,23 @@ export async function generateJsiiLanguage(
|
|
|
265
266
|
});
|
|
266
267
|
}
|
|
267
268
|
|
|
268
|
-
type ConstraintFile = {
|
|
269
|
+
type ConstraintFile = {
|
|
270
|
+
providers: Record<string, string>;
|
|
271
|
+
cdktf: string;
|
|
272
|
+
/**
|
|
273
|
+
* The project's declared targetVersions (cdktf.json), stamped for
|
|
274
|
+
* diagnostics/cache debugging. Purely informational - never read back to
|
|
275
|
+
* decide whether cached output is stale, since it doesn't affect the
|
|
276
|
+
* generated surface (the full surface is always generated; narrowing
|
|
277
|
+
* happens at synth).
|
|
278
|
+
*/
|
|
279
|
+
targetVersions?: TerraformTargetVersions;
|
|
280
|
+
/**
|
|
281
|
+
* Identity of the CLI (terraform/opentofu) that fetched the schemas used
|
|
282
|
+
* for this generation, stamped for diagnostics/cache debugging.
|
|
283
|
+
*/
|
|
284
|
+
cli?: { name?: string; version?: string };
|
|
285
|
+
};
|
|
269
286
|
|
|
270
287
|
export interface GetOptions {
|
|
271
288
|
readonly targetLanguage: Language;
|
|
@@ -280,6 +297,13 @@ export interface GetOptions {
|
|
|
280
297
|
* Language-specific code generation options.
|
|
281
298
|
*/
|
|
282
299
|
readonly languageOptions?: LanguageOptions;
|
|
300
|
+
/**
|
|
301
|
+
* The project's declared targetVersions (cdktf.json). Used to drive the
|
|
302
|
+
* fetch-time emission-gap warning in @cdktn/provider-schema and to stamp
|
|
303
|
+
* the generated constraints.json for diagnostics. Does NOT filter codegen:
|
|
304
|
+
* the full surface is always generated, narrowing happens at synth.
|
|
305
|
+
*/
|
|
306
|
+
readonly targetVersions?: TerraformTargetVersions;
|
|
283
307
|
}
|
|
284
308
|
|
|
285
309
|
export class ConstructsMaker {
|
|
@@ -570,6 +594,7 @@ export class ConstructsMaker {
|
|
|
570
594
|
// this is used for caching purposes
|
|
571
595
|
private emitConstraintsFile(
|
|
572
596
|
allowedConstraints: TerraformDependencyConstraint[],
|
|
597
|
+
providerSchema?: ProviderSchema,
|
|
573
598
|
) {
|
|
574
599
|
const filePath = "constraints.json";
|
|
575
600
|
|
|
@@ -586,6 +611,17 @@ export class ConstructsMaker {
|
|
|
586
611
|
),
|
|
587
612
|
};
|
|
588
613
|
|
|
614
|
+
if (this.options.targetVersions) {
|
|
615
|
+
content.targetVersions = this.options.targetVersions;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (providerSchema?.cli_name || providerSchema?.cli_version) {
|
|
619
|
+
content.cli = {
|
|
620
|
+
name: providerSchema.cli_name,
|
|
621
|
+
version: providerSchema.cli_version,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
589
625
|
this.code.openFile(filePath);
|
|
590
626
|
this.code.line(JSON.stringify(content, null, 2));
|
|
591
627
|
this.code.closeFile(filePath);
|
|
@@ -686,7 +722,11 @@ a NODE_OPTIONS variable, we won't override it. Hence, the provider generation mi
|
|
|
686
722
|
}
|
|
687
723
|
|
|
688
724
|
public async getSchemas(targets: TerraformDependencyConstraint[]) {
|
|
689
|
-
return await readSchema(
|
|
725
|
+
return await readSchema(
|
|
726
|
+
targets,
|
|
727
|
+
this.schemaCachePath,
|
|
728
|
+
this.options.targetVersions,
|
|
729
|
+
);
|
|
690
730
|
}
|
|
691
731
|
|
|
692
732
|
public async generate(
|
|
@@ -708,7 +748,7 @@ a NODE_OPTIONS variable, we won't override it. Hence, the provider generation mi
|
|
|
708
748
|
endGenerateTimer();
|
|
709
749
|
|
|
710
750
|
this.updateVersionsFile(allConstraints);
|
|
711
|
-
this.emitConstraintsFile(allConstraints);
|
|
751
|
+
this.emitConstraintsFile(allConstraints, schemas.providerSchema);
|
|
712
752
|
|
|
713
753
|
if (this.isJavascriptTarget) {
|
|
714
754
|
await this.save();
|