@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
package/src/plugin.ts ADDED
@@ -0,0 +1,514 @@
1
+ import type { LexiconPlugin, IntrinsicDef, SkillDefinition } from "@intentius/chant/lexicon";
2
+ import type { LintRule } from "@intentius/chant/lint/rule";
3
+ import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
4
+ import type { TemplateParser } from "@intentius/chant/import/parser";
5
+ import type { TypeScriptGenerator } from "@intentius/chant/import/generator";
6
+ import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
7
+ import type { McpToolContribution, McpResourceContribution } from "@intentius/chant/mcp/types";
8
+ import { awsSerializer } from "./serializer";
9
+
10
+ /**
11
+ * AWS CloudFormation lexicon plugin.
12
+ *
13
+ * Provides serializer, lint rules, template detection,
14
+ * import parsing, and code generation for AWS CloudFormation.
15
+ */
16
+ export const awsPlugin: LexiconPlugin = {
17
+ name: "aws",
18
+ serializer: awsSerializer,
19
+
20
+ lintRules(): LintRule[] {
21
+ // Lazy-load to avoid pulling in rules unless needed
22
+ const { hardcodedRegionRule } = require("./lint/rules/hardcoded-region");
23
+ const { s3EncryptionRule } = require("./lint/rules/s3-encryption");
24
+ const { iamWildcardRule } = require("./lint/rules/iam-wildcard");
25
+ return [hardcodedRegionRule, s3EncryptionRule, iamWildcardRule];
26
+ },
27
+
28
+ intrinsics(): IntrinsicDef[] {
29
+ return [
30
+ { name: "Sub", description: "Fn::Sub template string interpolation" },
31
+ { name: "Ref", description: "Reference a parameter or resource" },
32
+ { name: "GetAtt", description: "Fn::GetAtt — get resource attribute" },
33
+ { name: "If", description: "Fn::If — conditional value" },
34
+ { name: "Join", description: "Fn::Join — join values with delimiter" },
35
+ { name: "Select", description: "Fn::Select — select value by index" },
36
+ { name: "Split", description: "Fn::Split — split string by delimiter" },
37
+ { name: "Base64", description: "Fn::Base64 — encode to Base64" },
38
+ ];
39
+ },
40
+
41
+ pseudoParameters(): string[] {
42
+ return [
43
+ "AWS::StackName",
44
+ "AWS::Region",
45
+ "AWS::AccountId",
46
+ "AWS::StackId",
47
+ "AWS::URLSuffix",
48
+ "AWS::NoValue",
49
+ "AWS::NotificationARNs",
50
+ "AWS::Partition",
51
+ ];
52
+ },
53
+
54
+ initTemplates(): Record<string, string> {
55
+ return {
56
+ "_.ts": `export * from "./config";\n`,
57
+ "config.ts": `/**
58
+ * Shared bucket configuration — encryption, versioning, public access
59
+ */
60
+
61
+ import * as aws from "@intentius/chant-lexicon-aws";
62
+
63
+ // Encryption default — AES256 server-side encryption
64
+ export const encryptionDefault = new aws.ServerSideEncryptionByDefault({
65
+ sseAlgorithm: "AES256",
66
+ });
67
+
68
+ // Encryption rule wrapping the default
69
+ export const encryptionRule = new aws.ServerSideEncryptionRule({
70
+ serverSideEncryptionByDefault: encryptionDefault,
71
+ });
72
+
73
+ // Bucket encryption configuration
74
+ export const bucketEncryption = new aws.BucketEncryption({
75
+ serverSideEncryptionConfiguration: [encryptionRule],
76
+ });
77
+
78
+ // Public access block — deny all public access
79
+ export const publicAccessBlock = new aws.PublicAccessBlockConfiguration({
80
+ blockPublicAcls: true,
81
+ blockPublicPolicy: true,
82
+ ignorePublicAcls: true,
83
+ restrictPublicBuckets: true,
84
+ });
85
+
86
+ // Versioning — enabled
87
+ export const versioningEnabled = new aws.VersioningConfiguration({
88
+ status: "Enabled",
89
+ });
90
+ `,
91
+ "data-bucket.ts": `/**
92
+ * Data bucket — primary storage with encryption and versioning
93
+ */
94
+
95
+ import * as aws from "@intentius/chant-lexicon-aws";
96
+ import * as _ from "./_";
97
+
98
+ export const dataBucket = new aws.Bucket({
99
+ bucketName: aws.Sub\`\${aws.AWS.StackName}-data\`,
100
+ versioningConfiguration: _.versioningEnabled,
101
+ bucketEncryption: _.bucketEncryption,
102
+ publicAccessBlockConfiguration: _.publicAccessBlock,
103
+ });
104
+ `,
105
+ "logs-bucket.ts": `/**
106
+ * Logs bucket — log delivery with encryption and versioning
107
+ */
108
+
109
+ import * as aws from "@intentius/chant-lexicon-aws";
110
+ import * as _ from "./_";
111
+
112
+ export const logsBucket = new aws.Bucket({
113
+ bucketName: aws.Sub\`\${aws.AWS.StackName}-logs\`,
114
+ accessControl: "LogDeliveryWrite",
115
+ versioningConfiguration: _.versioningEnabled,
116
+ bucketEncryption: _.bucketEncryption,
117
+ publicAccessBlockConfiguration: _.publicAccessBlock,
118
+ });
119
+ `,
120
+ };
121
+ },
122
+
123
+ detectTemplate(data: unknown): boolean {
124
+ if (typeof data !== "object" || data === null) return false;
125
+ const obj = data as Record<string, unknown>;
126
+
127
+ // CloudFormation has AWSTemplateFormatVersion
128
+ if (obj.AWSTemplateFormatVersion !== undefined) return true;
129
+
130
+ // Or Resources with AWS::* types
131
+ if (typeof obj.Resources === "object" && obj.Resources !== null) {
132
+ for (const resource of Object.values(obj.Resources as Record<string, unknown>)) {
133
+ if (typeof resource === "object" && resource !== null) {
134
+ const type = (resource as Record<string, unknown>).Type;
135
+ if (typeof type === "string" && type.startsWith("AWS::")) {
136
+ return true;
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ return false;
143
+ },
144
+
145
+ templateParser(): TemplateParser {
146
+ const { CFParser } = require("./import/parser");
147
+ return new CFParser();
148
+ },
149
+
150
+ templateGenerator(): TypeScriptGenerator {
151
+ const { CFGenerator } = require("./import/generator");
152
+ return new CFGenerator();
153
+ },
154
+
155
+ postSynthChecks(): PostSynthCheck[] {
156
+ // Lazy-load to avoid pulling in checks unless needed
157
+ const { waw010 } = require("./lint/post-synth/waw010");
158
+ const { waw011 } = require("./lint/post-synth/waw011");
159
+ const { cor020 } = require("./lint/post-synth/cor020");
160
+ const { ext001 } = require("./lint/post-synth/ext001");
161
+ const { waw013 } = require("./lint/post-synth/waw013");
162
+ const { waw014 } = require("./lint/post-synth/waw014");
163
+ const { waw015 } = require("./lint/post-synth/waw015");
164
+ return [waw010, waw011, cor020, ext001, waw013, waw014, waw015];
165
+ },
166
+
167
+ async generate(options?: { verbose?: boolean }): Promise<void> {
168
+ const { generate, writeGeneratedFiles } = await import("./codegen/generate");
169
+ const { dirname } = await import("path");
170
+ const { fileURLToPath } = await import("url");
171
+
172
+ const result = await generate({ verbose: options?.verbose ?? true });
173
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
174
+ writeGeneratedFiles(result, pkgDir);
175
+
176
+ console.error(`Generated ${result.resources} resources, ${result.properties} property types, ${result.enums} enums`);
177
+ if (result.warnings.length > 0) {
178
+ console.error(`${result.warnings.length} warnings`);
179
+ }
180
+
181
+ const { PINNED_VERSIONS } = await import("./codegen/versions");
182
+ console.error(`cfn-lint patches: ${PINNED_VERSIONS.cfnLint}`);
183
+ },
184
+
185
+ async validate(options?: { verbose?: boolean }): Promise<void> {
186
+ const { validate } = await import("./validate");
187
+ const result = await validate();
188
+
189
+ for (const check of result.checks) {
190
+ const status = check.ok ? "OK" : "FAIL";
191
+ const msg = check.error ? ` — ${check.error}` : "";
192
+ console.error(` [${status}] ${check.name}${msg}`);
193
+ }
194
+
195
+ if (!result.success) {
196
+ throw new Error("Validation failed");
197
+ }
198
+ console.error("All validation checks passed.");
199
+ },
200
+
201
+ async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
202
+ const { readFileSync } = await import("fs");
203
+ const { join, dirname } = await import("path");
204
+ const { fileURLToPath } = await import("url");
205
+ const { computeCoverage, checkThresholds, formatSummary, formatVerbose } = await import("./coverage");
206
+
207
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
208
+ const lexiconPath = join(pkgDir, "src", "generated", "lexicon-aws.json");
209
+ const content = readFileSync(lexiconPath, "utf-8");
210
+ const report = computeCoverage(content);
211
+
212
+ if (options?.verbose) {
213
+ console.log(formatVerbose(report));
214
+ } else {
215
+ console.log(formatSummary(report));
216
+ }
217
+
218
+ if (typeof options?.minOverall === "number") {
219
+ const result = checkThresholds(report, { minOverallPct: options.minOverall });
220
+ if (!result.ok) {
221
+ for (const v of result.violations) console.error(` FAIL: ${v}`);
222
+ throw new Error("Coverage below threshold");
223
+ }
224
+ }
225
+ },
226
+
227
+ async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
228
+ const { packageLexicon } = await import("./codegen/package");
229
+ const { writeFileSync, mkdirSync } = await import("fs");
230
+ const { join, dirname } = await import("path");
231
+ const { fileURLToPath } = await import("url");
232
+
233
+ const { spec, stats } = await packageLexicon({ verbose: options?.verbose, force: options?.force });
234
+
235
+ // Write manifest and artifacts to dist/
236
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
237
+ const distDir = join(pkgDir, "dist");
238
+ mkdirSync(join(distDir, "types"), { recursive: true });
239
+ mkdirSync(join(distDir, "rules"), { recursive: true });
240
+ mkdirSync(join(distDir, "skills"), { recursive: true });
241
+
242
+ writeFileSync(join(distDir, "manifest.json"), JSON.stringify(spec.manifest, null, 2));
243
+ writeFileSync(join(distDir, "meta.json"), spec.registry);
244
+ writeFileSync(join(distDir, "types", "index.d.ts"), spec.typesDTS);
245
+
246
+ for (const [name, content] of spec.rules) {
247
+ writeFileSync(join(distDir, "rules", name), content);
248
+ }
249
+ for (const [name, content] of spec.skills) {
250
+ writeFileSync(join(distDir, "skills", name), content);
251
+ }
252
+
253
+ // Write integrity.json if available
254
+ if (spec.integrity) {
255
+ writeFileSync(join(distDir, "integrity.json"), JSON.stringify(spec.integrity, null, 2));
256
+ }
257
+
258
+ console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
259
+
260
+ // Produce .tgz via bun pm pack
261
+ const packProc = Bun.spawn(["bun", "pm", "pack"], {
262
+ cwd: pkgDir,
263
+ stdout: "pipe",
264
+ stderr: "pipe",
265
+ });
266
+ const packOut = await new Response(packProc.stdout).text();
267
+ const packErr = await new Response(packProc.stderr).text();
268
+ const packExit = await packProc.exited;
269
+ if (packExit === 0) {
270
+ console.error(`Tarball: ${packOut.trim()}`);
271
+ } else {
272
+ console.error(`bun pm pack failed: ${packErr}`);
273
+ }
274
+ },
275
+
276
+ async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
277
+ const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
278
+ const { join, dirname } = await import("path");
279
+ const { fileURLToPath } = await import("url");
280
+
281
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
282
+ const snapshotsDir = join(pkgDir, ".snapshots");
283
+
284
+ if (options?.restore) {
285
+ const generatedDir = join(pkgDir, "src", "generated");
286
+ restoreSnapshot(String(options.restore), generatedDir);
287
+ console.error(`Restored snapshot: ${options.restore}`);
288
+ } else {
289
+ const snapshots = listSnapshots(snapshotsDir);
290
+ if (snapshots.length === 0) {
291
+ console.error("No snapshots available.");
292
+ } else {
293
+ console.error(`Available snapshots (${snapshots.length}):`);
294
+ for (const s of snapshots) {
295
+ console.error(` ${s.timestamp} ${s.resourceCount} resources ${s.path}`);
296
+ }
297
+ }
298
+ }
299
+ },
300
+
301
+ async docs(options?: { verbose?: boolean }): Promise<void> {
302
+ const { generateDocs } = await import("./codegen/docs");
303
+ await generateDocs(options);
304
+ },
305
+
306
+ skills(): SkillDefinition[] {
307
+ return [
308
+ {
309
+ name: "aws-cloudformation",
310
+ description: "AWS CloudFormation best practices and common patterns",
311
+ content: `---
312
+ name: aws-cloudformation
313
+ description: AWS CloudFormation best practices and common patterns
314
+ ---
315
+
316
+ # AWS CloudFormation with Chant
317
+
318
+ ## Common Resource Types
319
+
320
+ - \`AWS::S3::Bucket\` — Object storage
321
+ - \`AWS::Lambda::Function\` — Serverless compute
322
+ - \`AWS::DynamoDB::Table\` — NoSQL database
323
+ - \`AWS::IAM::Role\` — Identity and access management
324
+ - \`AWS::SNS::Topic\` — Pub/sub messaging
325
+ - \`AWS::SQS::Queue\` — Message queue
326
+ - \`AWS::EC2::SecurityGroup\` — Network firewall rules
327
+
328
+ ## Intrinsic Functions
329
+
330
+ - \`Sub\` — String interpolation with \`\${}\` syntax
331
+ - \`Ref\` — Reference a resource or parameter
332
+ - \`GetAtt\` — Get a resource attribute (e.g. ARN)
333
+ - \`If\` — Conditional value based on a condition
334
+ - \`Join\` — Join strings with a delimiter
335
+ - \`Select\` — Pick an item from a list by index
336
+
337
+ ## Pseudo Parameters
338
+
339
+ - \`AWS::StackName\` — Name of the current stack
340
+ - \`AWS::Region\` — Current deployment region
341
+ - \`AWS::AccountId\` — Current AWS account ID
342
+ - \`AWS::Partition\` — Partition (aws, aws-cn, aws-us-gov)
343
+
344
+ ## Best Practices
345
+
346
+ 1. **Always enable encryption** — Use \`BucketEncryption\` for S3, \`SSESpecification\` for DynamoDB
347
+ 2. **Block public access** — Set \`PublicAccessBlockConfiguration\` on all S3 buckets
348
+ 3. **Use least-privilege IAM** — Avoid \`*\` in IAM policy actions and resources
349
+ 4. **Enable versioning** — Turn on \`VersioningConfiguration\` for data buckets
350
+ 5. **Use Sub for dynamic names** — \`Sub\\\`\\\${AWS::StackName}-suffix\\\`\` for unique naming
351
+ 6. **Share config via barrel files** — Put common settings in \`_.ts\` and import as \`* as _\`
352
+ `,
353
+ triggers: [
354
+ { type: "file-pattern", value: "**/*.aws.ts" },
355
+ { type: "context", value: "aws" },
356
+ ],
357
+ parameters: [
358
+ {
359
+ name: "resourceType",
360
+ description: "AWS CloudFormation resource type (e.g. AWS::S3::Bucket)",
361
+ type: "string",
362
+ required: false,
363
+ },
364
+ ],
365
+ examples: [
366
+ {
367
+ title: "S3 Bucket with encryption",
368
+ description: "Create an S3 bucket with server-side encryption enabled",
369
+ input: "Create an encrypted S3 bucket",
370
+ output: `new Bucket("MyBucket", {
371
+ BucketEncryption: {
372
+ ServerSideEncryptionConfiguration: [
373
+ { ServerSideEncryptionByDefault: { SSEAlgorithm: "aws:kms" } }
374
+ ]
375
+ },
376
+ PublicAccessBlockConfiguration: {
377
+ BlockPublicAcls: true,
378
+ BlockPublicPolicy: true,
379
+ IgnorePublicAcls: true,
380
+ RestrictPublicBuckets: true,
381
+ },
382
+ })`,
383
+ },
384
+ ],
385
+ },
386
+ ];
387
+ },
388
+
389
+ completionProvider(ctx: CompletionContext): CompletionItem[] {
390
+ const { awsCompletions } = require("./lsp/completions");
391
+ return awsCompletions(ctx);
392
+ },
393
+
394
+ hoverProvider(ctx: HoverContext): HoverInfo | undefined {
395
+ const { awsHover } = require("./lsp/hover");
396
+ return awsHover(ctx);
397
+ },
398
+
399
+ mcpTools(): McpToolContribution[] {
400
+ return [
401
+ {
402
+ name: "diff",
403
+ description: "Compare current build output against previous output for AWS CloudFormation",
404
+ inputSchema: {
405
+ type: "object",
406
+ properties: {
407
+ path: {
408
+ type: "string",
409
+ description: "Path to the infrastructure project directory",
410
+ },
411
+ output: {
412
+ type: "string",
413
+ description: "Path to the existing output file to compare against",
414
+ },
415
+ },
416
+ required: ["path"],
417
+ },
418
+ async handler(params: Record<string, unknown>): Promise<unknown> {
419
+ const { diffCommand } = await import("@intentius/chant/cli/commands/diff");
420
+ const result = await diffCommand({
421
+ path: (params.path as string) ?? ".",
422
+ output: params.output as string | undefined,
423
+ serializers: [awsSerializer],
424
+ });
425
+ return result;
426
+ },
427
+ },
428
+ ];
429
+ },
430
+
431
+ mcpResources(): McpResourceContribution[] {
432
+ return [
433
+ {
434
+ uri: "resource-catalog",
435
+ name: "AWS Resource Catalog",
436
+ description: "JSON list of all supported AWS CloudFormation resource types",
437
+ mimeType: "application/json",
438
+ async handler(): Promise<string> {
439
+ const lexicon = require("./generated/lexicon-aws.json") as Record<string, { resourceType: string; kind: string }>;
440
+ const resources = Object.entries(lexicon)
441
+ .filter(([, entry]) => entry.kind === "resource")
442
+ .map(([className, entry]) => ({
443
+ className,
444
+ resourceType: entry.resourceType,
445
+ }));
446
+ return JSON.stringify(resources);
447
+ },
448
+ },
449
+ {
450
+ uri: "examples/aws-s3-bucket",
451
+ name: "AWS S3 Bucket Example",
452
+ description: "AWS S3 bucket with versioning and encryption",
453
+ mimeType: "text/typescript",
454
+ async handler(): Promise<string> {
455
+ return `import * as aws from "@intentius/chant-lexicon-aws";
456
+
457
+ // Encryption configuration
458
+ export const encryptionDefault = new aws.ServerSideEncryptionByDefault({
459
+ sseAlgorithm: "AES256",
460
+ });
461
+
462
+ export const encryptionRule = new aws.ServerSideEncryptionRule({
463
+ serverSideEncryptionByDefault: encryptionDefault,
464
+ });
465
+
466
+ export const bucketEncryption = new aws.BucketEncryption({
467
+ serverSideEncryptionConfiguration: [encryptionRule],
468
+ });
469
+
470
+ // Versioning
471
+ export const versioningEnabled = new aws.VersioningConfiguration({
472
+ status: "Enabled",
473
+ });
474
+
475
+ // Create a versioned bucket with encryption
476
+ export const dataBucket = new aws.Bucket({
477
+ bucketName: aws.Sub\`\${aws.AWS.StackName}-data\`,
478
+ versioningConfiguration: versioningEnabled,
479
+ bucketEncryption: bucketEncryption,
480
+ });
481
+ `;
482
+ },
483
+ },
484
+ {
485
+ uri: "examples/cross-references",
486
+ name: "Cross References Example",
487
+ description: "Using AttrRefs for cross-resource references",
488
+ mimeType: "text/typescript",
489
+ async handler(): Promise<string> {
490
+ return `import * as aws from "@intentius/chant-lexicon-aws";
491
+
492
+ // Create a bucket
493
+ export const dataBucket = new aws.Bucket({
494
+ bucketName: "my-data-bucket",
495
+ versioningConfiguration: new aws.VersioningConfiguration({ status: "Enabled" }),
496
+ });
497
+
498
+ // Create a role that references the bucket's ARN
499
+ export const role = new aws.Role({
500
+ assumeRolePolicyDocument: {
501
+ Version: "2012-10-17",
502
+ Statement: [{
503
+ Effect: "Allow",
504
+ Principal: { Service: "lambda.amazonaws.com" },
505
+ Action: "sts:AssumeRole",
506
+ }],
507
+ },
508
+ });
509
+ `;
510
+ },
511
+ },
512
+ ];
513
+ },
514
+ };
@@ -0,0 +1,55 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import {
3
+ AWS,
4
+ StackName,
5
+ Region,
6
+ AccountId,
7
+ StackId,
8
+ URLSuffix,
9
+ NoValue,
10
+ NotificationARNs,
11
+ Partition,
12
+ } from "./pseudo";
13
+
14
+ describe("Pseudo-parameters", () => {
15
+ test.each([
16
+ { param: StackName, name: "StackName" },
17
+ { param: Region, name: "Region" },
18
+ { param: AccountId, name: "AccountId" },
19
+ { param: StackId, name: "StackId" },
20
+ { param: URLSuffix, name: "URLSuffix" },
21
+ { param: NoValue, name: "NoValue" },
22
+ { param: NotificationARNs, name: "NotificationARNs" },
23
+ { param: Partition, name: "Partition" },
24
+ ])("$name serializes correctly", ({ param, name }) => {
25
+ expect(param.toJSON()).toEqual({ Ref: `AWS::${name}` });
26
+ });
27
+ });
28
+
29
+ describe("AWS namespace", () => {
30
+ test("contains all pseudo-parameters", () => {
31
+ expect(AWS.StackName).toBe(StackName);
32
+ expect(AWS.Region).toBe(Region);
33
+ expect(AWS.AccountId).toBe(AccountId);
34
+ expect(AWS.StackId).toBe(StackId);
35
+ expect(AWS.URLSuffix).toBe(URLSuffix);
36
+ expect(AWS.NoValue).toBe(NoValue);
37
+ expect(AWS.NotificationARNs).toBe(NotificationARNs);
38
+ expect(AWS.Partition).toBe(Partition);
39
+ });
40
+
41
+ test("pseudo-parameters are accessible via AWS namespace", () => {
42
+ expect(AWS.StackName.toJSON()).toEqual({ Ref: "AWS::StackName" });
43
+ expect(AWS.Region.toJSON()).toEqual({ Ref: "AWS::Region" });
44
+ });
45
+ });
46
+
47
+ describe("toString", () => {
48
+ test("StackName toString for Sub templates", () => {
49
+ expect(StackName.toString()).toBe("${AWS::StackName}");
50
+ });
51
+
52
+ test("Region toString for Sub templates", () => {
53
+ expect(Region.toString()).toBe("${AWS::Region}");
54
+ });
55
+ });
package/src/pseudo.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { PseudoParameter, createPseudoParameters } from "@intentius/chant/pseudo-parameter";
2
+
3
+ export { PseudoParameter };
4
+
5
+ export const { StackName, Region, AccountId, StackId, URLSuffix, NoValue, NotificationARNs, Partition } =
6
+ createPseudoParameters({
7
+ StackName: "AWS::StackName",
8
+ Region: "AWS::Region",
9
+ AccountId: "AWS::AccountId",
10
+ StackId: "AWS::StackId",
11
+ URLSuffix: "AWS::URLSuffix",
12
+ NoValue: "AWS::NoValue",
13
+ NotificationARNs: "AWS::NotificationARNs",
14
+ Partition: "AWS::Partition",
15
+ });
16
+
17
+ /**
18
+ * AWS namespace containing all pseudo-parameters
19
+ */
20
+ export const AWS = {
21
+ StackName,
22
+ Region,
23
+ AccountId,
24
+ StackId,
25
+ URLSuffix,
26
+ NoValue,
27
+ NotificationARNs,
28
+ Partition,
29
+ } as const;