@twin.org/cli-core 0.0.2-next.8 → 0.0.3-next.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/dist/es/cliBase.js +122 -0
- package/dist/es/cliBase.js.map +1 -0
- package/dist/es/cliDisplay.js +166 -0
- package/dist/es/cliDisplay.js.map +1 -0
- package/dist/es/cliOptions.js +36 -0
- package/dist/es/cliOptions.js.map +1 -0
- package/dist/es/cliParam.js +248 -0
- package/dist/es/cliParam.js.map +1 -0
- package/dist/es/cliUtils.js +235 -0
- package/dist/es/cliUtils.js.map +1 -0
- package/dist/es/commands/global.js +61 -0
- package/dist/es/commands/global.js.map +1 -0
- package/dist/es/index.js +14 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/ICliOptions.js +2 -0
- package/dist/es/models/ICliOptions.js.map +1 -0
- package/dist/es/models/ICliOutputOptionsConsole.js +2 -0
- package/dist/es/models/ICliOutputOptionsConsole.js.map +1 -0
- package/dist/es/models/ICliOutputOptionsEnv.js +2 -0
- package/dist/es/models/ICliOutputOptionsEnv.js.map +1 -0
- package/dist/es/models/ICliOutputOptionsJson.js +2 -0
- package/dist/es/models/ICliOutputOptionsJson.js.map +1 -0
- package/dist/es/models/cliOutputOptions.js +2 -0
- package/dist/es/models/cliOutputOptions.js.map +1 -0
- package/dist/types/cliBase.d.ts +1 -1
- package/dist/types/cliDisplay.d.ts +5 -0
- package/dist/types/cliParam.d.ts +10 -9
- package/dist/types/index.d.ts +11 -11
- package/dist/types/models/cliOutputOptions.d.ts +3 -3
- package/docs/changelog.md +297 -0
- package/docs/reference/classes/CLIDisplay.md +20 -0
- package/docs/reference/classes/CLIParam.md +62 -50
- package/docs/reference/classes/CLIUtils.md +10 -10
- package/locales/.validate-ignore +1 -0
- package/locales/en.json +0 -1
- package/package.json +25 -14
- package/dist/cjs/index.cjs +0 -878
- package/dist/esm/index.mjs +0 -849
package/dist/cjs/index.cjs
DELETED
|
@@ -1,878 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var core = require('@twin.org/core');
|
|
4
|
-
var commander = require('commander');
|
|
5
|
-
var node_readline = require('node:readline');
|
|
6
|
-
var node_util = require('node:util');
|
|
7
|
-
var chalk = require('chalk');
|
|
8
|
-
var path = require('node:path');
|
|
9
|
-
var dotenv = require('dotenv');
|
|
10
|
-
var node_child_process = require('node:child_process');
|
|
11
|
-
var node_fs = require('node:fs');
|
|
12
|
-
var promises = require('node:fs/promises');
|
|
13
|
-
var crypto = require('@twin.org/crypto');
|
|
14
|
-
|
|
15
|
-
function _interopNamespaceDefault(e) {
|
|
16
|
-
var n = Object.create(null);
|
|
17
|
-
if (e) {
|
|
18
|
-
Object.keys(e).forEach(function (k) {
|
|
19
|
-
if (k !== 'default') {
|
|
20
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
21
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
22
|
-
enumerable: true,
|
|
23
|
-
get: function () { return e[k]; }
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
n.default = e;
|
|
29
|
-
return Object.freeze(n);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
var dotenv__namespace = /*#__PURE__*/_interopNamespaceDefault(dotenv);
|
|
33
|
-
|
|
34
|
-
// Copyright 2024 IOTA Stiftung.
|
|
35
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
36
|
-
/**
|
|
37
|
-
* Display utilities for the CLI.
|
|
38
|
-
*/
|
|
39
|
-
class CLIDisplay {
|
|
40
|
-
/**
|
|
41
|
-
* The interval ID for the spinner.
|
|
42
|
-
* @internal
|
|
43
|
-
*/
|
|
44
|
-
static _spinnerIntervalId;
|
|
45
|
-
/**
|
|
46
|
-
* The default output method for writing standard messages.
|
|
47
|
-
* @param buffer The message to output.
|
|
48
|
-
*/
|
|
49
|
-
static write = buffer => process.stdout.write(buffer);
|
|
50
|
-
/**
|
|
51
|
-
* The default output method for writing error messages.
|
|
52
|
-
* @param buffer The message to output.
|
|
53
|
-
*/
|
|
54
|
-
static writeError = buffer => process.stderr.write(buffer);
|
|
55
|
-
/**
|
|
56
|
-
* The default output method for clearing the current line.
|
|
57
|
-
*/
|
|
58
|
-
static clearLine = () => {
|
|
59
|
-
node_readline.clearLine(process.stdout, 0);
|
|
60
|
-
node_readline.cursorTo(process.stdout, 0);
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* Display the header for the CLI.
|
|
64
|
-
* @param title The title of the CLI.
|
|
65
|
-
* @param version The version of the CLI.
|
|
66
|
-
* @param icon The icon for the CLI.
|
|
67
|
-
*/
|
|
68
|
-
static header(title, version, icon) {
|
|
69
|
-
const titleVersion = `${title} v${version}`;
|
|
70
|
-
CLIDisplay.write(`${icon} ${chalk.underline.bold.blue(titleVersion)}\n`);
|
|
71
|
-
CLIDisplay.write("\n");
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Display an error message.
|
|
75
|
-
* @param error The error to display.
|
|
76
|
-
* @param lineBreaks Whether to add a line break after the error.
|
|
77
|
-
*/
|
|
78
|
-
static error(error, lineBreaks = true) {
|
|
79
|
-
CLIDisplay.writeError("❗ ");
|
|
80
|
-
CLIDisplay.writeError(chalk.red(core.I18n.formatMessage("cli.progress.error")));
|
|
81
|
-
if (lineBreaks) {
|
|
82
|
-
CLIDisplay.writeError("\n");
|
|
83
|
-
}
|
|
84
|
-
const formatted = core.ErrorHelper.formatErrors(error, true);
|
|
85
|
-
CLIDisplay.writeError(chalk.red(formatted.map(e => `\t${e}`).join("\n")));
|
|
86
|
-
if (lineBreaks) {
|
|
87
|
-
CLIDisplay.writeError("\n");
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Display a section.
|
|
92
|
-
* @param label The label for the section.
|
|
93
|
-
*/
|
|
94
|
-
static section(label) {
|
|
95
|
-
CLIDisplay.write(chalk.hex("#FFA500").bold.underline(label));
|
|
96
|
-
CLIDisplay.write("\n\n");
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Display a value with a label.
|
|
100
|
-
* @param label The label for the value.
|
|
101
|
-
* @param value The value to display.
|
|
102
|
-
* @param indentLevel The level of indentation.
|
|
103
|
-
*/
|
|
104
|
-
static value(label, value, indentLevel = 0) {
|
|
105
|
-
CLIDisplay.write("\t".repeat(indentLevel));
|
|
106
|
-
if (core.Is.stringValue(label)) {
|
|
107
|
-
CLIDisplay.write(chalk.hex("#FFA500").bold(`${label}: `));
|
|
108
|
-
}
|
|
109
|
-
CLIDisplay.write(core.Coerce.string(value) ?? "");
|
|
110
|
-
CLIDisplay.write("\n");
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Display a task with a label.
|
|
114
|
-
* @param label The label for the value.
|
|
115
|
-
* @param task The task to display.
|
|
116
|
-
*/
|
|
117
|
-
static task(label, task) {
|
|
118
|
-
CLIDisplay.write("➡️ ");
|
|
119
|
-
if (core.Is.empty(task)) {
|
|
120
|
-
CLIDisplay.write(chalk.cyan(label));
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
CLIDisplay.write(chalk.cyan(`${label}: `));
|
|
124
|
-
CLIDisplay.write(task);
|
|
125
|
-
}
|
|
126
|
-
CLIDisplay.write("\n");
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Display a break.
|
|
130
|
-
*/
|
|
131
|
-
static break() {
|
|
132
|
-
CLIDisplay.write("\n");
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Display formatted and colorized JSON.
|
|
136
|
-
* @param obj The object to display.
|
|
137
|
-
*/
|
|
138
|
-
static json(obj) {
|
|
139
|
-
CLIDisplay.write(node_util.inspect(obj, false, undefined, true));
|
|
140
|
-
CLIDisplay.write("\n");
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Display a warning.
|
|
144
|
-
* @param label The label for the warning.
|
|
145
|
-
*/
|
|
146
|
-
static warning(label) {
|
|
147
|
-
CLIDisplay.write("⚠️ ");
|
|
148
|
-
CLIDisplay.write(chalk.hex("#FFA500").bold(label));
|
|
149
|
-
CLIDisplay.write("\n\n");
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Display the processing is done.
|
|
153
|
-
*/
|
|
154
|
-
static done() {
|
|
155
|
-
CLIDisplay.write("🎉 ");
|
|
156
|
-
CLIDisplay.write(chalk.greenBright.bold(core.I18n.formatMessage("cli.progress.done")));
|
|
157
|
-
CLIDisplay.write("\n");
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Start the spinner.
|
|
161
|
-
* @param i18nMessage The message to display with the spinner.
|
|
162
|
-
* @param spinnerCharacters The characters to use in the spinner.
|
|
163
|
-
* @param interval The interval for the spinner.
|
|
164
|
-
*/
|
|
165
|
-
static spinnerStart(i18nMessage = "cli.progress.pleaseWait", spinnerCharacters = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"], interval = 100) {
|
|
166
|
-
let spinnerIndex = 0;
|
|
167
|
-
if (CLIDisplay._spinnerIntervalId) {
|
|
168
|
-
clearInterval(CLIDisplay._spinnerIntervalId);
|
|
169
|
-
}
|
|
170
|
-
CLIDisplay._spinnerIntervalId = setInterval(() => {
|
|
171
|
-
CLIDisplay.clearLine();
|
|
172
|
-
CLIDisplay.write(`${spinnerCharacters[spinnerIndex++ % spinnerCharacters.length]} ${core.I18n.formatMessage(i18nMessage)}`);
|
|
173
|
-
}, interval);
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Stop the spinner.
|
|
177
|
-
*/
|
|
178
|
-
static spinnerStop() {
|
|
179
|
-
if (CLIDisplay._spinnerIntervalId) {
|
|
180
|
-
clearInterval(CLIDisplay._spinnerIntervalId);
|
|
181
|
-
CLIDisplay._spinnerIntervalId = undefined;
|
|
182
|
-
}
|
|
183
|
-
CLIDisplay.clearLine();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Copyright 2024 IOTA Stiftung.
|
|
188
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
189
|
-
/**
|
|
190
|
-
* Utilities function for helping in the CLI.
|
|
191
|
-
*/
|
|
192
|
-
class CLIUtils {
|
|
193
|
-
/**
|
|
194
|
-
* Does the specified file exist.
|
|
195
|
-
* @param filename The filename to check for existence.
|
|
196
|
-
* @returns True if the file exists.
|
|
197
|
-
*/
|
|
198
|
-
static async fileExists(filename) {
|
|
199
|
-
try {
|
|
200
|
-
const stats = await promises.stat(filename);
|
|
201
|
-
return stats.isFile();
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Does the specified file exist, synchronously.
|
|
209
|
-
* @param filename The filename to check for existence.
|
|
210
|
-
* @returns True if the file exists.
|
|
211
|
-
*/
|
|
212
|
-
static fileExistsSync(filename) {
|
|
213
|
-
try {
|
|
214
|
-
const stats = node_fs.statSync(filename);
|
|
215
|
-
return stats.isFile();
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Check if the dir exists.
|
|
223
|
-
* @param dir The directory to check.
|
|
224
|
-
* @returns True if the dir exists.
|
|
225
|
-
*/
|
|
226
|
-
static async dirExists(dir) {
|
|
227
|
-
try {
|
|
228
|
-
await promises.access(dir);
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Check if the dir exists, synchronously.
|
|
237
|
-
* @param dir The directory to check.
|
|
238
|
-
* @returns True if the dir exists.
|
|
239
|
-
*/
|
|
240
|
-
static dirExistsSync(dir) {
|
|
241
|
-
try {
|
|
242
|
-
node_fs.accessSync(dir);
|
|
243
|
-
return true;
|
|
244
|
-
}
|
|
245
|
-
catch {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Read a JSON file and parse it.
|
|
251
|
-
* @param filename The filename to read.
|
|
252
|
-
* @returns The parsed JSON.
|
|
253
|
-
*/
|
|
254
|
-
static async readJsonFile(filename) {
|
|
255
|
-
if (await CLIUtils.fileExists(filename)) {
|
|
256
|
-
const content = await promises.readFile(filename, "utf8");
|
|
257
|
-
return JSON.parse(content);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Read a JSON file and parse it, synchronously.
|
|
262
|
-
* @param filename The filename to read.
|
|
263
|
-
* @returns The parsed JSON.
|
|
264
|
-
*/
|
|
265
|
-
static readJsonFileSync(filename) {
|
|
266
|
-
if (CLIUtils.fileExistsSync(filename)) {
|
|
267
|
-
const content = node_fs.readFileSync(filename, "utf8");
|
|
268
|
-
return JSON.parse(content);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Read a file as lines.
|
|
273
|
-
* @param filename The filename to read.
|
|
274
|
-
* @returns The lines.
|
|
275
|
-
*/
|
|
276
|
-
static async readLinesFile(filename) {
|
|
277
|
-
if (await CLIUtils.fileExists(filename)) {
|
|
278
|
-
const content = await promises.readFile(filename, "utf8");
|
|
279
|
-
return content.split("\n");
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Read a file as lines, synchronously.
|
|
284
|
-
* @param filename The filename to read.
|
|
285
|
-
* @returns The lines.
|
|
286
|
-
*/
|
|
287
|
-
static readLinesFileSync(filename) {
|
|
288
|
-
if (CLIUtils.fileExistsSync(filename)) {
|
|
289
|
-
const content = node_fs.readFileSync(filename, "utf8");
|
|
290
|
-
return content.split("\n");
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Find the NPM root based on a package.json path.
|
|
295
|
-
* @param rootFolder The path to the package.json.
|
|
296
|
-
* @returns The root path.
|
|
297
|
-
*/
|
|
298
|
-
static async findNpmRoot(rootFolder) {
|
|
299
|
-
return new Promise((resolve, reject) => {
|
|
300
|
-
node_child_process.exec("npm root", { cwd: rootFolder }, (error, stdout, stderr) => {
|
|
301
|
-
if (error) {
|
|
302
|
-
reject(error);
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
resolve(stdout.trim());
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Run a shell command.
|
|
312
|
-
* @param command The app to run in the shell.
|
|
313
|
-
* @param args The args for the app.
|
|
314
|
-
* @param cwd The working directory to execute the command in.
|
|
315
|
-
* @returns Promise to wait for command execution to complete.
|
|
316
|
-
*/
|
|
317
|
-
static async runShellCmd(command, args, cwd) {
|
|
318
|
-
const osCommand = process.platform.startsWith("win") ? `${command}.cmd` : command;
|
|
319
|
-
return CLIUtils.runShellApp(osCommand, args, cwd);
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Run a shell app.
|
|
323
|
-
* @param app The app to run in the shell.
|
|
324
|
-
* @param args The args for the app.
|
|
325
|
-
* @param cwd The working directory to execute the command in.
|
|
326
|
-
* @returns Promise to wait for command execution to complete.
|
|
327
|
-
*/
|
|
328
|
-
static async runShellApp(app, args, cwd) {
|
|
329
|
-
return new Promise((resolve, reject) => {
|
|
330
|
-
const sp = node_child_process.spawn(app, args, {
|
|
331
|
-
shell: true,
|
|
332
|
-
cwd
|
|
333
|
-
});
|
|
334
|
-
sp.stdout?.on("data", data => {
|
|
335
|
-
CLIDisplay.write(data);
|
|
336
|
-
});
|
|
337
|
-
sp.stderr?.on("data", data => {
|
|
338
|
-
CLIDisplay.writeError(data);
|
|
339
|
-
});
|
|
340
|
-
sp.on("exit", (exitCode, signals) => {
|
|
341
|
-
if (core.Coerce.number(exitCode) !== 0 || signals?.length) {
|
|
342
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
343
|
-
reject(new Error("Run failed"));
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
resolve();
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Write a JSON file.
|
|
353
|
-
* @param jsonFilename The filename to write.
|
|
354
|
-
* @param data The data to write.
|
|
355
|
-
* @param append Append to the file.
|
|
356
|
-
*/
|
|
357
|
-
static async writeJsonFile(jsonFilename, data, append) {
|
|
358
|
-
if (core.Is.stringValue(jsonFilename)) {
|
|
359
|
-
const filename = path.resolve(jsonFilename);
|
|
360
|
-
let currentJson = {};
|
|
361
|
-
if (append) {
|
|
362
|
-
CLIDisplay.task(core.I18n.formatMessage("cli.progress.readingJsonFile"), filename);
|
|
363
|
-
currentJson = (await CLIUtils.readJsonFile(filename)) ?? {};
|
|
364
|
-
}
|
|
365
|
-
CLIDisplay.task(core.I18n.formatMessage("cli.progress.writingJsonFile"), filename);
|
|
366
|
-
CLIDisplay.break();
|
|
367
|
-
await promises.mkdir(path.dirname(filename), { recursive: true });
|
|
368
|
-
await promises.writeFile(filename, `${JSON.stringify(core.ObjectHelper.merge(currentJson, data), undefined, "\t")}\n`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Write an env file.
|
|
373
|
-
* @param envFilename The filename to write.
|
|
374
|
-
* @param data The data to write.
|
|
375
|
-
* @param append Append to the file.
|
|
376
|
-
*/
|
|
377
|
-
static async writeEnvFile(envFilename, data, append) {
|
|
378
|
-
if (core.Is.stringValue(envFilename)) {
|
|
379
|
-
const filename = path.resolve(envFilename);
|
|
380
|
-
const outputKeys = [];
|
|
381
|
-
const outputDict = {};
|
|
382
|
-
if (append) {
|
|
383
|
-
CLIDisplay.task(core.I18n.formatMessage("cli.progress.readingEnvFile"), filename);
|
|
384
|
-
const lines = await CLIUtils.readLinesFile(filename);
|
|
385
|
-
if (core.Is.arrayValue(lines)) {
|
|
386
|
-
for (const line of lines) {
|
|
387
|
-
const parts = line.split("=");
|
|
388
|
-
outputKeys.push(parts[0]);
|
|
389
|
-
outputDict[parts[0]] = parts.slice(1).join("=");
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
CLIDisplay.task(core.I18n.formatMessage("cli.progress.writingEnvFile"), filename);
|
|
394
|
-
CLIDisplay.break();
|
|
395
|
-
if (core.Is.arrayValue(data)) {
|
|
396
|
-
for (const line of data) {
|
|
397
|
-
const parts = line.split("=");
|
|
398
|
-
const currentIndex = outputKeys.indexOf(parts[0]);
|
|
399
|
-
if (currentIndex !== -1) {
|
|
400
|
-
outputKeys.splice(currentIndex, 1);
|
|
401
|
-
}
|
|
402
|
-
outputKeys.push(parts[0]);
|
|
403
|
-
outputDict[parts[0]] = parts.slice(1).join("=");
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const output = [];
|
|
407
|
-
for (const outputKey of outputKeys) {
|
|
408
|
-
output.push(`${outputKey}=${outputDict[outputKey]}`);
|
|
409
|
-
}
|
|
410
|
-
await promises.mkdir(path.dirname(filename), { recursive: true });
|
|
411
|
-
await promises.writeFile(filename, output.join("\n"));
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Copyright 2024 IOTA Stiftung.
|
|
417
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
418
|
-
let localesDir;
|
|
419
|
-
/**
|
|
420
|
-
* Initialize the global options.
|
|
421
|
-
* @param localesDirectory The path to load the locales from.
|
|
422
|
-
*/
|
|
423
|
-
function initGlobalOptions(localesDirectory) {
|
|
424
|
-
localesDir = localesDirectory;
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Add the global options.
|
|
428
|
-
* @param program The program to add the options to.
|
|
429
|
-
* @param supportsLang Whether the CLI supports different languages.
|
|
430
|
-
* @param supportsEnvFiles Whether the CLI supports loading env files
|
|
431
|
-
*/
|
|
432
|
-
function addGlobalOptions(program, supportsLang, supportsEnvFiles) {
|
|
433
|
-
if (supportsLang) {
|
|
434
|
-
program.option(core.I18n.formatMessage("cli.options.lang.param"), core.I18n.formatMessage("cli.options.lang.description"), "en");
|
|
435
|
-
}
|
|
436
|
-
if (supportsEnvFiles) {
|
|
437
|
-
program.option(core.I18n.formatMessage("cli.options.load-env.param"), core.I18n.formatMessage("cli.options.load-env.description"));
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Handle the global options.
|
|
442
|
-
* @param command The command to use for context.
|
|
443
|
-
* @internal
|
|
444
|
-
*/
|
|
445
|
-
function handleGlobalOptions(command) {
|
|
446
|
-
const globalOpts = command.optsWithGlobals();
|
|
447
|
-
if (core.Is.stringValue(globalOpts?.lang)) {
|
|
448
|
-
initLocales(globalOpts.lang);
|
|
449
|
-
}
|
|
450
|
-
const loadEnv = globalOpts?.loadEnv;
|
|
451
|
-
if (core.Is.arrayValue(loadEnv)) {
|
|
452
|
-
const resolvedEnv = loadEnv.map(e => path.resolve(e));
|
|
453
|
-
CLIDisplay.task(core.I18n.formatMessage("cli.progress.loadingEnvFiles"), resolvedEnv.join(", "));
|
|
454
|
-
CLIDisplay.break();
|
|
455
|
-
dotenv__namespace.config({ path: resolvedEnv, quiet: true });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Initialize the locales for the CLI.
|
|
460
|
-
* @param locale The locale to use.
|
|
461
|
-
* @internal
|
|
462
|
-
*/
|
|
463
|
-
function initLocales(locale) {
|
|
464
|
-
const localePath = path.join(localesDir, `${locale}.json`);
|
|
465
|
-
const localeContent = CLIUtils.readJsonFileSync(localePath);
|
|
466
|
-
if (core.Is.objectValue(localeContent)) {
|
|
467
|
-
core.I18n.addDictionary(locale, localeContent);
|
|
468
|
-
core.I18n.setLocale(locale);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Copyright 2024 IOTA Stiftung.
|
|
473
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
474
|
-
/**
|
|
475
|
-
* The main entry point for the CLI.
|
|
476
|
-
*/
|
|
477
|
-
class CLIBase {
|
|
478
|
-
/**
|
|
479
|
-
* Execute the command line processing.
|
|
480
|
-
* @param options The options for the CLI.
|
|
481
|
-
* @param localesDirectory The path to load the locales from.
|
|
482
|
-
* @param argv The process arguments.
|
|
483
|
-
* @returns The exit code.
|
|
484
|
-
*/
|
|
485
|
-
async execute(options, localesDirectory, argv) {
|
|
486
|
-
initGlobalOptions(localesDirectory);
|
|
487
|
-
initLocales("en");
|
|
488
|
-
try {
|
|
489
|
-
process.title = options.title;
|
|
490
|
-
if (!argv.includes("--version") && !argv.includes("-v")) {
|
|
491
|
-
CLIDisplay.header(options.title, options.version, options.icon);
|
|
492
|
-
}
|
|
493
|
-
const program = new commander.Command();
|
|
494
|
-
let outputWidth = options.overrideOutputWidth;
|
|
495
|
-
let outputErrWidth = options.overrideOutputWidth;
|
|
496
|
-
if (core.Is.undefined(outputWidth) && process.stdout.isTTY) {
|
|
497
|
-
outputWidth = process.stdout.columns;
|
|
498
|
-
outputErrWidth = process.stderr.columns;
|
|
499
|
-
}
|
|
500
|
-
program
|
|
501
|
-
.name(options.appName)
|
|
502
|
-
.version(options.version)
|
|
503
|
-
.usage("[command]")
|
|
504
|
-
.showHelpAfterError()
|
|
505
|
-
.configureOutput({
|
|
506
|
-
writeOut: str => CLIDisplay.write(str),
|
|
507
|
-
writeErr: str => CLIDisplay.writeError(str),
|
|
508
|
-
getOutHelpWidth: () => outputWidth,
|
|
509
|
-
getErrHelpWidth: () => outputErrWidth,
|
|
510
|
-
outputError: (str, write) => CLIDisplay.error(str.replace(/^error: /, ""), false)
|
|
511
|
-
})
|
|
512
|
-
.exitOverride(err => {
|
|
513
|
-
// By default commander still calls process.exit on exit
|
|
514
|
-
// which we don't want as we might have subsequent
|
|
515
|
-
// processing to handle, so instead we throw the exit code
|
|
516
|
-
// as a way to skip the process.exit call.
|
|
517
|
-
// If the error code is commander.help then we return 0 as
|
|
518
|
-
// we don't want displaying help to be an error.
|
|
519
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
520
|
-
throw new Error(err.code === "commander.help" ? "0" : err.exitCode.toString());
|
|
521
|
-
});
|
|
522
|
-
if (options.showDevToolWarning ?? false) {
|
|
523
|
-
program.hook("preAction", () => CLIDisplay.warning(core.I18n.formatMessage("warn.common.devOnlyTool")));
|
|
524
|
-
}
|
|
525
|
-
this.configureRoot(program);
|
|
526
|
-
addGlobalOptions(program, options.supportsLang ?? true, options.supportsEnvFiles ?? false);
|
|
527
|
-
// We parse the options before building the command
|
|
528
|
-
// in case the language has been set, then the
|
|
529
|
-
// help for the options will be in the correct language.
|
|
530
|
-
program.parseOptions(argv);
|
|
531
|
-
handleGlobalOptions(program);
|
|
532
|
-
const commandCount = this.buildCommands(program);
|
|
533
|
-
if (commandCount === 0) {
|
|
534
|
-
program.usage(" ");
|
|
535
|
-
}
|
|
536
|
-
await program.parseAsync(argv);
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
CLIDisplay.spinnerStop();
|
|
540
|
-
let exitCode;
|
|
541
|
-
if (error instanceof Error) {
|
|
542
|
-
// This error could be the exit code we errored with
|
|
543
|
-
// from the exitOverride so parse and resolve with it
|
|
544
|
-
exitCode = core.Coerce.number(error.message);
|
|
545
|
-
}
|
|
546
|
-
if (core.Is.integer(exitCode)) {
|
|
547
|
-
return exitCode;
|
|
548
|
-
}
|
|
549
|
-
CLIDisplay.error(error);
|
|
550
|
-
return 1;
|
|
551
|
-
}
|
|
552
|
-
return 0;
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Configure any options or actions at the root program level.
|
|
556
|
-
* @param program The root program command.
|
|
557
|
-
*/
|
|
558
|
-
configureRoot(program) {
|
|
559
|
-
program.action(() => {
|
|
560
|
-
program.help();
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Get the commands for the CLI, override in derived class to supply your own.
|
|
565
|
-
* @param program The main program that the commands will be added to.
|
|
566
|
-
* @returns The commands for the CLI.
|
|
567
|
-
*/
|
|
568
|
-
getCommands(program) {
|
|
569
|
-
return [];
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Build the commands for the CLI.
|
|
573
|
-
* @param program The main program that the commands are added to.
|
|
574
|
-
* @returns The number of commands added.
|
|
575
|
-
* @internal
|
|
576
|
-
*/
|
|
577
|
-
buildCommands(program) {
|
|
578
|
-
const commands = this.getCommands(program);
|
|
579
|
-
for (const command of commands) {
|
|
580
|
-
program.addCommand(command.copyInheritedSettings(program));
|
|
581
|
-
}
|
|
582
|
-
return commands.length;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Copyright 2024 IOTA Stiftung.
|
|
587
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
588
|
-
/**
|
|
589
|
-
* Utilities for getting standard options.
|
|
590
|
-
*/
|
|
591
|
-
class CLIOptions {
|
|
592
|
-
/**
|
|
593
|
-
* Get the options for output.
|
|
594
|
-
* @param command The command to add the options to.
|
|
595
|
-
* @param opts The options of what to include.
|
|
596
|
-
* @param opts.noConsole Do not output to the console.
|
|
597
|
-
* @param opts.json Output to a JSON file.
|
|
598
|
-
* @param opts.env Output to an environment file.
|
|
599
|
-
* @param opts.mergeJson Merge existing JSON file.
|
|
600
|
-
* @param opts.mergeEnv Merge existing environment file.
|
|
601
|
-
*/
|
|
602
|
-
static output(command, opts) {
|
|
603
|
-
if (opts.noConsole) {
|
|
604
|
-
command.option(core.I18n.formatMessage("cli.options.no-console.param"), core.I18n.formatMessage("cli.options.no-console.description"));
|
|
605
|
-
}
|
|
606
|
-
if (opts.json) {
|
|
607
|
-
command.option(core.I18n.formatMessage("cli.options.json.param"), core.I18n.formatMessage("cli.options.json.description"));
|
|
608
|
-
}
|
|
609
|
-
if (opts.mergeJson) {
|
|
610
|
-
command.option(core.I18n.formatMessage("cli.options.merge-json.param"), core.I18n.formatMessage("cli.options.merge-json.description"));
|
|
611
|
-
}
|
|
612
|
-
if (opts.env) {
|
|
613
|
-
command.option(core.I18n.formatMessage("cli.options.env.param"), core.I18n.formatMessage("cli.options.env.description"));
|
|
614
|
-
}
|
|
615
|
-
if (opts.mergeEnv) {
|
|
616
|
-
command.option(core.I18n.formatMessage("cli.options.merge-env.param"), core.I18n.formatMessage("cli.options.merge-env.description"));
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// Copyright 2024 IOTA Stiftung.
|
|
622
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
623
|
-
/**
|
|
624
|
-
* Parameter utilities for the CLI.
|
|
625
|
-
*/
|
|
626
|
-
class CLIParam {
|
|
627
|
-
/**
|
|
628
|
-
* Check the option to see if it exists.
|
|
629
|
-
* @param optionName The name of the option.
|
|
630
|
-
* @param optionValue The option value.
|
|
631
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
632
|
-
* @returns The final option value.
|
|
633
|
-
* @throws An error if the option is invalid.
|
|
634
|
-
*/
|
|
635
|
-
static env(optionName, optionValue, allowEnvVar) {
|
|
636
|
-
if (allowEnvVar && optionValue?.startsWith("!")) {
|
|
637
|
-
const envValueName = optionValue.slice(1);
|
|
638
|
-
const envValue = process.env[envValueName];
|
|
639
|
-
if (core.Is.empty(envValue)) {
|
|
640
|
-
throw new core.GeneralError("commands", "commands.common.missingEnv", {
|
|
641
|
-
option: optionName,
|
|
642
|
-
value: envValueName
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
else {
|
|
646
|
-
optionValue = envValue;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
return optionValue;
|
|
650
|
-
}
|
|
651
|
-
/**
|
|
652
|
-
* Check the option to see if the String exists.
|
|
653
|
-
* @param optionName The name of the option.
|
|
654
|
-
* @param optionValue The option value.
|
|
655
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
656
|
-
* @returns The final option value.
|
|
657
|
-
* @throws An error if the option is invalid.
|
|
658
|
-
*/
|
|
659
|
-
static stringValue(optionName, optionValue, allowEnvVar = true) {
|
|
660
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
661
|
-
core.Guards.stringValue("commands", optionName, optionValue);
|
|
662
|
-
return optionValue;
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Check the option to see if it is a url.
|
|
666
|
-
* @param optionName The name of the option.
|
|
667
|
-
* @param optionValue The option value.
|
|
668
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
669
|
-
* @returns The final option value.
|
|
670
|
-
* @throws An error if the option is invalid.
|
|
671
|
-
*/
|
|
672
|
-
static url(optionName, optionValue, allowEnvVar = true) {
|
|
673
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
674
|
-
core.Url.guard("commands", optionName, optionValue);
|
|
675
|
-
return optionValue;
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Check the option to see if it exists and is a number.
|
|
679
|
-
* @param optionName The name of the option.
|
|
680
|
-
* @param optionValue The option value.
|
|
681
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
682
|
-
* @param minValue The minimum value.
|
|
683
|
-
* @param maxValue The maximum value.
|
|
684
|
-
* @returns The final option value.
|
|
685
|
-
* @throws An error if the option is invalid.
|
|
686
|
-
*/
|
|
687
|
-
static number(optionName, optionValue, allowEnvVar = true, minValue = 0, maxValue) {
|
|
688
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
689
|
-
const coerced = core.Coerce.number(optionValue);
|
|
690
|
-
if (core.Is.number(coerced)) {
|
|
691
|
-
if (core.Is.number(minValue) && coerced < minValue) {
|
|
692
|
-
throw new core.GeneralError("commands", "commands.common.optionMinValue", {
|
|
693
|
-
option: optionName,
|
|
694
|
-
value: optionValue,
|
|
695
|
-
minValue
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
else if (core.Is.number(maxValue) && coerced > maxValue) {
|
|
699
|
-
throw new core.GeneralError("commands", "commands.common.optionMaxValue", {
|
|
700
|
-
option: optionName,
|
|
701
|
-
value: optionValue,
|
|
702
|
-
maxValue
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
return coerced;
|
|
706
|
-
}
|
|
707
|
-
core.Guards.number("commands", optionName, optionValue);
|
|
708
|
-
return 0;
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* Check the option to see if it exists and is an integer.
|
|
712
|
-
* @param optionName The name of the option.
|
|
713
|
-
* @param optionValue The option value.
|
|
714
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
715
|
-
* @param minValue The minimum value.
|
|
716
|
-
* @param maxValue The maximum value.
|
|
717
|
-
* @returns The final option value.
|
|
718
|
-
* @throws An error if the option is invalid.
|
|
719
|
-
*/
|
|
720
|
-
static integer(optionName, optionValue, allowEnvVar = true, minValue = 0, maxValue) {
|
|
721
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
722
|
-
const coerced = core.Coerce.number(optionValue);
|
|
723
|
-
if (core.Is.integer(coerced)) {
|
|
724
|
-
if (core.Is.number(minValue) && coerced < minValue) {
|
|
725
|
-
throw new core.GeneralError("commands", "commands.common.optionMinValue", {
|
|
726
|
-
option: optionName,
|
|
727
|
-
value: optionValue,
|
|
728
|
-
minValue
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
else if (core.Is.number(maxValue) && coerced > maxValue) {
|
|
732
|
-
throw new core.GeneralError("commands", "commands.common.optionMaxValue", {
|
|
733
|
-
option: optionName,
|
|
734
|
-
value: optionValue,
|
|
735
|
-
maxValue
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
return coerced;
|
|
739
|
-
}
|
|
740
|
-
core.Guards.integer("commands", optionName, optionValue);
|
|
741
|
-
return 0;
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Check the option to see if it exists and is a big number.
|
|
745
|
-
* @param optionName The name of the option.
|
|
746
|
-
* @param optionValue The option value.
|
|
747
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
748
|
-
* @param minValue The minimum value.
|
|
749
|
-
* @param maxValue The maximum value.
|
|
750
|
-
* @returns The final option value.
|
|
751
|
-
* @throws An error if the option is invalid.
|
|
752
|
-
*/
|
|
753
|
-
static bigint(optionName, optionValue, allowEnvVar = true, minValue = 0n, maxValue) {
|
|
754
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
755
|
-
const coerced = core.Coerce.bigint(optionValue);
|
|
756
|
-
if (core.Is.bigint(coerced)) {
|
|
757
|
-
if (core.Is.bigint(minValue) && coerced < minValue) {
|
|
758
|
-
throw new core.GeneralError("commands", "commands.common.optionMinValue", {
|
|
759
|
-
option: optionName,
|
|
760
|
-
value: optionValue,
|
|
761
|
-
minValue
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
else if (core.Is.bigint(maxValue) && coerced > maxValue) {
|
|
765
|
-
throw new core.GeneralError("commands", "commands.common.optionMaxValue", {
|
|
766
|
-
option: optionName,
|
|
767
|
-
value: optionValue,
|
|
768
|
-
maxValue
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
return coerced;
|
|
772
|
-
}
|
|
773
|
-
core.Guards.bigint("commands", optionName, optionValue);
|
|
774
|
-
return 0n;
|
|
775
|
-
}
|
|
776
|
-
/**
|
|
777
|
-
* Check the option to see if it exists and is a boolean.
|
|
778
|
-
* @param optionName The name of the option.
|
|
779
|
-
* @param optionValue The option value.
|
|
780
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
781
|
-
* @returns The final option value.
|
|
782
|
-
* @throws An error if the option is invalid.
|
|
783
|
-
*/
|
|
784
|
-
static boolean(optionName, optionValue, allowEnvVar = true) {
|
|
785
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
786
|
-
const coerced = core.Coerce.boolean(optionValue);
|
|
787
|
-
if (core.Is.boolean(coerced)) {
|
|
788
|
-
return coerced;
|
|
789
|
-
}
|
|
790
|
-
core.Guards.boolean("commands", optionName, optionValue);
|
|
791
|
-
return false;
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Check the option to see if it exists and is hex.
|
|
795
|
-
* @param optionName The name of the option.
|
|
796
|
-
* @param optionValue The option value.
|
|
797
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
798
|
-
* @returns The final option value.
|
|
799
|
-
* @throws An error if the option is invalid.
|
|
800
|
-
*/
|
|
801
|
-
static hex(optionName, optionValue, allowEnvVar = true) {
|
|
802
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
803
|
-
if (core.Is.stringHex(optionValue, true)) {
|
|
804
|
-
return core.Converter.hexToBytes(optionValue);
|
|
805
|
-
}
|
|
806
|
-
throw new core.GeneralError("commands", "commands.common.optionInvalidHex", {
|
|
807
|
-
option: optionName,
|
|
808
|
-
value: optionValue
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Check the option to see if it exists and is base64.
|
|
813
|
-
* @param optionName The name of the option.
|
|
814
|
-
* @param optionValue The option value.
|
|
815
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
816
|
-
* @returns The final option value.
|
|
817
|
-
* @throws An error if the option is invalid.
|
|
818
|
-
*/
|
|
819
|
-
static base64(optionName, optionValue, allowEnvVar = true) {
|
|
820
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
821
|
-
if (core.Is.stringBase64(optionValue)) {
|
|
822
|
-
return core.Converter.base64ToBytes(optionValue);
|
|
823
|
-
}
|
|
824
|
-
throw new core.GeneralError("commands", "commands.common.optionInvalidBase64", {
|
|
825
|
-
option: optionName,
|
|
826
|
-
value: optionValue
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Check the option to see if it exists and is hex or base64.
|
|
831
|
-
* @param optionName The name of the option.
|
|
832
|
-
* @param optionValue The option value.
|
|
833
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
834
|
-
* @returns The final option value.
|
|
835
|
-
* @throws An error if the option is invalid.
|
|
836
|
-
*/
|
|
837
|
-
static hexBase64(optionName, optionValue, allowEnvVar = true) {
|
|
838
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
839
|
-
if (core.Is.stringHex(optionValue, true)) {
|
|
840
|
-
return core.Converter.hexToBytes(optionValue);
|
|
841
|
-
}
|
|
842
|
-
else if (core.Is.stringBase64(optionValue)) {
|
|
843
|
-
return core.Converter.base64ToBytes(optionValue);
|
|
844
|
-
}
|
|
845
|
-
throw new core.GeneralError("commands", "commands.common.optionInvalidHexBase64", {
|
|
846
|
-
option: optionName,
|
|
847
|
-
value: optionValue
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* Check the option to see if it exists and is bech32.
|
|
852
|
-
* @param optionName The name of the option.
|
|
853
|
-
* @param optionValue The option value.
|
|
854
|
-
* @param allowEnvVar Allow the option to be read from an env var.
|
|
855
|
-
* @returns The final option value.
|
|
856
|
-
* @throws An error if the option is invalid.
|
|
857
|
-
*/
|
|
858
|
-
static bech32(optionName, optionValue, allowEnvVar = true) {
|
|
859
|
-
optionValue = CLIParam.env(optionName, optionValue, allowEnvVar);
|
|
860
|
-
if (crypto.Bech32.isBech32(optionValue)) {
|
|
861
|
-
return optionValue;
|
|
862
|
-
}
|
|
863
|
-
throw new core.GeneralError("commands", "commands.common.optionInvalidBech32", {
|
|
864
|
-
option: optionName,
|
|
865
|
-
value: optionValue
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
exports.CLIBase = CLIBase;
|
|
871
|
-
exports.CLIDisplay = CLIDisplay;
|
|
872
|
-
exports.CLIOptions = CLIOptions;
|
|
873
|
-
exports.CLIParam = CLIParam;
|
|
874
|
-
exports.CLIUtils = CLIUtils;
|
|
875
|
-
exports.addGlobalOptions = addGlobalOptions;
|
|
876
|
-
exports.handleGlobalOptions = handleGlobalOptions;
|
|
877
|
-
exports.initGlobalOptions = initGlobalOptions;
|
|
878
|
-
exports.initLocales = initLocales;
|