npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

parse-hcl

v1.0.2

Published

CLI-first Terraform/HCL parser with JSON/YAML output, dependency graph export, and per-file metadata for IaC tooling.

Readme

parse-hcl (TypeScript/Node.js)

npm version license Node.js TypeScript

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.

Table of Contents


Installation

# Global installation (recommended for CLI usage)
npm install -g parse-hcl
yarn global add parse-hcl
pnpm add -g parse-hcl

# Local installation (for programmatic usage)
npm install parse-hcl
yarn add parse-hcl
pnpm add parse-hcl

Requirements: Node.js >= 18


CLI Usage

The parse-hcl CLI provides instant Terraform configuration analysis from your terminal.

Command Synopsis

parse-hcl --file <path> | --dir <path> [--format json|yaml] [--graph] [--no-prune]

Basic Commands

# Parse a single Terraform file (JSON output)
parse-hcl --file main.tf

# Parse with YAML output
parse-hcl --file main.tf --format yaml

# Parse entire directory
parse-hcl --dir ./terraform

# Parse directory with dependency graph
parse-hcl --dir ./terraform --graph

# Parse tfvars file
parse-hcl --file variables.tfvars

# Parse Terraform state file
parse-hcl --file terraform.tfstate

# Parse Terraform plan output
parse-hcl --file plan.json

# Keep empty arrays/objects in output
parse-hcl --file main.tf --no-prune

# Save to custom path and also print
parse-hcl --file main.tf --out ./out/result.json --stdout

Options Reference

| Option | Description | Default | |--------|-------------|---------| | --file <path> | Parse a single file | - | | --dir <path> | Parse all .tf and .tf.json files in directory (recursive) | - | | --format <type> | Output format: json or yaml | json | | --graph | Include dependency graph (nodes, edges, references) | false | | --no-prune | Keep empty arrays and objects in output | false | | --out <path> | Save output to file (or directory for combined output) | ./parse-hcl-output*.{json,yaml} | | --out-dir <dir> | Save per-file results under this directory (directory mode) | ./parse-hcl-output/files | | --split / --no-split | Enable/disable per-file saving in directory mode | true | | --stdout / --no-stdout | Also print to stdout (default off) | false |

Behavior and Defaults

  • Pass either --file or --dir; if both are present, --file is used. Missing inputs print usage to stderr and exit with code 1.
  • Default output is files, stdout off.
    • Single file: writes ./parse-hcl-output.{json|yaml}.
    • Directory: writes combined ./parse-hcl-output.combined.{json|yaml} and per-file under ./parse-hcl-output/files/<relative-path>.{json|yaml}.
    • Add --stdout to also print.
  • --out overrides the combined/single output path. If it points to a directory, the tool writes output.{json|yaml} (single file) or combined.{json|yaml} (directory). If no extension is given, one is added based on --format.
  • --out-dir sets the root for per-file outputs (directory mode). If omitted but --out is provided, per-file results go under per-file/ next to the --out target. Disable per-file writes with --no-split.
  • --file auto-detects artifacts: paths containing tfvars use the tfvars parser, .tfstate uses the state parser, and plan.json uses the plan parser. Other files are treated as Terraform configs. The --graph flag only applies to Terraform configs; artifact parsers ignore it and emit the raw parse.
  • --dir walks recursively, parsing only .tf and .tf.json files while skipping .terraform, .git, and node_modules. Default output contains combined (aggregated document) and files (per-file results). With --graph, the dependency graph is built from the aggregated document.
  • When split outputs are enabled, each files entry includes relative_path, output_path, and output_dir (all relative). Module blocks include source_raw (as written) and, when local, source_output_dir, pointing to the per-file output directory for that module.
  • Warnings and usage go to stderr. The CLI exits non-zero on invalid arguments or parsing failures.
  • --format applies to every output shape; --no-prune keeps empty arrays/objects that are removed by default for compactness.
  • Run without a global install via npx parse-hcl ... or yarn dlx parse-hcl ....

