configorama 0.11.0 → 1.0.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.
Files changed (78) hide show
  1. package/README.md +429 -123
  2. package/cli.js +282 -49
  3. package/index.d.ts +43 -1
  4. package/package.json +5 -1
  5. package/src/capabilities.js +59 -0
  6. package/src/capabilities.test.js +44 -0
  7. package/src/display.js +70 -7
  8. package/src/display.test.js +82 -0
  9. package/src/errors.js +73 -0
  10. package/src/index.js +91 -1
  11. package/src/main.js +159 -19
  12. package/src/parsers/esm.js +1 -16
  13. package/src/parsers/typescript.js +1 -48
  14. package/src/resolvers/valueFromCron.js +4 -25
  15. package/src/resolvers/valueFromEval.js +11 -1
  16. package/src/resolvers/valueFromFile.js +8 -1
  17. package/src/resolvers/valueFromGit.js +43 -17
  18. package/src/resolvers/valueFromOptions.js +5 -4
  19. package/src/utils/filters/filterArgs.js +57 -0
  20. package/src/utils/filters/oneOf.js +77 -0
  21. package/src/utils/introspection/audit.js +78 -0
  22. package/src/utils/introspection/graph.js +43 -0
  23. package/src/utils/introspection/model.js +150 -0
  24. package/src/utils/introspection/model.test.js +93 -0
  25. package/src/utils/parsing/commentAnnotations.js +107 -0
  26. package/src/utils/parsing/commentAnnotations.test.js +123 -0
  27. package/src/utils/parsing/enrichMetadata.js +64 -1
  28. package/src/utils/parsing/enrichMetadata.test.js +84 -0
  29. package/src/utils/parsing/extractComment.js +145 -0
  30. package/src/utils/parsing/extractComment.test.js +182 -0
  31. package/src/utils/parsing/preProcess.js +2 -1
  32. package/src/utils/paths/findLineForKey.js +2 -2
  33. package/src/utils/paths/ignorePaths.js +22 -9
  34. package/src/utils/redaction/redact.js +78 -0
  35. package/src/utils/redaction/redact.test.js +38 -0
  36. package/src/utils/redaction/setupRedaction.js +47 -0
  37. package/src/utils/redaction/setupRedaction.test.js +68 -0
  38. package/src/utils/requirements/configRequirements.js +351 -0
  39. package/src/utils/requirements/configRequirements.test.js +380 -0
  40. package/src/utils/requirements/serializeRequirements.js +120 -0
  41. package/src/utils/requirements/serializeRequirements.test.js +211 -0
  42. package/src/utils/security/evalSafety.js +86 -0
  43. package/src/utils/security/evalSafety.test.js +61 -0
  44. package/src/utils/security/safetyPolicy.js +110 -0
  45. package/src/utils/security/safetyPolicy.test.js +29 -0
  46. package/src/utils/strings/didYouMean.js +70 -0
  47. package/src/utils/strings/didYouMean.test.js +52 -0
  48. package/src/utils/strings/formatFunctionArgs.js +6 -1
  49. package/src/utils/strings/splitByComma.js +5 -0
  50. package/src/utils/ui/configWizard.js +208 -34
  51. package/src/utils/ui/createEditorLink.js +17 -1
  52. package/src/utils/ui/promptDescriptors.js +196 -0
  53. package/src/utils/ui/promptDescriptors.test.js +162 -0
  54. package/src/utils/variables/cleanVariable.js +22 -0
  55. package/src/utils/variables/getVariableType.js +1 -0
  56. package/types/src/index.d.ts +0 -24
  57. package/types/src/index.d.ts.map +1 -1
  58. package/types/src/main.d.ts +16 -8
  59. package/types/src/main.d.ts.map +1 -1
  60. package/types/src/resolvers/valueFromFile.d.ts +0 -2
  61. package/types/src/resolvers/valueFromFile.d.ts.map +1 -1
  62. package/types/src/resolvers/valueFromGit.d.ts.map +1 -1
  63. package/types/src/resolvers/valueFromSelf.d.ts +1 -0
  64. package/types/src/resolvers/valueFromSelf.d.ts.map +1 -0
  65. package/types/src/utils/parsing/parse.d.ts.map +1 -1
  66. package/types/src/utils/parsing/preProcess.d.ts.map +1 -1
  67. package/types/src/utils/paths/findLineForKey.d.ts +0 -9
  68. package/types/src/utils/paths/findLineForKey.d.ts.map +1 -1
  69. package/types/src/utils/strings/replaceAll.d.ts.map +1 -1
  70. package/types/src/utils/variables/variableUtils.d.ts +1 -1
  71. package/types/src/display.d.ts +0 -62
  72. package/types/src/display.d.ts.map +0 -1
  73. package/types/src/metadata.d.ts +0 -28
  74. package/types/src/metadata.d.ts.map +0 -1
  75. package/types/src/utils/BoundedMap.d.ts +0 -10
  76. package/types/src/utils/BoundedMap.d.ts.map +0 -1
  77. package/types/src/utils/paths/ignorePaths.d.ts +0 -5
  78. package/types/src/utils/paths/ignorePaths.d.ts.map +0 -1
