@stoplight/spectral-cli
Version:
[](https://stoplight.io/api-governance?utm_source=github&utm_medium=spectral&utm_campaign=readme) [;
exports.severeEnoughToFail = void 0;
const tslib_1 = require("tslib");
const types_1 = require("@stoplight/types");
const json_1 = require("@stoplight/json");
const spectral_core_1 = require("@stoplight/spectral-core");
const lodash_1 = require("lodash");
const process = (0, tslib_1.__importStar)(require("process"));
const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
const stacktracey_1 = (0, tslib_1.__importDefault)(require("stacktracey"));
const linter_1 = require("../services/linter");
const output_1 = require("../services/output");
const config_1 = require("../services/config");
const errors_1 = require("../errors");
const formatOptions = Object.values(config_1.OutputFormat);
const lintCommand = {
describe: 'lint JSON/YAML documents from files or URLs',
command: 'lint [documents..]',
builder: yargs => yargs
.strict()
.positional('documents', {
description: 'Location of JSON/YAML documents. Can be either a file, a glob or fetchable resource(s) on the web.',
coerce(values) {
if (Array.isArray(values) && values.length > 0) {
return values;
}
if (process.stdin.isTTY) {
return [];
}
return [process.stdin.fd];
},
})
.middleware((argv) => {
const formats = argv.format;
if (argv.output === void 0) {
argv.output = { [formats[0]]: '<stdout>' };
}
else if (typeof argv.output === 'string') {
argv.output = { [formats[0]]: argv.output };
}
else {
const output = argv.output;
if (Object.keys(output).length >= formats.length) {
return;
}
const firstMissingFormat = formats.find(f => !(f in output));
if (firstMissingFormat !== void 0) {
output[firstMissingFormat] = '<stdout>';
}
}
})
.check((argv) => {
if (!Array.isArray(argv.documents) || argv.documents.length === 0) {
throw new errors_1.CLIError('No documents provided.');
}
const format = argv.format;
const output = argv.output;
if (format.length === 1) {
if (output === void 0 || Object.keys(output).length === 1) {
return true;
}
throw new errors_1.CLIError('Output must be either string or unspecified when a single format is specified');
}
if (!(0, json_1.isPlainObject)(output)) {
throw new errors_1.CLIError('Multiple outputs have to be provided when more than a single format is specified');
}
const keys = Object.keys(output);
if (format.length !== keys.length) {
throw new errors_1.CLIError('The number of outputs must match the number of formats');
}
const diff = (0, lodash_1.difference)(format, keys);
if (diff.length !== 0) {
throw new errors_1.CLIError(`Missing outputs for the following formats: ${diff.join(', ')}`);
}
return true;
})
.options({
encoding: {
alias: 'e',
description: 'text encoding to use',
type: 'string',
default: 'utf8',
choices: ['utf8', 'ascii', 'utf-8', 'utf16le', 'ucs2', 'ucs-2', 'base64', 'latin1'],
},
format: {
alias: 'f',
description: 'formatters to use for outputting results, more than one can be provided by using multiple flags',
choices: formatOptions,
default: config_1.OutputFormat.STYLISH,
type: 'string',
coerce(values) {
return Array.isArray(values) ? values : [values];
},
},
output: {
alias: 'o',
description: `where to output results, can be a single file name, multiple "output.<format>" or missing to print to stdout`,
type: 'string',
},
'stdin-filepath': {
description: 'path to a file to pretend that stdin comes from',
type: 'string',
},
resolver: {
description: 'path to custom json-ref-resolver instance',
type: 'string',
},
ruleset: {
alias: 'r',
description: 'path/URL to a ruleset file',
type: 'string',
},
'fail-severity': {
alias: 'F',
description: 'results of this level or above will trigger a failure exit code',
choices: ['error', 'warn', 'info', 'hint'],
default: 'error',
type: 'string',
},
'display-only-failures': {
alias: 'D',
description: 'only output results equal to or greater than --fail-severity',
type: 'boolean',
default: false,
},
'ignore-unknown-format': {
description: 'do not warn about unmatched formats',
type: 'boolean',
default: false,
},
'fail-on-unmatched-globs': {
description: 'fail on unmatched glob patterns',
type: 'boolean',
default: false,
},
'show-documentation-url': {
description: 'show documentation url in output result',
type: 'boolean',
default: false,
},
verbose: {
alias: 'v',
description: 'increase verbosity',
type: 'boolean',
},
quiet: {
alias: 'q',
description: 'no logging - output only',
type: 'boolean',
},
}),
async handler(args) {
const { documents, failSeverity, displayOnlyFailures, ruleset, stdinFilepath, format, output, encoding, ignoreUnknownFormat, failOnUnmatchedGlobs, showDocumentationUrl, ...config } = args;
try {
const linterResult = await (0, linter_1.lint)(documents, {
format,
output,
encoding,
ignoreUnknownFormat,
failOnUnmatchedGlobs,
showDocumentationUrl,
ruleset,
stdinFilepath,
...(0, lodash_1.pick)(config, ['verbose', 'quiet', 'resolver']),
});
if (displayOnlyFailures) {
linterResult.results = filterResultsBySeverity(linterResult.results, failSeverity);
}
if (!showDocumentationUrl) {
linterResult.results = removeDocumentationUrlFromResults(linterResult.results);
}
await Promise.all(format.map(f => {
var _a;
const formattedOutput = (0, output_1.formatOutput)(linterResult.results, f, { failSeverity: (0, spectral_core_1.getDiagnosticSeverity)(failSeverity) }, linterResult.resolvedRuleset);
return (0, output_1.writeOutput)(formattedOutput, (_a = output === null || output === void 0 ? void 0 : output[f]) !== null && _a !== void 0 ? _a : '<stdout>');
}));
if (linterResult.results.length > 0) {
process.exit((0, exports.severeEnoughToFail)(linterResult.results, failSeverity) ? 1 : 0);
}
else if (config.quiet !== true) {
const isErrorSeverity = (0, spectral_core_1.getDiagnosticSeverity)(failSeverity) === types_1.DiagnosticSeverity.Error;
process.stdout.write(`No results with a severity of '${failSeverity}' ${isErrorSeverity ? '' : 'or higher '}found!\n`);
}
}
catch (ex) {
fail((0, lodash_1.isError)(ex) ? ex : new Error(String(ex)), config.verbose === true);
}
},
};
const fail = (error, verbose) => {
if (error instanceof errors_1.CLIError) {
process.stderr.write(chalk_1.default.red(error.message));
process.exit(2);
}
const errors = 'errors' in error ? error.errors : [error];
process.stderr.write(chalk_1.default.red('Error running Spectral!\n'));
if (!verbose) {
process.stderr.write(chalk_1.default.red('Use --verbose flag to print the error stack.\n'));
}
for (const [i, error] of errors.entries()) {
const actualError = (0, lodash_1.isError)(error) && 'cause' in error ? error.cause : error;
const message = (0, lodash_1.isError)(actualError) ? actualError.message : String(actualError);
const location = formatErrorLocation(actualError);
const info = `Error #${i + 1}: `;
process.stderr.write(`${info}${location}${chalk_1.default.red(message)}\n`);
if (verbose && (0, lodash_1.isError)(actualError)) {
process.stderr.write(`${chalk_1.default.red(printErrorStacks(actualError, info.length))}\n`);
}
}
process.exit(2);
};
function formatErrorLocation(error) {
var _a;
if (typeof error !== 'object' || error === null)
return '';
const source = error.source;
if (typeof source !== 'string')
return '';
const range = error.range;
if (typeof ((_a = range === null || range === void 0 ? void 0 : range.start) === null || _a === void 0 ? void 0 : _a.line) === 'number' && typeof range.start.character === 'number') {
return `${source}:${range.start.line + 1}:${range.start.character + 1} — `;
}
return `${source} — `;
}
function getWidth(ratio) {
return Math.min(20, Math.floor(ratio * process.stderr.columns));
}
function printErrorStacks(error, padding) {
return new stacktracey_1.default(error)
.slice(0, 5)
.withSources()
.asTable({
maxColumnWidths: {
callee: getWidth(0.2),
file: getWidth(0.4),
sourceLine: getWidth(0.4),
},
})
.split('\n')
.map(error => `${' '.repeat(padding)}${error}`)
.join('\n');
}
const filterResultsBySeverity = (results, failSeverity) => {
const diagnosticSeverity = (0, spectral_core_1.getDiagnosticSeverity)(failSeverity);
return results.filter(r => r.severity <= diagnosticSeverity);
};
const removeDocumentationUrlFromResults = (results) => {
return results.map(r => ({ ...r, documentationUrl: undefined }));
};
const severeEnoughToFail = (results, failSeverity) => {
const diagnosticSeverity = (0, spectral_core_1.getDiagnosticSeverity)(failSeverity);
return results.some(r => r.severity <= diagnosticSeverity);
};
exports.severeEnoughToFail = severeEnoughToFail;
exports.default = lintCommand;
//# sourceMappingURL=lint.js.map