@capawesome/cli 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [2.1.0](https://github.com/capawesome-team/cli/compare/v2.0.3...v2.1.0) (2025-08-26)
6
+
7
+
8
+ ### Features
9
+
10
+ * retry failed http requests if status code is `5xx` ([4a2399f](https://github.com/capawesome-team/cli/commit/4a2399f8219edd35d4a5a6f79e3f4c5e15fa8659)), closes [#67](https://github.com/capawesome-team/cli/issues/67)
11
+
5
12
  ## [2.0.3](https://github.com/capawesome-team/cli/compare/v2.0.2...v2.0.3) (2025-08-26)
6
13
 
7
14
 
@@ -1,6 +1,17 @@
1
+ import configService from '../services/config.js';
1
2
  import axios from 'axios';
3
+ import axiosRetry from 'axios-retry';
2
4
  import pkg from '../../package.json' with { type: 'json' };
3
- import configService from '../services/config.js';
5
+ // Register middleware to retry failed requests
6
+ axiosRetry(axios, {
7
+ retries: 3,
8
+ retryDelay: axiosRetry.exponentialDelay,
9
+ retryCondition: (error) => {
10
+ // Network errors and 5xx responses are retried
11
+ return (axiosRetry.isNetworkOrIdempotentRequestError(error) ||
12
+ (error.response?.status !== undefined && error.response.status >= 500));
13
+ },
14
+ });
4
15
  class HttpClientImpl {
5
16
  baseHeaders = {
6
17
  'User-Agent': `Capawesome CLI v${pkg.version}`,
@@ -0,0 +1,72 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import nock from 'nock';
3
+ import configService from '../services/config.js';
4
+ // Mock the config service
5
+ vi.mock('@/services/config.js', () => ({
6
+ default: {
7
+ getValueForKey: vi.fn().mockResolvedValue('https://api.example.com'),
8
+ },
9
+ }));
10
+ describe('http-client', () => {
11
+ beforeEach(() => {
12
+ nock.cleanAll();
13
+ vi.clearAllMocks();
14
+ });
15
+ it('should retry requests on 5xx status codes', async () => {
16
+ // Mock the API_BASE_URL
17
+ vi.mocked(configService.getValueForKey).mockResolvedValue('https://api.example.com');
18
+ // Mock the first two requests to return 500, then succeed on the third
19
+ nock('https://api.example.com')
20
+ .get('/test')
21
+ .reply(500, { error: 'Internal Server Error' })
22
+ .get('/test')
23
+ .reply(502, { error: 'Bad Gateway' })
24
+ .get('/test')
25
+ .reply(200, { success: true });
26
+ // Import http-client after mocking to ensure axios-retry is configured
27
+ const { default: httpClient } = await import('./http-client.js');
28
+ const response = await httpClient.get('/test');
29
+ expect(response.status).toBe(200);
30
+ expect(response.data).toEqual({ success: true });
31
+ expect(nock.isDone()).toBe(true);
32
+ });
33
+ it('should not retry requests on 4xx status codes', async () => {
34
+ vi.mocked(configService.getValueForKey).mockResolvedValue('https://api.example.com');
35
+ // Mock a 404 response - should not be retried
36
+ nock('https://api.example.com').get('/not-found').reply(404, { error: 'Not Found' });
37
+ const { default: httpClient } = await import('./http-client.js');
38
+ await expect(httpClient.get('/not-found')).rejects.toThrow();
39
+ expect(nock.isDone()).toBe(true);
40
+ });
41
+ it('should eventually fail after maximum retries on persistent 5xx errors', async () => {
42
+ vi.mocked(configService.getValueForKey).mockResolvedValue('https://api.example.com');
43
+ // Mock 4 consecutive 500 responses (initial + 3 retries)
44
+ nock('https://api.example.com').get('/persistent-error').times(4).reply(500, { error: 'Internal Server Error' });
45
+ const { default: httpClient } = await import('./http-client.js');
46
+ await expect(httpClient.get('/persistent-error')).rejects.toThrow();
47
+ expect(nock.isDone()).toBe(true);
48
+ });
49
+ it('should succeed on first try when no errors occur', async () => {
50
+ vi.mocked(configService.getValueForKey).mockResolvedValue('https://api.example.com');
51
+ nock('https://api.example.com').get('/success').reply(200, { data: 'success' });
52
+ const { default: httpClient } = await import('./http-client.js');
53
+ const response = await httpClient.get('/success');
54
+ expect(response.status).toBe(200);
55
+ expect(response.data).toEqual({ data: 'success' });
56
+ expect(nock.isDone()).toBe(true);
57
+ });
58
+ it('should retry on network errors', async () => {
59
+ vi.mocked(configService.getValueForKey).mockResolvedValue('https://api.example.com');
60
+ // Mock a network error followed by success
61
+ nock('https://api.example.com')
62
+ .get('/network-error')
63
+ .replyWithError('Network Error')
64
+ .get('/network-error')
65
+ .reply(200, { recovered: true });
66
+ const { default: httpClient } = await import('./http-client.js');
67
+ const response = await httpClient.get('/network-error');
68
+ expect(response.status).toBe(200);
69
+ expect(response.data).toEqual({ recovered: true });
70
+ expect(nock.isDone()).toBe(true);
71
+ });
72
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/cli",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
4
4
  "description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -53,6 +53,7 @@
53
53
  "@sentry/node": "8.55.0",
54
54
  "archiver": "7.0.1",
55
55
  "axios": "1.8.4",
56
+ "axios-retry": "4.5.0",
56
57
  "c12": "2.0.1",
57
58
  "consola": "3.3.0",
58
59
  "form-data": "4.0.4",