@intentius/chant-lexicon-aws 0.0.8 → 0.0.10

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 (115) hide show
  1. package/dist/integrity.json +25 -10
  2. package/dist/manifest.json +1 -1
  3. package/dist/meta.json +5743 -896
  4. package/dist/rules/cf-refs.ts +99 -0
  5. package/dist/rules/ext001.ts +30 -21
  6. package/dist/rules/hardcoded-region.ts +1 -0
  7. package/dist/rules/iam-wildcard.ts +1 -0
  8. package/dist/rules/s3-encryption.ts +1 -0
  9. package/dist/rules/waw016.ts +86 -0
  10. package/dist/rules/waw017.ts +53 -0
  11. package/dist/rules/waw018.ts +71 -0
  12. package/dist/rules/waw019.ts +82 -0
  13. package/dist/rules/waw020.ts +64 -0
  14. package/dist/rules/waw021.ts +53 -0
  15. package/dist/rules/waw022.ts +43 -0
  16. package/dist/rules/waw023.ts +47 -0
  17. package/dist/rules/waw024.ts +54 -0
  18. package/dist/rules/waw025.ts +43 -0
  19. package/dist/rules/waw026.ts +46 -0
  20. package/dist/rules/waw027.ts +50 -0
  21. package/dist/rules/waw028.ts +47 -0
  22. package/dist/rules/waw029.ts +62 -0
  23. package/dist/rules/waw030.ts +246 -0
  24. package/dist/skills/chant-aws.md +388 -30
  25. package/dist/types/index.d.ts +1552 -1528
  26. package/package.json +2 -2
  27. package/src/actions/actions.test.ts +75 -0
  28. package/src/actions/dynamodb.ts +36 -0
  29. package/src/actions/ecr.ts +9 -0
  30. package/src/actions/ecs.ts +5 -0
  31. package/src/actions/iam.ts +3 -0
  32. package/src/actions/index.ts +9 -0
  33. package/src/actions/lambda.ts +11 -0
  34. package/src/actions/logs.ts +4 -0
  35. package/src/actions/s3.ts +34 -0
  36. package/src/actions/sns.ts +5 -0
  37. package/src/actions/sqs.ts +15 -0
  38. package/src/codegen/__snapshots__/snapshot.test.ts.snap +2 -2
  39. package/src/codegen/docs-links.test.ts +143 -0
  40. package/src/codegen/docs.ts +247 -132
  41. package/src/codegen/generate-lexicon.ts +8 -0
  42. package/src/codegen/generate-typescript.ts +25 -1
  43. package/src/composites/composites.test.ts +442 -0
  44. package/src/composites/fargate-alb.ts +253 -0
  45. package/src/composites/index.ts +20 -0
  46. package/src/composites/lambda-api.ts +20 -0
  47. package/src/composites/lambda-dynamodb.ts +64 -0
  48. package/src/composites/lambda-eventbridge.ts +36 -0
  49. package/src/composites/lambda-function.ts +76 -0
  50. package/src/composites/lambda-s3.ts +72 -0
  51. package/src/composites/lambda-sns.ts +30 -0
  52. package/src/composites/lambda-sqs.ts +44 -0
  53. package/src/composites/scheduled-lambda.ts +37 -0
  54. package/src/composites/vpc-default.ts +148 -0
  55. package/src/default-tags.test.ts +38 -0
  56. package/src/default-tags.ts +77 -0
  57. package/src/generated/index.d.ts +1552 -1528
  58. package/src/generated/lexicon-aws.json +5743 -896
  59. package/src/import/roundtrip-fixtures.test.ts +1 -1
  60. package/src/index.ts +21 -0
  61. package/src/integration.test.ts +71 -0
  62. package/src/intrinsics.ts +24 -13
  63. package/src/lint/post-synth/cf-refs.ts +99 -0
  64. package/src/lint/post-synth/ext001.test.ts +214 -31
  65. package/src/lint/post-synth/ext001.ts +30 -21
  66. package/src/lint/post-synth/waw013.test.ts +120 -0
  67. package/src/lint/post-synth/waw014.test.ts +121 -0
  68. package/src/lint/post-synth/waw015.test.ts +147 -0
  69. package/src/lint/post-synth/waw016.test.ts +141 -0
  70. package/src/lint/post-synth/waw016.ts +86 -0
  71. package/src/lint/post-synth/waw017.test.ts +130 -0
  72. package/src/lint/post-synth/waw017.ts +53 -0
  73. package/src/lint/post-synth/waw018.test.ts +109 -0
  74. package/src/lint/post-synth/waw018.ts +71 -0
  75. package/src/lint/post-synth/waw019.test.ts +138 -0
  76. package/src/lint/post-synth/waw019.ts +82 -0
  77. package/src/lint/post-synth/waw020.test.ts +125 -0
  78. package/src/lint/post-synth/waw020.ts +64 -0
  79. package/src/lint/post-synth/waw021.test.ts +81 -0
  80. package/src/lint/post-synth/waw021.ts +53 -0
  81. package/src/lint/post-synth/waw022.test.ts +54 -0
  82. package/src/lint/post-synth/waw022.ts +43 -0
  83. package/src/lint/post-synth/waw023.test.ts +53 -0
  84. package/src/lint/post-synth/waw023.ts +47 -0
  85. package/src/lint/post-synth/waw024.test.ts +64 -0
  86. package/src/lint/post-synth/waw024.ts +54 -0
  87. package/src/lint/post-synth/waw025.test.ts +42 -0
  88. package/src/lint/post-synth/waw025.ts +43 -0
  89. package/src/lint/post-synth/waw026.test.ts +54 -0
  90. package/src/lint/post-synth/waw026.ts +46 -0
  91. package/src/lint/post-synth/waw027.test.ts +63 -0
  92. package/src/lint/post-synth/waw027.ts +50 -0
  93. package/src/lint/post-synth/waw028.test.ts +68 -0
  94. package/src/lint/post-synth/waw028.ts +47 -0
  95. package/src/lint/post-synth/waw029.test.ts +179 -0
  96. package/src/lint/post-synth/waw029.ts +62 -0
  97. package/src/lint/post-synth/waw030.test.ts +800 -0
  98. package/src/lint/post-synth/waw030.ts +246 -0
  99. package/src/lint/rules/hardcoded-region.ts +1 -0
  100. package/src/lint/rules/iam-wildcard.ts +1 -0
  101. package/src/lint/rules/s3-encryption.ts +1 -0
  102. package/src/lsp/hover.ts +15 -0
  103. package/src/nested-stack-integration.test.ts +100 -0
  104. package/src/nested-stack.ts +1 -1
  105. package/src/plugin.ts +468 -36
  106. package/src/serializer.test.ts +330 -2
  107. package/src/serializer.ts +62 -1
  108. package/src/spec/fetch.ts +10 -0
  109. package/src/spec/parse.test.ts +141 -0
  110. package/src/spec/parse.ts +40 -0
  111. package/src/taggable.ts +44 -0
  112. package/src/testdata/nested-stacks/app.ts +26 -0
  113. package/src/testdata/nested-stacks/network/outputs.ts +17 -0
  114. package/src/testdata/nested-stacks/network/security.ts +17 -0
  115. package/src/testdata/nested-stacks/network/vpc.ts +54 -0
