configorama 0.4.10 → 0.5.1

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.
Files changed (40) hide show
  1. package/cli.js +116 -0
  2. package/package.json +9 -6
  3. package/{lib → src}/main.js +371 -451
  4. package/{lib → src}/parsers/yaml.js +30 -18
  5. package/src/parsers/yaml.test.js +169 -0
  6. package/{lib → src}/resolvers/valueFromEnv.js +10 -0
  7. package/{lib → src}/resolvers/valueFromGit.js +95 -1
  8. package/{lib → src}/utils/PromiseTracker.js +7 -4
  9. package/src/utils/arrayToJsonPath.js +11 -0
  10. package/src/utils/find-project-root.js +25 -0
  11. package/{lib → src}/utils/formatFunctionArgs.js +1 -1
  12. package/src/utils/lodash.js +91 -0
  13. package/src/utils/mergeByKeys.js +29 -0
  14. package/src/utils/parse.js +62 -0
  15. package/src/utils/replaceAll.js +16 -0
  16. package/{lib → src}/utils/splitByComma.js +7 -2
  17. package/src/utils/splitCsv.js +29 -0
  18. package/src/utils/textUtils.js +31 -0
  19. package/src/utils/unknownValues.js +46 -0
  20. package/src/utils/variableUtils.js +52 -0
  21. /package/{lib → src}/functions/md5.js +0 -0
  22. /package/{lib → src}/index.js +0 -0
  23. /package/{lib → src}/parsers/hcl.js +0 -0
  24. /package/{lib → src}/parsers/index.js +0 -0
  25. /package/{lib → src}/parsers/json5.js +0 -0
  26. /package/{lib → src}/parsers/toml.js +0 -0
  27. /package/{lib → src}/resolvers/valueFromNumber.js +0 -0
  28. /package/{lib → src}/resolvers/valueFromOptions.js +0 -0
  29. /package/{lib → src}/resolvers/valueFromSelf.js +0 -0
  30. /package/{lib → src}/resolvers/valueFromString.js +0 -0
  31. /package/{lib → src}/sync.js +0 -0
  32. /package/{lib → src}/utils/appendDeepVariable.js +0 -0
  33. /package/{lib → src}/utils/cleanVariable.js +0 -0
  34. /package/{lib → src}/utils/cloudformationSchema.js +0 -0
  35. /package/{lib → src}/utils/deep-log.js +0 -0
  36. /package/{lib → src}/utils/getFullFilePath.js +0 -0
  37. /package/{lib → src}/utils/handleSignalEvents.js +0 -0
  38. /package/{lib → src}/utils/isValidValue.js +0 -0
  39. /package/{lib → src}/utils/regex/index.js +0 -0
  40. /package/{lib → src}/utils/trimSurroundingQuotes.js +0 -0
