[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
[e29cc2e] | 3 | const fs = require('fs');
|
---|
| 4 | const path = require('path');
|
---|
| 5 | const colors = require('picocolors');
|
---|
[6a3a178] | 6 | const { loadConfig, optimize } = require('../svgo-node.js');
|
---|
| 7 | const pluginsMap = require('../../plugins/plugins.js');
|
---|
| 8 | const PKG = require('../../package.json');
|
---|
| 9 | const { encodeSVGDatauri, decodeSVGDatauri } = require('./tools.js');
|
---|
| 10 |
|
---|
| 11 | const regSVGFile = /\.svg$/i;
|
---|
| 12 |
|
---|
| 13 | /**
|
---|
| 14 | * Synchronously check if path is a directory. Tolerant to errors like ENOENT.
|
---|
| 15 | * @param {string} path
|
---|
| 16 | */
|
---|
| 17 | function checkIsDir(path) {
|
---|
| 18 | try {
|
---|
[e29cc2e] | 19 | return fs.lstatSync(path).isDirectory();
|
---|
[6a3a178] | 20 | } catch (e) {
|
---|
| 21 | return false;
|
---|
| 22 | }
|
---|
| 23 | }
|
---|
| 24 |
|
---|
| 25 | module.exports = function makeProgram(program) {
|
---|
| 26 | program
|
---|
| 27 | .name(PKG.name)
|
---|
| 28 | .description(PKG.description, {
|
---|
| 29 | INPUT: 'Alias to --input',
|
---|
| 30 | })
|
---|
| 31 | .version(PKG.version, '-v, --version')
|
---|
| 32 | .arguments('[INPUT...]')
|
---|
| 33 | .option('-i, --input <INPUT...>', 'Input files, "-" for STDIN')
|
---|
| 34 | .option('-s, --string <STRING>', 'Input SVG data string')
|
---|
| 35 | .option(
|
---|
| 36 | '-f, --folder <FOLDER>',
|
---|
| 37 | 'Input folder, optimize and rewrite all *.svg files'
|
---|
| 38 | )
|
---|
| 39 | .option(
|
---|
| 40 | '-o, --output <OUTPUT...>',
|
---|
| 41 | 'Output file or folder (by default the same as the input), "-" for STDOUT'
|
---|
| 42 | )
|
---|
| 43 | .option(
|
---|
| 44 | '-p, --precision <INTEGER>',
|
---|
| 45 | 'Set number of digits in the fractional part, overrides plugins params'
|
---|
| 46 | )
|
---|
| 47 | .option('--config <CONFIG>', 'Custom config file, only .js is supported')
|
---|
| 48 | .option(
|
---|
| 49 | '--datauri <FORMAT>',
|
---|
| 50 | 'Output as Data URI string (base64), URI encoded (enc) or unencoded (unenc)'
|
---|
| 51 | )
|
---|
| 52 | .option(
|
---|
| 53 | '--multipass',
|
---|
| 54 | 'Pass over SVGs multiple times to ensure all optimizations are applied'
|
---|
| 55 | )
|
---|
| 56 | .option('--pretty', 'Make SVG pretty printed')
|
---|
| 57 | .option('--indent <INTEGER>', 'Indent number when pretty printing SVGs')
|
---|
| 58 | .option(
|
---|
| 59 | '--eol <EOL>',
|
---|
| 60 | 'Line break to use when outputting SVG: lf, crlf. If unspecified, uses platform default.'
|
---|
| 61 | )
|
---|
| 62 | .option('--final-newline', 'Ensure SVG ends with a line break')
|
---|
| 63 | .option(
|
---|
| 64 | '-r, --recursive',
|
---|
| 65 | "Use with '--folder'. Optimizes *.svg files in folders recursively."
|
---|
| 66 | )
|
---|
| 67 | .option(
|
---|
| 68 | '--exclude <PATTERN...>',
|
---|
| 69 | "Use with '--folder'. Exclude files matching regular expression pattern."
|
---|
| 70 | )
|
---|
| 71 | .option(
|
---|
| 72 | '-q, --quiet',
|
---|
| 73 | 'Only output error messages, not regular status messages'
|
---|
| 74 | )
|
---|
| 75 | .option('--show-plugins', 'Show available plugins and exit')
|
---|
[e29cc2e] | 76 | // used by picocolors internally
|
---|
| 77 | .option('--no-color', 'Output plain text without color')
|
---|
[6a3a178] | 78 | .action(action);
|
---|
| 79 | };
|
---|
| 80 |
|
---|
| 81 | async function action(args, opts, command) {
|
---|
| 82 | var input = opts.input || args;
|
---|
| 83 | var output = opts.output;
|
---|
| 84 | var config = {};
|
---|
| 85 |
|
---|
| 86 | if (opts.precision != null) {
|
---|
| 87 | const number = Number.parseInt(opts.precision, 10);
|
---|
| 88 | if (Number.isNaN(number)) {
|
---|
| 89 | console.error(
|
---|
| 90 | "error: option '-p, --precision' argument must be an integer number"
|
---|
| 91 | );
|
---|
| 92 | process.exit(1);
|
---|
| 93 | } else {
|
---|
| 94 | opts.precision = number;
|
---|
| 95 | }
|
---|
| 96 | }
|
---|
| 97 |
|
---|
| 98 | if (opts.datauri != null) {
|
---|
| 99 | if (
|
---|
| 100 | opts.datauri !== 'base64' &&
|
---|
| 101 | opts.datauri !== 'enc' &&
|
---|
| 102 | opts.datauri !== 'unenc'
|
---|
| 103 | ) {
|
---|
| 104 | console.error(
|
---|
| 105 | "error: option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'"
|
---|
| 106 | );
|
---|
| 107 | process.exit(1);
|
---|
| 108 | }
|
---|
| 109 | }
|
---|
| 110 |
|
---|
| 111 | if (opts.indent != null) {
|
---|
| 112 | const number = Number.parseInt(opts.indent, 10);
|
---|
| 113 | if (Number.isNaN(number)) {
|
---|
| 114 | console.error(
|
---|
| 115 | "error: option '--indent' argument must be an integer number"
|
---|
| 116 | );
|
---|
| 117 | process.exit(1);
|
---|
| 118 | } else {
|
---|
| 119 | opts.indent = number;
|
---|
| 120 | }
|
---|
| 121 | }
|
---|
| 122 |
|
---|
| 123 | if (opts.eol != null && opts.eol !== 'lf' && opts.eol !== 'crlf') {
|
---|
| 124 | console.error(
|
---|
| 125 | "error: option '--eol' must have one of the following values: 'lf' or 'crlf'"
|
---|
| 126 | );
|
---|
| 127 | process.exit(1);
|
---|
| 128 | }
|
---|
| 129 |
|
---|
| 130 | // --show-plugins
|
---|
| 131 | if (opts.showPlugins) {
|
---|
| 132 | showAvailablePlugins();
|
---|
| 133 | return;
|
---|
| 134 | }
|
---|
| 135 |
|
---|
| 136 | // w/o anything
|
---|
| 137 | if (
|
---|
| 138 | (input.length === 0 || input[0] === '-') &&
|
---|
| 139 | !opts.string &&
|
---|
| 140 | !opts.stdin &&
|
---|
| 141 | !opts.folder &&
|
---|
| 142 | process.stdin.isTTY === true
|
---|
| 143 | ) {
|
---|
| 144 | return command.help();
|
---|
| 145 | }
|
---|
| 146 |
|
---|
| 147 | if (
|
---|
| 148 | typeof process == 'object' &&
|
---|
| 149 | process.versions &&
|
---|
| 150 | process.versions.node &&
|
---|
| 151 | PKG &&
|
---|
| 152 | PKG.engines.node
|
---|
| 153 | ) {
|
---|
| 154 | var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
|
---|
| 155 | if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) {
|
---|
| 156 | throw Error(
|
---|
| 157 | `${PKG.name} requires Node.js version ${nodeVersion} or higher.`
|
---|
| 158 | );
|
---|
| 159 | }
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | // --config
|
---|
| 163 | const loadedConfig = await loadConfig(opts.config);
|
---|
| 164 | if (loadedConfig != null) {
|
---|
| 165 | config = loadedConfig;
|
---|
| 166 | }
|
---|
| 167 |
|
---|
| 168 | // --quiet
|
---|
| 169 | if (opts.quiet) {
|
---|
| 170 | config.quiet = opts.quiet;
|
---|
| 171 | }
|
---|
| 172 |
|
---|
| 173 | // --recursive
|
---|
| 174 | if (opts.recursive) {
|
---|
| 175 | config.recursive = opts.recursive;
|
---|
| 176 | }
|
---|
| 177 |
|
---|
| 178 | // --exclude
|
---|
| 179 | config.exclude = opts.exclude
|
---|
| 180 | ? opts.exclude.map((pattern) => RegExp(pattern))
|
---|
| 181 | : [];
|
---|
| 182 |
|
---|
| 183 | // --precision
|
---|
| 184 | if (opts.precision != null) {
|
---|
| 185 | var precision = Math.min(Math.max(0, opts.precision), 20);
|
---|
| 186 | config.floatPrecision = precision;
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | // --multipass
|
---|
| 190 | if (opts.multipass) {
|
---|
| 191 | config.multipass = true;
|
---|
| 192 | }
|
---|
| 193 |
|
---|
| 194 | // --pretty
|
---|
| 195 | if (opts.pretty) {
|
---|
| 196 | config.js2svg = config.js2svg || {};
|
---|
| 197 | config.js2svg.pretty = true;
|
---|
| 198 | if (opts.indent != null) {
|
---|
| 199 | config.js2svg.indent = opts.indent;
|
---|
| 200 | }
|
---|
| 201 | }
|
---|
| 202 |
|
---|
| 203 | // --eol
|
---|
| 204 | if (opts.eol) {
|
---|
| 205 | config.js2svg = config.js2svg || {};
|
---|
| 206 | config.js2svg.eol = opts.eol;
|
---|
| 207 | }
|
---|
| 208 |
|
---|
| 209 | // --final-newline
|
---|
| 210 | if (opts.finalNewline) {
|
---|
| 211 | config.js2svg = config.js2svg || {};
|
---|
| 212 | config.js2svg.finalNewline = true;
|
---|
| 213 | }
|
---|
| 214 |
|
---|
| 215 | // --output
|
---|
| 216 | if (output) {
|
---|
| 217 | if (input.length && input[0] != '-') {
|
---|
| 218 | if (output.length == 1 && checkIsDir(output[0])) {
|
---|
| 219 | var dir = output[0];
|
---|
| 220 | for (var i = 0; i < input.length; i++) {
|
---|
| 221 | output[i] = checkIsDir(input[i])
|
---|
| 222 | ? input[i]
|
---|
[e29cc2e] | 223 | : path.resolve(dir, path.basename(input[i]));
|
---|
[6a3a178] | 224 | }
|
---|
| 225 | } else if (output.length < input.length) {
|
---|
| 226 | output = output.concat(input.slice(output.length));
|
---|
| 227 | }
|
---|
| 228 | }
|
---|
| 229 | } else if (input.length) {
|
---|
| 230 | output = input;
|
---|
| 231 | } else if (opts.string) {
|
---|
| 232 | output = '-';
|
---|
| 233 | }
|
---|
| 234 |
|
---|
| 235 | if (opts.datauri) {
|
---|
| 236 | config.datauri = opts.datauri;
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 | // --folder
|
---|
| 240 | if (opts.folder) {
|
---|
| 241 | var ouputFolder = (output && output[0]) || opts.folder;
|
---|
| 242 | await optimizeFolder(config, opts.folder, ouputFolder);
|
---|
| 243 | }
|
---|
| 244 |
|
---|
| 245 | // --input
|
---|
| 246 | if (input.length !== 0) {
|
---|
| 247 | // STDIN
|
---|
| 248 | if (input[0] === '-') {
|
---|
| 249 | return new Promise((resolve, reject) => {
|
---|
| 250 | var data = '',
|
---|
| 251 | file = output[0];
|
---|
| 252 |
|
---|
| 253 | process.stdin
|
---|
| 254 | .on('data', (chunk) => (data += chunk))
|
---|
| 255 | .once('end', () =>
|
---|
| 256 | processSVGData(config, { input: 'string' }, data, file).then(
|
---|
| 257 | resolve,
|
---|
| 258 | reject
|
---|
| 259 | )
|
---|
| 260 | );
|
---|
| 261 | });
|
---|
| 262 | // file
|
---|
| 263 | } else {
|
---|
| 264 | await Promise.all(
|
---|
| 265 | input.map((file, n) => optimizeFile(config, file, output[n]))
|
---|
| 266 | );
|
---|
| 267 | }
|
---|
| 268 |
|
---|
| 269 | // --string
|
---|
| 270 | } else if (opts.string) {
|
---|
| 271 | var data = decodeSVGDatauri(opts.string);
|
---|
| 272 |
|
---|
| 273 | return processSVGData(config, { input: 'string' }, data, output[0]);
|
---|
| 274 | }
|
---|
| 275 | }
|
---|
| 276 |
|
---|
| 277 | /**
|
---|
| 278 | * Optimize SVG files in a directory.
|
---|
| 279 | * @param {Object} config options
|
---|
| 280 | * @param {string} dir input directory
|
---|
| 281 | * @param {string} output output directory
|
---|
| 282 | * @return {Promise}
|
---|
| 283 | */
|
---|
| 284 | function optimizeFolder(config, dir, output) {
|
---|
| 285 | if (!config.quiet) {
|
---|
| 286 | console.log(`Processing directory '${dir}':\n`);
|
---|
| 287 | }
|
---|
[e29cc2e] | 288 | return fs.promises
|
---|
[6a3a178] | 289 | .readdir(dir)
|
---|
| 290 | .then((files) => processDirectory(config, dir, files, output));
|
---|
| 291 | }
|
---|
| 292 |
|
---|
| 293 | /**
|
---|
| 294 | * Process given files, take only SVG.
|
---|
| 295 | * @param {Object} config options
|
---|
| 296 | * @param {string} dir input directory
|
---|
| 297 | * @param {Array} files list of file names in the directory
|
---|
| 298 | * @param {string} output output directory
|
---|
| 299 | * @return {Promise}
|
---|
| 300 | */
|
---|
| 301 | function processDirectory(config, dir, files, output) {
|
---|
| 302 | // take only *.svg files, recursively if necessary
|
---|
| 303 | var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output);
|
---|
| 304 |
|
---|
| 305 | return svgFilesDescriptions.length
|
---|
| 306 | ? Promise.all(
|
---|
| 307 | svgFilesDescriptions.map((fileDescription) =>
|
---|
| 308 | optimizeFile(
|
---|
| 309 | config,
|
---|
| 310 | fileDescription.inputPath,
|
---|
| 311 | fileDescription.outputPath
|
---|
| 312 | )
|
---|
| 313 | )
|
---|
| 314 | )
|
---|
| 315 | : Promise.reject(
|
---|
| 316 | new Error(`No SVG files have been found in '${dir}' directory.`)
|
---|
| 317 | );
|
---|
| 318 | }
|
---|
| 319 |
|
---|
| 320 | /**
|
---|
| 321 | * Get svg files descriptions
|
---|
| 322 | * @param {Object} config options
|
---|
| 323 | * @param {string} dir input directory
|
---|
| 324 | * @param {Array} files list of file names in the directory
|
---|
| 325 | * @param {string} output output directory
|
---|
| 326 | * @return {Array}
|
---|
| 327 | */
|
---|
| 328 | function getFilesDescriptions(config, dir, files, output) {
|
---|
| 329 | const filesInThisFolder = files
|
---|
| 330 | .filter(
|
---|
| 331 | (name) =>
|
---|
| 332 | regSVGFile.test(name) &&
|
---|
| 333 | !config.exclude.some((regExclude) => regExclude.test(name))
|
---|
| 334 | )
|
---|
| 335 | .map((name) => ({
|
---|
[e29cc2e] | 336 | inputPath: path.resolve(dir, name),
|
---|
| 337 | outputPath: path.resolve(output, name),
|
---|
[6a3a178] | 338 | }));
|
---|
| 339 |
|
---|
| 340 | return config.recursive
|
---|
| 341 | ? [].concat(
|
---|
| 342 | filesInThisFolder,
|
---|
| 343 | files
|
---|
[e29cc2e] | 344 | .filter((name) => checkIsDir(path.resolve(dir, name)))
|
---|
[6a3a178] | 345 | .map((subFolderName) => {
|
---|
[e29cc2e] | 346 | const subFolderPath = path.resolve(dir, subFolderName);
|
---|
| 347 | const subFolderFiles = fs.readdirSync(subFolderPath);
|
---|
| 348 | const subFolderOutput = path.resolve(output, subFolderName);
|
---|
[6a3a178] | 349 | return getFilesDescriptions(
|
---|
| 350 | config,
|
---|
| 351 | subFolderPath,
|
---|
| 352 | subFolderFiles,
|
---|
| 353 | subFolderOutput
|
---|
| 354 | );
|
---|
| 355 | })
|
---|
| 356 | .reduce((a, b) => [].concat(a, b), [])
|
---|
| 357 | )
|
---|
| 358 | : filesInThisFolder;
|
---|
| 359 | }
|
---|
| 360 |
|
---|
| 361 | /**
|
---|
| 362 | * Read SVG file and pass to processing.
|
---|
| 363 | * @param {Object} config options
|
---|
| 364 | * @param {string} file
|
---|
| 365 | * @param {string} output
|
---|
| 366 | * @return {Promise}
|
---|
| 367 | */
|
---|
| 368 | function optimizeFile(config, file, output) {
|
---|
[e29cc2e] | 369 | return fs.promises.readFile(file, 'utf8').then(
|
---|
[6a3a178] | 370 | (data) =>
|
---|
| 371 | processSVGData(config, { input: 'file', path: file }, data, output, file),
|
---|
| 372 | (error) => checkOptimizeFileError(config, file, output, error)
|
---|
| 373 | );
|
---|
| 374 | }
|
---|
| 375 |
|
---|
| 376 | /**
|
---|
| 377 | * Optimize SVG data.
|
---|
| 378 | * @param {Object} config options
|
---|
| 379 | * @param {string} data SVG content to optimize
|
---|
| 380 | * @param {string} output where to write optimized file
|
---|
| 381 | * @param {string} [input] input file name (being used if output is a directory)
|
---|
| 382 | * @return {Promise}
|
---|
| 383 | */
|
---|
| 384 | function processSVGData(config, info, data, output, input) {
|
---|
| 385 | var startTime = Date.now(),
|
---|
| 386 | prevFileSize = Buffer.byteLength(data, 'utf8');
|
---|
| 387 |
|
---|
| 388 | const result = optimize(data, { ...config, ...info });
|
---|
| 389 | if (result.modernError) {
|
---|
[e29cc2e] | 390 | console.error(colors.red(result.modernError.toString()));
|
---|
[6a3a178] | 391 | process.exit(1);
|
---|
| 392 | }
|
---|
| 393 | if (config.datauri) {
|
---|
| 394 | result.data = encodeSVGDatauri(result.data, config.datauri);
|
---|
| 395 | }
|
---|
| 396 | var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
|
---|
| 397 | processingTime = Date.now() - startTime;
|
---|
| 398 |
|
---|
| 399 | return writeOutput(input, output, result.data).then(
|
---|
| 400 | function () {
|
---|
| 401 | if (!config.quiet && output != '-') {
|
---|
| 402 | if (input) {
|
---|
[e29cc2e] | 403 | console.log(`\n${path.basename(input)}:`);
|
---|
[6a3a178] | 404 | }
|
---|
| 405 | printTimeInfo(processingTime);
|
---|
| 406 | printProfitInfo(prevFileSize, resultFileSize);
|
---|
| 407 | }
|
---|
| 408 | },
|
---|
| 409 | (error) =>
|
---|
| 410 | Promise.reject(
|
---|
| 411 | new Error(
|
---|
| 412 | error.code === 'ENOTDIR'
|
---|
| 413 | ? `Error: output '${output}' is not a directory.`
|
---|
| 414 | : error
|
---|
| 415 | )
|
---|
| 416 | )
|
---|
| 417 | );
|
---|
| 418 | }
|
---|
| 419 |
|
---|
| 420 | /**
|
---|
| 421 | * Write result of an optimization.
|
---|
| 422 | * @param {string} input
|
---|
| 423 | * @param {string} output output file name. '-' for stdout
|
---|
| 424 | * @param {string} data data to write
|
---|
| 425 | * @return {Promise}
|
---|
| 426 | */
|
---|
| 427 | function writeOutput(input, output, data) {
|
---|
| 428 | if (output == '-') {
|
---|
| 429 | console.log(data);
|
---|
| 430 | return Promise.resolve();
|
---|
| 431 | }
|
---|
| 432 |
|
---|
[e29cc2e] | 433 | fs.mkdirSync(path.dirname(output), { recursive: true });
|
---|
[6a3a178] | 434 |
|
---|
[e29cc2e] | 435 | return fs.promises
|
---|
[6a3a178] | 436 | .writeFile(output, data, 'utf8')
|
---|
| 437 | .catch((error) => checkWriteFileError(input, output, data, error));
|
---|
| 438 | }
|
---|
| 439 |
|
---|
| 440 | /**
|
---|
| 441 | * Write a time taken by optimization.
|
---|
| 442 | * @param {number} time time in milliseconds.
|
---|
| 443 | */
|
---|
| 444 | function printTimeInfo(time) {
|
---|
| 445 | console.log(`Done in ${time} ms!`);
|
---|
| 446 | }
|
---|
| 447 |
|
---|
| 448 | /**
|
---|
| 449 | * Write optimizing information in human readable format.
|
---|
| 450 | * @param {number} inBytes size before optimization.
|
---|
| 451 | * @param {number} outBytes size after optimization.
|
---|
| 452 | */
|
---|
| 453 | function printProfitInfo(inBytes, outBytes) {
|
---|
| 454 | var profitPercents = 100 - (outBytes * 100) / inBytes;
|
---|
| 455 |
|
---|
| 456 | console.log(
|
---|
| 457 | Math.round((inBytes / 1024) * 1000) / 1000 +
|
---|
| 458 | ' KiB' +
|
---|
| 459 | (profitPercents < 0 ? ' + ' : ' - ') +
|
---|
[e29cc2e] | 460 | colors.green(Math.abs(Math.round(profitPercents * 10) / 10) + '%') +
|
---|
[6a3a178] | 461 | ' = ' +
|
---|
| 462 | Math.round((outBytes / 1024) * 1000) / 1000 +
|
---|
| 463 | ' KiB'
|
---|
| 464 | );
|
---|
| 465 | }
|
---|
| 466 |
|
---|
| 467 | /**
|
---|
| 468 | * Check for errors, if it's a dir optimize the dir.
|
---|
| 469 | * @param {Object} config
|
---|
| 470 | * @param {string} input
|
---|
| 471 | * @param {string} output
|
---|
| 472 | * @param {Error} error
|
---|
| 473 | * @return {Promise}
|
---|
| 474 | */
|
---|
| 475 | function checkOptimizeFileError(config, input, output, error) {
|
---|
| 476 | if (error.code == 'EISDIR') {
|
---|
| 477 | return optimizeFolder(config, input, output);
|
---|
| 478 | } else if (error.code == 'ENOENT') {
|
---|
| 479 | return Promise.reject(
|
---|
| 480 | new Error(`Error: no such file or directory '${error.path}'.`)
|
---|
| 481 | );
|
---|
| 482 | }
|
---|
| 483 | return Promise.reject(error);
|
---|
| 484 | }
|
---|
| 485 |
|
---|
| 486 | /**
|
---|
| 487 | * Check for saving file error. If the output is a dir, then write file there.
|
---|
| 488 | * @param {string} input
|
---|
| 489 | * @param {string} output
|
---|
| 490 | * @param {string} data
|
---|
| 491 | * @param {Error} error
|
---|
| 492 | * @return {Promise}
|
---|
| 493 | */
|
---|
| 494 | function checkWriteFileError(input, output, data, error) {
|
---|
| 495 | if (error.code == 'EISDIR' && input) {
|
---|
[e29cc2e] | 496 | return fs.promises.writeFile(
|
---|
| 497 | path.resolve(output, path.basename(input)),
|
---|
[6a3a178] | 498 | data,
|
---|
| 499 | 'utf8'
|
---|
| 500 | );
|
---|
| 501 | } else {
|
---|
| 502 | return Promise.reject(error);
|
---|
| 503 | }
|
---|
| 504 | }
|
---|
| 505 |
|
---|
| 506 | /**
|
---|
| 507 | * Show list of available plugins with short description.
|
---|
| 508 | */
|
---|
| 509 | function showAvailablePlugins() {
|
---|
| 510 | const list = Object.entries(pluginsMap)
|
---|
| 511 | .sort(([a], [b]) => a.localeCompare(b))
|
---|
[e29cc2e] | 512 | .map(([name, plugin]) => ` [ ${colors.green(name)} ] ${plugin.description}`)
|
---|
[6a3a178] | 513 | .join('\n');
|
---|
| 514 | console.log('Currently available plugins:\n' + list);
|
---|
| 515 | }
|
---|
| 516 |
|
---|
| 517 | module.exports.checkIsDir = checkIsDir;
|
---|