configorama 0.9.12 → 0.9.14
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 +2067 -392
- package/cli.js +47 -9
- package/index.d.ts +1 -0
- package/package.json +1 -17
- package/src/main.js +39 -27
- package/src/parsers/index.js +3 -1
- package/src/parsers/markdown.js +69 -0
- package/src/parsers/markdown.test.js +132 -0
- package/src/resolvers/valueFromEnv.js +3 -6
- package/src/resolvers/valueFromFile.js +4 -4
- package/src/resolvers/valueFromGit.js +128 -86
- package/src/resolvers/valueFromNumber.js +10 -1
- package/src/resolvers/valueFromOptions.js +3 -7
- package/src/resolvers/valueFromParam.js +2 -1
- package/src/types.d.ts +1 -1
- package/src/utils/handleSignalEvents.js +1 -5
- package/src/utils/lodash.js +91 -37
- package/src/utils/parsing/cloudformationSchema.js +5 -10
- package/src/utils/parsing/getValueAtPath.js +111 -0
- package/src/utils/parsing/getValueAtPath.test.js +152 -0
- package/src/utils/parsing/parse.js +22 -1
- package/src/utils/parsing/preProcess.js +16 -10
- package/src/utils/regex/index.js +6 -9
- package/src/utils/ui/configWizard.js +4 -4
- package/src/utils/validation/warnIfNotFound.js +5 -1
- package/src/utils/variables/cleanVariable.js +1 -24
- package/types/src/main.d.ts +2 -0
- package/types/src/main.d.ts.map +1 -1
- package/types/src/parsers/markdown.d.ts +17 -0
- package/types/src/parsers/markdown.d.ts.map +1 -0
- package/types/src/resolvers/valueFromEnv.d.ts +1 -1
- package/types/src/resolvers/valueFromEnv.d.ts.map +1 -1
- package/types/src/resolvers/valueFromGit.d.ts.map +1 -1
- package/types/src/resolvers/valueFromNumber.d.ts +10 -2
- package/types/src/resolvers/valueFromNumber.d.ts.map +1 -1
- package/types/src/resolvers/valueFromOptions.d.ts.map +1 -1
- package/types/src/resolvers/valueFromParam.d.ts.map +1 -1
- package/types/src/utils/handleSignalEvents.d.ts.map +1 -1
- package/types/src/utils/lodash.d.ts +50 -3
- package/types/src/utils/lodash.d.ts.map +1 -1
- package/types/src/utils/parsing/getValueAtPath.d.ts +18 -0
- package/types/src/utils/parsing/getValueAtPath.d.ts.map +1 -0
- package/types/src/utils/parsing/parse.d.ts.map +1 -1
- package/types/src/utils/parsing/preProcess.d.ts.map +1 -1
- package/types/src/utils/regex/index.d.ts +5 -6
- package/types/src/utils/regex/index.d.ts.map +1 -1
- package/types/src/utils/validation/warnIfNotFound.d.ts +4 -0
- package/types/src/utils/validation/warnIfNotFound.d.ts.map +1 -1
- package/types/src/utils/variables/cleanVariable.d.ts +1 -1
- package/types/src/utils/variables/cleanVariable.d.ts.map +1 -1
- package/src/resolvers/valueFromSelf.js +0 -0
|
@@ -10,6 +10,21 @@ const { findProjectRoot } = require('../utils/paths/findProjectRoot')
|
|
|
10
10
|
const GIT_PREFIX = 'git'
|
|
11
11
|
const gitVariableSyntax = RegExp(/^git:/g)
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Check if a directory is inside a git repository.
|
|
15
|
+
* @param {string} [dir] - Directory to check (defaults to process.cwd())
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function isGitRepo(dir) {
|
|
19
|
+
const start = dir || process.cwd()
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(start)) return false
|
|
22
|
+
return findProjectRoot(start) !== null
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
/**
|
|
14
29
|
* Execute a shell command
|
|
15
30
|
* @param {string} cmd - Command to execute
|
|
@@ -27,6 +42,43 @@ async function _exec(cmd, options = { timeout: 1000 }) {
|
|
|
27
42
|
})
|
|
28
43
|
}
|
|
29
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Execute a command with arguments array (safe from shell injection)
|
|
47
|
+
* @param {string} command - Command to execute
|
|
48
|
+
* @param {string[]} args - Arguments array
|
|
49
|
+
* @param {import('child_process').ExecFileOptions} [options] - ExecFile options
|
|
50
|
+
* @returns {Promise<string>}
|
|
51
|
+
*/
|
|
52
|
+
async function _execFile(command, args, options = { timeout: 1000 }) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
childProcess.execFile(command, args, options, (err, stdout) => {
|
|
55
|
+
if (err) {
|
|
56
|
+
return reject(err)
|
|
57
|
+
}
|
|
58
|
+
return resolve(String(stdout).trim())
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run a git command and return undefined on failure. This lets the variable
|
|
65
|
+
* resolver fall through to user-provided fallbacks (e.g. `${git:branch, "main"}`)
|
|
66
|
+
* when not in a git repo, without surfacing raw `fatal: not a git repository`
|
|
67
|
+
* errors. When no fallback is provided, the outer resolver in main.js still
|
|
68
|
+
* produces a clear "Unable to resolve config variable" error pointing at the
|
|
69
|
+
* exact config path.
|
|
70
|
+
*
|
|
71
|
+
* @param {() => Promise<string>} cmdFn - Function that runs the git command
|
|
72
|
+
* @returns {Promise<string|undefined>}
|
|
73
|
+
*/
|
|
74
|
+
async function _safeGit(cmdFn) {
|
|
75
|
+
try {
|
|
76
|
+
return await cmdFn()
|
|
77
|
+
} catch (err) {
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
30
82
|
// TODO denote computed fields in metadata
|
|
31
83
|
/*
|
|
32
84
|
{
|
|
@@ -65,14 +117,21 @@ function createResolver(cwd) {
|
|
|
65
117
|
const variable = variableString.split(`${GIT_PREFIX}:`)[1]
|
|
66
118
|
let value = null
|
|
67
119
|
// console.log('createResolver variableString', variableString)
|
|
120
|
+
|
|
121
|
+
// If we're not inside a git repository, every git: variable resolves to
|
|
122
|
+
// undefined. This lets fallbacks like `${git:branch, "main"}` work, and
|
|
123
|
+
// when there's no fallback the outer resolver throws a clear "Unable to
|
|
124
|
+
// resolve config variable" error pointing at the config path.
|
|
125
|
+
if (!isGitRepo(cwd)) {
|
|
126
|
+
return undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
68
129
|
if (variable.match(/^remote/i)) {
|
|
69
130
|
const hasParams = functionRegex.exec(variableString)
|
|
70
131
|
const remoteName = (hasParams && hasParams[2]) ? formatFunctionArgs(hasParams[2]) : 'origin'
|
|
71
|
-
|
|
72
|
-
return value
|
|
132
|
+
return _safeGit(() => getGitRemote(remoteName))
|
|
73
133
|
}
|
|
74
134
|
|
|
75
|
-
const verifyMsg = `Verify the cwd has a .git directory\n`
|
|
76
135
|
const normalizedVar = (variable || '').toLowerCase()
|
|
77
136
|
// console.log('normalizedVar', normalizedVar)
|
|
78
137
|
|
|
@@ -82,7 +141,7 @@ function createResolver(cwd) {
|
|
|
82
141
|
const funcName = argsMatch[1]
|
|
83
142
|
const args = argsMatch[2]
|
|
84
143
|
if (funcName === 'timestamp' && args) {
|
|
85
|
-
value = await getGitTimestamp(args, cwd)
|
|
144
|
+
value = await getGitTimestamp(args, cwd, false)
|
|
86
145
|
}
|
|
87
146
|
}
|
|
88
147
|
|
|
@@ -91,55 +150,61 @@ function createResolver(cwd) {
|
|
|
91
150
|
case GIT_KEYS.repo:
|
|
92
151
|
case 'repository':
|
|
93
152
|
case 'reposlug':
|
|
94
|
-
case 'repo-slug':
|
|
95
|
-
const urla = await getGitRemote()
|
|
153
|
+
case 'repo-slug': {
|
|
154
|
+
const urla = await _safeGit(() => getGitRemote())
|
|
155
|
+
if (!urla) return undefined
|
|
96
156
|
const parseda = GitUrlParse(urla)
|
|
97
157
|
value = parseda.full_name
|
|
98
|
-
break
|
|
158
|
+
break
|
|
159
|
+
}
|
|
99
160
|
// Repo name
|
|
100
161
|
case GIT_KEYS.name:
|
|
101
162
|
case 'reponame': // repoName
|
|
102
|
-
case 'repo-name':
|
|
103
|
-
|
|
104
|
-
|
|
163
|
+
case 'repo-name': {
|
|
164
|
+
const toplevel = await _safeGit(() => _execFile('git', ['rev-parse', '--show-toplevel']))
|
|
165
|
+
if (!toplevel) return undefined
|
|
166
|
+
value = path.basename(toplevel)
|
|
167
|
+
break
|
|
168
|
+
}
|
|
105
169
|
// Repo org or owner
|
|
106
170
|
case GIT_KEYS.org:
|
|
107
171
|
case 'owner':
|
|
108
172
|
case 'organization':
|
|
109
173
|
case 'repoowner': // repoOwner
|
|
110
|
-
case 'repo-owner':
|
|
111
|
-
const url = await getGitRemote()
|
|
174
|
+
case 'repo-owner': {
|
|
175
|
+
const url = await _safeGit(() => getGitRemote())
|
|
176
|
+
if (!url) return undefined
|
|
112
177
|
const parsed = GitUrlParse(url)
|
|
113
178
|
value = parsed.organization || parsed.owner
|
|
114
|
-
break
|
|
179
|
+
break
|
|
180
|
+
}
|
|
115
181
|
// Repo name
|
|
116
182
|
case GIT_KEYS.dir:
|
|
117
183
|
case 'directory':
|
|
118
184
|
case 'dirpath': // dirPath
|
|
119
185
|
case 'dir-path':
|
|
120
|
-
case 'dir_path':
|
|
121
|
-
const gitBasePath = await
|
|
186
|
+
case 'dir_path': {
|
|
187
|
+
const gitBasePath = await _safeGit(() => _execFile('git', ['rev-parse', '--show-toplevel']))
|
|
188
|
+
if (!gitBasePath) return undefined
|
|
122
189
|
if (cwd) {
|
|
123
190
|
const subPath = cwd.replace(gitBasePath, '')
|
|
124
|
-
const branch = await
|
|
125
|
-
const url = await getGitRemote()
|
|
126
|
-
|
|
191
|
+
const branch = await _safeGit(() => _execFile('git', ['rev-parse', '--abbrev-ref', 'HEAD']))
|
|
192
|
+
const url = await _safeGit(() => getGitRemote())
|
|
193
|
+
if (!url) return undefined
|
|
194
|
+
value = (subPath && branch) ? `${url}/tree/${branch}${subPath}` : url
|
|
127
195
|
}
|
|
128
|
-
break
|
|
196
|
+
break
|
|
197
|
+
}
|
|
129
198
|
// Repo url
|
|
130
199
|
case GIT_KEYS.url:
|
|
131
200
|
case 'repourl': // repoUrl
|
|
132
201
|
case 'repo-url':
|
|
133
|
-
value = await getGitRemote()
|
|
134
|
-
break
|
|
202
|
+
value = await _safeGit(() => getGitRemote())
|
|
203
|
+
break
|
|
135
204
|
// Current commit sha
|
|
136
205
|
case 'sha':
|
|
137
206
|
case 'sha1':
|
|
138
|
-
|
|
139
|
-
value = await _exec('git rev-parse --short HEAD')
|
|
140
|
-
} catch (err) {
|
|
141
|
-
throw new Error(`\${git:sha1} error ${verifyMsg}`)
|
|
142
|
-
}
|
|
207
|
+
value = await _safeGit(() => _execFile('git', ['rev-parse', '--short', 'HEAD']))
|
|
143
208
|
break
|
|
144
209
|
// Current commit full sha
|
|
145
210
|
case GIT_KEYS.commit:
|
|
@@ -147,11 +212,7 @@ function createResolver(cwd) {
|
|
|
147
212
|
case 'commit-sha':
|
|
148
213
|
case 'commithash':
|
|
149
214
|
case 'commit-hash':
|
|
150
|
-
|
|
151
|
-
value = await _exec('git rev-parse HEAD')
|
|
152
|
-
} catch (err) {
|
|
153
|
-
throw new Error(`\${git:commit} error. ${verifyMsg}`)
|
|
154
|
-
}
|
|
215
|
+
value = await _safeGit(() => _execFile('git', ['rev-parse', 'HEAD']))
|
|
155
216
|
break
|
|
156
217
|
// Branches
|
|
157
218
|
case GIT_KEYS.branch:
|
|
@@ -159,11 +220,7 @@ function createResolver(cwd) {
|
|
|
159
220
|
case 'branch-name':
|
|
160
221
|
case 'currentbranch': // currentBranch
|
|
161
222
|
case 'current-branch':
|
|
162
|
-
|
|
163
|
-
value = await _exec('git rev-parse --abbrev-ref HEAD')
|
|
164
|
-
} catch (err) {
|
|
165
|
-
throw new Error(`\${git:branch} error. ${verifyMsg}`)
|
|
166
|
-
}
|
|
223
|
+
value = await _safeGit(() => _execFile('git', ['rev-parse', '--abbrev-ref', 'HEAD']))
|
|
167
224
|
break
|
|
168
225
|
// Commit msg
|
|
169
226
|
case GIT_KEYS.message:
|
|
@@ -172,42 +229,36 @@ function createResolver(cwd) {
|
|
|
172
229
|
case 'commit-message':
|
|
173
230
|
case 'commitmsg': // commitMsg
|
|
174
231
|
case 'commit-msg':
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
} catch (err) {
|
|
178
|
-
throw new Error(`\${git:message} error. ${verifyMsg}`)
|
|
179
|
-
}
|
|
180
|
-
break;
|
|
232
|
+
value = await _safeGit(() => _execFile('git', ['log', '-1', '--pretty=%B']))
|
|
233
|
+
break
|
|
181
234
|
// Git tags
|
|
182
235
|
case GIT_KEYS.tag:
|
|
183
236
|
case 'describe':
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
} catch (err) {
|
|
187
|
-
throw new Error(`\${git:describeLight} error. ${verifyMsg}`)
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
237
|
+
value = await _safeGit(() => _execFile('git', ['describe', '--always']))
|
|
238
|
+
break
|
|
190
239
|
// Git tags
|
|
191
240
|
case 'describeLight':
|
|
192
241
|
case 'describelight':
|
|
193
242
|
case 'describe-light':
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
} catch (err) {
|
|
197
|
-
throw new Error(`\${git:describeLight} error. ${verifyMsg}`)
|
|
198
|
-
}
|
|
199
|
-
break;
|
|
243
|
+
value = await _safeGit(() => _execFile('git', ['describe', '--always', '--tags']))
|
|
244
|
+
break
|
|
200
245
|
// Is branch dirty
|
|
201
246
|
case 'isDirty':
|
|
202
247
|
case 'isdirty':
|
|
203
|
-
case 'is-dirty':
|
|
204
|
-
const writeTree = await
|
|
205
|
-
|
|
248
|
+
case 'is-dirty': {
|
|
249
|
+
const writeTree = await _safeGit(() => _execFile('git', ['write-tree']))
|
|
250
|
+
if (!writeTree) return undefined
|
|
251
|
+
const changes = await _safeGit(() => _execFile('git', ['diff-index', writeTree.trim(), '--']))
|
|
252
|
+
if (changes === undefined) return undefined
|
|
206
253
|
value = `${changes.length > 0}`
|
|
207
254
|
break
|
|
255
|
+
}
|
|
208
256
|
default:
|
|
209
257
|
if (!value) {
|
|
210
|
-
|
|
258
|
+
// Unknown variable name (likely a typo). This is a config error,
|
|
259
|
+
// not an environment one, so throw a helpful message listing the
|
|
260
|
+
// valid keys.
|
|
261
|
+
throw new Error(`Git variable "${variable}" is unknown. Valid options: ${Object.values(GIT_KEYS).join(', ')}`)
|
|
211
262
|
}
|
|
212
263
|
}
|
|
213
264
|
return value
|
|
@@ -230,28 +281,16 @@ async function getGitTimestamp(_file, cwd, throwOnMissing = true) {
|
|
|
230
281
|
throw new Error('File path must be a string')
|
|
231
282
|
}
|
|
232
283
|
|
|
233
|
-
//
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// /\.\./, // Directory traversal
|
|
238
|
-
// /^[\/\\]/, // Absolute paths
|
|
239
|
-
/[\x00-\x1f\x7f-\x9f]/ // Control characters
|
|
240
|
-
]
|
|
241
|
-
|
|
242
|
-
if (dangerousPatterns.some(pattern => pattern.test(_file))) {
|
|
243
|
-
throw new Error('Invalid characters or pattern in file path')
|
|
244
|
-
}
|
|
284
|
+
// Strip surrounding quotes and leading slash
|
|
285
|
+
const file = _file
|
|
286
|
+
.replace(/^['"]|['"]$/g, '')
|
|
287
|
+
.replace(/^\//, '')
|
|
245
288
|
|
|
246
|
-
//
|
|
247
|
-
if (
|
|
289
|
+
// Reject control characters
|
|
290
|
+
if (/[\x00-\x1f\x7f-\x9f]/.test(file)) {
|
|
248
291
|
throw new Error('File path contains invalid characters')
|
|
249
292
|
}
|
|
250
293
|
|
|
251
|
-
// Normalize path and remove leading slash
|
|
252
|
-
const file = _file
|
|
253
|
-
.replace(/^\//, '')
|
|
254
|
-
|
|
255
294
|
const cachedTimestamp = cache.get(file)
|
|
256
295
|
if (cachedTimestamp) return cachedTimestamp
|
|
257
296
|
|
|
@@ -263,10 +302,7 @@ async function getGitTimestamp(_file, cwd, throwOnMissing = true) {
|
|
|
263
302
|
}
|
|
264
303
|
|
|
265
304
|
try {
|
|
266
|
-
const
|
|
267
|
-
// console.log('cmd', cmd)
|
|
268
|
-
// console.log('cwd', cwd)
|
|
269
|
-
const output = await _exec(cmd, { cwd })
|
|
305
|
+
const output = await _execFile('git', ['log', '-1', '--pretty=%ai', '--', file], { cwd })
|
|
270
306
|
const date = new Date(output)
|
|
271
307
|
const dateString = date.toISOString()
|
|
272
308
|
cache.set(file, dateString)
|
|
@@ -281,8 +317,8 @@ async function getGitTimestamp(_file, cwd, throwOnMissing = true) {
|
|
|
281
317
|
}
|
|
282
318
|
|
|
283
319
|
try {
|
|
284
|
-
const backupFile = path.join(projectRoot,
|
|
285
|
-
const output = await
|
|
320
|
+
const backupFile = path.join(projectRoot, file)
|
|
321
|
+
const output = await _execFile('git', ['log', '-1', '--pretty=%ai', '--', backupFile], { cwd: projectRoot })
|
|
286
322
|
const date = new Date(output)
|
|
287
323
|
const dateString = date.toISOString()
|
|
288
324
|
cache.set(file, dateString)
|
|
@@ -296,14 +332,19 @@ async function getGitTimestamp(_file, cwd, throwOnMissing = true) {
|
|
|
296
332
|
}
|
|
297
333
|
}
|
|
298
334
|
|
|
335
|
+
const remoteCache = new Map()
|
|
336
|
+
|
|
299
337
|
async function getGitRemote(name = 'origin') {
|
|
300
|
-
|
|
338
|
+
if (remoteCache.has(name)) {
|
|
339
|
+
return remoteCache.get(name)
|
|
340
|
+
}
|
|
341
|
+
const remoteValues = await _execFile('git', ['remote', '-v'])
|
|
301
342
|
const remotes = remoteValues.toString().split(os.EOL)
|
|
302
343
|
.filter(function filterOnlyFetchRows(remote) {
|
|
303
344
|
return remote.match('(fetch)')
|
|
304
345
|
})
|
|
305
346
|
.map(function mapRemoteLineToObject(remote) {
|
|
306
|
-
|
|
347
|
+
const parts = remote.split('\t')
|
|
307
348
|
if (parts.length < 2) {
|
|
308
349
|
return
|
|
309
350
|
}
|
|
@@ -323,7 +364,6 @@ async function getGitRemote(name = 'origin') {
|
|
|
323
364
|
|
|
324
365
|
if (!originUrl) {
|
|
325
366
|
throw new Error(`No git remote "${name}" found. Please double check your remote names`)
|
|
326
|
-
return
|
|
327
367
|
}
|
|
328
368
|
// console.log('originUrl', originUrl)
|
|
329
369
|
const parsed = GitUrlParse(originUrl)
|
|
@@ -331,7 +371,9 @@ async function getGitRemote(name = 'origin') {
|
|
|
331
371
|
// @TODO finish git api
|
|
332
372
|
// console.log('parsed', parsed)
|
|
333
373
|
if (parsed && parsed.source && parsed.full_name) {
|
|
334
|
-
|
|
374
|
+
const result = `https://${parsed.source}/${parsed.full_name}`
|
|
375
|
+
remoteCache.set(name, result)
|
|
376
|
+
return result
|
|
335
377
|
}
|
|
336
378
|
}
|
|
337
379
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// Resolves numeric literal variables to their Number values
|
|
2
|
+
const { isNumber } = require('../utils/lodash')
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} variableString
|
|
6
|
+
* @returns {boolean}
|
|
7
|
+
*/
|
|
3
8
|
function isNumberVariable(variableString) {
|
|
4
9
|
if (!variableString || variableString.trim().length === 0) {
|
|
5
10
|
return false
|
|
@@ -8,6 +13,10 @@ function isNumberVariable(variableString) {
|
|
|
8
13
|
return !isNaN(num) && isNumber(num)
|
|
9
14
|
}
|
|
10
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} variableString
|
|
18
|
+
* @returns {Promise<number>}
|
|
19
|
+
*/
|
|
11
20
|
function getValueFromNumber(variableString) {
|
|
12
21
|
return Promise.resolve(Number(variableString))
|
|
13
22
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// Resolves values from CLI option flags
|
|
2
|
+
// Matches ${opt:FLAG_NAME} syntax with optional fallback values
|
|
2
3
|
const optRefSyntax = RegExp(/^opt:/g)
|
|
3
4
|
|
|
4
5
|
function getValueFromOptions(variableString, options) {
|
|
5
6
|
const requestedOption = variableString.split(':')[1]
|
|
6
|
-
|
|
7
|
-
if (requestedOption !== '' || '' in options) {
|
|
8
|
-
valueToPopulate = options[requestedOption]
|
|
9
|
-
} else {
|
|
10
|
-
valueToPopulate = options
|
|
11
|
-
}
|
|
7
|
+
const valueToPopulate = options[requestedOption]
|
|
12
8
|
return Promise.resolve(valueToPopulate)
|
|
13
9
|
}
|
|
14
10
|
|
package/src/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// This file provides TypeScript support for validating configuration variables
|
|
3
3
|
|
|
4
4
|
// Valid variable prefixes supported by configorama
|
|
5
|
-
export type KnownVariablePrefix = 'env:' | 'opt:' | 'self:' | 'file:' | 'git:' | 'cron:'
|
|
5
|
+
export type KnownVariablePrefix = 'env:' | 'opt:' | 'self:' | 'file:' | 'git:' | 'cron:' | 'param:'
|
|
6
6
|
|
|
7
7
|
// Quoted string literal type for fallback values
|
|
8
8
|
type QuotedString = `"${string}"` | `'${string}'`
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Registers graceful shutdown handlers for SIGINT/SIGTERM/SIGBREAK
|
|
1
2
|
const readline = require('readline')
|
|
2
3
|
|
|
3
4
|
function handleSignalEvents() {
|
|
@@ -29,11 +30,6 @@ Exit received. Waiting for current operation to finish...
|
|
|
29
30
|
rl.on('SIGBREAK', () => process.emit('SIGBREAK'))
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
// Remove any existing listeners before adding new ones
|
|
33
|
-
process.removeAllListeners('SIGINT')
|
|
34
|
-
process.removeAllListeners('SIGTERM')
|
|
35
|
-
process.removeAllListeners('SIGBREAK')
|
|
36
|
-
|
|
37
33
|
process.on('SIGINT', () => {
|
|
38
34
|
global.signalEventHandling.SIGINTCount += 1
|
|
39
35
|
global.signalEventHandling.shouldExitGracefully = true
|
package/src/utils/lodash.js
CHANGED
|
@@ -1,70 +1,124 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
1
|
+
// Native replacements for lodash utilities used across the codebase
|
|
2
|
+
const isArray = Array.isArray
|
|
3
|
+
const isString = (val) => typeof val === 'string'
|
|
4
|
+
const isNumber = (val) => typeof val === 'number' && !isNaN(val)
|
|
5
|
+
const isObject = (val) => val != null && typeof val === 'object'
|
|
6
|
+
const isDate = (val) => val instanceof Date
|
|
7
|
+
const isRegExp = (val) => val instanceof RegExp
|
|
8
|
+
const isFunction = (val) => typeof val === 'function'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {*} val
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
function isEmpty(val) {
|
|
15
|
+
if (val == null) return true
|
|
16
|
+
if (isArray(val) || isString(val)) return val.length === 0
|
|
17
|
+
if (val instanceof Map || val instanceof Set) return val.size === 0
|
|
18
|
+
if (isObject(val)) return Object.keys(val).length === 0
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Non-trivial utilities kept as dependencies
|
|
9
23
|
const camelCase = require('lodash.camelcase')
|
|
10
24
|
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
25
|
const cloneDeep = require('lodash.clonedeep')
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} str
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function capitalize(str) {
|
|
32
|
+
if (!str) return ''
|
|
33
|
+
const s = String(str)
|
|
34
|
+
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {*[]} arr
|
|
39
|
+
* @param {Function} fn
|
|
40
|
+
* @returns {*[]}
|
|
41
|
+
*/
|
|
42
|
+
function map(arr, fn) {
|
|
43
|
+
if (arr == null) return []
|
|
44
|
+
return Array.prototype.map.call(arr, fn)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {Object} obj
|
|
49
|
+
* @param {Function} fn
|
|
50
|
+
* @returns {Object}
|
|
51
|
+
*/
|
|
52
|
+
function mapValues(obj, fn) {
|
|
53
|
+
if (obj == null) return {}
|
|
54
|
+
const result = {}
|
|
55
|
+
const keys = Object.keys(obj)
|
|
56
|
+
for (let i = 0; i < keys.length; i++) {
|
|
57
|
+
result[keys[i]] = fn(obj[keys[i]], keys[i], obj)
|
|
58
|
+
}
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {Object} object - Target object
|
|
64
|
+
* @param {string|string[]} path - Dot-delimited path or array of keys
|
|
65
|
+
* @param {*} value - Value to set
|
|
66
|
+
* @returns {Object} The mutated object
|
|
67
|
+
*/
|
|
19
68
|
function set(object, path, value) {
|
|
20
69
|
if (object === null || typeof object !== 'object') {
|
|
21
|
-
return object
|
|
70
|
+
return object
|
|
22
71
|
}
|
|
23
|
-
|
|
72
|
+
|
|
24
73
|
const keys = Array.isArray(path) ? path : String(path)
|
|
25
74
|
.split('.')
|
|
26
75
|
.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
|
-
|
|
76
|
+
const numKey = Number(key)
|
|
77
|
+
return Number.isInteger(numKey) && numKey >= 0 ? numKey : key
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
let current = object
|
|
81
|
+
const lastIndex = keys.length - 1
|
|
82
|
+
|
|
34
83
|
for (let i = 0; i < lastIndex; i++) {
|
|
35
84
|
const key = keys[i]
|
|
36
|
-
|
|
85
|
+
|
|
37
86
|
// Check if value is undefined, null, or not an object (primitives can't have properties)
|
|
38
87
|
if (current[key] == null || typeof current[key] !== 'object') {
|
|
39
88
|
// Create appropriate container based on next key type
|
|
40
|
-
|
|
89
|
+
const nextKey = keys[i + 1]
|
|
90
|
+
current[key] = Number.isInteger(nextKey) && /** @type {number} */ (nextKey) >= 0 ? [] : {}
|
|
41
91
|
}
|
|
42
|
-
|
|
92
|
+
|
|
43
93
|
current = current[key]
|
|
44
94
|
}
|
|
45
|
-
|
|
46
|
-
current[keys[lastIndex]] = value
|
|
47
|
-
return object
|
|
95
|
+
|
|
96
|
+
current[keys[lastIndex]] = value
|
|
97
|
+
return object
|
|
48
98
|
}
|
|
49
99
|
|
|
50
100
|
// Cache for trim regex patterns (perf: avoid recompilation)
|
|
51
101
|
const trimRegexCache = new Map()
|
|
52
102
|
|
|
53
|
-
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} string - String to trim
|
|
105
|
+
* @param {string} [chars] - Characters to trim (defaults to whitespace)
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
54
108
|
function trim(string, chars) {
|
|
55
109
|
if (string === null || string === undefined) {
|
|
56
|
-
return ''
|
|
110
|
+
return ''
|
|
57
111
|
}
|
|
58
112
|
|
|
59
|
-
string = String(string)
|
|
113
|
+
string = String(string)
|
|
60
114
|
|
|
61
115
|
if (!chars && String.prototype.trim) {
|
|
62
|
-
return string.trim()
|
|
116
|
+
return string.trim()
|
|
63
117
|
}
|
|
64
118
|
|
|
65
119
|
if (!chars) {
|
|
66
120
|
// Default characters to trim (whitespace)
|
|
67
|
-
chars = ' \t\n\r\f\v\u00a0\u1680\u2000\u200a\u2028\u2029\u202f\u205f\u3000\ufeff'
|
|
121
|
+
chars = ' \t\n\r\f\v\u00a0\u1680\u2000\u200a\u2028\u2029\u202f\u205f\u3000\ufeff'
|
|
68
122
|
}
|
|
69
123
|
|
|
70
124
|
// Check cache first
|
|
@@ -78,7 +132,7 @@ function trim(string, chars) {
|
|
|
78
132
|
|
|
79
133
|
// Reset lastIndex for global regex reuse
|
|
80
134
|
pattern.lastIndex = 0
|
|
81
|
-
return string.replace(pattern, '')
|
|
135
|
+
return string.replace(pattern, '')
|
|
82
136
|
}
|
|
83
137
|
|
|
84
138
|
module.exports = {
|
|
@@ -94,10 +148,10 @@ module.exports = {
|
|
|
94
148
|
camelCase,
|
|
95
149
|
kebabCase,
|
|
96
150
|
capitalize,
|
|
97
|
-
split,
|
|
151
|
+
split: (str, sep) => String(str).split(sep),
|
|
98
152
|
map,
|
|
99
153
|
mapValues,
|
|
100
|
-
assign,
|
|
154
|
+
assign: Object.assign,
|
|
101
155
|
set,
|
|
102
156
|
cloneDeep,
|
|
103
157
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
const YAML = require('js-yaml');
|
|
2
|
-
const
|
|
3
|
-
const isString = require('lodash.isstring');
|
|
4
|
-
const flatten = require('lodash.flatten');
|
|
5
|
-
const map = require('lodash.map');
|
|
2
|
+
const { isString } = require('../lodash')
|
|
6
3
|
|
|
7
4
|
const functionNames = [
|
|
8
5
|
// Standard intrinsic functions
|
|
@@ -36,7 +33,7 @@ const functionNames = [
|
|
|
36
33
|
];
|
|
37
34
|
|
|
38
35
|
const yamlType = (name, kind) => {
|
|
39
|
-
const functionName =
|
|
36
|
+
const functionName = ['Ref', 'Condition'].includes(name) ? name : `Fn::${name}`;
|
|
40
37
|
return new YAML.Type(`!${name}`, {
|
|
41
38
|
kind,
|
|
42
39
|
construct: data => {
|
|
@@ -61,11 +58,9 @@ const yamlType = (name, kind) => {
|
|
|
61
58
|
};
|
|
62
59
|
|
|
63
60
|
const createSchema = () => {
|
|
64
|
-
const types =
|
|
65
|
-
map(
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
);
|
|
61
|
+
const types = functionNames.flatMap(functionName =>
|
|
62
|
+
['mapping', 'scalar', 'sequence'].map(kind => yamlType(functionName, kind))
|
|
63
|
+
)
|
|
69
64
|
return YAML.Schema.create(YAML.DEFAULT_SAFE_SCHEMA, types);
|
|
70
65
|
};
|
|
71
66
|
|