@@ -83,28 +83,29 @@ function findOutermostVariables(text) {
83
83
 
84
84
 
85
85
  function matchOutermostBraces(text) {
86
- let depth = 0;
87
- let startIndex = -1;
88
- let results = [];
89
-
90
- for (let i = 0; i < text.length; i++) {
91
- if (text[i] === '{') {
92
- if (depth === 0) {
93
- startIndex = i;
94
- }
95
- depth++;
96
- } else if (text[i] === '}') {
97
- depth--;
98
- if (depth === 0 && startIndex !== -1) {
99
- results.push(text.substring(startIndex, i + 1));
100
- startIndex = -1;
101
- }
102
- }
86
+ let depth = 0
87
+ let startIndex = -1
88
+ let results = []
89
+
90
+ for (let i = 0; i < text.length; i++) {
91
+ if (text[i] === '{') {
92
+ if (depth === 0) {
93
+ startIndex = i
94
+ }
95
+ depth++
96
+ } else if (text[i] === '}') {
97
+ depth--
98
+ if (depth === 0 && startIndex !== -1) {
99
+ results.push(text.substring(startIndex, i + 1))
100
+ startIndex = -1
101
+ }
103
102
  }
103
+ }
104
104
 
105
- return results;
105
+ return results
106
106
  }
107
107
 
108
+
108
109
  // https://regex101.com/r/XIltbc/1
109
110
  const KEY_OBJECT = /^[ \t]*[^":\s]*:\s+\{/gm
110
111
 
@@ -161,7 +162,18 @@ function preProcess(ymlStr = '') {
161
162
  if (hasNestedVars && hasNestedVars.length) {
162
163
  let fixedText = txt
163
164
  hasNestedVars.forEach((nested) => {
165
+ const isObject = txt.match(/^\{/) && txt.match(/}$/)
164
166
  // console.log('nested', nested)
167
+
168
+ if (nested.match(/^\${/) && (nested.match(/"/) || nested.match(/'/)) && !isObject) {
169
+ return
170
+ }
171
+
172
+ // Fallback comma ${opt:stage, dev}
173
+ if (nested.match(/^\${/) && nested.match(/,/) && !txt.match(/\n/) ) {
174
+ return
175
+ }
176
+
165
177
  if (txt.indexOf(`"${nested}"`) > -1) {
166
178
  return
167
179
  }
@@ -0,0 +1,169 @@
1
+ const { test } = require('uvu')
2
+ const assert = require('uvu/assert')
3
+ const { preProcess } = require('./yaml')
4
+
5
+ test('preProcess - should wrap variables in quotes inside array brackets', () => {
6
+ const input = `
7
+
8
+ x: !Not [!Equals [!Join ['', "\${param:githubActionsAllowedAwsActions}"]]]
9
+
10
+ y: !Not [!Equals [!Join ['', \${param:xyz}]]]
11
+
12
+ # empty: "\${file(./config.json):na, ''}"
13
+
14
+ TestThree:
15
+ foo:
16
+ - ['a', 'b', 'c']
17
+ - ['d', 'e', \${ opt:otherFlag }, \${ opt:chillFlag }]
18
+ - ['d', 'e', "\${opt:otherFlag}", "\${opt:chillFlag}"]
19
+ - ['d', 'e', "\${opt:otherFlag}", "\${opt:chillFlag}"]
20
+
21
+
22
+ key: ['string1', 'string2', 'string3',
23
+ 'string4', \${opt:otherFlag},
24
+ 'string6'
25
+ ]
26
+
27
+ keyTwo: ['string1', 'long
28
+ string', 'string3', 'string4', 'string5', 'string6']
29
+
30
+ myarray: [
31
+ String1, String2, String3,
32
+ String4, String5, String5, String7
33
+ ]
34
+
35
+ xx: {
36
+ cool: \${self:empty, 'no value here'}
37
+ }
38
+
39
+ myarrayTwo: [
40
+ String1, \${self:empty, 'no value here'}, String3,
41
+ String4, String5, String5, String7
42
+ ]
43
+
44
+ normalObject:
45
+ cool: \${self:empty, 'no value here'}
46
+
47
+ # shorthand variable declaration
48
+ domainNameTwo: my-site-two.com
49
+ stage: dev
50
+ domainsTwo:
51
+ prod: api.\${domainNameTwo}
52
+ staging: api-staging.\${domainNameTwo}
53
+ dev: api-dev.\${domainNameTwo}
54
+ resolvedDomainNameTwo: \${domainsTwo.\${opt:stage, "prod"}}
55
+
56
+ `
57
+ const expected = `
58
+
59
+ x: !Not [!Equals [!Join ['', "\${param:githubActionsAllowedAwsActions}"]]]
60
+
61
+ y: !Not [!Equals [!Join ['', "\${param:xyz}"]]]
62
+
63
+ # empty: "\${file(./config.json):na, ''}"
64
+
65
+ TestThree:
66
+ foo:
67
+ - ['a', 'b', 'c']
68
+ - ['d', 'e', "\${ opt:otherFlag }", "\${ opt:chillFlag }"]
69
+ - ['d', 'e', "\${opt:otherFlag}", "\${opt:chillFlag}"]
70
+ - ['d', 'e', "\${opt:otherFlag}", "\${opt:chillFlag}"]
71
+
72
+
73
+ key: ['string1', 'string2', 'string3',
74
+ 'string4', "\${opt:otherFlag}",
75
+ 'string6'
76
+ ]
77
+
78
+ keyTwo: ['string1', 'long
79
+ string', 'string3', 'string4', 'string5', 'string6']
80
+
81
+ myarray: [
82
+ String1, String2, String3,
83
+ String4, String5, String5, String7
84
+ ]
85
+
86
+ xx: {
87
+ cool: "\${self:empty, 'no value here'}"
88
+ }
89
+
90
+ myarrayTwo: [
91
+ String1, "\${self:empty, 'no value here'}", String3,
92
+ String4, String5, String5, String7
93
+ ]
94
+
95
+ normalObject:
96
+ cool: \${self:empty, 'no value here'}
97
+
98
+ # shorthand variable declaration
99
+ domainNameTwo: my-site-two.com
100
+ stage: dev
101
+ domainsTwo:
102
+ prod: api.\${domainNameTwo}
103
+ staging: api-staging.\${domainNameTwo}
104
+ dev: api-dev.\${domainNameTwo}
105
+ resolvedDomainNameTwo: \${domainsTwo.\${opt:stage, "prod"}}
106
+
107
+ `
108
+ const result = preProcess(input)
109
+ console.log('result', result)
110
+ assert.equal(result, expected)
111
+ })
112
+
113
+ test('preProcess - should wrap variables in quotes inside array brackets', () => {
114
+ const input = `
115
+ service: my-service
116
+ custom:
117
+ myValue: !Not [!Equals [!Join ['', \${param:xyz}]]]
118
+ `
119
+ const expected = `
120
+ service: my-service
121
+ custom:
122
+ myValue: !Not [!Equals [!Join ['', "\${param:xyz}"]]]
123
+ `
124
+ const result = preProcess(input)
125
+ assert.equal(result, expected)
126
+ })
127
+
128
+ test('preProcess - should wrap variables in quotes inside objects', () => {
129
+ const input = `
130
+ resources:
131
+ MyResource:
132
+ Type: AWS::Lambda::Function
133
+ Properties: {
134
+ FunctionName: \${env:FUNC_NAME},
135
+ Handler: index.handler
136
+ }
137
+ `
138
+ const expected = `
139
+ resources:
140
+ MyResource:
141
+ Type: AWS::Lambda::Function
142
+ Properties: {
143
+ FunctionName: "\${env:FUNC_NAME}",
144
+ Handler: index.handler
145
+ }
146
+ `
147
+ const result = preProcess(input)
148
+ assert.equal(result, expected)
149
+ })
150
+
151
+ test('preProcess - should not wrap already quoted variables', () => {
152
+ const input = `
153
+ custom:
154
+ alreadyQuoted: !Not [!Equals [!Join ['', "\${param:xyz}"]]]
155
+ objectQuoted:
156
+ Properties: {
157
+ Name: "\${env:NAME}"
158
+ }
159
+ `
160
+ const result = preProcess(input)
161
+ assert.equal(result, input)
162
+ })
163
+
164
+ test('preProcess - should handle empty input', () => {
165
+ const result = preProcess()
166
+ assert.equal(result, '')
167
+ })
168
+
169
+ test.run()
@@ -3,6 +3,16 @@ const envRefSyntax = RegExp(/^env:/g)
3
3
 
4
4
  function getValueFromEnv(variableString) {
5
5
  const requestedEnvVar = variableString.split(':')[1]
6
+ // console.log('requestedEnvVar', requestedEnvVar)
7
+ if (requestedEnvVar === '') {
8
+ throw new Error(`Invalid variable syntax for environment variable reference "${variableString}".
9
+
10
+ \${env} variable must have a key path.
11
+
12
+ Example: \${env:MY_ENV_VAR}
13
+ `)
14
+ }
15
+
6
16
  let valueToPopulate
7
17
  if (requestedEnvVar !== '' || '' in process.env) {
8
18
  valueToPopulate = process.env[requestedEnvVar]
@@ -1,9 +1,12 @@
1
1
  /* from https://github.com/jacob-meacham/serverless-plugin-git-variables/blob/develop/src/index.js */
2
2
  const os = require('os')
3
+ const fs = require('fs')
4
+ const path = require('path')
3
5
  const childProcess = require('child_process')
4
6
  const GitUrlParse = require('git-url-parse')
5
7
  const { functionRegex } = require('../utils/regex')
6
8
  const formatFunctionArgs = require('../utils/formatFunctionArgs')
9
+ const { findProjectRoot } = require('../utils/find-project-root')
7
10
  const GIT_PREFIX = 'git'
8
11
  const gitVariableSyntax = RegExp(/^git:/g)
9
12
 
@@ -32,6 +35,17 @@ function createResolver(cwd) {
32
35
 
33
36
  const verifyMsg = `Verify the cwd has a .git directory\n`
34
37
  const normalizedVar = (variable || '').toLowerCase()
38
+ // console.log('normalizedVar', normalizedVar)
39
+
40
+ const argsMatch = (variable || '').match(/(.*)\((.*)\)/)
41
+ // console.log('argsMatch', argsMatch)
42
+ if (argsMatch) {
43
+ const funcName = argsMatch[1]
44
+ const args = argsMatch[2]
45
+ if (funcName === 'timestamp' && args) {
46
+ value = await getGitTimestamp(args, cwd)
47
+ }
48
+ }
35
49
 
36
50
  switch (normalizedVar) {
37
51
  // Repo owner/name
@@ -61,6 +75,7 @@ function createResolver(cwd) {
61
75
  break;
62
76
  // Repo name
63
77
  case 'dir':
78
+ case 'directory':
64
79
  case 'dirpath': // dirPath
65
80
  case 'dir-path':
66
81
  case 'dir_path':
@@ -152,13 +167,92 @@ function createResolver(cwd) {
152
167
  value = `${changes.length > 0}`
153
168
  break
154
169
  default:
155
- throw new Error(`Git variable ${variable} is unknown. Candidates are 'describe', 'describeLight', 'sha1', 'commit', 'branch', 'message', 'repository'`)
170
+ if (!value) {
171
+ throw new Error(`Git variable ${variable} is unknown. Candidates are 'describe', 'describeLight', 'sha1', 'commit', 'branch', 'message', 'repository'`)
172
+ }
156
173
  }
157
174
  return value
158
175
  }
159
176
  return _getValueFromGit
160
177
  }
161
178
 
179
+ const cache = new Map()
180
+
181
+ /**
182
+ * Gets the last Git commit timestamp for a file
183
+ * @param {string} file - Path to the file to check
184
+ * @returns {Promise<Date|undefined>} The commit timestamp or undefined if not in Git
185
+ */
186
+ async function getGitTimestamp(_file, cwd, throwOnMissing = true) {
187
+ // Validate file path to prevent command injection
188
+ if (typeof _file !== 'string') {
189
+ throw new Error('File path must be a string')
190
+ }
191
+
192
+ // Check for suspicious characters and patterns that could be used for command injection or path traversal
193
+ const dangerousPatterns = [
194
+ /[;&|`$]/, // Command injection chars
195
+ // /\.\.\//, // Directory traversal
196
+ // /\.\./, // Directory traversal
197
+ // /^[\/\\]/, // Absolute paths
198
+ /[\x00-\x1f\x7f-\x9f]/ // Control characters
199
+ ]
200
+
201
+ if (dangerousPatterns.some(pattern => pattern.test(_file))) {
202
+ throw new Error('Invalid characters or pattern in file path')
203
+ }
204
+
205
+ // Only allow alphanumeric chars, dashes, underscores, forward slashes, and dots
206
+ if (!/^[a-zA-Z0-9-_./\\'"]+$/.test(_file)) {
207
+ throw new Error('File path contains invalid characters')
208
+ }
209
+
210
+ // Normalize path and remove leading slash
211
+ const file = _file
212
+ .replace(/^\//, '')
213
+
214
+ const cachedTimestamp = cache.get(file)
215
+ if (cachedTimestamp) return cachedTimestamp
216
+
217
+ if (!fs.existsSync(cwd)) {
218
+ if (throwOnMissing) {
219
+ throw new Error(`Directory ${cwd} does not exist`)
220
+ }
221
+ return undefined
222
+ }
223
+
224
+ try {
225
+ const cmd = `git log -1 --pretty="%ai" ${file}`
226
+ // console.log('cmd', cmd)
227
+ // console.log('cwd', cwd)
228
+ const output = await _exec(cmd, { cwd })
229
+ const date = new Date(output)
230
+ cache.set(file, date)
231
+ return date.toISOString()
232
+ } catch (err) {
233
+ const projectRoot = findProjectRoot(cwd)
234
+ if (!projectRoot) {
235
+ if (throwOnMissing) {
236
+ throw new Error(`No Git repository found in ${cwd}`)
237
+ }
238
+ return undefined
239
+ }
240
+
241
+ try {
242
+ const backupFile = path.join(projectRoot, _file)
243
+ const output = await _exec(`git log -1 --pretty="%ai" ${backupFile}`, { cwd: projectRoot })
244
+ const date = new Date(output)
245
+ cache.set(file, date)
246
+ return date.toISOString()
247
+ } catch (err) {
248
+ if (throwOnMissing) {
249
+ throw new Error(`File ${file} does not exist in Git`)
250
+ }
251
+ return undefined
252
+ }
253
+ }
254
+ }
255
+
162
256
  async function getGitRemote(name = 'origin') {
163
257
  const remoteValues = await _exec('git remote -v')
164
258
  const remotes = remoteValues.toString().split(os.EOL)
@@ -1,4 +1,4 @@
1
- const trim = require('lodash.trim')
1
+ const { trim } = require('./lodash')
2
2
 
3
3
  // Track promise resolution
4
4
  class PromiseTracker {
@@ -41,7 +41,7 @@ class PromiseTracker {
41
41
  clearInterval(this.interval)
42
42
  this.reset()
43
43
  }
44
- add(variable, promise, specifier, hasFilter) {
44
+ add(variable, promise, specifier, hasFilter, promiseKey) {
45
45
  // Refactor promise tracker to account for multiple instances of a given variable
46
46
 
47
47
  // console.log(`${specifier} hasFilter`, hasFilter)
@@ -55,8 +55,11 @@ class PromiseTracker {
55
55
  }, '')
56
56
  }
57
57
 
58
- const nameSpacedVariable = `${variable}${uniqueId}`
59
- // console.log('nameSpacedVariable', nameSpacedVariable)
58
+ let nameSpacedVariable = `${variable}${uniqueId}`
59
+ if (promiseKey) {
60
+ nameSpacedVariable = promiseKey
61
+ }
62
+ // console.log('SET PROMISE', nameSpacedVariable)
60
63
 
61
64
  promise.waitList = `${variable} waited on by: ${specifier} ${nameSpacedVariable}`
62
65
  promise.state = 'pending'
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Convert array of paths to JSON path string
3
+ */
4
+ function arrayToJsonPath(paths) {
5
+ return paths.reduce((result, path, index) => {
6
+ if (index === 0) return path.toString()
7
+ return typeof path === 'string' ? `${result}.${path}` : `${result}[${path}]`
8
+ }, '')
9
+ }
10
+
11
+ module.exports = { arrayToJsonPath }
@@ -0,0 +1,25 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ function findProjectRoot(startDir = process.cwd(), maxLookup = 7) {
5
+ // Start from the current directory or a specified directory
6
+ let currentDir = startDir
7
+ let lookupCount = 0
8
+
9
+ // Keep looking up until we find .git, hit the root, or reach max lookup
10
+ while (currentDir !== path.parse(currentDir).root && lookupCount < maxLookup) {
11
+ if (fs.existsSync(path.join(currentDir, '.git'))) {
12
+ return currentDir
13
+ }
14
+ // Move up one directory
15
+ currentDir = path.dirname(currentDir)
16
+ lookupCount++
17
+ }
18
+
19
+ // If we reach here, we couldn't find a .git directory within the limit
20
+ return null
21
+ }
22
+
23
+ module.exports = {
24
+ findProjectRoot
25
+ }
@@ -1,4 +1,4 @@
1
- const trim = require('lodash.trim')
1
+ const { trim } = require('./lodash')
2
2
 
3
3
  function formatArg(arg) {
4
4
  const cleanArg = trim(arg).replace(/^('|")/, '').replace(/('|")$/, '')
@@ -0,0 +1,91 @@
1
+ const isArray = require('lodash.isarray')
2
+ const isString = require('lodash.isstring')
3
+ const isNumber = require('lodash.isnumber')
4
+ const isObject = require('lodash.isobject')
5
+ const isDate = require('lodash.isdate')
6
+ const isRegExp = require('lodash.isregexp')
7
+ const isFunction = require('lodash.isfunction')
8
+ const isEmpty = require('lodash.isempty')
9
+ const camelCase = require('lodash.camelcase')
10
+ const kebabCase = require('lodash.kebabcase')
11
+ const capitalize = require('lodash.capitalize')
12
+ const split = require('lodash.split')
13
+ const map = require('lodash.map')
14
+ const mapValues = require('lodash.mapvalues')
15
+ const assign = require('lodash.assign')
16
+ const cloneDeep = require('lodash.clonedeep')
17
+
18
+ // Custom implementation of lodash.set
19
+ function set(object, path, value) {
20
+ if (object === null || typeof object !== 'object') {
21
+ return object;
22
+ }
23
+
24
+ const keys = Array.isArray(path) ? path : String(path)
25
+ .split('.')
26
+ .map(key => {
27
+ const numKey = Number(key);
28
+ return Number.isInteger(numKey) && numKey >= 0 ? numKey : key;
29
+ });
30
+
31
+ let current = object;
32
+ const lastIndex = keys.length - 1;
33
+
34
+ for (let i = 0; i < lastIndex; i++) {
35
+ const key = keys[i];
36
+
37
+ if (current[key] === undefined) {
38
+ // Create appropriate container based on next key type
39
+ current[key] = Number.isInteger(keys[i + 1]) && keys[i + 1] >= 0 ? [] : {};
40
+ }
41
+
42
+ current = current[key];
43
+ }
44
+
45
+ current[keys[lastIndex]] = value;
46
+ return object;
47
+ }
48
+
49
+ // Custom implementation of lodash.trim
50
+ function trim(string, chars) {
51
+ if (string === null || string === undefined) {
52
+ return '';
53
+ }
54
+
55
+ string = String(string);
56
+
57
+ if (!chars && String.prototype.trim) {
58
+ return string.trim();
59
+ }
60
+
61
+ if (!chars) {
62
+ // Default characters to trim (whitespace)
63
+ chars = ' \t\n\r\f\v\u00a0\u1680\u2000\u200a\u2028\u2029\u202f\u205f\u3000\ufeff';
64
+ }
65
+
66
+ // Create a regex pattern with the characters to trim
67
+ const pattern = new RegExp(`^[${chars.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}]+|[${chars.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}]+$`, 'g');
68
+
69
+ return string.replace(pattern, '');
70
+ }
71
+
72
+ module.exports = {
73
+ isArray,
74
+ isString,
75
+ isNumber,
76
+ isObject,
77
+ isDate,
78
+ isRegExp,
79
+ isFunction,
80
+ isEmpty,
81
+ trim,
82
+ camelCase,
83
+ kebabCase,
84
+ capitalize,
85
+ split,
86
+ map,
87
+ mapValues,
88
+ assign,
89
+ set,
90
+ cloneDeep,
91
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Merge objects by specified keys
3
+ */
4
+ function mergeByKeys(data, path, keysToMerge) {
5
+ if (!data) return {}
6
+
7
+ const items = path.split('.').reduce((obj, key) => obj?.[key], data)
8
+ if (!Array.isArray(items)) return {}
9
+
10
+ const result = {}
11
+ const mergeAll = !keysToMerge || !Array.isArray(keysToMerge) || keysToMerge.length === 0
12
+
13
+ for (const item of items) {
14
+ const key = Object.keys(item)[0]
15
+
16
+ if (mergeAll || keysToMerge.includes(key)) {
17
+ if (!result[key]) {
18
+ result[key] = Object.assign({}, item[key])
19
+ } else {
20
+ result[key] = Object.assign({}, result[key], item[key])
21
+ }
22
+ } else {
23
+ result[key] = item[key]
24
+ }
25
+ }
26
+ return result
27
+ }
28
+
29
+ module.exports = { mergeByKeys }
@@ -0,0 +1,62 @@
1
+ const YAML = require('../parsers/yaml')
2
+ const TOML = require('../parsers/toml')
3
+ const cloudFormationSchema = require('./cloudformationSchema')
4
+
5
+ /**
6
+ * Parse file contents based on file extension
7
+ * @param {string} fileContents - Raw file contents to parse
8
+ * @param {string} fileType - File extension (.yml, .yaml, .json, etc)
9
+ * @param {string} filePath - Full file path (used for error messages)
10
+ * @param {RegExp} varRegex - Variable syntax regex
11
+ * @param {Object} opts - Additional options
12
+ * @returns {Object} Parsed configuration object
13
+ */
14
+ function parseFileContents(fileContents, fileType, filePath, varRegex, opts = {}) {
15
+ let configObject
16
+
17
+ if (fileType.match(/\.(yml|yaml)/)) {
18
+ try {
19
+ const ymlText = YAML.preProcess(fileContents, varRegex)
20
+ configObject = YAML.parse(ymlText)
21
+ } catch (err) {
22
+ // Attempt to fix cloudformation refs
23
+ if (err.message.match(/YAMLException/)) {
24
+ const ymlText = YAML.preProcess(fileContents, varRegex)
25
+ const result = YAML.load(ymlText, {
26
+ filename: filePath,
27
+ schema: cloudFormationSchema.schema,
28
+ })
29
+ if (result.error) {
30
+ throw result.error
31
+ }
32
+ configObject = result.data
33
+ }
34
+ }
35
+ } else if (fileType.match(/\.(toml)/)) {
36
+ configObject = TOML.parse(fileContents)
37
+ } else if (fileType.match(/\.(json)/)) {
38
+ configObject = JSON.parse(fileContents)
39
+ } else if (fileType.match(/\.(js)/)) {
40
+ let jsFile
41
+ try {
42
+ jsFile = require(filePath)
43
+ if (typeof jsFile !== 'function') {
44
+ configObject = jsFile
45
+ } else {
46
+ let jsArgs = opts.dynamicArgs || {}
47
+ if (jsArgs && typeof jsArgs === 'function') {
48
+ jsArgs = jsArgs()
49
+ }
50
+ configObject = jsFile(jsArgs)
51
+ }
52
+ } catch (err) {
53
+ throw new Error(err)
54
+ }
55
+ }
56
+
57
+ return configObject
58
+ }
59
+
60
+ module.exports = {
61
+ parseFileContents
62
+ }
@@ -0,0 +1,16 @@
1
+ const REPLACE_PATTERN = /([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|<>\-\&])/g
2
+
3
+ /**
4
+ * Replace all occurrences of a string while handling regex special characters
5
+ * @param {string} replaceThis - String to replace
6
+ * @param {string} withThis - Replacement string
7
+ * @param {string} inThis - Source string
8
+ * @returns {string} String with all replacements made
9
+ */
10
+ function replaceAll(replaceThis, withThis, inThis) {
11
+ withThis = withThis.replace(/\$/g, '$$$$')
12
+ const pat = new RegExp(replaceThis.replace(REPLACE_PATTERN, '\\$&'), 'g')
13
+ return inThis.replace(pat, withThis)
14
+ }
15
+
16
+ module.exports = { replaceAll }
@@ -11,9 +11,10 @@ const stringRefSyntax = match
11
11
  => ["env:BAZ", "'defaultEnvValue'"]
12
12
  */
13
13
 
14
+ // https://regex101.com/r/4uPmpt/1
14
15
  const commasOutsideOfParens = /(?!<(?:\(|\[)[^)\]]+),(?![^(\[]+(?:\)|\]))/
15
16
  // const commasOutOfParens = /(?!(?:\()[^)\]]+),(?![^(\[]+(?:\)))/g
16
- module.exports = function splitByComma(string) {
17
+ function splitByComma(string) {
17
18
  // If comma is not outside of parens, return early,
18
19
  // for file(file.js, param, paramTwo) & function support
19
20
  // TODO clean this up
@@ -40,7 +41,7 @@ module.exports = function splitByComma(string) {
40
41
  while (match) {
41
42
  const matchContained = contained(match)
42
43
  const containedBy = stringMatches.find(matchContained)
43
- if (!containedBy) { // if uncontained, this comma respresents a splitting location
44
+ if (!containedBy) { // if un-contained, this comma represents a splitting location
44
45
  commaReplacements.push({
45
46
  start: match.index,
46
47
  end: overwriteSyntax.lastIndex,
@@ -64,3 +65,7 @@ module.exports = function splitByComma(string) {
64
65
  results.push(input.slice(prior))
65
66
  return results
66
67
  }
68
+
69
+ module.exports = {
70
+ splitByComma
71
+ }