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.
Files changed (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +749 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +91 -0
  5. package/dist/index.d.ts +51 -0
  6. package/dist/index.js +74 -0
  7. package/dist/parsers/genericParser.d.ts +167 -0
  8. package/dist/parsers/genericParser.js +268 -0
  9. package/dist/parsers/localsParser.d.ts +30 -0
  10. package/dist/parsers/localsParser.js +43 -0
  11. package/dist/parsers/outputParser.d.ts +25 -0
  12. package/dist/parsers/outputParser.js +44 -0
  13. package/dist/parsers/variableParser.d.ts +62 -0
  14. package/dist/parsers/variableParser.js +249 -0
  15. package/dist/services/artifactParsers.d.ts +12 -0
  16. package/dist/services/artifactParsers.js +157 -0
  17. package/dist/services/terraformJsonParser.d.ts +16 -0
  18. package/dist/services/terraformJsonParser.js +212 -0
  19. package/dist/services/terraformParser.d.ts +91 -0
  20. package/dist/services/terraformParser.js +191 -0
  21. package/dist/types/artifacts.d.ts +210 -0
  22. package/dist/types/artifacts.js +5 -0
  23. package/dist/types/blocks.d.ts +419 -0
  24. package/dist/types/blocks.js +28 -0
  25. package/dist/utils/common/errors.d.ts +46 -0
  26. package/dist/utils/common/errors.js +54 -0
  27. package/dist/utils/common/fs.d.ts +5 -0
  28. package/dist/utils/common/fs.js +48 -0
  29. package/dist/utils/common/logger.d.ts +5 -0
  30. package/dist/utils/common/logger.js +17 -0
  31. package/dist/utils/common/valueHelpers.d.ts +4 -0
  32. package/dist/utils/common/valueHelpers.js +23 -0
  33. package/dist/utils/graph/graphBuilder.d.ts +33 -0
  34. package/dist/utils/graph/graphBuilder.js +373 -0
  35. package/dist/utils/lexer/blockScanner.d.ts +36 -0
  36. package/dist/utils/lexer/blockScanner.js +143 -0
  37. package/dist/utils/lexer/hclLexer.d.ts +119 -0
  38. package/dist/utils/lexer/hclLexer.js +525 -0
  39. package/dist/utils/parser/bodyParser.d.ts +26 -0
  40. package/dist/utils/parser/bodyParser.js +81 -0
  41. package/dist/utils/parser/valueClassifier.d.ts +21 -0
  42. package/dist/utils/parser/valueClassifier.js +434 -0
  43. package/dist/utils/serialization/serializer.d.ts +9 -0
  44. package/dist/utils/serialization/serializer.js +63 -0
  45. package/dist/utils/serialization/yaml.d.ts +1 -0
  46. package/dist/utils/serialization/yaml.js +81 -0
  47. package/package.json +66 -0
package/README.md ADDED
@@ -0,0 +1,749 @@
1
+ # parse-hcl (TypeScript/Node.js)
2
+
3
+ [![npm version](https://img.shields.io/npm/v/parse-hcl.svg?label=npm&color=blue)](https://www.npmjs.com/package/parse-hcl)
4
+ [![license](https://img.shields.io/npm/l/parse-hcl.svg?color=blue)](../LICENSE)
5
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
6
+ [![TypeScript](https://img.shields.io/badge/typescript-5.x-blue)](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