@redocly/cli 1.3.0 → 1.4.1

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 +19 -0
  2. package/README.md +15 -7
  3. package/lib/__mocks__/@redocly/openapi-core.d.ts +1 -0
  4. package/lib/__mocks__/@redocly/openapi-core.js +4 -3
  5. package/lib/__mocks__/utils.d.ts +2 -0
  6. package/lib/__mocks__/utils.js +3 -1
  7. package/lib/__tests__/commands/build-docs.test.js +2 -2
  8. package/lib/__tests__/commands/bundle.test.js +7 -7
  9. package/lib/__tests__/commands/join.test.js +25 -18
  10. package/lib/__tests__/commands/lint.test.js +15 -15
  11. package/lib/__tests__/commands/push-region.test.js +2 -2
  12. package/lib/__tests__/commands/push.test.js +30 -30
  13. package/lib/__tests__/fetch-with-timeout.test.js +2 -2
  14. package/lib/__tests__/utils.test.js +63 -32
  15. package/lib/__tests__/wrapper.test.js +3 -3
  16. package/lib/assert-node-version.js +1 -1
  17. package/lib/commands/build-docs/index.js +9 -9
  18. package/lib/commands/build-docs/types.d.ts +2 -2
  19. package/lib/commands/build-docs/utils.js +10 -10
  20. package/lib/commands/bundle.d.ts +1 -1
  21. package/lib/commands/bundle.js +25 -25
  22. package/lib/commands/join.d.ts +1 -1
  23. package/lib/commands/join.js +49 -48
  24. package/lib/commands/lint.d.ts +1 -1
  25. package/lib/commands/lint.js +22 -22
  26. package/lib/commands/login.d.ts +1 -1
  27. package/lib/commands/login.js +3 -3
  28. package/lib/commands/preview-docs/index.d.ts +1 -1
  29. package/lib/commands/preview-docs/index.js +7 -7
  30. package/lib/commands/preview-docs/preview-server/hot.js +19 -2
  31. package/lib/commands/preview-docs/preview-server/preview-server.js +15 -14
  32. package/lib/commands/preview-docs/preview-server/server.d.ts +3 -1
  33. package/lib/commands/preview-docs/preview-server/server.js +2 -2
  34. package/lib/commands/push.d.ts +2 -2
  35. package/lib/commands/push.js +31 -31
  36. package/lib/commands/split/__tests__/index.test.js +9 -9
  37. package/lib/commands/split/index.d.ts +2 -2
  38. package/lib/commands/split/index.js +41 -40
  39. package/lib/commands/split/types.d.ts +2 -2
  40. package/lib/commands/split/types.js +2 -2
  41. package/lib/commands/stats.d.ts +1 -1
  42. package/lib/commands/stats.js +9 -9
  43. package/lib/fetch-with-timeout.js +5 -2
  44. package/lib/index.js +11 -12
  45. package/lib/types.d.ts +6 -6
  46. package/lib/update-version-notifier.js +18 -18
  47. package/lib/utils.d.ts +6 -3
  48. package/lib/utils.js +66 -38
  49. package/lib/wrapper.js +5 -5
  50. package/package.json +4 -3
  51. package/src/__mocks__/@redocly/openapi-core.ts +1 -0
  52. package/src/__mocks__/utils.ts +2 -0
  53. package/src/__tests__/commands/join.test.ts +37 -7
  54. package/src/__tests__/utils.test.ts +45 -1
  55. package/src/commands/join.ts +8 -3
  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 +0 -1
  63. package/src/utils.ts +40 -1
  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");
@@ -46,7 +46,7 @@ function getFallbackApisOrExit(argsApis, config) {
46
46
  const filteredInvalidEntrypoints = res.filter(({ path }) => !isApiPathValid(path));
47
47
  if (isNotEmptyArray(filteredInvalidEntrypoints)) {
48
48
  for (const { path } of filteredInvalidEntrypoints) {
49
- 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`)}`));
50
50
  }
51
51
  exitWithError('Please provide a valid path.');
52
52
  }
@@ -55,7 +55,7 @@ function getFallbackApisOrExit(argsApis, config) {
55
55
  }
56
56
  exports.getFallbackApisOrExit = getFallbackApisOrExit;
