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 +158 -13
- package/cli.js +3 -1
- package/index.d.ts +36 -3
- package/package.json +1 -1
- package/src/index.js +15 -2
- package/src/main.js +108 -47
- package/src/resolvers/valueFromFile.js +7 -1
- package/src/resolvers/valueFromParam.js +91 -0
- package/src/resolvers/valueFromParam.test.js +207 -0
- package/src/utils/paths/filePathUtils.js +30 -6
- package/src/utils/paths/filePathUtils.test.js +16 -0
- package/src/utils/strings/splitCsv.js +10 -1
- package/src/utils/variables/variableUtils.js +54 -1
- package/src/utils/variables/variableUtils.test.js +44 -0
- package/types/src/index.d.ts +4 -2
- package/types/src/index.d.ts.map +1 -1
- package/types/src/main.d.ts +18 -0
- package/types/src/main.d.ts.map +1 -1
- package/types/src/resolvers/valueFromFile.d.ts.map +1 -1
- package/types/src/resolvers/valueFromParam.d.ts +19 -0
- package/types/src/resolvers/valueFromParam.d.ts.map +1 -0
- package/types/src/utils/paths/filePathUtils.d.ts +1 -1
- package/types/src/utils/paths/filePathUtils.d.ts.map +1 -1
- package/types/src/utils/strings/splitCsv.d.ts +5 -1
- package/types/src/utils/strings/splitCsv.d.ts.map +1 -1
- package/types/src/utils/variables/variableUtils.d.ts +9 -0
- package/types/src/utils/variables/variableUtils.d.ts.map +1 -1
|
@@ -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
|
|
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()
|
|
@@ -34,20 +34,44 @@ function normalizePath(filePath) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Extract file path from a file() or text() variable string
|
|
37
|
+
* Extract file path from a file() or text() variable string using balanced paren matching
|
|
38
38
|
* @param {string} variableString - The variable string (with or without ${} wrapper)
|
|
39
39
|
* @returns {object|null} Object with filePath, or null if no match
|
|
40
40
|
*/
|
|
41
41
|
function extractFilePath(variableString) {
|
|
42
|
-
// Match
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
42
|
+
// Match the file( or text( prefix
|
|
43
|
+
const prefixMatch = variableString.match(/^(?:\$\{)?(file|text)\(/)
|
|
44
|
+
if (!prefixMatch) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Find matching closing paren using depth tracking
|
|
49
|
+
const startIndex = prefixMatch[0].length - 1 // Position of opening (
|
|
50
|
+
let depth = 1
|
|
51
|
+
let i = startIndex + 1
|
|
52
|
+
|
|
53
|
+
while (i < variableString.length && depth > 0) {
|
|
54
|
+
if (variableString[i] === '(') {
|
|
55
|
+
depth++
|
|
56
|
+
} else if (variableString[i] === ')') {
|
|
57
|
+
depth--
|
|
58
|
+
}
|
|
59
|
+
i++
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (depth !== 0) {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Extract content between balanced parens
|
|
67
|
+
const fileContent = variableString.substring(startIndex + 1, i - 1).trim()
|
|
68
|
+
if (!fileContent) {
|
|
45
69
|
return null
|
|
46
70
|
}
|
|
47
71
|
|
|
48
72
|
const { trimSurroundingQuotes } = require('../strings/quoteUtils')
|
|
49
|
-
|
|
50
|
-
const parts = splitCsv(fileContent)
|
|
73
|
+
// Protect ${} variables from being split (e.g., file paths with default values)
|
|
74
|
+
const parts = splitCsv(fileContent, undefined, { protectVariables: true })
|
|
51
75
|
let filePath = parts[0].trim()
|
|
52
76
|
|
|
53
77
|
// Remove quotes if present
|
|
@@ -103,6 +103,22 @@ test('extractFilePath - handles bare filename', () => {
|
|
|
103
103
|
assert.is(result.filePath, 'config.json')
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
+
test('extractFilePath - handles nested variable with default value in path', () => {
|
|
107
|
+
// This is the bug case: ${self:provider.stage, 'dev'} has a comma inside
|
|
108
|
+
const result = extractFilePath("file(./env.${self:provider.stage, 'dev'}.yml):FOO")
|
|
109
|
+
assert.is(result.filePath, "./env.${self:provider.stage, 'dev'}.yml")
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('extractFilePath - handles nested variable without default in path', () => {
|
|
113
|
+
const result = extractFilePath("file(./env.${self:provider.stage}.yml):FOO")
|
|
114
|
+
assert.is(result.filePath, "./env.${self:provider.stage}.yml")
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('extractFilePath - handles multiple nested variables in path', () => {
|
|
118
|
+
const result = extractFilePath("file(./config-${self:stage, 'dev'}-${self:region}.yml)")
|
|
119
|
+
assert.is(result.filePath, "./config-${self:stage, 'dev'}-${self:region}.yml")
|
|
120
|
+
})
|
|
121
|
+
|
|
106
122
|
// normalizeFileVariable tests
|
|
107
123
|
|
|
108
124
|
test('normalizeFileVariable - returns non-file strings unchanged', () => {
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
const { splitByComma } = require('./splitByComma')
|
|
2
2
|
|
|
3
|
+
// Regex to match ${...} variables (used for protection during file path splitting)
|
|
4
|
+
const VARIABLE_SYNTAX = /\${[^}]+}/g
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* Split a string by comma while preserving quoted content
|
|
5
8
|
* NOTE: This is a simpler version that delegates to splitByComma for consistency.
|
|
6
9
|
* For advanced use cases with bracket depth tracking and regex protection, use splitByComma directly.
|
|
7
10
|
* @param {string} str - String to split
|
|
8
11
|
* @param {string} [splitter] - Optional custom splitter (defaults to ',')
|
|
12
|
+
* @param {object} [options] - Options object
|
|
13
|
+
* @param {boolean} [options.protectVariables] - If true, protect ${} variables from splitting
|
|
9
14
|
* @returns {string[]} Array of split strings
|
|
10
15
|
*/
|
|
11
|
-
function splitCsv(str, splitter) {
|
|
16
|
+
function splitCsv(str, splitter, options = {}) {
|
|
12
17
|
// If custom splitter is provided, fall back to original simple implementation
|
|
13
18
|
if (splitter && splitter !== ',') {
|
|
14
19
|
const splitSyntax = splitter
|
|
@@ -32,6 +37,10 @@ function splitCsv(str, splitter) {
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
// For standard comma splitting, use the more robust splitByComma
|
|
40
|
+
// Pass VARIABLE_SYNTAX if protectVariables is true to protect ${} from splitting
|
|
41
|
+
if (options.protectVariables) {
|
|
42
|
+
return splitByComma(str, VARIABLE_SYNTAX)
|
|
43
|
+
}
|
|
35
44
|
return splitByComma(str)
|
|
36
45
|
}
|
|
37
46
|
|
|
@@ -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()
|
package/types/src/index.d.ts
CHANGED
|
@@ -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
|
package/types/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":";;;
|
|
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"}
|
package/types/src/main.d.ts
CHANGED
|
@@ -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.
|
package/types/src/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";
|
|
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,
|
|
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"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export function normalizePath(filePath: string): string | null;
|
|
7
7
|
/**
|
|
8
|
-
* Extract file path from a file() or text() variable string
|
|
8
|
+
* Extract file path from a file() or text() variable string using balanced paren matching
|
|
9
9
|
* @param {string} variableString - The variable string (with or without ${} wrapper)
|
|
10
10
|
* @returns {object|null} Object with filePath, or null if no match
|
|
11
11
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filePathUtils.d.ts","sourceRoot":"","sources":["../../../../src/utils/paths/filePathUtils.js"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wCAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CA0BvB;AAED;;;;GAIG;AACH,gDAHW,MAAM,GACJ,MAAM,GAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"filePathUtils.d.ts","sourceRoot":"","sources":["../../../../src/utils/paths/filePathUtils.js"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wCAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CA0BvB;AAED;;;;GAIG;AACH,gDAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CA0CvB;AAED;;;;;GAKG;AACH,sDAHW,MAAM,GACJ,MAAM,CAkBlB;AAED;;;;;;;GAOG;AACH,2CANW,MAAM,kBACN,MAAM,UACN,MAAM,sBAEJ;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAC,CAwCnD"}
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* For advanced use cases with bracket depth tracking and regex protection, use splitByComma directly.
|
|
5
5
|
* @param {string} str - String to split
|
|
6
6
|
* @param {string} [splitter] - Optional custom splitter (defaults to ',')
|
|
7
|
+
* @param {object} [options] - Options object
|
|
8
|
+
* @param {boolean} [options.protectVariables] - If true, protect ${} variables from splitting
|
|
7
9
|
* @returns {string[]} Array of split strings
|
|
8
10
|
*/
|
|
9
|
-
export function splitCsv(str: string, splitter?: string
|
|
11
|
+
export function splitCsv(str: string, splitter?: string, options?: {
|
|
12
|
+
protectVariables?: boolean;
|
|
13
|
+
}): string[];
|
|
10
14
|
//# sourceMappingURL=splitCsv.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"splitCsv.d.ts","sourceRoot":"","sources":["../../../../src/utils/strings/splitCsv.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"splitCsv.d.ts","sourceRoot":"","sources":["../../../../src/utils/strings/splitCsv.js"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,8BANW,MAAM,aACN,MAAM,YAEd;IAA0B,gBAAgB,GAAlC,OAAO;CACf,GAAU,MAAM,EAAE,CA+BpB"}
|