@intentius/chant-lexicon-aws 0.0.2

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 (94) hide show
  1. package/README.md +438 -0
  2. package/package.json +30 -0
  3. package/src/codegen/__snapshots__/snapshot.test.ts.snap +197 -0
  4. package/src/codegen/docs-cli.ts +3 -0
  5. package/src/codegen/docs.ts +1206 -0
  6. package/src/codegen/extensions.ts +171 -0
  7. package/src/codegen/fallback.ts +33 -0
  8. package/src/codegen/generate-cli.ts +17 -0
  9. package/src/codegen/generate-lexicon.ts +98 -0
  10. package/src/codegen/generate-typescript.ts +257 -0
  11. package/src/codegen/generate.test.ts +125 -0
  12. package/src/codegen/generate.ts +226 -0
  13. package/src/codegen/idempotency.test.ts +28 -0
  14. package/src/codegen/naming.ts +120 -0
  15. package/src/codegen/package.test.ts +60 -0
  16. package/src/codegen/package.ts +84 -0
  17. package/src/codegen/patches.ts +98 -0
  18. package/src/codegen/rollback.test.ts +80 -0
  19. package/src/codegen/rollback.ts +20 -0
  20. package/src/codegen/sam.ts +387 -0
  21. package/src/codegen/snapshot.test.ts +84 -0
  22. package/src/codegen/typecheck.test.ts +50 -0
  23. package/src/codegen/typecheck.ts +4 -0
  24. package/src/codegen/versions.ts +37 -0
  25. package/src/coverage.ts +14 -0
  26. package/src/generated/index.d.ts +160753 -0
  27. package/src/generated/index.ts +14396 -0
  28. package/src/generated/lexicon-aws.json +114563 -0
  29. package/src/generated/runtime.ts +4 -0
  30. package/src/import/generator.test.ts +181 -0
  31. package/src/import/generator.ts +349 -0
  32. package/src/import/parser.test.ts +200 -0
  33. package/src/import/parser.ts +350 -0
  34. package/src/import/roundtrip-fixtures.test.ts +78 -0
  35. package/src/import/roundtrip.test.ts +195 -0
  36. package/src/index.ts +63 -0
  37. package/src/integration.test.ts +129 -0
  38. package/src/intrinsics.test.ts +167 -0
  39. package/src/intrinsics.ts +223 -0
  40. package/src/lint/post-synth/cf-refs.ts +91 -0
  41. package/src/lint/post-synth/cor020.ts +72 -0
  42. package/src/lint/post-synth/ext001.test.ts +68 -0
  43. package/src/lint/post-synth/ext001.ts +222 -0
  44. package/src/lint/post-synth/post-synth.test.ts +280 -0
  45. package/src/lint/post-synth/waw010.ts +49 -0
  46. package/src/lint/post-synth/waw011.ts +49 -0
  47. package/src/lint/post-synth/waw013.ts +45 -0
  48. package/src/lint/post-synth/waw014.ts +50 -0
  49. package/src/lint/post-synth/waw015.ts +100 -0
  50. package/src/lint/rules/hardcoded-region.ts +43 -0
  51. package/src/lint/rules/iam-wildcard.ts +66 -0
  52. package/src/lint/rules/index.ts +7 -0
  53. package/src/lint/rules/rules.test.ts +175 -0
  54. package/src/lint/rules/s3-encryption.ts +69 -0
  55. package/src/lsp/completions.test.ts +72 -0
  56. package/src/lsp/completions.ts +18 -0
  57. package/src/lsp/hover.test.ts +53 -0
  58. package/src/lsp/hover.ts +53 -0
  59. package/src/nested-stack.test.ts +83 -0
  60. package/src/nested-stack.ts +125 -0
  61. package/src/plugin.test.ts +316 -0
  62. package/src/plugin.ts +514 -0
  63. package/src/pseudo.test.ts +55 -0
  64. package/src/pseudo.ts +29 -0
  65. package/src/serializer.test.ts +507 -0
  66. package/src/serializer.ts +333 -0
  67. package/src/spec/fetch.test.ts +27 -0
  68. package/src/spec/fetch.ts +107 -0
  69. package/src/spec/parse.test.ts +153 -0
  70. package/src/spec/parse.ts +202 -0
  71. package/src/testdata/load-fixtures.ts +17 -0
  72. package/src/testdata/roundtrip/conditions.json +21 -0
  73. package/src/testdata/roundtrip/intrinsic-calls.json +31 -0
  74. package/src/testdata/roundtrip/intrinsics.json +18 -0
  75. package/src/testdata/roundtrip/multi-resource.json +37 -0
  76. package/src/testdata/roundtrip/parameters.json +23 -0
  77. package/src/testdata/roundtrip/simple.json +12 -0
  78. package/src/testdata/sam-fixtures/api.yaml +14 -0
  79. package/src/testdata/sam-fixtures/application.yaml +13 -0
  80. package/src/testdata/sam-fixtures/function.yaml +22 -0
  81. package/src/testdata/sam-fixtures/graphql-api.yaml +13 -0
  82. package/src/testdata/sam-fixtures/http-api.yaml +15 -0
  83. package/src/testdata/sam-fixtures/layer-version.yaml +15 -0
  84. package/src/testdata/sam-fixtures/multi-type-a.yaml +23 -0
  85. package/src/testdata/sam-fixtures/multi-type-b.yaml +29 -0
  86. package/src/testdata/sam-fixtures/simple-table.yaml +12 -0
  87. package/src/testdata/sam-fixtures/state-machine.yaml +14 -0
  88. package/src/testdata/schemas/aws-dynamodb-table.json +126 -0
  89. package/src/testdata/schemas/aws-iam-role.json +85 -0
  90. package/src/testdata/schemas/aws-lambda-function.json +90 -0
  91. package/src/testdata/schemas/aws-s3-bucket.json +83 -0
  92. package/src/testdata/schemas/aws-sns-topic.json +71 -0
  93. package/src/validate-cli.ts +19 -0
  94. package/src/validate.ts +34 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * cfn-lint extension constraints — cross-property validation rules.
