parse-hcl 0.1.0 → 0.1.1
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 +35 -0
- package/dist/cli.js +102 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,12 @@ pnpm add parse-hcl
|
|
|
49
49
|
|
|
50
50
|
The `parse-hcl` CLI provides instant Terraform configuration analysis from your terminal.
|
|
51
51
|
|
|
52
|
+
### Command Synopsis
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
parse-hcl --file <path> | --dir <path> [--format json|yaml] [--graph] [--no-prune]
|
|
56
|
+
```
|
|
57
|
+
|
|
52
58
|
### Basic Commands
|
|
53
59
|
|
|
54
60
|
```bash
|
|
@@ -75,6 +81,9 @@ parse-hcl --file plan.json
|
|
|
75
81
|
|
|
76
82
|
# Keep empty arrays/objects in output
|
|
77
83
|
parse-hcl --file main.tf --no-prune
|
|
84
|
+
|
|
85
|
+
# Save to custom path and also print
|
|
86
|
+
parse-hcl --file main.tf --out ./out/result.json --stdout
|
|
78
87
|
```
|
|
79
88
|
|
|
80
89
|
### Options Reference
|
|
@@ -86,6 +95,25 @@ parse-hcl --file main.tf --no-prune
|
|
|
86
95
|
| `--format <type>` | Output format: `json` or `yaml` | `json` |
|
|
87
96
|
| `--graph` | Include dependency graph (nodes, edges, references) | `false` |
|
|
88
97
|
| `--no-prune` | Keep empty arrays and objects in output | `false` |
|
|
98
|
+
| `--out <path>` | Save output to file (or directory for combined output) | `./parse-hcl-output*.{json,yaml}` |
|
|
99
|
+
| `--out-dir <dir>` | Save per-file results under this directory (directory mode) | `./parse-hcl-output/files` |
|
|
100
|
+
| `--split` / `--no-split` | Enable/disable per-file saving in directory mode | `true` |
|
|
101
|
+
| `--stdout` / `--no-stdout` | Also print to stdout (default off) | `false` |
|
|
102
|
+
|
|
103
|
+
### Behavior and Defaults
|
|
104
|
+
|
|
105
|
+
- Pass either `--file` or `--dir`; if both are present, `--file` is used. Missing inputs print usage to stderr and exit with code `1`.
|
|
106
|
+
- **Default output is files, stdout off.**
|
|
107
|
+
- Single file: writes `./parse-hcl-output.{json|yaml}`.
|
|
108
|
+
- Directory: writes combined `./parse-hcl-output.combined.{json|yaml}` and per-file under `./parse-hcl-output/files/<relative-path>.{json|yaml}`.
|
|
109
|
+
- Add `--stdout` to also print.
|
|
110
|
+
- `--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`.
|
|
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
|
+
- `--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
|
+
- `--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
|
+
- Warnings and usage go to stderr. The CLI exits non-zero on invalid arguments or parsing failures.
|
|
115
|
+
- `--format` applies to every output shape; `--no-prune` keeps empty arrays/objects that are removed by default for compactness.
|
|
116
|
+
- Run without a global install via `npx parse-hcl ...` or `yarn dlx parse-hcl ...`.
|
|
89
117
|
|
|
90
118
|
### Output Formats
|
|
91
119
|
|
|
@@ -154,6 +182,13 @@ variable:
|
|
|
154
182
|
value: us-east-1
|
|
155
183
|
```
|
|
156
184
|
|
|
185
|
+
**Default saved files (no flags):**
|
|
186
|
+
```bash
|
|
187
|
+
$ ls parse-hcl-output*
|
|
188
|
+
parse-hcl-output.combined.json
|
|
189
|
+
parse-hcl-output/files/main.tf.json
|
|
190
|
+
```
|
|
191
|
+
|
|
157
192
|
**Graph Output:**
|
|
158
193
|
```bash
|
|
159
194
|
$ parse-hcl --file main.tf --graph --format json
|
package/dist/cli.js
CHANGED
|
@@ -4,12 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
8
|
const path_1 = __importDefault(require("path"));
|
|
8
9
|
const terraformParser_1 = require("./services/terraformParser");
|
|
9
10
|
const artifactParsers_1 = require("./services/artifactParsers");
|
|
10
11
|
const serializer_1 = require("./utils/serialization/serializer");
|
|
12
|
+
const DEFAULT_SINGLE_BASENAME = 'parse-hcl-output';
|
|
13
|
+
const DEFAULT_COMBINED_BASENAME = 'parse-hcl-output.combined';
|
|
14
|
+
const DEFAULT_PER_FILE_DIR = 'parse-hcl-output/files';
|
|
11
15
|
function parseArgs(argv) {
|
|
12
|
-
const opts = { format: 'json', graph: false, prune: true };
|
|
16
|
+
const opts = { format: 'json', graph: false, prune: true, split: true, stdout: false };
|
|
13
17
|
for (let i = 0; i < argv.length; i += 1) {
|
|
14
18
|
const arg = argv[i];
|
|
15
19
|
if (arg === '--file' && argv[i + 1]) {
|
|
@@ -30,6 +34,24 @@ function parseArgs(argv) {
|
|
|
30
34
|
else if (arg === '--no-prune') {
|
|
31
35
|
opts.prune = false;
|
|
32
36
|
}
|
|
37
|
+
else if (arg === '--out' && argv[i + 1]) {
|
|
38
|
+
opts.out = argv[++i];
|
|
39
|
+
}
|
|
40
|
+
else if (arg === '--out-dir' && argv[i + 1]) {
|
|
41
|
+
opts.outDir = argv[++i];
|
|
42
|
+
}
|
|
43
|
+
else if (arg === '--split') {
|
|
44
|
+
opts.split = true;
|
|
45
|
+
}
|
|
46
|
+
else if (arg === '--no-split') {
|
|
47
|
+
opts.split = false;
|
|
48
|
+
}
|
|
49
|
+
else if (arg === '--stdout') {
|
|
50
|
+
opts.stdout = true;
|
|
51
|
+
}
|
|
52
|
+
else if (arg === '--no-stdout') {
|
|
53
|
+
opts.stdout = false;
|
|
54
|
+
}
|
|
33
55
|
}
|
|
34
56
|
return opts;
|
|
35
57
|
}
|
|
@@ -37,51 +59,117 @@ function main() {
|
|
|
37
59
|
const opts = parseArgs(process.argv.slice(2));
|
|
38
60
|
const parser = new terraformParser_1.TerraformParser();
|
|
39
61
|
if (!opts.file && !opts.dir) {
|
|
40
|
-
console.error('Usage: parse-hcl --file <path> | --dir <path> [--format json|yaml] [--graph] [--no-prune]');
|
|
62
|
+
console.error('Usage: parse-hcl --file <path> | --dir <path> [--format json|yaml] [--graph] [--no-prune] [--out <path>] [--out-dir <dir>] [--stdout]');
|
|
41
63
|
process.exit(1);
|
|
42
64
|
}
|
|
43
65
|
if (opts.file) {
|
|
44
66
|
const filePath = path_1.default.resolve(opts.file);
|
|
45
67
|
const ext = path_1.default.extname(filePath);
|
|
46
68
|
if (ext.includes('tfvars')) {
|
|
47
|
-
|
|
69
|
+
const data = tfvarsParse(filePath);
|
|
70
|
+
emitSingle(filePath, data, opts);
|
|
48
71
|
return;
|
|
49
72
|
}
|
|
50
73
|
if (ext === '.tfstate') {
|
|
51
|
-
|
|
74
|
+
const data = new artifactParsers_1.TfStateParser().parseFile(filePath);
|
|
75
|
+
emitSingle(filePath, data, opts);
|
|
52
76
|
return;
|
|
53
77
|
}
|
|
54
78
|
if (ext === '.json' && filePath.endsWith('plan.json')) {
|
|
55
|
-
|
|
79
|
+
const data = new artifactParsers_1.TfPlanParser().parseFile(filePath);
|
|
80
|
+
emitSingle(filePath, data, opts);
|
|
56
81
|
return;
|
|
57
82
|
}
|
|
58
83
|
const doc = parser.parseFile(filePath);
|
|
59
|
-
|
|
84
|
+
emitSingle(filePath, doc, opts);
|
|
60
85
|
return;
|
|
61
86
|
}
|
|
62
87
|
if (opts.dir) {
|
|
63
88
|
const dirPath = path_1.default.resolve(opts.dir);
|
|
64
89
|
const result = parser.parseDirectory(dirPath);
|
|
65
|
-
const
|
|
66
|
-
|
|
90
|
+
const combinedDoc = result.combined ?? parser.combine(result.files.map((f) => f.document));
|
|
91
|
+
emitDirectory(dirPath, result.files, combinedDoc, opts);
|
|
67
92
|
}
|
|
68
93
|
}
|
|
69
94
|
function tfvarsParse(filePath) {
|
|
70
95
|
return new artifactParsers_1.TfVarsParser().parseFile(filePath);
|
|
71
96
|
}
|
|
72
|
-
function
|
|
97
|
+
function emitSingle(filePath, data, opts) {
|
|
98
|
+
const rendered = render(data, opts);
|
|
99
|
+
const ext = getExt(opts.format);
|
|
100
|
+
const defaultName = `${DEFAULT_SINGLE_BASENAME}${ext}`;
|
|
101
|
+
const targetPath = resolveOutPath(opts.out, defaultName, opts.format);
|
|
102
|
+
writeFile(targetPath, rendered);
|
|
103
|
+
if (opts.stdout) {
|
|
104
|
+
console.info(rendered);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function emitDirectory(dirPath, files, combinedDoc, opts) {
|
|
108
|
+
const ext = getExt(opts.format);
|
|
109
|
+
const combinedData = opts.graph ? combinedDoc : { combined: combinedDoc, files: opts.split ? files : [] };
|
|
110
|
+
const combinedRendered = render(combinedData, opts);
|
|
111
|
+
const combinedDefaultName = `${DEFAULT_COMBINED_BASENAME}${ext}`;
|
|
112
|
+
const combinedTarget = resolveOutPath(opts.out, combinedDefaultName, opts.format, true);
|
|
113
|
+
writeFile(combinedTarget, combinedRendered);
|
|
114
|
+
if (opts.split) {
|
|
115
|
+
const baseDir = resolvePerFileBase(opts);
|
|
116
|
+
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}`);
|
|
119
|
+
const rendered = render(file.document, opts);
|
|
120
|
+
writeFile(perFileTarget, rendered);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (opts.stdout) {
|
|
124
|
+
console.info(combinedRendered);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function render(data, opts) {
|
|
73
128
|
if (opts.graph && !isTerraformDoc(data)) {
|
|
74
129
|
console.warn('Graph export requested but input is not a Terraform document; emitting raw output.');
|
|
75
130
|
}
|
|
76
131
|
if (opts.format === 'yaml') {
|
|
77
|
-
|
|
78
|
-
return;
|
|
132
|
+
return (0, serializer_1.toYamlDocument)(data, { pruneEmpty: opts.prune });
|
|
79
133
|
}
|
|
80
134
|
if (opts.graph && isTerraformDoc(data)) {
|
|
81
|
-
|
|
82
|
-
|
|
135
|
+
return (0, serializer_1.toJsonExport)(data, { pruneEmpty: opts.prune });
|
|
136
|
+
}
|
|
137
|
+
return (0, serializer_1.toJson)(data, { pruneEmpty: opts.prune });
|
|
138
|
+
}
|
|
139
|
+
function resolveOutPath(out, defaultName, format, isDirMode = false) {
|
|
140
|
+
const resolvedDefault = path_1.default.resolve(defaultName);
|
|
141
|
+
if (!out) {
|
|
142
|
+
return resolvedDefault;
|
|
83
143
|
}
|
|
84
|
-
|
|
144
|
+
const resolvedOut = path_1.default.resolve(out);
|
|
145
|
+
if (fs_1.default.existsSync(resolvedOut) && fs_1.default.statSync(resolvedOut).isDirectory()) {
|
|
146
|
+
const name = isDirMode ? `combined${getExt(format)}` : `output${getExt(format)}`;
|
|
147
|
+
return path_1.default.join(resolvedOut, name);
|
|
148
|
+
}
|
|
149
|
+
if (!path_1.default.extname(resolvedOut)) {
|
|
150
|
+
return `${resolvedOut}${getExt(format)}`;
|
|
151
|
+
}
|
|
152
|
+
return resolvedOut;
|
|
153
|
+
}
|
|
154
|
+
function resolvePerFileBase(opts) {
|
|
155
|
+
if (opts.outDir) {
|
|
156
|
+
return path_1.default.resolve(opts.outDir);
|
|
157
|
+
}
|
|
158
|
+
if (opts.out) {
|
|
159
|
+
const resolvedOut = path_1.default.resolve(opts.out);
|
|
160
|
+
if (fs_1.default.existsSync(resolvedOut) && fs_1.default.statSync(resolvedOut).isDirectory()) {
|
|
161
|
+
return path_1.default.join(resolvedOut, 'per-file');
|
|
162
|
+
}
|
|
163
|
+
return path_1.default.join(path_1.default.dirname(resolvedOut), 'per-file');
|
|
164
|
+
}
|
|
165
|
+
return path_1.default.resolve(DEFAULT_PER_FILE_DIR);
|
|
166
|
+
}
|
|
167
|
+
function writeFile(targetPath, contents) {
|
|
168
|
+
fs_1.default.mkdirSync(path_1.default.dirname(targetPath), { recursive: true });
|
|
169
|
+
fs_1.default.writeFileSync(targetPath, contents, 'utf-8');
|
|
170
|
+
}
|
|
171
|
+
function getExt(format) {
|
|
172
|
+
return format === 'yaml' ? '.yaml' : '.json';
|
|
85
173
|
}
|
|
86
174
|
function isTerraformDoc(data) {
|
|
87
175
|
return Boolean(data && typeof data === 'object' && 'resource' in data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "parse-hcl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Lightweight HCL parser focused on identifying and classifying blocks. Supports common Terraform blocks (resource, variable, output, locals, etc.) for tooling and automation.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|