57
57
  function getConfigDirectory(config) {
58
- return config.configFile ? path_1.dirname(config.configFile) : process.cwd();
58
+ return config.configFile ? (0, path_1.dirname)(config.configFile) : process.cwd();
59
59
  }
60
60
  function isNotEmptyArray(args) {
61
61
  return Array.isArray(args) && !!args.length;
@@ -65,11 +65,11 @@ function isApiPathValid(apiPath) {
65
65
  exitWithError('Path cannot be empty.');
66
66
  return;
67
67
  }
68
- return fs.existsSync(apiPath) || openapi_core_1.isAbsoluteUrl(apiPath) ? apiPath : undefined;
68
+ return fs.existsSync(apiPath) || (0, openapi_core_1.isAbsoluteUrl)(apiPath) ? apiPath : undefined;
69
69
  }
70
70
  function fallbackToAllDefinitions(apis, config) {
71
71
  return Object.entries(apis).map(([alias, { root }]) => ({
72
- 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),
73
73
  alias,
74
74
  }));
75
75
  }
@@ -81,14 +81,14 @@ function getAliasOrPath(config, aliasOrPath) {
81
81
  path: aliasOrPath,
82
82
  // find alias by path, take the first match
83
83
  alias: (_c = (_b = Object.entries(config.apis).find(([_alias, api]) => {
84
- return path_1.resolve(api.root) === path_1.resolve(aliasOrPath);
84
+ return (0, path_1.resolve)(api.root) === (0, path_1.resolve)(aliasOrPath);
85
85
  })) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : undefined,
86
86
  };
87
87
  }
88
88
  function expandGlobsInEntrypoints(args, config) {
89
89
  return __awaiter(this, void 0, void 0, function* () {
90
90
  return (yield Promise.all(args.map((aliasOrPath) => __awaiter(this, void 0, void 0, function* () {
91
- return glob.hasMagic(aliasOrPath) && !openapi_core_1.isAbsoluteUrl(aliasOrPath)
91
+ return glob.hasMagic(aliasOrPath) && !(0, openapi_core_1.isAbsoluteUrl)(aliasOrPath)
92
92
  ? (yield glob.__promisify__(aliasOrPath)).map((g) => getAliasOrPath(config, g))
93
93
  : getAliasOrPath(config, aliasOrPath);
94
94
  })))).flat();
@@ -102,7 +102,7 @@ function getExecutionTime(startedAt) {
102
102
  exports.getExecutionTime = getExecutionTime;
103
103
  function printExecutionTime(commandName, startedAt, api) {
104
104
  const elapsed = getExecutionTime(startedAt);
105
- 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`));
106
106
  }
107
107
  exports.printExecutionTime = printExecutionTime;
108
108
  function pathToFilename(path, pathSeparator) {
@@ -153,7 +153,7 @@ function dumpBundle(obj, format, dereference) {
153
153
  }
154
154
  }
155
155
  else {
156
- return openapi_core_1.stringifyYaml(obj, {
156
+ return (0, openapi_core_1.stringifyYaml)(obj, {
157
157
  noRefs: !dereference,
158
158
  lineWidth: -1,
159
159
  });
@@ -161,7 +161,7 @@ function dumpBundle(obj, format, dereference) {
161
161
  }
162
162
  exports.dumpBundle = dumpBundle;
163
163
  function saveBundle(filename, output) {
164
- fs.mkdirSync(path_1.dirname(filename), { recursive: true });
164
+ fs.mkdirSync((0, path_1.dirname)(filename), { recursive: true });
165
165
  fs.writeFileSync(filename, output);
166
166
  }
167
167
  exports.saveBundle = saveBundle;
@@ -196,19 +196,47 @@ function promptUser(query, hideUserInput = false) {
196
196
  }
197
197
  exports.promptUser = promptUser;
198
198
  function readYaml(filename) {
199
- return openapi_core_1.parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
199
+ return (0, openapi_core_1.parseYaml)(fs.readFileSync(filename, 'utf-8'), { filename });
200
200
  }
201
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;
202
211
  function writeYaml(data, filename, noRefs = false) {
203
- const content = openapi_core_1.stringifyYaml(data, { noRefs });
212
+ const content = (0, openapi_core_1.stringifyYaml)(data, { noRefs });
204
213
  if (process.env.NODE_ENV === 'test') {
205
214
  process.stderr.write(content);
206
215
  return;
207
216
  }
208
- fs.mkdirSync(path_1.dirname(filename), { recursive: true });
217
+ fs.mkdirSync((0, path_1.dirname)(filename), { recursive: true });
209
218
  fs.writeFileSync(filename, content);
210
219
  }
211
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;
212
240
  function pluralize(label, num) {
213
241
  if (label.endsWith('is')) {
214
242
  [label] = label.split(' ');
@@ -229,7 +257,7 @@ function handleError(e, ref) {
229
257
  return exitWithError(`Failed to parse API description at ${ref}:\n\n - ${e.message}.`);
230
258
  case CircularJSONNotSupportedError: {
231
259
  return exitWithError(`Detected circular reference which can't be converted to JSON.\n` +
232
- `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')}.`);
233
261
  }
234
262
  case SyntaxError:
235
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]}`);
@@ -246,32 +274,32 @@ class HandledError extends Error {
246
274
  exports.HandledError = HandledError;
247
275
  function printLintTotals(totals, definitionsCount) {
248
276
  const ignored = totals.ignored
249
- ? 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`)
250
278
  : '';
