@cdktn/provider-generator 0.25.0-pre.9 → 0.25.0

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 (68) hide show
  1. package/README.md +12 -0
  2. package/build/__tests__/edge-provider-schema/builder.js +3 -3
  3. package/build/__tests__/edge-provider-schema/cli.js +18 -8
  4. package/build/__tests__/edge-provider-schema.test.js +18 -8
  5. package/build/__tests__/provider.test.js +44 -16
  6. package/build/get/__tests__/constructs-maker.test.js +91 -8
  7. package/build/get/__tests__/generator/complex-computed-types.test.js +18 -8
  8. package/build/get/__tests__/generator/deep-nested-attributes.test.js +18 -8
  9. package/build/get/__tests__/generator/description-escaping.test.js +18 -8
  10. package/build/get/__tests__/generator/empty-provider-resources.test.js +18 -8
  11. package/build/get/__tests__/generator/ephemeral-resources.test.js +18 -8
  12. package/build/get/__tests__/generator/export-sharding.test.js +18 -8
  13. package/build/get/__tests__/generator/import-style.test.js +18 -8
  14. package/build/get/__tests__/generator/module-docs-registry.test.d.ts +2 -0
  15. package/build/get/__tests__/generator/module-docs-registry.test.js +91 -0
  16. package/build/get/__tests__/generator/module-generator.test.js +18 -8
  17. package/build/get/__tests__/generator/nested-types.test.js +18 -8
  18. package/build/get/__tests__/generator/provider-functions-name-collision.test.d.ts +2 -0
  19. package/build/get/__tests__/generator/provider-functions-name-collision.test.js +89 -0
  20. package/build/get/__tests__/generator/provider-functions.test.js +18 -8
  21. package/build/get/__tests__/generator/provider.test.js +18 -8
  22. package/build/get/__tests__/generator/resource-types.test.js +18 -8
  23. package/build/get/__tests__/generator/skipped-attributes.test.js +18 -8
  24. package/build/get/__tests__/generator/types.test.js +18 -8
  25. package/build/get/__tests__/generator/versions-file.test.js +25 -10
  26. package/build/get/__tests__/generator/write-only.test.js +18 -8
  27. package/build/get/__tests__/registry-class-invalidation.test.d.ts +2 -0
  28. package/build/get/__tests__/registry-class-invalidation.test.js +120 -0
  29. package/build/get/__tests__/target-versions.test.js +46 -8
  30. package/build/get/__tests__/util.js +20 -11
  31. package/build/get/constructs-maker.d.ts +17 -0
  32. package/build/get/constructs-maker.js +86 -11
  33. package/build/get/generator/emitter/struct-emitter.js +20 -10
  34. package/build/get/generator/loop-detection.js +2 -3
  35. package/build/get/generator/models/attribute-model.js +3 -3
  36. package/build/get/generator/models/attribute-type-model.js +3 -3
  37. package/build/get/generator/models/provider-function-model.js +3 -4
  38. package/build/get/generator/models/resource-model.d.ts +5 -1
  39. package/build/get/generator/models/resource-model.js +20 -10
  40. package/build/get/generator/models/supported-stored-classes.js +2 -3
  41. package/build/get/generator/module-generator.d.ts +3 -2
  42. package/build/get/generator/module-generator.js +15 -17
  43. package/build/get/generator/provider-generator.d.ts +15 -1
  44. package/build/get/generator/provider-generator.js +20 -5
  45. package/build/get/generator/resource-parser.d.ts +3 -1
  46. package/build/get/generator/resource-parser.js +16 -7
  47. package/build/get/generator/sanitized-comments.js +2 -3
  48. package/build/get/generator/skipped-attributes.js +2 -3
  49. package/build/index.js +3 -3
  50. package/build/util.js +3 -4
  51. package/package.json +32 -9
  52. package/src/__tests__/__snapshots__/provider.test.ts.snap +1309 -3206
  53. package/src/__tests__/provider.test.ts +31 -8
  54. package/src/get/__tests__/constructs-maker.test.ts +125 -1
  55. package/src/get/__tests__/generator/fixtures/provider-functions-name-collision.test.fixture.json +42 -0
  56. package/src/get/__tests__/generator/module-docs-registry.test.ts +89 -0
  57. package/src/get/__tests__/generator/provider-functions-name-collision.test.ts +84 -0
  58. package/src/get/__tests__/generator/versions-file.test.ts +10 -5
  59. package/src/get/__tests__/registry-class-invalidation.test.ts +123 -0
  60. package/src/get/__tests__/target-versions.test.ts +43 -0
  61. package/src/get/constructs-maker.ts +108 -7
  62. package/src/get/generator/emitter/struct-emitter.ts +2 -2
  63. package/src/get/generator/models/attribute-type-model.ts +1 -2
  64. package/src/get/generator/models/resource-model.ts +32 -9
  65. package/src/get/generator/module-generator.ts +20 -18
  66. package/src/get/generator/provider-generator.ts +27 -4
  67. package/src/get/generator/resource-parser.ts +21 -3
  68. package/tsconfig.json +4 -0
