@redocly/cli 1.11.0 → 1.12.1
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/lib/__tests__/utils.test.js +3 -3
- package/lib/cms/api/types.d.ts +22 -11
- package/lib/cms/commands/__tests__/push-status.test.js +338 -29
- package/lib/cms/commands/__tests__/push.test.js +32 -2
- package/lib/cms/commands/__tests__/utils.test.d.ts +1 -0
- package/lib/cms/commands/__tests__/utils.test.js +60 -0
- package/lib/cms/commands/push-status.d.ts +14 -4
- package/lib/cms/commands/push-status.js +160 -90
- package/lib/cms/commands/push.d.ts +6 -2
- package/lib/cms/commands/push.js +8 -2
- package/lib/cms/commands/utils.d.ts +22 -0
- package/lib/cms/commands/utils.js +53 -0
- package/lib/index.js +12 -1
- package/lib/utils/miscellaneous.js +5 -4
- package/lib/wrapper.d.ts +1 -1
- package/package.json +2 -2
- package/src/__tests__/utils.test.ts +3 -3
- package/src/cms/api/types.ts +19 -12
- package/src/cms/commands/__tests__/push-status.test.ts +473 -47
- package/src/cms/commands/__tests__/push.test.ts +40 -2
- package/src/cms/commands/__tests__/utils.test.ts +62 -0
- package/src/cms/commands/push-status.ts +242 -120
- package/src/cms/commands/push.ts +21 -5
- package/src/cms/commands/utils.ts +52 -0
- package/src/index.ts +13 -2
- package/src/utils/miscellaneous.ts +5 -4
- package/src/wrapper.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
const utils_1 = require("../utils");
|
|
13
|
+
jest.mock('@redocly/openapi-core', () => ({
|
|
14
|
+
pause: jest.requireActual('@redocly/openapi-core').pause,
|
|
15
|
+
}));
|
|
16
|
+
describe('retryUntilConditionMet()', () => {
|
|
17
|
+
it('should retry until condition meet and return result', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
const operation = jest
|
|
19
|
+
.fn()
|
|
20
|
+
.mockResolvedValueOnce({ status: 'pending' })
|
|
21
|
+
.mockResolvedValueOnce({ status: 'pending' })
|
|
22
|
+
.mockResolvedValueOnce({ status: 'done' });
|
|
23
|
+
const data = yield (0, utils_1.retryUntilConditionMet)({
|
|
24
|
+
operation,
|
|
25
|
+
condition: (result) => (result === null || result === void 0 ? void 0 : result.status) === 'done',
|
|
26
|
+
retryIntervalMs: 100,
|
|
27
|
+
retryTimeoutMs: 1000,
|
|
28
|
+
});
|
|
29
|
+
expect(data).toEqual({ status: 'done' });
|
|
30
|
+
}));
|
|
31
|
+
it('should throw error if condition not meet for desired timeout', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
|
+
const operation = jest.fn().mockResolvedValue({ status: 'pending' });
|
|
33
|
+
yield expect((0, utils_1.retryUntilConditionMet)({
|
|
34
|
+
operation,
|
|
35
|
+
condition: (result) => (result === null || result === void 0 ? void 0 : result.status) === 'done',
|
|
36
|
+
retryIntervalMs: 100,
|
|
37
|
+
retryTimeoutMs: 1000,
|
|
38
|
+
})).rejects.toThrow('Timeout exceeded');
|
|
39
|
+
}));
|
|
40
|
+
it('should call "onConditionNotMet" and "onRetry" callbacks', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
+
const operation = jest
|
|
42
|
+
.fn()
|
|
43
|
+
.mockResolvedValueOnce({ status: 'pending' })
|
|
44
|
+
.mockResolvedValueOnce({ status: 'pending' })
|
|
45
|
+
.mockResolvedValueOnce({ status: 'done' });
|
|
46
|
+
const onConditionNotMet = jest.fn();
|
|
47
|
+
const onRetry = jest.fn();
|
|
48
|
+
const data = yield (0, utils_1.retryUntilConditionMet)({
|
|
49
|
+
operation,
|
|
50
|
+
condition: (result) => (result === null || result === void 0 ? void 0 : result.status) === 'done',
|
|
51
|
+
retryIntervalMs: 100,
|
|
52
|
+
retryTimeoutMs: 1000,
|
|
53
|
+
onConditionNotMet,
|
|
54
|
+
onRetry,
|
|
55
|
+
});
|
|
56
|
+
expect(data).toEqual({ status: 'done' });
|
|
57
|
+
expect(onConditionNotMet).toHaveBeenCalledTimes(2);
|
|
58
|
+
expect(onRetry).toHaveBeenCalledTimes(2);
|
|
59
|
+
}));
|
|
60
|
+
});
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { Config } from '@redocly/openapi-core';
|
|
1
|
+
import type { Config, OutputFormat } from '@redocly/openapi-core';
|
|
2
|
+
import type { DeploymentStatusResponse, PushResponse } from '../api/types';
|
|
2
3
|
export type PushStatusOptions = {
|
|
3
4
|
organization: string;
|
|
4
5
|
project: string;
|
|
5
6
|
pushId: string;
|
|
6
7
|
domain?: string;
|
|
7
8
|
config?: string;
|
|
8
|
-
format?: 'stylish'
|
|
9
|
+
format?: Extract<OutputFormat, 'stylish'>;
|
|
9
10
|
wait?: boolean;
|
|
10
|
-
'max-execution-time'
|
|
11
|
+
'max-execution-time'?: number;
|
|
12
|
+
'retry-interval'?: number;
|
|
13
|
+
'start-time'?: number;
|
|
14
|
+
'continue-on-deploy-failures'?: boolean;
|
|
15
|
+
onRetry?: (lasSummary: PushStatusSummary) => void;
|
|
11
16
|
};
|
|
12
|
-
export
|
|
17
|
+
export interface PushStatusSummary {
|
|
18
|
+
preview: DeploymentStatusResponse;
|
|
19
|
+
production: DeploymentStatusResponse | null;
|
|
20
|
+
commit: PushResponse['commit'];
|
|
21
|
+
}
|
|
22
|
+
export declare function handlePushStatus(argv: PushStatusOptions, config: Config): Promise<PushStatusSummary | undefined>;
|
|
@@ -14,10 +14,10 @@ const colors = require("colorette");
|
|
|
14
14
|
const miscellaneous_1 = require("../../utils/miscellaneous");
|
|
15
15
|
const spinner_1 = require("../../utils/spinner");
|
|
16
16
|
const utils_1 = require("../utils");
|
|
17
|
-
const colorette_1 = require("colorette");
|
|
18
17
|
const api_1 = require("../api");
|
|
19
18
|
const js_utils_1 = require("../../utils/js-utils");
|
|
20
|
-
const
|
|
19
|
+
const utils_2 = require("./utils");
|
|
20
|
+
const RETRY_INTERVAL_MS = 5000; // 5 sec
|
|
21
21
|
function handlePushStatus(argv, config) {
|
|
22
22
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23
23
|
const startedAt = performance.now();
|
|
@@ -25,97 +25,154 @@ function handlePushStatus(argv, config) {
|
|
|
25
25
|
const { organization, project: projectId, pushId, wait } = argv;
|
|
26
26
|
const orgId = organization || config.organization;
|
|
27
27
|
if (!orgId) {
|
|
28
|
-
|
|
28
|
+
(0, miscellaneous_1.exitWithError)(`No organization provided, please use --organization option or specify the 'organization' field in the config file.`);
|
|
29
|
+
return;
|
|
29
30
|
}
|
|
30
31
|
const domain = argv.domain || (0, api_1.getDomain)();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const maxExecutionTime = argv['max-execution-time'] || 1200; // 20 min
|
|
33
|
+
const retryIntervalMs = argv['retry-interval']
|
|
34
|
+
? argv['retry-interval'] * 1000
|
|
35
|
+
: RETRY_INTERVAL_MS;
|
|
36
|
+
const startTime = argv['start-time'] || Date.now();
|
|
37
|
+
const retryTimeoutMs = maxExecutionTime * 1000;
|
|
38
|
+
const continueOnDeployFailures = argv['continue-on-deploy-failures'] || false;
|
|
35
39
|
try {
|
|
36
40
|
const apiKey = (0, api_1.getApiKeys)(domain);
|
|
37
41
|
const client = new api_1.ReuniteApiClient(domain, apiKey);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
let pushResponse;
|
|
43
|
+
pushResponse = yield (0, utils_2.retryUntilConditionMet)({
|
|
44
|
+
operation: () => client.remotes.getPush({
|
|
45
|
+
organizationId: orgId,
|
|
46
|
+
projectId,
|
|
47
|
+
pushId,
|
|
48
|
+
}),
|
|
49
|
+
condition: wait
|
|
50
|
+
? // Keep retrying if status is "pending" or "running" (returning false, so the operation will be retried)
|
|
51
|
+
(result) => !['pending', 'running'].includes(result.status['preview'].deploy.status)
|
|
52
|
+
: null,
|
|
53
|
+
onConditionNotMet: (lastResult) => {
|
|
54
|
+
displayDeploymentAndBuildStatus({
|
|
55
|
+
status: lastResult.status['preview'].deploy.status,
|
|
56
|
+
url: lastResult.status['preview'].deploy.url,
|
|
57
|
+
spinner,
|
|
58
|
+
buildType: 'preview',
|
|
59
|
+
continueOnDeployFailures,
|
|
60
|
+
wait,
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
onRetry: (lastResult) => {
|
|
64
|
+
if (argv.onRetry) {
|
|
65
|
+
argv.onRetry({
|
|
66
|
+
preview: lastResult.status.preview,
|
|
67
|
+
production: lastResult.isMainBranch ? lastResult.status.production : null,
|
|
68
|
+
commit: lastResult.commit,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
startTime,
|
|
73
|
+
retryTimeoutMs,
|
|
74
|
+
retryIntervalMs,
|
|
75
|
+
});
|
|
76
|
+
printPushStatus({
|
|
77
|
+
buildType: 'preview',
|
|
78
|
+
spinner,
|
|
79
|
+
wait,
|
|
80
|
+
push: pushResponse,
|
|
81
|
+
continueOnDeployFailures,
|
|
82
|
+
});
|
|
83
|
+
printScorecard(pushResponse.status.preview.scorecard);
|
|
84
|
+
const shouldWaitForProdDeployment = pushResponse.isMainBranch &&
|
|
85
|
+
(wait ? pushResponse.status.preview.deploy.status === 'success' : true);
|
|
86
|
+
if (shouldWaitForProdDeployment) {
|
|
87
|
+
pushResponse = yield (0, utils_2.retryUntilConditionMet)({
|
|
88
|
+
operation: () => client.remotes.getPush({
|
|
89
|
+
organizationId: orgId,
|
|
90
|
+
projectId,
|
|
91
|
+
pushId,
|
|
92
|
+
}),
|
|
93
|
+
condition: wait
|
|
94
|
+
? // Keep retrying if status is "pending" or "running" (returning false, so the operation will be retried)
|
|
95
|
+
(result) => !['pending', 'running'].includes(result.status['production'].deploy.status)
|
|
96
|
+
: null,
|
|
97
|
+
onConditionNotMet: (lastResult) => {
|
|
98
|
+
displayDeploymentAndBuildStatus({
|
|
99
|
+
status: lastResult.status['production'].deploy.status,
|
|
100
|
+
url: lastResult.status['production'].deploy.url,
|
|
101
|
+
spinner,
|
|
102
|
+
buildType: 'production',
|
|
103
|
+
continueOnDeployFailures,
|
|
104
|
+
wait,
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
onRetry: (lastResult) => {
|
|
108
|
+
if (argv.onRetry) {
|
|
109
|
+
argv.onRetry({
|
|
110
|
+
preview: lastResult.status.preview,
|
|
111
|
+
production: lastResult.isMainBranch ? lastResult.status.production : null,
|
|
112
|
+
commit: lastResult.commit,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
startTime,
|
|
117
|
+
retryTimeoutMs,
|
|
118
|
+
retryIntervalMs,
|
|
119
|
+
});
|
|
45
120
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
121
|
+
if (pushResponse.isMainBranch) {
|
|
122
|
+
printPushStatus({
|
|
123
|
+
buildType: 'production',
|
|
124
|
+
spinner,
|
|
125
|
+
wait,
|
|
126
|
+
push: pushResponse,
|
|
127
|
+
continueOnDeployFailures,
|
|
128
|
+
});
|
|
129
|
+
printScorecard(pushResponse.status.production.scorecard);
|
|
51
130
|
}
|
|
52
|
-
printPushStatusInfo();
|
|
131
|
+
printPushStatusInfo({ orgId, projectId, pushId, startedAt });
|
|
132
|
+
const summary = {
|
|
133
|
+
preview: pushResponse.status.preview,
|
|
134
|
+
production: pushResponse.isMainBranch ? pushResponse.status.production : null,
|
|
135
|
+
commit: pushResponse.commit,
|
|
136
|
+
};
|
|
137
|
+
return summary;
|
|
53
138
|
}
|
|
54
139
|
catch (err) {
|
|
140
|
+
spinner.stop(); // Spinner can block process exit, so we need to stop it explicitly.
|
|
55
141
|
const message = err instanceof utils_1.DeploymentError
|
|
56
142
|
? err.message
|
|
57
143
|
: `✗ Failed to get push status. Reason: ${err.message}\n`;
|
|
58
144
|
(0, miscellaneous_1.exitWithError)(message);
|
|
145
|
+
return;
|
|
59
146
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
(0, miscellaneous_1.printExecutionTime)('push-status', startedAt, 'Finished');
|
|
63
|
-
}
|
|
64
|
-
function waitForDeployment(client, buildType) {
|
|
65
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
-
return new Promise((resolve, reject) => {
|
|
67
|
-
if (performance.now() - startedAt > maxExecutionTime * 1000) {
|
|
68
|
-
spinner.stop();
|
|
69
|
-
reject(new Error(`Time limit exceeded.`));
|
|
70
|
-
}
|
|
71
|
-
getAndPrintPushStatus(client, buildType)
|
|
72
|
-
.then((push) => {
|
|
73
|
-
if (!['pending', 'running'].includes(push.status[buildType].deploy.status)) {
|
|
74
|
-
printScorecard(push.status[buildType].scorecard);
|
|
75
|
-
resolve(push);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
79
|
-
try {
|
|
80
|
-
const pushResponse = yield waitForDeployment(client, buildType);
|
|
81
|
-
resolve(pushResponse);
|
|
82
|
-
}
|
|
83
|
-
catch (e) {
|
|
84
|
-
reject(e);
|
|
85
|
-
}
|
|
86
|
-
}), INTERVAL);
|
|
87
|
-
})
|
|
88
|
-
.catch(reject);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
function getAndPrintPushStatus(client, buildType) {
|
|
93
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
-
const push = yield client.remotes.getPush({
|
|
95
|
-
organizationId: orgId,
|
|
96
|
-
projectId,
|
|
97
|
-
pushId,
|
|
98
|
-
});
|
|
99
|
-
if (push.isOutdated || !push.hasChanges) {
|
|
100
|
-
process.stderr.write((0, colorette_1.yellow)(`Files not uploaded. Reason: ${push.isOutdated ? 'outdated' : 'no changes'}.\n`));
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
displayDeploymentAndBuildStatus({
|
|
104
|
-
status: push.status[buildType].deploy.status,
|
|
105
|
-
previewUrl: push.status[buildType].deploy.url,
|
|
106
|
-
buildType,
|
|
107
|
-
spinner,
|
|
108
|
-
wait,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return push;
|
|
112
|
-
});
|
|
147
|
+
finally {
|
|
148
|
+
spinner.stop(); // Spinner can block process exit, so we need to stop it explicitly.
|
|
113
149
|
}
|
|
114
150
|
});
|
|
115
151
|
}
|
|
116
152
|
exports.handlePushStatus = handlePushStatus;
|
|
153
|
+
function printPushStatusInfo({ orgId, projectId, pushId, startedAt, }) {
|
|
154
|
+
process.stderr.write(`\nProcessed push-status for ${colors.yellow(orgId)}, ${colors.yellow(projectId)} and pushID ${colors.yellow(pushId)}.\n`);
|
|
155
|
+
(0, miscellaneous_1.printExecutionTime)('push-status', startedAt, 'Finished');
|
|
156
|
+
}
|
|
157
|
+
function printPushStatus({ buildType, spinner, push, continueOnDeployFailures, }) {
|
|
158
|
+
if (!push) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (push.isOutdated || !push.hasChanges) {
|
|
162
|
+
process.stderr.write(colors.yellow(`Files not added to your project. Reason: ${push.isOutdated ? 'outdated' : 'no changes'}.\n`));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
displayDeploymentAndBuildStatus({
|
|
166
|
+
status: push.status[buildType].deploy.status,
|
|
167
|
+
url: push.status[buildType].deploy.url,
|
|
168
|
+
buildType,
|
|
169
|
+
spinner,
|
|
170
|
+
continueOnDeployFailures,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
117
174
|
function printScorecard(scorecard) {
|
|
118
|
-
if (!scorecard.length) {
|
|
175
|
+
if (!scorecard || scorecard.length === 0) {
|
|
119
176
|
return;
|
|
120
177
|
}
|
|
121
178
|
process.stdout.write(`\n${colors.magenta('Scorecard')}:`);
|
|
@@ -128,24 +185,37 @@ function printScorecard(scorecard) {
|
|
|
128
185
|
}
|
|
129
186
|
process.stdout.write(`\n`);
|
|
130
187
|
}
|
|
131
|
-
function displayDeploymentAndBuildStatus({ status,
|
|
188
|
+
function displayDeploymentAndBuildStatus({ status, url, spinner, buildType, continueOnDeployFailures, wait, }) {
|
|
189
|
+
const message = getMessage({ status, url, buildType, wait });
|
|
190
|
+
if (status === 'failed' && !continueOnDeployFailures) {
|
|
191
|
+
spinner.stop();
|
|
192
|
+
throw new utils_1.DeploymentError(message);
|
|
193
|
+
}
|
|
194
|
+
if (wait && (status === 'pending' || status === 'running')) {
|
|
195
|
+
return spinner.start(message);
|
|
196
|
+
}
|
|
197
|
+
spinner.stop();
|
|
198
|
+
return process.stdout.write(message);
|
|
199
|
+
}
|
|
200
|
+
function getMessage({ status, url, buildType, wait, }) {
|
|
132
201
|
switch (status) {
|
|
202
|
+
case 'skipped':
|
|
203
|
+
return `${colors.yellow(`Skipped ${buildType}`)}\n`;
|
|
204
|
+
case 'pending': {
|
|
205
|
+
const message = `${colors.yellow(`Pending ${buildType}`)}`;
|
|
206
|
+
return wait ? message : `Status: ${message}\n`;
|
|
207
|
+
}
|
|
208
|
+
case 'running': {
|
|
209
|
+
const message = `${colors.yellow(`Running ${buildType}`)}`;
|
|
210
|
+
return wait ? message : `Status: ${message}\n`;
|
|
211
|
+
}
|
|
133
212
|
case 'success':
|
|
134
|
-
|
|
135
|
-
return process.stdout.write(`${colors.green(`🚀 ${(0, js_utils_1.capitalize)(buildType)} deploy success.`)}\n${colors.magenta(`${(0, js_utils_1.capitalize)(buildType)} URL`)}: ${colors.cyan(previewUrl)}\n`);
|
|
213
|
+
return `${colors.green(`🚀 ${(0, js_utils_1.capitalize)(buildType)} deploy success.`)}\n${colors.magenta(`${(0, js_utils_1.capitalize)(buildType)} URL`)}: ${colors.cyan(url || 'No URL yet.')}\n`;
|
|
136
214
|
case 'failed':
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return wait
|
|
141
|
-
|
|
142
|
-
: process.stdout.write(`Status: ${colors.yellow(`Pending ${buildType}`)}\n`);
|
|
143
|
-
case 'skipped':
|
|
144
|
-
spinner.stop();
|
|
145
|
-
return process.stdout.write(`${colors.yellow(`Skipped ${buildType}`)}\n`);
|
|
146
|
-
case 'running':
|
|
147
|
-
return wait
|
|
148
|
-
? spinner.start(`${colors.yellow(`Running ${buildType}`)}`)
|
|
149
|
-
: process.stdout.write(`Status: ${colors.yellow(`Running ${buildType}`)}\n`);
|
|
215
|
+
return `${colors.red(`❌ ${(0, js_utils_1.capitalize)(buildType)} deploy fail.`)}\n${colors.magenta(`${(0, js_utils_1.capitalize)(buildType)} URL`)}: ${colors.cyan(url || 'No URL yet.')}`;
|
|
216
|
+
default: {
|
|
217
|
+
const message = `${colors.yellow(`No status yet for ${buildType} deploy`)}`;
|
|
218
|
+
return wait ? message : `Status: ${message}\n`;
|
|
219
|
+
}
|
|
150
220
|
}
|
|
151
221
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Config } from '@redocly/openapi-core';
|
|
1
|
+
import type { OutputFormat, Config } from '@redocly/openapi-core';
|
|
2
2
|
export type PushOptions = {
|
|
3
3
|
apis?: string[];
|
|
4
4
|
organization?: string;
|
|
@@ -18,6 +18,10 @@ export type PushOptions = {
|
|
|
18
18
|
config?: string;
|
|
19
19
|
'wait-for-deployment'?: boolean;
|
|
20
20
|
'max-execution-time': number;
|
|
21
|
+
'continue-on-deploy-failures'?: boolean;
|
|
21
22
|
verbose?: boolean;
|
|
23
|
+
format?: Extract<OutputFormat, 'stylish'>;
|
|
22
24
|
};
|
|
23
|
-
export declare function handlePush(argv: PushOptions, config: Config): Promise<
|
|
25
|
+
export declare function handlePush(argv: PushOptions, config: Config): Promise<{
|
|
26
|
+
pushId: string;
|
|
27
|
+
} | void>;
|
package/lib/cms/commands/push.js
CHANGED
|
@@ -13,14 +13,15 @@ exports.handlePush = void 0;
|
|
|
13
13
|
const fs = require("fs");
|
|
14
14
|
const path = require("path");
|
|
15
15
|
const openapi_core_1 = require("@redocly/openapi-core");
|
|
16
|
-
const miscellaneous_1 = require("../../utils/miscellaneous");
|
|
17
16
|
const colorette_1 = require("colorette");
|
|
18
17
|
const pluralize = require("pluralize");
|
|
18
|
+
const miscellaneous_1 = require("../../utils/miscellaneous");
|
|
19
19
|
const push_status_1 = require("./push-status");
|
|
20
20
|
const api_1 = require("../api");
|
|
21
21
|
function handlePush(argv, config) {
|
|
22
22
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
-
const startedAt = performance.now();
|
|
23
|
+
const startedAt = performance.now(); // for printing execution time
|
|
24
|
+
const startTime = Date.now(); // for push-status command
|
|
24
25
|
const { organization, project: projectId, 'mount-path': mountPath, verbose } = argv;
|
|
25
26
|
const orgId = organization || config.organization;
|
|
26
27
|
if (!argv.message || !argv.author || !argv.branch) {
|
|
@@ -76,10 +77,15 @@ function handlePush(argv, config) {
|
|
|
76
77
|
wait: true,
|
|
77
78
|
domain,
|
|
78
79
|
'max-execution-time': maxExecutionTime,
|
|
80
|
+
'start-time': startTime,
|
|
81
|
+
'continue-on-deploy-failures': argv['continue-on-deploy-failures'],
|
|
79
82
|
}, config);
|
|
80
83
|
}
|
|
81
84
|
verbose &&
|
|
82
85
|
(0, miscellaneous_1.printExecutionTime)('push', startedAt, `${pluralize('file', filesToUpload.length)} uploaded to organization ${orgId}, project ${projectId}. Push ID: ${id}.`);
|
|
86
|
+
return {
|
|
87
|
+
pushId: id,
|
|
88
|
+
};
|
|
83
89
|
}
|
|
84
90
|
catch (err) {
|
|
85
91
|
const message = err instanceof miscellaneous_1.HandledError ? '' : `✗ File upload failed. Reason: ${err.message}`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function retries an operation until a condition is met or a timeout is exceeded.
|
|
3
|
+
* If the condition is not met within the timeout, an error is thrown.
|
|
4
|
+
* @operation The operation to retry.
|
|
5
|
+
* @condition The condition to check after each operation result. Return false to continue retrying. Return true to stop retrying.
|
|
6
|
+
* If not provided, the first result will be returned.
|
|
7
|
+
* @param onConditionNotMet Will be called with the last result right after checking condition and before timeout and retrying.
|
|
8
|
+
* @param onRetry Will be called right before retrying operation with the last result before retrying.
|
|
9
|
+
* @param startTime The start time of the operation. Default is the current time.
|
|
10
|
+
* @param retryTimeoutMs The maximum time to retry the operation. Default is 10 minutes.
|
|
11
|
+
* @param retryIntervalMs The interval between retries. Default is 5 seconds.
|
|
12
|
+
*/
|
|
13
|
+
export declare function retryUntilConditionMet<T>({ operation, condition, onConditionNotMet, onRetry, startTime, retryTimeoutMs, // 10 min
|
|
14
|
+
retryIntervalMs, }: {
|
|
15
|
+
operation: () => Promise<T>;
|
|
16
|
+
condition?: ((result: T) => boolean) | null;
|
|
17
|
+
onConditionNotMet?: (lastResult: T) => void;
|
|
18
|
+
onRetry?: (lastResult: T) => void | Promise<void>;
|
|
19
|
+
startTime?: number;
|
|
20
|
+
retryTimeoutMs?: number;
|
|
21
|
+
retryIntervalMs?: number;
|
|
22
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,53 @@
|
|
|
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.retryUntilConditionMet = void 0;
|
|
13
|
+
const openapi_core_1 = require("@redocly/openapi-core");
|
|
14
|
+
/**
|
|
15
|
+
* This function retries an operation until a condition is met or a timeout is exceeded.
|
|
16
|
+
* If the condition is not met within the timeout, an error is thrown.
|
|
17
|
+
* @operation The operation to retry.
|
|
18
|
+
* @condition The condition to check after each operation result. Return false to continue retrying. Return true to stop retrying.
|
|
19
|
+
* If not provided, the first result will be returned.
|
|
20
|
+
* @param onConditionNotMet Will be called with the last result right after checking condition and before timeout and retrying.
|
|
21
|
+
* @param onRetry Will be called right before retrying operation with the last result before retrying.
|
|
22
|
+
* @param startTime The start time of the operation. Default is the current time.
|
|
23
|
+
* @param retryTimeoutMs The maximum time to retry the operation. Default is 10 minutes.
|
|
24
|
+
* @param retryIntervalMs The interval between retries. Default is 5 seconds.
|
|
25
|
+
*/
|
|
26
|
+
function retryUntilConditionMet({ operation, condition, onConditionNotMet, onRetry, startTime = Date.now(), retryTimeoutMs = 600000, // 10 min
|
|
27
|
+
retryIntervalMs = 5000, // 5 sec
|
|
28
|
+
}) {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
function attempt() {
|
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
const result = yield operation();
|
|
33
|
+
if (!condition) {
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
if (condition(result)) {
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
else if (Date.now() - startTime > retryTimeoutMs) {
|
|
40
|
+
throw new Error('Timeout exceeded');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
onConditionNotMet === null || onConditionNotMet === void 0 ? void 0 : onConditionNotMet(result);
|
|
44
|
+
yield (0, openapi_core_1.pause)(retryIntervalMs);
|
|
45
|
+
yield (onRetry === null || onRetry === void 0 ? void 0 : onRetry(result));
|
|
46
|
+
return attempt();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return attempt();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
exports.retryUntilConditionMet = retryUntilConditionMet;
|
package/lib/index.js
CHANGED
|
@@ -174,9 +174,10 @@ yargs
|
|
|
174
174
|
project: {
|
|
175
175
|
description: 'Name of the project to push to.',
|
|
176
176
|
type: 'string',
|
|
177
|
+
required: true,
|
|
177
178
|
alias: 'p',
|
|
178
179
|
},
|
|
179
|
-
domain: { description: 'Specify a domain.', alias: 'd', type: 'string' },
|
|
180
|
+
domain: { description: 'Specify a domain.', alias: 'd', type: 'string', required: false },
|
|
180
181
|
wait: {
|
|
181
182
|
description: 'Wait for build to finish.',
|
|
182
183
|
type: 'boolean',
|
|
@@ -186,6 +187,11 @@ yargs
|
|
|
186
187
|
description: 'Maximum execution time in seconds.',
|
|
187
188
|
type: 'number',
|
|
188
189
|
},
|
|
190
|
+
'continue-on-deploy-failures': {
|
|
191
|
+
description: 'Command does not fail even if the deployment fails.',
|
|
192
|
+
type: 'boolean',
|
|
193
|
+
default: false,
|
|
194
|
+
},
|
|
189
195
|
}), (argv) => {
|
|
190
196
|
process.env.REDOCLY_CLI_COMMAND = 'push-status';
|
|
191
197
|
(0, wrapper_1.commandWrapper)(push_status_1.handlePushStatus)(argv);
|
|
@@ -339,6 +345,11 @@ yargs
|
|
|
339
345
|
type: 'boolean',
|
|
340
346
|
default: false,
|
|
341
347
|
},
|
|
348
|
+
'continue-on-deploy-failures': {
|
|
349
|
+
description: 'Command does not fail even if the deployment fails.',
|
|
350
|
+
type: 'boolean',
|
|
351
|
+
default: false,
|
|
352
|
+
},
|
|
342
353
|
}), (argv) => {
|
|
343
354
|
process.env.REDOCLY_CLI_COMMAND = 'push';
|
|
344
355
|
(0, wrapper_1.commandWrapper)((0, push_1.commonPushHandler)(argv))(argv);
|
|
@@ -30,6 +30,7 @@ const fs = require("fs");
|
|
|
30
30
|
const readline = require("readline");
|
|
31
31
|
const stream_1 = require("stream");
|
|
32
32
|
const child_process_1 = require("child_process");
|
|
33
|
+
const util_1 = require("util");
|
|
33
34
|
const openapi_core_1 = require("@redocly/openapi-core");
|
|
34
35
|
const types_1 = require("../types");
|
|
35
36
|
const utils_1 = require("@redocly/openapi-core/lib/utils");
|
|
@@ -89,7 +90,7 @@ function expandGlobsInEntrypoints(args, config) {
|
|
|
89
90
|
return __awaiter(this, void 0, void 0, function* () {
|
|
90
91
|
return (yield Promise.all(args.map((aliasOrPath) => __awaiter(this, void 0, void 0, function* () {
|
|
91
92
|
return glob.hasMagic(aliasOrPath) && !(0, openapi_core_1.isAbsoluteUrl)(aliasOrPath)
|
|
92
|
-
? (yield
|
|
93
|
+
? (yield (0, util_1.promisify)(glob)(aliasOrPath)).map((g) => getAliasOrPath(config, g))
|
|
93
94
|
: getAliasOrPath(config, aliasOrPath);
|
|
94
95
|
})))).flat();
|
|
95
96
|
});
|
|
@@ -270,9 +271,9 @@ function handleError(e, ref) {
|
|
|
270
271
|
throw e;
|
|
271
272
|
}
|
|
272
273
|
case openapi_core_1.ResolveError:
|
|
273
|
-
return exitWithError(`Failed to resolve API description at ${ref}:\n\n - ${e.message}
|
|
274
|
+
return exitWithError(`Failed to resolve API description at ${ref}:\n\n - ${e.message}`);
|
|
274
275
|
case openapi_core_1.YamlParseError:
|
|
275
|
-
return exitWithError(`Failed to parse API description at ${ref}:\n\n - ${e.message}
|
|
276
|
+
return exitWithError(`Failed to parse API description at ${ref}:\n\n - ${e.message}`);
|
|
276
277
|
case CircularJSONNotSupportedError: {
|
|
277
278
|
return exitWithError(`Detected circular reference which can't be converted to JSON.\n` +
|
|
278
279
|
`Try to use ${(0, colorette_1.blue)('yaml')} output or remove ${(0, colorette_1.blue)('--dereferenced')}.`);
|
|
@@ -282,7 +283,7 @@ function handleError(e, ref) {
|
|
|
282
283
|
case config_1.ConfigValidationError:
|
|
283
284
|
return exitWithError(e.message);
|
|
284
285
|
default: {
|
|
285
|
-
exitWithError(`Something went wrong when processing ${ref}:\n\n - ${e.message}
|
|
286
|
+
exitWithError(`Something went wrong when processing ${ref}:\n\n - ${e.message}`);
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
}
|
package/lib/wrapper.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Config } from '@redocly/openapi-core';
|
|
2
2
|
import type { Arguments } from 'yargs';
|
|
3
3
|
import type { CommandOptions } from './types';
|
|
4
|
-
export declare function commandWrapper<T extends CommandOptions>(commandHandler?: (argv: T, config: Config, version: string) => Promise<
|
|
4
|
+
export declare function commandWrapper<T extends CommandOptions>(commandHandler?: (argv: T, config: Config, version: string) => Promise<unknown>): (argv: Arguments<T>) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.1",
|
|
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.12.1",
|
|
40
40
|
"abort-controller": "^3.0.0",
|
|
41
41
|
"chokidar": "^3.5.1",
|
|
42
42
|
"colorette": "^1.2.0",
|
|
@@ -415,7 +415,7 @@ describe('handleErrors', () => {
|
|
|
415
415
|
});
|
|
416
416
|
|
|
417
417
|
it('should handle ResolveError', () => {
|
|
418
|
-
const resolveError = new ResolveError(new Error('File not found'));
|
|
418
|
+
const resolveError = new ResolveError(new Error('File not found.'));
|
|
419
419
|
expect(() => handleError(resolveError, ref)).toThrowError(HandledError);
|
|
420
420
|
expect(redColoretteMocks).toHaveBeenCalledTimes(1);
|
|
421
421
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
|
@@ -424,7 +424,7 @@ describe('handleErrors', () => {
|
|
|
424
424
|
});
|
|
425
425
|
|
|
426
426
|
it('should handle YamlParseError', () => {
|
|
427
|
-
const yamlParseError = new YamlParseError(new Error('Invalid yaml'), {} as any);
|
|
427
|
+
const yamlParseError = new YamlParseError(new Error('Invalid yaml.'), {} as any);
|
|
428
428
|
expect(() => handleError(yamlParseError, ref)).toThrowError(HandledError);
|
|
429
429
|
expect(redColoretteMocks).toHaveBeenCalledTimes(1);
|
|
430
430
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
|
@@ -451,7 +451,7 @@ describe('handleErrors', () => {
|
|
|
451
451
|
});
|
|
452
452
|
|
|
453
453
|
it('should throw unknown error', () => {
|
|
454
|
-
const testError = new Error('Test error');
|
|
454
|
+
const testError = new Error('Test error.');
|
|
455
455
|
expect(() => handleError(testError, ref)).toThrowError(HandledError);
|
|
456
456
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
|
457
457
|
`Something went wrong when processing openapi/test.yaml:\n\n - Test error.\n\n`
|