@cmflow/cli 3.0.1 → 3.1.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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmflow/cli",
3
- "version": "3.0.1",
3
+ "version": "3.1.1",
4
4
  "description": "An awesome Git Flow",
5
5
  "author": "camfou",
6
6
  "license": "MIT",
@@ -31,6 +31,7 @@
31
31
  "dependencies": {
32
32
  "any-observable": "0.5.1",
33
33
  "axios": "1.8.2",
34
+ "axios-retry": "^4.5.0",
34
35
  "chalk": "^4.1.0",
35
36
  "commander": "5.1.0",
36
37
  "execa": "5.1.1",
@@ -1,3 +1,5 @@
1
+ import axiosRetry from "axios-retry";
2
+
1
3
  import { getConfig } from "../../get-config.js";
2
4
  import { httpRequest } from "../http/http-request.js";
3
5
 
@@ -25,6 +27,8 @@ export async function triggerDirectusFlow(projectId, versions, opts = {}) {
25
27
  projectId,
26
28
  versions
27
29
  },
30
+ retries: 3,
31
+ retryDelay: axiosRetry.exponentialDelay,
28
32
  ...opts
29
33
  };
30
34
 
@@ -1,19 +1,24 @@
1
- import fetch from "node-fetch";
1
+ import axios from "axios";
2
+ import axiosRetry from "axios-retry";
2
3
 
3
4
  import { getHttpProxy } from "./http-proxy.js";
4
5
 
5
- // export type HttpRequestOptions = {
6
- // rejectUnauthorized?: boolean;
7
- // callee?: string;
8
- // params?: Record<string, string>;
9
- // } & fetch.RequestInit;
10
6
  /**
11
- * @typedef {Object & RequestInit} HttpRequestOptions
7
+ * @typedef {Object} RetryOptions
8
+ * @property {number} [retries=3] - Number of retry attempts
9
+ * @property {number} [retryDelay=1000] - Delay between retries in ms
10
+ * @property {boolean} [retryOnNetworkError=true] - Retry on network errors
11
+ * @property {boolean} [retryOnIdempotentRequests=true] - Retry on 5xx for idempotent requests
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} HttpRequestOptions
12
16
  * @property {boolean} [rejectUnauthorized] - Whether to reject unauthorized SSL certificates
13
17
  * @property {string} [callee] - The name of the caller
14
18
  * @property {Record<string, string>} [params] - Query parameters to be included in the request
15
19
  * @property {string} [method] - HTTP method (default: 'get')
16
20
  * @property {Record<string, string>} [headers] - Custom headers to be included in the request
21
+ * @property {RetryOptions|boolean} [retry] - Retry configuration (true for defaults, or custom config)
17
22
  */
18
23
 
19
24
  /**
@@ -23,43 +28,50 @@ import { getHttpProxy } from "./http-proxy.js";
23
28
  * @return {Promise<*>}
24
29
  */
