@redocly/openapi-core 1.0.0-beta.89 → 1.0.0-beta.92

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.
Files changed (50) hide show
  1. package/__tests__/__snapshots__/bundle.test.ts.snap +15 -13
  2. package/__tests__/ref-utils.test.ts +23 -1
  3. package/lib/config/all.js +1 -0
  4. package/lib/config/config.js +6 -11
  5. package/lib/config/minimal.js +1 -0
  6. package/lib/config/recommended.js +1 -0
  7. package/lib/config/rules.d.ts +1 -1
  8. package/lib/config/rules.js +10 -2
  9. package/lib/redocly/index.d.ts +4 -1
  10. package/lib/redocly/index.js +28 -19
  11. package/lib/redocly/registry-api.d.ts +4 -1
  12. package/lib/redocly/registry-api.js +3 -3
  13. package/lib/ref-utils.js +1 -1
  14. package/lib/rules/common/assertions/asserts.d.ts +5 -0
  15. package/lib/rules/common/assertions/asserts.js +143 -0
  16. package/lib/rules/common/assertions/index.d.ts +2 -0
  17. package/lib/rules/common/assertions/index.js +52 -0
  18. package/lib/rules/common/assertions/utils.d.ts +20 -0
  19. package/lib/rules/common/assertions/utils.js +123 -0
  20. package/lib/rules/oas2/index.d.ts +1 -0
  21. package/lib/rules/oas2/index.js +2 -0
  22. package/lib/rules/oas3/index.js +2 -0
  23. package/lib/types/redocly-yaml.js +24 -11
  24. package/lib/visitors.d.ts +2 -2
  25. package/lib/walk.d.ts +1 -0
  26. package/lib/walk.js +1 -1
  27. package/package.json +1 -1
  28. package/src/__tests__/lint.test.ts +40 -6
  29. package/src/bundle.ts +1 -1
  30. package/src/config/all.ts +1 -0
  31. package/src/config/config.ts +15 -12
  32. package/src/config/minimal.ts +1 -0
  33. package/src/config/recommended.ts +1 -0
  34. package/src/config/rules.ts +11 -2
  35. package/src/lint.ts +3 -3
  36. package/src/redocly/index.ts +49 -30
  37. package/src/redocly/registry-api.ts +38 -21
  38. package/src/ref-utils.ts +1 -1
  39. package/src/rules/common/assertions/__tests__/asserts.test.ts +231 -0
  40. package/src/rules/common/assertions/__tests__/index.test.ts +65 -0
  41. package/src/rules/common/assertions/__tests__/utils.test.ts +89 -0
  42. package/src/rules/common/assertions/asserts.ts +137 -0
  43. package/src/rules/common/assertions/index.ts +75 -0
  44. package/src/rules/common/assertions/utils.ts +164 -0
  45. package/src/rules/oas2/index.ts +2 -0
  46. package/src/rules/oas3/index.ts +2 -0
  47. package/src/types/redocly-yaml.ts +30 -11
  48. package/src/visitors.ts +2 -2
  49. package/src/walk.ts +2 -1
  50. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = void 0;
