@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/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";
|
|
@@ -54,68 +56,67 @@ export const awsPlugin: LexiconPlugin = {
|
|
|
54
56
|
|
|
55
57
|
initTemplates(): Record<string, string> {
|
|
56
58
|
return {
|
|
57
|
-
"_.ts": `export * from "./config";\n`,
|
|
58
59
|
"config.ts": `/**
|
|
59
60
|
* Shared bucket configuration — encryption, versioning, public access
|
|
60
61
|
*/
|
|
61
62
|
|
|
62
|
-
import
|
|
63
|
+
import { ServerSideEncryptionByDefault, ServerSideEncryptionRule, BucketEncryption, PublicAccessBlockConfiguration, VersioningConfiguration } from "@intentius/chant-lexicon-aws";
|
|
63
64
|
|
|
64
65
|
// Encryption default — AES256 server-side encryption
|
|
65
|
-
export const encryptionDefault = new
|
|
66
|
-
|
|
66
|
+
export const encryptionDefault = new ServerSideEncryptionByDefault({
|
|
67
|
+
SSEAlgorithm: "AES256",
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
// Encryption rule wrapping the default
|
|
70
|
-
export const encryptionRule = new
|
|
71
|
-
|
|
71
|
+
export const encryptionRule = new ServerSideEncryptionRule({
|
|
72
|
+
ServerSideEncryptionByDefault: encryptionDefault,
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
// Bucket encryption configuration
|
|
75
|
-
export const bucketEncryption = new
|
|
76
|
-
|
|
76
|
+
export const bucketEncryption = new BucketEncryption({
|
|
77
|
+
ServerSideEncryptionConfiguration: [encryptionRule],
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
// Public access block — deny all public access
|
|
80
|
-
export const publicAccessBlock = new
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
export const publicAccessBlock = new PublicAccessBlockConfiguration({
|
|
82
|
+
BlockPublicAcls: true,
|
|
83
|
+
BlockPublicPolicy: true,
|
|
84
|
+
IgnorePublicAcls: true,
|
|
85
|
+
RestrictPublicBuckets: true,
|
|
85
86
|
});
|
|
86
87
|
|
|
87
88
|
// Versioning — enabled
|
|
88
|
-
export const versioningEnabled = new
|
|
89
|
-
|
|
89
|
+
export const versioningEnabled = new VersioningConfiguration({
|
|
90
|
+
Status: "Enabled",
|
|
90
91
|
});
|
|
91
92
|
`,
|
|
92
93
|
"data-bucket.ts": `/**
|
|
93
94
|
* Data bucket — primary storage with encryption and versioning
|
|
94
95
|
*/
|
|
95
96
|
|
|
96
|
-
import
|
|
97
|
-
import
|
|
97
|
+
import { Bucket, Sub, AWS } from "@intentius/chant-lexicon-aws";
|
|
98
|
+
import { versioningEnabled, bucketEncryption, publicAccessBlock } from "./config";
|
|
98
99
|
|
|
99
|
-
export const dataBucket = new
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
export const dataBucket = new Bucket({
|
|
101
|
+
BucketName: Sub\`\${AWS.StackName}-\${AWS.AccountId}-data\`,
|
|
102
|
+
VersioningConfiguration: versioningEnabled,
|
|
103
|
+
BucketEncryption: bucketEncryption,
|
|
104
|
+
PublicAccessBlockConfiguration: publicAccessBlock,
|
|
104
105
|
});
|
|
105
106
|
`,
|
|
106
107
|
"logs-bucket.ts": `/**
|
|
107
108
|
* Logs bucket — log delivery with encryption and versioning
|
|
108
109
|
*/
|
|
109
110
|
|
|
110
|
-
import
|
|
111
|
-
import
|
|
111
|
+
import { Bucket, Sub, AWS } from "@intentius/chant-lexicon-aws";
|
|
112
|
+
import { versioningEnabled, bucketEncryption, publicAccessBlock } from "./config";
|
|
112
113
|
|
|
113
|
-
export const logsBucket = new
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
export const logsBucket = new Bucket({
|
|
115
|
+
BucketName: Sub\`\${AWS.StackName}-\${AWS.AccountId}-logs\`,
|
|
116
|
+
AccessControl: "LogDeliveryWrite",
|
|
117
|
+
VersioningConfiguration: versioningEnabled,
|
|
118
|
+
BucketEncryption: bucketEncryption,
|
|
119
|
+
PublicAccessBlockConfiguration: publicAccessBlock,
|
|
119
120
|
});
|
|
120
121
|
`,
|
|
121
122
|
};
|
|
@@ -185,18 +186,9 @@ export const logsBucket = new aws.Bucket({
|
|
|
185
186
|
|
|
186
187
|
async validate(options?: { verbose?: boolean }): Promise<void> {
|
|
187
188
|
const { validate } = await import("./validate");
|
|
189
|
+
const { printValidationResult } = await import("@intentius/chant/codegen/validate");
|
|
188
190
|
const result = await validate();
|
|
189
|
-
|
|
190
|
-
for (const check of result.checks) {
|
|
191
|
-
const status = check.ok ? "OK" : "FAIL";
|
|
192
|
-
const msg = check.error ? ` — ${check.error}` : "";
|
|
193
|
-
console.error(` [${status}] ${check.name}${msg}`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (!result.success) {
|
|
197
|
-
throw new Error("Validation failed");
|
|
198
|
-
}
|
|
199
|
-
console.error("All validation checks passed.");
|
|
191
|
+
printValidationResult(result);
|
|
200
192
|
},
|
|
201
193
|
|
|
202
194
|
async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
|
|
@@ -227,75 +219,29 @@ export const logsBucket = new aws.Bucket({
|
|
|
227
219
|
|
|
228
220
|
async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
|
|
229
221
|
const { packageLexicon } = await import("./codegen/package");
|
|
230
|
-
const {
|
|
222
|
+
const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
|
|
231
223
|
const { join, dirname } = await import("path");
|
|
232
224
|
const { fileURLToPath } = await import("url");
|
|
233
225
|
|
|
234
226
|
const { spec, stats } = await packageLexicon({ verbose: options?.verbose, force: options?.force });
|
|
235
227
|
|
|
236
|
-
// Write manifest and artifacts to dist/
|
|
237
228
|
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
238
229
|
const distDir = join(pkgDir, "dist");
|
|
239
|
-
|
|
240
|
-
mkdirSync(join(distDir, "rules"), { recursive: true });
|
|
241
|
-
mkdirSync(join(distDir, "skills"), { recursive: true });
|
|
242
|
-
|
|
243
|
-
writeFileSync(join(distDir, "manifest.json"), JSON.stringify(spec.manifest, null, 2));
|
|
244
|
-
writeFileSync(join(distDir, "meta.json"), spec.registry);
|
|
245
|
-
writeFileSync(join(distDir, "types", "index.d.ts"), spec.typesDTS);
|
|
246
|
-
|
|
247
|
-
for (const [name, content] of spec.rules) {
|
|
248
|
-
writeFileSync(join(distDir, "rules", name), content);
|
|
249
|
-
}
|
|
250
|
-
for (const [name, content] of spec.skills) {
|
|
251
|
-
writeFileSync(join(distDir, "skills", name), content);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Write integrity.json if available
|
|
255
|
-
if (spec.integrity) {
|
|
256
|
-
writeFileSync(join(distDir, "integrity.json"), JSON.stringify(spec.integrity, null, 2));
|
|
257
|
-
}
|
|
230
|
+
writeBundleSpec(spec, distDir);
|
|
258
231
|
|
|
259
232
|
console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
|
|
260
233
|
|
|
261
|
-
// Produce .tgz via
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const packErr = await new Response(packProc.stderr).text();
|
|
269
|
-
const packExit = await packProc.exited;
|
|
234
|
+
// Produce .tgz via pack command
|
|
235
|
+
const { getRuntime } = await import("@intentius/chant/runtime-adapter");
|
|
236
|
+
const rt = getRuntime();
|
|
237
|
+
const { stdout: packOut, stderr: packErr, exitCode: packExit } = await rt.spawn(
|
|
238
|
+
rt.commands.packCmd,
|
|
239
|
+
{ cwd: pkgDir },
|
|
240
|
+
);
|
|
270
241
|
if (packExit === 0) {
|
|
271
242
|
console.error(`Tarball: ${packOut.trim()}`);
|
|
272
243
|
} else {
|
|
273
|
-
console.error(
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
|
|
278
|
-
const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
|
|
279
|
-
const { join, dirname } = await import("path");
|
|
280
|
-
const { fileURLToPath } = await import("url");
|
|
281
|
-
|
|
282
|
-
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
283
|
-
const snapshotsDir = join(pkgDir, ".snapshots");
|
|
284
|
-
|
|
285
|
-
if (options?.restore) {
|
|
286
|
-
const generatedDir = join(pkgDir, "src", "generated");
|
|
287
|
-
restoreSnapshot(String(options.restore), generatedDir);
|
|
288
|
-
console.error(`Restored snapshot: ${options.restore}`);
|
|
289
|
-
} else {
|
|
290
|
-
const snapshots = listSnapshots(snapshotsDir);
|
|
291
|
-
if (snapshots.length === 0) {
|
|
292
|
-
console.error("No snapshots available.");
|
|
293
|
-
} else {
|
|
294
|
-
console.error(`Available snapshots (${snapshots.length}):`);
|
|
295
|
-
for (const s of snapshots) {
|
|
296
|
-
console.error(` ${s.timestamp} ${s.resourceCount} resources ${s.path}`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
244
|
+
console.error(`${rt.commands.packCmd.join(" ")} failed: ${packErr}`);
|
|
299
245
|
}
|
|
300
246
|
},
|
|
301
247
|
|
|
@@ -307,49 +253,80 @@ export const logsBucket = new aws.Bucket({
|
|
|
307
253
|
skills(): SkillDefinition[] {
|
|
308
254
|
return [
|
|
309
255
|
{
|
|
310
|
-
name: "aws
|
|
311
|
-
description: "AWS CloudFormation
|
|
256
|
+
name: "chant-aws",
|
|
257
|
+
description: "AWS CloudFormation template management — workflows, patterns, and troubleshooting",
|
|
312
258
|
content: `---
|
|
313
|
-
|
|
314
|
-
description:
|
|
259
|
+
skill: chant-aws
|
|
260
|
+
description: Build, validate, and deploy CloudFormation templates from a chant project
|
|
261
|
+
user-invocable: true
|
|
315
262
|
---
|
|
316
263
|
|
|
317
|
-
#
|
|
264
|
+
# Deploying CloudFormation from Chant
|
|
265
|
+
|
|
266
|
+
This project defines CloudFormation resources as TypeScript in \`src/\`. Use these steps to build, validate, and deploy.
|
|
267
|
+
|
|
268
|
+
## Build the template
|
|
269
|
+
|
|
270
|
+
\`\`\`bash
|
|
271
|
+
chant build src/ --output stack.json
|
|
272
|
+
\`\`\`
|
|
273
|
+
|
|
274
|
+
## Validate before deploying
|
|
275
|
+
|
|
276
|
+
\`\`\`bash
|
|
277
|
+
chant lint src/
|
|
278
|
+
aws cloudformation validate-template --template-body file://stack.json
|
|
279
|
+
\`\`\`
|
|
280
|
+
|
|
281
|
+
## Deploy a new stack
|
|
282
|
+
|
|
283
|
+
\`\`\`bash
|
|
284
|
+
aws cloudformation deploy \\
|
|
285
|
+
--template-file stack.json \\
|
|
286
|
+
--stack-name <stack-name> \\
|
|
287
|
+
--capabilities CAPABILITY_NAMED_IAM
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
Add \`--parameter-overrides Key=Value\` if the template has parameters.
|
|
291
|
+
|
|
292
|
+
## Update an existing stack
|
|
318
293
|
|
|
319
|
-
|
|
294
|
+
1. Edit the TypeScript source
|
|
295
|
+
2. Rebuild: \`chant build src/ --output stack.json\`
|
|
296
|
+
3. Preview changes:
|
|
297
|
+
\`\`\`bash
|
|
298
|
+
aws cloudformation create-change-set \\
|
|
299
|
+
--stack-name <stack-name> \\
|
|
300
|
+
--template-body file://stack.json \\
|
|
301
|
+
--change-set-name update-$(date +%s) \\
|
|
302
|
+
--capabilities CAPABILITY_NAMED_IAM
|
|
303
|
+
aws cloudformation describe-change-set \\
|
|
304
|
+
--stack-name <stack-name> \\
|
|
305
|
+
--change-set-name update-<id>
|
|
306
|
+
\`\`\`
|
|
307
|
+
4. Execute: \`aws cloudformation execute-change-set --stack-name <stack-name> --change-set-name update-<id>\`
|
|
320
308
|
|
|
321
|
-
|
|
322
|
-
- \`AWS::Lambda::Function\` — Serverless compute
|
|
323
|
-
- \`AWS::DynamoDB::Table\` — NoSQL database
|
|
324
|
-
- \`AWS::IAM::Role\` — Identity and access management
|
|
325
|
-
- \`AWS::SNS::Topic\` — Pub/sub messaging
|
|
326
|
-
- \`AWS::SQS::Queue\` — Message queue
|
|
327
|
-
- \`AWS::EC2::SecurityGroup\` — Network firewall rules
|
|
309
|
+
Or deploy directly: \`aws cloudformation deploy --template-file stack.json --stack-name <stack-name> --capabilities CAPABILITY_NAMED_IAM\`
|
|
328
310
|
|
|
329
|
-
##
|
|
311
|
+
## Delete a stack
|
|
330
312
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
- \`Join\` — Join strings with a delimiter
|
|
336
|
-
- \`Select\` — Pick an item from a list by index
|
|
313
|
+
\`\`\`bash
|
|
314
|
+
aws cloudformation delete-stack --stack-name <stack-name>
|
|
315
|
+
aws cloudformation wait stack-delete-complete --stack-name <stack-name>
|
|
316
|
+
\`\`\`
|
|
337
317
|
|
|
338
|
-
##
|
|
318
|
+
## Check stack status
|
|
339
319
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
320
|
+
\`\`\`bash
|
|
321
|
+
aws cloudformation describe-stacks --stack-name <stack-name>
|
|
322
|
+
aws cloudformation describe-stack-events --stack-name <stack-name> --max-items 10
|
|
323
|
+
\`\`\`
|
|
344
324
|
|
|
345
|
-
##
|
|
325
|
+
## Troubleshooting deploy failures
|
|
346
326
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
4. **Enable versioning** — Turn on \`VersioningConfiguration\` for data buckets
|
|
351
|
-
5. **Use Sub for dynamic names** — \`Sub\\\`\\\${AWS::StackName}-suffix\\\`\` for unique naming
|
|
352
|
-
6. **Share config via barrel files** — Put common settings in \`_.ts\` and import as \`* as _\`
|
|
327
|
+
- Check events: \`aws cloudformation describe-stack-events --stack-name <stack-name>\`
|
|
328
|
+
- Rollback stuck: \`aws cloudformation continue-update-rollback --stack-name <stack-name>\`
|
|
329
|
+
- Drift: \`aws cloudformation detect-stack-drift --stack-name <stack-name>\`
|
|
353
330
|
`,
|
|
354
331
|
triggers: [
|
|
355
332
|
{ type: "file-pattern", value: "**/*.aws.ts" },
|
|
@@ -453,31 +430,31 @@ description: AWS CloudFormation best practices and common patterns
|
|
|
453
430
|
description: "AWS S3 bucket with versioning and encryption",
|
|
454
431
|
mimeType: "text/typescript",
|
|
455
432
|
async handler(): Promise<string> {
|
|
456
|
-
return `import
|
|
433
|
+
return `import { ServerSideEncryptionByDefault, ServerSideEncryptionRule, BucketEncryption, VersioningConfiguration, Bucket, Sub, AWS } from "@intentius/chant-lexicon-aws";
|
|
457
434
|
|
|
458
435
|
// Encryption configuration
|
|
459
|
-
export const encryptionDefault = new
|
|
460
|
-
|
|
436
|
+
export const encryptionDefault = new ServerSideEncryptionByDefault({
|
|
437
|
+
SSEAlgorithm: "AES256",
|
|
461
438
|
});
|
|
462
439
|
|
|
463
|
-
export const encryptionRule = new
|
|
464
|
-
|
|
440
|
+
export const encryptionRule = new ServerSideEncryptionRule({
|
|
441
|
+
ServerSideEncryptionByDefault: encryptionDefault,
|
|
465
442
|
});
|
|
466
443
|
|
|
467
|
-
export const bucketEncryption = new
|
|
468
|
-
|
|
444
|
+
export const bucketEncryption = new BucketEncryption({
|
|
445
|
+
ServerSideEncryptionConfiguration: [encryptionRule],
|
|
469
446
|
});
|
|
470
447
|
|
|
471
448
|
// Versioning
|
|
472
|
-
export const versioningEnabled = new
|
|
473
|
-
|
|
449
|
+
export const versioningEnabled = new VersioningConfiguration({
|
|
450
|
+
Status: "Enabled",
|
|
474
451
|
});
|
|
475
452
|
|
|
476
|
-
// Create a versioned bucket with encryption
|
|
477
|
-
export const dataBucket = new
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
453
|
+
// Create a versioned bucket with encryption (AccountId ensures global uniqueness)
|
|
454
|
+
export const dataBucket = new Bucket({
|
|
455
|
+
BucketName: Sub\`\${AWS.StackName}-\${AWS.AccountId}-data\`,
|
|
456
|
+
VersioningConfiguration: versioningEnabled,
|
|
457
|
+
BucketEncryption: bucketEncryption,
|
|
481
458
|
});
|
|
482
459
|
`;
|
|
483
460
|
},
|
|
@@ -488,17 +465,17 @@ export const dataBucket = new aws.Bucket({
|
|
|
488
465
|
description: "Using AttrRefs for cross-resource references",
|
|
489
466
|
mimeType: "text/typescript",
|
|
490
467
|
async handler(): Promise<string> {
|
|
491
|
-
return `import
|
|
468
|
+
return `import { Bucket, VersioningConfiguration, Role } from "@intentius/chant-lexicon-aws";
|
|
492
469
|
|
|
493
470
|
// Create a bucket
|
|
494
|
-
export const dataBucket = new
|
|
495
|
-
|
|
496
|
-
|
|
471
|
+
export const dataBucket = new Bucket({
|
|
472
|
+
BucketName: "my-data-bucket",
|
|
473
|
+
VersioningConfiguration: new VersioningConfiguration({ Status: "Enabled" }),
|
|
497
474
|
});
|
|
498
475
|
|
|
499
476
|
// Create a role that references the bucket's ARN
|
|
500
|
-
export const role = new
|
|
501
|
-
|
|
477
|
+
export const role = new Role({
|
|
478
|
+
AssumeRolePolicyDocument: {
|
|
502
479
|
Version: "2012-10-17",
|
|
503
480
|
Statement: [{
|
|
504
481
|
Effect: "Allow",
|
package/src/serializer.test.ts
CHANGED
|
@@ -18,9 +18,9 @@ class MockBucket implements Declarable {
|
|
|
18
18
|
readonly lexicon = "aws";
|
|
19
19
|
readonly entityType = "AWS::S3::Bucket";
|
|
20
20
|
readonly arn: AttrRef;
|
|
21
|
-
readonly props: {
|
|
21
|
+
readonly props: { BucketName?: string; VersioningConfiguration?: { Status: string } };
|
|
22
22
|
|
|
23
|
-
constructor(props: {
|
|
23
|
+
constructor(props: { BucketName?: string; VersioningConfiguration?: { Status: string } } = {}) {
|
|
24
24
|
this.props = props;
|
|
25
25
|
this.arn = new AttrRef(this, "Arn");
|
|
26
26
|
}
|
|
@@ -57,7 +57,7 @@ describe("awsSerializer.serialize", () => {
|
|
|
57
57
|
|
|
58
58
|
test("serializes resources", () => {
|
|
59
59
|
const entities = new Map<string, Declarable>();
|
|
60
|
-
entities.set("MyBucket", new MockBucket({
|
|
60
|
+
entities.set("MyBucket", new MockBucket({ BucketName: "my-bucket" }));
|
|
61
61
|
|
|
62
62
|
const output = awsSerializer.serialize(entities);
|
|
63
63
|
const template = JSON.parse(output);
|
|
@@ -86,8 +86,8 @@ describe("awsSerializer.serialize", () => {
|
|
|
86
86
|
test("serializes nested properties", () => {
|
|
87
87
|
const entities = new Map<string, Declarable>();
|
|
88
88
|
entities.set("MyBucket", new MockBucket({
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
BucketName: "my-bucket",
|
|
90
|
+
VersioningConfiguration: { Status: "Enabled" },
|
|
91
91
|
}));
|
|
92
92
|
|
|
93
93
|
const output = awsSerializer.serialize(entities);
|
|
@@ -98,21 +98,20 @@ describe("awsSerializer.serialize", () => {
|
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
test("
|
|
101
|
+
test("passes through property names verbatim", () => {
|
|
102
102
|
const entities = new Map<string, Declarable>();
|
|
103
|
-
entities.set("MyBucket", new MockBucket({
|
|
103
|
+
entities.set("MyBucket", new MockBucket({ BucketName: "test" }));
|
|
104
104
|
|
|
105
105
|
const output = awsSerializer.serialize(entities);
|
|
106
106
|
const template = JSON.parse(output);
|
|
107
107
|
|
|
108
108
|
expect(template.Resources.MyBucket.Properties.BucketName).toBeDefined();
|
|
109
|
-
expect(template.Resources.MyBucket.Properties.bucketName).toBeUndefined();
|
|
110
109
|
});
|
|
111
110
|
|
|
112
111
|
test("handles multiple resources", () => {
|
|
113
112
|
const entities = new Map<string, Declarable>();
|
|
114
|
-
entities.set("DataBucket", new MockBucket({
|
|
115
|
-
entities.set("LogsBucket", new MockBucket({
|
|
113
|
+
entities.set("DataBucket", new MockBucket({ BucketName: "data-bucket" }));
|
|
114
|
+
entities.set("LogsBucket", new MockBucket({ BucketName: "logs-bucket" }));
|
|
116
115
|
|
|
117
116
|
const output = awsSerializer.serialize(entities);
|
|
118
117
|
const template = JSON.parse(output);
|
|
@@ -125,7 +124,7 @@ describe("awsSerializer.serialize", () => {
|
|
|
125
124
|
test("handles resources and parameters together", () => {
|
|
126
125
|
const entities = new Map<string, Declarable>();
|
|
127
126
|
entities.set("Env", new Parameter("String"));
|
|
128
|
-
entities.set("MyBucket", new MockBucket({
|
|
127
|
+
entities.set("MyBucket", new MockBucket({ BucketName: "bucket" }));
|
|
129
128
|
|
|
130
129
|
const output = awsSerializer.serialize(entities);
|
|
131
130
|
const template = JSON.parse(output);
|
|
@@ -154,9 +153,9 @@ class MockEncryption implements Declarable {
|
|
|
154
153
|
readonly lexicon = "aws";
|
|
155
154
|
readonly entityType = "AWS::S3::Bucket.BucketEncryption";
|
|
156
155
|
readonly kind = "property" as const;
|
|
157
|
-
readonly props: {
|
|
156
|
+
readonly props: { ServerSideEncryptionConfiguration: unknown[] };
|
|
158
157
|
|
|
159
|
-
constructor(props: {
|
|
158
|
+
constructor(props: { ServerSideEncryptionConfiguration: unknown[] }) {
|
|
160
159
|
this.props = props;
|
|
161
160
|
}
|
|
162
161
|
}
|
|
@@ -164,14 +163,14 @@ class MockEncryption implements Declarable {
|
|
|
164
163
|
describe("property-kind Declarables", () => {
|
|
165
164
|
test("property-kind Declarables are inlined into parent properties", () => {
|
|
166
165
|
const encryption = new MockEncryption({
|
|
167
|
-
|
|
168
|
-
{
|
|
166
|
+
ServerSideEncryptionConfiguration: [
|
|
167
|
+
{ ServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" } },
|
|
169
168
|
],
|
|
170
169
|
});
|
|
171
170
|
|
|
172
|
-
const bucket = new MockBucket({
|
|
171
|
+
const bucket = new MockBucket({ BucketName: "my-bucket" });
|
|
173
172
|
// Manually set encryption as a prop
|
|
174
|
-
(bucket.props as Record<string, unknown>).
|
|
173
|
+
(bucket.props as Record<string, unknown>).BucketEncryption = encryption;
|
|
175
174
|
|
|
176
175
|
const entities = new Map<string, Declarable>();
|
|
177
176
|
entities.set("DataEncryption", encryption);
|
|
@@ -181,23 +180,23 @@ describe("property-kind Declarables", () => {
|
|
|
181
180
|
const template = JSON.parse(output);
|
|
182
181
|
|
|
183
182
|
// Encryption should be inlined, not a Ref
|
|
184
|
-
expect(template.Resources.MyBucket.Properties.
|
|
183
|
+
expect(template.Resources.MyBucket.Properties.BucketEncryption).toEqual({
|
|
185
184
|
ServerSideEncryptionConfiguration: [
|
|
186
|
-
{ ServerSideEncryptionByDefault: {
|
|
185
|
+
{ ServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" } },
|
|
187
186
|
],
|
|
188
187
|
});
|
|
189
188
|
});
|
|
190
189
|
|
|
191
190
|
test("property-kind Declarables do NOT appear as standalone Resources", () => {
|
|
192
191
|
const encryption = new MockEncryption({
|
|
193
|
-
|
|
194
|
-
{
|
|
192
|
+
ServerSideEncryptionConfiguration: [
|
|
193
|
+
{ ServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" } },
|
|
195
194
|
],
|
|
196
195
|
});
|
|
197
196
|
|
|
198
197
|
const entities = new Map<string, Declarable>();
|
|
199
198
|
entities.set("DataEncryption", encryption);
|
|
200
|
-
entities.set("MyBucket", new MockBucket({
|
|
199
|
+
entities.set("MyBucket", new MockBucket({ BucketName: "my-bucket" }));
|
|
201
200
|
|
|
202
201
|
const output = awsSerializer.serialize(entities);
|
|
203
202
|
const template = JSON.parse(output);
|
|
@@ -207,16 +206,16 @@ describe("property-kind Declarables", () => {
|
|
|
207
206
|
});
|
|
208
207
|
|
|
209
208
|
test("resource-kind Declarables still emit Ref when referenced", () => {
|
|
210
|
-
const sourceBucket = new MockBucket({
|
|
209
|
+
const sourceBucket = new MockBucket({ BucketName: "source" });
|
|
211
210
|
|
|
212
211
|
class MockConfig implements Declarable {
|
|
213
212
|
readonly [DECLARABLE_MARKER] = true as const;
|
|
214
213
|
readonly lexicon = "aws";
|
|
215
214
|
readonly entityType = "AWS::S3::ReplicationDestination";
|
|
216
|
-
readonly props: {
|
|
215
|
+
readonly props: { Bucket: Declarable };
|
|
217
216
|
|
|
218
|
-
constructor(
|
|
219
|
-
this.props = {
|
|
217
|
+
constructor(Bucket: Declarable) {
|
|
218
|
+
this.props = { Bucket };
|
|
220
219
|
}
|
|
221
220
|
}
|
|
222
221
|
|
|
@@ -233,7 +232,7 @@ describe("property-kind Declarables", () => {
|
|
|
233
232
|
|
|
234
233
|
describe("intrinsic serialization", () => {
|
|
235
234
|
test("handles AttrRef in properties", () => {
|
|
236
|
-
const source = new MockBucket({
|
|
235
|
+
const source = new MockBucket({ BucketName: "source" });
|
|
237
236
|
// Set the logical name on the AttrRef before using it
|
|
238
237
|
(source.arn as Record<string, unknown>)._setLogicalName("SourceBucket");
|
|
239
238
|
|
|
@@ -241,10 +240,10 @@ describe("intrinsic serialization", () => {
|
|
|
241
240
|
readonly [DECLARABLE_MARKER] = true as const;
|
|
242
241
|
readonly lexicon = "aws";
|
|
243
242
|
readonly entityType = "AWS::S3::ReplicationConfiguration";
|
|
244
|
-
readonly props: {
|
|
243
|
+
readonly props: { SourceArn: AttrRef };
|
|
245
244
|
|
|
246
|
-
constructor(
|
|
247
|
-
this.props = {
|
|
245
|
+
constructor(SourceArn: AttrRef) {
|
|
246
|
+
this.props = { SourceArn };
|
|
248
247
|
}
|
|
249
248
|
}
|
|
250
249
|
|
|
@@ -256,14 +255,14 @@ describe("intrinsic serialization", () => {
|
|
|
256
255
|
const template = JSON.parse(output);
|
|
257
256
|
|
|
258
257
|
expect(template.Resources.Replication.Properties.SourceArn).toEqual({
|
|
259
|
-
"Fn::
|
|
258
|
+
"Fn::GetAtt": ["SourceBucket", "Arn"],
|
|
260
259
|
});
|
|
261
260
|
});
|
|
262
261
|
});
|
|
263
262
|
|
|
264
263
|
describe("LexiconOutput serialization", () => {
|
|
265
264
|
test("generates CF Outputs section for LexiconOutputs", () => {
|
|
266
|
-
const bucket = new MockBucket({
|
|
265
|
+
const bucket = new MockBucket({ BucketName: "data-bucket" });
|
|
267
266
|
const lexiconOutput = new LexiconOutput(bucket.arn, "DataBucketArn");
|
|
268
267
|
lexiconOutput._setSourceEntity("dataBucket");
|
|
269
268
|
|
|
@@ -275,13 +274,13 @@ describe("LexiconOutput serialization", () => {
|
|
|
275
274
|
|
|
276
275
|
expect(template.Outputs).toBeDefined();
|
|
277
276
|
expect(template.Outputs.DataBucketArn).toEqual({
|
|
278
|
-
Value: { "Fn::
|
|
277
|
+
Value: { "Fn::GetAtt": ["dataBucket", "Arn"] },
|
|
279
278
|
});
|
|
280
279
|
});
|
|
281
280
|
|
|
282
281
|
test("generates multiple CF Outputs", () => {
|
|
283
|
-
const dataBucket = new MockBucket({
|
|
284
|
-
const logsBucket = new MockBucket({
|
|
282
|
+
const dataBucket = new MockBucket({ BucketName: "data-bucket" });
|
|
283
|
+
const logsBucket = new MockBucket({ BucketName: "logs-bucket" });
|
|
285
284
|
|
|
286
285
|
const dataOutput = new LexiconOutput(dataBucket.arn, "DataBucketArn");
|
|
287
286
|
dataOutput._setSourceEntity("dataBucket");
|
|
@@ -298,16 +297,16 @@ describe("LexiconOutput serialization", () => {
|
|
|
298
297
|
expect(template.Outputs).toBeDefined();
|
|
299
298
|
expect(Object.keys(template.Outputs)).toHaveLength(2);
|
|
300
299
|
expect(template.Outputs.DataBucketArn.Value).toEqual({
|
|
301
|
-
"Fn::
|
|
300
|
+
"Fn::GetAtt": ["dataBucket", "Arn"],
|
|
302
301
|
});
|
|
303
302
|
expect(template.Outputs.LogsBucketArn.Value).toEqual({
|
|
304
|
-
"Fn::
|
|
303
|
+
"Fn::GetAtt": ["logsBucket", "Arn"],
|
|
305
304
|
});
|
|
306
305
|
});
|
|
307
306
|
|
|
308
307
|
test("omits Outputs section when no LexiconOutputs provided", () => {
|
|
309
308
|
const entities = new Map<string, Declarable>();
|
|
310
|
-
entities.set("MyBucket", new MockBucket({
|
|
309
|
+
entities.set("MyBucket", new MockBucket({ BucketName: "bucket" }));
|
|
311
310
|
|
|
312
311
|
const result = awsSerializer.serialize(entities);
|
|
313
312
|
const template = JSON.parse(result);
|
|
@@ -317,7 +316,7 @@ describe("LexiconOutput serialization", () => {
|
|
|
317
316
|
|
|
318
317
|
test("omits Outputs section when empty LexiconOutputs array", () => {
|
|
319
318
|
const entities = new Map<string, Declarable>();
|
|
320
|
-
entities.set("MyBucket", new MockBucket({
|
|
319
|
+
entities.set("MyBucket", new MockBucket({ BucketName: "bucket" }));
|
|
321
320
|
|
|
322
321
|
const result = awsSerializer.serialize(entities, []);
|
|
323
322
|
const template = JSON.parse(result as string);
|
|
@@ -430,7 +429,7 @@ describe("nested stack serialization", () => {
|
|
|
430
429
|
Resources: {},
|
|
431
430
|
});
|
|
432
431
|
|
|
433
|
-
const bucket = new MockBucket({
|
|
432
|
+
const bucket = new MockBucket({ BucketName: "data" });
|
|
434
433
|
|
|
435
434
|
const entities = new Map<string, Declarable>();
|
|
436
435
|
entities.set("network", stack as unknown as Declarable);
|
|
@@ -446,7 +445,7 @@ describe("nested stack serialization", () => {
|
|
|
446
445
|
|
|
447
446
|
test("without nested stacks returns plain string", () => {
|
|
448
447
|
const entities = new Map<string, Declarable>();
|
|
449
|
-
entities.set("MyBucket", new MockBucket({
|
|
448
|
+
entities.set("MyBucket", new MockBucket({ BucketName: "bucket" }));
|
|
450
449
|
|
|
451
450
|
const result = awsSerializer.serialize(entities);
|
|
452
451
|
expect(typeof result).toBe("string");
|
|
@@ -460,7 +459,7 @@ describe("nested stack serialization", () => {
|
|
|
460
459
|
subnet: { Type: "AWS::EC2::Subnet" },
|
|
461
460
|
},
|
|
462
461
|
Outputs: {
|
|
463
|
-
subnetId: { Value: { "Fn::
|
|
462
|
+
subnetId: { Value: { "Fn::GetAtt": ["subnet", "SubnetId"] } },
|
|
464
463
|
},
|
|
465
464
|
});
|
|
466
465
|
|
|
@@ -472,7 +471,7 @@ describe("nested stack serialization", () => {
|
|
|
472
471
|
entityType: "AWS::Lambda::Function",
|
|
473
472
|
kind: "resource" as const,
|
|
474
473
|
props: {
|
|
475
|
-
|
|
474
|
+
VpcConfig: { SubnetIds: [subnetRef] },
|
|
476
475
|
},
|
|
477
476
|
} as unknown as Declarable;
|
|
478
477
|
|