@redocly/cli 1.22.1 → 1.23.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/lib/__tests__/commands/bundle.test.js +146 -1
  3. package/lib/__tests__/fetch-with-timeout.test.js +29 -5
  4. package/lib/__tests__/utils.test.js +54 -32
  5. package/lib/cms/api/__tests__/api.client.test.js +17 -9
  6. package/lib/cms/api/api-client.d.ts +26 -7
  7. package/lib/cms/api/api-client.js +103 -72
  8. package/lib/cms/commands/__tests__/push-status.test.js +1 -1
  9. package/lib/cms/commands/__tests__/push.test.js +41 -1
  10. package/lib/cms/commands/__tests__/utils.test.js +1 -1
  11. package/lib/cms/commands/push-status.d.ts +1 -1
  12. package/lib/cms/commands/push-status.js +3 -7
  13. package/lib/cms/commands/push.js +4 -4
  14. package/lib/cms/commands/utils.d.ts +3 -0
  15. package/lib/cms/commands/utils.js +8 -1
  16. package/lib/commands/bundle.d.ts +1 -1
  17. package/lib/commands/bundle.js +15 -9
  18. package/lib/commands/eject.d.ts +1 -1
  19. package/lib/commands/eject.js +1 -1
  20. package/lib/commands/preview-project/index.js +1 -1
  21. package/lib/index.js +1 -2
  22. package/lib/types.d.ts +1 -0
  23. package/lib/utils/__mocks__/miscellaneous.d.ts +1 -0
  24. package/lib/utils/__mocks__/miscellaneous.js +2 -1
  25. package/lib/utils/fetch-with-timeout.d.ts +6 -1
  26. package/lib/utils/fetch-with-timeout.js +16 -14
  27. package/lib/utils/miscellaneous.d.ts +10 -1
  28. package/lib/utils/miscellaneous.js +24 -20
  29. package/lib/utils/update-version-notifier.js +8 -4
  30. package/package.json +2 -2
  31. package/src/__tests__/commands/bundle.test.ts +172 -4
  32. package/src/__tests__/fetch-with-timeout.test.ts +36 -6
  33. package/src/__tests__/utils.test.ts +58 -33
  34. package/src/cms/api/__tests__/api.client.test.ts +20 -11
  35. package/src/cms/api/api-client.ts +158 -91
  36. package/src/cms/commands/__tests__/push-status.test.ts +1 -1
  37. package/src/cms/commands/__tests__/push.test.ts +49 -2
  38. package/src/cms/commands/__tests__/utils.test.ts +1 -1
  39. package/src/cms/commands/push-status.ts +5 -9
  40. package/src/cms/commands/push.ts +5 -6
  41. package/src/cms/commands/utils.ts +15 -1
  42. package/src/commands/bundle.ts +20 -12
  43. package/src/commands/eject.ts +2 -2
  44. package/src/commands/preview-project/index.ts +1 -1
  45. package/src/index.ts +1 -2
  46. package/src/types.ts +1 -0
  47. package/src/utils/__mocks__/miscellaneous.ts +1 -0
  48. package/src/utils/fetch-with-timeout.ts +23 -14
  49. package/src/utils/miscellaneous.ts +45 -30
  50. package/src/utils/update-version-notifier.ts +11 -5
  51. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @redocly/cli
2
2
 
3
+ ## 1.23.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed a bug where bundling multiple API description files specified as CLI arguments, along with the `--output` option, stored the result in a single file instead of a folder.
8
+ - Updated @redocly/openapi-core to v1.23.1.
9
+
10
+ ## 1.23.0
11
+
12
+ ### Minor Changes
13
+
14
+ - Added support for the `output` option in the per-API configuration so that the destination file can be specified in configuration.
15
+
16
+ ### Patch Changes
17
+
18
+ - Fixed the absolute path for importing plugins in Windows.
19
+ - Added the ability to run the `eject` command without specifying components, which displays a selectable list of all available components.
20
+ - Updated @redocly/openapi-core to v1.23.0.
21
+
3
22
  ## 1.22.1
