@redocly/cli 1.27.1 → 1.28.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 +17 -0
- package/README.md +1 -1
- package/lib/__tests__/commands/push-region.test.js +44 -9
- package/lib/__tests__/commands/push.test.js +39 -6
- package/lib/__tests__/fetch-with-timeout.test.js +33 -13
- package/lib/__tests__/wrapper.test.js +9 -1
- package/lib/cms/api/__tests__/api.client.test.js +17 -13
- package/lib/cms/api/api-client.d.ts +2 -1
- package/lib/cms/api/api-client.js +13 -3
- package/lib/commands/push.js +12 -6
- package/lib/commands/split/index.d.ts +1 -1
- package/lib/commands/split/types.d.ts +2 -2
- package/lib/utils/fetch-with-timeout.d.ts +1 -2
- package/lib/utils/fetch-with-timeout.js +5 -7
- package/package.json +4 -5
- package/src/__tests__/commands/push-region.test.ts +54 -10
- package/src/__tests__/commands/push.test.ts +47 -7
- package/src/__tests__/fetch-with-timeout.test.ts +41 -13
- package/src/__tests__/wrapper.test.ts +13 -1
- package/src/cms/api/__tests__/api.client.test.ts +18 -16
- package/src/cms/api/api-client.ts +14 -4
- package/src/commands/join.ts +12 -6
- package/src/commands/push.ts +20 -6
- package/src/commands/split/index.ts +16 -14
- package/src/commands/split/types.ts +3 -26
- package/src/utils/fetch-with-timeout.ts +7 -10
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @redocly/cli
|
|
2
2
|
|
|
3
|
+
## 1.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Switched to using native `fetch` API instead of `node-fetch` dependency, improving performance and reducing bundle size.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated @redocly/openapi-core to v1.28.0.
|
|
12
|
+
|
|
13
|
+
## 1.27.2
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated the `sideNavStyle` configuration schema to include the `path-only` option.
|
|
18
|
+
- Updated @redocly/openapi-core to v1.27.2.
|
|
19
|
+
|
|
3
20
|
## 1.27.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Then you can use it as `redocly [command] [options]`, for example:
|
|
|
28
28
|
redocly lint path-to-root-file.yaml
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
The minimum required versions of Node.js and NPM are
|
|
31
|
+
The minimum required versions of Node.js and NPM are 18.17.0 and 10.8.2 respectively.
|
|
32
32
|
|
|
33
33
|
### Docker
|
|
34
34
|
|
|
@@ -4,26 +4,59 @@ const openapi_core_1 = require("@redocly/openapi-core");
|
|
|
4
4
|
const push_1 = require("../../commands/push");
|
|
5
5
|
const login_1 = require("../../commands/login");
|
|
6
6
|
const config_1 = require("../fixtures/config");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const node_stream_1 = require("node:stream");
|
|
8
|
+
// Mock fs operations
|
|
9
|
+
jest.mock('fs', () => ({
|
|
10
|
+
...jest.requireActual('fs'),
|
|
11
|
+
createReadStream: () => {
|
|
12
|
+
const readable = new node_stream_1.Readable();
|
|
13
|
+
readable.push('test data');
|
|
14
|
+
readable.push(null);
|
|
15
|
+
return readable;
|
|
16
|
+
},
|
|
17
|
+
statSync: () => ({ size: 9 }),
|
|
18
|
+
readFileSync: () => Buffer.from('test data'),
|
|
19
|
+
existsSync: () => false,
|
|
20
|
+
readdirSync: () => [],
|
|
13
21
|
}));
|
|
22
|
+
openapi_core_1.getMergedConfig.mockImplementation((config) => config);
|
|
23
|
+
// Mock OpenAPI core
|
|
14
24
|
jest.mock('@redocly/openapi-core');
|
|
15
25
|
jest.mock('../../commands/login');
|
|
16
26
|
jest.mock('../../utils/miscellaneous');
|
|
17
|
-
openapi_core_1.getMergedConfig.mockImplementation((config) => config);
|
|
18
27
|
const mockPromptClientToken = login_1.promptClientToken;
|
|
19
28
|
describe('push-with-region', () => {
|
|
20
29
|
const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
|
|
21
30
|
redoclyClient.isAuthorizedWithRedoclyByRegion = jest.fn().mockResolvedValue(false);
|
|
31
|
+
const originalFetch = fetch;
|
|
22
32
|
beforeAll(() => {
|
|
33
|
+
// Mock global fetch
|
|
34
|
+
global.fetch = jest.fn(() => Promise.resolve({
|
|
35
|
+
ok: true,
|
|
36
|
+
status: 200,
|
|
37
|
+
json: () => Promise.resolve({}),
|
|
38
|
+
headers: new Headers(),
|
|
39
|
+
statusText: 'OK',
|
|
40
|
+
redirected: false,
|
|
41
|
+
type: 'default',
|
|
42
|
+
url: '',
|
|
43
|
+
clone: () => ({}),
|
|
44
|
+
body: new ReadableStream(),
|
|
45
|
+
bodyUsed: false,
|
|
46
|
+
arrayBuffer: async () => new ArrayBuffer(0),
|
|
47
|
+
blob: async () => new Blob(),
|
|
48
|
+
formData: async () => new FormData(),
|
|
49
|
+
text: async () => '',
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
afterAll(() => {
|
|
53
|
+
global.fetch = originalFetch;
|
|
54
|
+
});
|
|
55
|
+
beforeEach(() => {
|
|
23
56
|
jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
24
57
|
});
|
|
25
58
|
it('should call login with default domain when region is US', async () => {
|
|
26
|
-
redoclyClient.domain = '
|
|
59
|
+
redoclyClient.domain = 'redocly.com';
|
|
27
60
|
await (0, push_1.handlePush)({
|
|
28
61
|
argv: {
|
|
29
62
|
upsert: true,
|
|
@@ -39,6 +72,8 @@ describe('push-with-region', () => {
|
|
|
39
72
|
});
|
|
40
73
|
it('should call login with EU domain when region is EU', async () => {
|
|
41
74
|
redoclyClient.domain = 'eu.redocly.com';
|
|
75
|
+
// Update config for EU region
|
|
76
|
+
const euConfig = { ...config_1.ConfigFixture, region: 'eu' };
|
|
42
77
|
await (0, push_1.handlePush)({
|
|
43
78
|
argv: {
|
|
44
79
|
upsert: true,
|
|
@@ -46,7 +81,7 @@ describe('push-with-region', () => {
|
|
|
46
81
|
destination: '@org/my-api@1.0.0',
|
|
47
82
|
branchName: 'test',
|
|
48
83
|
},
|
|
49
|
-
config:
|
|
84
|
+
config: euConfig,
|
|
50
85
|
version: 'cli-version',
|
|
51
86
|
});
|
|
52
87
|
expect(mockPromptClientToken).toBeCalledTimes(1);
|
|
@@ -6,21 +6,54 @@ const miscellaneous_1 = require("../../utils/miscellaneous");
|
|
|
6
6
|
const push_1 = require("../../commands/push");
|
|
7
7
|
const config_1 = require("../fixtures/config");
|
|
8
8
|
const colorette_1 = require("colorette");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const node_stream_1 = require("node:stream");
|
|
10
|
+
// Mock fs operations
|
|
11
|
+
jest.mock('fs', () => ({
|
|
12
|
+
...jest.requireActual('fs'),
|
|
13
|
+
createReadStream: jest.fn(() => {
|
|
14
|
+
const readable = new node_stream_1.Readable();
|
|
15
|
+
readable.push('test data');
|
|
16
|
+
readable.push(null);
|
|
17
|
+
return readable;
|
|
18
|
+
}),
|
|
19
|
+
statSync: jest.fn(() => ({ isDirectory: () => false, size: 10 })),
|
|
20
|
+
readFileSync: jest.fn(() => Buffer.from('test data')),
|
|
21
|
+
existsSync: jest.fn(() => false),
|
|
22
|
+
readdirSync: jest.fn(() => []),
|
|
15
23
|
}));
|
|
16
24
|
jest.mock('@redocly/openapi-core');
|
|
17
25
|
jest.mock('../../utils/miscellaneous');
|
|
26
|
+
// Mock fetch
|
|
27
|
+
const mockFetch = jest.fn(() => Promise.resolve({
|
|
28
|
+
ok: true,
|
|
29
|
+
status: 200,
|
|
30
|
+
json: () => Promise.resolve({}),
|
|
31
|
+
headers: new Headers(),
|
|
32
|
+
statusText: 'OK',
|
|
33
|
+
redirected: false,
|
|
34
|
+
type: 'default',
|
|
35
|
+
url: '',
|
|
36
|
+
clone: () => ({}),
|
|
37
|
+
body: null,
|
|
38
|
+
bodyUsed: false,
|
|
39
|
+
arrayBuffer: async () => new ArrayBuffer(0),
|
|
40
|
+
blob: async () => new Blob(),
|
|
41
|
+
formData: async () => new FormData(),
|
|
42
|
+
text: async () => '',
|
|
43
|
+
}));
|
|
44
|
+
const originalFetch = global.fetch;
|
|
18
45
|
openapi_core_1.getMergedConfig.mockImplementation((config) => config);
|
|
19
46
|
describe('push', () => {
|
|
20
47
|
const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
|
|
48
|
+
beforeAll(() => {
|
|
49
|
+
global.fetch = mockFetch;
|
|
50
|
+
});
|
|
21
51
|
beforeEach(() => {
|
|
22
52
|
jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
23
53
|
});
|
|
54
|
+
afterAll(() => {
|
|
55
|
+
global.fetch = originalFetch;
|
|
56
|
+
});
|
|
24
57
|
it('pushes definition', async () => {
|
|
25
58
|
await (0, push_1.handlePush)({
|
|
26
59
|
argv: {
|
|
@@ -2,11 +2,31 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const abort_controller_1 = require("abort-controller");
|
|
4
4
|
const fetch_with_timeout_1 = require("../utils/fetch-with-timeout");
|
|
5
|
-
const node_fetch_1 = require("node-fetch");
|
|
6
5
|
const openapi_core_1 = require("@redocly/openapi-core");
|
|
7
6
|
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
8
|
-
jest.mock('node-fetch');
|
|
9
7
|
jest.mock('@redocly/openapi-core');
|
|
8
|
+
const signalInstance = new abort_controller_1.default().signal;
|
|
9
|
+
const mockFetch = jest.fn(() => Promise.resolve({
|
|
10
|
+
ok: true,
|
|
11
|
+
status: 200,
|
|
12
|
+
json: () => Promise.resolve({}),
|
|
13
|
+
headers: new Headers(),
|
|
14
|
+
statusText: 'OK',
|
|
15
|
+
redirected: false,
|
|
16
|
+
type: 'default',
|
|
17
|
+
url: '',
|
|
18
|
+
clone: () => ({}),
|
|
19
|
+
body: null,
|
|
20
|
+
bodyUsed: false,
|
|
21
|
+
arrayBuffer: async () => new ArrayBuffer(0),
|
|
22
|
+
blob: async () => new Blob(),
|
|
23
|
+
formData: async () => new FormData(),
|
|
24
|
+
text: async () => '',
|
|
25
|
+
signal: signalInstance,
|
|
26
|
+
dispatcher: undefined,
|
|
27
|
+
}));
|
|
28
|
+
const originalFetch = global.fetch;
|
|
29
|
+
global.fetch = mockFetch;
|
|
10
30
|
describe('fetchWithTimeout', () => {
|
|
11
31
|
beforeAll(() => {
|
|
12
32
|
// @ts-ignore
|
|
@@ -16,29 +36,29 @@ describe('fetchWithTimeout', () => {
|
|
|
16
36
|
beforeEach(() => {
|
|
17
37
|
openapi_core_1.getProxyAgent.mockReturnValueOnce(undefined);
|
|
18
38
|
});
|
|
19
|
-
|
|
20
|
-
|
|
39
|
+
afterAll(() => {
|
|
40
|
+
global.fetch = originalFetch;
|
|
21
41
|
});
|
|
22
|
-
it('should call
|
|
42
|
+
it('should call fetch with signal', async () => {
|
|
23
43
|
await (0, fetch_with_timeout_1.default)('url', { timeout: 1000 });
|
|
24
44
|
expect(global.setTimeout).toHaveBeenCalledTimes(1);
|
|
25
|
-
expect(
|
|
26
|
-
signal:
|
|
27
|
-
|
|
28
|
-
});
|
|
45
|
+
expect(global.fetch).toHaveBeenCalledWith('url', expect.objectContaining({
|
|
46
|
+
signal: expect.any(AbortSignal),
|
|
47
|
+
dispatcher: undefined,
|
|
48
|
+
}));
|
|
29
49
|
expect(global.clearTimeout).toHaveBeenCalledTimes(1);
|
|
30
50
|
});
|
|
31
|
-
it('should call
|
|
51
|
+
it('should call fetch with proxy agent', async () => {
|
|
32
52
|
openapi_core_1.getProxyAgent.mockRestore();
|
|
33
53
|
const proxyAgent = new https_proxy_agent_1.HttpsProxyAgent('http://localhost');
|
|
34
54
|
openapi_core_1.getProxyAgent.mockReturnValueOnce(proxyAgent);
|
|
35
55
|
await (0, fetch_with_timeout_1.default)('url');
|
|
36
|
-
expect(
|
|
56
|
+
expect(global.fetch).toHaveBeenCalledWith('url', { dispatcher: proxyAgent });
|
|
37
57
|
});
|
|
38
|
-
it('should call
|
|
58
|
+
it('should call fetch without signal when timeout is not passed', async () => {
|
|
39
59
|
await (0, fetch_with_timeout_1.default)('url');
|
|
40
60
|
expect(global.setTimeout).not.toHaveBeenCalled();
|
|
41
|
-
expect(
|
|
61
|
+
expect(global.fetch).toHaveBeenCalledWith('url', { agent: undefined });
|
|
42
62
|
expect(global.clearTimeout).not.toHaveBeenCalled();
|
|
43
63
|
});
|
|
44
64
|
});
|
|
@@ -6,11 +6,19 @@ const wrapper_1 = require("../wrapper");
|
|
|
6
6
|
const lint_1 = require("../commands/lint");
|
|
7
7
|
const push_1 = require("../commands/push");
|
|
8
8
|
const openapi_core_1 = require("@redocly/openapi-core");
|
|
9
|
-
jest.
|
|
9
|
+
const mockFetch = jest.fn();
|
|
10
|
+
const originalFetch = global.fetch;
|
|
10
11
|
jest.mock('../utils/miscellaneous', () => ({
|
|
11
12
|
sendTelemetry: jest.fn(),
|
|
12
13
|
loadConfigAndHandleErrors: jest.fn(),
|
|
13
14
|
}));
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
global.fetch = mockFetch;
|
|
17
|
+
});
|
|
18
|
+
afterAll(() => {
|
|
19
|
+
jest.resetAllMocks();
|
|
20
|
+
global.fetch = originalFetch;
|
|
21
|
+
});
|
|
14
22
|
jest.mock('../commands/lint', () => ({
|
|
15
23
|
handleLint: jest.fn().mockImplementation(({ collectSpecData }) => {
|
|
16
24
|
collectSpecData({ openapi: '3.1.0' });
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const node_fetch_1 = require("node-fetch");
|
|
4
|
-
const FormData = require("form-data");
|
|
5
3
|
const colorette_1 = require("colorette");
|
|
6
4
|
const api_client_1 = require("../api-client");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const originalFetch = global.fetch;
|
|
6
|
+
beforeAll(() => {
|
|
7
|
+
// Reset fetch mock before each test
|
|
8
|
+
global.fetch = jest.fn();
|
|
9
|
+
});
|
|
10
|
+
afterAll(() => {
|
|
11
|
+
// Restore original fetch after each test
|
|
12
|
+
global.fetch = originalFetch;
|
|
13
|
+
});
|
|
10
14
|
function mockFetchResponse(response) {
|
|
11
|
-
|
|
15
|
+
global.fetch.mockResolvedValue(response);
|
|
12
16
|
}
|
|
13
17
|
describe('ApiClient', () => {
|
|
14
18
|
const testToken = 'test-token';
|
|
@@ -31,7 +35,7 @@ describe('ApiClient', () => {
|
|
|
31
35
|
}),
|
|
32
36
|
});
|
|
33
37
|
const result = await apiClient.remotes.getDefaultBranch(testOrg, testProject);
|
|
34
|
-
expect(
|
|
38
|
+
expect(global.fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/source`, {
|
|
35
39
|
method: 'GET',
|
|
36
40
|
headers: {
|
|
37
41
|
'Content-Type': 'application/json',
|
|
@@ -89,7 +93,7 @@ describe('ApiClient', () => {
|
|
|
89
93
|
json: jest.fn().mockResolvedValue(responseMock),
|
|
90
94
|
});
|
|
91
95
|
const result = await apiClient.remotes.upsert(testOrg, testProject, remotePayload);
|
|
92
|
-
expect(
|
|
96
|
+
expect(global.fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/remotes`, {
|
|
93
97
|
method: 'POST',
|
|
94
98
|
headers: {
|
|
95
99
|
'Content-Type': 'application/json',
|
|
@@ -167,29 +171,29 @@ describe('ApiClient', () => {
|
|
|
167
171
|
});
|
|
168
172
|
it('should push to remote', async () => {
|
|
169
173
|
let passedFormData = new FormData();
|
|
170
|
-
|
|
174
|
+
fetch.mockImplementationOnce(async (_, options) => {
|
|
171
175
|
passedFormData = options.body;
|
|
172
176
|
return {
|
|
173
177
|
ok: true,
|
|
174
178
|
json: jest.fn().mockResolvedValue(responseMock),
|
|
175
179
|
};
|
|
176
180
|
});
|
|
177
|
-
const formData = new FormData();
|
|
181
|
+
const formData = new globalThis.FormData();
|
|
178
182
|
formData.append('remoteId', testRemoteId);
|
|
179
183
|
formData.append('commit[message]', pushPayload.commit.message);
|
|
180
184
|
formData.append('commit[author][name]', pushPayload.commit.author.name);
|
|
181
185
|
formData.append('commit[author][email]', pushPayload.commit.author.email);
|
|
182
186
|
formData.append('commit[branchName]', pushPayload.commit.branchName);
|
|
183
|
-
formData.append('files[some-file.yaml]', filesMock[0].stream);
|
|
187
|
+
formData.append('files[some-file.yaml]', new Blob([filesMock[0].stream]));
|
|
184
188
|
const result = await apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock);
|
|
185
|
-
expect(
|
|
189
|
+
expect(fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/pushes`, expect.objectContaining({
|
|
186
190
|
method: 'POST',
|
|
187
191
|
headers: {
|
|
188
192
|
Authorization: `Bearer ${testToken}`,
|
|
189
193
|
'user-agent': expectedUserAgent,
|
|
190
194
|
},
|
|
191
195
|
}));
|
|
192
|
-
expect(
|
|
196
|
+
expect(passedFormData).toEqual(formData);
|
|
193
197
|
expect(result).toEqual(responseMock);
|
|
194
198
|
});
|
|
195
199
|
it('should throw parsed error if response is not ok', async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FetchWithTimeoutOptions } from '../../utils/fetch-with-timeout';
|
|
2
|
-
import type { Response } from 'node-fetch';
|
|
3
2
|
import type { ReadStream } from 'fs';
|
|
3
|
+
import type { Readable } from 'node:stream';
|
|
4
4
|
import type { ListRemotesResponse, PushResponse, UpsertRemoteResponse } from './types';
|
|
5
5
|
interface BaseApiClient {
|
|
6
6
|
request(url: string, options: FetchWithTimeoutOptions): Promise<Response>;
|
|
@@ -72,4 +72,5 @@ export type PushPayload = {
|
|
|
72
72
|
};
|
|
73
73
|
isMainBranch?: boolean;
|
|
74
74
|
};
|
|
75
|
+
export declare function streamToBuffer(stream: ReadStream | Readable): Promise<Buffer>;
|
|
75
76
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ReuniteApi = exports.ReuniteApiError = void 0;
|
|
4
|
+
exports.streamToBuffer = streamToBuffer;
|
|
4
5
|
const colorette_1 = require("colorette");
|
|
5
|
-
const FormData = require("form-data");
|
|
6
6
|
const fetch_with_timeout_1 = require("../../utils/fetch-with-timeout");
|
|
7
7
|
class ReuniteApiError extends Error {
|
|
8
8
|
constructor(message, status) {
|
|
@@ -120,7 +120,7 @@ class RemotesApi {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
async push(organizationId, projectId, payload, files) {
|
|
123
|
-
const formData = new FormData();
|
|
123
|
+
const formData = new globalThis.FormData();
|
|
124
124
|
formData.append('remoteId', payload.remoteId);
|
|
125
125
|
formData.append('commit[message]', payload.commit.message);
|
|
126
126
|
formData.append('commit[author][name]', payload.commit.author.name);
|
|
@@ -132,7 +132,10 @@ class RemotesApi {
|
|
|
132
132
|
payload.commit.repository && formData.append('commit[repositoryId]', payload.commit.repository);
|
|
133
133
|
payload.commit.createdAt && formData.append('commit[createdAt]', payload.commit.createdAt);
|
|
134
134
|
for (const file of files) {
|
|
135
|
-
|
|
135
|
+
const blob = Buffer.isBuffer(file.stream)
|
|
136
|
+
? new Blob([file.stream])
|
|
137
|
+
: new Blob([await streamToBuffer(file.stream)]);
|
|
138
|
+
formData.append(`files[${file.path}]`, blob, file.path);
|
|
136
139
|
}
|
|
137
140
|
payload.isMainBranch && formData.append('isMainBranch', 'true');
|
|
138
141
|
try {
|
|
@@ -223,3 +226,10 @@ class ReuniteApi {
|
|
|
223
226
|
}
|
|
224
227
|
}
|
|
225
228
|
exports.ReuniteApi = ReuniteApi;
|
|
229
|
+
async function streamToBuffer(stream) {
|
|
230
|
+
const chunks = [];
|
|
231
|
+
for await (const chunk of stream) {
|
|
232
|
+
chunks.push(chunk);
|
|
233
|
+
}
|
|
234
|
+
return Buffer.concat(chunks);
|
|
235
|
+
}
|
package/lib/commands/push.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.getDestinationProps = getDestinationProps;
|
|
|
7
7
|
exports.getApiRoot = getApiRoot;
|
|
8
8
|
const fs = require("fs");
|
|
9
9
|
const path = require("path");
|
|
10
|
-
const node_fetch_1 = require("node-fetch");
|
|
11
10
|
const perf_hooks_1 = require("perf_hooks");
|
|
12
11
|
const colorette_1 = require("colorette");
|
|
13
12
|
const crypto_1 = require("crypto");
|
|
@@ -16,6 +15,7 @@ const utils_1 = require("@redocly/openapi-core/lib/utils");
|
|
|
16
15
|
const miscellaneous_1 = require("../utils/miscellaneous");
|
|
17
16
|
const login_1 = require("./login");
|
|
18
17
|
const push_1 = require("../cms/commands/push");
|
|
18
|
+
const api_client_1 = require("../cms/api/api-client");
|
|
19
19
|
const DEFAULT_VERSION = 'latest';
|
|
20
20
|
exports.DESTINATION_REGEX =
|
|
21
21
|
// eslint-disable-next-line no-useless-escape
|
|
@@ -279,17 +279,23 @@ function getApiRoot({ name, version, config: { apis }, }) {
|
|
|
279
279
|
const api = apis?.[`${name}@${version}`] || (version === DEFAULT_VERSION && apis?.[name]);
|
|
280
280
|
return api?.root;
|
|
281
281
|
}
|
|
282
|
-
function uploadFileToS3(url, filePathOrBuffer) {
|
|
282
|
+
async function uploadFileToS3(url, filePathOrBuffer) {
|
|
283
283
|
const fileSizeInBytes = typeof filePathOrBuffer === 'string'
|
|
284
284
|
? fs.statSync(filePathOrBuffer).size
|
|
285
285
|
: filePathOrBuffer.byteLength;
|
|
286
286
|
const readStream = typeof filePathOrBuffer === 'string' ? fs.createReadStream(filePathOrBuffer) : filePathOrBuffer;
|
|
287
|
-
|
|
287
|
+
const requestOptions = {
|
|
288
288
|
method: 'PUT',
|
|
289
289
|
headers: {
|
|
290
290
|
'Content-Length': fileSizeInBytes.toString(),
|
|
291
291
|
},
|
|
292
|
-
body: readStream
|
|
293
|
-
|
|
294
|
-
|
|
292
|
+
body: Buffer.isBuffer(readStream)
|
|
293
|
+
? new Blob([readStream])
|
|
294
|
+
: new Blob([await (0, api_client_1.streamToBuffer)(readStream)]),
|
|
295
|
+
};
|
|
296
|
+
const proxyAgent = (0, openapi_core_1.getProxyAgent)();
|
|
297
|
+
if (proxyAgent) {
|
|
298
|
+
requestOptions.dispatcher = proxyAgent;
|
|
299
|
+
}
|
|
300
|
+
return fetch(url, requestOptions);
|
|
295
301
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Oas3PathItem, Referenced } from '
|
|
1
|
+
import type { Oas3PathItem, Referenced } from '@redocly/openapi-core/lib/typings/openapi';
|
|
2
2
|
import type { CommandArgs } from '../../wrapper';
|
|
3
3
|
import type { VerifyConfigOptions } from '../../types';
|
|
4
4
|
export type SplitOptions = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { Oas2Definition } from '@redocly/openapi-core';
|
|
2
|
+
import type { Oas3_1Definition, Oas3Definition } from '@redocly/openapi-core/lib/typings/openapi';
|
|
3
3
|
export type Definition = Oas3_1Definition | Oas3Definition | Oas2Definition;
|
|
4
4
|
export interface ComponentsFiles {
|
|
5
5
|
[schemas: string]: any;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type RequestInit } from 'node-fetch';
|
|
2
1
|
export declare const DEFAULT_FETCH_TIMEOUT = 3000;
|
|
3
2
|
export type FetchWithTimeoutOptions = RequestInit & {
|
|
4
3
|
timeout?: number;
|
|
5
4
|
};
|
|
6
|
-
declare const _default: (url: string, { timeout, ...options }?: FetchWithTimeoutOptions) => Promise<
|
|
5
|
+
declare const _default: (url: string, { timeout, ...options }?: FetchWithTimeoutOptions) => Promise<Response>;
|
|
7
6
|
export default _default;
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_FETCH_TIMEOUT = void 0;
|
|
4
|
-
const node_fetch_1 = require("node-fetch");
|
|
5
|
-
const abort_controller_1 = require("abort-controller");
|
|
6
4
|
const openapi_core_1 = require("@redocly/openapi-core");
|
|
7
5
|
exports.DEFAULT_FETCH_TIMEOUT = 3000;
|
|
8
6
|
exports.default = async (url, { timeout, ...options } = {}) => {
|
|
9
7
|
if (!timeout) {
|
|
10
|
-
return (
|
|
8
|
+
return fetch(url, {
|
|
11
9
|
...options,
|
|
12
|
-
|
|
10
|
+
dispatcher: (0, openapi_core_1.getProxyAgent)(),
|
|
13
11
|
});
|
|
14
12
|
}
|
|
15
|
-
const controller = new
|
|
13
|
+
const controller = new globalThis.AbortController();
|
|
16
14
|
const timeoutId = setTimeout(() => {
|
|
17
15
|
controller.abort();
|
|
18
16
|
}, timeout);
|
|
19
|
-
const res = await (
|
|
17
|
+
const res = await fetch(url, {
|
|
20
18
|
signal: controller.signal,
|
|
21
19
|
...options,
|
|
22
|
-
|
|
20
|
+
dispatcher: (0, openapi_core_1.getProxyAgent)(),
|
|
23
21
|
});
|
|
24
22
|
clearTimeout(timeoutId);
|
|
25
23
|
return res;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.28.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"redocly": "bin/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
|
-
"node": ">=
|
|
12
|
-
"npm": ">=
|
|
11
|
+
"node": ">=18.17.0",
|
|
12
|
+
"npm": ">=10.8.2"
|
|
13
13
|
},
|
|
14
14
|
"engineStrict": true,
|
|
15
15
|
"scripts": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"Roman Hotsiy <roman@redocly.com> (https://redocly.com/)"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@redocly/openapi-core": "1.
|
|
39
|
+
"@redocly/openapi-core": "1.28.0",
|
|
40
40
|
"abort-controller": "^3.0.0",
|
|
41
41
|
"chokidar": "^3.5.1",
|
|
42
42
|
"colorette": "^1.2.0",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"glob": "^7.1.6",
|
|
47
47
|
"handlebars": "^4.7.6",
|
|
48
48
|
"mobx": "^6.0.4",
|
|
49
|
-
"node-fetch": "^2.6.1",
|
|
50
49
|
"pluralize": "^8.0.0",
|
|
51
50
|
"react": "^17.0.0 || ^18.2.0",
|
|
52
51
|
"react-dom": "^17.0.0 || ^18.2.0",
|
|
@@ -2,32 +2,71 @@ import { getMergedConfig } from '@redocly/openapi-core';
|
|
|
2
2
|
import { handlePush } from '../../commands/push';
|
|
3
3
|
import { promptClientToken } from '../../commands/login';
|
|
4
4
|
import { ConfigFixture } from '../fixtures/config';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
jest.mock('
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
// Mock fs operations
|
|
8
|
+
jest.mock('fs', () => ({
|
|
9
|
+
...jest.requireActual('fs'),
|
|
10
|
+
createReadStream: () => {
|
|
11
|
+
const readable = new Readable();
|
|
12
|
+
readable.push('test data');
|
|
13
|
+
readable.push(null);
|
|
14
|
+
return readable;
|
|
15
|
+
},
|
|
16
|
+
statSync: () => ({ size: 9 }),
|
|
17
|
+
readFileSync: () => Buffer.from('test data'),
|
|
18
|
+
existsSync: () => false,
|
|
19
|
+
readdirSync: () => [],
|
|
12
20
|
}));
|
|
21
|
+
|
|
22
|
+
(getMergedConfig as jest.Mock).mockImplementation((config) => config);
|
|
23
|
+
|
|
24
|
+
// Mock OpenAPI core
|
|
13
25
|
jest.mock('@redocly/openapi-core');
|
|
14
26
|
jest.mock('../../commands/login');
|
|
15
27
|
jest.mock('../../utils/miscellaneous');
|
|
16
28
|
|
|
17
|
-
(getMergedConfig as jest.Mock).mockImplementation((config) => config);
|
|
18
|
-
|
|
19
29
|
const mockPromptClientToken = promptClientToken as jest.MockedFunction<typeof promptClientToken>;
|
|
20
30
|
|
|
21
31
|
describe('push-with-region', () => {
|
|
22
32
|
const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
|
|
23
33
|
redoclyClient.isAuthorizedWithRedoclyByRegion = jest.fn().mockResolvedValue(false);
|
|
24
34
|
|
|
35
|
+
const originalFetch = fetch;
|
|
36
|
+
|
|
25
37
|
beforeAll(() => {
|
|
38
|
+
// Mock global fetch
|
|
39
|
+
global.fetch = jest.fn(() =>
|
|
40
|
+
Promise.resolve({
|
|
41
|
+
ok: true,
|
|
42
|
+
status: 200,
|
|
43
|
+
json: () => Promise.resolve({}),
|
|
44
|
+
headers: new Headers(),
|
|
45
|
+
statusText: 'OK',
|
|
46
|
+
redirected: false,
|
|
47
|
+
type: 'default',
|
|
48
|
+
url: '',
|
|
49
|
+
clone: () => ({} as Response),
|
|
50
|
+
body: new ReadableStream(),
|
|
51
|
+
bodyUsed: false,
|
|
52
|
+
arrayBuffer: async () => new ArrayBuffer(0),
|
|
53
|
+
blob: async () => new Blob(),
|
|
54
|
+
formData: async () => new FormData(),
|
|
55
|
+
text: async () => '',
|
|
56
|
+
} as Response)
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterAll(() => {
|
|
61
|
+
global.fetch = originalFetch;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
26
65
|
jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
27
66
|
});
|
|
28
67
|
|
|
29
68
|
it('should call login with default domain when region is US', async () => {
|
|
30
|
-
redoclyClient.domain = '
|
|
69
|
+
redoclyClient.domain = 'redocly.com';
|
|
31
70
|
await handlePush({
|
|
32
71
|
argv: {
|
|
33
72
|
upsert: true,
|
|
@@ -38,12 +77,16 @@ describe('push-with-region', () => {
|
|
|
38
77
|
config: ConfigFixture as any,
|
|
39
78
|
version: 'cli-version',
|
|
40
79
|
});
|
|
80
|
+
|
|
41
81
|
expect(mockPromptClientToken).toBeCalledTimes(1);
|
|
42
82
|
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
|
43
83
|
});
|
|
44
84
|
|
|
45
85
|
it('should call login with EU domain when region is EU', async () => {
|
|
46
86
|
redoclyClient.domain = 'eu.redocly.com';
|
|
87
|
+
// Update config for EU region
|
|
88
|
+
const euConfig = { ...ConfigFixture, region: 'eu' };
|
|
89
|
+
|
|
47
90
|
await handlePush({
|
|
48
91
|
argv: {
|
|
49
92
|
upsert: true,
|
|
@@ -51,9 +94,10 @@ describe('push-with-region', () => {
|
|
|
51
94
|
destination: '@org/my-api@1.0.0',
|
|
52
95
|
branchName: 'test',
|
|
53
96
|
},
|
|
54
|
-
config:
|
|
97
|
+
config: euConfig as any,
|
|
55
98
|
version: 'cli-version',
|
|
56
99
|
});
|
|
100
|
+
|
|
57
101
|
expect(mockPromptClientToken).toBeCalledTimes(1);
|
|
58
102
|
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
|
59
103
|
});
|