caprover-api 0.0.18 → 0.0.20
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/.github/workflows/publish.yml +32 -0
- package/dist/api/ApiManager.d.ts +1 -1
- package/dist/api/ApiManager.js +3 -2
- package/dist/api/HttpClient.d.ts +6 -3
- package/dist/api/HttpClient.js +28 -16
- package/package.json +2 -2
- package/src/api/ApiManager.ts +5 -2
- package/src/api/HttpClient.ts +66 -17
- package/test/HttpClient.test.js +61 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Publish npm package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- release
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Check out repository
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Node.js
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: 24
|
|
23
|
+
registry-url: https://registry.npmjs.org
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: npm ci
|
|
27
|
+
|
|
28
|
+
- name: Run tests
|
|
29
|
+
run: npm test
|
|
30
|
+
|
|
31
|
+
- name: Publish package
|
|
32
|
+
run: npm publish --access public
|
package/dist/api/ApiManager.d.ts
CHANGED
|
@@ -124,7 +124,7 @@ export default class ApiManager {
|
|
|
124
124
|
addNewCustomOneClickRepo(repositoryUrl: string): Promise<void>;
|
|
125
125
|
deleteCustomOneClickRepo(repositoryUrl: string): Promise<void>;
|
|
126
126
|
addDockerNode(nodeType: string, privateKey: string, remoteNodeIpAddress: string, sshPort: string, sshUser: string, captainIpAddress: string): Promise<void>;
|
|
127
|
-
startOneClickAppDeploy(template: any, values?: any): Promise<{
|
|
127
|
+
startOneClickAppDeploy(template: any, values?: any, templateName?: string): Promise<{
|
|
128
128
|
jobId: string;
|
|
129
129
|
}>;
|
|
130
130
|
getOneClickAppDeployProgress(jobId: string): Promise<OneClickAppDeploymentState>;
|
package/dist/api/ApiManager.js
CHANGED
|
@@ -172,7 +172,7 @@ class ApiManager {
|
|
|
172
172
|
let formData = new FormData();
|
|
173
173
|
formData.append('sourceFile', file);
|
|
174
174
|
return Promise.resolve() //
|
|
175
|
-
.then(http.fetch(http.POST, `/user/apps/appData/${appName}?detached=1`, formData));
|
|
175
|
+
.then(http.fetch(http.POST, `/user/apps/appData/${appName}?detached=1`, formData, http.FORM_DATA));
|
|
176
176
|
}
|
|
177
177
|
registerProject(selectedProject) {
|
|
178
178
|
const http = this.http;
|
|
@@ -507,12 +507,13 @@ class ApiManager {
|
|
|
507
507
|
captainIpAddress,
|
|
508
508
|
}));
|
|
509
509
|
}
|
|
510
|
-
startOneClickAppDeploy(template, values) {
|
|
510
|
+
startOneClickAppDeploy(template, values, templateName) {
|
|
511
511
|
const http = this.http;
|
|
512
512
|
return Promise.resolve() //
|
|
513
513
|
.then(http.fetch(http.POST, '/user/oneclick/deploy', {
|
|
514
514
|
template,
|
|
515
515
|
values,
|
|
516
|
+
templateName,
|
|
516
517
|
}));
|
|
517
518
|
}
|
|
518
519
|
getOneClickAppDeployProgress(jobId) {
|
package/dist/api/HttpClient.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
export type RequestBodyType = 'json' | 'form-data';
|
|
2
|
+
export declare function createPostRequestInit(variables: any, headers: Record<string, string>, bodyType: RequestBodyType): RequestInit;
|
|
1
3
|
export default class HttpClient {
|
|
2
4
|
private baseUrl;
|
|
3
5
|
private authTokenProvider;
|
|
4
6
|
private onLoginRequested;
|
|
5
7
|
readonly GET = "GET";
|
|
6
8
|
readonly POST = "POST";
|
|
9
|
+
readonly FORM_DATA = "form-data";
|
|
7
10
|
isDestroyed: boolean;
|
|
8
11
|
constructor(baseUrl: string, authTokenProvider: () => Promise<string>, onLoginRequested: () => Promise<void>);
|
|
9
12
|
createHeaders(): Promise<any>;
|
|
10
13
|
destroy(): void;
|
|
11
|
-
fetch(method: 'GET' | 'POST', endpoint: string, variables: any): () => Promise<any>;
|
|
12
|
-
fetchInternal(method: 'GET' | 'POST', endpoint: string, variables: any): Promise<any>;
|
|
14
|
+
fetch(method: 'GET' | 'POST', endpoint: string, variables: any, bodyType?: RequestBodyType): () => Promise<any>;
|
|
15
|
+
fetchInternal(method: 'GET' | 'POST', endpoint: string, variables: any, bodyType?: RequestBodyType): Promise<any>;
|
|
13
16
|
getReq(endpoint: string, variables: any): Promise<any>;
|
|
14
|
-
postReq(endpoint: string, variables: any): Promise<any>;
|
|
17
|
+
postReq(endpoint: string, variables: any, bodyType?: RequestBodyType): Promise<any>;
|
|
15
18
|
}
|
package/dist/api/HttpClient.js
CHANGED
|
@@ -3,8 +3,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createPostRequestInit = createPostRequestInit;
|
|
6
7
|
const ErrorFactory_1 = __importDefault(require("./ErrorFactory"));
|
|
7
8
|
const cross_fetch_1 = __importDefault(require("cross-fetch"));
|
|
9
|
+
function createPostRequestInit(variables, headers, bodyType) {
|
|
10
|
+
if (bodyType === 'form-data') {
|
|
11
|
+
return {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers,
|
|
14
|
+
body: variables,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
...headers,
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify(variables),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
8
26
|
function buildQueryParams(params) {
|
|
9
27
|
if (!params || Object.keys(params).length === 0)
|
|
10
28
|
return '';
|
|
@@ -17,15 +35,8 @@ let TOKEN_HEADER = 'x-captain-auth';
|
|
|
17
35
|
let NAMESPACE = 'x-namespace';
|
|
18
36
|
let CAPTAIN = 'captain';
|
|
19
37
|
class CrossFetchEngine {
|
|
20
|
-
static async post(url, variables, headers) {
|
|
21
|
-
const res = await (0, cross_fetch_1.default)(url,
|
|
22
|
-
method: 'POST',
|
|
23
|
-
headers: {
|
|
24
|
-
'Content-Type': 'application/json',
|
|
25
|
-
...headers,
|
|
26
|
-
},
|
|
27
|
-
body: JSON.stringify(variables),
|
|
28
|
-
});
|
|
38
|
+
static async post(url, variables, headers, bodyType) {
|
|
39
|
+
const res = await (0, cross_fetch_1.default)(url, createPostRequestInit(variables, headers, bodyType));
|
|
29
40
|
if (!res.ok) {
|
|
30
41
|
const errBody = await res.text();
|
|
31
42
|
throw new Error(`HTTP ${res.status}: ${errBody}`);
|
|
@@ -58,6 +69,7 @@ class HttpClient {
|
|
|
58
69
|
this.onLoginRequested = onLoginRequested;
|
|
59
70
|
this.GET = 'GET';
|
|
60
71
|
this.POST = 'POST';
|
|
72
|
+
this.FORM_DATA = 'form-data';
|
|
61
73
|
this.isDestroyed = false;
|
|
62
74
|
//
|
|
63
75
|
}
|
|
@@ -78,12 +90,12 @@ class HttpClient {
|
|
|
78
90
|
destroy() {
|
|
79
91
|
this.isDestroyed = true;
|
|
80
92
|
}
|
|
81
|
-
fetch(method, endpoint, variables) {
|
|
93
|
+
fetch(method, endpoint, variables, bodyType = 'json') {
|
|
82
94
|
const self = this;
|
|
83
95
|
return function () {
|
|
84
96
|
return Promise.resolve() //
|
|
85
97
|
.then(function () {
|
|
86
|
-
return self.fetchInternal(method, endpoint, variables); //
|
|
98
|
+
return self.fetchInternal(method, endpoint, variables, bodyType); //
|
|
87
99
|
})
|
|
88
100
|
.then(function (fetchResponse) {
|
|
89
101
|
if (
|
|
@@ -94,7 +106,7 @@ class HttpClient {
|
|
|
94
106
|
.onLoginRequested() //
|
|
95
107
|
.then(function () {
|
|
96
108
|
return self
|
|
97
|
-
.fetchInternal(method, endpoint, variables)
|
|
109
|
+
.fetchInternal(method, endpoint, variables, bodyType)
|
|
98
110
|
.then(function (httpResponse) {
|
|
99
111
|
return httpResponse;
|
|
100
112
|
});
|
|
@@ -134,11 +146,11 @@ class HttpClient {
|
|
|
134
146
|
});
|
|
135
147
|
};
|
|
136
148
|
}
|
|
137
|
-
fetchInternal(method, endpoint, variables) {
|
|
149
|
+
fetchInternal(method, endpoint, variables, bodyType = 'json') {
|
|
138
150
|
if (method === this.GET)
|
|
139
151
|
return this.getReq(endpoint, variables);
|
|
140
152
|
if (method === this.POST)
|
|
141
|
-
return this.postReq(endpoint, variables);
|
|
153
|
+
return this.postReq(endpoint, variables, bodyType);
|
|
142
154
|
throw new Error(`Unknown method: ${method}`);
|
|
143
155
|
}
|
|
144
156
|
getReq(endpoint, variables) {
|
|
@@ -155,14 +167,14 @@ class HttpClient {
|
|
|
155
167
|
return data;
|
|
156
168
|
});
|
|
157
169
|
}
|
|
158
|
-
postReq(endpoint, variables) {
|
|
170
|
+
postReq(endpoint, variables, bodyType = 'json') {
|
|
159
171
|
const self = this;
|
|
160
172
|
return Promise.resolve() //
|
|
161
173
|
.then(function () {
|
|
162
174
|
return self.createHeaders();
|
|
163
175
|
})
|
|
164
176
|
.then(function (headers) {
|
|
165
|
-
return CrossFetchEngine.post(self.baseUrl + endpoint, variables, headers);
|
|
177
|
+
return CrossFetchEngine.post(self.baseUrl + endpoint, variables, headers, bodyType);
|
|
166
178
|
})
|
|
167
179
|
.then(function (data) {
|
|
168
180
|
// console.log(data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "caprover-api",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "API client for CapRover",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"formatter-write": "prettier --write './src/**/*.ts*'",
|
|
10
10
|
"tslint": "tslint -c tslint.json -p tsconfig.json",
|
|
11
11
|
"tslint-fix": "tslint --fix -c tslint.json -p tsconfig.json",
|
|
12
|
-
"test": "
|
|
12
|
+
"test": "npm run build && node --test test/*.test.js",
|
|
13
13
|
"build": "rm -rf ./dist && npm run tslint && npx tsc && chmod +x ./dist -R",
|
|
14
14
|
"dev": "rm -rf ./dist && npx tsc && node ./dist/example.js",
|
|
15
15
|
"generate-barrels": "rm ./src/models/index.ts && barrelsby --directory src/models"
|
package/src/api/ApiManager.ts
CHANGED
|
@@ -291,7 +291,8 @@ export default class ApiManager {
|
|
|
291
291
|
http.fetch(
|
|
292
292
|
http.POST,
|
|
293
293
|
`/user/apps/appData/${appName}?detached=1`,
|
|
294
|
-
formData
|
|
294
|
+
formData,
|
|
295
|
+
http.FORM_DATA
|
|
295
296
|
)
|
|
296
297
|
)
|
|
297
298
|
}
|
|
@@ -865,7 +866,8 @@ export default class ApiManager {
|
|
|
865
866
|
|
|
866
867
|
startOneClickAppDeploy(
|
|
867
868
|
template: any,
|
|
868
|
-
values?: any
|
|
869
|
+
values?: any,
|
|
870
|
+
templateName?: string
|
|
869
871
|
): Promise<{ jobId: string }> {
|
|
870
872
|
const http = this.http
|
|
871
873
|
|
|
@@ -874,6 +876,7 @@ export default class ApiManager {
|
|
|
874
876
|
http.fetch(http.POST, '/user/oneclick/deploy', {
|
|
875
877
|
template,
|
|
876
878
|
values,
|
|
879
|
+
templateName,
|
|
877
880
|
})
|
|
878
881
|
)
|
|
879
882
|
}
|
package/src/api/HttpClient.ts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import ErrorFactory from './ErrorFactory'
|
|
2
2
|
import fetch from 'cross-fetch'
|
|
3
3
|
|
|
4
|
+
export type RequestBodyType = 'json' | 'form-data'
|
|
5
|
+
|
|
6
|
+
export function createPostRequestInit(
|
|
7
|
+
variables: any,
|
|
8
|
+
headers: Record<string, string>,
|
|
9
|
+
bodyType: RequestBodyType
|
|
10
|
+
): RequestInit {
|
|
11
|
+
if (bodyType === 'form-data') {
|
|
12
|
+
return {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers,
|
|
15
|
+
body: variables,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
...headers,
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(variables),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
4
29
|
function buildQueryParams(params: Record<string, any>): string {
|
|
5
30
|
if (!params || Object.keys(params).length === 0) return ''
|
|
6
31
|
return (
|
|
@@ -20,17 +45,14 @@ let CAPTAIN = 'captain'
|
|
|
20
45
|
class CrossFetchEngine {
|
|
21
46
|
public static async post(
|
|
22
47
|
url: string,
|
|
23
|
-
variables:
|
|
24
|
-
headers: Record<string, string
|
|
48
|
+
variables: any,
|
|
49
|
+
headers: Record<string, string>,
|
|
50
|
+
bodyType: RequestBodyType
|
|
25
51
|
): Promise<any> {
|
|
26
|
-
const res = await fetch(
|
|
27
|
-
|
|
28
|
-
headers
|
|
29
|
-
|
|
30
|
-
...headers,
|
|
31
|
-
},
|
|
32
|
-
body: JSON.stringify(variables),
|
|
33
|
-
})
|
|
52
|
+
const res = await fetch(
|
|
53
|
+
url,
|
|
54
|
+
createPostRequestInit(variables, headers, bodyType)
|
|
55
|
+
)
|
|
34
56
|
|
|
35
57
|
if (!res.ok) {
|
|
36
58
|
const errBody = await res.text()
|
|
@@ -69,6 +91,7 @@ class CrossFetchEngine {
|
|
|
69
91
|
export default class HttpClient {
|
|
70
92
|
public readonly GET = 'GET'
|
|
71
93
|
public readonly POST = 'POST'
|
|
94
|
+
public readonly FORM_DATA = 'form-data'
|
|
72
95
|
public isDestroyed = false
|
|
73
96
|
|
|
74
97
|
constructor(
|
|
@@ -99,12 +122,22 @@ export default class HttpClient {
|
|
|
99
122
|
this.isDestroyed = true
|
|
100
123
|
}
|
|
101
124
|
|
|
102
|
-
fetch(
|
|
125
|
+
fetch(
|
|
126
|
+
method: 'GET' | 'POST',
|
|
127
|
+
endpoint: string,
|
|
128
|
+
variables: any,
|
|
129
|
+
bodyType: RequestBodyType = 'json'
|
|
130
|
+
) {
|
|
103
131
|
const self = this
|
|
104
132
|
return function (): Promise<any> {
|
|
105
133
|
return Promise.resolve() //
|
|
106
134
|
.then(function () {
|
|
107
|
-
return self.fetchInternal(
|
|
135
|
+
return self.fetchInternal(
|
|
136
|
+
method,
|
|
137
|
+
endpoint,
|
|
138
|
+
variables,
|
|
139
|
+
bodyType
|
|
140
|
+
) //
|
|
108
141
|
})
|
|
109
142
|
.then(function (fetchResponse) {
|
|
110
143
|
if (
|
|
@@ -116,7 +149,12 @@ export default class HttpClient {
|
|
|
116
149
|
.onLoginRequested() //
|
|
117
150
|
.then(function () {
|
|
118
151
|
return self
|
|
119
|
-
.fetchInternal(
|
|
152
|
+
.fetchInternal(
|
|
153
|
+
method,
|
|
154
|
+
endpoint,
|
|
155
|
+
variables,
|
|
156
|
+
bodyType
|
|
157
|
+
)
|
|
120
158
|
.then(function (httpResponse) {
|
|
121
159
|
return httpResponse
|
|
122
160
|
})
|
|
@@ -159,10 +197,16 @@ export default class HttpClient {
|
|
|
159
197
|
}
|
|
160
198
|
}
|
|
161
199
|
|
|
162
|
-
fetchInternal(
|
|
200
|
+
fetchInternal(
|
|
201
|
+
method: 'GET' | 'POST',
|
|
202
|
+
endpoint: string,
|
|
203
|
+
variables: any,
|
|
204
|
+
bodyType: RequestBodyType = 'json'
|
|
205
|
+
) {
|
|
163
206
|
if (method === this.GET) return this.getReq(endpoint, variables)
|
|
164
207
|
|
|
165
|
-
if (method === this.POST)
|
|
208
|
+
if (method === this.POST)
|
|
209
|
+
return this.postReq(endpoint, variables, bodyType)
|
|
166
210
|
|
|
167
211
|
throw new Error(`Unknown method: ${method}`)
|
|
168
212
|
}
|
|
@@ -186,7 +230,11 @@ export default class HttpClient {
|
|
|
186
230
|
})
|
|
187
231
|
}
|
|
188
232
|
|
|
189
|
-
postReq(
|
|
233
|
+
postReq(
|
|
234
|
+
endpoint: string,
|
|
235
|
+
variables: any,
|
|
236
|
+
bodyType: RequestBodyType = 'json'
|
|
237
|
+
) {
|
|
190
238
|
const self = this
|
|
191
239
|
return Promise.resolve() //
|
|
192
240
|
.then(function () {
|
|
@@ -196,7 +244,8 @@ export default class HttpClient {
|
|
|
196
244
|
return CrossFetchEngine.post(
|
|
197
245
|
self.baseUrl + endpoint,
|
|
198
246
|
variables,
|
|
199
|
-
headers
|
|
247
|
+
headers,
|
|
248
|
+
bodyType
|
|
200
249
|
)
|
|
201
250
|
})
|
|
202
251
|
.then(function (data) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const test = require('node:test')
|
|
3
|
+
|
|
4
|
+
const ErrorFactory = require('../dist/api/ErrorFactory').default
|
|
5
|
+
const HttpClientModule = require('../dist/api/HttpClient')
|
|
6
|
+
const HttpClient = HttpClientModule.default
|
|
7
|
+
const { createPostRequestInit } = HttpClientModule
|
|
8
|
+
|
|
9
|
+
test('JSON requests preserve the existing serialization and content type', () => {
|
|
10
|
+
const payload = { appName: 'test-app' }
|
|
11
|
+
const request = createPostRequestInit(
|
|
12
|
+
payload,
|
|
13
|
+
{ 'x-captain-auth': 'token' },
|
|
14
|
+
'json'
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
assert.deepEqual(request.headers, {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'x-captain-auth': 'token',
|
|
20
|
+
})
|
|
21
|
+
assert.equal(request.body, JSON.stringify(payload))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('form data is passed through without setting a content type', () => {
|
|
25
|
+
const formData = { sourceFile: 'tar contents' }
|
|
26
|
+
const headers = { 'x-captain-auth': 'token' }
|
|
27
|
+
const request = createPostRequestInit(formData, headers, 'form-data')
|
|
28
|
+
|
|
29
|
+
assert.strictEqual(request.body, formData)
|
|
30
|
+
assert.deepEqual(request.headers, headers)
|
|
31
|
+
assert.equal(Object.hasOwn(request.headers, 'Content-Type'), false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('authentication retry preserves the form-data body type', async () => {
|
|
35
|
+
let loginRequests = 0
|
|
36
|
+
const bodyTypes = []
|
|
37
|
+
const client = new HttpClient(
|
|
38
|
+
'https://captain.example.com',
|
|
39
|
+
() => Promise.resolve('token'),
|
|
40
|
+
() => {
|
|
41
|
+
loginRequests++
|
|
42
|
+
return Promise.resolve()
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
client.fetchInternal = (_method, _endpoint, _variables, bodyType) => {
|
|
47
|
+
bodyTypes.push(bodyType)
|
|
48
|
+
if (bodyTypes.length === 1) {
|
|
49
|
+
return Promise.resolve({
|
|
50
|
+
status: ErrorFactory.STATUS_AUTH_TOKEN_INVALID,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Promise.resolve({ status: ErrorFactory.OKAY, data: {} })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await client.fetch(client.POST, '/upload', {}, client.FORM_DATA)()
|
|
58
|
+
|
|
59
|
+
assert.equal(loginRequests, 1)
|
|
60
|
+
assert.deepEqual(bodyTypes, ['form-data', 'form-data'])
|
|
61
|
+
})
|