4
+ const asserts_1 = require("./asserts");
5
+ function buildVisitorObject(subject, context, subjectVisitor) {
6
+ if (!context) {
7
+ return { [subject]: subjectVisitor };
8
+ }
9
+ let currentVisitorLevel = {};
10
+ const visitor = currentVisitorLevel;
11
+ for (let index = 0; index < context.length; index++) {
12
+ const node = context[index];
13
+ if (context.length === index + 1 && node.type === subject) {
14
+ // Visitors don't work properly for the same type nested nodes, so
15
+ // as a workaround for that we don't create separate visitor for the last element
16
+ // which is the same as subject;
17
+ // we will check includes/excludes it in the last visitor.
18
+ continue;
19
+ }
20
+ const matchParentKeys = node.matchParentKeys;
21
+ const excludeParentKeys = node.excludeParentKeys;
22
+ if (matchParentKeys && excludeParentKeys) {
23
+ throw new Error(`Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`);
24
+ }
25
+ if (matchParentKeys || excludeParentKeys) {
26
+ currentVisitorLevel[node.type] = {
27
+ skip: (_value, key) => {
28
+ if (matchParentKeys) {
29
+ return !matchParentKeys.includes(key);
30
+ }
31
+ if (excludeParentKeys) {
32
+ return excludeParentKeys.includes(key);
33
+ }
34
+ },
35
+ };
36
+ }
37
+ else {
38
+ currentVisitorLevel[node.type] = {};
39
+ }
40
+ currentVisitorLevel = currentVisitorLevel[node.type];
41
+ }
42
+ currentVisitorLevel[subject] = subjectVisitor;
43
+ return visitor;
44
+ }
45
+ exports.buildVisitorObject = buildVisitorObject;
46
+ function buildSubjectVisitor(properties, asserts, context) {
47
+ return function (node, { report, location, key, type }) {
48
+ // We need to check context's last node if it has the same type as subject node;
49
+ // if yes - that means we didn't create context's last node visitor,
50
+ // so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
51
+ if (context) {
52
+ const lastContextNode = context[context.length - 1];
53
+ if (lastContextNode.type === type.name) {
54
+ const matchParentKeys = lastContextNode.matchParentKeys;
55
+ const excludeParentKeys = lastContextNode.excludeParentKeys;
56
+ if (matchParentKeys && !matchParentKeys.includes(key)) {
57
+ return;
58
+ }
59
+ if (excludeParentKeys && excludeParentKeys.includes(key)) {
60
+ return;
61
+ }
62
+ }
63
+ }
64
+ if (properties) {
65
+ properties = Array.isArray(properties) ? properties : [properties];
66
+ }
67
+ for (const assert of asserts) {
68
+ if (properties) {
69
+ for (const property of properties) {
70
+ runAssertion(node[property], assert, location.child(property), report);
71
+ }
72
+ }
73
+ else {
74
+ runAssertion(Object.keys(node), assert, location.key(), report);
75
+ }
76
+ }
77
+ };
78
+ }
79
+ exports.buildSubjectVisitor = buildSubjectVisitor;
80
+ function getIntersectionLength(keys, properties) {
81
+ const props = new Set(properties);
82
+ let count = 0;
83
+ for (const key of keys) {
84
+ if (props.has(key)) {
85
+ count++;
86
+ }
87
+ }
88
+ return count;
89
+ }
90
+ exports.getIntersectionLength = getIntersectionLength;
91
+ function isOrdered(value, options) {
92
+ const direction = options.direction || options;
93
+ const property = options.property;
94
+ for (let i = 1; i < value.length; i++) {
95
+ let currValue = value[i];
96
+ let prevVal = value[i - 1];
97
+ if (property) {
98
+ if (!value[i][property] || !value[i - 1][property]) {
99
+ return false; // property doesn't exist, so collection is not ordered
100
+ }
101
+ currValue = value[i][property];
102
+ prevVal = value[i - 1][property];
103
+ }
104
+ const result = direction === 'asc' ? currValue >= prevVal : currValue <= prevVal;
105
+ if (!result) {
106
+ return false;
107
+ }
108
+ }
109
+ return true;
110
+ }
111
+ exports.isOrdered = isOrdered;
112
+ function runAssertion(values, assert, location, report) {
113
+ const lintResult = asserts_1.asserts[assert.name](values, assert.conditions);
114
+ if (!lintResult) {
115
+ report({
116
+ message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
117
+ location,
118
+ forceSeverity: assert.severity,
119
+ suggest: assert.suggest,
120
+ ruleId: assert.assertId,
121
+ });
122
+ }
123
+ }
@@ -15,6 +15,7 @@ export declare const rules: {
15
15
  'no-path-trailing-slash': Oas2Rule;
16
16
  'operation-2xx-response': Oas2Rule;
17
17
  'operation-4xx-response': Oas2Rule;
18
+ assertions: Oas2Rule;
18
19
  'operation-operationId-unique': Oas2Rule;
19
20
  'operation-parameters-unique': Oas2Rule;
20
21
  'path-parameters-defined': Oas2Rule;
@@ -16,6 +16,7 @@ const no_enum_type_mismatch_1 = require("../common/no-enum-type-mismatch");
16
16
  const no_path_trailing_slash_1 = require("../common/no-path-trailing-slash");
17
17
  const operation_2xx_response_1 = require("../common/operation-2xx-response");
18
18
  const operation_4xx_response_1 = require("../common/operation-4xx-response");
19
+ const assertions_1 = require("../common/assertions");
19
20
  const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
20
21
  const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
21
22
  const path_params_defined_1 = require("../common/path-params-defined");
@@ -54,6 +55,7 @@ exports.rules = {
54
55
  'no-path-trailing-slash': no_path_trailing_slash_1.NoPathTrailingSlash,
55
56
  'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
56
57
  'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
58
+ 'assertions': assertions_1.Assertions,
57
59
  'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
58
60
  'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
59
61
  'path-parameters-defined': path_params_defined_1.PathParamsDefined,
@@ -4,6 +4,7 @@ exports.preprocessors = exports.rules = void 0;
4
4
  const spec_1 = require("../common/spec");
5
5
  const operation_2xx_response_1 = require("../common/operation-2xx-response");
6
6
  const operation_4xx_response_1 = require("../common/operation-4xx-response");
7
+ const assertions_1 = require("../common/assertions");
7
8
  const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
8
9
  const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
9
10
  const path_params_defined_1 = require("../common/path-params-defined");
@@ -54,6 +55,7 @@ exports.rules = {
54
55
  'info-license-url': license_url_1.InfoLicenseUrl,
55
56
  'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
56
57
  'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
58
+ 'assertions': assertions_1.Assertions,
57
59
  'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
58
60
  'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
59
61
  'path-parameters-defined': path_params_defined_1.PathParamsDefined,
@@ -5,16 +5,24 @@ const _1 = require(".");
5
5
  const utils_1 = require("../utils");
6
6
  const ConfigRoot = {
7
7
  properties: {
8
- apiDefinitions: {
9
- type: 'object',
10
- properties: {},
11
- additionalProperties: { properties: { type: 'string' } },
12
- },
13
- lint: 'ConfigLint',
14
- referenceDocs: 'ConfigReferenceDocs',
8
+ organization: { type: 'string' },
9
+ apis: 'ConfigApis',
10
+ lint: 'RootConfigLint',
11
+ 'features.openapi': 'ConfigReferenceDocs',
15
12
  'features.mockServer': 'ConfigMockServer',
16
13
  },
17
14
  };
15
+ const ConfigApis = {
16
+ properties: {},
17
+ additionalProperties: 'ConfigApisProperties',
18
+ };
19
+ const ConfigApisProperties = {
20
+ properties: {
21
+ root: { type: 'string' },
22
+ lint: 'ConfigLint',
23
+ 'features.openapi': 'ConfigReferenceDocs',
24
+ },
25
+ };
18
26
  const ConfigHTTP = {
19
27
  properties: {
20
28
  headers: {
@@ -27,10 +35,6 @@ const ConfigHTTP = {
27
35
  };
28
36
  const ConfigLint = {
29
37
  properties: {
30
- plugins: {
31
- type: 'array',
32
- items: { type: 'string' },
33
- },
34
38
  extends: {
35
39
  type: 'array',
36
40
  items: {
@@ -57,6 +61,12 @@ const ConfigLint = {
57
61
  },
58
62
  },
59
63
  };
64
+ const RootConfigLint = {
65
+ properties: Object.assign({ plugins: {
66
+ type: 'array',
67
+ items: { type: 'string' },
68
+ } }, ConfigLint.properties),
69
+ };
60
70
  const ConfigLanguage = {
61
71
  properties: {
62
72
  label: { type: 'string' },
@@ -454,6 +464,9 @@ const ConfigMockServer = {
454
464
  };
455
465
  exports.ConfigTypes = {
456
466
  ConfigRoot,
467
+ ConfigApis,
468
+ ConfigApisProperties,
469
+ RootConfigLint,
457
470
  ConfigLint,
458
471
  ConfigReferenceDocs,
459
472
  ConfigMockServer,
package/lib/visitors.d.ts CHANGED
@@ -156,8 +156,8 @@ export declare type NormalizedOasVisitors<T extends BaseVisitor> = {
156
156
  leave: Array<VisitorNode<any>>;
157
157
  };
158
158
  };
159
- export declare type Oas3Rule = (options: Record<string, any>) => Oas3Visitor;
160
- export declare type Oas2Rule = (options: Record<string, any>) => Oas2Visitor;
159
+ export declare type Oas3Rule = (options: Record<string, any>) => Oas3Visitor | Oas3Visitor[];
160
+ export declare type Oas2Rule = (options: Record<string, any>) => Oas2Visitor | Oas2Visitor[];
161
161
  export declare type Oas3Preprocessor = (options: Record<string, any>) => Oas3TransformVisitor;
162
162
  export declare type Oas2Preprocessor = (options: Record<string, any>) => Oas2TransformVisitor;
163
163
  export declare type Oas3Decorator = (options: Record<string, any>) => Oas3TransformVisitor;
package/lib/walk.d.ts CHANGED
@@ -60,6 +60,7 @@ export declare type Problem = {
60
60
  location?: Partial<LocationObject> | Array<Partial<LocationObject>>;
61
61
  from?: LocationObject;
62
62
  forceSeverity?: ProblemSeverity;
63
+ ruleId?: string;
63
64
  };
64
65
  export declare type NormalizedProblem = {
65
66
  message: string;
package/lib/walk.js CHANGED
@@ -240,7 +240,7 @@ function walkDocument(opts) {
240
240
  ? opts.location
241
241
  : [opts.location]
242
242
  : [Object.assign(Object.assign({}, currentLocation), { reportOnKey: false })];
243
- ctx.problems.push(Object.assign(Object.assign({ ruleId, severity: opts.forceSeverity || severity }, opts), { suggest: opts.suggest || [], location: loc.map((loc) => {
243
+ ctx.problems.push(Object.assign(Object.assign({ ruleId: opts.ruleId || ruleId, severity: opts.forceSeverity || severity }, opts), { suggest: opts.suggest || [], location: loc.map((loc) => {
244
244
  return Object.assign(Object.assign(Object.assign({}, currentLocation), { reportOnKey: false }), loc);
245
245
  }) }));
246
246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.89",
3
+ "version": "1.0.0-beta.92",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -46,7 +46,7 @@ describe('lint', () => {
46
46
  it('lintConfig should work', async () => {
47
47
  const document = parseYamlToDocument(
48
48
  outdent`
49
- apiDefinitions: error string
49
+ apis: error string
50
50
  lint:
51
51
  plugins:
52
52
  - './local-plugin.js'
@@ -58,7 +58,7 @@ describe('lint', () => {
58
58
  no-invalid-media-type-examples: error
59
59
  path-http-verbs-order: error
60
60
  boolean-parameter-prefixes: off
61
- referenceDocs:
61
+ features.openapi:
62
62
  showConsole: true
63
63
  layout:
64
64
  scope: section
@@ -78,12 +78,12 @@ describe('lint', () => {
78
78
  Object {
79
79
  "location": Array [
80
80
  Object {
81
- "pointer": "#/apiDefinitions",
81
+ "pointer": "#/apis",
82
82
  "reportOnKey": false,
83
83
  "source": "",
84
84
  },
85
85
  ],
86
- "message": "Expected type \`object\` but got \`string\`.",
86
+ "message": "Expected type \`ConfigApis\` (object) but got \`string\`",
87
87
  "ruleId": "spec",
88
88
  "severity": "error",
89
89
  "suggest": Array [],
@@ -91,7 +91,7 @@ describe('lint', () => {
91
91
  Object {
92
92
  "location": Array [
93
93
  Object {
94
- "pointer": "#/referenceDocs/layout",
94
+ "pointer": "#/features.openapi/layout",
95
95
  "reportOnKey": false,
96
96
  "source": "",
97
97
  },
@@ -105,6 +105,40 @@ describe('lint', () => {
105
105
  `);
106
106
  });
107
107
 
108
+ it("'plugins' shouldn't be allowed in 'apis' -> 'lint' field", async () => {
109
+ const document = parseYamlToDocument(
110
+ outdent`
111
+ apis:
112
+ lint:
113
+ plugins:
114
+ - './local-plugin.js'
115
+ lint:
116
+ plugins:
117
+ - './local-plugin.js'
118
+ `,
119
+ '',
120
+ );
121
+ const results = await lintConfig({ document });
122
+
123
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
124
+ Array [
125
+ Object {
126
+ "location": Array [
127
+ Object {
128
+ "pointer": "#/apis/lint/plugins",
129
+ "reportOnKey": true,
130
+ "source": "",
131
+ },
132
+ ],
133
+ "message": "Property \`plugins\` is not expected here.",
134
+ "ruleId": "spec",
135
+ "severity": "error",
136
+ "suggest": Array [],
137
+ },
138
+ ]
139
+ `);
140
+ });
141
+
108
142
  it("'const' can have any type", async () => {
109
143
  const document = parseYamlToDocument(
110
144
  outdent`
@@ -140,7 +174,7 @@ describe('lint', () => {
140
174
  const results = await lintDocument({
141
175
  externalRefResolver: new BaseResolver(),
142
176
  document,
143
- config: makeConfig({ spec: 'error', }),
177
+ config: makeConfig({ spec: 'error' }),
144
178
  });
145
179
 
146
180
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
package/src/bundle.ts CHANGED
@@ -130,7 +130,7 @@ export async function bundleDocument(opts: {
130
130
  visitor: makeBundleVisitor(oasMajorVersion, dereference, skipRedoclyRegistryRefs, document, resolvedRefMap),
131
131
  },
132
132
  ...decorators,
133
- ],
133
+ ] as any,
134
134
  types,
135
135
  );
136
136
 
package/src/config/all.ts CHANGED
@@ -19,6 +19,7 @@ export default {
19
19
  'operation-description': 'error',
20
20
  'operation-2xx-response': 'error',
21
21
  'operation-4xx-response': 'error',
22
+ 'assertions': 'error',
22
23
  'operation-operationId': 'error',
23
24
  'operation-summary': 'error',
24
25
  'operation-operationId-unique': 'error',
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { dirname } from 'path';
4
- import { red, blue } from 'colorette';
4
+ import { red, blue, yellow, green } from 'colorette';
5
5
  import { parseYaml, stringifyYaml } from '../js-yaml';
6
6
  import { notUndefined, slash } from '../utils';
7
7
  import {
@@ -695,17 +695,20 @@ export function transformConfig(rawConfig: DeprecatedRawConfig | RawConfig): Raw
695
695
  throw new Error("Do not use 'referenceDocs' field. Use 'features.openapi' instead.\n");
696
696
  }
697
697
  const { apiDefinitions, referenceDocs, ...rest } = rawConfig as DeprecatedRawConfig & RawConfig;
698
- // TODO: put links to the changelog and uncomment this after successful release of ReferenceDocs/Redoc, Portal and Workflows
699
- // if (apiDefinitions) {
700
- // process.stderr.write(
701
- // `The ${yellow('apiDefinitions')} field is deprecated. Use ${green('apis')} instead, see changelog: <link>\n`
702
- // );
703
- // }
704
- // if (referenceDocs) {
705
- // process.stderr.write(
706
- // `The ${yellow('referenceDocs')} field is deprecated. Use ${green('features.openapi')} instead, see changelog: <link>\n`
707
- // );
708
- // }
698
+ if (apiDefinitions) {
699
+ process.stderr.write(
700
+ `The ${yellow('apiDefinitions')} field is deprecated. Use ${green(
701
+ 'apis',
702
+ )} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`,
703
+ );
704
+ }
705
+ if (referenceDocs) {
706
+ process.stderr.write(
707
+ `The ${yellow('referenceDocs')} field is deprecated. Use ${green(
708
+ 'features.openapi',
709
+ )} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`,
710
+ );
711
+ }
709
712
  return {
710
713
  'features.openapi': referenceDocs,
711
714
  apis: transformApiDefinitionsToApis(apiDefinitions),
@@ -18,6 +18,7 @@ export default {
18
18
  'operation-description': 'off',
19
19
  'operation-2xx-response': 'warn',
20
20
  'operation-4xx-response': 'off',
21
+ 'assertions': 'warn',
21
22
  'operation-operationId': 'warn',
22
23
  'operation-summary': 'warn',
23
24
  'operation-operationId-unique': 'warn',
@@ -17,6 +17,7 @@ export default {
17
17
  'path-parameters-defined': 'error',
18
18
  'operation-description': 'off',
19
19
  'operation-2xx-response': 'warn',
20
+ 'assertions': 'warn',
20
21
  'operation-4xx-response': 'warn',
21
22
  'operation-operationId': 'warn',
22
23
  'operation-summary': 'error',
@@ -24,14 +24,23 @@ export function initRules<T extends Function, P extends RuleSet<T>>(
24
24
  return undefined;
25
25
  }
26
26
 
27
- const visitor = rule(ruleSettings);
27
+ const visitors = rule(ruleSettings);
28
+
29
+ if (Array.isArray(visitors)) {
30
+ return visitors.map((visitor: any) => ({
31
+ severity: ruleSettings.severity,
32
+ ruleId,
33
+ visitor: visitor,
34
+ }))
35
+ }
28
36
 
29
37
  return {
30
38
  severity: ruleSettings.severity,
31
39
  ruleId,
32
- visitor,
40
+ visitor: visitors, // note: actually it is only one visitor object
33
41
  };
34
42
  }),
35
43
  )
44
+ .flatMap(visitor => visitor)
36
45
  .filter(notUndefined);
37
46
  }
package/src/lint.ts CHANGED
@@ -11,7 +11,7 @@ import { LintConfig, Config } from './config/config';
11
11
  import { normalizeTypes } from './types';
12
12
  import { initRules } from './config/rules';
13
13
  import { releaseAjvInstance } from './rules/ajv';
14
- import { detectOpenAPI, OasMajorVersion, OasVersion, openAPIMajor } from './oas-types';
14
+ import { detectOpenAPI, Oas3RuleSet, OasMajorVersion, OasVersion, openAPIMajor } from './oas-types';
15
15
  import { ConfigTypes } from './types/redocly-yaml';
16
16
  import { OasSpec } from './rules/common/spec';
17
17
  import { defaultPlugin } from './config/builtIn';
@@ -76,8 +76,8 @@ export async function lintDocument(opts: {
76
76
  };
77
77
 
78
78
  const preprocessors = initRules(rules as any, config, 'preprocessors', oasVersion);
79
- const regularRules = initRules(rules as any, config, 'rules', oasVersion);
80
- const normalizedVisitors = normalizeVisitors([...preprocessors, ...regularRules], types);
79
+ const regularRules = initRules(rules as Oas3RuleSet[], config, 'rules', oasVersion);
80
+ const normalizedVisitors = normalizeVisitors([...preprocessors, ...regularRules] as any, types);
81
81
  const resolvedRefMap = await resolveDocument({
82
82
  rootDocument: document,
83
83
  rootType: types.DefinitionRoot,
@@ -19,9 +19,7 @@ export class RedoclyClient {
19
19
  constructor(region?: Region) {
20
20
  this.region = this.loadRegion(region);
21
21
  this.loadTokens();
22
- this.domain = region
23
- ? DOMAINS[region]
24
- : process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
22
+ this.domain = region ? DOMAINS[region] : process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
25
23
 
26
24
  /*
27
25
  * We can't use process.env here because it is replaced by a const in some client-side bundles,
@@ -34,7 +32,11 @@ export class RedoclyClient {
34
32
  loadRegion(region?: Region) {
35
33
  if (region && !DOMAINS[region]) {
36
34
  process.stdout.write(
37
- red(`Invalid argument: region in config file.\nGiven: ${green(region)}, choices: "us", "eu".\n`),
35
+ red(
36
+ `Invalid argument: region in config file.\nGiven: ${green(
37
+ region,
38
+ )}, choices: "us", "eu".\n`,
39
+ ),
38
40
  );
39
41
  process.exit(1);
40
42
  }
@@ -86,37 +88,36 @@ export class RedoclyClient {
86
88
  if (isNotEmptyObject(credentials)) {
87
89
  this.setAccessTokens({
88
90
  ...credentials,
89
- ...(credentials.token && !credentials[this.region] && {
90
- [this.region]: credentials.token
91
- })
92
- })
91
+ ...(credentials.token &&
92
+ !credentials[this.region] && {
93
+ [this.region]: credentials.token,
94
+ }),
95
+ });
93
96
  }
94
97
  if (process.env.REDOCLY_AUTHORIZATION) {
95
98
  this.setAccessTokens({
96
99
  ...this.accessTokens,
97
- [this.region]: process.env.REDOCLY_AUTHORIZATION
98
- })
100
+ [this.region]: process.env.REDOCLY_AUTHORIZATION,
101
+ });
99
102
  }
100
103
  }
101
104
 
102
- getAllTokens (): RegionalToken[] {
103
- return (<[Region, string][]>Object.entries(this.accessTokens)).filter(
104
- ([region]) => AVAILABLE_REGIONS.includes(region)
105
- ).map(
106
- ([region, token]) => ({ region, token })
107
- );
105
+ getAllTokens(): RegionalToken[] {
106
+ return (<[Region, string][]>Object.entries(this.accessTokens))
107
+ .filter(([region]) => AVAILABLE_REGIONS.includes(region))
108
+ .map(([region, token]) => ({ region, token }));
108
109
  }
109
110
 
110
111
  async getValidTokens(): Promise<RegionalTokenWithValidity[]> {
111
- const validTokens = <RegionalTokenWithValidity[]>[];
112
+ const allTokens = this.getAllTokens();
112
113
 
113
- for (const { token, region } of this.getAllTokens()) {
114
- if (await this.verifyToken(token, region)) {
115
- validTokens.push({ token, region, valid: true });
116
- }
117
- }
118
-
119
- return validTokens;
114
+ const verifiedTokens = await Promise.allSettled(
115
+ allTokens.map(({ token, region }) => this.verifyToken(token, region)),
116
+ );
117
+
118
+ return allTokens
119
+ .filter((_, index) => verifiedTokens[index].status === 'fulfilled')
120
+ .map(({ token, region }) => ({ token, region, valid: true }));
120
121
  }
121
122
 
122
123
  async getTokens() {
@@ -124,9 +125,23 @@ export class RedoclyClient {
124
125
  }
125
126
 
126
127
  async isAuthorizedWithRedoclyByRegion(): Promise<boolean> {
127
- if (!this.hasTokens()) return false;
128
+ if (!this.hasTokens()) {
129
+ return false;
130
+ }
131
+
128
132
  const accessToken = this.accessTokens[this.region];
129
- return !!accessToken && await this.verifyToken(accessToken, this.region);
133
+
134
+ if (!accessToken) {
135
+ return false;
136
+ }
137
+
138
+ try {
139
+ await this.verifyToken(accessToken, this.region);
140
+
141
+ return true;
142
+ } catch (err) {
143
+ return false;
144
+ }
130
145
  }
131
146
 
132
147
  async isAuthorizedWithRedocly(): Promise<boolean> {
@@ -137,8 +152,11 @@ export class RedoclyClient {
137
152
  return existsSync(credentialsPath) ? JSON.parse(readFileSync(credentialsPath, 'utf-8')) : {};
138
153
  }
139
154
 
140
- async verifyToken(accessToken: string, region: Region, verbose: boolean = false): Promise<boolean> {
141
- if (!accessToken) return false;
155
+ async verifyToken(
156
+ accessToken: string,
157
+ region: Region,
158
+ verbose: boolean = false,
159
+ ): Promise<{ viewerId: string; organizations: string[] }> {
142
160
  return this.registryApi.authStatus(accessToken, region, verbose);
143
161
  }
144
162
 
@@ -146,8 +164,9 @@ export class RedoclyClient {
146
164
  const credentialsPath = resolve(homedir(), TOKEN_FILENAME);
147
165
  process.stdout.write(gray('\n Logging in...\n'));
148
166
 
149
- const authorized = await this.verifyToken(accessToken, this.region, verbose);
150
- if (!authorized) {
167
+ try {
168
+ await this.verifyToken(accessToken, this.region, verbose);
169
+ } catch (err) {
151
170
  process.stdout.write(
152
171
  red('Authorization failed. Please check if you entered a valid API key.\n'),
153
172
  );