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.
- package/README.md +70 -4
- package/dist/cli.js +104 -486
- package/dist/commands/doctor.js +427 -0
- package/dist/commands/init.js +371 -0
- package/dist/constants/api.js +94 -0
- package/dist/constants/commands.js +64 -0
- package/dist/constants/config.js +56 -0
- package/dist/constants/database.js +21 -0
- package/dist/constants/errors.js +79 -0
- package/dist/constants/index.js +28 -0
- package/dist/constants/paths.js +28 -0
- package/dist/constants/ui.js +73 -0
- package/dist/constants/validation.js +124 -0
- package/dist/daemon/lshd.js +11 -32
- package/dist/lib/daemon-client-helper.js +7 -4
- package/dist/lib/daemon-client.js +9 -2
- package/dist/lib/format-utils.js +163 -0
- package/dist/lib/job-manager.js +2 -1
- package/dist/lib/platform-utils.js +211 -0
- package/dist/lib/secrets-manager.js +11 -1
- package/dist/lib/string-utils.js +128 -0
- package/dist/services/daemon/daemon-registrar.js +3 -2
- package/dist/services/secrets/secrets.js +154 -30
- package/package.json +10 -74
- package/dist/app.js +0 -33
- package/dist/cicd/analytics.js +0 -261
- package/dist/cicd/auth.js +0 -269
- package/dist/cicd/cache-manager.js +0 -172
- package/dist/cicd/data-retention.js +0 -305
- package/dist/cicd/performance-monitor.js +0 -224
- package/dist/cicd/webhook-receiver.js +0 -640
- package/dist/commands/api.js +0 -346
- package/dist/commands/theme.js +0 -261
- package/dist/commands/zsh-import.js +0 -240
- package/dist/components/App.js +0 -1
- package/dist/components/Divider.js +0 -29
- package/dist/components/REPL.js +0 -43
- package/dist/components/Terminal.js +0 -232
- package/dist/components/UserInput.js +0 -30
- package/dist/daemon/api-server.js +0 -316
- package/dist/daemon/monitoring-api.js +0 -220
- package/dist/lib/api-error-handler.js +0 -185
- package/dist/lib/associative-arrays.js +0 -285
- package/dist/lib/base-api-server.js +0 -290
- package/dist/lib/brace-expansion.js +0 -160
- package/dist/lib/builtin-commands.js +0 -439
- package/dist/lib/executors/builtin-executor.js +0 -52
- package/dist/lib/extended-globbing.js +0 -411
- package/dist/lib/extended-parameter-expansion.js +0 -227
- package/dist/lib/interactive-shell.js +0 -460
- package/dist/lib/job-builtins.js +0 -582
- package/dist/lib/pathname-expansion.js +0 -216
- package/dist/lib/script-runner.js +0 -226
- package/dist/lib/shell-executor.js +0 -2504
- package/dist/lib/shell-parser.js +0 -958
- package/dist/lib/shell-types.js +0 -6
- package/dist/lib/shell.lib.js +0 -40
- package/dist/lib/theme-manager.js +0 -476
- package/dist/lib/variable-expansion.js +0 -385
- package/dist/lib/zsh-compatibility.js +0 -659
- package/dist/lib/zsh-import-manager.js +0 -707
- package/dist/lib/zsh-options.js +0 -328
- package/dist/pipeline/job-tracker.js +0 -491
- package/dist/pipeline/mcli-bridge.js +0 -309
- package/dist/pipeline/pipeline-service.js +0 -1119
- package/dist/pipeline/workflow-engine.js +0 -870
- package/dist/services/api/api.js +0 -58
- package/dist/services/api/auth.js +0 -35
- package/dist/services/api/config.js +0 -7
- package/dist/services/api/file.js +0 -22
- package/dist/services/shell/shell.js +0 -28
- package/dist/services/zapier.js +0 -16
- package/dist/simple-api-server.js +0 -148
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POSIX Parameter and Variable Expansion Implementation
|
|
3
|
-
* Implements POSIX.1-2017 Section 2.6 Parameter Expansion
|
|
4
|
-
*/
|
|
5
|
-
import { exec } from 'child_process';
|
|
6
|
-
import { promisify } from 'util';
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
export class VariableExpander {
|
|
9
|
-
context;
|
|
10
|
-
constructor(context) {
|
|
11
|
-
this.context = context;
|
|
12
|
-
}
|
|
13
|
-
updateContext(updates) {
|
|
14
|
-
this.context = { ...this.context, ...updates };
|
|
15
|
-
}
|
|
16
|
-
async expandString(input) {
|
|
17
|
-
let result = input;
|
|
18
|
-
// Process in order: parameter expansion, command substitution, arithmetic expansion
|
|
19
|
-
result = await this.processParameterExpansion(result);
|
|
20
|
-
result = await this.processCommandSubstitution(result);
|
|
21
|
-
result = await this.processArithmeticExpansion(result);
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
async processParameterExpansion(input) {
|
|
25
|
-
// Handle ${parameter} and $parameter forms
|
|
26
|
-
// More comprehensive regex to handle all parameter expansion forms
|
|
27
|
-
const paramRegex = /\$\{([^}]+)\}|\$([a-zA-Z_][a-zA-Z0-9_]*|\d+|[*@#?$!-])/g;
|
|
28
|
-
let result = input;
|
|
29
|
-
// Process all matches from right to left to avoid index shifting issues
|
|
30
|
-
const matches = Array.from(input.matchAll(paramRegex));
|
|
31
|
-
for (let i = matches.length - 1; i >= 0; i--) {
|
|
32
|
-
const match = matches[i];
|
|
33
|
-
const fullMatch = match[0];
|
|
34
|
-
const paramExpr = match[1] || match[2];
|
|
35
|
-
const startIndex = match.index;
|
|
36
|
-
const endIndex = startIndex + fullMatch.length;
|
|
37
|
-
const expandedValue = await this.expandParameter(paramExpr);
|
|
38
|
-
// Replace the match with the expanded value
|
|
39
|
-
result = result.slice(0, startIndex) + expandedValue + result.slice(endIndex);
|
|
40
|
-
}
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
async expandParameter(paramExpr) {
|
|
44
|
-
// Handle different parameter expansion forms
|
|
45
|
-
// String length: ${#VAR}
|
|
46
|
-
if (paramExpr.startsWith('#')) {
|
|
47
|
-
return this.handleStringLength(paramExpr.substring(1));
|
|
48
|
-
}
|
|
49
|
-
// Substring extraction: ${VAR:offset:length}
|
|
50
|
-
if (paramExpr.match(/^[^:]+:-?\d+/)) {
|
|
51
|
-
return this.handleSubstring(paramExpr);
|
|
52
|
-
}
|
|
53
|
-
// Case conversion: ${VAR^}, ${VAR,}, ${VAR^^}, ${VAR,,}
|
|
54
|
-
if (paramExpr.match(/\^+$|,+$/)) {
|
|
55
|
-
return this.handleCaseConversion(paramExpr);
|
|
56
|
-
}
|
|
57
|
-
// Check for parameter expansion operators
|
|
58
|
-
if (paramExpr.includes(':-')) {
|
|
59
|
-
return this.handleDefaultValue(paramExpr, ':-');
|
|
60
|
-
}
|
|
61
|
-
else if (paramExpr.includes(':=')) {
|
|
62
|
-
return this.handleAssignDefault(paramExpr, ':=');
|
|
63
|
-
}
|
|
64
|
-
else if (paramExpr.includes(':?')) {
|
|
65
|
-
return this.handleErrorIfNull(paramExpr, ':?');
|
|
66
|
-
}
|
|
67
|
-
else if (paramExpr.includes(':+')) {
|
|
68
|
-
return this.handleAlternativeValue(paramExpr, ':+');
|
|
69
|
-
}
|
|
70
|
-
else if (paramExpr.includes('%')) {
|
|
71
|
-
return this.handleSuffixRemoval(paramExpr);
|
|
72
|
-
}
|
|
73
|
-
else if (paramExpr.includes('#')) {
|
|
74
|
-
return this.handlePrefixRemoval(paramExpr);
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
// Simple parameter expansion
|
|
78
|
-
return this.getParameterValue(paramExpr);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
getParameterValue(param) {
|
|
82
|
-
// Handle special parameters
|
|
83
|
-
if (param in this.context.specialParams) {
|
|
84
|
-
const value = this.context.specialParams[param];
|
|
85
|
-
return Array.isArray(value) ? value.join(' ') : value;
|
|
86
|
-
}
|
|
87
|
-
// Handle positional parameters
|
|
88
|
-
if (/^\d+$/.test(param)) {
|
|
89
|
-
const index = parseInt(param, 10);
|
|
90
|
-
if (index === 0)
|
|
91
|
-
return this.context.specialParams['0'];
|
|
92
|
-
const value = this.context.positionalParams[index - 1];
|
|
93
|
-
// Implement set -u (nounset): error on unset parameters
|
|
94
|
-
if (this.context.options?.nounset && value === undefined) {
|
|
95
|
-
throw new Error(`${param}: parameter not set`);
|
|
96
|
-
}
|
|
97
|
-
return value || '';
|
|
98
|
-
}
|
|
99
|
-
// Handle regular variables
|
|
100
|
-
const value = this.context.variables[param] || this.context.env[param];
|
|
101
|
-
// Implement set -u (nounset): error on unset variables
|
|
102
|
-
if (this.context.options?.nounset && value === undefined && !(param in this.context.variables) && !(param in this.context.env)) {
|
|
103
|
-
throw new Error(`${param}: parameter not set`);
|
|
104
|
-
}
|
|
105
|
-
return value || '';
|
|
106
|
-
}
|
|
107
|
-
handleDefaultValue(paramExpr, operator) {
|
|
108
|
-
const parts = paramExpr.split(operator);
|
|
109
|
-
const param = parts[0].trim();
|
|
110
|
-
const defaultValue = parts.slice(1).join(operator).trim();
|
|
111
|
-
const value = this.getParameterValue(param);
|
|
112
|
-
// Use default if parameter is unset or null (for :- operator)
|
|
113
|
-
return (value === '' || value === undefined) ? (defaultValue || '') : value;
|
|
114
|
-
}
|
|
115
|
-
handleAssignDefault(paramExpr, operator) {
|
|
116
|
-
const parts = paramExpr.split(operator);
|
|
117
|
-
const param = parts[0].trim();
|
|
118
|
-
const defaultValue = parts.slice(1).join(operator).trim();
|
|
119
|
-
let value = this.getParameterValue(param);
|
|
120
|
-
// Assign default if parameter is unset or null
|
|
121
|
-
if (value === '' || value === undefined) {
|
|
122
|
-
value = defaultValue || '';
|
|
123
|
-
this.context.variables[param] = value;
|
|
124
|
-
}
|
|
125
|
-
return value;
|
|
126
|
-
}
|
|
127
|
-
handleErrorIfNull(paramExpr, operator) {
|
|
128
|
-
const parts = paramExpr.split(operator);
|
|
129
|
-
const param = parts[0].trim();
|
|
130
|
-
const errorMessage = parts.slice(1).join(operator).trim();
|
|
131
|
-
const value = this.getParameterValue(param);
|
|
132
|
-
if (value === '' || value === undefined) {
|
|
133
|
-
const message = errorMessage || `${param}: parameter null or not set`;
|
|
134
|
-
throw new Error(message);
|
|
135
|
-
}
|
|
136
|
-
return value;
|
|
137
|
-
}
|
|
138
|
-
handleAlternativeValue(paramExpr, operator) {
|
|
139
|
-
const parts = paramExpr.split(operator);
|
|
140
|
-
const param = parts[0].trim();
|
|
141
|
-
const altValue = parts.slice(1).join(operator).trim();
|
|
142
|
-
const value = this.getParameterValue(param);
|
|
143
|
-
// Use alternative value if parameter is set and not null
|
|
144
|
-
return (value !== '' && value !== undefined) ? (altValue || '') : '';
|
|
145
|
-
}
|
|
146
|
-
handleSuffixRemoval(paramExpr) {
|
|
147
|
-
const isLongest = paramExpr.includes('%%');
|
|
148
|
-
const operator = isLongest ? '%%' : '%';
|
|
149
|
-
const [param, pattern] = paramExpr.split(operator, 2);
|
|
150
|
-
const value = this.getParameterValue(param);
|
|
151
|
-
if (!value)
|
|
152
|
-
return '';
|
|
153
|
-
return this.removeSuffix(value, pattern, isLongest);
|
|
154
|
-
}
|
|
155
|
-
handlePrefixRemoval(paramExpr) {
|
|
156
|
-
const isLongest = paramExpr.includes('##');
|
|
157
|
-
const operator = isLongest ? '##' : '#';
|
|
158
|
-
const [param, pattern] = paramExpr.split(operator, 2);
|
|
159
|
-
const value = this.getParameterValue(param);
|
|
160
|
-
if (!value)
|
|
161
|
-
return '';
|
|
162
|
-
return this.removePrefix(value, pattern, isLongest);
|
|
163
|
-
}
|
|
164
|
-
removeSuffix(value, pattern, longest) {
|
|
165
|
-
const regex = this.patternToRegex(pattern);
|
|
166
|
-
if (longest) {
|
|
167
|
-
// Find longest matching suffix
|
|
168
|
-
for (let i = 0; i < value.length; i++) {
|
|
169
|
-
const suffix = value.slice(i);
|
|
170
|
-
if (regex.test(suffix)) {
|
|
171
|
-
return value.slice(0, i);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
// Find shortest matching suffix
|
|
177
|
-
for (let i = value.length; i > 0; i--) {
|
|
178
|
-
const suffix = value.slice(i - 1);
|
|
179
|
-
if (regex.test(suffix)) {
|
|
180
|
-
return value.slice(0, i - 1);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return value;
|
|
185
|
-
}
|
|
186
|
-
removePrefix(value, pattern, longest) {
|
|
187
|
-
const regex = this.patternToRegex(pattern);
|
|
188
|
-
if (longest) {
|
|
189
|
-
// Find longest matching prefix
|
|
190
|
-
for (let i = value.length; i > 0; i--) {
|
|
191
|
-
const prefix = value.slice(0, i);
|
|
192
|
-
if (regex.test(prefix)) {
|
|
193
|
-
return value.slice(i);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
// Find shortest matching prefix
|
|
199
|
-
for (let i = 1; i <= value.length; i++) {
|
|
200
|
-
const prefix = value.slice(0, i);
|
|
201
|
-
if (regex.test(prefix)) {
|
|
202
|
-
return value.slice(i);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return value;
|
|
207
|
-
}
|
|
208
|
-
handleStringLength(param) {
|
|
209
|
-
const value = this.getParameterValue(param);
|
|
210
|
-
return value.length.toString();
|
|
211
|
-
}
|
|
212
|
-
handleSubstring(paramExpr) {
|
|
213
|
-
// Parse ${VAR:offset:length} or ${VAR:offset}
|
|
214
|
-
const match = paramExpr.match(/^([^:]+):(-?\d+)(?::(\d+))?$/);
|
|
215
|
-
if (!match) {
|
|
216
|
-
// Invalid format, return empty
|
|
217
|
-
return '';
|
|
218
|
-
}
|
|
219
|
-
const [, param, offsetStr, lengthStr] = match;
|
|
220
|
-
const value = this.getParameterValue(param);
|
|
221
|
-
let offset = parseInt(offsetStr, 10);
|
|
222
|
-
// Handle negative offset (from end of string)
|
|
223
|
-
if (offset < 0) {
|
|
224
|
-
offset = value.length + offset;
|
|
225
|
-
if (offset < 0)
|
|
226
|
-
offset = 0;
|
|
227
|
-
}
|
|
228
|
-
if (lengthStr) {
|
|
229
|
-
const length = parseInt(lengthStr, 10);
|
|
230
|
-
return value.substring(offset, offset + length);
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
return value.substring(offset);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
handleCaseConversion(paramExpr) {
|
|
237
|
-
// Handle ${VAR^}, ${VAR,}, ${VAR^^}, ${VAR,,}
|
|
238
|
-
let operator = '';
|
|
239
|
-
let param = paramExpr;
|
|
240
|
-
if (paramExpr.endsWith('^^')) {
|
|
241
|
-
operator = '^^';
|
|
242
|
-
param = paramExpr.slice(0, -2);
|
|
243
|
-
}
|
|
244
|
-
else if (paramExpr.endsWith('^')) {
|
|
245
|
-
operator = '^';
|
|
246
|
-
param = paramExpr.slice(0, -1);
|
|
247
|
-
}
|
|
248
|
-
else if (paramExpr.endsWith(',,')) {
|
|
249
|
-
operator = ',,';
|
|
250
|
-
param = paramExpr.slice(0, -2);
|
|
251
|
-
}
|
|
252
|
-
else if (paramExpr.endsWith(',')) {
|
|
253
|
-
operator = ',';
|
|
254
|
-
param = paramExpr.slice(0, -1);
|
|
255
|
-
}
|
|
256
|
-
const value = this.getParameterValue(param);
|
|
257
|
-
switch (operator) {
|
|
258
|
-
case '^':
|
|
259
|
-
// Uppercase first character
|
|
260
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
261
|
-
case '^^':
|
|
262
|
-
// Uppercase all characters
|
|
263
|
-
return value.toUpperCase();
|
|
264
|
-
case ',':
|
|
265
|
-
// Lowercase first character
|
|
266
|
-
return value.charAt(0).toLowerCase() + value.slice(1);
|
|
267
|
-
case ',,':
|
|
268
|
-
// Lowercase all characters
|
|
269
|
-
return value.toLowerCase();
|
|
270
|
-
default:
|
|
271
|
-
return value;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
patternToRegex(pattern) {
|
|
275
|
-
// Convert shell pattern to regex
|
|
276
|
-
const regex = pattern
|
|
277
|
-
.replace(/\*/g, '.*') // * matches any string
|
|
278
|
-
.replace(/\?/g, '.') // ? matches any single character
|
|
279
|
-
.replace(/\[([^\]]+)\]/g, '[$1]'); // [abc] character class
|
|
280
|
-
return new RegExp(`^${regex}$`);
|
|
281
|
-
}
|
|
282
|
-
async processCommandSubstitution(input) {
|
|
283
|
-
// Handle both $(command) and `command` forms
|
|
284
|
-
const dollarParenRegex = /\$\(([^)]+)\)/g;
|
|
285
|
-
const backtickRegex = /`([^`]+)`/g;
|
|
286
|
-
let result = input;
|
|
287
|
-
// Process $(command) form
|
|
288
|
-
result = await this.processSubstitutionWithRegex(result, dollarParenRegex);
|
|
289
|
-
// Process `command` form
|
|
290
|
-
result = await this.processSubstitutionWithRegex(result, backtickRegex);
|
|
291
|
-
return result;
|
|
292
|
-
}
|
|
293
|
-
async processSubstitutionWithRegex(input, regex) {
|
|
294
|
-
let result = '';
|
|
295
|
-
let lastIndex = 0;
|
|
296
|
-
let match;
|
|
297
|
-
while ((match = regex.exec(input)) !== null) {
|
|
298
|
-
result += input.slice(lastIndex, match.index);
|
|
299
|
-
const command = match[1];
|
|
300
|
-
try {
|
|
301
|
-
const { stdout } = await execAsync(command, {
|
|
302
|
-
env: { ...this.context.env, ...this.context.variables },
|
|
303
|
-
});
|
|
304
|
-
// Remove trailing newlines as per POSIX
|
|
305
|
-
result += stdout.replace(/\n+$/, '');
|
|
306
|
-
}
|
|
307
|
-
catch (_error) {
|
|
308
|
-
// Command substitution failed, leave empty
|
|
309
|
-
result += '';
|
|
310
|
-
}
|
|
311
|
-
lastIndex = regex.lastIndex;
|
|
312
|
-
}
|
|
313
|
-
result += input.slice(lastIndex);
|
|
314
|
-
return result;
|
|
315
|
-
}
|
|
316
|
-
async processArithmeticExpansion(input) {
|
|
317
|
-
const arithmeticRegex = /\$\(\(([^)]+)\)\)/g;
|
|
318
|
-
let result = input;
|
|
319
|
-
const matches = Array.from(input.matchAll(arithmeticRegex));
|
|
320
|
-
// Process all matches from right to left to avoid index shifting issues
|
|
321
|
-
for (let i = matches.length - 1; i >= 0; i--) {
|
|
322
|
-
const match = matches[i];
|
|
323
|
-
const fullMatch = match[0];
|
|
324
|
-
const expression = match[1];
|
|
325
|
-
const startIndex = match.index;
|
|
326
|
-
const endIndex = startIndex + fullMatch.length;
|
|
327
|
-
const value = this.evaluateArithmetic(expression);
|
|
328
|
-
// Replace the match with the evaluated value
|
|
329
|
-
result = result.slice(0, startIndex) + value.toString() + result.slice(endIndex);
|
|
330
|
-
}
|
|
331
|
-
return result;
|
|
332
|
-
}
|
|
333
|
-
evaluateArithmetic(expression) {
|
|
334
|
-
// Simple arithmetic evaluator
|
|
335
|
-
// Replace variables with their numeric values
|
|
336
|
-
const expr = expression.replace(/[a-zA-Z_][a-zA-Z0-9_]*/g, (match) => {
|
|
337
|
-
const value = this.getParameterValue(match);
|
|
338
|
-
const numValue = parseInt(value, 10);
|
|
339
|
-
return isNaN(numValue) ? '0' : numValue.toString();
|
|
340
|
-
});
|
|
341
|
-
try {
|
|
342
|
-
// Use Function constructor for safe evaluation
|
|
343
|
-
// This is a simplified version - full implementation would need proper arithmetic parser
|
|
344
|
-
return Function(`"use strict"; return (${expr})`)() || 0;
|
|
345
|
-
}
|
|
346
|
-
catch (_error) {
|
|
347
|
-
return 0;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
// Public method to expand parameter expressions
|
|
351
|
-
async expandParameterExpression(paramExpr) {
|
|
352
|
-
return this.expandParameter(paramExpr);
|
|
353
|
-
}
|
|
354
|
-
// Public method to evaluate arithmetic expressions
|
|
355
|
-
evaluateArithmeticExpression(expression) {
|
|
356
|
-
return this.evaluateArithmetic(expression);
|
|
357
|
-
}
|
|
358
|
-
// Utility method for field splitting (will be used by shell executor)
|
|
359
|
-
splitFields(input, ifs = ' \t\n') {
|
|
360
|
-
if (!ifs)
|
|
361
|
-
return [input]; // No field splitting if IFS is empty
|
|
362
|
-
if (ifs === ' \t\n') {
|
|
363
|
-
// Default IFS behavior - split on any whitespace and trim
|
|
364
|
-
return input.trim().split(/\s+/).filter(field => field.length > 0);
|
|
365
|
-
}
|
|
366
|
-
// Custom IFS - more complex splitting rules
|
|
367
|
-
const fields = [];
|
|
368
|
-
let currentField = '';
|
|
369
|
-
for (const char of input) {
|
|
370
|
-
if (ifs.includes(char)) {
|
|
371
|
-
if (currentField || fields.length === 0) {
|
|
372
|
-
fields.push(currentField);
|
|
373
|
-
currentField = '';
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
currentField += char;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (currentField || fields.length === 0) {
|
|
381
|
-
fields.push(currentField);
|
|
382
|
-
}
|
|
383
|
-
return fields;
|
|
384
|
-
}
|
|
385
|
-
}
|