@redocly/cli 1.0.0-beta.96
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/README.md +39 -0
- package/bin/cli.js +3 -0
- package/lib/__mocks__/utils.d.ts +17 -0
- package/lib/__mocks__/utils.js +14 -0
- package/lib/__tests__/commands/bundle.test.d.ts +1 -0
- package/lib/__tests__/commands/bundle.test.js +92 -0
- package/lib/__tests__/commands/push-region.test.d.ts +1 -0
- package/lib/__tests__/commands/push-region.test.js +55 -0
- package/lib/__tests__/commands/push.test.d.ts +1 -0
- package/lib/__tests__/commands/push.test.js +153 -0
- package/lib/__tests__/utils.test.d.ts +1 -0
- package/lib/__tests__/utils.test.js +41 -0
- package/lib/assert-node-version.d.ts +1 -0
- package/lib/assert-node-version.js +10 -0
- package/lib/commands/bundle.d.ts +19 -0
- package/lib/commands/bundle.js +128 -0
- package/lib/commands/join.d.ts +7 -0
- package/lib/commands/join.js +421 -0
- package/lib/commands/lint.d.ts +11 -0
- package/lib/commands/lint.js +80 -0
- package/lib/commands/login.d.ts +6 -0
- package/lib/commands/login.js +28 -0
- package/lib/commands/preview-docs/index.d.ts +12 -0
- package/lib/commands/preview-docs/index.js +141 -0
- package/lib/commands/preview-docs/preview-server/default.hbs +24 -0
- package/lib/commands/preview-docs/preview-server/hot.js +42 -0
- package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
- package/lib/commands/preview-docs/preview-server/preview-server.d.ts +5 -0
- package/lib/commands/preview-docs/preview-server/preview-server.js +120 -0
- package/lib/commands/preview-docs/preview-server/server.d.ts +23 -0
- package/lib/commands/preview-docs/preview-server/server.js +85 -0
- package/lib/commands/push.d.ts +25 -0
- package/lib/commands/push.js +247 -0
- package/lib/commands/split/__tests__/index.test.d.ts +1 -0
- package/lib/commands/split/__tests__/index.test.js +70 -0
- package/lib/commands/split/index.d.ts +8 -0
- package/lib/commands/split/index.js +279 -0
- package/lib/commands/split/types.d.ts +37 -0
- package/lib/commands/split/types.js +52 -0
- package/lib/commands/stats.d.ts +5 -0
- package/lib/commands/stats.js +92 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +269 -0
- package/lib/js-utils.d.ts +3 -0
- package/lib/js-utils.js +16 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +5 -0
- package/lib/utils.d.ts +28 -0
- package/lib/utils.js +260 -0
- package/package.json +54 -0
- package/src/__mocks__/utils.ts +11 -0
- package/src/__tests__/commands/bundle.test.ts +120 -0
- package/src/__tests__/commands/push-region.test.ts +51 -0
- package/src/__tests__/commands/push.test.ts +156 -0
- package/src/__tests__/utils.test.ts +50 -0
- package/src/assert-node-version.ts +8 -0
- package/src/commands/bundle.ts +178 -0
- package/src/commands/join.ts +488 -0
- package/src/commands/lint.ts +110 -0
- package/src/commands/login.ts +19 -0
- package/src/commands/preview-docs/index.ts +188 -0
- package/src/commands/preview-docs/preview-server/default.hbs +24 -0
- package/src/commands/preview-docs/preview-server/hot.js +42 -0
- package/src/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
- package/src/commands/preview-docs/preview-server/preview-server.ts +150 -0
- package/src/commands/preview-docs/preview-server/server.ts +91 -0
- package/src/commands/push.ts +355 -0
- package/src/commands/split/__tests__/fixtures/spec.json +70 -0
- package/src/commands/split/__tests__/fixtures/webhooks.json +88 -0
- package/src/commands/split/__tests__/index.test.ts +96 -0
- package/src/commands/split/index.ts +349 -0
- package/src/commands/split/types.ts +73 -0
- package/src/commands/stats.ts +115 -0
- package/src/index.ts +311 -0
- package/src/js-utils.ts +12 -0
- package/src/types.ts +13 -0
- package/src/utils.ts +300 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
package/lib/utils.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.isSubdir = exports.exitWithError = exports.printUnusedWarnings = exports.getOutputFileName = exports.printLintTotals = exports.handleError = exports.pluralize = exports.writeYaml = exports.readYaml = exports.promptUser = exports.saveBundle = exports.dumpBundle = exports.CircularJSONNotSupportedError = exports.pathToFilename = exports.printExecutionTime = exports.getExecutionTime = exports.getFallbackEntryPointsOrExit = void 0;
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
const colorette_1 = require("colorette");
|
|
15
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
16
|
+
const glob = require("glob-promise");
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const readline = require("readline");
|
|
20
|
+
const stream_1 = require("stream");
|
|
21
|
+
const openapi_core_1 = require("@redocly/openapi-core");
|
|
22
|
+
const types_1 = require("./types");
|
|
23
|
+
function getFallbackEntryPointsOrExit(argsEntrypoints, config) {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const { apis } = config;
|
|
26
|
+
const shouldFallbackToAllDefinitions = !isNotEmptyArray(argsEntrypoints) && apis && Object.keys(apis).length > 0;
|
|
27
|
+
const res = shouldFallbackToAllDefinitions
|
|
28
|
+
? Object.entries(apis).map(([alias, { root }]) => ({
|
|
29
|
+
path: path_1.resolve(getConfigDirectory(config), root),
|
|
30
|
+
alias,
|
|
31
|
+
}))
|
|
32
|
+
: (yield expandGlobsInEntrypoints(argsEntrypoints, config));
|
|
33
|
+
if (!isNotEmptyArray(res)) {
|
|
34
|
+
process.stderr.write('error: missing required argument `entrypoints`.\n');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
return res;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
exports.getFallbackEntryPointsOrExit = getFallbackEntryPointsOrExit;
|
|
41
|
+
function getConfigDirectory(config) {
|
|
42
|
+
return config.configFile ? path_1.dirname(config.configFile) : process.cwd();
|
|
43
|
+
}
|
|
44
|
+
function isNotEmptyArray(args) {
|
|
45
|
+
return Array.isArray(args) && !!args.length;
|
|
46
|
+
}
|
|
47
|
+
function getAliasOrPath(config, aliasOrPath) {
|
|
48
|
+
var _a;
|
|
49
|
+
return config.apis[aliasOrPath]
|
|
50
|
+
? { path: (_a = config.apis[aliasOrPath]) === null || _a === void 0 ? void 0 : _a.root, alias: aliasOrPath }
|
|
51
|
+
: { path: aliasOrPath };
|
|
52
|
+
}
|
|
53
|
+
function expandGlobsInEntrypoints(args, config) {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
return (yield Promise.all(args.map((aliasOrPath) => __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
return glob.hasMagic(aliasOrPath)
|
|
57
|
+
? (yield glob(aliasOrPath)).map((g) => getAliasOrPath(config, g))
|
|
58
|
+
: getAliasOrPath(config, aliasOrPath);
|
|
59
|
+
})))).flat();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function getExecutionTime(startedAt) {
|
|
63
|
+
return process.env.NODE_ENV === 'test'
|
|
64
|
+
? '<test>ms'
|
|
65
|
+
: `${Math.ceil(perf_hooks_1.performance.now() - startedAt)}ms`;
|
|
66
|
+
}
|
|
67
|
+
exports.getExecutionTime = getExecutionTime;
|
|
68
|
+
function printExecutionTime(commandName, startedAt, entrypoint) {
|
|
69
|
+
const elapsed = getExecutionTime(startedAt);
|
|
70
|
+
process.stderr.write(colorette_1.gray(`\n${entrypoint}: ${commandName} processed in ${elapsed}\n\n`));
|
|
71
|
+
}
|
|
72
|
+
exports.printExecutionTime = printExecutionTime;
|
|
73
|
+
function pathToFilename(path, pathSeparator) {
|
|
74
|
+
return path
|
|
75
|
+
.replace(/~1/g, '/')
|
|
76
|
+
.replace(/~0/g, '~')
|
|
77
|
+
.replace(/^\//, '')
|
|
78
|
+
.replace(/\//g, pathSeparator);
|
|
79
|
+
}
|
|
80
|
+
exports.pathToFilename = pathToFilename;
|
|
81
|
+
class CircularJSONNotSupportedError extends Error {
|
|
82
|
+
constructor(originalError) {
|
|
83
|
+
super(originalError.message);
|
|
84
|
+
this.originalError = originalError;
|
|
85
|
+
// Set the prototype explicitly.
|
|
86
|
+
Object.setPrototypeOf(this, CircularJSONNotSupportedError.prototype);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.CircularJSONNotSupportedError = CircularJSONNotSupportedError;
|
|
90
|
+
function dumpBundle(obj, format, dereference) {
|
|
91
|
+
if (format === 'json') {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.stringify(obj, null, 2);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
if (e.message.indexOf('circular') > -1) {
|
|
97
|
+
throw new CircularJSONNotSupportedError(e);
|
|
98
|
+
}
|
|
99
|
+
throw e;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
return openapi_core_1.stringifyYaml(obj, {
|
|
104
|
+
noRefs: !dereference,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.dumpBundle = dumpBundle;
|
|
109
|
+
function saveBundle(filename, output) {
|
|
110
|
+
fs.mkdirSync(path.dirname(filename), { recursive: true });
|
|
111
|
+
fs.writeFileSync(filename, output);
|
|
112
|
+
}
|
|
113
|
+
exports.saveBundle = saveBundle;
|
|
114
|
+
function promptUser(query, hideUserInput = false) {
|
|
115
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
let output = process.stdout;
|
|
118
|
+
let isOutputMuted = false;
|
|
119
|
+
if (hideUserInput) {
|
|
120
|
+
output = new stream_1.Writable({
|
|
121
|
+
write: (chunk, encoding, callback) => {
|
|
122
|
+
if (!isOutputMuted) {
|
|
123
|
+
process.stdout.write(chunk, encoding);
|
|
124
|
+
}
|
|
125
|
+
callback();
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const rl = readline.createInterface({
|
|
130
|
+
input: process.stdin,
|
|
131
|
+
output,
|
|
132
|
+
terminal: true,
|
|
133
|
+
historySize: hideUserInput ? 0 : 30,
|
|
134
|
+
});
|
|
135
|
+
rl.question(`${query}:\n\n `, (answer) => {
|
|
136
|
+
rl.close();
|
|
137
|
+
resolve(answer);
|
|
138
|
+
});
|
|
139
|
+
isOutputMuted = hideUserInput;
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
exports.promptUser = promptUser;
|
|
144
|
+
function readYaml(filename) {
|
|
145
|
+
return openapi_core_1.parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
|
|
146
|
+
}
|
|
147
|
+
exports.readYaml = readYaml;
|
|
148
|
+
function writeYaml(data, filename, noRefs = false) {
|
|
149
|
+
const content = openapi_core_1.stringifyYaml(data, { noRefs });
|
|
150
|
+
if (process.env.NODE_ENV === 'test') {
|
|
151
|
+
process.stderr.write(content);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
fs.writeFileSync(filename, content);
|
|
155
|
+
}
|
|
156
|
+
exports.writeYaml = writeYaml;
|
|
157
|
+
function pluralize(label, num) {
|
|
158
|
+
if (label.endsWith('is')) {
|
|
159
|
+
[label] = label.split(' ');
|
|
160
|
+
return num === 1 ? `${label} is` : `${label}s are`;
|
|
161
|
+
}
|
|
162
|
+
return num === 1 ? `${label}` : `${label}s`;
|
|
163
|
+
}
|
|
164
|
+
exports.pluralize = pluralize;
|
|
165
|
+
function handleError(e, ref) {
|
|
166
|
+
if (e instanceof openapi_core_1.ResolveError) {
|
|
167
|
+
process.stderr.write(`Failed to resolve entrypoint definition at ${ref}:\n\n - ${e.message}.\n\n`);
|
|
168
|
+
}
|
|
169
|
+
else if (e instanceof openapi_core_1.YamlParseError) {
|
|
170
|
+
process.stderr.write(`Failed to parse entrypoint definition at ${ref}:\n\n - ${e.message}.\n\n`);
|
|
171
|
+
// TODO: codeframe
|
|
172
|
+
}
|
|
173
|
+
else { // @ts-ignore
|
|
174
|
+
if (e instanceof CircularJSONNotSupportedError) {
|
|
175
|
+
process.stderr.write(colorette_1.red(`Detected circular reference which can't be converted to JSON.\n`) +
|
|
176
|
+
`Try to use ${colorette_1.blue('yaml')} output or remove ${colorette_1.blue('--dereferenced')}.\n\n`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
process.stderr.write(`Something went wrong when processing ${ref}:\n\n - ${e.message}.\n\n`);
|
|
180
|
+
throw e;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.handleError = handleError;
|
|
185
|
+
function printLintTotals(totals, definitionsCount) {
|
|
186
|
+
const ignored = totals.ignored
|
|
187
|
+
? colorette_1.yellow(`${totals.ignored} ${pluralize('problem is', totals.ignored)} explicitly ignored.\n\n`)
|
|
188
|
+
: '';
|
|
189
|
+
if (totals.errors > 0) {
|
|
190
|
+
process.stderr.write(colorette_1.red(`❌ Validation failed with ${totals.errors} ${pluralize('error', totals.errors)}${totals.warnings > 0
|
|
191
|
+
? ` and ${totals.warnings} ${pluralize('warning', totals.warnings)}`
|
|
192
|
+
: ''}.\n${ignored}`));
|
|
193
|
+
}
|
|
194
|
+
else if (totals.warnings > 0) {
|
|
195
|
+
process.stderr.write(colorette_1.green(`Woohoo! Your OpenAPI ${pluralize('definition is', definitionsCount)} valid. 🎉\n`));
|
|
196
|
+
process.stderr.write(colorette_1.yellow(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n${ignored}`));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
process.stderr.write(colorette_1.green(`Woohoo! Your OpenAPI ${pluralize('definition is', definitionsCount)} valid. 🎉\n${ignored}`));
|
|
200
|
+
}
|
|
201
|
+
if (totals.errors > 0) {
|
|
202
|
+
process.stderr.write(colorette_1.gray(`run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.\n`));
|
|
203
|
+
}
|
|
204
|
+
process.stderr.write('\n');
|
|
205
|
+
}
|
|
206
|
+
exports.printLintTotals = printLintTotals;
|
|
207
|
+
function getOutputFileName(entrypoint, entries, output, ext) {
|
|
208
|
+
if (!output) {
|
|
209
|
+
return { outputFile: 'stdout', ext: ext || 'yaml' };
|
|
210
|
+
}
|
|
211
|
+
let outputFile = output;
|
|
212
|
+
if (entries > 1) {
|
|
213
|
+
ext = ext || path_1.extname(entrypoint).substring(1);
|
|
214
|
+
if (!types_1.outputExtensions.includes(ext)) {
|
|
215
|
+
throw new Error(`Invalid file extension: ${ext}.`);
|
|
216
|
+
}
|
|
217
|
+
outputFile = path_1.join(output, path_1.basename(entrypoint, path_1.extname(entrypoint))) + '.' + ext;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
if (output) {
|
|
221
|
+
ext = ext || path_1.extname(output).substring(1);
|
|
222
|
+
}
|
|
223
|
+
ext = ext || path_1.extname(entrypoint).substring(1);
|
|
224
|
+
if (!types_1.outputExtensions.includes(ext)) {
|
|
225
|
+
throw new Error(`Invalid file extension: ${ext}.`);
|
|
226
|
+
}
|
|
227
|
+
outputFile = path_1.join(path_1.dirname(outputFile), path_1.basename(outputFile, path_1.extname(outputFile))) + '.' + ext;
|
|
228
|
+
}
|
|
229
|
+
return { outputFile, ext };
|
|
230
|
+
}
|
|
231
|
+
exports.getOutputFileName = getOutputFileName;
|
|
232
|
+
function printUnusedWarnings(config) {
|
|
233
|
+
const { preprocessors, rules, decorators } = config.getUnusedRules();
|
|
234
|
+
if (rules.length) {
|
|
235
|
+
process.stderr.write(colorette_1.yellow(`[WARNING] Unused rules found in ${colorette_1.blue(config.configFile || '')}: ${rules.join(', ')}.\n`));
|
|
236
|
+
}
|
|
237
|
+
if (preprocessors.length) {
|
|
238
|
+
process.stderr.write(colorette_1.yellow(`[WARNING] Unused preprocessors found in ${colorette_1.blue(config.configFile || '')}: ${preprocessors.join(', ')}.\n`));
|
|
239
|
+
}
|
|
240
|
+
if (decorators.length) {
|
|
241
|
+
process.stderr.write(colorette_1.yellow(`[WARNING] Unused decorators found in ${colorette_1.blue(config.configFile || '')}: ${decorators.join(', ')}.\n`));
|
|
242
|
+
}
|
|
243
|
+
if (rules.length || preprocessors.length) {
|
|
244
|
+
process.stderr.write(`Check the spelling and verify you added plugin prefix.\n`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
exports.printUnusedWarnings = printUnusedWarnings;
|
|
248
|
+
function exitWithError(message) {
|
|
249
|
+
process.stderr.write(colorette_1.red(message) + '\n\n');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
exports.exitWithError = exitWithError;
|
|
253
|
+
/**
|
|
254
|
+
* Checks if dir is subdir of parent
|
|
255
|
+
*/
|
|
256
|
+
function isSubdir(parent, dir) {
|
|
257
|
+
const relative = path.relative(parent, dir);
|
|
258
|
+
return !!relative && !/^..($|\/)/.test(relative) && !path.isAbsolute(relative);
|
|
259
|
+
}
|
|
260
|
+
exports.isSubdir = isSubdir;
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@redocly/cli",
|
|
3
|
+
"version": "1.0.0-beta.96",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"openapi": "bin/cli.js",
|
|
8
|
+
"redocly": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=12.0.0"
|
|
12
|
+
},
|
|
13
|
+
"engineStrict": true,
|
|
14
|
+
"scripts": {
|
|
15
|
+
"compile": "tsc",
|
|
16
|
+
"copy-assets": "cp src/commands/preview-docs/preview-server/default.hbs lib/commands/preview-docs/preview-server/default.hbs && cp src/commands/preview-docs/preview-server/hot.js lib/commands/preview-docs/preview-server/hot.js && cp src/commands/preview-docs/preview-server/oauth2-redirect.html lib/commands/preview-docs/preview-server/oauth2-redirect.html",
|
|
17
|
+
"prepublishOnly": "npm run copy-assets"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/Redocly/redocly-cli.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/Redocly/redocly-cli",
|
|
24
|
+
"keywords": [
|
|
25
|
+
"linter",
|
|
26
|
+
"OpenAPI",
|
|
27
|
+
"Swagger",
|
|
28
|
+
"OpenAPI linter",
|
|
29
|
+
"Swagger linter",
|
|
30
|
+
"oas"
|
|
31
|
+
],
|
|
32
|
+
"contributors": [
|
|
33
|
+
"Sergey Dubovyk <serhii@redoc.ly> (https://redoc.ly/)",
|
|
34
|
+
"Roman Hotsiy <roman@redoc.ly> (https://redoc.ly/)",
|
|
35
|
+
"Andriy Leliv <andriy@redoc.ly> (https://redoc.ly/)"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@redocly/openapi-core": "1.0.0-beta.96",
|
|
39
|
+
"@types/node": "^14.11.8",
|
|
40
|
+
"assert-node-version": "^1.0.3",
|
|
41
|
+
"chokidar": "^3.5.1",
|
|
42
|
+
"colorette": "^1.2.0",
|
|
43
|
+
"glob": "^7.1.6",
|
|
44
|
+
"glob-promise": "^3.4.0",
|
|
45
|
+
"handlebars": "^4.7.6",
|
|
46
|
+
"portfinder": "^1.0.26",
|
|
47
|
+
"simple-websocket": "^9.0.0",
|
|
48
|
+
"yargs": "17.0.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/yargs": "16.0.2",
|
|
52
|
+
"typescript": "^4.0.3"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const getFallbackEntryPointsOrExit = jest.fn((entrypoints) => entrypoints.map(() => ({ path: '' })));
|
|
2
|
+
export const getTotals = jest.fn(() => ({ errors: 0 }));
|
|
3
|
+
export const dumpBundle = jest.fn(() => '');
|
|
4
|
+
export const slash = jest.fn();
|
|
5
|
+
export const pluralize = jest.fn();
|
|
6
|
+
export const getExecutionTime = jest.fn();
|
|
7
|
+
export const printExecutionTime = jest.fn();
|
|
8
|
+
export const printUnusedWarnings = jest.fn();
|
|
9
|
+
export const printLintTotals = jest.fn();
|
|
10
|
+
export const getOutputFileName = jest.fn(() => ({ outputFile: 'test.yaml', ext: 'yaml' }));
|
|
11
|
+
export const handleError = jest.fn();
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { lint, bundle, getTotals, getMergedConfig } from '@redocly/openapi-core';
|
|
2
|
+
|
|
3
|
+
import { handleBundle } from '../../commands/bundle';
|
|
4
|
+
import SpyInstance = jest.SpyInstance;
|
|
5
|
+
|
|
6
|
+
jest.mock('@redocly/openapi-core');
|
|
7
|
+
jest.mock('../../utils');
|
|
8
|
+
|
|
9
|
+
(getMergedConfig as jest.Mock).mockImplementation(config => config)
|
|
10
|
+
|
|
11
|
+
describe('bundle', () => {
|
|
12
|
+
let processExitMock: SpyInstance;
|
|
13
|
+
let exitCb: any;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
processExitMock = jest.spyOn(process, 'exit').mockImplementation();
|
|
17
|
+
jest.spyOn(process, 'once').mockImplementation((_e, cb) => {
|
|
18
|
+
exitCb = cb;
|
|
19
|
+
return process.on(_e, cb);
|
|
20
|
+
});
|
|
21
|
+
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
(lint as jest.Mock).mockClear();
|
|
26
|
+
(bundle as jest.Mock).mockClear();
|
|
27
|
+
(getTotals as jest.Mock).mockClear();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('bundles definitions w/o linting', async () => {
|
|
31
|
+
const entrypoints = ['foo.yaml', 'bar.yaml'];
|
|
32
|
+
|
|
33
|
+
await handleBundle(
|
|
34
|
+
{
|
|
35
|
+
entrypoints,
|
|
36
|
+
ext: 'yaml',
|
|
37
|
+
format: 'codeframe',
|
|
38
|
+
},
|
|
39
|
+
'1.0.0',
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(lint).toBeCalledTimes(0);
|
|
43
|
+
expect(bundle).toBeCalledTimes(entrypoints.length);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('exits with code 0 when bundles definitions', async () => {
|
|
47
|
+
const entrypoints = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
|
48
|
+
|
|
49
|
+
await handleBundle(
|
|
50
|
+
{
|
|
51
|
+
entrypoints,
|
|
52
|
+
ext: 'yaml',
|
|
53
|
+
format: 'codeframe',
|
|
54
|
+
},
|
|
55
|
+
'1.0.0',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
exitCb?.();
|
|
59
|
+
expect(processExitMock).toHaveBeenCalledWith(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('bundles definitions w/ linting', async () => {
|
|
63
|
+
const entrypoints = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
|
64
|
+
|
|
65
|
+
await handleBundle(
|
|
66
|
+
{
|
|
67
|
+
entrypoints,
|
|
68
|
+
ext: 'yaml',
|
|
69
|
+
format: 'codeframe',
|
|
70
|
+
lint: true,
|
|
71
|
+
},
|
|
72
|
+
'1.0.0',
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(lint).toBeCalledTimes(entrypoints.length);
|
|
76
|
+
expect(bundle).toBeCalledTimes(entrypoints.length);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('exits with code 0 when bundles definitions w/linting w/o errors', async () => {
|
|
80
|
+
const entrypoints = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
|
81
|
+
|
|
82
|
+
await handleBundle(
|
|
83
|
+
{
|
|
84
|
+
entrypoints,
|
|
85
|
+
ext: 'yaml',
|
|
86
|
+
format: 'codeframe',
|
|
87
|
+
lint: true,
|
|
88
|
+
},
|
|
89
|
+
'1.0.0',
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
exitCb?.();
|
|
93
|
+
expect(processExitMock).toHaveBeenCalledWith(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('exits with code 1 when bundles definitions w/linting w/errors', async () => {
|
|
97
|
+
const entrypoints = ['foo.yaml'];
|
|
98
|
+
|
|
99
|
+
(getTotals as jest.Mock).mockReturnValue({
|
|
100
|
+
errors: 1,
|
|
101
|
+
warnings: 0,
|
|
102
|
+
ignored: 0
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await handleBundle(
|
|
106
|
+
{
|
|
107
|
+
entrypoints,
|
|
108
|
+
ext: 'yaml',
|
|
109
|
+
format: 'codeframe',
|
|
110
|
+
lint: true,
|
|
111
|
+
},
|
|
112
|
+
'1.0.0',
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(lint).toBeCalledTimes(entrypoints.length);
|
|
116
|
+
exitCb?.();
|
|
117
|
+
expect(processExitMock).toHaveBeenCalledWith(1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getMergedConfig } from '@redocly/openapi-core';
|
|
2
|
+
import { handlePush } from '../../commands/push';
|
|
3
|
+
import { promptClientToken } from '../../commands/login';
|
|
4
|
+
|
|
5
|
+
jest.mock('fs');
|
|
6
|
+
jest.mock('node-fetch', () => ({
|
|
7
|
+
default: jest.fn(() => ({
|
|
8
|
+
ok: true,
|
|
9
|
+
json: jest.fn().mockResolvedValue({}),
|
|
10
|
+
})),
|
|
11
|
+
}));
|
|
12
|
+
jest.mock('@redocly/openapi-core');
|
|
13
|
+
jest.mock('../../commands/login');
|
|
14
|
+
jest.mock('../../utils');
|
|
15
|
+
|
|
16
|
+
(getMergedConfig as jest.Mock).mockImplementation((config) => config);
|
|
17
|
+
|
|
18
|
+
const mockPromptClientToken = promptClientToken as jest.MockedFunction<typeof promptClientToken>;
|
|
19
|
+
|
|
20
|
+
describe('push-with-region', () => {
|
|
21
|
+
const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
|
|
22
|
+
redoclyClient.isAuthorizedWithRedoclyByRegion = jest.fn().mockResolvedValue(false);
|
|
23
|
+
|
|
24
|
+
beforeAll(() => {
|
|
25
|
+
jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should call login with default domain when region is US', async () => {
|
|
29
|
+
redoclyClient.domain = 'redoc.ly';
|
|
30
|
+
await handlePush({
|
|
31
|
+
upsert: true,
|
|
32
|
+
entrypoint: 'spec.json',
|
|
33
|
+
destination: '@org/my-api@1.0.0',
|
|
34
|
+
branchName: 'test',
|
|
35
|
+
});
|
|
36
|
+
expect(mockPromptClientToken).toBeCalledTimes(1);
|
|
37
|
+
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should call login with EU domain when region is EU', async () => {
|
|
41
|
+
redoclyClient.domain = 'eu.redocly.com';
|
|
42
|
+
await handlePush({
|
|
43
|
+
upsert: true,
|
|
44
|
+
entrypoint: 'spec.json',
|
|
45
|
+
destination: '@org/my-api@1.0.0',
|
|
46
|
+
branchName: 'test',
|
|
47
|
+
});
|
|
48
|
+
expect(mockPromptClientToken).toBeCalledTimes(1);
|
|
49
|
+
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Config, getMergedConfig } from '@redocly/openapi-core';
|
|
2
|
+
import {
|
|
3
|
+
getApiEntrypoint,
|
|
4
|
+
getDestinationProps,
|
|
5
|
+
handlePush,
|
|
6
|
+
transformPush,
|
|
7
|
+
} from '../../commands/push';
|
|
8
|
+
|
|
9
|
+
jest.mock('fs');
|
|
10
|
+
jest.mock('node-fetch', () => ({
|
|
11
|
+
default: jest.fn(() => ({
|
|
12
|
+
ok: true,
|
|
13
|
+
json: jest.fn().mockResolvedValue({}),
|
|
14
|
+
})),
|
|
15
|
+
}));
|
|
16
|
+
jest.mock('@redocly/openapi-core');
|
|
17
|
+
jest.mock('../../utils');
|
|
18
|
+
|
|
19
|
+
(getMergedConfig as jest.Mock).mockImplementation((config) => config);
|
|
20
|
+
|
|
21
|
+
describe('push', () => {
|
|
22
|
+
const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('pushes definition', async () => {
|
|
29
|
+
await handlePush({
|
|
30
|
+
upsert: true,
|
|
31
|
+
entrypoint: 'spec.json',
|
|
32
|
+
destination: '@org/my-api@1.0.0',
|
|
33
|
+
branchName: 'test',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(redoclyClient.registryApi.prepareFileUpload).toBeCalledTimes(1);
|
|
37
|
+
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
|
38
|
+
expect(redoclyClient.registryApi.pushApi).toHaveBeenLastCalledWith({
|
|
39
|
+
branch: 'test',
|
|
40
|
+
filePaths: ['filePath'],
|
|
41
|
+
isUpsert: true,
|
|
42
|
+
name: 'my-api',
|
|
43
|
+
organizationId: 'org',
|
|
44
|
+
rootFilePath: 'filePath',
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('transformPush', () => {
|
|
51
|
+
it('should adapt the existing syntax', () => {
|
|
52
|
+
const cb = jest.fn();
|
|
53
|
+
transformPush(cb)({
|
|
54
|
+
maybeEntrypointOrAliasOrDestination: 'openapi.yaml',
|
|
55
|
+
maybeDestination: '@testing_org/main@v1',
|
|
56
|
+
});
|
|
57
|
+
expect(cb).toBeCalledWith({
|
|
58
|
+
entrypoint: 'openapi.yaml',
|
|
59
|
+
destination: '@testing_org/main@v1',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
it('should adapt the existing syntax (including branchName)', () => {
|
|
63
|
+
const cb = jest.fn();
|
|
64
|
+
transformPush(cb)({
|
|
65
|
+
maybeEntrypointOrAliasOrDestination: 'openapi.yaml',
|
|
66
|
+
maybeDestination: '@testing_org/main@v1',
|
|
67
|
+
maybeBranchName: 'other',
|
|
68
|
+
});
|
|
69
|
+
expect(cb).toBeCalledWith({
|
|
70
|
+
entrypoint: 'openapi.yaml',
|
|
71
|
+
destination: '@testing_org/main@v1',
|
|
72
|
+
branchName: 'other',
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
it('should use --branch option firstly', () => {
|
|
76
|
+
const cb = jest.fn();
|
|
77
|
+
transformPush(cb)({
|
|
78
|
+
maybeEntrypointOrAliasOrDestination: 'openapi.yaml',
|
|
79
|
+
maybeDestination: '@testing_org/main@v1',
|
|
80
|
+
maybeBranchName: 'other',
|
|
81
|
+
branch: 'priority-branch',
|
|
82
|
+
});
|
|
83
|
+
expect(cb).toBeCalledWith({
|
|
84
|
+
entrypoint: 'openapi.yaml',
|
|
85
|
+
destination: '@testing_org/main@v1',
|
|
86
|
+
branchName: 'priority-branch',
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
it('should work for a destination only', () => {
|
|
90
|
+
const cb = jest.fn();
|
|
91
|
+
transformPush(cb)({
|
|
92
|
+
maybeEntrypointOrAliasOrDestination: '@testing_org/main@v1',
|
|
93
|
+
});
|
|
94
|
+
expect(cb).toBeCalledWith({
|
|
95
|
+
destination: '@testing_org/main@v1',
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it('should accept aliases for the old syntax', () => {
|
|
99
|
+
const cb = jest.fn();
|
|
100
|
+
transformPush(cb)({
|
|
101
|
+
maybeEntrypointOrAliasOrDestination: 'alias',
|
|
102
|
+
maybeDestination: '@testing_org/main@v1',
|
|
103
|
+
});
|
|
104
|
+
expect(cb).toBeCalledWith({
|
|
105
|
+
destination: '@testing_org/main@v1',
|
|
106
|
+
entrypoint: 'alias',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
it('should accept no arguments at all', () => {
|
|
110
|
+
const cb = jest.fn();
|
|
111
|
+
transformPush(cb)({});
|
|
112
|
+
expect(cb).toBeCalledWith({});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('getDestinationProps', () => {
|
|
117
|
+
it('should get valid destination props for the full destination syntax', () => {
|
|
118
|
+
expect(getDestinationProps('@testing_org/main@v1', 'org-from-config')).toEqual([
|
|
119
|
+
'testing_org',
|
|
120
|
+
'main',
|
|
121
|
+
'v1',
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
it('should fallback the organizationId from a config for the short destination syntax', () => {
|
|
125
|
+
expect(getDestinationProps('main@v1', 'org-from-config')).toEqual([
|
|
126
|
+
'org-from-config',
|
|
127
|
+
'main',
|
|
128
|
+
'v1',
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
it('should fallback the organizationId from a config if no destination provided', () => {
|
|
132
|
+
expect(getDestinationProps(undefined, 'org-from-config')).toEqual(['org-from-config']);
|
|
133
|
+
});
|
|
134
|
+
it('should return empty organizationId if there is no one found', () => {
|
|
135
|
+
expect(getDestinationProps('main@v1', undefined)).toEqual([, 'main', 'v1']);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('getApiEntrypoint', () => {
|
|
140
|
+
let config: Config = {
|
|
141
|
+
apis: {
|
|
142
|
+
'main@v1': {
|
|
143
|
+
root: 'openapi.yaml',
|
|
144
|
+
},
|
|
145
|
+
main: {
|
|
146
|
+
root: 'latest.yaml',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
} as unknown as Config;
|
|
150
|
+
it('should resolve the correct api for a valid name & version', () => {
|
|
151
|
+
expect(getApiEntrypoint({ name: 'main', version: 'v1', config })).toEqual('openapi.yaml');
|
|
152
|
+
});
|
|
153
|
+
it('should resolve the latest version of api if there is no matching version', () => {
|
|
154
|
+
expect(getApiEntrypoint({ name: 'main', version: 'latest', config })).toEqual('latest.yaml');
|
|
155
|
+
});
|
|
156
|
+
});
|