@@ -70,28 +70,6 @@ rain deploy dist/template.json my-stack
70
70
  sam deploy --template-file dist/template.json --stack-name my-stack
71
71
  \`\`\`
72
72
 
73
- ## Multi-file output (nested stacks)
74
-
75
- When your project uses [nested stacks](./nested-stacks), \`chant build\` produces multiple template files:
76
-
77
- \`\`\`bash
78
- chant build -o template.json
79
- # Produces:
80
- # template.json — parent template
81
- # network.template.json — child template (one per nestedStack)
82
- \`\`\`
83
-
84
- The parent template includes a \`TemplateBasePath\` parameter that controls where CloudFormation looks for child templates. Override it at deploy time to point to an S3 bucket:
85
-
86
- \`\`\`bash
87
- aws cloudformation deploy \\
88
- --template-file template.json \\
89
- --stack-name my-stack \\
90
- --parameter-overrides TemplateBasePath=https://my-bucket.s3.amazonaws.com/templates
91
- \`\`\`
92
-
93
- All child template files must be uploaded alongside the parent template (or to the S3 path specified by \`TemplateBasePath\`).
94
-
95
73
  ## Compatibility
96
74
 
97
75
  The output is compatible with:
@@ -138,21 +116,9 @@ export async function generateDocs(options?: { verbose?: boolean }): Promise<voi
138
116
  - Resolves \`AttrRef\` references to \`Fn::GetAtt\`
139
117
  - Resolves resource references to \`Ref\` intrinsics
140
118
 
141
- {{file:getting-started/src/data-bucket.ts}}
142
-
143
- Produces this CloudFormation resource:
144
-
145
- \`\`\`json
146
- "DataBucket": {
147
- "Type": "AWS::S3::Bucket",
148
- "Properties": {
149
- "BucketName": { "Fn::Sub": "\${AWS::StackName}-data" },
150
- "VersioningConfiguration": { "Status": "Enabled" }
151
- }
152
- }
153
- \`\`\`
119
+ {{file:lambda-s3/src/main.ts}}
154
120
 
155
- Notice how \`dataBucket\` becomes \`DataBucket\` (PascalCase logical ID). Property names like \`BucketName\` use the CloudFormation spec-native PascalCase directly.
121
+ The \`LambdaS3\` composite expands to 3 CloudFormation resources: an S3 Bucket, an IAM Role (with S3 read policy), and a Lambda Function. Property names like \`BucketName\` use the CloudFormation spec-native PascalCase directly, and the export name \`app\` becomes the resource name prefix (e.g. \`appBucket\`, \`appRole\`, \`appFunc\`).
156
122
 
157
123
  ## Resource types and naming
158
124
 
@@ -173,26 +139,25 @@ Common resources get fixed short names for stability. When two services define t
173
139
 
174
140
  ## Imports and cross-file references
175
141
 
176
- Chant projects use standard TypeScript imports. Lexicon types come from the lexicon package, and sibling exports are imported directly from the file that defines them:
142
+ Chant projects use standard TypeScript imports. Lexicon types come from the lexicon package, and cross-file references are standard imports:
177
143
 
178
- {{file:getting-started/src/data-bucket.ts}}
144
+ {{file:lambda-api/src/health-api.ts}}
179
145
 
180
- When you reference a resource or attribute from another file (e.g. \`dataBucket.arn\`), the serializer resolves it to \`Fn::GetAtt\` or \`Ref\` as appropriate. This is how cross-file references work — standard imports, no indirection.
146
+ When you reference a resource or attribute from another file (e.g. \`dataBucket.Arn\`), the serializer resolves it to \`Fn::GetAtt\` or \`Ref\` as appropriate. This is how cross-file references work — standard imports, no indirection.
181
147
 
182
148
  ## Parameters
183
149
 
184
150
  CloudFormation parameters let you customize a stack at deploy time. Export a \`Parameter\` to add it to the template's \`Parameters\` section:
185
151
 
186
- {{file:getting-started/src/environment.ts}}
152
+ {{file:docs-snippets/src/parameter-ref.ts}}
187
153
 
188
154
  Produces:
189
155
 
190
156
  \`\`\`json
191
157
  "Parameters": {
192
- "Environment": {
158
+ "Name": {
193
159
  "Type": "String",
194
- "Description": "Deployment environment",
195
- "Default": "dev"
160
+ "Description": "Project name used in resource naming"
196
161
  }
197
162
  }
198
163
  \`\`\`
@@ -236,18 +201,38 @@ Runtime context values available in every template, accessed via the \`AWS\` nam
236
201
 
237
202
  ## Intrinsic functions
238
203
 
239
- The lexicon provides 8 intrinsic functions (\`Sub\`, \`Ref\`, \`GetAtt\`, \`If\`, \`Join\`, \`Select\`, \`Split\`, \`Base64\`) that map directly to CloudFormation \`Fn::\` calls. See [Intrinsic Functions](./intrinsics) for full usage examples.
204
+ The lexicon provides 8 intrinsic functions (\`Sub\`, \`Ref\`, \`GetAtt\`, \`If\`, \`Join\`, \`Select\`, \`Split\`, \`Base64\`) that map directly to CloudFormation \`Fn::\` calls. See [Intrinsic Functions](../intrinsics/) for full usage examples.
240
205
 
241
206
  ## Dependencies
242
207
 
243
208
  CloudFormation automatically creates dependencies between resources when you use \`Ref\` or \`Fn::GetAtt\`. Chant leverages this — when you reference \`$.myBucket.arn\`, the serializer emits \`Fn::GetAtt\` and CloudFormation infers the dependency.
244
209
 
245
- For cases where you need an explicit dependency without a property reference, set \`dependsOn\`:
210
+ For cases where you need an explicit dependency without a property reference, pass \`DependsOn\` as a resource-level attribute (second constructor argument):
246
211
 
247
212
  {{file:docs-snippets/src/depends-on.ts}}
248
213
 
214
+ \`DependsOn\` values can be string logical names or references to other resource objects — Declarable references are resolved to their logical names automatically at build time.
215
+
249
216
  The \`WAW010\` post-synth check warns if a \`DependsOn\` target is already referenced via \`Ref\` or \`Fn::GetAtt\` in properties — in that case the explicit dependency is redundant.
250
217
 
218
+ ## Resource attributes
219
+
220
+ Every resource constructor accepts an optional second argument for CloudFormation resource-level attributes. These control lifecycle behavior, conditional creation, and metadata — they are distinct from resource *properties* (the first argument).
221
+
222
+ {{file:docs-snippets/src/resource-attributes.ts}}
223
+
224
+ | Attribute | Type | Description |
225
+ |-----------|------|-------------|
226
+ | \`DependsOn\` | \`Declarable \\| Declarable[] \\| string \\| string[]\` | Explicit ordering dependency. Accepts resource references or logical name strings. |
227
+ | \`Condition\` | \`string\` | Only create this resource when the named Condition evaluates to true. |
228
+ | \`DeletionPolicy\` | \`"Delete" \\| "Retain" \\| "RetainExceptOnCreate" \\| "Snapshot"\` | What happens when the resource is removed from the template or the stack is deleted. |
229
+ | \`UpdateReplacePolicy\` | \`"Delete" \\| "Retain" \\| "Snapshot"\` | What happens to the old resource when CloudFormation replaces it during an update. |
230
+ | \`UpdatePolicy\` | \`object\` | Controls how Auto Scaling Groups perform rolling updates (\`AutoScalingRollingUpdate\`, \`AutoScalingReplacingUpdate\`). |
231
+ | \`CreationPolicy\` | \`object\` | Wait for resource signals before marking creation complete (\`ResourceSignal\` with \`Count\` and \`Timeout\`). |
232
+ | \`Metadata\` | \`Record<string, unknown>\` | Arbitrary metadata. Commonly used for \`AWS::CloudFormation::Init\` (cfn-init bootstrapping). Intrinsic functions in metadata values are resolved at build time. |
233
+
234
+ All attributes are optional. When omitted, CloudFormation uses its defaults (e.g. \`DeletionPolicy: "Delete"\`).
235
+
251
236
  ## Policy documents
252
237
 
253
238
  IAM policy documents appear on many AWS resources — \`Role.assumeRolePolicyDocument\`, \`ManagedPolicy.policyDocument\`, \`BucketPolicy.policyDocument\`, and others. These properties are typed as \`PolicyDocument\`, giving you autocomplete for the IAM JSON Policy Language.
@@ -308,25 +293,21 @@ For deploy-time region lookups, combine \`AWS.Region\` with \`If\` or use \`Fn::
308
293
 
309
294
  ## Nested stacks
310
295
 
311
- CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. Use \`nestedStack()\` to reference a child project directory — a subdirectory that builds independently:
312
-
313
- {{file:nested-stacks/src/network/outputs.ts}}
296
+ :::caution
297
+ Nested stacks add deployment complexity and are not recommended for most projects. Prefer [flat composites](../composites/) instead.
298
+ :::
314
299
 
315
- {{file:nested-stacks/src/app.ts}}
316
-
317
- chant handles the wiring: child template gets an \`Outputs\` section, parent uses \`Fn::GetAtt\` on the stack resource. A \`TemplateBasePath\` parameter lets you configure child template URLs per environment.
318
-
319
- See [Nested Stacks](./nested-stacks) for the full guide.
300
+ CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) split resources into child templates. The lexicon supports them via \`nestedStack()\` for cases where you exceed the 500-resource limit or need to package reusable infrastructure as a black box. See the [Nested Stacks](../nested-stacks/) page for details.
320
301
 
321
302
  ## Tagging
322
303
 
323
- Tags are standard CloudFormation \`Key\`/\`Value\` arrays. Pass them on any resource that supports tagging:
304
+ Use \`defaultTags()\` to declare project-wide tags. The serializer automatically injects them into every taggable resource at synthesis time:
324
305
 
325
306
  {{file:docs-snippets/src/tagging.ts}}
326
307
 
327
- To apply tags across all members of a composite, use [\`propagate\`](./composites#propagate--shared-properties):
308
+ No other changes needed all taggable resources in the project get these tags automatically. Resources with explicit \`Tags\` keep them (explicit key wins over default). Non-taggable resources like \`AWS::Lambda::Permission\` are never tagged.
328
309
 
329
- {{file:docs-snippets/src/propagate.ts}}`,
310
+ Tag values support strings, \`Parameter\` references, and intrinsic functions (\`Sub\`, \`Ref\`, etc.).`,
330
311
  },
