lsh-framework 1.1.0 → 1.2.1

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 (73) hide show
  1. package/README.md +70 -4
  2. package/dist/cli.js +104 -486
  3. package/dist/commands/doctor.js +427 -0
  4. package/dist/commands/init.js +371 -0
  5. package/dist/constants/api.js +94 -0
  6. package/dist/constants/commands.js +64 -0
  7. package/dist/constants/config.js +56 -0
  8. package/dist/constants/database.js +21 -0
  9. package/dist/constants/errors.js +79 -0
  10. package/dist/constants/index.js +28 -0
  11. package/dist/constants/paths.js +28 -0
  12. package/dist/constants/ui.js +73 -0
  13. package/dist/constants/validation.js +124 -0
  14. package/dist/daemon/lshd.js +11 -32
  15. package/dist/lib/daemon-client-helper.js +7 -4
  16. package/dist/lib/daemon-client.js +9 -2
  17. package/dist/lib/format-utils.js +163 -0
  18. package/dist/lib/job-manager.js +2 -1
  19. package/dist/lib/platform-utils.js +211 -0
  20. package/dist/lib/secrets-manager.js +11 -1
  21. package/dist/lib/string-utils.js +128 -0
  22. package/dist/services/daemon/daemon-registrar.js +3 -2
  23. package/dist/services/secrets/secrets.js +154 -30
  24. package/package.json +10 -74
  25. package/dist/app.js +0 -33
  26. package/dist/cicd/analytics.js +0 -261
  27. package/dist/cicd/auth.js +0 -269
  28. package/dist/cicd/cache-manager.js +0 -172
  29. package/dist/cicd/data-retention.js +0 -305
  30. package/dist/cicd/performance-monitor.js +0 -224
  31. package/dist/cicd/webhook-receiver.js +0 -640
  32. package/dist/commands/api.js +0 -346
  33. package/dist/commands/theme.js +0 -261
  34. package/dist/commands/zsh-import.js +0 -240
  35. package/dist/components/App.js +0 -1
  36. package/dist/components/Divider.js +0 -29
  37. package/dist/components/REPL.js +0 -43
  38. package/dist/components/Terminal.js +0 -232
  39. package/dist/components/UserInput.js +0 -30
  40. package/dist/daemon/api-server.js +0 -316
  41. package/dist/daemon/monitoring-api.js +0 -220
  42. package/dist/lib/api-error-handler.js +0 -185
  43. package/dist/lib/associative-arrays.js +0 -285
  44. package/dist/lib/base-api-server.js +0 -290
  45. package/dist/lib/brace-expansion.js +0 -160
  46. package/dist/lib/builtin-commands.js +0 -439
  47. package/dist/lib/executors/builtin-executor.js +0 -52
  48. package/dist/lib/extended-globbing.js +0 -411
  49. package/dist/lib/extended-parameter-expansion.js +0 -227
  50. package/dist/lib/interactive-shell.js +0 -460
  51. package/dist/lib/job-builtins.js +0 -582
  52. package/dist/lib/pathname-expansion.js +0 -216
  53. package/dist/lib/script-runner.js +0 -226
  54. package/dist/lib/shell-executor.js +0 -2504
  55. package/dist/lib/shell-parser.js +0 -958
  56. package/dist/lib/shell-types.js +0 -6
  57. package/dist/lib/shell.lib.js +0 -40
  58. package/dist/lib/theme-manager.js +0 -476
  59. package/dist/lib/variable-expansion.js +0 -385
  60. package/dist/lib/zsh-compatibility.js +0 -659
  61. package/dist/lib/zsh-import-manager.js +0 -707
  62. package/dist/lib/zsh-options.js +0 -328
  63. package/dist/pipeline/job-tracker.js +0 -491
  64. package/dist/pipeline/mcli-bridge.js +0 -309
  65. package/dist/pipeline/pipeline-service.js +0 -1119
  66. package/dist/pipeline/workflow-engine.js +0 -870
  67. package/dist/services/api/api.js +0 -58
  68. package/dist/services/api/auth.js +0 -35
  69. package/dist/services/api/config.js +0 -7
  70. package/dist/services/api/file.js +0 -22
  71. package/dist/services/shell/shell.js +0 -28
  72. package/dist/services/zapier.js +0 -16
  73. package/dist/simple-api-server.js +0 -148
