configorama 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,14 +29,18 @@ See [tests](https://github.com/DavidWells/configorama/tree/master/tests) for mor
29
29
  <summary>Click to expand</summary>
30
30
 
31
31
  - [About](#about)
32
- - [How it works](#how-it-works)
33
32
  - [Usage](#usage)
33
+ - [How it works](#how-it-works)
34
34
  - [Variable Sources](#variable-sources)
35
35
  - [Environment variables](#environment-variables)
36
36
  - [CLI option flags](#cli-option-flags)
37
+ - [Parameter values](#parameter-values)
37
38
  - [Self references](#self-references)
38
39
  - [File references](#file-references)
39
40
  - [Sync/Async file references](#syncasync-file-references)
41
+ - [Passing arguments to functions](#passing-arguments-to-functions)
42
+ - [ConfigContext](#configcontext)
43
+ - [Functions without arguments](#functions-without-arguments)
40
44
  - [TypeScript file references](#typescript-file-references)
41
45
  - [Git references](#git-references)
42
46
  - [Cron Values](#cron-values)
@@ -45,7 +49,11 @@ See [tests](https://github.com/DavidWells/configorama/tree/master/tests) for mor
45
49
  - [Functions (experimental)](#functions-experimental)
46
50
  - [More Examples](#more-examples)
47
51
  - [Custom Variable Sources](#custom-variable-sources)
52
+ - [Variable Source Types](#variable-source-types)
48
53
  - [Options](#options)
54
+ - [Custom Variable Syntax](#custom-variable-syntax)
55
+ - [allowUnknownVariableTypes](#allowunknownvariabletypes)
56
+ - [allowUnresolvedVariables](#allowunresolvedvariables)
49
57
  - [FAQ](#faq)
50
58
  - [Whats new](#whats-new)
51
59
  - [Alt libs](#alt-libs)
@@ -136,6 +144,7 @@ console.log(result.resolutionHistory) // Step-by-step resolution for each path
136
144
  |----------|-----------------------|------------------------|
137
145
  | env | ${env:VAR} | Environment variables |
138
146
  | opt | ${opt:flag} | CLI option flags |
147
+ | param | ${param:key} | Parameter values |
139
148
  | self | ${key} or ${self:key} | Self references |
140
149
  | file | ${file(path)} | File references |
141
150
  | git | ${git:value} | Git data |
@@ -168,6 +177,58 @@ foo: ${opt:stage}-hello
168
177
  foo: ${opt:stage, 'dev'}
169
178
  ```
170
179
 
180
+ ### Parameter values
181
+
182
+ Access parameter values via `${param:key}`. Parameters follow a resolution hierarchy:
183
+
184
+ 1. **CLI params** (`--param="key=value"`) - highest priority
185
+ 2. **Stage-specific params** (`stages.<stage>.params`)
186
+ 3. **Default params** (`stages.default.params`)
187
+
188
+ ```yml
189
+ # Direct parameter reference
190
+ appDomain: ${param:domain}
191
+
192
+ # Parameter with fallback
193
+ apiKey: ${param:apiKey, 'default-api-key'}
194
+
195
+ # Stage-specific parameters defined in config
196
+ stages:
197
+ dev:
198
+ params:
199
+ domain: dev.myapp.com
200
+ dbHost: localhost
201
+ prod:
202
+ params:
203
+ domain: myapp.com
204
+ dbHost: prod-db.myapp.com
205
+ default:
206
+ params:
207
+ domain: default.myapp.com
208
+ dbPort: 3306
209
+ ```
210
+
211
+ **CLI Usage:**
212
+
213
+ ```bash
214
+ # Single param
215
+ node app.js --param="domain=example.com"
216
+
217
+ # Multiple params
218
+ node app.js --param="domain=example.com" --param="apiKey=secret123"
219
+ ```
220
+
221
+ **Code Usage:**
222
+
223
+ ```js
224
+ const config = await configorama('config.yml', {
225
+ options: {
226
+ stage: 'prod',
227
+ param: ['domain=cli-override.com', 'apiKey=secret']
228
+ }
229
+ })
230
+ ```
231
+
171
232
  ### Self references
172
233
 
173
234
  Reference values from other key paths in the same configuration file.
@@ -682,6 +743,7 @@ The `source` property defines how the config wizard handles each variable type:
682
743
  |----------|-------------|-------------|
683
744
  | `${env:VAR}` | `user` | Environment variables |
684
745
  | `${opt:flag}` | `user` | CLI option flags |
746
+ | `${param:key}` | `user` | Parameter values |
685
747
  | `${self:key}` | `config` | Self references |
686
748
  | `${file(path)}` | `config` | File references |
687
749
  | `${text(path)}` | `config` | Raw text file references |
@@ -694,42 +756,125 @@ The `source` property defines how the config wizard handles each variable type:
694
756
  | Option | Type | Default | Description |
695
757
  |--------|------|---------|-------------|
696
758
  | `options` | object | `{}` | CLI options/flags to populate `${opt:xyz}` variables |
697
- | `allowUnknownVariables` | boolean | `false` | Allow unknown variable types to pass through (e.g., `${custom:thing}`) |
698
- | `allowUnresolvedVariables` | boolean | `false` | Allow known variable types that can't be resolved to pass through instead of throwing |
759
+ | `syntax` | string/RegExp | `${...}` | Custom variable syntax regex pattern |
760
+ | `allowUnknownVariableTypes` | boolean \| string[] | `false` | Allow unknown variable types to pass through (e.g., `${ssm:path}`) |
761
+ | `allowUnresolvedVariables` | boolean \| string[] | `false` | Allow known variable types that can't be resolved to pass through |
699
762
  | `allowUndefinedValues` | boolean | `false` | Allow undefined to be an end result |
700
763
  | `variableSources` | array | `[]` | Custom variable sources (see above) |
701
764
 
702
- > **Note:** `allowUnknownVars` is deprecated, use `allowUnknownVariables` instead.
765
+ > **Note:** Legacy options `allowUnknownVars`, `allowUnknownVariables`, `allowUnknownParams`, and `allowUnknownFileRefs` are deprecated. Use `allowUnknownVariableTypes` and `allowUnresolvedVariables` instead.
766
+
767
+ <details>
768
+ <summary><strong>Migration Guide</strong></summary>
769
+
770
+ **From legacy options to new API:**
771
+
772
+ ```js
773
+ // OLD → NEW
774
+
775
+ // Unknown variable types (unregistered resolvers)
776
+ { allowUnknownVars: true } → { allowUnknownVariableTypes: true }
777
+ { allowUnknownVariables: true } → { allowUnknownVariableTypes: true }
778
+
779
+ // Unresolved params
780
+ { allowUnknownParams: true } → { allowUnresolvedVariables: ['param'] }
781
+
782
+ // Unresolved file refs
783
+ { allowUnknownFileRefs: true } → { allowUnresolvedVariables: ['file'] }
784
+
785
+ // Both params and files
786
+ { allowUnknownParams: true, allowUnknownFileRefs: true }
787
+ → { allowUnresolvedVariables: ['param', 'file'] }
788
+
789
+ // All unresolved (env, opt, file, param, etc.)
790
+ { allowUnresolvedVariables: true } // unchanged, now also accepts arrays
791
+ ```
792
+
793
+ **New array syntax allows granular control:**
794
+
795
+ ```js
796
+ // Only allow specific unknown types
797
+ { allowUnknownVariableTypes: ['ssm', 'cf', 's3'] }
703
798
 
704
- ### allowUnknownVariables
799
+ // Only allow specific resolver types to fail gracefully
800
+ { allowUnresolvedVariables: ['param'] } // only params, env/file/opt still throw
801
+ ```
802
+
803
+ Legacy options still work but will be removed in a future major version.
804
+
805
+ </details>
705
806
 
706
- When `allowUnknownVariables: true`, unknown variable types (not registered resolvers) pass through as-is:
807
+ ### Custom Variable Syntax
808
+
809
+ 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:
707
810
 
708
811
  ```js
812
+ const configorama = require('configorama')
813
+ const { buildVariableSyntax } = require('configorama')
814
+
815
+ // Using buildVariableSyntax helper (recommended)
709
816
  const config = await configorama(configFile, {
710
- allowUnknownVariables: true,
817
+ syntax: buildVariableSyntax('{{', '}}'), // Mustache-style: {{env:FOO}}
711
818
  options: { stage: 'dev' }
712
819
  })
713
820
 
714
- // Input: { key: '${ssm:/path/to/secret}' } // ssm: not a registered type
715
- // Output: { key: '${ssm:/path/to/secret}' } // passes through instead of throwing
821
+ // Other examples:
822
+ buildVariableSyntax('${{', '}}') // ${{env:FOO}}
823
+ buildVariableSyntax('#{', '}') // #{env:FOO}
824
+ buildVariableSyntax('[[', ']]') // [[env:FOO]]
825
+ buildVariableSyntax('<', '>') // <env:FOO>
826
+ ```
827
+
828
+ The `buildVariableSyntax(prefix, suffix, excludePatterns)` function:
829
+ - Automatically excludes suffix characters from the allowed character class (prevents parsing issues)
830
+ - Supports nested variables by excluding `$` and `{` from values
831
+ - Third parameter `excludePatterns` is an array of strings to exclude via negative lookahead (default: `['AWS', 'stageVariables']`)
832
+
833
+ ### allowUnknownVariableTypes
834
+
835
+ Controls what happens when encountering unregistered variable types (e.g., `${ssm:path}` when `ssm` isn't a registered resolver).
836
+
837
+ ```js
838
+ // Allow ALL unknown types to pass through
839
+ const config = await configorama(configFile, {
840
+ allowUnknownVariableTypes: true,
841
+ options: { stage: 'dev' }
842
+ })
843
+ // Input: { key: '${ssm:/path/to/secret}' }
844
+ // Output: { key: '${ssm:/path/to/secret}' }
845
+
846
+ // Allow only SPECIFIC unknown types
847
+ const config = await configorama(configFile, {
848
+ allowUnknownVariableTypes: ['ssm', 'cf'], // only these pass through
849
+ options: { stage: 'dev' }
850
+ })
851
+ // ${ssm:path} and ${cf:stack.output} pass through
852
+ // ${custom:thing} throws an error
716
853
  ```
717
854
 
718
855
  ### allowUnresolvedVariables
719
856
 
720
- When `allowUnresolvedVariables: true`, variables that can't be resolved (missing env vars, missing files, etc.) pass through as-is instead of throwing an error:
857
+ Controls what happens when a known resolver can't find a value (missing env vars, missing files, etc.).
721
858
 
722
859
  ```js
860
+ // Allow ALL unresolved variables to pass through
723
861
  const config = await configorama(configFile, {
724
862
  allowUnresolvedVariables: true,
725
863
  options: { stage: 'dev' }
726
864
  })
727
-
728
865
  // Input: { key: '${env:MISSING_VAR}' }
729
- // Output: { key: '${env:MISSING_VAR}' } // passes through instead of throwing
866
+ // Output: { key: '${env:MISSING_VAR}' }
867
+
868
+ // Allow only SPECIFIC types to be unresolved
869
+ const config = await configorama(configFile, {
870
+ allowUnresolvedVariables: ['param', 'file'], // only these pass through
871
+ options: { stage: 'prod' }
872
+ })
873
+ // Unresolved ${param:x} and ${file(missing.yml)} pass through
874
+ // Unresolved ${env:MISSING} throws an error
730
875
  ```
731
876
 
732
- This is useful for multi-stage resolution or when you want to analyze config structure without providing all values.
877
+ This is useful for multi-stage resolution (e.g., Serverless Dashboard resolves params after local resolution).
733
878
 
734
879
  ## FAQ
735
880
 
package/cli.js CHANGED
@@ -10,7 +10,7 @@ const { makeBox } = require('@davidwells/box-logger')
10
10
 
11
11
  // Parse command line arguments
12
12
  const argv = minimist(process.argv.slice(2), {
13
- string: ['output', 'o', 'format', 'f'],
13
+ string: ['output', 'o', 'format', 'f', 'param'],
14
14
  boolean: ['help', 'h', 'version', 'v', 'debug', 'allow-unknown', 'allow-undefined', 'list', 'info', 'verify'],
15
15
  alias: {
16
16
  h: 'help',
@@ -41,6 +41,7 @@ Options:
41
41
  -d, --debug Enable debug mode
42
42
  -i, --info Show info about the config
43
43
  -v, --verify Verify the config
44
+ --param <key=value> Pass parameter values (can be used multiple times)
44
45
  --allow-unknown Allow unknown variables to pass through
45
46
  --allow-undefined Allow undefined values in the final output
46
47
 
@@ -49,6 +50,7 @@ Examples:
49
50
  configorama --info config.yml
50
51
  configorama --format yaml config.json
51
52
  configorama --output resolved.json config.yml
53
+ configorama --param="domain=myapp.com" --param="key=value" config.yml
52
54
  configorama --allow-unknown config.toml
53
55
  `)
54
56
  process.exit(0)
package/index.d.ts CHANGED
@@ -17,10 +17,43 @@ export interface ConfigoramaSettings {
17
17
  filters?: Record<string, Function>
18
18
  /** Object of custom functions */
19
19
  functions?: Record<string, Function>
20
- /** Allow unknown variables to pass through without throwing errors */
21
- allowUnknownVars?: boolean
22
- /** Allow undefined values to pass through without throwing errors */
20
+ /** Parameters to populate for ${param:xyz} */
21
+ params?: Record<string, any>
22
+
23
+ // === Variable Resolution Options ===
24
+
25
+ /**
26
+ * Allow unknown variable types (unregistered resolvers) to pass through.
27
+ * - `true`: All unknown types pass through (e.g., ${ssm:path}, ${cf:stack})
28
+ * - `false`: Throws on unknown types
29
+ * - `string[]`: Only specified types pass through (e.g., ['ssm', 'cf'])
30
+ */
31
+ allowUnknownVariableTypes?: boolean | string[]
32
+
33
+ /**
34
+ * Allow known variable types that can't resolve to pass through.
35
+ * - `true`: All unresolved variables pass through
36
+ * - `false`: Throws when resolution fails
37
+ * - `string[]`: Only specified types pass through (e.g., ['param', 'file'])
38
+ */
39
+ allowUnresolvedVariables?: boolean | string[]
40
+
41
+ /** Allow undefined values as final results */
23
42
  allowUndefinedValues?: boolean
43
+
44
+ // === Legacy Options (deprecated, use above instead) ===
45
+
46
+ /** @deprecated Use allowUnknownVariableTypes instead */
47
+ allowUnknownVars?: boolean
48
+ /** @deprecated Use allowUnknownVariableTypes instead */
49
+ allowUnknownVariables?: boolean
50
+ /** @deprecated Use allowUnresolvedVariables: ['param'] instead */
51
+ allowUnknownParams?: boolean
52
+ /** @deprecated Use allowUnresolvedVariables: ['file'] instead */
53
+ allowUnknownFileRefs?: boolean
54
+
55
+ // === Other Options ===
56
+
24
57
  /** Values passed into .js config files if user using javascript config */
25
58
  dynamicArgs?: object | Function
26
59
  /** Return both config and metadata about variables found */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "configorama",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Variable support for configuration files",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
package/src/index.js CHANGED
@@ -1,8 +1,7 @@
1
1
  const Configorama = require('./main')
2
2
  const parsers = require('./parsers')
3
3
  const enrichMetadata = require('./utils/parsing/enrichMetadata')
4
-
5
- module.exports.Configorama = Configorama
4
+ const { buildVariableSyntax } = require('./utils/variables/variableUtils')
6
5
 
7
6
  /**
8
7
  * @typedef {Object} ConfigoramaSettings
@@ -130,3 +129,17 @@ module.exports.analyze = async (configPathOrObject, settings = {}) => {
130
129
  * @type {Object}
131
130
  */
132
131
  module.exports.format = parsers
132
+
133
+ /**
134
+ * Configorama class for advanced usage
135
+ */
136
+ module.exports.Configorama = Configorama
137
+
138
+ /**
139
+ * Build variable syntax regex with proper character escaping
140
+ * @param {string} [prefix='${'] - Variable prefix (e.g., '${', '{{', '[[')
141
+ * @param {string} [suffix='}'] - Variable suffix (e.g., '}', '}}', ']]')
142
+ * @param {string[]} [excludePatterns] - Patterns to exclude via negative lookahead
143
+ * @returns {string} Regex source string for use with syntax option
144
+ */
145
+ module.exports.buildVariableSyntax = buildVariableSyntax
package/src/main.js CHANGED
@@ -2,19 +2,16 @@
2
2
  const os = require('os')
3
3
  const path = require('path')
4
4
  const fs = require('fs')
5
-
6
5
  /* // disable logs to find broken tests
7
6
  console.log = () => {}
8
7
  // process.exit(1)
9
8
  /** */
10
-
11
9
  /* External dependencies */
12
10
  const promiseFinallyShim = require('promise.prototype.finally').shim()
13
11
  const findUp = require('find-up')
14
12
  const traverse = require('traverse')
15
13
  const dotProp = require('dot-prop')
16
14
  const { makeBox, makeStackedBoxes } = require('@davidwells/box-logger')
17
-
18
15
  /* Utils - root */
19
16
  const {
20
17
  isArray, isString, isNumber, isObject, isDate, isRegExp, isFunction,
@@ -23,71 +20,56 @@ const {
23
20
  } = require('./utils/lodash')
24
21
  const PromiseTracker = require('./utils/PromiseTracker')
25
22
  const handleSignalEvents = require('./utils/handleSignalEvents')
26
-
27
23
  /* Utils - encoders */
28
24
  const { encodeUnknown, decodeUnknown } = require('./utils/encoders/unknown-values')
29
25
  const { decodeEncodedValue } = require('./utils/encoders')
30
- const { encodeJsSyntax, decodeJsSyntax, hasParenthesesPlaceholder, encodeJsonForVariable } = require('./utils/encoders/js-fixes')
31
-
26
+ const { decodeJsSyntax, hasParenthesesPlaceholder, encodeJsonForVariable } = require('./utils/encoders/js-fixes')
32
27
  /* Utils - parsing */
33
28
  const enrichMetadata = require('./utils/parsing/enrichMetadata')
34
29
  const preProcess = require('./utils/parsing/preProcess')
35
30
  const { parseFileContents } = require('./utils/parsing/parse')
36
31
  const { mergeByKeys } = require('./utils/parsing/mergeByKeys')
37
32
  const { arrayToJsonPath } = require('./utils/parsing/arrayToJsonPath')
38
-
39
33
  /* Utils - paths */
40
34
  const { normalizePath, extractFilePath, resolveInnerVariables } = require('./utils/paths/filePathUtils')
41
- const { resolveAlias } = require('./utils/paths/resolveAlias')
42
- const { resolveFilePathFromMatch } = require('./utils/paths/getFullFilePath')
43
35
  const { findLineForKey } = require('./utils/paths/findLineForKey')
44
-
45
36
  /* Utils - regex */
46
- const { combineRegexes, funcRegex, funcStartOfLineRegex, subFunctionRegex } = require('./utils/regex')
47
-
37
+ const { combineRegexes, funcRegex } = require('./utils/regex')
48
38
  /* Utils - strings */
49
39
  const formatFunctionArgs = require('./utils/strings/formatFunctionArgs')
50
-
51
40
  const { splitByComma } = require('./utils/strings/splitByComma')
52
41
  const { splitCsv } = require('./utils/strings/splitCsv')
53
42
  const { replaceAll } = require('./utils/strings/replaceAll')
54
43
  const { getTextAfterOccurrence, findNestedVariable } = require('./utils/strings/textUtils')
55
- const { trimSurroundingQuotes, ensureQuote, isSurroundedByQuotes, startsWithQuotedPipe } = require('./utils/strings/quoteUtils')
56
-
44
+ const { ensureQuote, isSurroundedByQuotes, startsWithQuotedPipe } = require('./utils/strings/quoteUtils')
57
45
  /* Utils - ui */
58
46
  const chalk = require('./utils/ui/chalk')
59
47
  const deepLog = require('./utils/ui/deep-log')
60
48
  const { logHeader } = require('./utils/ui/logs')
61
49
  const { createEditorLink } = require('./utils/ui/createEditorLink')
62
50
  const { runConfigWizard, isSensitiveVariable } = require('./utils/ui/configWizard')
63
-
64
51
  /* Utils - validation */
65
52
  const { warnIfNotFound, isValidValue } = require('./utils/validation/warnIfNotFound')
66
-
67
53
  /* Utils - variables */
68
54
  const cleanVariable = require('./utils/variables/cleanVariable')
69
55
  const appendDeepVariable = require('./utils/variables/appendDeepVariable')
70
- const { extractVariableWrapper, getFallbackString, verifyVariable } = require('./utils/variables/variableUtils')
56
+ const { extractVariableWrapper, getFallbackString, verifyVariable, buildVariableSyntax } = require('./utils/variables/variableUtils')
71
57
  const { findNestedVariables } = require('./utils/variables/findNestedVariables')
72
-
73
58
  /* Resolvers */
74
59
  const getValueFromString = require('./resolvers/valueFromString')
75
60
  const getValueFromNumber = require('./resolvers/valueFromNumber')
76
61
  const getValueFromEnv = require('./resolvers/valueFromEnv')
77
62
  const getValueFromOptions = require('./resolvers/valueFromOptions')
63
+ const getValueFromParam = require('./resolvers/valueFromParam')
78
64
  const getValueFromCron = require('./resolvers/valueFromCron')
79
65
  const getValueFromEval = require('./resolvers/valueFromEval')
80
66
  const createGitResolver = require('./resolvers/valueFromGit')
81
67
  const { getValueFromFile: getValueFromFileResolver } = require('./resolvers/valueFromFile')
82
-
83
68
  /* Parsers */
84
- const YAML = require('./parsers/yaml')
85
- const TOML = require('./parsers/toml')
86
- const INI = require('./parsers/ini')
87
69
  const JSON5 = require('./parsers/json5')
88
-
89
70
  /* Functions */
90
71
  const md5Function = require('./functions/md5')
72
+
91
73
  /**
92
74
  * Maintainer's notes:
93
75
  *
@@ -102,6 +84,7 @@ const md5Function = require('./functions/md5')
102
84
  * pause population, noting the continued depth to traverse. This motivated "deep" variables.
103
85
  * Original issue #4687
104
86
  */
87
+
105
88
  const deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/)
106
89
  const deepIndexReplacePattern = new RegExp(/^deep:|(\.[^}]+)*$/g)
107
90
  const deepIndexPattern = /deep\:(\d*)/
@@ -110,8 +93,6 @@ const fileRefSyntax = RegExp(/^file\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" =+]+?)\)/g)
110
93
  const textRefSyntax = RegExp(/^text\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" =+]+?)\)/g)
111
94
  // TODO update file regex ^file\((~?[a-zA-Z0-9._\-\/, ]+?)\)
112
95
  // To match file(asyncValue.js, lol) input params
113
- const envRefSyntax = RegExp(/^env:/g)
114
- const optRefSyntax = RegExp(/^opt:/g)
115
96
  const selfRefSyntax = RegExp(/^self:/g)
116
97
  const base64WrapperRegex = /\[_\[([A-Za-z0-9+/=\s]*)\]_\]/g
117
98
  const logLines = '─────────────────────────────────────────────────'
@@ -133,13 +114,13 @@ class Configorama {
133
114
  const options = opts || {}
134
115
  // Set opts to pass into JS file calls
135
116
  this.settings = Object.assign({}, {
136
- // Allow for unknown variable syntax to pass through without throwing errors
137
- allowUnknownVariables: false,
138
- // Allow undefined to be an end result.
117
+ // Allow unknown ${xyz:...} syntax where xyz is not a registered resolver
118
+ // Can be: false | true | ['ssm', 'cf', ...]
119
+ allowUnknownVariableTypes: false,
120
+ // Allow undefined to be an end result
139
121
  allowUndefinedValues: false,
140
- // Allow unknown file refs to pass through without throwing errors
141
- allowUnknownFileRefs: false,
142
122
  // Allow known variable types that can't be resolved to pass through
123
+ // Can be: false | true | ['param', 'file', 'env', ...]
143
124
  allowUnresolvedVariables: false,
144
125
  // Return metadata
145
126
  returnMetadata: false,
@@ -147,11 +128,27 @@ class Configorama {
147
128
  returnPreResolvedVariableDetails: false,
148
129
  }, options)
149
130
 
150
- // Backward compat: allowUnknownVars -> allowUnknownVariables
151
- if (options.allowUnknownVars !== undefined && options.allowUnknownVariables === undefined) {
152
- this.settings.allowUnknownVariables = options.allowUnknownVars
131
+ // Backward compat: allowUnknownVars -> allowUnknownVariableTypes
132
+ if (options.allowUnknownVars !== undefined && options.allowUnknownVariableTypes === undefined) {
133
+ this.settings.allowUnknownVariableTypes = options.allowUnknownVars
134
+ }
135
+ // Backward compat: allowUnknownVariables -> allowUnknownVariableTypes
136
+ if (options.allowUnknownVariables !== undefined && options.allowUnknownVariableTypes === undefined) {
137
+ this.settings.allowUnknownVariableTypes = options.allowUnknownVariables
153
138
  }
154
139
 
140
+ // Merge legacy allowUnknownParams and allowUnknownFileRefs into allowUnresolvedVariables
141
+ let unresolvedSetting = this.settings.allowUnresolvedVariables
142
+ if (unresolvedSetting !== true) {
143
+ const specificTypes = Array.isArray(unresolvedSetting) ? [...unresolvedSetting] : []
144
+ if (options.allowUnknownParams) specificTypes.push('param')
145
+ if (options.allowUnknownFileRefs) specificTypes.push('file')
146
+ if (specificTypes.length > 0) {
147
+ unresolvedSetting = [...new Set(specificTypes)]
148
+ }
149
+ }
150
+ this.settings.allowUnresolvedVariables = unresolvedSetting
151
+
155
152
  this.filterCache = {}
156
153
 
157
154
  this.foundVariables = []
@@ -160,8 +157,8 @@ class Configorama {
160
157
  // Track variable resolutions for metadata (keyed by path)
161
158
  this.resolutionTracking = {}
162
159
 
163
- const defaultSyntax = '\\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._\'",|\\-\\/\\(\\)\\\\]+?)}'
164
-
160
+ const defaultSyntax = buildVariableSyntax('${', '}', ['AWS', 'stageVariables'])
161
+
165
162
  const varSyntax = options.syntax || defaultSyntax
166
163
  let varRegex
167
164
  if (typeof varSyntax === 'string') {
@@ -229,6 +226,14 @@ class Configorama {
229
226
  */
230
227
  getValueFromOptions,
231
228
 
229
+ /**
230
+ * Parameters
231
+ * Usage:
232
+ * ${param:domain}
233
+ * ${param:key, "fallbackValue"}
234
+ */
235
+ getValueFromParam,
236
+
232
237
  /**
233
238
  * Cron expressions
234
239
  * Usage:
@@ -542,6 +547,56 @@ class Configorama {
542
547
  this.callCount = 0
543
548
  }
544
549
 
550
+ /**
551
+ * Check if unresolved variables of a given type should pass through
552
+ * @param {string} type - The resolver type (e.g., 'param', 'file', 'env')
553
+ * @returns {boolean}
554
+ */
555
+ isUnresolvedAllowed(type) {
556
+ const setting = this.settings.allowUnresolvedVariables
557
+ if (setting === true) return true
558
+ if (setting === false || setting === undefined) return false
559
+ if (Array.isArray(setting) && setting.includes(type)) return true
560
+ return false
561
+ }
562
+
563
+ /**
564
+ * Extract type prefix from a variable string
565
+ * @param {string} varString - Variable string like 'ssm:path/to/thing' or 'custom:value'
566
+ * @returns {string|null} The type prefix or null if not found
567
+ */
568
+ extractTypePrefix(varString) {
569
+ if (!varString || typeof varString !== 'string') return null
570
+ const colonIndex = varString.indexOf(':')
571
+ if (colonIndex === -1) return null
572
+ return varString.substring(0, colonIndex)
573
+ }
574
+
575
+ /**
576
+ * Check if unknown variable types should pass through
577
+ * @param {string} varString - Variable string like 'ssm:path' or full '${ssm:path}'
578
+ * @returns {boolean}
579
+ */
580
+ isUnknownTypeAllowed(varString) {
581
+ const setting = this.settings.allowUnknownVariableTypes
582
+ if (setting === true) return true
583
+ if (setting === false || setting === undefined) return false
584
+ if (Array.isArray(setting)) {
585
+ // Extract type prefix from variable string
586
+ // Handle both 'ssm:path' and '${ssm:path}' formats
587
+ let cleanVar = varString
588
+ if (cleanVar.startsWith(this.varPrefix)) {
589
+ cleanVar = cleanVar.slice(this.varPrefix.length)
590
+ }
591
+ if (cleanVar.endsWith(this.varSuffix)) {
592
+ cleanVar = cleanVar.slice(0, -this.varSuffix.length)
593
+ }
594
+ const typePrefix = this.extractTypePrefix(cleanVar)
595
+ if (typePrefix && setting.includes(typePrefix)) return true
596
+ }
597
+ return false
598
+ }
599
+
545
600
  // ################
546
601
  // ## PUBLIC API ##
547
602
  // ################
@@ -1941,8 +1996,8 @@ class Configorama {
1941
1996
  for (let i = 0; i < matches.length; i += 1) {
1942
1997
  warnIfNotFound(matches[i].variable, results[i], {
1943
1998
  patterns: {
1944
- env: envRefSyntax,
1945
- opt: optRefSyntax,
1999
+ env: getValueFromEnv.match,
2000
+ opt: getValueFromOptions.match,
1946
2001
  self: selfRefSyntax,
1947
2002
  file: fileRefSyntax,
1948
2003
  deep: deepRefSyntax,
@@ -2380,7 +2435,7 @@ class Configorama {
2380
2435
 
2381
2436
  if (nestedVar) {
2382
2437
  const fallbackStr = getFallbackString(splitVars, nestedVar)
2383
- if (!this.settings.allowUnknownVariables) {
2438
+ if (!this.isUnknownTypeAllowed(nestedVar)) {
2384
2439
  verifyVariable(nestedVar, valueObject, this.variableTypes, this.config)
2385
2440
  }
2386
2441
 
@@ -2864,14 +2919,21 @@ Missing Value ${missingValue} - ${matchedString}
2864
2919
  // console.log('nestedVars', nestedVars)
2865
2920
  const noNestedVars = nestedVars.length < 2
2866
2921
 
2867
- if (this.settings.allowUnknownFileRefs && variableString.match(fileRefSyntax)) {
2868
- // Encode the unknown file variable to pass through resolution
2922
+ // Check if this unresolved variable type should pass through
2923
+ const isFileRef = variableString.match(fileRefSyntax)
2924
+ const isParamRef = variableString.match(getValueFromParam.match)
2925
+
2926
+ // Params pass through entirely (including fallbacks) for third-party resolution
2927
+ if (isParamRef && this.isUnresolvedAllowed('param')) {
2869
2928
  return Promise.resolve(encodeUnknown(propertyString))
2870
2929
  }
2871
2930
 
2872
- if (this.settings.allowUnresolvedVariables) {
2931
+ const isUnresolvedAllowed =
2932
+ this.settings.allowUnresolvedVariables === true ||
2933
+ (isFileRef && this.isUnresolvedAllowed('file'))
2934
+
2935
+ if (isUnresolvedAllowed) {
2873
2936
  // Check if outer expression has fallbacks we can use
2874
- // valueCount[0] is the primary var, valueCount[1+] are fallbacks
2875
2937
  if (valueCount.length > 1) {
2876
2938
  const primaryVar = valueCount[0]
2877
2939
  // If the unresolvable variableString is used INSIDE the primary var,
@@ -2880,7 +2942,6 @@ Missing Value ${missingValue} - ${matchedString}
2880
2942
  return Promise.resolve(undefined)
2881
2943
  }
2882
2944
  }
2883
- // Encode unresolved variable to pass through resolution
2884
2945
  return Promise.resolve(encodeUnknown(propertyString))
2885
2946
  }
2886
2947
 
@@ -3046,7 +3107,7 @@ Missing Value ${missingValue} - ${matchedString}
3046
3107
  // console.log('nestedVar', nestedVar)
3047
3108
 
3048
3109
  if (nestedVar) {
3049
- if (!this.settings.allowUnknownVariables) {
3110
+ if (!this.isUnknownTypeAllowed(nestedVar)) {
3050
3111
  verifyVariable(nestedVar, valueObject, this.variableTypes, this.config)
3051
3112
  }
3052
3113
  const fallbackStr = getFallbackString(split, nestedVar)
@@ -3148,8 +3209,8 @@ Missing Value ${missingValue} - ${matchedString}
3148
3209
 
3149
3210
 
3150
3211
 
3151
- /* Pass through unknown variables */
3152
- if (this.settings.allowUnknownVariables || allowSpecialCase) {
3212
+ /* Pass through unknown variable types */
3213
+ if (allowSpecialCase || this.isUnknownTypeAllowed(propertyString)) {
3153
3214
  // console.log('allowUnknownVars propertyString', propertyString)
3154
3215
  const varMatches = propertyString.match(this.variableSyntax)
3155
3216
  let allowUnknownVars = propertyString