@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/src/serializer.ts CHANGED
@@ -3,7 +3,6 @@ import { isPropertyDeclarable } from "@intentius/chant/declarable";
3
3
  import type { Serializer, SerializerResult } from "@intentius/chant/serializer";
4
4
  import type { LexiconOutput } from "@intentius/chant/lexicon-output";
5
5
  import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-walker";
6
- import { toPascalCase } from "@intentius/chant/codegen/case";
7
6
  import { isChildProject, type ChildProjectInstance } from "@intentius/chant/child-project";
8
7
  import { isStackOutput, type StackOutput } from "@intentius/chant/stack-output";
9
8
 
@@ -68,7 +67,7 @@ interface CFOutput {
68
67
  */
69
68
  function cfnVisitor(entityNames: Map<Declarable, string>): SerializerVisitor {
70
69
  return {
71
- attrRef: (name, attr) => ({ "Fn::GetAttr": [name, attr] }),
70
+ attrRef: (name, attr) => ({ "Fn::GetAtt": [name, attr] }),
72
71
  resourceRef: (name) => ({ Ref: name }),
73
72
  propertyDeclarable: (entity, walk) => {
74
73
  if (!("props" in entity) || typeof entity.props !== "object" || entity.props === null) {
@@ -78,26 +77,19 @@ function cfnVisitor(entityNames: Map<Declarable, string>): SerializerVisitor {
78
77
  const cfProps: Record<string, unknown> = {};
79
78
  for (const [key, value] of Object.entries(props)) {
80
79
  if (value !== undefined) {
81
- const cfKey = toPascalCase(key);
82
- cfProps[cfKey] = walk(value);
80
+ cfProps[key] = walk(value);
83
81
  }
84
82
  }
85
83
  return Object.keys(cfProps).length > 0 ? cfProps : undefined;
86
84
  },
87
- transformKey: toPascalCase,
88
85
  };
89
86
  }
90
87
 
91
88
  /**
92
89
  * Convert a value to CF-compatible JSON using the generic walker.
93
90
  */
94
- function toCFValue(value: unknown, entityNames: Map<Declarable, string>, convertKeys = false): unknown {
95
- const visitor = cfnVisitor(entityNames);
96
- if (!convertKeys) {
97
- // When not converting keys, use a visitor without transformKey
98
- return walkValue(value, entityNames, { ...visitor, transformKey: undefined });
99
- }
100
- return walkValue(value, entityNames, visitor);
91
+ function toCFValue(value: unknown, entityNames: Map<Declarable, string>): unknown {
92
+ return walkValue(value, entityNames, cfnVisitor(entityNames));
101
93
  }
102
94
 
103
95
  /**
@@ -116,8 +108,7 @@ function toProperties(
116
108
 
117
109
  for (const [key, value] of Object.entries(props)) {
118
110
  if (value !== undefined) {
119
- const cfKey = toPascalCase(key);
120
- cfProps[cfKey] = toCFValue(value, entityNames, true);
111
+ cfProps[key] = toCFValue(value, entityNames);
121
112
  }
122
113
  }
123
114
 
@@ -231,7 +222,7 @@ function serializeToTemplate(
231
222
  const logicalName = ref.getLogicalName();
232
223
  if (logicalName) {
233
224
  const output: CFOutput = {
234
- Value: { "Fn::GetAttr": [logicalName, ref.attribute] },
225
+ Value: { "Fn::GetAtt": [logicalName, ref.attribute] },
235
226
  };
236
227
  if (stackOutput.description) {
237
228
  output.Description = stackOutput.description;
@@ -247,7 +238,7 @@ function serializeToTemplate(
247
238
  for (const output of outputs) {
248
239
  template.Outputs[output.outputName] = {
249
240
  Value: {
250
- "Fn::GetAttr": [output.sourceEntity, output.sourceAttribute],
241
+ "Fn::GetAtt": [output.sourceEntity, output.sourceAttribute],
251
242
  },
252
243
  };
253
244
  }
package/src/spec/parse.ts CHANGED
@@ -36,7 +36,7 @@ export interface ParsedAttribute {
36
36
 
37
37
  export interface ParsedPropertyType {
38
38
  name: string;
39
- cfnType: string;
39
+ specType: string;
40
40
  properties: ParsedProperty[];
41
41
  }
42
42
 
@@ -130,7 +130,7 @@ export function parseCFNSchema(data: string | Buffer): SchemaParseResult {
130
130
  }
131
131
  propertyTypes.push({
132
132
  name: `${shortName}_${defName}`,
133
- cfnType: defName,
133
+ specType: defName,
134
134
  properties: defProps,
135
135
  });
136
136
  }
@@ -1,41 +0,0 @@
1
- ---
2
- name: aws-cloudformation
3
- description: AWS CloudFormation best practices and common patterns
4
- ---
5
-
6
- # AWS CloudFormation with Chant
7
-
8
- ## Common Resource Types
9
-
10
- - `AWS::S3::Bucket` — Object storage
11
- - `AWS::Lambda::Function` — Serverless compute
12
- - `AWS::DynamoDB::Table` — NoSQL database
13
- - `AWS::IAM::Role` — Identity and access management
14
- - `AWS::SNS::Topic` — Pub/sub messaging
15
- - `AWS::SQS::Queue` — Message queue
16
- - `AWS::EC2::SecurityGroup` — Network firewall rules
17
-
18
- ## Intrinsic Functions
19
-
20
- - `Sub` — String interpolation with `${}` syntax
21
- - `Ref` — Reference a resource or parameter
22
- - `GetAtt` — Get a resource attribute (e.g. ARN)
23
- - `If` — Conditional value based on a condition
24
- - `Join` — Join strings with a delimiter
25
- - `Select` — Pick an item from a list by index
26
-
27
- ## Pseudo Parameters
28
-
29
- - `AWS::StackName` — Name of the current stack
30
- - `AWS::Region` — Current deployment region
31
- - `AWS::AccountId` — Current AWS account ID
32
- - `AWS::Partition` — Partition (aws, aws-cn, aws-us-gov)
33
-
34
- ## Best Practices
35
-
36
- 1. **Always enable encryption** — Use `BucketEncryption` for S3, `SSESpecification` for DynamoDB
37
- 2. **Block public access** — Set `PublicAccessBlockConfiguration` on all S3 buckets
38
- 3. **Use least-privilege IAM** — Avoid `*` in IAM policy actions and resources
39
- 4. **Enable versioning** — Turn on `VersioningConfiguration` for data buckets
40
- 5. **Use Sub for dynamic names** — `Sub\`\${AWS::StackName}-suffix\`` for unique naming
41
- 6. **Share config via barrel files** — Put common settings in `_.ts` and import as `* as _`
@@ -1,80 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { mkdirSync, writeFileSync, rmSync, readFileSync } from "fs";
3
- import { join } from "path";
4
- import { tmpdir } from "os";
5
- import { snapshotArtifacts, saveSnapshot, restoreSnapshot, listSnapshots } from "./rollback";
6
-
7
- function makeTempDir(): string {
8
- const dir = join(tmpdir(), `chant-rollback-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
9
- mkdirSync(dir, { recursive: true });
10
- return dir;
11
- }
12
-
13
- describe("rollback", () => {
14
- test("snapshot captures generated files", () => {
15
- const dir = makeTempDir();
16
- const genDir = join(dir, "generated");
17
- mkdirSync(genDir, { recursive: true });
18
-
19
- writeFileSync(join(genDir, "lexicon-aws.json"), '{"Bucket":{"kind":"resource"}}');
20
- writeFileSync(join(genDir, "index.d.ts"), "declare class Bucket {}");
21
- writeFileSync(join(genDir, "index.ts"), "export const Bucket = {};");
22
-
23
- const snapshot = snapshotArtifacts(genDir);
24
- expect(snapshot.files["lexicon-aws.json"]).toBeDefined();
25
- expect(snapshot.files["index.d.ts"]).toBeDefined();
26
- expect(snapshot.files["index.ts"]).toBeDefined();
27
- expect(snapshot.hashes["lexicon-aws.json"]).toBeDefined();
28
- expect(snapshot.resourceCount).toBe(1);
29
-
30
- rmSync(dir, { recursive: true, force: true });
31
- });
32
-
33
- test("save and list snapshots", () => {
34
- const dir = makeTempDir();
35
- const snapshotsDir = join(dir, ".snapshots");
36
-
37
- const snapshot = {
38
- timestamp: "2025-01-01T00:00:00.000Z",
39
- files: { "test.json": "{}" },
40
- hashes: { "test.json": "abc123" },
41
- resourceCount: 0,
42
- };
43
-
44
- saveSnapshot(snapshot, snapshotsDir);
45
- const list = listSnapshots(snapshotsDir);
46
- expect(list.length).toBe(1);
47
- expect(list[0].timestamp).toBe("2025-01-01T00:00:00.000Z");
48
-
49
- rmSync(dir, { recursive: true, force: true });
50
- });
51
-
52
- test("restore snapshot overwrites generated files", () => {
53
- const dir = makeTempDir();
54
- const genDir = join(dir, "generated");
55
- const snapshotsDir = join(dir, ".snapshots");
56
- mkdirSync(genDir, { recursive: true });
57
-
58
- // Write original files
59
- writeFileSync(join(genDir, "lexicon-aws.json"), '{"original":true}');
60
-
61
- // Snapshot them
62
- const snapshot = snapshotArtifacts(genDir);
63
- const snapshotPath = saveSnapshot(snapshot, snapshotsDir);
64
-
65
- // Overwrite with new content
66
- writeFileSync(join(genDir, "lexicon-aws.json"), '{"modified":true}');
67
- expect(readFileSync(join(genDir, "lexicon-aws.json"), "utf-8")).toBe('{"modified":true}');
68
-
69
- // Restore
70
- restoreSnapshot(snapshotPath, genDir);
71
- expect(readFileSync(join(genDir, "lexicon-aws.json"), "utf-8")).toBe('{"original":true}');
72
-
73
- rmSync(dir, { recursive: true, force: true });
74
- });
75
-
76
- test("listSnapshots returns empty for nonexistent dir", () => {
77
- const list = listSnapshots("/nonexistent/path");
78
- expect(list).toHaveLength(0);
79
- });
80
- });
@@ -1,20 +0,0 @@
1
- /**
2
- * Re-export from core with AWS-specific artifact names.
3
- */
4
- import {
5
- snapshotArtifacts as _snapshotArtifacts,
6
- saveSnapshot,
7
- restoreSnapshot,
8
- listSnapshots,
9
- } from "@intentius/chant/codegen/rollback";
10
- export type { ArtifactSnapshot, SnapshotInfo } from "@intentius/chant/codegen/rollback";
11
- export { saveSnapshot, restoreSnapshot, listSnapshots };
12
-
13
- const AWS_ARTIFACT_NAMES = ["lexicon-aws.json", "index.d.ts", "index.ts"];
14
-
15
- /**
16
- * Snapshot AWS lexicon artifacts.
17
- */
18
- export function snapshotArtifacts(generatedDir: string) {
19
- return _snapshotArtifacts(generatedDir, AWS_ARTIFACT_NAMES);
20
- }