@redocly/cli 0.0.0-snapshot.1737554067

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.
Files changed (133) hide show
  1. package/README.md +114 -0
  2. package/bin/cli.js +3 -0
  3. package/lib/__mocks__/@redocly/openapi-core.d.ts +99 -0
  4. package/lib/__mocks__/@redocly/openapi-core.js +84 -0
  5. package/lib/__mocks__/documents.d.ts +150 -0
  6. package/lib/__mocks__/documents.js +123 -0
  7. package/lib/__mocks__/fs.d.ts +8 -0
  8. package/lib/__mocks__/fs.js +9 -0
  9. package/lib/__mocks__/perf_hooks.d.ts +3 -0
  10. package/lib/__mocks__/perf_hooks.js +6 -0
  11. package/lib/__mocks__/redoc.d.ts +6 -0
  12. package/lib/__mocks__/redoc.js +5 -0
  13. package/lib/__tests__/commands/build-docs.test.d.ts +1 -0
  14. package/lib/__tests__/commands/build-docs.test.js +54 -0
  15. package/lib/__tests__/commands/bundle.test.d.ts +1 -0
  16. package/lib/__tests__/commands/bundle.test.js +235 -0
  17. package/lib/__tests__/commands/join.test.d.ts +1 -0
  18. package/lib/__tests__/commands/join.test.js +274 -0
  19. package/lib/__tests__/commands/lint.test.d.ts +1 -0
  20. package/lib/__tests__/commands/lint.test.js +149 -0
  21. package/lib/__tests__/commands/push-region.test.d.ts +1 -0
  22. package/lib/__tests__/commands/push-region.test.js +55 -0
  23. package/lib/__tests__/commands/push.test.d.ts +1 -0
  24. package/lib/__tests__/commands/push.test.js +463 -0
  25. package/lib/__tests__/fetch-with-timeout.test.d.ts +1 -0
  26. package/lib/__tests__/fetch-with-timeout.test.js +44 -0
  27. package/lib/__tests__/fixtures/config.d.ts +21 -0
  28. package/lib/__tests__/fixtures/config.js +24 -0
  29. package/lib/__tests__/spinner.test.d.ts +1 -0
  30. package/lib/__tests__/spinner.test.js +43 -0
  31. package/lib/__tests__/utils.test.d.ts +1 -0
  32. package/lib/__tests__/utils.test.js +593 -0
  33. package/lib/__tests__/wrapper.test.d.ts +1 -0
  34. package/lib/__tests__/wrapper.test.js +68 -0
  35. package/lib/cms/api/__tests__/api-keys.test.d.ts +1 -0
  36. package/lib/cms/api/__tests__/api-keys.test.js +26 -0
  37. package/lib/cms/api/__tests__/api.client.test.d.ts +1 -0
  38. package/lib/cms/api/__tests__/api.client.test.js +333 -0
  39. package/lib/cms/api/__tests__/domains.test.d.ts +1 -0
  40. package/lib/cms/api/__tests__/domains.test.js +13 -0
  41. package/lib/cms/api/api-client.d.ts +75 -0
  42. package/lib/cms/api/api-client.js +225 -0
  43. package/lib/cms/api/api-keys.d.ts +1 -0
  44. package/lib/cms/api/api-keys.js +23 -0
  45. package/lib/cms/api/domains.d.ts +1 -0
  46. package/lib/cms/api/domains.js +11 -0
  47. package/lib/cms/api/index.d.ts +3 -0
  48. package/lib/cms/api/index.js +19 -0
  49. package/lib/cms/api/types.d.ts +102 -0
  50. package/lib/cms/api/types.js +2 -0
  51. package/lib/cms/commands/__tests__/push-status.test.d.ts +1 -0
  52. package/lib/cms/commands/__tests__/push-status.test.js +563 -0
  53. package/lib/cms/commands/__tests__/push.test.d.ts +1 -0
  54. package/lib/cms/commands/__tests__/push.test.js +315 -0
  55. package/lib/cms/commands/__tests__/utils.test.d.ts +1 -0
  56. package/lib/cms/commands/__tests__/utils.test.js +51 -0
  57. package/lib/cms/commands/push-status.d.ts +23 -0
  58. package/lib/cms/commands/push-status.js +206 -0
  59. package/lib/cms/commands/push.d.ts +28 -0
  60. package/lib/cms/commands/push.js +142 -0
  61. package/lib/cms/commands/utils.d.ts +25 -0
  62. package/lib/cms/commands/utils.js +46 -0
  63. package/lib/cms/utils.d.ts +2 -0
  64. package/lib/cms/utils.js +6 -0
  65. package/lib/commands/build-docs/index.d.ts +3 -0
  66. package/lib/commands/build-docs/index.js +39 -0
  67. package/lib/commands/build-docs/template.hbs +23 -0
  68. package/lib/commands/build-docs/types.d.ts +23 -0
  69. package/lib/commands/build-docs/types.js +2 -0
  70. package/lib/commands/build-docs/utils.d.ts +7 -0
  71. package/lib/commands/build-docs/utils.js +87 -0
  72. package/lib/commands/bundle.d.ts +14 -0
  73. package/lib/commands/bundle.js +91 -0
  74. package/lib/commands/eject.d.ts +9 -0
  75. package/lib/commands/eject.js +28 -0
  76. package/lib/commands/join.d.ts +11 -0
  77. package/lib/commands/join.js +565 -0
  78. package/lib/commands/lint.d.ts +13 -0
  79. package/lib/commands/lint.js +108 -0
  80. package/lib/commands/login.d.ts +9 -0
  81. package/lib/commands/login.js +23 -0
  82. package/lib/commands/preview-docs/index.d.ts +12 -0
  83. package/lib/commands/preview-docs/index.js +127 -0
  84. package/lib/commands/preview-docs/preview-server/default.hbs +24 -0
  85. package/lib/commands/preview-docs/preview-server/hot.js +59 -0
  86. package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  87. package/lib/commands/preview-docs/preview-server/preview-server.d.ts +5 -0
  88. package/lib/commands/preview-docs/preview-server/preview-server.js +113 -0
  89. package/lib/commands/preview-docs/preview-server/server.d.ts +22 -0
  90. package/lib/commands/preview-docs/preview-server/server.js +85 -0
  91. package/lib/commands/preview-project/constants.d.ts +14 -0
  92. package/lib/commands/preview-project/constants.js +22 -0
  93. package/lib/commands/preview-project/index.d.ts +3 -0
  94. package/lib/commands/preview-project/index.js +56 -0
  95. package/lib/commands/preview-project/types.d.ts +10 -0
  96. package/lib/commands/preview-project/types.js +2 -0
  97. package/lib/commands/push.d.ts +44 -0
  98. package/lib/commands/push.js +295 -0
  99. package/lib/commands/split/__tests__/index.test.d.ts +1 -0
  100. package/lib/commands/split/__tests__/index.test.js +91 -0
  101. package/lib/commands/split/index.d.ts +13 -0
  102. package/lib/commands/split/index.js +259 -0
  103. package/lib/commands/split/types.d.ts +36 -0
  104. package/lib/commands/split/types.js +51 -0
  105. package/lib/commands/stats.d.ts +8 -0
  106. package/lib/commands/stats.js +96 -0
  107. package/lib/commands/translations.d.ts +7 -0
  108. package/lib/commands/translations.js +20 -0
  109. package/lib/index.d.ts +2 -0
  110. package/lib/index.js +733 -0
  111. package/lib/types.d.ts +43 -0
  112. package/lib/types.js +5 -0
  113. package/lib/utils/__mocks__/miscellaneous.d.ts +43 -0
  114. package/lib/utils/__mocks__/miscellaneous.js +24 -0
  115. package/lib/utils/assert-node-version.d.ts +1 -0
  116. package/lib/utils/assert-node-version.js +16 -0
  117. package/lib/utils/fetch-with-timeout.d.ts +7 -0
  118. package/lib/utils/fetch-with-timeout.js +26 -0
  119. package/lib/utils/getCommandNameFromArgs.d.ts +2 -0
  120. package/lib/utils/getCommandNameFromArgs.js +6 -0
  121. package/lib/utils/js-utils.d.ts +5 -0
  122. package/lib/utils/js-utils.js +28 -0
  123. package/lib/utils/miscellaneous.d.ts +81 -0
  124. package/lib/utils/miscellaneous.js +551 -0
  125. package/lib/utils/platform.d.ts +16 -0
  126. package/lib/utils/platform.js +34 -0
  127. package/lib/utils/spinner.d.ts +10 -0
  128. package/lib/utils/spinner.js +42 -0
  129. package/lib/utils/update-version-notifier.d.ts +3 -0
  130. package/lib/utils/update-version-notifier.js +102 -0
  131. package/lib/wrapper.d.ts +11 -0
  132. package/lib/wrapper.js +65 -0
  133. package/package.json +69 -0
