forked from lix-project/lix-website
545 lines
14 KiB
JavaScript
545 lines
14 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const chalk = require('chalk');
|
||
|
const checkInvalidCLIOptions = require('./utils/checkInvalidCLIOptions');
|
||
|
const EOL = require('os').EOL;
|
||
|
const getFormatterOptionsText = require('./utils/getFormatterOptionsText');
|
||
|
const getModulePath = require('./utils/getModulePath');
|
||
|
const getStdin = require('get-stdin');
|
||
|
const meow = require('meow');
|
||
|
const path = require('path');
|
||
|
const printConfig = require('./printConfig');
|
||
|
const resolveFrom = require('resolve-from');
|
||
|
const standalone = require('./standalone');
|
||
|
const writeOutputFile = require('./writeOutputFile');
|
||
|
|
||
|
const EXIT_CODE_ERROR = 2;
|
||
|
|
||
|
/**
|
||
|
* @typedef {object} CLIFlags
|
||
|
* @property {boolean} [cache]
|
||
|
* @property {string} [cacheLocation]
|
||
|
* @property {string | false} config
|
||
|
* @property {string} [configBasedir]
|
||
|
* @property {string} [customSyntax]
|
||
|
* @property {string} [printConfig]
|
||
|
* @property {string} [color]
|
||
|
* @property {string} [customFormatter]
|
||
|
* @property {boolean} [disableDefaultIgnores]
|
||
|
* @property {boolean} [fix]
|
||
|
* @property {string} [formatter="string"]
|
||
|
* @property {string} [help]
|
||
|
* @property {boolean} [ignoreDisables]
|
||
|
* @property {string} [ignorePath]
|
||
|
* @property {string[]} [ignorePattern]
|
||
|
* @property {string} [noColor]
|
||
|
* @property {string} [outputFile]
|
||
|
* @property {boolean} [stdin]
|
||
|
* @property {string} [stdinFilename]
|
||
|
* @property {boolean} [reportNeedlessDisables]
|
||
|
* @property {boolean} [reportInvalidScopeDisables]
|
||
|
* @property {boolean} [reportDescriptionlessDisables]
|
||
|
* @property {number} [maxWarnings]
|
||
|
* @property {string | boolean} quiet
|
||
|
* @property {string} [syntax]
|
||
|
* @property {string} [version]
|
||
|
* @property {boolean} [allowEmptyInput]
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @typedef {object} CLIOptions
|
||
|
* @property {any} input
|
||
|
* @property {any} help
|
||
|
* @property {any} pkg
|
||
|
* @property {Function} showHelp
|
||
|
* @property {Function} showVersion
|
||
|
* @property {CLIFlags} flags
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @typedef {object} OptionBaseType
|
||
|
* @property {any} formatter
|
||
|
* @property {boolean} [cache]
|
||
|
* @property {string} [configFile]
|
||
|
* @property {string} [cacheLocation]
|
||
|
* @property {string} [customSyntax]
|
||
|
* @property {string} [codeFilename]
|
||
|
* @property {string} [configBasedir]
|
||
|
* @property {{ quiet?: any }} configOverrides
|
||
|
* @property {any} [printConfig]
|
||
|
* @property {boolean} [fix]
|
||
|
* @property {boolean} [ignoreDisables]
|
||
|
* @property {any} [ignorePath]
|
||
|
* @property {string} [outputFile]
|
||
|
* @property {boolean} [reportNeedlessDisables]
|
||
|
* @property {boolean} [reportInvalidScopeDisables]
|
||
|
* @property {boolean} [reportDescriptionlessDisables]
|
||
|
* @property {boolean} [disableDefaultIgnores]
|
||
|
* @property {number} [maxWarnings]
|
||
|
* @property {string} [syntax]
|
||
|
* @property {string[]} [ignorePattern]
|
||
|
* @property {boolean} [allowEmptyInput]
|
||
|
* @property {string} [files]
|
||
|
* @property {string} [code]
|
||
|
*/
|
||
|
|
||
|
const meowOptions = {
|
||
|
autoHelp: false,
|
||
|
autoVersion: false,
|
||
|
help: `
|
||
|
Usage: stylelint [input] [options]
|
||
|
|
||
|
Input: Files(s), glob(s), or nothing to use stdin.
|
||
|
|
||
|
If an input argument is wrapped in quotation marks, it will be passed to
|
||
|
globby for cross-platform glob support. node_modules are always ignored.
|
||
|
You can also pass no input and use stdin, instead.
|
||
|
|
||
|
Options:
|
||
|
|
||
|
--config
|
||
|
|
||
|
Path to a specific configuration file (JSON, YAML, or CommonJS), or the
|
||
|
name of a module in node_modules that points to one. If no --config
|
||
|
argument is provided, stylelint will search for configuration files in
|
||
|
the following places, in this order:
|
||
|
- a stylelint property in package.json
|
||
|
- a .stylelintrc file (with or without filename extension:
|
||
|
.json, .yaml, .yml, and .js are available)
|
||
|
- a stylelint.config.js file exporting a JS object
|
||
|
The search will begin in the working directory and move up the directory
|
||
|
tree until a configuration file is found.
|
||
|
|
||
|
--config-basedir
|
||
|
|
||
|
An absolute path to the directory that relative paths defining "extends"
|
||
|
and "plugins" are *relative to*. Only necessary if these values are
|
||
|
relative paths.
|
||
|
|
||
|
--print-config
|
||
|
|
||
|
Print the configuration for the given path.
|
||
|
|
||
|
--ignore-path, -i
|
||
|
|
||
|
Path to a file containing patterns that describe files to ignore. The
|
||
|
path can be absolute or relative to process.cwd(). By default, stylelint
|
||
|
looks for .stylelintignore in process.cwd().
|
||
|
|
||
|
--ignore-pattern, --ip
|
||
|
|
||
|
Pattern of files to ignore (in addition to those in .stylelintignore)
|
||
|
|
||
|
--syntax, -s
|
||
|
|
||
|
Specify a syntax. Options: "css", "css-in-js", "html", "less",
|
||
|
"markdown", "sass", "scss", "sugarss". If you do not specify a syntax,
|
||
|
syntaxes will be automatically inferred by the file extensions
|
||
|
and file content.
|
||
|
|
||
|
--fix
|
||
|
|
||
|
Automatically fix violations of certain rules.
|
||
|
|
||
|
--custom-syntax
|
||
|
|
||
|
Module name or path to a JS file exporting a PostCSS-compatible syntax.
|
||
|
|
||
|
--stdin
|
||
|
|
||
|
Accept stdin input even if it is empty.
|
||
|
|
||
|
--stdin-filename
|
||
|
|
||
|
A filename to assign stdin input.
|
||
|
|
||
|
--ignore-disables, --id
|
||
|
|
||
|
Ignore styleline-disable comments.
|
||
|
|
||
|
--disable-default-ignores, --di
|
||
|
|
||
|
Allow linting of node_modules.
|
||
|
|
||
|
--cache [default: false]
|
||
|
|
||
|
Store the info about processed files in order to only operate on the
|
||
|
changed ones the next time you run stylelint. By default, the cache
|
||
|
is stored in "./.stylelintcache". To adjust this, use --cache-location.
|
||
|
|
||
|
--cache-location [default: '.stylelintcache']
|
||
|
|
||
|
Path to a file or directory to be used for the cache location.
|
||
|
Default is "./.stylelintcache". If a directory is specified, a cache
|
||
|
file will be created inside the specified folder, with a name derived
|
||
|
from a hash of the current working directory.
|
||
|
|
||
|
If the directory for the cache does not exist, make sure you add a trailing "/"
|
||
|
on *nix systems or "\\" on Windows. Otherwise the path will be assumed to be a file.
|
||
|
|
||
|
--formatter, -f [default: "string"]
|
||
|
|
||
|
The output formatter: ${getFormatterOptionsText({ useOr: true })}.
|
||
|
|
||
|
--custom-formatter
|
||
|
|
||
|
Path to a JS file exporting a custom formatting function.
|
||
|
|
||
|
--quiet, -q
|
||
|
|
||
|
Only register violations for rules with an "error"-level severity (ignore
|
||
|
"warning"-level).
|
||
|
|
||
|
--color
|
||
|
--no-color
|
||
|
|
||
|
Force enabling/disabling of color.
|
||
|
|
||
|
--report-needless-disables, --rd
|
||
|
|
||
|
Also report errors for stylelint-disable comments that are not blocking a lint warning.
|
||
|
The process will exit with code ${EXIT_CODE_ERROR} if needless disables are found.
|
||
|
|
||
|
--report-invalid-scope-disables, --risd
|
||
|
|
||
|
Report stylelint-disable comments that used for rules that don't exist within the configuration object.
|
||
|
The process will exit with code ${EXIT_CODE_ERROR} if invalid scope disables are found.
|
||
|
|
||
|
--report-descriptionless-disables, --rdd
|
||
|
|
||
|
Report stylelint-disable comments without a description.
|
||
|
The process will exit with code ${EXIT_CODE_ERROR} if descriptionless disables are found.
|
||
|
|
||
|
--max-warnings, --mw
|
||
|
|
||
|
Number of warnings above which the process will exit with code ${EXIT_CODE_ERROR}.
|
||
|
Useful when setting "defaultSeverity" to "warning" and expecting the
|
||
|
process to fail on warnings (e.g. CI build).
|
||
|
|
||
|
--output-file, -o
|
||
|
|
||
|
Path of file to write report.
|
||
|
|
||
|
--version, -v
|
||
|
|
||
|
Show the currently installed version of stylelint.
|
||
|
|
||
|
--allow-empty-input, --aei
|
||
|
|
||
|
When glob pattern matches no files, the process will exit without throwing an error.
|
||
|
`,
|
||
|
flags: {
|
||
|
allowEmptyInput: {
|
||
|
alias: 'aei',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
cache: {
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
cacheLocation: {
|
||
|
type: 'string',
|
||
|
},
|
||
|
color: {
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
config: {
|
||
|
type: 'string',
|
||
|
},
|
||
|
configBasedir: {
|
||
|
type: 'string',
|
||
|
},
|
||
|
customFormatter: {
|
||
|
type: 'string',
|
||
|
},
|
||
|
customSyntax: {
|
||
|
type: 'string',
|
||
|
},
|
||
|
disableDefaultIgnores: {
|
||
|
alias: 'di',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
fix: {
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
formatter: {
|
||
|
alias: 'f',
|
||
|
default: 'string',
|
||
|
type: 'string',
|
||
|
},
|
||
|
help: {
|
||
|
alias: 'h',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
ignoreDisables: {
|
||
|
alias: 'id',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
ignorePath: {
|
||
|
alias: 'i',
|
||
|
type: 'string',
|
||
|
},
|
||
|
ignorePattern: {
|
||
|
alias: 'ip',
|
||
|
type: 'string',
|
||
|
isMultiple: true,
|
||
|
},
|
||
|
maxWarnings: {
|
||
|
alias: 'mw',
|
||
|
type: 'number',
|
||
|
},
|
||
|
outputFile: {
|
||
|
alias: 'o',
|
||
|
type: 'string',
|
||
|
},
|
||
|
printConfig: {
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
quiet: {
|
||
|
alias: 'q',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
reportDescriptionlessDisables: {
|
||
|
alias: 'rdd',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
reportInvalidScopeDisables: {
|
||
|
alias: 'risd',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
reportNeedlessDisables: {
|
||
|
alias: 'rd',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
stdin: {
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
stdinFilename: {
|
||
|
type: 'string',
|
||
|
},
|
||
|
syntax: {
|
||
|
alias: 's',
|
||
|
type: 'string',
|
||
|
},
|
||
|
version: {
|
||
|
alias: 'v',
|
||
|
type: 'boolean',
|
||
|
},
|
||
|
},
|
||
|
pkg: require('../package.json'),
|
||
|
argv: /** @type {string[]} */ ([]),
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @param {string[]} argv
|
||
|
* @returns {Promise<any>}
|
||
|
*/
|
||
|
module.exports = (argv) => {
|
||
|
const cli = buildCLI(argv);
|
||
|
|
||
|
const invalidOptionsMessage = checkInvalidCLIOptions(meowOptions.flags, cli.flags);
|
||
|
|
||
|
if (invalidOptionsMessage) {
|
||
|
process.stderr.write(invalidOptionsMessage);
|
||
|
process.exit(EXIT_CODE_ERROR); // eslint-disable-line no-process-exit
|
||
|
}
|
||
|
|
||
|
let formatter = cli.flags.formatter;
|
||
|
|
||
|
if (cli.flags.customFormatter) {
|
||
|
const customFormatter = path.isAbsolute(cli.flags.customFormatter)
|
||
|
? cli.flags.customFormatter
|
||
|
: path.join(process.cwd(), cli.flags.customFormatter);
|
||
|
|
||
|
formatter = require(customFormatter);
|
||
|
}
|
||
|
|
||
|
/** @type {OptionBaseType} */
|
||
|
const optionsBase = {
|
||
|
formatter,
|
||
|
configOverrides: {},
|
||
|
};
|
||
|
|
||
|
if (cli.flags.quiet) {
|
||
|
optionsBase.configOverrides.quiet = cli.flags.quiet;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.syntax) {
|
||
|
optionsBase.syntax = cli.flags.syntax;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.customSyntax) {
|
||
|
optionsBase.customSyntax = getModulePath(process.cwd(), cli.flags.customSyntax);
|
||
|
}
|
||
|
|
||
|
if (cli.flags.config) {
|
||
|
// Should check these possibilities:
|
||
|
// a. name of a node_module
|
||
|
// b. absolute path
|
||
|
// c. relative path relative to `process.cwd()`.
|
||
|
// If none of the above work, we'll try a relative path starting
|
||
|
// in `process.cwd()`.
|
||
|
optionsBase.configFile =
|
||
|
resolveFrom.silent(process.cwd(), cli.flags.config) ||
|
||
|
path.join(process.cwd(), cli.flags.config);
|
||
|
}
|
||
|
|
||
|
if (cli.flags.configBasedir) {
|
||
|
optionsBase.configBasedir = path.isAbsolute(cli.flags.configBasedir)
|
||
|
? cli.flags.configBasedir
|
||
|
: path.resolve(process.cwd(), cli.flags.configBasedir);
|
||
|
}
|
||
|
|
||
|
if (cli.flags.stdinFilename) {
|
||
|
optionsBase.codeFilename = cli.flags.stdinFilename;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.ignorePath) {
|
||
|
optionsBase.ignorePath = cli.flags.ignorePath;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.ignorePattern) {
|
||
|
optionsBase.ignorePattern = cli.flags.ignorePattern;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.ignoreDisables) {
|
||
|
optionsBase.ignoreDisables = cli.flags.ignoreDisables;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.disableDefaultIgnores) {
|
||
|
optionsBase.disableDefaultIgnores = cli.flags.disableDefaultIgnores;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.cache) {
|
||
|
optionsBase.cache = true;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.cacheLocation) {
|
||
|
optionsBase.cacheLocation = cli.flags.cacheLocation;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.fix) {
|
||
|
optionsBase.fix = cli.flags.fix;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.outputFile) {
|
||
|
optionsBase.outputFile = cli.flags.outputFile;
|
||
|
}
|
||
|
|
||
|
const reportNeedlessDisables = cli.flags.reportNeedlessDisables;
|
||
|
const reportInvalidScopeDisables = cli.flags.reportInvalidScopeDisables;
|
||
|
const reportDescriptionlessDisables = cli.flags.reportDescriptionlessDisables;
|
||
|
|
||
|
if (reportNeedlessDisables) {
|
||
|
optionsBase.reportNeedlessDisables = reportNeedlessDisables;
|
||
|
}
|
||
|
|
||
|
if (reportInvalidScopeDisables) {
|
||
|
optionsBase.reportInvalidScopeDisables = reportInvalidScopeDisables;
|
||
|
}
|
||
|
|
||
|
if (reportDescriptionlessDisables) {
|
||
|
optionsBase.reportDescriptionlessDisables = reportDescriptionlessDisables;
|
||
|
}
|
||
|
|
||
|
const maxWarnings = cli.flags.maxWarnings;
|
||
|
|
||
|
if (maxWarnings !== undefined) {
|
||
|
optionsBase.maxWarnings = maxWarnings;
|
||
|
}
|
||
|
|
||
|
if (cli.flags.help) {
|
||
|
cli.showHelp(0);
|
||
|
|
||
|
return Promise.resolve();
|
||
|
}
|
||
|
|
||
|
if (cli.flags.version) {
|
||
|
cli.showVersion();
|
||
|
|
||
|
return Promise.resolve();
|
||
|
}
|
||
|
|
||
|
if (cli.flags.allowEmptyInput) {
|
||
|
optionsBase.allowEmptyInput = cli.flags.allowEmptyInput;
|
||
|
}
|
||
|
|
||
|
return Promise.resolve()
|
||
|
.then(
|
||
|
/**
|
||
|
* @returns {Promise<OptionBaseType>}
|
||
|
*/
|
||
|
() => {
|
||
|
// Add input/code into options
|
||
|
if (cli.input.length) {
|
||
|
return Promise.resolve({ ...optionsBase, files: /** @type {string} */ (cli.input) });
|
||
|
}
|
||
|
|
||
|
return getStdin().then((stdin) => ({ ...optionsBase, code: stdin }));
|
||
|
},
|
||
|
)
|
||
|
.then((options) => {
|
||
|
if (cli.flags.printConfig) {
|
||
|
return printConfig(options)
|
||
|
.then((config) => {
|
||
|
process.stdout.write(JSON.stringify(config, null, ' '));
|
||
|
})
|
||
|
.catch(handleError);
|
||
|
}
|
||
|
|
||
|
if (!options.files && !options.code && !cli.flags.stdin) {
|
||
|
cli.showHelp();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
return standalone(options)
|
||
|
.then((linted) => {
|
||
|
if (!linted.output) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
process.stdout.write(linted.output);
|
||
|
|
||
|
if (options.outputFile) {
|
||
|
writeOutputFile(linted.output, options.outputFile).catch(handleError);
|
||
|
}
|
||
|
|
||
|
if (linted.errored) {
|
||
|
process.exitCode = EXIT_CODE_ERROR;
|
||
|
} else if (maxWarnings !== undefined && linted.maxWarningsExceeded) {
|
||
|
const foundWarnings = linted.maxWarningsExceeded.foundWarnings;
|
||
|
|
||
|
process.stderr.write(
|
||
|
`${EOL}${chalk.red(`Max warnings exceeded: `)}${foundWarnings} found. ${chalk.dim(
|
||
|
`${maxWarnings} allowed${EOL}${EOL}`,
|
||
|
)}`,
|
||
|
);
|
||
|
process.exitCode = EXIT_CODE_ERROR;
|
||
|
}
|
||
|
})
|
||
|
.catch(handleError);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @param {{ stack: any, code: any }} err
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
function handleError(err) {
|
||
|
process.stderr.write(err.stack + EOL);
|
||
|
const exitCode = typeof err.code === 'number' ? err.code : 1;
|
||
|
|
||
|
process.exitCode = exitCode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {string[]} argv
|
||
|
* @returns {CLIOptions}
|
||
|
*/
|
||
|
function buildCLI(argv) {
|
||
|
// @ts-ignore TODO TYPES
|
||
|
return meow({ ...meowOptions, argv });
|
||
|
}
|
||
|
|
||
|
module.exports.buildCLI = buildCLI;
|