@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,98 @@
1
+ /**
2
+ * cfn-lint patch fetcher and applier.
3
+ *
4
+ * Downloads cfn-lint patches from GitHub and applies them to raw schemas
5
+ * before parsing. This enriches the schemas with additional type information,
6
+ * enum values, and constraint fixes.
7
+ */
8
+
9
+ import { readFileSync } from "fs";
10
+ import { join } from "path";
11
+ import { homedir } from "os";
12
+ import { rfc6902Apply } from "@intentius/chant/codegen/json-patch";
13
+ import { fetchAndExtractTar } from "@intentius/chant/codegen/fetch";
14
+ import { cfnLintTarballUrl } from "./versions";
15
+
16
+ const CFN_LINT_TARBALL_URL = cfnLintTarballUrl();
17
+ const PATCHES_DEST_DIR = join(homedir(), ".chant", "cfn-lint-patches");
18
+ const PATCHES_TAR_PREFIX = "src/cfnlint/data/schemas/patches/extensions/all/";
19
+ const PATCH_FILES = ["manual.json", "smithy.json", "format.json"] as const;
20
+
21
+ export interface PatchWarning {
22
+ typeName: string;
23
+ patchFile: string;
24
+ error: Error;
25
+ }
26
+
27
+ export interface PatchStats {
28
+ patchesApplied: number;
29
+ resourcesFixed: number;
30
+ warnings: PatchWarning[];
31
+ }
32
+
33
+ /**
34
+ * Fetch cfn-lint patches from GitHub tarball and extract to cache.
35
+ * Returns the path to the extracted patches directory.
36
+ */
37
+ export async function fetchCfnLintPatches(force = false): Promise<string> {
38
+ return fetchAndExtractTar(
39
+ { url: CFN_LINT_TARBALL_URL, destDir: PATCHES_DEST_DIR },
40
+ PATCHES_TAR_PREFIX,
41
+ force,
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Apply cfn-lint RFC 6902 patches to raw schema bytes.
47
+ * For each schema, converts type name to directory name and applies
48
+ * manual.json, smithy.json, format.json in order.
49
+ */
50
+ export function applyPatches(
51
+ schemas: Map<string, Buffer>,
52
+ patchesDir: string
53
+ ): { schemas: Map<string, Buffer>; stats: PatchStats } {
54
+ const stats: PatchStats = { patchesApplied: 0, resourcesFixed: 0, warnings: [] };
55
+ const result = new Map<string, Buffer>();
56
+
57
+ for (const [typeName, data] of schemas) {
58
+ const dirName = typeNameToPatchDir(typeName);
59
+ const patchPath = join(patchesDir, dirName);
60
+
61
+ let patched = data;
62
+ for (const pf of PATCH_FILES) {
63
+ const fullPath = join(patchPath, pf);
64
+ let patchData: Buffer;
65
+ try {
66
+ patchData = readFileSync(fullPath) as unknown as Buffer;
67
+ } catch {
68
+ continue; // Patch file doesn't exist
69
+ }
70
+
71
+ try {
72
+ patched = Buffer.from(rfc6902Apply(patched.toString("utf-8"), patchData.toString("utf-8")));
73
+ stats.patchesApplied++;
74
+ } catch (err) {
75
+ stats.warnings.push({
76
+ typeName,
77
+ patchFile: pf,
78
+ error: err instanceof Error ? err : new Error(String(err)),
79
+ });
80
+ }
81
+ }
82
+
83
+ if (patched !== data) {
84
+ stats.resourcesFixed++;
85
+ }
86
+ result.set(typeName, patched);
87
+ }
88
+
89
+ return { schemas: result, stats };
90
+ }
91
+
92
+ /**
93
+ * Convert CloudFormation type name to cfn-lint patch directory name.
94
+ * AWS::S3::Bucket → aws_s3_bucket
95
+ */
96
+ export function typeNameToPatchDir(typeName: string): string {
97
+ return typeName.replaceAll("::", "_").toLowerCase();
98
+ }
@@ -0,0 +1,80 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { mkdirSync, writeFileSync, rmSync, readFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { snapshotArtifacts, saveSnapshot, restoreSnapshot, listSnapshots } from "./rollback";
6
+
7
+ function makeTempDir(): string {
8
+ const dir = join(tmpdir(), `chant-rollback-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
9
+ mkdirSync(dir, { recursive: true });
10
+ return dir;
11
+ }
12
+
13
+ describe("rollback", () => {
14
+ test("snapshot captures generated files", () => {
15
+ const dir = makeTempDir();
16
+ const genDir = join(dir, "generated");
17
+ mkdirSync(genDir, { recursive: true });
18
+
19
+ writeFileSync(join(genDir, "lexicon-aws.json"), '{"Bucket":{"kind":"resource"}}');
20
+ writeFileSync(join(genDir, "index.d.ts"), "declare class Bucket {}");
21
+ writeFileSync(join(genDir, "index.ts"), "export const Bucket = {};");
22
+
23
+ const snapshot = snapshotArtifacts(genDir);
24
+ expect(snapshot.files["lexicon-aws.json"]).toBeDefined();
25
+ expect(snapshot.files["index.d.ts"]).toBeDefined();
26
+ expect(snapshot.files["index.ts"]).toBeDefined();
27
+ expect(snapshot.hashes["lexicon-aws.json"]).toBeDefined();
28
+ expect(snapshot.resourceCount).toBe(1);
29
+
30
+ rmSync(dir, { recursive: true, force: true });
31
+ });
32
+
33
+ test("save and list snapshots", () => {
34
+ const dir = makeTempDir();
35
+ const snapshotsDir = join(dir, ".snapshots");
36
+
37
+ const snapshot = {
38
+ timestamp: "2025-01-01T00:00:00.000Z",
39
+ files: { "test.json": "{}" },
40
+ hashes: { "test.json": "abc123" },
41
+ resourceCount: 0,
42
+ };
43
+
44
+ saveSnapshot(snapshot, snapshotsDir);
45
+ const list = listSnapshots(snapshotsDir);
46
+ expect(list.length).toBe(1);
47
+ expect(list[0].timestamp).toBe("2025-01-01T00:00:00.000Z");
48
+
49
+ rmSync(dir, { recursive: true, force: true });
50
+ });
51
+
52
+ test("restore snapshot overwrites generated files", () => {
53
+ const dir = makeTempDir();
54
+ const genDir = join(dir, "generated");
55
+ const snapshotsDir = join(dir, ".snapshots");
56
+ mkdirSync(genDir, { recursive: true });
57
+
58
+ // Write original files
59
+ writeFileSync(join(genDir, "lexicon-aws.json"), '{"original":true}');
60
+
61
+ // Snapshot them
62
+ const snapshot = snapshotArtifacts(genDir);
63
+ const snapshotPath = saveSnapshot(snapshot, snapshotsDir);
64
+
65
+ // Overwrite with new content
66
+ writeFileSync(join(genDir, "lexicon-aws.json"), '{"modified":true}');
67
+ expect(readFileSync(join(genDir, "lexicon-aws.json"), "utf-8")).toBe('{"modified":true}');
68
+
69
+ // Restore
70
+ restoreSnapshot(snapshotPath, genDir);
71
+ expect(readFileSync(join(genDir, "lexicon-aws.json"), "utf-8")).toBe('{"original":true}');
72
+
73
+ rmSync(dir, { recursive: true, force: true });
74
+ });
75
+
76
+ test("listSnapshots returns empty for nonexistent dir", () => {
77
+ const list = listSnapshots("/nonexistent/path");
78
+ expect(list).toHaveLength(0);
79
+ });
80
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Re-export from core with AWS-specific artifact names.
3
+ */
4
+ import {
5
+ snapshotArtifacts as _snapshotArtifacts,
6
+ saveSnapshot,
7
+ restoreSnapshot,
8
+ listSnapshots,
9
+ } from "@intentius/chant/codegen/rollback";
10
+ export type { ArtifactSnapshot, SnapshotInfo } from "@intentius/chant/codegen/rollback";
11
+ export { saveSnapshot, restoreSnapshot, listSnapshots };
12
+
13
+ const AWS_ARTIFACT_NAMES = ["lexicon-aws.json", "index.d.ts", "index.ts"];
14
+
15
+ /**
16
+ * Snapshot AWS lexicon artifacts.
17
+ */
18
+ export function snapshotArtifacts(generatedDir: string) {
19
+ return _snapshotArtifacts(generatedDir, AWS_ARTIFACT_NAMES);
20
+ }
@@ -0,0 +1,387 @@
1
+ /**
2
+ * SAM (Serverless Application Model) resource definitions.
3
+ *
4
+ * Hand-authored since no machine-readable SAM spec exists.
5
+ * 9 resources matching the Go implementation.
6
+ */
7
+
8
+ import type { SchemaParseResult, ParsedProperty, ParsedAttribute, ParsedPropertyType } from "../spec/parse";
9
+
10
+ export function samResources(): SchemaParseResult[] {
11
+ return [
12
+ samFunction(),
13
+ samApi(),
14
+ samHttpApi(),
15
+ samSimpleTable(),
16
+ samLayerVersion(),
17
+ samStateMachine(),
18
+ samApplication(),
19
+ samConnector(),
20
+ samGraphQLApi(),
21
+ ];
22
+ }
23
+
24
+ function samFunction(): SchemaParseResult {
25
+ return {
26
+ resource: {
27
+ typeName: "AWS::Serverless::Function",
28
+ properties: [
29
+ { name: "Handler", tsType: "string", required: true, constraints: {} },
30
+ { name: "Runtime", tsType: "string", required: true, constraints: {} },
31
+ { name: "CodeUri", tsType: "any", required: true, constraints: {} },
32
+ { name: "FunctionName", tsType: "string", required: false, constraints: {} },
33
+ { name: "Description", tsType: "string", required: false, constraints: {} },
34
+ { name: "MemorySize", tsType: "number", required: false, constraints: {} },
35
+ { name: "Timeout", tsType: "number", required: false, constraints: {} },
36
+ { name: "Role", tsType: "string", required: false, constraints: {} },
37
+ { name: "Policies", tsType: "any[]", required: false, constraints: {} },
38
+ { name: "Environment", tsType: "Function_Environment", required: false, constraints: {} },
39
+ { name: "Events", tsType: "Record<string, any>", required: false, constraints: {} },
40
+ { name: "VpcConfig", tsType: "Function_VpcConfig", required: false, constraints: {} },
41
+ { name: "Architectures", tsType: "string[]", required: false, constraints: {} },
42
+ { name: "Layers", tsType: "string[]", required: false, constraints: {} },
43
+ { name: "Tracing", tsType: "string", required: false, constraints: {} },
44
+ { name: "Tags", tsType: "Record<string, any>", required: false, constraints: {} },
45
+ { name: "DeadLetterQueue", tsType: "Function_DeadLetterQueue", required: false, constraints: {} },
46
+ { name: "DeploymentPreference", tsType: "Function_DeploymentPreference", required: false, constraints: {} },
47
+ { name: "ReservedConcurrentExecutions", tsType: "number", required: false, constraints: {} },
48
+ { name: "AutoPublishAlias", tsType: "string", required: false, constraints: {} },
49
+ { name: "PackageType", tsType: "string", required: false, constraints: {} },
50
+ { name: "ImageUri", tsType: "string", required: false, constraints: {} },
51
+ { name: "ImageConfig", tsType: "any", required: false, constraints: {} },
52
+ { name: "EphemeralStorage", tsType: "any", required: false, constraints: {} },
53
+ { name: "SnapStart", tsType: "any", required: false, constraints: {} },
54
+ { name: "FunctionUrlConfig", tsType: "any", required: false, constraints: {} },
55
+ { name: "InlineCode", tsType: "string", required: false, constraints: {} },
56
+ ],
57
+ attributes: [{ name: "Arn", tsType: "string" }],
58
+ createOnly: [],
59
+ writeOnly: [],
60
+ primaryIdentifier: [],
61
+ },
62
+ propertyTypes: [
63
+ {
64
+ name: "Function_Environment",
65
+ cfnType: "Environment",
66
+ properties: [
67
+ { name: "Variables", tsType: "Record<string, any>", required: false, constraints: {} },
68
+ ],
69
+ },
70
+ {
71
+ name: "Function_VpcConfig",
72
+ cfnType: "VpcConfig",
73
+ properties: [
74
+ { name: "SecurityGroupIds", tsType: "string[]", required: true, constraints: {} },
75
+ { name: "SubnetIds", tsType: "string[]", required: true, constraints: {} },
76
+ ],
77
+ },
78
+ {
79
+ name: "Function_DeadLetterQueue",
80
+ cfnType: "DeadLetterQueue",
81
+ properties: [
82
+ { name: "Type", tsType: "string", required: true, constraints: {} },
83
+ { name: "TargetArn", tsType: "string", required: true, constraints: {} },
84
+ ],
85
+ },
86
+ {
87
+ name: "Function_DeploymentPreference",
88
+ cfnType: "DeploymentPreference",
89
+ properties: [
90
+ { name: "Type", tsType: "string", required: true, constraints: {} },
91
+ { name: "Enabled", tsType: "boolean", required: false, constraints: {} },
92
+ { name: "Alarms", tsType: "string[]", required: false, constraints: {} },
93
+ { name: "Hooks", tsType: "any", required: false, constraints: {} },
94
+ ],
95
+ },
96
+ {
97
+ name: "Function_EventSource",
98
+ cfnType: "EventSource",
99
+ properties: [
100
+ { name: "Type", tsType: "string", required: true, constraints: {} },
101
+ { name: "Properties", tsType: "any", required: false, constraints: {} },
102
+ ],
103
+ },
104
+ {
105
+ name: "Function_S3Location",
106
+ cfnType: "S3Location",
107
+ properties: [
108
+ { name: "Bucket", tsType: "string", required: true, constraints: {} },
109
+ { name: "Key", tsType: "string", required: true, constraints: {} },
110
+ { name: "Version", tsType: "string", required: false, constraints: {} },
111
+ ],
112
+ },
113
+ ],
114
+ enums: [],
115
+ };
116
+ }
117
+
118
+ function samApi(): SchemaParseResult {
119
+ return {
120
+ resource: {
121
+ typeName: "AWS::Serverless::Api",
122
+ properties: [
123
+ { name: "StageName", tsType: "string", required: true, constraints: {} },
124
+ { name: "DefinitionBody", tsType: "any", required: false, constraints: {} },
125
+ { name: "DefinitionUri", tsType: "any", required: false, constraints: {} },
126
+ { name: "Name", tsType: "string", required: false, constraints: {} },
127
+ { name: "Auth", tsType: "Api_Auth", required: false, constraints: {} },
128
+ { name: "Cors", tsType: "any", required: false, constraints: {} },
129
+ { name: "EndpointConfiguration", tsType: "string", required: false, constraints: {} },
130
+ { name: "TracingEnabled", tsType: "boolean", required: false, constraints: {} },
131
+ { name: "CacheClusterEnabled", tsType: "boolean", required: false, constraints: {} },
132
+ { name: "CacheClusterSize", tsType: "string", required: false, constraints: {} },
133
+ { name: "Variables", tsType: "Record<string, any>", required: false, constraints: {} },
134
+ { name: "Tags", tsType: "Record<string, any>", required: false, constraints: {} },
135
+ { name: "AccessLogSetting", tsType: "any", required: false, constraints: {} },
136
+ { name: "CanarySetting", tsType: "any", required: false, constraints: {} },
137
+ { name: "MethodSettings", tsType: "any[]", required: false, constraints: {} },
138
+ { name: "BinaryMediaTypes", tsType: "string[]", required: false, constraints: {} },
139
+ { name: "MinimumCompressionSize", tsType: "number", required: false, constraints: {} },
140
+ { name: "OpenApiVersion", tsType: "string", required: false, constraints: {} },
141
+ { name: "GatewayResponses", tsType: "Record<string, any>", required: false, constraints: {} },
142
+ ],
143
+ attributes: [{ name: "RootResourceId", tsType: "string" }],
144
+ createOnly: [],
145
+ writeOnly: [],
146
+ primaryIdentifier: [],
147
+ },
148
+ propertyTypes: [
149
+ {
150
+ name: "Api_Auth",
151
+ cfnType: "Auth",
152
+ properties: [
153
+ { name: "DefaultAuthorizer", tsType: "string", required: false, constraints: {} },
154
+ { name: "Authorizers", tsType: "Record<string, any>", required: false, constraints: {} },
155
+ { name: "ApiKeyRequired", tsType: "boolean", required: false, constraints: {} },
156
+ { name: "UsagePlan", tsType: "any", required: false, constraints: {} },
157
+ ],
158
+ },
159
+ {
160
+ name: "Api_CorsConfiguration",
161
+ cfnType: "CorsConfiguration",
162
+ properties: [
163
+ { name: "AllowOrigin", tsType: "string", required: true, constraints: {} },
164
+ { name: "AllowHeaders", tsType: "string", required: false, constraints: {} },
165
+ { name: "AllowMethods", tsType: "string", required: false, constraints: {} },
166
+ { name: "AllowCredentials", tsType: "boolean", required: false, constraints: {} },
167
+ { name: "MaxAge", tsType: "number", required: false, constraints: {} },
168
+ ],
169
+ },
170
+ ],
171
+ enums: [],
172
+ };
173
+ }
174
+
175
+ function samHttpApi(): SchemaParseResult {
176
+ return {
177
+ resource: {
178
+ typeName: "AWS::Serverless::HttpApi",
179
+ properties: [
180
+ { name: "StageName", tsType: "string", required: false, constraints: {} },
181
+ { name: "DefinitionBody", tsType: "any", required: false, constraints: {} },
182
+ { name: "DefinitionUri", tsType: "any", required: false, constraints: {} },
183
+ { name: "Name", tsType: "string", required: false, constraints: {} },
184
+ { name: "CorsConfiguration", tsType: "any", required: false, constraints: {} },
185
+ { name: "Auth", tsType: "any", required: false, constraints: {} },
186
+ { name: "AccessLogSettings", tsType: "any", required: false, constraints: {} },
187
+ { name: "DefaultRouteSettings", tsType: "any", required: false, constraints: {} },
188
+ { name: "RouteSettings", tsType: "Record<string, any>", required: false, constraints: {} },
189
+ { name: "StageVariables", tsType: "Record<string, any>", required: false, constraints: {} },
190
+ { name: "Tags", tsType: "Record<string, any>", required: false, constraints: {} },
191
+ { name: "FailOnWarnings", tsType: "boolean", required: false, constraints: {} },
192
+ { name: "DisableExecuteApiEndpoint", tsType: "boolean", required: false, constraints: {} },
193
+ ],
194
+ attributes: [{ name: "ApiEndpoint", tsType: "string" }],
195
+ createOnly: [],
196
+ writeOnly: [],
197
+ primaryIdentifier: [],
198
+ },
199
+ propertyTypes: [
200
+ {
201
+ name: "HttpApi_CorsConfiguration",
202
+ cfnType: "CorsConfiguration",
203
+ properties: [
204
+ { name: "AllowOrigins", tsType: "string[]", required: false, constraints: {} },
205
+ { name: "AllowHeaders", tsType: "string[]", required: false, constraints: {} },
206
+ { name: "AllowMethods", tsType: "string[]", required: false, constraints: {} },
207
+ { name: "AllowCredentials", tsType: "boolean", required: false, constraints: {} },
208
+ { name: "ExposeHeaders", tsType: "string[]", required: false, constraints: {} },
209
+ { name: "MaxAge", tsType: "number", required: false, constraints: {} },
210
+ ],
211
+ },
212
+ ],
213
+ enums: [],
214
+ };
215
+ }
216
+
217
+ function samSimpleTable(): SchemaParseResult {
218
+ return {
219
+ resource: {
220
+ typeName: "AWS::Serverless::SimpleTable",
221
+ properties: [
222
+ { name: "PrimaryKey", tsType: "SimpleTable_PrimaryKey", required: false, constraints: {} },
223
+ { name: "ProvisionedThroughput", tsType: "any", required: false, constraints: {} },
224
+ { name: "TableName", tsType: "string", required: false, constraints: {} },
225
+ { name: "Tags", tsType: "Record<string, any>", required: false, constraints: {} },
226
+ { name: "SSESpecification", tsType: "any", required: false, constraints: {} },
227
+ ],
228
+ attributes: [],
229
+ createOnly: [],
230
+ writeOnly: [],
231
+ primaryIdentifier: [],
232
+ },
233
+ propertyTypes: [
234
+ {
235
+ name: "SimpleTable_PrimaryKey",
236
+ cfnType: "PrimaryKey",
237
+ properties: [
238
+ { name: "Name", tsType: "string", required: true, constraints: {} },
239
+ { name: "Type", tsType: "string", required: true, constraints: {} },
240
+ ],
241
+ },
242
+ ],
243
+ enums: [],
244
+ };
245
+ }
246
+
247
+ function samLayerVersion(): SchemaParseResult {
248
+ return {
249
+ resource: {
250
+ typeName: "AWS::Serverless::LayerVersion",
251
+ properties: [
252
+ { name: "ContentUri", tsType: "any", required: true, constraints: {} },
253
+ { name: "LayerName", tsType: "string", required: false, constraints: {} },
254
+ { name: "Description", tsType: "string", required: false, constraints: {} },
255
+ { name: "CompatibleRuntimes", tsType: "string[]", required: false, constraints: {} },
256
+ { name: "CompatibleArchitectures", tsType: "string[]", required: false, constraints: {} },
257
+ { name: "LicenseInfo", tsType: "string", required: false, constraints: {} },
258
+ { name: "RetentionPolicy", tsType: "string", required: false, constraints: {} },
259
+ ],
260
+ attributes: [
261
+ { name: "Arn", tsType: "string" },
262
+ { name: "LayerArn", tsType: "string" },
263
+ ],
264
+ createOnly: [],
265
+ writeOnly: [],
266
+ primaryIdentifier: [],
267
+ },
268
+ propertyTypes: [],
269
+ enums: [],
270
+ };
271
+ }
272
+
273
+ function samStateMachine(): SchemaParseResult {
274
+ return {
275
+ resource: {
276
+ typeName: "AWS::Serverless::StateMachine",
277
+ properties: [
278
+ { name: "Definition", tsType: "any", required: false, constraints: {} },
279
+ { name: "DefinitionUri", tsType: "any", required: false, constraints: {} },
280
+ { name: "DefinitionSubstitutions", tsType: "Record<string, any>", required: false, constraints: {} },
281
+ { name: "Name", tsType: "string", required: false, constraints: {} },
282
+ { name: "Role", tsType: "string", required: false, constraints: {} },
283
+ { name: "Policies", tsType: "any[]", required: false, constraints: {} },
284
+ { name: "Type", tsType: "string", required: false, constraints: {} },
285
+ { name: "Logging", tsType: "any", required: false, constraints: {} },
286
+ { name: "Tracing", tsType: "any", required: false, constraints: {} },
287
+ { name: "Events", tsType: "Record<string, any>", required: false, constraints: {} },
288
+ { name: "Tags", tsType: "Record<string, any>", required: false, constraints: {} },
289
+ { name: "PermissionsBoundary", tsType: "string", required: false, constraints: {} },
290
+ ],
291
+ attributes: [
292
+ { name: "Arn", tsType: "string" },
293
+ { name: "Name", tsType: "string" },
294
+ ],
295
+ createOnly: [],
296
+ writeOnly: [],
297
+ primaryIdentifier: [],
298
+ },
299
+ propertyTypes: [
300
+ {
301
+ name: "StateMachine_S3Location",
302
+ cfnType: "S3Location",
303
+ properties: [
304
+ { name: "Bucket", tsType: "string", required: true, constraints: {} },
305
+ { name: "Key", tsType: "string", required: true, constraints: {} },
306
+ { name: "Version", tsType: "string", required: false, constraints: {} },
307
+ ],
308
+ },
309
+ ],
310
+ enums: [],
311
+ };
312
+ }
313
+
314
+ function samApplication(): SchemaParseResult {
315
+ return {
316
+ resource: {
317
+ typeName: "AWS::Serverless::Application",
318
+ properties: [
319
+ { name: "Location", tsType: "any", required: true, constraints: {} },
320
+ { name: "Parameters", tsType: "Record<string, any>", required: false, constraints: {} },
321
+ { name: "NotificationArns", tsType: "string[]", required: false, constraints: {} },
322
+ { name: "Tags", tsType: "Record<string, any>", required: false, constraints: {} },
323
+ { name: "TimeoutInMinutes", tsType: "number", required: false, constraints: {} },
324
+ ],
325
+ attributes: [{ name: "Outputs", tsType: "any" }],
326
+ createOnly: [],
327
+ writeOnly: [],
328
+ primaryIdentifier: [],
329
+ },
330
+ propertyTypes: [],
331
+ enums: [],
332
+ };
333
+ }
334
+
335
+ function samConnector(): SchemaParseResult {
336
+ return {
337
+ resource: {
338
+ typeName: "AWS::Serverless::Connector",
339
+ properties: [
340
+ { name: "Source", tsType: "any", required: true, constraints: {} },
341
+ { name: "Destination", tsType: "any", required: true, constraints: {} },
342
+ { name: "Permissions", tsType: "string[]", required: true, constraints: {} },
343
+ ],
344
+ attributes: [],
345
+ createOnly: [],
346
+ writeOnly: [],
347
+ primaryIdentifier: [],
348
+ },
349
+ propertyTypes: [],
350
+ enums: [],
351
+ };
352
+ }
353
+
354
+ function samGraphQLApi(): SchemaParseResult {
355
+ return {
356
+ resource: {
357
+ typeName: "AWS::Serverless::GraphQLApi",
358
+ properties: [
359
+ { name: "SchemaUri", tsType: "string", required: false, constraints: {} },
360
+ { name: "SchemaInline", tsType: "string", required: false, constraints: {} },
361
+ { name: "Name", tsType: "string", required: false, constraints: {} },
362
+ { name: "Auth", tsType: "any", required: true, constraints: {} },
363
+ { name: "DataSources", tsType: "any", required: false, constraints: {} },
364
+ { name: "Functions", tsType: "any", required: false, constraints: {} },
365
+ { name: "Resolvers", tsType: "any", required: false, constraints: {} },
366
+ { name: "Logging", tsType: "any", required: false, constraints: {} },
367
+ { name: "XrayEnabled", tsType: "boolean", required: false, constraints: {} },
368
+ { name: "Tags", tsType: "Record<string, any>", required: false, constraints: {} },
369
+ { name: "Cache", tsType: "any", required: false, constraints: {} },
370
+ { name: "DomainName", tsType: "any", required: false, constraints: {} },
371
+ ],
372
+ attributes: [
373
+ { name: "ApiId", tsType: "string" },
374
+ { name: "Arn", tsType: "string" },
375
+ { name: "GraphQLUrl", tsType: "string" },
376
+ { name: "GraphQLDns", tsType: "string" },
377
+ { name: "RealtimeUrl", tsType: "string" },
378
+ { name: "RealtimeDns", tsType: "string" },
379
+ ],
380
+ createOnly: [],
381
+ writeOnly: [],
382
+ primaryIdentifier: [],
383
+ },
384
+ propertyTypes: [],
385
+ enums: [],
386
+ };
387
+ }
@@ -0,0 +1,84 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { generate } from "./generate";
3
+ import { loadSchemaFixtures } from "../testdata/load-fixtures";
4
+
5
+ describe("snapshot tests", () => {
6
+ // Generate once for all snapshot tests
7
+ let result: Awaited<ReturnType<typeof generate>>;
8
+
9
+ test("generate from fixtures", async () => {
10
+ const fixtures = loadSchemaFixtures();
11
+ result = await generate({ schemaSource: fixtures });
12
+ expect(result.resources).toBeGreaterThanOrEqual(5);
13
+ });
14
+
15
+ test("Bucket lexicon entry", () => {
16
+ const lexicon = JSON.parse(result.lexiconJSON);
17
+ const bucket = lexicon["Bucket"];
18
+ expect(bucket).toBeDefined();
19
+ expect(bucket).toMatchSnapshot();
20
+ });
21
+
22
+ test("Function lexicon entry", () => {
23
+ const lexicon = JSON.parse(result.lexiconJSON);
24
+ const fn = lexicon["Function"];
25
+ expect(fn).toBeDefined();
26
+ expect(fn).toMatchSnapshot();
27
+ });
28
+
29
+ test("Role lexicon entry", () => {
30
+ const lexicon = JSON.parse(result.lexiconJSON);
31
+ const role = lexicon["Role"];
32
+ expect(role).toBeDefined();
33
+ expect(role).toMatchSnapshot();
34
+ });
35
+
36
+ test("generated resource names", () => {
37
+ const lexicon = JSON.parse(result.lexiconJSON);
38
+ const names = Object.keys(lexicon).sort();
39
+ expect(names).toMatchSnapshot();
40
+ });
41
+
42
+ test("Bucket .d.ts class declaration", () => {
43
+ // Extract the Bucket class from typesDTS
44
+ const lines = result.typesDTS.split("\n");
45
+ const bucketStart = lines.findIndex((l) => /class\s+Bucket\b/.test(l));
46
+ expect(bucketStart).toBeGreaterThan(-1);
47
+
48
+ // Find closing brace (simple: next line with just "}")
49
+ let bucketEnd = bucketStart + 1;
50
+ let braceDepth = 1;
51
+ while (bucketEnd < lines.length && braceDepth > 0) {
52
+ const line = lines[bucketEnd];
53
+ for (const ch of line) {
54
+ if (ch === "{") braceDepth++;
55
+ if (ch === "}") braceDepth--;
56
+ }
57
+ bucketEnd++;
58
+ }
59
+
60
+ const bucketClass = lines.slice(bucketStart, bucketEnd).join("\n");
61
+ expect(bucketClass).toMatchSnapshot();
62
+ });
63
+
64
+ test("Function .d.ts class declaration", () => {
65
+ const lines = result.typesDTS.split("\n");
66
+ // Look for "class Function" or "class LambdaFunction" — naming may vary
67
+ const fnStart = lines.findIndex((l) => /class\s+(Function|LambdaFunction)\b/.test(l));
68
+ expect(fnStart).toBeGreaterThan(-1);
69
+
70
+ let fnEnd = fnStart + 1;
71
+ let braceDepth = 1;
72
+ while (fnEnd < lines.length && braceDepth > 0) {
73
+ const line = lines[fnEnd];
74
+ for (const ch of line) {
75
+ if (ch === "{") braceDepth++;
76
+ if (ch === "}") braceDepth--;
77
+ }
78
+ fnEnd++;
79
+ }
80
+
81
+ const fnClass = lines.slice(fnStart, fnEnd).join("\n");
82
+ expect(fnClass).toMatchSnapshot();
83
+ });
84
+ });