parse-hcl 0.1.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/LICENSE +201 -0
- package/README.md +749 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +91 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +74 -0
- package/dist/parsers/genericParser.d.ts +167 -0
- package/dist/parsers/genericParser.js +268 -0
- package/dist/parsers/localsParser.d.ts +30 -0
- package/dist/parsers/localsParser.js +43 -0
- package/dist/parsers/outputParser.d.ts +25 -0
- package/dist/parsers/outputParser.js +44 -0
- package/dist/parsers/variableParser.d.ts +62 -0
- package/dist/parsers/variableParser.js +249 -0
- package/dist/services/artifactParsers.d.ts +12 -0
- package/dist/services/artifactParsers.js +157 -0
- package/dist/services/terraformJsonParser.d.ts +16 -0
- package/dist/services/terraformJsonParser.js +212 -0
- package/dist/services/terraformParser.d.ts +91 -0
- package/dist/services/terraformParser.js +191 -0
- package/dist/types/artifacts.d.ts +210 -0
- package/dist/types/artifacts.js +5 -0
- package/dist/types/blocks.d.ts +419 -0
- package/dist/types/blocks.js +28 -0
- package/dist/utils/common/errors.d.ts +46 -0
- package/dist/utils/common/errors.js +54 -0
- package/dist/utils/common/fs.d.ts +5 -0
- package/dist/utils/common/fs.js +48 -0
- package/dist/utils/common/logger.d.ts +5 -0
- package/dist/utils/common/logger.js +17 -0
- package/dist/utils/common/valueHelpers.d.ts +4 -0
- package/dist/utils/common/valueHelpers.js +23 -0
- package/dist/utils/graph/graphBuilder.d.ts +33 -0
- package/dist/utils/graph/graphBuilder.js +373 -0
- package/dist/utils/lexer/blockScanner.d.ts +36 -0
- package/dist/utils/lexer/blockScanner.js +143 -0
- package/dist/utils/lexer/hclLexer.d.ts +119 -0
- package/dist/utils/lexer/hclLexer.js +525 -0
- package/dist/utils/parser/bodyParser.d.ts +26 -0
- package/dist/utils/parser/bodyParser.js +81 -0
- package/dist/utils/parser/valueClassifier.d.ts +21 -0
- package/dist/utils/parser/valueClassifier.js +434 -0
- package/dist/utils/serialization/serializer.d.ts +9 -0
- package/dist/utils/serialization/serializer.js +63 -0
- package/dist/utils/serialization/yaml.d.ts +1 -0
- package/dist/utils/serialization/yaml.js +81 -0
- package/package.json +66 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TerraformJsonParser = void 0;
|
|
4
|
+
exports.convertJsonValue = convertJsonValue;
|
|
5
|
+
const blocks_1 = require("../types/blocks");
|
|
6
|
+
const fs_1 = require("../utils/common/fs");
|
|
7
|
+
const valueClassifier_1 = require("../utils/parser/valueClassifier");
|
|
8
|
+
class TerraformJsonParser {
|
|
9
|
+
parseFile(filePath) {
|
|
10
|
+
const json = (0, fs_1.readJsonFile)(filePath);
|
|
11
|
+
return this.parse(json, filePath);
|
|
12
|
+
}
|
|
13
|
+
parse(json, source = 'json-config') {
|
|
14
|
+
const doc = (0, blocks_1.createEmptyDocument)();
|
|
15
|
+
this.parseTerraform(json.terraform, doc, source);
|
|
16
|
+
this.parseProviders(json.provider, doc, source);
|
|
17
|
+
this.parseVariables(json.variable, doc, source);
|
|
18
|
+
this.parseOutputs(json.output, doc, source);
|
|
19
|
+
this.parseLocals(json.locals, doc, source);
|
|
20
|
+
this.parseModules(json.module, doc, source);
|
|
21
|
+
this.parseResources(json.resource, doc, source);
|
|
22
|
+
this.parseData(json.data, doc, source);
|
|
23
|
+
return doc;
|
|
24
|
+
}
|
|
25
|
+
parseTerraform(value, doc, source) {
|
|
26
|
+
if (!Array.isArray(value))
|
|
27
|
+
return;
|
|
28
|
+
for (const item of value) {
|
|
29
|
+
if (item && typeof item === 'object') {
|
|
30
|
+
doc.terraform.push({
|
|
31
|
+
properties: convertAttributes(item),
|
|
32
|
+
raw: JSON.stringify(item),
|
|
33
|
+
source
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
parseProviders(value, doc, source) {
|
|
39
|
+
if (!value || typeof value !== 'object')
|
|
40
|
+
return;
|
|
41
|
+
for (const [name, config] of Object.entries(value)) {
|
|
42
|
+
const configs = Array.isArray(config) ? config : [config];
|
|
43
|
+
for (const aliasCfg of configs) {
|
|
44
|
+
if (!isRecord(aliasCfg))
|
|
45
|
+
continue;
|
|
46
|
+
const alias = typeof aliasCfg.alias === 'string' ? aliasCfg.alias : undefined;
|
|
47
|
+
doc.provider.push({
|
|
48
|
+
name,
|
|
49
|
+
alias,
|
|
50
|
+
properties: convertAttributes(aliasCfg),
|
|
51
|
+
raw: JSON.stringify(aliasCfg),
|
|
52
|
+
source
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
parseVariables(value, doc, source) {
|
|
58
|
+
if (!value || typeof value !== 'object')
|
|
59
|
+
return;
|
|
60
|
+
for (const [name, config] of Object.entries(value)) {
|
|
61
|
+
const cfg = config || {};
|
|
62
|
+
doc.variable.push({
|
|
63
|
+
name,
|
|
64
|
+
description: typeof cfg.description === 'string' ? cfg.description : undefined,
|
|
65
|
+
type: typeof cfg.type === 'string' ? cfg.type : undefined,
|
|
66
|
+
default: cfg.default !== undefined ? convertJsonValue(cfg.default) : undefined,
|
|
67
|
+
validation: undefined,
|
|
68
|
+
sensitive: typeof cfg.sensitive === 'boolean' ? cfg.sensitive : undefined,
|
|
69
|
+
raw: JSON.stringify(cfg),
|
|
70
|
+
source
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
parseOutputs(value, doc, source) {
|
|
75
|
+
if (!value || typeof value !== 'object')
|
|
76
|
+
return;
|
|
77
|
+
for (const [name, config] of Object.entries(value)) {
|
|
78
|
+
const cfg = config || {};
|
|
79
|
+
doc.output.push({
|
|
80
|
+
name,
|
|
81
|
+
description: typeof cfg.description === 'string' ? cfg.description : undefined,
|
|
82
|
+
value: convertJsonValue(cfg.value),
|
|
83
|
+
sensitive: typeof cfg.sensitive === 'boolean' ? cfg.sensitive : undefined,
|
|
84
|
+
raw: JSON.stringify(cfg),
|
|
85
|
+
source
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
parseLocals(value, doc, source) {
|
|
90
|
+
if (!value || typeof value !== 'object')
|
|
91
|
+
return;
|
|
92
|
+
for (const [name, val] of Object.entries(value)) {
|
|
93
|
+
const converted = convertJsonValue(val);
|
|
94
|
+
doc.locals.push({
|
|
95
|
+
name,
|
|
96
|
+
type: converted.type,
|
|
97
|
+
value: converted,
|
|
98
|
+
raw: converted.raw,
|
|
99
|
+
source
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
parseModules(value, doc, source) {
|
|
104
|
+
if (!value || typeof value !== 'object')
|
|
105
|
+
return;
|
|
106
|
+
for (const [name, config] of Object.entries(value)) {
|
|
107
|
+
const cfg = config || {};
|
|
108
|
+
doc.module.push({
|
|
109
|
+
name,
|
|
110
|
+
properties: convertAttributes(cfg),
|
|
111
|
+
raw: JSON.stringify(cfg),
|
|
112
|
+
source
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
parseResources(value, doc, source) {
|
|
117
|
+
if (!value || typeof value !== 'object')
|
|
118
|
+
return;
|
|
119
|
+
for (const [type, resourceByName] of Object.entries(value)) {
|
|
120
|
+
if (!resourceByName || typeof resourceByName !== 'object')
|
|
121
|
+
continue;
|
|
122
|
+
for (const [name, configList] of Object.entries(resourceByName)) {
|
|
123
|
+
const items = Array.isArray(configList) ? configList : [configList];
|
|
124
|
+
for (const cfg of items) {
|
|
125
|
+
if (!cfg || typeof cfg !== 'object')
|
|
126
|
+
continue;
|
|
127
|
+
const parsed = convertAttributes(cfg);
|
|
128
|
+
doc.resource.push({
|
|
129
|
+
type,
|
|
130
|
+
name,
|
|
131
|
+
properties: parsed,
|
|
132
|
+
blocks: [],
|
|
133
|
+
dynamic_blocks: [],
|
|
134
|
+
meta: {},
|
|
135
|
+
raw: JSON.stringify(cfg),
|
|
136
|
+
source
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
parseData(value, doc, source) {
|
|
143
|
+
if (!value || typeof value !== 'object')
|
|
144
|
+
return;
|
|
145
|
+
for (const [dataType, dataByName] of Object.entries(value)) {
|
|
146
|
+
if (!dataByName || typeof dataByName !== 'object')
|
|
147
|
+
continue;
|
|
148
|
+
for (const [name, configList] of Object.entries(dataByName)) {
|
|
149
|
+
const items = Array.isArray(configList) ? configList : [configList];
|
|
150
|
+
for (const cfg of items) {
|
|
151
|
+
if (!cfg || typeof cfg !== 'object')
|
|
152
|
+
continue;
|
|
153
|
+
const parsed = convertAttributes(cfg);
|
|
154
|
+
doc.data.push({
|
|
155
|
+
dataType,
|
|
156
|
+
name,
|
|
157
|
+
properties: parsed,
|
|
158
|
+
blocks: [],
|
|
159
|
+
raw: JSON.stringify(cfg),
|
|
160
|
+
source
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.TerraformJsonParser = TerraformJsonParser;
|
|
168
|
+
function convertAttributes(obj) {
|
|
169
|
+
const out = {};
|
|
170
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
171
|
+
if (key === 'alias')
|
|
172
|
+
continue;
|
|
173
|
+
out[key] = convertJsonValue(val);
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
function convertJsonValue(input) {
|
|
178
|
+
if (input === null) {
|
|
179
|
+
return { type: 'literal', value: null, raw: 'null' };
|
|
180
|
+
}
|
|
181
|
+
if (typeof input === 'string') {
|
|
182
|
+
if (looksLikeExpression(input)) {
|
|
183
|
+
return (0, valueClassifier_1.classifyValue)(input);
|
|
184
|
+
}
|
|
185
|
+
return { type: 'literal', value: input, raw: input };
|
|
186
|
+
}
|
|
187
|
+
if (typeof input === 'number' || typeof input === 'boolean') {
|
|
188
|
+
return { type: 'literal', value: input, raw: String(input) };
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(input)) {
|
|
191
|
+
return {
|
|
192
|
+
type: 'array',
|
|
193
|
+
value: input.map((item) => convertJsonValue(item)),
|
|
194
|
+
raw: JSON.stringify(input)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (typeof input === 'object') {
|
|
198
|
+
const entries = Object.entries(input);
|
|
199
|
+
const value = {};
|
|
200
|
+
for (const [key, val] of entries) {
|
|
201
|
+
value[key] = convertJsonValue(val);
|
|
202
|
+
}
|
|
203
|
+
return { type: 'object', value, raw: JSON.stringify(input) };
|
|
204
|
+
}
|
|
205
|
+
return { type: 'literal', value: String(input), raw: String(input) };
|
|
206
|
+
}
|
|
207
|
+
function looksLikeExpression(value) {
|
|
208
|
+
return value.includes('${') || /^[\w.]+\(/.test(value) || /^[\w.]+$/.test(value);
|
|
209
|
+
}
|
|
210
|
+
function isRecord(value) {
|
|
211
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
212
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Terraform configuration parser.
|
|
3
|
+
* Parses .tf and .tf.json files into structured TerraformDocument objects.
|
|
4
|
+
*/
|
|
5
|
+
import { DirectoryParseOptions, DirectoryParseResult, TerraformDocument } from '../types/blocks';
|
|
6
|
+
/**
|
|
7
|
+
* Main parser for Terraform configuration files.
|
|
8
|
+
* Supports both HCL (.tf) and JSON (.tf.json) formats.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const parser = new TerraformParser();
|
|
13
|
+
*
|
|
14
|
+
* // Parse a single file
|
|
15
|
+
* const doc = parser.parseFile('main.tf');
|
|
16
|
+
* console.log(`Found ${doc.resource.length} resources`);
|
|
17
|
+
*
|
|
18
|
+
* // Parse a directory
|
|
19
|
+
* const result = parser.parseDirectory('./terraform');
|
|
20
|
+
* console.log(`Parsed ${result.files.length} files`);
|
|
21
|
+
*
|
|
22
|
+
* // Access parsed elements
|
|
23
|
+
* for (const resource of doc.resource) {
|
|
24
|
+
* console.log(`${resource.type}.${resource.name}`);
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class TerraformParser {
|
|
29
|
+
private readonly scanner;
|
|
30
|
+
private readonly variableParser;
|
|
31
|
+
private readonly outputParser;
|
|
32
|
+
private readonly localsParser;
|
|
33
|
+
private readonly moduleParser;
|
|
34
|
+
private readonly providerParser;
|
|
35
|
+
private readonly resourceParser;
|
|
36
|
+
private readonly dataParser;
|
|
37
|
+
private readonly terraformSettingsParser;
|
|
38
|
+
private readonly genericBlockParser;
|
|
39
|
+
private readonly jsonParser;
|
|
40
|
+
/**
|
|
41
|
+
* Parses a Terraform configuration file.
|
|
42
|
+
* Automatically detects format (.tf vs .tf.json) and uses appropriate parser.
|
|
43
|
+
*
|
|
44
|
+
* @param filePath - Path to the Terraform file
|
|
45
|
+
* @returns Parsed TerraformDocument containing all blocks
|
|
46
|
+
* @throws {Error} If file cannot be read or parsed
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const doc = parser.parseFile('main.tf');
|
|
51
|
+
* console.log(doc.variable[0].name);
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
parseFile(filePath: string): TerraformDocument;
|
|
55
|
+
/**
|
|
56
|
+
* Parses all Terraform files in a directory (recursively).
|
|
57
|
+
*
|
|
58
|
+
* @param dirPath - Path to the directory
|
|
59
|
+
* @param options - Parsing options
|
|
60
|
+
* @returns DirectoryParseResult with combined document and per-file results
|
|
61
|
+
* @throws {Error} If directory does not exist or is not accessible
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Parse with defaults (aggregate + per-file results)
|
|
66
|
+
* const result = parser.parseDirectory('./terraform');
|
|
67
|
+
*
|
|
68
|
+
* // Parse without aggregation
|
|
69
|
+
* const result = parser.parseDirectory('./terraform', { aggregate: false });
|
|
70
|
+
*
|
|
71
|
+
* // Parse without per-file results
|
|
72
|
+
* const result = parser.parseDirectory('./terraform', { includePerFile: false });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
parseDirectory(dirPath: string, options?: DirectoryParseOptions): DirectoryParseResult;
|
|
76
|
+
/**
|
|
77
|
+
* Combines multiple TerraformDocuments into a single document.
|
|
78
|
+
* Useful for aggregating configurations from multiple files.
|
|
79
|
+
*
|
|
80
|
+
* @param documents - Array of documents to combine
|
|
81
|
+
* @returns A single TerraformDocument containing all blocks
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const doc1 = parser.parseFile('main.tf');
|
|
86
|
+
* const doc2 = parser.parseFile('variables.tf');
|
|
87
|
+
* const combined = parser.combine([doc1, doc2]);
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
combine(documents: TerraformDocument[]): TerraformDocument;
|
|
91
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Main Terraform configuration parser.
|
|
4
|
+
* Parses .tf and .tf.json files into structured TerraformDocument objects.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.TerraformParser = void 0;
|
|
8
|
+
const blocks_1 = require("../types/blocks");
|
|
9
|
+
const blockScanner_1 = require("../utils/lexer/blockScanner");
|
|
10
|
+
const fs_1 = require("../utils/common/fs");
|
|
11
|
+
const logger_1 = require("../utils/common/logger");
|
|
12
|
+
const localsParser_1 = require("../parsers/localsParser");
|
|
13
|
+
const genericParser_1 = require("../parsers/genericParser");
|
|
14
|
+
const outputParser_1 = require("../parsers/outputParser");
|
|
15
|
+
const variableParser_1 = require("../parsers/variableParser");
|
|
16
|
+
const terraformJsonParser_1 = require("./terraformJsonParser");
|
|
17
|
+
/**
|
|
18
|
+
* Main parser for Terraform configuration files.
|
|
19
|
+
* Supports both HCL (.tf) and JSON (.tf.json) formats.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const parser = new TerraformParser();
|
|
24
|
+
*
|
|
25
|
+
* // Parse a single file
|
|
26
|
+
* const doc = parser.parseFile('main.tf');
|
|
27
|
+
* console.log(`Found ${doc.resource.length} resources`);
|
|
28
|
+
*
|
|
29
|
+
* // Parse a directory
|
|
30
|
+
* const result = parser.parseDirectory('./terraform');
|
|
31
|
+
* console.log(`Parsed ${result.files.length} files`);
|
|
32
|
+
*
|
|
33
|
+
* // Access parsed elements
|
|
34
|
+
* for (const resource of doc.resource) {
|
|
35
|
+
* console.log(`${resource.type}.${resource.name}`);
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
class TerraformParser {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.scanner = new blockScanner_1.BlockScanner();
|
|
42
|
+
this.variableParser = new variableParser_1.VariableParser();
|
|
43
|
+
this.outputParser = new outputParser_1.OutputParser();
|
|
44
|
+
this.localsParser = new localsParser_1.LocalsParser();
|
|
45
|
+
this.moduleParser = new genericParser_1.ModuleParser();
|
|
46
|
+
this.providerParser = new genericParser_1.ProviderParser();
|
|
47
|
+
this.resourceParser = new genericParser_1.ResourceParser();
|
|
48
|
+
this.dataParser = new genericParser_1.DataParser();
|
|
49
|
+
this.terraformSettingsParser = new genericParser_1.TerraformSettingsParser();
|
|
50
|
+
this.genericBlockParser = new genericParser_1.GenericBlockParser();
|
|
51
|
+
this.jsonParser = new terraformJsonParser_1.TerraformJsonParser();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parses a Terraform configuration file.
|
|
55
|
+
* Automatically detects format (.tf vs .tf.json) and uses appropriate parser.
|
|
56
|
+
*
|
|
57
|
+
* @param filePath - Path to the Terraform file
|
|
58
|
+
* @returns Parsed TerraformDocument containing all blocks
|
|
59
|
+
* @throws {Error} If file cannot be read or parsed
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const doc = parser.parseFile('main.tf');
|
|
64
|
+
* console.log(doc.variable[0].name);
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
parseFile(filePath) {
|
|
68
|
+
if (filePath.endsWith('.tf.json')) {
|
|
69
|
+
logger_1.logger.info(`Parsing Terraform JSON file: ${filePath}`);
|
|
70
|
+
return this.jsonParser.parseFile(filePath);
|
|
71
|
+
}
|
|
72
|
+
logger_1.logger.info(`Parsing Terraform file: ${filePath}`);
|
|
73
|
+
const content = (0, fs_1.readTextFile)(filePath);
|
|
74
|
+
const blocks = this.scanner.scan(content, filePath);
|
|
75
|
+
const document = (0, blocks_1.createEmptyDocument)();
|
|
76
|
+
for (const block of blocks) {
|
|
77
|
+
switch (block.kind) {
|
|
78
|
+
case 'variable':
|
|
79
|
+
document.variable.push(this.variableParser.parse(block));
|
|
80
|
+
break;
|
|
81
|
+
case 'output':
|
|
82
|
+
document.output.push(this.outputParser.parse(block));
|
|
83
|
+
break;
|
|
84
|
+
case 'locals':
|
|
85
|
+
document.locals.push(...this.localsParser.parse(block));
|
|
86
|
+
break;
|
|
87
|
+
case 'module':
|
|
88
|
+
document.module.push(this.moduleParser.parse(block));
|
|
89
|
+
break;
|
|
90
|
+
case 'provider':
|
|
91
|
+
document.provider.push(this.providerParser.parse(block));
|
|
92
|
+
break;
|
|
93
|
+
case 'resource':
|
|
94
|
+
document.resource.push(this.resourceParser.parse(block));
|
|
95
|
+
break;
|
|
96
|
+
case 'data':
|
|
97
|
+
document.data.push(this.dataParser.parse(block));
|
|
98
|
+
break;
|
|
99
|
+
case 'terraform':
|
|
100
|
+
document.terraform.push(this.terraformSettingsParser.parse(block));
|
|
101
|
+
break;
|
|
102
|
+
case 'moved':
|
|
103
|
+
document.moved.push(this.genericBlockParser.parse(block));
|
|
104
|
+
break;
|
|
105
|
+
case 'import':
|
|
106
|
+
document.import.push(this.genericBlockParser.parse(block));
|
|
107
|
+
break;
|
|
108
|
+
case 'check':
|
|
109
|
+
document.check.push(this.genericBlockParser.parse(block));
|
|
110
|
+
break;
|
|
111
|
+
case 'terraform_data':
|
|
112
|
+
document.terraform_data.push(this.genericBlockParser.parse(block));
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
document.unknown.push(this.genericBlockParser.parse(block));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return document;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Parses all Terraform files in a directory (recursively).
|
|
122
|
+
*
|
|
123
|
+
* @param dirPath - Path to the directory
|
|
124
|
+
* @param options - Parsing options
|
|
125
|
+
* @returns DirectoryParseResult with combined document and per-file results
|
|
126
|
+
* @throws {Error} If directory does not exist or is not accessible
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // Parse with defaults (aggregate + per-file results)
|
|
131
|
+
* const result = parser.parseDirectory('./terraform');
|
|
132
|
+
*
|
|
133
|
+
* // Parse without aggregation
|
|
134
|
+
* const result = parser.parseDirectory('./terraform', { aggregate: false });
|
|
135
|
+
*
|
|
136
|
+
* // Parse without per-file results
|
|
137
|
+
* const result = parser.parseDirectory('./terraform', { includePerFile: false });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
parseDirectory(dirPath, options) {
|
|
141
|
+
if (!(0, fs_1.pathExists)(dirPath) || !(0, fs_1.isDirectory)(dirPath)) {
|
|
142
|
+
throw new Error(`Invalid directory path: ${dirPath}`);
|
|
143
|
+
}
|
|
144
|
+
const aggregate = options?.aggregate !== false;
|
|
145
|
+
const includePerFile = options?.includePerFile !== false;
|
|
146
|
+
const files = (0, fs_1.listTerraformFiles)(dirPath);
|
|
147
|
+
const parsedFiles = files.map((filePath) => ({
|
|
148
|
+
path: filePath,
|
|
149
|
+
document: this.parseFile(filePath)
|
|
150
|
+
}));
|
|
151
|
+
const combined = aggregate ? this.combine(parsedFiles.map((item) => item.document)) : undefined;
|
|
152
|
+
return {
|
|
153
|
+
combined,
|
|
154
|
+
files: includePerFile ? parsedFiles : []
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Combines multiple TerraformDocuments into a single document.
|
|
159
|
+
* Useful for aggregating configurations from multiple files.
|
|
160
|
+
*
|
|
161
|
+
* @param documents - Array of documents to combine
|
|
162
|
+
* @returns A single TerraformDocument containing all blocks
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const doc1 = parser.parseFile('main.tf');
|
|
167
|
+
* const doc2 = parser.parseFile('variables.tf');
|
|
168
|
+
* const combined = parser.combine([doc1, doc2]);
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
combine(documents) {
|
|
172
|
+
const combined = (0, blocks_1.createEmptyDocument)();
|
|
173
|
+
for (const doc of documents) {
|
|
174
|
+
combined.terraform.push(...doc.terraform);
|
|
175
|
+
combined.provider.push(...doc.provider);
|
|
176
|
+
combined.variable.push(...doc.variable);
|
|
177
|
+
combined.output.push(...doc.output);
|
|
178
|
+
combined.module.push(...doc.module);
|
|
179
|
+
combined.resource.push(...doc.resource);
|
|
180
|
+
combined.data.push(...doc.data);
|
|
181
|
+
combined.locals.push(...doc.locals);
|
|
182
|
+
combined.moved.push(...doc.moved);
|
|
183
|
+
combined.import.push(...doc.import);
|
|
184
|
+
combined.check.push(...doc.check);
|
|
185
|
+
combined.terraform_data.push(...doc.terraform_data);
|
|
186
|
+
combined.unknown.push(...doc.unknown);
|
|
187
|
+
}
|
|
188
|
+
return combined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
exports.TerraformParser = TerraformParser;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Terraform artifacts (state files, plans, tfvars) and dependency graphs.
|
|
3
|
+
*/
|
|
4
|
+
import { BlockKind, Reference, TerraformDocument, Value } from './blocks';
|
|
5
|
+
/**
|
|
6
|
+
* Types of nodes in the dependency graph.
|
|
7
|
+
* Extends BlockKind with special node types for references.
|
|
8
|
+
*/
|
|
9
|
+
export type GraphNodeKind = BlockKind | 'module_output' | 'path' | 'each' | 'count' | 'self' | 'external';
|
|
10
|
+
/**
|
|
11
|
+
* A node in the dependency graph representing a Terraform element.
|
|
12
|
+
*/
|
|
13
|
+
export interface GraphNode {
|
|
14
|
+
/** Unique identifier for the node */
|
|
15
|
+
id: string;
|
|
16
|
+
/** The kind/type of the node */
|
|
17
|
+
kind: GraphNodeKind;
|
|
18
|
+
/** Display name for the node */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Resource/data type (for resource and data nodes) */
|
|
21
|
+
type?: string;
|
|
22
|
+
/** Source file path (if known) */
|
|
23
|
+
source?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* An edge in the dependency graph representing a reference.
|
|
27
|
+
*/
|
|
28
|
+
export interface GraphEdge {
|
|
29
|
+
/** The source node ID */
|
|
30
|
+
from: string;
|
|
31
|
+
/** The target node ID */
|
|
32
|
+
to: string;
|
|
33
|
+
/** The reference that created this edge */
|
|
34
|
+
reference: Reference;
|
|
35
|
+
/** Source file path where the reference was found */
|
|
36
|
+
source?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Complete dependency graph with nodes, edges, and orphan references.
|
|
40
|
+
*/
|
|
41
|
+
export interface DependencyGraph {
|
|
42
|
+
/** All nodes in the graph */
|
|
43
|
+
nodes: GraphNode[];
|
|
44
|
+
/** All edges (dependencies) in the graph */
|
|
45
|
+
edges: GraphEdge[];
|
|
46
|
+
/** References that could not be resolved to nodes */
|
|
47
|
+
orphanReferences: Reference[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Export format containing document, graph, and metadata.
|
|
51
|
+
*/
|
|
52
|
+
export interface TerraformExport {
|
|
53
|
+
/** Export format version */
|
|
54
|
+
version: string;
|
|
55
|
+
/** The parsed document (may be partial for filtered exports) */
|
|
56
|
+
document: Partial<TerraformDocument>;
|
|
57
|
+
/** The dependency graph */
|
|
58
|
+
graph: DependencyGraph;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parsed tfvars document containing variable assignments.
|
|
62
|
+
*/
|
|
63
|
+
export interface TfVarsDocument {
|
|
64
|
+
/** Source file path */
|
|
65
|
+
source: string;
|
|
66
|
+
/** Raw file content */
|
|
67
|
+
raw: string;
|
|
68
|
+
/** Variable assignments (name -> value) */
|
|
69
|
+
assignments: Record<string, Value>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* An output value from Terraform state.
|
|
73
|
+
*/
|
|
74
|
+
export interface TerraformStateOutput {
|
|
75
|
+
/** The output value */
|
|
76
|
+
value: unknown;
|
|
77
|
+
/** The output type */
|
|
78
|
+
type?: string | string[];
|
|
79
|
+
/** Whether the output is sensitive */
|
|
80
|
+
sensitive?: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A resource instance in Terraform state.
|
|
84
|
+
*/
|
|
85
|
+
export interface TerraformStateInstance {
|
|
86
|
+
/** Index key (for count/for_each resources) */
|
|
87
|
+
index_key?: string | number;
|
|
88
|
+
/** Resource attributes */
|
|
89
|
+
attributes?: Record<string, unknown>;
|
|
90
|
+
/** Instance status */
|
|
91
|
+
status?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A resource in Terraform state.
|
|
95
|
+
*/
|
|
96
|
+
export interface TerraformStateResource {
|
|
97
|
+
/** Module address (for resources in modules) */
|
|
98
|
+
module?: string;
|
|
99
|
+
/** Resource mode (managed or data) */
|
|
100
|
+
mode: 'managed' | 'data';
|
|
101
|
+
/** Resource type */
|
|
102
|
+
type: string;
|
|
103
|
+
/** Resource name */
|
|
104
|
+
name: string;
|
|
105
|
+
/** Provider configuration */
|
|
106
|
+
provider?: string;
|
|
107
|
+
/** Resource instances */
|
|
108
|
+
instances: TerraformStateInstance[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parsed Terraform state document.
|
|
112
|
+
*/
|
|
113
|
+
export interface TerraformStateDocument {
|
|
114
|
+
/** State format version */
|
|
115
|
+
version: number;
|
|
116
|
+
/** Terraform version that created the state */
|
|
117
|
+
terraform_version?: string;
|
|
118
|
+
/** State serial number */
|
|
119
|
+
serial?: number;
|
|
120
|
+
/** State lineage (unique identifier) */
|
|
121
|
+
lineage?: string;
|
|
122
|
+
/** Output values */
|
|
123
|
+
outputs: Record<string, TerraformStateOutput>;
|
|
124
|
+
/** Resources in state */
|
|
125
|
+
resources: TerraformStateResource[];
|
|
126
|
+
/** Raw state data */
|
|
127
|
+
raw: unknown;
|
|
128
|
+
/** Source file path */
|
|
129
|
+
source: string;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A resource change in a Terraform plan.
|
|
133
|
+
*/
|
|
134
|
+
export interface PlanResourceChange {
|
|
135
|
+
/** Resource address */
|
|
136
|
+
address: string;
|
|
137
|
+
/** Module address (if in a module) */
|
|
138
|
+
module_address?: string;
|
|
139
|
+
/** Resource mode */
|
|
140
|
+
mode: 'managed' | 'data';
|
|
141
|
+
/** Resource type */
|
|
142
|
+
type: string;
|
|
143
|
+
/** Resource name */
|
|
144
|
+
name: string;
|
|
145
|
+
/** Provider name */
|
|
146
|
+
provider_name?: string;
|
|
147
|
+
/** Change details */
|
|
148
|
+
change: {
|
|
149
|
+
/** Actions to perform (create, update, delete, etc.) */
|
|
150
|
+
actions: string[];
|
|
151
|
+
/** State before change */
|
|
152
|
+
before?: unknown;
|
|
153
|
+
/** State after change */
|
|
154
|
+
after?: unknown;
|
|
155
|
+
/** Unknown values after change */
|
|
156
|
+
after_unknown?: Record<string, unknown>;
|
|
157
|
+
/** Sensitive values before change */
|
|
158
|
+
before_sensitive?: Record<string, unknown>;
|
|
159
|
+
/** Sensitive values after change */
|
|
160
|
+
after_sensitive?: Record<string, unknown>;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A resource in planned values.
|
|
165
|
+
*/
|
|
166
|
+
export interface PlanResource {
|
|
167
|
+
/** Resource address */
|
|
168
|
+
address: string;
|
|
169
|
+
/** Resource mode */
|
|
170
|
+
mode: 'managed' | 'data';
|
|
171
|
+
/** Resource type */
|
|
172
|
+
type: string;
|
|
173
|
+
/** Resource name */
|
|
174
|
+
name: string;
|
|
175
|
+
/** Provider name */
|
|
176
|
+
provider_name?: string;
|
|
177
|
+
/** Resource values */
|
|
178
|
+
values?: Record<string, unknown>;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* A module in planned values.
|
|
182
|
+
*/
|
|
183
|
+
export interface PlanModule {
|
|
184
|
+
/** Module address */
|
|
185
|
+
address?: string;
|
|
186
|
+
/** Resources in the module */
|
|
187
|
+
resources?: PlanResource[];
|
|
188
|
+
/** Child modules */
|
|
189
|
+
child_modules?: PlanModule[];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Parsed Terraform plan document.
|
|
193
|
+
*/
|
|
194
|
+
export interface TerraformPlanDocument {
|
|
195
|
+
/** Plan format version */
|
|
196
|
+
format_version?: string;
|
|
197
|
+
/** Terraform version */
|
|
198
|
+
terraform_version?: string;
|
|
199
|
+
/** Planned values */
|
|
200
|
+
planned_values?: {
|
|
201
|
+
/** Root module */
|
|
202
|
+
root_module?: PlanModule;
|
|
203
|
+
};
|
|
204
|
+
/** Resource changes */
|
|
205
|
+
resource_changes: PlanResourceChange[];
|
|
206
|
+
/** Raw plan data */
|
|
207
|
+
raw: unknown;
|
|
208
|
+
/** Source file path */
|
|
209
|
+
source: string;
|
|
210
|
+
}
|