4
23
 
5
24
  ### Patch Changes
@@ -6,21 +6,28 @@ const miscellaneous_1 = require("../../utils/miscellaneous");
6
6
  const wrapper_1 = require("../../wrapper");
7
7
  jest.mock('@redocly/openapi-core');
8
8
  jest.mock('../../utils/miscellaneous');
9
+ // @ts-ignore
10
+ miscellaneous_1.getOutputFileName = jest.requireActual('../../utils/miscellaneous').getOutputFileName;
9
11
  openapi_core_1.getMergedConfig.mockImplementation((config) => config);
10
12
  describe('bundle', () => {
11
13
  let processExitMock;
12
14
  let exitCb;
15
+ let stderrWriteMock;
16
+ let stdoutWriteMock;
13
17
  beforeEach(() => {
14
18
  processExitMock = jest.spyOn(process, 'exit').mockImplementation();
15
19
  jest.spyOn(process, 'once').mockImplementation((_e, cb) => {
16
20
  exitCb = cb;
17
21
  return process.on(_e, cb);
18
22
  });
19
- jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
23
+ stderrWriteMock = jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
24
+ stdoutWriteMock = jest.spyOn(process.stdout, 'write').mockImplementation(jest.fn());
20
25
  });
21
26
  afterEach(() => {
22
27
  openapi_core_1.bundle.mockClear();
23
28
  openapi_core_1.getTotals.mockReset();
29
+ stderrWriteMock.mockRestore();
30
+ stdoutWriteMock.mockRestore();
24
31
  });
25
32
  it('bundles definitions', async () => {
26
33
  const apis = ['foo.yaml', 'bar.yaml'];
@@ -87,4 +94,142 @@ describe('bundle', () => {
87
94
  });
88
95
  expect(miscellaneous_1.handleError).toHaveBeenCalledTimes(0);
89
96
  });
97
+ describe('per api output', () => {
98
+ it('should store bundled API descriptions in the output files described in the apis section of config IF no positional apis provided AND output is specified for both apis', async () => {
99
+ const apis = {
100
+ foo: {
101
+ root: 'foo.yaml',
102
+ output: 'output/foo.yaml',
103
+ },
104
+ bar: {
105
+ root: 'bar.yaml',
106
+ output: 'output/bar.json',
107
+ },
108
+ };
109
+ const config = {
110
+ apis,
111
+ styleguide: {
112
+ skipPreprocessors: jest.fn(),
113
+ skipDecorators: jest.fn(),
114
+ },
115
+ };
116
+ // @ts-ignore
117
+ miscellaneous_1.getFallbackApisOrExit = jest
118
+ .fn()
119
+ .mockResolvedValueOnce(Object.entries(apis).map(([alias, { root, ...api }]) => ({ ...api, path: root, alias })));
120
+ openapi_core_1.getTotals.mockReturnValue({
121
+ errors: 0,
122
+ warnings: 0,
123
+ ignored: 0,
124
+ });
125
+ await (0, bundle_1.handleBundle)({
126
+ argv: { apis: [] }, // positional
127
+ version: 'test',
128
+ config,
129
+ });
130
+ expect(miscellaneous_1.saveBundle).toBeCalledTimes(2);
131
+ expect(miscellaneous_1.saveBundle).toHaveBeenNthCalledWith(1, 'output/foo.yaml', expect.any(String));
132
+ expect(miscellaneous_1.saveBundle).toHaveBeenNthCalledWith(2, 'output/bar.json', expect.any(String));
133
+ });
134
+ it('should store bundled API descriptions in the output files described in the apis section of config AND print the bundled api without the output specified to the terminal IF no positional apis provided AND output is specified for one api', async () => {
135
+ const apis = {
136
+ foo: {
137
+ root: 'foo.yaml',
138
+ output: 'output/foo.yaml',
139
+ },
140
+ bar: {
141
+ root: 'bar.yaml',
142
+ },
143
+ };
144
+ const config = {
145
+ apis,
146
+ styleguide: {
147
+ skipPreprocessors: jest.fn(),
148
+ skipDecorators: jest.fn(),
149
+ },
150
+ };
151
+ // @ts-ignore
152
+ miscellaneous_1.getFallbackApisOrExit = jest
153
+ .fn()
154
+ .mockResolvedValueOnce(Object.entries(apis).map(([alias, { root, ...api }]) => ({ ...api, path: root, alias })));
155
+ openapi_core_1.getTotals.mockReturnValue({
156
+ errors: 0,
157
+ warnings: 0,
158
+ ignored: 0,
159
+ });
160
+ await (0, bundle_1.handleBundle)({
161
+ argv: { apis: [] }, // positional
162
+ version: 'test',
163
+ config,
164
+ });
165
+ expect(miscellaneous_1.saveBundle).toBeCalledTimes(1);
166
+ expect(miscellaneous_1.saveBundle).toHaveBeenCalledWith('output/foo.yaml', expect.any(String));
167
+ expect(process.stdout.write).toHaveBeenCalledTimes(1);
168
+ });
169
+ it('should NOT store bundled API descriptions in the output files described in the apis section of config IF no there is a positional api provided', async () => {
170
+ const apis = {
171
+ foo: {
172
+ root: 'foo.yaml',
173
+ output: 'output/foo.yaml',
174
+ },
175
+ };
176
+ const config = {
177
+ apis,
178
+ styleguide: {
179
+ skipPreprocessors: jest.fn(),
180
+ skipDecorators: jest.fn(),
181
+ },
182
+ };
183
+ // @ts-ignore
184
+ miscellaneous_1.getFallbackApisOrExit = jest.fn().mockResolvedValueOnce([{ path: 'openapi.yaml' }]);
185
+ openapi_core_1.getTotals.mockReturnValue({
186
+ errors: 0,
187
+ warnings: 0,
188
+ ignored: 0,
189
+ });
190
+ await (0, bundle_1.handleBundle)({
191
+ argv: { apis: ['openapi.yaml'] }, // positional
192
+ version: 'test',
193
+ config,
194
+ });
195
+ expect(miscellaneous_1.saveBundle).toBeCalledTimes(0);
196
+ expect(process.stdout.write).toHaveBeenCalledTimes(1);
197
+ });
198
+ it('should store bundled API descriptions in the directory specified in argv IF multiple positional apis provided AND --output specified', async () => {
199
+ const apis = {
200
+ foo: {
201
+ root: 'foo.yaml',
202
+ output: 'output/foo.yaml',
203
+ },
204
+ bar: {
205
+ root: 'bar.yaml',
206
+ output: 'output/bar.json',
207
+ },
208
+ };
209
+ const config = {
210
+ apis,
211
+ styleguide: {
212
+ skipPreprocessors: jest.fn(),
213
+ skipDecorators: jest.fn(),
214
+ },
215
+ };
216
+ // @ts-ignore
217
+ miscellaneous_1.getFallbackApisOrExit = jest
218
+ .fn()
219
+ .mockResolvedValueOnce(Object.entries(apis).map(([alias, { root, ...api }]) => ({ ...api, path: root, alias })));
220
+ openapi_core_1.getTotals.mockReturnValue({
221
+ errors: 0,
222
+ warnings: 0,
223
+ ignored: 0,
224
+ });
225
+ await (0, bundle_1.handleBundle)({
226
+ argv: { apis: ['foo.yaml', 'bar.yaml'], output: 'dist' }, // cli options
227
+ version: 'test',
228
+ config,
229
+ });
230
+ expect(miscellaneous_1.saveBundle).toBeCalledTimes(2);
231
+ expect(miscellaneous_1.saveBundle).toHaveBeenNthCalledWith(1, 'dist/foo.yaml', expect.any(String));
232
+ expect(miscellaneous_1.saveBundle).toHaveBeenNthCalledWith(2, 'dist/bar.yaml', expect.any(String));
233
+ });
234
+ });
90
235
  });
