@redocly/cli 1.3.0 → 1.4.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/__mocks__/@redocly/openapi-core.d.ts +1 -0
  3. package/lib/__mocks__/@redocly/openapi-core.js +4 -3
  4. package/lib/__mocks__/utils.d.ts +2 -0
  5. package/lib/__mocks__/utils.js +3 -1
  6. package/lib/__tests__/commands/build-docs.test.js +2 -2
  7. package/lib/__tests__/commands/bundle.test.js +7 -7
  8. package/lib/__tests__/commands/join.test.js +25 -18
  9. package/lib/__tests__/commands/lint.test.js +15 -15
  10. package/lib/__tests__/commands/push-region.test.js +2 -2
  11. package/lib/__tests__/commands/push.test.js +30 -30
  12. package/lib/__tests__/fetch-with-timeout.test.js +2 -2
  13. package/lib/__tests__/utils.test.js +63 -32
  14. package/lib/__tests__/wrapper.test.js +3 -3
  15. package/lib/assert-node-version.js +1 -1
  16. package/lib/commands/build-docs/index.js +9 -9
  17. package/lib/commands/build-docs/types.d.ts +2 -2
  18. package/lib/commands/build-docs/utils.js +10 -10
  19. package/lib/commands/bundle.d.ts +1 -1
  20. package/lib/commands/bundle.js +25 -25
  21. package/lib/commands/join.d.ts +1 -1
  22. package/lib/commands/join.js +49 -48
  23. package/lib/commands/lint.d.ts +1 -1
  24. package/lib/commands/lint.js +22 -22
  25. package/lib/commands/login.d.ts +1 -1
  26. package/lib/commands/login.js +3 -3
  27. package/lib/commands/preview-docs/index.d.ts +1 -1
  28. package/lib/commands/preview-docs/index.js +7 -7
  29. package/lib/commands/preview-docs/preview-server/hot.js +19 -2
  30. package/lib/commands/preview-docs/preview-server/preview-server.js +15 -14
  31. package/lib/commands/preview-docs/preview-server/server.d.ts +3 -1
  32. package/lib/commands/preview-docs/preview-server/server.js +2 -2
  33. package/lib/commands/push.d.ts +2 -2
  34. package/lib/commands/push.js +31 -31
  35. package/lib/commands/split/__tests__/index.test.js +9 -9
  36. package/lib/commands/split/index.d.ts +2 -2
  37. package/lib/commands/split/index.js +41 -40
  38. package/lib/commands/split/types.d.ts +2 -2
  39. package/lib/commands/split/types.js +2 -2
  40. package/lib/commands/stats.d.ts +1 -1
  41. package/lib/commands/stats.js +9 -9
  42. package/lib/fetch-with-timeout.js +5 -2
  43. package/lib/index.js +11 -12
  44. package/lib/types.d.ts +6 -6
  45. package/lib/update-version-notifier.js +18 -18
  46. package/lib/utils.d.ts +6 -3
  47. package/lib/utils.js +66 -38
  48. package/lib/wrapper.js +5 -5
  49. package/package.json +3 -3
  50. package/src/__mocks__/@redocly/openapi-core.ts +1 -0
  51. package/src/__mocks__/utils.ts +2 -0
  52. package/src/__tests__/commands/join.test.ts +37 -7
  53. package/src/__tests__/utils.test.ts +45 -1
  54. package/src/commands/join.ts +8 -3
  55. package/src/commands/preview-docs/preview-server/hot.js +19 -2
  56. package/src/commands/preview-docs/preview-server/preview-server.ts +6 -4
  57. package/src/commands/preview-docs/preview-server/server.ts +2 -2
  58. package/src/commands/split/__tests__/index.test.ts +14 -5
  59. package/src/commands/split/index.ts +25 -17
  60. package/src/fetch-with-timeout.ts +3 -0
  61. package/src/index.ts +0 -1
  62. package/src/utils.ts +40 -1
  63. package/tsconfig.tsbuildinfo +1 -1
@@ -1,11 +1,12 @@
1
1
  import { handleJoin } from '../../commands/join';
