@redocly/cli 1.0.2 → 1.2.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.
@@ -1,7 +1,7 @@
1
1
  import { handleJoin } from '../../commands/join';
2
2
  import { exitWithError, writeYaml } from '../../utils';
3
3
  import { yellow } from 'colorette';
4
- import { detectOpenAPI } from '@redocly/openapi-core';
4
+ import { detectSpec } from '@redocly/openapi-core';
5
5
  import { loadConfig } from '../../__mocks__/@redocly/openapi-core';
6
6
  import { ConfigFixture } from '../fixtures/config';
7
7
 
@@ -50,7 +50,7 @@ describe('handleJoin fails', () => {
50
50
  );
51
51
  });
52
52
 
53
- it('should call exitWithError because Only OpenAPI 3 is supported', async () => {
53
+ it('should call exitWithError because Only OpenAPI 3.0 and OpenAPI 3.1 are supported', async () => {
54
54
  await handleJoin(
55
55
  {
56
56
  apis: ['first.yaml', 'second.yaml'],
@@ -58,11 +58,43 @@ describe('handleJoin fails', () => {
58
58
  ConfigFixture as any,
59
59
  'cli-version'
60
60
  );
61
- expect(exitWithError).toHaveBeenCalledWith('Only OpenAPI 3 is supported: undefined \n\n');
61
+ expect(exitWithError).toHaveBeenCalledWith(
62
+ 'Only OpenAPI 3.0 and OpenAPI 3.1 are supported: undefined \n\n'
63
+ );
64
+ });
65
+
66
+ it('should call exitWithError if mixing OpenAPI 3.0 and 3.1', async () => {
67
+ (detectSpec as jest.Mock)
68
+ .mockImplementationOnce(() => 'oas3_0')
69
+ .mockImplementationOnce(() => 'oas3_1');
70
+ await handleJoin(
71
+ {
72
+ apis: ['first.yaml', 'second.yaml'],
73
+ },
74
+ ConfigFixture as any,
75
+ 'cli-version'
76
+ );
77
+
78
+ expect(exitWithError).toHaveBeenCalledWith(
79
+ 'All APIs must use the same OpenAPI version: undefined \n\n'
80
+ );
62
81
  });
63
82
 
64
83
  it('should call writeYaml function', async () => {
65
- (detectOpenAPI as jest.Mock).mockReturnValue('oas3_0');
84
+ (detectSpec as jest.Mock).mockReturnValue('oas3_0');
85
+ await handleJoin(
86
+ {
87
+ apis: ['first.yaml', 'second.yaml'],
88
+ },
89
+ ConfigFixture as any,
90
+ 'cli-version'
91
+ );
92
+
93
+ expect(writeYaml).toHaveBeenCalledWith(expect.any(Object), 'openapi.yaml', expect.any(Boolean));
94
+ });
95
+
96
+ it('should call writeYaml function for OpenAPI 3.1', async () => {
97
+ (detectSpec as jest.Mock).mockReturnValue('oas3_1');
66
98
  await handleJoin(
67
99
  {
68
100
  apis: ['first.yaml', 'second.yaml'],
@@ -75,7 +107,7 @@ describe('handleJoin fails', () => {
75
107
  });
76
108
 
77
109
  it('should call writeYaml function with custom output file', async () => {
78
- (detectOpenAPI as jest.Mock).mockReturnValue('oas3_0');
110
+ (detectSpec as jest.Mock).mockReturnValue('oas3_0');
79
111
  await handleJoin(
80
112
  {
81
113
  apis: ['first.yaml', 'second.yaml'],
@@ -89,7 +121,7 @@ describe('handleJoin fails', () => {
89
121
  });
90
122
 
91
123
  it('should call skipDecorators and skipPreprocessors', async () => {
92
- (detectOpenAPI as jest.Mock).mockReturnValue('oas3_0');
124
+ (detectSpec as jest.Mock).mockReturnValue('oas3_0');
93
125
  await handleJoin(
94
126
  {
95
127
  apis: ['first.yaml', 'second.yaml'],
@@ -104,7 +136,7 @@ describe('handleJoin fails', () => {
104
136
  });
105
137
 
106
138
  it('should not call skipDecorators and skipPreprocessors', async () => {
107
- (detectOpenAPI as jest.Mock).mockReturnValue('oas3_0');
139
+ (detectSpec as jest.Mock).mockReturnValue('oas3_0');
108
140
  await handleJoin(
109
141
  {
110
142
  apis: ['first.yaml', 'second.yaml'],
@@ -406,7 +406,7 @@ describe('handleErrors', () => {
406
406
  expect(() => handleError(resolveError, ref)).toThrowError(HandledError);
407
407
  expect(redColoretteMocks).toHaveBeenCalledTimes(1);
408
408
  expect(process.stderr.write).toHaveBeenCalledWith(
409
- `Failed to resolve api definition at openapi/test.yaml:\n\n - File not found.\n\n`
409
+ `Failed to resolve API description at openapi/test.yaml:\n\n - File not found.\n\n`
410
410
  );
411
411
  });
412
412
 
@@ -415,7 +415,7 @@ describe('handleErrors', () => {
415
415
  expect(() => handleError(yamlParseError, ref)).toThrowError(HandledError);
416
416
  expect(redColoretteMocks).toHaveBeenCalledTimes(1);
417
417
  expect(process.stderr.write).toHaveBeenCalledWith(
418
- `Failed to parse api definition at openapi/test.yaml:\n\n - Invalid yaml.\n\n`
418
+ `Failed to parse API description at openapi/test.yaml:\n\n - Invalid yaml.\n\n`
419
419
  );
420
420
  });
421
421
 
@@ -460,6 +460,7 @@ describe('checkIfRulesetExist', () => {
460
460
  oas2: {},
461
461
  oas3_0: {},
462
462
  oas3_1: {},
463
+ async2: {},
463
464
  };
464
465
  expect(() => checkIfRulesetExist(rules)).toThrowError(
465
466
  '⚠️ No rules were configured. Learn how to configure rules: https://redocly.com/docs/cli/rules/'
@@ -1,8 +1,19 @@
1
+ import * as semver from 'semver';
1
2
  import * as path from 'path';
2
- import { exitWithError } from './utils';
3
+ import * as process from 'process';
4
+ import { yellow } from 'colorette';
3
5
 
4
6
  try {
5
- require('assert-node-version')(path.join(__dirname, '../'));
6
- } catch (err) {
7
- exitWithError(err.message);
7
+ const { engines } = require(path.join(__dirname, '../package.json'));
8
+ const version = engines.node;
9
+
10
+ if (!semver.satisfies(process.version, version)) {
11
+ process.stderr.write(
12
+ yellow(
13
+ `\n⚠️ Warning: failed to satisfy expected node version. Expected: "${version}", Current "${process.version}"\n\n`
14
+ )
15
+ );
16
+ }
17
+ } catch (e) {
18
+ // Do nothing
8
19
  }
@@ -5,7 +5,7 @@ const isEqual = require('lodash.isequal');
5
5
  import {
6
6
  Config,
7
7
  Oas3Definition,
8
- OasVersion,
8
+ SpecVersion,
9
9
  BaseResolver,
10
10
  Document,
11
11
  StyleguideConfig,
@@ -13,7 +13,7 @@ import {
13
13
  formatProblems,
14
14
  getTotals,
15
15
  lintDocument,
16
- detectOpenAPI,
16
+ detectSpec,
17
17
  bundleDocument,
18
18
  Referenced,
19
19
  isRef,
@@ -29,7 +29,12 @@ import {
29
29
  sortTopLevelKeysForOas,
30
30
  } from '../utils';
31
31
  import { isObject, isString, keysOf } from '../js-utils';
32
- import { Oas3Parameter, Oas3PathItem, Oas3Server } from '@redocly/openapi-core/lib/typings/openapi';
32
+ import {
33
+ Oas3Parameter,
34
+ Oas3PathItem,
35
+ Oas3Server,
36
+ Oas3_1Definition,
37
+ } from '@redocly/openapi-core/lib/typings/openapi';
33
38
  import { OPENAPI3_METHOD } from './split/types';
34
39
  import { BundleResult } from '@redocly/openapi-core/lib/bundle';
35
40
 
@@ -141,12 +146,22 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
141
146
  }
142
147
  }
143
148
 
149
+ let oasVersion: SpecVersion | null = null;
144
150
  for (const document of documents) {
145
151
  try {
146
- const version = detectOpenAPI(document.parsed);
147
- if (version !== OasVersion.Version3_0) {
152
+ const version = detectSpec(document.parsed);
153
+ if (version !== SpecVersion.OAS3_0 && version !== SpecVersion.OAS3_1) {
154
+ return exitWithError(
155
+ `Only OpenAPI 3.0 and OpenAPI 3.1 are supported: ${blue(
156
+ document.source.absoluteRef
157
+ )} \n\n`
158
+ );
159
+ }
160
+
161
+ oasVersion = oasVersion ?? version;
162
+ if (oasVersion !== version) {
148
163
  return exitWithError(
149
- `Only OpenAPI 3 is supported: ${blue(document.source.absoluteRef)} \n\n`
164
+ `All APIs must use the same OpenAPI version: ${blue(document.source.absoluteRef)} \n\n`
150
165
  );
151
166
  }
152
167
  } catch (e) {
@@ -165,7 +180,7 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
165
180
  tags: {},
166
181
  paths: {},
167
182
  components: {},
168
- xWebhooks: {},
183
+ webhooks: {},
169
184
  };
170
185
 
171
186
  addInfoSectionAndSpecVersion(documents, prefixComponentsWithInfoProp);
@@ -200,7 +215,7 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
200
215
  collectExternalDocs(openapi, context);
201
216
  collectPaths(openapi, context);
202
217
  collectComponents(openapi, context);
203
- collectXWebhooks(openapi, context);
218
+ collectWebhooks(oasVersion!, openapi, context);
204
219
  if (componentsPrefix) {
205
220
  replace$Refs(openapi, componentsPrefix);
206
221
  }
@@ -573,32 +588,33 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
573
588
  }
574
589
  }
575
590
 
576
- function collectXWebhooks(
577
- openapi: Oas3Definition,
591
+ function collectWebhooks(
592
+ oasVersion: SpecVersion,
593
+ openapi: Oas3_1Definition,
578
594
  { apiFilename, api, potentialConflicts, tagsPrefix, componentsPrefix }: JoinDocumentContext
579
595
  ) {
580
- const xWebhooks = 'x-webhooks';
581
- const openapiXWebhooks = openapi[xWebhooks];
582
- if (openapiXWebhooks) {
583
- if (!joinedDef.hasOwnProperty(xWebhooks)) {
584
- joinedDef[xWebhooks] = {};
596
+ const webhooks = oasVersion === SpecVersion.OAS3_1 ? 'webhooks' : 'x-webhooks';
597
+ const openapiWebhooks = openapi[webhooks];
598
+ if (openapiWebhooks) {
599
+ if (!joinedDef.hasOwnProperty(webhooks)) {
600
+ joinedDef[webhooks] = {};
585
601
  }
586
- for (const webhook of Object.keys(openapiXWebhooks)) {
587
- joinedDef[xWebhooks][webhook] = openapiXWebhooks[webhook];
602
+ for (const webhook of Object.keys(openapiWebhooks)) {
603
+ joinedDef[webhooks][webhook] = openapiWebhooks[webhook];
588
604
 
589
- if (!potentialConflicts.xWebhooks.hasOwnProperty(webhook)) {
590
- potentialConflicts.xWebhooks[webhook] = {};
605
+ if (!potentialConflicts.webhooks.hasOwnProperty(webhook)) {
606
+ potentialConflicts.webhooks[webhook] = {};
591
607
  }
592
- for (const operation of Object.keys(openapiXWebhooks[webhook])) {
593
- potentialConflicts.xWebhooks[webhook][operation] = [
594
- ...(potentialConflicts.xWebhooks[webhook][operation] || []),
608
+ for (const operation of Object.keys(openapiWebhooks[webhook])) {
609
+ potentialConflicts.webhooks[webhook][operation] = [
610
+ ...(potentialConflicts.webhooks[webhook][operation] || []),
595
611
  api,
596
612
  ];
597
613
  }
598
- for (const operationKey of Object.keys(joinedDef[xWebhooks][webhook])) {
599
- const { tags } = joinedDef[xWebhooks][webhook][operationKey];
614
+ for (const operationKey of Object.keys(joinedDef[webhooks][webhook])) {
615
+ const { tags } = joinedDef[webhooks][webhook][operationKey];
600
616
  if (tags) {
601
- joinedDef[xWebhooks][webhook][operationKey].tags = tags.map((tag: string) =>
617
+ joinedDef[webhooks][webhook][operationKey].tags = tags.map((tag: string) =>
602
618
  addPrefix(tag, tagsPrefix)
603
619
  );
604
620
  populateTags({
@@ -1,15 +1,7 @@
1
1
  import * as colorette from 'colorette';
2
2
  import * as chockidar from 'chokidar';
3
- import {
4
- bundle,
5
- ResolveError,
6
- YamlParseError,
7
- RedoclyClient,
8
- getTotals,
9
- getMergedConfig,
10
- Config,
11
- } from '@redocly/openapi-core';
12
- import { getFallbackApisOrExit, loadConfigAndHandleErrors } from '../../utils';
3
+ import { bundle, RedoclyClient, getTotals, getMergedConfig, Config } from '@redocly/openapi-core';
4
+ import { getFallbackApisOrExit, handleError, loadConfigAndHandleErrors } from '../../utils';
13
5
  import startPreviewServer from './preview-server/preview-server';
14
6
  import type { Skips } from '../../types';
15
7
 
@@ -178,13 +170,3 @@ export function debounce(func: Function, wait: number, immediate?: boolean) {
178
170
  if (callNow) func.apply(context, args);
179
171
  };
180
172
  }
181
-
182
- function handleError(e: Error, ref: string) {
183
- if (e instanceof ResolveError) {
184
- process.stderr.write(`Failed to resolve api definition at ${ref}:\n\n - ${e.message}.\n\n`);
185
- } else if (e instanceof YamlParseError) {
186
- process.stderr.write(`Failed to parse api definition at ${ref}:\n\n - ${e.message}.\n\n`);
187
- } else {
188
- process.stderr.write(`Something went wrong when processing ${ref}:\n\n - ${e.message}.\n\n`);
189
- }
190
- }
@@ -1,6 +1,6 @@
1
1
  import { compile } from 'handlebars';
2
2
  import * as colorette from 'colorette';
3
- import * as portfinder from 'portfinder';
3
+ import { getPort } from 'get-port-please';
4
4
  import { readFileSync, promises as fsPromises } from 'fs';
5
5
  import * as path from 'path';
6
6
 
@@ -143,7 +143,7 @@ export default async function startPreviewServer(
143
143
  console.timeEnd(colorette.dim(`GET ${request.url}`));
144
144
  };
145
145
 
146
- const wsPort = await portfinder.getPortPromise({ port: 32201 });
146
+ const wsPort = await getPort({ portRange: [32201, 32301] });
147
147
 
148
148
  const server = startHttpServer(port, host, handler);
149
149
  server.on('listening', () => {
@@ -4,13 +4,10 @@ import {
4
4
  Config,
5
5
  StyleguideConfig,
6
6
  normalizeTypes,
7
- Oas3Types,
8
- Oas2Types,
9
7
  BaseResolver,
10
8
  resolveDocument,
11
- detectOpenAPI,
12
- OasMajorVersion,
13
- openAPIMajor,
9
+ detectSpec,
10
+ getTypes,
14
11
  normalizeVisitors,
15
12
  walkDocument,
16
13
  Stats,
@@ -72,20 +69,16 @@ export async function handleStats(argv: StatsOptions, config: Config) {
72
69
  const externalRefResolver = new BaseResolver(config.resolve);
73
70
  const { bundle: document } = await bundle({ config, ref: path });
74
71
  const lintConfig: StyleguideConfig = config.styleguide;
75
- const oasVersion = detectOpenAPI(document.parsed);
76
- const oasMajorVersion = openAPIMajor(oasVersion);
72
+ const specVersion = detectSpec(document.parsed);
77
73
  const types = normalizeTypes(
78
- lintConfig.extendTypes(
79
- oasMajorVersion === OasMajorVersion.Version3 ? Oas3Types : Oas2Types,
80
- oasVersion
81
- ),
74
+ lintConfig.extendTypes(getTypes(specVersion), specVersion),
82
75
  lintConfig
83
76
  );
84
77
 
85
78
  const startedAt = performance.now();
86
79
  const ctx: WalkContext = {
87
80
  problems: [],
88
- oasVersion: oasVersion,
81
+ oasVersion: specVersion,
89
82
  visitorsData: {},
90
83
  };
91
84
 
package/src/index.ts CHANGED
@@ -20,6 +20,10 @@ import type { Arguments } from 'yargs';
20
20
  import type { OutputFormat, RuleSeverity } from '@redocly/openapi-core';
21
21
  import type { BuildDocsArgv } from './commands/build-docs/types';
22
22
 
23
+ if (!('replaceAll' in String.prototype)) {
24
+ require('core-js/actual/string/replace-all');
25
+ }
26
+
23
27
  cacheLatestVersion();
24
28
 
25
29
  yargs
@@ -45,11 +49,11 @@ yargs
45
49
  )
46
50
  .command(
47
51
  'split [api]',
48
- 'Split an API definition into a multi-file structure.',
52
+ 'Split an API description into a multi-file structure.',
49
53
  (yargs) =>
50
54
  yargs
51
55
  .positional('api', {
52
- description: 'API definition file that you want to split',
56
+ description: 'API description file that you want to split',
53
57
  type: 'string',
54
58
  })
55
59
  .option({
@@ -129,7 +133,7 @@ yargs
129
133
 
130
134
  .command(
131
135
  'push [api] [maybeDestination] [maybeBranchName]',
132
- 'Push an API definition to the Redocly API registry.',
136
+ 'Push an API description to the Redocly API registry.',
133
137
  (yargs) =>
134
138
  yargs
135
139
  .usage('push [api]')
@@ -184,7 +188,7 @@ yargs
184
188
  type: 'string',
185
189
  },
186
190
  public: {
187
- description: 'Make the API definition available to the public',
191
+ description: 'Make the API description available to the public',
188
192
  type: 'boolean',
189
193
  },
190
194
  files: {
@@ -314,7 +318,7 @@ yargs
314
318
  type: 'string',
315
319
  },
316
320
  lint: {
317
- description: 'Lint API definitions',
321
+ description: 'Lint API descriptions',
318
322
  type: 'boolean',
319
323
  default: false,
320
324
  },
package/src/utils.ts CHANGED
@@ -2,7 +2,7 @@ import fetch from './fetch-with-timeout';
2
2
  import { basename, dirname, extname, join, resolve, relative, isAbsolute } from 'path';
3
3
  import { blue, gray, green, red, yellow } from 'colorette';
4
4
  import { performance } from 'perf_hooks';
5
- import * as glob from 'glob-promise';
5
+ import * as glob from 'glob';
6
6
  import * as fs from 'fs';
7
7
  import * as readline from 'readline';
8
8
  import { Writable } from 'stream';
@@ -96,7 +96,7 @@ async function expandGlobsInEntrypoints(args: string[], config: ConfigApis) {
96
96
  await Promise.all(
97
97
  (args as string[]).map(async (aliasOrPath) => {
98
98
  return glob.hasMagic(aliasOrPath) && !isAbsoluteUrl(aliasOrPath)
99
- ? (await glob(aliasOrPath)).map((g: string) => getAliasOrPath(config, g))
99
+ ? (await glob.__promisify__(aliasOrPath)).map((g: string) => getAliasOrPath(config, g))
100
100
  : getAliasOrPath(config, aliasOrPath);
101
101
  })
102
102
  )
@@ -232,10 +232,9 @@ export function handleError(e: Error, ref: string) {
232
232
  throw e;
233
233
  }
234
234
  case ResolveError:
235
- return exitWithError(`Failed to resolve api definition at ${ref}:\n\n - ${e.message}.`);
235
+ return exitWithError(`Failed to resolve API description at ${ref}:\n\n - ${e.message}.`);
236
236
  case YamlParseError:
237
- return exitWithError(`Failed to parse api definition at ${ref}:\n\n - ${e.message}.`);
238
- // TODO: codeframe
237
+ return exitWithError(`Failed to parse API description at ${ref}:\n\n - ${e.message}.`);
239
238
  case CircularJSONNotSupportedError: {
240
239
  return exitWithError(
241
240
  `Detected circular reference which can't be converted to JSON.\n` +
@@ -269,7 +268,7 @@ export function printLintTotals(totals: Totals, definitionsCount: number) {
269
268
  );
270
269
  } else if (totals.warnings > 0) {
271
270
  process.stderr.write(
272
- green(`Woohoo! Your OpenAPI ${pluralize('definition is', definitionsCount)} valid. 🎉\n`)
271
+ green(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n`)
273
272
  );
274
273
  process.stderr.write(
275
274
  yellow(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n${ignored}`)
@@ -277,7 +276,7 @@ export function printLintTotals(totals: Totals, definitionsCount: number) {
277
276
  } else {
278
277
  process.stderr.write(
279
278
  green(
280
- `Woohoo! Your OpenAPI ${pluralize('definition is', definitionsCount)} valid. 🎉\n${ignored}`
279
+ `Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n${ignored}`
281
280
  )
282
281
  );
283
282
  }