configorama 0.7.2 → 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.2",
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
@@ -197,7 +197,13 @@ async function getValueFromFile(ctx, variableString, options) {
197
197
  // console.log('NO FILE FOUND', fullFilePath)
198
198
  // console.log('variableString', variableString)
199
199
 
200
- if (!hasFallback && !ctx.opts.allowUnknownFileRefs) {
200
+ // Check if file refs are allowed to pass through unresolved
201
+ const allowUnresolved = ctx.opts.allowUnresolvedVariables
202
+ const isFileAllowed = allowUnresolved === true ||
203
+ (Array.isArray(allowUnresolved) && allowUnresolved.includes('file')) ||
204
+ ctx.opts.allowUnknownFileRefs // backward compat
205
+
206
+ if (!hasFallback && !isFileAllowed) {
201
207
  const errorMsg = makeBox({
202
208
  title: `File Not Found in ${originalVar}`,
203
209
  minWidth: '100%',
@@ -0,0 +1,91 @@
1
+
2
+ const paramRefSyntax = RegExp(/^param:/g)
3
+
4
+ /**
5
+ * Resolves parameter values following the Serverless Framework parameter resolution hierarchy:
6
+ * 1. CLI params (--param="key=value")
7
+ * 2. Stage-specific params (stages.<stage>.params)
8
+ * 3. Default params (stages.default.params)
9
+ *
10
+ * @param {string} variableString - The variable string (e.g., "param:domain")
11
+ * @param {Object} options - CLI options that may contain params
12
+ * @param {Object} config - The full config object for stage-specific params
13
+ * @returns {Promise<any>} The resolved parameter value
14
+ */
15
+ function getValueFromParam(variableString, options = {}, config = {}) {
16
+ const requestedParam = variableString.split(':')[1]
17
+
18
+ if (requestedParam === '') {
19
+ throw new Error(`Invalid variable syntax for parameter reference "${variableString}".
20
+
21
+ \${param} variable must have a key path.
22
+
23
+ Example: \${param:domain}
24
+ `)
25
+ }
26
+
27
+ let valueToPopulate
28
+
29
+ // 1. First, check CLI params (--param="key=value")
30
+ // The param option can be either a string or an array of strings
31
+ if (options.param) {
32
+ const params = Array.isArray(options.param) ? options.param : [options.param]
33
+
34
+ // Parse param flags in the format "key=value"
35
+ for (const param of params) {
36
+ const [key, ...valueParts] = param.split('=')
37
+ if (key === requestedParam) {
38
+ valueToPopulate = valueParts.join('=') // rejoin in case value contains =
39
+ return Promise.resolve(valueToPopulate)
40
+ }
41
+ }
42
+ }
43
+
44
+ // 2. Check for stage-specific params (stages.<stage>.params)
45
+ const stage = options.stage || 'dev'
46
+ if (config.stages && config.stages[stage] && config.stages[stage].params) {
47
+ valueToPopulate = config.stages[stage].params[requestedParam]
48
+ if (valueToPopulate !== undefined) {
49
+ return Promise.resolve(valueToPopulate)
50
+ }
51
+ }
52
+
53
+ // 3. Check for default params (stages.default.params)
54
+ if (config.stages && config.stages.default && config.stages.default.params) {
55
+ valueToPopulate = config.stages.default.params[requestedParam]
56
+ if (valueToPopulate !== undefined) {
57
+ return Promise.resolve(valueToPopulate)
58
+ }
59
+ }
60
+
61
+ // 4. Check top-level params property (for backwards compatibility)
62
+ if (config.params) {
63
+ // Check stage-specific params first
64
+ if (config.params[stage]) {
65
+ valueToPopulate = config.params[stage][requestedParam]
66
+ if (valueToPopulate !== undefined) {
67
+ return Promise.resolve(valueToPopulate)
68
+ }
69
+ }
70
+
71
+ // Then check default params
72
+ if (config.params.default) {
73
+ valueToPopulate = config.params.default[requestedParam]
74
+ if (valueToPopulate !== undefined) {
75
+ return Promise.resolve(valueToPopulate)
76
+ }
77
+ }
78
+ }
79
+
80
+ // If not found, return undefined (will trigger fallback if specified)
81
+ return Promise.resolve(valueToPopulate)
82
+ }
83
+
84
+ module.exports = {
85
+ type: 'param',
86
+ source: 'user',
87
+ syntax: '${param:paramName}',
88
+ description: 'Resolves parameter values from CLI flags, stage-specific params, or default params. Examples: ${param:domain}, ${param:key, "fallbackValue"}',
89
+ match: paramRefSyntax,
90
+ resolver: getValueFromParam
91
+ }
@@ -0,0 +1,207 @@
1
+ const { test } = require('uvu')
2
+ const assert = require('uvu/assert')
3
+ const { resolver } = require('./valueFromParam')
4
+
5
+ test('Resolves parameter from CLI flag', async () => {
6
+ const options = { param: 'domain=myapp.com' }
7
+ const result = await resolver('param:domain', options)
8
+ assert.is(result, 'myapp.com')
9
+ })
10
+
11
+ test('Resolves parameter from multiple CLI flags', async () => {
12
+ const options = { param: ['domain=myapp.com', 'key=value'] }
13
+ const result = await resolver('param:key', options)
14
+ assert.is(result, 'value')
15
+ })
16
+
17
+ test('Resolves parameter with equals sign in value', async () => {
18
+ const options = { param: 'connectionString=Server=localhost;Port=5432' }
19
+ const result = await resolver('param:connectionString', options)
20
+ assert.is(result, 'Server=localhost;Port=5432')
21
+ })
22
+
23
+ test('Resolves parameter from stage-specific params', async () => {
24
+ const options = { stage: 'prod' }
25
+ const config = {
26
+ stages: {
27
+ prod: {
28
+ params: {
29
+ domain: 'production.myapp.com'
30
+ }
31
+ }
32
+ }
33
+ }
34
+ const result = await resolver('param:domain', options, config)
35
+ assert.is(result, 'production.myapp.com')
36
+ })
37
+
38
+ test('Resolves parameter from default stage params', async () => {
39
+ const options = { stage: 'dev' }
40
+ const config = {
41
+ stages: {
42
+ default: {
43
+ params: {
44
+ domain: 'default.myapp.com'
45
+ }
46
+ }
47
+ }
48
+ }
49
+ const result = await resolver('param:domain', options, config)
50
+ assert.is(result, 'default.myapp.com')
51
+ })
52
+
53
+ test('CLI params override stage params', async () => {
54
+ const options = {
55
+ stage: 'prod',
56
+ param: 'domain=cli-override.com'
57
+ }
58
+ const config = {
59
+ stages: {
60
+ prod: {
61
+ params: {
62
+ domain: 'production.myapp.com'
63
+ }
64
+ }
65
+ }
66
+ }
67
+ const result = await resolver('param:domain', options, config)
68
+ assert.is(result, 'cli-override.com')
69
+ })
70
+
71
+ test('Stage-specific params override default params', async () => {
72
+ const options = { stage: 'prod' }
73
+ const config = {
74
+ stages: {
75
+ default: {
76
+ params: {
77
+ domain: 'default.myapp.com'
78
+ }
79
+ },
80
+ prod: {
81
+ params: {
82
+ domain: 'production.myapp.com'
83
+ }
84
+ }
85
+ }
86
+ }
87
+ const result = await resolver('param:domain', options, config)
88
+ assert.is(result, 'production.myapp.com')
89
+ })
90
+
91
+ test('Returns undefined for non-existent parameter', async () => {
92
+ const options = { stage: 'dev' }
93
+ const config = { stages: { dev: { params: {} } } }
94
+ const result = await resolver('param:nonExistent', options, config)
95
+ assert.is(result, undefined)
96
+ })
97
+
98
+ test('Throws error for empty parameter name', async () => {
99
+ try {
100
+ await resolver('param:')
101
+ assert.unreachable('Should have thrown an error')
102
+ } catch (error) {
103
+ assert.ok(error.message.includes('Invalid variable syntax'))
104
+ assert.ok(error.message.includes('must have a key path'))
105
+ }
106
+ })
107
+
108
+ test('Defaults to dev stage when no stage specified', async () => {
109
+ const options = {}
110
+ const config = {
111
+ stages: {
112
+ dev: {
113
+ params: {
114
+ domain: 'dev.myapp.com'
115
+ }
116
+ }
117
+ }
118
+ }
119
+ const result = await resolver('param:domain', options, config)
120
+ assert.is(result, 'dev.myapp.com')
121
+ })
122
+
123
+ test('Supports top-level params property (backwards compatibility)', async () => {
124
+ const options = { stage: 'prod' }
125
+ const config = {
126
+ params: {
127
+ prod: {
128
+ domain: 'production.myapp.com'
129
+ }
130
+ }
131
+ }
132
+ const result = await resolver('param:domain', options, config)
133
+ assert.is(result, 'production.myapp.com')
134
+ })
135
+
136
+ test('Supports top-level params default property', async () => {
137
+ const options = { stage: 'dev' }
138
+ const config = {
139
+ params: {
140
+ default: {
141
+ domain: 'default.myapp.com'
142
+ }
143
+ }
144
+ }
145
+ const result = await resolver('param:domain', options, config)
146
+ assert.is(result, 'default.myapp.com')
147
+ })
148
+
149
+ test('Prefers stages property over params property', async () => {
150
+ const options = { stage: 'prod' }
151
+ const config = {
152
+ stages: {
153
+ prod: {
154
+ params: {
155
+ domain: 'stages.prod.myapp.com'
156
+ }
157
+ }
158
+ },
159
+ params: {
160
+ prod: {
161
+ domain: 'params.prod.myapp.com'
162
+ }
163
+ }
164
+ }
165
+ const result = await resolver('param:domain', options, config)
166
+ assert.is(result, 'stages.prod.myapp.com')
167
+ })
168
+
169
+ test('Returns Promise that resolves to value', async () => {
170
+ const options = { param: 'test=promise-value' }
171
+ const promise = resolver('param:test', options)
172
+ assert.ok(promise instanceof Promise)
173
+ const result = await promise
174
+ assert.is(result, 'promise-value')
175
+ })
176
+
177
+ test('Handles parameter with special characters in value', async () => {
178
+ const options = { param: 'special=value-with-special-chars-!@#$%' }
179
+ const result = await resolver('param:special', options)
180
+ assert.is(result, 'value-with-special-chars-!@#$%')
181
+ })
182
+
183
+ test('Handles empty string parameter value', async () => {
184
+ const options = { param: 'empty=' }
185
+ const result = await resolver('param:empty', options)
186
+ assert.is(result, '')
187
+ })
188
+
189
+ test('Handles numeric parameter value', async () => {
190
+ const options = { param: 'port=3000' }
191
+ const result = await resolver('param:port', options)
192
+ assert.is(result, '3000')
193
+ })
194
+
195
+ test('Handles parameter with underscore', async () => {
196
+ const options = { param: 'my_param=underscore-value' }
197
+ const result = await resolver('param:my_param', options)
198
+ assert.is(result, 'underscore-value')
199
+ })
200
+
201
+ test('Handles parameter with numbers', async () => {
202
+ const options = { param: 'param123=numeric-value' }
203
+ const result = await resolver('param:param123', options)
204
+ assert.is(result, 'numeric-value')
205
+ })
206
+
207
+ test.run()
@@ -88,8 +88,61 @@ Remove or update the \${${variableString}} to fix
88
88
  return isRealVariable
89
89
  }
90
90
 
91
+ /**
92
+ * Build default variable syntax regex with dynamic character class
93
+ * Excludes suffix characters from the allowed set to prevent parsing issues
94
+ * @param {string} [prefix='${'] - Variable prefix
95
+ * @param {string} [suffix='}'] - Variable suffix
96
+ * @param {string[]} [excludePatterns=['AWS', 'stageVariables']] - Patterns to exclude via negative lookahead
97
+ * @returns {string} Regex source string
98
+ */
99
+ function buildVariableSyntax(prefix = '${', suffix = '}', excludePatterns = ['AWS', 'stageVariables']) {
100
+ // All allowed characters, stored as individual escaped entries for regex character class
101
+ // Each entry is how it appears in a regex character class
102
+ // NOTE: { and } are intentionally excluded - they break nested variable matching
103
+ // NOTE: $ is intentionally excluded - it's part of variable prefix and breaks nesting
104
+ const allChars = [
105
+ ' ', '~', ':', 'a-z', 'A-Z', '0-9', '=', '+', '!', '@', '#', '%',
106
+ '\\^', '&', ';', '`', '\\*', '<', '>', '\\?', '\\.', '_', "'", '"', ',',
107
+ '\\|', '\\-', '\\/', '\\(', '\\)', '\\[', '\\]', '\\\\'
108
+ ]
109
+
110
+ // Map of unescaped char to its escaped form in regex character class
111
+ const charEscapeMap = {
112
+ '^': '\\^', '*': '\\*', '?': '\\?', '.': '\\.', '|': '\\|',
113
+ '-': '\\-', '/': '\\/', '(': '\\(', ')': '\\)', '[': '\\[', ']': '\\]',
114
+ '\\': '\\\\'
115
+ }
116
+
117
+ // Get unique characters from suffix that need to be excluded
118
+ const suffixChars = [...new Set(suffix.split(''))]
119
+
120
+ // Filter out chars that appear in suffix
121
+ const allowedChars = allChars.filter(charEntry => {
122
+ for (const sc of suffixChars) {
123
+ const escaped = charEscapeMap[sc] || sc
124
+ if (charEntry === escaped || charEntry === sc) {
125
+ return false
126
+ }
127
+ }
128
+ return true
129
+ })
130
+
131
+ // Escape prefix and suffix for regex
132
+ const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
133
+ const escapedSuffix = suffix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
134
+
135
+ // Build negative lookahead for excluded patterns
136
+ const lookahead = excludePatterns.length > 0
137
+ ? `(?!${excludePatterns.join('|')})`
138
+ : ''
139
+
140
+ return `${escapedPrefix}(${lookahead}[${allowedChars.join('')}]+?)${escapedSuffix}`
141
+ }
142
+
91
143
  module.exports = {
92
144
  extractVariableWrapper,
93
145
  getFallbackString,
94
- verifyVariable
146
+ verifyVariable,
147
+ buildVariableSyntax
95
148
  }
@@ -150,5 +150,49 @@ test('extractVariableWrapper - strips non-capturing group prefix', () => {
150
150
  assert.equal(result.suffix, '}')
151
151
  })
152
152
 
153
+ // Tests for buildVariableSyntax
154
+ const { buildVariableSyntax } = require('./variableUtils')
155
+
156
+ test('buildVariableSyntax - default ${} syntax excludes $ {', () => {
157
+ const syntax = buildVariableSyntax('${', '}')
158
+ const regex = new RegExp(syntax, 'g')
159
+ // $ and { in value cause no match (they're not in character class)
160
+ assert.not.ok("${env:FOO, 'test$value'}".match(regex))
161
+ assert.not.ok("${env:FOO, 'test{value'}".match(regex))
162
+ // } causes partial match (ends early at the } in value)
163
+ const partialMatch = "${env:FOO, 'test}value'}".match(regex)
164
+ assert.ok(partialMatch)
165
+ assert.is(partialMatch[0], "${env:FOO, 'test}")
166
+ })
167
+
168
+ test('buildVariableSyntax - supports backslash in values', () => {
169
+ const syntax = buildVariableSyntax('${', '}')
170
+ const regex = new RegExp(syntax, 'g')
171
+ const match = "${env:FOO, 'path\\to\\file'}".match(regex)
172
+ assert.is(match[0], "${env:FOO, 'path\\to\\file'}")
173
+ })
174
+
175
+ test('buildVariableSyntax - double brace ${{}} syntax excludes }', () => {
176
+ const syntax = buildVariableSyntax('${{', '}}')
177
+ const regex = new RegExp(syntax, 'g')
178
+ const match = "${{env:FOO, 'value'}}".match(regex)
179
+ assert.is(match[0], "${{env:FOO, 'value'}}")
180
+ })
181
+
182
+ test('buildVariableSyntax - angle bracket <> syntax excludes >', () => {
183
+ const syntax = buildVariableSyntax('<', '>')
184
+ const regex = new RegExp(syntax, 'g')
185
+ // > in value causes partial match
186
+ const match = "<env:FOO, 'a>b'>".match(regex)
187
+ assert.is(match[0], "<env:FOO, 'a>")
188
+ })
189
+
190
+ test('buildVariableSyntax - bracket [[]] syntax excludes ]', () => {
191
+ const syntax = buildVariableSyntax('[[', ']]')
192
+ const regex = new RegExp(syntax, 'g')
193
+ const match = "[[env:FOO, 'value']]".match(regex)
194
+ assert.is(match[0], "[[env:FOO, 'value']]")
195
+ })
196
+
153
197
  // Run all tests
154
198
  test.run()
@@ -3,10 +3,11 @@ declare namespace _exports {
3
3
  }
4
4
  declare function _exports<T = any>(configPathOrObject: string | any, settings?: ConfigoramaSettings): Promise<T | ConfigoramaResult<T>>;
5
5
  declare namespace _exports {
6
- export { Configorama };
7
6
  export function sync<T = any>(configPathOrObject: string | any, settings?: ConfigoramaSettings): T;
8
7
  export function analyze(configPathOrObject: string | object, settings?: object): Promise<any>;
9
8
  export { parsers as format };
9
+ export { Configorama };
10
+ export { buildVariableSyntax };
10
11
  }
11
12
  export = _exports;
12
13
  type ConfigoramaSettings = {
@@ -95,6 +96,7 @@ type ConfigoramaResult<T = any> = {
95
96
  */
96
97
  resolutionHistory: any;
97
98
  };
98
- import Configorama = require("./main");
99
99
  import parsers = require("./parsers");
100
+ import Configorama = require("./main");
101
+ import { buildVariableSyntax } from "./utils/variables/variableUtils";
100
102
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":";;;AAwCiB,0BALH,CAAC,4BACJ,MAAM,MAAO,aACb,mBAAmB,GACjB,OAAO,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAiD7C;;;IASqB,qBALR,CAAC,4BACJ,MAAM,MAAO,aACb,mBAAmB,GACjB,CAAC,CAgBb;IAQwB,4CAJb,MAAM,GAAC,MAAM,aACd,MAAM,gBAUhB;;;;;;;;;;;;;;aApHa,MAAM;;;;gBACN,MAAM;;;;;;;;;;;;;;;;;;;;uBAIN,OAAO;;;;2BACP,OAAO;;;;kBACP,cAAe;;;;qBACf,OAAO;;;;gBACP,MAAM,EAAE;;;;;;;;uBAKR,CAAC;;;;oBAED,MAAM;;;;;;;;;;YAEN,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":";;;AAuCiB,0BALH,CAAC,4BACJ,MAAM,MAAO,aACb,mBAAmB,GACjB,OAAO,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAiD7C;;IASqB,qBALR,CAAC,4BACJ,MAAM,MAAO,aACb,mBAAmB,GACjB,CAAC,CAgBb;IAQwB,4CAJb,MAAM,GAAC,MAAM,aACd,MAAM,gBAUhB;;;;;;;;;;;;;;;;aApHa,MAAM;;;;gBACN,MAAM;;;;;;;;;;;;;;;;;;;;uBAIN,OAAO;;;;2BACP,OAAO;;;;kBACP,cAAe;;;;qBACf,OAAO;;;;gBACP,MAAM,EAAE;;;;;;;;uBAKR,CAAC;;;;oBAED,MAAM;;;;;;;;;;YAEN,CAAC"}
@@ -27,6 +27,24 @@ declare class Configorama {
27
27
  deep: any[];
28
28
  leaves: any[];
29
29
  callCount: number;
30
+ /**
31
+ * Check if unresolved variables of a given type should pass through
32
+ * @param {string} type - The resolver type (e.g., 'param', 'file', 'env')
33
+ * @returns {boolean}
34
+ */
35
+ isUnresolvedAllowed(type: string): boolean;
36
+ /**
37
+ * Extract type prefix from a variable string
38
+ * @param {string} varString - Variable string like 'ssm:path/to/thing' or 'custom:value'
39
+ * @returns {string|null} The type prefix or null if not found
40
+ */
41
+ extractTypePrefix(varString: string): string | null;
42
+ /**
43
+ * Check if unknown variable types should pass through
44
+ * @param {string} varString - Variable string like 'ssm:path' or full '${ssm:path}'
45
+ * @returns {boolean}
46
+ */
47
+ isUnknownTypeAllowed(varString: string): boolean;
30
48
  /**
31
49
  * Populate all variables in the service, conveniently remove and restore the service attributes
32
50
  * that confuse the population methods.
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AA6HA;IACE,0CAgaC;IAxZC,cAaW;IAOX,gBAAqB;IAErB,sBAAwB;IACxB,qBAAuB;IAGvB,uBAA4B;IAc5B,uBAAoC;IAIpC,kBAAqC;IACrC,kBAAqC;IAErC,yBAA+F;IAC/F,yBAAuD;IACvD,kCAAyE;IAKvE,YAA0B;IAE1B,oBAA6C;IAE7C,gBAAoD;IAOpD,uBAAkC;IAElC,uBAA8B;IAE9B,uBAAkC;IASpC,wBAAmC;IAGnC,mBAsGC;IAoED,4BAA8C;IAO9C,aA2EC;IAUD,oBAEC;IAGD,eAkDC;IAOD,YAAc;IACd,cAAgB;IAChB,kBAAkB;IAMpB;;;;;OAKG;IACH,oBAFa,OAAO,CAAC,GAAG,CAAC,CAqsBxB;IAlsBC,aAA4B;IAc1B,2BAA4B;IAQ5B,uBAAgD;IA8qBpD;;;OAGG;IACH,2BAFa,MAAM,CA8alB;IAvBC;;;;;;;;;;;;;;;MAoBC;IAIH;;;;OAIG;IACH,uCAFa,OAAO,CAAC,GAAG,CAAC,CAIxB;IACD,+CAsBC;IAKD;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;OAWG;IACH,mFAHa;;;;cAZC,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAWD,CA+D9B;IACD;;;OAGG;IACH;;;;;OAKG;IACH,oCAHa,OAAO,CAAC;;;;cAnFP,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAkFgB,CAAC,EAAE,CA6BlD;IACD;;;;;OAKG;IACH,iDAFa,OAAO,CAAC,IAAI,CAAC,CAWzB;IAID;;;;;OAKG;IACH;;;;OAIG;IACH,2BAFa,eAAc;;;;;;;;;;OAAa,CAavC;IACD;;;;;OAKG;IACH,yBAHW;;;;;;;;;;OAAa,gCACX,cAAS,CAOrB;IACD;;;;;;OAMG;IACH,6DAFa,GAAC,CA+Kb;IAKD;;;;;;;OAOG;IACH,yDAHa,OAAO,CAAC,GAAG,CAAC,CAiCxB;IACD;;;;OAIG;IAOH;;;;;;OAMG;IACH,wFA2BC;IACD;;;;;;;;;;;OAWG;IACH,8BARG;QAAyB,KAAK,EAAtB,GAAG;QACoB,IAAI,GAA3B,MAAM,EAAE;QACa,cAAc,GAAnC,MAAM;QACc,iBAAiB;KAC7C,6CAEU;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,QAAQ;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,CAmY9J;IAID;;;;;;;;OAQG;IACH,qEAHa,OAAO,CAAC,GAAG,CAAC,CAoExB;IAKD;;;;;;;OAOG;IACH,0FAFa,OAAO,CAAC,GAAG,CAAC,CAofxB;IACD,+EA8BC;IACD,yDAeC;IACD,oEA6BC;IAKD,8CAQC;IACD,kDAyBC;IACD;;;;;;;;;;;;;OAaG;IACH,wEAoDC;IAKD,4BAOC;IACD,sCAoDC;CACF"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AA0GA;IACE,0CAwbC;IAhbC,cAaW;IAuBX,gBAAqB;IAErB,sBAAwB;IACxB,qBAAuB;IAGvB,uBAA4B;IAc5B,uBAAoC;IAIpC,kBAAqC;IACrC,kBAAqC;IAErC,yBAA+F;IAC/F,yBAAuD;IACvD,kCAAyE;IAKvE,YAA0B;IAE1B,oBAA6C;IAE7C,gBAAoD;IAOpD,uBAAkC;IAElC,uBAA8B;IAE9B,uBAAkC;IASpC,wBAAmC;IAGnC,mBA8GC;IAoED,4BAA8C;IAO9C,aA2EC;IAUD,oBAEC;IAGD,eAkDC;IAOD,YAAc;IACd,cAAgB;IAChB,kBAAkB;IAGpB;;;;OAIG;IACH,0BAHW,MAAM,GACJ,OAAO,CAQnB;IAED;;;;OAIG;IACH,6BAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAOvB;IAED;;;;OAIG;IACH,gCAHW,MAAM,GACJ,OAAO,CAoBnB;IAKD;;;;;OAKG;IACH,oBAFa,OAAO,CAAC,GAAG,CAAC,CAqsBxB;IAlsBC,aAA4B;IAc1B,2BAA4B;IAQ5B,uBAAgD;IA8qBpD;;;OAGG;IACH,2BAFa,MAAM,CA8alB;IAvBC;;;;;;;;;;;;;;;MAoBC;IAIH;;;;OAIG;IACH,uCAFa,OAAO,CAAC,GAAG,CAAC,CAIxB;IACD,+CAsBC;IAKD;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;OAWG;IACH,mFAHa;;;;cAZC,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAWD,CA+D9B;IACD;;;OAGG;IACH;;;;;OAKG;IACH,oCAHa,OAAO,CAAC;;;;cAnFP,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAkFgB,CAAC,EAAE,CA6BlD;IACD;;;;;OAKG;IACH,iDAFa,OAAO,CAAC,IAAI,CAAC,CAWzB;IAID;;;;;OAKG;IACH;;;;OAIG;IACH,2BAFa,eAAc;;;;;;;;;;OAAa,CAavC;IACD;;;;;OAKG;IACH,yBAHW;;;;;;;;;;OAAa,gCACX,cAAS,CAOrB;IACD;;;;;;OAMG;IACH,6DAFa,GAAC,CA+Kb;IAKD;;;;;;;OAOG;IACH,yDAHa,OAAO,CAAC,GAAG,CAAC,CAiCxB;IACD;;;;OAIG;IAOH;;;;;;OAMG;IACH,wFA2BC;IACD;;;;;;;;;;;OAWG;IACH,8BARG;QAAyB,KAAK,EAAtB,GAAG;QACoB,IAAI,GAA3B,MAAM,EAAE;QACa,cAAc,GAAnC,MAAM;QACc,iBAAiB;KAC7C,6CAEU;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,QAAQ;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,CAmY9J;IAID;;;;;;;;OAQG;IACH,qEAHa,OAAO,CAAC,GAAG,CAAC,CAoExB;IAKD;;;;;;;OAOG;IACH,0FAFa,OAAO,CAAC,GAAG,CAAC,CA0fxB;IACD,+EA8BC;IACD,yDAeC;IACD,oEA6BC;IAKD,8CAQC;IACD,kDAyBC;IACD;;;;;;;;;;;;;OAaG;IACH,wEAoDC;IAKD,4BAOC;IACD,sCAoDC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"valueFromFile.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromFile.js"],"names":[],"mappings":"AAkEA;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAfG;IAAoB,UAAU,EAAtB,MAAM;IACK,aAAa;IACZ,cAAc,EAA1B,MAAM;IACM,mBAAmB,EAA/B,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,IAAI,EAAhB,MAAM;IACM,cAAc,EAA1B,MAAM;IACM,MAAM,EAAlB,MAAM;IACQ,cAAc;IAChB,aAAa,EAAzB,MAAM;IACM,aAAa,EAAzB,MAAM;CACd,kBAAQ,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CA8SxB;AAxVD;;;;;GAKG;AACH,2CAJW,MAAM,YACN,MAAM,GACJ,GAAC,CAoBb;AAkUD;;;;;;GAMG;AACH,qDAJW,MAAM,qBACN,MAAM,GACJ;IAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,GAAC,IAAI,CAAA;CAAE,CAkBhE;AAED;;;;;;GAMG;AACH,sDALW,MAAM,qBACN,MAAM,yBACN,OAAO,GACL,MAAM,EAAE,CAcpB"}
1
+ {"version":3,"file":"valueFromFile.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromFile.js"],"names":[],"mappings":"AAkEA;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAfG;IAAoB,UAAU,EAAtB,MAAM;IACK,aAAa;IACZ,cAAc,EAA1B,MAAM;IACM,mBAAmB,EAA/B,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,IAAI,EAAhB,MAAM;IACM,cAAc,EAA1B,MAAM;IACM,MAAM,EAAlB,MAAM;IACQ,cAAc;IAChB,aAAa,EAAzB,MAAM;IACM,aAAa,EAAzB,MAAM;CACd,kBAAQ,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CAoTxB;AA9VD;;;;;GAKG;AACH,2CAJW,MAAM,YACN,MAAM,GACJ,GAAC,CAoBb;AAwUD;;;;;;GAMG;AACH,qDAJW,MAAM,qBACN,MAAM,GACJ;IAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,GAAC,IAAI,CAAA;CAAE,CAkBhE;AAED;;;;;;GAMG;AACH,sDALW,MAAM,qBACN,MAAM,yBACN,OAAO,GACL,MAAM,EAAE,CAcpB"}
@@ -0,0 +1,19 @@
1
+ declare const paramRefSyntax: RegExp;
2
+ /**
3
+ * Resolves parameter values following the Serverless Framework parameter resolution hierarchy:
4
+ * 1. CLI params (--param="key=value")
5
+ * 2. Stage-specific params (stages.<stage>.params)
6
+ * 3. Default params (stages.default.params)
7
+ *
8
+ * @param {string} variableString - The variable string (e.g., "param:domain")
9
+ * @param {Object} options - CLI options that may contain params
10
+ * @param {Object} config - The full config object for stage-specific params
11
+ * @returns {Promise<any>} The resolved parameter value
12
+ */
13
+ declare function getValueFromParam(variableString: string, options?: any, config?: any): Promise<any>;
14
+ export declare let type: string;
15
+ export declare let source: string;
16
+ export declare let syntax: string;
17
+ export declare let description: string;
18
+ export { paramRefSyntax as match, getValueFromParam as resolver };
19
+ //# sourceMappingURL=valueFromParam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valueFromParam.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromParam.js"],"names":[],"mappings":"AACA,qCAAyC;AAEzC;;;;;;;;;;GAUG;AACH,mDALW,MAAM,gCAGJ,OAAO,CAAC,GAAG,CAAC,CAqExB"}
@@ -18,4 +18,13 @@ export function getFallbackString(split: string[], nestedVar: string): string;
18
18
  * Verify if variable string is valid
19
19
  */
20
20
  export function verifyVariable(variableString: any, valueObject: any, variableTypes: any, config: any): any;
21
+ /**
22
+ * Build default variable syntax regex with dynamic character class
23
+ * Excludes suffix characters from the allowed set to prevent parsing issues
24
+ * @param {string} [prefix='${'] - Variable prefix
25
+ * @param {string} [suffix='}'] - Variable suffix
26
+ * @param {string[]} [excludePatterns=['AWS', 'stageVariables']] - Patterns to exclude via negative lookahead
27
+ * @returns {string} Regex source string
28
+ */
29
+ export function buildVariableSyntax(prefix?: string, suffix?: string, excludePatterns?: string[]): string;
21
30
  //# sourceMappingURL=variableUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"variableUtils.d.ts","sourceRoot":"","sources":["../../../../src/utils/variables/variableUtils.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qDAHW,MAAM,GACJ;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAqC9C;AAED;;;;;GAKG;AACH,yCAJW,MAAM,EAAE,aACR,MAAM,GACJ,MAAM,CAelB;AAED;;GAEG;AACH,4GAsBC"}
1
+ {"version":3,"file":"variableUtils.d.ts","sourceRoot":"","sources":["../../../../src/utils/variables/variableUtils.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qDAHW,MAAM,GACJ;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAqC9C;AAED;;;;;GAKG;AACH,yCAJW,MAAM,EAAE,aACR,MAAM,GACJ,MAAM,CAelB;AAED;;GAEG;AACH,4GAsBC;AAED;;;;;;;GAOG;AACH,6CALW,MAAM,WACN,MAAM,oBACN,MAAM,EAAE,GACN,MAAM,CA4ClB"}