configorama 0.9.3 → 0.9.8

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Dynamic configuration values with variable support.
4
4
 
5
- Works with `yml`, `json`, `toml` config formats and anything that parsed down to a plain ol' javascript object
5
+ Works with `yml`, `json`, `toml`, `hcl` (Terraform), and other config formats. Supports any format that can be parsed to a plain JavaScript object
6
6
 
7
7
  ## About
8
8
 
@@ -98,7 +98,7 @@ Configorama creates a graph of your config file and all its dependencies, then i
98
98
 
99
99
  ```mermaid
100
100
  flowchart TD
101
- A[Load config file] --> B[Parse yml/json/toml to object]
101
+ A[Load config file] --> B[Parse yml/json/toml/hcl to object]
102
102
  B --> C[Preprocess: raw config file]
103
103
  C --> D{Return metadata only?}
104
104
  D -->|Yes| E[Collect variable metadata]
@@ -253,10 +253,10 @@ three: ${zaz.wow.cool} # Resolves to `2`
253
253
 
254
254
  ### File references
255
255
 
256
- Import values from external yml, json, or toml files by relative path.
256
+ Import values from external yml, json, toml, or hcl files by relative path.
257
257
 
258
258
  ```yml
259
- # Import full yml/json/toml file via relative path
259
+ # Import full yml/json/toml/hcl file via relative path
260
260
  fileRef: ${file(./subFile.yml)}
261
261
 
262
262
  # Import sub values from files. This imports other-config.yml `topLevel:` value
@@ -279,7 +279,7 @@ Supported file types (extensions are case-insensitive):
279
279
  | YAML | `.yml`, `.yaml` |
280
280
  | TOML | `.toml`, `.tml` |
281
281
  | INI | `.ini` |
282
- | JSON | `.json`, `.json5` |
282
+ | JSON | `.json`, `.json5`, `.jsonc` |
283
283
 
284
284
  ### Sync/Async file references
285
285
 
@@ -533,6 +533,74 @@ npm install ts-node typescript --save-dev
533
533
  - Full TypeScript interface support
534
534
  - Comprehensive error handling with helpful dependency messages
535
535
 
536
+ ### Terraform HCL support
537
+
538
+ Configorama supports Terraform HCL (HashiCorp Configuration Language) files, allowing you to parse `.tf`, `.tf.json`, and `.hcl` files.
539
+
540
+ **Installation:**
541
+
542
+ HCL parsing requires the optional `@cdktf/hcl2json` package:
543
+
544
+ ```bash
545
+ npm install @cdktf/hcl2json
546
+ ```
547
+
548
+ **Supported file types:**
549
+ - `.tf` - Terraform configuration files
550
+ - `.hcl` - Generic HCL files
551
+ - `.tf.json` - Terraform JSON configuration files
552
+
553
+ **Example:**
554
+
555
+ ```js
556
+ const configorama = require('configorama')
557
+
558
+ // Parse a Terraform configuration file
559
+ const terraformConfig = await configorama('./main.tf')
560
+
561
+ // Access Terraform variables, resources, locals, etc.
562
+ console.log(terraformConfig.variable) // Variables defined in the file
563
+ console.log(terraformConfig.resource) // Resources
564
+ console.log(terraformConfig.locals) // Local values
565
+ console.log(terraformConfig.output) // Outputs
566
+ ```
567
+
568
+ **Importing Terraform files:**
569
+
570
+ ```yml
571
+ # Import Terraform variables from a .tf file
572
+ terraformVars: ${file(./terraform/variables.tf)}
573
+
574
+ # Import specific variable from Terraform file
575
+ region: ${file(./terraform/variables.tf):variable.region[0].default}
576
+ ```
577
+
578
+ **Variable syntax:**
579
+ When loading `.tf` or `.hcl` files directly, configorama automatically uses `$[...]` syntax instead of `${...}` to avoid conflicts with Terraform's native `${var.name}` interpolation. Terraform expressions like `${var.environment}` and `${map(string)}` are preserved as-is.
580
+
581
+ ```js
582
+ // Loading .tf directly - uses $[...] syntax automatically
583
+ const config = await configorama('./main.tf')
584
+ // config.locals[0].app_name = "myapp-${var.environment}" (preserved)
585
+
586
+ // Use $[...] for configorama variables in .tf files
587
+ // myvar: $[env:MY_VAR]
588
+ // myref: $[file(./other.yml)] # referenced files also use $[...]
589
+ ```
590
+
591
+ When importing `.tf` files from other config formats (yml, json, etc.) via `${file()}`, the parent file's syntax applies. Use `allowUnknownVariableTypes: true` if the imported `.tf` contains Terraform interpolations:
592
+
593
+ ```js
594
+ const config = await configorama('./config.yml', {
595
+ allowUnknownVariableTypes: true
596
+ })
597
+ ```
598
+
599
+ **Read-only support:**
600
+ Currently, HCL files can be read and parsed, but writing/generating HCL files is not supported.
601
+
602
+ See [tests/hclTests](./tests/hclTests) for example Terraform files.
603
+
536
604
  ### Git references
537
605
 
538
606
  Access repository information from the current working directory's git data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "configorama",
3
- "version": "0.9.3",
3
+ "version": "0.9.8",
4
4
  "description": "Variable support for configuration files",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
@@ -25,10 +25,11 @@
25
25
  "info": "repomix --include \"cli.js,src/**/*.js\" --ignore \"**/*.test.js\" --copy",
26
26
  "docs": "node ./scripts/docs.js",
27
27
  "t": "./scripts/run-tests.sh",
