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 +266 -0
- package/dist/blocks.d.ts +125 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +210971 -0
- package/dist/components/data-source.d.ts +21 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/components/locals.d.ts +12 -0
- package/dist/components/module.d.ts +24 -0
- package/dist/components/output.d.ts +18 -0
- package/dist/components/provider.d.ts +19 -0
- package/dist/components/resource.d.ts +21 -0
- package/dist/components/terraform.d.ts +15 -0
- package/dist/components/variable.d.ts +21 -0
- package/dist/conflict.d.ts +29 -0
- package/dist/generator.d.ts +36 -0
- package/dist/hcl-serializer.d.ts +144 -0
- package/dist/hcl-validator.d.ts +14 -0
- package/dist/helpers/tf.d.ts +27 -0
- package/dist/hooks/use-ref.d.ts +79 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +320 -0
- package/dist/jsx-runtime.d.ts +40 -0
- package/dist/jsx-runtime.js +23 -0
- package/dist/renderer.d.ts +12 -0
- package/package.json +52 -0
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)
|
package/dist/blocks.d.ts
ADDED
|
@@ -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 {};
|