@intentius/chant-lexicon-aws 0.0.5 → 0.0.8
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.
- package/README.md +9 -425
- package/dist/integrity.json +7 -7
- package/dist/manifest.json +1 -1
- package/dist/meta.json +3767 -3706
- package/dist/rules/ext001.ts +2 -4
- package/dist/rules/s3-encryption.ts +2 -3
- package/dist/skills/chant-aws.md +72 -0
- package/dist/types/index.d.ts +57174 -57070
- package/package.json +2 -2
- package/src/codegen/__snapshots__/snapshot.test.ts.snap +18 -18
- package/src/codegen/docs.ts +104 -349
- package/src/codegen/generate.ts +2 -14
- package/src/codegen/package.ts +2 -0
- package/src/codegen/sam.ts +11 -11
- package/src/codegen/typecheck.test.ts +1 -1
- package/src/generated/index.d.ts +57174 -57070
- package/src/generated/index.ts +1356 -1351
- package/src/generated/lexicon-aws.json +3767 -3706
- package/src/import/generator.test.ts +5 -5
- package/src/import/generator.ts +4 -4
- package/src/import/roundtrip-fixtures.test.ts +8 -28
- package/src/import/roundtrip.test.ts +5 -5
- package/src/integration.test.ts +21 -21
- package/src/lint/post-synth/ext001.ts +2 -4
- package/src/lint/rules/rules.test.ts +8 -8
- package/src/lint/rules/s3-encryption.ts +2 -3
- package/src/lsp/completions.ts +2 -0
- package/src/lsp/hover.ts +2 -0
- package/src/nested-stack.ts +1 -1
- package/src/plugin.test.ts +13 -15
- package/src/plugin.ts +126 -149
- package/src/serializer.test.ts +42 -43
- package/src/serializer.ts +7 -16
- package/src/spec/parse.ts +2 -2
- package/dist/skills/aws-cloudformation.md +0 -41
- package/src/codegen/rollback.test.ts +0 -80
- package/src/codegen/rollback.ts +0 -20
package/src/codegen/docs.ts
CHANGED
|
@@ -152,7 +152,7 @@ Produces this CloudFormation resource:
|
|
|
152
152
|
}
|
|
153
153
|
\`\`\`
|
|
154
154
|
|
|
155
|
-
Notice how \`dataBucket\` becomes \`DataBucket\` (PascalCase logical ID)
|
|
155
|
+
Notice how \`dataBucket\` becomes \`DataBucket\` (PascalCase logical ID). Property names like \`BucketName\` use the CloudFormation spec-native PascalCase directly.
|
|
156
156
|
|
|
157
157
|
## Resource types and naming
|
|
158
158
|
|
|
@@ -171,17 +171,13 @@ Common resources get fixed short names for stability. When two services define t
|
|
|
171
171
|
|
|
172
172
|
**Discovering available resources:** Your editor's autocomplete is the best tool — every resource is a named export from the lexicon. You can also run \`chant list\` to see all resource types, or browse the generated TypeScript types.
|
|
173
173
|
|
|
174
|
-
##
|
|
174
|
+
## Imports and cross-file references
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{{file:getting-started/src/_.ts}}
|
|
179
|
-
|
|
180
|
-
Other files import the barrel and use \`$\` to reference sibling exports:
|
|
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:
|
|
181
177
|
|
|
182
178
|
{{file:getting-started/src/data-bucket.ts}}
|
|
183
179
|
|
|
184
|
-
|
|
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.
|
|
185
181
|
|
|
186
182
|
## Parameters
|
|
187
183
|
|
|
@@ -203,23 +199,13 @@ Produces:
|
|
|
203
199
|
|
|
204
200
|
Reference parameters with \`Ref\`:
|
|
205
201
|
|
|
206
|
-
|
|
207
|
-
import { Ref } from "@intentius/chant-lexicon-aws";
|
|
208
|
-
|
|
209
|
-
export const bucket = new Bucket({
|
|
210
|
-
bucketName: Sub\`\${Ref("Environment")}-data\`,
|
|
211
|
-
});
|
|
212
|
-
\`\`\`
|
|
202
|
+
{{file:docs-snippets/src/parameter-ref.ts}}
|
|
213
203
|
|
|
214
204
|
## Outputs
|
|
215
205
|
|
|
216
206
|
Use \`output()\` to create explicit stack outputs. Cross-resource \`AttrRef\` usage is also auto-detected and promoted to outputs when needed.
|
|
217
207
|
|
|
218
|
-
|
|
219
|
-
import { output } from "@intentius/chant";
|
|
220
|
-
|
|
221
|
-
export const bucketArn = output(dataBucket.arn, "DataBucketArn");
|
|
222
|
-
\`\`\`
|
|
208
|
+
{{file:docs-snippets/src/output-explicit.ts}}
|
|
223
209
|
|
|
224
210
|
Produces:
|
|
225
211
|
|
|
@@ -235,11 +221,7 @@ Produces:
|
|
|
235
221
|
|
|
236
222
|
Runtime context values available in every template, accessed via the \`AWS\` namespace:
|
|
237
223
|
|
|
238
|
-
|
|
239
|
-
import { AWS, Sub } from "@intentius/chant-lexicon-aws";
|
|
240
|
-
|
|
241
|
-
const endpoint = Sub\`https://s3.\${AWS.Region}.\${AWS.URLSuffix}\`;
|
|
242
|
-
\`\`\`
|
|
224
|
+
{{file:docs-snippets/src/pseudo-params.ts}}
|
|
243
225
|
|
|
244
226
|
| Pseudo-parameter | Description |
|
|
245
227
|
|---|---|
|
|
@@ -280,58 +262,17 @@ The \`PolicyDocument\` interface and its supporting types:
|
|
|
280
262
|
|
|
281
263
|
Policy documents use **PascalCase keys** (\`Effect\`, \`Action\`, \`Resource\`) because they follow the IAM JSON Policy Language spec — CloudFormation passes them through to IAM as-is, unlike resource properties which are automatically converted from camelCase.
|
|
282
264
|
|
|
283
|
-
The recommended pattern is to extract policies into your \`defaults.ts\` and
|
|
284
|
-
|
|
285
|
-
\`\`\`typescript
|
|
286
|
-
// defaults.ts — shared trust policies and permission policies
|
|
287
|
-
import { Sub, AWS, type PolicyDocument } from "@intentius/chant-lexicon-aws";
|
|
288
|
-
|
|
289
|
-
export const lambdaTrustPolicy: PolicyDocument = {
|
|
290
|
-
Version: "2012-10-17",
|
|
291
|
-
Statement: [{
|
|
292
|
-
Effect: "Allow",
|
|
293
|
-
Principal: { Service: "lambda.amazonaws.com" },
|
|
294
|
-
Action: "sts:AssumeRole",
|
|
295
|
-
}],
|
|
296
|
-
};
|
|
265
|
+
The recommended pattern is to extract policies into your \`defaults.ts\` and import them directly:
|
|
297
266
|
|
|
298
|
-
|
|
299
|
-
Statement: [{
|
|
300
|
-
Effect: "Allow",
|
|
301
|
-
Action: ["s3:GetObject", "s3:ListBucket"],
|
|
302
|
-
Resource: "*",
|
|
303
|
-
}],
|
|
304
|
-
};
|
|
305
|
-
\`\`\`
|
|
267
|
+
{{file:docs-snippets/src/policy-trust.ts}}
|
|
306
268
|
|
|
307
269
|
Then reference them from resource files:
|
|
308
270
|
|
|
309
|
-
|
|
310
|
-
// role.ts
|
|
311
|
-
import * as _ from "./_";
|
|
312
|
-
|
|
313
|
-
export const functionRole = new _.Role({
|
|
314
|
-
assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
export const readPolicy = new _.ManagedPolicy({
|
|
318
|
-
policyDocument: _.$.s3ReadPolicy,
|
|
319
|
-
roles: [_.$.functionRole],
|
|
320
|
-
});
|
|
321
|
-
\`\`\`
|
|
271
|
+
{{file:docs-snippets/src/policy-role.ts}}
|
|
322
272
|
|
|
323
273
|
For scoped resource ARNs, use \`Sub\` in the policy constant:
|
|
324
274
|
|
|
325
|
-
|
|
326
|
-
// defaults.ts
|
|
327
|
-
export const bucketWritePolicy: PolicyDocument = {
|
|
328
|
-
Statement: [{
|
|
329
|
-
Effect: "Allow",
|
|
330
|
-
Action: ["s3:PutObject"],
|
|
331
|
-
Resource: Sub\`arn:aws:s3:::\${AWS.StackName}-data/*\`,
|
|
332
|
-
}],
|
|
333
|
-
};
|
|
334
|
-
\`\`\`
|
|
275
|
+
{{file:docs-snippets/src/policy-scoped.ts}}
|
|
335
276
|
|
|
336
277
|
The \`IamPolicyPrincipal\` type supports all principal forms — wildcard (\`"*"\`), AWS accounts, services, and federated providers:
|
|
337
278
|
|
|
@@ -361,44 +302,17 @@ CloudFormation \`Conditions\` blocks are recognized by the serializer when impor
|
|
|
361
302
|
|
|
362
303
|
CloudFormation Mappings are a static lookup mechanism. In chant, use TypeScript objects instead — they're evaluated at build time and produce the same result:
|
|
363
304
|
|
|
364
|
-
|
|
365
|
-
const regionAMIs: Record<string, string> = {
|
|
366
|
-
"us-east-1": "ami-12345678",
|
|
367
|
-
"us-west-2": "ami-87654321",
|
|
368
|
-
"eu-west-1": "ami-abcdef01",
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
// Use directly in resource properties
|
|
372
|
-
export const server = new Instance({
|
|
373
|
-
imageId: regionAMIs["us-east-1"],
|
|
374
|
-
instanceType: "t3.micro",
|
|
375
|
-
});
|
|
376
|
-
\`\`\`
|
|
305
|
+
{{file:docs-snippets/src/mappings.ts}}
|
|
377
306
|
|
|
378
307
|
For deploy-time region lookups, combine \`AWS.Region\` with \`If\` or use \`Fn::Sub\` with SSM parameter store references.
|
|
379
308
|
|
|
380
309
|
## Nested stacks
|
|
381
310
|
|
|
382
|
-
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
|
|
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:
|
|
383
312
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
export const vpcId = stackOutput(_.$.vpc.vpcId);
|
|
388
|
-
export const subnetId = stackOutput(_.$.subnet.subnetId);
|
|
389
|
-
export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId);
|
|
390
|
-
|
|
391
|
-
// Parent references the child project
|
|
392
|
-
// src/app.ts
|
|
393
|
-
const network = _.nestedStack("network", import.meta.dir + "/network");
|
|
394
|
-
|
|
395
|
-
export const handler = new _.Function({
|
|
396
|
-
vpcConfig: {
|
|
397
|
-
subnetIds: [network.outputs.subnetId], // cross-stack ref
|
|
398
|
-
securityGroupIds: [network.outputs.lambdaSgId],
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
\`\`\`
|
|
313
|
+
{{file:nested-stacks/src/network/outputs.ts}}
|
|
314
|
+
|
|
315
|
+
{{file:nested-stacks/src/app.ts}}
|
|
402
316
|
|
|
403
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.
|
|
404
318
|
|
|
@@ -412,14 +326,7 @@ Tags are standard CloudFormation \`Key\`/\`Value\` arrays. Pass them on any reso
|
|
|
412
326
|
|
|
413
327
|
To apply tags across all members of a composite, use [\`propagate\`](./composites#propagate--shared-properties):
|
|
414
328
|
|
|
415
|
-
|
|
416
|
-
import { propagate } from "@intentius/chant";
|
|
417
|
-
|
|
418
|
-
export const api = propagate(
|
|
419
|
-
LambdaApi({ name: "myApi", code: lambdaCode }),
|
|
420
|
-
{ tags: [{ key: "env", value: "prod" }] },
|
|
421
|
-
);
|
|
422
|
-
\`\`\``,
|
|
329
|
+
{{file:docs-snippets/src/propagate.ts}}`,
|
|
423
330
|
},
|
|
424
331
|
{
|
|
425
332
|
slug: "intrinsics",
|
|
@@ -435,18 +342,7 @@ Here is a complete example using all intrinsic functions:
|
|
|
435
342
|
|
|
436
343
|
Tagged template literal that produces \`Fn::Sub\`. The most common intrinsic — use it for dynamic naming with pseudo-parameters and attribute references:
|
|
437
344
|
|
|
438
|
-
|
|
439
|
-
// Simple pseudo-parameter substitution
|
|
440
|
-
const bucketName = Sub\`\${AWS.StackName}-data\`;
|
|
441
|
-
// → { "Fn::Sub": "\${AWS::StackName}-data" }
|
|
442
|
-
|
|
443
|
-
// Multiple pseudo-parameters
|
|
444
|
-
const arn = Sub\`arn:aws:s3:::\${AWS.AccountId}:\${AWS.Region}:*\`;
|
|
445
|
-
|
|
446
|
-
// With resource attribute references
|
|
447
|
-
const url = Sub\`https://\${bucket.domainName}/path\`;
|
|
448
|
-
// → { "Fn::Sub": "https://\${DataBucket.DomainName}/path" }
|
|
449
|
-
\`\`\`
|
|
345
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:3-5}}
|
|
450
346
|
|
|
451
347
|
\`Sub\` is a tagged template — use it with backticks, not as a function call.
|
|
452
348
|
|
|
@@ -454,89 +350,45 @@ const url = Sub\`https://\${bucket.domainName}/path\`;
|
|
|
454
350
|
|
|
455
351
|
References a resource's physical ID or a parameter's value:
|
|
456
352
|
|
|
457
|
-
|
|
458
|
-
// Reference a parameter
|
|
459
|
-
const envRef = Ref("Environment");
|
|
460
|
-
// → { "Ref": "Environment" }
|
|
461
|
-
|
|
462
|
-
// Reference a resource (returns its physical ID)
|
|
463
|
-
const bucketRef = Ref("DataBucket");
|
|
464
|
-
// → { "Ref": "DataBucket" }
|
|
465
|
-
\`\`\`
|
|
353
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:7-9}}
|
|
466
354
|
|
|
467
|
-
In most cases you don't need \`Ref\` directly — the serializer automatically generates \`Ref\` when you reference
|
|
355
|
+
In most cases you don't need \`Ref\` directly — the serializer automatically generates \`Ref\` when you reference an imported resource (e.g. \`dataBucket\` imported from another file).
|
|
468
356
|
|
|
469
357
|
## \`GetAtt\` — resource attributes
|
|
470
358
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
\`\`\`typescript
|
|
474
|
-
// Explicit GetAtt
|
|
475
|
-
const bucketArn = GetAtt("DataBucket", "Arn");
|
|
476
|
-
// → { "Fn::GetAtt": ["DataBucket", "Arn"] }
|
|
477
|
-
\`\`\`
|
|
478
|
-
|
|
479
|
-
**Preferred:** Use AttrRef directly via the resource's typed properties. When you write \`$.dataBucket.arn\`, the serializer automatically emits \`Fn::GetAtt\`. Explicit \`GetAtt\` is only needed for dynamic or imported resource names.
|
|
359
|
+
**Preferred:** Use AttrRef directly via the resource's typed properties. When you write \`dataBucket.arn\` (imported from the file that defines it), the serializer automatically emits \`Fn::GetAtt\`. Explicit \`GetAtt\` is only needed for dynamic or imported resource names.
|
|
480
360
|
|
|
481
361
|
## \`If\` — conditional values
|
|
482
362
|
|
|
483
363
|
Returns one of two values based on a condition:
|
|
484
364
|
|
|
485
|
-
|
|
486
|
-
const value = If("IsProduction", "prod-value", "dev-value");
|
|
487
|
-
// → { "Fn::If": ["IsProduction", "prod-value", "dev-value"] }
|
|
488
|
-
\`\`\`
|
|
489
|
-
|
|
490
|
-
Use with \`AWS.NoValue\` to conditionally omit a property:
|
|
365
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:11-12}}
|
|
491
366
|
|
|
492
|
-
|
|
493
|
-
export const bucket = new Bucket({
|
|
494
|
-
bucketName: "my-bucket",
|
|
495
|
-
accelerateConfiguration: If("EnableAcceleration",
|
|
496
|
-
{ accelerationStatus: "Enabled" },
|
|
497
|
-
AWS.NoValue,
|
|
498
|
-
),
|
|
499
|
-
});
|
|
500
|
-
\`\`\`
|
|
367
|
+
Use with \`AWS.NoValue\` to conditionally omit a property — see [Conditions](#conditions) on the CloudFormation Concepts page.
|
|
501
368
|
|
|
502
369
|
## \`Join\` — join values
|
|
503
370
|
|
|
504
371
|
Joins values with a delimiter:
|
|
505
372
|
|
|
506
|
-
|
|
507
|
-
const joined = Join("-", ["prefix", AWS.StackName, "suffix"]);
|
|
508
|
-
// → { "Fn::Join": ["-", ["prefix", { "Ref": "AWS::StackName" }, "suffix"]] }
|
|
509
|
-
\`\`\`
|
|
373
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:14-15}}
|
|
510
374
|
|
|
511
375
|
## \`Select\` — select by index
|
|
512
376
|
|
|
513
377
|
Selects a value from a list by index:
|
|
514
378
|
|
|
515
|
-
|
|
516
|
-
const first = Select(0, Split(",", "a,b,c"));
|
|
517
|
-
// → { "Fn::Select": [0, { "Fn::Split": [",", "a,b,c"] }] }
|
|
518
|
-
\`\`\`
|
|
379
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:17-18}}
|
|
519
380
|
|
|
520
381
|
## \`Split\` — split string
|
|
521
382
|
|
|
522
383
|
Splits a string by a delimiter:
|
|
523
384
|
|
|
524
|
-
|
|
525
|
-
const parts = Split(",", "a,b,c");
|
|
526
|
-
// → { "Fn::Split": [",", "a,b,c"] }
|
|
527
|
-
\`\`\`
|
|
385
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:20-21}}
|
|
528
386
|
|
|
529
387
|
## \`Base64\` — encode to Base64
|
|
530
388
|
|
|
531
389
|
Encodes a string to Base64, commonly used for EC2 user data:
|
|
532
390
|
|
|
533
|
-
|
|
534
|
-
const userData = Base64(Sub\`#!/bin/bash
|
|
535
|
-
echo "Stack: \${AWS.StackName}"
|
|
536
|
-
yum update -y
|
|
537
|
-
\`);
|
|
538
|
-
// → { "Fn::Base64": { "Fn::Sub": "..." } }
|
|
539
|
-
\`\`\``,
|
|
391
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:23-27}}`,
|
|
540
392
|
},
|
|
541
393
|
{
|
|
542
394
|
slug: "composites",
|
|
@@ -544,46 +396,11 @@ yum update -y
|
|
|
544
396
|
description: "Composite resources, withDefaults presets, and propagate in the AWS CloudFormation lexicon",
|
|
545
397
|
content: `Composites group related resources into reusable factories. See also the core [Composite Resources](/guide/composite-resources/) guide.
|
|
546
398
|
|
|
547
|
-
|
|
548
|
-
import * as _ from "./_";
|
|
549
|
-
|
|
550
|
-
export const LambdaApi = _.Composite<LambdaApiProps>((props) => {
|
|
551
|
-
const role = new _.Role({
|
|
552
|
-
assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
|
|
553
|
-
managedPolicyArns: [_.$.lambdaBasicExecutionArn],
|
|
554
|
-
policies: props.policies,
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
const func = new _.Function({
|
|
558
|
-
functionName: props.name,
|
|
559
|
-
runtime: props.runtime,
|
|
560
|
-
handler: props.handler,
|
|
561
|
-
code: props.code,
|
|
562
|
-
role: role.arn,
|
|
563
|
-
timeout: props.timeout,
|
|
564
|
-
memorySize: props.memorySize,
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
const permission = new _.Permission({
|
|
568
|
-
functionName: func.arn,
|
|
569
|
-
action: "lambda:InvokeFunction",
|
|
570
|
-
principal: "apigateway.amazonaws.com",
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
return { role, func, permission };
|
|
574
|
-
}, "LambdaApi");
|
|
575
|
-
\`\`\`
|
|
399
|
+
{{file:advanced/src/lambda-api.ts}}
|
|
576
400
|
|
|
577
401
|
Instantiate and export:
|
|
578
402
|
|
|
579
|
-
|
|
580
|
-
export const healthApi = LambdaApi({
|
|
581
|
-
name: Sub\`\${AWS.StackName}-health\`,
|
|
582
|
-
runtime: "nodejs20.x",
|
|
583
|
-
handler: "index.handler",
|
|
584
|
-
code: { zipFile: \`exports.handler = async () => ({ statusCode: 200 });\` },
|
|
585
|
-
});
|
|
586
|
-
\`\`\`
|
|
403
|
+
{{file:advanced/src/health-api.ts}}
|
|
587
404
|
|
|
588
405
|
During build, composites expand to flat CloudFormation resources: \`healthApi_role\` → \`HealthApiRole\`, \`healthApi_func\` → \`HealthApiFunc\`, \`healthApi_permission\` → \`HealthApiPermission\`.
|
|
589
406
|
|
|
@@ -591,25 +408,7 @@ During build, composites expand to flat CloudFormation resources: \`healthApi_ro
|
|
|
591
408
|
|
|
592
409
|
Wrap a composite with pre-applied defaults. Defaulted props become optional:
|
|
593
410
|
|
|
594
|
-
|
|
595
|
-
import { withDefaults } from "@intentius/chant";
|
|
596
|
-
|
|
597
|
-
const SecureApi = withDefaults(LambdaApi, {
|
|
598
|
-
runtime: "nodejs20.x",
|
|
599
|
-
handler: "index.handler",
|
|
600
|
-
timeout: 10,
|
|
601
|
-
memorySize: 256,
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
// Only name and code are required now
|
|
605
|
-
export const healthApi = SecureApi({
|
|
606
|
-
name: Sub\`\${AWS.StackName}-health\`,
|
|
607
|
-
code: { zipFile: \`exports.handler = async () => ({ statusCode: 200 });\` },
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
// Composable — stack defaults on top of defaults
|
|
611
|
-
const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 });
|
|
612
|
-
\`\`\`
|
|
411
|
+
{{file:docs-snippets/src/with-defaults.ts}}
|
|
613
412
|
|
|
614
413
|
\`withDefaults\` preserves the original composite's identity — same \`_id\` and \`compositeName\`, no new registry entry.
|
|
615
414
|
|
|
@@ -617,15 +416,7 @@ const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 })
|
|
|
617
416
|
|
|
618
417
|
Attach properties that merge into every member during expansion:
|
|
619
418
|
|
|
620
|
-
|
|
621
|
-
import { propagate } from "@intentius/chant";
|
|
622
|
-
|
|
623
|
-
export const api = propagate(
|
|
624
|
-
LambdaApi({ name: "myApi", code: lambdaCode }),
|
|
625
|
-
{ tags: [{ key: "env", value: "prod" }] },
|
|
626
|
-
);
|
|
627
|
-
// role, func, and permission all receive the env tag
|
|
628
|
-
\`\`\`
|
|
419
|
+
{{file:docs-snippets/src/propagate.ts}}
|
|
629
420
|
|
|
630
421
|
Merge semantics:
|
|
631
422
|
- **Scalars** — member-specific value wins over shared
|
|
@@ -634,17 +425,9 @@ Merge semantics:
|
|
|
634
425
|
|
|
635
426
|
## Nested stacks
|
|
636
427
|
|
|
637
|
-
When resources should produce a separate CloudFormation template instead of expanding into the parent, use a **child project** — a subdirectory
|
|
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()\`:
|
|
638
429
|
|
|
639
|
-
|
|
640
|
-
// src/app.ts — parent references child project directory
|
|
641
|
-
const network = _.nestedStack("network", import.meta.dir + "/network");
|
|
642
|
-
|
|
643
|
-
// Cross-stack reference via outputs proxy
|
|
644
|
-
export const handler = new _.Function({
|
|
645
|
-
vpcConfig: { subnetIds: [network.outputs.subnetId] },
|
|
646
|
-
});
|
|
647
|
-
\`\`\`
|
|
430
|
+
{{file:nested-stacks/src/app.ts}}
|
|
648
431
|
|
|
649
432
|
See [Nested Stacks](./nested-stacks) for the full guide.`,
|
|
650
433
|
},
|
|
@@ -652,18 +435,16 @@ See [Nested Stacks](./nested-stacks) for the full guide.`,
|
|
|
652
435
|
slug: "nested-stacks",
|
|
653
436
|
title: "Nested Stacks",
|
|
654
437
|
description: "Splitting resources into child CloudFormation templates with automatic cross-stack reference wiring",
|
|
655
|
-
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
|
|
438
|
+
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.
|
|
656
439
|
|
|
657
440
|
## Project structure
|
|
658
441
|
|
|
659
|
-
A nested stack is a child project — a subdirectory with its own
|
|
442
|
+
A nested stack is a child project — a subdirectory with its own resource files and explicit \`stackOutput()\` declarations:
|
|
660
443
|
|
|
661
444
|
\`\`\`
|
|
662
445
|
src/
|
|
663
|
-
_.ts # parent barrel
|
|
664
446
|
app.ts # parent resources
|
|
665
447
|
network/ # ← child project (nested stack)
|
|
666
|
-
_.ts # its own barrel
|
|
667
448
|
vpc.ts # VPC, subnet, internet gateway, routing
|
|
668
449
|
security.ts # security group for Lambda
|
|
669
450
|
outputs.ts # declares cross-stack outputs
|
|
@@ -673,15 +454,7 @@ src/
|
|
|
673
454
|
|
|
674
455
|
Use \`stackOutput()\` to mark values that the parent can reference. Each \`stackOutput()\` becomes an entry in the child template's \`Outputs\` section:
|
|
675
456
|
|
|
676
|
-
|
|
677
|
-
// src/network/outputs.ts
|
|
678
|
-
import * as _ from "./_";
|
|
679
|
-
import { stackOutput } from "@intentius/chant";
|
|
680
|
-
|
|
681
|
-
export const vpcId = stackOutput(_.$.vpc.vpcId, { description: "VPC ID" });
|
|
682
|
-
export const subnetId = stackOutput(_.$.subnet.subnetId, { description: "Public subnet ID" });
|
|
683
|
-
export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId, { description: "Lambda security group ID" });
|
|
684
|
-
\`\`\`
|
|
457
|
+
{{file:nested-stacks/src/network/outputs.ts}}
|
|
685
458
|
|
|
686
459
|
The child can be built independently:
|
|
687
460
|
|
|
@@ -694,24 +467,7 @@ chant build src/network/ -o network.json
|
|
|
694
467
|
|
|
695
468
|
Use \`nestedStack()\` in the parent to reference a child project directory. It returns an object with an \`outputs\` proxy for cross-stack references:
|
|
696
469
|
|
|
697
|
-
|
|
698
|
-
// src/app.ts
|
|
699
|
-
import * as _ from "./_";
|
|
700
|
-
|
|
701
|
-
const network = _.nestedStack("network", import.meta.dir + "/network");
|
|
702
|
-
|
|
703
|
-
export const handler = new _.Function({
|
|
704
|
-
functionName: _.Sub\`\${_.AWS.StackName}-handler\`,
|
|
705
|
-
runtime: "nodejs20.x",
|
|
706
|
-
handler: "index.handler",
|
|
707
|
-
role: _.Ref("LambdaExecutionRole"),
|
|
708
|
-
code: { zipFile: "exports.handler = async () => ({ statusCode: 200 });" },
|
|
709
|
-
vpcConfig: {
|
|
710
|
-
subnetIds: [network.outputs.subnetId],
|
|
711
|
-
securityGroupIds: [network.outputs.lambdaSgId],
|
|
712
|
-
},
|
|
713
|
-
});
|
|
714
|
-
\`\`\`
|
|
470
|
+
{{file:nested-stacks/src/app.ts}}
|
|
715
471
|
|
|
716
472
|
\`network.outputs.subnetId\` produces a \`NestedStackOutputRef\` that serializes to \`{ "Fn::GetAtt": ["Network", "Outputs.SubnetId"] }\`.
|
|
717
473
|
|
|
@@ -759,7 +515,9 @@ Child templates also receive the \`TemplateBasePath\` parameter so it propagates
|
|
|
759
515
|
Pass CloudFormation Parameters to child stacks with the \`parameters\` option:
|
|
760
516
|
|
|
761
517
|
\`\`\`typescript
|
|
762
|
-
|
|
518
|
+
import { nestedStack } from "@intentius/chant-lexicon-aws";
|
|
519
|
+
|
|
520
|
+
const network = nestedStack("network", import.meta.dirname + "/network", {
|
|
763
521
|
parameters: { Environment: "prod", CidrBlock: "10.0.0.0/16" },
|
|
764
522
|
});
|
|
765
523
|
\`\`\`
|
|
@@ -770,16 +528,12 @@ Child projects can themselves reference grandchild projects. Each level produces
|
|
|
770
528
|
|
|
771
529
|
\`\`\`
|
|
772
530
|
src/
|
|
773
|
-
_.ts
|
|
774
531
|
app.ts
|
|
775
532
|
infra/
|
|
776
|
-
_.ts
|
|
777
533
|
network/
|
|
778
|
-
_.ts
|
|
779
534
|
vpc.ts
|
|
780
535
|
outputs.ts
|
|
781
536
|
database/
|
|
782
|
-
_.ts
|
|
783
537
|
cluster.ts
|
|
784
538
|
outputs.ts
|
|
785
539
|
\`\`\`
|
|
@@ -941,67 +695,17 @@ See also [Custom Lint Rules](./custom-rules) for writing project-specific rules.
|
|
|
941
695
|
|
|
942
696
|
## Anatomy of a lint rule
|
|
943
697
|
|
|
944
|
-
|
|
945
|
-
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
946
|
-
import * as ts from "typescript";
|
|
947
|
-
|
|
948
|
-
export const apiTimeoutRule: LintRule = {
|
|
949
|
-
id: "WAW012", // unique ID (WAW = AWS-specific prefix)
|
|
950
|
-
severity: "error", // "error" | "warning"
|
|
951
|
-
category: "correctness", // "correctness" | "style" | "security"
|
|
952
|
-
|
|
953
|
-
check(context: LintContext): LintDiagnostic[] {
|
|
954
|
-
const { sourceFile } = context;
|
|
955
|
-
const diagnostics: LintDiagnostic[] = [];
|
|
956
|
-
|
|
957
|
-
function visit(node: ts.Node): void {
|
|
958
|
-
// Walk the AST looking for violations
|
|
959
|
-
if (ts.isCallExpression(node)) {
|
|
960
|
-
// Inspect arguments, report diagnostics
|
|
961
|
-
}
|
|
962
|
-
ts.forEachChild(node, visit);
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
visit(sourceFile);
|
|
966
|
-
return diagnostics;
|
|
967
|
-
},
|
|
968
|
-
};
|
|
969
|
-
\`\`\`
|
|
698
|
+
The advanced example includes a full custom rule implementation:
|
|
970
699
|
|
|
971
|
-
|
|
700
|
+
{{file:advanced/src/lint/api-timeout.ts}}
|
|
972
701
|
|
|
973
|
-
The
|
|
974
|
-
|
|
975
|
-
\`\`\`typescript
|
|
976
|
-
const API_FACTORIES = new Set(["LambdaApi", "SecureApi", "HighMemoryApi"]);
|
|
977
|
-
|
|
978
|
-
export const apiTimeoutRule: LintRule = {
|
|
979
|
-
id: "WAW012",
|
|
980
|
-
severity: "error",
|
|
981
|
-
category: "correctness",
|
|
982
|
-
|
|
983
|
-
check(context: LintContext): LintDiagnostic[] {
|
|
984
|
-
// Walks AST for calls to API factory functions,
|
|
985
|
-
// inspects the timeout property, reports if > 29
|
|
986
|
-
},
|
|
987
|
-
};
|
|
988
|
-
\`\`\`
|
|
702
|
+
The \`check\` function receives a \`LintContext\` containing the TypeScript \`sourceFile\` and returns an array of diagnostics with file, line, column, and message.
|
|
989
703
|
|
|
990
704
|
## Registering custom rules
|
|
991
705
|
|
|
992
706
|
Add a \`chant.config.ts\` to your project:
|
|
993
707
|
|
|
994
|
-
|
|
995
|
-
export default {
|
|
996
|
-
lint: {
|
|
997
|
-
extends: ["@intentius/chant/lint/presets/strict"],
|
|
998
|
-
rules: {
|
|
999
|
-
COR004: "off", // disable a built-in rule
|
|
1000
|
-
},
|
|
1001
|
-
plugins: ["./lint/api-timeout.ts"], // load custom rules
|
|
1002
|
-
},
|
|
1003
|
-
};
|
|
1004
|
-
\`\`\`
|
|
708
|
+
{{file:advanced/src/chant.config.ts}}
|
|
1005
709
|
|
|
1006
710
|
The \`plugins\` array accepts relative paths. Each plugin module should export a \`LintRule\` object.`,
|
|
1007
711
|
},
|
|
@@ -1025,9 +729,8 @@ bun test # runs the example's tests
|
|
|
1025
729
|
|
|
1026
730
|
\`\`\`
|
|
1027
731
|
src/
|
|
1028
|
-
├── _.ts # Barrel — re-exports lexicon + auto-discovers siblings
|
|
1029
732
|
├── defaults.ts # Shared config: encryption, versioning, public access block
|
|
1030
|
-
├── data-bucket.ts # S3 bucket using
|
|
733
|
+
├── data-bucket.ts # S3 bucket using shared defaults
|
|
1031
734
|
├── logs-bucket.ts # S3 bucket for access logs
|
|
1032
735
|
├── role.ts # IAM role with Lambda assume-role policy
|
|
1033
736
|
└── handler.ts # Lambda function referencing role and bucket
|
|
@@ -1035,9 +738,9 @@ src/
|
|
|
1035
738
|
|
|
1036
739
|
**Patterns demonstrated:**
|
|
1037
740
|
|
|
1038
|
-
1. **
|
|
1039
|
-
2. **Shared defaults** — \`defaults.ts\` exports reusable property objects (\`
|
|
1040
|
-
3. **Cross-resource references** — \`
|
|
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
|
|
1041
744
|
4. **Intrinsics** — \`Sub\` tagged templates with pseudo-parameters for dynamic naming
|
|
1042
745
|
|
|
1043
746
|
{{file:getting-started/src/handler.ts}}
|
|
@@ -1048,7 +751,6 @@ src/
|
|
|
1048
751
|
|
|
1049
752
|
\`\`\`
|
|
1050
753
|
src/
|
|
1051
|
-
├── _.ts # Barrel + re-exports Composite from core
|
|
1052
754
|
├── chant.config.ts # Lint config: strict preset + custom plugin
|
|
1053
755
|
├── defaults.ts # Encryption, versioning, access block, Lambda trust policy
|
|
1054
756
|
├── data-bucket.ts # S3 bucket
|
|
@@ -1076,10 +778,8 @@ The example produces 10 CloudFormation resources: 1 S3 bucket + 3 composites ×
|
|
|
1076
778
|
|
|
1077
779
|
\`\`\`
|
|
1078
780
|
src/
|
|
1079
|
-
├── _.ts # Parent barrel
|
|
1080
781
|
├── app.ts # Lambda function (references network outputs)
|
|
1081
782
|
└── network/ # Child project (nested stack)
|
|
1082
|
-
├── _.ts # Child barrel
|
|
1083
783
|
├── vpc.ts # VPC, subnet, internet gateway, route table
|
|
1084
784
|
├── security.ts # Security group for Lambda
|
|
1085
785
|
└── outputs.ts # stackOutput() declarations
|
|
@@ -1087,7 +787,7 @@ src/
|
|
|
1087
787
|
|
|
1088
788
|
**Patterns demonstrated:**
|
|
1089
789
|
|
|
1090
|
-
1. **Child project** — \`network/\` is a separate project directory with its own
|
|
790
|
+
1. **Child project** — \`network/\` is a separate project directory with its own resources and \`stackOutput()\` exports
|
|
1091
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
|
|
1092
792
|
3. **Multi-file output** — build produces \`template.json\` (parent) and \`network.template.json\` (child)
|
|
1093
793
|
4. **TemplateBasePath** — auto-generated parameter for configuring child template URLs per environment
|
|
@@ -1098,6 +798,61 @@ src/
|
|
|
1098
798
|
|
|
1099
799
|
See [Nested Stacks](./nested-stacks) for the full guide.`,
|
|
1100
800
|
},
|
|
801
|
+
{
|
|
802
|
+
slug: "skills",
|
|
803
|
+
title: "AI Skills",
|
|
804
|
+
description: "AI agent skills bundled with the AWS CloudFormation lexicon",
|
|
805
|
+
content: `The AWS lexicon ships an AI skill called **chant-aws** that teaches AI coding agents (like Claude Code) how to build, validate, and deploy CloudFormation templates from a chant project.
|
|
806
|
+
|
|
807
|
+
## What are skills?
|
|
808
|
+
|
|
809
|
+
Skills are structured markdown documents bundled with a lexicon. When an AI agent works in a chant project, it discovers and loads relevant skills automatically — giving it operational knowledge about the deployment workflow without requiring the user to explain each step.
|
|
810
|
+
|
|
811
|
+
## Installation
|
|
812
|
+
|
|
813
|
+
When you scaffold a new project with \`chant init --lexicon aws\`, the skill is installed to \`.claude/skills/chant-aws/SKILL.md\` for automatic discovery by Claude Code.
|
|
814
|
+
|
|
815
|
+
For existing projects, create the file manually:
|
|
816
|
+
|
|
817
|
+
\`\`\`
|
|
818
|
+
.claude/
|
|
819
|
+
skills/
|
|
820
|
+
chant-aws/
|
|
821
|
+
SKILL.md # skill content (see below)
|
|
822
|
+
\`\`\`
|
|
823
|
+
|
|
824
|
+
## Skill: chant-aws
|
|
825
|
+
|
|
826
|
+
The \`chant-aws\` skill covers the full deployment lifecycle:
|
|
827
|
+
|
|
828
|
+
- **Build** — \`chant build src/ --output stack.json\`
|
|
829
|
+
- **Validate** — \`chant lint src/\` + \`aws cloudformation validate-template\`
|
|
830
|
+
- **Deploy** — \`aws cloudformation deploy\` with capabilities
|
|
831
|
+
- **Update** — change sets for preview, or direct deploy
|
|
832
|
+
- **Delete** — \`aws cloudformation delete-stack\`
|
|
833
|
+
- **Status** — \`describe-stacks\` and \`describe-stack-events\`
|
|
834
|
+
- **Troubleshooting** — event inspection, rollback recovery, drift detection
|
|
835
|
+
|
|
836
|
+
The skill is invocable as a slash command: \`/chant-aws\`
|
|
837
|
+
|
|
838
|
+
## MCP integration
|
|
839
|
+
|
|
840
|
+
The lexicon also provides MCP (Model Context Protocol) tools and resources that AI agents can use programmatically:
|
|
841
|
+
|
|
842
|
+
| MCP tool | Description |
|
|
843
|
+
|----------|-------------|
|
|
844
|
+
| \`build\` | Build the chant project |
|
|
845
|
+
| \`lint\` | Run lint rules |
|
|
846
|
+
| \`explain\` | Summarize project resources |
|
|
847
|
+
| \`scaffold\` | Generate starter files |
|
|
848
|
+
| \`search\` | Search available resource types |
|
|
849
|
+
| \`aws:diff\` | Compare current build output against previous |
|
|
850
|
+
|
|
851
|
+
| MCP resource | Description |
|
|
852
|
+
|--------------|-------------|
|
|
853
|
+
| \`resource-catalog\` | JSON list of all supported CloudFormation resource types |
|
|
854
|
+
| \`examples/basic-stack\` | Example stack with S3 bucket and IAM role |`,
|
|
855
|
+
},
|
|
1101
856
|
],
|
|
1102
857
|
};
|
|
1103
858
|
|