vladx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -0
- package/bin/cli.js +486 -0
- package/bin/vlad.js +539 -0
- package/bin/vladpm.js +710 -0
- package/bin/vladx.js +491 -0
- package/package.json +57 -0
- package/src/engine/jit-compiler.js +285 -0
- package/src/engine/vladx-engine.js +941 -0
- package/src/index.js +44 -0
- package/src/interpreter/interpreter.js +2114 -0
- package/src/lexer/lexer.js +658 -0
- package/src/lexer/optimized-lexer.js +106 -0
- package/src/lexer/regex-cache.js +83 -0
- package/src/parser/ast-nodes.js +472 -0
- package/src/parser/parser.js +1408 -0
- package/src/runtime/advanced-type-system.js +209 -0
- package/src/runtime/async-manager.js +252 -0
- package/src/runtime/builtins.js +143 -0
- package/src/runtime/bundler.js +422 -0
- package/src/runtime/cache-manager.js +126 -0
- package/src/runtime/data-structures.js +612 -0
- package/src/runtime/debugger.js +260 -0
- package/src/runtime/enhanced-module-system.js +196 -0
- package/src/runtime/environment-enhanced.js +272 -0
- package/src/runtime/environment.js +140 -0
- package/src/runtime/event-emitter.js +232 -0
- package/src/runtime/formatter.js +280 -0
- package/src/runtime/functional.js +359 -0
- package/src/runtime/io-operations.js +390 -0
- package/src/runtime/linter.js +374 -0
- package/src/runtime/logging.js +314 -0
- package/src/runtime/minifier.js +242 -0
- package/src/runtime/module-system.js +377 -0
- package/src/runtime/network-operations.js +373 -0
- package/src/runtime/profiler.js +295 -0
- package/src/runtime/repl.js +336 -0
- package/src/runtime/security-manager.js +244 -0
- package/src/runtime/source-map-generator.js +208 -0
- package/src/runtime/test-runner.js +394 -0
- package/src/runtime/transformer.js +277 -0
- package/src/runtime/type-system.js +244 -0
- package/src/runtime/vladx-object.js +250 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI — Интерфейс командной строки для VladX
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { VladXEngine } from './vladx-engine.js';
|
|
6
|
+
import { Linter } from './runtime/linter.js';
|
|
7
|
+
import { Formatter } from './runtime/formatter.js';
|
|
8
|
+
import { Logging } from './runtime/logging.js';
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
10
|
+
import { resolve, dirname, join, basename } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const logger = new Logging({ level: 'info' });
|
|
15
|
+
|
|
16
|
+
export class CLI {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.engine = null;
|
|
19
|
+
this.commands = new Map();
|
|
20
|
+
this.registerCommands();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Регистрация команд
|
|
25
|
+
*/
|
|
26
|
+
registerCommands() {
|
|
27
|
+
this.commands.set('run', this.run.bind(this));
|
|
28
|
+
this.commands.set('repl', this.repl.bind(this));
|
|
29
|
+
this.commands.set('compile', this.compile.bind(this));
|
|
30
|
+
this.commands.set('lint', this.lint.bind(this));
|
|
31
|
+
this.commands.set('format', this.format.bind(this));
|
|
32
|
+
this.commands.set('test', this.test.bind(this));
|
|
33
|
+
this.commands.set('bundle', this.bundle.bind(this));
|
|
34
|
+
this.commands.set('watch', this.watch.bind(this));
|
|
35
|
+
this.commands.set('debug', this.debug.bind(this));
|
|
36
|
+
this.commands.set('help', this.help.bind(this));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Запуск CLI
|
|
41
|
+
*/
|
|
42
|
+
async runCLI(argv = process.argv) {
|
|
43
|
+
const command = argv[2];
|
|
44
|
+
const args = argv.slice(3);
|
|
45
|
+
|
|
46
|
+
if (!command || !this.commands.has(command)) {
|
|
47
|
+
this.help();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const cmd = this.commands.get(command);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await cmd(args);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.error(`Ошибка: ${error.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Выполнить файл
|
|
63
|
+
*/
|
|
64
|
+
async run(args) {
|
|
65
|
+
if (args.length === 0) {
|
|
66
|
+
logger.error('Укажите файл для выполнения');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const filepath = resolve(args[0]);
|
|
71
|
+
const options = this.parseOptions(args.slice(1));
|
|
72
|
+
|
|
73
|
+
if (!existsSync(filepath)) {
|
|
74
|
+
throw new Error(`Файл не найден: ${filepath}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const startTime = Date.now();
|
|
78
|
+
this.engine = new VladXEngine({
|
|
79
|
+
debug: options.debug || false,
|
|
80
|
+
strictMode: options.strict || false,
|
|
81
|
+
maxExecutionTime: options.timeout || 30000,
|
|
82
|
+
cache: options.cache,
|
|
83
|
+
security: options.security
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const result = await this.engine.executeFile(filepath);
|
|
88
|
+
|
|
89
|
+
if (options.time) {
|
|
90
|
+
const duration = Date.now() - startTime;
|
|
91
|
+
logger.info(`Время выполнения: ${duration}ms`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (result !== undefined && !options.silent) {
|
|
95
|
+
logger.info('Результат:', result);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
process.exit(0);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
logger.error(error.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* REPL режим
|
|
107
|
+
*/
|
|
108
|
+
async repl(args) {
|
|
109
|
+
const options = this.parseOptions(args);
|
|
110
|
+
|
|
111
|
+
this.engine = new VladXEngine({
|
|
112
|
+
debug: options.debug || false,
|
|
113
|
+
cache: options.cache,
|
|
114
|
+
security: options.security
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await this.engine.repl();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Компиляция
|
|
122
|
+
*/
|
|
123
|
+
async compile(args) {
|
|
124
|
+
if (args.length === 0) {
|
|
125
|
+
logger.error('Укажите файл для компиляции');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const filepath = resolve(args[0]);
|
|
130
|
+
const options = this.parseOptions(args.slice(1));
|
|
131
|
+
|
|
132
|
+
if (!existsSync(filepath)) {
|
|
133
|
+
throw new Error(`Файл не найден: ${filepath}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.engine = new VladXEngine();
|
|
137
|
+
|
|
138
|
+
const jsCode = this.engine.compile(readFileSync(filepath, 'utf-8'));
|
|
139
|
+
|
|
140
|
+
let output = options.output || filepath.replace(/\.vx$/, '.js');
|
|
141
|
+
|
|
142
|
+
if (options.format === 'cjs') {
|
|
143
|
+
jsCode = `"use strict";\n${jsCode}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
writeFileSync(output, jsCode, 'utf-8');
|
|
147
|
+
logger.info(`Скомпилировано: ${filepath} -> ${output}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Линтинг
|
|
152
|
+
*/
|
|
153
|
+
async lint(args) {
|
|
154
|
+
if (args.length === 0) {
|
|
155
|
+
logger.error('Укажите файл для линтинга');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const filepath = resolve(args[0]);
|
|
160
|
+
const options = this.parseOptions(args.slice(1));
|
|
161
|
+
|
|
162
|
+
if (!existsSync(filepath)) {
|
|
163
|
+
throw new Error(`Файл не найден: ${filepath}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const linter = new Linter({
|
|
167
|
+
autoFix: options.fix || false,
|
|
168
|
+
config: options.config
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const source = readFileSync(filepath, 'utf-8');
|
|
172
|
+
const results = linter.lint(source, filepath);
|
|
173
|
+
|
|
174
|
+
if (options.fix && results.all.length > 0) {
|
|
175
|
+
const fixed = linter.fix(source, filepath);
|
|
176
|
+
if (fixed.fixed) {
|
|
177
|
+
writeFileSync(filepath, fixed.source, 'utf-8');
|
|
178
|
+
logger.info('Автофикс применен');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (results.hasErrors) {
|
|
183
|
+
logger.error('Найдены ошибки:');
|
|
184
|
+
results.errors.forEach(err => {
|
|
185
|
+
logger.error(` ${filepath}:${err.line}:${err.column} - ${err.message}`);
|
|
186
|
+
});
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (results.warnings.length > 0) {
|
|
191
|
+
logger.warn('Предупреждения:');
|
|
192
|
+
results.warnings.forEach(warn => {
|
|
193
|
+
logger.warn(` ${filepath}:${warn.line}:${warn.column} - ${warn.message}`);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (results.all.length === 0) {
|
|
198
|
+
logger.info('Проблем не найдено');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Форматирование
|
|
206
|
+
*/
|
|
207
|
+
async format(args) {
|
|
208
|
+
if (args.length === 0) {
|
|
209
|
+
logger.error('Укажите файл для форматирования');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const filepath = resolve(args[0]);
|
|
214
|
+
const options = this.parseOptions(args.slice(1));
|
|
215
|
+
|
|
216
|
+
if (!existsSync(filepath)) {
|
|
217
|
+
throw new Error(`Файл не найден: ${filepath}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const formatter = new Formatter({
|
|
221
|
+
indentSize: options.indent || 4,
|
|
222
|
+
useTabs: options.tabs || false,
|
|
223
|
+
printWidth: options.width || 100
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const source = readFileSync(filepath, 'utf-8');
|
|
227
|
+
const formatted = formatter.format(source, filepath);
|
|
228
|
+
|
|
229
|
+
writeFileSync(filepath, formatted, 'utf-8');
|
|
230
|
+
logger.info(`Отформатировано: ${filepath}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Тесты
|
|
235
|
+
*/
|
|
236
|
+
async test(args) {
|
|
237
|
+
const TestRunner = await import('./runtime/test-runner.js');
|
|
238
|
+
const runner = new TestRunner.default();
|
|
239
|
+
|
|
240
|
+
const filepath = args[0] ? resolve(args[0]) : null;
|
|
241
|
+
|
|
242
|
+
if (filepath) {
|
|
243
|
+
if (!existsSync(filepath)) {
|
|
244
|
+
throw new Error(`Файл не найден: ${filepath}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const source = readFileSync(filepath, 'utf-8');
|
|
248
|
+
await this.engine.execute(source, { filename: filepath });
|
|
249
|
+
} else {
|
|
250
|
+
const files = this.findTestFiles();
|
|
251
|
+
|
|
252
|
+
for (const file of files) {
|
|
253
|
+
const source = readFileSync(file, 'utf-8');
|
|
254
|
+
await this.engine.execute(source, { filename: file });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const results = await runner.run();
|
|
259
|
+
|
|
260
|
+
if (results.failed > 0) {
|
|
261
|
+
logger.error(`Тесты провалены: ${results.failed}/${results.total}`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
logger.info(`Тесты пройдены: ${results.passed}/${results.total}`);
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Сборка модулей
|
|
271
|
+
*/
|
|
272
|
+
async bundle(args) {
|
|
273
|
+
if (args.length === 0) {
|
|
274
|
+
logger.error('Укажите точку входа');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const entry = resolve(args[0]);
|
|
279
|
+
const options = this.parseOptions(args.slice(1));
|
|
280
|
+
|
|
281
|
+
if (!existsSync(entry)) {
|
|
282
|
+
throw new Error(`Файл не найден: ${entry}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const { Bundle } = await import('./runtime/bundler.js');
|
|
286
|
+
const bundler = new Bundle({
|
|
287
|
+
entry,
|
|
288
|
+
output: options.output || 'bundle.vx',
|
|
289
|
+
format: options.format || 'esm',
|
|
290
|
+
minify: options.minify || false,
|
|
291
|
+
sourceMap: options.sourcemap || false
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const bundled = await bundler.build();
|
|
295
|
+
await bundler.write(bundled);
|
|
296
|
+
|
|
297
|
+
logger.info(`Собрано: ${bundled.modules} модулей -> ${bundler.output}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Watch режим
|
|
302
|
+
*/
|
|
303
|
+
async watch(args) {
|
|
304
|
+
if (args.length === 0) {
|
|
305
|
+
logger.error('Укажите файл для слежения');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const filepath = resolve(args[0]);
|
|
310
|
+
const options = this.parseOptions(args.slice(1));
|
|
311
|
+
|
|
312
|
+
if (!existsSync(filepath)) {
|
|
313
|
+
throw new Error(`Файл не найден: ${filepath}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
logger.info(`Слежение за: ${filepath}`);
|
|
317
|
+
|
|
318
|
+
this.engine = new VladXEngine({
|
|
319
|
+
debug: options.debug || false
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const runFile = async () => {
|
|
323
|
+
try {
|
|
324
|
+
await this.engine.executeFile(filepath);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
logger.error(error.message);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
await runFile();
|
|
331
|
+
|
|
332
|
+
const fs = await import('fs');
|
|
333
|
+
fs.watchFile(filepath, { interval: options.interval || 1000 }, async () => {
|
|
334
|
+
logger.info('Файл изменен, перезапуск...');
|
|
335
|
+
await runFile();
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Отладка
|
|
341
|
+
*/
|
|
342
|
+
async debug(args) {
|
|
343
|
+
if (args.length === 0) {
|
|
344
|
+
logger.error('Укажите файл для отладки');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const filepath = resolve(args[0]);
|
|
349
|
+
|
|
350
|
+
if (!existsSync(filepath)) {
|
|
351
|
+
throw new Error(`Файл не найден: ${filepath}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
this.engine = new VladXEngine({
|
|
355
|
+
debug: true
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
logger.info('Отладочный режим включен');
|
|
359
|
+
logger.info('Команды: точкаОстанова, пошаговыйРежим, продолжить');
|
|
360
|
+
|
|
361
|
+
await this.engine.executeFile(filepath);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Справка
|
|
366
|
+
*/
|
|
367
|
+
help() {
|
|
368
|
+
console.log(`
|
|
369
|
+
VladX - Мощный интерпретируемый язык программирования
|
|
370
|
+
|
|
371
|
+
Использование:
|
|
372
|
+
vladx <команда> [опции] [аргументы]
|
|
373
|
+
|
|
374
|
+
Команды:
|
|
375
|
+
run <файл> Выполнить файл
|
|
376
|
+
repl Интерактивная консоль
|
|
377
|
+
compile <файл> Скомпилировать в JavaScript
|
|
378
|
+
lint <файл> Проверить код на ошибки
|
|
379
|
+
format <файл> Отформатировать код
|
|
380
|
+
test [файл] Запустить тесты
|
|
381
|
+
bundle <entry> Собрать модули
|
|
382
|
+
watch <файл> Смотреть за изменениями файла
|
|
383
|
+
debug <файл> Отладочный режим
|
|
384
|
+
help Показать эту справку
|
|
385
|
+
|
|
386
|
+
Опции:
|
|
387
|
+
--debug Режим отладки
|
|
388
|
+
--strict Строгий режим
|
|
389
|
+
--timeout <ms> Таймаут выполнения
|
|
390
|
+
--output <file> Файл вывода
|
|
391
|
+
--format <format> Формат (esm, cjs, iife, umd)
|
|
392
|
+
--minify Минифицировать
|
|
393
|
+
--fix Автофикс для линтера
|
|
394
|
+
--watch Смотреть за изменениями
|
|
395
|
+
--time Показать время выполнения
|
|
396
|
+
--silent Без вывода результатов
|
|
397
|
+
--indent <n> Размер отступа
|
|
398
|
+
--tabs Использовать табы
|
|
399
|
+
--width <n> Максимальная ширина строки
|
|
400
|
+
|
|
401
|
+
Примеры:
|
|
402
|
+
vladx run main.vx
|
|
403
|
+
vladx repl
|
|
404
|
+
vladx compile main.vx --output main.js
|
|
405
|
+
vladx lint main.vx --fix
|
|
406
|
+
vladx format main.vx --indent 2
|
|
407
|
+
vladx bundle main.vx --format iife --minify
|
|
408
|
+
vladx watch main.vx
|
|
409
|
+
vladx debug main.vx
|
|
410
|
+
|
|
411
|
+
Документация: https://vladx.dev
|
|
412
|
+
`.trim());
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Парсинг опций
|
|
417
|
+
*/
|
|
418
|
+
parseOptions(args) {
|
|
419
|
+
const options = {};
|
|
420
|
+
|
|
421
|
+
for (let i = 0; i < args.length; i++) {
|
|
422
|
+
const arg = args[i];
|
|
423
|
+
|
|
424
|
+
if (arg === '--debug') {
|
|
425
|
+
options.debug = true;
|
|
426
|
+
} else if (arg === '--strict') {
|
|
427
|
+
options.strict = true;
|
|
428
|
+
} else if (arg === '--timeout' && args[i + 1]) {
|
|
429
|
+
options.timeout = parseInt(args[++i]);
|
|
430
|
+
} else if (arg === '--output' && args[i + 1]) {
|
|
431
|
+
options.output = args[++i];
|
|
432
|
+
} else if (arg === '--format' && args[i + 1]) {
|
|
433
|
+
options.format = args[++i];
|
|
434
|
+
} else if (arg === '--minify') {
|
|
435
|
+
options.minify = true;
|
|
436
|
+
} else if (arg === '--fix') {
|
|
437
|
+
options.fix = true;
|
|
438
|
+
} else if (arg === '--watch') {
|
|
439
|
+
options.watch = true;
|
|
440
|
+
} else if (arg === '--time') {
|
|
441
|
+
options.time = true;
|
|
442
|
+
} else if (arg === '--silent') {
|
|
443
|
+
options.silent = true;
|
|
444
|
+
} else if (arg === '--indent' && args[i + 1]) {
|
|
445
|
+
options.indent = parseInt(args[++i]);
|
|
446
|
+
} else if (arg === '--tabs') {
|
|
447
|
+
options.tabs = true;
|
|
448
|
+
} else if (arg === '--width' && args[i + 1]) {
|
|
449
|
+
options.width = parseInt(args[++i]);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return options;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Найти тестовые файлы
|
|
458
|
+
*/
|
|
459
|
+
findTestFiles() {
|
|
460
|
+
const fs = require('fs');
|
|
461
|
+
const path = require('path');
|
|
462
|
+
|
|
463
|
+
const testFiles = [];
|
|
464
|
+
|
|
465
|
+
const findFiles = (dir) => {
|
|
466
|
+
const files = fs.readdirSync(dir);
|
|
467
|
+
|
|
468
|
+
for (const file of files) {
|
|
469
|
+
const filepath = path.join(dir, file);
|
|
470
|
+
const stat = fs.statSync(filepath);
|
|
471
|
+
|
|
472
|
+
if (stat.isDirectory()) {
|
|
473
|
+
if (file === 'node_modules' || file.startsWith('.')) continue;
|
|
474
|
+
findFiles(filepath);
|
|
475
|
+
} else if (file.endsWith('.test.vx') || file.endsWith('.spec.vx')) {
|
|
476
|
+
testFiles.push(filepath);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
findFiles(process.cwd());
|
|
482
|
+
return testFiles;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export default CLI;
|