@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,316 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { awsPlugin } from "./plugin";
3
+ import { isLexiconPlugin } from "@intentius/chant/lexicon";
4
+
5
+ describe("awsPlugin", () => {
6
+ // -----------------------------------------------------------------------
7
+ // Basic interface
8
+ // -----------------------------------------------------------------------
9
+
10
+ test("satisfies isLexiconPlugin type guard", () => {
11
+ expect(isLexiconPlugin(awsPlugin)).toBe(true);
12
+ });
13
+
14
+ test("has correct name and serializer", () => {
15
+ expect(awsPlugin.name).toBe("aws");
16
+ expect(awsPlugin.serializer.name).toBe("aws");
17
+ expect(awsPlugin.serializer.rulePrefix).toBe("WAW");
18
+ });
19
+
20
+ // -----------------------------------------------------------------------
21
+ // Lint rules
22
+ // -----------------------------------------------------------------------
23
+
24
+ test("returns lint rules", () => {
25
+ const rules = awsPlugin.lintRules!();
26
+ expect(rules).toHaveLength(3);
27
+ const ids = rules.map((r) => r.id);
28
+ expect(ids).toContain("WAW001");
29
+ expect(ids).toContain("WAW006");
30
+ expect(ids).toContain("WAW009");
31
+ });
32
+
33
+ // -----------------------------------------------------------------------
34
+ // Intrinsics / pseudo-parameters
35
+ // -----------------------------------------------------------------------
36
+
37
+ test("returns intrinsics", () => {
38
+ const intrinsics = awsPlugin.intrinsics!();
39
+ expect(intrinsics.length).toBe(8);
40
+ const names = intrinsics.map((i) => i.name);
41
+ expect(names).toContain("Sub");
42
+ expect(names).toContain("Ref");
43
+ expect(names).toContain("GetAtt");
44
+ });
45
+
46
+ test("returns pseudo-parameters", () => {
47
+ const params = awsPlugin.pseudoParameters!();
48
+ expect(params).toContain("AWS::StackName");
49
+ expect(params).toContain("AWS::Region");
50
+ expect(params).toContain("AWS::AccountId");
51
+ });
52
+
53
+ // -----------------------------------------------------------------------
54
+ // Template detection
55
+ // -----------------------------------------------------------------------
56
+
57
+ test("detects CloudFormation templates", () => {
58
+ expect(
59
+ awsPlugin.detectTemplate!({ AWSTemplateFormatVersion: "2010-09-09" }),
60
+ ).toBe(true);
61
+
62
+ expect(
63
+ awsPlugin.detectTemplate!({
64
+ Resources: { MyBucket: { Type: "AWS::S3::Bucket" } },
65
+ }),
66
+ ).toBe(true);
67
+
68
+ expect(awsPlugin.detectTemplate!({})).toBe(false);
69
+ expect(awsPlugin.detectTemplate!(null)).toBe(false);
70
+ expect(awsPlugin.detectTemplate!("string")).toBe(false);
71
+ });
72
+
73
+ // -----------------------------------------------------------------------
74
+ // Template parsing / generation
75
+ // -----------------------------------------------------------------------
76
+
77
+ test("returns a template parser", () => {
78
+ const parser = awsPlugin.templateParser!();
79
+ expect(parser).toBeDefined();
80
+ expect(typeof parser.parse).toBe("function");
81
+ });
82
+
83
+ test("returns a template generator", () => {
84
+ const generator = awsPlugin.templateGenerator!();
85
+ expect(generator).toBeDefined();
86
+ expect(typeof generator.generate).toBe("function");
87
+ });
88
+
89
+ // -----------------------------------------------------------------------
90
+ // Skills
91
+ // -----------------------------------------------------------------------
92
+
93
+ describe("skills", () => {
94
+ test("returns at least one skill", () => {
95
+ const skills = awsPlugin.skills!();
96
+ expect(skills.length).toBeGreaterThanOrEqual(1);
97
+ });
98
+
99
+ test("aws-cloudformation skill has required fields", () => {
100
+ const skills = awsPlugin.skills!();
101
+ const cfnSkill = skills.find((s) => s.name === "aws-cloudformation");
102
+ expect(cfnSkill).toBeDefined();
103
+ expect(cfnSkill!.description.length).toBeGreaterThan(0);
104
+ expect(cfnSkill!.content.length).toBeGreaterThan(0);
105
+ });
106
+
107
+ test("aws-cloudformation skill has triggers", () => {
108
+ const skills = awsPlugin.skills!();
109
+ const cfnSkill = skills.find((s) => s.name === "aws-cloudformation")!;
110
+ expect(cfnSkill.triggers).toBeDefined();
111
+ expect(cfnSkill.triggers!.length).toBeGreaterThanOrEqual(1);
112
+
113
+ const filePatternTrigger = cfnSkill.triggers!.find((t) => t.type === "file-pattern");
114
+ expect(filePatternTrigger).toBeDefined();
115
+ expect(filePatternTrigger!.value).toContain("*.aws.ts");
116
+
117
+ const contextTrigger = cfnSkill.triggers!.find((t) => t.type === "context");
118
+ expect(contextTrigger).toBeDefined();
119
+ expect(contextTrigger!.value).toBe("aws");
120
+ });
121
+
122
+ test("aws-cloudformation skill has parameters", () => {
123
+ const skills = awsPlugin.skills!();
124
+ const cfnSkill = skills.find((s) => s.name === "aws-cloudformation")!;
125
+ expect(cfnSkill.parameters).toBeDefined();
126
+ expect(cfnSkill.parameters!.length).toBeGreaterThanOrEqual(1);
127
+
128
+ const resourceTypeParam = cfnSkill.parameters!.find((p) => p.name === "resourceType");
129
+ expect(resourceTypeParam).toBeDefined();
130
+ expect(resourceTypeParam!.description.length).toBeGreaterThan(0);
131
+ expect(resourceTypeParam!.type).toBe("string");
132
+ });
133
+
134
+ test("aws-cloudformation skill has examples", () => {
135
+ const skills = awsPlugin.skills!();
136
+ const cfnSkill = skills.find((s) => s.name === "aws-cloudformation")!;
137
+ expect(cfnSkill.examples).toBeDefined();
138
+ expect(cfnSkill.examples!.length).toBeGreaterThanOrEqual(1);
139
+
140
+ const s3Example = cfnSkill.examples![0];
141
+ expect(s3Example.title.length).toBeGreaterThan(0);
142
+ expect(s3Example.output).toBeDefined();
143
+ expect(s3Example.output!).toContain("Bucket");
144
+ });
145
+
146
+ test("skill content is valid markdown with frontmatter", () => {
147
+ const skills = awsPlugin.skills!();
148
+ const cfnSkill = skills.find((s) => s.name === "aws-cloudformation")!;
149
+ expect(cfnSkill.content).toContain("---");
150
+ expect(cfnSkill.content).toContain("# AWS CloudFormation with Chant");
151
+ expect(cfnSkill.content).toContain("## Common Resource Types");
152
+ expect(cfnSkill.content).toContain("## Best Practices");
153
+ });
154
+ });
155
+
156
+ // -----------------------------------------------------------------------
157
+ // Required lifecycle methods
158
+ // -----------------------------------------------------------------------
159
+
160
+ describe("lifecycle methods", () => {
161
+ test("generate is a function", () => {
162
+ expect(typeof awsPlugin.generate).toBe("function");
163
+ });
164
+
165
+ test("validate is a function", () => {
166
+ expect(typeof awsPlugin.validate).toBe("function");
167
+ });
168
+
169
+ test("coverage is a function", () => {
170
+ expect(typeof awsPlugin.coverage).toBe("function");
171
+ });
172
+
173
+ test("package is a function", () => {
174
+ expect(typeof awsPlugin.package).toBe("function");
175
+ });
176
+
177
+ test("rollback is a function", () => {
178
+ expect(typeof awsPlugin.rollback).toBe("function");
179
+ });
180
+ });
181
+
182
+ // -----------------------------------------------------------------------
183
+ // MCP tools
184
+ // -----------------------------------------------------------------------
185
+
186
+ describe("mcpTools", () => {
187
+ test("returns at least one tool", () => {
188
+ const tools = awsPlugin.mcpTools!();
189
+ expect(tools.length).toBeGreaterThanOrEqual(1);
190
+ });
191
+
192
+ test("diff tool has correct structure", () => {
193
+ const tools = awsPlugin.mcpTools!();
194
+ const diffTool = tools.find((t) => t.name === "diff");
195
+ expect(diffTool).toBeDefined();
196
+ expect(diffTool!.description.length).toBeGreaterThan(0);
197
+ expect(diffTool!.inputSchema.type).toBe("object");
198
+ expect(diffTool!.inputSchema.properties.path).toBeDefined();
199
+ expect(typeof diffTool!.handler).toBe("function");
200
+ });
201
+
202
+ test("diff tool schema has path as required", () => {
203
+ const tools = awsPlugin.mcpTools!();
204
+ const diffTool = tools.find((t) => t.name === "diff")!;
205
+ expect(diffTool.inputSchema.required).toContain("path");
206
+ });
207
+ });
208
+
209
+ // -----------------------------------------------------------------------
210
+ // MCP resources
211
+ // -----------------------------------------------------------------------
212
+
213
+ describe("mcpResources", () => {
214
+ test("returns at least one resource", () => {
215
+ const resources = awsPlugin.mcpResources!();
216
+ expect(resources.length).toBeGreaterThanOrEqual(1);
217
+ });
218
+
219
+ test("resource-catalog has correct structure", () => {
220
+ const resources = awsPlugin.mcpResources!();
221
+ const catalog = resources.find((r) => r.uri === "resource-catalog");
222
+ expect(catalog).toBeDefined();
223
+ expect(catalog!.name.length).toBeGreaterThan(0);
224
+ expect(catalog!.description.length).toBeGreaterThan(0);
225
+ expect(catalog!.mimeType).toBe("application/json");
226
+ expect(typeof catalog!.handler).toBe("function");
227
+ });
228
+
229
+ test("resource-catalog handler returns JSON array of resources", async () => {
230
+ const resources = awsPlugin.mcpResources!();
231
+ const catalog = resources.find((r) => r.uri === "resource-catalog")!;
232
+ const content = await catalog.handler();
233
+
234
+ const parsed = JSON.parse(content) as Array<{ className: string; resourceType: string }>;
235
+ expect(Array.isArray(parsed)).toBe(true);
236
+ expect(parsed.length).toBeGreaterThan(100);
237
+
238
+ // Spot-check known resources
239
+ const bucket = parsed.find((r) => r.className === "Bucket");
240
+ expect(bucket).toBeDefined();
241
+ expect(bucket!.resourceType).toBe("AWS::S3::Bucket");
242
+
243
+ const table = parsed.find((r) => r.className === "Table");
244
+ expect(table).toBeDefined();
245
+ expect(table!.resourceType).toBe("AWS::DynamoDB::Table");
246
+ });
247
+ });
248
+
249
+ // -----------------------------------------------------------------------
250
+ // LSP completionProvider
251
+ // -----------------------------------------------------------------------
252
+
253
+ describe("completionProvider", () => {
254
+ test("is defined", () => {
255
+ expect(awsPlugin.completionProvider).toBeDefined();
256
+ });
257
+
258
+ test("returns resource completions for new prefix", () => {
259
+ const items = awsPlugin.completionProvider!({
260
+ uri: "file:///a.ts",
261
+ content: "const b = new Bucket",
262
+ position: { line: 0, character: 20 },
263
+ wordAtCursor: "Bucket",
264
+ linePrefix: "const b = new Bucket",
265
+ });
266
+ expect(items.length).toBeGreaterThan(0);
267
+ const bucket = items.find((i) => i.label === "Bucket");
268
+ expect(bucket).toBeDefined();
269
+ expect(bucket!.kind).toBe("resource");
270
+ });
271
+
272
+ test("returns empty for non-constructor context", () => {
273
+ const items = awsPlugin.completionProvider!({
274
+ uri: "file:///a.ts",
275
+ content: "const x = 42",
276
+ position: { line: 0, character: 13 },
277
+ wordAtCursor: "42",
278
+ linePrefix: "const x = 42",
279
+ });
280
+ expect(items).toHaveLength(0);
281
+ });
282
+ });
283
+
284
+ // -----------------------------------------------------------------------
285
+ // LSP hoverProvider
286
+ // -----------------------------------------------------------------------
287
+
288
+ describe("hoverProvider", () => {
289
+ test("is defined", () => {
290
+ expect(awsPlugin.hoverProvider).toBeDefined();
291
+ });
292
+
293
+ test("returns hover info for known resource", () => {
294
+ const info = awsPlugin.hoverProvider!({
295
+ uri: "file:///a.ts",
296
+ content: "new Bucket()",
297
+ position: { line: 0, character: 5 },
298
+ word: "Bucket",
299
+ lineText: "new Bucket()",
300
+ });
301
+ expect(info).toBeDefined();
302
+ expect(info!.contents).toContain("AWS::S3::Bucket");
303
+ });
304
+
305
+ test("returns undefined for unknown word", () => {
306
+ const info = awsPlugin.hoverProvider!({
307
+ uri: "file:///a.ts",
308
+ content: "xyz",
309
+ position: { line: 0, character: 1 },
310
+ word: "NotAResource12345",
311
+ lineText: "xyz",
312
+ });
313
+ expect(info).toBeUndefined();
314
+ });
315
+ });
316
+ });