@redocly/openapi-core 1.0.0-beta.106 → 1.0.0-beta.109
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/lib/benchmark/benches/lint-with-top-level-rule-report.bench.js +0 -1
- package/lib/benchmark/benches/resolve-with-no-external.bench.js +1 -1
- package/lib/bundle.d.ts +1 -1
- package/lib/bundle.js +9 -6
- package/lib/config/all.js +5 -3
- package/lib/config/config-resolvers.js +32 -14
- package/lib/config/config.d.ts +3 -5
- package/lib/config/config.js +7 -4
- package/lib/config/load.d.ts +7 -0
- package/lib/config/load.js +14 -6
- package/lib/config/minimal.js +7 -4
- package/lib/config/recommended.js +7 -4
- package/lib/config/rules.d.ts +1 -1
- package/lib/config/rules.js +1 -1
- package/lib/config/types.d.ts +7 -0
- package/lib/config/utils.d.ts +2 -2
- package/lib/config/utils.js +49 -11
- package/lib/decorators/common/registry-dependencies.js +2 -2
- package/lib/env.d.ts +3 -0
- package/lib/env.js +8 -0
- package/lib/format/codeframes.js +16 -10
- package/lib/format/format.d.ts +1 -1
- package/lib/format/format.js +49 -26
- package/lib/index.d.ts +5 -5
- package/lib/index.js +3 -1
- package/lib/js-yaml/index.js +1 -0
- package/lib/lint.js +2 -2
- package/lib/logger.d.ts +10 -0
- package/lib/logger.js +31 -0
- package/lib/output.d.ts +3 -0
- package/lib/output.js +9 -0
- package/lib/redocly/index.js +10 -9
- package/lib/redocly/registry-api-types.d.ts +28 -30
- package/lib/redocly/registry-api.d.ts +3 -3
- package/lib/redocly/registry-api.js +7 -1
- package/lib/ref-utils.js +2 -1
- package/lib/resolve.d.ts +1 -1
- package/lib/resolve.js +4 -2
- package/lib/rules/ajv.d.ts +1 -1
- package/lib/rules/ajv.js +7 -7
- package/lib/rules/common/assertions/asserts.js +4 -4
- package/lib/rules/common/assertions/index.js +1 -1
- package/lib/rules/common/no-ambiguous-paths.js +1 -1
- package/lib/rules/common/no-identical-paths.js +1 -1
- package/lib/rules/common/no-invalid-parameter-examples.js +3 -3
- package/lib/rules/common/no-invalid-schema-examples.js +3 -3
- package/lib/rules/common/operation-2xx-response.js +1 -1
- package/lib/rules/common/operation-4xx-response.js +1 -1
- package/lib/rules/common/operation-operationId.js +1 -1
- package/lib/rules/common/operation-tag-defined.js +1 -1
- package/lib/rules/common/path-not-include-query.js +1 -1
- package/lib/rules/common/security-defined.d.ts +2 -0
- package/lib/rules/common/{operation-security-defined.js → security-defined.js} +19 -5
- package/lib/rules/common/spec.js +14 -3
- package/lib/rules/common/tags-alphabetical.js +1 -1
- package/lib/rules/oas2/index.d.ts +1 -1
- package/lib/rules/oas2/index.js +2 -2
- package/lib/rules/oas2/remove-unused-components.js +3 -3
- package/lib/rules/oas2/request-mime-type.js +1 -1
- package/lib/rules/oas2/response-mime-type.js +1 -1
- package/lib/rules/oas3/index.js +8 -4
- package/lib/rules/oas3/no-empty-servers.js +1 -1
- package/lib/rules/oas3/no-invalid-media-type-examples.js +2 -2
- package/lib/rules/oas3/no-server-variables-empty-enum.d.ts +2 -0
- package/lib/rules/oas3/{no-servers-empty-enum.js → no-server-variables-empty-enum.js} +5 -5
- package/lib/rules/oas3/no-unused-components.js +2 -2
- package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.d.ts +5 -0
- package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.js +36 -0
- package/lib/rules/oas3/remove-unused-components.js +4 -4
- package/lib/rules/oas3/request-mime-type.js +1 -1
- package/lib/rules/oas3/response-mime-type.js +1 -1
- package/lib/rules/oas3/spec-components-invalid-map-name.d.ts +2 -0
- package/lib/rules/oas3/spec-components-invalid-map-name.js +46 -0
- package/lib/rules/other/stats.d.ts +2 -2
- package/lib/rules/other/stats.js +2 -2
- package/lib/rules/utils.d.ts +3 -2
- package/lib/rules/utils.js +16 -4
- package/lib/types/oas2.js +5 -5
- package/lib/types/oas3.js +27 -20
- package/lib/types/oas3_1.js +3 -3
- package/lib/types/redocly-yaml.js +47 -56
- package/lib/utils.d.ts +6 -1
- package/lib/utils.js +24 -7
- package/lib/visitors.d.ts +12 -12
- package/lib/visitors.js +15 -3
- package/lib/walk.d.ts +2 -1
- package/lib/walk.js +6 -3
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +3 -3
- package/src/__tests__/fixtures/extension.js +3 -3
- package/src/__tests__/format.test.ts +76 -0
- package/src/__tests__/lint.test.ts +106 -131
- package/src/__tests__/logger-browser.test.ts +53 -0
- package/src/__tests__/logger.test.ts +47 -0
- package/src/__tests__/output-browser.test.ts +18 -0
- package/src/__tests__/output.test.ts +15 -0
- package/src/__tests__/resolve-http.test.ts +1 -1
- package/src/__tests__/resolve.test.ts +9 -9
- package/src/__tests__/utils-browser.test.ts +11 -0
- package/src/__tests__/utils.test.ts +7 -0
- package/src/__tests__/walk.test.ts +78 -10
- package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +0 -1
- package/src/benchmark/benches/resolve-with-no-external.bench.ts +1 -1
- package/src/bundle.ts +10 -7
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +12 -6
- package/src/config/__tests__/config.test.ts +35 -0
- package/src/config/__tests__/fixtures/plugin-config.yaml +2 -3
- package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -12
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -8
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -19
- package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -10
- package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -4
- package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -5
- package/src/config/__tests__/load.test.ts +76 -1
- package/src/config/__tests__/utils.test.ts +64 -4
- package/src/config/all.ts +5 -3
- package/src/config/config-resolvers.ts +45 -19
- package/src/config/config.ts +10 -8
- package/src/config/load.ts +31 -7
- package/src/config/minimal.ts +7 -4
- package/src/config/recommended.ts +7 -4
- package/src/config/rules.ts +2 -2
- package/src/config/types.ts +11 -0
- package/src/config/utils.ts +115 -25
- package/src/decorators/common/registry-dependencies.ts +2 -2
- package/src/env.ts +5 -0
- package/src/format/codeframes.ts +15 -9
- package/src/format/format.ts +59 -34
- package/src/index.ts +6 -4
- package/src/js-yaml/index.ts +1 -0
- package/src/lint.ts +2 -2
- package/src/logger.ts +34 -0
- package/src/output.ts +7 -0
- package/src/redocly/index.ts +7 -4
- package/src/redocly/registry-api-types.ts +27 -29
- package/src/redocly/registry-api.ts +18 -7
- package/src/ref-utils.ts +2 -1
- package/src/resolve.ts +7 -5
- package/src/rules/__tests__/utils.test.ts +39 -1
- package/src/rules/ajv.ts +7 -7
- package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +1 -0
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +1 -1
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +26 -3
- package/src/rules/common/__tests__/security-defined.test.ts +175 -0
- package/src/rules/common/__tests__/spec.test.ts +79 -0
- package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
- package/src/rules/common/assertions/asserts.ts +4 -4
- package/src/rules/common/assertions/index.ts +1 -1
- package/src/rules/common/no-ambiguous-paths.ts +1 -1
- package/src/rules/common/no-identical-paths.ts +1 -1
- package/src/rules/common/no-invalid-parameter-examples.ts +4 -4
- package/src/rules/common/no-invalid-schema-examples.ts +4 -4
- package/src/rules/common/operation-2xx-response.ts +1 -1
- package/src/rules/common/operation-4xx-response.ts +1 -1
- package/src/rules/common/operation-operationId.ts +1 -1
- package/src/rules/common/operation-tag-defined.ts +1 -1
- package/src/rules/common/path-not-include-query.ts +1 -1
- package/src/rules/common/{operation-security-defined.ts → security-defined.ts} +20 -5
- package/src/rules/common/spec.ts +17 -3
- package/src/rules/common/tags-alphabetical.ts +1 -1
- package/src/rules/oas2/index.ts +2 -2
- package/src/rules/oas2/remove-unused-components.ts +3 -3
- package/src/rules/oas2/request-mime-type.ts +1 -1
- package/src/rules/oas2/response-mime-type.ts +1 -1
- package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +16 -16
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +5 -5
- package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
- package/src/rules/oas3/__tests__/spec/spec.test.ts +10 -0
- package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +217 -0
- package/src/rules/oas3/index.ts +8 -4
- package/src/rules/oas3/no-empty-servers.ts +1 -1
- package/src/rules/oas3/no-invalid-media-type-examples.ts +3 -3
- package/src/rules/oas3/{no-servers-empty-enum.ts → no-server-variables-empty-enum.ts} +3 -3
- package/src/rules/oas3/no-unused-components.ts +2 -2
- package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +36 -0
- package/src/rules/oas3/remove-unused-components.ts +5 -5
- package/src/rules/oas3/request-mime-type.ts +1 -1
- package/src/rules/oas3/response-mime-type.ts +1 -1
- package/src/rules/oas3/spec-components-invalid-map-name.ts +53 -0
- package/src/rules/other/stats.ts +2 -2
- package/src/rules/utils.ts +17 -3
- package/src/types/index.ts +2 -2
- package/src/types/oas2.ts +5 -5
- package/src/types/oas3.ts +27 -20
- package/src/types/oas3_1.ts +3 -3
- package/src/types/redocly-yaml.ts +53 -41
- package/src/utils.ts +31 -4
- package/src/visitors.ts +34 -18
- package/src/walk.ts +15 -11
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/common/operation-security-defined.d.ts +0 -2
- package/lib/rules/oas3/no-servers-empty-enum.d.ts +0 -2
- package/src/rules/common/__tests__/operation-security-defined.test.ts +0 -69
package/src/env.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const isBrowser =
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
typeof window !== 'undefined' || typeof self !== 'undefined' || typeof process === 'undefined'; // main and worker thread
|
|
5
|
+
export const env = isBrowser ? {} : process.env || {};
|
package/src/format/codeframes.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { gray, red, options as colorOptions } from 'colorette';
|
|
2
1
|
import * as yamlAst from 'yaml-ast-parser';
|
|
3
2
|
import { unescapePointer } from '../ref-utils';
|
|
4
3
|
import { LineColLocationObject, Loc, LocationObject } from '../walk';
|
|
4
|
+
import { colorize, colorOptions } from '../logger';
|
|
5
5
|
|
|
6
6
|
type YAMLMapping = yamlAst.YAMLMapping & { kind: yamlAst.Kind.MAPPING };
|
|
7
7
|
type YAMLMap = yamlAst.YamlMap & { kind: yamlAst.Kind.MAP };
|
|
@@ -36,17 +36,23 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
|
|
|
36
36
|
if (skipLines > 0 && i >= endLineNum - skipLines) break;
|
|
37
37
|
const line = lines[i - 1] || '';
|
|
38
38
|
if (line !== '') currentPad = padSize(line);
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const startIdx = i === startLineNum ? start.col - 1 : currentPad;
|
|
40
|
+
const endIdx = i === endLineNum ? end.col - 1 : line.length;
|
|
41
41
|
|
|
42
|
-
prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, red)]);
|
|
42
|
+
prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, colorize.red)]);
|
|
43
43
|
if (!color) prefixedLines.push(['', underlineLine(line, startIdx, endIdx)]);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
if (skipLines > 0) {
|
|
47
|
-
prefixedLines.push([
|
|
47
|
+
prefixedLines.push([
|
|
48
|
+
`…`,
|
|
49
|
+
`${whitespace(currentPad)}${colorize.gray(`< ${skipLines} more lines >`)}`,
|
|
50
|
+
]);
|
|
48
51
|
// print last line
|
|
49
|
-
prefixedLines.push([
|
|
52
|
+
prefixedLines.push([
|
|
53
|
+
`${endLineNum}`,
|
|
54
|
+
markLine(lines[endLineNum - 1], -1, end.col - 1, colorize.red),
|
|
55
|
+
]);
|
|
50
56
|
|
|
51
57
|
if (!color) prefixedLines.push(['', underlineLine(lines[endLineNum - 1], -1, end.col - 1)]);
|
|
52
58
|
}
|
|
@@ -63,7 +69,7 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
|
|
|
63
69
|
line: string,
|
|
64
70
|
startIdx: number = -1,
|
|
65
71
|
endIdx: number = +Infinity,
|
|
66
|
-
variant = gray
|
|
72
|
+
variant = colorize.gray
|
|
67
73
|
) {
|
|
68
74
|
if (!color) return line;
|
|
69
75
|
if (!line) return line;
|
|
@@ -90,7 +96,7 @@ function printPrefixedLines(lines: [string, string][]): string {
|
|
|
90
96
|
return existingLines
|
|
91
97
|
.map(
|
|
92
98
|
([prefix, line]) =>
|
|
93
|
-
gray(leftPad(padLen, prefix) + ' |') +
|
|
99
|
+
colorize.gray(leftPad(padLen, prefix) + ' |') +
|
|
94
100
|
(line ? ' ' + limitLineLength(line.substring(dedentLen)) : '')
|
|
95
101
|
)
|
|
96
102
|
.join('\n');
|
|
@@ -99,7 +105,7 @@ function printPrefixedLines(lines: [string, string][]): string {
|
|
|
99
105
|
function limitLineLength(line: string, maxLen: number = MAX_LINE_LENGTH) {
|
|
100
106
|
const overflowLen = line.length - maxLen;
|
|
101
107
|
if (overflowLen > 0) {
|
|
102
|
-
const charsMoreText = gray(`...<${overflowLen} chars>`);
|
|
108
|
+
const charsMoreText = colorize.gray(`...<${overflowLen} chars>`);
|
|
103
109
|
return line.substring(0, maxLen - charsMoreText.length) + charsMoreText;
|
|
104
110
|
} else {
|
|
105
111
|
return line;
|
package/src/format/format.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
gray,
|
|
5
|
-
blue,
|
|
6
|
-
bgRed,
|
|
7
|
-
bgYellow,
|
|
8
|
-
black,
|
|
9
|
-
yellow,
|
|
10
|
-
red,
|
|
11
|
-
} from 'colorette';
|
|
2
|
+
import { colorOptions, colorize, logger } from '../logger';
|
|
3
|
+
import { output } from '../output';
|
|
12
4
|
|
|
13
5
|
const coreVersion = require('../../package.json').version;
|
|
14
6
|
|
|
15
7
|
import { NormalizedProblem, ProblemSeverity, LineColLocationObject, LocationObject } from '../walk';
|
|
16
8
|
import { getCodeframe, getLineColLocation } from './codeframes';
|
|
17
|
-
import { env } from '../
|
|
9
|
+
import { env } from '../env';
|
|
18
10
|
|
|
19
11
|
export type Totals = {
|
|
20
12
|
errors: number;
|
|
@@ -27,13 +19,13 @@ const ERROR_MESSAGE = {
|
|
|
27
19
|
};
|
|
28
20
|
|
|
29
21
|
const BG_COLORS = {
|
|
30
|
-
warn: (str: string) => bgYellow(black(str)),
|
|
31
|
-
error: bgRed,
|
|
22
|
+
warn: (str: string) => colorize.bgYellow(colorize.black(str)),
|
|
23
|
+
error: colorize.bgRed,
|
|
32
24
|
};
|
|
33
25
|
|
|
34
26
|
const COLORS = {
|
|
35
|
-
warn: yellow,
|
|
36
|
-
error: red,
|
|
27
|
+
warn: colorize.yellow,
|
|
28
|
+
error: colorize.red,
|
|
37
29
|
};
|
|
38
30
|
|
|
39
31
|
const SEVERITY_NAMES = {
|
|
@@ -52,7 +44,13 @@ function severityToNumber(severity: ProblemSeverity) {
|
|
|
52
44
|
return severity === 'error' ? 1 : 2;
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
export type OutputFormat =
|
|
47
|
+
export type OutputFormat =
|
|
48
|
+
| 'codeframe'
|
|
49
|
+
| 'stylish'
|
|
50
|
+
| 'json'
|
|
51
|
+
| 'checkstyle'
|
|
52
|
+
| 'codeclimate'
|
|
53
|
+
| 'summary';
|
|
56
54
|
|
|
57
55
|
export function getTotals(problems: (NormalizedProblem & { ignored?: boolean })[]): Totals {
|
|
58
56
|
let errors = 0;
|
|
@@ -114,7 +112,7 @@ export function formatProblems(
|
|
|
114
112
|
case 'codeframe':
|
|
115
113
|
for (let i = 0; i < problems.length; i++) {
|
|
116
114
|
const problem = problems[i];
|
|
117
|
-
|
|
115
|
+
logger.info(`${formatCodeframe(problem, i)}\n`);
|
|
118
116
|
}
|
|
119
117
|
break;
|
|
120
118
|
case 'stylish': {
|
|
@@ -122,40 +120,43 @@ export function formatProblems(
|
|
|
122
120
|
for (const [file, { ruleIdPad, locationPad: positionPad, fileProblems }] of Object.entries(
|
|
123
121
|
groupedByFile
|
|
124
122
|
)) {
|
|
125
|
-
|
|
123
|
+
logger.info(`${colorize.blue(path.relative(cwd, file))}:\n`);
|
|
126
124
|
|
|
127
125
|
for (let i = 0; i < fileProblems.length; i++) {
|
|
128
126
|
const problem = fileProblems[i];
|
|
129
|
-
|
|
127
|
+
logger.info(`${formatStylish(problem, positionPad, ruleIdPad)}\n`);
|
|
130
128
|
}
|
|
131
129
|
|
|
132
|
-
|
|
130
|
+
logger.info('\n');
|
|
133
131
|
}
|
|
134
132
|
break;
|
|
135
133
|
}
|
|
136
134
|
case 'checkstyle': {
|
|
137
135
|
const groupedByFile = groupByFiles(problems);
|
|
138
136
|
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
output.write('<?xml version="1.0" encoding="UTF-8"?>\n');
|
|
138
|
+
output.write('<checkstyle version="4.3">\n');
|
|
141
139
|
|
|
142
140
|
for (const [file, { fileProblems }] of Object.entries(groupedByFile)) {
|
|
143
|
-
|
|
141
|
+
output.write(`<file name="${xmlEscape(path.relative(cwd, file))}">\n`);
|
|
144
142
|
fileProblems.forEach(formatCheckstyle);
|
|
145
|
-
|
|
143
|
+
output.write(`</file>\n`);
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
|
|
146
|
+
output.write(`</checkstyle>\n`);
|
|
149
147
|
break;
|
|
150
148
|
}
|
|
151
149
|
case 'codeclimate':
|
|
152
150
|
outputForCodeClimate();
|
|
153
151
|
break;
|
|
152
|
+
case 'summary':
|
|
153
|
+
formatSummary(problems);
|
|
154
|
+
break;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
if (totalProblems - ignoredProblems > maxProblems) {
|
|
157
|
-
|
|
158
|
-
`< ... ${totalProblems - maxProblems} more problems hidden > ${gray(
|
|
158
|
+
logger.info(
|
|
159
|
+
`< ... ${totalProblems - maxProblems} more problems hidden > ${colorize.gray(
|
|
159
160
|
'increase with `--max-problems N`'
|
|
160
161
|
)}\n`
|
|
161
162
|
);
|
|
@@ -177,7 +178,7 @@ export function formatProblems(
|
|
|
177
178
|
fingerprint: `${p.ruleId}${p.location.length > 0 ? '-' + p.location[0].pointer : ''}`,
|
|
178
179
|
};
|
|
179
180
|
});
|
|
180
|
-
|
|
181
|
+
output.write(JSON.stringify(issues, null, 2));
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
function outputJSON() {
|
|
@@ -185,7 +186,7 @@ export function formatProblems(
|
|
|
185
186
|
totals,
|
|
186
187
|
version,
|
|
187
188
|
problems: problems.map((p) => {
|
|
188
|
-
|
|
189
|
+
const problem = {
|
|
189
190
|
...p,
|
|
190
191
|
location: p.location.map((location: any) => ({
|
|
191
192
|
...location,
|
|
@@ -211,7 +212,7 @@ export function formatProblems(
|
|
|
211
212
|
return problem;
|
|
212
213
|
}),
|
|
213
214
|
};
|
|
214
|
-
|
|
215
|
+
output.write(JSON.stringify(resultObject, null, 2));
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
function getBgColor(problem: NormalizedProblem) {
|
|
@@ -227,7 +228,7 @@ export function formatProblems(
|
|
|
227
228
|
const location = problem.location[0]; // TODO: support multiple locations
|
|
228
229
|
const relativePath = path.relative(cwd, location.source.absoluteRef);
|
|
229
230
|
const loc = getLineColLocation(location);
|
|
230
|
-
const atPointer = location.pointer ? gray(`at ${location.pointer}`) : '';
|
|
231
|
+
const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
|
|
231
232
|
const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
|
|
232
233
|
return (
|
|
233
234
|
`[${idx + 1}] ${bgColor(fileWithLoc)} ${atPointer}\n\n` +
|
|
@@ -236,7 +237,9 @@ export function formatProblems(
|
|
|
236
237
|
getCodeframe(loc, color) +
|
|
237
238
|
'\n\n' +
|
|
238
239
|
formatFrom(cwd, problem.from) +
|
|
239
|
-
`${SEVERITY_NAMES[problem.severity]} was generated by the ${blue(
|
|
240
|
+
`${SEVERITY_NAMES[problem.severity]} was generated by the ${colorize.blue(
|
|
241
|
+
problem.ruleId
|
|
242
|
+
)} rule.\n\n`
|
|
240
243
|
);
|
|
241
244
|
}
|
|
242
245
|
|
|
@@ -257,19 +260,40 @@ export function formatProblems(
|
|
|
257
260
|
const severity = problem.severity == 'warn' ? 'warning' : 'error';
|
|
258
261
|
const message = xmlEscape(problem.message);
|
|
259
262
|
const source = xmlEscape(problem.ruleId);
|
|
260
|
-
|
|
263
|
+
output.write(
|
|
261
264
|
`<error line="${line}" column="${col}" severity="${severity}" message="${message}" source="${source}" />\n`
|
|
262
265
|
);
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
|
|
269
|
+
function formatSummary(problems: NormalizedProblem[]): void {
|
|
270
|
+
const counts: Record<string, { count: number; severity: ProblemSeverity }> = {};
|
|
271
|
+
for (const problem of problems) {
|
|
272
|
+
counts[problem.ruleId] = counts[problem.ruleId] || { count: 0, severity: problem.severity };
|
|
273
|
+
counts[problem.ruleId].count++;
|
|
274
|
+
}
|
|
275
|
+
const sorted = Object.entries(counts).sort(([, a], [, b]) => {
|
|
276
|
+
const severityDiff = severityToNumber(a.severity) - severityToNumber(b.severity);
|
|
277
|
+
return severityDiff || b.count - a.count;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
for (const [ruleId, info] of sorted) {
|
|
281
|
+
const color = COLORS[info.severity];
|
|
282
|
+
const severityName = color(SEVERITY_NAMES[info.severity].toLowerCase().padEnd(7));
|
|
283
|
+
logger.info(`${severityName} ${ruleId}: ${info.count}\n`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
logger.info('\n');
|
|
287
|
+
}
|
|
288
|
+
|
|
266
289
|
function formatFrom(cwd: string, location?: LocationObject) {
|
|
267
290
|
if (!location) return '';
|
|
268
291
|
const relativePath = path.relative(cwd, location.source.absoluteRef);
|
|
269
292
|
const loc = getLineColLocation(location);
|
|
270
293
|
const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
|
|
294
|
+
const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
|
|
271
295
|
|
|
272
|
-
return `referenced from ${blue(fileWithLoc)}\n\n`;
|
|
296
|
+
return `referenced from ${colorize.blue(fileWithLoc)} ${atPointer} \n\n`;
|
|
273
297
|
}
|
|
274
298
|
|
|
275
299
|
function formatDidYouMean(problem: NormalizedProblem) {
|
|
@@ -320,6 +344,7 @@ const groupByFiles = (problems: NormalizedProblem[]) => {
|
|
|
320
344
|
};
|
|
321
345
|
|
|
322
346
|
function xmlEscape(s: string): string {
|
|
347
|
+
// eslint-disable-next-line no-control-regex
|
|
323
348
|
return s.replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, (char) => {
|
|
324
349
|
switch (char) {
|
|
325
350
|
case '<':
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist } from './utils';
|
|
1
|
+
export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist, isTruthy } from './utils';
|
|
2
2
|
export { Oas3_1Types } from './types/oas3_1';
|
|
3
3
|
export { Oas3Types } from './types/oas3';
|
|
4
4
|
export { Oas2Types } from './types/oas2';
|
|
5
5
|
export { ConfigTypes } from './types/redocly-yaml';
|
|
6
|
-
export {
|
|
6
|
+
export type {
|
|
7
7
|
Oas3Definition,
|
|
8
8
|
Oas3_1Definition,
|
|
9
9
|
Oas3Components,
|
|
@@ -15,9 +15,10 @@ export {
|
|
|
15
15
|
Oas3Tag,
|
|
16
16
|
Oas3_1Webhooks,
|
|
17
17
|
Referenced,
|
|
18
|
+
OasRef,
|
|
18
19
|
} from './typings/openapi';
|
|
19
|
-
export { Oas2Definition } from './typings/swagger';
|
|
20
|
-
export { StatsAccumulator, StatsName } from './typings/common';
|
|
20
|
+
export type { Oas2Definition } from './typings/swagger';
|
|
21
|
+
export type { StatsAccumulator, StatsName } from './typings/common';
|
|
21
22
|
export { normalizeTypes } from './types';
|
|
22
23
|
export { Stats } from './rules/other/stats';
|
|
23
24
|
|
|
@@ -34,6 +35,7 @@ export {
|
|
|
34
35
|
findConfig,
|
|
35
36
|
CONFIG_FILE_NAMES,
|
|
36
37
|
RuleSeverity,
|
|
38
|
+
createConfig,
|
|
37
39
|
} from './config';
|
|
38
40
|
|
|
39
41
|
export { RedoclyClient, isRedoclyRegistryURL } from './redocly';
|
package/src/js-yaml/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// TODO: add a type for "types" https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/js-yaml/index.d.ts
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
3
|
// @ts-ignore
|
|
3
4
|
import { JSON_SCHEMA, types, LoadOptions, DumpOptions, load, dump } from 'js-yaml';
|
|
4
5
|
|
package/src/lint.ts
CHANGED
|
@@ -80,13 +80,13 @@ export async function lintDocument(opts: {
|
|
|
80
80
|
const normalizedVisitors = normalizeVisitors([...preprocessors, ...regularRules] as any, types);
|
|
81
81
|
const resolvedRefMap = await resolveDocument({
|
|
82
82
|
rootDocument: document,
|
|
83
|
-
rootType: types.
|
|
83
|
+
rootType: types.Root,
|
|
84
84
|
externalRefResolver,
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
walkDocument({
|
|
88
88
|
document,
|
|
89
|
-
rootType: types.
|
|
89
|
+
rootType: types.Root,
|
|
90
90
|
normalizedVisitors,
|
|
91
91
|
resolvedRefMap,
|
|
92
92
|
ctx,
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as colorette from 'colorette';
|
|
2
|
+
export { options as colorOptions } from 'colorette';
|
|
3
|
+
|
|
4
|
+
import { isBrowser } from './env';
|
|
5
|
+
import { identity } from './utils';
|
|
6
|
+
|
|
7
|
+
export const colorize = new Proxy(colorette, {
|
|
8
|
+
get(target: typeof colorette, prop: string): typeof identity {
|
|
9
|
+
if (isBrowser) {
|
|
10
|
+
return identity;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (target as any)[prop];
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
class Logger {
|
|
17
|
+
protected stderr(str: string) {
|
|
18
|
+
return process.stderr.write(str);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
info(str: string) {
|
|
22
|
+
return isBrowser ? console.log(str) : this.stderr(str);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
warn(str: string) {
|
|
26
|
+
return isBrowser ? console.warn(str) : this.stderr(colorize.yellow(str));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
error(str: string) {
|
|
30
|
+
return isBrowser ? console.error(str) : this.stderr(colorize.red(str));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const logger = new Logger();
|
package/src/output.ts
ADDED
package/src/redocly/index.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { green } from 'colorette';
|
|
5
4
|
import { RegistryApi } from './registry-api';
|
|
6
|
-
import { DEFAULT_REGION, DOMAINS, AVAILABLE_REGIONS
|
|
5
|
+
import { DEFAULT_REGION, DOMAINS, AVAILABLE_REGIONS } from '../config/config';
|
|
6
|
+
import { env } from '../env';
|
|
7
7
|
import { RegionalToken, RegionalTokenWithValidity } from './redocly-client-types';
|
|
8
8
|
import { isNotEmptyObject } from '../utils';
|
|
9
|
+
import { colorize } from '../logger';
|
|
9
10
|
|
|
10
11
|
import type { AccessTokens, Region } from '../config/types';
|
|
11
12
|
|
|
@@ -29,7 +30,9 @@ export class RedoclyClient {
|
|
|
29
30
|
loadRegion(region?: Region) {
|
|
30
31
|
if (region && !DOMAINS[region]) {
|
|
31
32
|
throw new Error(
|
|
32
|
-
`Invalid argument: region in config file.\nGiven: ${green(
|
|
33
|
+
`Invalid argument: region in config file.\nGiven: ${colorize.green(
|
|
34
|
+
region
|
|
35
|
+
)}, choices: "us", "eu".`
|
|
33
36
|
);
|
|
34
37
|
}
|
|
35
38
|
|
|
@@ -152,7 +155,7 @@ export class RedoclyClient {
|
|
|
152
155
|
|
|
153
156
|
const credentials = {
|
|
154
157
|
...this.readCredentialsFile(credentialsPath),
|
|
155
|
-
[this.region
|
|
158
|
+
[this.region]: accessToken,
|
|
156
159
|
token: accessToken, // FIXME: backward compatibility, remove on 1.0.0
|
|
157
160
|
};
|
|
158
161
|
this.accessTokens = credentials;
|
|
@@ -1,34 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
1
|
+
interface VersionParams {
|
|
2
|
+
organizationId: string;
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
}
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
export interface PrepareFileuploadParams extends VersionParams {
|
|
8
|
+
filesHash: string;
|
|
9
|
+
filename: string;
|
|
10
|
+
isUpsert?: boolean;
|
|
11
|
+
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
export interface PushApiParams extends VersionParams {
|
|
14
|
+
rootFilePath: string;
|
|
15
|
+
filePaths: string[];
|
|
16
|
+
branch?: string;
|
|
17
|
+
isUpsert?: boolean;
|
|
18
|
+
isPublic?: boolean;
|
|
19
|
+
batchId?: string;
|
|
20
|
+
batchSize?: number;
|
|
21
|
+
}
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
export interface PrepareFileuploadOKResponse {
|
|
24
|
+
filePath: string;
|
|
25
|
+
signedUploadUrl: string;
|
|
26
|
+
}
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
28
|
+
export interface NotFoundProblemResponse {
|
|
29
|
+
status: 404;
|
|
30
|
+
title: 'Not Found';
|
|
31
|
+
code: 'ORGANIZATION_NOT_FOUND' | 'API_VERSION_NOT_FOUND';
|
|
34
32
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import fetch, { RequestInit, HeadersInit } from 'node-fetch';
|
|
2
|
-
import {
|
|
2
|
+
import type {
|
|
3
|
+
NotFoundProblemResponse,
|
|
4
|
+
PrepareFileuploadOKResponse,
|
|
5
|
+
PrepareFileuploadParams,
|
|
6
|
+
PushApiParams,
|
|
7
|
+
} from './registry-api-types';
|
|
8
|
+
import type { AccessTokens, Region } from '../config/types';
|
|
3
9
|
import { DEFAULT_REGION, DOMAINS } from '../config/config';
|
|
4
10
|
import { isNotEmptyObject } from '../utils';
|
|
5
11
|
const version = require('../../package.json').version;
|
|
6
12
|
|
|
7
|
-
import type { AccessTokens, Region } from '../config/types';
|
|
8
|
-
|
|
9
13
|
export class RegistryApi {
|
|
10
14
|
constructor(private accessTokens: AccessTokens, private region: Region) {}
|
|
11
15
|
|
|
@@ -23,7 +27,14 @@ export class RegistryApi {
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
private async request(path = '', options: RequestInit = {}, region?: Region) {
|
|
26
|
-
const
|
|
30
|
+
const currentCommand =
|
|
31
|
+
typeof process !== 'undefined' ? process.env?.REDOCLY_CLI_COMMAND || '' : '';
|
|
32
|
+
const redoclyEnv = typeof process !== 'undefined' ? process.env?.REDOCLY_ENVIRONMENT || '' : '';
|
|
33
|
+
|
|
34
|
+
const headers = Object.assign({}, options.headers || {}, {
|
|
35
|
+
'x-redocly-cli-version': version,
|
|
36
|
+
'user-agent': `redocly-cli / ${version} ${currentCommand} ${redoclyEnv}`,
|
|
37
|
+
});
|
|
27
38
|
|
|
28
39
|
if (!headers.hasOwnProperty('authorization')) {
|
|
29
40
|
throw new Error('Unauthorized');
|
|
@@ -39,7 +50,7 @@ export class RegistryApi {
|
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
if (response.status === 404) {
|
|
42
|
-
const body:
|
|
53
|
+
const body: NotFoundProblemResponse = await response.json();
|
|
43
54
|
throw new Error(body.code);
|
|
44
55
|
}
|
|
45
56
|
|
|
@@ -71,7 +82,7 @@ export class RegistryApi {
|
|
|
71
82
|
filesHash,
|
|
72
83
|
filename,
|
|
73
84
|
isUpsert,
|
|
74
|
-
}:
|
|
85
|
+
}: PrepareFileuploadParams): Promise<PrepareFileuploadOKResponse> {
|
|
75
86
|
const response = await this.request(
|
|
76
87
|
`/${organizationId}/${name}/${version}/prepare-file-upload`,
|
|
77
88
|
{
|
|
@@ -107,7 +118,7 @@ export class RegistryApi {
|
|
|
107
118
|
isPublic,
|
|
108
119
|
batchId,
|
|
109
120
|
batchSize,
|
|
110
|
-
}:
|
|
121
|
+
}: PushApiParams) {
|
|
111
122
|
const response = await this.request(
|
|
112
123
|
`/${organizationId}/${name}/${version}`,
|
|
113
124
|
{
|
package/src/ref-utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Source } from './resolve';
|
|
2
2
|
import { OasRef } from './typings/openapi';
|
|
3
|
+
import { isTruthy } from './utils';
|
|
3
4
|
|
|
4
5
|
export function joinPointer(base: string, key: string | number) {
|
|
5
6
|
if (base === '') base = '#/';
|
|
@@ -45,7 +46,7 @@ export function parseRef(ref: string): { uri: string | null; pointer: string[] }
|
|
|
45
46
|
const [uri, pointer] = ref.split('#/');
|
|
46
47
|
return {
|
|
47
48
|
uri: uri || null,
|
|
48
|
-
pointer: pointer ? pointer.split('/').map(unescapePointer).filter(
|
|
49
|
+
pointer: pointer ? pointer.split('/').map(unescapePointer).filter(isTruthy) : [],
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
package/src/resolve.ts
CHANGED
|
@@ -91,7 +91,7 @@ export function makeDocumentFromString(sourceString: string, absoluteRef: string
|
|
|
91
91
|
export class BaseResolver {
|
|
92
92
|
cache: Map<string, Promise<Document | ResolveError>> = new Map();
|
|
93
93
|
|
|
94
|
-
constructor(
|
|
94
|
+
constructor(protected config: ResolveConfig = { http: { headers: [] } }) {}
|
|
95
95
|
|
|
96
96
|
getFiles() {
|
|
97
97
|
return new Set(Array.from(this.cache.keys()));
|
|
@@ -115,7 +115,9 @@ export class BaseResolver {
|
|
|
115
115
|
const { body, mimeType } = await readFileFromUrl(absoluteRef, this.config.http);
|
|
116
116
|
return new Source(absoluteRef, body, mimeType);
|
|
117
117
|
} else {
|
|
118
|
-
|
|
118
|
+
const content = await fs.promises.readFile(absoluteRef, 'utf-8');
|
|
119
|
+
// In some cases file have \r\n line delimeters like on windows, we should skip it.
|
|
120
|
+
return new Source(absoluteRef, content.replace(/\r\n/g, '\n'));
|
|
119
121
|
}
|
|
120
122
|
} catch (error) {
|
|
121
123
|
throw new ResolveError(error);
|
|
@@ -323,7 +325,7 @@ export async function resolveDocument(opts: {
|
|
|
323
325
|
: document;
|
|
324
326
|
} catch (error) {
|
|
325
327
|
const resolvedRef = {
|
|
326
|
-
resolved: false as
|
|
328
|
+
resolved: false as const,
|
|
327
329
|
isRemote,
|
|
328
330
|
document: undefined,
|
|
329
331
|
error: error,
|
|
@@ -334,7 +336,7 @@ export async function resolveDocument(opts: {
|
|
|
334
336
|
}
|
|
335
337
|
|
|
336
338
|
let resolvedRef: ResolvedRef = {
|
|
337
|
-
resolved: true as
|
|
339
|
+
resolved: true as const,
|
|
338
340
|
document: targetDoc,
|
|
339
341
|
isRemote,
|
|
340
342
|
node: document.parsed,
|
|
@@ -344,7 +346,7 @@ export async function resolveDocument(opts: {
|
|
|
344
346
|
let target = targetDoc.parsed as any;
|
|
345
347
|
|
|
346
348
|
const segments = pointer;
|
|
347
|
-
for (
|
|
349
|
+
for (const segment of segments) {
|
|
348
350
|
if (typeof target !== 'object') {
|
|
349
351
|
target = undefined;
|
|
350
352
|
break;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
fieldNonEmpty,
|
|
3
|
+
matchesJsonSchemaType,
|
|
4
|
+
missingRequiredField,
|
|
5
|
+
oasTypeOf,
|
|
6
|
+
getAdditionalPropertiesOption,
|
|
7
|
+
} from '../utils';
|
|
2
8
|
|
|
3
9
|
describe('field-non-empty', () => {
|
|
4
10
|
it('should match expected message', () => {
|
|
@@ -120,3 +126,35 @@ describe('oas-type-of', () => {
|
|
|
120
126
|
expect(results).toBe('object');
|
|
121
127
|
});
|
|
122
128
|
});
|
|
129
|
+
|
|
130
|
+
describe('get-additional-properties-option', () => {
|
|
131
|
+
it('should return actual option', () => {
|
|
132
|
+
const options = {
|
|
133
|
+
allowAdditionalProperties: true,
|
|
134
|
+
};
|
|
135
|
+
expect(getAdditionalPropertiesOption(options)).toBeTruthy();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should reverse option', () => {
|
|
139
|
+
const options = {
|
|
140
|
+
disallowAdditionalProperties: true,
|
|
141
|
+
};
|
|
142
|
+
expect(getAdditionalPropertiesOption(options)).toBeFalsy();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should throw error with message', () => {
|
|
146
|
+
const options = {
|
|
147
|
+
allowAdditionalProperties: true,
|
|
148
|
+
disallowAdditionalProperties: false,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
getAdditionalPropertiesOption(options);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
expect(error).toBeInstanceOf(Error);
|
|
155
|
+
expect(error.message).toEqual(
|
|
156
|
+
"Do not use 'disallowAdditionalProperties' field. Use 'allowAdditionalProperties' instead. \n"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|