@redocly/cli 1.22.0 → 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 +18 -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
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import fetch from 'node-fetch';
|
|
2
1
|
import * as FormData from 'form-data';
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import fetchWithTimeout, {
|
|
3
|
+
type FetchWithTimeoutOptions,
|
|
4
|
+
DEFAULT_FETCH_TIMEOUT,
|
|
5
|
+
} from '../../utils/fetch-with-timeout';
|
|
5
6
|
|
|
6
7
|
import type { Response } from 'node-fetch';
|
|
7
8
|
import type { ReadStream } from 'fs';
|
|
@@ -12,41 +13,76 @@ import type {
|
|
|
12
13
|
UpsertRemoteResponse,
|
|
13
14
|
} from './types';
|
|
14
15
|
|
|
15
|
-
class
|
|
16
|
-
constructor(
|
|
16
|
+
export class ReuniteApiError extends Error {
|
|
17
|
+
constructor(message: string, public status: number) {
|
|
18
|
+
super(message);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
class ReuniteBaseApiClient {
|
|
23
|
+
constructor(protected version: string, protected command: string) {}
|
|
24
|
+
|
|
25
|
+
protected async getParsedResponse<T>(response: Response): Promise<T> {
|
|
19
26
|
const responseBody = await response.json();
|
|
20
27
|
|
|
21
28
|
if (response.ok) {
|
|
22
29
|
return responseBody as T;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
throw new
|
|
32
|
+
throw new ReuniteApiError(
|
|
33
|
+
`${responseBody.title || response.statusText || 'Unknown error'}.`,
|
|
34
|
+
response.status
|
|
35
|
+
);
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
headers: {
|
|
34
|
-
'Content-Type': 'application/json',
|
|
35
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
36
|
-
},
|
|
37
|
-
}
|
|
38
|
-
);
|
|
38
|
+
protected request(url: string, options: FetchWithTimeoutOptions) {
|
|
39
|
+
const headers = {
|
|
40
|
+
...options.headers,
|
|
41
|
+
'user-agent': `redocly-cli/${this.version.trim()} ${this.command}`,
|
|
42
|
+
};
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
return fetchWithTimeout(url, {
|
|
45
|
+
...options,
|
|
46
|
+
headers,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
|
|
51
|
+
class RemotesApiClient extends ReuniteBaseApiClient {
|
|
52
|
+
constructor(
|
|
53
|
+
private readonly domain: string,
|
|
54
|
+
private readonly apiKey: string,
|
|
55
|
+
version: string,
|
|
56
|
+
command: string
|
|
57
|
+
) {
|
|
58
|
+
super(version, command);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getDefaultBranch(organizationId: string, projectId: string) {
|
|
44
62
|
try {
|
|
63
|
+
const response = await this.request(
|
|
64
|
+
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/source`,
|
|
65
|
+
{
|
|
66
|
+
timeout: DEFAULT_FETCH_TIMEOUT,
|
|
67
|
+
method: 'GET',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
45
75
|
const source = await this.getParsedResponse<ProjectSourceResponse>(response);
|
|
46
76
|
|
|
47
77
|
return source.branchName;
|
|
48
78
|
} catch (err) {
|
|
49
|
-
|
|
79
|
+
const message = `Failed to fetch default branch. ${err.message}`;
|
|
80
|
+
|
|
81
|
+
if (err instanceof ReuniteApiError) {
|
|
82
|
+
throw new ReuniteApiError(message, err.status);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
throw new Error(message);
|
|
50
86
|
}
|
|
51
87
|
}
|
|
52
88
|
|
|
@@ -58,31 +94,34 @@ class RemotesApiClient {
|
|
|
58
94
|
mountBranchName: string;
|
|
59
95
|
}
|
|
60
96
|
): Promise<UpsertRemoteResponse> {
|
|
61
|
-
const response = await fetchWithTimeout(
|
|
62
|
-
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/remotes`,
|
|
63
|
-
{
|
|
64
|
-
method: 'POST',
|
|
65
|
-
headers: {
|
|
66
|
-
'Content-Type': 'application/json',
|
|
67
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
68
|
-
},
|
|
69
|
-
body: JSON.stringify({
|
|
70
|
-
mountPath: remote.mountPath,
|
|
71
|
-
mountBranchName: remote.mountBranchName,
|
|
72
|
-
type: 'CICD',
|
|
73
|
-
autoMerge: true,
|
|
74
|
-
}),
|
|
75
|
-
}
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
if (!response) {
|
|
79
|
-
throw new Error(`Failed to upsert.`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
97
|
try {
|
|
98
|
+
const response = await this.request(
|
|
99
|
+
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/remotes`,
|
|
100
|
+
{
|
|
101
|
+
timeout: DEFAULT_FETCH_TIMEOUT,
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify({
|
|
108
|
+
mountPath: remote.mountPath,
|
|
109
|
+
mountBranchName: remote.mountBranchName,
|
|
110
|
+
type: 'CICD',
|
|
111
|
+
autoMerge: true,
|
|
112
|
+
}),
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
83
116
|
return await this.getParsedResponse<UpsertRemoteResponse>(response);
|
|
84
117
|
} catch (err) {
|
|
85
|
-
|
|
118
|
+
const message = `Failed to upsert remote. ${err.message}`;
|
|
119
|
+
|
|
120
|
+
if (err instanceof ReuniteApiError) {
|
|
121
|
+
throw new ReuniteApiError(message, err.status);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new Error(message);
|
|
86
125
|
}
|
|
87
126
|
}
|
|
88
127
|
|
|
@@ -110,46 +149,61 @@ class RemotesApiClient {
|
|
|
110
149
|
}
|
|
111
150
|
|
|
112
151
|
payload.isMainBranch && formData.append('isMainBranch', 'true');
|
|
113
|
-
|
|
114
|
-
const response = await fetch(
|
|
115
|
-
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/pushes`,
|
|
116
|
-
{
|
|
117
|
-
method: 'POST',
|
|
118
|
-
headers: {
|
|
119
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
120
|
-
},
|
|
121
|
-
body: formData,
|
|
122
|
-
agent: getProxyAgent(),
|
|
123
|
-
}
|
|
124
|
-
);
|
|
125
|
-
|
|
126
152
|
try {
|
|
153
|
+
const response = await this.request(
|
|
154
|
+
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/pushes`,
|
|
155
|
+
{
|
|
156
|
+
method: 'POST',
|
|
157
|
+
headers: {
|
|
158
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
159
|
+
},
|
|
160
|
+
body: formData,
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
127
164
|
return await this.getParsedResponse<PushResponse>(response);
|
|
128
165
|
} catch (err) {
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
}
|
|
166
|
+
const message = `Failed to push. ${err.message}`;
|
|
132
167
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/remotes?filter=mountPath:/${mountPath}/`,
|
|
136
|
-
{
|
|
137
|
-
method: 'GET',
|
|
138
|
-
headers: {
|
|
139
|
-
'Content-Type': 'application/json',
|
|
140
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
141
|
-
},
|
|
168
|
+
if (err instanceof ReuniteApiError) {
|
|
169
|
+
throw new ReuniteApiError(message, err.status);
|
|
142
170
|
}
|
|
143
|
-
);
|
|
144
171
|
|
|
145
|
-
|
|
146
|
-
throw new Error(`Failed to get remotes list.`);
|
|
172
|
+
throw new Error(message);
|
|
147
173
|
}
|
|
174
|
+
}
|
|
148
175
|
|
|
176
|
+
async getRemotesList({
|
|
177
|
+
organizationId,
|
|
178
|
+
projectId,
|
|
179
|
+
mountPath,
|
|
180
|
+
}: {
|
|
181
|
+
organizationId: string;
|
|
182
|
+
projectId: string;
|
|
183
|
+
mountPath: string;
|
|
184
|
+
}) {
|
|
149
185
|
try {
|
|
186
|
+
const response = await this.request(
|
|
187
|
+
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/remotes?filter=mountPath:/${mountPath}/`,
|
|
188
|
+
{
|
|
189
|
+
timeout: DEFAULT_FETCH_TIMEOUT,
|
|
190
|
+
method: 'GET',
|
|
191
|
+
headers: {
|
|
192
|
+
'Content-Type': 'application/json',
|
|
193
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
|
|
150
198
|
return await this.getParsedResponse<ListRemotesResponse>(response);
|
|
151
199
|
} catch (err) {
|
|
152
|
-
|
|
200
|
+
const message = `Failed to get remote list. ${err.message}`;
|
|
201
|
+
|
|
202
|
+
if (err instanceof ReuniteApiError) {
|
|
203
|
+
throw new ReuniteApiError(message, err.status);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
throw new Error(message);
|
|
153
207
|
}
|
|
154
208
|
}
|
|
155
209
|
|
|
@@ -162,25 +216,28 @@ class RemotesApiClient {
|
|
|
162
216
|
projectId: string;
|
|
163
217
|
pushId: string;
|
|
164
218
|
}) {
|
|
165
|
-
const response = await fetchWithTimeout(
|
|
166
|
-
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/pushes/${pushId}`,
|
|
167
|
-
{
|
|
168
|
-
method: 'GET',
|
|
169
|
-
headers: {
|
|
170
|
-
'Content-Type': 'application/json',
|
|
171
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
172
|
-
},
|
|
173
|
-
}
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
if (!response) {
|
|
177
|
-
throw new Error(`Failed to get push status.`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
219
|
try {
|
|
220
|
+
const response = await this.request(
|
|
221
|
+
`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/pushes/${pushId}`,
|
|
222
|
+
{
|
|
223
|
+
timeout: DEFAULT_FETCH_TIMEOUT,
|
|
224
|
+
method: 'GET',
|
|
225
|
+
headers: {
|
|
226
|
+
'Content-Type': 'application/json',
|
|
227
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
|
|
181
232
|
return await this.getParsedResponse<PushResponse>(response);
|
|
182
233
|
} catch (err) {
|
|
183
|
-
|
|
234
|
+
const message = `Failed to get push status. ${err.message}`;
|
|
235
|
+
|
|
236
|
+
if (err instanceof ReuniteApiError) {
|
|
237
|
+
throw new ReuniteApiError(message, err.status);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error(message);
|
|
184
241
|
}
|
|
185
242
|
}
|
|
186
243
|
}
|
|
@@ -188,8 +245,18 @@ class RemotesApiClient {
|
|
|
188
245
|
export class ReuniteApiClient {
|
|
189
246
|
remotes: RemotesApiClient;
|
|
190
247
|
|
|
191
|
-
constructor(
|
|
192
|
-
|
|
248
|
+
constructor({
|
|
249
|
+
domain,
|
|
250
|
+
apiKey,
|
|
251
|
+
version,
|
|
252
|
+
command,
|
|
253
|
+
}: {
|
|
254
|
+
domain: string;
|
|
255
|
+
apiKey: string;
|
|
256
|
+
version: string;
|
|
257
|
+
command: 'push' | 'push-status';
|
|
258
|
+
}) {
|
|
259
|
+
this.remotes = new RemotesApiClient(domain, apiKey, version, command);
|
|
193
260
|
}
|
|
194
261
|
}
|
|
195
262
|
|
|
@@ -644,7 +644,7 @@ describe('handlePushStatus()', () => {
|
|
|
644
644
|
version: 'cli-version',
|
|
645
645
|
})
|
|
646
646
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
647
|
-
"✗ Failed to get push status. Reason: Timeout exceeded
|
|
647
|
+
"✗ Failed to get push status. Reason: Timeout exceeded.
|
|
648
648
|
"
|
|
649
649
|
`);
|
|
650
650
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { handlePush } from '../push';
|
|
4
|
-
import { ReuniteApiClient } from '../../api';
|
|
4
|
+
import { ReuniteApiClient, ReuniteApiError } from '../../api';
|
|
5
5
|
|
|
6
6
|
const remotes = {
|
|
7
7
|
push: jest.fn(),
|
|
@@ -332,6 +332,53 @@ describe('handlePush()', () => {
|
|
|
332
332
|
version: 'cli-version',
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
-
expect(ReuniteApiClient).toBeCalledWith(
|
|
335
|
+
expect(ReuniteApiClient).toBeCalledWith({
|
|
336
|
+
domain: 'test-domain-from-env',
|
|
337
|
+
apiKey: 'test-api-key',
|
|
338
|
+
version: 'cli-version',
|
|
339
|
+
command: 'push',
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should print error message', async () => {
|
|
344
|
+
const mockConfig = { apis: {} } as any;
|
|
345
|
+
process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
|
|
346
|
+
|
|
347
|
+
remotes.push.mockRestore();
|
|
348
|
+
remotes.push.mockRejectedValueOnce(new ReuniteApiError('Deprecated.', 412));
|
|
349
|
+
|
|
350
|
+
fsStatSyncSpy.mockReturnValueOnce({
|
|
351
|
+
isDirectory() {
|
|
352
|
+
return false;
|
|
353
|
+
},
|
|
354
|
+
} as any);
|
|
355
|
+
|
|
356
|
+
pathResolveSpy.mockImplementationOnce((p) => p);
|
|
357
|
+
pathRelativeSpy.mockImplementationOnce((_, p) => p);
|
|
358
|
+
pathDirnameSpy.mockImplementation((_: string) => '.');
|
|
359
|
+
|
|
360
|
+
expect(
|
|
361
|
+
handlePush({
|
|
362
|
+
argv: {
|
|
363
|
+
domain: 'test-domain',
|
|
364
|
+
'mount-path': 'test-mount-path',
|
|
365
|
+
organization: 'test-org',
|
|
366
|
+
project: 'test-project',
|
|
367
|
+
branch: 'test-branch',
|
|
368
|
+
namespace: 'test-namespace',
|
|
369
|
+
repository: 'test-repository',
|
|
370
|
+
'commit-sha': 'test-commit-sha',
|
|
371
|
+
'commit-url': 'test-commit-url',
|
|
372
|
+
'default-branch': 'test-branch',
|
|
373
|
+
'created-at': 'test-created-at',
|
|
374
|
+
author: 'TestAuthor <test-author@mail.com>',
|
|
375
|
+
message: 'Test message',
|
|
376
|
+
files: ['test-file'],
|
|
377
|
+
'max-execution-time': 10,
|
|
378
|
+
},
|
|
379
|
+
config: mockConfig,
|
|
380
|
+
version: 'cli-version',
|
|
381
|
+
})
|
|
382
|
+
).rejects.toThrow('✗ File upload failed. Reason: Deprecated.');
|
|
336
383
|
});
|
|
337
384
|
});
|
|
@@ -32,7 +32,7 @@ describe('retryUntilConditionMet()', () => {
|
|
|
32
32
|
retryIntervalMs: 100,
|
|
33
33
|
retryTimeoutMs: 1000,
|
|
34
34
|
})
|
|
35
|
-
).rejects.toThrow('Timeout exceeded');
|
|
35
|
+
).rejects.toThrow('Timeout exceeded.');
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
it('should call "onConditionNotMet" and "onRetry" callbacks', async () => {
|
|
@@ -4,7 +4,7 @@ import { Spinner } from '../../utils/spinner';
|
|
|
4
4
|
import { DeploymentError } from '../utils';
|
|
5
5
|
import { ReuniteApiClient, getApiKeys, getDomain } from '../api';
|
|
6
6
|
import { capitalize } from '../../utils/js-utils';
|
|
7
|
-
import { retryUntilConditionMet } from './utils';
|
|
7
|
+
import { handleReuniteError, retryUntilConditionMet } from './utils';
|
|
8
8
|
|
|
9
9
|
import type { OutputFormat } from '@redocly/openapi-core';
|
|
10
10
|
import type { CommandArgs } from '../../wrapper';
|
|
@@ -41,7 +41,8 @@ export interface PushStatusSummary {
|
|
|
41
41
|
export async function handlePushStatus({
|
|
42
42
|
argv,
|
|
43
43
|
config,
|
|
44
|
-
|
|
44
|
+
version,
|
|
45
|
+
}: CommandArgs<PushStatusOptions>): Promise<PushStatusSummary | void> {
|
|
45
46
|
const startedAt = performance.now();
|
|
46
47
|
const spinner = new Spinner();
|
|
47
48
|
|
|
@@ -67,7 +68,7 @@ export async function handlePushStatus({
|
|
|
67
68
|
|
|
68
69
|
try {
|
|
69
70
|
const apiKey = getApiKeys(domain);
|
|
70
|
-
const client = new ReuniteApiClient(domain, apiKey);
|
|
71
|
+
const client = new ReuniteApiClient({ domain, apiKey, version, command: 'push-status' });
|
|
71
72
|
|
|
72
73
|
let pushResponse: PushResponse;
|
|
73
74
|
|
|
@@ -178,12 +179,7 @@ export async function handlePushStatus({
|
|
|
178
179
|
} catch (err) {
|
|
179
180
|
spinner.stop(); // Spinner can block process exit, so we need to stop it explicitly.
|
|
180
181
|
|
|
181
|
-
|
|
182
|
-
err instanceof DeploymentError
|
|
183
|
-
? err.message
|
|
184
|
-
: `✗ Failed to get push status. Reason: ${err.message}\n`;
|
|
185
|
-
exitWithError(message);
|
|
186
|
-
return;
|
|
182
|
+
handleReuniteError('✗ Failed to get push status.', err);
|
|
187
183
|
} finally {
|
|
188
184
|
spinner.stop(); // Spinner can block process exit, so we need to stop it explicitly.
|
|
189
185
|
}
|
package/src/cms/commands/push.ts
CHANGED
|
@@ -3,9 +3,10 @@ import * as path from 'path';
|
|
|
3
3
|
import { slash } from '@redocly/openapi-core';
|
|
4
4
|
import { pluralize } from '@redocly/openapi-core/lib/utils';
|
|
5
5
|
import { green, yellow } from 'colorette';
|
|
6
|
-
import { exitWithError,
|
|
6
|
+
import { exitWithError, printExecutionTime } from '../../utils/miscellaneous';
|
|
7
7
|
import { handlePushStatus } from './push-status';
|
|
8
8
|
import { ReuniteApiClient, getDomain, getApiKeys } from '../api';
|
|
9
|
+
import { handleReuniteError } from './utils';
|
|
9
10
|
|
|
10
11
|
import type { OutputFormat } from '@redocly/openapi-core';
|
|
11
12
|
import type { CommandArgs } from '../../wrapper';
|
|
@@ -52,7 +53,7 @@ export async function handlePush({
|
|
|
52
53
|
const orgId = organization || config.organization;
|
|
53
54
|
|
|
54
55
|
if (!argv.message || !argv.author || !argv.branch) {
|
|
55
|
-
exitWithError('Error: message, author and branch are required for push to the
|
|
56
|
+
exitWithError('Error: message, author and branch are required for push to the Reunite.');
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
if (!orgId) {
|
|
@@ -85,7 +86,7 @@ export async function handlePush({
|
|
|
85
86
|
return printExecutionTime('push', startedAt, `No files to upload`);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
const client = new ReuniteApiClient(domain, apiKey);
|
|
89
|
+
const client = new ReuniteApiClient({ domain, apiKey, version, command: 'push' });
|
|
89
90
|
const projectDefaultBranch = await client.remotes.getDefaultBranch(orgId, projectId);
|
|
90
91
|
const remote = await client.remotes.upsert(orgId, projectId, {
|
|
91
92
|
mountBranchName: projectDefaultBranch,
|
|
@@ -158,9 +159,7 @@ export async function handlePush({
|
|
|
158
159
|
pushId: id,
|
|
159
160
|
};
|
|
160
161
|
} catch (err) {
|
|
161
|
-
|
|
162
|
-
err instanceof HandledError ? '' : `✗ File upload failed. Reason: ${err.message}`;
|
|
163
|
-
exitWithError(message);
|
|
162
|
+
handleReuniteError('✗ File upload failed.', err);
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
165
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { pause } from '@redocly/openapi-core';
|
|
2
|
+
import { DeploymentError } from '../utils';
|
|
3
|
+
import { exitWithError } from '../../utils/miscellaneous';
|
|
4
|
+
|
|
5
|
+
import type { ReuniteApiError } from '../api';
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* This function retries an operation until a condition is met or a timeout is exceeded.
|
|
@@ -39,7 +43,7 @@ export async function retryUntilConditionMet<T>({
|
|
|
39
43
|
if (condition(result)) {
|
|
40
44
|
return result;
|
|
41
45
|
} else if (Date.now() - startTime > retryTimeoutMs) {
|
|
42
|
-
throw new Error('Timeout exceeded');
|
|
46
|
+
throw new Error('Timeout exceeded.');
|
|
43
47
|
} else {
|
|
44
48
|
onConditionNotMet?.(result);
|
|
45
49
|
await pause(retryIntervalMs);
|
|
@@ -50,3 +54,13 @@ export async function retryUntilConditionMet<T>({
|
|
|
50
54
|
|
|
51
55
|
return attempt();
|
|
52
56
|
}
|
|
57
|
+
|
|
58
|
+
export function handleReuniteError(
|
|
59
|
+
message: string,
|
|
60
|
+
error: ReuniteApiError | DeploymentError | Error
|
|
61
|
+
) {
|
|
62
|
+
const errorMessage =
|
|
63
|
+
error instanceof DeploymentError ? error.message : `${message} Reason: ${error.message}\n`;
|
|
64
|
+
|
|
65
|
+
return exitWithError(errorMessage);
|
|
66
|
+
}
|
package/src/commands/bundle.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type BundleOptions = {
|
|
|
21
21
|
apis?: string[];
|
|
22
22
|
extends?: string[];
|
|
23
23
|
output?: string;
|
|
24
|
-
ext
|
|
24
|
+
ext?: OutputExtensions;
|
|
25
25
|
dereferenced?: boolean;
|
|
26
26
|
force?: boolean;
|
|
27
27
|
metafile?: string;
|
|
@@ -45,7 +45,7 @@ export async function handleBundle({
|
|
|
45
45
|
|
|
46
46
|
checkForDeprecatedOptions(argv, deprecatedOptions);
|
|
47
47
|
|
|
48
|
-
for (const { path, alias } of apis) {
|
|
48
|
+
for (const { path, alias, output } of apis) {
|
|
49
49
|
try {
|
|
50
50
|
const startedAt = performance.now();
|
|
51
51
|
const resolvedConfig = getMergedConfig(config, alias);
|
|
@@ -70,19 +70,19 @@ export async function handleBundle({
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
const fileTotals = getTotals(problems);
|
|
73
|
-
const { outputFile, ext } = getOutputFileName(path,
|
|
73
|
+
const { outputFile, ext } = getOutputFileName(path, output || argv.output, argv.ext);
|
|
74
74
|
|
|
75
75
|
if (fileTotals.errors === 0 || argv.force) {
|
|
76
|
-
if (!
|
|
77
|
-
const
|
|
76
|
+
if (!outputFile) {
|
|
77
|
+
const bundled = dumpBundle(
|
|
78
78
|
sortTopLevelKeysForOas(result.parsed),
|
|
79
79
|
argv.ext || 'yaml',
|
|
80
80
|
argv.dereferenced
|
|
81
81
|
);
|
|
82
|
-
process.stdout.write(
|
|
82
|
+
process.stdout.write(bundled);
|
|
83
83
|
} else {
|
|
84
|
-
const
|
|
85
|
-
saveBundle(outputFile,
|
|
84
|
+
const bundled = dumpBundle(sortTopLevelKeysForOas(result.parsed), ext, argv.dereferenced);
|
|
85
|
+
saveBundle(outputFile, bundled);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -111,9 +111,9 @@ export async function handleBundle({
|
|
|
111
111
|
if (fileTotals.errors > 0) {
|
|
112
112
|
if (argv.force) {
|
|
113
113
|
process.stderr.write(
|
|
114
|
-
`❓ Created a bundle for ${blue(path)} at ${blue(
|
|
115
|
-
|
|
116
|
-
)}.\n${yellow('Errors ignored because of --force')}.\n`
|
|
114
|
+
`❓ Created a bundle for ${blue(path)} at ${blue(
|
|
115
|
+
outputFile || 'stdout'
|
|
116
|
+
)} with errors ${green(elapsed)}.\n${yellow('Errors ignored because of --force')}.\n`
|
|
117
117
|
);
|
|
118
118
|
} else {
|
|
119
119
|
process.stderr.write(
|
|
@@ -124,7 +124,9 @@ export async function handleBundle({
|
|
|
124
124
|
}
|
|
125
125
|
} else {
|
|
126
126
|
process.stderr.write(
|
|
127
|
-
`📦 Created a bundle for ${blue(path)} at ${blue(outputFile)} ${green(
|
|
127
|
+
`📦 Created a bundle for ${blue(path)} at ${blue(outputFile || 'stdout')} ${green(
|
|
128
|
+
elapsed
|
|
129
|
+
)}.\n`
|
|
128
130
|
);
|
|
129
131
|
}
|
|
130
132
|
|
package/src/commands/eject.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { VerifyConfigOptions } from '../types';
|
|
|
5
5
|
|
|
6
6
|
export type EjectOptions = {
|
|
7
7
|
type: 'component';
|
|
8
|
-
path
|
|
8
|
+
path?: string;
|
|
9
9
|
'project-dir'?: string;
|
|
10
10
|
force: boolean;
|
|
11
11
|
} & VerifyConfigOptions;
|
|
@@ -20,7 +20,7 @@ export const handleEject = async ({ argv }: CommandArgs<EjectOptions>) => {
|
|
|
20
20
|
'@redocly/realm',
|
|
21
21
|
'eject',
|
|
22
22
|
`${argv.type}`,
|
|
23
|
-
`${argv.path}`,
|
|
23
|
+
`${argv.path ?? ''}`,
|
|
24
24
|
`-d=${argv['project-dir']}`,
|
|
25
25
|
argv.force ? `--force=${argv.force}` : '',
|
|
26
26
|
],
|
|
@@ -26,7 +26,7 @@ export const previewProject = async ({ argv }: CommandArgs<PreviewProjectOptions
|
|
|
26
26
|
|
|
27
27
|
spawn(
|
|
28
28
|
npxExecutableName,
|
|
29
|
-
['-y', packageName, '
|
|
29
|
+
['-y', packageName, 'preview', `--plan=${plan}`, `--port=${port || 4000}`],
|
|
30
30
|
{
|
|
31
31
|
stdio: 'inherit',
|
|
32
32
|
cwd: projectDir,
|
package/src/index.ts
CHANGED
|
@@ -817,7 +817,7 @@ yargs
|
|
|
817
817
|
}
|
|
818
818
|
)
|
|
819
819
|
.command(
|
|
820
|
-
'eject <type>
|
|
820
|
+
'eject <type> [path]',
|
|
821
821
|
'Helper function to eject project elements for customization.',
|
|
822
822
|
(yargs) =>
|
|
823
823
|
yargs
|
|
@@ -830,7 +830,6 @@ yargs
|
|
|
830
830
|
.positional('path', {
|
|
831
831
|
description: 'Filepath to a component or filepath with glob pattern.',
|
|
832
832
|
type: 'string',
|
|
833
|
-
demandOption: true,
|
|
834
833
|
})
|
|
835
834
|
.options({
|
|
836
835
|
'project-dir': {
|
package/src/types.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type Totals = {
|
|
|
23
23
|
export type Entrypoint = {
|
|
24
24
|
path: string;
|
|
25
25
|
alias?: string;
|
|
26
|
+
output?: string;
|
|
26
27
|
};
|
|
27
28
|
export const outputExtensions = ['json', 'yaml', 'yml'] as ReadonlyArray<BundleOutputFormat>;
|
|
28
29
|
export type OutputExtensions = 'json' | 'yaml' | 'yml' | undefined;
|
|
@@ -20,3 +20,4 @@ export const sortTopLevelKeysForOas = jest.fn((document) => document);
|
|
|
20
20
|
export const getAndValidateFileExtension = jest.fn((fileName: string) => fileName.split('.').pop());
|
|
21
21
|
export const writeToFileByExtension = jest.fn();
|
|
22
22
|
export const checkForDeprecatedOptions = jest.fn();
|
|
23
|
+
export const saveBundle = jest.fn();
|