@intentius/chant-lexicon-aws 0.0.6 → 0.0.9

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 (128) hide show
  1. package/dist/integrity.json +25 -10
  2. package/dist/manifest.json +1 -1
  3. package/dist/meta.json +9444 -4597
  4. package/dist/rules/cf-refs.ts +99 -0
  5. package/dist/rules/ext001.ts +32 -25
  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 +3 -3
  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 +430 -0
  25. package/dist/types/index.d.ts +58525 -58501
  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 +20 -20
  39. package/src/codegen/docs-links.test.ts +143 -0
  40. package/src/codegen/docs.ts +294 -124
  41. package/src/codegen/generate-lexicon.ts +8 -0
  42. package/src/codegen/generate-typescript.ts +25 -1
  43. package/src/codegen/generate.ts +1 -13
  44. package/src/codegen/package.ts +2 -0
  45. package/src/codegen/typecheck.test.ts +1 -1
  46. package/src/composites/composites.test.ts +442 -0
  47. package/src/composites/fargate-alb.ts +253 -0
  48. package/src/composites/index.ts +20 -0
  49. package/src/composites/lambda-api.ts +20 -0
  50. package/src/composites/lambda-dynamodb.ts +64 -0
  51. package/src/composites/lambda-eventbridge.ts +36 -0
  52. package/src/composites/lambda-function.ts +76 -0
  53. package/src/composites/lambda-s3.ts +72 -0
  54. package/src/composites/lambda-sns.ts +30 -0
  55. package/src/composites/lambda-sqs.ts +44 -0
  56. package/src/composites/scheduled-lambda.ts +37 -0
  57. package/src/composites/vpc-default.ts +148 -0
  58. package/src/default-tags.test.ts +38 -0
  59. package/src/default-tags.ts +77 -0
  60. package/src/generated/index.d.ts +58525 -58501
  61. package/src/generated/index.ts +1351 -1351
  62. package/src/generated/lexicon-aws.json +9444 -4597
  63. package/src/import/generator.test.ts +5 -5
  64. package/src/import/generator.ts +4 -4
  65. package/src/import/roundtrip-fixtures.test.ts +2 -1
  66. package/src/import/roundtrip.test.ts +5 -5
  67. package/src/index.ts +21 -0
  68. package/src/integration.test.ts +92 -21
  69. package/src/intrinsics.ts +24 -13
  70. package/src/lint/post-synth/cf-refs.ts +99 -0
  71. package/src/lint/post-synth/ext001.test.ts +214 -31
  72. package/src/lint/post-synth/ext001.ts +32 -25
  73. package/src/lint/post-synth/waw013.test.ts +120 -0
  74. package/src/lint/post-synth/waw014.test.ts +121 -0
  75. package/src/lint/post-synth/waw015.test.ts +147 -0
  76. package/src/lint/post-synth/waw016.test.ts +141 -0
  77. package/src/lint/post-synth/waw016.ts +86 -0
  78. package/src/lint/post-synth/waw017.test.ts +130 -0
  79. package/src/lint/post-synth/waw017.ts +53 -0
  80. package/src/lint/post-synth/waw018.test.ts +109 -0
  81. package/src/lint/post-synth/waw018.ts +71 -0
  82. package/src/lint/post-synth/waw019.test.ts +138 -0
  83. package/src/lint/post-synth/waw019.ts +82 -0
  84. package/src/lint/post-synth/waw020.test.ts +125 -0
  85. package/src/lint/post-synth/waw020.ts +64 -0
  86. package/src/lint/post-synth/waw021.test.ts +81 -0
  87. package/src/lint/post-synth/waw021.ts +53 -0
  88. package/src/lint/post-synth/waw022.test.ts +54 -0
  89. package/src/lint/post-synth/waw022.ts +43 -0
  90. package/src/lint/post-synth/waw023.test.ts +53 -0
  91. package/src/lint/post-synth/waw023.ts +47 -0
  92. package/src/lint/post-synth/waw024.test.ts +64 -0
  93. package/src/lint/post-synth/waw024.ts +54 -0
  94. package/src/lint/post-synth/waw025.test.ts +42 -0
  95. package/src/lint/post-synth/waw025.ts +43 -0
  96. package/src/lint/post-synth/waw026.test.ts +54 -0
  97. package/src/lint/post-synth/waw026.ts +46 -0
  98. package/src/lint/post-synth/waw027.test.ts +63 -0
  99. package/src/lint/post-synth/waw027.ts +50 -0
  100. package/src/lint/post-synth/waw028.test.ts +68 -0
  101. package/src/lint/post-synth/waw028.ts +47 -0
  102. package/src/lint/post-synth/waw029.test.ts +179 -0
  103. package/src/lint/post-synth/waw029.ts +62 -0
  104. package/src/lint/post-synth/waw030.test.ts +800 -0
  105. package/src/lint/post-synth/waw030.ts +246 -0
  106. package/src/lint/rules/hardcoded-region.ts +1 -0
  107. package/src/lint/rules/iam-wildcard.ts +1 -0
  108. package/src/lint/rules/rules.test.ts +8 -8
  109. package/src/lint/rules/s3-encryption.ts +3 -3
  110. package/src/lsp/completions.ts +2 -0
  111. package/src/lsp/hover.ts +17 -0
  112. package/src/nested-stack-integration.test.ts +100 -0
  113. package/src/nested-stack.ts +2 -2
  114. package/src/plugin.test.ts +13 -15
  115. package/src/plugin.ts +552 -114
  116. package/src/serializer.test.ts +370 -43
  117. package/src/serializer.ts +69 -17
  118. package/src/spec/fetch.ts +10 -0
  119. package/src/spec/parse.test.ts +141 -0
  120. package/src/spec/parse.ts +40 -0
  121. package/src/taggable.ts +44 -0
  122. package/src/testdata/nested-stacks/app.ts +26 -0
  123. package/src/testdata/nested-stacks/network/outputs.ts +17 -0
  124. package/src/testdata/nested-stacks/network/security.ts +17 -0
  125. package/src/testdata/nested-stacks/network/vpc.ts +54 -0
  126. package/dist/skills/aws-cloudformation.md +0 -41
  127. package/src/codegen/rollback.test.ts +0 -80
  128. package/src/codegen/rollback.ts +0 -20