331
312
  {
332
313
  slug: "intrinsics",
@@ -393,16 +374,61 @@ Encodes a string to Base64, commonly used for EC2 user data:
393
374
  {
394
375
  slug: "composites",
395
376
  title: "Composites",
396
- description: "Composite resources, withDefaults presets, and propagate in the AWS CloudFormation lexicon",
377
+ description: "Composite resources, built-in composites, action constants, and withDefaults presets in the AWS CloudFormation lexicon",
397
378
  content: `Composites group related resources into reusable factories. See also the core [Composite Resources](/guide/composite-resources/) guide.
398
379
 
399
- {{file:advanced/src/lambda-api.ts}}
380
+ {{file:lambda-api/src/lambda-api.ts}}
400
381
 
401
382
  Instantiate and export:
402
383
 
403
- {{file:advanced/src/health-api.ts}}
384
+ {{file:lambda-api/src/health-api.ts}}
385
+
386
+ During build, composites expand to flat CloudFormation resources: \`healthApiRole\`, \`healthApiFunc\`, \`healthApiPermission\`.
387
+
388
+ ## Built-in composites
389
+
390
+ The AWS lexicon ships ready-to-use composites for common patterns. Import them from \`@intentius/chant-lexicon-aws\`:
391
+
392
+ {{file:docs-snippets/src/builtin-composites.ts}}
393
+
394
+ | Composite | Members | Description |
395
+ |-----------|---------|-------------|
396
+ | \`LambdaFunction\` | \`role\`, \`func\` | IAM Role + Lambda Function. Auto-attaches \`AWSLambdaBasicExecutionRole\`; adds \`AWSLambdaVPCAccessExecutionRole\` when \`VpcConfig\` is provided. |
397
+ | \`LambdaNode\` | \`role\`, \`func\` | \`LambdaFunction\` preset with \`Runtime: "nodejs20.x"\` and \`Handler: "index.handler"\` |
398
+ | \`LambdaPython\` | \`role\`, \`func\` | \`LambdaFunction\` preset with \`Runtime: "python3.12"\` and \`Handler: "handler.handler"\` |
399
+ | \`LambdaApi\` | \`role\`, \`func\`, \`permission\` | \`LambdaFunction\` + Lambda Permission for API Gateway invocation |
400
+ | \`LambdaScheduled\` | \`role\`, \`func\`, \`rule\`, \`permission\` | \`LambdaFunction\` + EventBridge Rule + Lambda Permission |
401
+ | \`LambdaSqs\` | \`queue\`, \`role\`, \`func\` | SQS Queue + Lambda + EventSourceMapping. Auto-attaches SQS receive policy. |
402
+ | \`LambdaEventBridge\` | \`rule\`, \`role\`, \`func\`, \`permission\` | EventBridge Rule + Lambda. Supports \`schedule\` and/or \`eventPattern\`. |
403
+ | \`LambdaDynamoDB\` | \`table\`, \`role\`, \`func\` | DynamoDB Table + Lambda. Auto-attaches DynamoDB policy and injects \`TABLE_NAME\` env var. |
404
+ | \`LambdaS3\` | \`bucket\`, \`role\`, \`func\` | S3 Bucket (encrypted, public access blocked) + Lambda. Auto-attaches S3 policy and injects \`BUCKET_NAME\` env var. |
405
+ | \`LambdaSns\` | \`topic\`, \`role\`, \`func\`, \`subscription\`, \`permission\` | SNS Topic + Lambda via Subscription. Auto-attaches invoke permission for SNS. |
406
+ | \`VpcDefault\` | \`vpc\`, \`igw\`, \`igwAttachment\`, \`publicSubnet1\`, \`publicSubnet2\`, \`privateSubnet1\`, \`privateSubnet2\`, \`publicRouteTable\`, \`publicRoute\`, \`publicRta1\`, \`publicRta2\`, \`privateRouteTable\`, \`privateRta1\`, \`privateRta2\`, \`natEip\`, \`natGateway\`, \`privateRoute\` | Production-ready VPC: 2 public + 2 private subnets across 2 AZs, internet gateway, single NAT gateway. |
407
+ | \`FargateAlb\` | \`cluster\`, \`executionRole\`, \`taskRole\`, \`logGroup\`, \`taskDef\`, \`albSg\`, \`taskSg\`, \`alb\`, \`targetGroup\`, \`listener\`, \`service\` | Fargate service behind an ALB. Accepts VPC outputs as props. |
408
+
409
+ All built-in composites accept \`ManagedPolicyArns\` and \`Policies\` for adding IAM permissions to the auto-created role.
404
410
 
405
- During build, composites expand to flat CloudFormation resources: \`healthApi_role\` → \`HealthApiRole\`, \`healthApi_func\` → \`HealthApiFunc\`, \`healthApi_permission\` → \`HealthApiPermission\`.
411
+ ## Action constants
412
+
413
+ Typed IAM action constants for common AWS services. Use them in policy documents instead of hand-typing action strings:
414
+
415
+ {{file:docs-snippets/src/action-constants.ts}}
416
+
417
+ Available constants:
418
+
419
+ | Constant | Key groups |
420
+ |----------|------------|
421
+ | \`S3Actions\` | \`ReadOnly\`, \`WriteOnly\`, \`ReadWrite\`, \`Full\`, \`GetObject\`, \`PutObject\`, \`DeleteObject\`, \`ListObjects\` |
422
+ | \`LambdaActions\` | \`Invoke\`, \`ReadOnly\`, \`Full\` |
423
+ | \`DynamoDBActions\` | \`ReadOnly\`, \`WriteOnly\`, \`ReadWrite\`, \`Full\`, \`GetItem\`, \`PutItem\`, \`Query\`, \`Scan\` |
424
+ | \`SQSActions\` | \`SendMessage\`, \`ReceiveMessage\`, \`Full\` |
425
+ | \`SNSActions\` | \`Publish\`, \`Subscribe\`, \`Full\` |
426
+ | \`IAMActions\` | \`PassRole\` |
427
+ | \`ECRActions\` | \`Pull\`, \`Full\` |
428
+ | \`LogsActions\` | \`Write\`, \`Full\` |
429
+ | \`ECSActions\` | \`RunTask\`, \`Service\`, \`Full\` |
430
+
431
+ Broad groups like \`ReadWrite\` are always supersets of their narrow counterparts (\`ReadOnly\` + \`WriteOnly\`). All values are \`as const\` arrays for full type safety.
406
432
 
407
433
  ## \`withDefaults\` — composite presets
408
434
 
@@ -412,6 +438,14 @@ Wrap a composite with pre-applied defaults. Defaulted props become optional:
412
438
 
413
439
  \`withDefaults\` preserves the original composite's identity — same \`_id\` and \`compositeName\`, no new registry entry.
414
440
 
441
+ ### Computed defaults
442
+
443
+ \`withDefaults\` also accepts a function that receives the caller's props and returns defaults. This enables conditional logic without generating extra resources:
444
+
445
+ {{file:docs-snippets/src/computed-defaults.ts}}
446
+
447
+ Merge order: computed defaults are applied first, then user-provided props override them.
448
+
415
449
  ## \`propagate\` — shared properties
416
450
 
417
451
  Attach properties that merge into every member during expansion:
@@ -425,18 +459,23 @@ Merge semantics:
425
459
 
426
460
  ## Nested stacks
427
461
 
428
- When resources should produce a separate CloudFormation template instead of expanding into the parent, use a **child project** — a subdirectory that builds independently to its own CloudFormation template. The parent references it with \`nestedStack()\`:
429
-
430
- {{file:nested-stacks/src/app.ts}}
462
+ :::caution
463
+ Nested stacks add deployment complexity and are not recommended for most projects. Prefer flat composites instead.
464
+ :::
431
465
 
432
- See [Nested Stacks](./nested-stacks) for the full guide.`,
466
+ When you need to split resources into a separate CloudFormation template, the lexicon supports nested stacks via \`nestedStack()\`. See the [Nested Stacks](../nested-stacks/) page for details.`,
433
467
  },
434
468
  {
435
469
  slug: "nested-stacks",
436
470
  title: "Nested Stacks",
471
+ sidebar: false,
437
472
  description: "Splitting resources into child CloudFormation templates with automatic cross-stack reference wiring",
438
473
  content: `CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. The AWS lexicon's \`nestedStack()\` function references a **child project directory** — a subdirectory that builds independently to a valid CloudFormation template.
439
474
 
475
+ :::caution[Consider alternatives first]
476
+ Nested stacks add deployment complexity: child templates must be uploaded to S3, rollbacks are all-or-nothing at the parent level, drift detection doesn't recurse into children, and debugging failures requires drilling into child stack events. For most projects, [flat composites](../composites/) are simpler. Nested stacks are supported for specific cases — exceeding CloudFormation's 500-resource limit or packaging reusable infrastructure as a black box — but are not the recommended default.
477
+ :::
478
+
440
479
  ## Project structure
441
480
 
442
481
  A nested stack is a child project — a subdirectory with its own resource files and explicit \`stackOutput()\` declarations:
@@ -454,7 +493,7 @@ src/
454
493
 
455
494
  Use \`stackOutput()\` to mark values that the parent can reference. Each \`stackOutput()\` becomes an entry in the child template's \`Outputs\` section:
456
495
 
457
- {{file:nested-stacks/src/network/outputs.ts}}
496
+ {{file:../src/testdata/nested-stacks/network/outputs.ts}}
458
497
 
459
498
  The child can be built independently:
460
499
 
@@ -467,7 +506,7 @@ chant build src/network/ -o network.json
467
506
 
468
507
  Use \`nestedStack()\` in the parent to reference a child project directory. It returns an object with an \`outputs\` proxy for cross-stack references:
469
508
 
470
- {{file:nested-stacks/src/app.ts}}
509
+ {{file:../src/testdata/nested-stacks/app.ts}}
471
510
 
472
511
  \`network.outputs.subnetId\` produces a \`NestedStackOutputRef\` that serializes to \`{ "Fn::GetAtt": ["Network", "Outputs.SubnetId"] }\`.
473
512
 
@@ -510,6 +549,8 @@ aws cloudformation deploy \\
510
549
 
511
550
  Child templates also receive the \`TemplateBasePath\` parameter so it propagates through all nesting levels.
512
551
 
552
+ All child template files must be uploaded alongside the parent template (or to the S3 path specified by \`TemplateBasePath\`).
553
+
513
554
  ## Explicit parameters
514
555
 
515
556
  Pass CloudFormation Parameters to child stacks with the \`parameters\` option:
@@ -552,17 +593,14 @@ Three lint rules help catch common nested stack issues:
552
593
 
553
594
  ## When to use nested stacks
554
595
 
555
- **Use nested stacks when:**
556
- - Your template exceeds CloudFormation's 500-resource limit
557
- - You want to reuse a group of resources across multiple parent stacks
558
- - You need independent update/rollback boundaries for parts of your infrastructure
596
+ **Prefer flat composites** for most projects. Composites expand into a single template, deploy atomically, and are simpler to debug.
559
597
 
560
- **Use flat composites when:**
561
- - Resources are tightly coupled and always deploy together
562
- - You don't need independent update boundaries
563
- - Your template is within resource limits
598
+ **Use nested stacks only when:**
599
+ - Your template exceeds CloudFormation's 500-resource limit
600
+ - You're packaging reusable infrastructure for other teams to deploy as a black box
601
+ - You need independent update/rollback boundaries (rare — this usually means the resources should be separate stacks entirely)
564
602
 
565
- See [Composites](./composites) for the flat composite approach, and [Examples](./examples#nested-stacks) for a runnable nested stack example.`,
603
+ See [Composites](../composites/) for the flat composite approach.`,
566
604
  },
567
605
  {
568
606
  slug: "lint-rules",
@@ -656,6 +694,59 @@ Flags \`nestedStack()\` references whose outputs are never used from the parent.
656
694
 
657
695
  Detects circular references between child projects (e.g. project A references project B which references project A). Circular project dependencies cause infinite build recursion.
658
696
 
697
+ ### WAW016 — Deprecated Property Usage
698
+
699
+ **Severity:** warning | **Category:** correctness
700
+
701
+ Flags properties marked as deprecated in the CloudFormation Registry. Data comes from two sources: the explicit \`deprecatedProperties\` array in the Registry schema, and description text mining (keywords like "deprecated", "legacy", "no longer recommended").
702
+
703
+ For example, \`AccessControl\` on \`AWS::S3::Bucket\` is a legacy property — use a bucket policy to grant access instead.
704
+
705
+ \`\`\`
706
+ WAW016: Resource "MyBucket" (AWS::S3::Bucket) uses deprecated property "AccessControl" — consider alternatives
707
+ \`\`\`
708
+
709
+ ### WAW017 — Missing Tags on Taggable Resource
710
+
711
+ **Severity:** info | **Category:** best practice
712
+
713
+ Flags resources that support tagging but have no \`Tags\` property set. Tags are important for cost allocation, compliance, and operational visibility. The check uses the \`tagging\` metadata from the CloudFormation Registry to determine which resources are taggable.
714
+
715
+ \`\`\`
716
+ WAW017: Resource "MyBucket" (AWS::S3::Bucket) supports tagging but has no Tags — consider adding tags for cost allocation and compliance
717
+ \`\`\`
718
+
719
+ ### WAW029 — Invalid DependsOn Target
720
+
721
+ **Severity:** error | **Category:** correctness
722
+
723
+ Flags \`DependsOn\` entries that reference a non-existent resource (typo or deleted resource) or that create a self-reference. Both cases cause CloudFormation deployments to fail immediately.
724
+
725
+ \`\`\`
726
+ WAW029: Resource "MyService" has DependsOn "MyBukcet" which does not exist in the template
727
+ WAW029: Resource "MyBucket" has a DependsOn on itself — self-references are invalid
728
+ \`\`\`
729
+
730
+ ### WAW030 — Missing DependsOn for Known Patterns
731
+
732
+ **Severity:** warning | **Category:** best practice
733
+
734
+ Flags resources that are likely missing a required explicit \`DependsOn\` based on well-known CloudFormation ordering requirements:
735
+
736
+ - **ECS Service + Listener**: An ECS Service with \`LoadBalancers\` should depend on the ALB Listener so the target group is fully configured before the service starts registering tasks.
737
+ - **EC2 Route + VPCGatewayAttachment**: A Route using a \`GatewayId\` should depend on the VPCGatewayAttachment so the gateway is attached to the VPC before the route is created.
738
+ - **API Gateway Deployment + Method**: A Deployment only references \`RestApiId\` — it needs an explicit \`DependsOn\` on its Methods or CloudFormation may create the deployment before any methods exist.
739
+ - **API Gateway V2 Deployment + Route**: Same as above for HTTP APIs — a V2 Deployment needs \`DependsOn\` on its Routes.
740
+ - **DynamoDB Table + ScalableTarget**: A ScalableTarget with \`ServiceNamespace: "dynamodb"\` references the table by string \`ResourceId\`, not \`Ref\` — it needs \`DependsOn\` so the table exists before scaling is registered.
741
+ - **ECS Service + ScalableTarget**: A ScalableTarget with \`ServiceNamespace: "ecs"\` references the service by string — it needs \`DependsOn\` so the ECS Service exists first.
742
+
743
+ \`\`\`
744
+ WAW030: ECS Service "MyService" has LoadBalancers but no DependsOn on a Listener
745
+ WAW030: Route "PublicRoute" uses a Gateway but has no dependency on VPCGatewayAttachment
746
+ WAW030: API Gateway Deployment "MyDeployment" has no DependsOn on any Method
747
+ WAW030: ScalableTarget "MyTarget" targets DynamoDB but has no DependsOn on any Table
748
+ \`\`\`
749
+
659
750
  ## Running lint
660
751
 
661
752
  \`\`\`bash
@@ -685,7 +776,7 @@ export default {
685
776
  };
686
777
  \`\`\`
687
778
 
688
- See also [Custom Lint Rules](./custom-rules) for writing project-specific rules.`,
779
+ See also [Custom Lint Rules](../custom-rules/) for writing project-specific rules.`,
689
780
  },
690
781
  {
691
782
  slug: "custom-rules",
@@ -695,9 +786,9 @@ See also [Custom Lint Rules](./custom-rules) for writing project-specific rules.
695
786
 
696
787
  ## Anatomy of a lint rule
697
788
 
698
- The advanced example includes a full custom rule implementation:
789
+ The lambda-api example includes a full custom rule implementation:
699
790
 
700
- {{file:advanced/src/lint/api-timeout.ts}}
791
+ {{file:lambda-api/src/lint/api-timeout.ts}}
701
792
 
702
793
  The \`check\` function receives a \`LintContext\` containing the TypeScript \`sourceFile\` and returns an array of diagnostics with file, line, column, and message.
703
794
 
@@ -705,49 +796,101 @@ The \`check\` function receives a \`LintContext\` containing the TypeScript \`so
705
796
 
706
797
  Add a \`chant.config.ts\` to your project:
707
798
 
708
- {{file:advanced/src/chant.config.ts}}
799
+ {{file:lambda-api/src/chant.config.ts}}
709
800
 
710
801
  The \`plugins\` array accepts relative paths. Each plugin module should export a \`LintRule\` object.`,
711
802
  },
712
803
  {
713
804
  slug: "examples",
714
805
  title: "Examples",
715
- description: "Walkthrough of the getting-started and advanced AWS CloudFormation examples",
716
- content: `Two runnable examples live in the lexicon's \`examples/\` directory. Clone the repo and try them:
806
+ description: "Walkthrough of the AWS CloudFormation lexicon examples",
807
+ content: `Runnable examples live in the lexicon's \`examples/\` directory — one per built-in composite. Clone the repo and try them:
717
808
 
718
809
  \`\`\`bash
719
- cd examples/getting-started
810
+ cd examples/lambda-function
720
811
  bun install
721
812
  chant build # produces CloudFormation JSON
722
813
  chant lint # runs lint rules
723
814
  bun test # runs the example's tests
724
815
  \`\`\`
725
816
 
726
- ## Getting Started
817
+ ## Lambda Function
727
818
 
728
- \`examples/getting-started/\` — 4 resources across separate files: two S3 buckets, an IAM role, and a Lambda function.
819
+ \`examples/lambda-function/\` — the simplest possible example. Uses \`LambdaNode\` to create a basic Lambda.
729
820
 
730
- \`\`\`
731
- src/
732
- ├── defaults.ts # Shared config: encryption, versioning, public access block
733
- ├── data-bucket.ts # S3 bucket using shared defaults
734
- ├── logs-bucket.ts # S3 bucket for access logs
735
- ├── role.ts # IAM role with Lambda assume-role policy
736
- └── handler.ts # Lambda function referencing role and bucket
737
- \`\`\`
821
+ {{file:lambda-function/src/main.ts}}
738
822
 
739
- **Patterns demonstrated:**
823
+ Produces 2 CloudFormation resources: IAM Role + Lambda Function.
824
+
825
+ ## Lambda S3
826
+
827
+ \`examples/lambda-s3/\` — Lambda that lists S3 objects using the \`LambdaS3\` composite.
828
+
829
+ {{file:lambda-s3/src/main.ts}}
830
+
831
+ Produces 3 resources: S3 Bucket (encrypted, public access blocked) + IAM Role (with S3 read policy) + Lambda Function. The \`BUCKET_NAME\` environment variable is auto-injected.
832
+
833
+ ## Lambda DynamoDB
834
+
835
+ \`examples/lambda-dynamodb/\` — Lambda that reads/writes DynamoDB items using the \`LambdaDynamoDB\` composite.
836
+
837
+ {{file:lambda-dynamodb/src/main.ts}}
838
+
839
+ Produces 3 resources: DynamoDB Table + IAM Role (with DynamoDB read/write policy) + Lambda Function. The \`TABLE_NAME\` environment variable is auto-injected.
840
+
841
+ ## Lambda SQS
842
+
843
+ \`examples/lambda-sqs/\` — Lambda processing messages from an SQS queue using the \`LambdaSqs\` composite.
740
844
 
741
- 1. **Direct imports** — lexicon types come from \`@intentius/chant-lexicon-aws\`, sibling exports are imported from the file that defines them
742
- 2. **Shared defaults** — \`defaults.ts\` exports reusable property objects (\`BucketEncryption\`, \`PublicAccessBlockConfiguration\`) that other files import directly
743
- 3. **Cross-resource references** — \`dataBucket.Arn\` in \`handler.ts\` serializes to \`Fn::GetAtt\` in the template
744
- 4. **Intrinsics** — \`Sub\` tagged templates with pseudo-parameters for dynamic naming
845
+ {{file:lambda-sqs/src/main.ts}}
745
846
 
746
- {{file:getting-started/src/handler.ts}}
847
+ Produces 4 resources: SQS Queue + IAM Role (with SQS receive policy) + Lambda Function + EventSourceMapping.
747
848
 
748
- ## Advanced
849
+ ## Lambda SNS
749
850
 
750
- \`examples/advanced/\` — builds on getting-started with composites, presets, inline IAM policies, and a custom lint rule.
851
+ \`examples/lambda-sns/\` — Lambda triggered by SNS notifications using the \`LambdaSns\` composite.
852
+
853
+ {{file:lambda-sns/src/main.ts}}
854
+
855
+ Produces 5 resources: SNS Topic + IAM Role + Lambda Function + SNS Subscription + Lambda Permission.
856
+
857
+ ## Lambda Scheduled
858
+
859
+ \`examples/lambda-scheduled/\` — Lambda on a cron schedule using the \`LambdaScheduled\` composite.
860
+
861
+ {{file:lambda-scheduled/src/main.ts}}
862
+
863
+ Produces 4 resources: IAM Role + Lambda Function + EventBridge Rule + Lambda Permission.
864
+
865
+ ## Lambda EventBridge
866
+
867
+ \`examples/lambda-eventbridge/\` — Lambda triggered by EventBridge events using the \`LambdaEventBridge\` composite.
868
+
869
+ {{file:lambda-eventbridge/src/main.ts}}
870
+
871
+ Produces 4 resources: EventBridge Rule + IAM Role + Lambda Function + Lambda Permission.
872
+
873
+ ## VPC
874
+
875
+ \`examples/vpc/\` — production-ready VPC using the \`VpcDefault\` composite.
876
+
877
+ {{file:vpc/src/main.ts}}
878
+
879
+ Produces 17 CloudFormation resources: VPC, Internet Gateway, 2 public + 2 private subnets, NAT Gateway with EIP, route tables, routes, and associations.
880
+
881
+ ## Fargate ALB
882
+
883
+ \`examples/fargate-alb/\` — Fargate service behind an ALB, consuming a VPC. Demonstrates composite composability.
884
+
885
+ {{file:fargate-alb/src/network.ts}}
886
+
887
+ {{file:fargate-alb/src/service.ts}}
888
+
889
+ Produces 28 CloudFormation resources: 17 from VpcDefault + 11 from FargateAlb (ECS Cluster, execution/task roles, log group, task definition, security groups, ALB, target group, listener, and ECS service).
890
+
891
+ ## Lambda API (Custom Composite)
892
+
893
+ \`examples/lambda-api/\` — demonstrates building your own composite factory with presets and a custom lint rule. This is the only example that teaches custom composite authoring.
751
894
 
752
895
  \`\`\`
753
896
  src/
@@ -762,41 +905,13 @@ src/
762
905
  └── api-timeout.ts # Custom WAW012 rule
763
906
  \`\`\`
764
907
 
765
- **What it adds:**
766
-
767
- - **Composites** — \`LambdaApi\` groups Role + Function + Permission into a reusable unit (see [Composites](./composites))
768
- - **Composite presets** — \`SecureApi\` (low memory, short timeout) and \`HighMemoryApi\` (high memory, longer timeout) created with \`withDefaults\`
769
- - **Inline IAM policies** — \`upload-api.ts\` and \`process-api.ts\` attach \`Role_Policy\` objects for scoped S3 access
770
- - **Custom lint rule** — \`api-timeout.ts\` enforces API Gateway's 29-second timeout limit (see [Custom Lint Rules](./custom-rules))
771
- - **Lint config** — \`chant.config.ts\` extends the strict preset and loads the custom plugin
772
-
773
- The example produces 10 CloudFormation resources: 1 S3 bucket + 3 composites × 3 members each.
774
-
775
- ## Nested Stacks
776
-
777
- \`examples/nested-stacks/\` — demonstrates child projects for splitting resources into child CloudFormation templates with automatic cross-stack reference wiring.
778
-
779
- \`\`\`
780
- src/
781
- ├── app.ts # Lambda function (references network outputs)
782
- └── network/ # Child project (nested stack)
783
- ├── vpc.ts # VPC, subnet, internet gateway, route table
784
- ├── security.ts # Security group for Lambda
785
- └── outputs.ts # stackOutput() declarations
786
- \`\`\`
787
-
788
908
  **Patterns demonstrated:**
789
909
 
790
- 1. **Child project** — \`network/\` is a separate project directory with its own resources and \`stackOutput()\` exports
791
- 2. **Cross-stack references** — \`app.ts\` accesses \`network.outputs.subnetId\` and \`network.outputs.lambdaSgId\`, which serialize to \`Fn::GetAtt\` on the parent's \`AWS::CloudFormation::Stack\` resource
792
- 3. **Multi-file output** — build produces \`template.json\` (parent) and \`network.template.json\` (child)
793
- 4. **TemplateBasePath** — auto-generated parameter for configuring child template URLs per environment
794
-
795
- {{file:nested-stacks/src/network/outputs.ts}}
796
-
797
- {{file:nested-stacks/src/app.ts}}
910
+ - **Custom composites** — \`LambdaApi\` groups Role + Function + Permission into a reusable unit (see [Composites](../composites/))
911
+ - **Composite presets** — \`SecureApi\` (low memory, short timeout) and \`HighMemoryApi\` (high memory, longer timeout)
912
+ - **Custom lint rule** — \`api-timeout.ts\` enforces API Gateway's 29-second timeout limit (see [Custom Lint Rules](../custom-rules/))
798
913
 
799
- See [Nested Stacks](./nested-stacks) for the full guide.`,
914
+ The example produces 10 CloudFormation resources: 1 S3 bucket + 3 composites × 3 members each.`,
800
915
  },
801
916
  {
802
917
  slug: "skills",
@@ -25,6 +25,10 @@ export interface LexiconEntry {
25
25
  createOnly?: string[];
26
26
  writeOnly?: string[];
27
27
  primaryIdentifier?: string[];
28
+ deprecatedProperties?: string[];
29
+ conditionalCreateOnly?: string[];
30
+ replacementStrategy?: "delete_then_create" | "create_then_delete";
31
+ tagging?: { taggable: boolean; tagOnCreate: boolean; tagUpdatable: boolean };
28
32
  runtimeDeprecations?: Record<string, string>;
29
33
  }
30
34
 
@@ -67,6 +71,10 @@ export function generateLexiconJSON(
67
71
  ...(r.resource.createOnly.length > 0 && { createOnly: r.resource.createOnly }),
68
72
  ...(r.resource.writeOnly.length > 0 && { writeOnly: r.resource.writeOnly }),
69
73
  ...(r.resource.primaryIdentifier.length > 0 && { primaryIdentifier: r.resource.primaryIdentifier }),
74
+ ...(r.resource.deprecatedProperties?.length && { deprecatedProperties: r.resource.deprecatedProperties }),
75
+ ...(r.resource.conditionalCreateOnly?.length && { conditionalCreateOnly: r.resource.conditionalCreateOnly }),
76
+ ...(r.resource.replacementStrategy && { replacementStrategy: r.resource.replacementStrategy }),
77
+ ...(r.resource.tagging && { tagging: r.resource.tagging }),
70
78
  ...(runtimeDepr && { runtimeDeprecations: runtimeDepr }),
71
79
  };
72
80
  },
@@ -126,7 +126,7 @@ export function generateTypeScriptDeclarations(
126
126
  lines.push("");
127
127
  lines.push("// --- Resource classes ---");
128
128
  for (const re of resources) {
129
- writeResourceClass(lines, re.tsName, re.properties, re.attributes, re.remap);
129
+ writeResourceClass(lines, re.tsName, re.properties, re.attributes, re.remap, "CFResourceAttributes");
130
130
  }
131
131
 
132
132
  // Section 2: Property classes
@@ -224,6 +224,30 @@ function staticTypeScript(): string {
224
224
  lines.push("");
225
225
  lines.push("// --- Type interfaces ---");
226
226
  lines.push("");
227
+ lines.push("export interface Declarable {");
228
+ lines.push(" readonly entityType: string;");
229
+ lines.push("}");
230
+ lines.push("");
231
+ lines.push("export interface CFResourceAttributes {");
232
+ lines.push(" DependsOn?: Declarable | Declarable[] | string | string[];");
233
+ lines.push(" Condition?: string;");
234
+ lines.push(' DeletionPolicy?: "Delete" | "Retain" | "RetainExceptOnCreate" | "Snapshot";');
235
+ lines.push(' UpdateReplacePolicy?: "Delete" | "Retain" | "Snapshot";');
236
+ lines.push(" UpdatePolicy?: {");
237
+ lines.push(" AutoScalingReplacingUpdate?: { WillReplace?: boolean };");
238
+ lines.push(" AutoScalingRollingUpdate?: {");
239
+ lines.push(" MaxBatchSize?: number;");
240
+ lines.push(" MinInstancesInService?: number;");
241
+ lines.push(" PauseTime?: string;");
242
+ lines.push(" WaitOnResourceSignals?: boolean;");
243
+ lines.push(" };");
244
+ lines.push(" };");
245
+ lines.push(" CreationPolicy?: {");
246
+ lines.push(" ResourceSignal?: { Count?: number; Timeout?: string };");
247
+ lines.push(" };");
248
+ lines.push(" Metadata?: Record<string, unknown>;");
249
+ lines.push("}");
250
+ lines.push("");
227
251
  lines.push("export interface PolicyDocument {");
228
252
  lines.push(' Version?: "2012-10-17" | "2008-10-17";');
229
253
  lines.push(" Id?: string;");