react-hcl 0.1.0

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 ADDED
@@ -0,0 +1,266 @@
1
+ # react-hcl
2
+
3
+ A transpiler that converts TSX into Terraform `.tf` files. Write Terraform configurations using JSX/TSX syntax with a custom JSX runtime (no React dependency).
4
+
5
+ ## Why React for IaC?
6
+
7
+ This project starts from a readability problem in IaC.
8
+ In HCL, reference direction and state flow are hard to constrain at the notation level, so causality tends to spread across the config.
9
+ That makes end-to-end reasoning from input to output expensive.
10
+ We use React because component boundaries and data flow provide structure for understanding first.
11
+ The goal is not to rewrite Terraform in JS, but to structurally improve IaC comprehensibility.
12
+
13
+ ## Usage
14
+
15
+ Use the CLI to print Terraform to stdout or write it to a file.
16
+
17
+ ```bash
18
+ react-hcl infra.tsx # output to stdout
19
+ react-hcl infra.tsx -o ./tf/main.tf # write to file
20
+ ```
21
+
22
+ ## Example
23
+
24
+ `main.tsx` — A VPC with a web server, using a verified module and a custom component:
25
+
26
+ ```tsx
27
+ import { DataSource, Module, Output, Provider, raw, useRef } from "react-hcl";
28
+ import { WebServer } from "./web-server";
29
+
30
+ function Main({ region, instanceType }) {
31
+ const azRef = useRef();
32
+ const vpcRef = useRef();
33
+
34
+ return (
35
+ <>
36
+ <Provider type="aws" region={region} />
37
+ <DataSource type="aws_availability_zones" name="available" ref={azRef} />
38
+
39
+ <Module
40
+ name="vpc"
41
+ ref={vpcRef}
42
+ source="terraform-aws-modules/vpc/aws"
43
+ cidr="10.0.0.0/16"
44
+ azs={raw(`${azRef.names}`)}
45
+ public_subnets={["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]}
46
+ enable_dns_hostnames={true}
47
+ />
48
+
49
+ <WebServer
50
+ vpcId={vpcRef.vpc_id}
51
+ subnetId={raw(`${vpcRef.public_subnets}[0]`)}
52
+ instanceType={instanceType}
53
+ />
54
+
55
+ <Output name="vpc_id" value={vpcRef.vpc_id} />
56
+ </>
57
+ );
58
+ }
59
+
60
+ export default <Main region="us-east-1" instanceType="t3.micro" />;
61
+ ```
62
+
63
+ `web-server.tsx`
64
+
65
+ Component implementation for AMI lookup, security group rules, and EC2 instance creation.
66
+
67
+ ```tsx
68
+ import { DataSource, Resource, useRef } from "react-hcl";
69
+
70
+ export function WebServer({ vpcId, subnetId, instanceType }) {
71
+ const amiRef = useRef();
72
+ const sgRef = useRef();
73
+
74
+ return (
75
+ <>
76
+ <DataSource
77
+ type="aws_ami"
78
+ name="ubuntu"
79
+ ref={amiRef}
80
+ most_recent={true}
81
+ owners={["099720109477"]}
82
+ filter={[
83
+ { name: "name", values: ["ubuntu/images/hvm-ssd/ubuntu-*-amd64-server-*"] },
84
+ ]}
85
+ />
86
+ <Resource
87
+ type="aws_security_group"
88
+ name="web"
89
+ ref={sgRef}
90
+ vpc_id={vpcId}
91
+ />
92
+ <Resource
93
+ type="aws_vpc_security_group_ingress_rule"
94
+ name="web_http"
95
+ security_group_id={sgRef.id}
96
+ from_port={80}
97
+ to_port={80}
98
+ ip_protocol="tcp"
99
+ cidr_ipv4="0.0.0.0/0"
100
+ />
101
+ <Resource
102
+ type="aws_vpc_security_group_egress_rule"
103
+ name="web_all"
104
+ security_group_id={sgRef.id}
105
+ ip_protocol="-1"
106
+ cidr_ipv4="0.0.0.0/0"
107
+ />
108
+ <Resource
109
+ type="aws_instance"
110
+ name="web"
111
+ ami={amiRef.id}
112
+ instance_type={instanceType}
113
+ subnet_id={subnetId}
114
+ vpc_security_group_ids={[sgRef.id]}
115
+ />
116
+ </>
117
+ );
118
+ }
119
+ ```
120
+
121
+ Run the transpiler for `main.tsx`:
122
+
123
+ ```bash
124
+ $ react-hcl main.tsx
125
+ ```
126
+
127
+ <details>
128
+ <summary>Generated <code>.tf</code> — refs resolve to Terraform references, component boundaries dissolve into a flat file</summary>
129
+
130
+ Generated Terraform output:
131
+
132
+ ```hcl
133
+ provider "aws" {
134
+ region = "us-east-1"
135
+ }
136
+
137
+ data "aws_availability_zones" "available" {
138
+ }
139
+
140
+ module "vpc" {
141
+ source = "terraform-aws-modules/vpc/aws"
142
+ cidr = "10.0.0.0/16"
143
+ azs = data.aws_availability_zones.available.names
144
+ public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
145
+ enable_dns_hostnames = true
146
+ }
147
+
148
+ data "aws_ami" "ubuntu" {
149
+ most_recent = true
150
+ owners = ["099720109477"]
151
+
152
+ filter {
153
+ name = "name"
154
+ values = ["ubuntu/images/hvm-ssd/ubuntu-*-amd64-server-*"]
155
+ }
156
+ }
157
+
158
+ resource "aws_security_group" "web" {
159
+ vpc_id = module.vpc.vpc_id
160
+ }
161
+
162
+ resource "aws_vpc_security_group_ingress_rule" "web_http" {
163
+ security_group_id = aws_security_group.web.id
164
+ from_port = 80
165
+ to_port = 80
166
+ ip_protocol = "tcp"
167
+ cidr_ipv4 = "0.0.0.0/0"
168
+ }
169
+
170
+ resource "aws_vpc_security_group_egress_rule" "web_all" {
171
+ security_group_id = aws_security_group.web.id
172
+ ip_protocol = "-1"
173
+ cidr_ipv4 = "0.0.0.0/0"
174
+ }
175
+
176
+ resource "aws_instance" "web" {
177
+ ami = data.aws_ami.ubuntu.id
178
+ instance_type = "t3.micro"
179
+ subnet_id = module.vpc.public_subnets[0]
180
+ vpc_security_group_ids = [aws_security_group.web.id]
181
+ }
182
+
183
+ output "vpc_id" {
184
+ value = module.vpc.vpc_id
185
+ }
186
+ ```
187
+
188
+ </details>
189
+
190
+ See [`samples/`](samples/) for more examples including ECS Fargate and S3+CloudFront.
191
+
192
+ ## Components
193
+
194
+ | Component | HCL block |
195
+ |---|---|
196
+ | `<Resource>` | `resource "type" "name" { ... }` |
197
+ | `<DataSource>` | `data "type" "name" { ... }` |
198
+ | `<Variable>` | `variable "name" { ... }` |
199
+ | `<Output>` | `output "name" { ... }` |
200
+ | `<Locals>` | `locals { ... }` |
201
+ | `<Provider>` | `provider "type" { ... }` |
202
+ | `<Terraform>` | `terraform { ... }` |
203
+
204
+ ## Hooks & Helpers
205
+
206
+ - `useRef()` - Create a reference to a resource/data source (`ref.id`, `ref.arn`, etc.)
207
+ - `tf.var("name")` - Reference a variable (`var.name`)
208
+ - `tf.local("name")` - Reference a local value (`local.name`)
209
+
210
+ ## Installation
211
+
212
+ ### From npm
213
+
214
+ Install the published CLI globally from npm.
215
+
216
+ ```bash
217
+ npm install -g react-hcl
218
+ ```
219
+
220
+ ### Manual install from source
221
+
222
+ Clone, build, and link the CLI from source.
223
+
224
+ ```bash
225
+ git clone https://github.com/jugyo/react-hcl.git
226
+ cd react-hcl
227
+ bun install
228
+ bun run build
229
+ npm link
230
+ ```
231
+
232
+ After this, the `react-hcl` command is available globally.
233
+
234
+ ## Development
235
+
236
+ Set up a local development environment.
237
+
238
+ ```bash
239
+ git clone https://github.com/jugyo/react-hcl.git
240
+ cd react-hcl
241
+ bun install
242
+ ```
243
+
244
+ Run the CLI directly without building.
245
+
246
+ ```bash
247
+ bun src/cli.ts infra.tsx
248
+ bun src/cli.ts infra.tsx -o ./tf/main.tf
249
+ ```
250
+
251
+ Run the test suite.
252
+
253
+ ```bash
254
+ bun test
255
+ ```
256
+
257
+ Build distributable output.
258
+
259
+ ```bash
260
+ bun run build
261
+ ```
262
+
263
+ ## Documentation
264
+
265
+ - [Design Document](docs/design-doc.md)
266
+ - [Implementation Plan](docs/plan.md)
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Intermediate representation (IR) types for HCL blocks.
3
+ *
4
+ * These types serve as the bridge between JSX component evaluation and HCL code generation.
5
+ * The JSX components (Resource, Data, Variable, etc.) produce Block objects,
6
+ * and the generator (generator.ts) consumes them to emit valid HCL/Terraform source code.
7
+ *
8
+ * Each block type corresponds to a top-level Terraform construct and carries:
9
+ * - `blockType`: discriminant tag for the union type (used in switch/case dispatch)
10
+ * - `attributes`: key-value pairs serialized into the block body via serializeHCLAttributes()
11
+ * - `innerText` (optional): pre-formatted HCL string that replaces attribute serialization.
12
+ * When present, the generator outputs innerText as-is instead of serializing attributes.
13
+ * This is used for cases where children provide raw HCL content.
14
+ * With 2-pass rendering, template literals in children are resolved automatically.
15
+ *
16
+ * Supported HCL output forms:
17
+ * resource "type" "name" { ... } — ResourceBlock
18
+ * data "type" "name" { ... } — DataSourceBlock
19
+ * variable "name" { ... } — VariableBlock
20
+ * output "name" { ... } — OutputBlock
21
+ * locals { ... } — LocalsBlock (no name label)
22
+ * provider "type" { ... } — ProviderBlock
23
+ * terraform { ... } — TerraformBlock (no name label)
24
+ * module "name" { ... } — ModuleBlock
25
+ */
26
+ /**
27
+ * Represents a Terraform resource block.
28
+ * Output: resource "<type>" "<name>" { <attributes> }
29
+ *
30
+ * Example:
31
+ * { blockType: "resource", type: "aws_vpc", name: "main", attributes: { cidr_block: "10.0.0.0/16" } }
32
+ * → resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" }
33
+ */
34
+ export type ResourceBlock = {
35
+ blockType: "resource";
36
+ type: string;
37
+ name: string;
38
+ attributes: Record<string, any>;
39
+ innerText?: string;
40
+ };
41
+ /**
42
+ * Represents a Terraform data source block.
43
+ * Output: data "<type>" "<name>" { <attributes> }
44
+ *
45
+ * Example:
46
+ * { blockType: "data", type: "aws_ami", name: "latest", attributes: { most_recent: true } }
47
+ * → data "aws_ami" "latest" { most_recent = true }
48
+ */
49
+ export type DataSourceBlock = {
50
+ blockType: "data";
51
+ type: string;
52
+ name: string;
53
+ attributes: Record<string, any>;
54
+ innerText?: string;
55
+ };
56
+ /**
57
+ * Represents a Terraform variable block.
58
+ * Output: variable "<name>" { <attributes> }
59
+ * Attributes typically include: type, default, description, validation, sensitive, nullable
60
+ */
61
+ export type VariableBlock = {
62
+ blockType: "variable";
63
+ name: string;
64
+ attributes: Record<string, any>;
65
+ };
66
+ /**
67
+ * Represents a Terraform output block.
68
+ * Output: output "<name>" { <attributes> }
69
+ * Attributes typically include: value, description, sensitive
70
+ */
71
+ export type OutputBlock = {
72
+ blockType: "output";
73
+ name: string;
74
+ attributes: Record<string, any>;
75
+ };
76
+ /**
77
+ * Represents a Terraform locals block.
78
+ * Output: locals { <attributes> }
79
+ * Unlike most blocks, locals has no name label — there is only one locals block per scope.
80
+ */
81
+ export type LocalsBlock = {
82
+ blockType: "locals";
83
+ attributes: Record<string, any>;
84
+ };
85
+ /**
86
+ * Represents a Terraform provider block.
87
+ * Output: provider "<type>" { <attributes> }
88
+ * Attributes typically include: region, access_key, alias, etc.
89
+ */
90
+ export type ProviderBlock = {
91
+ blockType: "provider";
92
+ type: string;
93
+ attributes: Record<string, any>;
94
+ };
95
+ /**
96
+ * Represents the top-level terraform {} configuration block.
97
+ * Output: terraform { <attributes> }
98
+ * Typically contains: required_version, required_providers, backend config.
99
+ * Uses innerText for nested blocks like `backend "s3" { ... }` that need raw HCL.
100
+ */
101
+ export type TerraformBlock = {
102
+ blockType: "terraform";
103
+ attributes: Record<string, any>;
104
+ innerText?: string;
105
+ };
106
+ /**
107
+ * Represents a Terraform module block.
108
+ * Output: module "<name>" { <attributes> }
109
+ *
110
+ * Example:
111
+ * { blockType: "module", name: "vpc", attributes: { source: "terraform-aws-modules/vpc/aws", cidr: "10.0.0.0/16" } }
112
+ * → module "vpc" { source = "terraform-aws-modules/vpc/aws" cidr = "10.0.0.0/16" }
113
+ */
114
+ export type ModuleBlock = {
115
+ blockType: "module";
116
+ name: string;
117
+ attributes: Record<string, any>;
118
+ innerText?: string;
119
+ };
120
+ /**
121
+ * Union type of all supported HCL block types.
122
+ * Used as the input to the generate() function in generator.ts.
123
+ * The `blockType` field acts as a discriminated union tag.
124
+ */
125
+ export type Block = ResourceBlock | DataSourceBlock | VariableBlock | OutputBlock | LocalsBlock | ProviderBlock | TerraformBlock | ModuleBlock;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * CLI entry point for react-hcl.
3
+ *
4
+ * Usage: bun src/cli.ts <input.tsx> [-o <file>]
5
+ *
6
+ * Pipeline:
7
+ * 1. Takes a user-authored .tsx file as input
8
+ * 2. Transpiles and bundles it with esbuild (JSX → custom runtime calls)
9
+ * 3. Writes the bundled ESM code to a temp file and dynamically imports it
10
+ * 4. render() evaluates the JSXElement tree into Block[] IR
11
+ * 5. generate() converts Block[] into HCL string
12
+ * 6. Outputs to stdout, or writes to a file if -o / --output is given
13
+ *
14
+ * esbuild configuration:
15
+ * - jsx: "automatic" — uses the new JSX transform (no manual import needed)
16
+ * - jsxImportSource: "react-hcl" — resolves jsx/jsxs from our custom runtime
17
+ * - alias: maps "react-hcl" and "react-hcl/jsx-runtime" to local source
18
+ * - format: "esm" — output as ES modules for dynamic import() compatibility
19
+ * - bundle: true — inline all imports so the temp file is self-contained
20
+ * - write: false — keep output in memory (outputFiles) instead of writing to disk
21
+ */
22
+ export {};