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 +2063 -389
- package/cli.js +1 -1
- package/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/main.js +16 -18
- package/src/resolvers/valueFromEnv.js +3 -6
- package/src/resolvers/valueFromFile.js +4 -4
- package/src/resolvers/valueFromGit.js +101 -62
- package/src/resolvers/valueFromOptions.js +3 -7
- package/src/resolvers/valueFromParam.js +2 -1
- package/src/utils/lodash.js +19 -19
- package/src/utils/parsing/parse.js +1 -1
- package/src/utils/parsing/preProcess.js +16 -10
- package/src/utils/regex/index.js +1 -9
- package/src/utils/variables/cleanVariable.js +0 -21
- package/types/src/main.d.ts.map +1 -1
- package/types/src/resolvers/valueFromEnv.d.ts +1 -1
- package/types/src/resolvers/valueFromEnv.d.ts.map +1 -1
- package/types/src/resolvers/valueFromGit.d.ts.map +1 -1
- package/types/src/resolvers/valueFromOptions.d.ts.map +1 -1
- package/types/src/resolvers/valueFromParam.d.ts.map +1 -1
- package/types/src/utils/parsing/preProcess.d.ts.map +1 -1
- package/types/src/utils/regex/index.d.ts +3 -6
- package/types/src/utils/regex/index.d.ts.map +1 -1
- package/types/src/utils/variables/cleanVariable.d.ts.map +1 -1
- package/src/resolvers/valueFromSelf.js +0 -0
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
|
-
|
|
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
|
-
##
|
|
7
|
+
## Key Features
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
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
|
|
37
|
-
- [CLI
|
|
38
|
-
- [Parameter
|
|
39
|
-
- [Self
|
|
40
|
-
- [File
|
|
41
|
-
- [Sync/Async
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
49
|
-
- [If
|
|
50
|
-
- [Filters (
|
|
51
|
-
- [Functions (
|
|
52
|
-
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
55
|
-
- [
|
|
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
|
-
- [
|
|
61
|
-
- [
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
216
|
+
**Resolution process:**
|
|
114
217
|
|
|
115
|
-
|
|
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)
|
|
120
|
-
console.log(result.variables)
|
|
121
|
-
console.log(result.uniqueVariables)
|
|
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
|
-
**
|
|
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
|
-
|
|
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.
|
|
136
|
-
console.log(result.
|
|
137
|
-
console.log(result.metadata.variables)
|
|
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)
|
|
140
|
-
console.log(result.resolutionHistory)
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
327
|
+
```yaml
|
|
328
|
+
# Basic env var
|
|
162
329
|
apiKey: ${env:SECRET_KEY}
|
|
163
330
|
|
|
164
|
-
#
|
|
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
|
-
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
### CLI Option Flags
|
|
169
359
|
|
|
170
360
|
Access values from command line arguments passed via the `options` parameter.
|
|
171
361
|
|
|
172
|
-
```
|
|
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
|
-
#
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
```
|
|
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}
|
|
500
|
+
# Shorthand dot.prop reference
|
|
501
|
+
two: ${foo} # Resolves to 'bar'
|
|
249
502
|
|
|
250
|
-
#
|
|
251
|
-
one: ${self:foo}
|
|
503
|
+
# Explicit self file reference
|
|
504
|
+
one: ${self:foo} # Resolves to 'bar'
|
|
252
505
|
|
|
253
|
-
# Dot prop reference
|
|
254
|
-
three: ${zaz.wow.cool}
|
|
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
|
-
|
|
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
|
-
|
|
533
|
+
---
|
|
260
534
|
|
|
261
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
578
|
+
**Example file structure:**
|
|
291
579
|
|
|
292
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
612
|
+
**JavaScript file example (`async-value.js`):**
|
|
298
613
|
|
|
299
|
-
```
|
|
614
|
+
```javascript
|
|
300
615
|
async function fetchSecretsFromRemoteStore() {
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
|
643
|
+
You can pass resolved values from your config as arguments to JavaScript/TypeScript functions:
|
|
311
644
|
|
|
312
|
-
```
|
|
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
|
-
```
|
|
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
|
|
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
|
-
```
|
|
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
|
-
|
|
719
|
+
---
|
|
720
|
+
|
|
721
|
+
### TypeScript File References
|
|
385
722
|
|
|
386
723
|
Execute TypeScript files using tsx (recommended) or ts-node.
|
|
387
724
|
|
|
388
|
-
|
|
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: '
|
|
779
|
+
environment: process.env.STAGE || 'development',
|
|
429
780
|
database: {
|
|
430
|
-
host:
|
|
431
|
-
port: parseInt(
|
|
432
|
-
database:
|
|
433
|
-
ssl:
|
|
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:
|
|
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:
|
|
442
|
-
debugMode:
|
|
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
|
|
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
|
-
```
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
1058
|
+
### Eval Expressions
|
|
703
1059
|
|
|
704
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
1110
|
+
```yaml
|
|
727
1111
|
# Basic ternary (condition ? "yes" : "no")
|
|
728
|
-
status: ${if(
|
|
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(
|
|
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(
|
|
1121
|
+
grade: ${if(${score} >= 90 ? "A" : ${score} >= 80 ? "B" : "C")} # "B"
|
|
738
1122
|
|
|
739
1123
|
# Boolean result (no ternary needed)
|
|
740
|
-
isValid: ${if(${
|
|
1124
|
+
isValid: ${if(${value} > 0)} # true
|
|
741
1125
|
|
|
742
1126
|
# Logical operators
|
|
743
1127
|
enabled: true
|
|
744
1128
|
count: 5
|
|
745
|
-
canProceed: ${if(${
|
|
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
|
-
```
|
|
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:
|
|
1154
|
+
memorySize: ${if(${provider.stage} === "prod" ? 1024 : 512)}
|
|
770
1155
|
|
|
771
1156
|
# Different log retention by stage
|
|
772
|
-
logRetention: ${if(
|
|
1157
|
+
logRetention: ${if(${provider.stage} === "prod" ? 30 : 7)}
|
|
773
1158
|
|
|
774
1159
|
# Enable features per environment
|
|
775
|
-
enableDebugEndpoints: ${if(
|
|
776
|
-
enableMetrics: ${if(
|
|
1160
|
+
enableDebugEndpoints: ${if(${provider.stage} !== "prod")}
|
|
1161
|
+
enableMetrics: ${if(${provider.stage} === "prod")}
|
|
777
1162
|
|
|
778
1163
|
# Regional settings
|
|
779
|
-
replicaCount: ${if(
|
|
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(
|
|
783
|
-
role: ${if(
|
|
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
|
-
|
|
1186
|
+
---
|
|
1187
|
+
|
|
1188
|
+
### Filters (Experimental)
|
|
802
1189
|
|
|
803
1190
|
Pipe resolved values through transformation functions like case conversion.
|
|
804
1191
|
|
|
805
|
-
```
|
|
806
|
-
|
|
1192
|
+
```yaml
|
|
1193
|
+
# String transformations
|
|
1194
|
+
toUpperCaseString: ${'value' | toUpperCase } # 'VALUE'
|
|
1195
|
+
toLowerCaseString: ${'VALUE' | toLowerCase } # 'value'
|
|
807
1196
|
|
|
808
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1216
|
+
**Custom filters:**
|
|
815
1217
|
|
|
816
|
-
|
|
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
|
-
|
|
1235
|
+
---
|
|
1236
|
+
|
|
1237
|
+
### Functions (Experimental)
|
|
820
1238
|
|
|
821
1239
|
Apply built-in functions to combine, transform, or manipulate values.
|
|
822
1240
|
|
|
823
|
-
```
|
|
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
|
-
|
|
1254
|
+
# String concatenation
|
|
1255
|
+
fullName: ${concat(${firstName}, ' ', ${lastName})}
|
|
836
1256
|
|
|
837
|
-
|
|
1257
|
+
# Array operations
|
|
1258
|
+
items:
|
|
1259
|
+
- a
|
|
1260
|
+
- b
|
|
1261
|
+
- c
|
|
838
1262
|
|
|
839
|
-
|
|
1263
|
+
joinedItems: ${join(${items}, ', ')} # 'a, b, c'
|
|
1264
|
+
```
|
|
840
1265
|
|
|
841
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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
|
-
|
|
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
|
-
|
|
877
|
-
key: ${consul:xyz}
|
|
878
|
-
```
|
|
1291
|
+
---
|
|
879
1292
|
|
|
880
|
-
|
|
1293
|
+
## API Reference
|
|
881
1294
|
|
|
882
|
-
|
|
1295
|
+
### Async API
|
|
883
1296
|
|
|
884
|
-
|
|
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
|
-
**
|
|
1299
|
+
**Signature:**
|
|
892
1300
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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
|
-
|
|
1308
|
+
**Parameters:**
|
|
907
1309
|
|
|
908
|
-
|
|
|
909
|
-
|
|
910
|
-
| `
|
|
911
|
-
| `
|
|
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
|
-
|
|
1315
|
+
**Settings object:**
|
|
918
1316
|
|
|
919
|
-
|
|
920
|
-
|
|
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
|
-
|
|
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
|
-
|
|
925
|
-
// OLD → NEW
|
|
1339
|
+
**Example:**
|
|
926
1340
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
{ allowUnknownVariables: true } → { allowUnknownVariableTypes: true }
|
|
1341
|
+
```javascript
|
|
1342
|
+
const configorama = require('configorama')
|
|
930
1343
|
|
|
931
|
-
//
|
|
932
|
-
|
|
1344
|
+
// Basic usage
|
|
1345
|
+
const config = await configorama('./config.yml')
|
|
933
1346
|
|
|
934
|
-
//
|
|
935
|
-
|
|
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
|
-
//
|
|
938
|
-
|
|
939
|
-
|
|
1353
|
+
// With metadata
|
|
1354
|
+
const result = await configorama('./config.yml', {
|
|
1355
|
+
returnMetadata: true,
|
|
1356
|
+
options: { stage: 'prod' }
|
|
1357
|
+
})
|
|
940
1358
|
|
|
941
|
-
//
|
|
942
|
-
|
|
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
|
-
|
|
1364
|
+
---
|
|
1365
|
+
|
|
1366
|
+
### Sync API
|
|
946
1367
|
|
|
947
|
-
|
|
948
|
-
// Only allow specific unknown types
|
|
949
|
-
{ allowUnknownVariableTypes: ['ssm', 'cf', 's3'] }
|
|
1368
|
+
Synchronous API for blocking config resolution.
|
|
950
1369
|
|
|
951
|
-
|
|
952
|
-
|
|
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
|
-
|
|
1379
|
+
**Parameters:**
|
|
956
1380
|
|
|
957
|
-
|
|
1381
|
+
Same as async API, but `dynamicArgs` cannot be a function (must be serializable).
|
|
958
1382
|
|
|
959
|
-
|
|
1383
|
+
**Returns:**
|
|
960
1384
|
|
|
961
|
-
|
|
1385
|
+
`T` - Resolved config object (synchronously)
|
|
1386
|
+
|
|
1387
|
+
**Limitations:**
|
|
962
1388
|
|
|
963
|
-
|
|
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
|
-
//
|
|
968
|
-
const config =
|
|
969
|
-
|
|
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
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1618
|
+
// Mixed scenario
|
|
1029
1619
|
const config = await configorama(configFile, {
|
|
1030
|
-
allowUnresolvedVariables: ['param', 'file'],
|
|
1620
|
+
allowUnresolvedVariables: ['param', 'file'],
|
|
1031
1621
|
options: { stage: 'prod' }
|
|
1032
1622
|
})
|
|
1033
|
-
// Input: {
|
|
1034
|
-
//
|
|
1035
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1642
|
+
---
|
|
1043
1643
|
|
|
1044
|
-
|
|
1644
|
+
### Complete Options Reference
|
|
1045
1645
|
|
|
1046
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
-
**
|
|
1750
|
+
**This would match:**
|
|
1069
1751
|
|
|
1070
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1817
|
+
---
|
|
1075
1818
|
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
-
|
|
2083
|
+
**serverless.config.yml:**
|
|
1089
2084
|
|
|
1090
|
-
|
|
2085
|
+
```yaml
|
|
2086
|
+
service: my-service
|
|
1091
2087
|
|
|
1092
|
-
|
|
2088
|
+
provider:
|
|
2089
|
+
name: aws
|
|
2090
|
+
runtime: nodejs18.x
|
|
2091
|
+
stage: ${opt:stage, 'dev'}
|
|
2092
|
+
region: ${opt:region, 'us-east-1'}
|
|
1093
2093
|
|
|
1094
|
-
|
|
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
|
-
|
|
2100
|
+
custom:
|
|
2101
|
+
# Load stage-specific config
|
|
2102
|
+
stageConfig: ${file(./config/${opt:stage}.yml)}
|
|
1097
2103
|
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
2120
|
+
**Deploy:**
|
|
1103
2121
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
2122
|
+
```bash
|
|
2123
|
+
# Deploy to dev
|
|
2124
|
+
serverless deploy --stage dev
|
|
1107
2125
|
|
|
1108
|
-
|
|
1109
|
-
|
|
2126
|
+
# Deploy to production
|
|
2127
|
+
serverless deploy --stage prod --region us-west-2
|
|
2128
|
+
```
|
|
1110
2129
|
|
|
1111
|
-
|
|
1112
|
-
key: ${keyOne.subKey}
|
|
1113
|
-
```
|
|
2130
|
+
---
|
|
1114
2131
|
|
|
1115
|
-
|
|
2132
|
+
### Docker Deployment
|
|
1116
2133
|
|
|
1117
|
-
|
|
1118
|
-
key: ${env:whatever, 2}
|
|
1119
|
-
```
|
|
2134
|
+
**Dockerfile:**
|
|
1120
2135
|
|
|
1121
|
-
|
|
2136
|
+
```dockerfile
|
|
2137
|
+
FROM node:18-alpine
|
|
1122
2138
|
|
|
1123
|
-
|
|
2139
|
+
WORKDIR /app
|
|
1124
2140
|
|
|
1125
|
-
|
|
2141
|
+
# Copy package files
|
|
2142
|
+
COPY package*.json ./
|
|
1126
2143
|
|
|
1127
|
-
|
|
2144
|
+
# Install dependencies
|
|
2145
|
+
RUN npm ci --only=production
|
|
1128
2146
|
|
|
1129
|
-
|
|
2147
|
+
# Copy application files
|
|
2148
|
+
COPY . .
|
|
1130
2149
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
2150
|
+
# Set environment variables (can be overridden at runtime)
|
|
2151
|
+
ENV NODE_ENV=production
|
|
2152
|
+
ENV STAGE=prod
|
|
1134
2153
|
|
|
1135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2800
|
+
---
|
|
1144
2801
|
|
|
1145
2802
|
## Inspiration
|
|
1146
2803
|
|
|
1147
|
-
This is forked
|
|
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
|
|
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)
|