@@ -3,18 +3,42 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const abort_controller_1 = require("abort-controller");
4
4
  const fetch_with_timeout_1 = require("../utils/fetch-with-timeout");
5
5
  const node_fetch_1 = require("node-fetch");
6
+ const openapi_core_1 = require("@redocly/openapi-core");
7
+ const https_proxy_agent_1 = require("https-proxy-agent");
6
8
  jest.mock('node-fetch');
9
+ jest.mock('@redocly/openapi-core');
7
10
  describe('fetchWithTimeout', () => {
11
+ beforeAll(() => {
12
+ // @ts-ignore
13
+ global.setTimeout = jest.fn();
14
+ global.clearTimeout = jest.fn();
15
+ });
16
+ beforeEach(() => {
17
+ openapi_core_1.getProxyAgent.mockReturnValueOnce(undefined);
18
+ });
8
19
  afterEach(() => {
9
20
  jest.clearAllMocks();
10
21
  });
11
22
  it('should call node-fetch with signal', async () => {
12
- // @ts-ignore
13
- global.setTimeout = jest.fn();
14
- global.clearTimeout = jest.fn();
15
- await (0, fetch_with_timeout_1.default)('url');
23
+ await (0, fetch_with_timeout_1.default)('url', { timeout: 1000 });
16
24
  expect(global.setTimeout).toHaveBeenCalledTimes(1);
17
- expect(node_fetch_1.default).toHaveBeenCalledWith('url', { signal: new abort_controller_1.default().signal });
25
+ expect(node_fetch_1.default).toHaveBeenCalledWith('url', {
26
+ signal: new abort_controller_1.default().signal,
27
+ agent: undefined,
28
+ });
18
29
  expect(global.clearTimeout).toHaveBeenCalledTimes(1);
19
30
  });
31
+ it('should call node-fetch with proxy agent', async () => {
32
+ openapi_core_1.getProxyAgent.mockRestore();
33
+ const proxyAgent = new https_proxy_agent_1.HttpsProxyAgent('http://localhost');
34
+ openapi_core_1.getProxyAgent.mockReturnValueOnce(proxyAgent);
35
+ await (0, fetch_with_timeout_1.default)('url');
36
+ expect(node_fetch_1.default).toHaveBeenCalledWith('url', { agent: proxyAgent });
37
+ });
38
+ it('should call node-fetch without signal when timeout is not passed', async () => {
39
+ await (0, fetch_with_timeout_1.default)('url');
40
+ expect(global.setTimeout).not.toHaveBeenCalled();
41
+ expect(node_fetch_1.default).toHaveBeenCalledWith('url', { agent: undefined });
42
+ expect(global.clearTimeout).not.toHaveBeenCalled();
43
+ });
20
44
  });
