@redocly/openapi-core 1.0.0-beta.95 → 1.0.0-beta.98

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 (40) hide show
  1. package/README.md +1 -1
  2. package/lib/config/config-resolvers.d.ts +1 -1
  3. package/lib/config/config-resolvers.js +31 -2
  4. package/lib/config/config.d.ts +4 -1
  5. package/lib/config/config.js +18 -12
  6. package/lib/format/format.js +2 -1
  7. package/lib/redocly/index.js +10 -26
  8. package/lib/redocly/registry-api-types.d.ts +1 -0
  9. package/lib/redocly/registry-api.d.ts +1 -1
  10. package/lib/redocly/registry-api.js +2 -1
  11. package/lib/rules/common/assertions/index.js +1 -1
  12. package/lib/types/oas2.js +3 -1
  13. package/lib/types/oas3.js +4 -2
  14. package/lib/types/oas3_1.js +3 -1
  15. package/lib/types/redocly-yaml.js +2 -0
  16. package/lib/typings/openapi.d.ts +2 -0
  17. package/lib/typings/swagger.d.ts +2 -0
  18. package/lib/utils.d.ts +0 -1
  19. package/lib/utils.js +3 -3
  20. package/package.json +5 -5
  21. package/src/benchmark/benchmark.js +1 -1
  22. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +18 -1
  23. package/src/config/__tests__/config-resolvers.test.ts +31 -0
  24. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +5 -0
  25. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +7 -0
  26. package/src/config/config-resolvers.ts +58 -3
  27. package/src/config/config.ts +21 -12
  28. package/src/format/format.ts +2 -1
  29. package/src/redocly/index.ts +12 -41
  30. package/src/redocly/registry-api-types.ts +1 -0
  31. package/src/redocly/registry-api.ts +2 -0
  32. package/src/rules/common/assertions/index.ts +1 -1
  33. package/src/types/oas2.ts +2 -0
  34. package/src/types/oas3.ts +3 -1
  35. package/src/types/oas3_1.ts +2 -0
  36. package/src/types/redocly-yaml.ts +2 -0
  37. package/src/typings/openapi.ts +2 -0
  38. package/src/typings/swagger.ts +2 -0
  39. package/src/utils.ts +3 -2
  40. package/tsconfig.tsbuildinfo +1 -1
@@ -11,7 +11,14 @@ import {
11
11
  prefixRules,
12
12
  transformConfig,
13
13
  } from './utils';
14
- import type { LintRawConfig, Plugin, RawConfig, ResolvedApi, ResolvedLintConfig } from './types';
14
+ import type {
15
+ LintRawConfig,
16
+ Plugin,
17
+ RawConfig,
18
+ ResolvedApi,
19
+ ResolvedLintConfig,
20
+ RuleConfig,
21
+ } from './types';
15
22
  import { isNotString, isString, notUndefined, parseYaml } from '../utils';
16
23
  import { Config } from './config';
17
24
 