28
- "test": "npm run test:lib && uvu tests \".*\\.test.js$\" ",
28
+ "test": "npm run test:slow && npm run test:lib && uvu tests \".*\\.test.js$\"",
29
29
  "test:tests": "uvu tests \".*\\.test.js$\" ",
30
30
  "test:api": "uvu tests/api api.test.js",
31
31
  "test:lib": "uvu src \".*\\.test.js$\"",
32
+ "test:slow": "uvu src \".*\\.slow-test.js$\" && uvu tests \".*\\.slow-test.js$\"",
32
33
  "watch": "watchlist tests -- npm test",
33
34
  "typecheck": "tsc --noEmit",
34
35
  "types": "tsc",
@@ -84,11 +85,28 @@
84
85
  "traverse": "^0.6.8"
85
86
  },
86
87
  "devDependencies": {
88
+ "@cdktf/hcl2json": "^0.21.0",
87
89
  "@types/node": "^24.10.1",
88
90
  "markdown-magic": "^3.4.0",
89
91
  "tsx": "^4.7.0",
90
92
  "typescript": "^5.8.3",
91
93
  "uvu": "^0.5.6",
92
94
  "watchlist": "^0.3.1"
95
+ },
96
+ "peerDependencies": {
97
+ "@cdktf/hcl2json": ">=0.20.0",
98
+ "tsx": ">=4.0.0",
99
+ "ts-node": ">=10.0.0"
100
+ },
101
+ "peerDependenciesMeta": {
102
+ "@cdktf/hcl2json": {
103
+ "optional": true
104
+ },
105
+ "tsx": {
106
+ "optional": true
107
+ },
108
+ "ts-node": {
109
+ "optional": true
110
+ }
93
111
  }
94
112
  }
package/src/main.js CHANGED
@@ -158,17 +158,25 @@ class Configorama {
158
158
  // Track variable resolutions for metadata (keyed by path)
159
159
  this.resolutionTracking = {}
160
160
 
161
- const defaultSyntax = buildVariableSyntax('${', '}', ['AWS', 'stageVariables'])
161
+ // Detect file type early to determine default syntax
162
+ let detectedFileType = null
163
+ if (typeof fileOrObject === 'string') {
164
+ detectedFileType = path.extname(fileOrObject).toLowerCase()
165
+ }
166
+
167
+ // Use $[...] syntax for HCL/Terraform files to avoid conflicts with Terraform's ${} syntax
168
+ const isHclFile = detectedFileType === '.tf' || detectedFileType === '.hcl'
169
+ const defaultSyntax = isHclFile
170
+ ? buildVariableSyntax('$[', ']', ['AWS', 'stageVariables'])
171
+ : buildVariableSyntax('${', '}', ['AWS', 'stageVariables'])
162
172
 
163
173
  const varSyntax = options.syntax || defaultSyntax
164
174
  let varRegex
165
175
  if (typeof varSyntax === 'string') {
166
176
  varRegex = new RegExp(varSyntax, 'g')
167
- // this.variableSyntax = /\${((?!AWS)([ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?|(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*))}/
168
177
  } else if (varSyntax instanceof RegExp) {
169
178
  varRegex = varSyntax
170
179
  }
171
- // console.log('varRegex', varRegex)
172
180
  const variableSyntax = varRegex
173
181
  this.variableSyntax = variableSyntax
174
182
 
@@ -1255,8 +1263,8 @@ class Configorama {
1255
1263
  Config error:\n
1256
1264
  Path "${configValuePath}" resolved to "undefined".\n
1257
1265
  Verify the ${varDisplay} in config at "${configValuePath}".\n
1258
- ${leaf ? `See:\n ${leaf.originalValuePath}: ${leaf.originalSource} ` : ''}
1259
- ${leaf && leaf.isFileRef ? `\n The error could be deeper in the referenced file at ${configValuePath.replace(leaf.originalValuePath, '').replace(/^\./, '')} key.\n` : ''}`
1266
+ ${leaf ? `See:\n ${configValuePath}: ${leaf.originalSource} ` : ''}
1267
+ ${leaf && leaf.isFileRef ? `\n The error could be deeper in the referenced file at ${configValuePath.replace(leaf.originalValuePath || configValuePath, '').replace(/^\./, '')} key.\n` : ''}`
1260
1268
  throw new Error(errorMessage)
1261
1269
  }
1262
1270
  if (typeof rawValue === 'string') {
@@ -3298,7 +3306,9 @@ Missing Value ${missingValue} - ${matchedString}
3298
3306
  config: this.config,
3299
3307
  getDeeperValue: this.getDeeperValue.bind(this),
3300
3308
  fileRefSyntax: fileRefSyntax,
3301
- textRefSyntax: textRefSyntax
3309
+ textRefSyntax: textRefSyntax,
3310
+ varPrefix: this.varPrefix,
3311
+ varSuffix: this.varSuffix
3302
3312
  }
3303
3313
  return getValueFromFileResolver(ctx, variableString, options)
3304
3314
  }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone script to parse HCL content
