configorama 0.4.9 → 0.5.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/cli.js +116 -0
- package/lib/main.js +419 -481
- package/lib/parsers/yaml.js +30 -18
- package/lib/parsers/yaml.test.js +169 -0
- package/lib/resolvers/valueFromEnv.js +10 -0
- package/lib/resolvers/valueFromGit.js +95 -1
- package/lib/resolvers/valueFromNumber.js +2 -2
- package/lib/utils/PromiseTracker.js +10 -8
- package/lib/utils/arrayToJsonPath.js +11 -0
- package/lib/utils/cloudformationSchema.js +10 -6
- package/lib/utils/find-project-root.js +25 -0
- package/lib/utils/formatFunctionArgs.js +2 -2
- package/lib/utils/handleSignalEvents.js +17 -14
- package/lib/utils/isValidValue.js +2 -2
- package/lib/utils/lodash.js +91 -0
- package/lib/utils/mergeByKeys.js +29 -0
- package/lib/utils/parse.js +62 -0
- package/lib/utils/replaceAll.js +16 -0
- package/lib/utils/splitByComma.js +7 -2
- package/lib/utils/splitCsv.js +29 -0
- package/lib/utils/textUtils.js +31 -0
- package/lib/utils/unknownValues.js +46 -0
- package/lib/utils/variableUtils.js +52 -0
- package/package.json +30 -19
package/lib/parsers/yaml.js
CHANGED
|
@@ -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,8 +1,8 @@
|
|
|
1
|
-
const
|
|
1
|
+
const isNumber = require('lodash.isnumber')
|
|
2
2
|
|
|
3
3
|
function isNumberVariable(variableString) {
|
|
4
4
|
const num = Number(variableString)
|
|
5
|
-
return !isNaN(num) &&
|
|
5
|
+
return !isNaN(num) && isNumber(num)
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function getValueFromNumber(variableString) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { trim } = require('./lodash')
|
|
2
2
|
|
|
3
3
|
// Track promise resolution
|
|
4
4
|
class PromiseTracker {
|
|
@@ -18,7 +18,7 @@ class PromiseTracker {
|
|
|
18
18
|
report() {
|
|
19
19
|
const delta = Date.now() - this.startTime
|
|
20
20
|
const pending = this.getPending()
|
|
21
|
-
const dots =
|
|
21
|
+
const dots = dotDotDot(this.cursor++, 100, '...')
|
|
22
22
|
console.log([
|
|
23
23
|
`Fetching Async values${dots}`,
|
|
24
24
|
].concat(
|
|
@@ -41,8 +41,7 @@ class PromiseTracker {
|
|
|
41
41
|
clearInterval(this.interval)
|
|
42
42
|
this.reset()
|
|
43
43
|
}
|
|
44
|
-
add(variable,
|
|
45
|
-
const promise = prms
|
|
44
|
+
add(variable, promise, specifier, hasFilter, promiseKey) {
|
|
46
45
|
// Refactor promise tracker to account for multiple instances of a given variable
|
|
47
46
|
|
|
48
47
|
// console.log(`${specifier} hasFilter`, hasFilter)
|
|
@@ -51,13 +50,16 @@ class PromiseTracker {
|
|
|
51
50
|
let uniqueId = ''
|
|
52
51
|
if (hasFilter) {
|
|
53
52
|
uniqueId = hasFilter.reduce((acc, currentFilter, i) => {
|
|
54
|
-
acc += `|${
|
|
53
|
+
acc += `|${trim(currentFilter)}`
|
|
55
54
|
return acc
|
|
56
55
|
}, '')
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
let nameSpacedVariable = `${variable}${uniqueId}`
|
|
59
|
+
if (promiseKey) {
|
|
60
|
+
nameSpacedVariable = promiseKey
|
|
61
|
+
}
|
|
62
|
+
// console.log('SET PROMISE', nameSpacedVariable)
|
|
61
63
|
|
|
62
64
|
promise.waitList = `${variable} waited on by: ${specifier} ${nameSpacedVariable}`
|
|
63
65
|
promise.state = 'pending'
|
|
@@ -89,7 +91,7 @@ class PromiseTracker {
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
function
|
|
94
|
+
function dotDotDot(cursor, times, string) {
|
|
93
95
|
return Array(times - Math.abs(cursor % (times * 2) - times) + 1).join(string)
|
|
94
96
|
}
|
|
95
97
|
|
|
@@ -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 }
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const YAML = require('js-yaml');
|
|
2
|
-
const
|
|
2
|
+
const includes = require('lodash.includes');
|
|
3
|
+
const isString = require('lodash.isstring');
|
|
4
|
+
const split = require('lodash.split');
|
|
5
|
+
const flatten = require('lodash.flatten');
|
|
6
|
+
const map = require('lodash.map');
|
|
3
7
|
|
|
4
8
|
const functionNames = [
|
|
5
9
|
'And',
|
|
@@ -22,13 +26,13 @@ const functionNames = [
|
|
|
22
26
|
];
|
|
23
27
|
|
|
24
28
|
const yamlType = (name, kind) => {
|
|
25
|
-
const functionName =
|
|
29
|
+
const functionName = includes(['Ref', 'Condition'], name) ? name : `Fn::${name}`;
|
|
26
30
|
return new YAML.Type(`!${name}`, {
|
|
27
31
|
kind,
|
|
28
32
|
construct: data => {
|
|
29
33
|
if (name === 'GetAtt') {
|
|
30
34
|
// special GetAtt dot syntax
|
|
31
|
-
return { [functionName]:
|
|
35
|
+
return { [functionName]: isString(data) ? split(data, '.', 2) : data };
|
|
32
36
|
}
|
|
33
37
|
return { [functionName]: data };
|
|
34
38
|
},
|
|
@@ -36,9 +40,9 @@ const yamlType = (name, kind) => {
|
|
|
36
40
|
};
|
|
37
41
|
|
|
38
42
|
const createSchema = () => {
|
|
39
|
-
const types =
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
const types = flatten(
|
|
44
|
+
map(functionNames, functionName =>
|
|
45
|
+
map(['mapping', 'scalar', 'sequence'], kind => yamlType(functionName, kind))
|
|
42
46
|
)
|
|
43
47
|
);
|
|
44
48
|
return YAML.Schema.create(types);
|
|
@@ -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,7 +1,7 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { trim } = require('./lodash')
|
|
2
2
|
|
|
3
3
|
function formatArg(arg) {
|
|
4
|
-
const cleanArg =
|
|
4
|
+
const cleanArg = trim(arg).replace(/^('|")/, '').replace(/('|")$/, '')
|
|
5
5
|
if (cleanArg.match(/^{([^}]+)}$/)) {
|
|
6
6
|
return JSON.parse(cleanArg)
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const readline = require('readline')
|
|
2
2
|
|
|
3
3
|
function handleSignalEvents() {
|
|
4
|
+
if (global.signalEventHandling) return // Only set up handlers once
|
|
4
5
|
// NOTE: instantiating this global variable here to keep track of the state
|
|
5
6
|
// usually global variables should be "considered harmful" but are a good fit in this case
|
|
6
7
|
global.signalEventHandling = {
|
|
@@ -8,7 +9,7 @@ function handleSignalEvents() {
|
|
|
8
9
|
shouldExitGracefully: false
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const msg = `
|
|
12
13
|
───────────────────────────────────────────────────────────
|
|
13
14
|
Exit received. Waiting for current operation to finish...
|
|
14
15
|
───────────────────────────────────────────────────────────
|
|
@@ -20,23 +21,25 @@ Exit received. Waiting for current operation to finish...
|
|
|
20
21
|
output: process.stdout
|
|
21
22
|
})
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
rl.
|
|
30
|
-
process.emit('SIGBREAK')
|
|
31
|
-
})
|
|
24
|
+
// Clean up readline interface when done
|
|
25
|
+
process.once('exit', () => rl.close())
|
|
26
|
+
|
|
27
|
+
// Use once() instead of on()
|
|
28
|
+
rl.once('SIGINT', () => process.emit('SIGINT'))
|
|
29
|
+
rl.once('SIGTERM', () => process.emit('SIGTERM'))
|
|
30
|
+
rl.once('SIGBREAK', () => process.emit('SIGBREAK'))
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
// Remove any existing listeners before adding new ones
|
|
34
|
+
process.removeAllListeners('SIGINT')
|
|
35
|
+
process.removeAllListeners('SIGTERM')
|
|
36
|
+
process.removeAllListeners('SIGBREAK')
|
|
37
|
+
|
|
34
38
|
process.on('SIGINT', () => {
|
|
35
39
|
global.signalEventHandling.SIGINTCount += 1
|
|
36
40
|
global.signalEventHandling.shouldExitGracefully = true
|
|
37
41
|
if (global.signalEventHandling.SIGINTCount < 2) {
|
|
38
|
-
|
|
39
|
-
console.log(msg) // eslint-disable-line no-console
|
|
42
|
+
console.log(`${msg} Press CTRL + C again to force an exit\nNOTE: Doing so might corrupt the applications state information!`)
|
|
40
43
|
} else {
|
|
41
44
|
process.exit(1)
|
|
42
45
|
}
|
|
@@ -44,12 +47,12 @@ Exit received. Waiting for current operation to finish...
|
|
|
44
47
|
|
|
45
48
|
process.on('SIGTERM', () => {
|
|
46
49
|
global.signalEventHandling.shouldExitGracefully = true
|
|
47
|
-
console.log(msg)
|
|
50
|
+
console.log(msg)
|
|
48
51
|
})
|
|
49
52
|
|
|
50
53
|
process.on('SIGBREAK', () => {
|
|
51
54
|
global.signalEventHandling.shouldExitGracefully = true
|
|
52
|
-
console.log(msg)
|
|
55
|
+
console.log(msg)
|
|
53
56
|
})
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const
|
|
1
|
+
const isEmpty = require('lodash.isempty')
|
|
2
2
|
|
|
3
3
|
module.exports = function isValidValue(val) {
|
|
4
4
|
if (typeof val === 'object' && val.hasOwnProperty('__internal_only_flag')) {
|
|
5
5
|
return false
|
|
6
6
|
}
|
|
7
|
-
return val !== null && typeof val !== 'undefined' && !(typeof val === 'object' &&
|
|
7
|
+
return val !== null && typeof val !== 'undefined' && !(typeof val === 'object' && isEmpty(val))
|
|
8
8
|
}
|