Output Formats

JSON Output (default):

$ parse-hcl --file main.tf --format json
{
  "resource": [
    {
      "type": "aws_s3_bucket",
      "name": "demo",
      "properties": {
        "bucket": {
          "type": "expression",
          "kind": "template",
          "raw": "${local.name_prefix}-bucket",
          "references": [
            { "kind": "local", "name": "name_prefix" }
          ]
        }
      },
      "meta": {
        "count": { "type": "literal", "value": 2, "raw": "2" }
      },
      "raw": "resource \"aws_s3_bucket\" \"demo\" { ... }",
      "source": "/path/to/main.tf"
    }
  ],
  "variable": [...],
  "output": [...],
  "locals": [...],
  "provider": [...],
  "terraform": [...]
}

YAML Output:

$ parse-hcl --file main.tf --format yaml
resource:
  - type: aws_s3_bucket
    name: demo
    properties:
      bucket:
        type: expression
        kind: template
        raw: "${local.name_prefix}-bucket"
        references:
          - kind: local
            name: name_prefix
    meta:
      count:
        type: literal
        value: 2
variable:
  - name: region
    type: string
    default:
      type: literal
      value: us-east-1

Default saved files (no flags):

$ ls parse-hcl-output*
parse-hcl-output.combined.json
parse-hcl-output/files/main.tf.json

Graph Output:

$ parse-hcl --file main.tf --graph --format json
{
  "version": "1.0.0",
  "document": {
    "resource": [...],
    "variable": [...],
    "output": [...],
    "locals": [...]
  },
  "graph": {
    "nodes": [
      {
        "id": "resource.aws_s3_bucket.demo",
        "kind": "resource",
        "type": "aws_s3_bucket",
        "name": "demo",
        "source": "/path/to/main.tf"
      },
      {
        "id": "locals.name_prefix",
        "kind": "locals",
        "name": "name_prefix"
      },
      {
        "id": "output.bucket_name",
        "kind": "output",
        "name": "bucket_name"
      }
    ],
    "edges": [
      {
        "from": "resource.aws_s3_bucket.demo",
        "to": "locals.name_prefix",
        "reference": { "kind": "local", "name": "name_prefix" }
      },
      {
        "from": "output.bucket_name",
        "to": "resource.aws_s3_bucket.demo",
        "reference": { "kind": "resource", "resource_type": "aws_s3_bucket", "name": "demo" }
      }
    ],
    "orphanReferences": []
  }
}

File Type Detection

The CLI automatically detects file types:

| Extension/Pattern | Parser Used | Description | |-------------------|-------------|-------------| | *.tf | TerraformParser | HCL configuration files | | *.tf.json | TerraformJsonParser | JSON-format Terraform configs | | *.tfvars, *.tfvars.json | TfVarsParser | Variable assignment files | | *.tfstate | TfStateParser | Terraform state files | | *plan.json | TfPlanParser | terraform show -json output |


Programmatic API

Parsing Files

import { TerraformParser, toJson, toYamlDocument } from 'parse-hcl';

const parser = new TerraformParser();

// Parse a single .tf file
const doc = parser.parseFile('main.tf');

// Access parsed blocks
console.log(doc.resource);   // ResourceBlock[]
console.log(doc.variable);   // VariableBlock[]
console.log(doc.output);     // OutputBlock[]
console.log(doc.locals);     // LocalValue[]
console.log(doc.module);     // ModuleBlock[]
console.log(doc.provider);   // ProviderBlock[]
console.log(doc.data);       // DataBlock[]
console.log(doc.terraform);  // TerraformSettingsBlock[]

// Serialize to JSON string
const jsonStr = toJson(doc);
console.log(jsonStr);

// Serialize to YAML string
const yamlStr = toYamlDocument(doc);
console.log(yamlStr);

Parsing Directories

import { TerraformParser, toJson } from 'parse-hcl';

const parser = new TerraformParser();