3
+ *
4
+ * Downloads extension schemas from the same cfn-lint tarball and parses
5
+ * them into typed constraint objects for inclusion in lexicon JSON.
6
+ */
7
+
8
+ import { readFileSync, readdirSync, statSync } from "fs";
9
+ import { join } from "path";
10
+ import { homedir } from "os";
11
+ import { fetchAndExtractTar } from "@intentius/chant/codegen/fetch";
12
+ import { typeNameToPatchDir } from "./patches";
13
+ import { cfnLintTarballUrl } from "./versions";
14
+
15
+ const CFN_LINT_TARBALL_URL = cfnLintTarballUrl();
16
+ const EXTENSIONS_DEST_DIR = join(homedir(), ".chant", "cfn-lint-extensions");
17
+ const EXTENSIONS_TAR_PREFIX = "src/cfnlint/data/schemas/extensions/";
18
+
19
+ /**
20
+ * A single cross-property constraint extracted from a cfn-lint extension schema.
21
+ */
22
+ export interface ExtensionConstraint {
23
+ name: string;
24
+ type: "if_then" | "dependent_excluded" | "required_or" | "required_xor";
25
+ condition?: unknown;
26
+ requirement?: unknown;
27
+ }
28
+
29
+ /**
30
+ * Fetch cfn-lint extensions from GitHub tarball and extract to cache.
31
+ */
32
+ export async function fetchCfnLintExtensions(force = false): Promise<string> {
33
+ return fetchAndExtractTar(
34
+ { url: CFN_LINT_TARBALL_URL, destDir: EXTENSIONS_DEST_DIR },
35
+ EXTENSIONS_TAR_PREFIX,
36
+ force,
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Load extension schemas for known resource types.
42
+ * Returns a map of CFN type name → extension constraints.
43
+ */
44
+ export function loadExtensionSchemas(
45
+ extensionsDir: string,
46
+ knownTypes: Set<string>
47
+ ): Map<string, ExtensionConstraint[]> {
48
+ // Build reverse map: directory name → CFN type name
49
+ const dirToType = new Map<string, string>();
50
+ for (const typeName of knownTypes) {
51
+ dirToType.set(typeNameToPatchDir(typeName), typeName);
52
+ }
53
+
54
+ const result = new Map<string, ExtensionConstraint[]>();
55
+
56
+ let entries: string[];
57
+ try {
58
+ entries = readdirSync(extensionsDir);
59
+ } catch {
60
+ return result;
61
+ }
62
+
63
+ for (const entry of entries) {
64
+ const fullPath = join(extensionsDir, entry);
65
+ try {
66
+ if (!statSync(fullPath).isDirectory()) continue;
67
+ } catch {
68
+ continue;
69
+ }
70
+
71
+ const cfnType = dirToType.get(entry);
72
+ if (!cfnType) continue;
73
+
74
+ const constraints = loadDirectoryConstraints(fullPath);
75
+ if (constraints.length > 0) {
76
+ result.set(cfnType, constraints);
77
+ }
78
+ }
79
+
80
+ return result;
81
+ }
82
+
83
+ /**
84
+ * Load all constraints from JSON files in a directory.
85
+ */
86
+ function loadDirectoryConstraints(dir: string): ExtensionConstraint[] {
87
+ const constraints: ExtensionConstraint[] = [];
88
+
89
+ let entries: string[];
90
+ try {
91
+ entries = readdirSync(dir);
92
+ } catch {
93
+ return constraints;
94
+ }
95
+
96
+ for (const entry of entries) {
97
+ if (!entry.endsWith(".json")) continue;
98
+
99
+ let data: string;
100
+ try {
101
+ data = readFileSync(join(dir, entry), "utf-8");
102
+ } catch {
103
+ continue;
104
+ }
105
+
106
+ try {
107
+ const parsed = parseExtensionSchema(entry, data);
108
+ constraints.push(...parsed);
109
+ } catch {
110
+ // Skip files that don't parse as constraints
111
+ }
112
+ }
113
+
114
+ return constraints;
115
+ }
116
+
117
+ /**
118
+ * Parse a single extension JSON Schema file into constraint objects.
119
+ */
120
+ function parseExtensionSchema(fileName: string, data: string): ExtensionConstraint[] {
121
+ const raw = JSON.parse(data) as Record<string, unknown>;
122
+ const name = fileName.replace(/\.json$/, "");
123
+ const constraints: ExtensionConstraint[] = [];
124
+
125
+ // Check for allOf containing multiple constraints
126
+ if ("allOf" in raw && Array.isArray(raw.allOf)) {
127
+ for (let i = 0; i < raw.allOf.length; i++) {
128
+ const item = raw.allOf[i] as Record<string, unknown>;
129
+ try {
130
+ const c = classifySingleConstraint(`${name}_${i}`, item);
131
+ constraints.push(c);
132
+ } catch {
133
+ // Skip unrecognized items
134
+ }
135
+ }
136
+ if (constraints.length > 0) return constraints;
137
+ }
138
+
139
+ // Single top-level constraint
140
+ const c = classifySingleConstraint(name, raw);
141
+ return [c];
142
+ }
143
+
144
+ /**
145
+ * Classify a single JSON Schema document into an ExtensionConstraint.
146
+ */
147
+ function classifySingleConstraint(
148
+ name: string,
149
+ raw: Record<string, unknown>
150
+ ): ExtensionConstraint {
151
+ const hasIf = "if" in raw;
152
+ const hasThen = "then" in raw;
153
+ const hasDependentExcluded = "dependentExcluded" in raw;
154
+ const hasRequiredOr = "requiredOr" in raw;
155
+ const hasRequiredXor = "requiredXor" in raw;
156
+
157
+ if (hasRequiredXor) {
158
+ return { name, type: "required_xor", requirement: raw.requiredXor };
159
+ }
160
+ if (hasRequiredOr) {
161
+ return { name, type: "required_or", requirement: raw.requiredOr };
162
+ }
163
+ if (hasDependentExcluded) {
164
+ return { name, type: "dependent_excluded", requirement: raw.dependentExcluded };
165
+ }
166
+ if (hasIf && hasThen) {
167
+ return { name, type: "if_then", condition: raw.if, requirement: raw.then };
168
+ }
169
+
170
+ throw new Error(`unrecognized constraint pattern in ${name}`);
171
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Fallback resource definitions for priority types missing from the schema zip.
3
+ * Acts as a safety net — if the schema zip already contains the resource,
4
+ * the fallback is not used.
5
+ */
6
+
7
+ import type { SchemaParseResult } from "../spec/parse";
8
+
9
+ export function fallbackResources(): SchemaParseResult[] {
10
+ return [fallbackLogGroup()];
11
+ }
12
+
13
+ function fallbackLogGroup(): SchemaParseResult {
14
+ return {
15
+ resource: {
16
+ typeName: "AWS::Logs::LogGroup",
17
+ properties: [
18
+ { name: "LogGroupName", tsType: "string", required: false, constraints: {} },
19
+ { name: "RetentionInDays", tsType: "number", required: false, constraints: {} },
20
+ { name: "KmsKeyId", tsType: "string", required: false, constraints: {} },
21
+ { name: "DataProtectionPolicy", tsType: "any", required: false, constraints: {} },
22
+ { name: "Tags", tsType: "Tag[]", required: false, constraints: {} },
23
+ { name: "LogGroupClass", tsType: "string", required: false, constraints: {} },
24
+ ],
25
+ attributes: [{ name: "Arn", tsType: "string" }],
26
+ createOnly: [],
27
+ writeOnly: [],
28
+ primaryIdentifier: [],
29
+ },
30
+ propertyTypes: [],
31
+ enums: [],
32
+ };
33
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Thin entry point for `bun run generate` in lexicon-aws.
4
+ */
5
+ import { generate, writeGeneratedFiles } from "./generate";
6
+ import { dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const result = await generate({ verbose: true });
10
+ // src/codegen/generate-cli.ts → dirname x3 → package root
11
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
12
+ writeGeneratedFiles(result, pkgDir);
13
+
14
+ console.error(`Generated ${result.resources} resources, ${result.properties} property types, ${result.enums} enums`);
15
+ if (result.warnings.length > 0) {
16
+ console.error(`${result.warnings.length} warnings`);
17
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Lexicon JSON generator — produces lexicon-aws.json with metadata for all resources.
3
+ *
4
+ * Output is a Record<string, LexiconEntry> keyed by generated class name.
5
+ */
6
+
7
+ import type { SchemaParseResult } from "../spec/parse";
8
+ import { cfnShortName } from "../spec/parse";
9
+ import type { NamingStrategy } from "./naming";
10
+ import type { ExtensionConstraint } from "./extensions";
11
+ import type { PropertyConstraints } from "@intentius/chant/codegen/json-schema";
12
+ import {
13
+ buildRegistry,
14
+ serializeRegistry,
15
+ type RegistryResource,
16
+ } from "@intentius/chant/codegen/generate-registry";
17
+
18
+ export interface LexiconEntry {
19
+ resourceType: string;
20
+ kind: "resource" | "property";
21
+ lexicon: "aws";
22
+ attrs?: Record<string, string>;
23
+ constraints?: ExtensionConstraint[];
24
+ propertyConstraints?: Record<string, PropertyConstraints>;
25
+ createOnly?: string[];
26
+ writeOnly?: string[];
27
+ primaryIdentifier?: string[];
28
+ runtimeDeprecations?: Record<string, string>;
29
+ }
30
+
31
+ /**
32
+ * Generate the lexicon-aws.json content.
33
+ */
34
+ export function generateLexiconJSON(
35
+ results: SchemaParseResult[],
36
+ naming: NamingStrategy,
37
+ constraints: Map<string, ExtensionConstraint[]>,
38
+ runtimeDeprecations: Record<string, string> | null,
39
+ ): string {
40
+ // Adapt SchemaParseResult[] to RegistryResource[]
41
+ const registryResources: RegistryResource[] = results.map((r) => ({
42
+ typeName: r.resource.typeName,
43
+ attributes: r.resource.attributes,
44
+ properties: r.resource.properties,
45
+ propertyTypes: r.propertyTypes,
46
+ }));
47
+
48
+ const entries = buildRegistry<LexiconEntry>(registryResources, naming, {
49
+ shortName: cfnShortName,
50
+ buildEntry: (resource, _tsName, attrs, propConstraints) => {
51
+ const cfnType = resource.typeName;
52
+ const r = results.find((res) => res.resource.typeName === cfnType)!;
53
+
54
+ // Runtime deprecation data for Lambda resources with Runtime property
55
+ let runtimeDepr: Record<string, string> | undefined;
56
+ if (runtimeDeprecations && r.resource.properties.some((p) => p.name === "Runtime")) {
57
+ runtimeDepr = runtimeDeprecations;
58
+ }
59
+
60
+ return {
61
+ resourceType: cfnType,
62
+ kind: "resource" as const,
63
+ lexicon: "aws" as const,
64
+ ...(attrs && { attrs }),
65
+ ...(constraints.has(cfnType) && { constraints: constraints.get(cfnType) }),
66
+ ...(propConstraints && { propertyConstraints: propConstraints }),
67
+ ...(r.resource.createOnly.length > 0 && { createOnly: r.resource.createOnly }),
68
+ ...(r.resource.writeOnly.length > 0 && { writeOnly: r.resource.writeOnly }),
69
+ ...(r.resource.primaryIdentifier.length > 0 && { primaryIdentifier: r.resource.primaryIdentifier }),
70
+ ...(runtimeDepr && { runtimeDeprecations: runtimeDepr }),
71
+ };
72
+ },
73
+ buildPropertyEntry: (resourceType, propertyType) => ({
74
+ resourceType: `${resourceType}.${propertyType}`,
75
+ kind: "property" as const,
76
+ lexicon: "aws" as const,
77
+ }),
78
+ });
79
+
80
+ return serializeRegistry(entries);
81
+ }
82
+
83
+ /**
84
+ * Lambda runtime deprecation fallback data.
85
+ */
86
+ export function lambdaRuntimeDeprecations(): Record<string, string> {
87
+ return {
88
+ "nodejs14.x": "deprecated",
89
+ "nodejs16.x": "deprecated",
90
+ "nodejs18.x": "approaching_eol",
91
+ "python3.7": "deprecated",
92
+ "python3.8": "deprecated",
93
+ "python3.9": "approaching_eol",
94
+ "dotnet6": "deprecated",
95
+ "java8": "deprecated",
96
+ "ruby2.7": "deprecated",
97
+ };
98
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * TypeScript declaration file generator — produces index.d.ts with all resource classes.
3
+ *
4
+ * Output includes:
5
+ * - Resource classes with typed constructors and readonly attributes
6
+ * - Property type classes
7
+ * - Enum union types
8
+ * - Static content (intrinsics, pseudo-parameters, common interfaces)
9
+ */
10
+
11
+ import type { SchemaParseResult, ParsedPropertyType } from "../spec/parse";
12
+ import { cfnShortName } from "../spec/parse";
13
+ import type { NamingStrategy } from "./naming";
14
+ import { propertyTypeName, extractDefName } from "./naming";
15
+ import {
16
+ writeResourceClass,
17
+ writePropertyClass,
18
+ writeEnumType,
19
+ type DtsProperty,
20
+ type DtsAttribute,
21
+ } from "@intentius/chant/codegen/generate-typescript";
22
+
23
+ interface ResourceEntry {
24
+ tsName: string;
25
+ properties: DtsProperty[];
26
+ attributes: DtsAttribute[];
27
+ remap: Map<string, string>;
28
+ }
29
+
30
+ interface PropertyEntry {
31
+ tsName: string;
32
+ properties: DtsProperty[];
33
+ remap: Map<string, string>;
34
+ }
35
+
36
+ interface EnumEntry {
37
+ tsName: string;
38
+ values: string[];
39
+ }
40
+
41
+ /**
42
+ * Generate the complete .d.ts file content.
43
+ */
44
+ export function generateTypeScriptDeclarations(
45
+ results: SchemaParseResult[],
46
+ naming: NamingStrategy,
47
+ ): string {
48
+ const lines: string[] = [];
49
+ lines.push("// Code generated by chant aws generate. DO NOT EDIT generated sections.");
50
+
51
+ const resources: ResourceEntry[] = [];
52
+ const properties: PropertyEntry[] = [];
53
+ const enums: EnumEntry[] = [];
54
+
55
+ for (const r of results) {
56
+ const cfnType = r.resource.typeName;
57
+ const tsName = naming.resolve(cfnType);
58
+ if (!tsName) continue;
59
+
60
+ // Build remap from parser property type names to resolved names
61
+ const shortName = cfnShortName(cfnType);
62
+ const remap = new Map<string, string>();
63
+ for (const pt of r.propertyTypes) {
64
+ const defName = extractDefName(pt.name, shortName);
65
+ remap.set(pt.name, propertyTypeName(tsName, defName));
66
+ }
67
+
68
+ for (const e of r.enums) {
69
+ const defName = extractDefName(e.name, shortName);
70
+ remap.set(e.name, propertyTypeName(tsName, defName));
71
+ }
72
+
73
+ // Map ParsedProperty/ParsedAttribute → DtsProperty/DtsAttribute
74
+ const dtsProps: DtsProperty[] = r.resource.properties.map((p) => ({
75
+ name: p.name,
76
+ type: isPolicyDocumentProperty(p.name, p.tsType) ? "PolicyDocument" : p.tsType,
77
+ required: p.required,
78
+ description: p.description,
79
+ }));
80
+ const dtsAttrs: DtsAttribute[] = r.resource.attributes.map((a) => ({
81
+ name: a.name,
82
+ type: a.tsType,
83
+ }));
84
+
85
+ resources.push({ tsName, properties: dtsProps, attributes: dtsAttrs, remap });
86
+
87
+ // Alias entries as separate resource entries
88
+ for (const alias of naming.aliases(cfnType)) {
89
+ resources.push({ tsName: alias, properties: dtsProps, attributes: dtsAttrs, remap });
90
+ }
91
+
92
+ const ptAliases = naming.propertyTypeAliases(cfnType);
93
+ for (const pt of r.propertyTypes) {
94
+ const defName = extractDefName(pt.name, shortName);
95
+ const ptName = propertyTypeName(tsName, defName);
96
+ const ptProps: DtsProperty[] = pt.properties.map((p) => ({
97
+ name: p.name,
98
+ type: isPolicyDocumentProperty(p.name, p.tsType) ? "PolicyDocument" : p.tsType,
99
+ required: p.required,
100
+ description: p.description,
101
+ }));
102
+ properties.push({ tsName: ptName, properties: ptProps, remap });
103
+
104
+ // Alias class declaration for globally unique property types
105
+ if (ptAliases) {
106
+ const aliasName = ptAliases.get(defName);
107
+ if (aliasName) {
108
+ properties.push({ tsName: aliasName, properties: ptProps, remap });
109
+ }
110
+ }
111
+ }
112
+
113
+ for (const e of r.enums) {
114
+ const defName = extractDefName(e.name, shortName);
115
+ const eName = propertyTypeName(tsName, defName);
116
+ enums.push({ tsName: eName, values: e.values });
117
+ }
118
+ }
119
+
120
+ // Sort all entries alphabetically
121
+ resources.sort((a, b) => a.tsName.localeCompare(b.tsName));
122
+ properties.sort((a, b) => a.tsName.localeCompare(b.tsName));
123
+ enums.sort((a, b) => a.tsName.localeCompare(b.tsName));
124
+
125
+ // Section 1: Resource classes
126
+ lines.push("");
127
+ lines.push("// --- Resource classes ---");
128
+ for (const re of resources) {
129
+ writeResourceClass(lines, re.tsName, re.properties, re.attributes, re.remap);
130
+ }
131
+
132
+ // Section 2: Property classes
133
+ lines.push("");
134
+ lines.push("// --- Property classes ---");
135
+ for (const pe of properties) {
136
+ writePropertyClass(lines, pe.tsName, pe.properties, pe.remap);
137
+ }
138
+
139
+ // Section 3: Enum types
140
+ if (enums.length > 0) {
141
+ lines.push("");
142
+ lines.push("// --- Enum types ---");
143
+ for (const ee of enums) {
144
+ writeEnumType(lines, ee.tsName, ee.values);
145
+ }
146
+ }
147
+
148
+ // Section 4: Static content
149
+ lines.push("");
150
+ lines.push(staticTypeScript());
151
+ lines.push("");
152
+
153
+ return lines.join("\n");
154
+ }
155
+
156
+ // --- PolicyDocument type overrides ---
157
+
158
+ const policyDocumentPropertyNames = new Set([
159
+ "assumerolepolicydocument",
160
+ "policydocument",
161
+ "permissionspolicydocument",
162
+ "resourcepolicydocument",
163
+ ]);
164
+
165
+ function isPolicyDocumentProperty(name: string, tsType: string): boolean {
166
+ return tsType === "Record<string, any>" &&
167
+ policyDocumentPropertyNames.has(name.toLowerCase());
168
+ }
169
+
170
+ // --- Static content ---
171
+
172
+ function staticTypeScript(): string {
173
+ const lines: string[] = [];
174
+
175
+ // Intrinsic functions
176
+ lines.push("// --- Intrinsic functions ---");
177
+ lines.push("");
178
+ const intrinsics: [string, string][] = [
179
+ ["And", "(conditions: unknown[]): any"],
180
+ ["Base64", "(value: unknown): any"],
181
+ ["Cidr", "(ipBlock: unknown, count: unknown, cidrBits?: unknown): any"],
182
+ ["Condition", "(condition: string): any"],
183
+ ["Equals", "(a: unknown, b: unknown): any"],
184
+ ["FindInMap", "(mapName: string, firstKey: unknown, secondKey: unknown): any"],
185
+ ["GetAtt", "(logicalName: string, attribute: string): any"],
186
+ ["GetAZs", "(region?: string): any"],
187
+ ["If", "(condition: string, valueIfTrue: unknown, valueIfFalse: unknown): any"],
188
+ ["ImportValue", "(sharedValue: unknown): any"],
189
+ ["Join", "(delimiter: string, values: unknown[]): any"],
190
+ ["Not", "(condition: unknown): any"],
191
+ ["Or", "(conditions: unknown[]): any"],
192
+ ["Ref", "(logicalName: string): any"],
193
+ ["Select", "(index: number, values: unknown[]): any"],
194
+ ["Split", "(delimiter: string, source: unknown): any"],
195
+ ["Sub", "(parts: TemplateStringsArray, ...values: unknown[]): any"],
196
+ ["Transform", "(name: string, parameters: Record<string, unknown>): any"],
197
+ ];
198
+ intrinsics.sort((a, b) => a[0].localeCompare(b[0]));
199
+ for (const [name, sig] of intrinsics) {
200
+ lines.push(`export declare function ${name}${sig};`);
201
+ }
202
+
203
+ // Pseudo-parameters
204
+ lines.push("");
205
+ lines.push("// --- Pseudo-parameters ---");
206
+ lines.push("");
207
+ lines.push("export declare const AWS: {");
208
+ const pseudoParams = [
209
+ "AccountId",
210
+ "NotificationARNs",
211
+ "NoValue",
212
+ "Partition",
213
+ "Region",
214
+ "StackId",
215
+ "StackName",
216
+ "URLSuffix",
217
+ ];
218
+ for (const p of pseudoParams) {
219
+ lines.push(` readonly ${p}: any;`);
220
+ }
221
+ lines.push("};");
222
+
223
+ // Type interfaces
224
+ lines.push("");
225
+ lines.push("// --- Type interfaces ---");
226
+ lines.push("");
227
+ lines.push("export interface PolicyDocument {");
228
+ lines.push(' Version?: "2012-10-17" | "2008-10-17";');
229
+ lines.push(" Id?: string;");
230
+ lines.push(" Statement: IamPolicyStatement | IamPolicyStatement[];");
231
+ lines.push("}");
232
+ lines.push("");
233
+ lines.push("export interface IamPolicyStatement {");
234
+ lines.push(" Sid?: string;");
235
+ lines.push(' Effect: "Allow" | "Deny";');
236
+ lines.push(" Principal?: IamPolicyPrincipal;");
237
+ lines.push(" NotPrincipal?: IamPolicyPrincipal;");
238
+ lines.push(" Action?: string | string[];");
239
+ lines.push(" NotAction?: string | string[];");
240
+ lines.push(" Resource?: string | string[];");
241
+ lines.push(" NotResource?: string | string[];");
242
+ lines.push(" Condition?: Record<string, Record<string, string | string[]>>;");
243
+ lines.push("}");
244
+ lines.push("");
245
+ lines.push('export type IamPolicyPrincipal = "*" | {');
246
+ lines.push(" AWS?: string | string[];");
247
+ lines.push(" Service?: string | string[];");
248
+ lines.push(" Federated?: string | string[];");
249
+ lines.push("};")
250
+ lines.push("");
251
+ lines.push("export interface Tag {");
252
+ lines.push(" key: string;");
253
+ lines.push(" value: string;");
254
+ lines.push("}");
255
+
256
+ return lines.join("\n");
257
+ }