4
+ * Used by hcl.js for synchronous parsing via child_process
5
+ */
6
+
7
+ async function main() {
8
+ try {
9
+ const args = process.argv.slice(2)
10
+ const filename = args[0] || 'config.tf'
11
+ const contents = args[1] || ''
12
+
13
+ if (!contents) {
14
+ throw new Error('HCL content is required')
15
+ }
16
+
17
+ let hcl2json
18
+ try {
19
+ hcl2json = require('@cdktf/hcl2json')
20
+ } catch (err) {
21
+ if (err.code === 'MODULE_NOT_FOUND') {
22
+ throw new Error(
23
+ 'HCL/Terraform file support requires "@cdktf/hcl2json" to be installed. ' +
24
+ 'Please install it: npm install @cdktf/hcl2json'
25
+ )
26
+ }
27
+ throw err
28
+ }
29
+
30
+ const result = await hcl2json.parse(filename, contents)
31
+
32
+ // Output result as JSON
33
+ console.log(JSON.stringify(result))
34
+ } catch (error) {
35
+ console.error(JSON.stringify({ error: error.message }))
36
+ process.exit(1)
37
+ }
38
+ }
39
+
40
+ main()
@@ -1,3 +1,131 @@
1
- // TODO hashicorp HCL
2
- // https://www.npmjs.com/package/hcl-to-json
3
- // https://www.npmjs.com/package/hcl2json
1
+ const YAML = require('./yaml')
2
+ const JSON = require('./json5')
3
+
4
+ /**
5
+ * Get the hcl2json module, throwing helpful error if not installed
6
+ * @returns {{ parse: Function }} The hcl2json module
7
+ * @throws {Error} If @cdktf/hcl2json is not installed
8
+ */
9
+ function getHcl2Json() {
10
+ try {
11
+ return require('@cdktf/hcl2json')
12
+ } catch (err) {
13
+ if (err.code === 'MODULE_NOT_FOUND') {
14
+ throw new Error(
15
+ 'HCL/Terraform file support requires "@cdktf/hcl2json" to be installed.\n' +
16
+ 'Please install it:\n' +
17
+ ' npm install @cdktf/hcl2json'
18
+ )
19
+ }
20
+ throw err
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Parse HCL content into JavaScript object
26
+ * Uses @cdktf/hcl2json to convert HCL to JSON
27
+ * @param {string} hclContents - HCL string to parse
28
+ * @param {string} [filename='config.tf'] - Filename for context
29
+ * @returns {Promise<Object>} Parsed HCL object
30
+ * @throws {Error} If HCL parsing fails
31
+ */
32
+ async function parse(hclContents, filename = 'config.tf') {
33
+ let hclObject = {}
34
+ try {
35
+ const { parse: hclParse } = getHcl2Json()
36
+ const result = await hclParse(filename, hclContents)
37
+ hclObject = result
38
+ } catch (e) {
39
+ throw new Error(`Failed to parse HCL: ${e.message}`)
40
+ }
41
+ return hclObject
42
+ }
43
+
44
+ /**
45
+ * Synchronous HCL parsing using child process
46
+ * @param {string} hclContents - HCL string to parse
47
+ * @param {string} [filename='config.tf'] - Filename for context
48
+ * @returns {Object} Parsed HCL object
49
+ * @throws {Error} If HCL parsing fails
50
+ */
51
+ function parseSync(hclContents, filename = 'config.tf') {
52
+ const { execFileSync } = require('child_process')
53
+ const scriptPath = require.resolve('./hcl-parse-script.js')
54
+
55
+ try {
56
+ const result = execFileSync(process.execPath, [scriptPath, filename, hclContents], {
57
+ encoding: 'utf8',
58
+ maxBuffer: 10 * 1024 * 1024 // 10MB buffer
59
+ })
60
+ return JSON.parse(result.trim())
61
+ } catch (error) {
62
+ // Check if error output contains JSON error
63
+ if (error.stderr) {
64
+ try {
65
+ const errorData = JSON.parse(error.stderr)
66
+ throw new Error(`Failed to parse HCL: ${errorData.error}`)
67
+ } catch (parseErr) {
68
+ // If stderr is not JSON, use original error
69
+ throw new Error(`Failed to parse HCL: ${error.message}`)
70
+ }
71
+ }
72
+ throw new Error(`Failed to parse HCL: ${error.message}`)
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Convert JavaScript object to HCL string
78
+ * Note: HCL generation is complex and not fully supported
79
+ * This is a placeholder for potential future implementation
80
+ * @param {Object} object - Object to convert to HCL
81
+ * @returns {string} HCL string representation
82
+ * @throws {Error} Always throws - HCL generation not implemented
83
+ */
84
+ function dump(object) {
85
+ throw new Error('HCL generation (dump) is not currently supported. HCL files can be read but not written.')
86
+ }
87
+
88
+ /**
89
+ * Convert HCL content to YAML format
90
+ * @param {string} hclContents - HCL string to convert
91
+ * @param {string} [filename='config.tf'] - Filename for context
92
+ * @returns {Promise<string>} YAML string representation
93
+ * @throws {Error} If conversion fails
94
+ */
95
+ async function toYaml(hclContents, filename = 'config.tf') {
96
+ let yml
97
+ try {
98
+ const parsed = await parse(hclContents, filename)
99
+ yml = YAML.dump(parsed)
100
+ } catch (e) {
101
+ throw new Error(`Failed to convert HCL to YAML: ${e.message}`)
102
+ }
103
+ return yml
104
+ }
105
+
106
+ /**
107
+ * Convert HCL content to JSON format
108
+ * @param {string} hclContents - HCL string to convert
109
+ * @param {string} [filename='config.tf'] - Filename for context
110
+ * @returns {Promise<string>} JSON string representation
111
+ * @throws {Error} If conversion fails
112
+ */
113
+ async function toJson(hclContents, filename = 'config.tf') {
114
+ let json
115
+ try {
116
+ const parsed = await parse(hclContents, filename)
117
+ json = JSON.dump(parsed)
118
+ } catch (e) {
119
+ throw new Error(`Failed to convert HCL to JSON: ${e.message}`)
120
+ }
121
+ return json
122
+ }
123
+
124
+ module.exports = {
125
+ parse: parseSync, // Export sync version for compatibility with existing parsers
126
+ parseAsync: parse, // Export async version for direct use
127
+ parseSync: parseSync,
128
+ dump: dump,
129
+ toYaml: toYaml,
130
+ toJson: toJson
131
+ }
@@ -0,0 +1,141 @@
1
+ /* eslint-disable no-template-curly-in-string */
2
+ const { test } = require('uvu')
3
+ const assert = require('uvu/assert')
4
+ const hcl = require('./hcl')
5
+ const JSON5 = require('json5')
6
+ const path = require('path')
7
+ const fs = require('fs')
8
+
9
+ function normalize(obj) {
10
+ return JSON.parse(JSON.stringify(obj))
11
+ }
12
+
13
+ test('hcl parse basic variable', async () => {
14
+ const hclContent = `variable "name" {
15
+ description = "Name to be used"
16
+ type = string
17
+ default = "test"
18
+ }`
19
+ const result = hcl.parse(hclContent, 'test.tf')
20
+ console.log('basic result', JSON.stringify(result, null, 2))
21
+
22
+ assert.ok(result, 'result should exist')
23
+ assert.ok(result.variable, 'should have variable section')
24
+ })
25
+
26
+ test('hcl parse multiple variables', async () => {
27
+ const hclContent = `variable "region" {
28
+ description = "AWS region"
29
+ type = string
30
+ default = "us-east-1"
31
+ }
32
+
33
+ variable "count" {
34
+ description = "Number of instances"
35
+ type = number
36
+ default = 2
37
+ }
38
+
39
+ variable "enabled" {
40
+ description = "Feature flag"
41
+ type = bool
42
+ default = true
43
+ }`
44
+ const result = hcl.parse(hclContent, 'test.tf')
45
+ console.log('multiple vars result', JSON.stringify(result, null, 2))
46
+
47
+ assert.ok(result, 'result should exist')
48
+ assert.ok(result.variable, 'should have variable section')
49
+ })
50
+
51
+ test('hcl parse with locals', async () => {
52
+ const hclContent = `variable "environment" {
53
+ type = string
54
+ default = "dev"
55
+ }
56
+
57
+ locals {
58
+ app_name = "myapp-\${var.environment}"
59
+ }`
60
+ const result = hcl.parse(hclContent, 'test.tf')
61
+ console.log('locals result', JSON.stringify(result, null, 2))
62
+
63
+ assert.ok(result, 'result should exist')
64
+ assert.ok(result.variable || result.locals, 'should have variable or locals section')
65
+ })
66
+
67
+ test('hcl parse resource block', async () => {
68
+ const hclContent = `resource "aws_instance" "app" {
69
+ ami = "ami-12345678"
70
+ instance_type = "t3.micro"
71
+
72
+ tags = {
73
+ Name = "MyApp"
74
+ Environment = "dev"
75
+ }
76
+ }`
77
+ const result = hcl.parse(hclContent, 'test.tf')
78
+ console.log('resource result', JSON.stringify(result, null, 2))
79
+
80
+ assert.ok(result, 'result should exist')
81
+ assert.ok(result.resource, 'should have resource section')
82
+ })
83
+
84
+ test('hcl parse from file - simple.tf', async () => {
85
+ const filePath = path.join(__dirname, '../../tests/hclTests/simple.tf')
86
+
87
+ // Skip if file doesn't exist
88
+ if (!fs.existsSync(filePath)) {
89
+ console.log('Skipping test - fixture file not found')
90
+ return
91
+ }
92
+
93
+ const contents = fs.readFileSync(filePath, 'utf8')
94
+ const result = hcl.parse(contents, 'simple.tf')
95
+ console.log('file parse result', JSON.stringify(result, null, 2))
96
+
97
+ assert.ok(result, 'result should exist')
98
+ assert.ok(result.variable, 'should have variable section')
99
+ })
100
+
101
+ test('hcl dump should throw error', () => {
102
+ let error
103
+ try {
104
+ hcl.dump({ foo: 'bar' })
105
+ } catch (e) {
106
+ error = e
107
+ }
108
+ assert.ok(error instanceof Error, 'should throw error')
109
+ assert.ok(error.message.includes('not currently supported'), 'error message should indicate not supported')
110
+ })
111
+
112
+ test('hcl toJson basic', async () => {
113
+ const hclContent = `variable "test" {
114
+ type = string
115
+ default = "value"
116
+ }`
117
+
118
+ const result = await hcl.toJson(hclContent, 'test.tf')
119
+ console.log('toJson result', result)
120
+
121
+ assert.ok(result, 'result should exist')
122
+ assert.ok(typeof result === 'string', 'result should be string')
123
+
124
+ const parsed = JSON5.parse(result)
125
+ assert.ok(parsed, 'should be valid JSON5')
126
+ })
127
+
128
+ test('hcl toYaml basic', async () => {
129
+ const hclContent = `variable "test" {
130
+ type = string
131
+ default = "value"
132
+ }`
133
+
134
+ const result = await hcl.toYaml(hclContent, 'test.tf')
135
+ console.log('toYaml result', result)
136
+
137
+ assert.ok(result, 'result should exist')
138
+ assert.ok(typeof result === 'string', 'result should be string')
139
+ })
140
+
141
+ test.run()
@@ -8,6 +8,7 @@ const json = require('./json5')
8
8
  const toml = require('./toml')
9
9
  const yaml = require('./yaml')
10
10
  const ini = require('./ini')
11
+ const hcl = require('./hcl')
11
12
 
12
13
  /**
13
14
  * Collection of format parsers for different config file types
@@ -17,5 +18,6 @@ module.exports = {
17
18
  json: json,
18
19
  toml: toml,
19
20
  yaml: yaml,
20
- ini: ini
21
+ ini: ini,
22
+ hcl: hcl
21
23
  }
@@ -14,6 +14,34 @@ const YAML = require('../parsers/yaml')
14
14
  const TOML = require('../parsers/toml')
15
15
  const INI = require('../parsers/ini')
16
16
  const JSON5 = require('../parsers/json5')
17
+ const HCL = require('../parsers/hcl')
18
+
19
+ /**
20
+ * Convert HCL $[...] syntax to the main config's variable syntax
21
+ * This allows configorama variables in .tf files to work when imported from other formats
22
+ * @param {*} obj - Object to convert
23
+ * @param {string} varPrefix - Variable prefix (e.g., '${')
24
+ * @param {string} varSuffix - Variable suffix (e.g., '}')
25
+ * @returns {*} Converted object
26
+ */
27
+ function convertHclVarSyntax(obj, varPrefix, varSuffix) {
28
+ if (!obj) return obj
29
+ if (typeof obj === 'string') {
30
+ // Convert $[...] to the main config's syntax (e.g., ${...})
31
+ return obj.replace(/\$\[([^\]]+)\]/g, `${varPrefix}$1${varSuffix}`)
32
+ }
33
+ if (Array.isArray(obj)) {
34
+ return obj.map(item => convertHclVarSyntax(item, varPrefix, varSuffix))
35
+ }
36
+ if (typeof obj === 'object') {
37
+ const converted = {}
38
+ for (const key of Object.keys(obj)) {
39
+ converted[key] = convertHclVarSyntax(obj[key], varPrefix, varSuffix)
40
+ }
41
+ return converted
42
+ }
43
+ return obj
44
+ }
17
45
 
18
46
  /**
19
47
  * Recursively clean encoded JSON from an object
@@ -78,6 +106,8 @@ function parseFileContents(content, filePath) {
78
106
  * @param {Function} ctx.getDeeperValue - Method for nested lookups
79
107
  * @param {RegExp} ctx.fileRefSyntax - Regex for file() syntax
80
108
  * @param {RegExp} ctx.textRefSyntax - Regex for text() syntax
109
+ * @param {string} ctx.varPrefix - Variable prefix (e.g., '${')
110
+ * @param {string} ctx.varSuffix - Variable suffix (e.g., '}')
81
111
  * @param {string} variableString - The variable string to resolve
82
112
  * @param {object} options - Resolution options
83
113
  * @returns {Promise<any>}
@@ -365,6 +395,19 @@ ${JSON.stringify(options.context, null, 2)}`,
365
395
  if (fileExtension === 'ini') {
366
396
  valueToPopulate = INI.toJson(valueToPopulate)
367
397
  }
398
+ if (fileExtension === 'tf' || fileExtension === 'hcl') {
399
+ // Parse HCL and convert $[...] to main config's syntax for variable resolution
400
+ const parsed = convertHclVarSyntax(HCL.parse(valueToPopulate, relativePath), ctx.varPrefix, ctx.varSuffix)
401
+ valueToPopulate = JSON.stringify(parsed)
402
+ }
403
+ if (fileExtension === 'json' || fileExtension === 'json5') {
404
+ let parsed = JSON5.parse(valueToPopulate)
405
+ // Convert $[...] to main config's syntax for .tf.json files
406
+ if (relativePath.endsWith('.tf.json')) {
407
+ parsed = convertHclVarSyntax(parsed, ctx.varPrefix, ctx.varSuffix)
408
+ }
409
+ valueToPopulate = JSON.stringify(parsed)
410
+ }
368
411
  // console.log('deep', variableString)
369
412
  // console.log('matchedFileString', matchedFileString)
370
413
  const deepPropertiesStr = variableString.replace(matchedFileString, '')
@@ -396,6 +439,16 @@ Please use ":" or "." to reference sub properties. ${deepPropertiesStr}`
396
439
 
397
440
  if (fileExtension === 'json' || fileExtension === 'json5') {
398
441
  valueToPopulate = JSON5.parse(valueToPopulate)
442
+ // Convert $[...] to main config's syntax for .tf.json files (Terraform JSON format)
443
+ if (relativePath.endsWith('.tf.json')) {
444
+ valueToPopulate = convertHclVarSyntax(valueToPopulate, ctx.varPrefix, ctx.varSuffix)
445
+ }
446
+ return Promise.resolve(valueToPopulate)
447
+ }
448
+
449
+ if (fileExtension === 'tf' || fileExtension === 'hcl') {
450
+ // Parse HCL and convert $[...] to main config's syntax for variable resolution
451
+ valueToPopulate = convertHclVarSyntax(HCL.parse(valueToPopulate, relativePath), ctx.varPrefix, ctx.varSuffix)
399
452
  return Promise.resolve(valueToPopulate)
400
453
  }
401
454
  }
@@ -5,6 +5,7 @@ const YAML = require('../../parsers/yaml')
5
5
  const TOML = require('../../parsers/toml')
6
6
  const INI = require('../../parsers/ini')
7
7
  const JSON5 = require('../../parsers/json5')
8
+ const HCL = require('../../parsers/hcl')
8
9
  const { executeTypeScriptFileSync } = require('../../parsers/typescript')
9
10
  const { executeESMFileSync } = require('../../parsers/esm')
10
11
  const cloudFormationSchema = require('./cloudformationSchema')
@@ -54,8 +55,17 @@ function parseFileContents({ contents, filePath, varRegex, dynamicArgs }) {
54
55
  configObject = TOML.parse(contents)
55
56
  } else if (fileType.match(/\.(ini)/i)) {
56
57
  configObject = INI.parse(contents)
57
- } else if (fileType.match(/\.(json|json5)/i)) {
58
+ } else if (fileType.match(/\.(json|json5|jsonc)/i)) {
58
59
  configObject = JSON5.parse(contents)
60
+ } else if (fileType.match(/\.(tf|hcl)$/i) || filePath.match(/\.tf\.json$/i)) {
61
+ // Handle Terraform HCL files (.tf, .hcl) and Terraform JSON (.tf.json)
62
+ if (filePath.match(/\.tf\.json$/i)) {
63
+ // .tf.json files are just JSON
64
+ configObject = JSON5.parse(contents)
65
+ } else {
66
+ // .tf and .hcl files need HCL parsing
67
+ configObject = HCL.parse(contents, path.basename(filePath))
68
+ }
59
69
  // TODO detect js syntax and use appropriate parser
60
70
  } else if (fileType.match(/\.(js|cjs)/i)) {
61
71
  let jsFile
@@ -4,7 +4,7 @@
4
4
  const isEmpty = require('lodash.isempty')
5
5
 
6
6
  function isValidValue(val) {
7
- if (typeof val === 'object' && (val.hasOwnProperty('__internal_only_flag') || val.hasOwnProperty('__internal_metadata'))) {
7
+ if (val !== null && typeof val === 'object' && (val.hasOwnProperty('__internal_only_flag') || val.hasOwnProperty('__internal_metadata'))) {
8
8
  return false
9
9
  }
10
10
  return val !== null && typeof val !== 'undefined' && !(typeof val === 'object' && isEmpty(val))
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AA0GA;IACE,0CA6bC;IArbC,cAcW;IAuBX,gBAAqB;IAErB,sBAAwB;IACxB,qBAAuB;IAGvB,uBAA4B;IAc5B,uBAAoC;IAIpC,kBAAqC;IACrC,kBAAqC;IAErC,yBAA+F;IAC/F,yBAAuD;IACvD,kCAAyE;IAKvE,YAA0B;IAE1B,oBAA6C;IAE7C,gBAAoD;IAOpD,uBAAkC;IAElC,uBAA8B;IAE9B,uBAAkC;IASpC,wBAAmC;IAGnC,mBA8GC;IAwED,4BAA8C;IAO9C,aA2EC;IAUD,oBAEC;IAGD,eAkDC;IAOD,YAAc;IACd,cAAgB;IAChB,kBAAkB;IAGpB;;;;OAIG;IACH,0BAHW,MAAM,GACJ,OAAO,CAQnB;IAED;;;;OAIG;IACH,6BAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAOvB;IAED;;;;OAIG;IACH,gCAHW,MAAM,GACJ,OAAO,CAoBnB;IAKD;;;;;OAKG;IACH,oBAFa,OAAO,CAAC,GAAG,CAAC,CAqsBxB;IAlsBC,aAA4B;IAc1B,2BAA4B;IAQ5B,uBAAgD;IA8qBpD;;;OAGG;IACH,2BAFa,MAAM,CA8alB;IAvBC;;;;;;;;;;;;;;;MAoBC;IAIH;;;;OAIG;IACH,uCAFa,OAAO,CAAC,GAAG,CAAC,CAIxB;IACD,+CAsBC;IAKD;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;OAWG;IACH,mFAHa;;;;cAZC,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAWD,CA+D9B;IACD;;;OAGG;IACH;;;;;OAKG;IACH,oCAHa,OAAO,CAAC;;;;cAnFP,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAkFgB,CAAC,EAAE,CA6BlD;IACD;;;;;OAKG;IACH,iDAFa,OAAO,CAAC,IAAI,CAAC,CAWzB;IAID;;;;;OAKG;IACH;;;;OAIG;IACH,2BAFa,eAAc;;;;;;;;;;OAAa,CAavC;IACD;;;;;OAKG;IACH,yBAHW;;;;;;;;;;OAAa,gCACX,cAAS,CAOrB;IACD;;;;;;OAMG;IACH,6DAFa,GAAC,CA+Kb;IAKD;;;;;;;OAOG;IACH,yDAHa,OAAO,CAAC,GAAG,CAAC,CAiCxB;IACD;;;;OAIG;IAOH;;;;;;OAMG;IACH,wFA2BC;IACD;;;;;;;;;;;OAWG;IACH,8BARG;QAAyB,KAAK,EAAtB,GAAG;QACoB,IAAI,GAA3B,MAAM,EAAE;QACa,cAAc,GAAnC,MAAM;QACc,iBAAiB;KAC7C,6CAEU;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,QAAQ;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,CAmY9J;IAID;;;;;;;;OAQG;IACH,qEAHa,OAAO,CAAC,GAAG,CAAC,CAoExB;IAKD;;;;;;;OAOG;IACH,0FAFa,OAAO,CAAC,GAAG,CAAC,CA6gBxB;IACD,+EA+BC;IACD,yDAeC;IACD,oEA6BC;IAKD,8CAQC;IACD,kDAyBC;IACD;;;;;;;;;;;;;OAaG;IACH,wEAoDC;IAKD,4BAOC;IACD,sCA2DC;CACF"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AA0GA;IACE,0CAqcC;IA7bC,cAcW;IAuBX,gBAAqB;IAErB,sBAAwB;IACxB,qBAAuB;IAGvB,uBAA4B;IAsB5B,uBAAoC;IAIpC,kBAAqC;IACrC,kBAAqC;IAErC,yBAA+F;IAC/F,yBAAuD;IACvD,kCAAyE;IAKvE,YAA0B;IAE1B,oBAA6C;IAE7C,gBAAoD;IAOpD,uBAAkC;IAElC,uBAA8B;IAE9B,uBAAkC;IASpC,wBAAmC;IAGnC,mBA8GC;IAwED,4BAA8C;IAO9C,aA2EC;IAUD,oBAEC;IAGD,eAkDC;IAOD,YAAc;IACd,cAAgB;IAChB,kBAAkB;IAGpB;;;;OAIG;IACH,0BAHW,MAAM,GACJ,OAAO,CAQnB;IAED;;;;OAIG;IACH,6BAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAOvB;IAED;;;;OAIG;IACH,gCAHW,MAAM,GACJ,OAAO,CAoBnB;IAKD;;;;;OAKG;IACH,oBAFa,OAAO,CAAC,GAAG,CAAC,CAqsBxB;IAlsBC,aAA4B;IAc1B,2BAA4B;IAQ5B,uBAAgD;IA8qBpD;;;OAGG;IACH,2BAFa,MAAM,CA8alB;IAvBC;;;;;;;;;;;;;;;MAoBC;IAIH;;;;OAIG;IACH,uCAFa,OAAO,CAAC,GAAG,CAAC,CAIxB;IACD,+CAsBC;IAKD;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;OAWG;IACH,mFAHa;;;;cAZC,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAWD,CA+D9B;IACD;;;OAGG;IACH;;;;;OAKG;IACH,oCAHa,OAAO,CAAC;;;;cAnFP,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAkFgB,CAAC,EAAE,CA6BlD;IACD;;;;;OAKG;IACH,iDAFa,OAAO,CAAC,IAAI,CAAC,CAWzB;IAID;;;;;OAKG;IACH;;;;OAIG;IACH,2BAFa,eAAc;;;;;;;;;;OAAa,CAavC;IACD;;;;;OAKG;IACH,yBAHW;;;;;;;;;;OAAa,gCACX,cAAS,CAOrB;IACD;;;;;;OAMG;IACH,6DAFa,GAAC,CA+Kb;IAKD;;;;;;;OAOG;IACH,yDAHa,OAAO,CAAC,GAAG,CAAC,CAiCxB;IACD;;;;OAIG;IAOH;;;;;;OAMG;IACH,wFA2BC;IACD;;;;;;;;;;;OAWG;IACH,8BARG;QAAyB,KAAK,EAAtB,GAAG;QACoB,IAAI,GAA3B,MAAM,EAAE;QACa,cAAc,GAAnC,MAAM;QACc,iBAAiB;KAC7C,6CAEU;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,QAAQ;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,CAmY9J;IAID;;;;;;;;OAQG;IACH,qEAHa,OAAO,CAAC,GAAG,CAAC,CAoExB;IAKD;;;;;;;OAOG;IACH,0FAFa,OAAO,CAAC,GAAG,CAAC,CA6gBxB;IACD,+EA+BC;IACD,yDAiBC;IACD,oEA6BC;IAKD,8CAQC;IACD,kDAyBC;IACD;;;;;;;;;;;;;OAaG;IACH,wEAoDC;IAKD,4BAOC;IACD,sCA2DC;CACF"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=hcl-parse-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hcl-parse-script.d.ts","sourceRoot":"","sources":["../../../src/parsers/hcl-parse-script.js"],"names":[],"mappings":""}
@@ -1 +1,44 @@
1
+ /**
2
+ * Synchronous HCL parsing using child process
3
+ * @param {string} hclContents - HCL string to parse
4
+ * @param {string} [filename='config.tf'] - Filename for context
5
+ * @returns {Object} Parsed HCL object
6
+ * @throws {Error} If HCL parsing fails
7
+ */
8
+ export function parseSync(hclContents: string, filename?: string): any;
9
+ /**
10
+ * Parse HCL content into JavaScript object
11
+ * Uses @cdktf/hcl2json to convert HCL to JSON
12
+ * @param {string} hclContents - HCL string to parse
13
+ * @param {string} [filename='config.tf'] - Filename for context
14
+ * @returns {Promise<Object>} Parsed HCL object
15
+ * @throws {Error} If HCL parsing fails
16
+ */
17
+ declare function parse(hclContents: string, filename?: string): Promise<any>;
18
+ /**
19
+ * Convert JavaScript object to HCL string
20
+ * Note: HCL generation is complex and not fully supported
21
+ * This is a placeholder for potential future implementation
22
+ * @param {Object} object - Object to convert to HCL
23
+ * @returns {string} HCL string representation
24
+ * @throws {Error} Always throws - HCL generation not implemented
25
+ */
26
+ export function dump(object: any): string;
27
+ /**
28
+ * Convert HCL content to YAML format
29
+ * @param {string} hclContents - HCL string to convert
30
+ * @param {string} [filename='config.tf'] - Filename for context
31
+ * @returns {Promise<string>} YAML string representation
32
+ * @throws {Error} If conversion fails
33
+ */
34
+ export function toYaml(hclContents: string, filename?: string): Promise<string>;
35
+ /**
36
+ * Convert HCL content to JSON format
37
+ * @param {string} hclContents - HCL string to convert
38
+ * @param {string} [filename='config.tf'] - Filename for context
39
+ * @returns {Promise<string>} JSON string representation
40
+ * @throws {Error} If conversion fails
41
+ */
42
+ export function toJson(hclContents: string, filename?: string): Promise<string>;
43
+ export { parseSync as parse, parse as parseAsync };
1
44
  //# sourceMappingURL=hcl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hcl.d.ts","sourceRoot":"","sources":["../../../src/parsers/hcl.js"],"names":[],"mappings":""}
1
+ {"version":3,"file":"hcl.d.ts","sourceRoot":"","sources":["../../../src/parsers/hcl.js"],"names":[],"mappings":"AA2CA;;;;;;GAMG;AACH,uCALW,MAAM,aACN,MAAM,OA2BhB;AAlDD;;;;;;;GAOG;AACH,oCALW,MAAM,aACN,MAAM,GACJ,OAAO,KAAQ,CAa3B;AAkCD;;;;;;;GAOG;AACH,mCAHa,MAAM,CAKlB;AAED;;;;;;GAMG;AACH,oCALW,MAAM,aACN,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAY3B;AAED;;;;;;GAMG;AACH,oCALW,MAAM,aACN,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAY3B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hcl.slow-test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hcl.slow-test.d.ts","sourceRoot":"","sources":["../../../src/parsers/hcl.slow-test.js"],"names":[],"mappings":""}
@@ -12,6 +12,8 @@
12
12
  * @param {Function} ctx.getDeeperValue - Method for nested lookups
13
13
  * @param {RegExp} ctx.fileRefSyntax - Regex for file() syntax
14
14
  * @param {RegExp} ctx.textRefSyntax - Regex for text() syntax
15
+ * @param {string} ctx.varPrefix - Variable prefix (e.g., '${')
16
+ * @param {string} ctx.varSuffix - Variable suffix (e.g., '}')
15
17
  * @param {string} variableString - The variable string to resolve
16
18
  * @param {object} options - Resolution options
17
19
  * @returns {Promise<any>}
@@ -28,6 +30,8 @@ export function getValueFromFile(ctx: {
28
30
  getDeeperValue: Function;
29
31
  fileRefSyntax: RegExp;
30
32
  textRefSyntax: RegExp;
33
+ varPrefix: string;
34
+ varSuffix: string;
31
35
  }, variableString: string, options: object): Promise<any>;
32
36
  /**
33
37
  * Parse file contents based on file extension
@@ -1 +1 @@
1
- {"version":3,"file":"valueFromFile.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromFile.js"],"names":[],"mappings":"AAkEA;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAfG;IAAoB,UAAU,EAAtB,MAAM;IACK,aAAa;IACZ,cAAc,EAA1B,MAAM;IACM,mBAAmB,EAA/B,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,IAAI,EAAhB,MAAM;IACM,cAAc,EAA1B,MAAM;IACM,MAAM,EAAlB,MAAM;IACQ,cAAc;IAChB,aAAa,EAAzB,MAAM;IACM,aAAa,EAAzB,MAAM;CACd,kBAAQ,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CAiUxB;AA3WD;;;;;GAKG;AACH,2CAJW,MAAM,YACN,MAAM,GACJ,GAAC,CAoBb;AAqVD;;;;;;GAMG;AACH,qDAJW,MAAM,qBACN,MAAM,GACJ;IAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,GAAC,IAAI,CAAA;CAAE,CAkBhE;AAED;;;;;;GAMG;AACH,sDALW,MAAM,qBACN,MAAM,yBACN,OAAO,GACL,MAAM,EAAE,CAcpB"}
1
+ {"version":3,"file":"valueFromFile.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromFile.js"],"names":[],"mappings":"AA8FA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,sCAjBG;IAAoB,UAAU,EAAtB,MAAM;IACK,aAAa;IACZ,cAAc,EAA1B,MAAM;IACM,mBAAmB,EAA/B,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,IAAI,EAAhB,MAAM;IACM,cAAc,EAA1B,MAAM;IACM,MAAM,EAAlB,MAAM;IACQ,cAAc;IAChB,aAAa,EAAzB,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,SAAS,EAArB,MAAM;IACM,SAAS,EAArB,MAAM;CACd,kBAAQ,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CAwVxB;AApYD;;;;;GAKG;AACH,2CAJW,MAAM,YACN,MAAM,GACJ,GAAC,CAoBb;AA8WD;;;;;;GAMG;AACH,qDAJW,MAAM,qBACN,MAAM,GACJ;IAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,GAAC,IAAI,CAAA;CAAE,CAkBhE;AAED;;;;;;GAMG;AACH,sDALW,MAAM,qBACN,MAAM,yBACN,OAAO,GACL,MAAM,EAAE,CAcpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/utils/parsing/parse.js"],"names":[],"mappings":";;;;cAec,MAAM;;;;cACN,MAAM;;;;eACN,MAAM;;;;kBACN,cAAe;;;;;;eAqGf,MAAM;;;;kBACN,cAAe;;AA3G7B;;;;;;GAMG;AAEH;;;;GAIG;AACH,iFAHW,YAAY,OA4FtB;AAED;;;;GAIG;AAEH;;;;;GAKG;AACH,oCAJW,MAAM,SACN,gBAAgB,OAW1B"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/utils/parsing/parse.js"],"names":[],"mappings":";;;;cAgBc,MAAM;;;;cACN,MAAM;;;;eACN,MAAM;;;;kBACN,cAAe;;;;;;eA8Gf,MAAM;;;;kBACN,cAAe;;AApH7B;;;;;;GAMG;AAEH;;;;GAIG;AACH,iFAHW,YAAY,OAqGtB;AAED;;;;GAIG;AAEH;;;;;GAKG;AACH,oCAJW,MAAM,SACN,gBAAgB,OAW1B"}