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

@cruglobal/js-hcl2

v0.1.1

Published

Parse and encode HashiCorp Configuration Language v2 (HCL2) in TypeScript, with lossless round-trip support.

Readme

@cruglobal/js-hcl2

npm license

Status: AI-generated, not actively maintained. This library was authored primarily by an AI assistant against the specification in docs/design.md and is not on anyone's active roadmap. Dependabot keeps dependencies and security advisories up to date automatically (patch + minor bumps auto-merge; majors require manual review), but feature work, bug fixes, and other changes happen on a best-effort basis. Pull requests and issues are welcome — they may take time to be reviewed. See CONTRIBUTING.md for the contribution workflow.

Parse and encode HashiCorp Configuration Language v2 (HCL2) in TypeScript. Unlike every other npm HCL2 reader, this library supports both directions — reading HCL into JS values and emitting HCL from JS values — plus a lossless round-trip Document API that preserves comments and formatting across edits.

import * as HCL from "@cruglobal/js-hcl2";

// Parse
HCL.parse('name = "demo"\nport = 8080\n');
// → { name: "demo", port: 8080 }

// Emit
HCL.stringify({ name: "demo", port: 8080 });
// → 'name = "demo"\nport = 8080\n'

// Edit while preserving trivia
const doc = HCL.parseDocument('# greeting\nname = "demo"\n');
doc.set("name", "production");
doc.toString();
// → '# greeting\nname = "production"\n'

Runs on Node.js, Bun, Deno, and modern browsers. Zero runtime dependencies.


Install

npm install @cruglobal/js-hcl2

The package ships both ESM and CJS builds plus TypeScript .d.tss. Pick whichever your bundler or runtime prefers:

// ESM
import { parse, stringify, parseDocument } from "@cruglobal/js-hcl2";

// default export — same namespace
import HCL from "@cruglobal/js-hcl2";
HCL.parse(source);
// CJS
const { parse, stringify, parseDocument } = require("@cruglobal/js-hcl2");

Quickstart

Parsing HCL into a plain JS value

import { parse } from "@cruglobal/js-hcl2";

parse(`
  terraform_version = "1.5.0"
  enabled           = true
  regions           = ["us-east-1", "us-west-2"]
`);
/*  {
      terraform_version: "1.5.0",
      enabled: true,
      regions: ["us-east-1", "us-west-2"],
    }
*/

Blocks — including Terraform's resource "type" "name" { … } shape — project into nested objects. Repeated blocks with identical labels collect into arrays:

parse(`
  resource "aws_s3_bucket" "a" { acl = "private" }
  resource "aws_s3_bucket" "b" { acl = "public" }
`);
/*  {
      resource: {
        aws_s3_bucket: {
          a: { acl: "private" },
          b: { acl: "public" },
        },
      },
    }
*/

Expressions that involve variables, operators, calls, or interpolated templates don't collapse to primitives — they come back as an opaque Expression wrapper with the original source and structural AST preserved:

import { isExpression, parse } from "@cruglobal/js-hcl2";

const v = parse('tags = merge(var.a, { env = "dev" })\n');
const expr = (v as Record<string, unknown>).tags;
if (isExpression(expr)) {
  expr.source; // → 'merge(var.a, { env = "dev" })'
  expr.kind;   // → "function-call"
  expr.ast;    // → full FunctionCallNode
}

Emitting HCL from a plain JS value

import { stringify } from "@cruglobal/js-hcl2";

stringify({
  resource: {
    aws_s3_bucket: {
      a: { acl: "private" },
      b: { acl: "public" },
    },
  },
});
/*  resource "aws_s3_bucket" "a" {
      acl = "private"
    }
    resource "aws_s3_bucket" "b" {
      acl = "public"
    }
*/

stringify accepts JSON-style options:

stringify(value, {
  indent: 4,           // spaces per nesting level (default 2)
  sortKeys: true,      // alphabetize body and object keys
  trailingNewline: false,
  replacer: (key, val) => (key === "secret" ? undefined : val),
});

Editing HCL with preserved comments and formatting

import { parseDocument } from "@cruglobal/js-hcl2";

const doc = parseDocument(`
  # Production database
  resource "aws_db_instance" "main" {
    engine = "postgres"
    engine_version = "15.3" # pinned to match prod
  }
`);

doc.set(["resource", "aws_db_instance", "main", "engine_version"], "16.1");
doc.set(["resource", "aws_db_instance", "main", "skip_final_snapshot"], true);

console.log(doc.toString());
// # Production database
// resource "aws_db_instance" "main" {
//   engine = "postgres"
//   engine_version = "16.1" # pinned to match prod
//   skip_final_snapshot = true
// }

