@cdktn/hcl2cdk 0.24.0-pre.45 → 0.24.0-pre.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/LICENSE +355 -0
  2. package/README.md +1 -1
  3. package/build/__tests__/expressions.test.js +10 -19
  4. package/build/__tests__/functions.test.js +8 -18
  5. package/build/__tests__/testHelpers.js +3 -2
  6. package/build/coerceType.js +11 -21
  7. package/build/dynamic-blocks.js +3 -3
  8. package/build/expressions.js +13 -22
  9. package/build/function-bindings/functions.generated.js +2 -2
  10. package/build/generation.js +24 -34
  11. package/build/index.js +15 -25
  12. package/build/iteration.js +7 -6
  13. package/build/jsii-rosetta-workarounds.js +6 -5
  14. package/build/partialCode.js +11 -20
  15. package/build/provider.js +4 -3
  16. package/build/references.js +6 -5
  17. package/build/schema.js +8 -18
  18. package/build/terraformSchema.js +4 -4
  19. package/build/utils.js +3 -3
  20. package/build/variables.js +12 -21
  21. package/package.json +20 -17
  22. package/package.sh +1 -1
  23. package/src/__tests__/coerceType.test.ts +207 -0
  24. package/src/__tests__/expressionToTs.test.ts +1167 -0
  25. package/src/__tests__/expressions.test.ts +541 -0
  26. package/src/__tests__/findExpressionType.test.ts +112 -0
  27. package/src/__tests__/functions.test.ts +768 -0
  28. package/src/__tests__/generation.test.ts +72 -0
  29. package/src/__tests__/jsii-rosetta-workarounds.test.ts +145 -0
  30. package/src/__tests__/partialCode.test.ts +432 -0
  31. package/src/__tests__/terraformSchema.test.ts +107 -0
  32. package/src/__tests__/testHelpers.ts +11 -0
  33. package/src/coerceType.ts +261 -0
  34. package/src/dynamic-blocks.ts +61 -0
  35. package/src/expressions.ts +968 -0
  36. package/src/function-bindings/functions.generated.ts +1139 -0
  37. package/src/function-bindings/functions.ts +104 -0
  38. package/src/generation.ts +1189 -0
  39. package/src/index.ts +584 -0
  40. package/src/iteration.ts +156 -0
  41. package/src/jsii-rosetta-workarounds.ts +145 -0
  42. package/src/partialCode.ts +132 -0
  43. package/src/provider.ts +60 -0
  44. package/src/references.ts +193 -0
  45. package/src/schema.ts +74 -0
  46. package/src/terraformSchema.ts +182 -0
  47. package/src/types.ts +58 -0
  48. package/src/utils.ts +19 -0
  49. package/src/variables.ts +214 -0
  50. package/test/__snapshots__/backends.test.ts.snap +70 -0
  51. package/test/__snapshots__/externals.test.ts.snap +37 -0
  52. package/test/__snapshots__/granular-imports.test.ts.snap +180 -0
  53. package/test/__snapshots__/imports.test.ts.snap +159 -0
  54. package/test/__snapshots__/iteration.test.ts.snap +532 -0
  55. package/test/__snapshots__/jsiiLanguage.test.ts.snap +347 -0
  56. package/test/__snapshots__/locals.test.ts.snap +55 -0
  57. package/test/__snapshots__/modules.test.ts.snap +127 -0
  58. package/test/__snapshots__/outputs.test.ts.snap +77 -0
  59. package/test/__snapshots__/partialCode.test.ts.snap +120 -0
  60. package/test/__snapshots__/provider.test.ts.snap +128 -0
  61. package/test/__snapshots__/references.test.ts.snap +376 -0
  62. package/test/__snapshots__/resource-meta-properties.test.ts.snap +342 -0
  63. package/test/__snapshots__/resources.test.ts.snap +613 -0
  64. package/test/__snapshots__/tfExpressions.test.ts.snap +537 -0
  65. package/test/__snapshots__/typeCoercion.test.ts.snap +253 -0
  66. package/test/__snapshots__/variables.test.ts.snap +150 -0
  67. package/test/backends.test.ts +75 -0
  68. package/test/convertProject.test.ts +257 -0
  69. package/test/externals.test.ts +35 -0
  70. package/test/globalSetup.ts +224 -0
  71. package/test/globalTeardown.ts +11 -0
  72. package/test/granular-imports.test.ts +161 -0
  73. package/test/hcl2cdk.test.ts +88 -0
  74. package/test/helpers/convert.ts +543 -0
  75. package/test/helpers/tmp.ts +25 -0
  76. package/test/imports.test.ts +141 -0
  77. package/test/iteration.test.ts +342 -0
  78. package/test/jsiiLanguage.test.ts +73 -0
  79. package/test/locals.test.ts +47 -0
  80. package/test/modules.test.ts +143 -0
  81. package/test/outputs.test.ts +69 -0
  82. package/test/partialCode.test.ts +25 -0
  83. package/test/provider.test.ts +106 -0
  84. package/test/references.test.ts +287 -0
  85. package/test/resource-meta-properties.test.ts +288 -0
  86. package/test/resources.test.ts +551 -0
  87. package/test/tfExpressions.test.ts +300 -0
  88. package/test/typeCoercion.test.ts +154 -0
  89. package/test/variables.test.ts +96 -0
