@redocly/openapi-core
Version:
See https://github.com/Redocly/openapi-cli
456 lines (455 loc) • 22.5 kB
JavaScript
;
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformConfig = exports.getMergedLintConfig = exports.getMergedConfig = exports.Config = exports.LintConfig = exports.AVAILABLE_REGIONS = exports.DOMAINS = exports.DEFAULT_REGION = exports.IGNORE_FILE = void 0;
const fs = require("fs");
const path = require("path");
const path_1 = require("path");
const colorette_1 = require("colorette");
const js_yaml_1 = require("../js-yaml");
const utils_1 = require("../utils");
const oas_types_1 = require("../oas-types");
const recommended_1 = require("./recommended");
exports.IGNORE_FILE = '.redocly.lint-ignore.yaml';
const IGNORE_BANNER = `# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
`# See https://redoc.ly/docs/cli/ for more information.\n`;
exports.DEFAULT_REGION = 'us';
const REDOCLY_DOMAIN = process.env.REDOCLY_DOMAIN;
exports.DOMAINS = {
us: 'redocly.com',
eu: 'eu.redocly.com',
};
// FIXME: temporary fix for our lab environments
if (REDOCLY_DOMAIN === null || REDOCLY_DOMAIN === void 0 ? void 0 : REDOCLY_DOMAIN.endsWith('.redocly.host')) {
exports.DOMAINS[REDOCLY_DOMAIN.split('.')[0]] = REDOCLY_DOMAIN;
}
if (REDOCLY_DOMAIN === 'redoc.online') {
exports.DOMAINS[REDOCLY_DOMAIN] = REDOCLY_DOMAIN;
}
exports.AVAILABLE_REGIONS = Object.keys(exports.DOMAINS);
class LintConfig {
constructor(rawConfig, configFile) {
this.rawConfig = rawConfig;
this.configFile = configFile;
this.ignore = {};
this._usedRules = new Set();
this._usedVersions = new Set();
this.recommendedFallback = false;
this.plugins = rawConfig.plugins ? resolvePlugins(rawConfig.plugins, configFile) : [];
this.doNotResolveExamples = !!rawConfig.doNotResolveExamples;
if (!rawConfig.extends) {
this.recommendedFallback = true;
}
const extendConfigs = rawConfig.extends
? resolvePresets(rawConfig.extends, this.plugins)
: [recommended_1.default];
if (rawConfig.rules || rawConfig.preprocessors || rawConfig.decorators) {
extendConfigs.push({
rules: rawConfig.rules,
preprocessors: rawConfig.preprocessors,
decorators: rawConfig.decorators,
});
}
const merged = mergeExtends(extendConfigs);
this.rules = {
[oas_types_1.OasVersion.Version2]: Object.assign(Object.assign({}, merged.rules), merged.oas2Rules),
[oas_types_1.OasVersion.Version3_0]: Object.assign(Object.assign({}, merged.rules), merged.oas3_0Rules),
[oas_types_1.OasVersion.Version3_1]: Object.assign(Object.assign({}, merged.rules), merged.oas3_1Rules),
};
this.preprocessors = {
[oas_types_1.OasVersion.Version2]: Object.assign(Object.assign({}, merged.preprocessors), merged.oas2Preprocessors),
[oas_types_1.OasVersion.Version3_0]: Object.assign(Object.assign({}, merged.preprocessors), merged.oas3_0Preprocessors),
[oas_types_1.OasVersion.Version3_1]: Object.assign(Object.assign({}, merged.preprocessors), merged.oas3_1Preprocessors),
};
this.decorators = {
[oas_types_1.OasVersion.Version2]: Object.assign(Object.assign({}, merged.decorators), merged.oas2Decorators),
[oas_types_1.OasVersion.Version3_0]: Object.assign(Object.assign({}, merged.decorators), merged.oas3_0Decorators),
[oas_types_1.OasVersion.Version3_1]: Object.assign(Object.assign({}, merged.decorators), merged.oas3_1Decorators),
};
const dir = this.configFile
? path.dirname(this.configFile)
: (typeof process !== 'undefined' && process.cwd()) || '';
const ignoreFile = path.join(dir, exports.IGNORE_FILE);
/* no crash when using it on the client */
if (fs.hasOwnProperty('existsSync') && fs.existsSync(ignoreFile)) {
// TODO: parse errors
this.ignore =
js_yaml_1.parseYaml(fs.readFileSync(ignoreFile, 'utf-8')) || {};
// resolve ignore paths
for (const fileName of Object.keys(this.ignore)) {
this.ignore[path.resolve(path_1.dirname(ignoreFile), fileName)] = this.ignore[fileName];
for (const ruleId of Object.keys(this.ignore[fileName])) {
this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
}
delete this.ignore[fileName];
}
}
}
saveIgnore() {
const dir = this.configFile ? path.dirname(this.configFile) : process.cwd();
const ignoreFile = path.join(dir, exports.IGNORE_FILE);
const mapped = {};
for (const absFileName of Object.keys(this.ignore)) {
const ignoredRules = (mapped[utils_1.slash(path.relative(dir, absFileName))] =
this.ignore[absFileName]);
for (const ruleId of Object.keys(ignoredRules)) {
ignoredRules[ruleId] = Array.from(ignoredRules[ruleId]);
}
}
fs.writeFileSync(ignoreFile, IGNORE_BANNER + js_yaml_1.stringifyYaml(mapped));
}
addIgnore(problem) {
const ignore = this.ignore;
const loc = problem.location[0];
if (loc.pointer === undefined)
return;
const fileIgnore = (ignore[loc.source.absoluteRef] = ignore[loc.source.absoluteRef] || {});
const ruleIgnore = (fileIgnore[problem.ruleId] = fileIgnore[problem.ruleId] || new Set());
ruleIgnore.add(loc.pointer);
}
addProblemToIgnore(problem) {
const loc = problem.location[0];
if (loc.pointer === undefined)
return problem;
const fileIgnore = this.ignore[loc.source.absoluteRef] || {};
const ruleIgnore = fileIgnore[problem.ruleId];
const ignored = ruleIgnore && ruleIgnore.has(loc.pointer);
return ignored
? Object.assign(Object.assign({}, problem), { ignored }) : problem;
}
extendTypes(types, version) {
let extendedTypes = types;
for (const plugin of this.plugins) {
if (plugin.typeExtension !== undefined) {
switch (version) {
case oas_types_1.OasVersion.Version3_0:
case oas_types_1.OasVersion.Version3_1:
if (!plugin.typeExtension.oas3)
continue;
extendedTypes = plugin.typeExtension.oas3(extendedTypes, version);
case oas_types_1.OasVersion.Version2:
if (!plugin.typeExtension.oas2)
continue;
extendedTypes = plugin.typeExtension.oas2(extendedTypes, version);
default:
throw new Error('Not implemented');
}
}
}
return extendedTypes;
}
getRuleSettings(ruleId, oasVersion) {
this._usedRules.add(ruleId);
this._usedVersions.add(oasVersion);
const settings = this.rules[oasVersion][ruleId] || 'off';
if (typeof settings === 'string') {
return {
severity: settings,
};
}
else {
return Object.assign({ severity: 'error' }, settings);
}
}
getPreprocessorSettings(ruleId, oasVersion) {
this._usedRules.add(ruleId);
this._usedVersions.add(oasVersion);
const settings = this.preprocessors[oasVersion][ruleId] || 'off';
if (typeof settings === 'string') {
return {
severity: settings === 'on' ? 'error' : settings,
};
}
else {
return Object.assign({ severity: 'error' }, settings);
}
}
getDecoratorSettings(ruleId, oasVersion) {
this._usedRules.add(ruleId);
this._usedVersions.add(oasVersion);
const settings = this.decorators[oasVersion][ruleId] || 'off';
if (typeof settings === 'string') {
return {
severity: settings === 'on' ? 'error' : settings,
};
}
else {
return Object.assign({ severity: 'error' }, settings);
}
}
getUnusedRules() {
const rules = [];
const decorators = [];
const preprocessors = [];
for (const usedVersion of Array.from(this._usedVersions)) {
rules.push(...Object.keys(this.rules[usedVersion]).filter((name) => !this._usedRules.has(name)));
decorators.push(...Object.keys(this.decorators[usedVersion]).filter((name) => !this._usedRules.has(name)));
preprocessors.push(...Object.keys(this.preprocessors[usedVersion]).filter((name) => !this._usedRules.has(name)));
}
return {
rules,
preprocessors,
decorators,
};
}
getRulesForOasVersion(version) {
switch (version) {
case oas_types_1.OasMajorVersion.Version3:
const oas3Rules = []; // default ruleset
this.plugins.forEach((p) => { var _a; return ((_a = p.preprocessors) === null || _a === void 0 ? void 0 : _a.oas3) && oas3Rules.push(p.preprocessors.oas3); });
this.plugins.forEach((p) => { var _a; return ((_a = p.rules) === null || _a === void 0 ? void 0 : _a.oas3) && oas3Rules.push(p.rules.oas3); });
this.plugins.forEach((p) => { var _a; return ((_a = p.decorators) === null || _a === void 0 ? void 0 : _a.oas3) && oas3Rules.push(p.decorators.oas3); });
return oas3Rules;
case oas_types_1.OasMajorVersion.Version2:
const oas2Rules = []; // default ruleset
this.plugins.forEach((p) => { var _a; return ((_a = p.preprocessors) === null || _a === void 0 ? void 0 : _a.oas2) && oas2Rules.push(p.preprocessors.oas2); });
this.plugins.forEach((p) => { var _a; return ((_a = p.rules) === null || _a === void 0 ? void 0 : _a.oas2) && oas2Rules.push(p.rules.oas2); });
this.plugins.forEach((p) => { var _a; return ((_a = p.decorators) === null || _a === void 0 ? void 0 : _a.oas2) && oas2Rules.push(p.decorators.oas2); });
return oas2Rules;
}
}
skipRules(rules) {
for (const ruleId of rules || []) {
for (const version of Object.values(oas_types_1.OasVersion)) {
if (this.rules[version][ruleId]) {
this.rules[version][ruleId] = 'off';
}
}
}
}
skipPreprocessors(preprocessors) {
for (const preprocessorId of preprocessors || []) {
for (const version of Object.values(oas_types_1.OasVersion)) {
if (this.preprocessors[version][preprocessorId]) {
this.preprocessors[version][preprocessorId] = 'off';
}
}
}
}
skipDecorators(decorators) {
for (const decoratorId of decorators || []) {
for (const version of Object.values(oas_types_1.OasVersion)) {
if (this.decorators[version][decoratorId]) {
this.decorators[version][decoratorId] = 'off';
}
}
}
}
}
exports.LintConfig = LintConfig;
class Config {
constructor(rawConfig, configFile) {
var _a, _b, _c;
this.rawConfig = rawConfig;
this.configFile = configFile;
this.apis = rawConfig.apis || {};
this.lint = new LintConfig(rawConfig.lint || {}, configFile);
this['features.openapi'] = rawConfig['features.openapi'] || {};
this['features.mockServer'] = rawConfig['features.mockServer'] || {};
this.resolve = {
http: {
headers: (_c = (_b = (_a = rawConfig === null || rawConfig === void 0 ? void 0 : rawConfig.resolve) === null || _a === void 0 ? void 0 : _a.http) === null || _b === void 0 ? void 0 : _b.headers) !== null && _c !== void 0 ? _c : [],
customFetch: undefined,
},
};
this.region = rawConfig.region;
this.organization = rawConfig.organization;
}
}
exports.Config = Config;
function resolvePresets(presets, plugins) {
return presets.map((presetName) => {
var _a;
const { pluginId, configName } = parsePresetName(presetName);
const plugin = plugins.find((p) => p.id === pluginId);
if (!plugin) {
throw new Error(`Invalid config ${colorette_1.red(presetName)}: plugin ${pluginId} is not included.`);
}
const preset = (_a = plugin.configs) === null || _a === void 0 ? void 0 : _a[configName];
if (!preset) {
throw new Error(pluginId
? `Invalid config ${colorette_1.red(presetName)}: plugin ${pluginId} doesn't export config with name ${configName}.`
: `Invalid config ${colorette_1.red(presetName)}: there is no such built-in config.`);
}
return preset;
});
}
function parsePresetName(presetName) {
if (presetName.indexOf('/') > -1) {
const [pluginId, configName] = presetName.split('/');
return { pluginId, configName };
}
else {
return { pluginId: '', configName: presetName };
}
}
function resolvePlugins(plugins, configPath = '') {
if (!plugins)
return [];
// @ts-ignore
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
const seenPluginIds = new Map();
return plugins
.map((p) => {
// TODO: resolve npm packages similar to eslint
const pluginModule = typeof p === 'string'
? requireFunc(path.resolve(path.dirname(configPath), p))
: p;
const id = pluginModule.id;
if (typeof id !== 'string') {
throw new Error(colorette_1.red(`Plugin must define \`id\` property in ${colorette_1.blue(p.toString())}.`));
}
if (seenPluginIds.has(id)) {
const pluginPath = seenPluginIds.get(id);
throw new Error(colorette_1.red(`Plugin "id" must be unique. Plugin ${colorette_1.blue(p.toString())} uses id "${colorette_1.blue(id)}" already seen in ${colorette_1.blue(pluginPath)}`));
}
seenPluginIds.set(id, p.toString());
const plugin = Object.assign(Object.assign({ id }, (pluginModule.configs ? { configs: pluginModule.configs } : {})), (pluginModule.typeExtension ? { typeExtension: pluginModule.typeExtension } : {}));
if (pluginModule.rules) {
if (!pluginModule.rules.oas3 && !pluginModule.rules.oas2) {
throw new Error(`Plugin rules must have \`oas3\` or \`oas2\` rules "${p}.`);
}
plugin.rules = {};
if (pluginModule.rules.oas3) {
plugin.rules.oas3 = prefixRules(pluginModule.rules.oas3, id);
}
if (pluginModule.rules.oas2) {
plugin.rules.oas2 = prefixRules(pluginModule.rules.oas2, id);
}
}
if (pluginModule.preprocessors) {
if (!pluginModule.preprocessors.oas3 && !pluginModule.preprocessors.oas2) {
throw new Error(`Plugin \`preprocessors\` must have \`oas3\` or \`oas2\` preprocessors "${p}.`);
}
plugin.preprocessors = {};
if (pluginModule.preprocessors.oas3) {
plugin.preprocessors.oas3 = prefixRules(pluginModule.preprocessors.oas3, id);
}
if (pluginModule.preprocessors.oas2) {
plugin.preprocessors.oas2 = prefixRules(pluginModule.preprocessors.oas2, id);
}
}
if (pluginModule.decorators) {
if (!pluginModule.decorators.oas3 && !pluginModule.decorators.oas2) {
throw new Error(`Plugin \`decorators\` must have \`oas3\` or \`oas2\` decorators "${p}.`);
}
plugin.decorators = {};
if (pluginModule.decorators.oas3) {
plugin.decorators.oas3 = prefixRules(pluginModule.decorators.oas3, id);
}
if (pluginModule.decorators.oas2) {
plugin.decorators.oas2 = prefixRules(pluginModule.decorators.oas2, id);
}
}
return plugin;
})
.filter(utils_1.notUndefined);
}
function prefixRules(rules, prefix) {
if (!prefix)
return rules;
const res = {};
for (const name of Object.keys(rules)) {
res[`${prefix}/${name}`] = rules[name];
}
return res;
}
function mergeExtends(rulesConfList) {
const result = {
rules: {},
oas2Rules: {},
oas3_0Rules: {},
oas3_1Rules: {},
preprocessors: {},
oas2Preprocessors: {},
oas3_0Preprocessors: {},
oas3_1Preprocessors: {},
decorators: {},
oas2Decorators: {},
oas3_0Decorators: {},
oas3_1Decorators: {},
};
for (let rulesConf of rulesConfList) {
if (rulesConf.extends) {
throw new Error(`\`extends\` is not supported in shared configs yet: ${JSON.stringify(rulesConf, null, 2)}.`);
}
Object.assign(result.rules, rulesConf.rules);
Object.assign(result.oas2Rules, rulesConf.oas2Rules);
assignExisting(result.oas2Rules, rulesConf.rules || {});
Object.assign(result.oas3_0Rules, rulesConf.oas3_0Rules);
assignExisting(result.oas3_0Rules, rulesConf.rules || {});
Object.assign(result.oas3_1Rules, rulesConf.oas3_1Rules);
assignExisting(result.oas3_1Rules, rulesConf.rules || {});
Object.assign(result.preprocessors, rulesConf.preprocessors);
Object.assign(result.oas2Preprocessors, rulesConf.oas2Preprocessors);
assignExisting(result.oas2Preprocessors, rulesConf.preprocessors || {});
Object.assign(result.oas3_0Preprocessors, rulesConf.oas3_0Preprocessors);
assignExisting(result.oas3_0Preprocessors, rulesConf.preprocessors || {});
Object.assign(result.oas3_1Preprocessors, rulesConf.oas3_1Preprocessors);
assignExisting(result.oas3_1Preprocessors, rulesConf.preprocessors || {});
Object.assign(result.decorators, rulesConf.decorators);
Object.assign(result.oas2Decorators, rulesConf.oas2Decorators);
assignExisting(result.oas2Decorators, rulesConf.decorators || {});
Object.assign(result.oas3_0Decorators, rulesConf.oas3_0Decorators);
assignExisting(result.oas3_0Decorators, rulesConf.decorators || {});
Object.assign(result.oas3_1Decorators, rulesConf.oas3_1Decorators);
assignExisting(result.oas3_1Decorators, rulesConf.decorators || {});
}
return result;
}
function assignExisting(target, obj) {
for (let k of Object.keys(obj)) {
if (target.hasOwnProperty(k)) {
target[k] = obj[k];
}
}
}
function getMergedConfig(config, entrypointAlias) {
var _a, _b;
return entrypointAlias
? new Config(Object.assign(Object.assign({}, config.rawConfig), { lint: getMergedLintConfig(config, entrypointAlias), 'features.openapi': Object.assign(Object.assign({}, config['features.openapi']), (_a = config.apis[entrypointAlias]) === null || _a === void 0 ? void 0 : _a['features.openapi']), 'features.mockServer': Object.assign(Object.assign({}, config['features.mockServer']), (_b = config.apis[entrypointAlias]) === null || _b === void 0 ? void 0 : _b['features.mockServer']) }))
: config;
}
exports.getMergedConfig = getMergedConfig;
function getMergedLintConfig(config, entrypointAlias) {
var _a, _b, _c, _d;
const apiLint = entrypointAlias ? (_a = config.apis[entrypointAlias]) === null || _a === void 0 ? void 0 : _a.lint : {};
const mergedLint = Object.assign(Object.assign(Object.assign({}, config.rawConfig.lint), apiLint), { rules: Object.assign(Object.assign({}, (_b = config.rawConfig.lint) === null || _b === void 0 ? void 0 : _b.rules), apiLint === null || apiLint === void 0 ? void 0 : apiLint.rules), preprocessors: Object.assign(Object.assign({}, (_c = config.rawConfig.lint) === null || _c === void 0 ? void 0 : _c.preprocessors), apiLint === null || apiLint === void 0 ? void 0 : apiLint.preprocessors), decorators: Object.assign(Object.assign({}, (_d = config.rawConfig.lint) === null || _d === void 0 ? void 0 : _d.decorators), apiLint === null || apiLint === void 0 ? void 0 : apiLint.decorators) });
return mergedLint;
}
exports.getMergedLintConfig = getMergedLintConfig;
function transformApiDefinitionsToApis(apiDefinitions = {}) {
let apis = {};
for (const [apiName, apiPath] of Object.entries(apiDefinitions)) {
apis[apiName] = { root: apiPath };
}
return apis;
}
function transformConfig(rawConfig) {
if (rawConfig.apis && rawConfig.apiDefinitions) {
throw new Error("Do not use 'apiDefinitions' field. Use 'apis' instead.\n");
}
if (rawConfig['features.openapi'] &&
rawConfig.referenceDocs) {
throw new Error("Do not use 'referenceDocs' field. Use 'features.openapi' instead.\n");
}
const _a = rawConfig, { apiDefinitions, referenceDocs } = _a, rest = __rest(_a, ["apiDefinitions", "referenceDocs"]);
if (apiDefinitions) {
process.stderr.write(`The ${colorette_1.yellow('apiDefinitions')} field is deprecated. Use ${colorette_1.green('apis')} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`);
}
if (referenceDocs) {
process.stderr.write(`The ${colorette_1.yellow('referenceDocs')} field is deprecated. Use ${colorette_1.green('features.openapi')} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`);
}
return Object.assign({ 'features.openapi': referenceDocs, apis: transformApiDefinitionsToApis(apiDefinitions) }, rest);
}
exports.transformConfig = transformConfig;