@@ -17,7 +17,11 @@ import {
17
17
  type LanguageOptions,
18
18
  type TerraformTargetVersions,
19
19
  } from "@cdktn/commons";
20
- import { DISPLAY_VERSION, Language } from "@cdktn/commons";
20
+ import {
21
+ DISPLAY_VERSION,
22
+ Language,
23
+ registryForTargetVersions,
24
+ } from "@cdktn/commons";
21
25
  import { TerraformProviderGenerator } from "./generator/provider-generator";
22
26
  import { ModuleGenerator } from "./generator/module-generator";
23
27
  import { glob } from "glob";
@@ -275,11 +279,13 @@ type ConstraintFile = {
275
279
  providers: Record<string, string>;
276
280
  cdktf: string;
277
281
  /**
278
- * The project's declared targetVersions (cdktf.json), stamped for
279
- * diagnostics/cache debugging. Purely informational - never read back to
280
- * decide whether cached output is stale, since it doesn't affect the
281
- * generated surface (the full surface is always generated; narrowing
282
- * happens at synth).
282
+ * The project's declared targetVersions (cdktf.json). Read back by
283
+ * `filterAlreadyGenerated` to compare registry *classes*: which registry the
284
+ * generated docs links point at follows from this, so output produced for the
285
+ * other class is stale. The version ranges themselves do not affect the
286
+ * generated surface - the full surface is always generated and narrowing
287
+ * happens at synth - so a range moving within a class is not a reason to
288
+ * regenerate.
283
289
  */
284
290
  targetVersions?: TerraformTargetVersions;
285
291
  /**
@@ -315,6 +321,8 @@ export class ConstructsMaker {
315
321
  private readonly codeMakerOutdir: string;
316
322
  private readonly code: CodeMaker;
317
323
  private versions: { [providerName: string]: string | undefined };
324
+ /** Resource directory names emitted per provider folder during this run */
325
+ public emittedResourceFolders: { [providerName: string]: string[] };
318
326
 
319
327
  constructor(
320
328
  private readonly options: GetOptions,
@@ -328,6 +336,7 @@ export class ConstructsMaker {
328
336
  fs.mkdirpSync(this.codeMakerOutdir);
329
337
  this.code = new CodeMaker();
330
338
  this.versions = {};
339
+ this.emittedResourceFolders = {};
331
340
  }
332
341
  private async generateTypescriptProvider(
333
342
  target: ConstructsMakerProviderTarget,
@@ -336,10 +345,15 @@ export class ConstructsMaker {
336
345
  const endTSTimer = logTimespan(`Generate Typescript for ${target.name}`);
337
346
  const generator = new TerraformProviderGenerator(this.code, schema, {
338
347
  importExtension: this.options.languageOptions?.importExtension,
348
+ targetVersions: this.options.targetVersions,
339
349
  });
340
350
  generator.generate(target);
341
351
 
342
352
  this.versions = { ...this.versions, ...generator.versions };
353
+ this.emittedResourceFolders = {
354
+ ...this.emittedResourceFolders,
355
+ ...generator.emittedResourceFolders,
356
+ };
343
357
  endTSTimer();
344
358
  }
345
359
 
@@ -399,6 +413,22 @@ export class ConstructsMaker {
399
413
  return constraints;
400
414
  }
401
415
 
416
+ // Generated docs links, and the prose naming the registry around them, follow
417
+ // the project's declared targets, so output from the other registry class is
418
+ // stale even when every provider version still matches.
419
+ const previousRegistry = registryForTargetVersions(
420
+ previousConstraints.targetVersions,
421
+ );
422
+ const currentRegistry = registryForTargetVersions(
423
+ this.options.targetVersions,
424
+ );
425
+ if (previousRegistry.hostname !== currentRegistry.hostname) {
426
+ logger.info(
427
+ `The target registry has changed from ${previousRegistry.displayName} to ${currentRegistry.displayName}, generating all constraints.`,
428
+ );
429
+ return constraints;
430
+ }
431
+
402
432
  const constraintsToGenerate = constraints.filter((constraint) => {
403
433
  const constraintMatches =
404
434
  previousConstraints.providers![constraint.fqn] === constraint.version;
@@ -449,7 +479,7 @@ export class ConstructsMaker {
449
479
  ) {
450
480
  const endTSTimer = logTimespan(`Generate Typescript for ${target.name}`);
451
481
  target.spec = schema;
452
- new ModuleGenerator(this.code, [target]);
482
+ new ModuleGenerator(this.code, [target], this.options.targetVersions);
453
483
  endTSTimer();
454
484
  }
455
485
 
@@ -521,7 +551,21 @@ export class ConstructsMaker {
521
551
  );
522
552
  }
523
553
 
554
+ // Keys are fully qualified and the host is whichever registry the fetching CLI
555
+ // resolved from, so a provider regenerated this run can arrive under a different
556
+ // host than the file already holds. Carrying the old key forward as well would
557
+ // leave two entries for one provider, and the stale one sorts first.
558
+ const regenerated = new Set(
559
+ Object.keys(this.versions).map((fqpn) =>
560
+ fqpn.split("/").slice(1).join("/"),
561
+ ),
562
+ );
563
+
524
564
  const versions = allowedConstraints.reduce((acc, constraint) => {
565
+ if (regenerated.has(constraint.fqn)) {
566
+ return acc;
567
+ }
568
+
525
569
  const provider = Object.entries(previousVersions).find(([name]) =>
526
570
  // This could be more refined, but it's good enough for now
527
571
  name.endsWith(constraint.fqn),
@@ -595,6 +639,62 @@ export class ConstructsMaker {
595
639
  });
596
640
  }
597
641
 
642
+ /**
643
+ * Delete resource directories under `providers/` that this run did not emit, so each provider folder holds
644
+ * exactly the resources in the version it was generated against.
645
+ *
646
+ * Walks only providers present in `emittedResourceFolders`, keeping any provider this run skipped. Within those,
647
+ * removes every subdirectory not emitted and leaves provider-level files (`index.ts`, `lazy-index.ts`) in place.
648
+ * Deletes files, so it must run after `save()` has written the current run's output.
649
+ *
650
+ * TypeScript only: `providers/<name>/<resource>/` maps one-to-one onto the emitted index exports. Other languages
651
+ * co-locate providers and modules in the same folders, which makes a dropped resource indistinguishable from a
652
+ * hand-written construct.
653
+ */
654
+ public async removeOrphanedResourceFolders() {
655
+ if (!this.isJavascriptTarget) {
656
+ return;
657
+ }
658
+
659
+ for (const [provider, emittedFolders] of Object.entries(
660
+ this.emittedResourceFolders,
661
+ )) {
662
+ const providerFolder = path.resolve(
663
+ this.codeMakerOutdir,
664
+ "providers",
665
+ provider,
666
+ );
667
+
668
+ let entries: string[] = [];
669
+ try {
670
+ entries = await fs.readdir(providerFolder);
671
+ } catch (e) {
672
+ logger.debug(
673
+ `Could not list resource folders in '${providerFolder}': ${e}`,
674
+ );
675
+ continue;
676
+ }
677
+
678
+ const expected = new Set(emittedFolders);
679
+ for (const entry of entries) {
680
+ if (expected.has(entry)) {
681
+ continue;
682
+ }
683
+
684
+ const entryPath = path.resolve(providerFolder, entry);
685
+ if (!fs.statSync(entryPath).isDirectory()) {
686
+ // Leave index.ts / lazy-index.ts and any other provider-level files alone.
687
+ continue;
688
+ }
689
+
690
+ logger.debug(
691
+ `Removing orphaned resource folder '${entry}' from provider '${provider}': it was not emitted for the current provider version`,
692
+ );
693
+ await fs.remove(entryPath);
694
+ }
695
+ }
696
+ }
697
+
598
698
  // emits a constraints.json file with a map of the used provider fqpns and version constraints
599
699
  // this is used for caching purposes
600
700
  private emitConstraintsFile(
@@ -757,6 +857,7 @@ a NODE_OPTIONS variable, we won't override it. Hence, the provider generation mi
757
857
 
758
858
  if (this.isJavascriptTarget) {
759
859
  await this.save();
860
+ await this.removeOrphanedResourceFolders();
760
861
  }
761
862
 
762
863
  if (!this.isJavascriptTarget || this.options.outputJsii) {
@@ -61,13 +61,13 @@ export class StructEmitter {
61
61
  comment.line(att.description);
62
62
  comment.line(``);
63
63
  comment.line(
64
- `Docs at Terraform Registry: {@link ${resource.linkToDocs}#${att.terraformName} ${resource.className}#${att.terraformName}}`,
64
+ `Docs at ${resource.docsRegistryName}: {@link ${resource.linkToDocs}#${att.terraformName} ${resource.className}#${att.terraformName}}`,
65
65
  );
66
66
  this.warnAboutIdField(att);
67
67
  comment.end();
68
68
  } else {
69
69
  comment.line(
70
- `Docs at Terraform Registry: {@link ${resource.linkToDocs}#${att.terraformName} ${resource.className}#${att.terraformName}}`,
70
+ `Docs at ${resource.docsRegistryName}: {@link ${resource.linkToDocs}#${att.terraformName} ${resource.className}#${att.terraformName}}`,
71
71
  );
72
72
  this.warnAboutIdField(att);
73
73
  comment.end();
@@ -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
- | { name: string; collapsed: boolean }
219
- | undefined;
218
+ { name: string; collapsed: boolean } | undefined;
220
219
  }
221
220
 
222
221
  export class ListAttributeTypeModel implements CollectionAttributeTypeModel {
@@ -5,7 +5,11 @@ import path from "path";
5
5
  import { FQPN, parseFQPN, ProviderName } from "@cdktn/provider-schema";
6
6
  import { AttributeModel } from "./attribute-model";
7
7
  import { Struct, ConfigStruct } from "./struct";
8
- import { Schema } from "@cdktn/commons";
8
+ import {
9
+ Schema,
10
+ TerraformTargetVersions,
11
+ registryForTargetVersions,
12
+ } from "@cdktn/commons";
9
13
  import { ProviderFunctionsModel } from "./provider-function-model";
10
14
 
11
15
  // Limit is 1200 to prevent stack size error.
@@ -25,6 +29,7 @@ interface ResourceModelOptions {
25
29
  schema: Schema;
26
30
  terraformSchemaType: string;
27
31
  configStructName: string;
32
+ targetVersions?: TerraformTargetVersions;
28
33
  }
29
34
 
30
35
  type DefinitionPath = string;
@@ -35,6 +40,7 @@ export class ResourceModel {
35
40
  public baseName: string;
36
41
  public provider: ProviderName;
37
42
  public fqpn: FQPN;
43
+ private readonly targetVersions?: TerraformTargetVersions;
38
44
  public providerVersionConstraint?: string;
39
45
  public providerVersion?: string;
40
46
  public terraformProviderSource?: string;
@@ -68,6 +74,7 @@ export class ResourceModel {
68
74
  this.attributes = options.attributes;
69
75
  this.schema = options.schema;
70
76
  this.fqpn = options.fqpn;
77
+ this.targetVersions = options.targetVersions;
71
78
  this.provider = parseFQPN(options.fqpn).name;
72
79
  this.terraformProviderName = this.provider;
73
80
  this.fileName = options.fileName;
@@ -108,16 +115,32 @@ export class ResourceModel {
108
115
  return this.escapeSchema(JSON.stringify(this.schema, undefined, 2));
109
116
  }
110
117
 
118
+ /** Registry that linkToDocs points at, for use in the surrounding prose. */
119
+ public get docsRegistryName(): string {
120
+ return registryForTargetVersions(this.targetVersions).displayName;
121
+ }
122
+
111
123
  public get linkToDocs(): string {
112
- const { hostname, namespace, name } = parseFQPN(this.fqpn);
124
+ // Keyed off the project's declared targets, not the FQPN hostname: that
125
+ // hostname reflects whichever CLI fetched the schema, which would make the
126
+ // generated link differ between a terraform and a tofu `cdktn get`.
127
+ const registry = registryForTargetVersions(this.targetVersions);
128
+ const { namespace, name } = parseFQPN(this.fqpn);
113
129
  const version = this.providerVersion || "latest";
114
- const base = `https://${hostname}/providers/${namespace}/${name}/${version}/docs`;
115
- if (this.isProvider) return base;
116
- if (this.isDataSource)
117
- return `${base}/data-sources/${this.terraformDocName}`;
118
- if (this.isEphemeralResource)
119
- return `${base}/ephemeral-resources/${this.terraformDocName}`;
120
- return `${base}/resources/${this.terraformDocName}`;
130
+ const kind = this.isProvider
131
+ ? "provider"
132
+ : this.isDataSource
133
+ ? "data-source"
134
+ : this.isEphemeralResource
135
+ ? "ephemeral-resource"
136
+ : "resource";
137
+ return registry.docsUrl(
138
+ namespace,
139
+ name,
140
+ version,
141
+ kind,
142
+ this.terraformDocName,
143
+ );
121
144
  }
122
145
 
123
146
  public get isProvider(): boolean {
@@ -3,12 +3,17 @@
3
3
  import { CodeMaker, toCamelCase } from "codemaker";
4
4
  import { AttributeModel } from "./models";
5
5
  import { sanitizedComment } from "./sanitized-comments";
6
- import { ConstructsMakerModuleTarget } from "@cdktn/commons";
6
+ import {
7
+ ConstructsMakerModuleTarget,
8
+ TerraformTargetVersions,
9
+ registryForTargetVersions,
10
+ } from "@cdktn/commons";
7
11
 
8
12
  export class ModuleGenerator {
9
13
  constructor(
10
14
  private readonly code: CodeMaker,
11
15
  private readonly targets: ConstructsMakerModuleTarget[],
16
+ private readonly targetVersions?: TerraformTargetVersions,
12
17
  ) {
13
18
  this.code.indentation = 2;
14
19
 
@@ -76,22 +81,19 @@ export class ModuleGenerator {
76
81
  // ... and more
77
82
  // ../module and ./module (local paths)
78
83
  const isNonRegistryModule = target.source.includes(".");
79
- let registryPath;
80
- // Submodules also exist (e.g. terraform-aws-modules/vpc/aws//modules/vpc-endpoints)
81
- // And linking directly to them in the Registry requires including the version
82
- // like e.g. https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest/submodules/vpc-endpoints
83
- if (target.source.includes("//")) {
84
- registryPath = target.source.replace(
85
- "//modules",
86
- `/${target.version || "latest"}/submodules`,
87
- );
88
- // terraform-aws-modules/vpc/aws//modules/vpc-endpoints
89
- // ->
90
- // terraform-aws-modules/vpc/aws/latest/submodules/vpc-endpoints
91
- } else {
92
- // not submodule specified, just append the version
93
- registryPath = `${target.source}/${target.version || "latest"}`;
94
- }
84
+ // Keyed off the project's declared targets rather than a fixed registry, so
85
+ // the link matches the product the project builds for.
86
+ const registry = registryForTargetVersions(this.targetVersions);
87
+ // Registries only have pages for a direct child of `modules/`, e.g.
88
+ // terraform-aws-modules/vpc/aws//modules/vpc-endpoints; any other `//` path
89
+ // links to the module itself.
90
+ const [moduleSource, subPath] = target.source.split("//", 2);
91
+ const submodule = subPath?.match(/^modules\/([^/]+)$/)?.[1];
92
+ const docsUrl = registry.moduleDocsUrl(
93
+ moduleSource,
94
+ target.version,
95
+ submodule,
96
+ );
95
97
 
96
98
  const comment = sanitizedComment(this.code);
97
99
  comment.line(`Defines an ${baseName} based on a Terraform module`);
@@ -99,7 +101,7 @@ export class ModuleGenerator {
99
101
  comment.line(
100
102
  isNonRegistryModule
101
103
  ? `Source at ${target.source}`
102
- : `Docs at Terraform Registry: {@link https://registry.terraform.io/modules/${registryPath} ${target.source}}`,
104
+ : `Docs at ${registry.displayName}: {@link ${docsUrl} ${target.source}}`,
103
105
  );
104
106
  comment.end();
105
107
  this.code.openBlock(`export class ${baseName} extends TerraformModule`);
@@ -8,6 +8,7 @@ import {
8
8
  logger,
9
9
  ProviderSchema,
10
10
  TerraformProviderConstraint,
11
+ TerraformTargetVersions,
11
12
  } from "@cdktn/commons";
12
13
  import { FQPN, parseFQPN, ProviderName } from "@cdktn/provider-schema";
13
14
  import {
@@ -29,6 +30,8 @@ export interface TerraformProviderGeneratorOptions {
29
30
  * Defaults to "" (CommonJS).
30
31
  */
31
32
  readonly importExtension?: string;
33
+ /** Project's declared targetVersions; selects the docs registry. */
34
+ readonly targetVersions?: TerraformTargetVersions;
32
35
  }
33
36
 
34
37
  interface ProviderData {
@@ -107,12 +110,17 @@ export interface ProviderConstraints {
107
110
  }
108
111
 
109
112
  export class TerraformProviderGenerator {
110
- private resourceParser = new ResourceParser();
113
+ private resourceParser: ResourceParser;
111
114
  private resourceEmitter: ResourceEmitter;
112
115
  private structEmitter: StructEmitter;
113
116
  private providerFunctionsEmitter: ProviderFunctionsEmitter;
114
117
  private readonly importExtension: string;
115
118
  public versions: { [fqpn: string]: string | undefined } = {};
119
+ /**
120
+ * Resource directory names emitted per provider folder Used to prune directories
121
+ * left over from a previous provider version.
122
+ */
123
+ public emittedResourceFolders: { [provider: string]: string[] } = {};
116
124
 
117
125
  constructor(
118
126
  private readonly code: CodeMaker,
@@ -121,6 +129,7 @@ export class TerraformProviderGenerator {
121
129
  ) {
122
130
  this.code.indentation = 2;
123
131
  this.importExtension = options.importExtension ?? "";
132
+ this.resourceParser = new ResourceParser(options.targetVersions);
124
133
  this.resourceEmitter = new ResourceEmitter(this.code, this.importExtension);
125
134
  this.structEmitter = new StructEmitter(this.code, this.importExtension);
126
135
  this.providerFunctionsEmitter = new ProviderFunctionsEmitter(this.code);
@@ -298,6 +307,20 @@ export class TerraformProviderGenerator {
298
307
 
299
308
  this.emitIndexFile(name, files);
300
309
  this.emitLazyIndexFile(name, files);
310
+
311
+ // Record exactly what this run emitted, derived the same way the index
312
+ // files derive their exports, so pruning can never disagree with them.
313
+ this.emittedResourceFolders[name] = files.map((file) =>
314
+ this.resourceFolderName(name, file),
315
+ );
316
+ }
317
+
318
+ /**
319
+ * Map an emitted file path to the resource directory name it lives in, e.g.
320
+ * `providers/mongodbatlas/teams/index.ts` -> `teams`.
321
+ */
322
+ private resourceFolderName(provider: ProviderName, file: string): string {
323
+ return file.replace(`providers/${provider}/`, "").replace("/index.ts", "");
301
324
  }
302
325
 
303
326
  private emitProviderFunctions(
@@ -322,7 +345,7 @@ export class TerraformProviderGenerator {
322
345
  ? resource.provider
323
346
  : resource.terraformType;
324
347
  this.code.line(
325
- `Refer to the Terraform Registry for docs: [\`${type}\`](${resource.linkToDocs}).`,
348
+ `Refer to the ${resource.docsRegistryName} for docs: [\`${type}\`](${resource.linkToDocs}).`,
326
349
  );
327
350
  this.code.closeFile(filePath);
328
351
  }
@@ -333,7 +356,7 @@ export class TerraformProviderGenerator {
333
356
  this.code.openFile(filePath);
334
357
  this.code.line("// generated by cdktn get");
335
358
  for (const file of files) {
336
- const dirName = file.replace(`${folder}/`, "").replace("/index.ts", "");
359
+ const dirName = this.resourceFolderName(provider, file);
337
360
  this.code.line(
338
361
  `export * as ${toCamelCase(dirName)} from './${dirName}/index${this.importExtension}';`,
339
362
  );
@@ -348,7 +371,7 @@ export class TerraformProviderGenerator {
348
371
  this.code.openFile(filePath);
349
372
  this.code.line("// generated by cdktn get");
350
373
  for (const file of files) {
351
- const dirName = file.replace(`${folder}/`, "").replace("/index.ts", "");
374
+ const dirName = this.resourceFolderName(provider, file);
352
375
  this.code.line(
353
376
  `Object.defineProperty(exports, '${toCamelCase(
354
377
  dirName,
@@ -11,6 +11,7 @@ import {
11
11
  isAttributeNestedType,
12
12
  isNestedTypeAttribute,
13
13
  Schema,
14
+ TerraformTargetVersions,
14
15
  } from "@cdktn/commons";
15
16
  import { ProviderName, FQPN, parseFQPN } from "@cdktn/provider-schema";
16
17
  import {
@@ -82,8 +83,17 @@ export function sanitizeClassOrNamespaceName(
82
83
  isProvider = false,
83
84
  ) {
84
85
  const resourceIsNamedProvider = !isProvider && baseName === "provider";
85
-
86
- if (isReservedClassOrNamespaceName(baseName) || resourceIsNamedProvider) {
86
+ // A resource named `<provider>_provider_functions` maps to the same
87
+ // providers/<provider>/provider-functions/ directory that the provider's
88
+ // functions submodule is emitted to, and would silently overwrite it.
89
+ const resourceIsNamedProviderFunctions =
90
+ !isProvider && baseName === "provider_functions";
91
+
92
+ if (
93
+ isReservedClassOrNamespaceName(baseName) ||
94
+ resourceIsNamedProvider ||
95
+ resourceIsNamedProviderFunctions
96
+ ) {
87
97
  return `${baseName}_resource`;
88
98
  } else {
89
99
  return baseName;
@@ -114,6 +124,7 @@ class Parser {
114
124
  constructor(
115
125
  private classNames: string[],
116
126
  private usedBaseNames: Map<string, string>,
127
+ private targetVersions?: TerraformTargetVersions,
117
128
  ) {}
118
129
 
119
130
  private uniqueClassName(className: string): string {
@@ -285,6 +296,7 @@ class Parser {
285
296
  terraformSchemaType,
286
297
  structs: this.structs,
287
298
  configStructName,
299
+ targetVersions: this.targetVersions,
288
300
  });
289
301
 
290
302
  return resourceModel;
@@ -718,6 +730,8 @@ export class ResourceParser {
718
730
  private uniqueBaseNames: Map<string, string> = new Map();
719
731
  private resources: Record<string, ResourceModel> = {};
720
732
 
733
+ constructor(private readonly targetVersions?: TerraformTargetVersions) {}
734
+
721
735
  public parse(
722
736
  fqpn: FQPN,
723
737
  type: string,
@@ -729,7 +743,11 @@ export class ResourceParser {
729
743
  return this.resources[type];
730
744
  }
731
745
 
732
- const parser = new Parser(this.uniqueClassnames, this.uniqueBaseNames);
746
+ const parser = new Parser(
747
+ this.uniqueClassnames,
748
+ this.uniqueBaseNames,
749
+ this.targetVersions,
750
+ );
733
751
  const resource = parser.resourceFrom(
734
752
  fqpn,
735
753
  type,
package/tsconfig.json CHANGED
@@ -24,6 +24,10 @@
24
24
  "stripInternal": true,
25
25
  "skipLibCheck": true,
26
26
  "target": "ES2018",
27
+ "types": [
28
+ "node",
29
+ "jest"
30
+ ],
27
31
  "incremental": true,
28
32
  "esModuleInterop": true,
29
33
  "rootDir": "./src",