@intentius/chant-lexicon-aws 0.0.4 → 0.0.6
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 +11 -11
- package/dist/manifest.json +7 -1
- package/dist/meta.json +340 -320
- package/dist/skills/aws-cloudformation.md +1 -1
- package/dist/types/index.d.ts +39 -0
- package/package.json +2 -2
- package/src/codegen/docs.ts +80 -473
- package/src/codegen/generate.ts +1 -1
- package/src/codegen/sam.ts +11 -11
- package/src/generated/index.d.ts +39 -0
- package/src/generated/index.ts +4 -0
- package/src/generated/lexicon-aws.json +340 -320
- package/src/import/generator.test.ts +117 -6
- package/src/import/generator.ts +178 -62
- package/src/import/parser.ts +1 -1
- package/src/import/roundtrip-fixtures.test.ts +72 -11
- package/src/import/roundtrip.test.ts +6 -5
- package/src/index.ts +8 -1
- package/src/intrinsics.ts +27 -0
- package/src/parameter.ts +16 -0
- package/src/plugin.test.ts +1 -1
- package/src/plugin.ts +20 -48
- package/src/serializer.test.ts +4 -19
- package/src/spec/parse.ts +2 -2
package/src/codegen/docs.ts
CHANGED
|
@@ -125,6 +125,7 @@ export async function generateDocs(options?: { verbose?: boolean }): Promise<voi
|
|
|
125
125
|
outputFormat,
|
|
126
126
|
serviceFromType,
|
|
127
127
|
suppressPages: ["pseudo-parameters", "intrinsics", "rules"],
|
|
128
|
+
examplesDir: join(pkgDir, "examples"),
|
|
128
129
|
extraPages: [
|
|
129
130
|
{
|
|
130
131
|
slug: "cloudformation",
|
|
@@ -137,13 +138,7 @@ export async function generateDocs(options?: { verbose?: boolean }): Promise<voi
|
|
|
137
138
|
- Resolves \`AttrRef\` references to \`Fn::GetAtt\`
|
|
138
139
|
- Resolves resource references to \`Ref\` intrinsics
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
// This chant declaration...
|
|
142
|
-
export const dataBucket = new Bucket({
|
|
143
|
-
bucketName: Sub\`\${AWS.StackName}-data\`,
|
|
144
|
-
versioningConfiguration: $.versioningEnabled,
|
|
145
|
-
});
|
|
146
|
-
\`\`\`
|
|
141
|
+
{{file:getting-started/src/data-bucket.ts}}
|
|
147
142
|
|
|
148
143
|
Produces this CloudFormation resource:
|
|
149
144
|
|
|
@@ -176,43 +171,19 @@ Common resources get fixed short names for stability. When two services define t
|
|
|
176
171
|
|
|
177
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.
|
|
178
173
|
|
|
179
|
-
##
|
|
174
|
+
## Imports and cross-file references
|
|
180
175
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
\`\`\`typescript
|
|
184
|
-
// _.ts — the barrel file
|
|
185
|
-
export * from "@intentius/chant-lexicon-aws";
|
|
186
|
-
import * as core from "@intentius/chant";
|
|
187
|
-
export const $ = core.barrel(import.meta.dir);
|
|
188
|
-
\`\`\`
|
|
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:
|
|
189
177
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
\`\`\`typescript
|
|
193
|
-
// data-bucket.ts
|
|
194
|
-
import * as _ from "./_";
|
|
195
|
-
|
|
196
|
-
export const dataBucket = new _.Bucket({
|
|
197
|
-
bucketName: _.Sub\`\${_.AWS.StackName}-data\`,
|
|
198
|
-
serverSideEncryptionConfiguration: _.$.encryptionDefault, // from defaults.ts
|
|
199
|
-
});
|
|
200
|
-
\`\`\`
|
|
178
|
+
{{file:getting-started/src/data-bucket.ts}}
|
|
201
179
|
|
|
202
|
-
|
|
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.
|
|
203
181
|
|
|
204
182
|
## Parameters
|
|
205
183
|
|
|
206
184
|
CloudFormation parameters let you customize a stack at deploy time. Export a \`Parameter\` to add it to the template's \`Parameters\` section:
|
|
207
185
|
|
|
208
|
-
|
|
209
|
-
import { Parameter } from "@intentius/chant-lexicon-aws";
|
|
210
|
-
|
|
211
|
-
export const environment = new Parameter("String", {
|
|
212
|
-
description: "Deployment environment",
|
|
213
|
-
defaultValue: "dev",
|
|
214
|
-
});
|
|
215
|
-
\`\`\`
|
|
186
|
+
{{file:getting-started/src/environment.ts}}
|
|
216
187
|
|
|
217
188
|
Produces:
|
|
218
189
|
|
|
@@ -228,23 +199,13 @@ Produces:
|
|
|
228
199
|
|
|
229
200
|
Reference parameters with \`Ref\`:
|
|
230
201
|
|
|
231
|
-
|
|
232
|
-
import { Ref } from "@intentius/chant-lexicon-aws";
|
|
233
|
-
|
|
234
|
-
export const bucket = new Bucket({
|
|
235
|
-
bucketName: Sub\`\${Ref("Environment")}-data\`,
|
|
236
|
-
});
|
|
237
|
-
\`\`\`
|
|
202
|
+
{{file:docs-snippets/src/parameter-ref.ts}}
|
|
238
203
|
|
|
239
204
|
## Outputs
|
|
240
205
|
|
|
241
206
|
Use \`output()\` to create explicit stack outputs. Cross-resource \`AttrRef\` usage is also auto-detected and promoted to outputs when needed.
|
|
242
207
|
|
|
243
|
-
|
|
244
|
-
import { output } from "@intentius/chant";
|
|
245
|
-
|
|
246
|
-
export const bucketArn = output(dataBucket.arn, "DataBucketArn");
|
|
247
|
-
\`\`\`
|
|
208
|
+
{{file:docs-snippets/src/output-explicit.ts}}
|
|
248
209
|
|
|
249
210
|
Produces:
|
|
250
211
|
|
|
@@ -260,11 +221,7 @@ Produces:
|
|
|
260
221
|
|
|
261
222
|
Runtime context values available in every template, accessed via the \`AWS\` namespace:
|
|
262
223
|
|
|
263
|
-
|
|
264
|
-
import { AWS, Sub } from "@intentius/chant-lexicon-aws";
|
|
265
|
-
|
|
266
|
-
const endpoint = Sub\`https://s3.\${AWS.Region}.\${AWS.URLSuffix}\`;
|
|
267
|
-
\`\`\`
|
|
224
|
+
{{file:docs-snippets/src/pseudo-params.ts}}
|
|
268
225
|
|
|
269
226
|
| Pseudo-parameter | Description |
|
|
270
227
|
|---|---|
|
|
@@ -287,13 +244,7 @@ CloudFormation automatically creates dependencies between resources when you use
|
|
|
287
244
|
|
|
288
245
|
For cases where you need an explicit dependency without a property reference, set \`dependsOn\`:
|
|
289
246
|
|
|
290
|
-
|
|
291
|
-
export const appServer = new Instance({
|
|
292
|
-
imageId: "ami-12345678",
|
|
293
|
-
instanceType: "t3.micro",
|
|
294
|
-
dependsOn: ["DatabaseCluster"],
|
|
295
|
-
});
|
|
296
|
-
\`\`\`
|
|
247
|
+
{{file:docs-snippets/src/depends-on.ts}}
|
|
297
248
|
|
|
298
249
|
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.
|
|
299
250
|
|
|
@@ -311,58 +262,17 @@ The \`PolicyDocument\` interface and its supporting types:
|
|
|
311
262
|
|
|
312
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.
|
|
313
264
|
|
|
314
|
-
The recommended pattern is to extract policies into your \`defaults.ts\` and
|
|
315
|
-
|
|
316
|
-
\`\`\`typescript
|
|
317
|
-
// defaults.ts — shared trust policies and permission policies
|
|
318
|
-
import { Sub, AWS, type PolicyDocument } from "@intentius/chant-lexicon-aws";
|
|
319
|
-
|
|
320
|
-
export const lambdaTrustPolicy: PolicyDocument = {
|
|
321
|
-
Version: "2012-10-17",
|
|
322
|
-
Statement: [{
|
|
323
|
-
Effect: "Allow",
|
|
324
|
-
Principal: { Service: "lambda.amazonaws.com" },
|
|
325
|
-
Action: "sts:AssumeRole",
|
|
326
|
-
}],
|
|
327
|
-
};
|
|
265
|
+
The recommended pattern is to extract policies into your \`defaults.ts\` and import them directly:
|
|
328
266
|
|
|
329
|
-
|
|
330
|
-
Statement: [{
|
|
331
|
-
Effect: "Allow",
|
|
332
|
-
Action: ["s3:GetObject", "s3:ListBucket"],
|
|
333
|
-
Resource: "*",
|
|
334
|
-
}],
|
|
335
|
-
};
|
|
336
|
-
\`\`\`
|
|
267
|
+
{{file:docs-snippets/src/policy-trust.ts}}
|
|
337
268
|
|
|
338
269
|
Then reference them from resource files:
|
|
339
270
|
|
|
340
|
-
|
|
341
|
-
// role.ts
|
|
342
|
-
import * as _ from "./_";
|
|
343
|
-
|
|
344
|
-
export const functionRole = new _.Role({
|
|
345
|
-
assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
export const readPolicy = new _.ManagedPolicy({
|
|
349
|
-
policyDocument: _.$.s3ReadPolicy,
|
|
350
|
-
roles: [_.$.functionRole],
|
|
351
|
-
});
|
|
352
|
-
\`\`\`
|
|
271
|
+
{{file:docs-snippets/src/policy-role.ts}}
|
|
353
272
|
|
|
354
273
|
For scoped resource ARNs, use \`Sub\` in the policy constant:
|
|
355
274
|
|
|
356
|
-
|
|
357
|
-
// defaults.ts
|
|
358
|
-
export const bucketWritePolicy: PolicyDocument = {
|
|
359
|
-
Statement: [{
|
|
360
|
-
Effect: "Allow",
|
|
361
|
-
Action: ["s3:PutObject"],
|
|
362
|
-
Resource: Sub\`arn:aws:s3:::\${AWS.StackName}-data/*\`,
|
|
363
|
-
}],
|
|
364
|
-
};
|
|
365
|
-
\`\`\`
|
|
275
|
+
{{file:docs-snippets/src/policy-scoped.ts}}
|
|
366
276
|
|
|
367
277
|
The \`IamPolicyPrincipal\` type supports all principal forms — wildcard (\`"*"\`), AWS accounts, services, and federated providers:
|
|
368
278
|
|
|
@@ -384,13 +294,7 @@ Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
|
384
294
|
|
|
385
295
|
Use the \`If\` intrinsic for conditional values within resource properties:
|
|
386
296
|
|
|
387
|
-
|
|
388
|
-
import { If } from "@intentius/chant-lexicon-aws";
|
|
389
|
-
|
|
390
|
-
export const bucket = new Bucket({
|
|
391
|
-
bucketName: If("IsProduction", "prod-data", "dev-data"),
|
|
392
|
-
});
|
|
393
|
-
\`\`\`
|
|
297
|
+
{{file:docs-snippets/src/conditions.ts}}
|
|
394
298
|
|
|
395
299
|
CloudFormation \`Conditions\` blocks are recognized by the serializer when importing existing templates. For new stacks, use TypeScript logic for build-time decisions and \`If\` for deploy-time decisions.
|
|
396
300
|
|
|
@@ -398,44 +302,17 @@ CloudFormation \`Conditions\` blocks are recognized by the serializer when impor
|
|
|
398
302
|
|
|
399
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:
|
|
400
304
|
|
|
401
|
-
|
|
402
|
-
const regionAMIs: Record<string, string> = {
|
|
403
|
-
"us-east-1": "ami-12345678",
|
|
404
|
-
"us-west-2": "ami-87654321",
|
|
405
|
-
"eu-west-1": "ami-abcdef01",
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
// Use directly in resource properties
|
|
409
|
-
export const server = new Instance({
|
|
410
|
-
imageId: regionAMIs["us-east-1"],
|
|
411
|
-
instanceType: "t3.micro",
|
|
412
|
-
});
|
|
413
|
-
\`\`\`
|
|
305
|
+
{{file:docs-snippets/src/mappings.ts}}
|
|
414
306
|
|
|
415
307
|
For deploy-time region lookups, combine \`AWS.Region\` with \`If\` or use \`Fn::Sub\` with SSM parameter store references.
|
|
416
308
|
|
|
417
309
|
## Nested stacks
|
|
418
310
|
|
|
419
|
-
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:
|
|
420
312
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
export const vpcId = stackOutput(_.$.vpc.vpcId);
|
|
425
|
-
export const subnetId = stackOutput(_.$.subnet.subnetId);
|
|
426
|
-
export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId);
|
|
427
|
-
|
|
428
|
-
// Parent references the child project
|
|
429
|
-
// src/app.ts
|
|
430
|
-
const network = _.nestedStack("network", import.meta.dir + "/network");
|
|
431
|
-
|
|
432
|
-
export const handler = new _.Function({
|
|
433
|
-
vpcConfig: {
|
|
434
|
-
subnetIds: [network.outputs.subnetId], // cross-stack ref
|
|
435
|
-
securityGroupIds: [network.outputs.lambdaSgId],
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
\`\`\`
|
|
313
|
+
{{file:nested-stacks/src/network/outputs.ts}}
|
|
314
|
+
|
|
315
|
+
{{file:nested-stacks/src/app.ts}}
|
|
439
316
|
|
|
440
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.
|
|
441
318
|
|
|
@@ -445,26 +322,11 @@ See [Nested Stacks](./nested-stacks) for the full guide.
|
|
|
445
322
|
|
|
446
323
|
Tags are standard CloudFormation \`Key\`/\`Value\` arrays. Pass them on any resource that supports tagging:
|
|
447
324
|
|
|
448
|
-
|
|
449
|
-
export const bucket = new Bucket({
|
|
450
|
-
bucketName: "my-bucket",
|
|
451
|
-
tags: [
|
|
452
|
-
{ key: "Environment", value: "production" },
|
|
453
|
-
{ key: "Team", value: "platform" },
|
|
454
|
-
],
|
|
455
|
-
});
|
|
456
|
-
\`\`\`
|
|
325
|
+
{{file:docs-snippets/src/tagging.ts}}
|
|
457
326
|
|
|
458
327
|
To apply tags across all members of a composite, use [\`propagate\`](./composites#propagate--shared-properties):
|
|
459
328
|
|
|
460
|
-
|
|
461
|
-
import { propagate } from "@intentius/chant";
|
|
462
|
-
|
|
463
|
-
export const api = propagate(
|
|
464
|
-
LambdaApi({ name: "myApi", code: lambdaCode }),
|
|
465
|
-
{ tags: [{ key: "env", value: "prod" }] },
|
|
466
|
-
);
|
|
467
|
-
\`\`\``,
|
|
329
|
+
{{file:docs-snippets/src/propagate.ts}}`,
|
|
468
330
|
},
|
|
469
331
|
{
|
|
470
332
|
slug: "intrinsics",
|
|
@@ -472,26 +334,15 @@ export const api = propagate(
|
|
|
472
334
|
description: "CloudFormation intrinsic functions and their chant syntax",
|
|
473
335
|
content: `CloudFormation intrinsic functions are available as imports from the lexicon. They produce the corresponding \`Fn::\` calls in the serialized template.
|
|
474
336
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
337
|
+
Here is a complete example using all intrinsic functions:
|
|
338
|
+
|
|
339
|
+
{{file:docs-snippets/src/intrinsics.ts}}
|
|
478
340
|
|
|
479
341
|
## \`Sub\` — string substitution
|
|
480
342
|
|
|
481
343
|
Tagged template literal that produces \`Fn::Sub\`. The most common intrinsic — use it for dynamic naming with pseudo-parameters and attribute references:
|
|
482
344
|
|
|
483
|
-
|
|
484
|
-
// Simple pseudo-parameter substitution
|
|
485
|
-
const bucketName = Sub\`\${AWS.StackName}-data\`;
|
|
486
|
-
// → { "Fn::Sub": "\${AWS::StackName}-data" }
|
|
487
|
-
|
|
488
|
-
// Multiple pseudo-parameters
|
|
489
|
-
const arn = Sub\`arn:aws:s3:::\${AWS.AccountId}:\${AWS.Region}:*\`;
|
|
490
|
-
|
|
491
|
-
// With resource attribute references
|
|
492
|
-
const url = Sub\`https://\${bucket.domainName}/path\`;
|
|
493
|
-
// → { "Fn::Sub": "https://\${DataBucket.DomainName}/path" }
|
|
494
|
-
\`\`\`
|
|
345
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:3-5}}
|
|
495
346
|
|
|
496
347
|
\`Sub\` is a tagged template — use it with backticks, not as a function call.
|
|
497
348
|
|
|
@@ -499,89 +350,45 @@ const url = Sub\`https://\${bucket.domainName}/path\`;
|
|
|
499
350
|
|
|
500
351
|
References a resource's physical ID or a parameter's value:
|
|
501
352
|
|
|
502
|
-
|
|
503
|
-
// Reference a parameter
|
|
504
|
-
const envRef = Ref("Environment");
|
|
505
|
-
// → { "Ref": "Environment" }
|
|
506
|
-
|
|
507
|
-
// Reference a resource (returns its physical ID)
|
|
508
|
-
const bucketRef = Ref("DataBucket");
|
|
509
|
-
// → { "Ref": "DataBucket" }
|
|
510
|
-
\`\`\`
|
|
353
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:7-9}}
|
|
511
354
|
|
|
512
|
-
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).
|
|
513
356
|
|
|
514
357
|
## \`GetAtt\` — resource attributes
|
|
515
358
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
\`\`\`typescript
|
|
519
|
-
// Explicit GetAtt
|
|
520
|
-
const bucketArn = GetAtt("DataBucket", "Arn");
|
|
521
|
-
// → { "Fn::GetAtt": ["DataBucket", "Arn"] }
|
|
522
|
-
\`\`\`
|
|
523
|
-
|
|
524
|
-
**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.
|
|
525
360
|
|
|
526
361
|
## \`If\` — conditional values
|
|
527
362
|
|
|
528
363
|
Returns one of two values based on a condition:
|
|
529
364
|
|
|
530
|
-
|
|
531
|
-
const value = If("IsProduction", "prod-value", "dev-value");
|
|
532
|
-
// → { "Fn::If": ["IsProduction", "prod-value", "dev-value"] }
|
|
533
|
-
\`\`\`
|
|
534
|
-
|
|
535
|
-
Use with \`AWS.NoValue\` to conditionally omit a property:
|
|
365
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:11-12}}
|
|
536
366
|
|
|
537
|
-
|
|
538
|
-
export const bucket = new Bucket({
|
|
539
|
-
bucketName: "my-bucket",
|
|
540
|
-
accelerateConfiguration: If("EnableAcceleration",
|
|
541
|
-
{ accelerationStatus: "Enabled" },
|
|
542
|
-
AWS.NoValue,
|
|
543
|
-
),
|
|
544
|
-
});
|
|
545
|
-
\`\`\`
|
|
367
|
+
Use with \`AWS.NoValue\` to conditionally omit a property — see [Conditions](#conditions) on the CloudFormation Concepts page.
|
|
546
368
|
|
|
547
369
|
## \`Join\` — join values
|
|
548
370
|
|
|
549
371
|
Joins values with a delimiter:
|
|
550
372
|
|
|
551
|
-
|
|
552
|
-
const joined = Join("-", ["prefix", AWS.StackName, "suffix"]);
|
|
553
|
-
// → { "Fn::Join": ["-", ["prefix", { "Ref": "AWS::StackName" }, "suffix"]] }
|
|
554
|
-
\`\`\`
|
|
373
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:14-15}}
|
|
555
374
|
|
|
556
375
|
## \`Select\` — select by index
|
|
557
376
|
|
|
558
377
|
Selects a value from a list by index:
|
|
559
378
|
|
|
560
|
-
|
|
561
|
-
const first = Select(0, Split(",", "a,b,c"));
|
|
562
|
-
// → { "Fn::Select": [0, { "Fn::Split": [",", "a,b,c"] }] }
|
|
563
|
-
\`\`\`
|
|
379
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:17-18}}
|
|
564
380
|
|
|
565
381
|
## \`Split\` — split string
|
|
566
382
|
|
|
567
383
|
Splits a string by a delimiter:
|
|
568
384
|
|
|
569
|
-
|
|
570
|
-
const parts = Split(",", "a,b,c");
|
|
571
|
-
// → { "Fn::Split": [",", "a,b,c"] }
|
|
572
|
-
\`\`\`
|
|
385
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:20-21}}
|
|
573
386
|
|
|
574
387
|
## \`Base64\` — encode to Base64
|
|
575
388
|
|
|
576
389
|
Encodes a string to Base64, commonly used for EC2 user data:
|
|
577
390
|
|
|
578
|
-
|
|
579
|
-
const userData = Base64(Sub\`#!/bin/bash
|
|
580
|
-
echo "Stack: \${AWS.StackName}"
|
|
581
|
-
yum update -y
|
|
582
|
-
\`);
|
|
583
|
-
// → { "Fn::Base64": { "Fn::Sub": "..." } }
|
|
584
|
-
\`\`\``,
|
|
391
|
+
{{file:docs-snippets/src/intrinsics-detail.ts:23-27}}`,
|
|
585
392
|
},
|
|
586
393
|
{
|
|
587
394
|
slug: "composites",
|
|
@@ -589,46 +396,11 @@ yum update -y
|
|
|
589
396
|
description: "Composite resources, withDefaults presets, and propagate in the AWS CloudFormation lexicon",
|
|
590
397
|
content: `Composites group related resources into reusable factories. See also the core [Composite Resources](/guide/composite-resources/) guide.
|
|
591
398
|
|
|
592
|
-
|
|
593
|
-
import * as _ from "./_";
|
|
594
|
-
|
|
595
|
-
export const LambdaApi = _.Composite<LambdaApiProps>((props) => {
|
|
596
|
-
const role = new _.Role({
|
|
597
|
-
assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
|
|
598
|
-
managedPolicyArns: [_.$.lambdaBasicExecutionArn],
|
|
599
|
-
policies: props.policies,
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
const func = new _.Function({
|
|
603
|
-
functionName: props.name,
|
|
604
|
-
runtime: props.runtime,
|
|
605
|
-
handler: props.handler,
|
|
606
|
-
code: props.code,
|
|
607
|
-
role: role.arn,
|
|
608
|
-
timeout: props.timeout,
|
|
609
|
-
memorySize: props.memorySize,
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
const permission = new _.Permission({
|
|
613
|
-
functionName: func.arn,
|
|
614
|
-
action: "lambda:InvokeFunction",
|
|
615
|
-
principal: "apigateway.amazonaws.com",
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
return { role, func, permission };
|
|
619
|
-
}, "LambdaApi");
|
|
620
|
-
\`\`\`
|
|
399
|
+
{{file:advanced/src/lambda-api.ts}}
|
|
621
400
|
|
|
622
401
|
Instantiate and export:
|
|
623
402
|
|
|
624
|
-
|
|
625
|
-
export const healthApi = LambdaApi({
|
|
626
|
-
name: Sub\`\${AWS.StackName}-health\`,
|
|
627
|
-
runtime: "nodejs20.x",
|
|
628
|
-
handler: "index.handler",
|
|
629
|
-
code: { zipFile: \`exports.handler = async () => ({ statusCode: 200 });\` },
|
|
630
|
-
});
|
|
631
|
-
\`\`\`
|
|
403
|
+
{{file:advanced/src/health-api.ts}}
|
|
632
404
|
|
|
633
405
|
During build, composites expand to flat CloudFormation resources: \`healthApi_role\` → \`HealthApiRole\`, \`healthApi_func\` → \`HealthApiFunc\`, \`healthApi_permission\` → \`HealthApiPermission\`.
|
|
634
406
|
|
|
@@ -636,25 +408,7 @@ During build, composites expand to flat CloudFormation resources: \`healthApi_ro
|
|
|
636
408
|
|
|
637
409
|
Wrap a composite with pre-applied defaults. Defaulted props become optional:
|
|
638
410
|
|
|
639
|
-
|
|
640
|
-
import { withDefaults } from "@intentius/chant";
|
|
641
|
-
|
|
642
|
-
const SecureApi = withDefaults(LambdaApi, {
|
|
643
|
-
runtime: "nodejs20.x",
|
|
644
|
-
handler: "index.handler",
|
|
645
|
-
timeout: 10,
|
|
646
|
-
memorySize: 256,
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
// Only name and code are required now
|
|
650
|
-
export const healthApi = SecureApi({
|
|
651
|
-
name: Sub\`\${AWS.StackName}-health\`,
|
|
652
|
-
code: { zipFile: \`exports.handler = async () => ({ statusCode: 200 });\` },
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
// Composable — stack defaults on top of defaults
|
|
656
|
-
const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 });
|
|
657
|
-
\`\`\`
|
|
411
|
+
{{file:docs-snippets/src/with-defaults.ts}}
|
|
658
412
|
|
|
659
413
|
\`withDefaults\` preserves the original composite's identity — same \`_id\` and \`compositeName\`, no new registry entry.
|
|
660
414
|
|
|
@@ -662,15 +416,7 @@ const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 })
|
|
|
662
416
|
|
|
663
417
|
Attach properties that merge into every member during expansion:
|
|
664
418
|
|
|
665
|
-
|
|
666
|
-
import { propagate } from "@intentius/chant";
|
|
667
|
-
|
|
668
|
-
export const api = propagate(
|
|
669
|
-
LambdaApi({ name: "myApi", code: lambdaCode }),
|
|
670
|
-
{ tags: [{ key: "env", value: "prod" }] },
|
|
671
|
-
);
|
|
672
|
-
// role, func, and permission all receive the env tag
|
|
673
|
-
\`\`\`
|
|
419
|
+
{{file:docs-snippets/src/propagate.ts}}
|
|
674
420
|
|
|
675
421
|
Merge semantics:
|
|
676
422
|
- **Scalars** — member-specific value wins over shared
|
|
@@ -679,17 +425,9 @@ Merge semantics:
|
|
|
679
425
|
|
|
680
426
|
## Nested stacks
|
|
681
427
|
|
|
682
|
-
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()\`:
|
|
683
429
|
|
|
684
|
-
|
|
685
|
-
// src/app.ts — parent references child project directory
|
|
686
|
-
const network = _.nestedStack("network", import.meta.dir + "/network");
|
|
687
|
-
|
|
688
|
-
// Cross-stack reference via outputs proxy
|
|
689
|
-
export const handler = new _.Function({
|
|
690
|
-
vpcConfig: { subnetIds: [network.outputs.subnetId] },
|
|
691
|
-
});
|
|
692
|
-
\`\`\`
|
|
430
|
+
{{file:nested-stacks/src/app.ts}}
|
|
693
431
|
|
|
694
432
|
See [Nested Stacks](./nested-stacks) for the full guide.`,
|
|
695
433
|
},
|
|
@@ -697,18 +435,16 @@ See [Nested Stacks](./nested-stacks) for the full guide.`,
|
|
|
697
435
|
slug: "nested-stacks",
|
|
698
436
|
title: "Nested Stacks",
|
|
699
437
|
description: "Splitting resources into child CloudFormation templates with automatic cross-stack reference wiring",
|
|
700
|
-
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.
|
|
701
439
|
|
|
702
440
|
## Project structure
|
|
703
441
|
|
|
704
|
-
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:
|
|
705
443
|
|
|
706
444
|
\`\`\`
|
|
707
445
|
src/
|
|
708
|
-
_.ts # parent barrel
|
|
709
446
|
app.ts # parent resources
|
|
710
447
|
network/ # ← child project (nested stack)
|
|
711
|
-
_.ts # its own barrel
|
|
712
448
|
vpc.ts # VPC, subnet, internet gateway, routing
|
|
713
449
|
security.ts # security group for Lambda
|
|
714
450
|
outputs.ts # declares cross-stack outputs
|
|
@@ -718,15 +454,7 @@ src/
|
|
|
718
454
|
|
|
719
455
|
Use \`stackOutput()\` to mark values that the parent can reference. Each \`stackOutput()\` becomes an entry in the child template's \`Outputs\` section:
|
|
720
456
|
|
|
721
|
-
|
|
722
|
-
// src/network/outputs.ts
|
|
723
|
-
import * as _ from "./_";
|
|
724
|
-
import { stackOutput } from "@intentius/chant";
|
|
725
|
-
|
|
726
|
-
export const vpcId = stackOutput(_.$.vpc.vpcId, { description: "VPC ID" });
|
|
727
|
-
export const subnetId = stackOutput(_.$.subnet.subnetId, { description: "Public subnet ID" });
|
|
728
|
-
export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId, { description: "Lambda security group ID" });
|
|
729
|
-
\`\`\`
|
|
457
|
+
{{file:nested-stacks/src/network/outputs.ts}}
|
|
730
458
|
|
|
731
459
|
The child can be built independently:
|
|
732
460
|
|
|
@@ -739,24 +467,7 @@ chant build src/network/ -o network.json
|
|
|
739
467
|
|
|
740
468
|
Use \`nestedStack()\` in the parent to reference a child project directory. It returns an object with an \`outputs\` proxy for cross-stack references:
|
|
741
469
|
|
|
742
|
-
|
|
743
|
-
// src/app.ts
|
|
744
|
-
import * as _ from "./_";
|
|
745
|
-
|
|
746
|
-
const network = _.nestedStack("network", import.meta.dir + "/network");
|
|
747
|
-
|
|
748
|
-
export const handler = new _.Function({
|
|
749
|
-
functionName: _.Sub\`\${_.AWS.StackName}-handler\`,
|
|
750
|
-
runtime: "nodejs20.x",
|
|
751
|
-
handler: "index.handler",
|
|
752
|
-
role: _.Ref("LambdaExecutionRole"),
|
|
753
|
-
code: { zipFile: "exports.handler = async () => ({ statusCode: 200 });" },
|
|
754
|
-
vpcConfig: {
|
|
755
|
-
subnetIds: [network.outputs.subnetId],
|
|
756
|
-
securityGroupIds: [network.outputs.lambdaSgId],
|
|
757
|
-
},
|
|
758
|
-
});
|
|
759
|
-
\`\`\`
|
|
470
|
+
{{file:nested-stacks/src/app.ts}}
|
|
760
471
|
|
|
761
472
|
\`network.outputs.subnetId\` produces a \`NestedStackOutputRef\` that serializes to \`{ "Fn::GetAtt": ["Network", "Outputs.SubnetId"] }\`.
|
|
762
473
|
|
|
@@ -804,7 +515,9 @@ Child templates also receive the \`TemplateBasePath\` parameter so it propagates
|
|
|
804
515
|
Pass CloudFormation Parameters to child stacks with the \`parameters\` option:
|
|
805
516
|
|
|
806
517
|
\`\`\`typescript
|
|
807
|
-
|
|
518
|
+
import { nestedStack } from "@intentius/chant-lexicon-aws";
|
|
519
|
+
|
|
520
|
+
const network = nestedStack("network", import.meta.dir + "/network", {
|
|
808
521
|
parameters: { Environment: "prod", CidrBlock: "10.0.0.0/16" },
|
|
809
522
|
});
|
|
810
523
|
\`\`\`
|
|
@@ -815,16 +528,12 @@ Child projects can themselves reference grandchild projects. Each level produces
|
|
|
815
528
|
|
|
816
529
|
\`\`\`
|
|
817
530
|
src/
|
|
818
|
-
_.ts
|
|
819
531
|
app.ts
|
|
820
532
|
infra/
|
|
821
|
-
_.ts
|
|
822
533
|
network/
|
|
823
|
-
_.ts
|
|
824
534
|
vpc.ts
|
|
825
535
|
outputs.ts
|
|
826
536
|
database/
|
|
827
|
-
_.ts
|
|
828
537
|
cluster.ts
|
|
829
538
|
outputs.ts
|
|
830
539
|
\`\`\`
|
|
@@ -871,13 +580,13 @@ Lint rules analyze your TypeScript source code before build.
|
|
|
871
580
|
|
|
872
581
|
Flags hardcoded AWS region strings like \`us-east-1\`. Use \`AWS.Region\` instead so templates are portable across regions.
|
|
873
582
|
|
|
874
|
-
|
|
875
|
-
// Triggers WAW001
|
|
876
|
-
const endpoint = "s3.us-east-1.amazonaws.com";
|
|
583
|
+
**Bad** — triggers WAW001:
|
|
877
584
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
585
|
+
{{file:docs-snippets/src/lint-waw001-bad.ts}}
|
|
586
|
+
|
|
587
|
+
**Good** — uses \`AWS.Region\`:
|
|
588
|
+
|
|
589
|
+
{{file:docs-snippets/src/lint-waw001-good.ts}}
|
|
881
590
|
|
|
882
591
|
### WAW006 — S3 Bucket Encryption
|
|
883
592
|
|
|
@@ -885,20 +594,13 @@ const endpoint = Sub\`s3.\${AWS.Region}.amazonaws.com\`;
|
|
|
885
594
|
|
|
886
595
|
Flags S3 buckets that don't configure server-side encryption. AWS recommends enabling encryption on all buckets.
|
|
887
596
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
bucketEncryption: {
|
|
896
|
-
serverSideEncryptionConfiguration: [{
|
|
897
|
-
serverSideEncryptionByDefault: { sseAlgorithm: "AES256" },
|
|
898
|
-
}],
|
|
899
|
-
},
|
|
900
|
-
});
|
|
901
|
-
\`\`\`
|
|
597
|
+
**Bad** — triggers WAW006:
|
|
598
|
+
|
|
599
|
+
{{file:docs-snippets/src/lint-waw006-bad.ts}}
|
|
600
|
+
|
|
601
|
+
**Good** — encryption configured:
|
|
602
|
+
|
|
603
|
+
{{file:docs-snippets/src/lint-waw006-good.ts}}
|
|
902
604
|
|
|
903
605
|
### WAW009 — IAM Wildcard Resource
|
|
904
606
|
|
|
@@ -906,13 +608,13 @@ export const bucket = new Bucket({
|
|
|
906
608
|
|
|
907
609
|
Flags IAM policy statements that use \`"Resource": "*"\`. Prefer scoped resource ARNs following the principle of least privilege.
|
|
908
610
|
|
|
909
|
-
|
|
910
|
-
// Triggers WAW009
|
|
911
|
-
{ Effect: "Allow", Action: ["s3:GetObject"], Resource: "*" }
|
|
611
|
+
**Bad** — triggers WAW009:
|
|
912
612
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
613
|
+
{{file:docs-snippets/src/lint-waw009-bad.ts}}
|
|
614
|
+
|
|
615
|
+
**Good** — scoped ARN:
|
|
616
|
+
|
|
617
|
+
{{file:docs-snippets/src/lint-waw009-good.ts}}
|
|
916
618
|
|
|
917
619
|
IAM policy documents use PascalCase keys (\`Effect\`, \`Action\`, \`Resource\`) matching the IAM JSON Policy Language spec. The \`PolicyDocument\` and \`IamPolicyStatement\` types provide full autocomplete for these fields.
|
|
918
620
|
|
|
@@ -993,67 +695,17 @@ See also [Custom Lint Rules](./custom-rules) for writing project-specific rules.
|
|
|
993
695
|
|
|
994
696
|
## Anatomy of a lint rule
|
|
995
697
|
|
|
996
|
-
|
|
997
|
-
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
998
|
-
import * as ts from "typescript";
|
|
999
|
-
|
|
1000
|
-
export const apiTimeoutRule: LintRule = {
|
|
1001
|
-
id: "WAW012", // unique ID (WAW = AWS-specific prefix)
|
|
1002
|
-
severity: "error", // "error" | "warning"
|
|
1003
|
-
category: "correctness", // "correctness" | "style" | "security"
|
|
1004
|
-
|
|
1005
|
-
check(context: LintContext): LintDiagnostic[] {
|
|
1006
|
-
const { sourceFile } = context;
|
|
1007
|
-
const diagnostics: LintDiagnostic[] = [];
|
|
1008
|
-
|
|
1009
|
-
function visit(node: ts.Node): void {
|
|
1010
|
-
// Walk the AST looking for violations
|
|
1011
|
-
if (ts.isCallExpression(node)) {
|
|
1012
|
-
// Inspect arguments, report diagnostics
|
|
1013
|
-
}
|
|
1014
|
-
ts.forEachChild(node, visit);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
visit(sourceFile);
|
|
1018
|
-
return diagnostics;
|
|
1019
|
-
},
|
|
1020
|
-
};
|
|
1021
|
-
\`\`\`
|
|
1022
|
-
|
|
1023
|
-
## Example: API Gateway timeout (WAW012)
|
|
698
|
+
The advanced example includes a full custom rule implementation:
|
|
1024
699
|
|
|
1025
|
-
|
|
700
|
+
{{file:advanced/src/lint/api-timeout.ts}}
|
|
1026
701
|
|
|
1027
|
-
|
|
1028
|
-
const API_FACTORIES = new Set(["LambdaApi", "SecureApi", "HighMemoryApi"]);
|
|
1029
|
-
|
|
1030
|
-
export const apiTimeoutRule: LintRule = {
|
|
1031
|
-
id: "WAW012",
|
|
1032
|
-
severity: "error",
|
|
1033
|
-
category: "correctness",
|
|
1034
|
-
|
|
1035
|
-
check(context: LintContext): LintDiagnostic[] {
|
|
1036
|
-
// Walks AST for calls to API factory functions,
|
|
1037
|
-
// inspects the timeout property, reports if > 29
|
|
1038
|
-
},
|
|
1039
|
-
};
|
|
1040
|
-
\`\`\`
|
|
702
|
+
The \`check\` function receives a \`LintContext\` containing the TypeScript \`sourceFile\` and returns an array of diagnostics with file, line, column, and message.
|
|
1041
703
|
|
|
1042
704
|
## Registering custom rules
|
|
1043
705
|
|
|
1044
706
|
Add a \`chant.config.ts\` to your project:
|
|
1045
707
|
|
|
1046
|
-
|
|
1047
|
-
export default {
|
|
1048
|
-
lint: {
|
|
1049
|
-
extends: ["@intentius/chant/lint/presets/strict"],
|
|
1050
|
-
rules: {
|
|
1051
|
-
COR004: "off", // disable a built-in rule
|
|
1052
|
-
},
|
|
1053
|
-
plugins: ["./lint/api-timeout.ts"], // load custom rules
|
|
1054
|
-
},
|
|
1055
|
-
};
|
|
1056
|
-
\`\`\`
|
|
708
|
+
{{file:advanced/src/chant.config.ts}}
|
|
1057
709
|
|
|
1058
710
|
The \`plugins\` array accepts relative paths. Each plugin module should export a \`LintRule\` object.`,
|
|
1059
711
|
},
|
|
@@ -1077,9 +729,8 @@ bun test # runs the example's tests
|
|
|
1077
729
|
|
|
1078
730
|
\`\`\`
|
|
1079
731
|
src/
|
|
1080
|
-
├── _.ts # Barrel — re-exports lexicon + auto-discovers siblings
|
|
1081
732
|
├── defaults.ts # Shared config: encryption, versioning, public access block
|
|
1082
|
-
├── data-bucket.ts # S3 bucket using
|
|
733
|
+
├── data-bucket.ts # S3 bucket using shared defaults
|
|
1083
734
|
├── logs-bucket.ts # S3 bucket for access logs
|
|
1084
735
|
├── role.ts # IAM role with Lambda assume-role policy
|
|
1085
736
|
└── handler.ts # Lambda function referencing role and bucket
|
|
@@ -1087,28 +738,12 @@ src/
|
|
|
1087
738
|
|
|
1088
739
|
**Patterns demonstrated:**
|
|
1089
740
|
|
|
1090
|
-
1. **
|
|
1091
|
-
2. **Shared defaults** — \`defaults.ts\` exports reusable property objects (\`
|
|
1092
|
-
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\`, \`publicAccessBlock\`) that other files import directly
|
|
743
|
+
3. **Cross-resource references** — \`dataBucket.arn\` in \`handler.ts\` serializes to \`Fn::GetAtt\` in the template
|
|
1093
744
|
4. **Intrinsics** — \`Sub\` tagged templates with pseudo-parameters for dynamic naming
|
|
1094
745
|
|
|
1095
|
-
|
|
1096
|
-
// handler.ts — Lambda function referencing other resources
|
|
1097
|
-
import * as _ from "./_";
|
|
1098
|
-
|
|
1099
|
-
const lambdaCode = { zipFile: "exports.handler = async () => ({ statusCode: 200 });" };
|
|
1100
|
-
|
|
1101
|
-
export const handler = new _.Function({
|
|
1102
|
-
functionName: _.Sub\`\${_.AWS.StackName}-handler\`,
|
|
1103
|
-
handler: "index.handler",
|
|
1104
|
-
runtime: "nodejs20.x",
|
|
1105
|
-
role: _.$.functionRole.arn, // → Fn::GetAtt
|
|
1106
|
-
code: lambdaCode,
|
|
1107
|
-
environment: {
|
|
1108
|
-
variables: { BUCKET_ARN: _.$.dataBucket.arn }, // → Fn::GetAtt
|
|
1109
|
-
},
|
|
1110
|
-
});
|
|
1111
|
-
\`\`\`
|
|
746
|
+
{{file:getting-started/src/handler.ts}}
|
|
1112
747
|
|
|
1113
748
|
## Advanced
|
|
1114
749
|
|
|
@@ -1116,7 +751,6 @@ export const handler = new _.Function({
|
|
|
1116
751
|
|
|
1117
752
|
\`\`\`
|
|
1118
753
|
src/
|
|
1119
|
-
├── _.ts # Barrel + re-exports Composite from core
|
|
1120
754
|
├── chant.config.ts # Lint config: strict preset + custom plugin
|
|
1121
755
|
├── defaults.ts # Encryption, versioning, access block, Lambda trust policy
|
|
1122
756
|
├── data-bucket.ts # S3 bucket
|
|
@@ -1144,10 +778,8 @@ The example produces 10 CloudFormation resources: 1 S3 bucket + 3 composites ×
|
|
|
1144
778
|
|
|
1145
779
|
\`\`\`
|
|
1146
780
|
src/
|
|
1147
|
-
├── _.ts # Parent barrel
|
|
1148
781
|
├── app.ts # Lambda function (references network outputs)
|
|
1149
782
|
└── network/ # Child project (nested stack)
|
|
1150
|
-
├── _.ts # Child barrel
|
|
1151
783
|
├── vpc.ts # VPC, subnet, internet gateway, route table
|
|
1152
784
|
├── security.ts # Security group for Lambda
|
|
1153
785
|
└── outputs.ts # stackOutput() declarations
|
|
@@ -1155,39 +787,14 @@ src/
|
|
|
1155
787
|
|
|
1156
788
|
**Patterns demonstrated:**
|
|
1157
789
|
|
|
1158
|
-
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
|
|
1159
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
|
|
1160
792
|
3. **Multi-file output** — build produces \`template.json\` (parent) and \`network.template.json\` (child)
|
|
1161
793
|
4. **TemplateBasePath** — auto-generated parameter for configuring child template URLs per environment
|
|
1162
794
|
|
|
1163
|
-
|
|
1164
|
-
// network/outputs.ts — child declares what the parent can reference
|
|
1165
|
-
import * as _ from "./_";
|
|
1166
|
-
import { stackOutput } from "@intentius/chant";
|
|
1167
|
-
|
|
1168
|
-
export const vpcId = stackOutput(_.$.vpc.vpcId, { description: "VPC ID" });
|
|
1169
|
-
export const subnetId = stackOutput(_.$.subnet.subnetId, { description: "Public subnet ID" });
|
|
1170
|
-
export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId, { description: "Lambda security group ID" });
|
|
1171
|
-
\`\`\`
|
|
795
|
+
{{file:nested-stacks/src/network/outputs.ts}}
|
|
1172
796
|
|
|
1173
|
-
|
|
1174
|
-
// app.ts — parent references child project
|
|
1175
|
-
import * as _ from "./_";
|
|
1176
|
-
|
|
1177
|
-
const network = _.nestedStack("network", import.meta.dir + "/network");
|
|
1178
|
-
|
|
1179
|
-
export const handler = new _.Function({
|
|
1180
|
-
functionName: _.Sub\`\${_.AWS.StackName}-handler\`,
|
|
1181
|
-
runtime: "nodejs20.x",
|
|
1182
|
-
handler: "index.handler",
|
|
1183
|
-
role: _.Ref("LambdaExecutionRole"),
|
|
1184
|
-
code: { zipFile: "exports.handler = async () => ({ statusCode: 200 });" },
|
|
1185
|
-
vpcConfig: {
|
|
1186
|
-
subnetIds: [network.outputs.subnetId],
|
|
1187
|
-
securityGroupIds: [network.outputs.lambdaSgId],
|
|
1188
|
-
},
|
|
1189
|
-
});
|
|
1190
|
-
\`\`\`
|
|
797
|
+
{{file:nested-stacks/src/app.ts}}
|
|
1191
798
|
|
|
1192
799
|
See [Nested Stacks](./nested-stacks) for the full guide.`,
|
|
1193
800
|
},
|