@@ -46,19 +46,6 @@ describe('pathToFilename', () => {
46
46
  expect(processedPath).toEqual('user_createWithList');
47
47
  });
48
48
  });
49
- describe('getFallbackApisOrExit', () => {
50
- it('should find alias by filename', async () => {
51
- fs_1.existsSync.mockImplementationOnce(() => true);
52
- const entry = await (0, miscellaneous_1.getFallbackApisOrExit)(['./test.yaml'], {
53
- apis: {
54
- main: {
55
- root: 'test.yaml',
56
- },
57
- },
58
- });
59
- expect(entry).toEqual([{ path: './test.yaml', alias: 'main' }]);
60
- });
61
- });
62
49
  describe('printConfigLintTotals', () => {
63
50
  const totalProblemsMock = {
64
51
  errors: 1,
@@ -143,6 +130,7 @@ describe('getFallbackApisOrExit', () => {
143
130
  {
144
131
  alias: 'main',
145
132
  path: 'someFile.yaml',
133
+ output: undefined,
146
134
  },
147
135
  ]);
148
136
  });
@@ -215,6 +203,40 @@ describe('getFallbackApisOrExit', () => {
215
203
  {
216
204
  alias: 'main',
217
205
  path: 'https://someLinkt/petstore.yaml?main',
206
+ output: undefined,
207
+ },
208
+ ]);
209
+ openapi_core_1.isAbsoluteUrl.mockReset();
210
+ });
211
+ it('should find alias by filename', async () => {
212
+ fs_1.existsSync.mockImplementationOnce(() => true);
213
+ const entry = await (0, miscellaneous_1.getFallbackApisOrExit)(['./test.yaml'], {
214
+ apis: {
215
+ main: {
216
+ root: 'test.yaml',
217
+ styleguide: {},
218
+ },
219
+ },
220
+ });
221
+ expect(entry).toEqual([{ path: './test.yaml', alias: 'main' }]);
222
+ });
223
+ it('should return apis from config with paths and outputs resolved relatively to the config location', async () => {
224
+ fs_1.existsSync.mockImplementationOnce(() => true);
225
+ const entry = await (0, miscellaneous_1.getFallbackApisOrExit)(undefined, {
226
+ apis: {
227
+ main: {
228
+ root: 'test.yaml',
229
+ output: 'output/test.yaml',
230
+ styleguide: {},
231
+ },
232
+ },
233
+ configFile: 'project-folder/redocly.yaml',
234
+ });
235
+ expect(entry).toEqual([
236
+ {
237
+ path: expect.stringMatching(/project\-folder\/test\.yaml$/),
238
+ output: expect.stringMatching(/project\-folder\/output\/test\.yaml$/),
239
+ alias: 'main',
218
240
  },
219
241
  ]);
220
242
  });