// Parse entire directory (default: aggregate + per-file)
const result = parser.parseDirectory('./terraform');

// Access combined document (all files merged)
console.log(result.combined);

// Access per-file results
result.files.forEach(file => {
  console.log(`File: ${file.path}`);
  console.log(`Resources: ${file.document.resource.length}`);
});

// Parse with options
const result2 = parser.parseDirectory('./terraform', {
  aggregate: true,      // Combine all files into one document (default: true)
  includePerFile: true  // Include per-file results (default: true)
});

// Combine multiple documents manually
const combined = parser.combine([doc1, doc2, doc3]);

Parsing Artifacts

import {
  TfVarsParser,
  TfStateParser,
  TfPlanParser
} from 'parse-hcl';

// Parse .tfvars file
const tfvars = new TfVarsParser().parseFile('terraform.tfvars');
console.log(tfvars.assignments);
// { project: { type: 'literal', value: 'demo' }, ... }

// Parse .tfstate file
const state = new TfStateParser().parseFile('terraform.tfstate');
console.log(state.terraform_version);  // "1.6.0"
console.log(state.outputs);            // { bucket_name: { value: "...", type: "string" } }
console.log(state.resources);          // TerraformStateResource[]

// Parse plan.json (terraform show -json planfile)
const plan = new TfPlanParser().parseFile('plan.json');
console.log(plan.resource_changes);    // PlanResourceChange[]
console.log(plan.planned_values);      // PlannedValues

Building Dependency Graphs

import {
  TerraformParser,
  buildDependencyGraph,
  toJsonExport,
  createExport
} from 'parse-hcl';

const parser = new TerraformParser();
const doc = parser.parseFile('main.tf');

// Build dependency graph
const graph = buildDependencyGraph(doc);

// Graph structure
console.log(graph.nodes);  // GraphNode[] - all resources, variables, etc.
console.log(graph.edges);  // GraphEdge[] - dependency relationships
console.log(graph.orphanReferences);  // Reference[] - unresolved references

// Iterate over nodes
graph.nodes.forEach(node => {
  console.log(`${node.kind}: ${node.id}`);
  // Output: "resource: resource.aws_s3_bucket.demo"
  // Output: "variable: variable.region"
});

// Iterate over edges (dependencies)
graph.edges.forEach(edge => {
  console.log(`${edge.from} -> ${edge.to}`);
  // Output: "resource.aws_s3_bucket.demo -> locals.name_prefix"
});

// Create full export with version
const exportData = createExport(doc);
// { version: "1.0.0", document: {...}, graph: {...} }

// Serialize export to JSON
const exportJson = toJsonExport(doc);
console.log(exportJson);

Serialization

import {
  TerraformParser,
  toJson,
  toYamlDocument,
  toJsonExport,
  toExport
} from 'parse-hcl';

const parser = new TerraformParser();
const doc = parser.parseFile('main.tf');

// JSON serialization
const json = toJson(doc);                              // Pruned (default)
const jsonFull = toJson(doc, { pruneEmpty: false });   // Keep empty arrays

// YAML serialization
const yaml = toYamlDocument(doc);                              // Pruned
const yamlFull = toYamlDocument(doc, { pruneEmpty: false });   // Keep empty

// Export with graph (JSON string)
const exportJson = toJsonExport(doc);                           // Pruned
const exportJsonFull = toJsonExport(doc, { pruneEmpty: false }); // Keep empty

// Export object (for further processing)
const exportObj = toExport(doc);
const exportObjFull = toExport(doc, { pruneEmpty: false });

Type Definitions

Core Types

// Main document structure
interface TerraformDocument {
  terraform: TerraformSettingsBlock[];
  provider: ProviderBlock[];
  variable: VariableBlock[];
  output: OutputBlock[];
  resource: ResourceBlock[];
  data: DataBlock[];
  module: ModuleBlock[];
  locals: LocalValue[];
  moved: GenericBlock[];
  import: GenericBlock[];
  check: GenericBlock[];
}

