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.
- package/cli.js +116 -0
- package/package.json +9 -6
- package/{lib → src}/main.js +371 -451
- package/{lib → src}/parsers/yaml.js +30 -18
- package/src/parsers/yaml.test.js +169 -0
- package/{lib → src}/resolvers/valueFromEnv.js +10 -0
- package/{lib → src}/resolvers/valueFromGit.js +95 -1
- package/{lib → src}/utils/PromiseTracker.js +7 -4
- package/src/utils/arrayToJsonPath.js +11 -0
- package/src/utils/find-project-root.js +25 -0
- package/{lib → src}/utils/formatFunctionArgs.js +1 -1
- package/src/utils/lodash.js +91 -0
- package/src/utils/mergeByKeys.js +29 -0
- package/src/utils/parse.js +62 -0
- package/src/utils/replaceAll.js +16 -0
- package/{lib → src}/utils/splitByComma.js +7 -2
- package/src/utils/splitCsv.js +29 -0
- package/src/utils/textUtils.js +31 -0
- package/src/utils/unknownValues.js +46 -0
- package/src/utils/variableUtils.js +52 -0
- /package/{lib → src}/functions/md5.js +0 -0
- /package/{lib → src}/index.js +0 -0
- /package/{lib → src}/parsers/hcl.js +0 -0
- /package/{lib → src}/parsers/index.js +0 -0
- /package/{lib → src}/parsers/json5.js +0 -0
- /package/{lib → src}/parsers/toml.js +0 -0
- /package/{lib → src}/resolvers/valueFromNumber.js +0 -0
- /package/{lib → src}/resolvers/valueFromOptions.js +0 -0
- /package/{lib → src}/resolvers/valueFromSelf.js +0 -0
- /package/{lib → src}/resolvers/valueFromString.js +0 -0
- /package/{lib → src}/sync.js +0 -0
- /package/{lib → src}/utils/appendDeepVariable.js +0 -0
- /package/{lib → src}/utils/cleanVariable.js +0 -0
- /package/{lib → src}/utils/cloudformationSchema.js +0 -0
- /package/{lib → src}/utils/deep-log.js +0 -0
- /package/{lib → src}/utils/getFullFilePath.js +0 -0
- /package/{lib → src}/utils/handleSignalEvents.js +0 -0
- /package/{lib → src}/utils/isValidValue.js +0 -0
- /package/{lib → src}/utils/regex/index.js +0 -0
- /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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
59
|
-
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
+
}
|