@@ -487,24 +509,24 @@ describe('cleanRawInput', () => {
487
509
  expect(stderrMock).toHaveBeenCalledWith(`Unsupported file extension: xml. Using yaml.\n`);
488
510
  });
489
511
  });
490
- describe('writeToFileByExtension', () => {
491
- beforeEach(() => {
492
- jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
493
- colorette_1.yellow.mockImplementation((text) => text);
494
- });
495
- afterEach(() => {
496
- jest.restoreAllMocks();
497
- });
498
- it('should call stringifyYaml function', () => {
499
- (0, miscellaneous_1.writeToFileByExtension)('test data', 'test.yaml');
500
- expect(openapi_core_1.stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
501
- expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
502
- });
503
- it('should call JSON.stringify function', () => {
504
- const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
505
- (0, miscellaneous_1.writeToFileByExtension)('test data', 'test.json');
506
- expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
507
- expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
508
- });
512
+ });
513
+ describe('writeToFileByExtension', () => {
514
+ beforeEach(() => {
515
+ jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
516
+ colorette_1.yellow.mockImplementation((text) => text);
517
+ });
518
+ afterEach(() => {
519
+ jest.restoreAllMocks();
520
+ });
521
+ it('should call stringifyYaml function', () => {
522
+ (0, miscellaneous_1.writeToFileByExtension)('test data', 'test.yaml');
523
+ expect(openapi_core_1.stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
524
+ expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
525
+ });
526
+ it('should call JSON.stringify function', () => {
527
+ const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
528
+ (0, miscellaneous_1.writeToFileByExtension)('test data', 'test.json');
529
+ expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
530
+ expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
509
531
  });
510
532
  });
@@ -14,10 +14,13 @@ describe('ApiClient', () => {
14
14
  const testDomain = 'test-domain.com';
15
15
  const testOrg = 'test-org';
16
16
  const testProject = 'test-project';
17
+ const version = '1.0.0';
18
+ const command = 'push';
19
+ const expectedUserAgent = `redocly-cli/${version} ${command}`;
17
20
  describe('getDefaultBranch()', () => {
18
21
  let apiClient;
19
22
  beforeEach(() => {
20
- apiClient = new api_client_1.ReuniteApiClient(testDomain, testToken);
23
+ apiClient = new api_client_1.ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
21
24
  });
22
25
  it('should get default project branch', async () => {
23
26
  mockFetchResponse({
@@ -32,6 +35,7 @@ describe('ApiClient', () => {
32
35
  headers: {
33
36
  'Content-Type': 'application/json',
34
37
  Authorization: `Bearer ${testToken}`,
38
+ 'user-agent': expectedUserAgent,
35
39
  },
36
40
  signal: expect.any(Object),
37
41
  });
@@ -48,7 +52,7 @@ describe('ApiClient', () => {
48
52
  object: 'problem',
49
53
  }),
50
54
  });
51
- await expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(new Error('Failed to fetch default branch: Project source not found'));
55
+ await expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to fetch default branch. Project source not found.', 404));
52
56
  });
53
57
  it('should throw statusText error if response is not ok', async () => {
54
58
  mockFetchResponse({
@@ -58,7 +62,7 @@ describe('ApiClient', () => {
58
62
  unknownField: 'unknown-error',
59
63
  }),
60
64
  });
61
- await expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(new Error('Failed to fetch default branch: Not found'));
65
+ await expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to fetch default branch. Not found.', 404));
62
66
  });
63
67
  });
64
68
  describe('upsert()', () => {
@@ -68,7 +72,7 @@ describe('ApiClient', () => {
68
72
  };
69
73
  let apiClient;
70
74
  beforeEach(() => {
71
- apiClient = new api_client_1.ReuniteApiClient(testDomain, testToken);
75
+ apiClient = new api_client_1.ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
72
76
  });
73
77
  it('should upsert remote', async () => {
74
78
  const responseMock = {
@@ -89,6 +93,7 @@ describe('ApiClient', () => {
89
93
  headers: {
90
94
  'Content-Type': 'application/json',
91
95
  Authorization: `Bearer ${testToken}`,
96
+ 'user-agent': expectedUserAgent,
92
97
  },
93
98
  body: JSON.stringify({
94
99
  mountPath: remotePayload.mountPath,
@@ -112,17 +117,18 @@ describe('ApiClient', () => {
112
117
  object: 'problem',
113
118
  }),
114
119
  });
115
- await expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(new Error('Failed to upsert remote: Not allowed to mount remote outside of project content path: /docs'));
120
+ await expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to upsert remote. Not allowed to mount remote outside of project content path: /docs.', 403));
116
121
  });
117
122
  it('should throw statusText error if response is not ok', async () => {
118
123
  mockFetchResponse({
119
124
  ok: false,
125
+ status: 404,
120
126
  statusText: 'Not found',
121
127
  json: jest.fn().mockResolvedValue({
122
128
  unknownField: 'unknown-error',
123
129
  }),
124
130
  });
125
- await expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(new Error('Failed to upsert remote: Not found'));
131
+ await expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to upsert remote. Not found.', 404));
126
132
  });
127
133
  });
128
134
  describe('push()', () => {
@@ -156,7 +162,7 @@ describe('ApiClient', () => {
156
162
  };
157
163
  let apiClient;
158
164
  beforeEach(() => {
159
- apiClient = new api_client_1.ReuniteApiClient(testDomain, testToken);
165
+ apiClient = new api_client_1.ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
160
166
  });
161
167
  it('should push to remote', async () => {
162
168
  let passedFormData = new FormData();
@@ -179,6 +185,7 @@ describe('ApiClient', () => {
179
185
  method: 'POST',
180
186
  headers: {
181
187
  Authorization: `Bearer ${testToken}`,
188
+ 'user-agent': expectedUserAgent,
182
189
  },
183
190
  }));
184
191
  expect(JSON.stringify(passedFormData).replace(new RegExp(passedFormData.getBoundary(), 'g'), '')).toEqual(JSON.stringify(formData).replace(new RegExp(formData.getBoundary(), 'g'), ''));
@@ -195,17 +202,18 @@ describe('ApiClient', () => {
195
202
  object: 'problem',
196
203
  }),
197
204
  });
198
- await expect(apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)).rejects.toThrow(new Error('Failed to push: Cannot push to remote'));
205
+ await expect(apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to push. Cannot push to remote.', 403));
199
206
  });
200
207
  it('should throw statusText error if response is not ok', async () => {
201
208
  mockFetchResponse({
202
209
  ok: false,
210
+ status: 404,
203
211
  statusText: 'Not found',
204
212
  json: jest.fn().mockResolvedValue({
205
213
  unknownField: 'unknown-error',
206
214
  }),
207
215
  });
208
- await expect(apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)).rejects.toThrow(new Error('Failed to push: Not found'));
216
+ await expect(apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to push. Not found.', 404));
209
217
  });
210
218
  });
211
219
  });
@@ -1,10 +1,22 @@
1
+ import { type FetchWithTimeoutOptions } from '../../utils/fetch-with-timeout';
2
+ import type { Response } from 'node-fetch';
1
3
  import type { ReadStream } from 'fs';
2
4
  import type { ListRemotesResponse, PushResponse, UpsertRemoteResponse } from './types';
3
- declare class RemotesApiClient {
5
+ export declare class ReuniteApiError extends Error {
6
+ status: number;
7
+ constructor(message: string, status: number);
8
+ }
9
+ declare class ReuniteBaseApiClient {
10
+ protected version: string;
11
+ protected command: string;
12
+ constructor(version: string, command: string);
13
+ protected getParsedResponse<T>(response: Response): Promise<T>;
14
+ protected request(url: string, options: FetchWithTimeoutOptions): Promise<Response>;
15
+ }
16
+ declare class RemotesApiClient extends ReuniteBaseApiClient {
4
17
  private readonly domain;
5
18
  private readonly apiKey;
6
- constructor(domain: string, apiKey: string);
7
- private getParsedResponse;
19
+ constructor(domain: string, apiKey: string, version: string, command: string);
8
20
  getDefaultBranch(organizationId: string, projectId: string): Promise<string>;
9
21
  upsert(organizationId: string, projectId: string, remote: {
10
22
  mountPath: string;
@@ -14,7 +26,11 @@ declare class RemotesApiClient {
14
26
  path: string;
15
27
  stream: ReadStream | Buffer;
16
28
  }[]): Promise<PushResponse>;
17
- getRemotesList(organizationId: string, projectId: string, mountPath: string): Promise<ListRemotesResponse>;
29
+ getRemotesList({ organizationId, projectId, mountPath, }: {
30
+ organizationId: string;
31
+ projectId: string;
32
+ mountPath: string;
33
+ }): Promise<ListRemotesResponse>;
18
34
  getPush({ organizationId, projectId, pushId, }: {
19
35
  organizationId: string;
20
36
  projectId: string;
@@ -22,10 +38,13 @@ declare class RemotesApiClient {
22
38
  }): Promise<PushResponse>;
23
39
  }
24
40
  export declare class ReuniteApiClient {
25
- domain: string;
26
- private readonly apiKey;
27
41
  remotes: RemotesApiClient;
28
- constructor(domain: string, apiKey: string);
42
+ constructor({ domain, apiKey, version, command, }: {
43
+ domain: string;
44
+ apiKey: string;
45
+ version: string;
46
+ command: 'push' | 'push-status';
47
+ });
29
48
  }
30
49
  export type PushPayload = {
31
50
  remoteId: string;