// Resource block
interface ResourceBlock {
  type: string;           // e.g., "aws_s3_bucket"
  name: string;           // e.g., "demo"
  properties: ParsedBody; // Attributes
  meta?: {
    count?: Value;
    for_each?: Value;
    depends_on?: Value;
    provider?: Value;
    lifecycle?: Value;
  };
  blocks?: NestedBlock[];
  dynamic_blocks?: DynamicBlock[];
  raw: string;            // Original HCL source
  source?: string;        // File path
}

// Variable block
interface VariableBlock {
  name: string;
  type?: string;
  typeConstraint?: TypeConstraint;
  default?: Value;
  description?: string;
  sensitive?: boolean;
  nullable?: boolean;
  validation?: VariableValidation[];
  raw: string;
  source?: string;
}

// Output block
interface OutputBlock {
  name: string;
  value: Value;
  description?: string;
  sensitive?: boolean;
  depends_on?: Value;
  raw: string;
  source?: string;
}

Value Types

// Value union type
type Value = LiteralValue | ArrayValue | ObjectValue | ExpressionValue;

// Literal values (string, number, boolean, null)
interface LiteralValue {
  type: 'literal';
  value: string | number | boolean | null;
  raw: string;
}

// Array values
interface ArrayValue {
  type: 'array';
  value: Value[];
  raw: string;
  references?: Reference[];
}

// Object values
interface ObjectValue {
  type: 'object';
  value: Record<string, Value>;
  raw: string;
  references?: Reference[];
}

// Expression values (references, function calls, templates, etc.)
interface ExpressionValue {
  type: 'expression';
  kind: ExpressionKind;  // 'traversal' | 'function_call' | 'template' | 'for_expr' | ...
  raw: string;
  references?: Reference[];
}

Reference Types

type Reference =
  | VariableReference
  | LocalReference
  | ResourceReference
  | DataReference
  | ModuleOutputReference
  | PathReference
  | EachReference
  | CountReference
  | SelfReference;

interface VariableReference {
  kind: 'variable';
  name: string;
  attribute?: string;
}

interface ResourceReference {
  kind: 'resource';
  resource_type: string;
  name: string;
  attribute?: string;
}

interface ModuleOutputReference {
  kind: 'module_output';
  module: string;
  output: string;
}

// ... other reference types

Graph Types

interface DependencyGraph {
  nodes: GraphNode[];
  edges: GraphEdge[];
  orphanReferences: Reference[];
}

interface GraphNode {
  id: string;           // e.g., "resource.aws_s3_bucket.demo"
  kind: GraphNodeKind;  // 'resource' | 'variable' | 'output' | 'locals' | ...
  name: string;
  type?: string;        // For resources: resource type
  source?: string;      // File path
}

interface GraphEdge {
  from: string;         // Source node ID
  to: string;           // Target node ID
  reference: Reference; // The reference that created this edge
  source?: string;      // File path
}

interface TerraformExport {
  version: string;      // "1.0.0"
  document: TerraformDocument;
  graph: DependencyGraph;
}

Examples

Example 1: List All Resources in a Project

import { TerraformParser } from 'parse-hcl';

const parser = new TerraformParser();
const result = parser.parseDirectory('./infrastructure');

if (result.combined) {
  result.combined.resource.forEach(resource => {
    console.log(`${resource.type}.${resource.name}`);
  });
}

// Output:
// aws_s3_bucket.data
// aws_s3_bucket.logs
// aws_iam_role.lambda_exec
// aws_lambda_function.processor

Example 2: Find All Variable References

import { TerraformParser, buildDependencyGraph } from 'parse-hcl';

const parser = new TerraformParser();
const doc = parser.parseFile('main.tf');
const graph = buildDependencyGraph(doc);

// Find all edges pointing to variables
const variableUsages = graph.edges.filter(edge =>
  edge.to.startsWith('variable.')
);

