parse-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/LICENSE +201 -0
- package/README.md +749 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +91 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +74 -0
- package/dist/parsers/genericParser.d.ts +167 -0
- package/dist/parsers/genericParser.js +268 -0
- package/dist/parsers/localsParser.d.ts +30 -0
- package/dist/parsers/localsParser.js +43 -0
- package/dist/parsers/outputParser.d.ts +25 -0
- package/dist/parsers/outputParser.js +44 -0
- package/dist/parsers/variableParser.d.ts +62 -0
- package/dist/parsers/variableParser.js +249 -0
- package/dist/services/artifactParsers.d.ts +12 -0
- package/dist/services/artifactParsers.js +157 -0
- package/dist/services/terraformJsonParser.d.ts +16 -0
- package/dist/services/terraformJsonParser.js +212 -0
- package/dist/services/terraformParser.d.ts +91 -0
- package/dist/services/terraformParser.js +191 -0
- package/dist/types/artifacts.d.ts +210 -0
- package/dist/types/artifacts.js +5 -0
- package/dist/types/blocks.d.ts +419 -0
- package/dist/types/blocks.js +28 -0
- package/dist/utils/common/errors.d.ts +46 -0
- package/dist/utils/common/errors.js +54 -0
- package/dist/utils/common/fs.d.ts +5 -0
- package/dist/utils/common/fs.js +48 -0
- package/dist/utils/common/logger.d.ts +5 -0
- package/dist/utils/common/logger.js +17 -0
- package/dist/utils/common/valueHelpers.d.ts +4 -0
- package/dist/utils/common/valueHelpers.js +23 -0
- package/dist/utils/graph/graphBuilder.d.ts +33 -0
- package/dist/utils/graph/graphBuilder.js +373 -0
- package/dist/utils/lexer/blockScanner.d.ts +36 -0
- package/dist/utils/lexer/blockScanner.js +143 -0
- package/dist/utils/lexer/hclLexer.d.ts +119 -0
- package/dist/utils/lexer/hclLexer.js +525 -0
- package/dist/utils/parser/bodyParser.d.ts +26 -0
- package/dist/utils/parser/bodyParser.js +81 -0
- package/dist/utils/parser/valueClassifier.d.ts +21 -0
- package/dist/utils/parser/valueClassifier.js +434 -0
- package/dist/utils/serialization/serializer.d.ts +9 -0
- package/dist/utils/serialization/serializer.js +63 -0
- package/dist/utils/serialization/yaml.d.ts +1 -0
- package/dist/utils/serialization/yaml.js +81 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
# parse-hcl (TypeScript/Node.js)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/parse-hcl)
|
|
4
|
+
[](../LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
A lightweight, zero-dependency Terraform/HCL parser for TypeScript and Node.js. This package provides both a powerful CLI tool and a programmatic API for parsing Terraform configurations.
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [CLI Usage](#cli-usage)
|
|
14
|
+
- [Basic Commands](#basic-commands)
|
|
15
|
+
- [Options Reference](#options-reference)
|
|
16
|
+
- [Output Formats](#output-formats)
|
|
17
|
+
- [File Type Detection](#file-type-detection)
|
|
18
|
+
- [Programmatic API](#programmatic-api)
|
|
19
|
+
- [Parsing Files](#parsing-files)
|
|
20
|
+
- [Parsing Directories](#parsing-directories)
|
|
21
|
+
- [Parsing Artifacts](#parsing-artifacts)
|
|
22
|
+
- [Building Dependency Graphs](#building-dependency-graphs)
|
|
23
|
+
- [Serialization](#serialization)
|
|
24
|
+
- [Type Definitions](#type-definitions)
|
|
25
|
+
- [Examples](#examples)
|
|
26
|
+
- [Development](#development)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Global installation (recommended for CLI usage)
|
|
34
|
+
npm install -g parse-hcl
|
|
35
|
+
yarn global add parse-hcl
|
|
36
|
+
pnpm add -g parse-hcl
|
|
37
|
+
|
|
38
|
+
# Local installation (for programmatic usage)
|
|
39
|
+
npm install parse-hcl
|
|
40
|
+
yarn add parse-hcl
|
|
41
|
+
pnpm add parse-hcl
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Requirements:** Node.js >= 18
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## CLI Usage
|
|
49
|
+
|
|
50
|
+
The `parse-hcl` CLI provides instant Terraform configuration analysis from your terminal.
|
|
51
|
+
|
|
52
|
+
### Basic Commands
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Parse a single Terraform file (JSON output)
|
|
56
|
+
parse-hcl --file main.tf
|
|
57
|
+
|
|
58
|
+
# Parse with YAML output
|
|
59
|
+
parse-hcl --file main.tf --format yaml
|
|
60
|
+
|
|
61
|
+
# Parse entire directory
|
|
62
|
+
parse-hcl --dir ./terraform
|
|
63
|
+
|
|
64
|
+
# Parse directory with dependency graph
|
|
65
|
+
parse-hcl --dir ./terraform --graph
|
|
66
|
+
|
|
67
|
+
# Parse tfvars file
|
|
68
|
+
parse-hcl --file variables.tfvars
|
|
69
|
+
|
|
70
|
+
# Parse Terraform state file
|
|
71
|
+
parse-hcl --file terraform.tfstate
|
|
72
|
+
|
|
73
|
+
# Parse Terraform plan output
|
|
74
|
+
parse-hcl --file plan.json
|
|
75
|
+
|
|
76
|
+
# Keep empty arrays/objects in output
|
|
77
|
+
parse-hcl --file main.tf --no-prune
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Options Reference
|
|
81
|
+
|
|
82
|
+
| Option | Description | Default |
|
|
83
|
+
|--------|-------------|---------|
|
|
84
|
+
| `--file <path>` | Parse a single file | - |
|
|
85
|
+
| `--dir <path>` | Parse all `.tf` and `.tf.json` files in directory (recursive) | - |
|
|
86
|
+
| `--format <type>` | Output format: `json` or `yaml` | `json` |
|
|
87
|
+
| `--graph` | Include dependency graph (nodes, edges, references) | `false` |
|
|
88
|
+
| `--no-prune` | Keep empty arrays and objects in output | `false` |
|
|
89
|
+
|
|
90
|
+
### Output Formats
|
|
91
|
+
|
|
92
|
+
**JSON Output (default):**
|
|
93
|
+
```bash
|
|
94
|
+
$ parse-hcl --file main.tf --format json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"resource": [
|
|
100
|
+
{
|
|
101
|
+
"type": "aws_s3_bucket",
|
|
102
|
+
"name": "demo",
|
|
103
|
+
"properties": {
|
|
104
|
+
"bucket": {
|
|
105
|
+
"type": "expression",
|
|
106
|
+
"kind": "template",
|
|
107
|
+
"raw": "${local.name_prefix}-bucket",
|
|
108
|
+
"references": [
|
|
109
|
+
{ "kind": "local", "name": "name_prefix" }
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"meta": {
|
|
114
|
+
"count": { "type": "literal", "value": 2, "raw": "2" }
|
|
115
|
+
},
|
|
116
|
+
"raw": "resource \"aws_s3_bucket\" \"demo\" { ... }",
|
|
117
|
+
"source": "/path/to/main.tf"
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
"variable": [...],
|
|
121
|
+
"output": [...],
|
|
122
|
+
"locals": [...],
|
|
123
|
+
"provider": [...],
|
|
124
|
+
"terraform": [...]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**YAML Output:**
|
|
129
|
+
```bash
|
|
130
|
+
$ parse-hcl --file main.tf --format yaml
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
resource:
|
|
135
|
+
- type: aws_s3_bucket
|
|
136
|
+
name: demo
|
|
137
|
+
properties:
|
|
138
|
+
bucket:
|
|
139
|
+
type: expression
|
|
140
|
+
kind: template
|
|
141
|
+
raw: "${local.name_prefix}-bucket"
|
|
142
|
+
references:
|
|
143
|
+
- kind: local
|
|
144
|
+
name: name_prefix
|
|
145
|
+
meta:
|
|
146
|
+
count:
|
|
147
|
+
type: literal
|
|
148
|
+
value: 2
|
|
149
|
+
variable:
|
|
150
|
+
- name: region
|
|
151
|
+
type: string
|
|
152
|
+
default:
|
|
153
|
+
type: literal
|
|
154
|
+
value: us-east-1
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Graph Output:**
|
|
158
|
+
```bash
|
|
159
|
+
$ parse-hcl --file main.tf --graph --format json
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"version": "1.0.0",
|
|
165
|
+
"document": {
|
|
166
|
+
"resource": [...],
|
|
167
|
+
"variable": [...],
|
|
168
|
+
"output": [...],
|
|
169
|
+
"locals": [...]
|
|
170
|
+
},
|
|
171
|
+
"graph": {
|
|
172
|
+
"nodes": [
|
|
173
|
+
{
|
|
174
|
+
"id": "resource.aws_s3_bucket.demo",
|
|
175
|
+
"kind": "resource",
|
|
176
|
+
"type": "aws_s3_bucket",
|
|
177
|
+
"name": "demo",
|
|
178
|
+
"source": "/path/to/main.tf"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"id": "locals.name_prefix",
|
|
182
|
+
"kind": "locals",
|
|
183
|
+
"name": "name_prefix"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"id": "output.bucket_name",
|
|
187
|
+
"kind": "output",
|
|
188
|
+
"name": "bucket_name"
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
"edges": [
|
|
192
|
+
{
|
|
193
|
+
"from": "resource.aws_s3_bucket.demo",
|
|
194
|
+
"to": "locals.name_prefix",
|
|
195
|
+
"reference": { "kind": "local", "name": "name_prefix" }
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"from": "output.bucket_name",
|
|
199
|
+
"to": "resource.aws_s3_bucket.demo",
|
|
200
|
+
"reference": { "kind": "resource", "resource_type": "aws_s3_bucket", "name": "demo" }
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"orphanReferences": []
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### File Type Detection
|
|
209
|
+
|
|
210
|
+
The CLI automatically detects file types:
|
|
211
|
+
|
|
212
|
+
| Extension/Pattern | Parser Used | Description |
|
|
213
|
+
|-------------------|-------------|-------------|
|
|
214
|
+
| `*.tf` | TerraformParser | HCL configuration files |
|
|
215
|
+
| `*.tf.json` | TerraformJsonParser | JSON-format Terraform configs |
|
|
216
|
+
| `*.tfvars`, `*.tfvars.json` | TfVarsParser | Variable assignment files |
|
|
217
|
+
| `*.tfstate` | TfStateParser | Terraform state files |
|
|
218
|
+
| `*plan.json` | TfPlanParser | `terraform show -json` output |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Programmatic API
|
|
223
|
+
|
|
224
|
+
### Parsing Files
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { TerraformParser, toJson, toYamlDocument } from 'parse-hcl';
|
|
228
|
+
|
|
229
|
+
const parser = new TerraformParser();
|
|
230
|
+
|
|
231
|
+
// Parse a single .tf file
|
|
232
|
+
const doc = parser.parseFile('main.tf');
|
|
233
|
+
|
|
234
|
+
// Access parsed blocks
|
|
235
|
+
console.log(doc.resource); // ResourceBlock[]
|
|
236
|
+
console.log(doc.variable); // VariableBlock[]
|
|
237
|
+
console.log(doc.output); // OutputBlock[]
|
|
238
|
+
console.log(doc.locals); // LocalValue[]
|
|
239
|
+
console.log(doc.module); // ModuleBlock[]
|
|
240
|
+
console.log(doc.provider); // ProviderBlock[]
|
|
241
|
+
console.log(doc.data); // DataBlock[]
|
|
242
|
+
console.log(doc.terraform); // TerraformSettingsBlock[]
|
|
243
|
+
|
|
244
|
+
// Serialize to JSON string
|
|
245
|
+
const jsonStr = toJson(doc);
|
|
246
|
+
console.log(jsonStr);
|
|
247
|
+
|
|
248
|
+
// Serialize to YAML string
|
|
249
|
+
const yamlStr = toYamlDocument(doc);
|
|
250
|
+
console.log(yamlStr);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Parsing Directories
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { TerraformParser, toJson } from 'parse-hcl';
|
|
257
|
+
|
|
258
|
+
const parser = new TerraformParser();
|
|
259
|
+
|
|
260
|
+
// Parse entire directory (default: aggregate + per-file)
|
|
261
|
+
const result = parser.parseDirectory('./terraform');
|
|
262
|
+
|
|
263
|
+
// Access combined document (all files merged)
|
|
264
|
+
console.log(result.combined);
|
|
265
|
+
|
|
266
|
+
// Access per-file results
|
|
267
|
+
result.files.forEach(file => {
|
|
268
|
+
console.log(`File: ${file.path}`);
|
|
269
|
+
console.log(`Resources: ${file.document.resource.length}`);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Parse with options
|
|
273
|
+
const result2 = parser.parseDirectory('./terraform', {
|
|
274
|
+
aggregate: true, // Combine all files into one document (default: true)
|
|
275
|
+
includePerFile: true // Include per-file results (default: true)
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Combine multiple documents manually
|
|
279
|
+
const combined = parser.combine([doc1, doc2, doc3]);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Parsing Artifacts
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import {
|
|
286
|
+
TfVarsParser,
|
|
287
|
+
TfStateParser,
|
|
288
|
+
TfPlanParser
|
|
289
|
+
} from 'parse-hcl';
|
|
290
|
+
|
|
291
|
+
// Parse .tfvars file
|
|
292
|
+
const tfvars = new TfVarsParser().parseFile('terraform.tfvars');
|
|
293
|
+
console.log(tfvars.assignments);
|
|
294
|
+
// { project: { type: 'literal', value: 'demo' }, ... }
|
|
295
|
+
|
|
296
|
+
// Parse .tfstate file
|
|
297
|
+
const state = new TfStateParser().parseFile('terraform.tfstate');
|
|
298
|
+
console.log(state.terraform_version); // "1.6.0"
|
|
299
|
+
console.log(state.outputs); // { bucket_name: { value: "...", type: "string" } }
|
|
300
|
+
console.log(state.resources); // TerraformStateResource[]
|
|
301
|
+
|
|
302
|
+
// Parse plan.json (terraform show -json planfile)
|
|
303
|
+
const plan = new TfPlanParser().parseFile('plan.json');
|
|
304
|
+
console.log(plan.resource_changes); // PlanResourceChange[]
|
|
305
|
+
console.log(plan.planned_values); // PlannedValues
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Building Dependency Graphs
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import {
|
|
312
|
+
TerraformParser,
|
|
313
|
+
buildDependencyGraph,
|
|
314
|
+
toJsonExport,
|
|
315
|
+
createExport
|
|
316
|
+
} from 'parse-hcl';
|
|
317
|
+
|
|
318
|
+
const parser = new TerraformParser();
|
|
319
|
+
const doc = parser.parseFile('main.tf');
|
|
320
|
+
|
|
321
|
+
// Build dependency graph
|
|
322
|
+
const graph = buildDependencyGraph(doc);
|
|
323
|
+
|
|
324
|
+
// Graph structure
|
|
325
|
+
console.log(graph.nodes); // GraphNode[] - all resources, variables, etc.
|
|
326
|
+
console.log(graph.edges); // GraphEdge[] - dependency relationships
|
|
327
|
+
console.log(graph.orphanReferences); // Reference[] - unresolved references
|
|
328
|
+
|
|
329
|
+
// Iterate over nodes
|
|
330
|
+
graph.nodes.forEach(node => {
|
|
331
|
+
console.log(`${node.kind}: ${node.id}`);
|
|
332
|
+
// Output: "resource: resource.aws_s3_bucket.demo"
|
|
333
|
+
// Output: "variable: variable.region"
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Iterate over edges (dependencies)
|
|
337
|
+
graph.edges.forEach(edge => {
|
|
338
|
+
console.log(`${edge.from} -> ${edge.to}`);
|
|
339
|
+
// Output: "resource.aws_s3_bucket.demo -> locals.name_prefix"
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Create full export with version
|
|
343
|
+
const exportData = createExport(doc);
|
|
344
|
+
// { version: "1.0.0", document: {...}, graph: {...} }
|
|
345
|
+
|
|
346
|
+
// Serialize export to JSON
|
|
347
|
+
const exportJson = toJsonExport(doc);
|
|
348
|
+
console.log(exportJson);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Serialization
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import {
|
|
355
|
+
TerraformParser,
|
|
356
|
+
toJson,
|
|
357
|
+
toYamlDocument,
|
|
358
|
+
toJsonExport,
|
|
359
|
+
toExport
|
|
360
|
+
} from 'parse-hcl';
|
|
361
|
+
|
|
362
|
+
const parser = new TerraformParser();
|
|
363
|
+
const doc = parser.parseFile('main.tf');
|
|
364
|
+
|
|
365
|
+
// JSON serialization
|
|
366
|
+
const json = toJson(doc); // Pruned (default)
|
|
367
|
+
const jsonFull = toJson(doc, { pruneEmpty: false }); // Keep empty arrays
|
|
368
|
+
|
|
369
|
+
// YAML serialization
|
|
370
|
+
const yaml = toYamlDocument(doc); // Pruned
|
|
371
|
+
const yamlFull = toYamlDocument(doc, { pruneEmpty: false }); // Keep empty
|
|
372
|
+
|
|
373
|
+
// Export with graph (JSON string)
|
|
374
|
+
const exportJson = toJsonExport(doc); // Pruned
|
|
375
|
+
const exportJsonFull = toJsonExport(doc, { pruneEmpty: false }); // Keep empty
|
|
376
|
+
|
|
377
|
+
// Export object (for further processing)
|
|
378
|
+
const exportObj = toExport(doc);
|
|
379
|
+
const exportObjFull = toExport(doc, { pruneEmpty: false });
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Type Definitions
|
|
385
|
+
|
|
386
|
+
### Core Types
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// Main document structure
|
|
390
|
+
interface TerraformDocument {
|
|
391
|
+
terraform: TerraformSettingsBlock[];
|
|
392
|
+
provider: ProviderBlock[];
|
|
393
|
+
variable: VariableBlock[];
|
|
394
|
+
output: OutputBlock[];
|
|
395
|
+
resource: ResourceBlock[];
|
|
396
|
+
data: DataBlock[];
|
|
397
|
+
module: ModuleBlock[];
|
|
398
|
+
locals: LocalValue[];
|
|
399
|
+
moved: GenericBlock[];
|
|
400
|
+
import: GenericBlock[];
|
|
401
|
+
check: GenericBlock[];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Resource block
|
|
405
|
+
interface ResourceBlock {
|
|
406
|
+
type: string; // e.g., "aws_s3_bucket"
|
|
407
|
+
name: string; // e.g., "demo"
|
|
408
|
+
properties: ParsedBody; // Attributes
|
|
409
|
+
meta?: {
|
|
410
|
+
count?: Value;
|
|
411
|
+
for_each?: Value;
|
|
412
|
+
depends_on?: Value;
|
|
413
|
+
provider?: Value;
|
|
414
|
+
lifecycle?: Value;
|
|
415
|
+
};
|
|
416
|
+
blocks?: NestedBlock[];
|
|
417
|
+
dynamic_blocks?: DynamicBlock[];
|
|
418
|
+
raw: string; // Original HCL source
|
|
419
|
+
source?: string; // File path
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Variable block
|
|
423
|
+
interface VariableBlock {
|
|
424
|
+
name: string;
|
|
425
|
+
type?: string;
|
|
426
|
+
typeConstraint?: TypeConstraint;
|
|
427
|
+
default?: Value;
|
|
428
|
+
description?: string;
|
|
429
|
+
sensitive?: boolean;
|
|
430
|
+
nullable?: boolean;
|
|
431
|
+
validation?: VariableValidation[];
|
|
432
|
+
raw: string;
|
|
433
|
+
source?: string;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Output block
|
|
437
|
+
interface OutputBlock {
|
|
438
|
+
name: string;
|
|
439
|
+
value: Value;
|
|
440
|
+
description?: string;
|
|
441
|
+
sensitive?: boolean;
|
|
442
|
+
depends_on?: Value;
|
|
443
|
+
raw: string;
|
|
444
|
+
source?: string;
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Value Types
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
// Value union type
|
|
452
|
+
type Value = LiteralValue | ArrayValue | ObjectValue | ExpressionValue;
|
|
453
|
+
|
|
454
|
+
// Literal values (string, number, boolean, null)
|
|
455
|
+
interface LiteralValue {
|
|
456
|
+
type: 'literal';
|
|
457
|
+
value: string | number | boolean | null;
|
|
458
|
+
raw: string;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Array values
|
|
462
|
+
interface ArrayValue {
|
|
463
|
+
type: 'array';
|
|
464
|
+
value: Value[];
|
|
465
|
+
raw: string;
|
|
466
|
+
references?: Reference[];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Object values
|
|
470
|
+
interface ObjectValue {
|
|
471
|
+
type: 'object';
|
|
472
|
+
value: Record<string, Value>;
|
|
473
|
+
raw: string;
|
|
474
|
+
references?: Reference[];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Expression values (references, function calls, templates, etc.)
|
|
478
|
+
interface ExpressionValue {
|
|
479
|
+
type: 'expression';
|
|
480
|
+
kind: ExpressionKind; // 'traversal' | 'function_call' | 'template' | 'for_expr' | ...
|
|
481
|
+
raw: string;
|
|
482
|
+
references?: Reference[];
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Reference Types
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
type Reference =
|
|
490
|
+
| VariableReference
|
|
491
|
+
| LocalReference
|
|
492
|
+
| ResourceReference
|
|
493
|
+
| DataReference
|
|
494
|
+
| ModuleOutputReference
|
|
495
|
+
| PathReference
|
|
496
|
+
| EachReference
|
|
497
|
+
| CountReference
|
|
498
|
+
| SelfReference;
|
|
499
|
+
|
|
500
|
+
interface VariableReference {
|
|
501
|
+
kind: 'variable';
|
|
502
|
+
name: string;
|
|
503
|
+
attribute?: string;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
interface ResourceReference {
|
|
507
|
+
kind: 'resource';
|
|
508
|
+
resource_type: string;
|
|
509
|
+
name: string;
|
|
510
|
+
attribute?: string;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
interface ModuleOutputReference {
|
|
514
|
+
kind: 'module_output';
|
|
515
|
+
module: string;
|
|
516
|
+
output: string;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ... other reference types
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Graph Types
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
interface DependencyGraph {
|
|
526
|
+
nodes: GraphNode[];
|
|
527
|
+
edges: GraphEdge[];
|
|
528
|
+
orphanReferences: Reference[];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
interface GraphNode {
|
|
532
|
+
id: string; // e.g., "resource.aws_s3_bucket.demo"
|
|
533
|
+
kind: GraphNodeKind; // 'resource' | 'variable' | 'output' | 'locals' | ...
|
|
534
|
+
name: string;
|
|
535
|
+
type?: string; // For resources: resource type
|
|
536
|
+
source?: string; // File path
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
interface GraphEdge {
|
|
540
|
+
from: string; // Source node ID
|
|
541
|
+
to: string; // Target node ID
|
|
542
|
+
reference: Reference; // The reference that created this edge
|
|
543
|
+
source?: string; // File path
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
interface TerraformExport {
|
|
547
|
+
version: string; // "1.0.0"
|
|
548
|
+
document: TerraformDocument;
|
|
549
|
+
graph: DependencyGraph;
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Examples
|
|
556
|
+
|
|
557
|
+
### Example 1: List All Resources in a Project
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
import { TerraformParser } from 'parse-hcl';
|
|
561
|
+
|
|
562
|
+
const parser = new TerraformParser();
|
|
563
|
+
const result = parser.parseDirectory('./infrastructure');
|
|
564
|
+
|
|
565
|
+
if (result.combined) {
|
|
566
|
+
result.combined.resource.forEach(resource => {
|
|
567
|
+
console.log(`${resource.type}.${resource.name}`);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Output:
|
|
572
|
+
// aws_s3_bucket.data
|
|
573
|
+
// aws_s3_bucket.logs
|
|
574
|
+
// aws_iam_role.lambda_exec
|
|
575
|
+
// aws_lambda_function.processor
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Example 2: Find All Variable References
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
import { TerraformParser, buildDependencyGraph } from 'parse-hcl';
|
|
582
|
+
|
|
583
|
+
const parser = new TerraformParser();
|
|
584
|
+
const doc = parser.parseFile('main.tf');
|
|
585
|
+
const graph = buildDependencyGraph(doc);
|
|
586
|
+
|
|
587
|
+
// Find all edges pointing to variables
|
|
588
|
+
const variableUsages = graph.edges.filter(edge =>
|
|
589
|
+
edge.to.startsWith('variable.')
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
variableUsages.forEach(edge => {
|
|
593
|
+
console.log(`${edge.from} uses ${edge.to}`);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Output:
|
|
597
|
+
// resource.aws_s3_bucket.demo uses variable.bucket_name
|
|
598
|
+
// locals.full_name uses variable.environment
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Example 3: Validate Required Variables Have Defaults
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
import { TerraformParser } from 'parse-hcl';
|
|
605
|
+
|
|
606
|
+
const parser = new TerraformParser();
|
|
607
|
+
const result = parser.parseDirectory('./modules/vpc');
|
|
608
|
+
|
|
609
|
+
if (result.combined) {
|
|
610
|
+
const missingDefaults = result.combined.variable.filter(v =>
|
|
611
|
+
!v.default && !v.nullable
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
if (missingDefaults.length > 0) {
|
|
615
|
+
console.log('Variables without defaults:');
|
|
616
|
+
missingDefaults.forEach(v => {
|
|
617
|
+
console.log(` - ${v.name} (${v.source})`);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Example 4: Export to File
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
import { writeFileSync } from 'fs';
|
|
627
|
+
import { TerraformParser, toJsonExport, toYamlDocument } from 'parse-hcl';
|
|
628
|
+
|
|
629
|
+
const parser = new TerraformParser();
|
|
630
|
+
const doc = parser.parseFile('main.tf');
|
|
631
|
+
|
|
632
|
+
// Export as JSON with graph
|
|
633
|
+
writeFileSync('output.json', toJsonExport(doc));
|
|
634
|
+
|
|
635
|
+
// Export as YAML
|
|
636
|
+
writeFileSync('output.yaml', toYamlDocument(doc));
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Example 5: Analyze Terraform State
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
import { TfStateParser } from 'parse-hcl';
|
|
643
|
+
|
|
644
|
+
const state = new TfStateParser().parseFile('terraform.tfstate');
|
|
645
|
+
|
|
646
|
+
console.log(`Terraform version: ${state.terraform_version}`);
|
|
647
|
+
console.log(`Serial: ${state.serial}`);
|
|
648
|
+
|
|
649
|
+
// List all managed resources
|
|
650
|
+
state.resources
|
|
651
|
+
.filter(r => r.mode === 'managed')
|
|
652
|
+
.forEach(r => {
|
|
653
|
+
console.log(`${r.type}.${r.name}`);
|
|
654
|
+
r.instances.forEach((inst, i) => {
|
|
655
|
+
console.log(` [${i}] id=${inst.attributes?.id}`);
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## Development
|
|
663
|
+
|
|
664
|
+
### Setup
|
|
665
|
+
|
|
666
|
+
```bash
|
|
667
|
+
git clone https://github.com/sigmoid-hq/parse-hcl.git
|
|
668
|
+
cd parse-hcl/typescript
|
|
669
|
+
yarn install
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### Scripts
|
|
673
|
+
|
|
674
|
+
```bash
|
|
675
|
+
# Build
|
|
676
|
+
yarn build
|
|
677
|
+
|
|
678
|
+
# Run tests
|
|
679
|
+
yarn test
|
|
680
|
+
|
|
681
|
+
# Run tests with coverage
|
|
682
|
+
yarn test:coverage
|
|
683
|
+
|
|
684
|
+
# Lint
|
|
685
|
+
yarn lint
|
|
686
|
+
yarn lint:fix
|
|
687
|
+
|
|
688
|
+
# Format
|
|
689
|
+
yarn format
|
|
690
|
+
yarn format:check
|
|
691
|
+
|
|
692
|
+
# Run CLI locally
|
|
693
|
+
yarn cli --file test/fixtures/main.tf
|
|
694
|
+
|
|
695
|
+
# Run examples
|
|
696
|
+
yarn example # Full example with output files
|
|
697
|
+
yarn example:usage # Basic usage demo
|
|
698
|
+
yarn example:artifacts # Artifact parsing demo
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Project Structure
|
|
702
|
+
|
|
703
|
+
```
|
|
704
|
+
typescript/
|
|
705
|
+
├── src/
|
|
706
|
+
│ ├── index.ts # Main exports
|
|
707
|
+
│ ├── cli.ts # CLI entry point
|
|
708
|
+
│ ├── types/
|
|
709
|
+
│ │ ├── blocks.ts # Block type definitions
|
|
710
|
+
│ │ └── artifacts.ts # Artifact type definitions
|
|
711
|
+
│ ├── services/
|
|
712
|
+
│ │ ├── terraformParser.ts # Main HCL parser
|
|
713
|
+
│ │ ├── terraformJsonParser.ts # .tf.json parser
|
|
714
|
+
│ │ └── artifactParsers.ts # tfvars/state/plan parsers
|
|
715
|
+
│ ├── parsers/
|
|
716
|
+
│ │ ├── genericParser.ts # Generic block parsing
|
|
717
|
+
│ │ ├── variableParser.ts # Variable block parsing
|
|
718
|
+
│ │ ├── outputParser.ts # Output block parsing
|
|
719
|
+
│ │ └── localsParser.ts # Locals block parsing
|
|
720
|
+
│ └── utils/
|
|
721
|
+
│ ├── lexer/
|
|
722
|
+
│ │ ├── hclLexer.ts # HCL tokenization
|
|
723
|
+
│ │ └── blockScanner.ts # Block boundary detection
|
|
724
|
+
│ ├── parser/
|
|
725
|
+
│ │ ├── bodyParser.ts # Block body parsing
|
|
726
|
+
│ │ └── valueClassifier.ts # Value classification
|
|
727
|
+
│ ├── serialization/
|
|
728
|
+
│ │ ├── serializer.ts # JSON/YAML serialization
|
|
729
|
+
│ │ └── yaml.ts # YAML utilities
|
|
730
|
+
│ ├── graph/
|
|
731
|
+
│ │ └── graphBuilder.ts # Dependency graph builder
|
|
732
|
+
│ └── common/
|
|
733
|
+
│ ├── errors.ts # Error types
|
|
734
|
+
│ ├── logger.ts # Logging utilities
|
|
735
|
+
│ └── fs.ts # File system utilities
|
|
736
|
+
├── test/
|
|
737
|
+
│ ├── unit/ # Unit tests
|
|
738
|
+
│ ├── integration/ # Integration tests
|
|
739
|
+
│ └── fixtures/ # Test Terraform files
|
|
740
|
+
├── examples/ # Example scripts
|
|
741
|
+
├── package.json
|
|
742
|
+
└── tsconfig.json
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
## License
|
|
748
|
+
|
|
749
|
+
[Apache-2.0](../LICENSE) - Copyright 2025 Juan Lee
|