2
- import { exitWithError, writeYaml } from '../../utils';
2
+ import { exitWithError, writeToFileByExtension, writeYaml } from '../../utils';
3
3
  import { yellow } from 'colorette';
4
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
 
8
8
  jest.mock('../../utils');
9
+
9
10
  jest.mock('colorette');
10
11
 
11
12
  describe('handleJoin fails', () => {
@@ -80,7 +81,7 @@ describe('handleJoin fails', () => {
80
81
  );
81
82
  });
82
83
 
83
- it('should call writeYaml function', async () => {
84
+ it('should call writeToFileByExtension function', async () => {
84
85
  (detectSpec as jest.Mock).mockReturnValue('oas3_0');
85
86
  await handleJoin(
86
87
  {
@@ -90,10 +91,14 @@ describe('handleJoin fails', () => {
90
91
  'cli-version'
91
92
  );
92
93
 
93
- expect(writeYaml).toHaveBeenCalledWith(expect.any(Object), 'openapi.yaml', expect.any(Boolean));
94
+ expect(writeToFileByExtension).toHaveBeenCalledWith(
95
+ expect.any(Object),
96
+ 'openapi.yaml',
97
+ expect.any(Boolean)
98
+ );
94
99
  });
95
100
 
96
- it('should call writeYaml function for OpenAPI 3.1', async () => {
101
+ it('should call writeToFileByExtension function for OpenAPI 3.1', async () => {
97
102
  (detectSpec as jest.Mock).mockReturnValue('oas3_1');
98
103
  await handleJoin(
99
104
  {
@@ -103,10 +108,14 @@ describe('handleJoin fails', () => {
103
108
  'cli-version'
104
109
  );
105
110
 
106
- expect(writeYaml).toHaveBeenCalledWith(expect.any(Object), 'openapi.yaml', expect.any(Boolean));
111
+ expect(writeToFileByExtension).toHaveBeenCalledWith(
112
+ expect.any(Object),
113
+ 'openapi.yaml',
114
+ expect.any(Boolean)
115
+ );
107
116
  });
108
117
 
109
- it('should call writeYaml function with custom output file', async () => {
118
+ it('should call writeToFileByExtension function with custom output file', async () => {
110
119
  (detectSpec as jest.Mock).mockReturnValue('oas3_0');
111
120
  await handleJoin(
112
121
  {
@@ -117,7 +126,28 @@ describe('handleJoin fails', () => {
117
126
  'cli-version'
118
127
  );
119
128
 
120
- expect(writeYaml).toHaveBeenCalledWith(expect.any(Object), 'output.yml', expect.any(Boolean));
129
+ expect(writeToFileByExtension).toHaveBeenCalledWith(
130
+ expect.any(Object),
131
+ 'output.yml',
132
+ expect.any(Boolean)
133
+ );
134
+ });
135
+
136
+ it('should call writeToFileByExtension function with json file extension', async () => {
137
+ (detectSpec as jest.Mock).mockReturnValue('oas3_0');
138
+ await handleJoin(
139
+ {
140
+ apis: ['first.json', 'second.yaml'],
141
+ },
142
+ ConfigFixture as any,
143
+ 'cli-version'
144
+ );
145
+
146
+ expect(writeToFileByExtension).toHaveBeenCalledWith(
147
+ expect.any(Object),
148
+ 'openapi.json',
149
+ expect.any(Boolean)
150
+ );
121
151
  });
122
152
 
123
153
  it('should call skipDecorators and skipPreprocessors', async () => {
@@ -12,6 +12,10 @@ import {
12
12
  HandledError,
13
13
  cleanArgs,
14
14
  cleanRawInput,
15
+ getAndValidateFileExtension,
16
+ writeYaml,
17
+ writeJson,
18
+ writeToFileByExtension,
15
19
  } from '../utils';
16
20
  import {
17
21
  ResolvedApi,
@@ -19,11 +23,13 @@ import {
19
23
  isAbsoluteUrl,
20
24
  ResolveError,
21
25
  YamlParseError,
26
+ stringifyYaml,
22
27
  } from '@redocly/openapi-core';
23
28
  import { blue, red, yellow } from 'colorette';
24
- import { existsSync, statSync } from 'fs';
29
+ import { existsSync, statSync, writeFileSync } from 'fs';
25
30
  import * as path from 'path';
26
31
  import * as process from 'process';
32
+ import * as utils from '../utils';
27
33
 
28
34
  jest.mock('os');
29
35
  jest.mock('colorette');
@@ -554,4 +560,42 @@ describe('cleanRawInput', () => {
554
560
  'redocly lint file-json --format stylish --extends=minimal --skip-rule operation-4xx-response'
555
561
  );
556
562
  });
563
+
564
+ describe('validateFileExtension', () => {
565
+ it('should return current file extension', () => {
566
+ expect(getAndValidateFileExtension('test.json')).toEqual('json');
567
+ });
568
+
569
+ it('should return yaml and print warning if file extension does not supported', () => {
570
+ const stderrMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
571
+ (yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
572
+
573
+ expect(getAndValidateFileExtension('test.xml')).toEqual('yaml');
574
+ expect(stderrMock).toHaveBeenCalledWith(`Unsupported file extension: xml. Using yaml.\n`);
575
+ });
576
+ });
577
+
578
+ describe('writeToFileByExtension', () => {
579
+ beforeEach(() => {
580
+ jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
581
+ (yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
582
+ });
583
+
584
+ afterEach(() => {
585
+ jest.restoreAllMocks();
586
+ });
587
+
588
+ it('should call stringifyYaml function', () => {
589
+ writeToFileByExtension('test data', 'test.yaml');
590
+ expect(stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
591
+ expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
592
+ });
593
+
594
+ it('should call JSON.stringify function', () => {
595
+ const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
596
+ writeToFileByExtension('test data', 'test.json');
597
+ expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
598
+ expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
599
+ });
600
+ });
557
601
  });
@@ -25,9 +25,10 @@ import {
25
25
  printExecutionTime,
26
26
  handleError,
27
27
  printLintTotals,
28
- writeYaml,
29
28
  exitWithError,
30
29
  sortTopLevelKeysForOas,
30
+ getAndValidateFileExtension,
31
+ writeToFileByExtension,
31
32
  } from '../utils';
32
33
  import { isObject, isString, keysOf } from '../js-utils';
33
34
  import {
@@ -70,16 +71,19 @@ export type JoinOptions = {
70
71
 
71
72
  export async function handleJoin(argv: JoinOptions, config: Config, packageVersion: string) {
72
73
  const startedAt = performance.now();
74
+
73
75
  if (argv.apis.length < 2) {
74
76
  return exitWithError(`At least 2 apis should be provided. \n\n`);
75
77
  }
76
78
 
79
+ const fileExtension = getAndValidateFileExtension(argv.output || argv.apis[0]);
80
+
77
81
  const {
78
82
  'prefix-components-with-info-prop': prefixComponentsWithInfoProp,
79
83
  'prefix-tags-with-filename': prefixTagsWithFilename,
80
84
  'prefix-tags-with-info-prop': prefixTagsWithInfoProp,
81
85
  'without-x-tag-groups': withoutXTagGroups,
82
- output: specFilename = 'openapi.yaml',
86
+ output: specFilename = `openapi.${fileExtension}`,
83
87
  } = argv;
84
88
 
85
89
  const usedTagsOptions = [
@@ -229,7 +233,8 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
229
233
  return exitWithError(`Please fix conflicts before running ${yellow('join')}.`);
230
234
  }
231
235
 
232
- writeYaml(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
236
+ writeToFileByExtension(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
237
+
233
238
  printExecutionTime('join', startedAt, specFilename);
234
239
 
235
240
  function populateTags({
@@ -1,13 +1,29 @@
1
1
  (function run() {
2
2
  const Socket = window.SimpleWebsocket;
3
3
  const port = window.__OPENAPI_CLI_WS_PORT;
4
+ const host = window.__OPENAPI_CLI_WS_HOST;
4
5
 
5
6
  let socket;
6
7
 
7
8
  reconnect();
8
9
 
10
+ function getFormattedHost() {
11
+ // Use localhost when bound to all interfaces
12
+ if (host === '::' || host === '0.0.0.0') {
13
+ return 'localhost';
14
+ }
15
+
16
+ // Other IPv6 addresses must be wrapped in brackets
17
+ if (host.includes('::')) {
18
+ return `[${host}]`;
19
+ }
20
+
21
+ // Otherwise return as-is
22
+ return host;
23
+ }
24
+
9
25
  function reconnect() {
10
- socket = new Socket(`ws://127.0.0.1:${port}`);
26
+ socket = new Socket(`ws://${getFormattedHost()}:${port}`);
11
27
  socket.on('connect', () => {
12
28
  socket.send('{"type": "ping"}');
13
29
  });
@@ -29,13 +45,14 @@
29
45
 
30
46
  socket.on('close', () => {
31
47
  socket.destroy();
32
- console.log('Connection lost, trying to reconnect in 4s');
48
+ console.log('[hot] Connection lost, trying to reconnect in 4s');
33
49
  setTimeout(() => {
34
50
  reconnect();
35
51
  }, 4000);
36
52
  });
37
53
 
38
54
  socket.on('error', () => {
55
+ console.log('[hot] Error connecting to hot reloading server');
39
56
  socket.destroy();
40
57
  });
41
58
  }
@@ -12,7 +12,8 @@ function getPageHTML(
12
12
  htmlTemplate: string,
13
13
  redocOptions: object = {},
14
14
  useRedocPro: boolean,
15
- wsPort: number
15
+ wsPort: number,
16
+ host: string
16
17
  ) {
17
18
  let templateSrc = readFileSync(htmlTemplate, 'utf-8');
18
19
 
@@ -28,6 +29,7 @@ function getPageHTML(
28
29
  <script>
29
30
  window.__REDOC_EXPORT = '${useRedocPro ? 'RedoclyReferenceDocs' : 'Redoc'}';
30
31
  window.__OPENAPI_CLI_WS_PORT = ${wsPort};
32
+ window.__OPENAPI_CLI_WS_HOST = "${host}";
31
33
  </script>
32
34
  <script src="/simplewebsocket.min.js"></script>
33
35
  <script src="/hot.js"></script>
@@ -67,7 +69,7 @@ export default async function startPreviewServer(
67
69
 
68
70
  if (request.url?.endsWith('/') || path.extname(request.url!) === '') {
69
71
  respondWithGzip(
70
- getPageHTML(htmlTemplate || defaultTemplate, getOptions(), useRedocPro, wsPort),
72
+ getPageHTML(htmlTemplate || defaultTemplate, getOptions(), useRedocPro, wsPort, host),
71
73
  request,
72
74
  response,
73
75
  {
@@ -143,7 +145,7 @@ export default async function startPreviewServer(
143
145
  console.timeEnd(colorette.dim(`GET ${request.url}`));
144
146
  };
145
147
 
146
- const wsPort = await getPort({ portRange: [32201, 32301] });
148
+ const wsPort = await getPort({ port: 32201, portRange: [32201, 32301], host });
147
149
 
148
150
  const server = startHttpServer(port, host, handler);
149
151
  server.on('listening', () => {
@@ -152,5 +154,5 @@ export default async function startPreviewServer(
152
154
  );
153
155
  });
154
156
 
155
- return startWsServer(wsPort);
157
+ return startWsServer(wsPort, host);
156
158
  }
@@ -62,8 +62,8 @@ export function startHttpServer(port: number, host: string, handler: http.Reques
62
62
  return http.createServer(handler).listen(port, host);
63
63
  }
64
64
 
65
- export function startWsServer(port: number) {
66
- const socketServer = new SocketServer({ port, clientTracking: true });
65
+ export function startWsServer(port: number, host: string) {
66
+ const socketServer = new SocketServer({ port, host, clientTracking: true });
67
67
 
68
68
  socketServer.on('connection', (socket: any) => {
69
69
  socket.on('data', (data: string) => {
@@ -3,12 +3,13 @@ import * as path from 'path';
3
3
  import * as openapiCore from '@redocly/openapi-core';
4
4
  import { ComponentsFiles } from '../types';
5
5
  import { blue, green } from 'colorette';
6
+ import { writeToFileByExtension } from '../../../utils';
6
7
 
7
8
  const utils = require('../../../utils');
8
9
 
9
10
  jest.mock('../../../utils', () => ({
10
11
  ...jest.requireActual('../../../utils'),
11
- writeYaml: jest.fn(),
12
+ writeToFileByExtension: jest.fn(),
12
13
  }));
13
14
 
14
15
  jest.mock('@redocly/openapi-core', () => ({
@@ -65,7 +66,9 @@ describe('#split', () => {
65
66
  openapiDir,
66
67
  path.join(openapiDir, 'paths'),
67
68
  componentsFiles,
68
- '_'
69
+ '_',
70
+ undefined,
71
+ 'yaml'
69
72
  );
70
73
 
71
74
  expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
@@ -82,7 +85,9 @@ describe('#split', () => {
82
85
  openapiDir,
83
86
  path.join(openapiDir, 'webhooks'),
84
87
  componentsFiles,
85
- 'webhook_'
88
+ 'webhook_',
89
+ undefined,
90
+ 'yaml'
86
91
  );
87
92
 
88
93
  expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
@@ -99,7 +104,9 @@ describe('#split', () => {
99
104
  openapiDir,
100
105
  path.join(openapiDir, 'webhooks'),
101
106
  componentsFiles,
102
- 'webhook_'
107
+ 'webhook_',
108
+ undefined,
109
+ 'yaml'
103
110
  );
104
111
 
105
112
  expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
@@ -118,7 +125,9 @@ describe('#split', () => {
118
125
  openapiDir,
119
126
  path.join(openapiDir, 'paths'),
120
127
  componentsFiles,
121
- '_'
128
+ '_',
129
+ undefined,
130
+ 'yaml'
122
131
  );
123
132
 
124
133
  expect(utils.escapeLanguageName).nthCalledWith(1, 'C#');
@@ -10,10 +10,11 @@ import {
10
10
  printExecutionTime,
11
11
  pathToFilename,
12
12
  readYaml,
13
- writeYaml,
14
13
  exitWithError,
15
14
  escapeLanguageName,
16
15
  langToExt,
16
+ writeToFileByExtension,
17
+ getAndValidateFileExtension,
17
18
  } from '../../utils';
18
19
  import { isString, isObject, isEmptyObject } from '../../js-utils';
19
20
  import {
@@ -46,8 +47,9 @@ export async function handleSplit(argv: SplitOptions) {
46
47
  const startedAt = performance.now();
47
48
  const { api, outDir, separator } = argv;
48
49
  validateDefinitionFileName(api!);
50
+ const ext = getAndValidateFileExtension(api);
49
51
  const openapi = readYaml(api!) as Oas3Definition | Oas3_1Definition;
50
- splitDefinition(openapi, outDir, separator);
52
+ splitDefinition(openapi, outDir, separator, ext);
51
53
  process.stderr.write(
52
54
  `🪓 Document: ${blue(api!)} ${green('is successfully split')}
53
55
  and all related files are saved to the directory: ${blue(outDir)} \n`
@@ -58,18 +60,21 @@ export async function handleSplit(argv: SplitOptions) {
58
60
  function splitDefinition(
59
61
  openapi: Oas3Definition | Oas3_1Definition,
60
62
  openapiDir: string,
61
- pathSeparator: string
63
+ pathSeparator: string,
64
+ ext: string
62
65
  ) {
63
66
  fs.mkdirSync(openapiDir, { recursive: true });
64
67
 
65
68
  const componentsFiles: ComponentsFiles = {};
66
- iterateComponents(openapi, openapiDir, componentsFiles);
69
+ iterateComponents(openapi, openapiDir, componentsFiles, ext);
67
70
  iteratePathItems(
68
71
  openapi.paths,
69
72
  openapiDir,
70
73
  path.join(openapiDir, 'paths'),
71
74
  componentsFiles,
72
- pathSeparator
75
+ pathSeparator,
76
+ undefined,
77
+ ext
73
78
  );
74
79
  const webhooks =
75
80
  (openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks'];
@@ -80,11 +85,12 @@ function splitDefinition(
80
85
  path.join(openapiDir, 'webhooks'),
81
86
  componentsFiles,
82
87
  pathSeparator,
83
- 'webhook_'
88
+ 'webhook_',
89
+ ext
84
90
  );
85
91
 
86
92
  replace$Refs(openapi, openapiDir, componentsFiles);
87
- writeYaml(openapi, path.join(openapiDir, 'openapi.yaml'));
93
+ writeToFileByExtension(openapi, path.join(openapiDir, `openapi.${ext}`));
88
94
  }
89
95
 
90
96
  function isStartsWithComponents(node: string) {
@@ -135,7 +141,7 @@ function traverseDirectoryDeepCallback(
135
141
  if (isNotYaml(filename)) return;
136
142
  const pathData = readYaml(filename);
137
143
  replace$Refs(pathData, directory, componentsFiles);
138
- writeYaml(pathData, filename);
144
+ writeToFileByExtension(pathData, filename);
139
145
  }
140
146
 
141
147
  function crawl(object: any, visitor: any) {
@@ -251,8 +257,8 @@ function extractFileNameFromPath(filename: string) {
251
257
  return path.basename(filename, path.extname(filename));
252
258
  }
253
259
 
254
- function getFileNamePath(componentDirPath: string, componentName: string) {
255
- return path.join(componentDirPath, componentName) + '.yaml';
260
+ function getFileNamePath(componentDirPath: string, componentName: string, ext: string) {
261
+ return path.join(componentDirPath, componentName) + `.${ext}`;
256
262
  }
257
263
 
258
264
  function gatherComponentsFiles(
@@ -278,13 +284,14 @@ function iteratePathItems(
278
284
  outDir: string,
279
285
  componentsFiles: object,
280
286
  pathSeparator: string,
281
- codeSamplesPathPrefix: string = ''
287
+ codeSamplesPathPrefix: string = '',
288
+ ext: string
282
289
  ) {
283
290
  if (!pathItems) return;
284
291
  fs.mkdirSync(outDir, { recursive: true });
285
292
 
286
293
  for (const pathName of Object.keys(pathItems)) {
287
- const pathFile = `${path.join(outDir, pathToFilename(pathName, pathSeparator))}.yaml`;
294
+ const pathFile = `${path.join(outDir, pathToFilename(pathName, pathSeparator))}.${ext}`;
288
295
  const pathData = pathItems[pathName] as Oas3PathItem;
289
296
 
290
297
  if (isRef(pathData)) continue;
@@ -314,7 +321,7 @@ function iteratePathItems(
314
321
  };
315
322
  }
316
323
  }
317
- writeYaml(pathData, pathFile);
324
+ writeToFileByExtension(pathData, pathFile);
318
325
  pathItems[pathName] = {
319
326
  $ref: slash(path.relative(openapiDir, pathFile)),
320
327
  };
@@ -326,7 +333,8 @@ function iteratePathItems(
326
333
  function iterateComponents(
327
334
  openapi: Oas3Definition | Oas3_1Definition,
328
335
  openapiDir: string,
329
- componentsFiles: ComponentsFiles
336
+ componentsFiles: ComponentsFiles,
337
+ ext: string
330
338
  ) {
331
339
  const { components } = openapi;
332
340
  if (components) {
@@ -340,7 +348,7 @@ function iterateComponents(
340
348
  function iterateAndGatherComponentsFiles(componentType: Oas3ComponentName) {
341
349
  const componentDirPath = path.join(componentsDir, componentType);
342
350
  for (const componentName of Object.keys(components?.[componentType] || {})) {
343
- const filename = getFileNamePath(componentDirPath, componentName);
351
+ const filename = getFileNamePath(componentDirPath, componentName, ext);
344
352
  gatherComponentsFiles(components!, componentsFiles, componentType, componentName, filename);
345
353
  }
346
354
  }
@@ -350,7 +358,7 @@ function iterateComponents(
350
358
  const componentDirPath = path.join(componentsDir, componentType);
351
359
  createComponentDir(componentDirPath, componentType);
352
360
  for (const componentName of Object.keys(components?.[componentType] || {})) {
353
- const filename = getFileNamePath(componentDirPath, componentName);
361
+ const filename = getFileNamePath(componentDirPath, componentName, ext);
354
362
  const componentData = components?.[componentType]?.[componentName];
355
363
  replace$Refs(componentData, path.dirname(filename), componentsFiles);
356
364
  implicitlyReferenceDiscriminator(
@@ -369,7 +377,7 @@ function iterateComponents(
369
377
  )
370
378
  );
371
379
  } else {
372
- writeYaml(componentData, filename);
380
+ writeToFileByExtension(componentData, filename);
373
381
  }
374
382
 
375
383
  if (isNotSecurityComponentType(componentType)) {
@@ -12,6 +12,9 @@ export default async (url: string, options = {}) => {
12
12
  controller.abort();
13
13
  }, TIMEOUT);
14
14
 
15
+ // FIXME: fix this (possibly along with this issue: https://github.com/Redocly/redocly-cli/issues/1260)
16
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17
+ // @ts-ignore
15
18
  const res = await nodeFetch(url, { signal: controller.signal, ...options });
16
19
  clearTimeout(timeout);
17
20
  return res;
package/src/index.ts CHANGED
@@ -127,7 +127,6 @@ yargs
127
127
  describe: 'Output file',
128
128
  alias: 'o',
129
129
  type: 'string',
130
- default: 'openapi.yaml',
131
130
  },
132
131
  config: {
133
132
  description: 'Path to the config file.',
package/src/utils.ts CHANGED
@@ -25,7 +25,14 @@ import {
25
25
  RedoclyClient,
26
26
  } from '@redocly/openapi-core';
27
27
  import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
28
- import { Totals, outputExtensions, Entrypoint, ConfigApis, CommandOptions } from './types';
28
+ import {
29
+ Totals,
30
+ outputExtensions,
31
+ Entrypoint,
32
+ ConfigApis,
33
+ CommandOptions,
34
+ OutputExtensions,
35
+ } from './types';
29
36
  import { isEmptyObject } from '@redocly/openapi-core/lib/utils';
30
37
  import { Arguments } from 'yargs';
31
38
  import { version } from './update-version-notifier';
@@ -209,6 +216,17 @@ export function readYaml(filename: string) {
209
216
  return parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
210
217
  }
211
218
 
219
+ export function writeToFileByExtension(data: unknown, filePath: string, noRefs?: boolean) {
220
+ const ext = getAndValidateFileExtension(filePath);
221
+
222
+ if (ext === 'json') {
223
+ writeJson(data, filePath);
224
+ return;
225
+ }
226
+
227
+ writeYaml(data, filePath, noRefs);
228
+ }
229
+
212
230
  export function writeYaml(data: any, filename: string, noRefs = false) {
213
231
  const content = stringifyYaml(data, { noRefs });
214
232
 
@@ -220,6 +238,27 @@ export function writeYaml(data: any, filename: string, noRefs = false) {
220
238
  fs.writeFileSync(filename, content);
221
239
  }
222
240
 
241
+ export function writeJson(data: unknown, filename: string) {
242
+ const content = JSON.stringify(data, null, 2);
243
+
244
+ if (process.env.NODE_ENV === 'test') {
245
+ process.stderr.write(content);
246
+ return;
247
+ }
248
+ fs.mkdirSync(dirname(filename), { recursive: true });
249
+ fs.writeFileSync(filename, content);
250
+ }
251
+
252
+ export function getAndValidateFileExtension(fileName: string): NonNullable<OutputExtensions> {
253
+ const ext = fileName.split('.').pop();
254
+
255
+ if (['yaml', 'yml', 'json'].includes(ext!)) {
256
+ return ext as NonNullable<OutputExtensions>;
257
+ }
258
+ process.stderr.write(yellow(`Unsupported file extension: ${ext}. Using yaml.\n`));
259
+ return 'yaml';
260
+ }
261
+
223
262
  export function pluralize(label: string, num: number) {
224
263
  if (label.endsWith('is')) {
225
264
  [label] = label.split(' ');