configorama 0.6.4 → 0.6.6
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 +211 -12
- package/cli.js +10 -3
- package/index.d.ts +45 -0
- package/package.json +12 -3
- package/src/index.js +9 -5
- package/src/main.js +387 -96
- package/src/parsers/esm.js +69 -0
- package/src/parsers/index.js +3 -1
- package/src/parsers/ini.js +51 -0
- package/src/parsers/ini.test.js +133 -0
- package/src/parsers/json5.js +1 -0
- package/src/parsers/typescript.js +154 -0
- package/src/parsers/yaml.test.js +1 -1
- package/src/resolvers/valueFromCron.js +252 -0
- package/src/resolvers/valueFromCron.test.js +132 -0
- package/src/resolvers/valueFromEval.js +37 -0
- package/src/resolvers/valueFromEval.test.js +44 -0
- package/src/resolvers/valueFromGit.js +6 -4
- package/src/types.d.ts +112 -0
- package/src/utils/cleanVariable.js +67 -3
- package/src/utils/createEditorLink.js +23 -0
- package/src/utils/find-nested-variables.js +10 -1
- package/src/utils/logs.js +2 -1
- package/src/utils/parse.js +40 -0
- package/src/utils/resolveAlias.js +152 -0
- package/src/utils/resolveAlias.test.js +98 -0
- package/src/utils/resolveAliasOld.js +65 -0
- package/src/utils/textUtils.js +2 -2
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const { test } = require('uvu')
|
|
2
|
+
const assert = require('uvu/assert')
|
|
3
|
+
const { _parseCronExpression } = require('./valueFromCron')
|
|
4
|
+
|
|
5
|
+
test('parseCronExpression: basic patterns', () => {
|
|
6
|
+
assert.equal(_parseCronExpression('every minute'), '* * * * *')
|
|
7
|
+
assert.equal(_parseCronExpression('every hour'), '0 * * * *')
|
|
8
|
+
assert.equal(_parseCronExpression('every day'), '0 0 * * *')
|
|
9
|
+
assert.equal(_parseCronExpression('daily'), '0 0 * * *')
|
|
10
|
+
assert.equal(_parseCronExpression('hourly'), '0 * * * *')
|
|
11
|
+
assert.equal(_parseCronExpression('yearly'), '0 0 1 1 *')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('parseCronExpression: business patterns', () => {
|
|
15
|
+
assert.equal(_parseCronExpression('weekdays'), '0 0 * * 1-5')
|
|
16
|
+
assert.equal(_parseCronExpression('weekends'), '0 0 * * 0,6')
|
|
17
|
+
assert.equal(_parseCronExpression('business hours'), '0 9-17 * * 1-5')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('parseCronExpression: interval patterns', () => {
|
|
21
|
+
assert.equal(_parseCronExpression('every 5 minutes'), '*/5 * * * *')
|
|
22
|
+
assert.equal(_parseCronExpression('every 15 minutes'), '*/15 * * * *')
|
|
23
|
+
assert.equal(_parseCronExpression('every 2 hours'), '0 */2 * * *')
|
|
24
|
+
assert.equal(_parseCronExpression('every 3 days'), '0 0 */3 * *')
|
|
25
|
+
assert.equal(_parseCronExpression('every 2 weeks'), '0 0 * * 0/2')
|
|
26
|
+
assert.equal(_parseCronExpression('every 6 months'), '0 0 1 */6 *')
|
|
27
|
+
|
|
28
|
+
// Test singular/plural forms
|
|
29
|
+
assert.equal(_parseCronExpression('every 1 minute'), '*/1 * * * *')
|
|
30
|
+
assert.equal(_parseCronExpression('every 1 hour'), '0 */1 * * *')
|
|
31
|
+
assert.equal(_parseCronExpression('every 1 day'), '0 0 */1 * *')
|
|
32
|
+
assert.equal(_parseCronExpression('every 1 week'), '0 0 * * 0/1')
|
|
33
|
+
assert.equal(_parseCronExpression('every 1 month'), '0 0 1 */1 *')
|
|
34
|
+
|
|
35
|
+
// Test plural forms
|
|
36
|
+
assert.equal(_parseCronExpression('every 5 minutes'), '*/5 * * * *')
|
|
37
|
+
assert.equal(_parseCronExpression('every 2 hours'), '0 */2 * * *')
|
|
38
|
+
assert.equal(_parseCronExpression('every 3 days'), '0 0 */3 * *')
|
|
39
|
+
assert.equal(_parseCronExpression('every 2 weeks'), '0 0 * * 0/2')
|
|
40
|
+
assert.equal(_parseCronExpression('every 6 months'), '0 0 1 */6 *')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('parseCronExpression: simple interval patterns', () => {
|
|
44
|
+
// Test singular forms
|
|
45
|
+
assert.equal(_parseCronExpression('1 minute'), '*/1 * * * *')
|
|
46
|
+
assert.equal(_parseCronExpression('1 hour'), '0 */1 * * *')
|
|
47
|
+
assert.equal(_parseCronExpression('1 day'), '0 0 */1 * *')
|
|
48
|
+
assert.equal(_parseCronExpression('1 week'), '0 0 * * 0/1')
|
|
49
|
+
assert.equal(_parseCronExpression('1 month'), '0 0 1 */1 *')
|
|
50
|
+
|
|
51
|
+
// Test plural forms
|
|
52
|
+
assert.equal(_parseCronExpression('5 minutes'), '*/5 * * * *')
|
|
53
|
+
assert.equal(_parseCronExpression('2 hours'), '0 */2 * * *')
|
|
54
|
+
assert.equal(_parseCronExpression('3 days'), '0 0 */3 * *')
|
|
55
|
+
assert.equal(_parseCronExpression('2 weeks'), '0 0 * * 0/2')
|
|
56
|
+
assert.equal(_parseCronExpression('6 months'), '0 0 1 */6 *')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('parseCronExpression: specific times', () => {
|
|
60
|
+
assert.equal(_parseCronExpression('at 9:30'), '30 9 * * *')
|
|
61
|
+
assert.equal(_parseCronExpression('at 14:15'), '15 14 * * *')
|
|
62
|
+
assert.equal(_parseCronExpression('at 9:30 am'), '30 9 * * *')
|
|
63
|
+
assert.equal(_parseCronExpression('at 9:30 pm'), '30 21 * * *')
|
|
64
|
+
assert.equal(_parseCronExpression('at 12:30 am'), '30 0 * * *')
|
|
65
|
+
assert.equal(_parseCronExpression('at 12:30 pm'), '30 12 * * *')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('parseCronExpression: weekday + time patterns', () => {
|
|
69
|
+
assert.equal(_parseCronExpression('on monday at 9:00'), '0 9 * * 1', 'monday')
|
|
70
|
+
assert.equal(_parseCronExpression('on friday at 17:30'), '30 17 * * 5', 'friday')
|
|
71
|
+
assert.equal(_parseCronExpression('on sunday at 12:00'), '0 12 * * 0', 'sunday')
|
|
72
|
+
assert.equal(_parseCronExpression('on wednesday at 9:30 pm'), '30 21 * * 3', 'wednesday')
|
|
73
|
+
assert.equal(_parseCronExpression('on saturday,sunday at 12:00'), '0 12 * * 6,0', 'saturday,sunday')
|
|
74
|
+
const mwf = _parseCronExpression('on monday,wednesday,friday at 9:00')
|
|
75
|
+
console.log('mwf', mwf)
|
|
76
|
+
assert.equal(mwf, '0 9 * * 1,3,5', 'monday,wednesday,friday')
|
|
77
|
+
const tth = _parseCronExpression('on tuesday,thursday at 2:30 pm')
|
|
78
|
+
assert.equal(tth, '30 14 * * 2,4', 'tuesday,thursday')
|
|
79
|
+
const sst = _parseCronExpression('on saturday,sunday at 12:00')
|
|
80
|
+
assert.equal(sst, '0 12 * * 6,0', 'saturday,sunday')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('parseCronExpression: ordinal dates of month', () => {
|
|
84
|
+
assert.equal(_parseCronExpression('on 1st of month at 00:00'), '0 0 1 * *')
|
|
85
|
+
assert.equal(_parseCronExpression('on 15th of month at 9:30 am'), '30 9 15 * *')
|
|
86
|
+
assert.equal(_parseCronExpression('on 31st of month at 2:00 pm'), '0 14 31 * *')
|
|
87
|
+
assert.equal(_parseCronExpression('on 2nd of month at 12:00'), '0 12 2 * *')
|
|
88
|
+
assert.equal(_parseCronExpression('on 3rd of month at 15:30'), '30 15 3 * *')
|
|
89
|
+
assert.equal(_parseCronExpression('on 4th of month at 12:00 am'), '0 0 4 * *')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('parseCronExpression: case insensitive', () => {
|
|
93
|
+
assert.equal(_parseCronExpression('EVERY MINUTE'), '* * * * *')
|
|
94
|
+
assert.equal(_parseCronExpression('Weekdays'), '0 0 * * 1-5')
|
|
95
|
+
assert.equal(_parseCronExpression('At 9:30 PM'), '30 21 * * *')
|
|
96
|
+
assert.equal(_parseCronExpression('ON MONDAY AT 9:00'), '0 9 * * 1')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('parseCronExpression: existing cron expressions pass through', () => {
|
|
100
|
+
assert.equal(_parseCronExpression('0 12 * * *'), '0 12 * * *')
|
|
101
|
+
assert.equal(_parseCronExpression('*/5 * * * *'), '*/5 * * * *')
|
|
102
|
+
//assert.equal(_parseCronExpression('@reboot'), '@reboot')
|
|
103
|
+
assert.equal(_parseCronExpression('15 2,14 * * *'), '15 2,14 * * *')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('parseCronExpression: days of week', () => {
|
|
107
|
+
assert.equal(_parseCronExpression('monday'), '0 0 * * 1')
|
|
108
|
+
assert.equal(_parseCronExpression('tuesday'), '0 0 * * 2')
|
|
109
|
+
assert.equal(_parseCronExpression('wednesday'), '0 0 * * 3')
|
|
110
|
+
assert.equal(_parseCronExpression('thursday'), '0 0 * * 4')
|
|
111
|
+
assert.equal(_parseCronExpression('friday'), '0 0 * * 5')
|
|
112
|
+
assert.equal(_parseCronExpression('saturday'), '0 0 * * 6')
|
|
113
|
+
assert.equal(_parseCronExpression('sunday'), '0 0 * * 0')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('parseCronExpression: special patterns', () => {
|
|
117
|
+
assert.equal(_parseCronExpression('first day of month'), '0 0 1 * *')
|
|
118
|
+
assert.equal(_parseCronExpression('middle of month'), '0 0 15 * *')
|
|
119
|
+
assert.equal(_parseCronExpression('never'), '0 0 30 2 *')
|
|
120
|
+
assert.equal(_parseCronExpression('reboot'), '@reboot')
|
|
121
|
+
assert.equal(_parseCronExpression('startup'), '@reboot')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('parseCronExpression: error handling', () => {
|
|
125
|
+
assert.throws(() => _parseCronExpression(''), /must be a non-empty string/)
|
|
126
|
+
assert.throws(() => _parseCronExpression(null), /must be a non-empty string/)
|
|
127
|
+
assert.throws(() => _parseCronExpression(123), /must be a non-empty string/)
|
|
128
|
+
assert.throws(() => _parseCronExpression('invalid pattern'), /Unrecognized cron pattern/)
|
|
129
|
+
assert.throws(() => _parseCronExpression('every xyz'), /Unrecognized cron pattern/)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test.run()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// const evalRefSyntax = RegExp(/^eval\((~?[\{\}\:\${}a-zA=>+!-Z0-9._\-\/,'"\*\` ]+?)?\)/g)
|
|
2
|
+
const evalRefSyntax = RegExp(/^eval\((.*)?\)/g)
|
|
3
|
+
|
|
4
|
+
async function getValueFromEval(variableString) {
|
|
5
|
+
// console.log('getValueFromEval variableString', variableString)
|
|
6
|
+
// console.log('getValueFromEval variableString', variableString)
|
|
7
|
+
// Extract the expression inside eval()
|
|
8
|
+
const match = variableString.match(/^eval\((.+)\)$/)
|
|
9
|
+
// console.log('match', match)
|
|
10
|
+
if (!match) {
|
|
11
|
+
throw new Error(`Invalid eval syntax: ${variableString}. Expected format: eval(expression)`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const expression = match[1].trim()
|
|
15
|
+
// console.log('expression', expression)
|
|
16
|
+
|
|
17
|
+
// Use "justin" variant to support strict comparison (===, !==) and other JS-like operators
|
|
18
|
+
try {
|
|
19
|
+
const { default: subscript } = await import('subscript/justin')
|
|
20
|
+
|
|
21
|
+
// Handle string comparisons by ensuring both sides are quoted
|
|
22
|
+
const processedExpression = expression.replace(/([a-zA-Z0-9_]+)\s*([=!<>]=?)\s*['"]([^'"]+)['"]/g, '"$1"$2"$3"')
|
|
23
|
+
|
|
24
|
+
// console.log('processedExpression', processedExpression)
|
|
25
|
+
const fn = subscript(processedExpression)
|
|
26
|
+
const result = fn()
|
|
27
|
+
return result
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`Error evaluating expression "${expression}": ${error.message}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
type: 'eval',
|
|
35
|
+
match: evalRefSyntax,
|
|
36
|
+
resolver: getValueFromEval
|
|
37
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { test } = require('uvu')
|
|
2
|
+
const assert = require('uvu/assert')
|
|
3
|
+
const { resolver } = require('./valueFromEval')
|
|
4
|
+
|
|
5
|
+
test('Basic boolean evaluation - true', async () => {
|
|
6
|
+
const result = await resolver('eval(200 > 100)')
|
|
7
|
+
assert.is(result, true)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test('Basic boolean evaluation - false', async () => {
|
|
11
|
+
const result = await resolver('eval(100 > 200)')
|
|
12
|
+
assert.is(result, false)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('Numeric evaluation', async () => {
|
|
16
|
+
const result = await resolver('eval(10 + 5)')
|
|
17
|
+
assert.is(result, 15)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('String comparison', async () => {
|
|
21
|
+
const result = await resolver('eval("hello" == "hello")')
|
|
22
|
+
assert.is(result, true)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('String comparison - strict', async () => {
|
|
26
|
+
const result = await resolver('eval("hello" === "hello")')
|
|
27
|
+
assert.is(result, true)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('Complex boolean expression', async () => {
|
|
31
|
+
const result = await resolver('eval(100 > 50)')
|
|
32
|
+
assert.is(result, true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('Invalid syntax throws error', async () => {
|
|
36
|
+
try {
|
|
37
|
+
await resolver('eval(')
|
|
38
|
+
assert.unreachable('Should have thrown an error')
|
|
39
|
+
} catch (error) {
|
|
40
|
+
assert.ok(error.message.includes('Invalid eval syntax'))
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test.run()
|
|
@@ -227,8 +227,9 @@ async function getGitTimestamp(_file, cwd, throwOnMissing = true) {
|
|
|
227
227
|
// console.log('cwd', cwd)
|
|
228
228
|
const output = await _exec(cmd, { cwd })
|
|
229
229
|
const date = new Date(output)
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
const dateString = date.toISOString()
|
|
231
|
+
cache.set(file, dateString)
|
|
232
|
+
return dateString
|
|
232
233
|
} catch (err) {
|
|
233
234
|
const projectRoot = findProjectRoot(cwd)
|
|
234
235
|
if (!projectRoot) {
|
|
@@ -242,8 +243,9 @@ async function getGitTimestamp(_file, cwd, throwOnMissing = true) {
|
|
|
242
243
|
const backupFile = path.join(projectRoot, _file)
|
|
243
244
|
const output = await _exec(`git log -1 --pretty="%ai" ${backupFile}`, { cwd: projectRoot })
|
|
244
245
|
const date = new Date(output)
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
const dateString = date.toISOString()
|
|
247
|
+
cache.set(file, dateString)
|
|
248
|
+
return dateString
|
|
247
249
|
} catch (err) {
|
|
248
250
|
if (throwOnMissing) {
|
|
249
251
|
throw new Error(`File ${file} does not exist in Git`)
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Type definitions for configorama variable validation
|
|
2
|
+
// This file provides TypeScript support for validating configuration variables
|
|
3
|
+
|
|
4
|
+
// Valid variable prefixes supported by configorama
|
|
5
|
+
export type KnownVariablePrefix = 'env:' | 'opt:' | 'self:' | 'file:' | 'git:' | 'cron:'
|
|
6
|
+
|
|
7
|
+
// Quoted string literal type for fallback values
|
|
8
|
+
type QuotedString = `"${string}"` | `'${string}'`
|
|
9
|
+
|
|
10
|
+
// Helper to detect unknown variable prefixes (for validation)
|
|
11
|
+
type UnknownVariablePrefix<S extends string> = S extends `\${${string}:${string}}`
|
|
12
|
+
? S extends `\${${KnownVariablePrefix}${string}}`
|
|
13
|
+
? never
|
|
14
|
+
: S
|
|
15
|
+
: never
|
|
16
|
+
|
|
17
|
+
// Helper to exclude spaces or commas in the key section
|
|
18
|
+
type NoCommaOrSpace<S extends string> = S extends `${string} ${string}`
|
|
19
|
+
? never
|
|
20
|
+
: S extends `${string},${string}`
|
|
21
|
+
? never
|
|
22
|
+
: S
|
|
23
|
+
|
|
24
|
+
// Main prefix token that must not include spaces or commas
|
|
25
|
+
type PrefixedKey = NoCommaOrSpace<string>
|
|
26
|
+
|
|
27
|
+
// Helper for recursively building dot-prop paths from an object
|
|
28
|
+
export type ObjectPaths<T> = T extends object
|
|
29
|
+
? {
|
|
30
|
+
[K in keyof T & string]: T[K] extends object ? `${K}` | `${K}.${ObjectPaths<T[K]>}` : `${K}`
|
|
31
|
+
}[keyof T & string]
|
|
32
|
+
: never
|
|
33
|
+
|
|
34
|
+
// Variables with fallback values (fully validated)
|
|
35
|
+
type PrefixedVariableWithFallbackNumber = `\${${KnownVariablePrefix}${PrefixedKey}, ${number}}`
|
|
36
|
+
type PrefixedVariableWithFallbackString = `\${${KnownVariablePrefix}${PrefixedKey}, ${QuotedString}}`
|
|
37
|
+
type PrefixedVariableWithFallbackBoolean = `\${${KnownVariablePrefix}${PrefixedKey}, ${boolean}}`
|
|
38
|
+
|
|
39
|
+
// Variables without fallback values (prefix-only)
|
|
40
|
+
type PrefixedVariableNoFallback = `\${${KnownVariablePrefix}${PrefixedKey}}`
|
|
41
|
+
|
|
42
|
+
// Self-reference (dot-prop) variables
|
|
43
|
+
type SelfReferenceVariable<Root> = `\${${ObjectPaths<Root>}}`
|
|
44
|
+
|
|
45
|
+
// Generic dot-prop variables (non-typed)
|
|
46
|
+
type GenericDotPropVariable = `\${${string}.${string}}`
|
|
47
|
+
|
|
48
|
+
// Union of all variable styles supported – narrowed by the primitive type T
|
|
49
|
+
export type VariableToken<T, Root> = T extends string
|
|
50
|
+
? PrefixedVariableWithFallbackString | PrefixedVariableNoFallback | SelfReferenceVariable<Root> | GenericDotPropVariable
|
|
51
|
+
: T extends number
|
|
52
|
+
? PrefixedVariableWithFallbackNumber | PrefixedVariableNoFallback | SelfReferenceVariable<Root> | GenericDotPropVariable
|
|
53
|
+
: T extends boolean
|
|
54
|
+
? PrefixedVariableWithFallbackBoolean | PrefixedVariableNoFallback | SelfReferenceVariable<Root> | GenericDotPropVariable
|
|
55
|
+
: never
|
|
56
|
+
|
|
57
|
+
// Resolution rule for primitive types
|
|
58
|
+
export type ResolvedPrimitive<T, Root> = T extends string
|
|
59
|
+
? T | VariableToken<T, Root>
|
|
60
|
+
: T | VariableToken<T, Root>
|
|
61
|
+
|
|
62
|
+
// Recursively resolve all values in an object tree
|
|
63
|
+
export type DeepResolved<T, Root = T> = T extends object
|
|
64
|
+
? { [K in keyof T]: DeepResolved<T[K], Root> }
|
|
65
|
+
: ResolvedPrimitive<T, Root>
|
|
66
|
+
|
|
67
|
+
// Generic configuration resolver function type
|
|
68
|
+
export type ConfigResolver<T> = (config: DeepResolved<T>) => DeepResolved<T>
|
|
69
|
+
|
|
70
|
+
// Example usage with a custom config interface:
|
|
71
|
+
/*
|
|
72
|
+
interface MyConfig {
|
|
73
|
+
database: {
|
|
74
|
+
host: string
|
|
75
|
+
port: number
|
|
76
|
+
ssl: boolean
|
|
77
|
+
}
|
|
78
|
+
api: {
|
|
79
|
+
baseUrl: string
|
|
80
|
+
timeout: number
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// This type allows variables in your config while maintaining type safety
|
|
85
|
+
type ResolvedMyConfig = DeepResolved<MyConfig>
|
|
86
|
+
|
|
87
|
+
// Usage example:
|
|
88
|
+
const config: ResolvedMyConfig = {
|
|
89
|
+
database: {
|
|
90
|
+
host: '${env:DB_HOST, "localhost"}', // ✅ String with quoted fallback
|
|
91
|
+
port: '${env:DB_PORT, 5432}', // ✅ Number with unquoted fallback
|
|
92
|
+
ssl: '${env:SSL_ENABLED, false}' // ✅ Boolean with unquoted fallback
|
|
93
|
+
},
|
|
94
|
+
api: {
|
|
95
|
+
baseUrl: '${env:API_URL}', // ✅ Variable without fallback
|
|
96
|
+
timeout: '${api.defaultTimeout}' // ✅ Dot-prop reference
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// These would cause TypeScript errors:
|
|
101
|
+
const badConfig: ResolvedMyConfig = {
|
|
102
|
+
database: {
|
|
103
|
+
host: '${env:DB_HOST, localhost}', // ❌ Unquoted string fallback
|
|
104
|
+
port: '${env:DB_PORT, "5432"}', // ❌ Quoted number fallback
|
|
105
|
+
ssl: '${env:SSL_ENABLED, "false"}' // ❌ Quoted boolean fallback
|
|
106
|
+
},
|
|
107
|
+
api: {
|
|
108
|
+
baseUrl: '${unknownPrefix:API_URL}', // ❌ Unknown variable prefix
|
|
109
|
+
timeout: 5000
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
*/
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const { findNestedVariables } = require('./find-nested-variables')
|
|
2
|
+
|
|
3
|
+
const DEBUG = false
|
|
2
4
|
/**
|
|
3
5
|
* Convert variable into string
|
|
4
6
|
* ${opt:foo} => 'opt:foo'
|
|
@@ -8,8 +10,20 @@ const { findNestedVariables } = require('./find-nested-variables')
|
|
|
8
10
|
|
|
9
11
|
const fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-\/,'" ]+?)\)/g)
|
|
10
12
|
const funcRegex = /(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
11
|
-
module.exports = function cleanVariable(
|
|
12
|
-
|
|
13
|
+
module.exports = function cleanVariable(
|
|
14
|
+
match,
|
|
15
|
+
variableSyntax,
|
|
16
|
+
simple,
|
|
17
|
+
caller,
|
|
18
|
+
recursive = false,
|
|
19
|
+
) {
|
|
20
|
+
if (DEBUG) {
|
|
21
|
+
console.log(`Clean input [${caller}]`, match)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// const outermostMatch = removeOuterMostBraces(match)
|
|
25
|
+
// console.log('outermostMatch', outermostMatch)
|
|
26
|
+
// return outermostMatch
|
|
13
27
|
|
|
14
28
|
let varToClean = match
|
|
15
29
|
|
|
@@ -38,7 +52,13 @@ module.exports = function cleanVariable(match, variableSyntax, simple, caller) {
|
|
|
38
52
|
const clean = varToClean.replace(variableSyntax, (context, contents) => {
|
|
39
53
|
return contents.trim()
|
|
40
54
|
})
|
|
41
|
-
|
|
55
|
+
|
|
56
|
+
// if (recursive && clean.match(variableSyntax)) {
|
|
57
|
+
// return cleanVariable(clean, variableSyntax, simple, caller, true)
|
|
58
|
+
// }
|
|
59
|
+
if (DEBUG) {
|
|
60
|
+
console.log(`Clean output [${caller}]`, clean)
|
|
61
|
+
}
|
|
42
62
|
return clean
|
|
43
63
|
|
|
44
64
|
// Support for simple variable cleaning with no space tweaks
|
|
@@ -62,3 +82,47 @@ module.exports = function cleanVariable(match, variableSyntax, simple, caller) {
|
|
|
62
82
|
// ^ trim White Space OutSide Quotes https://regex101.com/r/BuBNPN/1
|
|
63
83
|
// Needed for fallback values with spaces. ${empty, 'fallback value with space'}
|
|
64
84
|
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
function findOutermostBraces(str) {
|
|
88
|
+
const matches = []
|
|
89
|
+
let i = 0
|
|
90
|
+
|
|
91
|
+
while (i < str.length) {
|
|
92
|
+
if (str.substring(i, i + 2) === '${') {
|
|
93
|
+
let braceCount = 1
|
|
94
|
+
let start = i
|
|
95
|
+
i += 2
|
|
96
|
+
|
|
97
|
+
while (i < str.length && braceCount > 0) {
|
|
98
|
+
if (str[i] === '{') braceCount++
|
|
99
|
+
else if (str[i] === '}') braceCount--
|
|
100
|
+
i++
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (braceCount === 0) {
|
|
104
|
+
matches.push(str.substring(start, i))
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
i++
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return matches
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Removes the outermost ${} from a string
|
|
116
|
+
* @param {string} str - The input string containing ${} syntax
|
|
117
|
+
* @returns {string} The string with outermost ${} removed
|
|
118
|
+
* @example
|
|
119
|
+
* removeOuterMostBraces('${eval(${self:three} > ${self:four})}')
|
|
120
|
+
* // returns 'eval(${self:three} > ${self:four})'
|
|
121
|
+
*/
|
|
122
|
+
function removeOuterMostBraces(str) {
|
|
123
|
+
const matches = findOutermostBraces(str)
|
|
124
|
+
if (matches.length === 0) return str
|
|
125
|
+
|
|
126
|
+
const outermostMatch = matches[0]
|
|
127
|
+
return outermostMatch.slice(2, -1)
|
|
128
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const chalk = require('./chalk')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a hyperlink for the default editor (Cursor/VS Code)
|
|
6
|
+
* @param {string} filePath - The file path to link to
|
|
7
|
+
* @param {number} line - Line number (default: 1)
|
|
8
|
+
* @param {number} column - Column number (default: 1)
|
|
9
|
+
* @param {string} customDisplay - Custom display text (default: filename:line)
|
|
10
|
+
* @param {string} color - Chalk color for the link (default: 'cyanBright')
|
|
11
|
+
* @returns {string} The hyperlink string
|
|
12
|
+
*/
|
|
13
|
+
function createEditorLink(filePath, line = 1, column = 1, customDisplay = null, color = 'cyanBright') {
|
|
14
|
+
const absolutePath = path.resolve(filePath)
|
|
15
|
+
const url = `cursor://file${absolutePath}:${line}:${column}`
|
|
16
|
+
const display = customDisplay ? customDisplay: `${path.basename(filePath)}:${line}`
|
|
17
|
+
|
|
18
|
+
return `\x1b]8;;${url}\x1b\\${chalk[color](display)}\x1b]8;;\x1b\\`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
createEditorLink
|
|
23
|
+
}
|
|
@@ -193,7 +193,16 @@ function findNestedVariables(input, regex, variablesKnownTypes, location, debug
|
|
|
193
193
|
fallbackData.stringValue = trimQuotes(item)
|
|
194
194
|
fallbackData.isResolvedFallback = true
|
|
195
195
|
}
|
|
196
|
-
|
|
196
|
+
|
|
197
|
+
if (isVariable) {
|
|
198
|
+
const varType = item.match(variablesKnownTypes)[1]
|
|
199
|
+
fallbackData.varType = varType
|
|
200
|
+
// if (varType === 'self:') {
|
|
201
|
+
// fallbackData.fullMatch = item.replace('self:', '')
|
|
202
|
+
// fallbackData.variable = item.replace('self:', '')
|
|
203
|
+
// fallbackData.varType = 'dot.prop'
|
|
204
|
+
// }
|
|
205
|
+
}
|
|
197
206
|
return fallbackData
|
|
198
207
|
})
|
|
199
208
|
}
|
package/src/utils/logs.js
CHANGED
package/src/utils/parse.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const YAML = require('../parsers/yaml')
|
|
2
2
|
const TOML = require('../parsers/toml')
|
|
3
|
+
const INI = require('../parsers/ini')
|
|
4
|
+
const { executeTypeScriptFileSync } = require('../parsers/typescript')
|
|
5
|
+
const { executeESMFileSync } = require('../parsers/esm')
|
|
3
6
|
const cloudFormationSchema = require('./cloudformationSchema')
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -34,6 +37,8 @@ function parseFileContents(fileContents, fileType, filePath, varRegex, opts = {}
|
|
|
34
37
|
}
|
|
35
38
|
} else if (fileType.match(/\.(toml)/)) {
|
|
36
39
|
configObject = TOML.parse(fileContents)
|
|
40
|
+
} else if (fileType.match(/\.(ini)/)) {
|
|
41
|
+
configObject = INI.parse(fileContents)
|
|
37
42
|
} else if (fileType.match(/\.(json)/)) {
|
|
38
43
|
configObject = JSON.parse(fileContents)
|
|
39
44
|
} else if (fileType.match(/\.(js)/)) {
|
|
@@ -47,11 +52,46 @@ function parseFileContents(fileContents, fileType, filePath, varRegex, opts = {}
|
|
|
47
52
|
if (jsArgs && typeof jsArgs === 'function') {
|
|
48
53
|
jsArgs = jsArgs()
|
|
49
54
|
}
|
|
55
|
+
// console.log('jsArgs', jsArgs)
|
|
50
56
|
configObject = jsFile(jsArgs)
|
|
51
57
|
}
|
|
52
58
|
} catch (err) {
|
|
53
59
|
throw new Error(err)
|
|
54
60
|
}
|
|
61
|
+
} else if (fileType.match(/\.(ts|tsx)/)) {
|
|
62
|
+
try {
|
|
63
|
+
let jsArgs = opts.dynamicArgs || {}
|
|
64
|
+
if (jsArgs && typeof jsArgs === 'function') {
|
|
65
|
+
jsArgs = jsArgs()
|
|
66
|
+
}
|
|
67
|
+
configObject = executeTypeScriptFileSync(filePath, opts)
|
|
68
|
+
if (configObject.config) {
|
|
69
|
+
configObject = (typeof configObject.config === 'function') ? configObject.config(jsArgs) : configObject.config
|
|
70
|
+
} else if (configObject.default) {
|
|
71
|
+
configObject = (typeof configObject.default === 'function') ? configObject.default(jsArgs) : configObject.default
|
|
72
|
+
}
|
|
73
|
+
// console.log('parseFileContents configObject', configObject, opts)
|
|
74
|
+
} catch (err) {
|
|
75
|
+
throw new Error(`Failed to execute TypeScript file ${filePath}: ${err.message}`)
|
|
76
|
+
}
|
|
77
|
+
} else if (fileType.match(/\.(mjs|esm)/)) {
|
|
78
|
+
try {
|
|
79
|
+
let jsArgs = opts.dynamicArgs || {}
|
|
80
|
+
if (jsArgs && typeof jsArgs === 'function') {
|
|
81
|
+
jsArgs = jsArgs()
|
|
82
|
+
}
|
|
83
|
+
configObject = executeESMFileSync(filePath, opts)
|
|
84
|
+
if (configObject.config) {
|
|
85
|
+
configObject = (typeof configObject.config === 'function') ? configObject.config(jsArgs) : configObject.config
|
|
86
|
+
} else if (configObject.default) {
|
|
87
|
+
configObject = (typeof configObject.default === 'function') ? configObject.default(jsArgs) : configObject.default
|
|
88
|
+
} else if (typeof configObject === 'function') {
|
|
89
|
+
configObject = configObject(jsArgs)
|
|
90
|
+
}
|
|
91
|
+
// console.log('parseFileContents ESM configObject', configObject, opts)
|
|
92
|
+
} catch (err) {
|
|
93
|
+
throw new Error(`Failed to execute ESM file ${filePath}: ${err.message}`)
|
|
94
|
+
}
|
|
55
95
|
}
|
|
56
96
|
|
|
57
97
|
return configObject
|