configorama 0.10.2 → 0.10.3
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 +42 -9
- package/cli.js +67 -4
- package/index.d.ts +12 -0
- package/package.json +1 -1
- package/src/index.js +3 -0
- package/src/main.js +17 -9
- package/src/metadata.js +5 -2
- package/src/utils/parsing/parse.js +1 -1
- package/src/utils/paths/ignorePaths.js +69 -0
- package/src/utils/strings/splitByComma.test.js +2 -2
- package/src/utils/variables/findNestedVariables.js +2 -2
- package/src/utils/variables/findNestedVariables.test.js +2 -2
- package/src/utils/variables/variableUtils.js +3 -3
- package/src/utils/variables/variableUtils.test.js +9 -0
package/README.md
CHANGED
|
@@ -1667,7 +1667,7 @@ const config = await configorama('config.yml', { syntax })
|
|
|
1667
1667
|
|---|---|---|---|
|
|
1668
1668
|
| `prefix` | `string` | `'${'` | Opening delimiter |
|
|
1669
1669
|
| `suffix` | `string` | `'}'` | Closing delimiter |
|
|
1670
|
-
| `excludePatterns` | `string[]` | `['AWS', 'stageVariables']` | Patterns to exclude via negative lookahead (so e.g. `${AWS::Region}`
|
|
1670
|
+
| `excludePatterns` | `string[]` | `['AWS', 'aws:', 'stageVariables']` | Patterns to exclude via negative lookahead (so e.g. `${AWS::Region}` and `${aws:username}` are left untouched by CloudFormation users) |
|
|
1671
1671
|
|
|
1672
1672
|
---
|
|
1673
1673
|
|
|
@@ -1762,7 +1762,7 @@ buildVariableSyntax('<', '>') // <env:FOO>
|
|
|
1762
1762
|
function buildVariableSyntax(
|
|
1763
1763
|
prefix: string = '${',
|
|
1764
1764
|
suffix: string = '}',
|
|
1765
|
-
excludePatterns: string[] = ['AWS', 'stageVariables']
|
|
1765
|
+
excludePatterns: string[] = ['AWS', 'aws:', 'stageVariables']
|
|
1766
1766
|
): string
|
|
1767
1767
|
```
|
|
1768
1768
|
|
|
@@ -1891,6 +1891,9 @@ const config = await configorama(configFile, {
|
|
|
1891
1891
|
| `allowUnknownVariableTypes` | `boolean \| string[]` | `false` | Allow unknown variable types to pass through |
|
|
1892
1892
|
| `allowUnresolvedVariables` | `boolean \| string[]` | `false` | Allow known types that can't resolve to pass through |
|
|
1893
1893
|
| `allowUndefinedValues` | `boolean` | `false` | Allow undefined as a valid end result |
|
|
1894
|
+
| `ignorePaths` | `string[]` | Built-in CloudFormation/code paths | Glob-like config paths whose values should be left verbatim |
|
|
1895
|
+
| `skipResolutionPaths` | `string[]` | `[]` | Alias for `ignorePaths` |
|
|
1896
|
+
| `disableDefaultIgnorePaths` | `boolean` | `false` | Disable the built-in CloudFormation/code ignore paths |
|
|
1894
1897
|
| `returnMetadata` | `boolean` | `false` | Return `{ config, metadata }` instead of just the resolved config |
|
|
1895
1898
|
| `returnPreResolvedVariableDetails` | `boolean` | `false` | Return metadata about variables *without* resolving them (used by `analyze()`) |
|
|
1896
1899
|
| `useDotEnvFiles` | `boolean` | `false` | Auto-load `.env`, `.env.{stage}`, etc. into `process.env` before resolution (via [env-stage-loader](https://www.npmjs.com/package/env-stage-loader)) |
|
|
@@ -2084,8 +2087,14 @@ configorama config.yml --info
|
|
|
2084
2087
|
# Verify config (check for errors without resolving)
|
|
2085
2088
|
configorama config.yml --verify
|
|
2086
2089
|
|
|
2087
|
-
# Extract specific path from config
|
|
2088
|
-
configorama config.yml database.host
|
|
2090
|
+
# Extract a specific path from config
|
|
2091
|
+
configorama config.yml .database.host
|
|
2092
|
+
|
|
2093
|
+
# Print an extracted scalar without JSON quotes
|
|
2094
|
+
configorama config.yml .database.host --raw
|
|
2095
|
+
|
|
2096
|
+
# Copy the formatted output to your clipboard
|
|
2097
|
+
configorama config.yml .database.host --raw --copy
|
|
2089
2098
|
|
|
2090
2099
|
# Output as YAML
|
|
2091
2100
|
configorama config.yml --format yaml
|
|
@@ -2102,6 +2111,8 @@ Options:
|
|
|
2102
2111
|
-v, --version Show version number
|
|
2103
2112
|
-o, --output <file> Write output to file instead of stdout
|
|
2104
2113
|
-f, --format <format> Output format: json, yaml, or js (default: json)
|
|
2114
|
+
-r, --raw Print extracted scalar values without JSON quoting
|
|
2115
|
+
-c, --copy Copy the formatted output to the clipboard
|
|
2105
2116
|
-d, --debug Enable debug mode
|
|
2106
2117
|
-i, --info Show info about the config
|
|
2107
2118
|
-V, --verify Verify the config
|
|
@@ -2110,10 +2121,24 @@ Options:
|
|
|
2110
2121
|
--allow-undefined Allow undefined values in the final output
|
|
2111
2122
|
|
|
2112
2123
|
Path Extraction:
|
|
2113
|
-
configorama config.yml database.host
|
|
2114
|
-
configorama config.yml functions[0]
|
|
2124
|
+
configorama config.yml .database.host Extract a nested value
|
|
2125
|
+
configorama config.yml '.functions[0]' Extract from an array
|
|
2126
|
+
configorama -r config.yml .stage Print raw scalar output
|
|
2127
|
+
configorama -r -c config.yml .stage Print and copy raw scalar output
|
|
2115
2128
|
```
|
|
2116
2129
|
|
|
2130
|
+
Path extraction uses jq-style paths. JSON remains the default output format, so extracted strings are quoted by default:
|
|
2131
|
+
|
|
2132
|
+
```bash
|
|
2133
|
+
configorama config.yml .stage
|
|
2134
|
+
# "prod"
|
|
2135
|
+
|
|
2136
|
+
configorama config.yml .stage --raw
|
|
2137
|
+
# prod
|
|
2138
|
+
```
|
|
2139
|
+
|
|
2140
|
+
`--copy` copies exactly the formatted value that the CLI prints. It uses native clipboard commands where available: `pbcopy` on macOS, `clip` on Windows, and `wl-copy`, `xclip`, or `xsel` on Linux.
|
|
2141
|
+
|
|
2117
2142
|
### CLI Examples
|
|
2118
2143
|
|
|
2119
2144
|
**Basic resolution:**
|
|
@@ -2151,12 +2176,20 @@ database:
|
|
|
2151
2176
|
host: localhost
|
|
2152
2177
|
port: 5432
|
|
2153
2178
|
|
|
2154
|
-
# Extract database.host
|
|
2155
|
-
configorama config.yml database.host
|
|
2179
|
+
# Extract database.host as JSON
|
|
2180
|
+
configorama config.yml .database.host
|
|
2181
|
+
# Output: "localhost"
|
|
2182
|
+
|
|
2183
|
+
# Extract database.host as a raw scalar
|
|
2184
|
+
configorama config.yml .database.host --raw
|
|
2185
|
+
# Output: localhost
|
|
2186
|
+
|
|
2187
|
+
# Extract and copy the raw scalar
|
|
2188
|
+
configorama config.yml .database.host --raw --copy
|
|
2156
2189
|
# Output: localhost
|
|
2157
2190
|
|
|
2158
2191
|
# Extract database config as JSON
|
|
2159
|
-
configorama config.yml database --format json
|
|
2192
|
+
configorama config.yml .database --format json
|
|
2160
2193
|
# Output: {"host":"localhost","port":5432}
|
|
2161
2194
|
```
|
|
2162
2195
|
|
package/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const minimist = require('minimist')
|
|
5
|
+
const { spawnSync } = require('child_process')
|
|
5
6
|
const Configorama = require('./src/main')
|
|
6
7
|
const deepLog = require('./src/utils/ui/deep-log')
|
|
7
8
|
const { logHeader } = require('./src/utils/ui/logs')
|
|
@@ -12,13 +13,15 @@ const getValueAtPath = require('./src/utils/parsing/getValueAtPath')
|
|
|
12
13
|
// Parse command line arguments
|
|
13
14
|
const argv = minimist(process.argv.slice(2), {
|
|
14
15
|
string: ['output', 'o', 'format', 'f', 'param'],
|
|
15
|
-
boolean: ['help', 'h', 'version', 'v', 'V', 'debug', 'allow-unknown', 'allow-undefined', 'list', 'info', 'verify'],
|
|
16
|
+
boolean: ['help', 'h', 'version', 'v', 'V', 'debug', 'allow-unknown', 'allow-undefined', 'list', 'info', 'verify', 'raw', 'r', 'copy', 'c'],
|
|
16
17
|
alias: {
|
|
17
18
|
h: 'help',
|
|
18
19
|
v: 'version',
|
|
19
20
|
V: 'verify',
|
|
20
21
|
o: 'output',
|
|
21
22
|
f: 'format',
|
|
23
|
+
r: 'raw',
|
|
24
|
+
c: 'copy',
|
|
22
25
|
l: 'list',
|
|
23
26
|
i: 'info',
|
|
24
27
|
},
|
|
@@ -40,6 +43,8 @@ Options:
|
|
|
40
43
|
-v, --version Show version number
|
|
41
44
|
-o, --output <file> Write output to file instead of stdout
|
|
42
45
|
-f, --format <format> Output format: json, yaml, or js (default: json)
|
|
46
|
+
-r, --raw Print extracted scalar values without JSON quoting
|
|
47
|
+
-c, --copy Copy the formatted output to the clipboard
|
|
43
48
|
-d, --debug Enable debug mode
|
|
44
49
|
-i, --info Show info about the config
|
|
45
50
|
-V, --verify Verify the config
|
|
@@ -62,6 +67,8 @@ Path Extraction:
|
|
|
62
67
|
Examples:
|
|
63
68
|
configorama config.yml
|
|
64
69
|
configorama config.yml .database.host
|
|
70
|
+
configorama -r config.yml .database.host
|
|
71
|
+
configorama -r --copy config.yml .database.host
|
|
65
72
|
configorama '.servers[0].port' config.yml
|
|
66
73
|
configorama --info config.yml
|
|
67
74
|
configorama --format yaml config.json
|
|
@@ -84,11 +91,53 @@ if (argv.version) {
|
|
|
84
91
|
let inputFile = null
|
|
85
92
|
let extractPath = null
|
|
86
93
|
|
|
94
|
+
function isFileArg(arg) {
|
|
95
|
+
if (fs.existsSync(arg) && fs.statSync(arg).isFile()) return true
|
|
96
|
+
return arg.startsWith('./') || arg.startsWith('../')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getClipboardCommands() {
|
|
100
|
+
if (process.env.CONFIGORAMA_CLIPBOARD_COMMAND) {
|
|
101
|
+
return [{ command: process.env.CONFIGORAMA_CLIPBOARD_COMMAND, shell: true }]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (process.platform === 'darwin') return [{ command: 'pbcopy', args: [] }]
|
|
105
|
+
if (process.platform === 'win32') return [{ command: 'clip', args: [] }]
|
|
106
|
+
|
|
107
|
+
return [
|
|
108
|
+
{ command: 'wl-copy', args: [] },
|
|
109
|
+
{ command: 'xclip', args: ['-selection', 'clipboard'] },
|
|
110
|
+
{ command: 'xsel', args: ['--clipboard', '--input'] }
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function copyToClipboard(value) {
|
|
115
|
+
let lastError = ''
|
|
116
|
+
for (const candidate of getClipboardCommands()) {
|
|
117
|
+
const result = spawnSync(candidate.command, candidate.args || [], {
|
|
118
|
+
input: String(value),
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
shell: !!candidate.shell,
|
|
121
|
+
stdio: ['pipe', 'ignore', 'pipe']
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (!result.error && result.status === 0) return { ok: true }
|
|
125
|
+
lastError = result.error ? result.error.message : (result.stderr || '').trim()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
error: lastError || 'No supported clipboard command found'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
87
134
|
for (const arg of argv._) {
|
|
88
135
|
if (arg === 'setup') continue
|
|
89
136
|
|
|
90
137
|
// jq-style paths start with '.' or '['
|
|
91
|
-
if (
|
|
138
|
+
if (!inputFile && isFileArg(arg)) {
|
|
139
|
+
inputFile = arg
|
|
140
|
+
} else if (arg.startsWith('.') || arg.startsWith('[')) {
|
|
92
141
|
extractPath = arg
|
|
93
142
|
} else if (!inputFile) {
|
|
94
143
|
inputFile = arg
|
|
@@ -134,6 +183,10 @@ const {
|
|
|
134
183
|
l,
|
|
135
184
|
info,
|
|
136
185
|
i,
|
|
186
|
+
r,
|
|
187
|
+
raw,
|
|
188
|
+
c,
|
|
189
|
+
copy,
|
|
137
190
|
'allow-unknown': allowUnknown,
|
|
138
191
|
'allow-undefined': allowUndefined,
|
|
139
192
|
'allow-unknown-file-refs': allowUnknownFileRefs,
|
|
@@ -177,7 +230,9 @@ configorama(inputFile, options)
|
|
|
177
230
|
let output
|
|
178
231
|
|
|
179
232
|
// Format the output
|
|
180
|
-
|
|
233
|
+
if (argv.raw && extractPath && (config === null || ['string', 'number', 'boolean'].includes(typeof config))) {
|
|
234
|
+
output = config === null ? 'null' : String(config)
|
|
235
|
+
} else switch (argv.format.toLowerCase()) {
|
|
181
236
|
case 'yaml':
|
|
182
237
|
case 'yml':
|
|
183
238
|
const YAML = require('./src/parsers/yaml')
|
|
@@ -204,6 +259,14 @@ configorama(inputFile, options)
|
|
|
204
259
|
output = JSON.stringify(config, null, 2)
|
|
205
260
|
}
|
|
206
261
|
|
|
262
|
+
if (argv.copy) {
|
|
263
|
+
const copyResult = copyToClipboard(output)
|
|
264
|
+
if (!copyResult.ok) {
|
|
265
|
+
console.error(`Error: Unable to copy to clipboard: ${copyResult.error}`)
|
|
266
|
+
process.exit(1)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
207
270
|
// Write to file or stdout
|
|
208
271
|
if (argv.output) {
|
|
209
272
|
fs.writeFileSync(argv.output, output)
|
|
@@ -230,4 +293,4 @@ configorama(inputFile, options)
|
|
|
230
293
|
console.error('error', error)
|
|
231
294
|
}
|
|
232
295
|
process.exit(1)
|
|
233
|
-
})
|
|
296
|
+
})
|
package/index.d.ts
CHANGED
|
@@ -38,6 +38,18 @@ interface ConfigoramaSettings {
|
|
|
38
38
|
/** Allow undefined values as final results */
|
|
39
39
|
allowUndefinedValues?: boolean
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Glob-like config paths whose values should be left verbatim.
|
|
43
|
+
* Useful for embedded languages that also use ${...}.
|
|
44
|
+
*/
|
|
45
|
+
ignorePaths?: string[]
|
|
46
|
+
|
|
47
|
+
/** Alias for ignorePaths */
|
|
48
|
+
skipResolutionPaths?: string[]
|
|
49
|
+
|
|
50
|
+
/** Disable the built-in CloudFormation and embedded-code ignore paths */
|
|
51
|
+
disableDefaultIgnorePaths?: boolean
|
|
52
|
+
|
|
41
53
|
// === Legacy Options (deprecated, use above instead) ===
|
|
42
54
|
|
|
43
55
|
/** @deprecated Use allowUnknownVariableTypes instead */
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -13,6 +13,9 @@ const { buildVariableSyntax } = require('./utils/variables/variableUtils')
|
|
|
13
13
|
* @property {Object.<string, Function>} [functions] - Object of custom functions
|
|
14
14
|
* @property {boolean} [allowUnknownVars] - allow unknown variables to pass through without throwing errors
|
|
15
15
|
* @property {boolean} [allowUndefinedValues] - allow undefined values to pass through without throwing errors
|
|
16
|
+
* @property {string[]} [ignorePaths] - glob-like config paths whose values should be left verbatim
|
|
17
|
+
* @property {string[]} [skipResolutionPaths] - alias for ignorePaths
|
|
18
|
+
* @property {boolean} [disableDefaultIgnorePaths] - disable built-in CloudFormation and embedded-code ignore paths
|
|
16
19
|
* @property {Object|Function} [dynamicArgs] - values passed into .js config files if user using javascript config
|
|
17
20
|
* @property {boolean} [returnMetadata] - return both config and metadata about variables found
|
|
18
21
|
* @property {boolean} [dotEnvSilent] - suppress env-stage-loader logs when useDotenv/useDotEnv is enabled
|
package/src/main.js
CHANGED
|
@@ -60,6 +60,7 @@ const { mergeByKeys } = require('./utils/parsing/mergeByKeys')
|
|
|
60
60
|
const { arrayToJsonPath } = require('./utils/parsing/arrayToJsonPath')
|
|
61
61
|
/* Utils - paths */
|
|
62
62
|
const { findLineByPath } = require('./utils/paths/findLineForKey')
|
|
63
|
+
const { normalizeIgnorePaths, compileIgnorePaths, shouldIgnorePath } = require('./utils/paths/ignorePaths')
|
|
63
64
|
/* Utils - regex */
|
|
64
65
|
const { combineRegexes, funcRegex, fileRefSyntax, textRefSyntax } = require('./utils/regex')
|
|
65
66
|
/* Utils - strings */
|
|
@@ -162,6 +163,11 @@ class Configorama {
|
|
|
162
163
|
// CLI users can still see them with --verbose or dotEnvSilent: false.
|
|
163
164
|
dotEnvSilent: !VERBOSE,
|
|
164
165
|
dotEnvDebug: false,
|
|
166
|
+
// Glob-like path patterns whose values should be left verbatim.
|
|
167
|
+
// Useful for embedded languages that also use ${...}, such as
|
|
168
|
+
// CloudFormation Fn::Sub, inline Lambda code, and CloudFront functions.
|
|
169
|
+
ignorePaths: [],
|
|
170
|
+
skipResolutionPaths: [],
|
|
165
171
|
}, options)
|
|
166
172
|
|
|
167
173
|
// Backward compat: allowUnknownVars -> allowUnknownVariableTypes
|
|
@@ -221,9 +227,10 @@ class Configorama {
|
|
|
221
227
|
|
|
222
228
|
// Use $[...] syntax for HCL/Terraform files to avoid conflicts with Terraform's ${} syntax
|
|
223
229
|
const isHclFile = detectedFileType === '.tf' || detectedFileType === '.hcl'
|
|
230
|
+
const defaultExcludedPatterns = ['AWS', 'aws:', 'stageVariables']
|
|
224
231
|
const defaultSyntax = isHclFile
|
|
225
|
-
? buildVariableSyntax('$[', ']',
|
|
226
|
-
: buildVariableSyntax('${', '}',
|
|
232
|
+
? buildVariableSyntax('$[', ']', defaultExcludedPatterns)
|
|
233
|
+
: buildVariableSyntax('${', '}', defaultExcludedPatterns)
|
|
227
234
|
|
|
228
235
|
const varSyntax = options.syntax || defaultSyntax
|
|
229
236
|
let varRegex
|
|
@@ -247,6 +254,7 @@ class Configorama {
|
|
|
247
254
|
this.varPrefixPattern = new RegExp('^' + this.varPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
248
255
|
this.varSuffixPattern = new RegExp(escapedSuffix + '$')
|
|
249
256
|
this.varSuffixWithSpacePattern = new RegExp('\\s+' + escapedSuffix + '$')
|
|
257
|
+
this.ignorePathPatterns = compileIgnorePaths(normalizeIgnorePaths(this.settings))
|
|
250
258
|
|
|
251
259
|
// Set initial config object to populate
|
|
252
260
|
if (typeof fileOrObject === 'object') {
|
|
@@ -1063,6 +1071,7 @@ class Configorama {
|
|
|
1063
1071
|
originalConfig: this.originalConfig,
|
|
1064
1072
|
varSuffix: this.varSuffix,
|
|
1065
1073
|
varSuffixWithSpacePattern: this.varSuffixWithSpacePattern,
|
|
1074
|
+
ignorePathPatterns: this.ignorePathPatterns,
|
|
1066
1075
|
})
|
|
1067
1076
|
|
|
1068
1077
|
return this._cachedMetadata
|
|
@@ -1102,9 +1111,8 @@ class Configorama {
|
|
|
1102
1111
|
// #######################
|
|
1103
1112
|
// ## PROPERTY HANDLING ##
|
|
1104
1113
|
// #######################
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
return pathValue[pathValue.length - 1] === 'Fn::Sub'
|
|
1114
|
+
shouldSkipResolution(pathValue) {
|
|
1115
|
+
return shouldIgnorePath(pathValue, this.ignorePathPatterns)
|
|
1108
1116
|
}
|
|
1109
1117
|
|
|
1110
1118
|
/**
|
|
@@ -1264,10 +1272,10 @@ class Configorama {
|
|
|
1264
1272
|
}
|
|
1265
1273
|
return false
|
|
1266
1274
|
})
|
|
1267
|
-
/* Leave
|
|
1268
|
-
CloudFormation
|
|
1275
|
+
/* Leave opaque paths verbatim. These often contain non-configorama
|
|
1276
|
+
`${...}` syntax from CloudFormation, JavaScript, shell, VTL, etc. */
|
|
1269
1277
|
variables = variables.filter((property) => {
|
|
1270
|
-
return !this.
|
|
1278
|
+
return !this.shouldSkipResolution(property.path)
|
|
1271
1279
|
})
|
|
1272
1280
|
/*
|
|
1273
1281
|
console.log(`variables at call count ${this.callCount}`, variables)
|
|
@@ -1555,7 +1563,7 @@ class Configorama {
|
|
|
1555
1563
|
console.log(valueObject)
|
|
1556
1564
|
}
|
|
1557
1565
|
const property = valueObject.value
|
|
1558
|
-
if (this.
|
|
1566
|
+
if (this.shouldSkipResolution(valueObject.path)) {
|
|
1559
1567
|
return Promise.resolve(property)
|
|
1560
1568
|
}
|
|
1561
1569
|
const matches = this.getMatches(property)
|
package/src/metadata.js
CHANGED
|
@@ -6,6 +6,7 @@ const fs = require('fs')
|
|
|
6
6
|
const traverse = require('traverse')
|
|
7
7
|
const dotProp = require('dot-prop')
|
|
8
8
|
const { normalizePath, extractFilePath, resolveInnerVariables } = require('./utils/paths/filePathUtils')
|
|
9
|
+
const { shouldIgnorePath } = require('./utils/paths/ignorePaths')
|
|
9
10
|
const { findNestedVariables } = require('./utils/variables/findNestedVariables')
|
|
10
11
|
const { splitOnPipe } = require('./utils/strings/splitOnPipe')
|
|
11
12
|
|
|
@@ -21,6 +22,7 @@ const { splitOnPipe } = require('./utils/strings/splitOnPipe')
|
|
|
21
22
|
* @param {Object} params.originalConfig - this.originalConfig, used for dotProp.get checks
|
|
22
23
|
* @param {string} params.varSuffix
|
|
23
24
|
* @param {RegExp} params.varSuffixWithSpacePattern
|
|
25
|
+
* @param {string[][]} [params.ignorePathPatterns]
|
|
24
26
|
* @returns {Object} Metadata object containing variables, fileDependencies, and summary
|
|
25
27
|
*/
|
|
26
28
|
function collectVariableMetadata({
|
|
@@ -33,6 +35,7 @@ function collectVariableMetadata({
|
|
|
33
35
|
originalConfig,
|
|
34
36
|
varSuffix,
|
|
35
37
|
varSuffixWithSpacePattern,
|
|
38
|
+
ignorePathPatterns,
|
|
36
39
|
}) {
|
|
37
40
|
const foundVariables = []
|
|
38
41
|
const variableData = {}
|
|
@@ -46,8 +49,8 @@ function collectVariableMetadata({
|
|
|
46
49
|
traverse(displayConfig).forEach(function (rawValue) {
|
|
47
50
|
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
48
51
|
const configValuePath = this.path.join('.')
|
|
49
|
-
/* Skip
|
|
50
|
-
if (
|
|
52
|
+
/* Skip opaque paths that contain non-configorama ${...} syntax. */
|
|
53
|
+
if (shouldIgnorePath(this.path, ignorePathPatterns)) {
|
|
51
54
|
return
|
|
52
55
|
}
|
|
53
56
|
|
|
@@ -10,7 +10,7 @@ const { executeTypeScriptFileSync } = require('../../parsers/typescript')
|
|
|
10
10
|
const { executeESMFileSync } = require('../../parsers/esm')
|
|
11
11
|
const cloudFormationSchema = require('./cloudformationSchema')
|
|
12
12
|
|
|
13
|
-
const DEFAULT_VAR_SYNTAX = '\\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._\'",|\\-\\/\\(\\)\\\\]+?)}'
|
|
13
|
+
const DEFAULT_VAR_SYNTAX = '\\${((?!AWS|aws:|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._\'",|\\-\\/\\(\\)\\\\]+?)}'
|
|
14
14
|
|
|
15
15
|
const KNOWN_EXTENSIONS = new Set([
|
|
16
16
|
'.yml', '.yaml', '.json', '.json5', '.jsonc',
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const DEFAULT_IGNORE_PATHS = [
|
|
2
|
+
'**.Fn::Sub',
|
|
3
|
+
'**.Properties.Code.ZipFile',
|
|
4
|
+
'**.Properties.FunctionCode',
|
|
5
|
+
'**.Properties.UserData',
|
|
6
|
+
'**.Properties.BuildSpec',
|
|
7
|
+
'**.Properties.DefinitionString',
|
|
8
|
+
'**.Properties.DefinitionBody',
|
|
9
|
+
'**.Properties.RequestMappingTemplate',
|
|
10
|
+
'**.Properties.ResponseMappingTemplate',
|
|
11
|
+
'**.Properties.RequestTemplates.*',
|
|
12
|
+
'**.Properties.ResponseTemplates.*',
|
|
13
|
+
'**.Metadata.AWS::CloudFormation::Init.*.files.*.content',
|
|
14
|
+
'**.Metadata.AWS::CloudFormation::Init.*.commands.*.command'
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
function toArray(value) {
|
|
18
|
+
if (!value) return []
|
|
19
|
+
return Array.isArray(value) ? value : [value]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function pathToSegments(pathValue) {
|
|
23
|
+
if (!pathValue) return []
|
|
24
|
+
return Array.isArray(pathValue) ? pathValue.map(String) : String(pathValue).split('.')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function patternToSegments(pattern) {
|
|
28
|
+
return String(pattern).split('.')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function matchSegments(patternSegments, pathSegments) {
|
|
32
|
+
if (!patternSegments.length) return pathSegments.length === 0
|
|
33
|
+
|
|
34
|
+
const [head, ...tail] = patternSegments
|
|
35
|
+
if (head === '**') {
|
|
36
|
+
if (matchSegments(tail, pathSegments)) return true
|
|
37
|
+
return pathSegments.length > 0 && matchSegments(patternSegments, pathSegments.slice(1))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!pathSegments.length) return false
|
|
41
|
+
if (head !== '*' && head !== pathSegments[0]) return false
|
|
42
|
+
return matchSegments(tail, pathSegments.slice(1))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeIgnorePaths(options = {}) {
|
|
46
|
+
const defaults = options.disableDefaultIgnorePaths ? [] : DEFAULT_IGNORE_PATHS
|
|
47
|
+
return Array.from(new Set([
|
|
48
|
+
...defaults,
|
|
49
|
+
...toArray(options.ignorePaths),
|
|
50
|
+
...toArray(options.skipResolutionPaths)
|
|
51
|
+
]))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function compileIgnorePaths(patterns) {
|
|
55
|
+
return toArray(patterns).map(patternToSegments)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function shouldIgnorePath(pathValue, compiledPatterns) {
|
|
59
|
+
if (!compiledPatterns || !compiledPatterns.length) return false
|
|
60
|
+
const pathSegments = pathToSegments(pathValue)
|
|
61
|
+
return compiledPatterns.some((patternSegments) => matchSegments(patternSegments, pathSegments))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
DEFAULT_IGNORE_PATHS,
|
|
66
|
+
normalizeIgnorePaths,
|
|
67
|
+
compileIgnorePaths,
|
|
68
|
+
shouldIgnorePath
|
|
69
|
+
}
|
|
@@ -2,7 +2,7 @@ const { test } = require('uvu')
|
|
|
2
2
|
const assert = require('uvu/assert')
|
|
3
3
|
const { splitByComma } = require('./splitByComma')
|
|
4
4
|
|
|
5
|
-
const variableSyntax = /\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g
|
|
5
|
+
const variableSyntax = /\${((?!AWS|aws:|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g
|
|
6
6
|
|
|
7
7
|
test('splitByComma - should return array with original string when no commas present', () => {
|
|
8
8
|
const result = splitByComma('singleString')
|
|
@@ -145,4 +145,4 @@ test('splitByComma - should handle odd backslashes (escaped quote)', () => {
|
|
|
145
145
|
})
|
|
146
146
|
|
|
147
147
|
// Run all tests
|
|
148
|
-
test.run()
|
|
148
|
+
test.run()
|
|
@@ -358,7 +358,7 @@ function findNestedVariablesOld(input, regex, variablesKnownTypes, debug = false
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
// // Test with the example
|
|
361
|
-
// const regex = /\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g
|
|
361
|
+
// const regex = /\${((?!AWS|aws:|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g
|
|
362
362
|
// const input = '${file(./config.${opt:stage, ${defaultStage}}.json):CREDS}'
|
|
363
363
|
|
|
364
364
|
// // Run the function with debug output
|
|
@@ -374,4 +374,4 @@ function findNestedVariablesOld(input, regex, variablesKnownTypes, debug = false
|
|
|
374
374
|
|
|
375
375
|
module.exports = {
|
|
376
376
|
findNestedVariables
|
|
377
|
-
}
|
|
377
|
+
}
|
|
@@ -9,7 +9,7 @@ const getValueFromOptions = require('../../resolvers/valueFromOptions')
|
|
|
9
9
|
const getValueFromGit = require('../../resolvers/valueFromGit')
|
|
10
10
|
|
|
11
11
|
// Define the regex pattern as used in the main function
|
|
12
|
-
const regex = /\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g;
|
|
12
|
+
const regex = /\${((?!AWS|aws:|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g;
|
|
13
13
|
const variablesKnownTypes = /(^env:|^opt:|^self:|^file\((~?[\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)|^git:|(\${)?deep:\d+(\.[^}]+)*()}?)/
|
|
14
14
|
|
|
15
15
|
const fileRefSyntax = RegExp(/^file\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)/g)
|
|
@@ -239,4 +239,4 @@ test('findNestedVariables - deep - no var types passed', () => {
|
|
|
239
239
|
|
|
240
240
|
|
|
241
241
|
// Run all tests
|
|
242
|
-
test.run();
|
|
242
|
+
test.run();
|
|
@@ -93,10 +93,10 @@ Remove or update the \${${variableString}} to fix
|
|
|
93
93
|
* Excludes suffix characters from the allowed set to prevent parsing issues
|
|
94
94
|
* @param {string} [prefix='${'] - Variable prefix
|
|
95
95
|
* @param {string} [suffix='}'] - Variable suffix
|
|
96
|
-
* @param {string[]} [excludePatterns=['AWS', 'stageVariables']] - Patterns to exclude via negative lookahead
|
|
96
|
+
* @param {string[]} [excludePatterns=['AWS', 'aws:', 'stageVariables']] - Patterns to exclude via negative lookahead
|
|
97
97
|
* @returns {string} Regex source string
|
|
98
98
|
*/
|
|
99
|
-
function buildVariableSyntax(prefix = '${', suffix = '}', excludePatterns = ['AWS', 'stageVariables']) {
|
|
99
|
+
function buildVariableSyntax(prefix = '${', suffix = '}', excludePatterns = ['AWS', 'aws:', 'stageVariables']) {
|
|
100
100
|
// All allowed characters, stored as individual escaped entries for regex character class
|
|
101
101
|
// Each entry is how it appears in a regex character class
|
|
102
102
|
// NOTE: { and } are intentionally excluded - they break nested variable matching
|
|
@@ -145,4 +145,4 @@ module.exports = {
|
|
|
145
145
|
getFallbackString,
|
|
146
146
|
verifyVariable,
|
|
147
147
|
buildVariableSyntax
|
|
148
|
-
}
|
|
148
|
+
}
|
|
@@ -172,6 +172,15 @@ test('buildVariableSyntax - supports backslash in values', () => {
|
|
|
172
172
|
assert.is(match[0], "${env:FOO, 'path\\to\\file'}")
|
|
173
173
|
})
|
|
174
174
|
|
|
175
|
+
test('buildVariableSyntax - default ${} syntax excludes IAM aws variables', () => {
|
|
176
|
+
const syntax = buildVariableSyntax('${', '}')
|
|
177
|
+
const regex = new RegExp(syntax, 'g')
|
|
178
|
+
|
|
179
|
+
assert.not.ok('${aws:username}'.match(regex))
|
|
180
|
+
assert.not.ok('arn:aws:s3:::bucket/${aws:PrincipalTag/team}/*'.match(regex))
|
|
181
|
+
assert.ok('${self:custom.value}'.match(regex))
|
|
182
|
+
})
|
|
183
|
+
|
|
175
184
|
test('buildVariableSyntax - double brace ${{}} syntax excludes }', () => {
|
|
176
185
|
const syntax = buildVariableSyntax('${{', '}}')
|
|
177
186
|
const regex = new RegExp(syntax, 'g')
|