parseDocument(source).toString() === source for any unedited input (byte-identical). Edits preserve leading/trailing trivia around the node being replaced or deleted.


Public API

High-level surface. Full signatures and JSDoc in the generated TypeDoc site.

| Entry point | Purpose | | ------------------------------- | -------------------------------------------------------------------------- | | parse(source, options?) | Parse HCL text into a plain Value. | | stringify(value, options?) | Emit canonical HCL text from a Value. | | parseDocument(source, options?) | Parse into a trivia-aware Document supporting lossless round-trip + edits. | | Document#toString() | Re-emit the CST (byte-identical when unedited). | | Document#toValue() | Same shape as parse(). | | Document#get(path) | Resolve a dotted / array path to a CST node. | | Document#set(path, value) | Replace an attribute's value or insert a new attribute. | | Document#delete(path) | Remove an attribute or whole block, cleaning surrounding trivia. |

Lower-level building blocks are also exported — SourceFile, lex, Parser, parseExpr, print, toValue, exprToValue, HCLParseError, the full CST node type union (BodyNode, AttributeNode, BlockNode, ExprNode, TemplateNode, etc.), and the Expression wrapper type. See docs/design.md for how they fit together.

Error reporting

Both parse and parseDocument throw HCLParseError on malformed input. Each error carries filename, line, column, offset, range, and a caret-marked snippet:

import { HCLParseError, parse } from "@cruglobal/js-hcl2";

try {
  parse("x = \n", { filename: "main.tf" });
} catch (e) {
  if (e instanceof HCLParseError) {
    console.error(`${e.filename}:${e.line}:${e.column}: ${e.message}`);
    console.error(e.snippet);
  }
}

Pass { bail: false } to collect every error in one pass (thrown as an aggregate HCLParseError whose errors[] has one entry per failure).


Feature / compatibility matrix

HCL2 native syntax (v0.1)

| Feature | Supported | Notes | | ------------------------------------------------------ | :-------: | ----- | | Attributes | ✅ | | | Blocks (0 / 1 / 2 / 3+ labels) | ✅ | | | One-liner blocks (block { k = v }) | ✅ | | | Line comments (#, //) | ✅ | | | Block comments (/* … */) | ✅ | | | Primitive literals: number, bool, null, string | ✅ | Numbers are finite JS doubles; NaN/Infinity encode as null on emit. | | Quoted strings with escapes (\n \t \" \\ \uNNNN) | ✅ | | | Heredocs (<<EOT … EOT) | ✅ | | | Heredoc strip form (<<-EOT) | ✅ | Recognised structurally; body content stored verbatim (strip happens at evaluation time — see below). | | Tuple and object literals (with trailing commas) | ✅ | | | Traversal (.attr, [expr], legacy .digit) | ✅ | | | Attribute splat (a.*.b) and full splat (a[*].b) | ✅ | | | Function calls (f(a, b, c...)) | ✅ | | | Unary - / ! | ✅ | | | Binary + - * / % == != < <= > >= && \|\| | ✅ | | | Conditional cond ? then : else | ✅ | | | For expressions (tuple + object form with if, ...) | ✅ | | | Template interpolation (${…}) in strings + heredocs | ✅ | | | Template control directives (%{if}, %{for}) | ✅ | | | Strip markers (${~ ~}, %{~ ~}) | ✅ | | | Unicode identifiers (UAX #31) + dash in ID_Continue | ✅ | |

Out of scope for v0.x

| Feature | Status | Tracked as | | ------------------------------------ | -------- | ---------- | | Expression evaluation | ⏳ | Future milestone — everything non-literal is returned as an Expression wrapper rather than reduced to a primitive. | | Standard function library (jsonencode, merge, …) | ⏳ | Requires evaluator. | | JSON-syntax HCL (.tf.json) | ⏳ | Planned for v0.2. | | Schema-directed decoding (Zod-style) | ⏳ | Later sub-package. |

Runtime matrix

| Runtime | Supported | CI-enforced | | ------------------ | :-------: | :---------: | | Node.js 18+ | ✅ | ✅ (24.x) | | Bun 1.x | ✅ | ✅ | | Deno 2.x | ✅ | ✅ | | Modern browsers (ES2022) | ✅ | Smoke via happy-dom |


Development

This repo uses asdf to pin the exact Node.js version (see .tool-versions). After cloning:

asdf plugin add nodejs   # one-time, if not already set up
asdf install
npm install
npm test

See CONTRIBUTING.md for the full workflow and docs/design.md for the architectural overview.


License

BSD-3-Clause. Test fixtures vendored from external projects retain their original licenses; see NOTICES.md for attributions. Vendored fixtures are excluded from the published npm tarball.