{"_id":"@cruglobal/js-hcl2","_rev":"3-51a55784821a2ae09500c94e8a396c6b","name":"@cruglobal/js-hcl2","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@cruglobal/js-hcl2","version":"0.1.0","keywords":["hcl","hcl2","hashicorp","terraform","parser","encoder","stringify"],"author":{"name":"Cru"},"license":"BSD-3-Clause","_id":"@cruglobal/js-hcl2@0.1.0","maintainers":[{"name":"johnplastow","email":"john.plastow@cru.org"},{"name":"jcwatson11","email":"jon@sherlockwatson.com"},{"name":"canac-cru","email":"caleb.cox@cru.org"},{"name":"frett","email":"daniel.frett@gmail.com"},{"name":"daniel.bizz","email":"daniel@bizz-websites.com"},{"name":"omicron7","email":"brian.zoetewey@cru.org"},{"name":"wjames1111","email":"william.james@cru.org"}],"homepage":"https://github.com/CruGlobal/js-hcl2#readme","bugs":{"url":"https://github.com/CruGlobal/js-hcl2/issues"},"dist":{"shasum":"571b959033f94605781829459993da889261c909","tarball":"https://registry.npmjs.org/@cruglobal/js-hcl2/-/js-hcl2-0.1.0.tgz","fileCount":11,"integrity":"sha512-DssrOK6wgRZmbJSr/jreMD4QU4pmX09BOxvnndB7D6xi+vndZXAuwC+pYsT/M1LRA+eCLSX+Yl/Hecf3lko31Q==","signatures":[{"sig":"MEYCIQDE6otxVXo3oD0G1B+7ZI1NtExid0e2CRZE3JlH3bpeAAIhAI/N/U9gc6kQ2Ypx6gylkLFTnILOaRtpVD6ND5agf/yb","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":999485},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"d6dcb223ad1facdb4f47b6a3d9d45c1111028543","scripts":{"docs":"typedoc","lint":"eslint .","test":"vitest run","build":"tsup","format":"prettier --write .","typecheck":"tsc --noEmit","docs:watch":"typedoc --watch","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run lint && npm test && npm run build","generate:unicode":"node scripts/generate-unicode.mjs"},"_npmUser":{"name":"omicron7","email":"brian.zoetewey@cru.org"},"overrides":{"serialize-javascript":"^7.0.5"},"repository":{"url":"git+https://github.com/CruGlobal/js-hcl2.git","type":"git"},"_npmVersion":"11.12.1","description":"Parse and encode HashiCorp Configuration Language v2 (HCL2) in TypeScript, with lossless round-trip support.","directories":{},"_nodeVersion":"24.15.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","eslint":"^10.0.0","vitest":"^4.0.0","typedoc":"^0.28.19","prettier":"^3.0.0","happy-dom":"^20.9.0","@eslint/js":"^10.0.1","fast-check":"^4.7.0","typescript":"^6.0.0","@types/node":"^25.6.0","hcl2-json-parser":"^1.0.1","typescript-eslint":"^8.0.0","@vitest/coverage-v8":"^4.0.0","eslint-config-prettier":"^10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/js-hcl2_0.1.0_1776699422687_0.6218401334962345","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@cruglobal/js-hcl2","version":"0.1.1","keywords":["hcl","hcl2","hashicorp","terraform","parser","encoder","stringify"],"author":{"name":"Cru"},"license":"BSD-3-Clause","_id":"@cruglobal/js-hcl2@0.1.1","maintainers":[{"name":"omicron7","email":"brian.zoetewey@cru.org"}],"homepage":"https://github.com/CruGlobal/js-hcl2#readme","bugs":{"url":"https://github.com/CruGlobal/js-hcl2/issues"},"dist":{"shasum":"344b474bdd050d05f0322b4e9aaa3bc819ef1830","tarball":"https://registry.npmjs.org/@cruglobal/js-hcl2/-/js-hcl2-0.1.1.tgz","fileCount":11,"integrity":"sha512-XE9enPyjjWXmEsyspuIpo0p0azvSSd8M9qtZgC9v8oxVnnCw1vF6PQjN6IY79qKE1/TeWwJi4EqXEqR6+8i13w==","signatures":[{"sig":"MEUCIQCijuz4c+N27m4t+w9kirT3GZeckQqnAAPRTOcTMFkdXwIgQFpYMktDi0vBDw9p5ipz2dS3aXRPv1bmZUPa3eRmX6Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cruglobal%2fjs-hcl2@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1000167},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"8eff76e6fc2e6852ee2806db64fe894b6c80fc74","scripts":{"docs":"typedoc","lint":"eslint .","test":"vitest run","build":"tsup","format":"prettier --write .","typecheck":"tsc --noEmit","docs:watch":"typedoc --watch","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run lint && npm test && npm run build","generate:unicode":"node scripts/generate-unicode.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3d344cc-331a-44aa-b5a7-4041a973fe28"}},"overrides":{"serialize-javascript":"^7.0.5"},"repository":{"url":"git+https://github.com/CruGlobal/js-hcl2.git","type":"git"},"_npmVersion":"11.12.1","description":"Parse and encode HashiCorp Configuration Language v2 (HCL2) in TypeScript, with lossless round-trip support.","directories":{},"_nodeVersion":"24.15.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","eslint":"^10.0.0","vitest":"^4.0.0","typedoc":"^0.28.19","prettier":"^3.0.0","happy-dom":"^20.9.0","@eslint/js":"^10.0.1","fast-check":"^4.7.0","typescript":"^6.0.0","@types/node":"^25.6.0","hcl2-json-parser":"^1.0.1","typescript-eslint":"^8.0.0","@vitest/coverage-v8":"^4.0.0","eslint-config-prettier":"^10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/js-hcl2_0.1.1_1776704866703_0.5695851346295042","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@cruglobal/js-hcl2","version":"0.1.2","description":"Parse and encode HashiCorp Configuration Language v2 (HCL2) in TypeScript, with lossless round-trip support.","keywords":["hcl","hcl2","hashicorp","terraform","parser","encoder","stringify"],"homepage":"https://github.com/CruGlobal/js-hcl2#readme","bugs":{"url":"https://github.com/CruGlobal/js-hcl2/issues"},"repository":{"type":"git","url":"git+https://github.com/CruGlobal/js-hcl2.git"},"license":"BSD-3-Clause","author":{"name":"Cru"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","lint":"eslint .","format":"prettier --write .","docs":"typedoc","docs:watch":"typedoc --watch","generate:unicode":"node scripts/generate-unicode.mjs","prepublishOnly":"npm run typecheck && npm run lint && npm test && npm run build"},"engines":{"node":">=24.0.0"},"publishConfig":{"registry":"https://registry.npmjs.org","access":"public"},"overrides":{"serialize-javascript":"^7.0.5"},"devDependencies":{"@eslint/js":"^10.0.1","@types/node":"^25.6.0","@vitest/coverage-v8":"^4.0.0","eslint":"^10.0.0","eslint-config-prettier":"^10.0.0","fast-check":"^4.7.0","happy-dom":"^20.9.0","hcl2-json-parser":"^1.0.1","prettier":"^3.0.0","tsup":"^8.0.0","typedoc":"^0.28.19","typescript":"^6.0.0","typescript-eslint":"^8.0.0","vitest":"^4.0.0"},"gitHead":"c76fb08736bdc6bb285b0b79645fadb5b92fc378","_id":"@cruglobal/js-hcl2@0.1.2","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-L7DQnwvTLyq7WR2hXgk4uQxxYrO074Mc9eusziCTvwCQjt1OtDPvS4yGKKBHKm+582aDvvkkX9B62sIBd5zVNA==","shasum":"8437639f11aa3aeab91b3cc5932784a8c6f02a75","tarball":"https://registry.npmjs.org/@cruglobal/js-hcl2/-/js-hcl2-0.1.2.tgz","fileCount":11,"unpackedSize":1009947,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cruglobal%2fjs-hcl2@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCTxclrONtSfCVdapAh+9gdodQXCzd34ESYq2ve22l3pwIgax0TXzXlZeFr0hbUSuYZc7uJfT32V+sLz1t9ite7WnY="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3d344cc-331a-44aa-b5a7-4041a973fe28"}},"directories":{},"maintainers":[{"name":"omicron7","email":"brian.zoetewey@cru.org"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/js-hcl2_0.1.2_1781792992520_0.07381403454075275"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-20T15:37:02.611Z","modified":"2026-06-18T14:29:53.202Z","0.1.0":"2026-04-20T15:37:02.881Z","0.1.1":"2026-04-20T17:07:46.903Z","0.1.2":"2026-06-18T14:29:52.760Z"},"bugs":{"url":"https://github.com/CruGlobal/js-hcl2/issues"},"author":{"name":"Cru"},"license":"BSD-3-Clause","homepage":"https://github.com/CruGlobal/js-hcl2#readme","keywords":["hcl","hcl2","hashicorp","terraform","parser","encoder","stringify"],"repository":{"type":"git","url":"git+https://github.com/CruGlobal/js-hcl2.git"},"description":"Parse and encode HashiCorp Configuration Language v2 (HCL2) in TypeScript, with lossless round-trip support.","maintainers":[{"name":"omicron7","email":"brian.zoetewey@cru.org"}],"readme":"# @cruglobal/js-hcl2\n\n[![npm](https://img.shields.io/npm/v/@cruglobal/js-hcl2.svg)](https://www.npmjs.com/package/@cruglobal/js-hcl2)\n[![license](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](LICENSE)\n\n> **Status: AI-generated, not actively maintained.** This library was\n> authored primarily by an AI assistant against the specification in\n> [`docs/design.md`](docs/design.md) and is not on anyone's active\n> roadmap. Dependabot keeps dependencies and security advisories up to\n> date automatically (patch + minor bumps auto-merge; majors require\n> manual review), but feature work, bug fixes, and other changes\n> happen on a best-effort basis. **Pull requests and issues are\n> welcome** — they may take time to be reviewed. See\n> [`CONTRIBUTING.md`](CONTRIBUTING.md) for the contribution workflow.\n\nParse and encode [HashiCorp Configuration Language v2](https://github.com/hashicorp/hcl) (HCL2)\nin TypeScript. Unlike every other npm HCL2 reader, this library supports both\ndirections — reading HCL into JS values *and* emitting HCL from JS values —\nplus a **lossless round-trip** `Document` API that preserves comments and\nformatting across edits.\n\n```ts\nimport * as HCL from \"@cruglobal/js-hcl2\";\n\n// Parse\nHCL.parse('name = \"demo\"\\nport = 8080\\n');\n// → { name: \"demo\", port: 8080 }\n\n// Emit\nHCL.stringify({ name: \"demo\", port: 8080 });\n// → 'name = \"demo\"\\nport = 8080\\n'\n\n// Edit while preserving trivia\nconst doc = HCL.parseDocument('# greeting\\nname = \"demo\"\\n');\ndoc.set(\"name\", \"production\");\ndoc.toString();\n// → '# greeting\\nname = \"production\"\\n'\n```\n\nRuns on Node.js, Bun, Deno, and modern browsers. Zero runtime dependencies.\n\n---\n\n## Install\n\n```sh\nnpm install @cruglobal/js-hcl2\n```\n\nThe package ships both ESM and CJS builds plus TypeScript `.d.ts`s. Pick\nwhichever your bundler or runtime prefers:\n\n```ts\n// ESM\nimport { parse, stringify, parseDocument } from \"@cruglobal/js-hcl2\";\n\n// default export — same namespace\nimport HCL from \"@cruglobal/js-hcl2\";\nHCL.parse(source);\n```\n\n```js\n// CJS\nconst { parse, stringify, parseDocument } = require(\"@cruglobal/js-hcl2\");\n```\n\n---\n\n## Quickstart\n\n### Parsing HCL into a plain JS value\n\n```ts\nimport { parse } from \"@cruglobal/js-hcl2\";\n\nparse(`\n  terraform_version = \"1.5.0\"\n  enabled           = true\n  regions           = [\"us-east-1\", \"us-west-2\"]\n`);\n/*  {\n      terraform_version: \"1.5.0\",\n      enabled: true,\n      regions: [\"us-east-1\", \"us-west-2\"],\n    }\n*/\n```\n\nBlocks — including Terraform's `resource \"type\" \"name\" { … }` shape —\nproject into nested objects. Repeated blocks with identical labels collect\ninto arrays:\n\n```ts\nparse(`\n  resource \"aws_s3_bucket\" \"a\" { acl = \"private\" }\n  resource \"aws_s3_bucket\" \"b\" { acl = \"public\" }\n`);\n/*  {\n      resource: {\n        aws_s3_bucket: {\n          a: { acl: \"private\" },\n          b: { acl: \"public\" },\n        },\n      },\n    }\n*/\n```\n\nExpressions that involve variables, operators, calls, or interpolated\ntemplates don't collapse to primitives — they come back as an opaque\n`Expression` wrapper with the original source and structural AST preserved:\n\n```ts\nimport { isExpression, parse } from \"@cruglobal/js-hcl2\";\n\nconst v = parse('tags = merge(var.a, { env = \"dev\" })\\n');\nconst expr = (v as Record<string, unknown>).tags;\nif (isExpression(expr)) {\n  expr.source; // → 'merge(var.a, { env = \"dev\" })'\n  expr.kind;   // → \"function-call\"\n  expr.ast;    // → full FunctionCallNode\n}\n```\n\n### Emitting HCL from a plain JS value\n\n```ts\nimport { stringify } from \"@cruglobal/js-hcl2\";\n\nstringify({\n  resource: {\n    aws_s3_bucket: {\n      a: { acl: \"private\" },\n      b: { acl: \"public\" },\n    },\n  },\n});\n/*  resource \"aws_s3_bucket\" \"a\" {\n      acl = \"private\"\n    }\n    resource \"aws_s3_bucket\" \"b\" {\n      acl = \"public\"\n    }\n*/\n```\n\n`stringify` accepts JSON-style options:\n\n```ts\nstringify(value, {\n  indent: 4,           // spaces per nesting level (default 2)\n  sortKeys: true,      // alphabetize body and object keys\n  trailingNewline: false,\n  replacer: (key, val) => (key === \"secret\" ? undefined : val),\n});\n```\n\n### Editing HCL with preserved comments and formatting\n\n```ts\nimport { parseDocument } from \"@cruglobal/js-hcl2\";\n\nconst doc = parseDocument(`\n  # Production database\n  resource \"aws_db_instance\" \"main\" {\n    engine = \"postgres\"\n    engine_version = \"15.3\" # pinned to match prod\n  }\n`);\n\ndoc.set([\"resource\", \"aws_db_instance\", \"main\", \"engine_version\"], \"16.1\");\ndoc.set([\"resource\", \"aws_db_instance\", \"main\", \"skip_final_snapshot\"], true);\n\nconsole.log(doc.toString());\n// # Production database\n// resource \"aws_db_instance\" \"main\" {\n//   engine = \"postgres\"\n//   engine_version = \"16.1\" # pinned to match prod\n//   skip_final_snapshot = true\n// }\n```\n\n`parseDocument(source).toString() === source` for any unedited input\n(byte-identical). Edits preserve leading/trailing trivia around the\nnode being replaced or deleted.\n\n---\n\n## Public API\n\nHigh-level surface. Full signatures and JSDoc in the generated TypeDoc\nsite.\n\n| Entry point                     | Purpose                                                                    |\n| ------------------------------- | -------------------------------------------------------------------------- |\n| `parse(source, options?)`       | Parse HCL text into a plain `Value`.                                       |\n| `stringify(value, options?)`    | Emit canonical HCL text from a `Value`.                                    |\n| `parseDocument(source, options?)` | Parse into a trivia-aware `Document` supporting lossless round-trip + edits. |\n| `Document#toString()`           | Re-emit the CST (byte-identical when unedited).                            |\n| `Document#toValue()`            | Same shape as `parse()`.                                                   |\n| `Document#get(path)`            | Resolve a dotted / array path to a CST node.                               |\n| `Document#set(path, value)`     | Replace an attribute's value or insert a new attribute.                    |\n| `Document#delete(path)`         | Remove an attribute or whole block, cleaning surrounding trivia.           |\n\nLower-level building blocks are also exported — `SourceFile`, `lex`,\n`Parser`, `parseExpr`, `print`, `toValue`, `exprToValue`,\n`HCLParseError`, the full CST node type union (`BodyNode`,\n`AttributeNode`, `BlockNode`, `ExprNode`, `TemplateNode`, etc.), and\nthe `Expression` wrapper type. See\n[`docs/design.md`](docs/design.md) for how they fit together.\n\n### Error reporting\n\nBoth `parse` and `parseDocument` throw `HCLParseError` on malformed\ninput. Each error carries `filename`, `line`, `column`, `offset`,\n`range`, and a caret-marked `snippet`:\n\n```ts\nimport { HCLParseError, parse } from \"@cruglobal/js-hcl2\";\n\ntry {\n  parse(\"x = \\n\", { filename: \"main.tf\" });\n} catch (e) {\n  if (e instanceof HCLParseError) {\n    console.error(`${e.filename}:${e.line}:${e.column}: ${e.message}`);\n    console.error(e.snippet);\n  }\n}\n```\n\nPass `{ bail: false }` to collect every error in one pass (thrown as an\naggregate `HCLParseError` whose `errors[]` has one entry per failure).\n\n---\n\n## Feature / compatibility matrix\n\n### HCL2 native syntax (v0.1)\n\n| Feature                                                | Supported | Notes |\n| ------------------------------------------------------ | :-------: | ----- |\n| Attributes                                             | ✅        |       |\n| Blocks (0 / 1 / 2 / 3+ labels)                         | ✅        |       |\n| One-liner blocks (`block { k = v }`)                   | ✅        |       |\n| Line comments (`#`, `//`)                              | ✅        |       |\n| Block comments (`/* … */`)                             | ✅        |       |\n| Primitive literals: number, bool, `null`, string       | ✅        | Numbers are finite JS doubles; NaN/Infinity encode as `null` on emit. |\n| Quoted strings with escapes (`\\n \\t \\\" \\\\ \\uNNNN`)     | ✅        |       |\n| Heredocs (`<<EOT … EOT`)                               | ✅        |       |\n| Heredoc strip form (`<<-EOT`)                          | ✅        | Recognised structurally; body content stored verbatim (strip happens at evaluation time — see below). |\n| Tuple and object literals (with trailing commas)       | ✅        |       |\n| Traversal (`.attr`, `[expr]`, legacy `.digit`)         | ✅        |       |\n| Attribute splat (`a.*.b`) and full splat (`a[*].b`)    | ✅        |       |\n| Function calls (`f(a, b, c...)`)                       | ✅        |       |\n| Unary `-` / `!`                                        | ✅        |       |\n| Binary `+ - * / % == != < <= > >= && \\|\\|`             | ✅        |       |\n| Conditional `cond ? then : else`                       | ✅        |       |\n| For expressions (tuple + object form with `if`, `...`) | ✅        |       |\n| Template interpolation (`${…}`) in strings + heredocs  | ✅        |       |\n| Template control directives (`%{if}`, `%{for}`)        | ✅        |       |\n| Strip markers (`${~ ~}`, `%{~ ~}`)                     | ✅        |       |\n| Unicode identifiers (UAX #31) + dash in ID_Continue    | ✅        |       |\n\n### Out of scope for v0.x\n\n| Feature                              | Status   | Tracked as |\n| ------------------------------------ | -------- | ---------- |\n| Expression **evaluation**            | ⏳        | Future milestone — everything non-literal is returned as an `Expression` wrapper rather than reduced to a primitive. |\n| Standard function library (`jsonencode`, `merge`, …) | ⏳ | Requires evaluator. |\n| JSON-syntax HCL (`.tf.json`)         | ⏳        | Planned for v0.2. |\n| Schema-directed decoding (Zod-style) | ⏳        | Later sub-package. |\n\n### Runtime matrix\n\n| Runtime            | Supported | CI-enforced |\n| ------------------ | :-------: | :---------: |\n| Node.js 18+        | ✅        | ✅ (24.x)   |\n| Bun 1.x            | ✅        | ✅          |\n| Deno 2.x           | ✅        | ✅          |\n| Modern browsers (ES2022) | ✅  | Smoke via happy-dom |\n\n---\n\n## Development\n\nThis repo uses [`asdf`](https://asdf-vm.com/) to pin the exact Node.js\nversion (see [`.tool-versions`](.tool-versions)). After cloning:\n\n```sh\nasdf plugin add nodejs   # one-time, if not already set up\nasdf install\nnpm install\nnpm test\n```\n\nSee [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full workflow and\n[`docs/design.md`](docs/design.md) for the architectural overview.\n\n---\n\n## License\n\n[BSD-3-Clause](LICENSE). Test fixtures vendored from external projects\nretain their original licenses; see [`NOTICES.md`](NOTICES.md) for\nattributions. Vendored fixtures are excluded from the published npm\ntarball.\n","readmeFilename":"README.md"}