@redocly/cli 1.2.1 → 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 (64) hide show
  1. package/CHANGELOG.md +23 -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 +67 -41
  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 +3 -3
  22. package/lib/commands/join.js +49 -48
  23. package/lib/commands/lint.d.ts +1 -1
  24. package/lib/commands/lint.js +27 -23
  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 +46 -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 +69 -40
  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 +49 -13
  54. package/src/commands/join.ts +10 -4
  55. package/src/commands/lint.ts +6 -1
  56. package/src/commands/preview-docs/preview-server/hot.js +19 -2
  57. package/src/commands/preview-docs/preview-server/preview-server.ts +6 -4
  58. package/src/commands/preview-docs/preview-server/server.ts +2 -2
  59. package/src/commands/split/__tests__/index.test.ts +14 -5
  60. package/src/commands/split/index.ts +25 -17
  61. package/src/fetch-with-timeout.ts +3 -0
  62. package/src/index.ts +35 -1
  63. package/src/utils.ts +45 -9
  64. package/tsconfig.tsbuildinfo +1 -1
@@ -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
@@ -36,6 +36,11 @@ yargs
36
36
  (yargs) =>
37
37
  yargs.positional('api', { type: 'string' }).option({
38
38
  config: { description: 'Path to the config file.', type: 'string' },
39
+ 'lint-config': {
40
+ description: 'Severity level for config file linting.',
41
+ choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
42
+ default: 'warn' as RuleSeverity,
43
+ },
39
44
  format: {
40
45
  description: 'Use a specific output format.',
41
46
  choices: ['stylish', 'json'] as ReadonlyArray<OutputFormat>,
@@ -73,6 +78,11 @@ yargs
73
78
  requiresArg: true,
74
79
  type: 'string',
75
80
  },
81
+ 'lint-config': {
82
+ description: 'Severity level for config file linting.',
83
+ choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
84
+ default: 'warn' as RuleSeverity,
85
+ },
76
86
  })
77
87
  .demandOption('api'),
78
88
  (argv) => {
@@ -117,13 +127,17 @@ yargs
117
127
  describe: 'Output file',
118
128
  alias: 'o',
119
129
  type: 'string',
120
- default: 'openapi.yaml',
121
130
  },
122
131
  config: {
123
132
  description: 'Path to the config file.',
124
133
  requiresArg: true,
125
134
  type: 'string',
126
135
  },
136
+ 'lint-config': {
137
+ description: 'Severity level for config file linting.',
138
+ choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
139
+ default: 'warn' as RuleSeverity,
140
+ },
127
141
  }),
128
142
  (argv) => {
129
143
  process.env.REDOCLY_CLI_COMMAND = 'join';
@@ -196,6 +210,11 @@ yargs
196
210
  array: true,
197
211
  type: 'string',
198
212
  },
213
+ 'lint-config': {
214
+ description: 'Severity level for config file linting.',
215
+ choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
216
+ default: 'warn' as RuleSeverity,
217
+ },
199
218
  })
200
219
  .deprecateOption('batch-id', 'use --job-id')
201
220
  .deprecateOption('maybeDestination')
@@ -342,6 +361,11 @@ yargs
342
361
  type: 'boolean',
343
362
  alias: 'k',
344
363
  },
364
+ 'lint-config': {
365
+ description: 'Severity level for config file linting.',
366
+ choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
367
+ default: 'warn' as RuleSeverity,
368
+ },
345
369
  }),
346
370
  (argv) => {
347
371
  process.env.REDOCLY_CLI_COMMAND = 'bundle';
@@ -426,6 +450,11 @@ yargs
426
450
  description: 'Path to the config file.',
427
451
  type: 'string',
428
452
  },
453
+ 'lint-config': {
454
+ description: 'Severity level for config file linting.',
455
+ choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
456
+ default: 'warn' as RuleSeverity,
457
+ },
429
458
  }),
430
459
  (argv) => {
431
460
  process.env.REDOCLY_CLI_COMMAND = 'preview-docs';
@@ -471,6 +500,11 @@ yargs
471
500
  describe: 'Path to the config file.',
472
501
  type: 'string',
473
502
  },
503
+ 'lint-config': {
504
+ description: 'Severity level for config file linting.',
505
+ choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
506
+ default: 'warn' as RuleSeverity,
507
+ },
474
508
  })
475
509
  .check((argv: any) => {
476
510
  if (argv.theme && !argv.theme?.openapi)
package/src/utils.ts CHANGED
@@ -24,7 +24,15 @@ import {
24
24
  Oas2Definition,
25
25
  RedoclyClient,
26
26
  } from '@redocly/openapi-core';
27
- import { Totals, outputExtensions, Entrypoint, ConfigApis, CommandOptions } from './types';
27
+ import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
28
+ import {
29
+ Totals,
30
+ outputExtensions,
31
+ Entrypoint,
32
+ ConfigApis,
33
+ CommandOptions,
34
+ OutputExtensions,
35
+ } from './types';
28
36
  import { isEmptyObject } from '@redocly/openapi-core/lib/utils';
29
37
  import { Arguments } from 'yargs';
30
38
  import { version } from './update-version-notifier';
@@ -208,6 +216,17 @@ export function readYaml(filename: string) {
208
216
  return parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
209
217
  }
210
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
+
211
230
  export function writeYaml(data: any, filename: string, noRefs = false) {
212
231
  const content = stringifyYaml(data, { noRefs });
213
232
 
@@ -219,6 +238,27 @@ export function writeYaml(data: any, filename: string, noRefs = false) {
219
238
  fs.writeFileSync(filename, content);
220
239
  }
221
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
+
222
262
  export function pluralize(label: string, num: number) {
223
263
  if (label.endsWith('is')) {
224
264
  [label] = label.split(' ');
@@ -244,6 +284,8 @@ export function handleError(e: Error, ref: string) {
244
284
  }
245
285
  case SyntaxError:
246
286
  return exitWithError(`Syntax error: ${e.message} ${e.stack?.split('\n\n')?.[0]}`);
287
+ case ConfigValidationError:
288
+ return exitWithError(e.message);
247
289
  default: {
248
290
  exitWithError(`Something went wrong when processing ${ref}:\n\n - ${e.message}.`);
249
291
  }
@@ -294,17 +336,11 @@ export function printLintTotals(totals: Totals, definitionsCount: number) {
294
336
  export function printConfigLintTotals(totals: Totals): void {
295
337
  if (totals.errors > 0) {
296
338
  process.stderr.write(
297
- red(
298
- `❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}${
299
- totals.warnings > 0
300
- ? ` and ${totals.warnings} ${pluralize('warning', totals.warnings)}`
301
- : ''
302
- }.\n`
303
- )
339
+ red(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}.`)
304
340
  );
305
341
  } else if (totals.warnings > 0) {
306
342
  process.stderr.write(
307
- yellow(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n`)
343
+ yellow(`⚠️ Your config has ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n`)
308
344
  );
309
345
  }
310
346
  }