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.
Files changed (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +749 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +91 -0
  5. package/dist/index.d.ts +51 -0
  6. package/dist/index.js +74 -0
  7. package/dist/parsers/genericParser.d.ts +167 -0
  8. package/dist/parsers/genericParser.js +268 -0
  9. package/dist/parsers/localsParser.d.ts +30 -0
  10. package/dist/parsers/localsParser.js +43 -0
  11. package/dist/parsers/outputParser.d.ts +25 -0
  12. package/dist/parsers/outputParser.js +44 -0
  13. package/dist/parsers/variableParser.d.ts +62 -0
  14. package/dist/parsers/variableParser.js +249 -0
  15. package/dist/services/artifactParsers.d.ts +12 -0
  16. package/dist/services/artifactParsers.js +157 -0
  17. package/dist/services/terraformJsonParser.d.ts +16 -0
  18. package/dist/services/terraformJsonParser.js +212 -0
  19. package/dist/services/terraformParser.d.ts +91 -0
  20. package/dist/services/terraformParser.js +191 -0
  21. package/dist/types/artifacts.d.ts +210 -0
  22. package/dist/types/artifacts.js +5 -0
  23. package/dist/types/blocks.d.ts +419 -0
  24. package/dist/types/blocks.js +28 -0
  25. package/dist/utils/common/errors.d.ts +46 -0
  26. package/dist/utils/common/errors.js +54 -0
  27. package/dist/utils/common/fs.d.ts +5 -0
  28. package/dist/utils/common/fs.js +48 -0
  29. package/dist/utils/common/logger.d.ts +5 -0
  30. package/dist/utils/common/logger.js +17 -0
  31. package/dist/utils/common/valueHelpers.d.ts +4 -0
  32. package/dist/utils/common/valueHelpers.js +23 -0
  33. package/dist/utils/graph/graphBuilder.d.ts +33 -0
  34. package/dist/utils/graph/graphBuilder.js +373 -0
  35. package/dist/utils/lexer/blockScanner.d.ts +36 -0
  36. package/dist/utils/lexer/blockScanner.js +143 -0
  37. package/dist/utils/lexer/hclLexer.d.ts +119 -0
  38. package/dist/utils/lexer/hclLexer.js +525 -0
  39. package/dist/utils/parser/bodyParser.d.ts +26 -0
  40. package/dist/utils/parser/bodyParser.js +81 -0
  41. package/dist/utils/parser/valueClassifier.d.ts +21 -0
  42. package/dist/utils/parser/valueClassifier.js +434 -0
  43. package/dist/utils/serialization/serializer.d.ts +9 -0
  44. package/dist/utils/serialization/serializer.js +63 -0
  45. package/dist/utils/serialization/yaml.d.ts +1 -0
  46. package/dist/utils/serialization/yaml.js +81 -0
  47. package/package.json +66 -0
@@ -0,0 +1,434 @@
1
+ "use strict";
2
+ /**
3
+ * Value classifier for HCL expressions.
4
+ * Classifies raw value strings into typed Value structures and extracts references.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.classifyValue = classifyValue;
8
+ const hclLexer_1 = require("../lexer/hclLexer");
9
+ /**
10
+ * Pattern for matching traversal expressions (e.g., aws_instance.web.id).
11
+ * Handles indexed access like resource[0].attr and splat expressions like resource[*].attr.
12
+ */
13
+ const TRAVERSAL_PATTERN = /[A-Za-z_][\w-]*(?:\[(?:[^[\]]*|\*)])?(?:\.[A-Za-z_][\w-]*(?:\[(?:[^[\]]*|\*)])?)+/g;
14
+ /**
15
+ * Pattern for matching splat expressions (e.g., aws_instance.web[*].id).
16
+ */
17
+ const SPLAT_PATTERN = /\[\*]/g;
18
+ /**
19
+ * Classifies a raw HCL value string into a typed Value structure.
20
+ * Supports literals, quoted strings, arrays, objects, and expressions.
21
+ *
22
+ * @param raw - The raw value string to classify
23
+ * @returns The classified Value with type information and extracted references
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * classifyValue('true') // LiteralValue { type: 'literal', value: true }
28
+ * classifyValue('"hello"') // LiteralValue { type: 'literal', value: 'hello' }
29
+ * classifyValue('var.region') // ExpressionValue with variable reference
30
+ * classifyValue('[1, 2, 3]') // ArrayValue with parsed elements
31
+ * ```
32
+ */
33
+ function classifyValue(raw) {
34
+ const trimmed = raw.trim();
35
+ // Try literal classification first (booleans, numbers, null)
36
+ const literal = classifyLiteral(trimmed);
37
+ if (literal) {
38
+ return literal;
39
+ }
40
+ // Handle quoted strings
41
+ if (isQuotedString(trimmed)) {
42
+ const inner = unquote(trimmed);
43
+ if (inner.includes('${')) {
44
+ return classifyExpression(inner, 'template');
45
+ }
46
+ return {
47
+ type: 'literal',
48
+ value: inner,
49
+ raw: trimmed
50
+ };
51
+ }
52
+ // Handle heredocs
53
+ if (trimmed.startsWith('<<')) {
54
+ return classifyExpression(trimmed, 'template');
55
+ }
56
+ // Handle arrays with recursive parsing
57
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
58
+ return classifyArray(trimmed);
59
+ }
60
+ // Handle objects with recursive parsing
61
+ if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
62
+ return classifyObject(trimmed);
63
+ }
64
+ // Everything else is an expression
65
+ return classifyExpression(trimmed);
66
+ }
67
+ /**
68
+ * Classifies a raw value as a literal (boolean, number, or null).
69
+ * @param raw - The trimmed raw value
70
+ * @returns LiteralValue if it's a literal, null otherwise
71
+ */
72
+ function classifyLiteral(raw) {
73
+ // Boolean literals
74
+ if (raw === 'true' || raw === 'false') {
75
+ return {
76
+ type: 'literal',
77
+ value: raw === 'true',
78
+ raw
79
+ };
80
+ }
81
+ // Numeric literals (integer and float)
82
+ if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(raw)) {
83
+ return {
84
+ type: 'literal',
85
+ value: Number(raw),
86
+ raw
87
+ };
88
+ }
89
+ // Null literal
90
+ if (raw === 'null') {
91
+ return {
92
+ type: 'literal',
93
+ value: null,
94
+ raw
95
+ };
96
+ }
97
+ return null;
98
+ }
99
+ /**
100
+ * Classifies and parses an array value with recursive element parsing.
101
+ * @param raw - The raw array string including brackets
102
+ * @returns ArrayValue with parsed elements and extracted references
103
+ */
104
+ function classifyArray(raw) {
105
+ const elements = (0, hclLexer_1.splitArrayElements)(raw);
106
+ const parsedElements = elements.map((elem) => classifyValue(elem));
107
+ const references = collectReferences(parsedElements);
108
+ return {
109
+ type: 'array',
110
+ value: parsedElements.length > 0 ? parsedElements : undefined,
111
+ raw,
112
+ references: references.length > 0 ? references : undefined
113
+ };
114
+ }
115
+ /**
116
+ * Classifies and parses an object value with recursive entry parsing.
117
+ * @param raw - The raw object string including braces
118
+ * @returns ObjectValue with parsed entries and extracted references
119
+ */
120
+ function classifyObject(raw) {
121
+ const entries = (0, hclLexer_1.splitObjectEntries)(raw);
122
+ const parsedEntries = {};
123
+ for (const [key, value] of entries) {
124
+ parsedEntries[key] = classifyValue(value);
125
+ }
126
+ const references = collectReferences(Object.values(parsedEntries));
127
+ return {
128
+ type: 'object',
129
+ value: Object.keys(parsedEntries).length > 0 ? parsedEntries : undefined,
130
+ raw,
131
+ references: references.length > 0 ? references : undefined
132
+ };
133
+ }
134
+ /**
135
+ * Collects all references from an array of values.
136
+ * @param values - Array of Value objects
137
+ * @returns Deduplicated array of references
138
+ */
139
+ function collectReferences(values) {
140
+ const refs = [];
141
+ for (const value of values) {
142
+ if (value.type === 'expression' || value.type === 'array' || value.type === 'object') {
143
+ if (value.references) {
144
+ refs.push(...value.references);
145
+ }
146
+ }
147
+ // Recursively collect from nested structures
148
+ if (value.type === 'array' && value.value) {
149
+ refs.push(...collectReferences(value.value));
150
+ }
151
+ if (value.type === 'object' && value.value) {
152
+ refs.push(...collectReferences(Object.values(value.value)));
153
+ }
154
+ }
155
+ return uniqueReferences(refs);
156
+ }
157
+ /**
158
+ * Classifies an expression and extracts its references.
159
+ * @param raw - The raw expression string
160
+ * @param forcedKind - Optional forced expression kind
161
+ * @returns ExpressionValue with kind and references
162
+ */
163
+ function classifyExpression(raw, forcedKind) {
164
+ const kind = forcedKind || detectExpressionKind(raw);
165
+ const references = extractExpressionReferences(raw, kind);
166
+ return {
167
+ type: 'expression',
168
+ kind,
169
+ raw,
170
+ references: references.length > 0 ? references : undefined
171
+ };
172
+ }
173
+ /**
174
+ * Detects the kind of an expression based on its syntax.
175
+ * @param raw - The raw expression string
176
+ * @returns The detected ExpressionKind
177
+ */
178
+ function detectExpressionKind(raw) {
179
+ // Template interpolation
180
+ if (raw.includes('${')) {
181
+ return 'template';
182
+ }
183
+ // Conditional (ternary) expression
184
+ if (hasConditionalOperator(raw)) {
185
+ return 'conditional';
186
+ }
187
+ // Function call
188
+ if (/^[\w.-]+\(/.test(raw)) {
189
+ return 'function_call';
190
+ }
191
+ // For expression (list or map comprehension)
192
+ if (/^\[\s*for\s+.+\s+in\s+.+:\s+/.test(raw) || /^\{\s*for\s+.+\s+in\s+.+:\s+/.test(raw)) {
193
+ return 'for_expr';
194
+ }
195
+ // Splat expression
196
+ if (SPLAT_PATTERN.test(raw)) {
197
+ return 'splat';
198
+ }
199
+ // Simple traversal (e.g., var.name, local.value)
200
+ if (/^[\w.-]+(\[[^\]]*])?$/.test(raw)) {
201
+ return 'traversal';
202
+ }
203
+ return 'unknown';
204
+ }
205
+ /**
206
+ * Checks if an expression contains a conditional (ternary) operator.
207
+ * Handles nested expressions and strings correctly.
208
+ * @param raw - The raw expression string
209
+ * @returns True if the expression is a conditional
210
+ */
211
+ function hasConditionalOperator(raw) {
212
+ let depth = 0;
213
+ let inString = false;
214
+ let stringChar = null;
215
+ let questionMarkFound = false;
216
+ let questionMarkDepth = -1;
217
+ for (let i = 0; i < raw.length; i++) {
218
+ const char = raw[i];
219
+ if (!inString) {
220
+ if (char === '"' || char === "'") {
221
+ inString = true;
222
+ stringChar = char;
223
+ continue;
224
+ }
225
+ if (char === '(' || char === '[' || char === '{') {
226
+ depth++;
227
+ continue;
228
+ }
229
+ if (char === ')' || char === ']' || char === '}') {
230
+ depth--;
231
+ continue;
232
+ }
233
+ // Look for ? at depth 0
234
+ if (char === '?' && depth === 0) {
235
+ questionMarkFound = true;
236
+ questionMarkDepth = depth;
237
+ continue;
238
+ }
239
+ // Look for : after ? at the same depth
240
+ if (char === ':' && questionMarkFound && depth === questionMarkDepth) {
241
+ return true;
242
+ }
243
+ }
244
+ else {
245
+ if (char === stringChar && !(0, hclLexer_1.isEscaped)(raw, i)) {
246
+ inString = false;
247
+ stringChar = null;
248
+ }
249
+ }
250
+ }
251
+ return false;
252
+ }
253
+ /**
254
+ * Extracts references from an expression.
255
+ * @param raw - The raw expression string
256
+ * @param kind - The expression kind
257
+ * @returns Array of extracted references
258
+ */
259
+ function extractExpressionReferences(raw, kind) {
260
+ const baseRefs = extractReferencesFromText(raw);
261
+ // For templates, also extract from interpolated expressions
262
+ if (kind === 'template') {
263
+ const interpolationMatches = raw.match(/\${([^}]+)}/g) || [];
264
+ const innerRefs = interpolationMatches.flatMap((expr) => extractReferencesFromText(expr.replace(/^\${|}$/g, '')));
265
+ return uniqueReferences([...baseRefs, ...innerRefs]);
266
+ }
267
+ return baseRefs;
268
+ }
269
+ /**
270
+ * Extracts all references from a text string.
271
+ * Supports: var.*, local.*, module.*, data.*, resource references,
272
+ * path.*, each.*, count.*, self.*
273
+ *
274
+ * @param raw - The raw text to extract references from
275
+ * @returns Array of extracted references
276
+ */
277
+ function extractReferencesFromText(raw) {
278
+ const refs = [];
279
+ // Extract special references first (each, count, self)
280
+ const specialRefs = extractSpecialReferences(raw);
281
+ refs.push(...specialRefs);
282
+ // Extract traversal-based references
283
+ const matches = raw.match(TRAVERSAL_PATTERN) || [];
284
+ for (const match of matches) {
285
+ // Remove index notation for parsing, but track if it has splat
286
+ const hasSplat = match.includes('[*]');
287
+ const parts = match.split('.').map((part) => part.replace(/\[.*?]/g, ''));
288
+ // var.name
289
+ if (parts[0] === 'var' && parts[1]) {
290
+ refs.push({ kind: 'variable', name: parts[1] });
291
+ continue;
292
+ }
293
+ // local.name
294
+ if (parts[0] === 'local' && parts[1]) {
295
+ refs.push({ kind: 'local', name: parts[1] });
296
+ continue;
297
+ }
298
+ // module.name.output
299
+ if (parts[0] === 'module' && parts[1]) {
300
+ const attribute = parts.slice(2).join('.') || parts[1];
301
+ refs.push({ kind: 'module_output', module: parts[1], name: attribute });
302
+ continue;
303
+ }
304
+ // data.type.name
305
+ if (parts[0] === 'data' && parts[1] && parts[2]) {
306
+ const attribute = parts.slice(3).join('.') || undefined;
307
+ refs.push({
308
+ kind: 'data',
309
+ data_type: parts[1],
310
+ name: parts[2],
311
+ attribute,
312
+ splat: hasSplat || undefined
313
+ });
314
+ continue;
315
+ }
316
+ // path.module, path.root, path.cwd
317
+ if (parts[0] === 'path' && parts[1]) {
318
+ refs.push({ kind: 'path', name: parts[1] });
319
+ continue;
320
+ }
321
+ // Skip special references (handled separately)
322
+ if (parts[0] === 'each' || parts[0] === 'count' || parts[0] === 'self') {
323
+ continue;
324
+ }
325
+ // resource.type.name (e.g., aws_instance.web.id)
326
+ if (parts.length >= 2) {
327
+ const [resourceType, resourceName, ...rest] = parts;
328
+ const attribute = rest.length ? rest.join('.') : undefined;
329
+ refs.push({
330
+ kind: 'resource',
331
+ resource_type: resourceType,
332
+ name: resourceName,
333
+ attribute,
334
+ splat: hasSplat || undefined
335
+ });
336
+ }
337
+ }
338
+ return uniqueReferences(refs);
339
+ }
340
+ /**
341
+ * Extracts special references: each.key, each.value, count.index, self.*
342
+ * @param raw - The raw text to extract from
343
+ * @returns Array of special references
344
+ */
345
+ function extractSpecialReferences(raw) {
346
+ const refs = [];
347
+ // each.key and each.value
348
+ const eachMatches = raw.match(/\beach\.(key|value)\b/g) || [];
349
+ for (const match of eachMatches) {
350
+ const property = match.split('.')[1];
351
+ refs.push({ kind: 'each', property });
352
+ }
353
+ // count.index
354
+ if (/\bcount\.index\b/.test(raw)) {
355
+ refs.push({ kind: 'count', property: 'index' });
356
+ }
357
+ // self.* (in provisioners)
358
+ const selfMatches = raw.match(/\bself\.[\w-]+/g) || [];
359
+ for (const match of selfMatches) {
360
+ const attribute = match.split('.')[1];
361
+ refs.push({ kind: 'self', attribute });
362
+ }
363
+ return refs;
364
+ }
365
+ /**
366
+ * Removes duplicate references based on their JSON representation.
367
+ * @param refs - Array of references (may contain duplicates)
368
+ * @returns Deduplicated array of references
369
+ */
370
+ function uniqueReferences(refs) {
371
+ const seen = new Set();
372
+ return refs.filter((ref) => {
373
+ const key = JSON.stringify(ref);
374
+ if (seen.has(key)) {
375
+ return false;
376
+ }
377
+ seen.add(key);
378
+ return true;
379
+ });
380
+ }
381
+ /**
382
+ * Checks if a value is a quoted string (single or double quotes).
383
+ * @param value - The value to check
384
+ * @returns True if the value is a quoted string
385
+ */
386
+ function isQuotedString(value) {
387
+ return (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"));
388
+ }
389
+ /**
390
+ * Removes quotes from a quoted string and handles escape sequences.
391
+ * @param value - The quoted string
392
+ * @returns The unquoted string with escape sequences processed
393
+ */
394
+ function unquote(value) {
395
+ const quote = value[0];
396
+ const inner = value.slice(1, -1);
397
+ // Process escape sequences
398
+ let result = '';
399
+ let i = 0;
400
+ while (i < inner.length) {
401
+ if (inner[i] === '\\' && i + 1 < inner.length) {
402
+ const next = inner[i + 1];
403
+ switch (next) {
404
+ case 'n':
405
+ result += '\n';
406
+ i += 2;
407
+ continue;
408
+ case 't':
409
+ result += '\t';
410
+ i += 2;
411
+ continue;
412
+ case 'r':
413
+ result += '\r';
414
+ i += 2;
415
+ continue;
416
+ case '\\':
417
+ result += '\\';
418
+ i += 2;
419
+ continue;
420
+ case quote:
421
+ result += quote;
422
+ i += 2;
423
+ continue;
424
+ default:
425
+ result += inner[i];
426
+ i++;
427
+ continue;
428
+ }
429
+ }
430
+ result += inner[i];
431
+ i++;
432
+ }
433
+ return result;
434
+ }
@@ -0,0 +1,9 @@
1
+ import { TerraformDocument } from '../../types/blocks';
2
+ import { TerraformExport } from '../../types/artifacts';
3
+ export interface SerializeOptions {
4
+ pruneEmpty?: boolean;
5
+ }
6
+ export declare function toJson(document: TerraformDocument | unknown, options?: SerializeOptions): string;
7
+ export declare function toJsonExport(document: TerraformDocument, options?: SerializeOptions): string;
8
+ export declare function toExport(document: TerraformDocument, options?: SerializeOptions): TerraformExport;
9
+ export declare function toYamlDocument(document: TerraformDocument | unknown, options?: SerializeOptions): string;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toJson = toJson;
4
+ exports.toJsonExport = toJsonExport;
5
+ exports.toExport = toExport;
6
+ exports.toYamlDocument = toYamlDocument;
7
+ const graphBuilder_1 = require("../graph/graphBuilder");
8
+ const yaml_1 = require("./yaml");
9
+ function toJson(document, options) {
10
+ const value = shouldPrune(options) && isTerraformDocument(document) ? pruneDocument(document) : document;
11
+ return JSON.stringify(value, null, 2);
12
+ }
13
+ function toJsonExport(document, options) {
14
+ return JSON.stringify(toExport(document, options), null, 2);
15
+ }
16
+ function toExport(document, options) {
17
+ const exportPayload = (0, graphBuilder_1.createExport)(document);
18
+ const prunedDocument = shouldPrune(options) ? pruneDocument(document) : document;
19
+ return { ...exportPayload, document: prunedDocument };
20
+ }
21
+ function toYamlDocument(document, options) {
22
+ const value = shouldPrune(options) && isTerraformDocument(document) ? pruneDocument(document) : document;
23
+ return (0, yaml_1.toYaml)(value);
24
+ }
25
+ function shouldPrune(options) {
26
+ return options?.pruneEmpty !== false;
27
+ }
28
+ function pruneDocument(document) {
29
+ return pruneValue(document) ?? {};
30
+ }
31
+ function pruneValue(value) {
32
+ if (value === null || value === undefined) {
33
+ return undefined;
34
+ }
35
+ if (Array.isArray(value)) {
36
+ const items = value
37
+ .map((item) => pruneValue(item))
38
+ .filter((item) => item !== undefined);
39
+ return items.length > 0 ? items : undefined;
40
+ }
41
+ if (typeof value === 'object') {
42
+ const entries = Object.entries(value);
43
+ const pruned = {};
44
+ for (const [key, val] of entries) {
45
+ const next = pruneValue(val);
46
+ if (next === undefined) {
47
+ continue;
48
+ }
49
+ if (Array.isArray(next) && next.length === 0) {
50
+ continue;
51
+ }
52
+ if (typeof next === 'object' && next !== null && Object.keys(next).length === 0) {
53
+ continue;
54
+ }
55
+ pruned[key] = next;
56
+ }
57
+ return Object.keys(pruned).length > 0 ? pruned : undefined;
58
+ }
59
+ return value;
60
+ }
61
+ function isTerraformDocument(doc) {
62
+ return Boolean(doc && typeof doc === 'object' && 'resource' in doc);
63
+ }
@@ -0,0 +1 @@
1
+ export declare function toYaml(value: unknown): string;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toYaml = toYaml;
4
+ const PAD = ' ';
5
+ function toYaml(value) {
6
+ return render(value, 0);
7
+ }
8
+ function render(value, level) {
9
+ if (isScalar(value)) {
10
+ return formatScalar(value);
11
+ }
12
+ if (Array.isArray(value)) {
13
+ if (value.length === 0) {
14
+ return `${indent(level)}[]`;
15
+ }
16
+ return value
17
+ .map((item) => {
18
+ if (isScalar(item)) {
19
+ return `${indent(level)}- ${formatScalar(item)}`;
20
+ }
21
+ const rendered = render(item, level + 1);
22
+ const lines = rendered.split('\n');
23
+ const first = lines[0].startsWith(indent(level + 1))
24
+ ? lines[0].slice(indent(level + 1).length)
25
+ : lines[0];
26
+ const head = `${indent(level)}- ${first}`;
27
+ const tail = lines.length > 1
28
+ ? lines
29
+ .slice(1)
30
+ .map((line) => line)
31
+ .join('\n')
32
+ : '';
33
+ return tail ? `${head}\n${tail}` : head;
34
+ })
35
+ .join('\n');
36
+ }
37
+ if (isPlainObject(value)) {
38
+ const entries = Object.entries(value);
39
+ if (entries.length === 0) {
40
+ return `${indent(level)}{}`;
41
+ }
42
+ return entries
43
+ .map(([key, val]) => {
44
+ if (isScalar(val)) {
45
+ return `${indent(level)}${key}: ${formatScalar(val)}`;
46
+ }
47
+ const rendered = render(val, level + 1);
48
+ return `${indent(level)}${key}:\n${rendered}`;
49
+ })
50
+ .join('\n');
51
+ }
52
+ return `${indent(level)}${JSON.stringify(value)}`;
53
+ }
54
+ function formatScalar(value) {
55
+ if (value === null || value === undefined) {
56
+ return 'null';
57
+ }
58
+ if (typeof value === 'string') {
59
+ return needsQuoting(value) ? JSON.stringify(value) : value;
60
+ }
61
+ if (typeof value === 'number' || typeof value === 'boolean') {
62
+ return String(value);
63
+ }
64
+ return JSON.stringify(value);
65
+ }
66
+ function indent(level) {
67
+ return PAD.repeat(level);
68
+ }
69
+ function isPlainObject(value) {
70
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
71
+ }
72
+ function isScalar(value) {
73
+ return (value === null ||
74
+ value === undefined ||
75
+ typeof value === 'string' ||
76
+ typeof value === 'number' ||
77
+ typeof value === 'boolean');
78
+ }
79
+ function needsQuoting(value) {
80
+ return /[:{}[\],&*#?|<>=%@`]/.test(value) || value.includes('"') || value.includes("'") || value.includes('\n');
81
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "parse-hcl",
3
+ "version": "0.1.0",
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
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "require": "./dist/index.js",
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "bin": {
16
+ "parse-hcl": "./dist/cli.js"
17
+ },
18
+ "repository": "https://github.com/sigmoid-hq/parse-hcl",
19
+ "author": "Juan Lee <juan.lee@sigmoid.us>",
20
+ "license": "Apache-2.0",
21
+ "private": false,
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "keywords": [
28
+ "terraform",
29
+ "hcl",
30
+ "parser",
31
+ "tfvars",
32
+ "tfstate",
33
+ "plan"
34
+ ],
35
+ "sideEffects": false,
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.json",
41
+ "build:examples": "tsc -p tsconfig.examples.json",
42
+ "test": "vitest run",
43
+ "test:coverage": "vitest run --coverage",
44
+ "lint": "eslint src",
45
+ "lint:fix": "eslint src --fix",
46
+ "format": "prettier --write \"src/**/*.ts\"",
47
+ "format:check": "prettier --check \"src/**/*.ts\"",
48
+ "prepublishOnly": "yarn build && yarn test && yarn lint",
49
+ "example": "yarn build && yarn build:examples && node dist/examples/runExample.js",
50
+ "cli": "yarn build && node dist/cli.js",
51
+ "example:usage": "yarn build && yarn build:examples && node dist/examples/usageBasic.js",
52
+ "example:artifacts": "yarn build && yarn build:examples && node dist/examples/usageArtifacts.js"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^25.0.1",
56
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
57
+ "@typescript-eslint/parser": "^8.50.1",
58
+ "@vitest/coverage-v8": "^4.0.16",
59
+ "eslint": "^9.39.2",
60
+ "eslint-config-prettier": "^10.1.8",
61
+ "globals": "^16.5.0",
62
+ "prettier": "^3.7.4",
63
+ "typescript": "^5.9.3",
64
+ "vitest": "^4.0.15"
65
+ }
66
+ }