variableUsages.forEach(edge => {
  console.log(`${edge.from} uses ${edge.to}`);
});

// Output:
// resource.aws_s3_bucket.demo uses variable.bucket_name
// locals.full_name uses variable.environment

Example 3: Validate Required Variables Have Defaults

import { TerraformParser } from 'parse-hcl';

const parser = new TerraformParser();
const result = parser.parseDirectory('./modules/vpc');

if (result.combined) {
  const missingDefaults = result.combined.variable.filter(v =>
    !v.default && !v.nullable
  );

  if (missingDefaults.length > 0) {
    console.log('Variables without defaults:');
    missingDefaults.forEach(v => {
      console.log(`  - ${v.name} (${v.source})`);
    });
  }
}

Example 4: Export to File

import { writeFileSync } from 'fs';
import { TerraformParser, toJsonExport, toYamlDocument } from 'parse-hcl';

const parser = new TerraformParser();
const doc = parser.parseFile('main.tf');

// Export as JSON with graph
writeFileSync('output.json', toJsonExport(doc));

// Export as YAML
writeFileSync('output.yaml', toYamlDocument(doc));

Example 5: Analyze Terraform State

import { TfStateParser } from 'parse-hcl';

const state = new TfStateParser().parseFile('terraform.tfstate');

console.log(`Terraform version: ${state.terraform_version}`);
console.log(`Serial: ${state.serial}`);

// List all managed resources
state.resources
  .filter(r => r.mode === 'managed')
  .forEach(r => {
    console.log(`${r.type}.${r.name}`);
    r.instances.forEach((inst, i) => {
      console.log(`  [${i}] id=${inst.attributes?.id}`);
    });
  });

Development

Setup

git clone https://github.com/sigmoid-hq/parse-hcl.git
cd parse-hcl/typescript
yarn install

Scripts

# Build
yarn build

# Run tests
yarn test

# Run tests with coverage
yarn test:coverage

# Lint
yarn lint
yarn lint:fix

# Format
yarn format
yarn format:check

# Run CLI locally
yarn cli --file test/fixtures/main.tf

# Run examples
yarn example            # Full example with output files
yarn example:usage      # Basic usage demo
yarn example:artifacts  # Artifact parsing demo

Project Structure

typescript/
├── src/
│   ├── index.ts              # Main exports
│   ├── cli.ts                # CLI entry point
│   ├── types/
│   │   ├── blocks.ts         # Block type definitions
│   │   └── artifacts.ts      # Artifact type definitions
│   ├── services/
│   │   ├── terraformParser.ts      # Main HCL parser
│   │   ├── terraformJsonParser.ts  # .tf.json parser
│   │   └── artifactParsers.ts      # tfvars/state/plan parsers
│   ├── parsers/
│   │   ├── genericParser.ts        # Generic block parsing
│   │   ├── variableParser.ts       # Variable block parsing
│   │   ├── outputParser.ts         # Output block parsing
│   │   └── localsParser.ts         # Locals block parsing
│   └── utils/
│       ├── lexer/
│       │   ├── hclLexer.ts         # HCL tokenization
│       │   └── blockScanner.ts     # Block boundary detection
│       ├── parser/
│       │   ├── bodyParser.ts       # Block body parsing
│       │   └── valueClassifier.ts  # Value classification
│       ├── serialization/
│       │   ├── serializer.ts       # JSON/YAML serialization
│       │   └── yaml.ts             # YAML utilities
│       ├── graph/
│       │   └── graphBuilder.ts     # Dependency graph builder
│       └── common/
│           ├── errors.ts           # Error types
│           ├── logger.ts           # Logging utilities
│           └── fs.ts               # File system utilities
├── test/
│   ├── unit/                 # Unit tests
│   ├── integration/          # Integration tests
│   └── fixtures/             # Test Terraform files
├── examples/                 # Example scripts
├── package.json
└── tsconfig.json

License

Apache-2.0 - Copyright 2025 Juan Lee