@redocly/openapi-core 1.10.6 → 1.12.0
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/CHANGELOG.md +17 -0
- package/lib/config/config-resolvers.d.ts +5 -1
- package/lib/config/config-resolvers.js +4 -4
- package/lib/config/load.d.ts +1 -0
- package/lib/config/load.js +7 -2
- package/lib/config/utils.js +1 -1
- package/lib/decorators/oas2/remove-unused-components.js +36 -22
- package/lib/decorators/oas3/remove-unused-components.js +34 -19
- package/lib/format/format.d.ts +1 -1
- package/lib/format/format.js +57 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +3 -2
- package/lib/rules/common/assertions/utils.js +2 -2
- package/lib/rules/oas3/no-invalid-media-type-examples.js +3 -0
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +7 -1
- package/package.json +2 -2
- package/src/__tests__/format.test.ts +34 -0
- package/src/bundle.ts +1 -1
- package/src/config/__tests__/config-resolvers.test.ts +4 -4
- package/src/config/__tests__/fixtures/load-external.yaml +2 -0
- package/src/config/__tests__/load.test.ts +14 -0
- package/src/config/config-resolvers.ts +13 -7
- package/src/config/load.ts +13 -4
- package/src/config/utils.ts +1 -1
- package/src/decorators/oas2/__tests__/remove-unused-components.test.ts +74 -1
- package/src/decorators/oas2/remove-unused-components.ts +46 -24
- package/src/decorators/oas3/__tests__/remove-unused-components.test.ts +142 -0
- package/src/decorators/oas3/remove-unused-components.ts +45 -20
- package/src/format/format.ts +62 -1
- package/src/index.ts +8 -1
- package/src/rules/common/assertions/utils.ts +2 -2
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +145 -0
- package/src/rules/oas3/no-invalid-media-type-examples.ts +3 -0
- package/src/utils.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @redocly/openapi-core
|
|
2
2
|
|
|
3
|
+
## 1.12.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Improved caching for external configuration resources.
|
|
8
|
+
|
|
9
|
+
## 1.11.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Added support for a `github-actions` output format for the `lint` command to annotate reported problems on files when used in a GitHub Actions workflow.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Fixed [`no-invalid-media-type-examples`](https://redocly.com/docs/cli/rules/no-invalid-media-type-examples/) rule `externalValue` example validation.
|
|
18
|
+
- Process remove-unused-components rule transitively; components are now removed if they were previously referenced by a removed component.
|
|
19
|
+
|
|
3
20
|
## 1.10.6
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
@@ -9,7 +9,11 @@ export declare function resolveConfigFileAndRefs({ configPath, externalRefResolv
|
|
|
9
9
|
document: Document;
|
|
10
10
|
resolvedRefMap: ResolvedRefMap;
|
|
11
11
|
}>;
|
|
12
|
-
export declare function resolveConfig(rawConfig
|
|
12
|
+
export declare function resolveConfig({ rawConfig, configPath, externalRefResolver, }: {
|
|
13
|
+
rawConfig: RawConfig;
|
|
14
|
+
configPath?: string;
|
|
15
|
+
externalRefResolver?: BaseResolver;
|
|
16
|
+
}): Promise<Config>;
|
|
13
17
|
export declare function resolvePlugins(plugins: (string | Plugin)[] | null, configPath?: string): Plugin[];
|
|
14
18
|
export declare function resolveApis({ rawConfig, configPath, resolver, }: {
|
|
15
19
|
rawConfig: RawConfig;
|
|
@@ -53,13 +53,13 @@ function resolveConfigFileAndRefs({ configPath, externalRefResolver = new resolv
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
exports.resolveConfigFileAndRefs = resolveConfigFileAndRefs;
|
|
56
|
-
function resolveConfig(rawConfig, configPath) {
|
|
56
|
+
function resolveConfig({ rawConfig, configPath, externalRefResolver, }) {
|
|
57
57
|
var _a, _b;
|
|
58
58
|
return __awaiter(this, void 0, void 0, function* () {
|
|
59
59
|
if ((_b = (_a = rawConfig.styleguide) === null || _a === void 0 ? void 0 : _a.extends) === null || _b === void 0 ? void 0 : _b.some(utils_3.isNotString)) {
|
|
60
60
|
throw new Error(`Error configuration format not detected in extends value must contain strings`);
|
|
61
61
|
}
|
|
62
|
-
const resolver = new resolve_1.BaseResolver((0, utils_2.getResolveConfig)(rawConfig.resolve));
|
|
62
|
+
const resolver = externalRefResolver !== null && externalRefResolver !== void 0 ? externalRefResolver : new resolve_1.BaseResolver((0, utils_2.getResolveConfig)(rawConfig.resolve));
|
|
63
63
|
const apis = yield resolveApis({
|
|
64
64
|
rawConfig,
|
|
65
65
|
configPath,
|
|
@@ -263,8 +263,8 @@ exports.resolvePreset = resolvePreset;
|
|
|
263
263
|
function loadExtendStyleguideConfig(filePath, resolver) {
|
|
264
264
|
return __awaiter(this, void 0, void 0, function* () {
|
|
265
265
|
try {
|
|
266
|
-
const
|
|
267
|
-
const rawConfig = (0, utils_2.transformConfig)(
|
|
266
|
+
const { parsed } = (yield resolver.resolveDocument(null, filePath));
|
|
267
|
+
const rawConfig = (0, utils_2.transformConfig)(parsed);
|
|
268
268
|
if (!rawConfig.styleguide) {
|
|
269
269
|
throw new Error(`Styleguide configuration format not detected: "${filePath}"`);
|
|
270
270
|
}
|
package/lib/config/load.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ type CreateConfigOptions = {
|
|
|
23
23
|
extends?: string[];
|
|
24
24
|
tokens?: RegionalTokenWithValidity[];
|
|
25
25
|
configPath?: string;
|
|
26
|
+
externalRefResolver?: BaseResolver;
|
|
26
27
|
};
|
|
27
28
|
export declare function createConfig(config: string | RawUniversalConfig, options?: CreateConfigOptions): Promise<Config>;
|
|
28
29
|
export {};
|
package/lib/config/load.js
CHANGED
|
@@ -19,7 +19,7 @@ const config_1 = require("./config");
|
|
|
19
19
|
const utils_2 = require("./utils");
|
|
20
20
|
const config_resolvers_1 = require("./config-resolvers");
|
|
21
21
|
const bundle_1 = require("../bundle");
|
|
22
|
-
function addConfigMetadata({ rawConfig, customExtends, configPath, tokens, files, region, }) {
|
|
22
|
+
function addConfigMetadata({ rawConfig, customExtends, configPath, tokens, files, region, externalRefResolver, }) {
|
|
23
23
|
var _a;
|
|
24
24
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
25
|
if (customExtends !== undefined) {
|
|
@@ -56,7 +56,11 @@ function addConfigMetadata({ rawConfig, customExtends, configPath, tokens, files
|
|
|
56
56
|
: []));
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
return (0, config_resolvers_1.resolveConfig)(
|
|
59
|
+
return (0, config_resolvers_1.resolveConfig)({
|
|
60
|
+
rawConfig: Object.assign(Object.assign({}, rawConfig), { files: files !== null && files !== void 0 ? files : rawConfig.files, region: region !== null && region !== void 0 ? region : rawConfig.region }),
|
|
61
|
+
configPath,
|
|
62
|
+
externalRefResolver,
|
|
63
|
+
});
|
|
60
64
|
});
|
|
61
65
|
}
|
|
62
66
|
function loadConfig(options = {}) {
|
|
@@ -72,6 +76,7 @@ function loadConfig(options = {}) {
|
|
|
72
76
|
tokens,
|
|
73
77
|
files,
|
|
74
78
|
region,
|
|
79
|
+
externalRefResolver,
|
|
75
80
|
});
|
|
76
81
|
});
|
|
77
82
|
}
|
package/lib/config/utils.js
CHANGED
|
@@ -114,7 +114,7 @@ function mergeExtends(rulesConfList) {
|
|
|
114
114
|
};
|
|
115
115
|
for (const rulesConf of rulesConfList) {
|
|
116
116
|
if (rulesConf.extends) {
|
|
117
|
-
throw new Error(`'extends' is not supported in shared configs yet
|
|
117
|
+
throw new Error(`'extends' is not supported in shared configs yet:\n${JSON.stringify(rulesConf, null, 2)}`);
|
|
118
118
|
}
|
|
119
119
|
Object.assign(result.rules, rulesConf.rules);
|
|
120
120
|
Object.assign(result.oas2Rules, rulesConf.oas2Rules);
|
|
@@ -5,16 +5,38 @@ const utils_1 = require("../../utils");
|
|
|
5
5
|
const RemoveUnusedComponents = () => {
|
|
6
6
|
const components = new Map();
|
|
7
7
|
function registerComponent(location, componentType, name) {
|
|
8
|
-
var _a;
|
|
8
|
+
var _a, _b;
|
|
9
9
|
components.set(location.absolutePointer, {
|
|
10
|
-
|
|
10
|
+
usedIn: (_b = (_a = components.get(location.absolutePointer)) === null || _a === void 0 ? void 0 : _a.usedIn) !== null && _b !== void 0 ? _b : [],
|
|
11
11
|
componentType,
|
|
12
12
|
name,
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
function removeUnusedComponents(root, removedPaths) {
|
|
16
|
+
const removedLengthStart = removedPaths.length;
|
|
17
|
+
for (const [path, { usedIn, name, componentType }] of components) {
|
|
18
|
+
const used = usedIn.some((location) => !removedPaths.some((removed) =>
|
|
19
|
+
// Check if the current location's absolute pointer starts with the 'removed' path
|
|
20
|
+
// and either its length matches exactly with 'removed' or the character after the 'removed' path is a '/'
|
|
21
|
+
location.absolutePointer.startsWith(removed) &&
|
|
22
|
+
(location.absolutePointer.length === removed.length ||
|
|
23
|
+
location.absolutePointer[removed.length] === '/')));
|
|
24
|
+
if (!used && componentType) {
|
|
25
|
+
removedPaths.push(path);
|
|
26
|
+
delete root[componentType][name];
|
|
27
|
+
components.delete(path);
|
|
28
|
+
if ((0, utils_1.isEmptyObject)(root[componentType])) {
|
|
29
|
+
delete root[componentType];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return removedPaths.length > removedLengthStart
|
|
34
|
+
? removeUnusedComponents(root, removedPaths)
|
|
35
|
+
: removedPaths.length;
|
|
36
|
+
}
|
|
15
37
|
return {
|
|
16
38
|
ref: {
|
|
17
|
-
leave(ref, { type, resolve, key }) {
|
|
39
|
+
leave(ref, { location, type, resolve, key }) {
|
|
18
40
|
if (['Schema', 'Parameter', 'Response', 'SecurityScheme'].includes(type.name)) {
|
|
19
41
|
const resolvedRef = resolve(ref);
|
|
20
42
|
if (!resolvedRef.location)
|
|
@@ -22,31 +44,23 @@ const RemoveUnusedComponents = () => {
|
|
|
22
44
|
const [fileLocation, localPointer] = resolvedRef.location.absolutePointer.split('#', 2);
|
|
23
45
|
const componentLevelLocalPointer = localPointer.split('/').slice(0, 3).join('/');
|
|
24
46
|
const pointer = `${fileLocation}#${componentLevelLocalPointer}`;
|
|
25
|
-
components.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
47
|
+
const registered = components.get(pointer);
|
|
48
|
+
if (registered) {
|
|
49
|
+
registered.usedIn.push(location);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
components.set(pointer, {
|
|
53
|
+
usedIn: [location],
|
|
54
|
+
name: key.toString(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
29
57
|
}
|
|
30
58
|
},
|
|
31
59
|
},
|
|
32
60
|
Root: {
|
|
33
61
|
leave(root, ctx) {
|
|
34
62
|
const data = ctx.getVisitorData();
|
|
35
|
-
data.removedCount =
|
|
36
|
-
const rootComponents = new Set();
|
|
37
|
-
components.forEach((usageInfo) => {
|
|
38
|
-
const { used, name, componentType } = usageInfo;
|
|
39
|
-
if (!used && componentType) {
|
|
40
|
-
rootComponents.add(componentType);
|
|
41
|
-
delete root[componentType][name];
|
|
42
|
-
data.removedCount++;
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
for (const component of rootComponents) {
|
|
46
|
-
if ((0, utils_1.isEmptyObject)(root[component])) {
|
|
47
|
-
delete root[component];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
63
|
+
data.removedCount = removeUnusedComponents(root, []);
|
|
50
64
|
},
|
|
51
65
|
},
|
|
52
66
|
NamedSchemas: {
|
|
@@ -5,16 +5,36 @@ const utils_1 = require("../../utils");
|
|
|
5
5
|
const RemoveUnusedComponents = () => {
|
|
6
6
|
const components = new Map();
|
|
7
7
|
function registerComponent(location, componentType, name) {
|
|
8
|
-
var _a;
|
|
8
|
+
var _a, _b;
|
|
9
9
|
components.set(location.absolutePointer, {
|
|
10
|
-
|
|
10
|
+
usedIn: (_b = (_a = components.get(location.absolutePointer)) === null || _a === void 0 ? void 0 : _a.usedIn) !== null && _b !== void 0 ? _b : [],
|
|
11
11
|
componentType,
|
|
12
12
|
name,
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
function removeUnusedComponents(root, removedPaths) {
|
|
16
|
+
const removedLengthStart = removedPaths.length;
|
|
17
|
+
for (const [path, { usedIn, name, componentType }] of components) {
|
|
18
|
+
const used = usedIn.some((location) => !removedPaths.some((removed) => location.absolutePointer.startsWith(removed) &&
|
|
19
|
+
(location.absolutePointer.length === removed.length ||
|
|
20
|
+
location.absolutePointer[removed.length] === '/')));
|
|
21
|
+
if (!used && componentType && root.components) {
|
|
22
|
+
removedPaths.push(path);
|
|
23
|
+
const componentChild = root.components[componentType];
|
|
24
|
+
delete componentChild[name];
|
|
25
|
+
components.delete(path);
|
|
26
|
+
if ((0, utils_1.isEmptyObject)(componentChild)) {
|
|
27
|
+
delete root.components[componentType];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return removedPaths.length > removedLengthStart
|
|
32
|
+
? removeUnusedComponents(root, removedPaths)
|
|
33
|
+
: removedPaths.length;
|
|
34
|
+
}
|
|
15
35
|
return {
|
|
16
36
|
ref: {
|
|
17
|
-
leave(ref, { type, resolve, key }) {
|
|
37
|
+
leave(ref, { location, type, resolve, key }) {
|
|
18
38
|
if (['Schema', 'Header', 'Parameter', 'Response', 'Example', 'RequestBody'].includes(type.name)) {
|
|
19
39
|
const resolvedRef = resolve(ref);
|
|
20
40
|
if (!resolvedRef.location)
|
|
@@ -22,28 +42,23 @@ const RemoveUnusedComponents = () => {
|
|
|
22
42
|
const [fileLocation, localPointer] = resolvedRef.location.absolutePointer.split('#', 2);
|
|
23
43
|
const componentLevelLocalPointer = localPointer.split('/').slice(0, 4).join('/');
|
|
24
44
|
const pointer = `${fileLocation}#${componentLevelLocalPointer}`;
|
|
25
|
-
components.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
45
|
+
const registered = components.get(pointer);
|
|
46
|
+
if (registered) {
|
|
47
|
+
registered.usedIn.push(location);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
components.set(pointer, {
|
|
51
|
+
usedIn: [location],
|
|
52
|
+
name: key.toString(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
29
55
|
}
|
|
30
56
|
},
|
|
31
57
|
},
|
|
32
58
|
Root: {
|
|
33
59
|
leave(root, ctx) {
|
|
34
60
|
const data = ctx.getVisitorData();
|
|
35
|
-
data.removedCount =
|
|
36
|
-
components.forEach((usageInfo) => {
|
|
37
|
-
const { used, componentType, name } = usageInfo;
|
|
38
|
-
if (!used && componentType && root.components) {
|
|
39
|
-
const componentChild = root.components[componentType];
|
|
40
|
-
delete componentChild[name];
|
|
41
|
-
data.removedCount++;
|
|
42
|
-
if ((0, utils_1.isEmptyObject)(componentChild)) {
|
|
43
|
-
delete root.components[componentType];
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
});
|
|
61
|
+
data.removedCount = removeUnusedComponents(root, []);
|
|
47
62
|
if ((0, utils_1.isEmptyObject)(root.components)) {
|
|
48
63
|
delete root.components;
|
|
49
64
|
}
|
package/lib/format/format.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type Totals = {
|
|
|
4
4
|
warnings: number;
|
|
5
5
|
ignored: number;
|
|
6
6
|
};
|
|
7
|
-
export type OutputFormat = 'codeframe' | 'stylish' | 'json' | 'checkstyle' | 'codeclimate' | 'summary';
|
|
7
|
+
export type OutputFormat = 'codeframe' | 'stylish' | 'json' | 'checkstyle' | 'codeclimate' | 'summary' | 'github-actions' | 'markdown';
|
|
8
8
|
export declare function getTotals(problems: (NormalizedProblem & {
|
|
9
9
|
ignored?: boolean;
|
|
10
10
|
})[]): Totals;
|
package/lib/format/format.js
CHANGED
|
@@ -103,6 +103,8 @@ function formatProblems(problems, opts) {
|
|
|
103
103
|
case 'summary':
|
|
104
104
|
formatSummary(problems);
|
|
105
105
|
break;
|
|
106
|
+
case 'github-actions':
|
|
107
|
+
outputForGithubActions(problems, cwd);
|
|
106
108
|
}
|
|
107
109
|
if (totalProblems - ignoredProblems > maxProblems) {
|
|
108
110
|
logger_1.logger.info(`< ... ${totalProblems - maxProblems} more problems hidden > ${logger_1.colorize.gray('increase with `--max-problems N`')}\n`);
|
|
@@ -266,3 +268,58 @@ function xmlEscape(s) {
|
|
|
266
268
|
}
|
|
267
269
|
});
|
|
268
270
|
}
|
|
271
|
+
function outputForGithubActions(problems, cwd) {
|
|
272
|
+
var _a, _b;
|
|
273
|
+
for (const problem of problems) {
|
|
274
|
+
for (const location of problem.location.map(codeframes_1.getLineColLocation)) {
|
|
275
|
+
let command;
|
|
276
|
+
switch (problem.severity) {
|
|
277
|
+
case 'error':
|
|
278
|
+
command = 'error';
|
|
279
|
+
break;
|
|
280
|
+
case 'warn':
|
|
281
|
+
command = 'warning';
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
const suggest = formatDidYouMean(problem);
|
|
285
|
+
const message = suggest !== '' ? problem.message + '\n\n' + suggest : problem.message;
|
|
286
|
+
const properties = {
|
|
287
|
+
title: problem.ruleId,
|
|
288
|
+
file: (0, ref_utils_1.isAbsoluteUrl)(location.source.absoluteRef)
|
|
289
|
+
? location.source.absoluteRef
|
|
290
|
+
: path.relative(cwd, location.source.absoluteRef),
|
|
291
|
+
line: location.start.line,
|
|
292
|
+
col: location.start.col,
|
|
293
|
+
endLine: (_a = location.end) === null || _a === void 0 ? void 0 : _a.line,
|
|
294
|
+
endColumn: (_b = location.end) === null || _b === void 0 ? void 0 : _b.col,
|
|
295
|
+
};
|
|
296
|
+
output_1.output.write(`::${command} ${formatProperties(properties)}::${escapeMessage(message)}\n`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function formatProperties(props) {
|
|
300
|
+
return Object.entries(props)
|
|
301
|
+
.filter(([, v]) => v !== null && v !== undefined)
|
|
302
|
+
.map(([k, v]) => `${k}=${escapeProperty(v)}`)
|
|
303
|
+
.join(',');
|
|
304
|
+
}
|
|
305
|
+
function toString(v) {
|
|
306
|
+
if (v === null || v === undefined) {
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
else if (typeof v === 'string' || v instanceof String) {
|
|
310
|
+
return v;
|
|
311
|
+
}
|
|
312
|
+
return JSON.stringify(v);
|
|
313
|
+
}
|
|
314
|
+
function escapeMessage(v) {
|
|
315
|
+
return toString(v).replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
|
|
316
|
+
}
|
|
317
|
+
function escapeProperty(v) {
|
|
318
|
+
return toString(v)
|
|
319
|
+
.replace(/%/g, '%25')
|
|
320
|
+
.replace(/\r/g, '%0D')
|
|
321
|
+
.replace(/\n/g, '%0A')
|
|
322
|
+
.replace(/:/g, '%3A')
|
|
323
|
+
.replace(/,/g, '%2C');
|
|
324
|
+
}
|
|
325
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist, isTruthy } from './utils';
|
|
1
|
+
export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist, isTruthy, pause, } 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';
|
package/lib/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.bundleFromString = exports.mapTypeToComponent = exports.bundleDocument = exports.bundle = void 0;
|
|
3
|
+
exports.lintFromString = exports.lintDocument = exports.validate = exports.lint = exports.getTotals = exports.formatProblems = exports.getLineColLocation = exports.getAstNodeByPointer = exports.walkDocument = exports.normalizeVisitors = exports.getTypes = exports.detectSpec = exports.SpecVersion = exports.getMajorSpecVersion = exports.SpecMajorVersion = exports.isAbsoluteUrl = exports.isRef = exports.unescapePointer = exports.stringifyYaml = exports.parseYaml = exports.makeDocumentFromString = exports.YamlParseError = exports.ResolveError = exports.resolveDocument = exports.BaseResolver = exports.Source = exports.isRedoclyRegistryURL = exports.RedoclyClient = exports.createConfig = exports.CONFIG_FILE_NAMES = exports.findConfig = exports.getConfig = exports.loadConfig = exports.transformConfig = exports.getMergedConfig = exports.IGNORE_FILE = exports.StyleguideConfig = exports.Config = exports.Stats = exports.normalizeTypes = exports.ConfigTypes = exports.AsyncApi2Types = exports.Oas2Types = exports.Oas3Types = exports.Oas3_1Types = exports.pause = exports.isTruthy = exports.doesYamlFileExist = exports.slash = exports.readFileFromUrl = void 0;
|
|
4
|
+
exports.bundleFromString = exports.mapTypeToComponent = exports.bundleDocument = exports.bundle = exports.lintConfig = void 0;
|
|
5
5
|
var utils_1 = require("./utils");
|
|
6
6
|
Object.defineProperty(exports, "readFileFromUrl", { enumerable: true, get: function () { return utils_1.readFileFromUrl; } });
|
|
7
7
|
Object.defineProperty(exports, "slash", { enumerable: true, get: function () { return utils_1.slash; } });
|
|
8
8
|
Object.defineProperty(exports, "doesYamlFileExist", { enumerable: true, get: function () { return utils_1.doesYamlFileExist; } });
|
|
9
9
|
Object.defineProperty(exports, "isTruthy", { enumerable: true, get: function () { return utils_1.isTruthy; } });
|
|
10
|
+
Object.defineProperty(exports, "pause", { enumerable: true, get: function () { return utils_1.pause; } });
|
|
10
11
|
var oas3_1_1 = require("./types/oas3_1");
|
|
11
12
|
Object.defineProperty(exports, "Oas3_1Types", { enumerable: true, get: function () { return oas3_1_1.Oas3_1Types; } });
|
|
12
13
|
var oas3_1 = require("./types/oas3");
|
|
@@ -32,10 +32,10 @@ function getAssertsToApply(assertion) {
|
|
|
32
32
|
const shouldRunOnKeys = assertsToApply.find((assert) => assert.runsOnKeys && !assert.runsOnValues);
|
|
33
33
|
const shouldRunOnValues = assertsToApply.find((assert) => assert.runsOnValues && !assert.runsOnKeys);
|
|
34
34
|
if (shouldRunOnValues && !assertion.subject.property) {
|
|
35
|
-
throw new Error(
|
|
35
|
+
throw new Error(`The '${shouldRunOnValues.name}' assertion can't be used on all keys. Please provide a single property.`);
|
|
36
36
|
}
|
|
37
37
|
if (shouldRunOnKeys && assertion.subject.property) {
|
|
38
|
-
throw new Error(
|
|
38
|
+
throw new Error(`The '${shouldRunOnKeys.name}' assertion can't be used on properties. Please remove the 'property' key.`);
|
|
39
39
|
}
|
|
40
40
|
return assertsToApply;
|
|
41
41
|
}
|
|
@@ -28,6 +28,9 @@ const ValidContentExamples = (opts) => {
|
|
|
28
28
|
location = isMultiple ? resolved.location.child('value') : resolved.location;
|
|
29
29
|
example = resolved.node;
|
|
30
30
|
}
|
|
31
|
+
if (isMultiple && typeof example.value === 'undefined') {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
31
34
|
(0, utils_1.validateExample)(isMultiple ? example.value : example, mediaType.schema, location, ctx, allowAdditionalProperties);
|
|
32
35
|
}
|
|
33
36
|
},
|
package/lib/utils.d.ts
CHANGED
|
@@ -50,3 +50,4 @@ export declare function identity<T>(value: T): T;
|
|
|
50
50
|
export declare function keysOf<T>(obj: T): (keyof T)[];
|
|
51
51
|
export declare function pickDefined<T extends Record<string, unknown>>(obj?: T): Record<string, unknown> | undefined;
|
|
52
52
|
export declare function nextTick(): void;
|
|
53
|
+
export declare function pause(ms: number): Promise<void>;
|
package/lib/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.nextTick = exports.pickDefined = exports.keysOf = exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.yamlAndJsonSyncReader = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
12
|
+
exports.pause = exports.nextTick = exports.pickDefined = exports.keysOf = exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.yamlAndJsonSyncReader = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
|
|
13
13
|
const fs = require("fs");
|
|
14
14
|
const path_1 = require("path");
|
|
15
15
|
const minimatch = require("minimatch");
|
|
@@ -236,6 +236,12 @@ function nextTick() {
|
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
238
|
exports.nextTick = nextTick;
|
|
239
|
+
function pause(ms) {
|
|
240
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
241
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
exports.pause = pause;
|
|
239
245
|
function getUpdatedFieldName(updatedField, updatedObject) {
|
|
240
246
|
return `${typeof updatedObject !== 'undefined' ? `${updatedObject}.` : ''}${updatedField}`;
|
|
241
247
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/openapi-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@redocly/ajv": "^8.11.0",
|
|
38
|
-
"@redocly/config": "^0.
|
|
38
|
+
"@redocly/config": "^0.2.0",
|
|
39
39
|
"colorette": "^1.2.0",
|
|
40
40
|
"js-levenshtein": "^1.1.6",
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { outdent } from 'outdent';
|
|
2
2
|
|
|
3
3
|
import { formatProblems, getTotals } from '../format/format';
|
|
4
|
+
import { LocationObject, NormalizedProblem } from '../walk';
|
|
5
|
+
import { Source } from '../resolve';
|
|
4
6
|
|
|
5
7
|
describe('format', () => {
|
|
6
8
|
function replaceColors(log: string) {
|
|
@@ -40,6 +42,10 @@ describe('format', () => {
|
|
|
40
42
|
output += str;
|
|
41
43
|
return true;
|
|
42
44
|
});
|
|
45
|
+
jest.spyOn(process.stdout, 'write').mockImplementation((str: string | Uint8Array) => {
|
|
46
|
+
output += str;
|
|
47
|
+
return true;
|
|
48
|
+
});
|
|
43
49
|
});
|
|
44
50
|
|
|
45
51
|
it('should correctly format summary output', () => {
|
|
@@ -73,4 +79,32 @@ describe('format', () => {
|
|
|
73
79
|
"
|
|
74
80
|
`);
|
|
75
81
|
});
|
|
82
|
+
|
|
83
|
+
it('should format problems using github-actions', () => {
|
|
84
|
+
const problems = [
|
|
85
|
+
{
|
|
86
|
+
ruleId: 'spec',
|
|
87
|
+
message: 'message',
|
|
88
|
+
severity: 'error' as const,
|
|
89
|
+
location: [
|
|
90
|
+
{
|
|
91
|
+
source: { absoluteRef: 'openapi.yaml' } as Source,
|
|
92
|
+
start: { line: 1, col: 2 },
|
|
93
|
+
end: { line: 3, col: 4 },
|
|
94
|
+
} as LocationObject,
|
|
95
|
+
],
|
|
96
|
+
suggest: [],
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
formatProblems(problems, {
|
|
101
|
+
format: 'github-actions',
|
|
102
|
+
version: '1.0.0',
|
|
103
|
+
totals: getTotals(problems),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(output).toEqual(
|
|
107
|
+
'::error title=spec,file=openapi.yaml,line=1,col=2,endLine=3,endColumn=4::message\n'
|
|
108
|
+
);
|
|
109
|
+
});
|
|
76
110
|
});
|
package/src/bundle.ts
CHANGED
|
@@ -390,7 +390,7 @@ describe('resolveConfig', () => {
|
|
|
390
390
|
},
|
|
391
391
|
};
|
|
392
392
|
|
|
393
|
-
const { apis } = await resolveConfig(rawConfig, configPath);
|
|
393
|
+
const { apis } = await resolveConfig({ rawConfig, configPath });
|
|
394
394
|
//@ts-ignore
|
|
395
395
|
expect(apis['petstore'].styleguide.plugins.length).toEqual(1);
|
|
396
396
|
//@ts-ignore
|
|
@@ -427,7 +427,7 @@ describe('resolveConfig', () => {
|
|
|
427
427
|
},
|
|
428
428
|
};
|
|
429
429
|
|
|
430
|
-
const { apis } = await resolveConfig(rawConfig, configPath);
|
|
430
|
+
const { apis } = await resolveConfig({ rawConfig, configPath });
|
|
431
431
|
expect(apis['petstore'].styleguide.rules).toBeDefined();
|
|
432
432
|
expect(Object.keys(apis['petstore'].styleguide.rules || {}).length).toEqual(7);
|
|
433
433
|
expect(apis['petstore'].styleguide.rules?.['operation-2xx-response']).toEqual('warn');
|
|
@@ -469,7 +469,7 @@ describe('resolveConfig', () => {
|
|
|
469
469
|
},
|
|
470
470
|
};
|
|
471
471
|
|
|
472
|
-
const { apis } = await resolveConfig(rawConfig, configPath);
|
|
472
|
+
const { apis } = await resolveConfig({ rawConfig, configPath });
|
|
473
473
|
expect(apis['petstore'].styleguide.rules).toBeDefined();
|
|
474
474
|
expect(apis['petstore'].styleguide.rules?.['operation-2xx-response']).toEqual('warn');
|
|
475
475
|
expect(apis['petstore'].styleguide.rules?.['operation-4xx-response']).toEqual('error');
|
|
@@ -513,7 +513,7 @@ describe('resolveConfig', () => {
|
|
|
513
513
|
},
|
|
514
514
|
};
|
|
515
515
|
|
|
516
|
-
const { apis } = await resolveConfig(rawConfig, configPath);
|
|
516
|
+
const { apis } = await resolveConfig({ rawConfig, configPath });
|
|
517
517
|
expect(apis['petstore'].styleguide.rules).toBeDefined();
|
|
518
518
|
expect(apis['petstore'].styleguide.rules?.['operation-2xx-response']).toEqual('warn'); // from minimal ruleset
|
|
519
519
|
});
|
|
@@ -5,6 +5,7 @@ import { lintConfig } from '../../lint';
|
|
|
5
5
|
import { replaceSourceWithRef } from '../../../__tests__/utils';
|
|
6
6
|
import type { RuleConfig, FlatRawConfig } from './../types';
|
|
7
7
|
import type { NormalizedProblem } from '../../walk';
|
|
8
|
+
import { BaseResolver } from '../../resolve';
|
|
8
9
|
|
|
9
10
|
const fs = require('fs');
|
|
10
11
|
const path = require('path');
|
|
@@ -58,6 +59,19 @@ describe('loadConfig', () => {
|
|
|
58
59
|
});
|
|
59
60
|
expect(mockFn).toHaveBeenCalled();
|
|
60
61
|
});
|
|
62
|
+
|
|
63
|
+
it('should call externalRefResolver if such passed', async () => {
|
|
64
|
+
const externalRefResolver = new BaseResolver();
|
|
65
|
+
const resolverSpy = jest.spyOn(externalRefResolver, 'resolveDocument');
|
|
66
|
+
await loadConfig({
|
|
67
|
+
configPath: path.join(__dirname, './fixtures/load-external.yaml'),
|
|
68
|
+
externalRefResolver: externalRefResolver as any,
|
|
69
|
+
});
|
|
70
|
+
expect(resolverSpy).toHaveBeenCalledWith(
|
|
71
|
+
null,
|
|
72
|
+
'https://raw.githubusercontent.com/Redocly/redocly-cli-cookbook/main/rulesets/spec-compliant/redocly.yaml'
|
|
73
|
+
);
|
|
74
|
+
});
|
|
61
75
|
});
|
|
62
76
|
|
|
63
77
|
describe('findConfig', () => {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
transformConfig,
|
|
13
13
|
} from './utils';
|
|
14
14
|
import { isBrowser } from '../env';
|
|
15
|
-
import { isNotString, isString, isDefined,
|
|
15
|
+
import { isNotString, isString, isDefined, keysOf } from '../utils';
|
|
16
16
|
import { Config } from './config';
|
|
17
17
|
import { colorize, logger } from '../logger';
|
|
18
18
|
import { asserts, buildAssertCustomFunction } from '../rules/common/assertions/asserts';
|
|
@@ -63,14 +63,22 @@ export async function resolveConfigFileAndRefs({
|
|
|
63
63
|
return { document, resolvedRefMap };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
export async function resolveConfig(
|
|
66
|
+
export async function resolveConfig({
|
|
67
|
+
rawConfig,
|
|
68
|
+
configPath,
|
|
69
|
+
externalRefResolver,
|
|
70
|
+
}: {
|
|
71
|
+
rawConfig: RawConfig;
|
|
72
|
+
configPath?: string;
|
|
73
|
+
externalRefResolver?: BaseResolver;
|
|
74
|
+
}): Promise<Config> {
|
|
67
75
|
if (rawConfig.styleguide?.extends?.some(isNotString)) {
|
|
68
76
|
throw new Error(
|
|
69
77
|
`Error configuration format not detected in extends value must contain strings`
|
|
70
78
|
);
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
const resolver = new BaseResolver(getResolveConfig(rawConfig.resolve));
|
|
81
|
+
const resolver = externalRefResolver ?? new BaseResolver(getResolveConfig(rawConfig.resolve));
|
|
74
82
|
|
|
75
83
|
const apis = await resolveApis({
|
|
76
84
|
rawConfig,
|
|
@@ -388,10 +396,8 @@ async function loadExtendStyleguideConfig(
|
|
|
388
396
|
resolver: BaseResolver
|
|
389
397
|
): Promise<StyleguideRawConfig> {
|
|
390
398
|
try {
|
|
391
|
-
const
|
|
392
|
-
const rawConfig = transformConfig(
|
|
393
|
-
parseYaml(fileSource.body) as RawConfig & DeprecatedInRawConfig
|
|
394
|
-
);
|
|
399
|
+
const { parsed } = (await resolver.resolveDocument(null, filePath)) as Document;
|
|
400
|
+
const rawConfig = transformConfig(parsed as RawConfig & DeprecatedInRawConfig);
|
|
395
401
|
if (!rawConfig.styleguide) {
|
|
396
402
|
throw new Error(`Styleguide configuration format not detected: "${filePath}"`);
|
|
397
403
|
}
|