@intentius/chant-lexicon-aws 0.0.2
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 +438 -0
- package/package.json +30 -0
- package/src/codegen/__snapshots__/snapshot.test.ts.snap +197 -0
- package/src/codegen/docs-cli.ts +3 -0
- package/src/codegen/docs.ts +1206 -0
- package/src/codegen/extensions.ts +171 -0
- package/src/codegen/fallback.ts +33 -0
- package/src/codegen/generate-cli.ts +17 -0
- package/src/codegen/generate-lexicon.ts +98 -0
- package/src/codegen/generate-typescript.ts +257 -0
- package/src/codegen/generate.test.ts +125 -0
- package/src/codegen/generate.ts +226 -0
- package/src/codegen/idempotency.test.ts +28 -0
- package/src/codegen/naming.ts +120 -0
- package/src/codegen/package.test.ts +60 -0
- package/src/codegen/package.ts +84 -0
- package/src/codegen/patches.ts +98 -0
- package/src/codegen/rollback.test.ts +80 -0
- package/src/codegen/rollback.ts +20 -0
- package/src/codegen/sam.ts +387 -0
- package/src/codegen/snapshot.test.ts +84 -0
- package/src/codegen/typecheck.test.ts +50 -0
- package/src/codegen/typecheck.ts +4 -0
- package/src/codegen/versions.ts +37 -0
- package/src/coverage.ts +14 -0
- package/src/generated/index.d.ts +160753 -0
- package/src/generated/index.ts +14396 -0
- package/src/generated/lexicon-aws.json +114563 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +181 -0
- package/src/import/generator.ts +349 -0
- package/src/import/parser.test.ts +200 -0
- package/src/import/parser.ts +350 -0
- package/src/import/roundtrip-fixtures.test.ts +78 -0
- package/src/import/roundtrip.test.ts +195 -0
- package/src/index.ts +63 -0
- package/src/integration.test.ts +129 -0
- package/src/intrinsics.test.ts +167 -0
- package/src/intrinsics.ts +223 -0
- package/src/lint/post-synth/cf-refs.ts +91 -0
- package/src/lint/post-synth/cor020.ts +72 -0
- package/src/lint/post-synth/ext001.test.ts +68 -0
- package/src/lint/post-synth/ext001.ts +222 -0
- package/src/lint/post-synth/post-synth.test.ts +280 -0
- package/src/lint/post-synth/waw010.ts +49 -0
- package/src/lint/post-synth/waw011.ts +49 -0
- package/src/lint/post-synth/waw013.ts +45 -0
- package/src/lint/post-synth/waw014.ts +50 -0
- package/src/lint/post-synth/waw015.ts +100 -0
- package/src/lint/rules/hardcoded-region.ts +43 -0
- package/src/lint/rules/iam-wildcard.ts +66 -0
- package/src/lint/rules/index.ts +7 -0
- package/src/lint/rules/rules.test.ts +175 -0
- package/src/lint/rules/s3-encryption.ts +69 -0
- package/src/lsp/completions.test.ts +72 -0
- package/src/lsp/completions.ts +18 -0
- package/src/lsp/hover.test.ts +53 -0
- package/src/lsp/hover.ts +53 -0
- package/src/nested-stack.test.ts +83 -0
- package/src/nested-stack.ts +125 -0
- package/src/plugin.test.ts +316 -0
- package/src/plugin.ts +514 -0
- package/src/pseudo.test.ts +55 -0
- package/src/pseudo.ts +29 -0
- package/src/serializer.test.ts +507 -0
- package/src/serializer.ts +333 -0
- package/src/spec/fetch.test.ts +27 -0
- package/src/spec/fetch.ts +107 -0
- package/src/spec/parse.test.ts +153 -0
- package/src/spec/parse.ts +202 -0
- package/src/testdata/load-fixtures.ts +17 -0
- package/src/testdata/roundtrip/conditions.json +21 -0
- package/src/testdata/roundtrip/intrinsic-calls.json +31 -0
- package/src/testdata/roundtrip/intrinsics.json +18 -0
- package/src/testdata/roundtrip/multi-resource.json +37 -0
- package/src/testdata/roundtrip/parameters.json +23 -0
- package/src/testdata/roundtrip/simple.json +12 -0
- package/src/testdata/sam-fixtures/api.yaml +14 -0
- package/src/testdata/sam-fixtures/application.yaml +13 -0
- package/src/testdata/sam-fixtures/function.yaml +22 -0
- package/src/testdata/sam-fixtures/graphql-api.yaml +13 -0
- package/src/testdata/sam-fixtures/http-api.yaml +15 -0
- package/src/testdata/sam-fixtures/layer-version.yaml +15 -0
- package/src/testdata/sam-fixtures/multi-type-a.yaml +23 -0
- package/src/testdata/sam-fixtures/multi-type-b.yaml +29 -0
- package/src/testdata/sam-fixtures/simple-table.yaml +12 -0
- package/src/testdata/sam-fixtures/state-machine.yaml +14 -0
- package/src/testdata/schemas/aws-dynamodb-table.json +126 -0
- package/src/testdata/schemas/aws-iam-role.json +85 -0
- package/src/testdata/schemas/aws-lambda-function.json +90 -0
- package/src/testdata/schemas/aws-s3-bucket.json +83 -0
- package/src/testdata/schemas/aws-sns-topic.json +71 -0
- package/src/validate-cli.ts +19 -0
- package/src/validate.ts +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# @intentius/chant-lexicon-aws
|
|
2
|
+
|
|
3
|
+
> Part of the [chant](../../README.md) monorepo. Not yet published to npm.
|
|
4
|
+
|
|
5
|
+
AWS CloudFormation lexicon for chant — declare infrastructure as flat, typed TypeScript that serializes to CloudFormation JSON templates.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This package provides:
|
|
10
|
+
|
|
11
|
+
- **CloudFormation serializer** — converts chant declarables to CloudFormation JSON templates
|
|
12
|
+
- **Intrinsic functions** — type-safe `Fn::Sub`, `Fn::GetAtt`, `Fn::Join`, etc.
|
|
13
|
+
- **Pseudo-parameters** — `AWS::Region`, `AWS::AccountId`, `AWS::StackName`, etc.
|
|
14
|
+
- **Resource types** — generated constructors for S3, Lambda, IAM, and all CloudFormation resource types
|
|
15
|
+
- **Lint rules** — AWS-specific validation (e.g. hardcoded region detection)
|
|
16
|
+
- **Code generation** — generates TypeScript types from CloudFormation resource specs
|
|
17
|
+
- **LSP/MCP support** — completions and hover for AWS resource types
|
|
18
|
+
|
|
19
|
+
## CloudFormation Concepts
|
|
20
|
+
|
|
21
|
+
### Templates
|
|
22
|
+
|
|
23
|
+
Chant builds a CloudFormation template from your declarations. Every exported resource becomes a logical resource in the `Resources` section. The serializer automatically:
|
|
24
|
+
|
|
25
|
+
- Wraps everything in `AWSTemplateFormatVersion: "2010-09-09"`
|
|
26
|
+
- Converts camelCase properties to PascalCase (CloudFormation convention)
|
|
27
|
+
- Resolves `AttrRef` references to `Fn::GetAtt`
|
|
28
|
+
- Resolves resource references to `Ref` intrinsics
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// This declaration...
|
|
32
|
+
export const dataBucket = new Bucket({
|
|
33
|
+
bucketName: Sub`${AWS.StackName}-data`,
|
|
34
|
+
versioningConfiguration: $.versioningEnabled,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ...becomes this in the template:
|
|
38
|
+
// "DataBucket": {
|
|
39
|
+
// "Type": "AWS::S3::Bucket",
|
|
40
|
+
// "Properties": {
|
|
41
|
+
// "BucketName": { "Fn::Sub": "${AWS::StackName}-data" },
|
|
42
|
+
// "VersioningConfiguration": { "Status": "Enabled" }
|
|
43
|
+
// }
|
|
44
|
+
// }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Build a template with:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { awsDomain } from "@intentius/chant-lexicon-aws";
|
|
51
|
+
import { build } from "@intentius/chant";
|
|
52
|
+
|
|
53
|
+
const result = await build("./src/infra", awsDomain);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Parameters
|
|
57
|
+
|
|
58
|
+
CloudFormation parameters become template `Parameters` entries. Declare them using `CoreParameter`-implementing entities — the serializer detects them and places them in the `Parameters` section instead of `Resources`.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Parameters appear in the template as:
|
|
62
|
+
// "Parameters": {
|
|
63
|
+
// "EnvName": { "Type": "String", "Default": "prod" }
|
|
64
|
+
// }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Outputs
|
|
68
|
+
|
|
69
|
+
Use `output()` to create explicit stack outputs. The serializer collects them into the template's `Outputs` section. Cross-lexicon `AttrRef` usage is also auto-detected and promoted to outputs.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { output } from "@intentius/chant";
|
|
73
|
+
|
|
74
|
+
// Explicit output
|
|
75
|
+
const bucketArn = output(dataBucket.arn, "DataBucketArn");
|
|
76
|
+
|
|
77
|
+
// In the template:
|
|
78
|
+
// "Outputs": {
|
|
79
|
+
// "DataBucketArn": {
|
|
80
|
+
// "Value": { "Fn::GetAttr": ["DataBucket", "Arn"] }
|
|
81
|
+
// }
|
|
82
|
+
// }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Tagging
|
|
86
|
+
|
|
87
|
+
Tags are standard CloudFormation `Key`/`Value` arrays. Pass them as `tags` props on any resource that supports them:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
export const bucket = new Bucket({
|
|
91
|
+
bucketName: "my-bucket",
|
|
92
|
+
tags: [
|
|
93
|
+
{ key: "Environment", value: "production" },
|
|
94
|
+
{ key: "Team", value: "platform" },
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
To apply tags across all members of a composite, use `propagate`:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { propagate } from "@intentius/chant";
|
|
103
|
+
|
|
104
|
+
export const api = propagate(
|
|
105
|
+
LambdaApi({ name: "myApi", code: lambdaCode }),
|
|
106
|
+
{ tags: [{ key: "env", value: "prod" }] },
|
|
107
|
+
);
|
|
108
|
+
// All expanded members (role, function, permission) will have these tags
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
See [Composites](#composites) for more on `propagate`.
|
|
112
|
+
|
|
113
|
+
## Intrinsic Functions
|
|
114
|
+
|
|
115
|
+
### Fn::Sub
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { Sub, AWS } from "@intentius/chant-lexicon-aws";
|
|
119
|
+
|
|
120
|
+
const url = Sub`https://${bucket.domainName}/path`;
|
|
121
|
+
const arn = Sub`arn:aws:s3:::${AWS.Region}:${AWS.AccountId}:*`;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Fn::GetAtt
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Preferred: use AttrRef directly
|
|
128
|
+
const arnRef = myBucket.arn;
|
|
129
|
+
|
|
130
|
+
// Or explicit:
|
|
131
|
+
import { GetAtt } from "@intentius/chant-lexicon-aws";
|
|
132
|
+
const bucketArn = GetAtt("MyBucket", "Arn");
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Fn::Ref, Fn::Join, Fn::If, Fn::Select, Fn::Split, Fn::Base64
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { Ref, Join, If, Select, Split, Base64 } from "@intentius/chant-lexicon-aws";
|
|
139
|
+
|
|
140
|
+
const paramRef = Ref("MyParameter");
|
|
141
|
+
const joined = Join("-", ["prefix", AWS.StackName, "suffix"]);
|
|
142
|
+
const value = If("UseProduction", "prod-value", "dev-value");
|
|
143
|
+
const firstItem = Select(0, Split(",", "a,b,c"));
|
|
144
|
+
const userData = Base64("#!/bin/bash\necho hello");
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Pseudo-Parameters
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { AWS, Sub } from "@intentius/chant-lexicon-aws";
|
|
151
|
+
|
|
152
|
+
const endpoint = Sub`https://s3.${AWS.Region}.${AWS.URLSuffix}`;
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Pseudo-parameter | Description |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `AWS.StackName` | Name of the stack |
|
|
158
|
+
| `AWS.Region` | AWS region where stack is created |
|
|
159
|
+
| `AWS.AccountId` | AWS account ID |
|
|
160
|
+
| `AWS.StackId` | Stack ID |
|
|
161
|
+
| `AWS.URLSuffix` | Domain suffix (usually `amazonaws.com`) |
|
|
162
|
+
| `AWS.Partition` | Partition (`aws`, `aws-cn`, `aws-us-gov`) |
|
|
163
|
+
| `AWS.NotificationARNs` | Notification ARNs |
|
|
164
|
+
| `AWS.NoValue` | Removes property when used with `Fn::If` |
|
|
165
|
+
|
|
166
|
+
## Examples
|
|
167
|
+
|
|
168
|
+
Two runnable examples live in `examples/`. Both have tests you can run with `bun test`.
|
|
169
|
+
|
|
170
|
+
### Getting Started (`examples/getting-started/`)
|
|
171
|
+
|
|
172
|
+
Declares 4 resources across separate files: two S3 buckets, an IAM role, and a Lambda function.
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
src/
|
|
176
|
+
├── _.ts # Barrel — re-exports lexicon + auto-discovers sibling files
|
|
177
|
+
├── defaults.ts # Shared config: encryption, versioning, public access block
|
|
178
|
+
├── data-bucket.ts # S3 bucket using barrel defaults
|
|
179
|
+
├── logs-bucket.ts # S3 bucket for access logs
|
|
180
|
+
├── role.ts # IAM role with Lambda assume-role policy
|
|
181
|
+
└── handler.ts # Lambda function referencing role and bucket
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Patterns demonstrated:**
|
|
185
|
+
|
|
186
|
+
1. **Flat declarations** — sub-resources like `encryptionDefault` and `publicAccessBlock` are their own named exports in `defaults.ts`, then referenced by other files via the barrel
|
|
187
|
+
2. **Barrel sharing** — `import * as _ from "./_"` gives every file access to all siblings via `_.$`
|
|
188
|
+
3. **Cross-resource references** — `$.dataBucket.arn` and `$.functionRole.arn` automatically serialize to `Fn::GetAtt`
|
|
189
|
+
4. **Intrinsics** — `Sub` with pseudo-parameters for dynamic naming: `Sub`\``${AWS.StackName}-data`\`
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// handler.ts — references role and bucket from other files
|
|
193
|
+
export const handler = new _.Function({
|
|
194
|
+
functionName: _.Sub`${_.AWS.StackName}-handler`,
|
|
195
|
+
handler: "index.handler",
|
|
196
|
+
runtime: "nodejs20.x",
|
|
197
|
+
role: _.$.functionRole.arn, // cross-file AttrRef
|
|
198
|
+
code: lambdaCode,
|
|
199
|
+
environment: { variables: { BUCKET_ARN: _.$.dataBucket.arn } },
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Advanced (`examples/advanced/`)
|
|
204
|
+
|
|
205
|
+
Builds on the getting-started patterns with composites, composite presets, custom lint rules, and IAM inline policies.
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
src/
|
|
209
|
+
├── _.ts # Barrel — also re-exports Composite from core
|
|
210
|
+
├── chant.config.ts # Lint config: strict preset + custom plugin
|
|
211
|
+
├── defaults.ts # Encryption, versioning, access block, Lambda trust policy
|
|
212
|
+
├── data-bucket.ts # S3 bucket
|
|
213
|
+
├── lambda-api.ts # Composite factory + SecureApi/HighMemoryApi presets
|
|
214
|
+
├── health-api.ts # Uses SecureApi preset — minimal health check
|
|
215
|
+
├── upload-api.ts # Uses SecureApi + S3 PutObject policy
|
|
216
|
+
├── process-api.ts # Uses HighMemoryApi + S3 read/write policy
|
|
217
|
+
└── lint/
|
|
218
|
+
└── api-timeout.ts # Custom WAW012 rule: Lambda API timeout check
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**What it adds over getting-started:**
|
|
222
|
+
|
|
223
|
+
- **Composites** — `LambdaApi` groups a Role + Function + Permission into a reusable unit (see [Composites](#composites))
|
|
224
|
+
- **Composite presets** — `SecureApi` and `HighMemoryApi` wrap `LambdaApi` with sensible defaults for different workloads
|
|
225
|
+
- **Inline IAM policies** — `upload-api.ts` and `process-api.ts` attach `Role_Policy` objects to restrict S3 access per-API
|
|
226
|
+
- **Custom lint rules** — `api-timeout.ts` enforces API Gateway's 29-second timeout limit (see [Custom Lint Rules](#custom-lint-rules))
|
|
227
|
+
- **Lint configuration** — `chant.config.ts` extends the strict preset and loads the custom plugin
|
|
228
|
+
|
|
229
|
+
The advanced example produces 10 CloudFormation resources: 1 S3 bucket + 3 composites × 3 resources each (role, function, permission).
|
|
230
|
+
|
|
231
|
+
## Composites
|
|
232
|
+
|
|
233
|
+
Composites group related resources into reusable factories. A composite is a function that takes typed props and returns named members:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { Composite, Sub, AWS } from "@intentius/chant-lexicon-aws";
|
|
237
|
+
|
|
238
|
+
export const LambdaApi = Composite<LambdaApiProps>((props) => {
|
|
239
|
+
const role = new Role({
|
|
240
|
+
assumeRolePolicyDocument: $.lambdaTrustPolicy,
|
|
241
|
+
managedPolicyArns: [$.lambdaBasicExecutionArn],
|
|
242
|
+
policies: props.policies,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const func = new Function({
|
|
246
|
+
functionName: props.name,
|
|
247
|
+
runtime: props.runtime,
|
|
248
|
+
handler: props.handler,
|
|
249
|
+
code: props.code,
|
|
250
|
+
role: role.arn, // cross-reference within the composite
|
|
251
|
+
timeout: props.timeout,
|
|
252
|
+
memorySize: props.memorySize,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const permission = new Permission({
|
|
256
|
+
functionName: func.arn,
|
|
257
|
+
action: "lambda:InvokeFunction",
|
|
258
|
+
principal: "apigateway.amazonaws.com",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return { role, func, permission };
|
|
262
|
+
}, "LambdaApi");
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Instantiate it like a function call:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
export const healthApi = LambdaApi({
|
|
269
|
+
name: Sub`${AWS.StackName}-health`,
|
|
270
|
+
runtime: "nodejs20.x",
|
|
271
|
+
handler: "index.handler",
|
|
272
|
+
code: { zipFile: `exports.handler = async () => ({ statusCode: 200 });` },
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
During build, composites expand to flat resources: `healthApi_role`, `healthApi_func`, `healthApi_permission`.
|
|
277
|
+
|
|
278
|
+
### `withDefaults` — composite presets
|
|
279
|
+
|
|
280
|
+
Wraps a composite with pre-applied defaults. Defaulted props become optional:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { withDefaults } from "@intentius/chant";
|
|
284
|
+
|
|
285
|
+
const SecureApi = withDefaults(LambdaApi, {
|
|
286
|
+
runtime: "nodejs20.x",
|
|
287
|
+
handler: "index.handler",
|
|
288
|
+
timeout: 10,
|
|
289
|
+
memorySize: 256,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Only name and code are required now
|
|
293
|
+
export const healthApi = SecureApi({
|
|
294
|
+
name: Sub`${AWS.StackName}-health`,
|
|
295
|
+
code: { zipFile: `exports.handler = async () => ({ statusCode: 200 });` },
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Composable — stack defaults on top of defaults
|
|
299
|
+
const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 });
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`withDefaults` preserves the original composite's identity — it shares the same `_id` and `compositeName`, and does not create a new registry entry.
|
|
303
|
+
|
|
304
|
+
### `propagate` — shared properties
|
|
305
|
+
|
|
306
|
+
Attaches properties that merge into every member of a composite during expansion:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { propagate } from "@intentius/chant";
|
|
310
|
+
|
|
311
|
+
export const api = propagate(
|
|
312
|
+
LambdaApi({ name: "myApi", code: lambdaCode }),
|
|
313
|
+
{ tags: [{ key: "env", value: "prod" }] },
|
|
314
|
+
);
|
|
315
|
+
// role, func, and permission all get the env tag
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Merge semantics:
|
|
319
|
+
- **Scalars** — member-specific value wins over shared
|
|
320
|
+
- **Arrays** (e.g. tags) — shared values are prepended, then member values appended
|
|
321
|
+
- **`undefined`** — stripped from shared props, never overwrites
|
|
322
|
+
|
|
323
|
+
## Custom Lint Rules
|
|
324
|
+
|
|
325
|
+
Chant's lint engine runs TypeScript AST visitors. You can write project-specific rules that enforce domain conventions.
|
|
326
|
+
|
|
327
|
+
### Anatomy of a lint rule
|
|
328
|
+
|
|
329
|
+
A lint rule implements the `LintRule` interface:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
333
|
+
import * as ts from "typescript";
|
|
334
|
+
|
|
335
|
+
export const apiTimeoutRule: LintRule = {
|
|
336
|
+
id: "WAW012", // unique ID (WAW = AWS-specific prefix)
|
|
337
|
+
severity: "error", // "error" | "warning"
|
|
338
|
+
category: "correctness", // "correctness" | "style" | "security"
|
|
339
|
+
|
|
340
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
341
|
+
const { sourceFile } = context;
|
|
342
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
343
|
+
|
|
344
|
+
function visit(node: ts.Node): void {
|
|
345
|
+
// Walk the AST looking for violations...
|
|
346
|
+
if (ts.isCallExpression(node)) {
|
|
347
|
+
// Check call arguments, report diagnostics
|
|
348
|
+
}
|
|
349
|
+
ts.forEachChild(node, visit);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
visit(sourceFile);
|
|
353
|
+
return diagnostics;
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
The `check` function receives a `LintContext` containing the TypeScript `sourceFile` and returns an array of diagnostics with file, line, column, and message.
|
|
359
|
+
|
|
360
|
+
### Example: API Gateway timeout (`WAW012`)
|
|
361
|
+
|
|
362
|
+
The advanced example includes a custom rule that flags Lambda API composites with `timeout > 29` — API Gateway's synchronous limit:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// lint/api-timeout.ts
|
|
366
|
+
const API_FACTORIES = new Set(["LambdaApi", "SecureApi", "HighMemoryApi"]);
|
|
367
|
+
|
|
368
|
+
export const apiTimeoutRule: LintRule = {
|
|
369
|
+
id: "WAW012",
|
|
370
|
+
severity: "error",
|
|
371
|
+
category: "correctness",
|
|
372
|
+
|
|
373
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
374
|
+
// Walks AST for calls to API factory functions,
|
|
375
|
+
// checks timeout property value, reports if > 29
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Registering custom rules
|
|
381
|
+
|
|
382
|
+
Add a `chant.config.ts` to your project root:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
// chant.config.ts
|
|
386
|
+
export default {
|
|
387
|
+
lint: {
|
|
388
|
+
extends: ["@intentius/chant/lint/presets/strict"],
|
|
389
|
+
rules: {
|
|
390
|
+
COR004: "off", // disable a built-in rule
|
|
391
|
+
},
|
|
392
|
+
plugins: ["./lint/api-timeout.ts"], // load custom rules
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
The `plugins` array accepts relative paths. Each plugin module should export a `LintRule` object (named or as `apiTimeoutRule`, etc.).
|
|
398
|
+
|
|
399
|
+
### Built-in AWS lint rules
|
|
400
|
+
|
|
401
|
+
**`hardcoded-region`** — detects hardcoded AWS region strings:
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// Bad — hardcoded region
|
|
405
|
+
const endpoint = "s3.us-east-1.amazonaws.com";
|
|
406
|
+
|
|
407
|
+
// Good — use pseudo-parameter
|
|
408
|
+
const endpoint = Sub`s3.${AWS.Region}.amazonaws.com`;
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Code Generation
|
|
412
|
+
|
|
413
|
+
The AWS lexicon uses core's `generatePipeline` with AWS-specific callbacks:
|
|
414
|
+
|
|
415
|
+
- `codegen/generate.ts` — calls core `generatePipeline<SchemaParseResult>` with AWS callbacks
|
|
416
|
+
- `codegen/naming.ts` — extends core `NamingStrategy` with AWS data tables
|
|
417
|
+
- `codegen/package.ts` — calls core `packagePipeline` with AWS manifest and skill collector
|
|
418
|
+
- `spec/fetch.ts` — uses core `fetchWithCache` + `extractFromZip` for CloudFormation schema
|
|
419
|
+
|
|
420
|
+
## Template Import
|
|
421
|
+
|
|
422
|
+
Convert existing CloudFormation JSON/YAML to TypeScript:
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { parseTemplate } from "@intentius/chant-lexicon-aws";
|
|
426
|
+
|
|
427
|
+
const ir = parseTemplate(cfTemplate);
|
|
428
|
+
// Generate TypeScript from the intermediate representation
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Related Packages
|
|
432
|
+
|
|
433
|
+
- `@intentius/chant` — core functionality, type system, and CLI
|
|
434
|
+
- `@intentius/chant-test-utils` — testing utilities
|
|
435
|
+
|
|
436
|
+
## License
|
|
437
|
+
|
|
438
|
+
See the main project LICENSE file.
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intentius/chant-lexicon-aws",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": ["src/", "dist/"],
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./*": "./src/*",
|
|
12
|
+
"./manifest": "./dist/manifest.json",
|
|
13
|
+
"./meta": "./dist/meta.json",
|
|
14
|
+
"./types": "./dist/types/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"generate": "bun run src/codegen/generate-cli.ts",
|
|
18
|
+
"validate": "bun run src/validate-cli.ts",
|
|
19
|
+
"docs": "bun src/codegen/docs-cli.ts",
|
|
20
|
+
"prepack": "bun run generate && bun run validate"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@intentius/chant": "0.0.1",
|
|
24
|
+
"fflate": "^0.8.2",
|
|
25
|
+
"js-yaml": "^4.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
|
|
2
|
+
|
|
3
|
+
exports[`snapshot tests Bucket lexicon entry 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"attrs": {
|
|
6
|
+
"arn": "Arn",
|
|
7
|
+
},
|
|
8
|
+
"createOnly": [
|
|
9
|
+
"BucketName",
|
|
10
|
+
],
|
|
11
|
+
"kind": "resource",
|
|
12
|
+
"lexicon": "aws",
|
|
13
|
+
"primaryIdentifier": [
|
|
14
|
+
"BucketName",
|
|
15
|
+
],
|
|
16
|
+
"propertyConstraints": {
|
|
17
|
+
"BucketName": {
|
|
18
|
+
"maxLength": 63,
|
|
19
|
+
"minLength": 3,
|
|
20
|
+
"pattern": "^[a-z0-9][a-z0-9.-]*[a-z0-9]$",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
"resourceType": "AWS::S3::Bucket",
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
exports[`snapshot tests Function lexicon entry 1`] = `
|
|
28
|
+
{
|
|
29
|
+
"attrs": {
|
|
30
|
+
"arn": "Arn",
|
|
31
|
+
},
|
|
32
|
+
"kind": "resource",
|
|
33
|
+
"lexicon": "aws",
|
|
34
|
+
"primaryIdentifier": [
|
|
35
|
+
"FunctionName",
|
|
36
|
+
],
|
|
37
|
+
"propertyConstraints": {
|
|
38
|
+
"FunctionName": {
|
|
39
|
+
"maxLength": 64,
|
|
40
|
+
"minLength": 1,
|
|
41
|
+
},
|
|
42
|
+
"Handler": {
|
|
43
|
+
"maxLength": 128,
|
|
44
|
+
"pattern": "^[^\\s]+$",
|
|
45
|
+
},
|
|
46
|
+
"MemorySize": {
|
|
47
|
+
"maximum": 10240,
|
|
48
|
+
"minimum": 128,
|
|
49
|
+
},
|
|
50
|
+
"Role": {
|
|
51
|
+
"pattern": "^arn:(aws[a-zA-Z-]*)?:iam::\\d{12}:role/?[a-zA-Z_0-9+=,.@\\-_/]+$",
|
|
52
|
+
},
|
|
53
|
+
"Runtime": {
|
|
54
|
+
"enum": [
|
|
55
|
+
"nodejs20.x",
|
|
56
|
+
"nodejs18.x",
|
|
57
|
+
"python3.12",
|
|
58
|
+
"python3.11",
|
|
59
|
+
"java21",
|
|
60
|
+
"java17",
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"resourceType": "AWS::Lambda::Function",
|
|
65
|
+
"runtimeDeprecations": {
|
|
66
|
+
"dotnet6": "deprecated",
|
|
67
|
+
"java8": "deprecated",
|
|
68
|
+
"nodejs14.x": "deprecated",
|
|
69
|
+
"nodejs16.x": "deprecated",
|
|
70
|
+
"nodejs18.x": "approaching_eol",
|
|
71
|
+
"python3.7": "deprecated",
|
|
72
|
+
"python3.8": "deprecated",
|
|
73
|
+
"python3.9": "approaching_eol",
|
|
74
|
+
"ruby2.7": "deprecated",
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
exports[`snapshot tests Role lexicon entry 1`] = `
|
|
80
|
+
{
|
|
81
|
+
"attrs": {
|
|
82
|
+
"arn": "Arn",
|
|
83
|
+
"roleId": "RoleId",
|
|
84
|
+
},
|
|
85
|
+
"kind": "resource",
|
|
86
|
+
"lexicon": "aws",
|
|
87
|
+
"primaryIdentifier": [
|
|
88
|
+
"RoleName",
|
|
89
|
+
],
|
|
90
|
+
"propertyConstraints": {
|
|
91
|
+
"RoleName": {
|
|
92
|
+
"maxLength": 64,
|
|
93
|
+
"minLength": 1,
|
|
94
|
+
"pattern": "^[\\w+=,.@-]+$",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
"resourceType": "AWS::IAM::Role",
|
|
98
|
+
}
|
|
99
|
+
`;
|
|
100
|
+
|
|
101
|
+
exports[`snapshot tests generated resource names 1`] = `
|
|
102
|
+
[
|
|
103
|
+
"Api",
|
|
104
|
+
"Api_Auth",
|
|
105
|
+
"Api_CorsConfiguration",
|
|
106
|
+
"Application",
|
|
107
|
+
"AttributeDefinition",
|
|
108
|
+
"Auth",
|
|
109
|
+
"Bucket",
|
|
110
|
+
"BucketEncryption",
|
|
111
|
+
"Bucket_BucketEncryption",
|
|
112
|
+
"Bucket_Tag",
|
|
113
|
+
"Bucket_VersioningConfiguration",
|
|
114
|
+
"Code",
|
|
115
|
+
"Connector",
|
|
116
|
+
"DeadLetterQueue",
|
|
117
|
+
"DeploymentPreference",
|
|
118
|
+
"EventSource",
|
|
119
|
+
"Function",
|
|
120
|
+
"Function_Code",
|
|
121
|
+
"Function_Tag",
|
|
122
|
+
"GraphQLApi",
|
|
123
|
+
"HttpApi",
|
|
124
|
+
"HttpApi_CorsConfiguration",
|
|
125
|
+
"KeySchema",
|
|
126
|
+
"LayerVersion",
|
|
127
|
+
"LogGroup",
|
|
128
|
+
"Policy",
|
|
129
|
+
"PrimaryKey",
|
|
130
|
+
"Role",
|
|
131
|
+
"Role_Policy",
|
|
132
|
+
"Role_Tag",
|
|
133
|
+
"SSESpecification",
|
|
134
|
+
"ServerlessFunction",
|
|
135
|
+
"ServerlessFunction_DeadLetterQueue",
|
|
136
|
+
"ServerlessFunction_DeploymentPreference",
|
|
137
|
+
"ServerlessFunction_Environment",
|
|
138
|
+
"ServerlessFunction_EventSource",
|
|
139
|
+
"ServerlessFunction_S3Location",
|
|
140
|
+
"ServerlessFunction_VpcConfig",
|
|
141
|
+
"SimpleTable",
|
|
142
|
+
"SimpleTable_PrimaryKey",
|
|
143
|
+
"StateMachine",
|
|
144
|
+
"StateMachine_S3Location",
|
|
145
|
+
"Subscription",
|
|
146
|
+
"Table",
|
|
147
|
+
"Table_AttributeDefinition",
|
|
148
|
+
"Table_KeySchema",
|
|
149
|
+
"Table_SSESpecification",
|
|
150
|
+
"Table_Tag",
|
|
151
|
+
"Topic",
|
|
152
|
+
"Topic_Subscription",
|
|
153
|
+
"Topic_Tag",
|
|
154
|
+
"VersioningConfiguration",
|
|
155
|
+
"VpcConfig",
|
|
156
|
+
]
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
exports[`snapshot tests Bucket .d.ts class declaration 1`] = `
|
|
160
|
+
"export declare class Bucket {
|
|
161
|
+
constructor(props: {
|
|
162
|
+
/** A name for the bucket. */
|
|
163
|
+
bucketName: string;
|
|
164
|
+
/** The Amazon Resource Name (ARN) of the bucket. */
|
|
165
|
+
arn?: string;
|
|
166
|
+
/** Specifies default encryption for a bucket. */
|
|
167
|
+
bucketEncryption?: Bucket_BucketEncryption;
|
|
168
|
+
/** An arbitrary set of tags (key-value pairs) for this S3 bucket. */
|
|
169
|
+
tags?: Bucket_Tag[];
|
|
170
|
+
/** Enables multiple versions of all objects in this bucket. */
|
|
171
|
+
versioningConfiguration?: Bucket_VersioningConfiguration;
|
|
172
|
+
});
|
|
173
|
+
readonly arn: string;
|
|
174
|
+
}"
|
|
175
|
+
`;
|
|
176
|
+
|
|
177
|
+
exports[`snapshot tests Function .d.ts class declaration 1`] = `
|
|
178
|
+
"export declare class Function {
|
|
179
|
+
constructor(props: {
|
|
180
|
+
/** The code for the function. */
|
|
181
|
+
code: Function_Code;
|
|
182
|
+
/** The ARN of the function's execution role. */
|
|
183
|
+
role: string;
|
|
184
|
+
/** The ARN of the function. */
|
|
185
|
+
arn?: string;
|
|
186
|
+
/** The name of the Lambda function. */
|
|
187
|
+
functionName?: string;
|
|
188
|
+
/** The name of the method within your code that Lambda calls to run your function. */
|
|
189
|
+
handler?: string;
|
|
190
|
+
/** The amount of memory available to the function at runtime. */
|
|
191
|
+
memorySize?: number;
|
|
192
|
+
/** The identifier of the function's runtime. */
|
|
193
|
+
runtime?: "java17" | "java21" | "nodejs18.x" | "nodejs20.x" | "python3.11" | "python3.12";
|
|
194
|
+
});
|
|
195
|
+
readonly arn: string;
|
|
196
|
+
}"
|
|
197
|
+
`;
|