parse-hcl 0.1.1 → 1.0.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/README.md CHANGED
@@ -111,6 +111,7 @@ parse-hcl --file main.tf --out ./out/result.json --stdout
111
111
  - `--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`.
112
112
  - `--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.
113
113
  - `--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.
114
+ - 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.
114
115
  - Warnings and usage go to stderr. The CLI exits non-zero on invalid arguments or parsing failures.
115
116
  - `--format` applies to every output shape; `--no-prune` keeps empty arrays/objects that are removed by default for compactness.
116
117
  - Run without a global install via `npx parse-hcl ...` or `yarn dlx parse-hcl ...`.
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ const path_1 = __importDefault(require("path"));
9
9
  const terraformParser_1 = require("./services/terraformParser");
10
10
  const artifactParsers_1 = require("./services/artifactParsers");
11
11
  const serializer_1 = require("./utils/serialization/serializer");
12
+ const outputMetadata_1 = require("./utils/outputMetadata");
12
13
  const DEFAULT_SINGLE_BASENAME = 'parse-hcl-output';
13
14
  const DEFAULT_COMBINED_BASENAME = 'parse-hcl-output.combined';
14
15
  const DEFAULT_PER_FILE_DIR = 'parse-hcl-output/files';
@@ -106,16 +107,23 @@ function emitSingle(filePath, data, opts) {
106
107
  }