251
279
  if (totals.errors > 0) {
252
- 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
253
281
  ? ` and ${totals.warnings} ${pluralize('warning', totals.warnings)}`
254
282
  : ''}.\n${ignored}`));
255
283
  }
256
284
  else if (totals.warnings > 0) {
257
- process.stderr.write(colorette_1.green(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n`));
258
- 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}`));
259
287
  }
260
288
  else {
261
- 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}`));
262
290
  }
263
291
  if (totals.errors > 0) {
264
- 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`));
265
293
  }
266
294
  process.stderr.write('\n');
267
295
  }
268
296
  exports.printLintTotals = printLintTotals;
269
297
  function printConfigLintTotals(totals) {
270
298
  if (totals.errors > 0) {
271
- process.stderr.write(colorette_1.red(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}.`));
299
+ process.stderr.write((0, colorette_1.red)(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}.`));
272
300
  }
273
301
  else if (totals.warnings > 0) {
274
- process.stderr.write(colorette_1.yellow(`⚠️ Your config has ${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`));
275
303
  }
276
304
  }
277
305
  exports.printConfigLintTotals = printConfigLintTotals;
@@ -281,21 +309,21 @@ function getOutputFileName(entrypoint, entries, output, ext) {
281
309
  }
282
310
  let outputFile = output;
283
311
  if (entries > 1) {
284
- ext = ext || path_1.extname(entrypoint).substring(1);
312
+ ext = ext || (0, path_1.extname)(entrypoint).substring(1);
285
313
  if (!types_1.outputExtensions.includes(ext)) {
286
314
  throw new Error(`Invalid file extension: ${ext}.`);
287
315
  }
288
- 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;
289
317
  }
290
318
  else {
291
319
  if (output) {
292
- ext = ext || path_1.extname(output).substring(1);
320
+ ext = ext || (0, path_1.extname)(output).substring(1);
293
321
  }
294
- ext = ext || path_1.extname(entrypoint).substring(1);
322
+ ext = ext || (0, path_1.extname)(entrypoint).substring(1);
295
323
  if (!types_1.outputExtensions.includes(ext)) {
296
324
  throw new Error(`Invalid file extension: ${ext}.`);
297
325
  }
298
- 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;
299
327
  }
300
328
  return { outputFile, ext };
301
329
  }
@@ -303,13 +331,13 @@ exports.getOutputFileName = getOutputFileName;
303
331
  function printUnusedWarnings(config) {
304
332
  const { preprocessors, rules, decorators } = config.getUnusedRules();
305
333
  if (rules.length) {
306
- 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`));
307
335
  }
308
336
  if (preprocessors.length) {
309
- 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`));
310
338
  }
311
339
  if (decorators.length) {
312
- 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`));
313
341
  }
