configorama 0.9.13 → 0.9.14

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
@@ -1,102 +1,205 @@
1
1
  # Configorama
2
2
 
3
- Dynamic configuration values with variable support.
3
+ Dynamic configuration values with variable support for yml, json, toml, hcl (Terraform), and other config formats.
4
4
 
5
- Works with `yml`, `json`, `toml`, `hcl` (Terraform), and other config formats. Supports any format that can be parsed to a plain JavaScript object
5
+ Configorama extends your configuration with a powerful variable system that resolves values from CLI options, environment variables, file references, TypeScript/JavaScript files, git data, self-references, conditional expressions, and any custom source you define.
6
6
 
7
- ## About
7
+ ## Key Features
8
8
 
9
- Configorama extends your configuration with a powerful variable system. It resolves configuration variables from:
10
-
11
- - CLI options
12
- - ENV variables
13
- - File references
14
- - TypeScript file references
15
- - Self references (other keys/values in config)
16
- - Git references
17
- - Cron values
18
- - Eval expressions
19
- - If/conditional expressions
20
- - Async/sync JS functions
21
- - Filters (experimental)
22
- - Functions (experimental)
23
- - Any source you'd like...
24
-
25
- See [tests](https://github.com/DavidWells/configorama/tree/master/tests) for more examples.
9
+ - **Multiple file formats** - yml, yaml, json, toml, ini, hcl (Terraform), TypeScript, JavaScript, markdown
10
+ - **Rich variable sources** - env vars, CLI flags, file refs, git data, cron expressions, eval/if expressions
11
+ - **Async/sync function execution** - Import and execute JavaScript/TypeScript files with argument passing
12
+ - **Self-referencing** - Reference other values within the same config using dot notation
13
+ - **Custom variable sources** - Pluggable architecture to add your own variable resolvers
14
+ - **Filters and functions** - Transform and combine values with built-in or custom operators
15
+ - **Metadata extraction** - Analyze configs without resolving them, or get full resolution history
16
+ - **Circular dependency detection** - Helpful error messages instead of infinite loops
17
+ - **TypeScript support** - Full type definitions and TypeScript file execution via tsx/ts-node
26
18
 
27
19
  ## Table of Contents
20
+
28
21
  <!-- doc-gen {TOC} collapse=true collapseText="Click to expand" -->
29
22
  <details>
30
23
  <summary>Click to expand</summary>
31
24
 
32
- - [About](#about)
33
- - [Usage](#usage)
34
- - [How it works](#how-it-works)
25
+ - [Getting Started](#getting-started)
26
+ - [Installation](#installation)
27
+ - [Quick Start](#quick-start)
28
+ - [Running Examples](#running-examples)
29
+ - [How It Works](#how-it-works)
30
+ - [Resolution Flow](#resolution-flow)
31
+ - [Analyzing Without Resolving](#analyzing-without-resolving)
32
+ - [Getting Metadata](#getting-metadata)
35
33
  - [Variable Sources](#variable-sources)
36
- - [Environment variables](#environment-variables)
37
- - [CLI option flags](#cli-option-flags)
38
- - [Parameter values](#parameter-values)
39
- - [Self references](#self-references)
40
- - [File references](#file-references)
41
- - [Sync/Async file references](#syncasync-file-references)
42
- - [Passing arguments to functions](#passing-arguments-to-functions)
43
- - [ConfigContext](#configcontext)
44
- - [Functions without arguments](#functions-without-arguments)
45
- - [TypeScript file references](#typescript-file-references)
46
- - [Git references](#git-references)
34
+ - [Environment Variables](#environment-variables)
35
+ - [CLI Option Flags](#cli-option-flags)
36
+ - [Parameter Values](#parameter-values)
37
+ - [Self References](#self-references)
38
+ - [File References](#file-references)
39
+ - [Sync/Async File References](#syncasync-file-references)
40
+ - [TypeScript File References](#typescript-file-references)
41
+ - [Terraform HCL Support](#terraform-hcl-support)
42
+ - [Git References](#git-references)
47
43
  - [Cron Values](#cron-values)
48
- - [Eval expressions](#eval-expressions)
49
- - [If expressions](#if-expressions)
50
- - [Filters (experimental)](#filters-experimental)
51
- - [Functions (experimental)](#functions-experimental)
52
- - [More Examples](#more-examples)
53
- - [Custom Variable Sources](#custom-variable-sources)
54
- - [Variable Source Types](#variable-source-types)
55
- - [Options](#options)
44
+ - [Eval Expressions](#eval-expressions)
45
+ - [If Expressions](#if-expressions)
46
+ - [Filters (Experimental)](#filters-experimental)
47
+ - [Functions (Experimental)](#functions-experimental)
48
+ - [API Reference](#api-reference)
49
+ - [Async API](#async-api)
50
+ - [Sync API](#sync-api)
51
+ - [Analyze API](#analyze-api)
52
+ - [Format Utilities](#format-utilities)
53
+ - [Configuration Options](#configuration-options)
56
54
  - [Custom Variable Syntax](#custom-variable-syntax)
57
55
  - [allowUnknownVariableTypes](#allowunknownvariabletypes)
58
56
  - [allowUnresolvedVariables](#allowunresolvedvariables)
57
+ - [Complete Options Reference](#complete-options-reference)
58
+ - [Custom Variable Sources](#custom-variable-sources)
59
+ - [Variable Source Types](#variable-source-types)
60
+ - [Creating a Custom Resolver](#creating-a-custom-resolver)
61
+ - [CLI Usage](#cli-usage)
62
+ - [Basic Commands](#basic-commands)
63
+ - [Command Options](#command-options)
64
+ - [Examples](#cli-examples)
65
+ - [Testing](#testing)
66
+ - [Running Tests](#running-tests)
67
+ - [Test Structure](#test-structure)
68
+ - [Writing Tests](#writing-tests)
69
+ - [Deployment](#deployment)
70
+ - [Using with Serverless Framework](#using-with-serverless-framework)
71
+ - [Docker Deployment](#docker-deployment)
72
+ - [CI/CD Integration](#cicd-integration)
73
+ - [Troubleshooting](#troubleshooting)
74
+ - [Common Issues](#common-issues)
75
+ - [Debug Mode](#debug-mode)
76
+ - [Circular Dependencies](#circular-dependencies)
59
77
  - [FAQ](#faq)
60
- - [Whats new](#whats-new)
61
- - [Alt libs](#alt-libs)
78
+ - [Advanced Usage](#advanced-usage)
79
+ - [Multi-Stage Resolution](#multi-stage-resolution)
80
+ - [Function Arguments and Context](#function-arguments-and-context)
81
+ - [Programmatic Usage](#programmatic-usage)
82
+ - [What's New](#whats-new)
83
+ - [Alternative Libraries](#alternative-libraries)
62
84
  - [Inspiration](#inspiration)
63
85
 
64
86
  </details>
65
87
  <!-- end-doc-gen -->
66
88
 
67
- ## Usage
89
+ ---
90
+
91
+ ## Getting Started
92
+
93
+ ### Installation
94
+
95
+ **As a library dependency:**
96
+
97
+ ```bash
98
+ npm install configorama
99
+ ```
100
+
101
+ **As a global CLI tool:**
102
+
103
+ ```bash
104
+ npm install -g configorama
105
+ ```
106
+
107
+ ### Quick Start
68
108
 
69
- Async API:
109
+ **Async API (recommended for most use cases):**
70
110
 
71
- ```js
111
+ ```javascript
72
112
  const path = require('path')
73
113
  const configorama = require('configorama')
74
114
  const cliFlags = require('minimist')(process.argv.slice(2))
75
115
 
76
116
  // Path to yaml/json/toml config
77
117
  const myConfigFilePath = path.join(__dirname, 'config.yml')
78
- // Execute config resolution asyncronously
118
+
119
+ // Execute config resolution asynchronously
79
120
  const config = await configorama(myConfigFilePath, { options: cliFlags })
121
+
80
122
  console.log(config) // resolved config
81
123
  ```
82
124
 
83
- Sync API:
125
+ **Sync API (for synchronous execution contexts):**
84
126
 
85
- ```js
127
+ ```javascript
86
128
  const path = require('path')
87
129
  const configorama = require('configorama')
88
130
  const cliFlags = require('minimist')(process.argv.slice(2))
89
131
 
90
132
  // Path to yaml/json/toml config
91
133
  const myConfigFilePath = path.join(__dirname, 'config.yml')
92
- // Execute config resolution syncronously
134
+
135
+ // Execute config resolution synchronously
93
136
  const config = configorama.sync(myConfigFilePath, { options: cliFlags })
137
+
94
138
  console.log(config) // resolved config
95
139
  ```
96
140
 
97
- ## How it works
141
+ **Example configuration file (`config.yml`):**
142
+
143
+ ```yaml
144
+ # Environment variable
145
+ apiKey: ${env:API_KEY}
146
+
147
+ # CLI option (e.g., --stage prod)
148
+ environment: ${opt:stage, 'dev'}
149
+
150
+ # Self-reference to other values
151
+ service: my-app
152
+ fullName: ${service}-api
153
+
154
+ # File reference
155
+ secrets: ${file(./secrets.yml)}
156
+
157
+ # Git information
158
+ branch: ${git:branch}
159
+ commit: ${git:sha1}
160
+
161
+ # Conditional logic
162
+ memorySize: ${if(${environment} === 'prod' ? 1024 : 512)}
163
+
164
+ # Nested references
165
+ database:
166
+ host: ${env:DB_HOST, 'localhost'}
167
+ port: ${env:DB_PORT, 5432}
168
+ name: ${service}-${environment}
169
+ ```
170
+
171
+ ### Running Examples
98
172
 
99
- Configorama creates a graph of your config file and all its dependencies, then it resolves the value based on it's variable sources. If `returnMetadata` option is set, you can see the entire graph and all file dependencies.
173
+ The project includes example files demonstrating various features:
174
+
175
+ ```bash
176
+ # Clone the repository
177
+ git clone https://github.com/DavidWells/configorama
178
+ cd configorama
179
+
180
+ # Install dependencies
181
+ npm install
182
+
183
+ # Run async API example
184
+ node examples/using-async-api.js --stage prod
185
+
186
+ # Run sync API example
187
+ node examples/using-sync-api.js --stage dev
188
+
189
+ # Run zero-config example
190
+ node examples/zero-config.js
191
+
192
+ # Run TypeScript example
193
+ node examples/typescript/using-typescript.js
194
+ ```
195
+
196
+ ---
197
+
198
+ ## How It Works
199
+
200
+ ### Resolution Flow
201
+
202
+ Configorama creates a dependency graph of your config file and all its dependencies, then resolves values based on their variable sources. The resolution process follows this flow:
100
203
 
101
204
  ```mermaid
102
205
  flowchart TD
@@ -110,77 +213,194 @@ flowchart TD
110
213
  H --> I[Return resolved config]
111
214
  ```
112
215
 
113
- **Analyze config without resolving:**
216
+ **Resolution process:**
114
217
 
115
- ```js
218
+ 1. **Load** - Read config file from disk or accept JavaScript object
219
+ 2. **Parse** - Convert to JavaScript object (format auto-detected by extension)
220
+ 3. **Preprocess** - Identify all variables and build dependency graph
221
+ 4. **Traverse** - Recursively resolve variables in dependency order
222
+ 5. **Post-process** - Apply filters and functions
223
+ 6. **Return** - Fully resolved configuration object
224
+
225
+ ### Analyzing Without Resolving
226
+
227
+ Analyze config structure and variables without actually resolving them:
228
+
229
+ ```javascript
116
230
  const result = await configorama.analyze('config.yml')
117
231
 
118
232
  // Returns metadata about variables without resolving them
119
- console.log(result.originalConfig) // Raw config object
120
- console.log(result.variables) // All variables found
121
- console.log(result.uniqueVariables) // Variables grouped by name
233
+ console.log(result.originalConfig) // Raw config object
234
+ console.log(result.variables) // All variables found
235
+ console.log(result.uniqueVariables) // Variables grouped by name
122
236
  console.log(result.fileDependencies) // File references found
123
237
  ```
124
238
 
125
- **Resolve config and return metadata:**
239
+ **Use cases:**
240
+ - Validate config structure before deployment
241
+ - Generate documentation of required environment variables
242
+ - Build dependency graphs for complex configs
243
+ - Audit what external resources a config depends on
244
+
245
+ ### Getting Metadata
126
246
 
127
- ```js
247
+ Resolve config and get detailed metadata about the resolution process:
248
+
249
+ ```javascript
128
250
  const result = await configorama('config.yml', {
129
251
  returnMetadata: true,
130
- // Example option for ${opt:stage}
131
252
  options: { stage: 'prod' }
132
253
  })
133
254
 
134
255
  // Returns both resolved config and metadata
135
- console.log(result.originalConfig) // Raw config object
136
- console.log(result.config) // Fully resolved config
137
- console.log(result.metadata.variables) // Variable info with resolution details
256
+ console.log(result.config) // Fully resolved config
257
+ console.log(result.originalConfig) // Raw config object
258
+ console.log(result.metadata.variables) // Variable info with resolution details
138
259
  console.log(result.metadata.fileDependencies) // All file dependencies
139
- console.log(result.metadata.summary) // { totalVariables, requiredVariables, variablesWithDefaults }
140
- console.log(result.resolutionHistory) // Step-by-step resolution for each path
260
+ console.log(result.metadata.summary) // { totalVariables, requiredVariables, variablesWithDefaults }
261
+ console.log(result.resolutionHistory) // Step-by-step resolution for each path
262
+ ```
263
+
264
+ **Metadata structure:**
265
+
266
+ ```javascript
267
+ {
268
+ config: { /* resolved config */ },
269
+ originalConfig: { /* raw config */ },
270
+ metadata: {
271
+ variables: [
272
+ {
273
+ variable: '${env:API_KEY}',
274
+ variableType: 'env',
275
+ variableName: 'API_KEY',
276
+ variablePath: 'apiKey',
277
+ defaultValue: null,
278
+ hasDefault: false,
279
+ resolved: true,
280
+ resolvedValue: 'secret-key-123'
281
+ },
282
+ // ... more variables
283
+ ],
284
+ summary: {
285
+ totalVariables: 15,
286
+ requiredVariables: 8,
287
+ variablesWithDefaults: 7
288
+ },
289
+ fileDependencies: ['./secrets.yml', './config.ts']
290
+ },
291
+ resolutionHistory: {
292
+ 'apiKey': [
293
+ { step: 1, value: '${env:API_KEY}', type: 'env' },
294
+ { step: 2, value: 'secret-key-123', resolved: true }
295
+ ]
296
+ }
297
+ }
141
298
  ```
142
299
 
300
+ ---
301
+
143
302
  ## Variable Sources
144
303
 
145
- | Variable | Syntax | Description |
146
- |----------|-----------------------|------------------------|
147
- | env | ${env:VAR} | Environment variables |
148
- | opt | ${opt:flag} | CLI option flags |
149
- | param | ${param:key} | Parameter values |
150
- | self | ${key} or ${self:key} | Self references |
151
- | file | ${file(path)} | File references |
152
- | git | ${git:value} | Git data |
153
- | cron | ${cron(expr)} | Cron expressions |
154
- | eval | ${eval(expr)} | Math/logic expressions |
155
- | if | ${if(expr)} | Conditional expressions |
304
+ Configorama supports multiple variable sources out of the box. All variable syntax follows the pattern `${type:value}` or `${type(value)}`.
305
+
306
+ ### Summary Table
156
307
 
157
- ### Environment variables
308
+ | Variable | Syntax | Description | Example |
309
+ |----------|-----------------------|------------------------|---------|
310
+ | env | `${env:VAR}` | Environment variables | `${env:NODE_ENV}` |
311
+ | opt | `${opt:flag}` | CLI option flags | `${opt:stage}` |
312
+ | param | `${param:key}` | Parameter values | `${param:domain}` |
313
+ | self | `${key}` or `${self:key}` | Self references | `${database.host}` |
314
+ | file | `${file(path)}` | File references | `${file(./secrets.yml)}` |
315
+ | text | `${text(path)}` | Raw text file | `${text(./README.md)}` |
316
+ | git | `${git:value}` | Git data | `${git:branch}` |
317
+ | cron | `${cron(expr)}` | Cron expressions | `${cron('every 5 minutes')}` |
318
+ | eval | `${eval(expr)}` | Math/logic expressions | `${eval(10 + 5)}` |
319
+ | if | `${if(expr)}` | Conditional expressions| `${if(x > 5 ? 'yes' : 'no')}` |
320
+
321
+ ---
322
+
323
+ ### Environment Variables
158
324
 
159
325
  Access values from `process.env` environment variables.
160
326
 
161
- ```yml
327
+ ```yaml
328
+ # Basic env var
162
329
  apiKey: ${env:SECRET_KEY}
163
330
 
164
- # Fallback to default value if env var not found
331
+ # With fallback default if env var not found
165
332
  apiKeyWithFallback: ${env:SECRET_KEY, 'defaultApiKey'}
333
+
334
+ # Common patterns
335
+ nodeEnv: ${env:NODE_ENV, 'development'}
336
+ port: ${env:PORT, 3000}
337
+ debug: ${env:DEBUG, false}
338
+ ```
339
+
340
+ **How it works:**
341
+ - Reads from `process.env` at resolution time
342
+ - Supports default values with comma syntax
343
+ - Throws error if env var not found and no default provided (unless `allowUnresolvedVariables` is set)
344
+
345
+ **CLI usage:**
346
+
347
+ ```bash
348
+ # Set env var then run
349
+ SECRET_KEY=abc123 node app.js
350
+
351
+ # Or export first
352
+ export SECRET_KEY=abc123
353
+ node app.js
166
354
  ```
167
355
 
168
- ### CLI option flags
356
+ ---
357
+
358
+ ### CLI Option Flags
169
359
 
170
360
  Access values from command line arguments passed via the `options` parameter.
171
361
 
172
- ```yml
362
+ ```yaml
173
363
  # CLI option. Example `cmd --stage dev` makes `bar: dev`
174
364
  bar: ${opt:stage}
175
365
 
176
366
  # Composed example makes `foo: dev-hello`
177
367
  foo: ${opt:stage}-hello
178
368
 
179
- # You can also provide a default value. If no --stage flag is provided, it will use 'dev'
180
- foo: ${opt:stage, 'dev'}
369
+ # With default value. If no --stage flag, uses 'dev'
370
+ environment: ${opt:stage, 'dev'}
371
+
372
+ # Boolean flags
373
+ verbose: ${opt:verbose, false}
374
+
375
+ # Nested paths
376
+ region: ${opt:aws.region, 'us-east-1'}
377
+ ```
378
+
379
+ **How it works:**
380
+ - Reads from the `options` object passed to configorama
381
+ - Typically populated from CLI args using `minimist` or similar parser
382
+ - Supports dot-notation for nested option paths
383
+
384
+ **Example:**
385
+
386
+ ```javascript
387
+ const minimist = require('minimist')
388
+ const configorama = require('configorama')
389
+
390
+ const argv = minimist(process.argv.slice(2))
391
+ // argv = { stage: 'prod', verbose: true, aws: { region: 'eu-west-1' } }
392
+
393
+ const config = await configorama('config.yml', { options: argv })
181
394
  ```
182
395
 
183
- ### Parameter values
396
+ ```bash
397
+ # Command line
398
+ node app.js --stage prod --verbose --aws.region eu-west-1
399
+ ```
400
+
401
+ ---
402
+
403
+ ### Parameter Values
184
404
 
185
405
  Access parameter values via `${param:key}`. Parameters follow a resolution hierarchy:
186
406
 
@@ -188,7 +408,7 @@ Access parameter values via `${param:key}`. Parameters follow a resolution hiera
188
408
  2. **Stage-specific params** (`stages.<stage>.params`)
189
409
  3. **Default params** (`stages.default.params`)
190
410
 
191
- ```yml
411
+ ```yaml
192
412
  # Direct parameter reference
193
413
  appDomain: ${param:domain}
194
414
 
@@ -219,11 +439,14 @@ node app.js --param="domain=example.com"
219
439
 
220
440
  # Multiple params
221
441
  node app.js --param="domain=example.com" --param="apiKey=secret123"
442
+
443
+ # With stage selection
444
+ node app.js --stage prod --param="domain=cli-override.com"
222
445
  ```
223
446
 
224
447
  **Code Usage:**
225
448
 
226
- ```js
449
+ ```javascript
227
450
  const config = await configorama('config.yml', {
228
451
  options: {
229
452
  stage: 'prod',
@@ -232,11 +455,41 @@ const config = await configorama('config.yml', {
232
455
  })
233
456
  ```
234
457
 
235
- ### Self references
458
+ **Resolution order example:**
459
+
460
+ ```yaml
461
+ stages:
462
+ prod:
463
+ params:
464
+ domain: prod.myapp.com # 2. Stage-specific
465
+ default:
466
+ params:
467
+ domain: default.myapp.com # 3. Default fallback
468
+
469
+ appUrl: ${param:domain}
470
+ ```
471
+
472
+ ```bash
473
+ # CLI override (highest priority)
474
+ node app.js --stage prod --param="domain=cli.myapp.com"
475
+ # Result: appUrl = 'cli.myapp.com'
476
+
477
+ # Stage param (no CLI override)
478
+ node app.js --stage prod
479
+ # Result: appUrl = 'prod.myapp.com'
480
+
481
+ # Default param (no CLI override, no stage match)
482
+ node app.js --stage staging
483
+ # Result: appUrl = 'default.myapp.com'
484
+ ```
485
+
486
+ ---
487
+
488
+ ### Self References
236
489
 
237
- Reference values from other key paths in the same configuration file.
490
+ Reference values from other key paths in the same configuration file using dot notation.
238
491
 
239
- ```yml
492
+ ```yaml
240
493
  foo: bar
241
494
 
242
495
  zaz:
@@ -244,35 +497,66 @@ zaz:
244
497
  wow:
245
498
  cool: 2
246
499
 
247
- # Shorthand dot.prop reference.
248
- two: ${foo} # Resolves to `bar`
500
+ # Shorthand dot.prop reference
501
+ two: ${foo} # Resolves to 'bar'
249
502
 
250
- # Longer more explicit self file reference.
251
- one: ${self:foo} # Resolves to `bar`
503
+ # Explicit self file reference
504
+ one: ${self:foo} # Resolves to 'bar'
252
505
 
253
- # Dot prop reference will traverse the object.
254
- three: ${zaz.wow.cool} # Resolves to `2`
506
+ # Dot prop reference traverses objects
507
+ three: ${zaz.wow.cool} # Resolves to 2
508
+
509
+ # Complex nested references
510
+ database:
511
+ host: localhost
512
+ port: 5432
513
+ name: mydb
514
+
515
+ connectionString: postgres://${database.host}:${database.port}/${database.name}
516
+ # Resolves to: postgres://localhost:5432/mydb
517
+
518
+ # Array access
519
+ items:
520
+ - first
521
+ - second
522
+ - third
523
+
524
+ selectedItem: ${items[1]} # Resolves to 'second'
255
525
  ```
256
526
 
257
- ### File references
527
+ **How it works:**
528
+ - Uses dot-notation for nested object access
529
+ - Supports array index access with bracket notation
530
+ - Resolves in dependency order (referenced values resolved first)
531
+ - Detects circular references and throws helpful errors
258
532
 
259
- Import values from external yml, json, toml, or hcl files by relative path.
533
+ ---
260
534
 
261
- ```yml
535
+ ### File References
536
+
537
+ Import values from external yml, json, toml, hcl, or other supported files by relative path.
538
+
539
+ ```yaml
262
540
  # Import full yml/json/toml/hcl file via relative path
263
541
  fileRef: ${file(./subFile.yml)}
264
542
 
265
- # Import sub values from files. This imports other-config.yml `topLevel:` value
543
+ # Import sub values from files (topLevel key from other-config.yml)
266
544
  fileValue: ${file(./other-config.yml):topLevel}
267
545
 
268
- # Import sub values from files. This imports other-config.json `nested.value` value
546
+ # Import nested sub values (nested.value from other-config.json)
269
547
  fileValueSubKey: ${file(./other-config.json):nested.value}
270
548
 
271
549
  # Fallback to default value if file not found
272
550
  fallbackValueExample: ${file(./not-found.yml), 'fall back value'}
551
+
552
+ # Relative paths from config file location
553
+ secrets: ${file(../shared/secrets.yml)}
554
+
555
+ # Import from subdirectory
556
+ dbConfig: ${file(./config/database.yml):production}
273
557
  ```
274
558
 
275
- Supported file types (extensions are case-insensitive):
559
+ **Supported file types (extensions are case-insensitive):**
276
560
 
277
561
  | Type | Extensions |
278
562
  |------|------------|
@@ -283,33 +567,82 @@ Supported file types (extensions are case-insensitive):
283
567
  | TOML | `.toml`, `.tml` |
284
568
  | INI | `.ini` |
285
569
  | JSON | `.json`, `.json5`, `.jsonc` |
570
+ | HCL (Terraform) | `.tf`, `.hcl`, `.tf.json` |
286
571
  | Markdown | `.md`, `.mdx`, `.markdown`, `.mdown`, `.mkdn`, `.mkd` |
287
572
 
288
- ### Sync/Async file references
573
+ **Path resolution:**
574
+ - Relative paths resolved from config file's directory
575
+ - Absolute paths supported
576
+ - `~` home directory expansion NOT supported (use absolute paths)
289
577
 
290
- Execute JavaScript files and use their exported function's return value.
578
+ **Example file structure:**
291
579
 
292
- ```yml
580
+ ```text
581
+ project/
582
+ ├── config.yml # Main config
583
+ ├── secrets.yml # Secrets file
584
+ └── environments/
585
+ ├── dev.yml
586
+ └── prod.yml
587
+ ```
588
+
589
+ ```yaml
590
+ # config.yml
591
+ secrets: ${file(./secrets.yml)}
592
+ environment: ${file(./environments/${opt:stage}.yml)}
593
+ ```
594
+
595
+ ---
596
+
597
+ ### Sync/Async File References
598
+
599
+ Execute JavaScript files and use their exported function's return value. Functions can be synchronous or asynchronous and receive arguments from your config.
600
+
601
+ ```yaml
602
+ # Async function execution
293
603
  asyncJSValue: ${file(./async-value.js)}
294
- # resolves to 'asyncval'
604
+
605
+ # Sync function execution
606
+ syncJSValue: ${file(./sync-value.js)}
607
+
608
+ # With arguments (resolved before being passed)
609
+ secrets: ${file(./fetch-secrets.js, ${self:environment}, ${self:region})}
295
610
  ```
296
611
 
297
- `${file(./async-value.js)}` will call into `async-value` and run/resolve the async function. Return values can be strings, objects, arrays, etc.
612
+ **JavaScript file example (`async-value.js`):**
298
613
 
299
- ```js
614
+ ```javascript
300
615
  async function fetchSecretsFromRemoteStore() {
301
- await delay(1000)
302
- return 'asyncval'
616
+ // Simulate async operation (AWS Secrets Manager, HashiCorp Vault, etc.)
617
+ await new Promise(resolve => setTimeout(resolve, 1000))
618
+ return {
619
+ apiKey: 'secret-key-123',
620
+ dbPassword: 'db-password-456'
621
+ }
303
622
  }
304
623
 
305
624
  module.exports = fetchSecretsFromRemoteStore
306
625
  ```
307
626
 
308
- #### Passing arguments to functions
627
+ **Sync function example (`sync-value.js`):**
628
+
629
+ ```javascript
630
+ function getEnvironmentConfig() {
631
+ return {
632
+ timeout: 5000,
633
+ retries: 3,
634
+ logLevel: process.env.NODE_ENV === 'production' ? 'error' : 'debug'
635
+ }
636
+ }
637
+
638
+ module.exports = getEnvironmentConfig
639
+ ```
640
+
641
+ #### Passing Arguments to Functions
309
642
 
310
- You can pass arguments from your config to JavaScript/TypeScript functions:
643
+ You can pass resolved values from your config as arguments to JavaScript/TypeScript functions:
311
644
 
312
- ```yml
645
+ ```yaml
313
646
  foo: bar
314
647
  baz:
315
648
  qux: quux
@@ -320,7 +653,7 @@ secrets: ${file(./fetch-secrets.js, ${self:foo}, ${self:baz})}
320
653
 
321
654
  Arguments are passed in order, with the config context always last:
322
655
 
323
- ```js
656
+ ```javascript
324
657
  /**
325
658
  * @param {string} foo - First arg from YAML ('bar')
326
659
  * @param {object} baz - Second arg from YAML ({ qux: 'quux' })
@@ -329,6 +662,8 @@ Arguments are passed in order, with the config context always last:
329
662
  async function fetchSecrets(foo, baz, ctx) {
330
663
  console.log(foo) // 'bar'
331
664
  console.log(baz) // { qux: 'quux' }
665
+
666
+ // Access config context
332
667
  console.log(ctx.originalConfig) // Original unresolved config
333
668
  console.log(ctx.currentConfig) // Current partially-resolved config
334
669
  console.log(ctx.options) // Options passed to configorama
@@ -349,7 +684,7 @@ The `ctx` parameter (always the last argument) provides access to:
349
684
  | `currentConfig` | The current (partially resolved) configuration |
350
685
  | `options` | Options passed to configorama (populates `${opt:xyz}` variables) |
351
686
 
352
- TypeScript users can import the type:
687
+ **TypeScript users can import the type:**
353
688
 
354
689
  ```typescript
355
690
  import type { ConfigContext } from 'configorama'
@@ -366,11 +701,11 @@ async function fetchSecrets(
366
701
  export = fetchSecrets
367
702
  ```
368
703
 
369
- #### Functions without arguments
704
+ #### Functions Without Arguments
370
705
 
371
706
  If you don't need arguments, the function still receives `ctx` as its only parameter:
372
707
 
373
- ```js
708
+ ```javascript
374
709
  // No args - ctx is the only parameter
375
710
  async function getSecrets(ctx) {
376
711
  return ctx.options.stage === 'prod'
@@ -381,11 +716,25 @@ async function getSecrets(ctx) {
381
716
  module.exports = getSecrets
382
717
  ```
383
718
 
384
- ### TypeScript file references
719
+ ---
720
+
721
+ ### TypeScript File References
385
722
 
386
723
  Execute TypeScript files using tsx (recommended) or ts-node.
387
724
 
388
- ```yml
725
+ **Installation:**
726
+
727
+ ```bash
728
+ # Recommended: Modern, fast TypeScript execution
729
+ npm install tsx --save-dev
730
+
731
+ # Alternative: Traditional ts-node approach
732
+ npm install ts-node typescript --save-dev
733
+ ```
734
+
735
+ **Usage in config:**
736
+
737
+ ```yaml
389
738
  # TypeScript configuration object
390
739
  config: ${file(./config.ts)}
391
740
 
@@ -394,52 +743,54 @@ secrets: ${file(./async-secrets.ts)}
394
743
 
395
744
  # Specific property from TypeScript export
396
745
  database: ${file(./config.ts):database}
746
+
747
+ # With arguments
748
+ apiConfig: ${file(./config.ts, ${opt:stage})}
397
749
  ```
398
750
 
399
- **TypeScript Object Export:**
751
+ **TypeScript Object Export (`typescript-config.ts`):**
400
752
 
401
753
  ```typescript
402
- /* typescript-config.ts */
403
754
  interface DatabaseConfig {
404
- host: string;
405
- port: number;
406
- database: string;
407
- ssl: boolean;
755
+ host: string
756
+ port: number
757
+ database: string
758
+ ssl: boolean
408
759
  }
409
760
 
410
761
  interface ApiConfig {
411
- baseUrl: string;
412
- timeout: number;
413
- retries: number;
762
+ baseUrl: string
763
+ timeout: number
764
+ retries: number
414
765
  }
415
766
 
416
767
  interface ConfigObject {
417
- environment: string;
418
- database: DatabaseConfig;
419
- api: ApiConfig;
768
+ environment: string
769
+ database: DatabaseConfig
770
+ api: ApiConfig
420
771
  features: {
421
- enableNewFeature: boolean;
422
- debugMode: boolean;
423
- };
772
+ enableNewFeature: boolean
773
+ debugMode: boolean
774
+ }
424
775
  }
425
776
 
426
777
  function createConfig(): ConfigObject {
427
778
  return {
428
- environment: '${opt:stage, "development"}',
779
+ environment: process.env.STAGE || 'development',
429
780
  database: {
430
- host: '${env:DB_HOST, "localhost"}',
431
- port: parseInt('${env:DB_PORT, "5432"}'),
432
- database: '${env:DB_NAME, "myapp"}',
433
- ssl: '${env:NODE_ENV}' === 'production'
781
+ host: process.env.DB_HOST || 'localhost',
782
+ port: parseInt(process.env.DB_PORT || '5432'),
783
+ database: process.env.DB_NAME || 'myapp',
784
+ ssl: process.env.NODE_ENV === 'production'
434
785
  },
435
786
  api: {
436
- baseUrl: '${env:API_BASE_URL, "http://localhost:3000"}',
787
+ baseUrl: process.env.API_BASE_URL || 'http://localhost:3000',
437
788
  timeout: 5000,
438
789
  retries: 3
439
790
  },
440
791
  features: {
441
- enableNewFeature: '${opt:stage}' === 'production',
442
- debugMode: '${env:DEBUG, "false"}' === 'true'
792
+ enableNewFeature: process.env.STAGE === 'production',
793
+ debugMode: process.env.DEBUG === 'true'
443
794
  }
444
795
  }
445
796
  }
@@ -447,14 +798,13 @@ function createConfig(): ConfigObject {
447
798
  export = createConfig
448
799
  ```
449
800
 
450
- **TypeScript Async Function:**
801
+ **TypeScript Async Function (`typescript-async.ts`):**
451
802
 
452
803
  ```typescript
453
- /* typescript-async.ts */
454
804
  interface SecretStore {
455
- apiKey: string;
456
- dbPassword: string;
457
- jwtSecret: string;
805
+ apiKey: string
806
+ dbPassword: string
807
+ jwtSecret: string
458
808
  }
459
809
 
460
810
  function delay(ms: number): Promise<void> {
@@ -463,10 +813,10 @@ function delay(ms: number): Promise<void> {
463
813
 
464
814
  async function fetchSecretsFromVault(): Promise<SecretStore> {
465
815
  console.log('Fetching secrets from vault...')
466
-
467
- // Simulate async operations like fetching from AWS Secrets Manager, HashiCorp Vault, etc.
816
+
817
+ // Simulate async operations (AWS Secrets Manager, HashiCorp Vault, etc.)
468
818
  await delay(100)
469
-
819
+
470
820
  return {
471
821
  apiKey: process.env.API_KEY || 'dev-api-key',
472
822
  dbPassword: process.env.DB_PASSWORD || 'dev-password',
@@ -479,7 +829,7 @@ export = fetchSecretsFromVault
479
829
 
480
830
  **Complete Example Configuration:**
481
831
 
482
- ```yml
832
+ ```yaml
483
833
  # config-with-typescript.yml
484
834
  service: my-awesome-app
485
835
 
@@ -493,10 +843,10 @@ secrets: ${file(./typescript-async.ts)}
493
843
  custom:
494
844
  stage: ${opt:stage, "dev"}
495
845
  region: ${opt:region, "us-east-1"}
496
-
497
- # You can also use TypeScript files for specific sections
846
+
847
+ # Use TypeScript files for specific sections
498
848
  databaseConfig: ${file(./typescript-config.ts):database}
499
-
849
+
500
850
  # Environment-specific overrides
501
851
  stageVariables:
502
852
  dev:
@@ -507,8 +857,7 @@ custom:
507
857
  # Regular configuration values
508
858
  resources:
509
859
  description: "Configuration loaded with TypeScript support"
510
- timestamp: ${timestamp}
511
-
860
+
512
861
  functions:
513
862
  hello:
514
863
  handler: handler.hello
@@ -518,26 +867,16 @@ functions:
518
867
  API_KEY: ${self:secrets.apiKey}
519
868
  ```
520
869
 
521
- **Installation Requirements:**
522
-
523
- TypeScript support requires either `tsx` (recommended) or `ts-node`:
524
-
525
- ```bash
526
- # Recommended: Modern, fast TypeScript execution
527
- npm install tsx --save-dev
528
-
529
- # Alternative: Traditional ts-node approach
530
- npm install ts-node typescript --save-dev
531
- ```
532
-
533
870
  **Features:**
534
871
  - Modern tsx execution (fast, no compilation) with ts-node fallback
535
872
  - Support for both sync and async TypeScript functions
536
- - Function argument passing via `dynamicArgs`
873
+ - Function argument passing via config variables
537
874
  - Full TypeScript interface support
538
875
  - Comprehensive error handling with helpful dependency messages
539
876
 
540
- ### Terraform HCL support
877
+ ---
878
+
879
+ ### Terraform HCL Support
541
880
 
542
881
  Configorama supports Terraform HCL (HashiCorp Configuration Language) files, allowing you to parse `.tf`, `.tf.json`, and `.hcl` files.
543
882
 
@@ -556,7 +895,7 @@ npm install @cdktf/hcl2json
556
895
 
557
896
  **Example:**
558
897
 
559
- ```js
898
+ ```javascript
560
899
  const configorama = require('configorama')
561
900
 
562
901
  // Parse a Terraform configuration file
@@ -571,7 +910,7 @@ console.log(terraformConfig.output) // Outputs
571
910
 
572
911
  **Importing Terraform files:**
573
912
 
574
- ```yml
913
+ ```yaml
575
914
  # Import Terraform variables from a .tf file
576
915
  terraformVars: ${file(./terraform/variables.tf)}
577
916
 
@@ -580,9 +919,10 @@ region: ${file(./terraform/variables.tf):variable.region[0].default}
580
919
  ```
581
920
 
582
921
  **Variable syntax:**
922
+
583
923
  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.
584
924
 
585
- ```js
925
+ ```javascript
586
926
  // Loading .tf directly - uses $[...] syntax automatically
587
927
  const config = await configorama('./main.tf')
588
928
  // config.locals[0].app_name = "myapp-${var.environment}" (preserved)
@@ -594,23 +934,26 @@ const config = await configorama('./main.tf')
594
934
 
595
935
  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:
596
936
 
597
- ```js
937
+ ```javascript
598
938
  const config = await configorama('./config.yml', {
599
939
  allowUnknownVariableTypes: true
600
940
  })
601
941
  ```
602
942
 
603
943
  **Read-only support:**
944
+
604
945
  Currently, HCL files can be read and parsed, but writing/generating HCL files is not supported.
605
946
 
606
947
  See [tests/hclTests](./tests/hclTests) for example Terraform files.
607
948
 
608
- ### Git references
949
+ ---
950
+
951
+ ### Git References
609
952
 
610
953
  Access repository information from the current working directory's git data.
611
954
 
612
955
  <!-- doc-gen CODE src=tests/gitVariables/gitVariables.yml -->
613
- ```yml
956
+ ```yaml
614
957
  ########################
615
958
  # Git Variables
616
959
  ########################
@@ -662,12 +1005,18 @@ gitTimestampAbsolutePath: ${git:timestamp('package.json')}
662
1005
  ```
663
1006
  <!-- end-doc-gen -->
664
1007
 
1008
+ **How it works:**
1009
+ - Reads git data from `.git` directory in current working directory or parent directories
1010
+ - Executes git commands via child process
1011
+ - Throws error if not in a git repository
1012
+
1013
+ ---
665
1014
 
666
1015
  ### Cron Values
667
1016
 
668
1017
  Convert human-readable time expressions into standard cron syntax.
669
1018
 
670
- ```yml
1019
+ ```yaml
671
1020
  # Basic patterns
672
1021
  everyMinute: ${cron('every minute')} # * * * * *
673
1022
  everyHour: ${cron('every hour')} # 0 * * * *
@@ -680,7 +1029,7 @@ noon: ${cron('noon')} # 0 12 * * *
680
1029
  every5Minutes: ${cron('every 5 minutes')} # */5 * * * *
681
1030
  every15Minutes: ${cron('every 15 minutes')} # */15 * * * *
682
1031
  every2Hours: ${cron('every 2 hours')} # 0 */2 * * *
683
- every3Days: ${cron('every 3 days')} # 0 0 */3 * * *
1032
+ every3Days: ${cron('every 3 days')} # 0 0 */3 * *
684
1033
 
685
1034
  # Specific times
686
1035
  at930: ${cron('at 9:30')} # 30 9 * * *
@@ -693,56 +1042,92 @@ mondayMorning: ${cron('on monday at 9:00')} # 0 9 * * 1
693
1042
  fridayEvening: ${cron('on friday at 17:00')} # 0 17 * * 5
694
1043
  sundayNoon: ${cron('on sunday at 12:00')} # 0 12 * * 0
695
1044
 
696
- # Pre-existing cron expressions
1045
+ # Pre-existing cron expressions (pass through)
697
1046
  customCron: ${cron('15 2 * * *')} # 15 2 * * *
698
1047
  ```
699
1048
 
700
- ### Eval expressions
1049
+ **Supported expressions:**
1050
+ - `every N minutes/hours/days`
1051
+ - `at HH:MM [am/pm]`
1052
+ - `on [weekday] at HH:MM`
1053
+ - `midnight`, `noon`, `weekdays`
1054
+ - Standard cron syntax (passed through unchanged)
1055
+
1056
+ ---
701
1057
 
702
- Evaluate mathematical and logical expressions safely (without using JavaScript's `eval`).
1058
+ ### Eval Expressions
703
1059
 
704
- ```yml
1060
+ Evaluate mathematical and logical expressions safely (without using JavaScript's `eval`). Uses the `subscript` library for safe expression evaluation.
1061
+
1062
+ ```yaml
705
1063
  # Math operations
706
1064
  sum: ${eval(10 + 5)} # 15
707
1065
  multiply: ${eval(10 * 3)} # 30
708
1066
  divide: ${eval(100 / 4)} # 25
1067
+ modulo: ${eval(17 % 5)} # 2
709
1068
 
710
1069
  # Comparisons (returns boolean)
711
1070
  isGreater: ${eval(200 > 100)} # true
712
1071
  isLess: ${eval(100 > 200)} # false
1072
+ isEqual: ${eval(10 == 10)} # true
713
1073
 
714
1074
  # String comparisons
715
1075
  isEqual: ${eval("hello" == "hello")} # true
716
1076
  strictEqual: ${eval("foo" === "foo")} # true
1077
+ notEqual: ${eval("a" != "b")} # true
717
1078
 
718
1079
  # Complex expressions
719
1080
  complex: ${eval((10 + 5) * 2)} # 30
1081
+ percentage: ${eval((75 / 100) * 200)} # 150
1082
+
1083
+ # With variables
1084
+ threshold: 50
1085
+ value: 75
1086
+ aboveThreshold: ${eval(${value} > ${threshold})} # true
720
1087
  ```
721
1088
 
722
- ### If expressions
1089
+ **Supported operators:**
1090
+
1091
+ | Category | Operators |
1092
+ |----------|-----------|
1093
+ | Arithmetic | `+` `-` `*` `/` `%` |
1094
+ | Comparison | `==` `!=` `===` `!==` `>` `<` `>=` `<=` |
1095
+ | Logical | `&&` `\|\|` `!` |
1096
+ | Grouping | `( )` |
1097
+
1098
+ **Security:**
1099
+ - Does NOT use JavaScript's `eval()`
1100
+ - Uses safe expression parser (subscript)
1101
+ - No access to global scope or functions
1102
+ - Only mathematical and logical operations allowed
1103
+
1104
+ ---
1105
+
1106
+ ### If Expressions
723
1107
 
724
1108
  Conditional expressions using ternary syntax. This is an alias for `eval` with a more intuitive name for conditionals.
725
1109
 
726
- ```yml
1110
+ ```yaml
727
1111
  # Basic ternary (condition ? "yes" : "no")
728
- status: ${if((5 > 3) ? "yes" : "no")} # "yes"
1112
+ status: ${if(5 > 3 ? "yes" : "no")} # "yes"
729
1113
 
730
1114
  # With variables
731
1115
  threshold: 50
732
1116
  value: 75
733
- result: ${if((${self:value} > ${self:threshold}) ? "above" : "below")} # "above"
1117
+ result: ${if(${value} > ${threshold} ? "above" : "below")} # "above"
734
1118
 
735
1119
  # Nested ternary (if/else if/else)
736
1120
  score: 85
737
- grade: ${if((${self:score} >= 90) ? "A" : (${self:score} >= 80) ? "B" : "C")} # "B"
1121
+ grade: ${if(${score} >= 90 ? "A" : ${score} >= 80 ? "B" : "C")} # "B"
738
1122
 
739
1123
  # Boolean result (no ternary needed)
740
- isValid: ${if(${self:value} > 0)} # true
1124
+ isValid: ${if(${value} > 0)} # true
741
1125
 
742
1126
  # Logical operators
743
1127
  enabled: true
744
1128
  count: 5
745
- canProceed: ${if(${self:enabled} && ${self:count} > 0)} # true
1129
+ canProceed: ${if(${enabled} && ${count} > 0)} # true
1130
+ hasIssues: ${if(!${enabled} || ${count} == 0)} # false
746
1131
  ```
747
1132
 
748
1133
  **Supported operators:**
@@ -756,7 +1141,7 @@ canProceed: ${if(${self:enabled} && ${self:count} > 0)} # true
756
1141
 
757
1142
  **Serverless deployment examples:**
758
1143
 
759
- ```yml
1144
+ ```yaml
760
1145
  service: my-service
761
1146
 
762
1147
  provider:
@@ -766,21 +1151,21 @@ provider:
766
1151
 
767
1152
  custom:
768
1153
  # Different memory by stage
769
- memorySize: '${ if( ${provider.stage} === "prod" ) ? 1024 : 512 }'
1154
+ memorySize: ${if(${provider.stage} === "prod" ? 1024 : 512)}
770
1155
 
771
1156
  # Different log retention by stage
772
- logRetention: ${if(("${provider.stage}" === "prod") ? 30 : 7)}
1157
+ logRetention: ${if(${provider.stage} === "prod" ? 30 : 7)}
773
1158
 
774
1159
  # Enable features per environment
775
- enableDebugEndpoints: ${if("${provider.stage}" !== "prod")}
776
- enableMetrics: ${if("${provider.stage}" === "prod")}
1160
+ enableDebugEndpoints: ${if(${provider.stage} !== "prod")}
1161
+ enableMetrics: ${if(${provider.stage} === "prod")}
777
1162
 
778
1163
  # Regional settings
779
- replicaCount: ${if(("${provider.region}" === "us-east-1") ? 3 : 1)}
1164
+ replicaCount: ${if(${provider.region} === "us-east-1" ? 3 : 1)}
780
1165
 
781
1166
  # Conditional IAM role (use predefined role in prod, inline in dev)
782
- useExternalRole: ${if("${provider.stage}" === "prod")}
783
- role: ${if((${custom.useExternalRole}) ? "arn:aws:iam::123:role/prod-role" : null)}
1167
+ useExternalRole: ${if(${provider.stage} === "prod")}
1168
+ role: ${if(${custom.useExternalRole} ? "arn:aws:iam::123:role/prod-role" : null)}
784
1169
 
785
1170
  functions:
786
1171
  api:
@@ -798,29 +1183,62 @@ functions:
798
1183
  enabled: ${custom.enableMetrics}
799
1184
  ```
800
1185
 
801
- ### Filters (experimental)
1186
+ ---
1187
+
1188
+ ### Filters (Experimental)
802
1189
 
803
1190
  Pipe resolved values through transformation functions like case conversion.
804
1191
 
805
- ```yml
806
- toUpperCaseString: ${'value' | toUpperCase }
1192
+ ```yaml
1193
+ # String transformations
1194
+ toUpperCaseString: ${'value' | toUpperCase } # 'VALUE'
1195
+ toLowerCaseString: ${'VALUE' | toLowerCase } # 'value'
807
1196
 
808
- toKebabCaseString: ${'valueHere' | toKebabCase }
1197
+ # Case conversions
1198
+ toKebabCaseString: ${'valueHere' | toKebabCase } # 'value-here'
1199
+ toCamelCaseString: ${'value-here' | toCamelCase } # 'valueHere'
809
1200
 
1201
+ # Chaining filters
810
1202
  key: lol_hi
1203
+ transformed: ${key | toKebabCase | toUpperCase } # 'LOL-HI'
1204
+
1205
+ # With variables
1206
+ serviceName: MyServiceName
1207
+ serviceSlug: ${serviceName | toKebabCase} # 'my-service-name'
1208
+ ```
811
1209
 
812
- keyTwo: lol_hi
1210
+ **Built-in filters:**
1211
+ - `toUpperCase` - Convert to uppercase
1212
+ - `toLowerCase` - Convert to lowercase
1213
+ - `toKebabCase` - Convert to kebab-case
1214
+ - `toCamelCase` - Convert to camelCase
813
1215
 
814
- toKebabCase: ${key | toKebabCase }
1216
+ **Custom filters:**
815
1217
 
816
- toCamelCase: ${keyTwo | toCamelCase }
1218
+ ```javascript
1219
+ const config = await configorama('config.yml', {
1220
+ filters: {
1221
+ // Custom filter
1222
+ reverse: (value) => value.split('').reverse().join(''),
1223
+ // Filter with options
1224
+ truncate: (value, length = 10) => value.substring(0, length)
1225
+ }
1226
+ })
1227
+ ```
1228
+
1229
+ ```yaml
1230
+ # Using custom filters
1231
+ reversed: ${'hello' | reverse} # 'olleh'
1232
+ truncated: ${'very long string' | truncate(5)} # 'very '
817
1233
  ```
818
1234
 
819
- ### Functions (experimental)
1235
+ ---
1236
+
1237
+ ### Functions (Experimental)
820
1238
 
821
1239
  Apply built-in functions to combine, transform, or manipulate values.
822
1240
 
823
- ```yml
1241
+ ```yaml
824
1242
  object:
825
1243
  one: once
826
1244
  two: twice
@@ -829,164 +1247,323 @@ objectTwo:
829
1247
  three: third
830
1248
  four: fourth
831
1249
 
1250
+ # Merge objects
832
1251
  mergeObjects: ${merge(${object}, ${objectTwo})}
833
- ```
1252
+ # Result: { one: 'once', two: 'twice', three: 'third', four: 'fourth' }
834
1253
 
835
- ### More Examples
1254
+ # String concatenation
1255
+ fullName: ${concat(${firstName}, ' ', ${lastName})}
836
1256
 
837
- See the [tests folder](./tests) for a bunch of examples!
1257
+ # Array operations
1258
+ items:
1259
+ - a
1260
+ - b
1261
+ - c
838
1262
 
839
- ## Custom Variable Sources
1263
+ joinedItems: ${join(${items}, ', ')} # 'a, b, c'
1264
+ ```
840
1265
 
841
- Configorama allows you to bring your own variable sources.
1266
+ **Built-in functions:**
1267
+ - `merge(obj1, obj2, ...)` - Merge multiple objects
1268
+ - `concat(str1, str2, ...)` - Concatenate strings
1269
+ - `join(array, separator)` - Join array elements
842
1270
 
843
- There are 2 ways to resolve variables from custom sources.
844
-
845
- 1. Use the baked in javascript method for [sync](https://github.com/DavidWells/configorama/blob/master/tests/syncValues/syncValue.yml) or [aysnc](https://github.com/DavidWells/configorama/blob/master/tests/asyncValues/asyncValue.yml) resolution.
846
-
847
- 2. Add your own variable syntax and resolver.
848
-
849
- ```js
850
- const config = configorama('path/to/configFile', {
851
- variableSources: [{
852
- // Variable type name (used in metadata)
853
- type: 'consul',
854
- // Source type for config wizard behavior (see table below)
855
- source: 'remote',
856
- // Prefix shown in syntax examples
857
- prefix: 'consul',
858
- // Example syntax for documentation
859
- syntax: '${consul:path/to/key}',
860
- // Description for help text
861
- description: 'Resolves values from Consul KV store',
862
- // Match variables ${consul:xyz}
863
- match: RegExp(/^consul:/g),
864
- // Custom variable source. Must return a promise
865
- resolver: (varToProcess, opts, currentObject) => {
866
- // Make remote call to consul
867
- return Promise.resolve(varToProcess)
868
- }
869
- }]
870
- })
871
- console.log(config)
872
- ```
1271
+ **Custom functions:**
1272
+
1273
+ ```javascript
1274
+ const config = await configorama('config.yml', {
1275
+ functions: {
1276
+ // Custom function
1277
+ add: (a, b) => a + b,
1278
+ // Function with multiple args
1279
+ between: (val, min, max) => val >= min && val <= max
1280
+ }
1281
+ })
1282
+ ```
873
1283
 
874
- This would match the following config:
1284
+ ```yaml
1285
+ # Using custom functions
1286
+ sum: ${add(5, 10)} # 15
1287
+ value: 75
1288
+ inRange: ${between(${value}, 50, 100)} # true
1289
+ ```
875
1290
 
876
- ```yml
877
- key: ${consul:xyz}
878
- ```
1291
+ ---
879
1292
 
880
- ### Variable Source Types
1293
+ ## API Reference
881
1294
 
882
- The `source` property defines how the config wizard handles each variable type:
1295
+ ### Async API
883
1296
 
884
- | Source | Description | Wizard Behavior | Examples |
885
- |--------|-------------|-----------------|----------|
886
- | `'user'` | Values provided by user at runtime | Prompt user for value | `env`, `opt` |
887
- | `'config'` | Values from config files or self-references | Check existence, can create | `self`, `file`, `text` |
888
- | `'remote'` | Values from external services | Fetch, prompt if missing, can write back | `ssm`, `vault`, `consul` |
889
- | `'readonly'` | Computed or system-derived values | Display only, cannot modify | `git`, `cron`, `eval` |
1297
+ The primary async API for resolving configurations.
890
1298
 
891
- **Built-in variable sources and their types:**
1299
+ **Signature:**
892
1300
 
893
- | Variable | Source Type | Description |
894
- |----------|-------------|-------------|
895
- | `${env:VAR}` | `user` | Environment variables |
896
- | `${opt:flag}` | `user` | CLI option flags |
897
- | `${param:key}` | `user` | Parameter values |
898
- | `${self:key}` | `config` | Self references |
899
- | `${file(path)}` | `config` | File references |
900
- | `${text(path)}` | `config` | Raw text file references |
901
- | `${git:branch}` | `readonly` | Git repository data |
902
- | `${cron(expr)}` | `readonly` | Cron expression conversion |
903
- | `${eval(expr)}` | `readonly` | Math/logic evaluation |
904
- | `${if(expr)}` | `readonly` | Conditional expressions |
1301
+ ```typescript
1302
+ function configorama<T = any>(
1303
+ configPathOrObject: string | object,
1304
+ settings?: ConfigoramaSettings
1305
+ ): Promise<T | ConfigoramaResult<T>>
1306
+ ```
905
1307
 
906
- ## Options
1308
+ **Parameters:**
907
1309
 
908
- | Option | Type | Default | Description |
909
- |--------|------|---------|-------------|
910
- | `options` | object | `{}` | CLI options/flags to populate `${opt:xyz}` variables |
911
- | `syntax` | string/RegExp | `${...}` | Custom variable syntax regex pattern |
912
- | `allowUnknownVariableTypes` | boolean \| string[] | `false` | Allow unknown variable types to pass through (e.g., `${ssm:path}`) |
913
- | `allowUnresolvedVariables` | boolean \| string[] | `false` | Allow known variable types that can't be resolved to pass through |
914
- | `allowUndefinedValues` | boolean | `false` | Allow undefined to be an end result |
915
- | `variableSources` | array | `[]` | Custom variable sources (see above) |
1310
+ | Parameter | Type | Required | Description |
1311
+ |-----------|------|----------|-------------|
1312
+ | `configPathOrObject` | `string \| object` | Yes | Path to config file or raw JavaScript object |
1313
+ | `settings` | `ConfigoramaSettings` | No | Configuration options |
916
1314
 
917
- > **Note:** Legacy options `allowUnknownVars`, `allowUnknownVariables`, `allowUnknownParams`, and `allowUnknownFileRefs` are deprecated. Use `allowUnknownVariableTypes` and `allowUnresolvedVariables` instead.
1315
+ **Settings object:**
918
1316
 
919
- <details>
920
- <summary><strong>Migration Guide</strong></summary>
1317
+ ```typescript
1318
+ interface ConfigoramaSettings {
1319
+ options?: Record<string, any> // CLI flags for ${opt:xyz}
1320
+ syntax?: string | RegExp // Custom variable syntax
1321
+ configDir?: string // Working directory for relative paths
1322
+ variableSources?: VariableSource[] // Custom variable resolvers
1323
+ filters?: Record<string, Function> // Custom filter functions
1324
+ functions?: Record<string, Function> // Custom functions
1325
+ allowUnknownVariableTypes?: boolean | string[] // Allow unknown var types
1326
+ allowUnresolvedVariables?: boolean | string[] // Allow unresolved vars
1327
+ allowUndefinedValues?: boolean // Allow undefined in output
1328
+ returnMetadata?: boolean // Return metadata with config
1329
+ mergeKeys?: string[] // Keys to merge in arrays
1330
+ filePathOverrides?: Record<string, string> // Override file paths
1331
+ }
1332
+ ```
1333
+
1334
+ **Returns:**
921
1335
 
922
- **From legacy options to new API:**
1336
+ - If `returnMetadata: false` (default): `Promise<T>` - Resolved config object
1337
+ - If `returnMetadata: true`: `Promise<ConfigoramaResult<T>>` - Object with config and metadata
923
1338
 
924
- ```js
925
- // OLD → NEW
1339
+ **Example:**
926
1340
 
927
- // Unknown variable types (unregistered resolvers)
928
- { allowUnknownVars: true } → { allowUnknownVariableTypes: true }
929
- { allowUnknownVariables: true } → { allowUnknownVariableTypes: true }
1341
+ ```javascript
1342
+ const configorama = require('configorama')
930
1343
 
931
- // Unresolved params
932
- { allowUnknownParams: true } → { allowUnresolvedVariables: ['param'] }
1344
+ // Basic usage
1345
+ const config = await configorama('./config.yml')
933
1346
 
934
- // Unresolved file refs
935
- { allowUnknownFileRefs: true } → { allowUnresolvedVariables: ['file'] }
1347
+ // With options
1348
+ const config = await configorama('./config.yml', {
1349
+ options: { stage: 'prod', region: 'us-east-1' },
1350
+ allowUnknownVariableTypes: ['ssm', 'cf']
1351
+ })
936
1352
 
937
- // Both params and files
938
- { allowUnknownParams: true, allowUnknownFileRefs: true }
939
- → { allowUnresolvedVariables: ['param', 'file'] }
1353
+ // With metadata
1354
+ const result = await configorama('./config.yml', {
1355
+ returnMetadata: true,
1356
+ options: { stage: 'prod' }
1357
+ })
940
1358
 
941
- // All unresolved (env, opt, file, param, etc.)
942
- { allowUnresolvedVariables: true } // unchanged, now also accepts arrays
1359
+ console.log(result.config) // Resolved config
1360
+ console.log(result.metadata) // Variable metadata
1361
+ console.log(result.resolutionHistory) // Resolution steps
943
1362
  ```
944
1363
 
945
- **New array syntax allows granular control:**
1364
+ ---
1365
+
1366
+ ### Sync API
946
1367
 
947
- ```js
948
- // Only allow specific unknown types
949
- { allowUnknownVariableTypes: ['ssm', 'cf', 's3'] }
1368
+ Synchronous API for blocking config resolution.
950
1369
 
951
- // Only allow specific resolver types to fail gracefully
952
- { allowUnresolvedVariables: ['param'] } // only params, env/file/opt still throw
1370
+ **Signature:**
1371
+
1372
+ ```typescript
1373
+ function configorama.sync<T = any>(
1374
+ configPathOrObject: string | object,
1375
+ settings?: ConfigoramaSettings
1376
+ ): T
953
1377
  ```
954
1378
 
955
- Legacy options still work but will be removed in a future major version.
1379
+ **Parameters:**
956
1380
 
957
- </details>
1381
+ Same as async API, but `dynamicArgs` cannot be a function (must be serializable).
958
1382
 
959
- ### Custom Variable Syntax
1383
+ **Returns:**
960
1384
 
961
- Use the `syntax` option to change the variable delimiters. You can provide a regex string directly or use `buildVariableSyntax()` to generate one with proper character escaping:
1385
+ `T` - Resolved config object (synchronously)
1386
+
1387
+ **Limitations:**
962
1388
 
963
- ```js
1389
+ - Cannot use async functions in JavaScript/TypeScript file references
1390
+ - `dynamicArgs` must be serializable (not a function)
1391
+ - CLI args automatically parsed from `process.argv` if `options` not provided
1392
+
1393
+ **Example:**
1394
+
1395
+ ```javascript
964
1396
  const configorama = require('configorama')
965
- const { buildVariableSyntax } = require('configorama')
966
1397
 
967
- // Using buildVariableSyntax helper (recommended)
968
- const config = await configorama(configFile, {
969
- syntax: buildVariableSyntax('{{', '}}'), // Mustache-style: {{env:FOO}}
1398
+ // Basic sync usage
1399
+ const config = configorama.sync('./config.yml')
1400
+
1401
+ // With options
1402
+ const config = configorama.sync('./config.yml', {
970
1403
  options: { stage: 'dev' }
971
1404
  })
972
-
973
- // Other examples:
974
- buildVariableSyntax('${{', '}}') // ${{env:FOO}}
975
- buildVariableSyntax('#{', '}') // #{env:FOO}
976
- buildVariableSyntax('[[', ']]') // [[env:FOO]]
977
- buildVariableSyntax('<', '>') // <env:FOO>
978
1405
  ```
979
1406
 
980
- The `buildVariableSyntax(prefix, suffix, excludePatterns)` function:
981
- - Automatically excludes suffix characters from the allowed character class (prevents parsing issues)
982
- - Supports nested variables by excluding `$` and `{` from values
983
- - Third parameter `excludePatterns` is an array of strings to exclude via negative lookahead (default: `['AWS', 'stageVariables']`)
1407
+ ---
1408
+
1409
+ ### Analyze API
1410
+
1411
+ Analyze config structure without resolving variables.
1412
+
1413
+ **Signature:**
1414
+
1415
+ ```typescript
1416
+ function configorama.analyze(
1417
+ configPathOrObject: string | object,
1418
+ settings?: ConfigoramaSettings
1419
+ ): Promise<AnalyzeResult>
1420
+ ```
1421
+
1422
+ **Returns:**
1423
+
1424
+ ```typescript
1425
+ interface AnalyzeResult {
1426
+ originalConfig: object // Raw config object
1427
+ variables: Variable[] // All variables found
1428
+ uniqueVariables: Record<string, Variable[]> // Variables grouped by name
1429
+ fileDependencies: string[] // File references
1430
+ }
1431
+
1432
+ interface Variable {
1433
+ variable: string // Full variable syntax (e.g., '${env:KEY}')
1434
+ variableType: string // Type (e.g., 'env', 'opt', 'file')
1435
+ variableName: string // Name/path (e.g., 'KEY')
1436
+ variablePath: string // Location in config (e.g., 'database.host')
1437
+ defaultValue: any // Default value if provided
1438
+ hasDefault: boolean // Whether default exists
1439
+ }
1440
+ ```
1441
+
1442
+ **Example:**
1443
+
1444
+ ```javascript
1445
+ const configorama = require('configorama')
1446
+
1447
+ const analysis = await configorama.analyze('./config.yml')
1448
+
1449
+ console.log(`Found ${analysis.variables.length} variables`)
1450
+ console.log(`File dependencies:`, analysis.fileDependencies)
1451
+
1452
+ // List all environment variables required
1453
+ const envVars = analysis.variables
1454
+ .filter(v => v.variableType === 'env' && !v.hasDefault)
1455
+ .map(v => v.variableName)
1456
+
1457
+ console.log('Required env vars:', envVars)
1458
+ ```
1459
+
1460
+ **Use cases:**
1461
+ - Generate documentation of required environment variables
1462
+ - Validate config structure in CI/CD
1463
+ - Build dependency graphs
1464
+ - Audit external dependencies before resolution
1465
+
1466
+ ---
1467
+
1468
+ ### Format Utilities
1469
+
1470
+ Parse various config formats to JavaScript objects.
1471
+
1472
+ **Available parsers:**
1473
+
1474
+ ```javascript
1475
+ const { format } = require('configorama')
1476
+
1477
+ // Parse YAML
1478
+ const yamlObj = format.yaml.parse('key: value')
1479
+
1480
+ // Parse JSON5
1481
+ const jsonObj = format.json5.parse('{ key: "value", }')
1482
+
1483
+ // Parse TOML
1484
+ const tomlObj = format.toml.parse('key = "value"')
1485
+
1486
+ // Parse INI
1487
+ const iniObj = format.ini.parse('[section]\nkey=value')
1488
+
1489
+ // Parse HCL (requires @cdktf/hcl2json)
1490
+ const hclObj = await format.hcl.parse('variable "example" { default = "value" }')
1491
+ ```
1492
+
1493
+ **Parser methods:**
1494
+
1495
+ Each parser has:
1496
+ - `parse(content)` - Parse string to JavaScript object
1497
+ - `stringify(obj)` - Convert JavaScript object to format string (if supported)
1498
+
1499
+ ---
1500
+
1501
+ ## Configuration Options
1502
+
1503
+ ### Custom Variable Syntax
1504
+
1505
+ Use the `syntax` option to change the variable delimiters. You can provide a regex string directly or use `buildVariableSyntax()` to generate one with proper character escaping:
1506
+
1507
+ ```javascript
1508
+ const configorama = require('configorama')
1509
+ const { buildVariableSyntax } = require('configorama')
1510
+
1511
+ // Using buildVariableSyntax helper (recommended)
1512
+ const config = await configorama(configFile, {
1513
+ syntax: buildVariableSyntax('{{', '}}'), // Mustache-style: {{env:FOO}}
1514
+ options: { stage: 'dev' }
1515
+ })
1516
+
1517
+ // Other examples:
1518
+ buildVariableSyntax('${{', '}}') // ${{env:FOO}}
1519
+ buildVariableSyntax('#{', '}') // #{env:FOO}
1520
+ buildVariableSyntax('[[', ']]') // [[env:FOO]]
1521
+ buildVariableSyntax('<', '>') // <env:FOO>
1522
+ ```
1523
+
1524
+ **Function signature:**
1525
+
1526
+ ```typescript
1527
+ function buildVariableSyntax(
1528
+ prefix: string = '${',
1529
+ suffix: string = '}',
1530
+ excludePatterns: string[] = ['AWS', 'stageVariables']
1531
+ ): string
1532
+ ```
1533
+
1534
+ The `buildVariableSyntax()` function:
1535
+ - Automatically excludes suffix characters from the allowed character class (prevents parsing issues)
1536
+ - Supports nested variables by excluding `$` and `{` from values
1537
+ - Third parameter `excludePatterns` is an array of strings to exclude via negative lookahead
1538
+
1539
+ **Example with custom syntax:**
1540
+
1541
+ ```javascript
1542
+ const config = await configorama('config.yml', {
1543
+ syntax: buildVariableSyntax('{{', '}}')
1544
+ })
1545
+ ```
1546
+
1547
+ ```yaml
1548
+ # config.yml with {{ }} syntax
1549
+ apiKey: {{env:API_KEY}}
1550
+ stage: {{opt:stage, 'dev'}}
1551
+ database: {{file(./db.yml)}}
1552
+ ```
1553
+
1554
+ ---
984
1555
 
985
1556
  ### allowUnknownVariableTypes
986
1557
 
987
1558
  Controls what happens when encountering unregistered variable types (e.g., `${ssm:path}` when `ssm` isn't a registered resolver).
988
1559
 
989
- ```js
1560
+ **Type:** `boolean | string[]`
1561
+
1562
+ **Default:** `false`
1563
+
1564
+ **Behavior:**
1565
+
1566
+ ```javascript
990
1567
  // Allow ALL unknown types to pass through
991
1568
  const config = await configorama(configFile, {
992
1569
  allowUnknownVariableTypes: true,
@@ -1004,11 +1581,24 @@ const config = await configorama(configFile, {
1004
1581
  // ${custom:thing} throws an error
1005
1582
  ```
1006
1583
 
1584
+ **Use cases:**
1585
+ - Multi-stage resolution (local resolution, then cloud provider resolves remaining vars)
1586
+ - Serverless Framework integration (let framework resolve SSM, CloudFormation refs)
1587
+ - Gradual migration (allow unknown types during transition period)
1588
+
1589
+ ---
1590
+
1007
1591
  ### allowUnresolvedVariables
1008
1592
 
1009
1593
  Controls what happens when a known resolver can't find a value (missing env vars, missing files, etc.).
1010
1594
 
1011
- ```js
1595
+ **Type:** `boolean | string[]`
1596
+
1597
+ **Default:** `false`
1598
+
1599
+ **Behavior:**
1600
+
1601
+ ```javascript
1012
1602
  // Allow ALL unresolved variables to pass through
1013
1603
  const config = await configorama(configFile, {
1014
1604
  allowUnresolvedVariables: true,
@@ -1025,56 +1615,461 @@ const config = await configorama(configFile, {
1025
1615
  // Input: { paramKey: '${param:x}', fileKey: '${file(missing.yml)}' }
1026
1616
  // Output: { paramKey: '${param:x}', fileKey: '${file(missing.yml)}' }
1027
1617
 
1028
- // Allow only SPECIFIC types to be unresolved
1618
+ // Mixed scenario
1029
1619
  const config = await configorama(configFile, {
1030
- allowUnresolvedVariables: ['param', 'file'], // only these pass through
1620
+ allowUnresolvedVariables: ['param', 'file'],
1031
1621
  options: { stage: 'prod' }
1032
1622
  })
1033
- // Input: { key: '${env:MISSING_VAR}', paramKey: '${param:x}', fileKey: '${file(missing.yml)}' }
1034
- // Unresolved ${param:x} and ${file(missing.yml)} pass through but
1035
- // Output error thrown because ${env:MISSING_VAR} throws an error
1623
+ // Input: {
1624
+ // key: '${env:MISSING_VAR}',
1625
+ // paramKey: '${param:x}',
1626
+ // fileKey: '${file(missing.yml)}'
1627
+ // }
1628
+ // Output: Error thrown because ${env:MISSING_VAR} cannot resolve
1629
+ // (param and file pass through, but env vars must resolve)
1036
1630
  ```
1037
1631
 
1038
- This is useful for multi-stage resolution (e.g., Downstream Serverless Dashboard resolves params after local resolution).
1632
+ **Important notes:**
1633
+ - This option does NOT apply to `self:` or dotProp variables (e.g., `${foo.bar.baz}`)
1634
+ - Self-references are local config errors, not external dependencies
1635
+ - Useful for multi-stage resolution pipelines
1039
1636
 
1040
- > **Note:** This option does NOT apply to `self:` or dotProp variables (e.g., `${foo.bar.baz}`). These are local references that configorama fully owns—if they can't be resolved, it's a config error, not something to defer to another system.
1637
+ **Use cases:**
1638
+ - Serverless Dashboard resolves params after local resolution
1639
+ - Gradual migration with optional external dependencies
1640
+ - Development mode where some services are unavailable
1041
1641
 
1042
- ## FAQ
1642
+ ---
1043
1643
 
1044
- **Q: What happens with circular variable dependencies?**
1644
+ ### Complete Options Reference
1045
1645
 
1046
- Configorama detects circular dependencies and throws a helpful error instead of hanging forever.
1646
+ | Option | Type | Default | Description |
1647
+ |--------|------|---------|-------------|
1648
+ | `options` | `object` | `{}` | CLI options/flags to populate `${opt:xyz}` variables |
1649
+ | `syntax` | `string \| RegExp` | `${...}` | Custom variable syntax regex pattern |
1650
+ | `configDir` | `string` | directory of config file | Working directory for relative file paths |
1651
+ | `variableSources` | `VariableSource[]` | `[]` | Custom variable sources (see below) |
1652
+ | `filters` | `Record<string, Function>` | `{}` | Custom filter functions for pipe operator |
1653
+ | `functions` | `Record<string, Function>` | `{}` | Custom functions for `${fn(...)}` syntax |
1654
+ | `allowUnknownVariableTypes` | `boolean \| string[]` | `false` | Allow unknown variable types to pass through |
1655
+ | `allowUnresolvedVariables` | `boolean \| string[]` | `false` | Allow known types that can't resolve to pass through |
1656
+ | `allowUndefinedValues` | `boolean` | `false` | Allow undefined as a valid end result |
1657
+ | `returnMetadata` | `boolean` | `false` | Return both config and metadata about variables |
1658
+ | `mergeKeys` | `string[]` | `[]` | Keys to merge in arrays of objects |
1659
+ | `filePathOverrides` | `Record<string, string>` | `{}` | Map of file paths to override (for testing/mocking) |
1660
+
1661
+ **Legacy options (deprecated):**
1662
+
1663
+ | Legacy Option | New Equivalent |
1664
+ |---------------|----------------|
1665
+ | `allowUnknownVars` | `allowUnknownVariableTypes` |
1666
+ | `allowUnknownVariables` | `allowUnknownVariableTypes` |
1667
+ | `allowUnknownParams` | `allowUnresolvedVariables: ['param']` |
1668
+ | `allowUnknownFileRefs` | `allowUnresolvedVariables: ['file']` |
1669
+
1670
+ ---
1047
1671
 
1048
- ```yml
1049
- # Direct cycle - throws error
1050
- a: ${self:b}
1051
- b: ${self:a}
1052
- # Error: Circular variable dependency detected: b → a → b
1672
+ ## Custom Variable Sources
1053
1673
 
1054
- # Indirect cycle - also detected
1055
- a: ${self:b}
1056
- b: ${self:c}
1057
- c: ${self:a}
1058
- # Error: Circular variable dependency detected: c → a → b → c
1674
+ Configorama allows you to bring your own variable sources.
1059
1675
 
1060
- # Works with shorthand syntax too
1061
- foo:
1062
- bar: ${baz.qux}
1063
- baz:
1064
- qux: ${foo.bar}
1065
- # Error: Circular variable dependency detected: baz.qux → foo.bar → baz.qux
1676
+ ### Variable Source Types
1677
+
1678
+ The `source` property defines how the config wizard handles each variable type:
1679
+
1680
+ | Source | Description | Wizard Behavior | Examples |
1681
+ |--------|-------------|-----------------|----------|
1682
+ | `'user'` | Values provided by user at runtime | Prompt user for value | `env`, `opt` |
1683
+ | `'config'` | Values from config files or self-references | Check existence, can create | `self`, `file`, `text` |
1684
+ | `'remote'` | Values from external services | Fetch, prompt if missing, can write back | `ssm`, `vault`, `consul` |
1685
+ | `'readonly'` | Computed or system-derived values | Display only, cannot modify | `git`, `cron`, `eval` |
1686
+
1687
+ **Built-in variable sources and their types:**
1688
+
1689
+ | Variable | Source Type | Description |
1690
+ |----------|-------------|-------------|
1691
+ | `${env:VAR}` | `user` | Environment variables |
1692
+ | `${opt:flag}` | `user` | CLI option flags |
1693
+ | `${param:key}` | `user` | Parameter values |
1694
+ | `${self:key}` | `config` | Self references |
1695
+ | `${file(path)}` | `config` | File references |
1696
+ | `${text(path)}` | `config` | Raw text file references |
1697
+ | `${git:branch}` | `readonly` | Git repository data |
1698
+ | `${cron(expr)}` | `readonly` | Cron expression conversion |
1699
+ | `${eval(expr)}` | `readonly` | Math/logic evaluation |
1700
+ | `${if(expr)}` | `readonly` | Conditional expressions |
1701
+
1702
+ ### Creating a Custom Resolver
1703
+
1704
+ There are 2 ways to resolve variables from custom sources:
1705
+
1706
+ 1. **Use built-in JavaScript method** for [sync](https://github.com/DavidWells/configorama/blob/master/tests/syncValues/syncValue.yml) or [async](https://github.com/DavidWells/configorama/blob/master/tests/asyncValues/asyncValue.yml) resolution.
1707
+
1708
+ 2. **Add your own variable syntax and resolver:**
1709
+
1710
+ ```javascript
1711
+ const configorama = require('configorama')
1712
+
1713
+ const config = await configorama('path/to/configFile', {
1714
+ variableSources: [{
1715
+ // Variable type name (used in metadata)
1716
+ type: 'consul',
1717
+
1718
+ // Source type for config wizard behavior
1719
+ source: 'remote',
1720
+
1721
+ // Prefix shown in syntax examples
1722
+ prefix: 'consul',
1723
+
1724
+ // Example syntax for documentation
1725
+ syntax: '${consul:path/to/key}',
1726
+
1727
+ // Description for help text
1728
+ description: 'Resolves values from Consul KV store',
1729
+
1730
+ // Match variables ${consul:xyz}
1731
+ match: RegExp(/^consul:/g),
1732
+
1733
+ // Custom variable source. Must return a promise
1734
+ resolver: async (varToProcess, opts, currentObject) => {
1735
+ // varToProcess = 'consul:path/to/key'
1736
+ const consulPath = varToProcess.replace(/^consul:/, '')
1737
+
1738
+ // Make remote call to consul
1739
+ const consulClient = require('consul')()
1740
+ const result = await consulClient.kv.get(consulPath)
1741
+
1742
+ return result.Value
1743
+ }
1744
+ }]
1745
+ })
1746
+
1747
+ console.log(config)
1066
1748
  ```
1067
1749
 
1068
- **Q: Why should I use this?**
1750
+ **This would match:**
1069
1751
 
1070
- Never rendering a stale configuration file again!
1752
+ ```yaml
1753
+ key: ${consul:path/to/my/key}
1754
+ ```
1755
+
1756
+ **Variable source interface:**
1757
+
1758
+ ```typescript
1759
+ interface VariableSource {
1760
+ type: string // Type name (e.g., 'consul', 'ssm')
1761
+ source: 'user' | 'config' | 'remote' | 'readonly'
1762
+ prefix?: string // Prefix for examples (defaults to type)
1763
+ syntax: string // Example syntax (e.g., '${consul:key}')
1764
+ description?: string // Help text description
1765
+ match: RegExp // Regex to match variables
1766
+ resolver: ( // Resolution function
1767
+ variable: string, // Variable string (e.g., 'consul:key')
1768
+ options: object, // Options from configorama call
1769
+ currentConfig: object // Current partially-resolved config
1770
+ ) => Promise<any>
1771
+ collectMetadata?: () => any // Optional: collect custom metadata
1772
+ metadataKey?: string // Optional: key for custom metadata
1773
+ }
1774
+ ```
1775
+
1776
+ **Advanced example with AWS SSM:**
1777
+
1778
+ ```javascript
1779
+ const AWS = require('aws-sdk')
1780
+ const ssm = new AWS.SSM()
1071
1781
 
1072
- **Q: Does this work with `serverless.yml`**
1782
+ const config = await configorama('config.yml', {
1783
+ variableSources: [{
1784
+ type: 'ssm',
1785
+ source: 'remote',
1786
+ syntax: '${ssm:/path/to/parameter}',
1787
+ description: 'Resolves values from AWS Systems Manager Parameter Store',
1788
+ match: /^ssm:/,
1789
+ resolver: async (variable, options, currentConfig) => {
1790
+ const paramPath = variable.replace(/^ssm:/, '')
1791
+
1792
+ try {
1793
+ const result = await ssm.getParameter({
1794
+ Name: paramPath,
1795
+ WithDecryption: true
1796
+ }).promise()
1797
+
1798
+ return result.Parameter.Value
1799
+ } catch (err) {
1800
+ if (options.allowUnresolvedVariables) {
1801
+ return `\${${variable}}` // Pass through unresolved
1802
+ }
1803
+ throw new Error(`SSM parameter not found: ${paramPath}`)
1804
+ }
1805
+ }
1806
+ }]
1807
+ })
1808
+ ```
1809
+
1810
+ ```yaml
1811
+ # config.yml
1812
+ database:
1813
+ password: ${ssm:/myapp/prod/db-password}
1814
+ apiKey: ${ssm:/myapp/prod/api-key}
1815
+ ```
1073
1816
 
1074
- Yes it does. Using `serverless.js` as your main entry point!
1817
+ ---
1075
1818
 
1076
- ```js
1077
- /* serverless.js */
1819
+ ## CLI Usage
1820
+
1821
+ Configorama includes a CLI tool for resolving configs from the command line.
1822
+
1823
+ ### Basic Commands
1824
+
1825
+ ```bash
1826
+ # Resolve a config file
1827
+ configorama config.yml
1828
+
1829
+ # Resolve and write to output file
1830
+ configorama config.yml --output resolved.json
1831
+
1832
+ # Resolve with CLI options
1833
+ configorama config.yml --stage prod --region us-east-1
1834
+
1835
+ # Show info about variables
1836
+ configorama config.yml --info
1837
+
1838
+ # Verify config (check for errors without resolving)
1839
+ configorama config.yml --verify
1840
+
1841
+ # Extract specific path from config
1842
+ configorama config.yml database.host
1843
+
1844
+ # Output as YAML
1845
+ configorama config.yml --format yaml
1846
+ ```
1847
+
1848
+ ### Command Options
1849
+
1850
+ ```text
1851
+ Usage:
1852
+ configorama [options] <file> [path]
1853
+
1854
+ Options:
1855
+ -h, --help Show this help message
1856
+ -v, --version Show version number
1857
+ -o, --output <file> Write output to file instead of stdout
1858
+ -f, --format <format> Output format: json, yaml, or js (default: json)
1859
+ -d, --debug Enable debug mode
1860
+ -i, --info Show info about the config
1861
+ -V, --verify Verify the config
1862
+ --param <key=value> Pass parameter values (can be used multiple times)
1863
+ --allow-unknown Allow unknown variables to pass through
1864
+ --allow-undefined Allow undefined values in the final output
1865
+
1866
+ Path Extraction:
1867
+ configorama config.yml database.host Extract specific value
1868
+ configorama config.yml functions[0] Extract from array
1869
+ ```
1870
+
1871
+ ### CLI Examples
1872
+
1873
+ **Basic resolution:**
1874
+
1875
+ ```bash
1876
+ # Input: config.yml
1877
+ apiKey: ${env:API_KEY}
1878
+ stage: ${opt:stage, 'dev'}
1879
+
1880
+ # Command
1881
+ export API_KEY=secret123
1882
+ configorama config.yml --stage prod
1883
+
1884
+ # Output
1885
+ {
1886
+ "apiKey": "secret123",
1887
+ "stage": "prod"
1888
+ }
1889
+ ```
1890
+
1891
+ **With parameters:**
1892
+
1893
+ ```bash
1894
+ configorama config.yml \
1895
+ --stage prod \
1896
+ --param "domain=myapp.com" \
1897
+ --param "apiKey=secret123"
1898
+ ```
1899
+
1900
+ **Extract specific path:**
1901
+
1902
+ ```bash
1903
+ # config.yml
1904
+ database:
1905
+ host: localhost
1906
+ port: 5432
1907
+
1908
+ # Extract database.host
1909
+ configorama config.yml database.host
1910
+ # Output: localhost
1911
+
1912
+ # Extract database config as JSON
1913
+ configorama config.yml database --format json
1914
+ # Output: {"host":"localhost","port":5432}
1915
+ ```
1916
+
1917
+ **Output to file:**
1918
+
1919
+ ```bash
1920
+ configorama config.yml --output resolved.json
1921
+ configorama config.yml --output resolved.yml --format yaml
1922
+ ```
1923
+
1924
+ **Show variable info:**
1925
+
1926
+ ```bash
1927
+ configorama config.yml --info
1928
+
1929
+ # Output:
1930
+ # Found 15 variables
1931
+ # env: 5
1932
+ # opt: 3
1933
+ # self: 4
1934
+ # file: 2
1935
+ # git: 1
1936
+ #
1937
+ # Required environment variables:
1938
+ # - API_KEY
1939
+ # - DB_HOST
1940
+ # - DB_PASSWORD
1941
+ #
1942
+ # File dependencies:
1943
+ # - ./secrets.yml
1944
+ # - ./config/database.yml
1945
+ ```
1946
+
1947
+ **Verify without resolving:**
1948
+
1949
+ ```bash
1950
+ configorama config.yml --verify
1951
+
1952
+ # Output:
1953
+ # ✓ Config structure valid
1954
+ # ✓ No circular dependencies
1955
+ # ✓ All file references exist
1956
+ # ! Warning: 3 environment variables not set
1957
+ # - API_KEY
1958
+ # - DB_HOST
1959
+ # - DB_PASSWORD
1960
+ ```
1961
+
1962
+ ---
1963
+
1964
+ ## Testing
1965
+
1966
+ ### Running Tests
1967
+
1968
+ ```bash
1969
+ # Run all tests
1970
+ npm test
1971
+
1972
+ # Run only fast tests (excludes slow tests)
1973
+ npm run test:lib
1974
+
1975
+ # Run API tests
1976
+ npm run test:api
1977
+
1978
+ # Run tests in a specific directory
1979
+ npm run test:tests
1980
+
1981
+ # Run slow tests only
1982
+ npm run test:slow
1983
+
1984
+ # Watch mode (reruns on file changes)
1985
+ npm run watch
1986
+
1987
+ # Type checking
1988
+ npm run typecheck
1989
+ ```
1990
+
1991
+ ### Test Structure
1992
+
1993
+ ```text
1994
+ tests/
1995
+ ├── _fixtures/ # Shared test fixtures
1996
+ ├── api/ # API tests
1997
+ ├── asyncValues/ # Async function resolution tests
1998
+ ├── syncValues/ # Sync function resolution tests
1999
+ ├── cronValues/ # Cron expression tests
2000
+ ├── gitVariables/ # Git variable tests
2001
+ ├── filePathOverrides/ # File path override tests
2002
+ ├── filterTests/ # Filter tests
2003
+ ├── hclTests/ # Terraform HCL tests
2004
+ ├── iniTests/ # INI format tests
2005
+ ├── tomlTests/ # TOML format tests
2006
+ ├── jsTests/ # JavaScript file tests
2007
+ └── ... # More test categories
2008
+ ```
2009
+
2010
+ ### Writing Tests
2011
+
2012
+ Configorama uses the `uvu` test framework. Tests can be run directly with Node.js:
2013
+
2014
+ ```bash
2015
+ # Run a single test file
2016
+ node tests/api/api.test.js
2017
+ ```
2018
+
2019
+ **Example test:**
2020
+
2021
+ ```javascript
2022
+ const { test } = require('uvu')
2023
+ const assert = require('uvu/assert')
2024
+ const path = require('path')
2025
+ const configorama = require('../src')
2026
+
2027
+ test('resolves environment variables', async () => {
2028
+ process.env.TEST_VAR = 'test-value'
2029
+
2030
+ const config = await configorama({
2031
+ key: '${env:TEST_VAR}'
2032
+ })
2033
+
2034
+ assert.equal(config.key, 'test-value')
2035
+
2036
+ delete process.env.TEST_VAR
2037
+ })
2038
+
2039
+ test('handles missing env vars with defaults', async () => {
2040
+ const config = await configorama({
2041
+ key: '${env:MISSING_VAR, "default"}'
2042
+ })
2043
+
2044
+ assert.equal(config.key, 'default')
2045
+ })
2046
+
2047
+ test.run()
2048
+ ```
2049
+
2050
+ **Test utilities available at `tests/utils.js`:**
2051
+
2052
+ ```javascript
2053
+ const { getFixturePath, loadFixture } = require('./tests/utils')
2054
+
2055
+ // Get path to fixture file
2056
+ const fixturePath = getFixturePath('config.yml')
2057
+
2058
+ // Load and parse fixture
2059
+ const fixtureData = loadFixture('config.yml')
2060
+ ```
2061
+
2062
+ ---
2063
+
2064
+ ## Deployment
2065
+
2066
+ ### Using with Serverless Framework
2067
+
2068
+ Configorama can be used as a drop-in replacement for the Serverless Framework variable system.
2069
+
2070
+ **serverless.js:**
2071
+
2072
+ ```javascript
1078
2073
  const path = require('path')
1079
2074
  const configorama = require('configorama')
1080
2075
  const args = require('minimist')(process.argv.slice(2))
@@ -1085,73 +2080,752 @@ const yamlFile = path.join(__dirname, 'serverless.config.yml')
1085
2080
  module.exports = configorama.sync(yamlFile, { options: args })
1086
2081
  ```
1087
2082
 
1088
- ## Whats new
2083
+ **serverless.config.yml:**
1089
2084
 
1090
- How is this different than the serverless variable system?
2085
+ ```yaml
2086
+ service: my-service
1091
2087
 
1092
- 1. You can use it with any other tool you'd like. Just include `configorama` and go nuts.
2088
+ provider:
2089
+ name: aws
2090
+ runtime: nodejs18.x
2091
+ stage: ${opt:stage, 'dev'}
2092
+ region: ${opt:region, 'us-east-1'}
1093
2093
 
1094
- 2. It's pluggable. Add whatever variable syntax/sources you wish.
2094
+ # Environment-specific config
2095
+ environment:
2096
+ STAGE: ${opt:stage}
2097
+ DB_HOST: ${env:DB_HOST}
2098
+ API_KEY: ${ssm:/my-service/${opt:stage}/api-key}
1095
2099
 
1096
- 3. Filters! You can filter values before they are resolved.
2100
+ custom:
2101
+ # Load stage-specific config
2102
+ stageConfig: ${file(./config/${opt:stage}.yml)}
1097
2103
 
1098
- ```yml
1099
- key: ${opt:stage | toUpperCase}
1100
- ```
2104
+ # Git info for tracking
2105
+ deploymentInfo:
2106
+ branch: ${git:branch}
2107
+ commit: ${git:sha1}
2108
+ timestamp: ${timestamp}
2109
+
2110
+ functions:
2111
+ api:
2112
+ handler: handler.api
2113
+ memorySize: ${if(${provider.stage} === 'prod' ? 1024 : 512)}
2114
+ events:
2115
+ - http:
2116
+ path: /
2117
+ method: ANY
2118
+ ```
1101
2119
 
1102
- 4. Cleaner self references
2120
+ **Deploy:**
1103
2121
 
1104
- ```yml
1105
- keyOne:
1106
- subKey: hi
2122
+ ```bash
2123
+ # Deploy to dev
2124
+ serverless deploy --stage dev
1107
2125
 
1108
- # Before
1109
- key: ${self:keyOne.subKey}
2126
+ # Deploy to production
2127
+ serverless deploy --stage prod --region us-west-2
2128
+ ```
1110
2129
 
1111
- # Now
1112
- key: ${keyOne.subKey}
1113
- ```
2130
+ ---
1114
2131
 
1115
- 5. Numbers as defaults are supported
2132
+ ### Docker Deployment
1116
2133
 
1117
- ```yml
1118
- key: ${env:whatever, 2}
1119
- ```
2134
+ **Dockerfile:**
1120
2135
 
1121
- 6. TOML, YML, JSON, INI etc support
2136
+ ```dockerfile
2137
+ FROM node:18-alpine
1122
2138
 
1123
- Configorama will work on any configuration format that can be converted into a JS object.
2139
+ WORKDIR /app
1124
2140
 
1125
- Parse any config format and pass it into configorama.
2141
+ # Copy package files
2142
+ COPY package*.json ./
1126
2143
 
1127
- 7. Configorama has a number of built-in functions.
2144
+ # Install dependencies
2145
+ RUN npm ci --only=production
1128
2146
 
1129
- Build in functions can be used within expressions as another way to transform and combine values. These are similar to the operators but all follow a common syntax:
2147
+ # Copy application files
2148
+ COPY . .
1130
2149
 
1131
- ```
1132
- <FUNCTION NAME>(<ARGUMENT 1>, <ARGUMENT 2>)
1133
- ```
2150
+ # Set environment variables (can be overridden at runtime)
2151
+ ENV NODE_ENV=production
2152
+ ENV STAGE=prod
1134
2153
 
1135
- example:
2154
+ # Run config resolution at build time (optional)
2155
+ # RUN node -e "require('configorama').sync('./config.yml', { options: { stage: process.env.STAGE } })"
1136
2156
 
2157
+ CMD ["node", "index.js"]
2158
+ ```
2159
+
2160
+ **docker-compose.yml:**
2161
+
2162
+ ```yaml
2163
+ version: '3.8'
2164
+
2165
+ services:
2166
+ app:
2167
+ build: .
2168
+ environment:
2169
+ - NODE_ENV=production
2170
+ - STAGE=prod
2171
+ - DB_HOST=postgres
2172
+ - DB_PASSWORD=${DB_PASSWORD}
2173
+ - API_KEY=${API_KEY}
2174
+ depends_on:
2175
+ - postgres
2176
+
2177
+ postgres:
2178
+ image: postgres:15
2179
+ environment:
2180
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
2181
+ ```
2182
+
2183
+ **Usage:**
2184
+
2185
+ ```bash
2186
+ # Build
2187
+ docker build -t myapp .
2188
+
2189
+ # Run with environment variables
2190
+ docker run \
2191
+ -e DB_HOST=mydb.example.com \
2192
+ -e DB_PASSWORD=secret \
2193
+ -e API_KEY=abc123 \
2194
+ myapp
2195
+ ```
2196
+
2197
+ ---
2198
+
2199
+ ### CI/CD Integration
2200
+
2201
+ **GitHub Actions example (.github/workflows/deploy.yml):**
2202
+
2203
+ ```yaml
2204
+ name: Deploy
2205
+
2206
+ on:
2207
+ push:
2208
+ branches: [main]
2209
+
2210
+ jobs:
2211
+ deploy:
2212
+ runs-on: ubuntu-latest
2213
+
2214
+ steps:
2215
+ - uses: actions/checkout@v3
2216
+
2217
+ - name: Setup Node.js
2218
+ uses: actions/setup-node@v3
2219
+ with:
2220
+ node-version: '18'
2221
+
2222
+ - name: Install dependencies
2223
+ run: npm ci
2224
+
2225
+ - name: Verify config
2226
+ run: npx configorama config.yml --verify
2227
+ env:
2228
+ STAGE: prod
2229
+ DB_HOST: ${{ secrets.DB_HOST }}
2230
+ DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
2231
+ API_KEY: ${{ secrets.API_KEY }}
2232
+
2233
+ - name: Run tests
2234
+ run: npm test
2235
+
2236
+ - name: Deploy to production
2237
+ run: npm run deploy
2238
+ env:
2239
+ STAGE: prod
2240
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
2241
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
2242
+ ```
2243
+
2244
+ **GitLab CI example (.gitlab-ci.yml):**
2245
+
2246
+ ```yaml
2247
+ stages:
2248
+ - verify
2249
+ - test
2250
+ - deploy
2251
+
2252
+ verify-config:
2253
+ stage: verify
2254
+ image: node:18
2255
+ script:
2256
+ - npm ci
2257
+ - npx configorama config.yml --verify --stage $CI_ENVIRONMENT_NAME
2258
+ variables:
2259
+ STAGE: $CI_ENVIRONMENT_NAME
2260
+
2261
+ test:
2262
+ stage: test
2263
+ image: node:18
2264
+ script:
2265
+ - npm ci
2266
+ - npm test
2267
+
2268
+ deploy-production:
2269
+ stage: deploy
2270
+ image: node:18
2271
+ script:
2272
+ - npm ci
2273
+ - npm run deploy
2274
+ environment:
2275
+ name: production
2276
+ only:
2277
+ - main
2278
+ variables:
2279
+ STAGE: prod
2280
+ ```
2281
+
2282
+ ---
2283
+
2284
+ ## Troubleshooting
2285
+
2286
+ ### Common Issues
2287
+
2288
+ **Q: Variable not resolving**
2289
+
2290
+ ```yaml
2291
+ # Problem
2292
+ apiKey: ${env:API_KEY}
2293
+ # Error: Environment variable 'API_KEY' not found
2294
+ ```
2295
+
2296
+ **Solutions:**
2297
+ 1. Ensure environment variable is set: `echo $API_KEY`
2298
+ 2. Check variable name spelling
2299
+ 3. Use default value: `${env:API_KEY, 'default'}`
2300
+ 4. Allow unresolved vars: `allowUnresolvedVariables: ['env']`
2301
+
2302
+ ---
2303
+
2304
+ **Q: Circular dependency error**
2305
+
2306
+ ```yaml
2307
+ # Problem
2308
+ a: ${self:b}
2309
+ b: ${self:a}
2310
+ # Error: Circular variable dependency detected: b → a → b
2311
+ ```
2312
+
2313
+ **Solutions:**
2314
+ 1. Restructure config to remove circular reference
2315
+ 2. Use intermediate values to break the cycle
2316
+ 3. Consider if one value should be a constant instead
2317
+
2318
+ ---
2319
+
2320
+ **Q: File not found**
2321
+
2322
+ ```yaml
2323
+ # Problem
2324
+ secrets: ${file(./secrets.yml)}
2325
+ # Error: File not found: ./secrets.yml
2326
+ ```
2327
+
2328
+ **Solutions:**
2329
+ 1. Check file path is correct relative to config file
2330
+ 2. Ensure file exists: `ls -la secrets.yml`
2331
+ 3. Use absolute path: `${file(/absolute/path/to/secrets.yml)}`
2332
+ 4. Add default: `${file(./secrets.yml), {}}`
2333
+
2334
+ ---
2335
+
2336
+ **Q: TypeScript file execution fails**
2337
+
2338
+ ```yaml
2339
+ # Problem
2340
+ config: ${file(./config.ts)}
2341
+ # Error: Cannot find module 'tsx'
2342
+ ```
2343
+
2344
+ **Solutions:**
2345
+ 1. Install tsx: `npm install tsx --save-dev`
2346
+ 2. Or install ts-node: `npm install ts-node typescript --save-dev`
2347
+ 3. Ensure TypeScript file exports correctly: `export = value` or `module.exports = value`
2348
+
2349
+ ---
2350
+
2351
+ **Q: HCL parsing fails**
2352
+
2353
+ ```yaml
2354
+ # Problem
2355
+ terraform: ${file(./main.tf)}
2356
+ # Error: HCL parsing requires @cdktf/hcl2json
2357
+ ```
2358
+
2359
+ **Solutions:**
2360
+ 1. Install dependency: `npm install @cdktf/hcl2json`
2361
+ 2. Ensure HCL file is valid Terraform syntax
2362
+ 3. Check file extension is `.tf`, `.hcl`, or `.tf.json`
2363
+
2364
+ ---
2365
+
2366
+ ### Debug Mode
2367
+
2368
+ Enable debug mode to see detailed resolution steps:
2369
+
2370
+ **CLI:**
2371
+
2372
+ ```bash
2373
+ configorama config.yml --debug
2374
+ ```
2375
+
2376
+ **Programmatic:**
2377
+
2378
+ ```javascript
2379
+ const config = await configorama('config.yml', {
2380
+ returnMetadata: true
2381
+ })
2382
+
2383
+ // Inspect resolution history
2384
+ console.log(config.resolutionHistory)
2385
+ ```
2386
+
2387
+ **Environment variable:**
2388
+
2389
+ ```bash
2390
+ DEBUG=configorama:* node app.js
2391
+ ```
2392
+
2393
+ **Output example:**
2394
+
2395
+ ```text
2396
+ configorama:resolve Resolving variable: ${env:API_KEY}
2397
+ configorama:resolve Type: env, Name: API_KEY
2398
+ configorama:resolve Resolved to: secret-key-123
2399
+ configorama:resolve Resolving variable: ${opt:stage}
2400
+ configorama:resolve Type: opt, Name: stage
2401
+ configorama:resolve Resolved to: prod
2402
+ ```
2403
+
2404
+ ---
2405
+
2406
+ ### Circular Dependencies
2407
+
2408
+ Configorama detects circular dependencies and provides helpful error messages:
2409
+
2410
+ ```yaml
2411
+ # Direct cycle
2412
+ a: ${self:b}
2413
+ b: ${self:a}
2414
+ ```
2415
+
2416
+ **Error:**
2417
+ ```text
2418
+ Circular variable dependency detected: b → a → b
2419
+
2420
+ Resolution path:
2421
+ 1. Started resolving 'b'
2422
+ 2. Required 'a' (from ${self:b})
2423
+ 3. Required 'b' (from ${self:a})
2424
+ 4. Circular dependency detected
2425
+
2426
+ To fix this, restructure your config to break the circular reference.
2427
+ ```
2428
+
2429
+ **How to fix:**
2430
+
2431
+ 1. **Use intermediate values:**
2432
+
2433
+ ```yaml
2434
+ # Before (circular)
2435
+ a: ${self:b}
2436
+ b: ${self:a}
2437
+
2438
+ # After (fixed)
2439
+ base: value
2440
+ a: ${self:base}
2441
+ b: ${self:base}
2442
+ ```
2443
+
2444
+ 2. **Make one value a constant:**
2445
+
2446
+ ```yaml
2447
+ # Before (circular)
2448
+ apiUrl: ${self:baseUrl}/api
2449
+ baseUrl: ${self:apiUrl}/v1
2450
+
2451
+ # After (fixed)
2452
+ baseUrl: https://example.com
2453
+ apiUrl: ${self:baseUrl}/api/v1
2454
+ ```
2455
+
2456
+ 3. **Restructure dependencies:**
2457
+
2458
+ ```yaml
2459
+ # Before (circular)
2460
+ database:
2461
+ connectionString: postgres://${database.host}:${database.port}/${database.name}
2462
+ host: ${self:database.connectionString}
2463
+
2464
+ # After (fixed)
2465
+ database:
2466
+ host: localhost
2467
+ port: 5432
2468
+ name: mydb
2469
+ connectionString: postgres://${database.host}:${database.port}/${database.name}
2470
+ ```
2471
+
2472
+ ---
2473
+
2474
+ ## FAQ
2475
+
2476
+ **Q: What happens with circular variable dependencies?**
2477
+
2478
+ Configorama detects circular dependencies and throws a helpful error instead of hanging forever. See [Circular Dependencies](#circular-dependencies) section for examples and fixes.
2479
+
2480
+ ---
2481
+
2482
+ **Q: Why should I use this?**
2483
+
2484
+ Never render a stale configuration file again! Configorama ensures your configs are always up-to-date with the latest environment variables, CLI flags, file contents, and custom sources.
2485
+
2486
+ ---
2487
+
2488
+ **Q: Does this work with `serverless.yml`?**
2489
+
2490
+ Yes! Use `serverless.js` as your main entry point. See [Using with Serverless Framework](#using-with-serverless-framework) for full example.
2491
+
2492
+ ---
2493
+
2494
+ **Q: Can I use this with other frameworks/tools?**
2495
+
2496
+ Yes! Configorama is framework-agnostic. It works with any tool that accepts a JavaScript object or can import a .js file. Examples:
2497
+
2498
+ - **Webpack**: `webpack.config.js`
2499
+ - **Vite**: `vite.config.js`
2500
+ - **Jest**: `jest.config.js`
2501
+ - **ESLint**: `eslint.config.js`
2502
+ - **Docker Compose**: Generate yaml from resolved config
2503
+ - **Kubernetes**: Generate manifests from resolved config
2504
+
2505
+ ---
2506
+
2507
+ **Q: How do I handle secrets securely?**
2508
+
2509
+ Best practices:
2510
+
2511
+ 1. **Use environment variables:**
2512
+ ```yaml
2513
+ apiKey: ${env:API_KEY}
2514
+ ```
2515
+
2516
+ 2. **Fetch from secret managers:**
2517
+ ```yaml
2518
+ secrets: ${file(./fetch-secrets.js)}
2519
+ ```
2520
+
2521
+ ```javascript
2522
+ // fetch-secrets.js
2523
+ const AWS = require('aws-sdk')
2524
+ const ssm = new AWS.SSM()
2525
+
2526
+ module.exports = async () => {
2527
+ const result = await ssm.getParameter({
2528
+ Name: '/myapp/api-key',
2529
+ WithDecryption: true
2530
+ }).promise()
2531
+
2532
+ return result.Parameter.Value
2533
+ }
2534
+ ```
2535
+
2536
+ 3. **Never commit secrets to version control**
2537
+ 4. **Use `.gitignore` for secret files**
2538
+ 5. **Rotate secrets regularly**
2539
+
2540
+ ---
2541
+
2542
+ **Q: Can I use variables in variable syntax?**
2543
+
2544
+ Yes! Variables are resolved recursively:
2545
+
2546
+ ```yaml
2547
+ stage: prod
2548
+ configFile: config-${stage}.yml
2549
+ config: ${file(${configFile})}
2550
+ # Resolves: ${file(config-prod.yml)}
2551
+ ```
2552
+
2553
+ ---
2554
+
2555
+ **Q: How do I migrate from Serverless Framework variables?**
2556
+
2557
+ Configorama is mostly compatible with Serverless Framework variable syntax. Key differences:
2558
+
2559
+ 1. **Cleaner self-references:**
2560
+ ```yaml
2561
+ # Serverless
2562
+ key: ${self:other.key}
2563
+
2564
+ # Configorama (both work)
2565
+ key: ${self:other.key}
2566
+ key: ${other.key}
2567
+ ```
2568
+
2569
+ 2. **Numbers as defaults:**
2570
+ ```yaml
2571
+ # Configorama supports numeric defaults
2572
+ timeout: ${env:TIMEOUT, 30}
2573
+ ```
2574
+
2575
+ 3. **Additional variable types:**
2576
+ - `${cron()}` - Cron expressions
2577
+ - `${eval()}` - Math expressions
2578
+ - `${if()}` - Conditionals
2579
+ - `${git:}` - Git data
2580
+
2581
+ ---
2582
+
2583
+ ## Advanced Usage
2584
+
2585
+ ### Multi-Stage Resolution
2586
+
2587
+ Resolve configs in multiple stages, allowing external systems to handle remaining variables:
2588
+
2589
+ ```javascript
2590
+ // Stage 1: Local resolution (resolve env, opt, file, etc.)
2591
+ const partiallyResolved = await configorama('config.yml', {
2592
+ options: { stage: 'prod' },
2593
+ allowUnresolvedVariables: ['ssm', 'cf'],
2594
+ allowUnknownVariableTypes: ['ssm', 'cf']
2595
+ })
2596
+
2597
+ // Stage 2: External system resolves SSM and CloudFormation refs
2598
+ // (e.g., Serverless Dashboard, AWS CloudFormation, etc.)
2599
+ const fullyResolved = await externalResolver(partiallyResolved)
2600
+ ```
2601
+
2602
+ **Use case:** Serverless Framework + Serverless Dashboard workflow.
2603
+
2604
+ ---
2605
+
2606
+ ### Function Arguments and Context
2607
+
2608
+ Pass dynamic data from your config to JavaScript/TypeScript functions:
2609
+
2610
+ ```yaml
2611
+ environment: prod
2612
+ region: us-east-1
2613
+ features:
2614
+ enableMetrics: true
2615
+
2616
+ # Pass resolved config values as arguments
2617
+ secrets: ${file(./get-secrets.js, ${environment}, ${region}, ${features})}
2618
+ ```
2619
+
2620
+ **get-secrets.js:**
2621
+
2622
+ ```javascript
2623
+ async function getSecrets(env, region, features, ctx) {
2624
+ // Arguments from YAML
2625
+ console.log(env) // 'prod'
2626
+ console.log(region) // 'us-east-1'
2627
+ console.log(features) // { enableMetrics: true }
2628
+
2629
+ // Context (always last argument)
2630
+ console.log(ctx.options) // CLI options
2631
+ console.log(ctx.originalConfig) // Original config
2632
+ console.log(ctx.currentConfig) // Partially resolved config
2633
+
2634
+ // Fetch secrets based on arguments
2635
+ if (env === 'prod') {
2636
+ return await fetchProdSecrets(region)
2637
+ }
2638
+
2639
+ return await fetchDevSecrets()
2640
+ }
2641
+
2642
+ module.exports = getSecrets
2643
+ ```
2644
+
2645
+ ---
2646
+
2647
+ ### Programmatic Usage
2648
+
2649
+ **Custom variable resolver:**
2650
+
2651
+ ```javascript
2652
+ const configorama = require('configorama')
2653
+
2654
+ // Add custom AWS SSM resolver
2655
+ const config = await configorama('config.yml', {
2656
+ variableSources: [{
2657
+ type: 'ssm',
2658
+ source: 'remote',
2659
+ syntax: '${ssm:/path}',
2660
+ description: 'AWS Systems Manager Parameter Store',
2661
+ match: /^ssm:/,
2662
+ resolver: async (variable) => {
2663
+ const AWS = require('aws-sdk')
2664
+ const ssm = new AWS.SSM()
2665
+
2666
+ const paramName = variable.replace(/^ssm:/, '')
2667
+ const result = await ssm.getParameter({
2668
+ Name: paramName,
2669
+ WithDecryption: true
2670
+ }).promise()
2671
+
2672
+ return result.Parameter.Value
2673
+ }
2674
+ }]
2675
+ })
2676
+ ```
2677
+
2678
+ **Custom filters:**
2679
+
2680
+ ```javascript
2681
+ const config = await configorama('config.yml', {
2682
+ filters: {
2683
+ // Custom string transformation
2684
+ slugify: (str) => str.toLowerCase().replace(/\s+/g, '-'),
2685
+
2686
+ // Custom formatting
2687
+ currency: (amount) => `$${parseFloat(amount).toFixed(2)}`,
2688
+
2689
+ // Chained filters work
2690
+ upperSnake: (str) => str.toUpperCase().replace(/\s+/g, '_')
2691
+ }
2692
+ })
2693
+ ```
2694
+
2695
+ ```yaml
2696
+ # Usage
2697
+ projectName: My Awesome Project
2698
+ slug: ${projectName | slugify} # 'my-awesome-project'
2699
+
2700
+ price: 19.99
2701
+ displayPrice: ${price | currency} # '$19.99'
2702
+
2703
+ constantName: my constant
2704
+ constName: ${constantName | upperSnake} # 'MY_CONSTANT'
2705
+ ```
2706
+
2707
+ **Custom functions:**
2708
+
2709
+ ```javascript
2710
+ const config = await configorama('config.yml', {
2711
+ functions: {
2712
+ // Timestamp generator
2713
+ timestamp: () => new Date().toISOString(),
2714
+
2715
+ // Random ID generator
2716
+ uuid: () => require('crypto').randomUUID(),
2717
+
2718
+ // Environment-based selector
2719
+ selectByEnv: (prodValue, devValue, env) => {
2720
+ return env === 'prod' ? prodValue : devValue
2721
+ }
2722
+ }
2723
+ })
2724
+ ```
2725
+
2726
+ ```yaml
2727
+ # Usage
2728
+ createdAt: ${timestamp()}
2729
+ id: ${uuid()}
2730
+
2731
+ environment: ${opt:stage, 'dev'}
2732
+ timeout: ${selectByEnv(30, 5, ${environment})}
2733
+ ```
2734
+
2735
+ ---
2736
+
2737
+ ## What's New
2738
+
2739
+ How is this different than the Serverless Framework variable system?
2740
+
2741
+ 1. **Framework-agnostic** - Use with any tool, not just Serverless Framework
2742
+
2743
+ 2. **Pluggable** - Add custom variable syntax and sources easily
2744
+
2745
+ 3. **Filters** - Transform values before resolution:
2746
+ ```yaml
2747
+ key: ${opt:stage | toUpperCase}
2748
+ ```
2749
+
2750
+ 4. **Cleaner self-references** - No need for `self:` prefix:
2751
+ ```yaml
2752
+ keyOne:
2753
+ subKey: hi
2754
+
2755
+ # Before
2756
+ key: ${self:keyOne.subKey}
2757
+
2758
+ # Now (both work)
2759
+ key: ${keyOne.subKey}
2760
+ key: ${self:keyOne.subKey}
2761
+ ```
2762
+
2763
+ 5. **Numbers as defaults** - Numeric defaults fully supported:
2764
+ ```yaml
2765
+ timeout: ${env:TIMEOUT, 30}
2766
+ port: ${opt:port, 3000}
2767
+ ```
2768
+
2769
+ 6. **Multiple format support** - TOML, YML, JSON, INI, HCL, etc.
2770
+
2771
+ 7. **Built-in functions** - Combine and transform values:
2772
+ ```yaml
2773
+ merged: ${merge(${obj1}, ${obj2})}
2774
+ ```
2775
+
2776
+ 8. **TypeScript support** - Execute TypeScript files directly:
2777
+ ```yaml
2778
+ config: ${file(./config.ts)}
2779
+ ```
2780
+
2781
+ 9. **Conditional expressions** - If/else logic in configs:
2782
+ ```yaml
2783
+ memory: ${if(${stage} === 'prod' ? 1024 : 512)}
2784
+ ```
2785
+
2786
+ 10. **Metadata extraction** - Analyze configs without resolving:
2787
+ ```javascript
2788
+ const meta = await configorama.analyze('config.yml')
1137
2789
  ```
1138
- ${merge('one', 'two')} => 'onetwo'
1139
- ```
1140
2790
 
1141
- ## Alt libs
2791
+ ---
2792
+
2793
+ ## Alternative Libraries
2794
+
2795
+ - [sls-yaml](https://github.com/01alchemist/sls-yaml) - YAML with variable support
2796
+ - [yaml-boost](https://github.com/blackflux/yaml-boost) - YAML preprocessing
2797
+ - [serverless-merge-config](https://github.com/CruGlobal/serverless-merge-config) - Merge Serverless configs
2798
+ - [serverless-terraform-variables](https://www.npmjs.com/package/serverless-terraform-variables) - Terraform variable support
1142
2799
 
1143
- - https://github.com/01alchemist/sls-yaml
2800
+ ---
1144
2801
 
1145
2802
  ## Inspiration
1146
2803
 
1147
- This is forked out of the [serverless framework](https://github.com/serverless/serverless/) variable system.
2804
+ This is forked from the [Serverless Framework](https://github.com/serverless/serverless/) variable system.
1148
2805
 
1149
2806
  **Mad props to:**
1150
2807
 
1151
2808
  [erikerikson](https://github.com/erikerikson), [eahefnawy](https://github.com/eahefnawy), [HyperBrain](https://github.com/HyperBrain), [ac360](https://github.com/ac360), [gcphost](https://github.com/gcphost), [pmuens](https://github.com/pmuens), [horike37](https://github.com/horike37), [lorengordon](https://github.com/lorengordon), [AndrewFarley](https://github.com/AndrewFarley), [tobyhede](https://github.com/tobyhede), [johncmckim](https://github.com/johncmckim), [mangas](https://github.com/mangas), [e-e-e](https://github.com/e-e-e), [BasileTrujillo](https://github.com/BasileTrujillo), [miltador](https://github.com/miltador), [sammarks](https://github.com/sammarks), [RafalWilinski](https://github.com/RafalWilinski), [indieisaconcept](https://github.com/indieisaconcept), [svdgraaf](https://github.com/svdgraaf), [infiniteluke](https://github.com/infiniteluke), [j0k3r](https://github.com/j0k3r), [craigw](https://github.com/craigw), [bsdkurt](https://github.com/bsdkurt), [aoskotsky-amplify](https://github.com/aoskotsky-amplify), and all the other folks who contributed to the variable system.
1152
2809
 
1153
- Additionally these tools were very helpful:
2810
+ **Additionally these tools were very helpful:**
1154
2811
 
1155
2812
  - [yaml-boost](https://github.com/blackflux/yaml-boost)
1156
2813
  - [serverless-merge-config](https://github.com/CruGlobal/serverless-merge-config)
1157
- - [terraform variables](https://www.npmjs.com/package/serverless-terraform-variables)
2814
+ - [serverless-terraform-variables](https://www.npmjs.com/package/serverless-terraform-variables)
2815
+
2816
+ ---
2817
+
2818
+ ## License
2819
+
2820
+ MIT © [David Wells](https://davidwells.io)
2821
+
2822
+ ## Contributing
2823
+
2824
+ Contributions welcome! Please read the [contributing guidelines](CONTRIBUTING.md) first.
2825
+
2826
+ ## Support
2827
+
2828
+ - 🐛 [Report bugs](https://github.com/DavidWells/configorama/issues)
2829
+ - 💡 [Request features](https://github.com/DavidWells/configorama/issues)
2830
+ - 📖 [Read the docs](https://github.com/DavidWells/configorama#readme)
2831
+ - 💬 [Join discussions](https://github.com/DavidWells/configorama/discussions)