@@ -175,7 +182,7 @@ export async function resolveApis({
175
182
  return resolvedApis;
176
183
  }
177
184
 
178
- export async function resolveLint(
185
+ async function resolveAndMergeNestedLint(
179
186
  {
180
187
  lintConfig,
181
188
  configPath = '',
@@ -213,7 +220,7 @@ export async function resolveLint(
213
220
  ? new URL(presetItem, configPath).href
214
221
  : path.resolve(path.dirname(configPath), presetItem);
215
222
  const extendedLintConfig = await loadExtendLintConfig(pathItem, resolver);
216
- return await resolveLint(
223
+ return await resolveAndMergeNestedLint(
217
224
  {
218
225
  lintConfig: extendedLintConfig,
219
226
  configPath: pathItem,
@@ -245,6 +252,23 @@ export async function resolveLint(
245
252
  };
246
253
  }
247
254
 
255
+ export async function resolveLint(
256
+ lintOpts: {
257
+ lintConfig?: LintRawConfig;
258
+ configPath?: string;
259
+ resolver?: BaseResolver;
260
+ },
261
+ parentConfigPaths: string[] = [],
262
+ extendPaths: string[] = [],
263
+ ): Promise<ResolvedLintConfig> {
264
+ const resolvedLint = await resolveAndMergeNestedLint(lintOpts, parentConfigPaths, extendPaths);
265
+
266
+ return {
267
+ ...resolvedLint,
268
+ rules: resolvedLint.rules && groupLintAssertionRules(resolvedLint.rules),
269
+ };
270
+ }
271
+
248
272
  export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedLintConfig {
249
273
  const { pluginId, configName } = parsePresetName(presetName);
250
274
  const plugin = plugins.find((p) => p.id === pluginId);
@@ -302,3 +326,34 @@ function getMergedLintRawConfig(configLint: LintRawConfig, apiLint?: LintRawConf
302
326
  };
303
327
  return resultLint;
304
328
  }
329
+
330
+ function groupLintAssertionRules(
331
+ rules: Record<string, RuleConfig> | undefined,
332
+ ): Record<string, RuleConfig> | undefined {
333
+ if (!rules) {
334
+ return rules;
335
+ }
336
+
337
+ // Create a new record to avoid mutating original
338
+ const transformedRules: Record<string, RuleConfig> = {};
339
+
340
+ // Collect assertion rules
341
+ const assertions = [];
342
+ for (const [ruleKey, rule] of Object.entries(rules)) {
343
+ if (ruleKey.startsWith('assert/') && typeof rule === 'object' && rule !== null) {
344
+ const assertion = rule;
345
+ assertions.push({
346
+ ...assertion,
347
+ assertionId: ruleKey.replace('assert/', ''),
348
+ });
349
+ } else {
350
+ // If it's not an assertion, keep it as is
351
+ transformedRules[ruleKey] = rule;
352
+ }
353
+ }
354
+ if (assertions.length > 0) {
355
+ transformedRules.assertions = assertions;
356
+ }
357
+
358
+ return transformedRules;
359
+ }
@@ -19,25 +19,34 @@ import type {
19
19
  } from './types';
20
20
  import { getResolveConfig } from './utils';
21
21
 
22
+ // Alias environment here so this file can work in browser environments too.
23
+ export const env = typeof process !== 'undefined' ? process.env || {} : {};
24
+
22
25
  export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
23
26
  const IGNORE_BANNER =
24
27
  `# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
25
28
  `# See https://redoc.ly/docs/cli/ for more information.\n`;
26
29
 
27
30
  export const DEFAULT_REGION = 'us';
28
- const REDOCLY_DOMAIN = process.env.REDOCLY_DOMAIN;
29
- export const DOMAINS: { [region in Region]: string } = {
30
- us: 'redocly.com',
31
- eu: 'eu.redocly.com',
32
- };
33
-
34
- // FIXME: temporary fix for our lab environments
35
- if (REDOCLY_DOMAIN?.endsWith('.redocly.host')) {
36
- DOMAINS[REDOCLY_DOMAIN.split('.')[0] as Region] = REDOCLY_DOMAIN;
37
- }
38
- if (REDOCLY_DOMAIN === 'redoc.online') {
39
- DOMAINS[REDOCLY_DOMAIN as Region] = REDOCLY_DOMAIN;
31
+
32
+ function getDomains() {
33
+ const domains: { [region in Region]: string } = {
34
+ us: 'redocly.com',
35
+ eu: 'eu.redocly.com',
36
+ };
37
+
38
+ // FIXME: temporary fix for our lab environments
39
+ const domain = env.REDOCLY_DOMAIN;
40
+ if (domain?.endsWith('.redocly.host')) {
41
+ domains[domain.split('.')[0] as Region] = domain;
42
+ }
43
+ if (domain === 'redoc.online') {
44
+ domains[domain as Region] = domain;
45
+ }
46
+ return domains;
40
47
  }
48
+
49
+ export const DOMAINS = getDomains();
41
50
  export const AVAILABLE_REGIONS = Object.keys(DOMAINS) as Region[];
42
51
 
43
52
  export class LintConfig {
@@ -14,6 +14,7 @@ const coreVersion = require('../../package.json').version;
14
14
 
15
15
  import { NormalizedProblem, ProblemSeverity, LineColLocationObject, LocationObject } from '../walk';
16
16
  import { getCodeframe, getLineColLocation } from './codeframes';
17
+ import { env } from "../config";
17
18
 
18
19
  export type Totals = {
19
20
  errors: number;
@@ -202,7 +203,7 @@ export function formatProblems(
202
203
  : undefined,
203
204
  };
204
205
 
205
- if (process.env.FORMAT_JSON_WITH_CODEFRAMES) {
206
+ if (env.FORMAT_JSON_WITH_CODEFRAMES) {
206
207
  const location = p.location[0]; // TODO: support multiple locations
207
208
  const loc = getLineColLocation(location);
208
209
  (problem as any).codeframe = getCodeframe(loc, color);
@@ -1,9 +1,9 @@
1
1
  import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
2
2
  import { resolve } from 'path';
3
3
  import { homedir } from 'os';
4
- import { red, green, gray, yellow } from 'colorette';
4
+ import { green } from 'colorette';
5
5
  import { RegistryApi } from './registry-api';
6
- import { DEFAULT_REGION, DOMAINS, AVAILABLE_REGIONS } from '../config/config';
6
+ import { DEFAULT_REGION, DOMAINS, AVAILABLE_REGIONS, env } from '../config/config';
7
7
  import { RegionalToken, RegionalTokenWithValidity } from './redocly-client-types';
8
8
  import { isNotEmptyObject } from '../utils';
9
9
 
@@ -11,7 +11,6 @@ import type { AccessTokens, Region } from '../config/types';
11
11
 
12
12
  const TOKEN_FILENAME = '.redocly-config.json';
13
13
 
14
- let REDOCLY_DOMAIN: string; // workaround for the isRedoclyRegistryURL, see more below
15
14
  export class RedoclyClient {
16
15
  private accessTokens: AccessTokens = {};
17
16
  private region: Region;
@@ -21,31 +20,20 @@ export class RedoclyClient {
21
20
  constructor(region?: Region) {
22
21
  this.region = this.loadRegion(region);
23
22
  this.loadTokens();
24
- this.domain = region ? DOMAINS[region] : process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
23
+ this.domain = region ? DOMAINS[region] : env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
25
24
 
26
- /*
27
- * We can't use process.env here because it is replaced by a const in some client-side bundles,
28
- * which breaks assignment.
29
- */
30
- REDOCLY_DOMAIN = this.domain; // isRedoclyRegistryURL depends on the value to be set
25
+ env.REDOCLY_DOMAIN = this.domain; // isRedoclyRegistryURL depends on the value to be set
31
26
  this.registryApi = new RegistryApi(this.accessTokens, this.region);
32
27
  }
33
28
 
34
29
  loadRegion(region?: Region) {
35
30
  if (region && !DOMAINS[region]) {
36
- process.stdout.write(
37
- red(
38
- `Invalid argument: region in config file.\nGiven: ${green(
39
- region,
40
- )}, choices: "us", "eu".\n`,
41
- ),
42
- );
43
- process.exit(1);
31
+ throw new Error(`Invalid argument: region in config file.\nGiven: ${green(region)}, choices: "us", "eu".`);
44
32
  }
45
33
 
46
- if (process.env.REDOCLY_DOMAIN) {
34
+ if (env.REDOCLY_DOMAIN) {
47
35
  return (AVAILABLE_REGIONS.find(
48
- (region) => DOMAINS[region as Region] === process.env.REDOCLY_DOMAIN,
36
+ (region) => DOMAINS[region as Region] === env.REDOCLY_DOMAIN,
49
37
  ) || DEFAULT_REGION) as Region;
50
38
  }
51
39
  return region || DEFAULT_REGION;
@@ -65,18 +53,7 @@ export class RedoclyClient {
65
53
  }
66
54
 
67
55
  async getAuthorizationHeader(): Promise<string | undefined> {
68
- const token = this.accessTokens[this.region];
69
- // print this only if there is token but invalid
70
- if (token && !this.isAuthorizedWithRedoclyByRegion()) {
71
- process.stderr.write(
72
- `${yellow(
73
- 'Warning:',
74
- )} invalid Redocly API key. Use "npx @redocly/openapi-cli login" to provide your API key\n`,
75
- );
76
- return undefined;
77
- }
78
-
79
- return token;
56
+ return this.accessTokens[this.region];
80
57
  }
81
58
  // </backward compatibility: portal>
82
59
 
@@ -96,10 +73,10 @@ export class RedoclyClient {
96
73
  }),
97
74
  });
98
75
  }
99
- if (process.env.REDOCLY_AUTHORIZATION) {
76
+ if (env.REDOCLY_AUTHORIZATION) {
100
77
  this.setAccessTokens({
101
78
  ...this.accessTokens,
102
- [this.region]: process.env.REDOCLY_AUTHORIZATION,
79
+ [this.region]: env.REDOCLY_AUTHORIZATION,
103
80
  });
104
81
  }
105
82
  }
@@ -164,15 +141,11 @@ export class RedoclyClient {
164
141
 
165
142
  async login(accessToken: string, verbose: boolean = false) {
166
143
  const credentialsPath = resolve(homedir(), TOKEN_FILENAME);
167
- process.stdout.write(gray('\n Logging in...\n'));
168
144
 
169
145
  try {
170
146
  await this.verifyToken(accessToken, this.region, verbose);
171
147
  } catch (err) {
172
- process.stdout.write(
173
- red('Authorization failed. Please check if you entered a valid API key.\n'),
174
- );
175
- process.exit(1);
148
+ throw new Error('Authorization failed. Please check if you entered a valid API key.');
176
149
  }
177
150
 
178
151
  const credentials = {
@@ -183,7 +156,6 @@ export class RedoclyClient {
183
156
  this.accessTokens = credentials;
184
157
  this.registryApi.setAccessTokens(credentials);
185
158
  writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2));
186
- process.stdout.write(green(' Authorization confirmed. ✅\n\n'));
187
159
  }
188
160
 
189
161
  logout(): void {
@@ -191,12 +163,11 @@ export class RedoclyClient {
191
163
  if (existsSync(credentialsPath)) {
192
164
  unlinkSync(credentialsPath);
193
165
  }
194
- process.stdout.write('Logged out from the Redocly account. ✋\n');
195
166
  }
196
167
  }
197
168
 
198
169
  export function isRedoclyRegistryURL(link: string): boolean {
199
- const domain = REDOCLY_DOMAIN || process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
170
+ const domain = env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
200
171
 
201
172
  const legacyDomain = domain === 'redocly.com' ? 'redoc.ly' : domain;
202
173
 
@@ -16,6 +16,7 @@ export namespace RegistryApiTypes {
16
16
  filePaths: string[];
17
17
  branch?: string;
18
18
  isUpsert?: boolean;
19
+ isPublic?: boolean;
19
20
  }
20
21
 
21
22
  export interface PrepareFileuploadOKResponse {
@@ -104,6 +104,7 @@ export class RegistryApi {
104
104
  filePaths,
105
105
  branch,
106
106
  isUpsert,
107
+ isPublic,
107
108
  }: RegistryApiTypes.PushApiParams) {
108
109
  const response = await this.request(
109
110
  `/${organizationId}/${name}/${version}`,
@@ -118,6 +119,7 @@ export class RegistryApi {
118
119
  filePaths,
119
120
  branch,
120
121
  isUpsert,
122
+ isPublic,
121
123
  }),
122
124
  },
123
125
  this.region,
@@ -7,7 +7,7 @@ export const Assertions: Oas3Rule | Oas2Rule = (opts: object) => {
7
7
 
8
8
  // As 'Assertions' has an array of asserts,
9
9
  // that array spreads into an 'opts' object on init rules phase here
10
- // https://github.com/Redocly/openapi-cli/blob/master/packages/core/src/config/config.ts#L311
10
+ // https://github.com/Redocly/redocly-cli/blob/master/packages/core/src/config/config.ts#L311
11
11
  // that is why we need to iterate through 'opts' values;
12
12
  // before - filter only object 'opts' values
13
13
  const assertions: any[] = Object.values(opts).filter(
package/src/types/oas2.ts CHANGED
@@ -88,6 +88,7 @@ const Operation: NodeType = {
88
88
  security: listOf('SecurityRequirement'),
89
89
  'x-codeSamples': listOf('XCodeSample'),
90
90
  'x-code-samples': listOf('XCodeSample'), // deprecated
91
+ 'x-hideTryItPanel': { type: 'boolean' },
91
92
  },
92
93
  required: ['responses'],
93
94
  };
@@ -287,6 +288,7 @@ const Schema: NodeType = {
287
288
  xml: 'Xml',
288
289
  externalDocs: 'ExternalDocs',
289
290
  example: { isExample: true },
291
+ 'x-tags': { type: 'array', items: { type: 'string' } },
290
292
  },
291
293
  };
292
294
 
package/src/types/oas3.ts CHANGED
@@ -163,6 +163,7 @@ const Operation: NodeType = {
163
163
  callbacks: mapOf('Callback'),
164
164
  'x-codeSamples': listOf('XCodeSample'),
165
165
  'x-code-samples': listOf('XCodeSample'), // deprecated
166
+ 'x-hideTryItPanel': { type: 'boolean' },
166
167
  },
167
168
  required: ['responses'],
168
169
  };
@@ -315,6 +316,7 @@ const Schema: NodeType = {
315
316
  xml: 'Xml',
316
317
  example: { isExample: true },
317
318
  deprecated: { type: 'boolean' },
319
+ 'x-tags': { type: 'array', items: { type: 'string' } },
318
320
  },
319
321
  };
320
322
 
@@ -372,7 +374,7 @@ const ImplicitFlow: NodeType = {
372
374
  scopes: { type: 'object', additionalProperties: { type: 'string' } }, // TODO: validate scopes
373
375
  authorizationUrl: { type: 'string' },
374
376
  },
375
- required: ['authorizationUrl', 'scopes']
377
+ required: ['authorizationUrl', 'scopes'],
376
378
  };
377
379
 
378
380
  const PasswordFlow: NodeType = {
@@ -74,6 +74,7 @@ const Operation: NodeType = {
74
74
  callbacks: mapOf('Callback'),
75
75
  'x-codeSamples': listOf('XCodeSample'),
76
76
  'x-code-samples': listOf('XCodeSample'), // deprecated
77
+ 'x-hideTryItPanel': { type: 'boolean' },
77
78
  },
78
79
  };
79
80
 
@@ -163,6 +164,7 @@ const Schema: NodeType = {
163
164
  deprecated: { type: 'boolean' },
164
165
  const: null,
165
166
  $comment: { type: 'string' },
167
+ 'x-tags': { type: 'array', items: { type: 'string' } },
166
168
  },
167
169
  };
168
170
 
@@ -526,6 +526,7 @@ const ConfigReferenceDocs: NodeType = {
526
526
  hideSchemaPattern: { type: 'boolean' },
527
527
  hideSchemaTitles: { type: 'boolean' },
528
528
  hideSingleRequestSampleTab: { type: 'boolean' },
529
+ hideTryItPanel: { type: 'boolean' },
529
530
  htmlTemplate: { type: 'string' },
530
531
  jsonSampleExpandLevel: { type: 'string' },
531
532
  labels: 'ConfigLabels',
@@ -545,6 +546,7 @@ const ConfigReferenceDocs: NodeType = {
545
546
  routingBasePath: { type: 'string' },
546
547
  samplesTabsMaxCount: { type: 'number' },
547
548
  schemaExpansionLevel: { type: 'string' },
549
+ schemaDefinitionsTagName: { type: 'string' },
548
550
  scrollYOffset: { type: 'string' },
549
551
  searchAutoExpand: { type: 'boolean' },
550
552
  searchFieldLevelBoost: { type: 'number' },
@@ -77,6 +77,7 @@ export interface Oas3Operation {
77
77
  servers?: Oas3Server[];
78
78
  'x-codeSamples'?: Oas3XCodeSample[];
79
79
  'x-code-samples'?: Oas3XCodeSample[]; // deprecated
80
+ 'x-hideTryItPanel'?: boolean;
80
81
  }
81
82
 
82
83
  export interface Oas3Parameter {
@@ -149,6 +150,7 @@ export interface Oas3Schema {
149
150
  example?: any;
150
151
 
151
152
  xml?: Oas3Xml;
153
+ 'x-tags'?: string[];
152
154
  }
153
155
 
154
156
  export interface Oas3_1Schema extends Oas3Schema {
@@ -73,6 +73,7 @@ export interface Oas2Operation {
73
73
  security?: Oas2SecurityRequirement[];
74
74
  'x-codeSamples'?: Oas2XCodeSample[];
75
75
  'x-code-samples'?: Oas2XCodeSample[]; // deprecated
76
+ 'x-hideTryItPanel'?: boolean;
76
77
  }
77
78
 
78
79
  export type Oas2Parameter = Oas2BodyParameter | Oas2SimpleParameter;
@@ -177,6 +178,7 @@ export interface Oas2Schema {
177
178
  example?: any;
178
179
 
179
180
  xml?: Oas2Xml;
181
+ 'x-tags'?: string[];
180
182
  }
181
183
 
182
184
  export type Oas2ParameterLocation = 'query' | 'header' | 'path' | 'formData';
package/src/utils.ts CHANGED
@@ -5,6 +5,7 @@ import * as pluralize from 'pluralize';
5
5
  import { parseYaml } from './js-yaml';
6
6
  import { UserContext } from './walk';
7
7
  import type { HttpResolveConfig } from './config';
8
+ import { env } from "./config";
8
9
 
9
10
  export { parseYaml, stringifyYaml } from './js-yaml';
10
11
 
@@ -51,7 +52,7 @@ export async function readFileFromUrl(url: string, config: HttpResolveConfig) {
51
52
  for (const header of config.headers) {
52
53
  if (match(url, header.matches)) {
53
54
  headers[header.name] =
54
- header.envVariable !== undefined ? process.env[header.envVariable] || '' : header.value;
55
+ header.envVariable !== undefined ? env[header.envVariable] || '' : header.value;
55
56
  }
56
57
  }
57
58
 
@@ -66,7 +67,7 @@ export async function readFileFromUrl(url: string, config: HttpResolveConfig) {
66
67
  return { body: await req.text(), mimeType: req.headers.get('content-type') };
67
68
  }
68
69
 
69
- export function match(url: string, pattern: string) {
70
+ function match(url: string, pattern: string) {
70
71
  if (!pattern.match(/^https?:\/\//)) {
71
72
  // if pattern doesn't specify protocol directly, do not match against it
72
73
  url = url.replace(/^https?:\/\//, '');