configorama 0.8.0 → 0.9.3
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 +38 -3
- package/package.json +1 -1
- package/src/main.js +53 -21
- package/src/parsers/yaml.js +4 -4
- package/src/parsers/yaml.test.js +52 -0
- package/src/resolvers/valueFromFile.js +15 -2
- package/src/utils/PromiseTracker.js +54 -0
- package/src/utils/encoders/unknown-values.js +1 -1
- package/src/utils/encoders/unknown-values.test.js +146 -0
- package/src/utils/lodash.js +5 -4
- package/src/utils/lodash.test.js +172 -0
- package/src/utils/parsing/cloudformationSchema.js +24 -2
- package/src/utils/parsing/cloudformationSchema.test.js +236 -0
- package/src/utils/parsing/mergeByKeys.js +9 -8
- package/src/utils/parsing/mergeByKeys.test.js +189 -0
- package/src/utils/parsing/parse.js +5 -2
- package/src/utils/paths/getFullFilePath.js +2 -2
- package/src/utils/paths/getFullFilePath.test.js +152 -0
- package/src/utils/regex/index.js +65 -1
- package/src/utils/regex/index.test.js +195 -0
- package/src/utils/strings/formatFunctionArgs.js +4 -0
- package/src/utils/strings/splitCsv.js +46 -19
- package/types/src/main.d.ts.map +1 -1
- package/types/src/resolvers/valueFromFile.d.ts.map +1 -1
- package/types/src/utils/PromiseTracker.d.ts +4 -0
- package/types/src/utils/PromiseTracker.d.ts.map +1 -1
- package/types/src/utils/lodash.d.ts.map +1 -1
- package/types/src/utils/parsing/mergeByKeys.d.ts.map +1 -1
- package/types/src/utils/parsing/parse.d.ts.map +1 -1
- package/types/src/utils/regex/index.d.ts +15 -1
- package/types/src/utils/regex/index.d.ts.map +1 -1
- package/types/src/utils/strings/formatFunctionArgs.d.ts.map +1 -1
- package/types/src/utils/strings/splitCsv.d.ts +1 -1
- package/types/src/utils/strings/splitCsv.d.ts.map +1 -1
|
@@ -17,8 +17,8 @@ function resolveFilePath(pathToResolve, basePath) {
|
|
|
17
17
|
if (fs.existsSync(fullFilePath)) {
|
|
18
18
|
// Get real path to handle potential symlinks (but don't fatal error)
|
|
19
19
|
fullFilePath = fs.realpathSync(fullFilePath)
|
|
20
|
-
// Only
|
|
21
|
-
} else if (
|
|
20
|
+
// Only use findUp for relative paths (not absolute paths)
|
|
21
|
+
} else if (!path.isAbsolute(pathToResolve)) {
|
|
22
22
|
const cleanName = path.basename(pathToResolve)
|
|
23
23
|
const findUpResult = findUp.sync(cleanName, { cwd: basePath })
|
|
24
24
|
if (findUpResult) {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for getFullFilePath.js - file path resolution with findUp support
|
|
3
|
+
*/
|
|
4
|
+
const { test } = require('uvu')
|
|
5
|
+
const assert = require('uvu/assert')
|
|
6
|
+
const path = require('path')
|
|
7
|
+
const fs = require('fs')
|
|
8
|
+
const getFullPath = require('./getFullFilePath')
|
|
9
|
+
const { resolveFilePath, resolveFilePathFromMatch } = require('./getFullFilePath')
|
|
10
|
+
|
|
11
|
+
// ==========================================
|
|
12
|
+
// Test directory setup/teardown
|
|
13
|
+
// ==========================================
|
|
14
|
+
|
|
15
|
+
const testDir = path.join(__dirname, '_test-getFullFilePath')
|
|
16
|
+
const subDir = path.join(testDir, 'subdir')
|
|
17
|
+
const deepDir = path.join(subDir, 'deepdir')
|
|
18
|
+
|
|
19
|
+
test.before(() => {
|
|
20
|
+
// Cleanup any existing test directory
|
|
21
|
+
if (fs.existsSync(testDir)) {
|
|
22
|
+
fs.rmSync(testDir, { recursive: true })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create directory structure:
|
|
26
|
+
// _test-getFullFilePath/
|
|
27
|
+
// config.yml <- file at root
|
|
28
|
+
// subdir/
|
|
29
|
+
// local.yml <- file in subdir
|
|
30
|
+
// deepdir/
|
|
31
|
+
// (empty - for testing findUp from here)
|
|
32
|
+
fs.mkdirSync(testDir, { recursive: true })
|
|
33
|
+
fs.mkdirSync(subDir, { recursive: true })
|
|
34
|
+
fs.mkdirSync(deepDir, { recursive: true })
|
|
35
|
+
|
|
36
|
+
// Create test files
|
|
37
|
+
fs.writeFileSync(path.join(testDir, 'config.yml'), 'root: true')
|
|
38
|
+
fs.writeFileSync(path.join(subDir, 'local.yml'), 'local: true')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test.after(() => {
|
|
42
|
+
// Cleanup
|
|
43
|
+
if (fs.existsSync(testDir)) {
|
|
44
|
+
fs.rmSync(testDir, { recursive: true })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// ==========================================
|
|
49
|
+
// resolveFilePath - basic functionality
|
|
50
|
+
// ==========================================
|
|
51
|
+
|
|
52
|
+
test('resolveFilePath - returns file when it exists at computed path', () => {
|
|
53
|
+
const result = resolveFilePath('./config.yml', testDir)
|
|
54
|
+
assert.is(result, path.join(testDir, 'config.yml'))
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('resolveFilePath - handles absolute paths', () => {
|
|
58
|
+
const absolutePath = path.join(testDir, 'config.yml')
|
|
59
|
+
const result = resolveFilePath(absolutePath, '/some/other/path')
|
|
60
|
+
assert.is(result, absolutePath)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('resolveFilePath - returns computed path when file does not exist', () => {
|
|
64
|
+
const result = resolveFilePath('./nonexistent.yml', testDir)
|
|
65
|
+
assert.is(result, path.join(testDir, 'nonexistent.yml'))
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// ==========================================
|
|
69
|
+
// resolveFilePath - findUp with "./" prefix
|
|
70
|
+
// ==========================================
|
|
71
|
+
|
|
72
|
+
test('resolveFilePath - with "./" prefix triggers findUp and finds file in parent directory', () => {
|
|
73
|
+
// From deepDir, look for config.yml which is in testDir (grandparent)
|
|
74
|
+
const result = resolveFilePath('./config.yml', deepDir)
|
|
75
|
+
const expected = path.join(testDir, 'config.yml')
|
|
76
|
+
assert.is(result, expected)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('resolveFilePath - with "./" prefix finds file in immediate parent', () => {
|
|
80
|
+
// From deepDir, look for local.yml which is in subDir (parent)
|
|
81
|
+
const result = resolveFilePath('./local.yml', deepDir)
|
|
82
|
+
const expected = path.join(subDir, 'local.yml')
|
|
83
|
+
assert.is(result, expected)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// ==========================================
|
|
87
|
+
// resolveFilePath - findUp WITHOUT "./" prefix (bug fix tests)
|
|
88
|
+
// ==========================================
|
|
89
|
+
|
|
90
|
+
test('resolveFilePath - bare filename should also trigger findUp', () => {
|
|
91
|
+
// From deepDir, look for config.yml (bare filename) which is in testDir
|
|
92
|
+
const result = resolveFilePath('config.yml', deepDir)
|
|
93
|
+
const expected = path.join(testDir, 'config.yml')
|
|
94
|
+
assert.is(result, expected,
|
|
95
|
+
`Bare filename should trigger findUp. Got ${result} instead of ${expected}`)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('resolveFilePath - bare filename finds file in immediate parent', () => {
|
|
99
|
+
// From deepDir, look for local.yml (bare filename) which is in subDir
|
|
100
|
+
const result = resolveFilePath('local.yml', deepDir)
|
|
101
|
+
const expected = path.join(subDir, 'local.yml')
|
|
102
|
+
assert.is(result, expected)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('resolveFilePath - relative path without ./ prefix triggers findUp', () => {
|
|
106
|
+
// Path like "subdir/file.yml" should also trigger findUp if not found
|
|
107
|
+
const result = resolveFilePath('config.yml', deepDir)
|
|
108
|
+
const expected = path.join(testDir, 'config.yml')
|
|
109
|
+
assert.is(result, expected)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// ==========================================
|
|
113
|
+
// getFullPath - wrapper function
|
|
114
|
+
// ==========================================
|
|
115
|
+
|
|
116
|
+
test('getFullPath - resolves file path using cwd', () => {
|
|
117
|
+
const result = getFullPath('./config.yml', testDir)
|
|
118
|
+
assert.is(result, path.join(testDir, 'config.yml'))
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('getFullPath - expands ~ to home directory', () => {
|
|
122
|
+
const os = require('os')
|
|
123
|
+
const result = getFullPath('~/somefile.yml', testDir)
|
|
124
|
+
assert.ok(result.startsWith(os.homedir()))
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// ==========================================
|
|
128
|
+
// resolveFilePathFromMatch - file() syntax parsing
|
|
129
|
+
// ==========================================
|
|
130
|
+
|
|
131
|
+
const fileRefSyntax = /^file\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" =+]+?)\)/g
|
|
132
|
+
|
|
133
|
+
test('resolveFilePathFromMatch - extracts path from file() syntax', () => {
|
|
134
|
+
const result = resolveFilePathFromMatch('file(./config.yml)', fileRefSyntax, testDir)
|
|
135
|
+
assert.is(result.relativePath, './config.yml')
|
|
136
|
+
assert.is(result.fullFilePath, path.join(testDir, 'config.yml'))
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('resolveFilePathFromMatch - handles quoted paths', () => {
|
|
140
|
+
const result = resolveFilePathFromMatch("file('./config.yml')", fileRefSyntax, testDir)
|
|
141
|
+
assert.is(result.relativePath, './config.yml')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('resolveFilePathFromMatch - handles bare filename in file() syntax', () => {
|
|
145
|
+
// This tests the bug fix - bare filename should work with findUp
|
|
146
|
+
const result = resolveFilePathFromMatch('file(config.yml)', fileRefSyntax, deepDir)
|
|
147
|
+
const expected = path.join(testDir, 'config.yml')
|
|
148
|
+
assert.is(result.fullFilePath, expected,
|
|
149
|
+
`file(config.yml) should find file via findUp. Got ${result.fullFilePath}`)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test.run()
|
package/src/utils/regex/index.js
CHANGED
|
@@ -2,10 +2,72 @@
|
|
|
2
2
|
* Shared regex patterns and utilities
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Legacy regex patterns (can't handle nested parentheses properly)
|
|
6
|
+
const funcRegexSimple = /(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
6
7
|
const funcStartOfLineRegex = /^(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
7
8
|
const subFunctionRegex = /(\w+):(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Parse a function call with balanced parentheses support
|
|
12
|
+
* Returns a regex-exec-like array: [fullMatch, funcName, args] with index and input properties
|
|
13
|
+
* or null if no function found
|
|
14
|
+
* @param {string} str - String to search for function call
|
|
15
|
+
* @returns {any} Regex-like result array or null
|
|
16
|
+
*/
|
|
17
|
+
function parseFunctionCall(str) {
|
|
18
|
+
if (!str || typeof str !== 'string') return null
|
|
19
|
+
|
|
20
|
+
// Find function name followed by opening paren
|
|
21
|
+
const funcMatch = str.match(/(\w+)\s*\(/)
|
|
22
|
+
if (!funcMatch) return null
|
|
23
|
+
|
|
24
|
+
const funcName = funcMatch[1]
|
|
25
|
+
const openParenIndex = funcMatch.index + funcMatch[0].length - 1
|
|
26
|
+
const startPos = openParenIndex + 1
|
|
27
|
+
|
|
28
|
+
let depth = 1
|
|
29
|
+
let pos = startPos
|
|
30
|
+
|
|
31
|
+
// Track parenthesis depth to find matching closing paren
|
|
32
|
+
while (pos < str.length && depth > 0) {
|
|
33
|
+
const char = str[pos]
|
|
34
|
+
if (char === '(') depth++
|
|
35
|
+
else if (char === ')') depth--
|
|
36
|
+
pos++
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (depth !== 0) return null // Unbalanced parens
|
|
40
|
+
|
|
41
|
+
const args = str.substring(startPos, pos - 1).trim()
|
|
42
|
+
|
|
43
|
+
// Skip trailing whitespace for fullMatch
|
|
44
|
+
let endPos = pos
|
|
45
|
+
while (endPos < str.length && /\s/.test(str[endPos])) {
|
|
46
|
+
endPos++
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const fullMatch = str.substring(funcMatch.index, endPos)
|
|
50
|
+
|
|
51
|
+
// Create regex-exec-like result array with index and input properties
|
|
52
|
+
/** @type {any} */
|
|
53
|
+
const result = [fullMatch, funcName, args || undefined]
|
|
54
|
+
result.index = funcMatch.index
|
|
55
|
+
result.input = str
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Enhanced funcRegex that handles nested parentheses
|
|
61
|
+
* Mimics RegExp interface with exec() method
|
|
62
|
+
*/
|
|
63
|
+
const funcRegex = {
|
|
64
|
+
exec: parseFunctionCall,
|
|
65
|
+
test: (str) => parseFunctionCall(str) !== null,
|
|
66
|
+
// Keep source for compatibility (shows what pattern we're conceptually matching)
|
|
67
|
+
source: '(\\w+)\\s*\\((.*)\\)\\s*',
|
|
68
|
+
toString: () => '/(\\w+)\\s*\\((.*)\\)\\s*/'
|
|
69
|
+
}
|
|
70
|
+
|
|
9
71
|
/**
|
|
10
72
|
* Combine multiple regex patterns into single OR pattern
|
|
11
73
|
* @param {RegExp[]} regexes - Array of regex patterns to combine
|
|
@@ -18,9 +80,11 @@ function combineRegexes(regexes) {
|
|
|
18
80
|
|
|
19
81
|
module.exports = {
|
|
20
82
|
funcRegex,
|
|
83
|
+
funcRegexSimple,
|
|
21
84
|
funcStartOfLineRegex,
|
|
22
85
|
subFunctionRegex,
|
|
23
86
|
combineRegexes,
|
|
87
|
+
parseFunctionCall,
|
|
24
88
|
// Keep old export name for backwards compat
|
|
25
89
|
functionRegex: funcRegex
|
|
26
90
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for regex utilities - especially funcRegex with nested parentheses
|
|
3
|
+
*/
|
|
4
|
+
const { test } = require('uvu')
|
|
5
|
+
const assert = require('uvu/assert')
|
|
6
|
+
const { funcRegex, parseFunctionCall } = require('./index')
|
|
7
|
+
|
|
8
|
+
// ==========================================
|
|
9
|
+
// parseFunctionCall - basic functionality
|
|
10
|
+
// ==========================================
|
|
11
|
+
|
|
12
|
+
test('parseFunctionCall - simple function with no args', () => {
|
|
13
|
+
const result = parseFunctionCall('myFunc()')
|
|
14
|
+
assert.ok(result)
|
|
15
|
+
assert.is(result[1], 'myFunc')
|
|
16
|
+
assert.is(result[2], undefined)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('parseFunctionCall - simple function with string arg', () => {
|
|
20
|
+
const result = parseFunctionCall("help('some text')")
|
|
21
|
+
assert.ok(result)
|
|
22
|
+
assert.is(result[1], 'help')
|
|
23
|
+
assert.is(result[2], "'some text'")
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('parseFunctionCall - function with multiple args', () => {
|
|
27
|
+
const result = parseFunctionCall("merge('a', 'b')")
|
|
28
|
+
assert.ok(result)
|
|
29
|
+
assert.is(result[1], 'merge')
|
|
30
|
+
assert.is(result[2], "'a', 'b'")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('parseFunctionCall - returns null for non-function string', () => {
|
|
34
|
+
const result = parseFunctionCall('not a function')
|
|
35
|
+
assert.is(result, null)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('parseFunctionCall - returns null for null input', () => {
|
|
39
|
+
const result = parseFunctionCall(null)
|
|
40
|
+
assert.is(result, null)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('parseFunctionCall - returns null for undefined input', () => {
|
|
44
|
+
const result = parseFunctionCall(undefined)
|
|
45
|
+
assert.is(result, null)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// ==========================================
|
|
49
|
+
// parseFunctionCall - nested parentheses (bug fix)
|
|
50
|
+
// ==========================================
|
|
51
|
+
|
|
52
|
+
test('parseFunctionCall - handles parentheses in text argument', () => {
|
|
53
|
+
const result = parseFunctionCall("help('Deployment stage (dev, staging, prod)')")
|
|
54
|
+
assert.ok(result)
|
|
55
|
+
assert.is(result[1], 'help')
|
|
56
|
+
assert.is(result[2], "'Deployment stage (dev, staging, prod)'")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('parseFunctionCall - handles nested function calls', () => {
|
|
60
|
+
const result = parseFunctionCall('outer(inner(arg))')
|
|
61
|
+
assert.ok(result)
|
|
62
|
+
assert.is(result[1], 'outer')
|
|
63
|
+
assert.is(result[2], 'inner(arg)')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('parseFunctionCall - handles multiple nested function args', () => {
|
|
67
|
+
const result = parseFunctionCall('merge(func1(a), func2(b))')
|
|
68
|
+
assert.ok(result)
|
|
69
|
+
assert.is(result[1], 'merge')
|
|
70
|
+
assert.is(result[2], 'func1(a), func2(b)')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('parseFunctionCall - handles deeply nested parentheses', () => {
|
|
74
|
+
const result = parseFunctionCall('a(b(c(d)))')
|
|
75
|
+
assert.ok(result)
|
|
76
|
+
assert.is(result[1], 'a')
|
|
77
|
+
assert.is(result[2], 'b(c(d))')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('parseFunctionCall - handles complex help text with variables', () => {
|
|
81
|
+
const result = parseFunctionCall("help('Deployment stage (${allowedValues}, funky)')")
|
|
82
|
+
assert.ok(result)
|
|
83
|
+
assert.is(result[1], 'help')
|
|
84
|
+
assert.is(result[2], "'Deployment stage (${allowedValues}, funky)'")
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('parseFunctionCall - handles multiple parens in text', () => {
|
|
88
|
+
const result = parseFunctionCall("help('Choose option (A) or (B) or (C)')")
|
|
89
|
+
assert.ok(result)
|
|
90
|
+
assert.is(result[1], 'help')
|
|
91
|
+
assert.is(result[2], "'Choose option (A) or (B) or (C)'")
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// ==========================================
|
|
95
|
+
// parseFunctionCall - edge cases
|
|
96
|
+
// ==========================================
|
|
97
|
+
|
|
98
|
+
test('parseFunctionCall - handles whitespace around args', () => {
|
|
99
|
+
const result = parseFunctionCall('func( arg )')
|
|
100
|
+
assert.ok(result)
|
|
101
|
+
assert.is(result[1], 'func')
|
|
102
|
+
assert.is(result[2], 'arg')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('parseFunctionCall - handles whitespace before opening paren', () => {
|
|
106
|
+
const result = parseFunctionCall('func (arg)')
|
|
107
|
+
assert.ok(result)
|
|
108
|
+
assert.is(result[1], 'func')
|
|
109
|
+
assert.is(result[2], 'arg')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('parseFunctionCall - returns null for unbalanced open paren', () => {
|
|
113
|
+
const result = parseFunctionCall('func((arg)')
|
|
114
|
+
assert.is(result, null)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('parseFunctionCall - handles function in middle of string', () => {
|
|
118
|
+
const result = parseFunctionCall('prefix func(arg) suffix')
|
|
119
|
+
assert.ok(result)
|
|
120
|
+
assert.is(result[1], 'func')
|
|
121
|
+
assert.is(result[2], 'arg')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('parseFunctionCall - includes index property', () => {
|
|
125
|
+
const result = parseFunctionCall('prefix func(arg)')
|
|
126
|
+
assert.ok(result)
|
|
127
|
+
assert.is(result.index, 7) // "prefix " is 7 chars
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('parseFunctionCall - includes input property', () => {
|
|
131
|
+
const input = 'func(arg)'
|
|
132
|
+
const result = parseFunctionCall(input)
|
|
133
|
+
assert.ok(result)
|
|
134
|
+
assert.is(result.input, input)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// ==========================================
|
|
138
|
+
// funcRegex.exec - same interface as RegExp
|
|
139
|
+
// ==========================================
|
|
140
|
+
|
|
141
|
+
test('funcRegex.exec - works like RegExp.exec', () => {
|
|
142
|
+
const result = funcRegex.exec("help('text')")
|
|
143
|
+
assert.ok(result)
|
|
144
|
+
assert.is(result[1], 'help')
|
|
145
|
+
assert.is(result[2], "'text'")
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('funcRegex.exec - handles nested parens', () => {
|
|
149
|
+
const result = funcRegex.exec("outer(inner(x))")
|
|
150
|
+
assert.ok(result)
|
|
151
|
+
assert.is(result[1], 'outer')
|
|
152
|
+
assert.is(result[2], 'inner(x)')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('funcRegex.test - returns true for function', () => {
|
|
156
|
+
assert.is(funcRegex.test('func()'), true)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('funcRegex.test - returns false for non-function', () => {
|
|
160
|
+
assert.is(funcRegex.test('not a function'), false)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// ==========================================
|
|
164
|
+
// Real-world examples from codebase
|
|
165
|
+
// ==========================================
|
|
166
|
+
|
|
167
|
+
test('real-world: split function', () => {
|
|
168
|
+
const result = funcRegex.exec("split('hello,world', ',')")
|
|
169
|
+
assert.ok(result)
|
|
170
|
+
assert.is(result[1], 'split')
|
|
171
|
+
assert.is(result[2], "'hello,world', ','")
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('real-world: merge function', () => {
|
|
175
|
+
const result = funcRegex.exec("merge('stuff', 'new')")
|
|
176
|
+
assert.ok(result)
|
|
177
|
+
assert.is(result[1], 'merge')
|
|
178
|
+
assert.is(result[2], "'stuff', 'new'")
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('real-world: nested split(merge())', () => {
|
|
182
|
+
const result = funcRegex.exec("split(merge('a', 'b'), ',')")
|
|
183
|
+
assert.ok(result)
|
|
184
|
+
assert.is(result[1], 'split')
|
|
185
|
+
assert.is(result[2], "merge('a', 'b'), ','")
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('real-world: git remote', () => {
|
|
189
|
+
const result = funcRegex.exec("remote('origin')")
|
|
190
|
+
assert.ok(result)
|
|
191
|
+
assert.is(result[1], 'remote')
|
|
192
|
+
assert.is(result[2], "'origin'")
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test.run()
|
|
@@ -20,6 +20,10 @@ module.exports = function formatArgs(args) {
|
|
|
20
20
|
return formatArg(args)
|
|
21
21
|
}
|
|
22
22
|
return args.map((arg) => {
|
|
23
|
+
// Skip formatting for non-string args (e.g., arrays/objects from nested function calls)
|
|
24
|
+
if (typeof arg !== 'string') {
|
|
25
|
+
return arg
|
|
26
|
+
}
|
|
23
27
|
return formatArg(arg)
|
|
24
28
|
})
|
|
25
29
|
}
|
|
@@ -4,7 +4,7 @@ const { splitByComma } = require('./splitByComma')
|
|
|
4
4
|
const VARIABLE_SYNTAX = /\${[^}]+}/g
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Split a string by
|
|
7
|
+
* Split a string by delimiter while preserving quoted content and balanced parentheses
|
|
8
8
|
* NOTE: This is a simpler version that delegates to splitByComma for consistency.
|
|
9
9
|
* For advanced use cases with bracket depth tracking and regex protection, use splitByComma directly.
|
|
10
10
|
* @param {string} str - String to split
|
|
@@ -14,26 +14,53 @@ const VARIABLE_SYNTAX = /\${[^}]+}/g
|
|
|
14
14
|
* @returns {string[]} Array of split strings
|
|
15
15
|
*/
|
|
16
16
|
function splitCsv(str, splitter, options = {}) {
|
|
17
|
-
// If custom splitter is provided,
|
|
17
|
+
// If custom splitter is provided, use implementation with parenthesis tracking
|
|
18
18
|
if (splitter && splitter !== ',') {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const result = []
|
|
20
|
+
let current = ''
|
|
21
|
+
let inQuote = false
|
|
22
|
+
let quoteChar = ''
|
|
23
|
+
let parenDepth = 0
|
|
24
|
+
let bracketDepth = 0
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < str.length; i++) {
|
|
27
|
+
const char = str[i]
|
|
28
|
+
|
|
29
|
+
// Handle quotes
|
|
30
|
+
if ((char === "'" || char === '"') && (i === 0 || str[i-1] !== '\\')) {
|
|
31
|
+
if (!inQuote) {
|
|
32
|
+
inQuote = true
|
|
33
|
+
quoteChar = char
|
|
34
|
+
} else if (char === quoteChar) {
|
|
35
|
+
inQuote = false
|
|
26
36
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Handle parentheses and brackets (only outside quotes)
|
|
40
|
+
if (!inQuote) {
|
|
41
|
+
if (char === '(') parenDepth++
|
|
42
|
+
else if (char === ')') parenDepth--
|
|
43
|
+
else if (char === '[') bracketDepth++
|
|
44
|
+
else if (char === ']') bracketDepth--
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check if we're at a splitter position
|
|
48
|
+
const atSplitter = str.substring(i, i + splitter.length) === splitter
|
|
49
|
+
|
|
50
|
+
if (atSplitter && !inQuote && parenDepth === 0 && bracketDepth === 0) {
|
|
51
|
+
result.push(current.trim())
|
|
52
|
+
current = ''
|
|
53
|
+
i += splitter.length - 1 // Skip rest of splitter
|
|
54
|
+
} else {
|
|
55
|
+
current += char
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (current.trim() || result.length > 0) {
|
|
60
|
+
result.push(current.trim())
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result
|
|
37
64
|
}
|
|
38
65
|
|
|
39
66
|
// For standard comma splitting, use the more robust splitByComma
|
package/types/src/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AA0GA;IACE,
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AA0GA;IACE,0CA6bC;IArbC,cAcW;IAuBX,gBAAqB;IAErB,sBAAwB;IACxB,qBAAuB;IAGvB,uBAA4B;IAc5B,uBAAoC;IAIpC,kBAAqC;IACrC,kBAAqC;IAErC,yBAA+F;IAC/F,yBAAuD;IACvD,kCAAyE;IAKvE,YAA0B;IAE1B,oBAA6C;IAE7C,gBAAoD;IAOpD,uBAAkC;IAElC,uBAA8B;IAE9B,uBAAkC;IASpC,wBAAmC;IAGnC,mBA8GC;IAwED,4BAA8C;IAO9C,aA2EC;IAUD,oBAEC;IAGD,eAkDC;IAOD,YAAc;IACd,cAAgB;IAChB,kBAAkB;IAGpB;;;;OAIG;IACH,0BAHW,MAAM,GACJ,OAAO,CAQnB;IAED;;;;OAIG;IACH,6BAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAOvB;IAED;;;;OAIG;IACH,gCAHW,MAAM,GACJ,OAAO,CAoBnB;IAKD;;;;;OAKG;IACH,oBAFa,OAAO,CAAC,GAAG,CAAC,CAqsBxB;IAlsBC,aAA4B;IAc1B,2BAA4B;IAQ5B,uBAAgD;IA8qBpD;;;OAGG;IACH,2BAFa,MAAM,CA8alB;IAvBC;;;;;;;;;;;;;;;MAoBC;IAIH;;;;OAIG;IACH,uCAFa,OAAO,CAAC,GAAG,CAAC,CAIxB;IACD,+CAsBC;IAKD;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;OAWG;IACH,mFAHa;;;;cAZC,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAWD,CA+D9B;IACD;;;OAGG;IACH;;;;;OAKG;IACH,oCAHa,OAAO,CAAC;;;;cAnFP,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAkFgB,CAAC,EAAE,CA6BlD;IACD;;;;;OAKG;IACH,iDAFa,OAAO,CAAC,IAAI,CAAC,CAWzB;IAID;;;;;OAKG;IACH;;;;OAIG;IACH,2BAFa,eAAc;;;;;;;;;;OAAa,CAavC;IACD;;;;;OAKG;IACH,yBAHW;;;;;;;;;;OAAa,gCACX,cAAS,CAOrB;IACD;;;;;;OAMG;IACH,6DAFa,GAAC,CA+Kb;IAKD;;;;;;;OAOG;IACH,yDAHa,OAAO,CAAC,GAAG,CAAC,CAiCxB;IACD;;;;OAIG;IAOH;;;;;;OAMG;IACH,wFA2BC;IACD;;;;;;;;;;;OAWG;IACH,8BARG;QAAyB,KAAK,EAAtB,GAAG;QACoB,IAAI,GAA3B,MAAM,EAAE;QACa,cAAc,GAAnC,MAAM;QACc,iBAAiB;KAC7C,6CAEU;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,QAAQ;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,CAmY9J;IAID;;;;;;;;OAQG;IACH,qEAHa,OAAO,CAAC,GAAG,CAAC,CAoExB;IAKD;;;;;;;OAOG;IACH,0FAFa,OAAO,CAAC,GAAG,CAAC,CA6gBxB;IACD,+EA+BC;IACD,yDAeC;IACD,oEA6BC;IAKD,8CAQC;IACD,kDAyBC;IACD;;;;;;;;;;;;;OAaG;IACH,wEAoDC;IAKD,4BAOC;IACD,sCA2DC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valueFromFile.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromFile.js"],"names":[],"mappings":"AAkEA;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAfG;IAAoB,UAAU,EAAtB,MAAM;IACK,aAAa;IACZ,cAAc,EAA1B,MAAM;IACM,mBAAmB,EAA/B,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,IAAI,EAAhB,MAAM;IACM,cAAc,EAA1B,MAAM;IACM,MAAM,EAAlB,MAAM;IACQ,cAAc;IAChB,aAAa,EAAzB,MAAM;IACM,aAAa,EAAzB,MAAM;CACd,kBAAQ,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"valueFromFile.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromFile.js"],"names":[],"mappings":"AAkEA;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAfG;IAAoB,UAAU,EAAtB,MAAM;IACK,aAAa;IACZ,cAAc,EAA1B,MAAM;IACM,mBAAmB,EAA/B,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,IAAI,EAAhB,MAAM;IACM,cAAc,EAA1B,MAAM;IACM,MAAM,EAAlB,MAAM;IACQ,cAAc;IAChB,aAAa,EAAzB,MAAM;IACM,aAAa,EAAzB,MAAM;CACd,kBAAQ,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CAiUxB;AA3WD;;;;;GAKG;AACH,2CAJW,MAAM,YACN,MAAM,GACJ,GAAC,CAoBb;AAqVD;;;;;;GAMG;AACH,qDAJW,MAAM,qBACN,MAAM,GACJ;IAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,GAAC,IAAI,CAAA;CAAE,CAkBhE;AAED;;;;;;GAMG;AACH,sDALW,MAAM,qBACN,MAAM,yBACN,OAAO,GACL,MAAM,EAAE,CAcpB"}
|
|
@@ -3,6 +3,7 @@ declare class PromiseTracker {
|
|
|
3
3
|
reset(): void;
|
|
4
4
|
promiseList: any[];
|
|
5
5
|
promiseMap: {};
|
|
6
|
+
dependencyGraph: {};
|
|
6
7
|
startTime: number;
|
|
7
8
|
cursor: number;
|
|
8
9
|
start(): void;
|
|
@@ -12,6 +13,9 @@ declare class PromiseTracker {
|
|
|
12
13
|
add(variable: any, promise: any, specifier: any, hasFilter: any, promiseKey: any): any;
|
|
13
14
|
contains(variable: any): boolean;
|
|
14
15
|
get(variable: any, specifier: any): any;
|
|
16
|
+
addDependency(from: any, to: any): void;
|
|
17
|
+
wouldCreateCycle(from: any, to: any): boolean;
|
|
18
|
+
getCyclePath(from: any, to: any): any[];
|
|
15
19
|
getPending(): any[];
|
|
16
20
|
getSettled(): any[];
|
|
17
21
|
getAll(): any[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromiseTracker.d.ts","sourceRoot":"","sources":["../../../src/utils/PromiseTracker.js"],"names":[],"mappings":";AAGA;IAIE,
|
|
1
|
+
{"version":3,"file":"PromiseTracker.d.ts","sourceRoot":"","sources":["../../../src/utils/PromiseTracker.js"],"names":[],"mappings":";AAGA;IAIE,cAOC;IANC,mBAAqB;IACrB,eAAoB;IAEpB,oBAAyB;IACzB,kBAA2B;IAC3B,eAAe;IAEjB,cAGC;IADC,yBAAyD;IAE3D,eAqBC;IACD,aAGC;IACD,uFA+BC;IACD,iCAEC;IACD,wCAIC;IAED,wCAKC;IAED,8CAqBC;IAED,wCAoBC;IACD,oBAEC;IACD,oBAEC;IACD,gBAEC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lodash.d.ts","sourceRoot":"","sources":["../../../src/utils/lodash.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lodash.d.ts","sourceRoot":"","sources":["../../../src/utils/lodash.js"],"names":[],"mappings":"AAkDA,mDAoBC;AApDD,6DA6BC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mergeByKeys.d.ts","sourceRoot":"","sources":["../../../../src/utils/parsing/mergeByKeys.js"],"names":[],"mappings":"AAAA;;GAEG;AACH,
|
|
1
|
+
{"version":3,"file":"mergeByKeys.d.ts","sourceRoot":"","sources":["../../../../src/utils/parsing/mergeByKeys.js"],"names":[],"mappings":"AAAA;;GAEG;AACH,yEAyBC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/utils/parsing/parse.js"],"names":[],"mappings":";;;;cAec,MAAM;;;;cACN,MAAM;;;;eACN,MAAM;;;;kBACN,cAAe;;;;;;
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/utils/parsing/parse.js"],"names":[],"mappings":";;;;cAec,MAAM;;;;cACN,MAAM;;;;eACN,MAAM;;;;kBACN,cAAe;;;;;;eAqGf,MAAM;;;;kBACN,cAAe;;AA3G7B;;;;;;GAMG;AAEH;;;;GAIG;AACH,iFAHW,YAAY,OA4FtB;AAED;;;;GAIG;AAEH;;;;;GAKG;AACH,oCAJW,MAAM,SACN,gBAAgB,OAW1B"}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
export namespace funcRegex {
|
|
2
|
+
export { parseFunctionCall as exec };
|
|
3
|
+
export function test(str: any): boolean;
|
|
4
|
+
export let source: string;
|
|
5
|
+
export function toString(): string;
|
|
6
|
+
}
|
|
1
7
|
/**
|
|
2
8
|
* Shared regex patterns and utilities
|
|
3
9
|
*/
|
|
4
|
-
export const
|
|
10
|
+
export const funcRegexSimple: RegExp;
|
|
5
11
|
export const funcStartOfLineRegex: RegExp;
|
|
6
12
|
export const subFunctionRegex: RegExp;
|
|
7
13
|
/**
|
|
@@ -10,5 +16,13 @@ export const subFunctionRegex: RegExp;
|
|
|
10
16
|
* @returns {RegExp} Combined regex with OR operator
|
|
11
17
|
*/
|
|
12
18
|
export function combineRegexes(regexes: RegExp[]): RegExp;
|
|
19
|
+
/**
|
|
20
|
+
* Parse a function call with balanced parentheses support
|
|
21
|
+
* Returns a regex-exec-like array: [fullMatch, funcName, args] with index and input properties
|
|
22
|
+
* or null if no function found
|
|
23
|
+
* @param {string} str - String to search for function call
|
|
24
|
+
* @returns {any} Regex-like result array or null
|
|
25
|
+
*/
|
|
26
|
+
export function parseFunctionCall(str: string): any;
|
|
13
27
|
export { funcRegex as functionRegex };
|
|
14
28
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/regex/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/regex/index.js"],"names":[],"mappings":";;IAgEQ,wCAAwC;;IAGpC,mCAAkC;;AAnE9C;;GAEG;AAGH,qCAA0D;AAC1D,0CAAgE;AAChE,sCAAiE;AA+DjE;;;;GAIG;AACH,wCAHW,MAAM,EAAE,GACN,MAAM,CAKlB;AArED;;;;;;GAMG;AACH,uCAHW,MAAM,GACJ,GAAG,CA0Cf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatFunctionArgs.d.ts","sourceRoot":"","sources":["../../../../src/utils/strings/formatFunctionArgs.js"],"names":[],"mappings":"AAiBiB,
|
|
1
|
+
{"version":3,"file":"formatFunctionArgs.d.ts","sourceRoot":"","sources":["../../../../src/utils/strings/formatFunctionArgs.js"],"names":[],"mappings":"AAiBiB,0CAWhB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Split a string by
|
|
2
|
+
* Split a string by delimiter while preserving quoted content and balanced parentheses
|
|
3
3
|
* NOTE: This is a simpler version that delegates to splitByComma for consistency.
|
|
4
4
|
* For advanced use cases with bracket depth tracking and regex protection, use splitByComma directly.
|
|
5
5
|
* @param {string} str - String to split
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"splitCsv.d.ts","sourceRoot":"","sources":["../../../../src/utils/strings/splitCsv.js"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,8BANW,MAAM,aACN,MAAM,YAEd;IAA0B,gBAAgB,GAAlC,OAAO;CACf,GAAU,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"splitCsv.d.ts","sourceRoot":"","sources":["../../../../src/utils/strings/splitCsv.js"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,8BANW,MAAM,aACN,MAAM,YAEd;IAA0B,gBAAgB,GAAlC,OAAO;CACf,GAAU,MAAM,EAAE,CA0DpB"}
|