@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.
@@ -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' | 'json';
9
+ format?: Extract<OutputFormat, 'stylish'>;
9
10
  wait?: boolean;
10
- 'max-execution-time': number;
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 declare function handlePushStatus(argv: PushStatusOptions, config: Config): Promise<void>;
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 INTERVAL = 5000;
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
- return (0, miscellaneous_1.exitWithError)(`No organization provided, please use --organization option or specify the 'organization' field in the config file.`);
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
- if (!domain) {
32
- return (0, miscellaneous_1.exitWithError)(`No domain provided, please use --domain option or environment variable REDOCLY_DOMAIN.`);
33
- }
34
- const maxExecutionTime = argv['max-execution-time'] || 600;
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
- if (wait) {
39
- const push = yield waitForDeployment(client, 'preview');
40
- if (push.isMainBranch && push.status.preview.deploy.status === 'success') {
41
- yield waitForDeployment(client, 'production');
42
- }
43
- printPushStatusInfo();
44
- return;
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
- const pushPreview = yield getAndPrintPushStatus(client, 'preview');
47
- printScorecard(pushPreview.status.preview.scorecard);
48
- if (pushPreview.isMainBranch) {
49
- yield getAndPrintPushStatus(client, 'production');
50
- printScorecard(pushPreview.status.production.scorecard);
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
- function printPushStatusInfo() {
61
- process.stderr.write(`\nProcessed push-status for ${colors.yellow(orgId)}, ${colors.yellow(projectId)} and pushID ${colors.yellow(pushId)}.\n`);
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, previewUrl, spinner, buildType, wait, }) {
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
- spinner.stop();
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
- spinner.stop();
138
- throw new utils_1.DeploymentError(`${colors.red(`❌ ${(0, js_utils_1.capitalize)(buildType)} deploy fail.`)}\n${colors.magenta(`${(0, js_utils_1.capitalize)(buildType)} URL`)}: ${colors.cyan(previewUrl)}`);
139
- case 'pending':
140
- return wait
141
- ? spinner.start(`${colors.yellow(`Pending ${buildType}`)}`)
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<void>;
25
+ export declare function handlePush(argv: PushOptions, config: Config): Promise<{
26
+ pushId: string;
27
+ } | void>;
@@ -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 glob.__promisify__(aliasOrPath)).map((g) => getAliasOrPath(config, g))
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<void>): (argv: Arguments<T>) => Promise<void>;
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.11.0",
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.11.0",
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`