@@ -1,411 +0,0 @@
1
- /**
2
- * Extended Globbing Implementation
3
- * Provides ZSH-compatible extended globbing patterns
4
- */
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- export class ExtendedGlobber {
8
- cwd;
9
- constructor(cwd = process.cwd()) {
10
- this.cwd = cwd;
11
- }
12
- /**
13
- * Expand extended glob patterns
14
- */
15
- async expandPattern(pattern, options = {}) {
16
- const opts = {
17
- cwd: this.cwd,
18
- includeHidden: false,
19
- followSymlinks: false,
20
- extendedGlob: true,
21
- ...options,
22
- };
23
- // Handle exclusion patterns: *.txt~*backup*
24
- if (pattern.includes('~')) {
25
- return this.expandExclusionPattern(pattern, opts);
26
- }
27
- // Handle alternation patterns: (foo|bar).txt
28
- if (pattern.includes('(') && pattern.includes('|') && pattern.includes(')')) {
29
- return this.expandAlternationPattern(pattern, opts);
30
- }
31
- // Handle numeric ranges: <1-10>.txt
32
- if (pattern.includes('<') && pattern.includes('-') && pattern.includes('>')) {
33
- return this.expandNumericRange(pattern, opts);
34
- }
35
- // Handle qualifiers: *.txt(.L+10)
36
- if (pattern.includes('(') && pattern.includes('.')) {
37
- return this.expandQualifiedPattern(pattern, opts);
38
- }
39
- // Handle negation patterns: ^*.backup
40
- if (pattern.startsWith('^')) {
41
- return this.expandNegationPattern(pattern, opts);
42
- }
43
- // Handle recursive patterns: **/*.txt
44
- if (pattern.includes('**')) {
45
- return this.expandRecursivePattern(pattern, opts);
46
- }
47
- // Fall back to regular globbing
48
- return this.expandRegularPattern(pattern, opts);
49
- }
50
- /**
51
- * Expand exclusion patterns: *.txt~*backup*
52
- */
53
- async expandExclusionPattern(pattern, options) {
54
- const [includePattern, excludePattern] = pattern.split('~');
55
- const includeResults = await this.expandRegularPattern(includePattern, options);
56
- const excludeResults = await this.expandRegularPattern(excludePattern, options);
57
- return includeResults.filter(file => !excludeResults.includes(file));
58
- }
59
- /**
60
- * Expand alternation patterns: (foo|bar).txt
61
- */
62
- async expandAlternationPattern(pattern, options) {
63
- const results = [];
64
- // Find alternation groups
65
- const alternationRegex = /\(([^)]+)\)/g;
66
- let match;
67
- while ((match = alternationRegex.exec(pattern)) !== null) {
68
- const alternatives = match[1].split('|');
69
- const prefix = pattern.substring(0, match.index);
70
- const suffix = pattern.substring(match.index + match[0].length);
71
- for (const alt of alternatives) {
72
- const altPattern = prefix + alt + suffix;
73
- const altResults = await this.expandPattern(altPattern, options);
74
- results.push(...altResults);
75
- }
76
- }
77
- return [...new Set(results)]; // Remove duplicates
78
- }
79
- /**
80
- * Expand numeric ranges: <1-10>.txt
81
- */
82
- async expandNumericRange(pattern, options) {
83
- const results = [];
84
- const rangeRegex = /<(\d+)-(\d+)>/g;
85
- let match;
86
- while ((match = rangeRegex.exec(pattern)) !== null) {
87
- const start = parseInt(match[1], 10);
88
- const end = parseInt(match[2], 10);
89
- for (let i = start; i <= end; i++) {
90
- const altPattern = pattern.replace(match[0], i.toString());
91
- const altResults = await this.expandPattern(altPattern, options);
92
- results.push(...altResults);
93
- }
94
- }
95
- return [...new Set(results)];
96
- }
97
- /**
98
- * Expand patterns with qualifiers: *.txt(.L+10)
99
- */
100
- async expandQualifiedPattern(pattern, options) {
101
- const qualifierMatch = pattern.match(/^(.+)\(([^)]+)\)$/);
102
- if (!qualifierMatch)
103
- return [];
104
- const [, basePattern, qualifierStr] = qualifierMatch;
105
- const baseResults = await this.expandRegularPattern(basePattern, options);
106
- const qualifiers = this.parseQualifiers(qualifierStr);
107
- return this.filterByQualifiers(baseResults, qualifiers);
108
- }
109
- /**
110
- * Expand negation patterns: ^*.backup
111
- */
112
- async expandNegationPattern(pattern, options) {
113
- const negatedPattern = pattern.substring(1); // Remove ^
114
- const allFiles = await this.getAllFiles(options.cwd || this.cwd, options);
115
- const negatedFiles = await this.expandRegularPattern(negatedPattern, options);
116
- return allFiles.filter(file => !negatedFiles.includes(file));
117
- }
118
- /**
119
- * Expand recursive patterns: **\/*.txt
120
- */
121
- async expandRecursivePattern(pattern, options) {
122
- const results = [];
123
- const searchDir = options.cwd || this.cwd;
124
- // Convert **/*.txt to recursive search
125
- const recursivePattern = pattern.replace(/\*\*\//g, '');
126
- await this.searchRecursively(searchDir, recursivePattern, results, options);
127
- return results;
128
- }
129
- /**
130
- * Expand regular glob patterns
131
- */
132
- async expandRegularPattern(pattern, options) {
133
- const results = [];
134
- const searchDir = options.cwd || this.cwd;
135
- // Handle tilde expansion
136
- const expandedPattern = this.expandTilde(pattern);
137
- // Split pattern into segments
138
- const segments = expandedPattern.split('/').filter(seg => seg.length > 0);
139
- if (segments.length === 0) {
140
- return [searchDir];
141
- }
142
- await this.matchSegments(searchDir, segments, results, options);
143
- return results.sort();
144
- }
145
- /**
146
- * Parse qualifiers from string
147
- */
148
- parseQualifiers(qualifierStr) {
149
- const qualifiers = [];
150
- // Parse size qualifiers: L+10, L-5, L=100
151
- const sizeMatch = qualifierStr.match(/L([+\-=])(\d+)/);
152
- if (sizeMatch) {
153
- qualifiers.push({
154
- type: 'size',
155
- operator: sizeMatch[1],
156
- value: sizeMatch[2],
157
- });
158
- }
159
- // Parse time qualifiers: m-1 (modified within 1 day)
160
- const timeMatch = qualifierStr.match(/m([+\-=])(\d+)/);
161
- if (timeMatch) {
162
- qualifiers.push({
163
- type: 'time',
164
- operator: timeMatch[1],
165
- value: timeMatch[2],
166
- });
167
- }
168
- // Parse type qualifiers: f (file), d (directory)
169
- const typeMatch = qualifierStr.match(/[fd]/);
170
- if (typeMatch) {
171
- qualifiers.push({
172
- type: 'type',
173
- operator: '=',
174
- value: typeMatch[0],
175
- });
176
- }
177
- return qualifiers;
178
- }
179
- /**
180
- * Filter files by qualifiers
181
- */
182
- filterByQualifiers(files, qualifiers) {
183
- return files.filter(file => {
184
- try {
185
- const stats = fs.statSync(file);
186
- for (const qualifier of qualifiers) {
187
- if (!this.matchesQualifier(file, stats, qualifier)) {
188
- return false;
189
- }
190
- }
191
- return true;
192
- }
193
- catch {
194
- return false;
195
- }
196
- });
197
- }
198
- /**
199
- * Check if file matches a qualifier
200
- */
201
- matchesQualifier(file, stats, qualifier) {
202
- switch (qualifier.type) {
203
- case 'size': {
204
- const size = stats.size;
205
- const targetSize = parseInt(qualifier.value, 10);
206
- switch (qualifier.operator) {
207
- case '=': return size === targetSize;
208
- case '+': return size > targetSize;
209
- case '-': return size < targetSize;
210
- case '>': return size >= targetSize;
211
- default: return false;
212
- }
213
- }
214
- case 'time': {
215
- const now = Date.now();
216
- const fileTime = stats.mtime.getTime();
217
- const daysDiff = (now - fileTime) / (1000 * 60 * 60 * 24);
218
- const targetDays = parseInt(qualifier.value, 10);
219
- switch (qualifier.operator) {
220
- case '=': return Math.abs(daysDiff) <= targetDays;
221
- case '+': return daysDiff > targetDays;
222
- case '-': return daysDiff < targetDays;
223
- case '>': return daysDiff >= targetDays;
224
- default: return false;
225
- }
226
- }
227
- case 'type':
228
- switch (qualifier.value) {
229
- case 'f': return stats.isFile();
230
- case 'd': return stats.isDirectory();
231
- default: return false;
232
- }
233
- default:
234
- return true;
235
- }
236
- }
237
- /**
238
- * Search recursively for files
239
- */
240
- async searchRecursively(dir, pattern, results, options) {
241
- try {
242
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
243
- for (const entry of entries) {
244
- const fullPath = path.join(dir, entry.name);
245
- // Skip hidden files unless explicitly included
246
- if (!options.includeHidden && entry.name.startsWith('.')) {
247
- continue;
248
- }
249
- if (entry.isDirectory()) {
250
- // Recursively search subdirectories
251
- await this.searchRecursively(fullPath, pattern, results, options);
252
- }
253
- else if (entry.isFile()) {
254
- // Check if file matches pattern
255
- if (this.matchesPattern(entry.name, pattern)) {
256
- results.push(fullPath);
257
- }
258
- }
259
- }
260
- }
261
- catch {
262
- // Directory doesn't exist or not readable
263
- }
264
- }
265
- /**
266
- * Get all files in directory
267
- */
268
- async getAllFiles(dir, options) {
269
- const files = [];
270
- try {
271
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
272
- for (const entry of entries) {
273
- const fullPath = path.join(dir, entry.name);
274
- if (!options.includeHidden && entry.name.startsWith('.')) {
275
- continue;
276
- }
277
- if (entry.isFile()) {
278
- files.push(fullPath);
279
- }
280
- else if (entry.isDirectory()) {
281
- const subFiles = await this.getAllFiles(fullPath, options);
282
- files.push(...subFiles);
283
- }
284
- }
285
- }
286
- catch {
287
- // Directory doesn't exist or not readable
288
- }
289
- return files;
290
- }
291
- /**
292
- * Match segments recursively
293
- */
294
- async matchSegments(currentPath, remainingSegments, results, options) {
295
- if (remainingSegments.length === 0) {
296
- results.push(currentPath);
297
- return;
298
- }
299
- const [currentSegment, ...restSegments] = remainingSegments;
300
- try {
301
- const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
302
- for (const entry of entries) {
303
- if (!options.includeHidden && entry.name.startsWith('.')) {
304
- continue;
305
- }
306
- if (this.matchesPattern(entry.name, currentSegment)) {
307
- const fullPath = path.join(currentPath, entry.name);
308
- if (restSegments.length === 0) {
309
- results.push(fullPath);
310
- }
311
- else if (entry.isDirectory()) {
312
- await this.matchSegments(fullPath, restSegments, results, options);
313
- }
314
- }
315
- }
316
- }
317
- catch {
318
- // Directory doesn't exist or not readable
319
- }
320
- }
321
- /**
322
- * Check if filename matches pattern
323
- */
324
- matchesPattern(filename, pattern) {
325
- const regex = this.patternToRegex(pattern);
326
- return regex.test(filename);
327
- }
328
- /**
329
- * Convert glob pattern to regex
330
- */
331
- patternToRegex(pattern) {
332
- let regexStr = '';
333
- let i = 0;
334
- while (i < pattern.length) {
335
- const char = pattern[i];
336
- switch (char) {
337
- case '*':
338
- regexStr += '.*';
339
- break;
340
- case '?':
341
- regexStr += '.';
342
- break;
343
- case '[': {
344
- const closeIdx = this.findClosingBracket(pattern, i);
345
- if (closeIdx === -1) {
346
- regexStr += '\\[';
347
- }
348
- else {
349
- let charClass = pattern.slice(i + 1, closeIdx);
350
- if (charClass.startsWith('!') || charClass.startsWith('^')) {
351
- charClass = '^' + charClass.slice(1);
352
- }
353
- regexStr += '[' + charClass + ']';
354
- i = closeIdx;
355
- }
356
- break;
357
- }
358
- case '\\':
359
- if (i + 1 < pattern.length) {
360
- regexStr += '\\' + pattern[i + 1];
361
- i++;
362
- }
363
- else {
364
- regexStr += '\\\\';
365
- }
366
- break;
367
- default:
368
- regexStr += this.escapeRegex(char);
369
- break;
370
- }
371
- i++;
372
- }
373
- return new RegExp('^' + regexStr + '$');
374
- }
375
- /**
376
- * Find closing bracket
377
- */
378
- findClosingBracket(str, startIdx) {
379
- let depth = 1;
380
- for (let i = startIdx + 1; i < str.length; i++) {
381
- if (str[i] === '[')
382
- depth++;
383
- else if (str[i] === ']') {
384
- depth--;
385
- if (depth === 0)
386
- return i;
387
- }
388
- }
389
- return -1;
390
- }
391
- /**
392
- * Expand tilde
393
- */
394
- expandTilde(pattern) {
395
- if (pattern.startsWith('~/')) {
396
- const homeDir = process.env.HOME || '/';
397
- return path.join(homeDir, pattern.slice(2));
398
- }
399
- if (pattern === '~') {
400
- return process.env.HOME || '/';
401
- }
402
- return pattern;
403
- }
404
- /**
405
- * Escape regex special characters
406
- */
407
- escapeRegex(str) {
408
- return str.replace(/[.+^$()|[\]{}\\]/g, '\\$&');
409
- }
410
- }
411
- export default ExtendedGlobber;
@@ -1,227 +0,0 @@
1
- /**
2
- * Extended Parameter Expansion Implementation
3
- * Provides ZSH-compatible parameter expansion features
4
- */
5
- export class ExtendedParameterExpander {
6
- context;
7
- constructor(context) {
8
- this.context = context;
9
- }
10
- /**
11
- * Expand extended parameter expressions
12
- */
13
- expandParameter(paramExpr) {
14
- // Handle ZSH-style parameter expansion patterns
15
- // Global substitution: ${name:gs/old/new}
16
- if (paramExpr.includes(':gs/')) {
17
- return this.handleGlobalSubstitution(paramExpr);
18
- }
19
- // Case conversion: ${name:l} or ${name:u}
20
- if (paramExpr.match(/:[lu]$/)) {
21
- return this.handleCaseConversion(paramExpr);
22
- }
23
- // Array slicing: ${array[2,4]}
24
- if (paramExpr.includes('[') && paramExpr.includes(',') && paramExpr.includes(']')) {
25
- return this.handleArraySlicing(paramExpr);
26
- }
27
- // Parameter type: ${(t)var}
28
- if (paramExpr.match(/^\(t\)/)) {
29
- return this.handleParameterType(paramExpr);
30
- }
31
- // Array keys: ${(k)array}
32
- if (paramExpr.match(/^\(k\)/)) {
33
- return this.handleArrayKeys(paramExpr);
34
- }
35
- // Array values: ${(v)array}
36
- if (paramExpr.match(/^\(v\)/)) {
37
- return this.handleArrayValues(paramExpr);
38
- }
39
- // Array length: ${#array}
40
- if (paramExpr.startsWith('#')) {
41
- return this.handleArrayLength(paramExpr);
42
- }
43
- // Default to regular parameter expansion
44
- return this.getParameterValue(paramExpr);
45
- }
46
- /**
47
- * Handle global substitution: ${name:gs/old/new}
48
- */
49
- handleGlobalSubstitution(paramExpr) {
50
- const match = paramExpr.match(/^([^:]+):gs\/([^/]+)\/(.*)$/);
51
- if (!match)
52
- return this.getParameterValue(paramExpr);
53
- const [, param, oldPattern, newPattern] = match;
54
- const value = this.getParameterValue(param);
55
- // Convert shell pattern to regex
56
- const regex = this.patternToRegex(oldPattern);
57
- return value.replace(regex, newPattern);
58
- }
59
- /**
60
- * Handle case conversion: ${name:l} or ${name:u}
61
- */
62
- handleCaseConversion(paramExpr) {
63
- const match = paramExpr.match(/^([^:]+):([lu])$/);
64
- if (!match)
65
- return this.getParameterValue(paramExpr);
66
- const [, param, conversion] = match;
67
- const value = this.getParameterValue(param);
68
- switch (conversion) {
69
- case 'l': return value.toLowerCase();
70
- case 'u': return value.toUpperCase();
71
- default: return value;
72
- }
73
- }
74
- /**
75
- * Handle array slicing: ${array[2,4]}
76
- */
77
- handleArraySlicing(paramExpr) {
78
- const match = paramExpr.match(/^([^[\]]+)\[(\d+),(\d+)\]$/);
79
- if (!match)
80
- return this.getParameterValue(paramExpr);
81
- const [, arrayName, startStr, endStr] = match;
82
- const start = parseInt(startStr, 10);
83
- const end = parseInt(endStr, 10);
84
- if (!this.context.arrays.hasArray(arrayName)) {
85
- return '';
86
- }
87
- const slice = this.context.arrays.getSlice(arrayName, start, end);
88
- return slice.join(' ');
89
- }
90
- /**
91
- * Handle parameter type: ${(t)var}
92
- */
93
- handleParameterType(paramExpr) {
94
- const match = paramExpr.match(/^\(t\)(.+)$/);
95
- if (!match)
96
- return '';
97
- const param = match[1];
98
- if (this.context.arrays.hasArray(param)) {
99
- const type = this.context.arrays.getArrayType(param);
100
- return type === 'associative' ? 'association' : 'array';
101
- }
102
- if (param in this.context.variables || param in this.context.env) {
103
- return 'scalar';
104
- }
105
- return 'unset';
106
- }
107
- /**
108
- * Handle array keys: ${(k)array}
109
- */
110
- handleArrayKeys(paramExpr) {
111
- const match = paramExpr.match(/^\(k\)(.+)$/);
112
- if (!match)
113
- return '';
114
- const arrayName = match[1];
115
- if (!this.context.arrays.hasArray(arrayName)) {
116
- return '';
117
- }
118
- const keys = this.context.arrays.getKeys(arrayName);
119
- return keys.join(' ');
120
- }
121
- /**
122
- * Handle array values: ${(v)array}
123
- */
124
- handleArrayValues(paramExpr) {
125
- const match = paramExpr.match(/^\(v\)(.+)$/);
126
- if (!match)
127
- return '';
128
- const arrayName = match[1];
129
- if (!this.context.arrays.hasArray(arrayName)) {
130
- return '';
131
- }
132
- const values = this.context.arrays.getValues(arrayName);
133
- return values.join(' ');
134
- }
135
- /**
136
- * Handle array length: ${#array}
137
- */
138
- handleArrayLength(paramExpr) {
139
- const arrayName = paramExpr.substring(1); // Remove #
140
- if (!this.context.arrays.hasArray(arrayName)) {
141
- return '0';
142
- }
143
- return this.context.arrays.getLength(arrayName).toString();
144
- }
145
- /**
146
- * Get parameter value from variables or environment
147
- */
148
- getParameterValue(param) {
149
- // Check arrays first
150
- if (this.context.arrays.hasArray(param)) {
151
- const values = this.context.arrays.getValues(param);
152
- return values.join(' ');
153
- }
154
- // Check regular variables
155
- if (param in this.context.variables) {
156
- return this.context.variables[param];
157
- }
158
- // Check environment variables
159
- if (param in this.context.env) {
160
- return this.context.env[param];
161
- }
162
- return '';
163
- }
164
- /**
165
- * Convert shell pattern to regex
166
- */
167
- patternToRegex(pattern) {
168
- const regex = pattern
169
- .replace(/\\/g, '\\\\') // Escape backslashes first
170
- .replace(/\*/g, '.*') // * matches any string
171
- .replace(/\?/g, '.') // ? matches any single character
172
- .replace(/\[([^\]]+)\]/g, '[$1]') // [abc] character class
173
- .replace(/\./g, '\\.') // Escape dots
174
- .replace(/\+/g, '\\+') // Escape plus signs
175
- .replace(/\^/g, '\\^') // Escape carets
176
- .replace(/\$/g, '\\$') // Escape dollar signs
177
- .replace(/\(/g, '\\(') // Escape parentheses
178
- .replace(/\)/g, '\\)') // Escape parentheses
179
- .replace(/\|/g, '\\|') // Escape pipes
180
- .replace(/\{/g, '\\{') // Escape braces
181
- .replace(/\}/g, '\\}'); // Escape braces
182
- return new RegExp(regex, 'g');
183
- }
184
- /**
185
- * Expand complex parameter expressions
186
- */
187
- expandComplexParameter(paramExpr) {
188
- // Handle nested expansions and complex expressions
189
- let result = paramExpr;
190
- // Process from innermost to outermost
191
- while (result.includes('${')) {
192
- const start = result.lastIndexOf('${');
193
- const end = result.indexOf('}', start);
194
- if (end === -1)
195
- break;
196
- const innerExpr = result.substring(start + 2, end);
197
- const expanded = this.expandParameter(innerExpr);
198
- result = result.substring(0, start) + expanded + result.substring(end + 1);
199
- }
200
- return result;
201
- }
202
- /**
203
- * Check if a parameter expression is an array reference
204
- */
205
- isArrayReference(paramExpr) {
206
- return (paramExpr.match(/^\([kv]\)/) !== null ||
207
- paramExpr.startsWith('#') ||
208
- paramExpr.includes('[') && paramExpr.includes(']'));
209
- }
210
- /**
211
- * Get array reference type
212
- */
213
- getArrayReferenceType(paramExpr) {
214
- if (paramExpr.match(/^\(k\)/))
215
- return 'keys';
216
- if (paramExpr.match(/^\(v\)/))
217
- return 'values';
218
- if (paramExpr.startsWith('#'))
219
- return 'length';
220
- if (paramExpr.includes('[') && paramExpr.includes(','))
221
- return 'slice';
222
- if (paramExpr.includes('[') && paramExpr.includes(']'))
223
- return 'element';
224
- return 'none';
225
- }
226
- }
227
- export default ExtendedParameterExpander;