@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
package/lib/utils.js CHANGED
@@ -20,7 +20,7 @@ var __rest = (this && this.__rest) || function (s, e) {
20
20
  return t;
21
21
  };
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.cleanRawInput = exports.cleanArgs = exports.sendTelemetry = exports.cleanColors = exports.checkIfRulesetExist = exports.sortTopLevelKeysForOas = exports.loadConfigAndHandleErrors = exports.isSubdir = exports.exitWithError = exports.printUnusedWarnings = exports.getOutputFileName = exports.printConfigLintTotals = exports.printLintTotals = exports.HandledError = exports.handleError = exports.pluralize = exports.writeYaml = exports.readYaml = exports.promptUser = exports.saveBundle = exports.dumpBundle = exports.CircularJSONNotSupportedError = exports.langToExt = exports.escapeLanguageName = exports.pathToFilename = exports.printExecutionTime = exports.getExecutionTime = exports.getFallbackApisOrExit = void 0;
23
+ exports.cleanRawInput = exports.cleanArgs = exports.sendTelemetry = exports.cleanColors = exports.checkIfRulesetExist = exports.sortTopLevelKeysForOas = exports.loadConfigAndHandleErrors = exports.isSubdir = exports.exitWithError = exports.printUnusedWarnings = exports.getOutputFileName = exports.printConfigLintTotals = exports.printLintTotals = exports.HandledError = exports.handleError = exports.pluralize = exports.getAndValidateFileExtension = exports.writeJson = exports.writeYaml = exports.writeToFileByExtension = exports.readYaml = exports.promptUser = exports.saveBundle = exports.dumpBundle = exports.CircularJSONNotSupportedError = exports.langToExt = exports.escapeLanguageName = exports.pathToFilename = exports.printExecutionTime = exports.getExecutionTime = exports.getFallbackApisOrExit = void 0;
24
24
  const fetch_with_timeout_1 = require("./fetch-with-timeout");
25
25
  const path_1 = require("path");
26
26
  const colorette_1 = require("colorette");
@@ -31,6 +31,7 @@ const readline = require("readline");
31
31
  const stream_1 = require("stream");
32
32
  const child_process_1 = require("child_process");
33
33
  const openapi_core_1 = require("@redocly/openapi-core");
34
+ const config_1 = require("@redocly/openapi-core/lib/config");
34
35
  const types_1 = require("./types");
35
36
  const utils_1 = require("@redocly/openapi-core/lib/utils");
36
37
  const update_version_notifier_1 = require("./update-version-notifier");