@@ -0,0 +1,315 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const push_1 = require("../push");
6
+ const api_1 = require("../../api");
7
+ const remotes = {
8
+ push: jest.fn(),
9
+ upsert: jest.fn(),
10
+ getDefaultBranch: jest.fn(),
11
+ };
12
+ jest.mock('@redocly/openapi-core', () => ({
13
+ slash: jest.fn().mockImplementation((p) => p),
14
+ }));
15
+ jest.mock('../../api', () => ({
16
+ ...jest.requireActual('../../api'),
17
+ ReuniteApi: jest.fn().mockImplementation(function (...args) {
18
+ this.remotes = remotes;
19
+ this.reportSunsetWarnings = jest.fn();
20
+ }),
21
+ }));
22
+ describe('handlePush()', () => {
23
+ let pathResolveSpy;
24
+ let pathRelativeSpy;
25
+ let pathDirnameSpy;
26
+ let fsStatSyncSpy;
27
+ let fsReaddirSyncSpy;
28
+ beforeEach(() => {
29
+ remotes.getDefaultBranch.mockResolvedValueOnce('test-default-branch');
30
+ remotes.upsert.mockResolvedValueOnce({ id: 'test-remote-id', mountPath: 'test-mount-path' });
31
+ remotes.push.mockResolvedValueOnce({ branchName: 'uploaded-to-branch', id: 'test-id' });
32
+ jest.spyOn(fs, 'createReadStream').mockReturnValue('stream');
33
+ pathResolveSpy = jest.spyOn(path, 'resolve');
34
+ pathRelativeSpy = jest.spyOn(path, 'relative');
35
+ pathDirnameSpy = jest.spyOn(path, 'dirname');
36
+ fsStatSyncSpy = jest.spyOn(fs, 'statSync');
37
+ fsReaddirSyncSpy = jest.spyOn(fs, 'readdirSync');
38
+ });
39
+ afterEach(() => {
40
+ pathResolveSpy.mockRestore();
41
+ pathRelativeSpy.mockRestore();
42
+ pathDirnameSpy.mockRestore();
43
+ fsStatSyncSpy.mockRestore();
44
+ fsReaddirSyncSpy.mockRestore();
45
+ });
46
+ it('should upload files', async () => {
47
+ const mockConfig = { apis: {} };
48
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
49
+ fsStatSyncSpy.mockReturnValueOnce({
50
+ isDirectory() {
51
+ return false;
52
+ },
53
+ });
54
+ pathResolveSpy.mockImplementationOnce((p) => p);
55
+ pathRelativeSpy.mockImplementationOnce((_, p) => p);
56
+ pathDirnameSpy.mockImplementation((_) => '.');
57
+ await (0, push_1.handlePush)({
58
+ argv: {
59
+ domain: 'test-domain',
60
+ 'mount-path': 'test-mount-path',
61
+ organization: 'test-org',
62
+ project: 'test-project',
63
+ branch: 'test-branch',
64
+ namespace: 'test-namespace',
65
+ repository: 'test-repository',
66
+ 'commit-sha': 'test-commit-sha',
67
+ 'commit-url': 'test-commit-url',
68
+ 'default-branch': 'test-branch',
69
+ 'created-at': 'test-created-at',
70
+ author: 'TestAuthor <test-author@mail.com>',
71
+ message: 'Test message',
72
+ files: ['test-file'],
73
+ 'max-execution-time': 10,
74
+ },
75
+ config: mockConfig,
76
+ version: 'cli-version',
77
+ });
78
+ expect(remotes.getDefaultBranch).toHaveBeenCalledWith('test-org', 'test-project');
79
+ expect(remotes.upsert).toHaveBeenCalledWith('test-org', 'test-project', {
80
+ mountBranchName: 'test-default-branch',
81
+ mountPath: 'test-mount-path',
82
+ });
83
+ expect(remotes.push).toHaveBeenCalledWith('test-org', 'test-project', {
84
+ isMainBranch: true,
85
+ remoteId: 'test-remote-id',
86
+ commit: {
87
+ message: 'Test message',
88
+ branchName: 'test-branch',
89
+ createdAt: 'test-created-at',
90
+ namespace: 'test-namespace',
91
+ repository: 'test-repository',
92
+ sha: 'test-commit-sha',
93
+ url: 'test-commit-url',
94
+ author: {
95
+ name: 'TestAuthor',
96
+ email: 'test-author@mail.com',
97
+ },
98
+ },
99
+ }, [
100
+ {
101
+ path: 'test-file',
102
+ stream: 'stream',
103
+ },
104
+ ]);
105
+ });
106
+ it('should return push id', async () => {
107
+ const mockConfig = { apis: {} };
108
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
109
+ fsStatSyncSpy.mockReturnValueOnce({
110
+ isDirectory() {
111
+ return false;
112
+ },
113
+ });
114
+ pathResolveSpy.mockImplementationOnce((p) => p);
115
+ pathRelativeSpy.mockImplementationOnce((_, p) => p);
116
+ pathDirnameSpy.mockImplementation((_) => '.');
117
+ const result = await (0, push_1.handlePush)({
118
+ argv: {
119
+ domain: 'test-domain',
120
+ 'mount-path': 'test-mount-path',
121
+ organization: 'test-org',
122
+ project: 'test-project',
123
+ branch: 'test-branch',
124
+ namespace: 'test-namespace',
125
+ repository: 'test-repository',
126
+ 'commit-sha': 'test-commit-sha',
127
+ 'commit-url': 'test-commit-url',
128
+ 'default-branch': 'test-branch',
129
+ 'created-at': 'test-created-at',
130
+ author: 'TestAuthor <test-author@mail.com>',
131
+ message: 'Test message',
132
+ files: ['test-file'],
133
+ 'max-execution-time': 10,
134
+ },
135
+ config: mockConfig,
136
+ version: 'cli-version',
137
+ });
138
+ expect(result).toEqual({ pushId: 'test-id' });
139
+ });
140
+ it('should collect files from directory and preserve file structure', async () => {
141
+ const mockConfig = { apis: {} };
142
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
143
+ /*
144
+ ├── app
145
+ │ ├── index.html
146
+ ├── openapi.yaml
147
+ └── some-ref.yaml
148
+ */
149
+ fsStatSyncSpy.mockImplementation((filePath) => ({
150
+ isDirectory() {
151
+ return filePath === 'test-folder' || filePath === 'test-folder/app';
152
+ },
153
+ }));
154
+ fsReaddirSyncSpy.mockImplementation((dirPath) => {
155
+ if (dirPath === 'test-folder') {
156
+ return ['app', 'another-ref.yaml', 'openapi.yaml'];
157
+ }
158
+ if (dirPath === 'test-folder/app') {
159
+ return ['index.html'];
160
+ }
161
+ throw new Error('Not a directory');
162
+ });
163
+ await (0, push_1.handlePush)({
164
+ argv: {
165
+ domain: 'test-domain',
166
+ 'mount-path': 'test-mount-path',
167
+ organization: 'test-org',
168
+ project: 'test-project',
169
+ branch: 'test-branch',
170
+ author: 'TestAuthor <test-author@mail.com>',
171
+ message: 'Test message',
172
+ 'default-branch': 'main',
173
+ files: ['test-folder'],
174
+ 'max-execution-time': 10,
175
+ },
176
+ config: mockConfig,
177
+ version: 'cli-version',
178
+ });
179
+ expect(remotes.push).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.anything(), [
180
+ {
181
+ path: 'app/index.html',
182
+ stream: 'stream',
183
+ },
184
+ {
185
+ path: 'another-ref.yaml',
186
+ stream: 'stream',
187
+ },
188
+ {
189
+ path: 'openapi.yaml',
190
+ stream: 'stream',
191
+ },
192
+ ]);
193
+ });
194
+ it('should not upload files if no files passed', async () => {
195
+ const mockConfig = { apis: {} };
196
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
197
+ await (0, push_1.handlePush)({
198
+ argv: {
199
+ domain: 'test-domain',
200
+ 'mount-path': 'test-mount-path',
201
+ organization: 'test-org',
202
+ project: 'test-project',
203
+ branch: 'test-branch',
204
+ author: 'TestAuthor <test-author@mail.com>',
205
+ message: 'Test message',
206
+ 'default-branch': 'main',
207
+ files: [],
208
+ 'max-execution-time': 10,
209
+ },
210
+ config: mockConfig,
211
+ version: 'cli-version',
212
+ });
213
+ expect(remotes.getDefaultBranch).not.toHaveBeenCalled();
214
+ expect(remotes.upsert).not.toHaveBeenCalled();
215
+ expect(remotes.push).not.toHaveBeenCalled();
216
+ });
217
+ it('should get organization from config if not passed', async () => {
218
+ const mockConfig = { organization: 'test-org-from-config', apis: {} };
219
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
220
+ fsStatSyncSpy.mockReturnValueOnce({
221
+ isDirectory() {
222
+ return false;
223
+ },
224
+ });
225
+ pathResolveSpy.mockImplementationOnce((p) => p);
226
+ pathRelativeSpy.mockImplementationOnce((_, p) => p);
227
+ pathDirnameSpy.mockImplementation((_) => '.');
228
+ await (0, push_1.handlePush)({
229
+ argv: {
230
+ domain: 'test-domain',
231
+ 'mount-path': 'test-mount-path',
232
+ project: 'test-project',
233
+ branch: 'test-branch',
234
+ author: 'TestAuthor <test-author@mail.com>',
235
+ message: 'Test message',
236
+ files: ['test-file'],
237
+ 'default-branch': 'main',
238
+ 'max-execution-time': 10,
239
+ },
240
+ config: mockConfig,
241
+ version: 'cli-version',
242
+ });
243
+ expect(remotes.getDefaultBranch).toHaveBeenCalledWith('test-org-from-config', expect.anything());
244
+ expect(remotes.upsert).toHaveBeenCalledWith('test-org-from-config', expect.anything(), expect.anything());
245
+ expect(remotes.push).toHaveBeenCalledWith('test-org-from-config', expect.anything(), expect.anything(), expect.anything());
246
+ });
247
+ it('should get domain from env if not passed', async () => {
248
+ const mockConfig = { organization: 'test-org-from-config', apis: {} };
249
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
250
+ process.env.REDOCLY_DOMAIN = 'test-domain-from-env';
251
+ fsStatSyncSpy.mockReturnValueOnce({
252
+ isDirectory() {
253
+ return false;
254
+ },
255
+ });
256
+ pathResolveSpy.mockImplementationOnce((p) => p);
257
+ pathRelativeSpy.mockImplementationOnce((_, p) => p);
258
+ pathDirnameSpy.mockImplementation((_) => '.');
259
+ await (0, push_1.handlePush)({
260
+ argv: {
261
+ 'mount-path': 'test-mount-path',
262
+ project: 'test-project',
263
+ branch: 'test-branch',
264
+ 'default-branch': 'main',
265
+ author: 'TestAuthor <test-author@mail.com>',
266
+ message: 'Test message',
267
+ files: ['test-file'],
268
+ 'max-execution-time': 10,
269
+ },
270
+ config: mockConfig,
271
+ version: 'cli-version',
272
+ });
273
+ expect(api_1.ReuniteApi).toBeCalledWith({
274
+ domain: 'test-domain-from-env',
275
+ apiKey: 'test-api-key',
276
+ version: 'cli-version',
277
+ command: 'push',
278
+ });
279
+ });
280
+ it('should print error message', async () => {
281
+ const mockConfig = { apis: {} };
282
+ process.env.REDOCLY_AUTHORIZATION = 'test-api-key';
283
+ remotes.push.mockRestore();
284
+ remotes.push.mockRejectedValueOnce(new api_1.ReuniteApiError('Deprecated.', 412));
285
+ fsStatSyncSpy.mockReturnValueOnce({
286
+ isDirectory() {
287
+ return false;
288
+ },
289
+ });
290
+ pathResolveSpy.mockImplementationOnce((p) => p);
291
+ pathRelativeSpy.mockImplementationOnce((_, p) => p);
292
+ pathDirnameSpy.mockImplementation((_) => '.');
293
+ expect((0, push_1.handlePush)({
294
+ argv: {
295
+ domain: 'test-domain',
296
+ 'mount-path': 'test-mount-path',
297
+ organization: 'test-org',
298
+ project: 'test-project',
299
+ branch: 'test-branch',
300
+ namespace: 'test-namespace',
301
+ repository: 'test-repository',
302
+ 'commit-sha': 'test-commit-sha',
303
+ 'commit-url': 'test-commit-url',
304
+ 'default-branch': 'test-branch',
305
+ 'created-at': 'test-created-at',
306
+ author: 'TestAuthor <test-author@mail.com>',
307
+ message: 'Test message',
308
+ files: ['test-file'],
309
+ 'max-execution-time': 10,
310
+ },
311
+ config: mockConfig,
312
+ version: 'cli-version',
313
+ })).rejects.toThrow('✗ File upload failed. Reason: Deprecated.');
314
+ });
315
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ jest.mock('@redocly/openapi-core', () => ({
5
+ pause: jest.requireActual('@redocly/openapi-core').pause,
6
+ }));
7
+ describe('retryUntilConditionMet()', () => {
8
+ it('should retry until condition meet and return result', async () => {
9
+ const operation = jest
10
+ .fn()
11
+ .mockResolvedValueOnce({ status: 'pending' })
12
+ .mockResolvedValueOnce({ status: 'pending' })
13
+ .mockResolvedValueOnce({ status: 'done' });
14
+ const data = await (0, utils_1.retryUntilConditionMet)({
15
+ operation,
16
+ condition: (result) => result?.status === 'done',
17
+ retryIntervalMs: 100,
18
+ retryTimeoutMs: 1000,
19
+ });
20
+ expect(data).toEqual({ status: 'done' });
21
+ });
22
+ it('should throw error if condition not meet for desired timeout', async () => {
23
+ const operation = jest.fn().mockResolvedValue({ status: 'pending' });
24
+ await expect((0, utils_1.retryUntilConditionMet)({
25
+ operation,
26
+ condition: (result) => result?.status === 'done',
27
+ retryIntervalMs: 100,
28
+ retryTimeoutMs: 1000,
29
+ })).rejects.toThrow('Timeout exceeded.');
30
+ });
31
+ it('should call "onConditionNotMet" and "onRetry" callbacks', async () => {
32
+ const operation = jest
33
+ .fn()
34
+ .mockResolvedValueOnce({ status: 'pending' })
35
+ .mockResolvedValueOnce({ status: 'pending' })
36
+ .mockResolvedValueOnce({ status: 'done' });
37
+ const onConditionNotMet = jest.fn();
38
+ const onRetry = jest.fn();
39
+ const data = await (0, utils_1.retryUntilConditionMet)({
40
+ operation,
41
+ condition: (result) => result?.status === 'done',
42
+ retryIntervalMs: 100,
43
+ retryTimeoutMs: 1000,
44
+ onConditionNotMet,
45
+ onRetry,
46
+ });
47
+ expect(data).toEqual({ status: 'done' });
48
+ expect(onConditionNotMet).toHaveBeenCalledTimes(2);
49
+ expect(onRetry).toHaveBeenCalledTimes(2);
50
+ });
51
+ });
@@ -0,0 +1,23 @@
1
+ import type { OutputFormat } from '@redocly/openapi-core';
2
+ import type { CommandArgs } from '../../wrapper';
3
+ import type { DeploymentStatusResponse, PushResponse } from '../api/types';
4
+ export type PushStatusOptions = {
5
+ organization: string;
6
+ project: string;
7
+ pushId: string;
8
+ domain?: string;
9
+ config?: string;
10
+ format?: Extract<OutputFormat, 'stylish'>;
11
+ wait?: boolean;
12
+ 'max-execution-time'?: number;
13
+ 'retry-interval'?: number;
14
+ 'start-time'?: number;
15
+ 'continue-on-deploy-failures'?: boolean;
16
+ onRetry?: (lasSummary: PushStatusSummary) => void;
17
+ };
18
+ export interface PushStatusSummary {
19
+ preview: DeploymentStatusResponse;
20
+ production: DeploymentStatusResponse | null;
21
+ commit: PushResponse['commit'];
22
+ }
23
+ export declare function handlePushStatus({ argv, config, version, }: CommandArgs<PushStatusOptions>): Promise<PushStatusSummary | void>;
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handlePushStatus = handlePushStatus;
4
+ const colors = require("colorette");
5
+ const miscellaneous_1 = require("../../utils/miscellaneous");
6
+ const spinner_1 = require("../../utils/spinner");
7
+ const utils_1 = require("../utils");
8
+ const api_1 = require("../api");
9
+ const js_utils_1 = require("../../utils/js-utils");
10
+ const utils_2 = require("./utils");
11
+ const RETRY_INTERVAL_MS = 5000; // 5 sec
12
+ async function handlePushStatus({ argv, config, version, }) {
13
+ const startedAt = performance.now();
14
+ const spinner = new spinner_1.Spinner();
15
+ const { organization, project: projectId, pushId, wait } = argv;
16
+ const orgId = organization || config.organization;
17
+ if (!orgId) {
18
+ (0, miscellaneous_1.exitWithError)(`No organization provided, please use --organization option or specify the 'organization' field in the config file.`);
19
+ return;
20
+ }
21
+ const domain = argv.domain || (0, api_1.getDomain)();
22
+ const maxExecutionTime = argv['max-execution-time'] || 1200; // 20 min
23
+ const retryIntervalMs = argv['retry-interval']
24
+ ? argv['retry-interval'] * 1000
25
+ : RETRY_INTERVAL_MS;
26
+ const startTime = argv['start-time'] || Date.now();
27
+ const retryTimeoutMs = maxExecutionTime * 1000;
28
+ const continueOnDeployFailures = argv['continue-on-deploy-failures'] || false;
29
+ try {
30
+ const apiKey = (0, api_1.getApiKeys)(domain);
31
+ const client = new api_1.ReuniteApi({ domain, apiKey, version, command: 'push-status' });
32
+ let pushResponse;
33
+ pushResponse = await (0, utils_2.retryUntilConditionMet)({
34
+ operation: () => client.remotes.getPush({
35
+ organizationId: orgId,
36
+ projectId,
37
+ pushId,
38
+ }),
39
+ condition: wait
40
+ ? // Keep retrying if status is "pending" or "running" (returning false, so the operation will be retried)
41
+ (result) => !['pending', 'running'].includes(result.status['preview'].deploy.status)
42
+ : null,
43
+ onConditionNotMet: (lastResult) => {
44
+ displayDeploymentAndBuildStatus({
45
+ status: lastResult.status['preview'].deploy.status,
46
+ url: lastResult.status['preview'].deploy.url,
47
+ spinner,
48
+ buildType: 'preview',
49
+ continueOnDeployFailures,
50
+ wait,
51
+ });
52
+ },
53
+ onRetry: (lastResult) => {
54
+ if (argv.onRetry) {
55
+ argv.onRetry({
56
+ preview: lastResult.status.preview,
57
+ production: lastResult.isMainBranch ? lastResult.status.production : null,
58
+ commit: lastResult.commit,
59
+ });
60
+ }
61
+ },
62
+ startTime,
63
+ retryTimeoutMs,
64
+ retryIntervalMs,
65
+ });
66
+ printPushStatus({
67
+ buildType: 'preview',
68
+ spinner,
69
+ wait,
70
+ push: pushResponse,
71
+ continueOnDeployFailures,
72
+ });
73
+ printScorecard(pushResponse.status.preview.scorecard);
74
+ const shouldWaitForProdDeployment = pushResponse.isMainBranch &&
75
+ (wait ? pushResponse.status.preview.deploy.status === 'success' : true);
76
+ if (shouldWaitForProdDeployment) {
77
+ pushResponse = await (0, utils_2.retryUntilConditionMet)({
78
+ operation: () => client.remotes.getPush({
79
+ organizationId: orgId,
80
+ projectId,
81
+ pushId,
82
+ }),
83
+ condition: wait
84
+ ? // Keep retrying if status is "pending" or "running" (returning false, so the operation will be retried)
85
+ (result) => !['pending', 'running'].includes(result.status['production'].deploy.status)
86
+ : null,
87
+ onConditionNotMet: (lastResult) => {
88
+ displayDeploymentAndBuildStatus({
89
+ status: lastResult.status['production'].deploy.status,
90
+ url: lastResult.status['production'].deploy.url,
91
+ spinner,
92
+ buildType: 'production',
93
+ continueOnDeployFailures,
94
+ wait,
95
+ });
96
+ },
97
+ onRetry: (lastResult) => {
98
+ if (argv.onRetry) {
99
+ argv.onRetry({
100
+ preview: lastResult.status.preview,
101
+ production: lastResult.isMainBranch ? lastResult.status.production : null,
102
+ commit: lastResult.commit,
103
+ });
104
+ }
105
+ },
106
+ startTime,
107
+ retryTimeoutMs,
108
+ retryIntervalMs,
109
+ });
110
+ }
111
+ if (pushResponse.isMainBranch) {
112
+ printPushStatus({
113
+ buildType: 'production',
114
+ spinner,
115
+ wait,
116
+ push: pushResponse,
117
+ continueOnDeployFailures,
118
+ });
119
+ printScorecard(pushResponse.status.production.scorecard);
120
+ }
121
+ printPushStatusInfo({ orgId, projectId, pushId, startedAt });
122
+ client.reportSunsetWarnings();
123
+ const summary = {
124
+ preview: pushResponse.status.preview,
125
+ production: pushResponse.isMainBranch ? pushResponse.status.production : null,
126
+ commit: pushResponse.commit,
127
+ };
128
+ return summary;
129
+ }
130
+ catch (err) {
131
+ spinner.stop(); // Spinner can block process exit, so we need to stop it explicitly.
132
+ (0, utils_2.handleReuniteError)('✗ Failed to get push status.', err);
133
+ }
134
+ finally {
135
+ spinner.stop(); // Spinner can block process exit, so we need to stop it explicitly.
136
+ }
137
+ }
138
+ function printPushStatusInfo({ orgId, projectId, pushId, startedAt, }) {
139
+ process.stderr.write(`\nProcessed push-status for ${colors.yellow(orgId)}, ${colors.yellow(projectId)} and pushID ${colors.yellow(pushId)}.\n`);
140
+ (0, miscellaneous_1.printExecutionTime)('push-status', startedAt, 'Finished');
141
+ }
142
+ function printPushStatus({ buildType, spinner, push, continueOnDeployFailures, }) {
143
+ if (!push) {
144
+ return;
145
+ }
146
+ if (push.isOutdated || !push.hasChanges) {
147
+ process.stderr.write(colors.yellow(`Files not added to your project. Reason: ${push.isOutdated ? 'outdated' : 'no changes'}.\n`));
148
+ }
149
+ else {
150
+ displayDeploymentAndBuildStatus({
151
+ status: push.status[buildType].deploy.status,
152
+ url: push.status[buildType].deploy.url,
153
+ buildType,
154
+ spinner,
155
+ continueOnDeployFailures,
156
+ });
157
+ }
158
+ }
159
+ function printScorecard(scorecard) {
160
+ if (!scorecard || scorecard.length === 0) {
161
+ return;
162
+ }
163
+ process.stdout.write(`\n${colors.magenta('Scorecard')}:`);
164
+ for (const scorecardItem of scorecard) {
165
+ process.stdout.write(`
166
+ ${colors.magenta('Name')}: ${scorecardItem.name}
167
+ ${colors.magenta('Status')}: ${scorecardItem.status}
168
+ ${colors.magenta('URL')}: ${colors.cyan(scorecardItem.url)}
169
+ ${colors.magenta('Description')}: ${scorecardItem.description}\n`);
170
+ }
171
+ process.stdout.write(`\n`);
172
+ }
173
+ function displayDeploymentAndBuildStatus({ status, url, spinner, buildType, continueOnDeployFailures, wait, }) {
174
+ const message = getMessage({ status, url, buildType, wait });
175
+ if (status === 'failed' && !continueOnDeployFailures) {
176
+ spinner.stop();
177
+ throw new utils_1.DeploymentError(message);
178
+ }
179
+ if (wait && (status === 'pending' || status === 'running')) {
180
+ return spinner.start(message);
181
+ }
182
+ spinner.stop();
183
+ return process.stdout.write(message);
184
+ }
185
+ function getMessage({ status, url, buildType, wait, }) {
186
+ switch (status) {
187
+ case 'skipped':
188
+ return `${colors.yellow(`Skipped ${buildType}`)}\n`;
189
+ case 'pending': {
190
+ const message = `${colors.yellow(`Pending ${buildType}`)}`;
191
+ return wait ? message : `Status: ${message}\n`;
192
+ }
193
+ case 'running': {
194
+ const message = `${colors.yellow(`Running ${buildType}`)}`;
195
+ return wait ? message : `Status: ${message}\n`;
196
+ }
197
+ case 'success':
198
+ 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`;
199
+ case 'failed':
200
+ 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.')}`;
201
+ default: {
202
+ const message = `${colors.yellow(`No status yet for ${buildType} deploy`)}`;
203
+ return wait ? message : `Status: ${message}\n`;
204
+ }
205
+ }
206
+ }
@@ -0,0 +1,28 @@
1
+ import type { OutputFormat } from '@redocly/openapi-core';
2
+ import type { CommandArgs } from '../../wrapper';
3
+ import type { VerifyConfigOptions } from '../../types';
4
+ export type PushOptions = {
5
+ apis?: string[];
6
+ organization?: string;
7
+ project: string;
8
+ 'mount-path': string;
9
+ branch: string;
10
+ author: string;
11
+ message: string;
12
+ 'commit-sha'?: string;
13
+ 'commit-url'?: string;
14
+ namespace?: string;
15
+ repository?: string;
16
+ 'created-at'?: string;
17
+ files: string[];
18
+ 'default-branch': string;
19
+ domain?: string;
20
+ 'wait-for-deployment'?: boolean;
21
+ 'max-execution-time': number;
22
+ 'continue-on-deploy-failures'?: boolean;
23
+ verbose?: boolean;
24
+ format?: Extract<OutputFormat, 'stylish'>;
25
+ } & VerifyConfigOptions;
26
+ export declare function handlePush({ argv, config, version, }: CommandArgs<PushOptions>): Promise<{
27
+ pushId: string;
28
+ } | void>;