@redocly/cli 1.22.1 → 1.23.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 +12 -0
- package/lib/__tests__/commands/bundle.test.js +110 -1
- package/lib/__tests__/fetch-with-timeout.test.js +29 -5
- package/lib/__tests__/utils.test.js +54 -32
- package/lib/cms/api/__tests__/api.client.test.js +17 -9
- package/lib/cms/api/api-client.d.ts +26 -7
- package/lib/cms/api/api-client.js +103 -72
- package/lib/cms/commands/__tests__/push-status.test.js +1 -1
- package/lib/cms/commands/__tests__/push.test.js +41 -1
- package/lib/cms/commands/__tests__/utils.test.js +1 -1
- package/lib/cms/commands/push-status.d.ts +1 -1
- package/lib/cms/commands/push-status.js +3 -7
- package/lib/cms/commands/push.js +4 -4
- package/lib/cms/commands/utils.d.ts +3 -0
- package/lib/cms/commands/utils.js +8 -1
- package/lib/commands/bundle.d.ts +1 -1
- package/lib/commands/bundle.js +9 -9
- package/lib/commands/eject.d.ts +1 -1
- package/lib/commands/eject.js +1 -1
- package/lib/commands/preview-project/index.js +1 -1
- package/lib/index.js +1 -2
- package/lib/types.d.ts +1 -0
- package/lib/utils/__mocks__/miscellaneous.d.ts +1 -0
- package/lib/utils/__mocks__/miscellaneous.js +2 -1
- package/lib/utils/fetch-with-timeout.d.ts +6 -1
- package/lib/utils/fetch-with-timeout.js +16 -14
- package/lib/utils/miscellaneous.d.ts +4 -1
- package/lib/utils/miscellaneous.js +24 -29
- package/lib/utils/update-version-notifier.js +8 -4
- package/package.json +2 -2
- package/src/__tests__/commands/bundle.test.ts +131 -4
- package/src/__tests__/fetch-with-timeout.test.ts +36 -6
- package/src/__tests__/utils.test.ts +58 -33
- package/src/cms/api/__tests__/api.client.test.ts +20 -11
- package/src/cms/api/api-client.ts +158 -91
- package/src/cms/commands/__tests__/push-status.test.ts +1 -1
- package/src/cms/commands/__tests__/push.test.ts +49 -2
- package/src/cms/commands/__tests__/utils.test.ts +1 -1
- package/src/cms/commands/push-status.ts +5 -9
- package/src/cms/commands/push.ts +5 -6
- package/src/cms/commands/utils.ts +15 -1
- package/src/commands/bundle.ts +14 -12
- package/src/commands/eject.ts +2 -2
- package/src/commands/preview-project/index.ts +1 -1
- package/src/index.ts +1 -2
- package/src/types.ts +1 -0
- package/src/utils/__mocks__/miscellaneous.ts +1 -0
- package/src/utils/fetch-with-timeout.ts +23 -14
- package/src/utils/miscellaneous.ts +32 -37
- package/src/utils/update-version-notifier.ts +11 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -50,12 +50,12 @@ const push_1 = require("../commands/push");
|
|
|
50
50
|
const fetch_with_timeout_1 = require("./fetch-with-timeout");
|
|
51
51
|
async function getFallbackApisOrExit(argsApis, config) {
|
|
52
52
|
const { apis } = config;
|
|
53
|
-
const shouldFallbackToAllDefinitions = !isNotEmptyArray(argsApis) &&
|
|
53
|
+
const shouldFallbackToAllDefinitions = !(0, utils_1.isNotEmptyArray)(argsApis) && (0, utils_1.isNotEmptyObject)(apis);
|
|
54
54
|
const res = shouldFallbackToAllDefinitions
|
|
55
55
|
? fallbackToAllDefinitions(apis, config)
|
|
56
56
|
: await expandGlobsInEntrypoints(argsApis, config);
|
|
57
57
|
const filteredInvalidEntrypoints = res.filter(({ path }) => !isApiPathValid(path));
|
|
58
|
-
if (isNotEmptyArray(filteredInvalidEntrypoints)) {
|
|
58
|
+
if ((0, utils_1.isNotEmptyArray)(filteredInvalidEntrypoints)) {
|
|
59
59
|
for (const { path } of filteredInvalidEntrypoints) {
|
|
60
60
|
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`)}`));
|
|
61
61
|
}
|
|
@@ -66,9 +66,6 @@ async function getFallbackApisOrExit(argsApis, config) {
|
|
|
66
66
|
function getConfigDirectory(config) {
|
|
67
67
|
return config.configFile ? (0, path_1.dirname)(config.configFile) : process.cwd();
|
|
68
68
|
}
|
|
69
|
-
function isNotEmptyArray(args) {
|
|
70
|
-
return Array.isArray(args) && !!args.length;
|
|
71
|
-
}
|
|
72
69
|
function isApiPathValid(apiPath) {
|
|
73
70
|
if (!apiPath.trim()) {
|
|
74
71
|
exitWithError('Path cannot be empty.');
|
|
@@ -77,14 +74,20 @@ function isApiPathValid(apiPath) {
|
|
|
77
74
|
return fs.existsSync(apiPath) || (0, openapi_core_1.isAbsoluteUrl)(apiPath) ? apiPath : undefined;
|
|
78
75
|
}
|
|
79
76
|
function fallbackToAllDefinitions(apis, config) {
|
|
80
|
-
return Object.entries(apis).map(([alias, { root }]) => ({
|
|
77
|
+
return Object.entries(apis).map(([alias, { root, output }]) => ({
|
|
81
78
|
path: (0, openapi_core_1.isAbsoluteUrl)(root) ? root : (0, path_1.resolve)(getConfigDirectory(config), root),
|
|
82
79
|
alias,
|
|
80
|
+
output: output && (0, path_1.resolve)(getConfigDirectory(config), output),
|
|
83
81
|
}));
|
|
84
82
|
}
|
|
85
83
|
function getAliasOrPath(config, aliasOrPath) {
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
const aliasApi = config.apis[aliasOrPath];
|
|
85
|
+
return aliasApi
|
|
86
|
+
? {
|
|
87
|
+
path: aliasApi.root,
|
|
88
|
+
alias: aliasOrPath,
|
|
89
|
+
output: aliasApi.output,
|
|
90
|
+
}
|
|
88
91
|
: {
|
|
89
92
|
path: aliasOrPath,
|
|
90
93
|
// find alias by path, take the first match
|
|
@@ -93,8 +96,8 @@ function getAliasOrPath(config, aliasOrPath) {
|
|
|
93
96
|
})?.[0] ?? undefined,
|
|
94
97
|
};
|
|
95
98
|
}
|
|
96
|
-
async function expandGlobsInEntrypoints(
|
|
97
|
-
return (await Promise.all(
|
|
99
|
+
async function expandGlobsInEntrypoints(argApis, config) {
|
|
100
|
+
return (await Promise.all(argApis.map(async (aliasOrPath) => {
|
|
98
101
|
return glob.hasMagic(aliasOrPath) && !(0, openapi_core_1.isAbsoluteUrl)(aliasOrPath)
|
|
99
102
|
? (await (0, util_1.promisify)(glob)(aliasOrPath)).map((g) => getAliasOrPath(config, g))
|
|
100
103
|
: getAliasOrPath(config, aliasOrPath);
|
|
@@ -303,28 +306,19 @@ function printConfigLintTotals(totals, command) {
|
|
|
303
306
|
process.stderr.write((0, colorette_1.green)('✅ Your config is valid.\n'));
|
|
304
307
|
}
|
|
305
308
|
}
|
|
306
|
-
function getOutputFileName(entrypoint,
|
|
307
|
-
if (!output) {
|
|
308
|
-
return { outputFile: 'stdout', ext: ext || 'yaml' };
|
|
309
|
-
}
|
|
309
|
+
function getOutputFileName(entrypoint, output, ext) {
|
|
310
310
|
let outputFile = output;
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
if (!types_1.outputExtensions.includes(ext)) {
|
|
314
|
-
throw new Error(`Invalid file extension: ${ext}.`);
|
|
315
|
-
}
|
|
316
|
-
outputFile = (0, path_1.join)(output, (0, path_1.basename)(entrypoint, (0, path_1.extname)(entrypoint))) + '.' + ext;
|
|
311
|
+
if (!outputFile) {
|
|
312
|
+
return { ext: ext || 'yaml' };
|
|
317
313
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
throw new Error(`Invalid file extension: ${ext}.`);
|
|
325
|
-
}
|
|
326
|
-
outputFile = (0, path_1.join)((0, path_1.dirname)(outputFile), (0, path_1.basename)(outputFile, (0, path_1.extname)(outputFile))) + '.' + ext;
|
|
314
|
+
if (outputFile) {
|
|
315
|
+
ext = ext || (0, path_1.extname)(outputFile).substring(1);
|
|
316
|
+
}
|
|
317
|
+
ext = ext || (0, path_1.extname)(entrypoint).substring(1);
|
|
318
|
+
if (!types_1.outputExtensions.includes(ext)) {
|
|
319
|
+
throw new Error(`Invalid file extension: ${ext}.`);
|
|
327
320
|
}
|
|
321
|
+
outputFile = (0, path_1.join)((0, path_1.dirname)(outputFile), (0, path_1.basename)(outputFile, (0, path_1.extname)(outputFile))) + '.' + ext;
|
|
328
322
|
return { outputFile, ext };
|
|
329
323
|
}
|
|
330
324
|
function printUnusedWarnings(config) {
|
|
@@ -462,6 +456,7 @@ async function sendTelemetry(argv, exit_code, has_config, spec_version, spec_key
|
|
|
462
456
|
spec_full_version,
|
|
463
457
|
};
|
|
464
458
|
await (0, fetch_with_timeout_1.default)(`https://api.redocly.com/registry/telemetry/cli`, {
|
|
459
|
+
timeout: fetch_with_timeout_1.DEFAULT_FETCH_TIMEOUT,
|
|
465
460
|
method: 'POST',
|
|
466
461
|
headers: {
|
|
467
462
|
'content-type': 'application/json',
|
|
@@ -32,11 +32,15 @@ exports.notifyUpdateCliVersion = notifyUpdateCliVersion;
|
|
|
32
32
|
const isNewVersionAvailable = (current, latest) => (0, semver_1.compare)(current, latest) < 0;
|
|
33
33
|
const getLatestVersion = async (packageName) => {
|
|
34
34
|
const latestUrl = `http://registry.npmjs.org/${packageName}/latest`;
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
try {
|
|
36
|
+
const response = await (0, fetch_with_timeout_1.default)(latestUrl, { timeout: fetch_with_timeout_1.DEFAULT_FETCH_TIMEOUT });
|
|
37
|
+
const info = await response.json();
|
|
38
|
+
return info.version;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Do nothing
|
|
37
42
|
return;
|
|
38
|
-
|
|
39
|
-
return info.version;
|
|
43
|
+
}
|
|
40
44
|
};
|
|
41
45
|
const cacheLatestVersion = () => {
|
|
42
46
|
if (!isNeedToBeCached() || SHOULD_NOT_NOTIFY) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.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.23.0",
|
|
40
40
|
"abort-controller": "^3.0.0",
|
|
41
41
|
"chokidar": "^3.5.1",
|
|
42
42
|
"colorette": "^1.2.0",
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { bundle, getTotals, getMergedConfig } from '@redocly/openapi-core';
|
|
1
|
+
import { bundle, getTotals, getMergedConfig, Config } from '@redocly/openapi-core';
|
|
2
2
|
|
|
3
3
|
import { BundleOptions, handleBundle } from '../../commands/bundle';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getFallbackApisOrExit,
|
|
6
|
+
getOutputFileName,
|
|
7
|
+
handleError,
|
|
8
|
+
saveBundle,
|
|
9
|
+
} from '../../utils/miscellaneous';
|
|
5
10
|
import { commandWrapper } from '../../wrapper';
|
|
6
11
|
import SpyInstance = jest.SpyInstance;
|
|
7
12
|
import { Arguments } from 'yargs';
|
|
@@ -9,24 +14,31 @@ import { Arguments } from 'yargs';
|
|
|
9
14
|
jest.mock('@redocly/openapi-core');
|
|
10
15
|
jest.mock('../../utils/miscellaneous');
|
|
11
16
|
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
getOutputFileName = jest.requireActual('../../utils/miscellaneous').getOutputFileName;
|
|
19
|
+
|
|
12
20
|
(getMergedConfig as jest.Mock).mockImplementation((config) => config);
|
|
13
21
|
|
|
14
22
|
describe('bundle', () => {
|
|
15
23
|
let processExitMock: SpyInstance;
|
|
16
24
|
let exitCb: any;
|
|
17
|
-
|
|
25
|
+
let stderrWriteMock: any;
|
|
26
|
+
let stdoutWriteMock: any;
|
|
18
27
|
beforeEach(() => {
|
|
19
28
|
processExitMock = jest.spyOn(process, 'exit').mockImplementation();
|
|
20
29
|
jest.spyOn(process, 'once').mockImplementation((_e, cb) => {
|
|
21
30
|
exitCb = cb;
|
|
22
31
|
return process.on(_e, cb);
|
|
23
32
|
});
|
|
24
|
-
jest.spyOn(process.stderr, 'write').mockImplementation(()
|
|
33
|
+
stderrWriteMock = jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
|
|
34
|
+
stdoutWriteMock = jest.spyOn(process.stdout, 'write').mockImplementation(jest.fn());
|
|
25
35
|
});
|
|
26
36
|
|
|
27
37
|
afterEach(() => {
|
|
28
38
|
(bundle as jest.Mock).mockClear();
|
|
29
39
|
(getTotals as jest.Mock).mockReset();
|
|
40
|
+
stderrWriteMock.mockRestore();
|
|
41
|
+
stdoutWriteMock.mockRestore();
|
|
30
42
|
});
|
|
31
43
|
|
|
32
44
|
it('bundles definitions', async () => {
|
|
@@ -114,4 +126,119 @@ describe('bundle', () => {
|
|
|
114
126
|
|
|
115
127
|
expect(handleError).toHaveBeenCalledTimes(0);
|
|
116
128
|
});
|
|
129
|
+
|
|
130
|
+
it('should store bundled API descriptions in the output files described in the apis section of config IF no positional apis provided AND output is specified for both apis', async () => {
|
|
131
|
+
const apis = {
|
|
132
|
+
foo: {
|
|
133
|
+
root: 'foo.yaml',
|
|
134
|
+
output: 'output/foo.yaml',
|
|
135
|
+
},
|
|
136
|
+
bar: {
|
|
137
|
+
root: 'bar.yaml',
|
|
138
|
+
output: 'output/bar.json',
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
const config = {
|
|
142
|
+
apis,
|
|
143
|
+
styleguide: {
|
|
144
|
+
skipPreprocessors: jest.fn(),
|
|
145
|
+
skipDecorators: jest.fn(),
|
|
146
|
+
},
|
|
147
|
+
} as unknown as Config;
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
getFallbackApisOrExit = jest
|
|
150
|
+
.fn()
|
|
151
|
+
.mockResolvedValueOnce(
|
|
152
|
+
Object.entries(apis).map(([alias, { root, ...api }]) => ({ ...api, path: root, alias }))
|
|
153
|
+
);
|
|
154
|
+
(getTotals as jest.Mock).mockReturnValue({
|
|
155
|
+
errors: 0,
|
|
156
|
+
warnings: 0,
|
|
157
|
+
ignored: 0,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await handleBundle({
|
|
161
|
+
argv: { apis: [] }, // positional
|
|
162
|
+
version: 'test',
|
|
163
|
+
config,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(saveBundle).toBeCalledTimes(2);
|
|
167
|
+
expect(saveBundle).toHaveBeenNthCalledWith(1, 'output/foo.yaml', expect.any(String));
|
|
168
|
+
expect(saveBundle).toHaveBeenNthCalledWith(2, 'output/bar.json', expect.any(String));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should store bundled API descriptions in the output files described in the apis section of config AND print the bundled api without the output specified to the terminal IF no positional apis provided AND output is specified for one api', async () => {
|
|
172
|
+
const apis = {
|
|
173
|
+
foo: {
|
|
174
|
+
root: 'foo.yaml',
|
|
175
|
+
output: 'output/foo.yaml',
|
|
176
|
+
},
|
|
177
|
+
bar: {
|
|
178
|
+
root: 'bar.yaml',
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
const config = {
|
|
182
|
+
apis,
|
|
183
|
+
styleguide: {
|
|
184
|
+
skipPreprocessors: jest.fn(),
|
|
185
|
+
skipDecorators: jest.fn(),
|
|
186
|
+
},
|
|
187
|
+
} as unknown as Config;
|
|
188
|
+
// @ts-ignore
|
|
189
|
+
getFallbackApisOrExit = jest
|
|
190
|
+
.fn()
|
|
191
|
+
.mockResolvedValueOnce(
|
|
192
|
+
Object.entries(apis).map(([alias, { root, ...api }]) => ({ ...api, path: root, alias }))
|
|
193
|
+
);
|
|
194
|
+
(getTotals as jest.Mock).mockReturnValue({
|
|
195
|
+
errors: 0,
|
|
196
|
+
warnings: 0,
|
|
197
|
+
ignored: 0,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await handleBundle({
|
|
201
|
+
argv: { apis: [] }, // positional
|
|
202
|
+
version: 'test',
|
|
203
|
+
config,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(saveBundle).toBeCalledTimes(1);
|
|
207
|
+
expect(saveBundle).toHaveBeenCalledWith('output/foo.yaml', expect.any(String));
|
|
208
|
+
expect(process.stdout.write).toHaveBeenCalledTimes(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('per api output', () => {
|
|
212
|
+
it('should NOT store bundled API descriptions in the output files described in the apis section of config IF no there is a positional api provided', async () => {
|
|
213
|
+
const apis = {
|
|
214
|
+
foo: {
|
|
215
|
+
root: 'foo.yaml',
|
|
216
|
+
output: 'output/foo.yaml',
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
const config = {
|
|
220
|
+
apis,
|
|
221
|
+
styleguide: {
|
|
222
|
+
skipPreprocessors: jest.fn(),
|
|
223
|
+
skipDecorators: jest.fn(),
|
|
224
|
+
},
|
|
225
|
+
} as unknown as Config;
|
|
226
|
+
// @ts-ignore
|
|
227
|
+
getFallbackApisOrExit = jest.fn().mockResolvedValueOnce([{ path: 'openapi.yaml' }]);
|
|
228
|
+
(getTotals as jest.Mock).mockReturnValue({
|
|
229
|
+
errors: 0,
|
|
230
|
+
warnings: 0,
|
|
231
|
+
ignored: 0,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await handleBundle({
|
|
235
|
+
argv: { apis: ['openapi.yaml'] }, // positional
|
|
236
|
+
version: 'test',
|
|
237
|
+
config,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(saveBundle).toBeCalledTimes(0);
|
|
241
|
+
expect(process.stdout.write).toHaveBeenCalledTimes(1);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
117
244
|
});
|
|
@@ -1,23 +1,53 @@
|
|
|
1
1
|
import AbortController from 'abort-controller';
|
|
2
2
|
import fetchWithTimeout from '../utils/fetch-with-timeout';
|
|
3
3
|
import nodeFetch from 'node-fetch';
|
|
4
|
+
import { getProxyAgent } from '@redocly/openapi-core';
|
|
5
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
6
|
|
|
5
7
|
jest.mock('node-fetch');
|
|
8
|
+
jest.mock('@redocly/openapi-core');
|
|
6
9
|
|
|
7
10
|
describe('fetchWithTimeout', () => {
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
global.setTimeout = jest.fn();
|
|
14
|
+
global.clearTimeout = jest.fn();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
(getProxyAgent as jest.Mock).mockReturnValueOnce(undefined);
|
|
19
|
+
});
|
|
20
|
+
|
|
8
21
|
afterEach(() => {
|
|
9
22
|
jest.clearAllMocks();
|
|
10
23
|
});
|
|
11
24
|
|
|
12
25
|
it('should call node-fetch with signal', async () => {
|
|
13
|
-
|
|
14
|
-
global.setTimeout = jest.fn();
|
|
15
|
-
|
|
16
|
-
global.clearTimeout = jest.fn();
|
|
17
|
-
await fetchWithTimeout('url');
|
|
26
|
+
await fetchWithTimeout('url', { timeout: 1000 });
|
|
18
27
|
|
|
19
28
|
expect(global.setTimeout).toHaveBeenCalledTimes(1);
|
|
20
|
-
expect(nodeFetch).toHaveBeenCalledWith('url', {
|
|
29
|
+
expect(nodeFetch).toHaveBeenCalledWith('url', {
|
|
30
|
+
signal: new AbortController().signal,
|
|
31
|
+
agent: undefined,
|
|
32
|
+
});
|
|
21
33
|
expect(global.clearTimeout).toHaveBeenCalledTimes(1);
|
|
22
34
|
});
|
|
35
|
+
|
|
36
|
+
it('should call node-fetch with proxy agent', async () => {
|
|
37
|
+
(getProxyAgent as jest.Mock).mockRestore();
|
|
38
|
+
const proxyAgent = new HttpsProxyAgent('http://localhost');
|
|
39
|
+
(getProxyAgent as jest.Mock).mockReturnValueOnce(proxyAgent);
|
|
40
|
+
|
|
41
|
+
await fetchWithTimeout('url');
|
|
42
|
+
|
|
43
|
+
expect(nodeFetch).toHaveBeenCalledWith('url', { agent: proxyAgent });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should call node-fetch without signal when timeout is not passed', async () => {
|
|
47
|
+
await fetchWithTimeout('url');
|
|
48
|
+
|
|
49
|
+
expect(global.setTimeout).not.toHaveBeenCalled();
|
|
50
|
+
expect(nodeFetch).toHaveBeenCalledWith('url', { agent: undefined });
|
|
51
|
+
expect(global.clearTimeout).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
23
53
|
});
|
|
@@ -27,6 +27,7 @@ import { blue, red, yellow } from 'colorette';
|
|
|
27
27
|
import { existsSync, statSync } from 'fs';
|
|
28
28
|
import * as path from 'path';
|
|
29
29
|
import * as process from 'process';
|
|
30
|
+
import { ConfigApis } from '../types';
|
|
30
31
|
|
|
31
32
|
jest.mock('os');
|
|
32
33
|
jest.mock('colorette');
|
|
@@ -79,20 +80,6 @@ describe('pathToFilename', () => {
|
|
|
79
80
|
});
|
|
80
81
|
});
|
|
81
82
|
|
|
82
|
-
describe('getFallbackApisOrExit', () => {
|
|
83
|
-
it('should find alias by filename', async () => {
|
|
84
|
-
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => true);
|
|
85
|
-
const entry = await getFallbackApisOrExit(['./test.yaml'], {
|
|
86
|
-
apis: {
|
|
87
|
-
main: {
|
|
88
|
-
root: 'test.yaml',
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
} as any);
|
|
92
|
-
expect(entry).toEqual([{ path: './test.yaml', alias: 'main' }]);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
83
|
describe('printConfigLintTotals', () => {
|
|
97
84
|
const totalProblemsMock: Totals = {
|
|
98
85
|
errors: 1,
|
|
@@ -190,6 +177,7 @@ describe('getFallbackApisOrExit', () => {
|
|
|
190
177
|
{
|
|
191
178
|
alias: 'main',
|
|
192
179
|
path: 'someFile.yaml',
|
|
180
|
+
output: undefined,
|
|
193
181
|
},
|
|
194
182
|
]);
|
|
195
183
|
});
|
|
@@ -277,6 +265,43 @@ describe('getFallbackApisOrExit', () => {
|
|
|
277
265
|
{
|
|
278
266
|
alias: 'main',
|
|
279
267
|
path: 'https://someLinkt/petstore.yaml?main',
|
|
268
|
+
output: undefined,
|
|
269
|
+
},
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
(isAbsoluteUrl as jest.Mock<any, any>).mockReset();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should find alias by filename', async () => {
|
|
276
|
+
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => true);
|
|
277
|
+
const entry = await getFallbackApisOrExit(['./test.yaml'], {
|
|
278
|
+
apis: {
|
|
279
|
+
main: {
|
|
280
|
+
root: 'test.yaml',
|
|
281
|
+
styleguide: {},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
expect(entry).toEqual([{ path: './test.yaml', alias: 'main' }]);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should return apis from config with paths and outputs resolved relatively to the config location', async () => {
|
|
289
|
+
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => true);
|
|
290
|
+
const entry = await getFallbackApisOrExit(undefined, {
|
|
291
|
+
apis: {
|
|
292
|
+
main: {
|
|
293
|
+
root: 'test.yaml',
|
|
294
|
+
output: 'output/test.yaml',
|
|
295
|
+
styleguide: {},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
configFile: 'project-folder/redocly.yaml',
|
|
299
|
+
});
|
|
300
|
+
expect(entry).toEqual([
|
|
301
|
+
{
|
|
302
|
+
path: expect.stringMatching(/project\-folder\/test\.yaml$/),
|
|
303
|
+
output: expect.stringMatching(/project\-folder\/output\/test\.yaml$/),
|
|
304
|
+
alias: 'main',
|
|
280
305
|
},
|
|
281
306
|
]);
|
|
282
307
|
});
|
|
@@ -591,28 +616,28 @@ describe('cleanRawInput', () => {
|
|
|
591
616
|
expect(stderrMock).toHaveBeenCalledWith(`Unsupported file extension: xml. Using yaml.\n`);
|
|
592
617
|
});
|
|
593
618
|
});
|
|
619
|
+
});
|
|
594
620
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
621
|
+
describe('writeToFileByExtension', () => {
|
|
622
|
+
beforeEach(() => {
|
|
623
|
+
jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
|
|
624
|
+
(yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
|
|
625
|
+
});
|
|
600
626
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
627
|
+
afterEach(() => {
|
|
628
|
+
jest.restoreAllMocks();
|
|
629
|
+
});
|
|
604
630
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
631
|
+
it('should call stringifyYaml function', () => {
|
|
632
|
+
writeToFileByExtension('test data', 'test.yaml');
|
|
633
|
+
expect(stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
|
|
634
|
+
expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
|
|
635
|
+
});
|
|
610
636
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
});
|
|
637
|
+
it('should call JSON.stringify function', () => {
|
|
638
|
+
const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
|
|
639
|
+
writeToFileByExtension('test data', 'test.json');
|
|
640
|
+
expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
|
|
641
|
+
expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
|
|
617
642
|
});
|
|
618
643
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fetch, { Response } from 'node-fetch';
|
|
2
2
|
import * as FormData from 'form-data';
|
|
3
3
|
|
|
4
|
-
import { ReuniteApiClient, PushPayload } from '../api-client';
|
|
4
|
+
import { ReuniteApiClient, PushPayload, ReuniteApiError } from '../api-client';
|
|
5
5
|
|
|
6
6
|
jest.mock('node-fetch', () => ({
|
|
7
7
|
default: jest.fn(),
|
|
@@ -16,12 +16,15 @@ describe('ApiClient', () => {
|
|
|
16
16
|
const testDomain = 'test-domain.com';
|
|
17
17
|
const testOrg = 'test-org';
|
|
18
18
|
const testProject = 'test-project';
|
|
19
|
+
const version = '1.0.0';
|
|
20
|
+
const command = 'push';
|
|
21
|
+
const expectedUserAgent = `redocly-cli/${version} ${command}`;
|
|
19
22
|
|
|
20
23
|
describe('getDefaultBranch()', () => {
|
|
21
24
|
let apiClient: ReuniteApiClient;
|
|
22
25
|
|
|
23
26
|
beforeEach(() => {
|
|
24
|
-
apiClient = new ReuniteApiClient(testDomain, testToken);
|
|
27
|
+
apiClient = new ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
|
|
25
28
|
});
|
|
26
29
|
|
|
27
30
|
it('should get default project branch', async () => {
|
|
@@ -41,6 +44,7 @@ describe('ApiClient', () => {
|
|
|
41
44
|
headers: {
|
|
42
45
|
'Content-Type': 'application/json',
|
|
43
46
|
Authorization: `Bearer ${testToken}`,
|
|
47
|
+
'user-agent': expectedUserAgent,
|
|
44
48
|
},
|
|
45
49
|
signal: expect.any(Object),
|
|
46
50
|
}
|
|
@@ -62,7 +66,7 @@ describe('ApiClient', () => {
|
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
await expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(
|
|
65
|
-
new
|
|
69
|
+
new ReuniteApiError('Failed to fetch default branch. Project source not found.', 404)
|
|
66
70
|
);
|
|
67
71
|
});
|
|
68
72
|
|
|
@@ -76,7 +80,7 @@ describe('ApiClient', () => {
|
|
|
76
80
|
});
|
|
77
81
|
|
|
78
82
|
await expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(
|
|
79
|
-
new
|
|
83
|
+
new ReuniteApiError('Failed to fetch default branch. Not found.', 404)
|
|
80
84
|
);
|
|
81
85
|
});
|
|
82
86
|
});
|
|
@@ -89,7 +93,7 @@ describe('ApiClient', () => {
|
|
|
89
93
|
let apiClient: ReuniteApiClient;
|
|
90
94
|
|
|
91
95
|
beforeEach(() => {
|
|
92
|
-
apiClient = new ReuniteApiClient(testDomain, testToken);
|
|
96
|
+
apiClient = new ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
|
|
93
97
|
});
|
|
94
98
|
|
|
95
99
|
it('should upsert remote', async () => {
|
|
@@ -116,6 +120,7 @@ describe('ApiClient', () => {
|
|
|
116
120
|
headers: {
|
|
117
121
|
'Content-Type': 'application/json',
|
|
118
122
|
Authorization: `Bearer ${testToken}`,
|
|
123
|
+
'user-agent': expectedUserAgent,
|
|
119
124
|
},
|
|
120
125
|
body: JSON.stringify({
|
|
121
126
|
mountPath: remotePayload.mountPath,
|
|
@@ -144,8 +149,9 @@ describe('ApiClient', () => {
|
|
|
144
149
|
});
|
|
145
150
|
|
|
146
151
|
await expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(
|
|
147
|
-
new
|
|
148
|
-
'Failed to upsert remote
|
|
152
|
+
new ReuniteApiError(
|
|
153
|
+
'Failed to upsert remote. Not allowed to mount remote outside of project content path: /docs.',
|
|
154
|
+
403
|
|
149
155
|
)
|
|
150
156
|
);
|
|
151
157
|
});
|
|
@@ -153,6 +159,7 @@ describe('ApiClient', () => {
|
|
|
153
159
|
it('should throw statusText error if response is not ok', async () => {
|
|
154
160
|
mockFetchResponse({
|
|
155
161
|
ok: false,
|
|
162
|
+
status: 404,
|
|
156
163
|
statusText: 'Not found',
|
|
157
164
|
json: jest.fn().mockResolvedValue({
|
|
158
165
|
unknownField: 'unknown-error',
|
|
@@ -160,7 +167,7 @@ describe('ApiClient', () => {
|
|
|
160
167
|
});
|
|
161
168
|
|
|
162
169
|
await expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(
|
|
163
|
-
new
|
|
170
|
+
new ReuniteApiError('Failed to upsert remote. Not found.', 404)
|
|
164
171
|
);
|
|
165
172
|
});
|
|
166
173
|
});
|
|
@@ -200,7 +207,7 @@ describe('ApiClient', () => {
|
|
|
200
207
|
let apiClient: ReuniteApiClient;
|
|
201
208
|
|
|
202
209
|
beforeEach(() => {
|
|
203
|
-
apiClient = new ReuniteApiClient(testDomain, testToken);
|
|
210
|
+
apiClient = new ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
|
|
204
211
|
});
|
|
205
212
|
|
|
206
213
|
it('should push to remote', async () => {
|
|
@@ -234,6 +241,7 @@ describe('ApiClient', () => {
|
|
|
234
241
|
method: 'POST',
|
|
235
242
|
headers: {
|
|
236
243
|
Authorization: `Bearer ${testToken}`,
|
|
244
|
+
'user-agent': expectedUserAgent,
|
|
237
245
|
},
|
|
238
246
|
})
|
|
239
247
|
);
|
|
@@ -258,12 +266,13 @@ describe('ApiClient', () => {
|
|
|
258
266
|
|
|
259
267
|
await expect(
|
|
260
268
|
apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)
|
|
261
|
-
).rejects.toThrow(new
|
|
269
|
+
).rejects.toThrow(new ReuniteApiError('Failed to push. Cannot push to remote.', 403));
|
|
262
270
|
});
|
|
263
271
|
|
|
264
272
|
it('should throw statusText error if response is not ok', async () => {
|
|
265
273
|
mockFetchResponse({
|
|
266
274
|
ok: false,
|
|
275
|
+
status: 404,
|
|
267
276
|
statusText: 'Not found',
|
|
268
277
|
json: jest.fn().mockResolvedValue({
|
|
269
278
|
unknownField: 'unknown-error',
|
|
@@ -272,7 +281,7 @@ describe('ApiClient', () => {
|
|
|
272
281
|
|
|
273
282
|
await expect(
|
|
274
283
|
apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)
|
|
275
|
-
).rejects.toThrow(new
|
|
284
|
+
).rejects.toThrow(new ReuniteApiError('Failed to push. Not found.', 404));
|
|
276
285
|
});
|
|
277
286
|
});
|
|
278
287
|
});
|