@@ -45,7 +46,7 @@ function getFallbackApisOrExit(argsApis, config) {
45
46
  const filteredInvalidEntrypoints = res.filter(({ path }) => !isApiPathValid(path));
46
47
  if (isNotEmptyArray(filteredInvalidEntrypoints)) {
47
48
  for (const { path } of filteredInvalidEntrypoints) {
48
- process.stderr.write(colorette_1.yellow(`\n${path_1.relative(process.cwd(), path)} ${colorette_1.red(`does not exist or is invalid.\n\n`)}`));
49
+ process.stderr.write((0, colorette_1.yellow)(`\n${(0, path_1.relative)(process.cwd(), path)} ${(0, colorette_1.red)(`does not exist or is invalid.\n\n`)}`));
49
50
  }
50
51
  exitWithError('Please provide a valid path.');
51
52
  }
@@ -54,7 +55,7 @@ function getFallbackApisOrExit(argsApis, config) {
54
55
  }
55
56
  exports.getFallbackApisOrExit = getFallbackApisOrExit;
56
57
  function getConfigDirectory(config) {
57
- return config.configFile ? path_1.dirname(config.configFile) : process.cwd();
58
+ return config.configFile ? (0, path_1.dirname)(config.configFile) : process.cwd();
58
59
  }
59
60
  function isNotEmptyArray(args) {
60
61
  return Array.isArray(args) && !!args.length;
@@ -64,11 +65,11 @@ function isApiPathValid(apiPath) {
64
65
  exitWithError('Path cannot be empty.');
65
66
  return;
66
67
  }
67
- return fs.existsSync(apiPath) || openapi_core_1.isAbsoluteUrl(apiPath) ? apiPath : undefined;
68
+ return fs.existsSync(apiPath) || (0, openapi_core_1.isAbsoluteUrl)(apiPath) ? apiPath : undefined;
68
69
  }
69
70
  function fallbackToAllDefinitions(apis, config) {
70
71
  return Object.entries(apis).map(([alias, { root }]) => ({
71
- path: openapi_core_1.isAbsoluteUrl(root) ? root : path_1.resolve(getConfigDirectory(config), root),
72
+ path: (0, openapi_core_1.isAbsoluteUrl)(root) ? root : (0, path_1.resolve)(getConfigDirectory(config), root),
72
73
  alias,
73
74
  }));
74
75
  }
@@ -80,14 +81,14 @@ function getAliasOrPath(config, aliasOrPath) {
80
81
  path: aliasOrPath,
81
82
  // find alias by path, take the first match
82
83
  alias: (_c = (_b = Object.entries(config.apis).find(([_alias, api]) => {
83
- return path_1.resolve(api.root) === path_1.resolve(aliasOrPath);
84
+ return (0, path_1.resolve)(api.root) === (0, path_1.resolve)(aliasOrPath);
84
85
  })) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : undefined,
85
86
  };
86
87
  }
87
88
  function expandGlobsInEntrypoints(args, config) {
88
89
  return __awaiter(this, void 0, void 0, function* () {
89
90
  return (yield Promise.all(args.map((aliasOrPath) => __awaiter(this, void 0, void 0, function* () {
90
- return glob.hasMagic(aliasOrPath) && !openapi_core_1.isAbsoluteUrl(aliasOrPath)
91
+ return glob.hasMagic(aliasOrPath) && !(0, openapi_core_1.isAbsoluteUrl)(aliasOrPath)
91
92
  ? (yield glob.__promisify__(aliasOrPath)).map((g) => getAliasOrPath(config, g))
92
93
  : getAliasOrPath(config, aliasOrPath);
93
94
  })))).flat();
@@ -101,7 +102,7 @@ function getExecutionTime(startedAt) {
101
102
  exports.getExecutionTime = getExecutionTime;
102
103
  function printExecutionTime(commandName, startedAt, api) {
103
104
  const elapsed = getExecutionTime(startedAt);
104
- process.stderr.write(colorette_1.gray(`\n${api}: ${commandName} processed in ${elapsed}\n\n`));
105
+ process.stderr.write((0, colorette_1.gray)(`\n${api}: ${commandName} processed in ${elapsed}\n\n`));
105
106
  }
106
107
  exports.printExecutionTime = printExecutionTime;
107
108
  function pathToFilename(path, pathSeparator) {
@@ -152,7 +153,7 @@ function dumpBundle(obj, format, dereference) {
152
153
  }
153
154
  }
154
155
  else {
155
- return openapi_core_1.stringifyYaml(obj, {
156
+ return (0, openapi_core_1.stringifyYaml)(obj, {
156
157
  noRefs: !dereference,
157
158
  lineWidth: -1,
158
159
  });
@@ -160,7 +161,7 @@ function dumpBundle(obj, format, dereference) {
160
161
  }
161
162
  exports.dumpBundle = dumpBundle;
162
163
  function saveBundle(filename, output) {
163
- fs.mkdirSync(path_1.dirname(filename), { recursive: true });
164
+ fs.mkdirSync((0, path_1.dirname)(filename), { recursive: true });
164
165
  fs.writeFileSync(filename, output);
165
166
  }
166
167
  exports.saveBundle = saveBundle;
@@ -195,19 +196,47 @@ function promptUser(query, hideUserInput = false) {
195
196
  }
196
197
  exports.promptUser = promptUser;
197
198
  function readYaml(filename) {
198
- return openapi_core_1.parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
199
+ return (0, openapi_core_1.parseYaml)(fs.readFileSync(filename, 'utf-8'), { filename });
199
200
  }
200
201
  exports.readYaml = readYaml;
202
+ function writeToFileByExtension(data, filePath, noRefs) {
203
+ const ext = getAndValidateFileExtension(filePath);
204
+ if (ext === 'json') {
205
+ writeJson(data, filePath);
206
+ return;
207
+ }
208
+ writeYaml(data, filePath, noRefs);
209
+ }
210
+ exports.writeToFileByExtension = writeToFileByExtension;
201
211
  function writeYaml(data, filename, noRefs = false) {
202
- const content = openapi_core_1.stringifyYaml(data, { noRefs });
212
+ const content = (0, openapi_core_1.stringifyYaml)(data, { noRefs });
203
213
  if (process.env.NODE_ENV === 'test') {
204
214
  process.stderr.write(content);
205
215
  return;
206
216
  }
207
- fs.mkdirSync(path_1.dirname(filename), { recursive: true });
217
+ fs.mkdirSync((0, path_1.dirname)(filename), { recursive: true });
208
218
  fs.writeFileSync(filename, content);
209
219
  }
210
220
  exports.writeYaml = writeYaml;
221
+ function writeJson(data, filename) {
222
+ const content = JSON.stringify(data, null, 2);
223
+ if (process.env.NODE_ENV === 'test') {
224
+ process.stderr.write(content);
225
+ return;
226
+ }
227
+ fs.mkdirSync((0, path_1.dirname)(filename), { recursive: true });
228
+ fs.writeFileSync(filename, content);
229
+ }
230
+ exports.writeJson = writeJson;
231
+ function getAndValidateFileExtension(fileName) {
232
+ const ext = fileName.split('.').pop();
233
+ if (['yaml', 'yml', 'json'].includes(ext)) {
234
+ return ext;
235
+ }
236
+ process.stderr.write((0, colorette_1.yellow)(`Unsupported file extension: ${ext}. Using yaml.\n`));
237
+ return 'yaml';
238
+ }
239
+ exports.getAndValidateFileExtension = getAndValidateFileExtension;
211
240
  function pluralize(label, num) {
212
241
  if (label.endsWith('is')) {
213
242
  [label] = label.split(' ');
@@ -228,10 +257,12 @@ function handleError(e, ref) {
228
257
  return exitWithError(`Failed to parse API description at ${ref}:\n\n - ${e.message}.`);
229
258
  case CircularJSONNotSupportedError: {
230
259
  return exitWithError(`Detected circular reference which can't be converted to JSON.\n` +
231
- `Try to use ${colorette_1.blue('yaml')} output or remove ${colorette_1.blue('--dereferenced')}.`);
260
+ `Try to use ${(0, colorette_1.blue)('yaml')} output or remove ${(0, colorette_1.blue)('--dereferenced')}.`);
232
261
  }
233
262
  case SyntaxError:
234
263
  return exitWithError(`Syntax error: ${e.message} ${(_b = (_a = e.stack) === null || _a === void 0 ? void 0 : _a.split('\n\n')) === null || _b === void 0 ? void 0 : _b[0]}`);
264
+ case config_1.ConfigValidationError:
265
+ return exitWithError(e.message);
235
266
  default: {
236
267
  exitWithError(`Something went wrong when processing ${ref}:\n\n - ${e.message}.`);
237
268
  }
@@ -243,34 +274,32 @@ class HandledError extends Error {
243
274
  exports.HandledError = HandledError;
244
275
  function printLintTotals(totals, definitionsCount) {
245
276
  const ignored = totals.ignored
246
- ? colorette_1.yellow(`${totals.ignored} ${pluralize('problem is', totals.ignored)} explicitly ignored.\n\n`)
277
+ ? (0, colorette_1.yellow)(`${totals.ignored} ${pluralize('problem is', totals.ignored)} explicitly ignored.\n\n`)
247
278
  : '';
248
279
  if (totals.errors > 0) {
249
- process.stderr.write(colorette_1.red(`❌ Validation failed with ${totals.errors} ${pluralize('error', totals.errors)}${totals.warnings > 0
280
+ process.stderr.write((0, colorette_1.red)(`❌ Validation failed with ${totals.errors} ${pluralize('error', totals.errors)}${totals.warnings > 0
250
281
  ? ` and ${totals.warnings} ${pluralize('warning', totals.warnings)}`
251
282
  : ''}.\n${ignored}`));
252
283
  }
253
284
  else if (totals.warnings > 0) {
254
- process.stderr.write(colorette_1.green(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n`));
255
- process.stderr.write(colorette_1.yellow(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n${ignored}`));
285
+ process.stderr.write((0, colorette_1.green)(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n`));
286
+ process.stderr.write((0, colorette_1.yellow)(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n${ignored}`));
256
287
  }
257
288
  else {
258
- process.stderr.write(colorette_1.green(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n${ignored}`));
289
+ process.stderr.write((0, colorette_1.green)(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n${ignored}`));
259
290
  }
260
291
  if (totals.errors > 0) {
261
- process.stderr.write(colorette_1.gray(`run \`redocly lint --generate-ignore-file\` to add all problems to the ignore file.\n`));
292
+ process.stderr.write((0, colorette_1.gray)(`run \`redocly lint --generate-ignore-file\` to add all problems to the ignore file.\n`));
262
293
  }
263
294
  process.stderr.write('\n');
264
295
  }
265
296
  exports.printLintTotals = printLintTotals;
266
297
  function printConfigLintTotals(totals) {
267
298
  if (totals.errors > 0) {
268
- process.stderr.write(colorette_1.red(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}${totals.warnings > 0
269
- ? ` and ${totals.warnings} ${pluralize('warning', totals.warnings)}`
270
- : ''}.\n`));
299
+ process.stderr.write((0, colorette_1.red)(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}.`));
271
300
  }
272
301
  else if (totals.warnings > 0) {
273
- process.stderr.write(colorette_1.yellow(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n`));
302
+ process.stderr.write((0, colorette_1.yellow)(`⚠️ Your config has ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n`));
274
303
  }
275
304
  }
276
305
  exports.printConfigLintTotals = printConfigLintTotals;
@@ -280,21 +309,21 @@ function getOutputFileName(entrypoint, entries, output, ext) {
280
309
  }
281
310
  let outputFile = output;
282
311
  if (entries > 1) {
283
- ext = ext || path_1.extname(entrypoint).substring(1);
312
+ ext = ext || (0, path_1.extname)(entrypoint).substring(1);
284
313
  if (!types_1.outputExtensions.includes(ext)) {
285
314
  throw new Error(`Invalid file extension: ${ext}.`);
286
315
  }
287
- outputFile = path_1.join(output, path_1.basename(entrypoint, path_1.extname(entrypoint))) + '.' + ext;
316
+ outputFile = (0, path_1.join)(output, (0, path_1.basename)(entrypoint, (0, path_1.extname)(entrypoint))) + '.' + ext;
288
317
  }
289
318
  else {
290
319
  if (output) {
291
- ext = ext || path_1.extname(output).substring(1);
320
+ ext = ext || (0, path_1.extname)(output).substring(1);
292
321
  }
293
- ext = ext || path_1.extname(entrypoint).substring(1);
322
+ ext = ext || (0, path_1.extname)(entrypoint).substring(1);
294
323
  if (!types_1.outputExtensions.includes(ext)) {
295
324
  throw new Error(`Invalid file extension: ${ext}.`);
296
325
  }
297
- outputFile = path_1.join(path_1.dirname(outputFile), path_1.basename(outputFile, path_1.extname(outputFile))) + '.' + ext;
326
+ outputFile = (0, path_1.join)((0, path_1.dirname)(outputFile), (0, path_1.basename)(outputFile, (0, path_1.extname)(outputFile))) + '.' + ext;
298
327
  }
299
328
  return { outputFile, ext };
300
329
  }
@@ -302,13 +331,13 @@ exports.getOutputFileName = getOutputFileName;
302
331
  function printUnusedWarnings(config) {
303
332
  const { preprocessors, rules, decorators } = config.getUnusedRules();
304
333
  if (rules.length) {
305
- process.stderr.write(colorette_1.yellow(`[WARNING] Unused rules found in ${colorette_1.blue(config.configFile || '')}: ${rules.join(', ')}.\n`));
334
+ process.stderr.write((0, colorette_1.yellow)(`[WARNING] Unused rules found in ${(0, colorette_1.blue)(config.configFile || '')}: ${rules.join(', ')}.\n`));
306
335
  }
307
336
  if (preprocessors.length) {
308
- process.stderr.write(colorette_1.yellow(`[WARNING] Unused preprocessors found in ${colorette_1.blue(config.configFile || '')}: ${preprocessors.join(', ')}.\n`));
337
+ process.stderr.write((0, colorette_1.yellow)(`[WARNING] Unused preprocessors found in ${(0, colorette_1.blue)(config.configFile || '')}: ${preprocessors.join(', ')}.\n`));
309
338
  }
310
339
  if (decorators.length) {
311
- process.stderr.write(colorette_1.yellow(`[WARNING] Unused decorators found in ${colorette_1.blue(config.configFile || '')}: ${decorators.join(', ')}.\n`));
340
+ process.stderr.write((0, colorette_1.yellow)(`[WARNING] Unused decorators found in ${(0, colorette_1.blue)(config.configFile || '')}: ${decorators.join(', ')}.\n`));
312
341
  }
313
342
  if (rules.length || preprocessors.length) {
314
343
  process.stderr.write(`Check the spelling and verify the added plugin prefix.\n`);
@@ -316,7 +345,7 @@ function printUnusedWarnings(config) {
316
345
  }
317
346
  exports.printUnusedWarnings = printUnusedWarnings;
318
347
  function exitWithError(message) {
319
- process.stderr.write(colorette_1.red(message) + '\n\n');
348
+ process.stderr.write((0, colorette_1.red)(message) + '\n\n');
320
349
  throw new HandledError(message);
321
350
  }
322
351
  exports.exitWithError = exitWithError;
@@ -324,14 +353,14 @@ exports.exitWithError = exitWithError;
324
353
  * Checks if dir is subdir of parent
325
354
  */
326
355
  function isSubdir(parent, dir) {
327
- const relativePath = path_1.relative(parent, dir);
328
- return !!relativePath && !/^..($|\/)/.test(relativePath) && !path_1.isAbsolute(relativePath);
356
+ const relativePath = (0, path_1.relative)(parent, dir);
357
+ return !!relativePath && !/^..($|\/)/.test(relativePath) && !(0, path_1.isAbsolute)(relativePath);
329
358
  }
330
359
  exports.isSubdir = isSubdir;
331
360
  function loadConfigAndHandleErrors(options = {}) {
332
361
  return __awaiter(this, void 0, void 0, function* () {
333
362
  try {
334
- return yield openapi_core_1.loadConfig(options);
363
+ return yield (0, openapi_core_1.loadConfig)(options);
335
364
  }
336
365
  catch (e) {
337
366
  handleError(e, '');
@@ -398,7 +427,7 @@ function sortOas3Keys(document) {
398
427
  }
399
428
  function checkIfRulesetExist(rules) {
400
429
  const ruleset = Object.assign(Object.assign(Object.assign({}, rules.oas2), rules.oas3_0), rules.oas3_0);
401
- if (utils_1.isEmptyObject(ruleset)) {
430
+ if ((0, utils_1.isEmptyObject)(ruleset)) {
402
431
  exitWithError('⚠️ No rules were configured. Learn how to configure rules: https://redocly.com/docs/cli/rules/');
403
432
  }
404
433
  }
@@ -425,7 +454,7 @@ function sendTelemetry(argv, exit_code, has_config) {
425
454
  command,
426
455
  arguments: cleanArgs(args),
427
456
  node_version: process.version,
428
- npm_version: child_process_1.execSync('npm -v').toString().replace('\n', ''),
457
+ npm_version: (0, child_process_1.execSync)('npm -v').toString().replace('\n', ''),
429
458
  version: update_version_notifier_1.version,
430
459
  exit_code,
431
460
  environment: process.env.REDOCLY_ENVIRONMENT,
@@ -433,7 +462,7 @@ function sendTelemetry(argv, exit_code, has_config) {
433
462
  raw_input: cleanRawInput(process.argv.slice(2)),
434
463
  has_config,
435
464
  };
436
- yield fetch_with_timeout_1.default(`https://api.redocly.com/registry/telemetry/cli`, {
465
+ yield (0, fetch_with_timeout_1.default)(`https://api.redocly.com/registry/telemetry/cli`, {
437
466
  method: 'POST',
438
467
  headers: {
439
468
  'content-type': 'application/json',
@@ -457,7 +486,7 @@ function cleanString(value) {
457
486
  if (!value) {
458
487
  return value;
459
488
  }
460
- if (openapi_core_1.isAbsoluteUrl(value)) {
489
+ if ((0, openapi_core_1.isAbsoluteUrl)(value)) {
461
490
  return value.split('://')[0] + '://url';
462
491
  }
463
492
  if (isFile(value)) {
package/lib/wrapper.js CHANGED
@@ -20,15 +20,15 @@ function commandWrapper(commandHandler) {
20
20
  let hasConfig;
21
21
  let telemetry;
22
22
  try {
23
- if (argv.config && !openapi_core_1.doesYamlFileExist(argv.config)) {
24
- utils_1.exitWithError('Please, provide valid path to the configuration file');
23
+ if (argv.config && !(0, openapi_core_1.doesYamlFileExist)(argv.config)) {
24
+ (0, utils_1.exitWithError)('Please, provide valid path to the configuration file');
25
25
  }
26
- const config = (yield utils_1.loadConfigAndHandleErrors({
26
+ const config = (yield (0, utils_1.loadConfigAndHandleErrors)({
27
27
  configPath: argv.config,
28
28
  customExtends: argv.extends,
29
29
  region: argv.region,
30
30
  files: argv.files,
31
- processRawConfig: lint_1.lintConfigCallback(argv, update_version_notifier_1.version),
31
+ processRawConfig: (0, lint_1.lintConfigCallback)(argv, update_version_notifier_1.version),
32
32
  }));
33
33
  telemetry = config.telemetry;
34
34
  hasConfig = !config.styleguide.recommendedFallback;
@@ -41,7 +41,7 @@ function commandWrapper(commandHandler) {
41
41
  }
42
42
  finally {
43
43
  if (process.env.REDOCLY_TELEMETRY !== 'off' && telemetry !== 'off') {
44
- yield utils_1.sendTelemetry(argv, code, hasConfig);
44
+ yield (0, utils_1.sendTelemetry)(argv, code, hasConfig);
45
45
  }
46
46
  process.once('beforeExit', () => {
47
47
  process.exit(code);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/cli",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "Roman Hotsiy <roman@redoc.ly> (https://redoc.ly/)"
37
37
  ],
38
38
  "dependencies": {
39
- "@redocly/openapi-core": "1.2.1",
39
+ "@redocly/openapi-core": "1.4.0",
40
40
  "chokidar": "^3.5.1",
41
41
  "colorette": "^1.2.0",
42
42
  "core-js": "^3.32.1",
@@ -59,6 +59,6 @@
59
59
  "@types/react-dom": "^17.0.0 || ^18.2.7",
60
60
  "@types/semver": "^7.5.0",
61
61
  "@types/yargs": "17.0.5",
62
- "typescript": "^4.0.3"
62
+ "typescript": "^5.2.2"
63
63
  }
64
64
  }
@@ -31,6 +31,7 @@ export const doesYamlFileExist = jest.fn();
31
31
  export const bundleDocument = jest.fn(() => Promise.resolve({ problems: {} }));
32
32
  export const detectSpec = jest.fn();
33
33
  export const isAbsoluteUrl = jest.fn();
34
+ export const stringifyYaml = jest.fn((data) => data);
34
35
 
35
36
  export class BaseResolver {
36
37
  cache = new Map<string, Promise<Document | ResolveError>>();
@@ -17,3 +17,5 @@ export const writeYaml = jest.fn();
17
17
  export const loadConfigAndHandleErrors = jest.fn(() => ConfigFixture);
18
18
  export const checkIfRulesetExist = jest.fn();
19
19
  export const sortTopLevelKeysForOas = jest.fn((document) => document);
20
+ export const getAndValidateFileExtension = jest.fn((fileName: string) => fileName.split('.').pop());
21
+ export const writeToFileByExtension = jest.fn();
@@ -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');
@@ -108,22 +114,14 @@ describe('printConfigLintTotals', () => {
108
114
 
109
115
  it('should print errors if such exist', () => {
110
116
  printConfigLintTotals(totalProblemsMock);
111
- expect(process.stderr.write).toHaveBeenCalledWith('❌ Your config has 1 error.\n');
112
- expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error.\n');
113
- });
114
-
115
- it('should print warnign and error', () => {
116
- printConfigLintTotals({ ...totalProblemsMock, warnings: 2 });
117
- expect(process.stderr.write).toHaveBeenCalledWith(
118
- '❌ Your config has 1 error and 2 warnings.\n'
119
- );
120
- expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error and 2 warnings.\n');
117
+ expect(process.stderr.write).toHaveBeenCalledWith('❌ Your config has 1 error.');
118
+ expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error.');
121
119
  });
122
120
 
123
121
  it('should print warnign if no error', () => {
124
122
  printConfigLintTotals({ ...totalProblemsMock, errors: 0, warnings: 2 });
125
- expect(process.stderr.write).toHaveBeenCalledWith('You have 2 warnings.\n');
126
- expect(yellowColoretteMocks).toHaveBeenCalledWith('You have 2 warnings.\n');
123
+ expect(process.stderr.write).toHaveBeenCalledWith('⚠️ Your config has 2 warnings.\n');
124
+ expect(yellowColoretteMocks).toHaveBeenCalledWith('⚠️ Your config has 2 warnings.\n');
127
125
  });
128
126
 
129
127
  it('should print nothing if no error and no warnings', () => {
@@ -562,4 +560,42 @@ describe('cleanRawInput', () => {
562
560
  'redocly lint file-json --format stylish --extends=minimal --skip-rule operation-4xx-response'
563
561
  );
564
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
+ });
565
601
  });
@@ -17,6 +17,7 @@ import {
17
17
  bundleDocument,
18
18
  Referenced,
19
19
  isRef,
20
+ RuleSeverity,
20
21
  } from '@redocly/openapi-core';
21
22
 
22
23
  import {
@@ -24,9 +25,10 @@ import {
24
25
  printExecutionTime,
25
26
  handleError,
26
27
  printLintTotals,
27
- writeYaml,
28
28
  exitWithError,
29
29
  sortTopLevelKeysForOas,
30
+ getAndValidateFileExtension,
31
+ writeToFileByExtension,
30
32
  } from '../utils';
31
33
  import { isObject, isString, keysOf } from '../js-utils';
32
34
  import {
@@ -64,21 +66,24 @@ export type JoinOptions = {
64
66
  output?: string;
65
67
  config?: string;
66
68
  extends?: undefined;
67
- 'lint-config'?: undefined;
69
+ 'lint-config'?: RuleSeverity;
68
70
  };
69
71
 
70
72
  export async function handleJoin(argv: JoinOptions, config: Config, packageVersion: string) {
71
73
  const startedAt = performance.now();
74
+
72
75
  if (argv.apis.length < 2) {
73
76
  return exitWithError(`At least 2 apis should be provided. \n\n`);
74
77
  }
75
78
 
79
+ const fileExtension = getAndValidateFileExtension(argv.output || argv.apis[0]);
80
+
76
81
  const {
77
82
  'prefix-components-with-info-prop': prefixComponentsWithInfoProp,
78
83
  'prefix-tags-with-filename': prefixTagsWithFilename,
79
84
  'prefix-tags-with-info-prop': prefixTagsWithInfoProp,
80
85
  'without-x-tag-groups': withoutXTagGroups,
81
- output: specFilename = 'openapi.yaml',
86
+ output: specFilename = `openapi.${fileExtension}`,
82
87
  } = argv;
83
88
 
84
89
  const usedTagsOptions = [
@@ -228,7 +233,8 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
228
233
  return exitWithError(`Please fix conflicts before running ${yellow('join')}.`);
229
234
  }
230
235
 
231
- writeYaml(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
236
+ writeToFileByExtension(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
237
+
232
238
  printExecutionTime('join', startedAt, specFilename);
233
239
 
234
240
  function populateTags({
@@ -9,6 +9,7 @@ import {
9
9
  makeDocumentFromString,
10
10
  stringifyYaml,
11
11
  } from '@redocly/openapi-core';
12
+ import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
12
13
  import {
13
14
  checkIfRulesetExist,
14
15
  exitWithError,
@@ -134,7 +135,7 @@ export function lintConfigCallback(
134
135
  const configContent = makeDocumentFromString(stringYaml, configPath);
135
136
  const problems = await lintConfig({
136
137
  document: configContent,
137
- severity: argv['lint-config'] as ProblemSeverity,
138
+ severity: (argv['lint-config'] || 'warn') as ProblemSeverity,
138
139
  });
139
140
 
140
141
  const fileTotals = getTotals(problems);
@@ -147,5 +148,9 @@ export function lintConfigCallback(
147
148
  });
148
149
 
149
150
  printConfigLintTotals(fileTotals);
151
+
152
+ if (fileTotals.errors > 0) {
153
+ throw new ConfigValidationError();
154
+ }
150
155
  };
151
156
  }