25
30
  export async function httpRequest(endpoint, options = {}) {
26
- const { callee, headers, method = "get", params = {}, rejectUnauthorized } = options;
27
- const agent = getHttpProxy({
31
+ const { callee, headers, method = "get", params = {}, rejectUnauthorized, ...rest } = options;
32
+ const httpsAgent = getHttpProxy({
28
33
  rejectUnauthorized,
29
34
  callee
30
35
  });
31
36
 
32
- const queryParams = new URLSearchParams(params);
33
- const url = `${endpoint}${queryParams.size ? "?" + queryParams : ""}`;
37
+ const instance = axios.create();
34
38
 
35
- const response = await fetch(url, {
36
- method,
37
- headers: {
38
- ...headers,
39
- "Content-Type": "application/json"
40
- },
41
- agent,
42
- ...options
43
- });
39
+ axiosRetry(instance, options);
40
+
41
+ try {
42
+ const response = await instance({
43
+ url: endpoint,
44
+ method,
45
+ params,
46
+ headers: {
47
+ ...headers,
48
+ "Content-Type": "application/json"
49
+ },
50
+ httpsAgent,
51
+ ...rest
52
+ });
53
+
54
+ console.info({
55
+ message: `Successfully retrieved data from ${callee}`,
56
+ url: endpoint,
57
+ method: method.toUpperCase(),
58
+ status: response.status
59
+ });
60
+
61
+ return response.data;
62
+ } catch (error) {
63
+ const status = error.response?.status;
64
+ const responseData = error.response?.data;
44
65
 
45
- if (!response.ok) {
46
66
  console.error({
47
67
  ...options,
48
68
  message: `Fail to retrieved data from ${callee}`,
49
- url,
69
+ url: endpoint,
50
70
  method: method.toUpperCase(),
51
- status: response.status,
52
- response: await response.json()
71
+ status,
72
+ response: responseData
53
73
  });
54
- throw new Error(`HTTP error! status: ${response.status}`);
55
- }
56
74
 
57
- console.info({
58
- message: `Successfully retrieved data from ${callee}`,
59
- url,
60
- method: method.toUpperCase(),
61
- status: response.status
62
- });
63
-
64
- return response.json();
75
+ throw new Error(`HTTP error! status: ${status}`);
76
+ }
65
77
  }
@@ -1,24 +1,26 @@
1
- import fetch from "node-fetch";
1
+ import axios from "axios";
2
+ import axiosRetry from "axios-retry";
2
3
 
3
4
  import { getHttpProxy } from "./http-proxy.js";
4
5
  import { httpRequest } from "./http-request.js";
5
6
 
6
- vi.mock("node-fetch");
7
+ vi.mock("axios");
8
+ vi.mock("axios-retry");
7
9
  vi.mock("./http-proxy.js");
8
10
 
9
11
  describe("httpRequest", () => {
12
+ let mockInstance;
13
+
10
14
  beforeEach(() => {
11
15
  vi.clearAllMocks();
16
+ mockInstance = vi.fn();
17
+ vi.mocked(axios.create).mockReturnValue(mockInstance);
12
18
  });
13
19
 
14
20
  it("should create an agent in production with proxy", async () => {
15
- vi.mocked(fetch).mockResolvedValue({
21
+ mockInstance.mockResolvedValue({
16
22
  status: 200,
17
- headers: { "content-type": "application/json" },
18
- ok: true,
19
- async json() {
20
- return { data: "test" };
21
- }
23
+ data: { data: "test" }
22
24
  });
23
25
 
24
26
  const result = await httpRequest("http://example.com/test-endpoint", {
@@ -29,18 +31,18 @@ describe("httpRequest", () => {
29
31
  rejectUnauthorized: undefined,
30
32
  callee: "test"
31
33
  });
34
+ expect(axios.create).toHaveBeenCalled();
35
+ expect(axiosRetry).toHaveBeenCalled();
32
36
  expect(result).toEqual({ data: "test" });
33
37
  });
34
38
 
35
39
  it("should throw an error if the response is not ok", async () => {
36
- vi.mocked(fetch).mockResolvedValue({
40
+ const axiosError = new Error("Request failed");
41
+ axiosError.response = {
37
42
  status: 500,
38
- headers: { "content-type": "application/json" },
39
- ok: false,
40
- async json() {
41
- return { data: "test" };
42
- }
43
- });
43
+ data: { error: "Internal server error" }
44
+ };
45
+ mockInstance.mockRejectedValue(axiosError);
44
46
 
45
47
  await expect(httpRequest("http://example.com/test-endpoint", { callee: "test" })).rejects.toThrow("HTTP error! status: 500");
46
48
  });
@@ -1,3 +1,5 @@
1
+ import axiosRetry from "axios-retry";
2
+
1
3
  import { getConfig } from "../../get-config.js";
2
4
  import { httpRequest } from "../http/http-request.js";
3
5
 
@@ -16,6 +18,8 @@ export async function jiraRequest(endpoint, opts = {}) {
16
18
  headers: {
17
19
  ...opts.headers,
18
20
  Authorization: `Basic ${auth}`
19
- }
21
+ },
22
+ retries: 3,
23
+ retryDelay: axiosRetry.exponentialDelay
20
24
  });
21
25
  }
@@ -2,6 +2,7 @@ import { httpRequest } from "../http/http-request.js";
2
2
  import { jiraRequest } from "./jira-request.js";
3
3
 
4
4
  vi.mock("../http/http-request.js");
5
+ vi.mock("axios-retry");
5
6
 
6
7
  describe("jiraRequest", () => {
7
8
  beforeEach(() => {
@@ -34,14 +35,19 @@ describe("jiraRequest", () => {
34
35
  params: { key: "value" }
35
36
  });
36
37
 
37
- expect(httpRequest).toHaveBeenCalledWith(expect.stringContaining("/test-endpoint"), {
38
- callee: "JIRA",
39
- method: "post",
40
- params: { key: "value" },
41
- headers: expect.objectContaining({
42
- Authorization: expect.stringContaining("Basic ")
38
+ expect(httpRequest).toHaveBeenCalledWith(
39
+ expect.stringContaining("/test-endpoint"),
40
+ expect.objectContaining({
41
+ callee: "JIRA",
42
+ method: "post",
43
+ params: { key: "value" },
44
+ headers: expect.objectContaining({
45
+ Authorization: expect.stringContaining("Basic ")
46
+ }),
47
+ retries: 3,
48
+ retryDelay: expect.any(Function)
43
49
  })
44
- });
50
+ );
45
51
  expect(result).toEqual(mockResponse);
46
52
  });
47
53
 
@@ -2,6 +2,7 @@ import { jiraRequest } from "./jira-request.js";
2
2
  import { jiraSearch } from "./jira-search.js";
3
3
 
4
4
  vi.mock("./jira-request.js");
5
+ vi.mock("axios-retry");
5
6
 
6
7
  describe("jiraSearch", () => {
7
8
  beforeEach(() => {
@@ -87,6 +87,7 @@ export function getConfig() {
87
87
  GIT_USER_EMAIL,
88
88
  GITLAB_USER_EMAIL,
89
89
  GITLAB_USER_NAME,
90
+ GIT_FETCH_MAX_DEPTH,
90
91
  JIRA_API,
91
92
  JIRA_EMAIL,
92
93
  JIRA_TOKEN,
@@ -137,6 +138,7 @@ export function getConfig() {
137
138
  REPOSITORY_URL: get(pkg, "repository.url", get(pkg, "repository", REPOSITORY_URL)),
138
139
  GIT_USER_NAME: GIT_USER_NAME || GITLAB_USER_NAME,
139
140
  GIT_USER_EMAIL: GIT_USER_EMAIL || GITLAB_USER_EMAIL,
141
+ GIT_FETCH_MAX_DEPTH,
140
142
  GIT_DISABLE_SSL,
141
143
  BUMP_METHOD,
142
144
  JIRA_API,
@@ -10,7 +10,7 @@ export function verifyConditions(pluginConfig, context) {
10
10
  context.config = getConfig();
11
11
  const {
12
12
  branch,
13
- config: { USER, EMAIL, ORIGIN }
13
+ config: { USER, EMAIL, ORIGIN, GIT_FETCH_MAX_DEPTH }
14
14
  } = context;
15
15
 
16
16
  if (USER && EMAIL) {
@@ -26,7 +26,8 @@ export function verifyConditions(pluginConfig, context) {
26
26
  process.exit(1);
27
27
  }
28
28
 
29
- git.fetch().sync();
29
+ GIT_FETCH_MAX_DEPTH ? git.fetch(`--depth=${GIT_FETCH_MAX_DEPTH}`).sync() : git.fetch().sync();
30
+
30
31
  git.checkout(branch.name).sync();
31
32
  git.branch(`--set-upstream-to=${ORIGIN}/${branch.name}`, branch.name).sync();
32
33
  }