107
108
  function emitDirectory(dirPath, files, combinedDoc, opts) {
108
109
  const ext = getExt(opts.format);
110
+ const perFileBase = opts.split ? resolvePerFileBase(opts) : undefined;
111
+ (0, outputMetadata_1.annotateOutputMetadata)({
112
+ dirPath,
113
+ files,
114
+ perFileBase,
115
+ ext,
116
+ cwd: process.cwd()
117
+ });
109
118
  const combinedData = opts.graph ? combinedDoc : { combined: combinedDoc, files: opts.split ? files : [] };
110
119
  const combinedRendered = render(combinedData, opts);
111
120
  const combinedDefaultName = `${DEFAULT_COMBINED_BASENAME}${ext}`;
112
121
  const combinedTarget = resolveOutPath(opts.out, combinedDefaultName, opts.format, true);
113
122
  writeFile(combinedTarget, combinedRendered);
114
- if (opts.split) {
115
- const baseDir = resolvePerFileBase(opts);
123
+ if (opts.split && perFileBase) {
116
124
  files.forEach((file) => {
117
- const relPath = path_1.default.relative(dirPath, path_1.default.resolve(file.path));
118
- const perFileTarget = path_1.default.join(baseDir, `${relPath}${ext}`);
125
+ const relPath = file.relative_path ?? path_1.default.relative(dirPath, path_1.default.resolve(file.path));
126
+ const perFileTarget = path_1.default.join(perFileBase, `${relPath}${ext}`);
119
127
  const rendered = render(file.document, opts);
120
128
  writeFile(perFileTarget, rendered);
121
129
  });
@@ -196,6 +196,10 @@ export interface ModuleBlock {
196
196
  name: string;
197
197
  /** All properties defined in the block (including source, version, etc.) */
198
198
  properties: Record<string, Value>;
199
+ /** Raw source string as written in HCL (useful when a resolved output dir is added) */
200
+ source_raw?: string;
201
+ /** Relative path to the per-file parse output for the referenced source directory (when available) */
202
+ source_output_dir?: string;
199
203
  /** The original raw text */
200
204
  raw: string;
201
205
  /** Source file path */
@@ -396,6 +400,12 @@ export declare function createEmptyDocument(): TerraformDocument;
396
400
  export interface FileParseResult {
397
401
  /** The file path */
398
402
  path: string;
403
+ /** Path relative to the parsed directory root */
404
+ relative_path?: string;
405
+ /** Relative path (from cwd) to the per-file parse output */
406
+ output_path?: string;
407
+ /** Relative directory containing the per-file parse output */
408
+ output_dir?: string;
399
409
  /** The parsed document */
400
410
  document: TerraformDocument;
401
411
  }
@@ -0,0 +1,20 @@
1
+ import { FileParseResult } from '../types/blocks';
2
+ type FileWithMetadata = FileParseResult & {
3
+ relative_path?: string;
4
+ output_path?: string;
5
+ output_dir?: string;
6
+ };
7
+ interface MetadataOptions {
8
+ dirPath: string;
9
+ files: FileWithMetadata[];
10
+ perFileBase?: string;
11
+ ext: '.json' | '.yaml';
12
+ cwd?: string;
13
+ }
14
+ /**
15
+ * Attaches relative path metadata to per-file results and module blocks.
16
+ * - Adds relative paths for files and their output targets (when split outputs are enabled).
17
+ * - Adds `source_output_dir` to module blocks that point to local directories within the parsed tree.
18
+ */
19
+ export declare function annotateOutputMetadata(options: MetadataOptions): void;
20
+ export {};
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.annotateOutputMetadata = annotateOutputMetadata;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const valueHelpers_1 = require("./common/valueHelpers");
10
+ /**
11
+ * Attaches relative path metadata to per-file results and module blocks.
12
+ * - Adds relative paths for files and their output targets (when split outputs are enabled).
13
+ * - Adds `source_output_dir` to module blocks that point to local directories within the parsed tree.
14
+ */
15
+ function annotateOutputMetadata(options) {
16
+ const root = path_1.default.resolve(options.dirPath);
17
+ const cwd = options.cwd ? path_1.default.resolve(options.cwd) : process.cwd();
18
+ const perFileBase = options.perFileBase ? path_1.default.resolve(options.perFileBase) : undefined;
19
+ for (const file of options.files) {
20
+ const absPath = path_1.default.resolve(file.path);
21
+ const relPath = path_1.default.relative(root, absPath);
22
+ file.relative_path = relPath;
23
+ if (perFileBase) {
24
+ const perFileTarget = path_1.default.join(perFileBase, `${relPath}${options.ext}`);
25
+ const outputPath = normalizeRelative(cwd, perFileTarget);
26
+ file.output_path = outputPath;
27
+ file.output_dir = path_1.default.dirname(outputPath);
28
+ }
29
+ }
30
+ for (const file of options.files) {
31
+ const fileDir = path_1.default.dirname(path_1.default.resolve(file.path));
32
+ for (const mod of file.document.module) {
33
+ const sourceRaw = getRawSource(mod.properties.source);
34
+ if (sourceRaw) {
35
+ mod.source_raw = sourceRaw;
36
+ }
37
+ if (!perFileBase) {
38
+ continue;
39
+ }
40
+ const sourceLiteral = (0, valueHelpers_1.literalString)(mod.properties.source);
41
+ if (!sourceLiteral || !isLocalPath(sourceLiteral)) {
42
+ continue;
43
+ }
44
+ const resolvedSource = path_1.default.resolve(fileDir, sourceLiteral);
45
+ if (!fs_1.default.existsSync(resolvedSource) || !fs_1.default.statSync(resolvedSource).isDirectory()) {
46
+ continue;
47
+ }
48
+ const relToRoot = path_1.default.relative(root, resolvedSource);
49
+ if (relToRoot.startsWith('..') || path_1.default.isAbsolute(relToRoot)) {
50
+ continue;
51
+ }
52
+ const outputDir = path_1.default.join(perFileBase, relToRoot);
53
+ mod.source_output_dir = normalizeRelative(cwd, outputDir);
54
+ }
55
+ }
56
+ }
57
+ function isLocalPath(source) {
58
+ if (source.includes('://') || source.includes('::')) {
59
+ return false;
60
+ }
61
+ return source.startsWith('.') || path_1.default.isAbsolute(source);
62
+ }
63
+ function normalizeRelative(from, to) {
64
+ const rel = path_1.default.relative(from, to);
65
+ return rel || '.';
66
+ }
67
+ function getRawSource(value) {
68
+ if (!value) {
69
+ return undefined;
70
+ }
71
+ if (value.type === 'literal' && typeof value.value === 'string') {
72
+ return value.value;
73
+ }
74
+ if ('raw' in value && typeof value.raw === 'string') {
75
+ return value.raw;
76
+ }
77
+ return undefined;
78
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "parse-hcl",
3
- "version": "0.1.1",
4
- "description": "Lightweight HCL parser focused on identifying and classifying blocks. Supports common Terraform blocks (resource, variable, output, locals, etc.) for tooling and automation.",
3
+ "version": "1.0.0",
4
+ "description": "CLI-first Terraform/HCL parser with JSON/YAML output, dependency graph export, and per-file metadata for IaC tooling.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
@@ -27,10 +27,18 @@
27
27
  "keywords": [
28
28
  "terraform",
29
29
  "hcl",
30
- "parser",
30
+ "terraform parser",
31
+ "iac",
32
+ "infrastructure-as-code",
31
33
  "tfvars",
32
34
  "tfstate",
33
- "plan"
35
+ "plan",
36
+ "plan.json",
37
+ "cli",
38
+ "json",
39
+ "yaml",
40
+ "dependency graph",
41
+ "static analysis"
34
42
  ],
35
43
  "sideEffects": false,
36
44
  "engines": {