package/src/plugin.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import { createRequire } from "module";
1
2
  import type { LexiconPlugin, IntrinsicDef, SkillDefinition } from "@intentius/chant/lexicon";
3
+ const require = createRequire(import.meta.url);
2
4
  import type { LintRule } from "@intentius/chant/lint/rule";
3
5
  import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
4
6
  import type { TemplateParser } from "@intentius/chant/import/parser";
@@ -52,40 +54,40 @@ export const awsPlugin: LexiconPlugin = {
52
54
  ];
53
55
  },
54
56
 
55
- initTemplates(): Record<string, string> {
56
- return {
57
+ initTemplates() {
58
+ return { src: {
57
59
  "config.ts": `/**
58
60
  * Shared bucket configuration — encryption, versioning, public access
59
61
  */
60
62
 
61
- import * as aws from "@intentius/chant-lexicon-aws";
63
+ import { ServerSideEncryptionByDefault, ServerSideEncryptionRule, BucketEncryption, PublicAccessBlockConfiguration, VersioningConfiguration } from "@intentius/chant-lexicon-aws";
62
64
 
63
65
  // Encryption default — AES256 server-side encryption
64
- export const encryptionDefault = new aws.ServerSideEncryptionByDefault({
65
- sseAlgorithm: "AES256",
66
+ export const encryptionDefault = new ServerSideEncryptionByDefault({
67
+ SSEAlgorithm: "AES256",
66
68
  });
67
69
 
68
70
  // Encryption rule wrapping the default
69
- export const encryptionRule = new aws.ServerSideEncryptionRule({
70
- serverSideEncryptionByDefault: encryptionDefault,
71
+ export const encryptionRule = new ServerSideEncryptionRule({
72
+ ServerSideEncryptionByDefault: encryptionDefault,
71
73
  });
72
74
 
73
75
  // Bucket encryption configuration
74
- export const bucketEncryption = new aws.BucketEncryption({
75
- serverSideEncryptionConfiguration: [encryptionRule],
76
+ export const bucketEncryption = new BucketEncryption({
77
+ ServerSideEncryptionConfiguration: [encryptionRule],
76
78
  });
77
79
 
78
80
  // 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,
81
+ export const publicAccessBlock = new PublicAccessBlockConfiguration({
82
+ BlockPublicAcls: true,
83
+ BlockPublicPolicy: true,
84
+ IgnorePublicAcls: true,
85
+ RestrictPublicBuckets: true,
84
86
  });
85
87
 
86
88
  // Versioning — enabled
87
- export const versioningEnabled = new aws.VersioningConfiguration({
88
- status: "Enabled",
89
+ export const versioningEnabled = new VersioningConfiguration({
90
+ Status: "Enabled",
89
91
  });
90
92
  `,
91
93
  "data-bucket.ts": `/**
@@ -96,28 +98,30 @@ import { Bucket, Sub, AWS } from "@intentius/chant-lexicon-aws";
96
98
  import { versioningEnabled, bucketEncryption, publicAccessBlock } from "./config";
97
99
 
98
100
  export const dataBucket = new Bucket({
99
- bucketName: Sub\`\${AWS.StackName}-data\`,
100
- versioningConfiguration: versioningEnabled,
101
- bucketEncryption: bucketEncryption,
102
- publicAccessBlockConfiguration: publicAccessBlock,
101
+ BucketName: Sub\`\${AWS.StackName}-\${AWS.AccountId}-data\`,
102
+ VersioningConfiguration: versioningEnabled,
103
+ BucketEncryption: bucketEncryption,
104
+ PublicAccessBlockConfiguration: publicAccessBlock,
103
105
  });
104
106
  `,
105
107
  "logs-bucket.ts": `/**
106
108
  * Logs bucket — log delivery with encryption and versioning
109
+ *
110
+ * Note: AccessControl is a legacy property. Use a bucket policy to grant
111
+ * log delivery access instead (s3:PutObject permission for the logging service principal).
107
112
  */
108
113
 
109
114
  import { Bucket, Sub, AWS } from "@intentius/chant-lexicon-aws";
110
115
  import { versioningEnabled, bucketEncryption, publicAccessBlock } from "./config";
111
116
 
112
117
  export const logsBucket = new Bucket({
113
- bucketName: Sub\`\${AWS.StackName}-logs\`,
114
- accessControl: "LogDeliveryWrite",
115
- versioningConfiguration: versioningEnabled,
116
- bucketEncryption: bucketEncryption,
117
- publicAccessBlockConfiguration: publicAccessBlock,
118
+ BucketName: Sub\`\${AWS.StackName}-\${AWS.AccountId}-logs\`,
119
+ VersioningConfiguration: versioningEnabled,
120
+ BucketEncryption: bucketEncryption,
121
+ PublicAccessBlockConfiguration: publicAccessBlock,
118
122
  });
119
123
  `,
120
- };
124
+ } };
121
125
  },
122
126
 
123
127
  detectTemplate(data: unknown): boolean {
@@ -161,7 +165,26 @@ export const logsBucket = new Bucket({
161
165
  const { waw013 } = require("./lint/post-synth/waw013");
162
166
  const { waw014 } = require("./lint/post-synth/waw014");
163
167
  const { waw015 } = require("./lint/post-synth/waw015");
164
- return [waw010, waw011, cor020, ext001, waw013, waw014, waw015];
168
+ const { waw016 } = require("./lint/post-synth/waw016");
169
+ const { waw017 } = require("./lint/post-synth/waw017");
170
+ const { waw018 } = require("./lint/post-synth/waw018");
171
+ const { waw019 } = require("./lint/post-synth/waw019");
172
+ const { waw020 } = require("./lint/post-synth/waw020");
173
+ const { waw021 } = require("./lint/post-synth/waw021");
174
+ const { waw022 } = require("./lint/post-synth/waw022");
175
+ const { waw023 } = require("./lint/post-synth/waw023");
176
+ const { waw024 } = require("./lint/post-synth/waw024");
177
+ const { waw025 } = require("./lint/post-synth/waw025");
178
+ const { waw026 } = require("./lint/post-synth/waw026");
179
+ const { waw027 } = require("./lint/post-synth/waw027");
180
+ const { waw028 } = require("./lint/post-synth/waw028");
181
+ const { waw029 } = require("./lint/post-synth/waw029");
182
+ const { waw030 } = require("./lint/post-synth/waw030");
183
+ return [
184
+ waw010, waw011, cor020, ext001, waw013, waw014, waw015, waw016, waw017,
185
+ waw018, waw019, waw020, waw021, waw022, waw023, waw024, waw025,
186
+ waw026, waw027, waw028, waw029, waw030,
187
+ ];
165
188
  },
166
189
 
167
190
  async generate(options?: { verbose?: boolean }): Promise<void> {
@@ -229,44 +252,17 @@ export const logsBucket = new Bucket({
229
252
 
230
253
  console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
231
254
 
232
- // Produce .tgz via bun pm pack
233
- const packProc = Bun.spawn(["bun", "pm", "pack"], {
234
- cwd: pkgDir,
235
- stdout: "pipe",
236
- stderr: "pipe",
237
- });
238
- const packOut = await new Response(packProc.stdout).text();
239
- const packErr = await new Response(packProc.stderr).text();
240
- const packExit = await packProc.exited;
255
+ // Produce .tgz via pack command
256
+ const { getRuntime } = await import("@intentius/chant/runtime-adapter");
257
+ const rt = getRuntime();
258
+ const { stdout: packOut, stderr: packErr, exitCode: packExit } = await rt.spawn(
259
+ rt.commands.packCmd,
260
+ { cwd: pkgDir },
261
+ );
241
262
  if (packExit === 0) {
242
263
  console.error(`Tarball: ${packOut.trim()}`);
243
264
  } else {
244
- console.error(`bun pm pack failed: ${packErr}`);
245
- }
246
- },
247
-
248
- async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
249
- const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
250
- const { join, dirname } = await import("path");
251
- const { fileURLToPath } = await import("url");
252
-
253
- const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
254
- const snapshotsDir = join(pkgDir, ".snapshots");
255
-
256
- if (options?.restore) {
257
- const generatedDir = join(pkgDir, "src", "generated");
258
- restoreSnapshot(String(options.restore), generatedDir);
259
- console.error(`Restored snapshot: ${options.restore}`);
260
- } else {
261
- const snapshots = listSnapshots(snapshotsDir);
262
- if (snapshots.length === 0) {
263
- console.error("No snapshots available.");
264
- } else {
265
- console.error(`Available snapshots (${snapshots.length}):`);
266
- for (const s of snapshots) {
267
- console.error(` ${s.timestamp} ${s.resourceCount} resources ${s.path}`);
268
- }
269
- }
265
+ console.error(`${rt.commands.packCmd.join(" ")} failed: ${packErr}`);
270
266
  }
271
267
  },
272
268
 
@@ -278,53 +274,455 @@ export const logsBucket = new Bucket({
278
274
  skills(): SkillDefinition[] {
279
275
  return [
280
276
  {
281
- name: "aws-cloudformation",
282
- description: "AWS CloudFormation best practices and common patterns",
277
+ name: "chant-aws",
278
+ description: "AWS CloudFormation lifecycle build, diff, deploy, rollback, and troubleshoot from a chant project",
283
279
  content: `---
284
- name: aws-cloudformation
285
- description: AWS CloudFormation best practices and common patterns
280
+ skill: chant-aws
281
+ description: Build, validate, and deploy CloudFormation templates from a chant project
282
+ user-invocable: true
286
283
  ---
287
284
 
288
- # AWS CloudFormation with Chant
285
+ # AWS CloudFormation Operational Playbook
286
+
287
+ ## How chant and CloudFormation relate
288
+
289
+ chant is a **synthesis-only** tool — it compiles TypeScript source files into CloudFormation JSON (or YAML). chant does NOT call AWS APIs. Your job as an agent is to bridge the two:
290
+
291
+ - Use **chant** for: build, lint, diff (local template comparison)
292
+ - Use **AWS CLI** for: validate-template, deploy, change sets, rollback, drift detection, and all stack operations
293
+
294
+ The source of truth for infrastructure is the TypeScript in \`src/\`. The generated template (\`stack.json\`) is an intermediate artifact.
295
+
296
+ ## Build and validate
297
+
298
+ ### Build the template
299
+
300
+ \`\`\`bash
301
+ chant build src/ --output stack.json
302
+ \`\`\`
303
+
304
+ Options:
305
+ - \`--format yaml\` — emit YAML instead of JSON
306
+ - \`--watch\` — rebuild on source changes
307
+
308
+ ### Lint the source
309
+
310
+ \`\`\`bash
311
+ chant lint src/
312
+ \`\`\`
313
+
314
+ Options:
315
+ - \`--fix\` — auto-fix violations where possible
316
+ - \`--format sarif\` — SARIF output for CI integration
317
+ - \`--watch\` — re-lint on changes
318
+
319
+ ### Validate with CloudFormation
320
+
321
+ \`\`\`bash
322
+ aws cloudformation validate-template --template-body file://stack.json
323
+ \`\`\`
324
+
325
+ ### What each step catches
326
+
327
+ | Step | Catches | When to run |
328
+ |------|---------|-------------|
329
+ | \`chant lint\` | Best-practice violations, security anti-patterns, naming issues | Every edit |
330
+ | \`chant build\` | TypeScript errors, missing properties, type mismatches | Before deploy |
331
+ | \`validate-template\` | CloudFormation schema errors, invalid intrinsic usage | Before deploy |
332
+
333
+ Always run all three before deploying. Lint catches things validate-template cannot (and vice versa).
334
+
335
+ ## Diffing and change preview
336
+
337
+ This is the most critical section for production safety. **Never deploy to production without previewing changes.**
338
+
339
+ ### Local diff
340
+
341
+ Compare your proposed template against what is currently deployed:
342
+
343
+ \`\`\`bash
344
+ # Get the currently deployed template
345
+ aws cloudformation get-template --stack-name <stack-name> --query TemplateBody --output json > deployed.json
346
+
347
+ # Build the proposed template
348
+ chant build src/ --output proposed.json
349
+
350
+ # Diff them
351
+ diff deployed.json proposed.json
352
+ \`\`\`
353
+
354
+ ### Change sets (recommended for production)
355
+
356
+ Change sets let you preview exactly what CloudFormation will do before it does it.
357
+
358
+ \`\`\`bash
359
+ # 1. Create the change set
360
+ aws cloudformation create-change-set \\
361
+ --stack-name <stack-name> \\
362
+ --template-body file://stack.json \\
363
+ --change-set-name review-$(date +%s) \\
364
+ --capabilities CAPABILITY_NAMED_IAM
365
+
366
+ # 2. Wait for it to compute
367
+ aws cloudformation wait change-set-create-complete \\
368
+ --stack-name <stack-name> \\
369
+ --change-set-name review-<id>
370
+
371
+ # 3. Review the changes
372
+ aws cloudformation describe-change-set \\
373
+ --stack-name <stack-name> \\
374
+ --change-set-name review-<id>
375
+
376
+ # 4a. Execute if changes look safe
377
+ aws cloudformation execute-change-set \\
378
+ --stack-name <stack-name> \\
379
+ --change-set-name review-<id>
380
+
381
+ # 4b. Or delete if you want to abort
382
+ aws cloudformation delete-change-set \\
383
+ --stack-name <stack-name> \\
384
+ --change-set-name review-<id>
385
+ \`\`\`
386
+
387
+ ### Interpreting change set results
388
+
389
+ Each resource change has an **Action** and a **Replacement** value. Read them together:
390
+
391
+ | Action | Replacement | Risk | Meaning |
392
+ |--------|-------------|------|---------|
393
+ | Add | — | Low | New resource will be created |
394
+ | Modify | False | Low | In-place update, no disruption |
395
+ | Modify | Conditional | **MEDIUM** | May replace depending on property — investigate further |
396
+ | Modify | True | **HIGH** | Resource will be DESTROYED and recreated — **data loss risk** |
397
+ | Remove | — | **HIGH** | Resource will be deleted |
398
+
399
+ ### Properties that always cause replacement
400
+
401
+ These property changes ALWAYS destroy and recreate the resource:
402
+ - \`BucketName\` on S3 buckets
403
+ - \`TableName\` on DynamoDB tables
404
+ - \`DBInstanceIdentifier\` on RDS instances
405
+ - \`FunctionName\` on Lambda functions
406
+ - \`CidrBlock\` on VPCs and subnets
407
+ - \`ClusterIdentifier\` on Redshift clusters
408
+ - \`DomainName\` on Elasticsearch/OpenSearch domains
409
+ - \`TopicName\` on SNS topics
410
+ - \`QueueName\` on SQS queues
411
+
412
+ **CRITICAL**: When you see \`Replacement: True\` on any stateful resource (databases, S3 buckets, queues with messages, DynamoDB tables), ALWAYS flag this to the user and get explicit confirmation before executing. This will destroy the existing resource and all its data.
413
+
414
+ ## Deploying a new stack
415
+
416
+ \`\`\`bash
417
+ aws cloudformation deploy \\
418
+ --template-file stack.json \\
419
+ --stack-name <stack-name> \\
420
+ --capabilities CAPABILITY_NAMED_IAM \\
421
+ --parameter-overrides Env=prod Version=1.0 \\
422
+ --tags Project=myapp Environment=prod
423
+ \`\`\`
424
+
425
+ ### Capabilities
426
+
427
+ | Capability | When needed |
428
+ |------------|-------------|
429
+ | \`CAPABILITY_IAM\` | Template creates IAM resources with auto-generated names |
430
+ | \`CAPABILITY_NAMED_IAM\` | Template creates IAM resources with custom names (use this by default — it's a superset) |
431
+ | \`CAPABILITY_AUTO_EXPAND\` | Template uses macros or nested stacks with transforms |
432
+
433
+ **Recommendation**: Default to \`CAPABILITY_NAMED_IAM\` unless the template also uses macros, in which case use \`--capabilities CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\`.
434
+
435
+ ### Monitoring deployment
436
+
437
+ \`\`\`bash
438
+ # Wait for completion (blocks until done)
439
+ aws cloudformation wait stack-create-complete --stack-name <stack-name>
440
+
441
+ # Or poll events in real-time
442
+ watch -n 5 "aws cloudformation describe-stack-events --stack-name <stack-name> --max-items 10 --query 'StackEvents[].{Time:Timestamp,Resource:LogicalResourceId,Status:ResourceStatus,Reason:ResourceStatusReason}' --output table"
443
+ \`\`\`
444
+
445
+ ### Getting outputs
446
+
447
+ \`\`\`bash
448
+ aws cloudformation describe-stacks \\
449
+ --stack-name <stack-name> \\
450
+ --query 'Stacks[0].Outputs'
451
+ \`\`\`
452
+
453
+ ## Updating an existing stack
454
+
455
+ ### Safe path — change set workflow (production / stateful stacks)
456
+
457
+ 1. Build: \`chant build src/ --output stack.json\`
458
+ 2. Create change set (see Diffing section above)
459
+ 3. Review every resource change — pay special attention to Replacement values
460
+ 4. Get user confirmation for any destructive changes
461
+ 5. Execute the change set
462
+ 6. Monitor: \`aws cloudformation wait stack-update-complete --stack-name <stack-name>\`
463
+
464
+ ### Fast path — direct deploy (dev / stateless stacks)
465
+
466
+ \`\`\`bash
467
+ aws cloudformation deploy \\
468
+ --template-file stack.json \\
469
+ --stack-name <stack-name> \\
470
+ --capabilities CAPABILITY_NAMED_IAM \\
471
+ --no-fail-on-empty-changeset
472
+ \`\`\`
473
+
474
+ The \`--no-fail-on-empty-changeset\` flag prevents a non-zero exit code when there are no changes (useful in CI).
475
+
476
+ ### Updating parameters only (no template change)
477
+
478
+ \`\`\`bash
479
+ aws cloudformation deploy \\
480
+ --stack-name <stack-name> \\
481
+ --use-previous-template \\
482
+ --capabilities CAPABILITY_NAMED_IAM \\
483
+ --parameter-overrides Env=staging
484
+ \`\`\`
485
+
486
+ ### Which path to use
487
+
488
+ | Scenario | Path |
489
+ |----------|------|
490
+ | Production stack with databases/storage | Safe path (change set) |
491
+ | Any stack with \`Replacement: True\` changes | Safe path (change set) |
492
+ | Dev/test stack, stateless resources only | Fast path (direct deploy) |
493
+ | CI/CD automated pipeline with approval gate | Safe path (change set with manual approval) |
494
+ | Parameter-only change, no template diff | Fast path with \`--use-previous-template\` |
495
+
496
+ ## Rollback and recovery
497
+
498
+ ### Stack states reference
499
+
500
+ | State | Meaning | Action |
501
+ |-------|---------|--------|
502
+ | \`CREATE_COMPLETE\` | Stack created successfully | None — healthy |
503
+ | \`UPDATE_COMPLETE\` | Update succeeded | None — healthy |
504
+ | \`DELETE_COMPLETE\` | Stack deleted | Gone — recreate if needed |
505
+ | \`CREATE_IN_PROGRESS\` | Creation underway | Wait |
506
+ | \`UPDATE_IN_PROGRESS\` | Update underway | Wait |
507
+ | \`DELETE_IN_PROGRESS\` | Deletion underway | Wait |
508
+ | \`ROLLBACK_IN_PROGRESS\` | Create failed, rolling back | Wait |
509
+ | \`UPDATE_ROLLBACK_IN_PROGRESS\` | Update failed, rolling back | Wait |
510
+ | \`CREATE_FAILED\` | Creation failed (rare) | Check events, delete stack |
511
+ | \`ROLLBACK_COMPLETE\` | Create failed, rollback finished | **Must delete and recreate** — cannot update |
512
+ | \`ROLLBACK_FAILED\` | Create rollback failed | Check events, may need manual cleanup |
513
+ | \`UPDATE_ROLLBACK_COMPLETE\` | Update failed, rolled back to previous | Healthy — fix template and try again |
514
+ | \`UPDATE_ROLLBACK_FAILED\` | Update rollback itself failed | **See recovery steps below** |
515
+ | \`DELETE_FAILED\` | Deletion failed | Check events, retry or use retain |
289
516
 
290
- ## Common Resource Types
517
+ ### Recovering from UPDATE_ROLLBACK_FAILED
291
518
 
292
- - \`AWS::S3::Bucket\` Object storage
293
- - \`AWS::Lambda::Function\` — Serverless compute
294
- - \`AWS::DynamoDB::Table\` — NoSQL database
295
- - \`AWS::IAM::Role\` — Identity and access management
296
- - \`AWS::SNS::Topic\` — Pub/sub messaging
297
- - \`AWS::SQS::Queue\` — Message queue
298
- - \`AWS::EC2::SecurityGroup\` — Network firewall rules
519
+ This is the most common "stuck" state. A resource that CloudFormation tried to roll back could not be restored.
299
520
 
300
- ## Intrinsic Functions
521
+ **Step 1**: Identify the stuck resource:
301
522
 
302
- - \`Sub\` — String interpolation with \`\${}\` syntax
303
- - \`Ref\` Reference a resource or parameter
304
- - \`GetAtt\` — Get a resource attribute (e.g. ARN)
305
- - \`If\` — Conditional value based on a condition
306
- - \`Join\` — Join strings with a delimiter
307
- - \`Select\` — Pick an item from a list by index
523
+ \`\`\`bash
524
+ aws cloudformation describe-stack-events \\
525
+ --stack-name <stack-name> \\
526
+ --query "StackEvents[?ResourceStatus=='UPDATE_FAILED'].[LogicalResourceId,ResourceStatusReason]" \\
527
+ --output table
528
+ \`\`\`
308
529
 
309
- ## Pseudo Parameters
530
+ **Step 2a** — Try continuing the rollback:
310
531
 
311
- - \`AWS::StackName\` — Name of the current stack
312
- - \`AWS::Region\` Current deployment region
313
- - \`AWS::AccountId\` Current AWS account ID
314
- - \`AWS::Partition\` — Partition (aws, aws-cn, aws-us-gov)
532
+ \`\`\`bash
533
+ aws cloudformation continue-update-rollback --stack-name <stack-name>
534
+ aws cloudformation wait stack-update-complete --stack-name <stack-name>
535
+ \`\`\`
315
536
 
316
- ## Best Practices
537
+ **Step 2b** — If that fails, skip the stuck resources:
317
538
 
318
- 1. **Always enable encryption** — Use \`BucketEncryption\` for S3, \`SSESpecification\` for DynamoDB
319
- 2. **Block public access** — Set \`PublicAccessBlockConfiguration\` on all S3 buckets
320
- 3. **Use least-privilege IAM** — Avoid \`*\` in IAM policy actions and resources
321
- 4. **Enable versioning** — Turn on \`VersioningConfiguration\` for data buckets
322
- 5. **Use Sub for dynamic names** — \`Sub\\\`\\\${AWS::StackName}-suffix\\\`\` for unique naming
323
- 6. **Share config via direct imports** — Put common settings in a config file and import directly
539
+ \`\`\`bash
540
+ aws cloudformation continue-update-rollback \\
541
+ --stack-name <stack-name> \\
542
+ --resources-to-skip LogicalResourceId1 LogicalResourceId2
543
+ \`\`\`
544
+
545
+ **WARNING**: Skipping resources causes state divergence — CloudFormation's view of the stack will no longer match reality. You may need to manually clean up skipped resources or import them back later.
546
+
547
+ ### Recovering from ROLLBACK_COMPLETE
548
+
549
+ A stack in \`ROLLBACK_COMPLETE\` cannot be updated. You must delete it and create a new one:
550
+
551
+ \`\`\`bash
552
+ aws cloudformation delete-stack --stack-name <stack-name>
553
+ aws cloudformation wait stack-delete-complete --stack-name <stack-name>
554
+ # Then deploy fresh
555
+ aws cloudformation deploy --template-file stack.json --stack-name <stack-name> --capabilities CAPABILITY_NAMED_IAM
556
+ \`\`\`
557
+
558
+ ## Stack lifecycle operations
559
+
560
+ ### Delete a stack
561
+
562
+ \`\`\`bash
563
+ aws cloudformation delete-stack --stack-name <stack-name>
564
+ aws cloudformation wait stack-delete-complete --stack-name <stack-name>
565
+ \`\`\`
566
+
567
+ If deletion fails because a resource cannot be deleted (e.g., non-empty S3 bucket), use retain:
568
+
569
+ \`\`\`bash
570
+ aws cloudformation delete-stack \\
571
+ --stack-name <stack-name> \\
572
+ --retain-resources BucketLogicalId
573
+ \`\`\`
574
+
575
+ To protect a stack from accidental deletion:
576
+
577
+ \`\`\`bash
578
+ aws cloudformation update-termination-protection \\
579
+ --enable-termination-protection \\
580
+ --stack-name <stack-name>
581
+ \`\`\`
582
+
583
+ ### Drift detection
584
+
585
+ Detect whether resources have been modified outside of CloudFormation:
586
+
587
+ \`\`\`bash
588
+ # Start detection
589
+ DRIFT_ID=$(aws cloudformation detect-stack-drift --stack-name <stack-name> --query StackDriftDetectionId --output text)
590
+
591
+ # Check status
592
+ aws cloudformation describe-stack-drift-detection-status --stack-drift-detection-id $DRIFT_ID
593
+
594
+ # View drifted resources
595
+ aws cloudformation describe-stack-resource-drifts \\
596
+ --stack-name <stack-name> \\
597
+ --stack-resource-drift-status-filters MODIFIED DELETED
598
+ \`\`\`
599
+
600
+ ### Import existing resources
601
+
602
+ Bring resources that were created outside CloudFormation under stack management:
603
+
604
+ \`\`\`bash
605
+ aws cloudformation create-change-set \\
606
+ --stack-name <stack-name> \\
607
+ --template-body file://stack.json \\
608
+ --change-set-name import-resources \\
609
+ --change-set-type IMPORT \\
610
+ --resources-to-import '[{"ResourceType":"AWS::S3::Bucket","LogicalResourceId":"MyBucket","ResourceIdentifier":{"BucketName":"existing-bucket-name"}}]'
611
+ \`\`\`
612
+
613
+ ## Troubleshooting decision tree
614
+
615
+ When a deployment fails, follow this diagnostic flow:
616
+
617
+ ### Step 1: Check the stack status
618
+
619
+ \`\`\`bash
620
+ aws cloudformation describe-stacks --stack-name <stack-name> --query 'Stacks[0].StackStatus' --output text
621
+ \`\`\`
622
+
623
+ ### Step 2: Branch on status
624
+
625
+ - **\`*_IN_PROGRESS\`** → Wait. Do not take action while an operation is in progress.
626
+ - **\`*_FAILED\` or \`*_ROLLBACK_*\`** → Read the events (Step 3).
627
+ - **\`*_COMPLETE\`** → Stack is stable. If behavior is wrong, check resource configuration.
628
+
629
+ ### Step 3: Read the failure events
630
+
631
+ \`\`\`bash
632
+ aws cloudformation describe-stack-events \\
633
+ --stack-name <stack-name> \\
634
+ --query "StackEvents[?contains(ResourceStatus, 'FAILED')].[LogicalResourceId,ResourceStatusReason]" \\
635
+ --output table
636
+ \`\`\`
637
+
638
+ ### Step 4: Diagnose by error pattern
639
+
640
+ | Error pattern | Likely cause | Fix |
641
+ |---------------|-------------|-----|
642
+ | "already exists" | Resource name collision — another stack or manual creation owns this name | Use dynamic names: \`Sub\\\`\\\${AWS.StackName}-myresource\\\`\` |
643
+ | "not authorized" or "AccessDenied" | Missing IAM permissions, SCP restriction, or wrong \`--capabilities\` | Check IAM policy, add \`--capabilities CAPABILITY_NAMED_IAM\` |
644
+ | "limit exceeded" or "LimitExceededException" | AWS service quota hit | Request quota increase or reduce resource count |
645
+ | "Template error" or "Template format error" | Invalid template syntax | Run \`aws cloudformation validate-template\` and \`chant lint src/\` |
646
+ | "Circular dependency" | Two resources reference each other | Break the cycle — extract one reference to an output or parameter |
647
+ | "is in UPDATE_ROLLBACK_FAILED state and can not be updated" | Stuck rollback | See UPDATE_ROLLBACK_FAILED recovery above |
648
+ | "is in ROLLBACK_COMPLETE state and can not be updated" | Failed creation, rolled back | Delete the stack and recreate |
649
+ | "No updates are to be performed" | Template unchanged | Use \`--no-fail-on-empty-changeset\` or verify your changes are in the built template |
650
+ | "Resource is not in the state" | Resource was modified outside CF | Run drift detection, then update or import |
651
+ | "Maximum number of addresses has been reached" | EIP limit (default 5) | Request EIP quota increase |
652
+
653
+ ## Quick reference
654
+
655
+ ### Stack info commands
656
+
657
+ \`\`\`bash
658
+ # List all stacks
659
+ aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE
660
+
661
+ # Describe a stack (status, params, outputs, tags)
662
+ aws cloudformation describe-stacks --stack-name <stack-name>
663
+
664
+ # List resources in a stack
665
+ aws cloudformation list-stack-resources --stack-name <stack-name>
666
+
667
+ # Get outputs only
668
+ aws cloudformation describe-stacks --stack-name <stack-name> --query 'Stacks[0].Outputs'
669
+
670
+ # Recent events
671
+ aws cloudformation describe-stack-events --stack-name <stack-name> --max-items 20
672
+
673
+ # Get deployed template
674
+ aws cloudformation get-template --stack-name <stack-name> --query TemplateBody
675
+ \`\`\`
676
+
677
+ ### Full build-to-deploy pipeline
678
+
679
+ \`\`\`bash
680
+ # 1. Lint
681
+ chant lint src/
682
+
683
+ # 2. Build
684
+ chant build src/ --output stack.json
685
+
686
+ # 3. Validate
687
+ aws cloudformation validate-template --template-body file://stack.json
688
+
689
+ # 4. Create change set
690
+ aws cloudformation create-change-set \\
691
+ --stack-name <stack-name> \\
692
+ --template-body file://stack.json \\
693
+ --change-set-name deploy-$(date +%s) \\
694
+ --capabilities CAPABILITY_NAMED_IAM
695
+
696
+ # 5. Review changes
697
+ aws cloudformation describe-change-set \\
698
+ --stack-name <stack-name> \\
699
+ --change-set-name deploy-<id>
700
+
701
+ # 6. Execute (after user confirms)
702
+ aws cloudformation execute-change-set \\
703
+ --stack-name <stack-name> \\
704
+ --change-set-name deploy-<id>
705
+
706
+ # 7. Wait for completion
707
+ aws cloudformation wait stack-update-complete --stack-name <stack-name>
708
+ \`\`\`
324
709
  `,
325
710
  triggers: [
326
711
  { type: "file-pattern", value: "**/*.aws.ts" },
712
+ { type: "file-pattern", value: "**/stack.json" },
713
+ { type: "file-pattern", value: "**/template.yaml" },
327
714
  { type: "context", value: "aws" },
715
+ { type: "context", value: "cloudformation" },
716
+ { type: "context", value: "deploy" },
717
+ ],
718
+ preConditions: [
719
+ "AWS CLI is installed and configured (aws sts get-caller-identity succeeds)",
720
+ "chant CLI is installed (chant --version succeeds)",
721
+ "Project has chant source files in src/",
722
+ ],
723
+ postConditions: [
724
+ "Stack is in a stable state (*_COMPLETE)",
725
+ "No failed resources in stack events",
328
726
  ],
329
727
  parameters: [
330
728
  {
@@ -353,6 +751,46 @@ description: AWS CloudFormation best practices and common patterns
353
751
  },
354
752
  })`,
355
753
  },
754
+ {
755
+ title: "Deploy a new stack",
756
+ description: "Build a chant project and deploy it as a new CloudFormation stack",
757
+ input: "Deploy this project as a new stack called my-app-prod",
758
+ output: `chant lint src/
759
+ chant build src/ --output stack.json
760
+ aws cloudformation validate-template --template-body file://stack.json
761
+ aws cloudformation deploy \\
762
+ --template-file stack.json \\
763
+ --stack-name my-app-prod \\
764
+ --capabilities CAPABILITY_NAMED_IAM`,
765
+ },
766
+ {
767
+ title: "Preview changes before updating",
768
+ description: "Create a change set to review what will change before applying an update",
769
+ input: "Show me what will change if I deploy this update to my-app-prod",
770
+ output: `chant build src/ --output stack.json
771
+ aws cloudformation create-change-set \\
772
+ --stack-name my-app-prod \\
773
+ --template-body file://stack.json \\
774
+ --change-set-name review-$(date +%s) \\
775
+ --capabilities CAPABILITY_NAMED_IAM
776
+ # Wait for change set to compute, then review:
777
+ aws cloudformation describe-change-set \\
778
+ --stack-name my-app-prod \\
779
+ --change-set-name review-<id>`,
780
+ },
781
+ {
782
+ title: "Fix a stuck rollback",
783
+ description: "Recover a stack stuck in UPDATE_ROLLBACK_FAILED state",
784
+ input: "My stack my-app-prod is stuck in UPDATE_ROLLBACK_FAILED, help me fix it",
785
+ output: `# Identify the stuck resource
786
+ aws cloudformation describe-stack-events \\
787
+ --stack-name my-app-prod \\
788
+ --query "StackEvents[?ResourceStatus=='UPDATE_FAILED'].[LogicalResourceId,ResourceStatusReason]" \\
789
+ --output table
790
+ # Attempt to continue the rollback
791
+ aws cloudformation continue-update-rollback --stack-name my-app-prod
792
+ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
793
+ },
356
794
  ],
357
795
  },
358
796
  ];
@@ -424,31 +862,31 @@ description: AWS CloudFormation best practices and common patterns
424
862
  description: "AWS S3 bucket with versioning and encryption",
425
863
  mimeType: "text/typescript",
426
864
  async handler(): Promise<string> {
427
- return `import * as aws from "@intentius/chant-lexicon-aws";
865
+ return `import { ServerSideEncryptionByDefault, ServerSideEncryptionRule, BucketEncryption, VersioningConfiguration, Bucket, Sub, AWS } from "@intentius/chant-lexicon-aws";
428
866
 
429
867
  // Encryption configuration
430
- export const encryptionDefault = new aws.ServerSideEncryptionByDefault({
431
- sseAlgorithm: "AES256",
868
+ export const encryptionDefault = new ServerSideEncryptionByDefault({
869
+ SSEAlgorithm: "AES256",
432
870
  });
433
871
 
434
- export const encryptionRule = new aws.ServerSideEncryptionRule({
435
- serverSideEncryptionByDefault: encryptionDefault,
872
+ export const encryptionRule = new ServerSideEncryptionRule({
873
+ ServerSideEncryptionByDefault: encryptionDefault,
436
874
  });
437
875
 
438
- export const bucketEncryption = new aws.BucketEncryption({
439
- serverSideEncryptionConfiguration: [encryptionRule],
876
+ export const bucketEncryption = new BucketEncryption({
877
+ ServerSideEncryptionConfiguration: [encryptionRule],
440
878
  });
441
879
 
442
880
  // Versioning
443
- export const versioningEnabled = new aws.VersioningConfiguration({
444
- status: "Enabled",
881
+ export const versioningEnabled = new VersioningConfiguration({
882
+ Status: "Enabled",
445
883
  });
446
884
 
447
- // Create a versioned bucket with encryption
448
- export const dataBucket = new aws.Bucket({
449
- bucketName: aws.Sub\`\${aws.AWS.StackName}-data\`,
450
- versioningConfiguration: versioningEnabled,
451
- bucketEncryption: bucketEncryption,
885
+ // Create a versioned bucket with encryption (AccountId ensures global uniqueness)
886
+ export const dataBucket = new Bucket({
887
+ BucketName: Sub\`\${AWS.StackName}-\${AWS.AccountId}-data\`,
888
+ VersioningConfiguration: versioningEnabled,
889
+ BucketEncryption: bucketEncryption,
452
890
  });
453
891
  `;
454
892
  },
@@ -459,17 +897,17 @@ export const dataBucket = new aws.Bucket({
459
897
  description: "Using AttrRefs for cross-resource references",
460
898
  mimeType: "text/typescript",
461
899
  async handler(): Promise<string> {
462
- return `import * as aws from "@intentius/chant-lexicon-aws";
900
+ return `import { Bucket, VersioningConfiguration, Role } from "@intentius/chant-lexicon-aws";
463
901
 
464
902
  // Create a bucket
465
- export const dataBucket = new aws.Bucket({
466
- bucketName: "my-data-bucket",
467
- versioningConfiguration: new aws.VersioningConfiguration({ status: "Enabled" }),
903
+ export const dataBucket = new Bucket({
904
+ BucketName: "my-data-bucket",
905
+ VersioningConfiguration: new VersioningConfiguration({ Status: "Enabled" }),
468
906
  });
469
907
 
470
908
  // Create a role that references the bucket's ARN
471
- export const role = new aws.Role({
472
- assumeRolePolicyDocument: {
909
+ export const role = new Role({
910
+ AssumeRolePolicyDocument: {
473
911
  Version: "2012-10-17",
474
912
  Statement: [{
475
913
  Effect: "Allow",