@@ -0,0 +1,351 @@
1
+ const { isSensitiveVariable } = require('../redaction/redact')
2
+
3
+ const TYPE_MAP = {
4
+ Boolean: 'boolean',
5
+ String: 'string',
6
+ Number: 'number',
7
+ Array: 'array',
8
+ Object: 'object',
9
+ Json: 'json',
10
+ }
11
+
12
+ const VARIABLE_TYPE_MAP = {
13
+ options: 'option',
14
+ opt: 'option',
15
+ option: 'option',
16
+ 'dot.prop': 'dotProp',
17
+ dotProp: 'dotProp',
18
+ if: 'eval',
19
+ }
20
+
21
+ function normalizeVariableType(variableType) {
22
+ return VARIABLE_TYPE_MAP[variableType] || variableType || 'unknown'
23
+ }
24
+
25
+ function normalizeType(type) {
26
+ if (!type) return 'string'
27
+ return TYPE_MAP[type] || String(type).toLowerCase()
28
+ }
29
+
30
+ function cleanDefaultValue(value) {
31
+ if (value === undefined || value === null) return null
32
+ if (typeof value !== 'string') return value
33
+ const match = value.match(/^(['"])([\s\S]*)\1$/)
34
+ return match ? match[2] : value
35
+ }
36
+
37
+ function unique(values) {
38
+ return [...new Set(values.filter(value => value !== undefined && value !== null))]
39
+ }
40
+
41
+ function uniqueBy(values, getKey) {
42
+ const seen = new Set()
43
+ const result = []
44
+ for (const value of values) {
45
+ const key = getKey(value)
46
+ if (seen.has(key)) continue
47
+ seen.add(key)
48
+ result.push(value)
49
+ }
50
+ return result
51
+ }
52
+
53
+ function firstValue(values) {
54
+ return values.find(value => value !== undefined && value !== null)
55
+ }
56
+
57
+ function getRequirementName(variable, variableType) {
58
+ const normalizedType = normalizeVariableType(variableType)
59
+ if (!variable) return ''
60
+
61
+ if (normalizedType === 'option') return variable.replace(/^(opt|option|options):/, '')
62
+ if (normalizedType === 'env') return variable.replace(/^env:/, '')
63
+ if (normalizedType === 'param') return variable.replace(/^param:/, '')
64
+ if (normalizedType === 'self') return variable.replace(/^self:/, '')
65
+ if (normalizedType === 'file' || normalizedType === 'text') {
66
+ const match = variable.match(/^(?:file|text)\((.+?)\)/)
67
+ return match ? match[1] : variable
68
+ }
69
+ return variable
70
+ }
71
+
72
+ function getOccurrenceTypes(occurrences) {
73
+ return unique((occurrences || []).map(occ => occ.type))
74
+ }
75
+
76
+ function getOccurrenceTypeEntries(occurrences) {
77
+ return (occurrences || [])
78
+ .filter(occ => occ.type)
79
+ .map(occ => ({
80
+ value: normalizeType(occ.type),
81
+ path: occ.path,
82
+ }))
83
+ }
84
+
85
+ function getDescriptionCandidates(entry, occurrences) {
86
+ const candidates = []
87
+ if (entry.description) {
88
+ candidates.push({
89
+ value: entry.description,
90
+ source: entry.descriptionSource || 'help',
91
+ path: null,
92
+ index: candidates.length,
93
+ })
94
+ }
95
+ for (const occ of occurrences || []) {
96
+ if (!occ.description) continue
97
+ candidates.push({
98
+ value: occ.description,
99
+ source: occ.descriptionSource || 'help',
100
+ path: occ.path,
101
+ index: candidates.length,
102
+ })
103
+ }
104
+ if (entry.descriptions && entry.descriptions.length) {
105
+ const occurrenceDescriptions = new Set(candidates.map(candidate => candidate.value))
106
+ for (const description of entry.descriptions) {
107
+ if (occurrenceDescriptions.has(description)) continue
108
+ candidates.push({
109
+ value: description,
110
+ source: 'help',
111
+ path: null,
112
+ index: candidates.length,
113
+ })
114
+ }
115
+ }
116
+ return uniqueBy(candidates, candidate => `${candidate.source}:${candidate.value}:${candidate.path || ''}`)
117
+ }
118
+
119
+ function getDescriptionPriority(source) {
120
+ if (source === 'commentTag') return -1
121
+ if (source === 'help') return 0
122
+ if (source === 'inlineComment' || source === 'comment') return 1
123
+ if (source === 'leadingComment') return 2
124
+ return 3
125
+ }
126
+
127
+ function selectDescription(candidates) {
128
+ const sorted = [...candidates].sort((a, b) => {
129
+ const priority = getDescriptionPriority(a.source) - getDescriptionPriority(b.source)
130
+ if (priority !== 0) return priority
131
+ return a.index - b.index
132
+ })
133
+ return sorted[0] || null
134
+ }
135
+
136
+ function getOccurrenceDefaults(entry, occurrences) {
137
+ const defaults = (occurrences || []).map(occ => occ.defaultValue)
138
+ if (entry.resolvedValue !== undefined) defaults.push(entry.resolvedValue)
139
+ return defaults
140
+ }
141
+
142
+ function getOccurrenceDefaultEntries(entry, occurrences) {
143
+ const entries = (occurrences || [])
144
+ .filter(occ => occ.defaultValue !== undefined && occ.defaultValue !== null)
145
+ .map(occ => ({
146
+ value: cleanDefaultValue(occ.defaultValue),
147
+ path: occ.path,
148
+ }))
149
+ if (entry.resolvedValue !== undefined && entry.resolvedValue !== null) {
150
+ entries.push({
151
+ value: cleanDefaultValue(entry.resolvedValue),
152
+ path: null,
153
+ })
154
+ }
155
+ return entries
156
+ }
157
+
158
+ function getAllowedValues(entry, occurrences) {
159
+ if (entry.allowedValues) return entry.allowedValues
160
+ const values = (occurrences || []).flatMap(occ => occ.allowedValues || [])
161
+ return values.length ? unique(values).map(String) : null
162
+ }
163
+
164
+ function collectFieldEntries(entry, occurrences, field) {
165
+ const entries = []
166
+ if (entry[field] !== undefined && entry[field] !== null) {
167
+ entries.push({
168
+ value: entry[field],
169
+ path: null,
170
+ })
171
+ }
172
+ for (const occ of occurrences || []) {
173
+ if (occ[field] === undefined || occ[field] === null) continue
174
+ entries.push({
175
+ value: occ[field],
176
+ path: occ.path,
177
+ })
178
+ }
179
+ return entries
180
+ }
181
+
182
+ function selectFieldValue(entries) {
183
+ const entry = (entries || []).find(item => item.value !== undefined && item.value !== null)
184
+ return entry ? entry.value : null
185
+ }
186
+
187
+ function mergeExamples(entry, occurrences) {
188
+ const values = []
189
+ if (entry.examples) values.push(...entry.examples)
190
+ for (const occ of occurrences || []) {
191
+ if (occ.examples) values.push(...occ.examples)
192
+ }
193
+ const merged = unique(values.map(String))
194
+ return merged.length ? merged : null
195
+ }
196
+
197
+ function collectScalarAnnotationConflicts(fieldEntriesByField) {
198
+ const conflicts = []
199
+ for (const [field, entries] of Object.entries(fieldEntriesByField)) {
200
+ const uniqueEntries = uniqueBy(entries, entry => String(entry.value))
201
+ if (uniqueEntries.length > 1) {
202
+ conflicts.push(makeConflict(field, uniqueEntries))
203
+ }
204
+ }
205
+ return conflicts
206
+ }
207
+
208
+ function normalizeAllowedSet(values) {
209
+ return (values || []).map(String).sort()
210
+ }
211
+
212
+ function allowedSetKey(values) {
213
+ return JSON.stringify(normalizeAllowedSet(values))
214
+ }
215
+
216
+ function collectAllowedSets(entry, occurrences) {
217
+ const sets = []
218
+ if (entry.allowedValues) {
219
+ sets.push({ values: entry.allowedValues, path: null })
220
+ }
221
+ for (const occ of occurrences || []) {
222
+ if (occ.allowedValues) {
223
+ sets.push({ values: occ.allowedValues, path: occ.path })
224
+ }
225
+ }
226
+ return sets
227
+ }
228
+
229
+ function getSourceClass(entry) {
230
+ return entry.variableSourceType || entry.sourceClass || null
231
+ }
232
+
233
+ function makeConflict(field, values) {
234
+ return {
235
+ field,
236
+ values,
237
+ paths: unique(values.flatMap(value => value.paths || (value.path ? [value.path] : []))),
238
+ }
239
+ }
240
+
241
+ function collectConflicts({ typeEntries, defaultEntries, allowedSets, scalarAnnotationEntries = {} }) {
242
+ const conflicts = []
243
+
244
+ const typedValues = uniqueBy(typeEntries, entry => entry.value)
245
+ if (typedValues.length > 1) {
246
+ conflicts.push(makeConflict('type', typedValues))
247
+ }
248
+
249
+ const defaultValues = uniqueBy(defaultEntries.filter(entry => entry.value !== null), entry => String(entry.value))
250
+ if (defaultValues.length > 1) {
251
+ conflicts.push(makeConflict('default', defaultValues))
252
+ }
253
+
254
+ const uniqueAllowedSets = uniqueBy(allowedSets, set => allowedSetKey(set.values))
255
+ if (uniqueAllowedSets.length > 1) {
256
+ conflicts.push(makeConflict('allowedValues', uniqueAllowedSets.map(set => ({
257
+ value: normalizeAllowedSet(set.values),
258
+ path: set.path,
259
+ }))))
260
+ }
261
+
262
+ conflicts.push(...collectScalarAnnotationConflicts(scalarAnnotationEntries))
263
+
264
+ return conflicts
265
+ }
266
+
267
+ function getSensitiveValue(name, sensitiveEntries) {
268
+ return isSensitiveVariable(name, { sensitiveEntries })
269
+ }
270
+
271
+ function buildRequirement(varKey, entry) {
272
+ const occurrences = entry.occurrences || []
273
+ const variableType = normalizeVariableType(entry.variableType)
274
+ const name = getRequirementName(entry.variable || varKey, entry.variableType)
275
+ const types = getOccurrenceTypes(occurrences)
276
+ const typeEntries = getOccurrenceTypeEntries(occurrences)
277
+ const descriptionCandidates = getDescriptionCandidates(entry, occurrences)
278
+ const selectedDescription = selectDescription(descriptionCandidates)
279
+ const defaults = getOccurrenceDefaults(entry, occurrences)
280
+ const defaultEntries = getOccurrenceDefaultEntries(entry, occurrences)
281
+ const defaultValue = cleanDefaultValue(firstValue(defaults))
282
+ const allowedSets = collectAllowedSets(entry, occurrences)
283
+ const obtainHintEntries = collectFieldEntries(entry, occurrences, 'obtainHint')
284
+ const defaultHintEntries = collectFieldEntries(entry, occurrences, 'defaultHint')
285
+ const groupEntries = collectFieldEntries(entry, occurrences, 'group')
286
+ const deprecationEntries = collectFieldEntries(entry, occurrences, 'deprecationMessage')
287
+ const sensitiveEntries = collectFieldEntries(entry, occurrences, 'sensitive')
288
+ const conflicts = collectConflicts({
289
+ typeEntries,
290
+ defaultEntries,
291
+ allowedSets,
292
+ scalarAnnotationEntries: {
293
+ obtainHint: obtainHintEntries,
294
+ defaultHint: defaultHintEntries,
295
+ group: groupEntries,
296
+ deprecationMessage: deprecationEntries,
297
+ sensitive: sensitiveEntries,
298
+ },
299
+ })
300
+ const obtainHint = selectFieldValue(obtainHintEntries)
301
+ const defaultHint = selectFieldValue(defaultHintEntries)
302
+ const group = selectFieldValue(groupEntries)
303
+ const deprecationMessage = selectFieldValue(deprecationEntries)
304
+ const sensitive = getSensitiveValue(name, sensitiveEntries)
305
+ const sensitiveSource = sensitiveEntries.length ? 'commentTag' : null
306
+
307
+ const paths = unique(occurrences.map(occ => occ.path))
308
+ const required = occurrences.some(occ => occ.isRequired === true) && defaultValue === null
309
+ const description = selectedDescription ? selectedDescription.value : null
310
+
311
+ return {
312
+ name,
313
+ variable: entry.variable || varKey,
314
+ variableType,
315
+ sourceClass: getSourceClass(entry),
316
+ type: normalizeType(firstValue(types)),
317
+ description,
318
+ descriptionSource: selectedDescription ? selectedDescription.source : null,
319
+ allowedValues: getAllowedValues(entry, occurrences),
320
+ sensitive,
321
+ sensitiveSource,
322
+ required,
323
+ default: defaultValue,
324
+ defaultHint,
325
+ obtainHint,
326
+ examples: mergeExamples(entry, occurrences),
327
+ group,
328
+ deprecationMessage,
329
+ fileExists: entry.fileExists,
330
+ innerVariables: entry.innerVariables || [],
331
+ paths,
332
+ conflicts,
333
+ occurrences,
334
+ }
335
+ }
336
+
337
+ function buildConfigRequirements(analysis) {
338
+ const uniqueVariables = analysis && analysis.uniqueVariables ? analysis.uniqueVariables : {}
339
+ return Object.entries(uniqueVariables).map(([varKey, entry]) => {
340
+ return buildRequirement(varKey, entry)
341
+ })
342
+ }
343
+
344
+ module.exports = {
345
+ buildConfigRequirements,
346
+ buildRequirement,
347
+ cleanDefaultValue,
348
+ collectConflicts,
349
+ normalizeType,
350
+ normalizeVariableType,
351
+ }