314
342
  if (rules.length || preprocessors.length) {
315
343
  process.stderr.write(`Check the spelling and verify the added plugin prefix.\n`);
@@ -317,7 +345,7 @@ function printUnusedWarnings(config) {
317
345
  }
318
346
  exports.printUnusedWarnings = printUnusedWarnings;
319
347
  function exitWithError(message) {
320
- process.stderr.write(colorette_1.red(message) + '\n\n');
348
+ process.stderr.write((0, colorette_1.red)(message) + '\n\n');
321
349
  throw new HandledError(message);
322
350
  }
323
351
  exports.exitWithError = exitWithError;
@@ -325,14 +353,14 @@ exports.exitWithError = exitWithError;
325
353
  * Checks if dir is subdir of parent
326
354
  */
327
355
  function isSubdir(parent, dir) {
328
- const relativePath = path_1.relative(parent, dir);
329
- 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);
330
358
  }
331
359
  exports.isSubdir = isSubdir;
332
360
  function loadConfigAndHandleErrors(options = {}) {
333
361
  return __awaiter(this, void 0, void 0, function* () {
334
362
  try {
335
- return yield openapi_core_1.loadConfig(options);
363
+ return yield (0, openapi_core_1.loadConfig)(options);
336
364
  }
337
365
  catch (e) {
338
366
  handleError(e, '');
@@ -399,7 +427,7 @@ function sortOas3Keys(document) {
399
427
  }
400
428
  function checkIfRulesetExist(rules) {
401
429
  const ruleset = Object.assign(Object.assign(Object.assign({}, rules.oas2), rules.oas3_0), rules.oas3_0);
402
- if (utils_1.isEmptyObject(ruleset)) {
430
+ if ((0, utils_1.isEmptyObject)(ruleset)) {
403
431
  exitWithError('⚠️ No rules were configured. Learn how to configure rules: https://redocly.com/docs/cli/rules/');
404
432
  }
405
433
  }
@@ -426,7 +454,7 @@ function sendTelemetry(argv, exit_code, has_config) {
426
454
  command,
427
455
  arguments: cleanArgs(args),
428
456
  node_version: process.version,
429
- npm_version: child_process_1.execSync('npm -v').toString().replace('\n', ''),
457
+ npm_version: (0, child_process_1.execSync)('npm -v').toString().replace('\n', ''),
430
458
  version: update_version_notifier_1.version,
431
459
  exit_code,
432
460
  environment: process.env.REDOCLY_ENVIRONMENT,
@@ -434,7 +462,7 @@ function sendTelemetry(argv, exit_code, has_config) {
434
462
  raw_input: cleanRawInput(process.argv.slice(2)),
435
463
  has_config,
436
464
  };
437
- 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`, {
438
466
  method: 'POST',
439
467
  headers: {
440
468
  'content-type': 'application/json',
@@ -458,7 +486,7 @@ function cleanString(value) {
458
486
  if (!value) {
459
487
  return value;
460
488
  }
461
- if (openapi_core_1.isAbsoluteUrl(value)) {
489
+ if ((0, openapi_core_1.isAbsoluteUrl)(value)) {
462
490
  return value.split('://')[0] + '://url';
463
491
  }
464
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.3.0",
3
+ "version": "1.4.1",
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.3.0",
39
+ "@redocly/openapi-core": "1.4.1",
40
40
  "chokidar": "^3.5.1",
41
41
  "colorette": "^1.2.0",
42
42
  "core-js": "^3.32.1",
@@ -44,6 +44,7 @@
44
44
  "glob": "^7.1.6",
45
45
  "handlebars": "^4.7.6",
46
46
  "mobx": "^6.0.4",
47
+ "node-fetch": "^2.6.1",
47
48
  "react": "^17.0.0 || ^18.2.0",
48
49
  "react-dom": "^17.0.0 || ^18.2.0",
49
50
  "redoc": "~2.1.2",
@@ -59,6 +60,6 @@
59
60
  "@types/react-dom": "^17.0.0 || ^18.2.7",
60
61
  "@types/semver": "^7.5.0",
61
62
  "@types/yargs": "17.0.5",
62
- "typescript": "^4.0.3"
63
+ "typescript": "^5.2.2"
63
64
  }
64
65
  }
@@ -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');
@@ -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) => {