@@ -0,0 +1,88 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { convert } from "../src/index";
4
+
5
+ describe("convert", () => {
6
+ it("exposes stats for telemetry", async () => {
7
+ const { stats } = await convert(
8
+ `
9
+ resource "aws_kms_key" "examplekms" {
10
+ description = "KMS key 1"
11
+ deletion_window_in_days = 7
12
+ }
13
+
14
+ resource "aws_s3_bucket" "examplebucket" {
15
+ bucket = "examplebuckettftest"
16
+ acl = "private"
17
+ }
18
+ resource "aws_s3_bucket" "examplebucket_two" {
19
+ bucket = "examplebuckettftest"
20
+ acl = "private"
21
+ }
22
+ resource "aws_s3_bucket" "examplebucket_three" {
23
+ bucket = "examplebuckettftest"
24
+ acl = "private"
25
+ }
26
+ resource "internal_cloud_s3_bucket" "examplebucket" {
27
+ bucket = "examplebuckettftest"
28
+ acl = "private"
29
+ }
30
+ resource "internal_cloud_s3_bucket_object" "examplebucket_two" {
31
+ bucket = "examplebuckettftest"
32
+ acl = "private"
33
+ }
34
+
35
+ resource "aws_s3_bucket_object" "examplebucket_object" {
36
+ key = "someobject"
37
+ bucket = element(aws_s3_bucket.examplebucket, 0).id
38
+ source = "index.html"
39
+ kms_key_id = aws_kms_key.examplekms.arn
40
+ }
41
+ resource "aws_s3_bucket_object" "examplebucket_object_two" {
42
+ key = "someobject"
43
+ bucket = element(aws_s3_bucket.examplebucket, 0).id
44
+ source = "index.html"
45
+ kms_key_id = aws_kms_key.examplekms.arn
46
+ }
47
+ resource "google_compute_autoscaler" "example" {
48
+ name = "example-autoscaler"
49
+ zone = "us-east1-b"
50
+
51
+ autoscaling_policy = {
52
+ max_replicas = 8
53
+ min_replicas = 2
54
+ cooldown_period = 60
55
+
56
+ cpu_utilization = {
57
+ target = 0.5
58
+ }
59
+ }
60
+ }
61
+ `,
62
+ { language: "typescript", providerSchema: {} },
63
+ );
64
+
65
+ expect(stats).toMatchInlineSnapshot(`
66
+ {
67
+ "convertedLines": 54,
68
+ "data": {},
69
+ "language": "typescript",
70
+ "numberOfModules": 0,
71
+ "numberOfProviders": 3,
72
+ "resources": {
73
+ "aws": {
74
+ "kms_key": 1,
75
+ "s3_bucket": 3,
76
+ "s3_bucket_object": 2,
77
+ },
78
+ "google": {
79
+ "compute_autoscaler": 1,
80
+ },
81
+ "other": {
82
+ "other": 1,
83
+ },
84
+ },
85
+ }
86
+ `);
87
+ });
88
+ });
@@ -0,0 +1,543 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { convert } from "../../src/index";
4
+ import * as fs from "fs-extra";
5
+ import * as path from "path";
6
+ import execa from "execa";
7
+ import {
8
+ LANGUAGES,
9
+ TerraformModuleConstraint,
10
+ TerraformProviderConstraint,
11
+ } from "@cdktn/commons";
12
+ import { readSchema } from "@cdktn/provider-schema";
13
+ import type { FixturesManifest } from "../globalSetup";
14
+ import { createTmpHelper } from "./tmp";
15
+
16
+ const tmp = createTmpHelper();
17
+
18
+ // Tests `process.chdir(projectDir)` for jsii-rosetta. If a test leaves cwd inside a projectDir that tmp()'s afterAll later removes, the worker's next test load sees uv_cwd ENOENT.
19
+ const _originalCwd = process.cwd();
20
+ if (typeof afterEach === "function") {
21
+ afterEach(() => process.chdir(_originalCwd));
22
+ }
23
+
24
+ const includeSynthTests = Boolean(process.env.CI);
25
+
26
+ // Load pre-generated fixtures manifest from globalSetup
27
+ let _manifest: FixturesManifest | undefined;
28
+ function getManifest(): FixturesManifest {
29
+ if (_manifest) return _manifest;
30
+ const manifestPath = process.env.HCL2CDK_FIXTURES_MANIFEST;
31
+ if (!manifestPath) {
32
+ throw new Error(
33
+ "HCL2CDK_FIXTURES_MANIFEST env var not set. Ensure globalSetup ran successfully.",
34
+ );
35
+ }
36
+ _manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
37
+ return _manifest!;
38
+ }
39
+
40
+ export enum Synth {
41
+ yes_all_languages, // Synth and snapshot all languages
42
+ yes,
43
+ yes_but_only_typescript_right_now_because_it_breaks,
44
+ no_cant_resolve_construct,
45
+ no_missing_map_access, // See https://github.com/hashicorp/terraform-cdk/issues/2670
46
+ no_missing_type_coercion, // We don't type coerce numbers yet
47
+ never, // Some examples are built so that they will never synth but test a specific generation edge case
48
+ }
49
+ export enum Snapshot {
50
+ yes_all_languages, // Synth and snapshot all languages
51
+ yes,
52
+ }
53
+
54
+ type PathToCopy = string;
55
+ type ProviderFqn = string;
56
+ enum ProviderType {
57
+ provider,
58
+ module,
59
+ }
60
+
61
+ type ProviderDefinition = {
62
+ fqn: ProviderFqn;
63
+ type: ProviderType;
64
+ path: PathToCopy;
65
+ };
66
+
67
+ type SchemaFilter = {
68
+ resources?: string[];
69
+ dataSources?: string[];
70
+ };
71
+
72
+ const cdktnBin = path.join(__dirname, "../../../../cdktn-cli/bin/cdktn");
73
+ const cdktnDist = path.join(__dirname, "../../../../../dist");
74
+ const tsxPkgJsonPath = require.resolve("tsx/package.json");
75
+ const tsxBin = path.join(
76
+ path.dirname(tsxPkgJsonPath),
77
+ fs.readJsonSync(tsxPkgJsonPath).bin,
78
+ );
79
+
80
+ export const binding = {
81
+ aws: {
82
+ fqn: "hashicorp/aws@=5.11.0",
83
+ type: ProviderType.provider,
84
+ path: "providers/aws",
85
+ },
86
+ docker: {
87
+ fqn: "kreuzwerker/docker@=3.0.1",
88
+ type: ProviderType.provider,
89
+ path: "providers/docker",
90
+ },
91
+ null: {
92
+ fqn: "hashicorp/null@=3.2.1",
93
+ type: ProviderType.provider,
94
+ path: "providers/null",
95
+ },
96
+ google: {
97
+ fqn: "hashicorp/google@=4.55.0",
98
+ type: ProviderType.provider,
99
+ path: "providers/google",
100
+ },
101
+ azuread: {
102
+ fqn: "hashicorp/azuread@=2.36.0",
103
+ type: ProviderType.provider,
104
+ path: "providers/azuread",
105
+ },
106
+ local: {
107
+ fqn: "hashicorp/local@=2.3.0",
108
+ type: ProviderType.provider,
109
+ path: "providers/local",
110
+ },
111
+ auth0: {
112
+ fqn: "alexkappa/auth0@=0.26.2",
113
+ type: ProviderType.provider,
114
+ path: "providers/auth0",
115
+ },
116
+ datadog: {
117
+ fqn: "DataDog/datadog@=3.21.0",
118
+ type: ProviderType.provider,
119
+ path: "providers/datadog",
120
+ },
121
+ kubernetes: {
122
+ fqn: "hashicorp/kubernetes@=2.18.0",
123
+ type: ProviderType.provider,
124
+ path: "providers/kubernetes",
125
+ },
126
+ scaleway: {
127
+ fqn: "scaleway/scaleway@ ~>2.10.0",
128
+ type: ProviderType.provider,
129
+ path: "providers/scaleway",
130
+ },
131
+ external: {
132
+ fqn: "hashicorp/external@=2.3.1",
133
+ type: ProviderType.provider,
134
+ path: "providers/external",
135
+ },
136
+ awsVpc: {
137
+ fqn: "terraform-aws-modules/vpc/aws@=3.19.0",
138
+ type: ProviderType.module,
139
+ path: "modules/terraform-aws-modules/aws",
140
+ },
141
+ };
142
+
143
+ type AbsolutePath = string;
144
+
145
+ async function copyBindingsForProvider(
146
+ binding: ProviderDefinition,
147
+ targetDirectory: AbsolutePath,
148
+ ) {
149
+ const manifest = getManifest();
150
+ const absoluteBindingPath = manifest.providerBindings[binding.fqn];
151
+ if (!absoluteBindingPath) {
152
+ throw new Error(
153
+ `No pre-generated binding found for ${binding.fqn}. Ensure globalSetup generates it.`,
154
+ );
155
+ }
156
+
157
+ const target = path.resolve(targetDirectory, ".gen", binding.path);
158
+ await fs.mkdirp(target);
159
+ await fs.copy(absoluteBindingPath, target);
160
+ }
161
+
162
+ /**
163
+ * Provisions `projectDir` from `baseDir`.
164
+ *
165
+ * Copies everything except `node_modules`, then drops a directory symlink in its place. `node_modules` is the heavy
166
+ * bit of the base project (hundreds of MB) and the test runner only reads from it — sharing it across tests avoids
167
+ * per-test copy cost. The base project must therefore be treated as read-only for the lifetime of the test suite.
168
+ *
169
+ * @param baseDir Source project (output of `cdktn init`) created once in globalSetup.
170
+ * @param projectDir Destination for this test's project.
171
+ */
172
+ async function copyBaseProject(baseDir: string, projectDir: string) {
173
+ await fs.copy(baseDir, projectDir, {
174
+ filter: (src) => path.basename(src) !== "node_modules",
175
+ });
176
+ await fs.symlink(
177
+ path.join(baseDir, "node_modules"),
178
+ path.join(projectDir, "node_modules"),
179
+ "dir",
180
+ );
181
+ }
182
+
183
+ const fileEndings: Record<string, string> = {
184
+ typescript: ".ts",
185
+ python: ".py",
186
+ csharp: ".cs",
187
+ };
188
+
189
+ const getFileContent: Record<
190
+ string,
191
+ (
192
+ code: { all: string; code: string; imports: string },
193
+ stackName: string,
194
+ ) => string
195
+ > = {
196
+ typescript: ({ all }, stackName) => `
197
+ ${all}
198
+ import { App } from "cdktn";
199
+ const app = new App();
200
+ new MyConvertedCode(app, "${stackName}");
201
+ app.synth();`,
202
+ python: ({ all }, stackName) => `
203
+ ${all}
204
+
205
+ app = App()
206
+ MyConvertedCode(app, "${stackName}")
207
+ app.synth()
208
+ `,
209
+
210
+ csharp: ({ all }, stackName) => {
211
+ const endOfStack = all.lastIndexOf("}");
212
+ const stack = all.substring(0, endOfStack);
213
+ return `
214
+ ${stack}
215
+ public static void Main(string[] args)
216
+ {
217
+ App app = new App();
218
+ new MyConvertedCode(app, "${stackName}");
219
+ app.Synth();
220
+ }
221
+ }
222
+ `;
223
+ },
224
+ };
225
+
226
+ const getAppCommand: Record<string, (stackName: string) => string> = {
227
+ typescript: (stackName) => `${tsxBin} ${stackName}.ts`,
228
+ python: (stackName) => `pipenv run python ${stackName}.py`,
229
+ csharp: (stackName) => `dotnet run --project ${stackName}.csproj`,
230
+ };
231
+
232
+ const preSynth: Record<
233
+ string,
234
+ (
235
+ stackName: string,
236
+ projectDir: string,
237
+ providers: ProviderDefinition[],
238
+ ) => Promise<void> | undefined
239
+ > = {
240
+ csharp: async (stackName, projectDir, providers) => {
241
+ await fs.writeFile(
242
+ path.join(projectDir, `${stackName}.csproj`),
243
+ `<Project Sdk="Microsoft.NET.Sdk">
244
+
245
+ <PropertyGroup>
246
+ <OutputType>Exe</OutputType>
247
+ <TargetFramework>net6.0</TargetFramework>
248
+ </PropertyGroup>
249
+
250
+ <ItemGroup>
251
+ <PackageReference Include="HashiCorp.Cdktf" Version="0.21.0" />
252
+ </ItemGroup>
253
+
254
+ <ItemGroup>
255
+ <PackageReference Include="Io.Cdktn" Version="0.0.0" />
256
+ </ItemGroup>
257
+
258
+ <ItemGroup>
259
+ ${providers
260
+ .map((provider) => {
261
+ const [, name] = provider.fqn.split("@")[0].split("/");
262
+ return `<ProjectReference Include=".gen\\${
263
+ provider.type === ProviderType.provider ? "providers" : "modules"
264
+ }\\${name}.csproj" />`;
265
+ })
266
+ .join("\n")}
267
+ </ItemGroup>
268
+
269
+ </Project>`,
270
+ "utf8",
271
+ );
272
+
273
+ await fs.writeFile(
274
+ path.join(projectDir, "NuGet.Config"),
275
+ `<?xml version="1.0" encoding="utf-8"?>
276
+ <configuration>
277
+ <packageSources>
278
+ <add key="Locally Distributed Packages" value="${cdktnDist}/dotnet/" />
279
+ <add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
280
+ </packageSources>
281
+ </configuration>
282
+ `,
283
+ "utf8",
284
+ );
285
+ },
286
+ };
287
+
288
+ /**
289
+ * Writes the converted code to a project directory and spawns `cdktn synth`
290
+ * against it, asserting that synth reports success on stdout.
291
+ *
292
+ * @param language Target language for the synth (typescript, python, csharp).
293
+ * @param name Human-readable test-case name.
294
+ * @param code Output of `convert()` for the HCL input
295
+ * @param providers Provider bindings the converted code imports.
296
+ * @param projectDir An existing project dir to reuse.
297
+ */
298
+ async function synthForLanguage(
299
+ language: string,
300
+ name: string,
301
+ code: { all: string; code: string; imports: string },
302
+ providers: ProviderDefinition[] = [],
303
+ projectDir?: string,
304
+ ) {
305
+ const stackName = name.replace(/\s/g, "-");
306
+ if (!projectDir) {
307
+ projectDir = await getProjectDirectory(language, providers);
308
+ }
309
+
310
+ // Have a before all somewhere above bootstrap a TS project
311
+ // __dirname should be replaceed by the bootstrapped directory
312
+ const pathToThisProjectsFile = path.join(
313
+ projectDir,
314
+ stackName + fileEndings[language],
315
+ );
316
+ const fileContent = getFileContent[language](code, stackName);
317
+
318
+ fs.writeFileSync(pathToThisProjectsFile, fileContent, "utf8");
319
+
320
+ const runBeforeSynth = preSynth[language];
321
+ if (runBeforeSynth) {
322
+ await runBeforeSynth(stackName, projectDir, providers);
323
+ }
324
+
325
+ const { all } = await execa(
326
+ cdktnBin,
327
+ [
328
+ "synth",
329
+ "-a",
330
+ `'${getAppCommand[language](stackName)}'`,
331
+ "-o",
332
+ `./${stackName}-output`,
333
+ ],
334
+ { cwd: projectDir, all: true, shell: true },
335
+ );
336
+
337
+ expect(all!).toEqual(
338
+ expect.stringContaining(`Generated Terraform code for the stacks`),
339
+ );
340
+ }
341
+
342
+ // getProviderSchema(Object.values(binding));
343
+
344
+ async function getProjectDirectory(
345
+ language: string,
346
+ providers: ProviderDefinition[],
347
+ ) {
348
+ const manifest = getManifest();
349
+ const baseDir = manifest.baseProjects[language];
350
+ if (!baseDir) {
351
+ throw new Error(
352
+ `Unsupported language used to synthesize code: ${language}`,
353
+ );
354
+ }
355
+ const projectDir = tmp("cdktf-convert-test-");
356
+
357
+ await Promise.all([
358
+ copyBaseProject(baseDir, projectDir),
359
+ ...providers.map((provider) =>
360
+ copyBindingsForProvider(provider, projectDir),
361
+ ),
362
+ ]);
363
+
364
+ // We only copy the TS bindings, but we need to run cdktn get for the language specific ones
365
+ if (language !== "typescript") {
366
+ await fs.writeFile(
367
+ path.resolve(projectDir, "cdktf.json"),
368
+ JSON.stringify(
369
+ {
370
+ language,
371
+ app: "echo 'app command should be overwritten'",
372
+ terraformProviders: providers
373
+ .filter((binding) => binding.type === ProviderType.provider)
374
+ .map((binding) => binding.fqn),
375
+ terraformModules: providers
376
+ .filter((binding) => binding.type === ProviderType.module)
377
+ .map((binding) => binding.fqn),
378
+ },
379
+ null,
380
+ 2,
381
+ ),
382
+ );
383
+ await execa(cdktnBin, ["get", "--force"], { cwd: projectDir });
384
+ }
385
+
386
+ return projectDir;
387
+ }
388
+
389
+ async function getProviderSchema(providers: ProviderDefinition[]) {
390
+ const constraints = providers.map(
391
+ (provider) =>
392
+ ProviderType.provider === provider.type
393
+ ? new TerraformProviderConstraint(provider.fqn)
394
+ : new TerraformModuleConstraint(provider.fqn),
395
+ LANGUAGES[0],
396
+ );
397
+
398
+ const schemaCacheDir =
399
+ process.env.CDKTF_EXPERIMENTAL_PROVIDER_SCHEMA_CACHE_PATH ||
400
+ getManifest().schemaCacheDir;
401
+
402
+ return await readSchema(constraints, schemaCacheDir);
403
+ }
404
+
405
+ function filterSchema(
406
+ providerSchema: any,
407
+ schemaFilter: SchemaFilter | undefined,
408
+ ) {
409
+ if (!schemaFilter) return providerSchema;
410
+
411
+ const { resources, dataSources } = schemaFilter;
412
+
413
+ const providerSchemaKey = Object.keys(providerSchema.provider_schemas)[0];
414
+ const actualSchema = providerSchema.provider_schemas[providerSchemaKey];
415
+
416
+ let filteredDataSourceSchemas = {};
417
+ let filteredResourceSchemas = {};
418
+
419
+ if (resources && resources.length > 0) {
420
+ filteredResourceSchemas = Object.fromEntries(
421
+ Object.entries(actualSchema.resource_schemas).filter(([resourceName]) =>
422
+ resources?.includes(resourceName),
423
+ ),
424
+ );
425
+ }
426
+
427
+ if (dataSources && dataSources.length > 0) {
428
+ filteredDataSourceSchemas = Object.fromEntries(
429
+ Object.entries(actualSchema.data_source_schemas).filter(
430
+ ([dataSourceName]) => dataSources?.includes(dataSourceName),
431
+ ),
432
+ );
433
+ }
434
+
435
+ return {
436
+ provider_schemas: {
437
+ [providerSchemaKey]: {
438
+ provider: providerSchema.provider_schemas[providerSchemaKey].provider,
439
+ resource_schemas: filteredResourceSchemas,
440
+ data_source_schemas: filteredDataSourceSchemas,
441
+ },
442
+ },
443
+ };
444
+ }
445
+
446
+ const createTestCase =
447
+ (opts: { skip?: true; only?: true }) =>
448
+ (
449
+ name: string,
450
+ hcl: string,
451
+ providers: ProviderDefinition[],
452
+ shouldSnapshot: Snapshot,
453
+ shouldSynth: Synth,
454
+ schemaFilter?: SchemaFilter,
455
+ ) => {
456
+ if (opts.skip) {
457
+ describe.skip(name, () => {});
458
+ return;
459
+ }
460
+
461
+ async function runConvert(language: string) {
462
+ let { providerSchema } = await getProviderSchema(providers);
463
+ if (schemaFilter) {
464
+ // TODO: Re-enable once we can trick Terraform CLI Checksums
465
+ providerSchema = filterSchema(providerSchema, undefined);
466
+ }
467
+ return await convert(hcl, {
468
+ language: language as any,
469
+ providerSchema: providerSchema || {
470
+ format_version: "0.1",
471
+ },
472
+ codeContainer: "cdktn.TerraformStack",
473
+ });
474
+ }
475
+
476
+ const testBody = () => {
477
+ describe("snapshot", () => {
478
+ it.each(
479
+ shouldSnapshot === Snapshot.yes_all_languages
480
+ ? ["typescript", "python", "csharp", "java", "go"]
481
+ : ["typescript"],
482
+ )(
483
+ "%s",
484
+ async (language) => {
485
+ const projectDir = await getProjectDirectory(
486
+ // We need the typescript project directory to start the convert so JSII has the right types
487
+ "typescript",
488
+ providers,
489
+ );
490
+ process.chdir(projectDir); // JSII rosetta needs to be run in the project directory with bindings included
491
+ const convertResult = await runConvert(language);
492
+ expect(convertResult.all).toMatchSnapshot();
493
+ },
494
+ 500_000,
495
+ );
496
+ });
497
+
498
+ if (
499
+ includeSynthTests &&
500
+ [Synth.yes_all_languages, Synth.yes].includes(shouldSynth)
501
+ ) {
502
+ describe("synth", () => {
503
+ it.each(
504
+ shouldSynth === Synth.yes_all_languages
505
+ ? ["typescript", "python", "csharp"]
506
+ : ["typescript"],
507
+ )(
508
+ "%s",
509
+ async (language) => {
510
+ const projectDir = await getProjectDirectory(
511
+ "typescript",
512
+ providers,
513
+ );
514
+ process.chdir(projectDir); // JSII rosetta needs to be run in the project directory with bindings included
515
+ const convertResult = await runConvert(language);
516
+ await synthForLanguage(
517
+ language,
518
+ name,
519
+ convertResult,
520
+ providers,
521
+ projectDir,
522
+ );
523
+ },
524
+ 500_000,
525
+ );
526
+ });
527
+ } else {
528
+ describe.skip("synth", () => {});
529
+ }
530
+ };
531
+
532
+ if (opts.only) {
533
+ describe.only(name, testBody);
534
+ return;
535
+ }
536
+ describe(name, testBody);
537
+ };
538
+
539
+ export const testCase = {
540
+ test: createTestCase({}),
541
+ skip: createTestCase({ skip: true }),
542
+ only: createTestCase({ only: true }),
543
+ };
@@ -0,0 +1,25 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import * as fs from "fs";
4
+ import * as os from "os";
5
+ import * as path from "path";
6
+
7
+ /**
8
+ * Creates a per-test-file temp-dir helper.
9
+ *
10
+ * Returns a `tmp(prefix)` function that creates a uniquely-named directory
11
+ * under `os.tmpdir()` and tracks it. An `afterAll` hook is registered on
12
+ * the calling test file's scope; when the file finishes running, every
13
+ * tracked directory is removed (recursively, force).
14
+ */
15
+ export function createTmpHelper(): (prefix: string) => string {
16
+ const dirs: string[] = [];
17
+ afterAll(() => {
18
+ for (const d of dirs) fs.rmSync(d, { recursive: true, force: true });
19
+ });
20
+ return (prefix: string) => {
21
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
22
+ dirs.push(d);
23
+ return d;
24
+ };
25
+ }