@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.
- package/CHANGELOG.md +23 -0
- package/lib/__mocks__/@redocly/openapi-core.d.ts +1 -0
- package/lib/__mocks__/@redocly/openapi-core.js +4 -3
- package/lib/__mocks__/utils.d.ts +2 -0
- package/lib/__mocks__/utils.js +3 -1
- package/lib/__tests__/commands/build-docs.test.js +2 -2
- package/lib/__tests__/commands/bundle.test.js +7 -7
- package/lib/__tests__/commands/join.test.js +25 -18
- package/lib/__tests__/commands/lint.test.js +15 -15
- package/lib/__tests__/commands/push-region.test.js +2 -2
- package/lib/__tests__/commands/push.test.js +30 -30
- package/lib/__tests__/fetch-with-timeout.test.js +2 -2
- package/lib/__tests__/utils.test.js +67 -41
- package/lib/__tests__/wrapper.test.js +3 -3
- package/lib/assert-node-version.js +1 -1
- package/lib/commands/build-docs/index.js +9 -9
- package/lib/commands/build-docs/types.d.ts +2 -2
- package/lib/commands/build-docs/utils.js +10 -10
- package/lib/commands/bundle.d.ts +1 -1
- package/lib/commands/bundle.js +25 -25
- package/lib/commands/join.d.ts +3 -3
- package/lib/commands/join.js +49 -48
- package/lib/commands/lint.d.ts +1 -1
- package/lib/commands/lint.js +27 -23
- package/lib/commands/login.d.ts +1 -1
- package/lib/commands/login.js +3 -3
- package/lib/commands/preview-docs/index.d.ts +1 -1
- package/lib/commands/preview-docs/index.js +7 -7
- package/lib/commands/preview-docs/preview-server/hot.js +19 -2
- package/lib/commands/preview-docs/preview-server/preview-server.js +15 -14
- package/lib/commands/preview-docs/preview-server/server.d.ts +3 -1
- package/lib/commands/preview-docs/preview-server/server.js +2 -2
- package/lib/commands/push.d.ts +2 -2
- package/lib/commands/push.js +31 -31
- package/lib/commands/split/__tests__/index.test.js +9 -9
- package/lib/commands/split/index.d.ts +2 -2
- package/lib/commands/split/index.js +41 -40
- package/lib/commands/split/types.d.ts +2 -2
- package/lib/commands/split/types.js +2 -2
- package/lib/commands/stats.d.ts +1 -1
- package/lib/commands/stats.js +9 -9
- package/lib/fetch-with-timeout.js +5 -2
- package/lib/index.js +46 -12
- package/lib/types.d.ts +6 -6
- package/lib/update-version-notifier.js +18 -18
- package/lib/utils.d.ts +6 -3
- package/lib/utils.js +69 -40
- package/lib/wrapper.js +5 -5
- package/package.json +3 -3
- package/src/__mocks__/@redocly/openapi-core.ts +1 -0
- package/src/__mocks__/utils.ts +2 -0
- package/src/__tests__/commands/join.test.ts +37 -7
- package/src/__tests__/utils.test.ts +49 -13
- package/src/commands/join.ts +10 -4
- package/src/commands/lint.ts +6 -1
- package/src/commands/preview-docs/preview-server/hot.js +19 -2
- package/src/commands/preview-docs/preview-server/preview-server.ts +6 -4
- package/src/commands/preview-docs/preview-server/server.ts +2 -2
- package/src/commands/split/__tests__/index.test.ts +14 -5
- package/src/commands/split/index.ts +25 -17
- package/src/fetch-with-timeout.ts +3 -0
- package/src/index.ts +35 -1
- package/src/utils.ts +45 -9
- 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)}
|
|
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(
|
|
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.
|
|
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.
|
|
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": "^
|
|
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>>();
|
package/src/__mocks__/utils.ts
CHANGED
|
@@ -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
|
|
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(
|
|
94
|
+
expect(writeToFileByExtension).toHaveBeenCalledWith(
|
|
95
|
+
expect.any(Object),
|
|
96
|
+
'openapi.yaml',
|
|
97
|
+
expect.any(Boolean)
|
|
98
|
+
);
|
|
94
99
|
});
|
|
95
100
|
|
|
96
|
-
it('should call
|
|
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(
|
|
111
|
+
expect(writeToFileByExtension).toHaveBeenCalledWith(
|
|
112
|
+
expect.any(Object),
|
|
113
|
+
'openapi.yaml',
|
|
114
|
+
expect.any(Boolean)
|
|
115
|
+
);
|
|
107
116
|
});
|
|
108
117
|
|
|
109
|
-
it('should call
|
|
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(
|
|
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
|
|
112
|
-
expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error
|
|
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('
|
|
126
|
-
expect(yellowColoretteMocks).toHaveBeenCalledWith('
|
|
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
|
});
|
package/src/commands/join.ts
CHANGED
|
@@ -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'?:
|
|
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 =
|
|
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
|
-
|
|
236
|
+
writeToFileByExtension(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
|
|
237
|
+
|
|
232
238
|
printExecutionTime('join', startedAt, specFilename);
|
|
233
239
|
|
|
234
240
|
function populateTags({
|
package/src/commands/lint.ts
CHANGED
|
@@ -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
|
}
|