@boltic/cli 1.0.44-dev1.3 → 1.0.44
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/api/integration.js +66 -40
- package/api/serverless.js +21 -39
- package/commands/login.js +4 -4
- package/commands/serverless.js +3 -3
- package/helper/error.js +95 -35
- package/helper/secure-storage.js +27 -82
- package/package.json +2 -1
package/api/integration.js
CHANGED
|
@@ -3,7 +3,8 @@ import FormData from "form-data";
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import https from "https";
|
|
5
5
|
import { handleError } from "../helper/error.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getSecret } from "../helper/secure-storage.js";
|
|
7
|
+
import { logApi, logApiRequest, logApiResponse } from "../helper/verbose.js";
|
|
7
8
|
|
|
8
9
|
const getHttpsAgentForUrl = (baseUrl) => {
|
|
9
10
|
try {
|
|
@@ -22,20 +23,33 @@ const getHttpsAgentForUrl = (baseUrl) => {
|
|
|
22
23
|
return undefined;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const buildAuthHeaders = async (token, session) => {
|
|
27
|
+
const pat = await getSecret("pat");
|
|
28
|
+
|
|
29
|
+
// If PAT exists, prefer PAT-based auth and do not send bearer/session
|
|
30
|
+
if (pat && pat.trim()) {
|
|
31
|
+
return {
|
|
32
|
+
"x-boltic-token": pat.trim(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Fallback to existing Bearer + Cookie auth
|
|
37
|
+
const headers = {};
|
|
38
|
+
if (token) {
|
|
39
|
+
headers.Authorization = `Bearer ${token}`;
|
|
40
|
+
}
|
|
28
41
|
if (session) {
|
|
29
|
-
const headers = {};
|
|
30
|
-
if (token) headers.Authorization = `Bearer ${token}`;
|
|
31
42
|
headers.Cookie = session;
|
|
32
|
-
return headers;
|
|
33
43
|
}
|
|
34
|
-
return
|
|
44
|
+
return headers;
|
|
35
45
|
};
|
|
36
46
|
|
|
37
|
-
const ensureAuthenticatedOrExit = (accountId, token) => {
|
|
38
|
-
|
|
47
|
+
const ensureAuthenticatedOrExit = async (accountId, token, session) => {
|
|
48
|
+
const pat = await getSecret("pat");
|
|
49
|
+
const hasPatAuth = pat && pat.trim() && accountId;
|
|
50
|
+
const hasSessionAuth = token && session && accountId;
|
|
51
|
+
|
|
52
|
+
if (!hasPatAuth && !hasSessionAuth) {
|
|
39
53
|
console.error(
|
|
40
54
|
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
41
55
|
);
|
|
@@ -47,8 +61,8 @@ const ensureAuthenticatedOrExit = (accountId, token) => {
|
|
|
47
61
|
|
|
48
62
|
const getIntegrationGroups = async (apiUrl, accountId, token, session) => {
|
|
49
63
|
try {
|
|
50
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
51
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
64
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
65
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
52
66
|
const axiosOptions = {
|
|
53
67
|
method: "get",
|
|
54
68
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}/integration-groups`,
|
|
@@ -73,8 +87,8 @@ const getIntegrationGroups = async (apiUrl, accountId, token, session) => {
|
|
|
73
87
|
|
|
74
88
|
const listAllIntegrations = async (apiUrl, token, accountId, session) => {
|
|
75
89
|
try {
|
|
76
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
77
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
90
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
91
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
78
92
|
const axiosOptions = {
|
|
79
93
|
method: "get",
|
|
80
94
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}/integrations`,
|
|
@@ -105,8 +119,8 @@ const saveIntegration = async (
|
|
|
105
119
|
integration
|
|
106
120
|
) => {
|
|
107
121
|
try {
|
|
108
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
109
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
122
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
123
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
110
124
|
const response = await axios({
|
|
111
125
|
method: "post",
|
|
112
126
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}/integrations`,
|
|
@@ -125,12 +139,14 @@ const saveIntegration = async (
|
|
|
125
139
|
|
|
126
140
|
const editIntegration = async (apiUrl, token, accountId, session, payload) => {
|
|
127
141
|
const { id } = payload;
|
|
142
|
+
const url = `${apiUrl}/service/panel/automation/v1.0/${accountId}/integrations/${id}/edit`;
|
|
128
143
|
try {
|
|
129
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
130
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
144
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
145
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
146
|
+
logApiRequest("post", url, payload);
|
|
131
147
|
const response = await axios({
|
|
132
148
|
method: "post",
|
|
133
|
-
url
|
|
149
|
+
url,
|
|
134
150
|
data: payload,
|
|
135
151
|
headers: {
|
|
136
152
|
"Content-Type": "application/json",
|
|
@@ -138,9 +154,10 @@ const editIntegration = async (apiUrl, token, accountId, session, payload) => {
|
|
|
138
154
|
},
|
|
139
155
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
140
156
|
});
|
|
141
|
-
|
|
157
|
+
logApiResponse(response.status, response.data);
|
|
142
158
|
return response.data.data;
|
|
143
159
|
} catch (error) {
|
|
160
|
+
logApiResponse(error.response?.status, error.response?.data);
|
|
144
161
|
handleError(error);
|
|
145
162
|
}
|
|
146
163
|
};
|
|
@@ -153,8 +170,8 @@ const updateIntegration = async (
|
|
|
153
170
|
integration
|
|
154
171
|
) => {
|
|
155
172
|
try {
|
|
156
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
157
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
173
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
174
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
158
175
|
const { id, ...rest } = integration;
|
|
159
176
|
const response = await axios({
|
|
160
177
|
method: "patch",
|
|
@@ -180,8 +197,8 @@ const getIntegrationById = async (
|
|
|
180
197
|
integrationId
|
|
181
198
|
) => {
|
|
182
199
|
try {
|
|
183
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
184
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
200
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
201
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
185
202
|
const response = await axios({
|
|
186
203
|
method: "get",
|
|
187
204
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}/integrations/${integrationId}`,
|
|
@@ -205,8 +222,8 @@ const getAuthenticationByIntegrationId = async (
|
|
|
205
222
|
integrationId
|
|
206
223
|
) => {
|
|
207
224
|
try {
|
|
208
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
209
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
225
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
226
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
210
227
|
const response = await axios({
|
|
211
228
|
method: "get",
|
|
212
229
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}integrations/${integrationId}/authentication`,
|
|
@@ -229,7 +246,14 @@ const getWebhooksByIntegrationId = async (
|
|
|
229
246
|
session,
|
|
230
247
|
integrationId
|
|
231
248
|
) => {
|
|
232
|
-
|
|
249
|
+
if (!token || !session || !accountId) {
|
|
250
|
+
console.error(
|
|
251
|
+
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
252
|
+
);
|
|
253
|
+
console.log("\n🔹 Please log in first using:");
|
|
254
|
+
console.log("\x1b[32m$ boltic login\x1b[0m\n");
|
|
255
|
+
process.exit(1); // Exit the CLI with an error code
|
|
256
|
+
}
|
|
233
257
|
|
|
234
258
|
try {
|
|
235
259
|
const response = await axios({
|
|
@@ -237,7 +261,8 @@ const getWebhooksByIntegrationId = async (
|
|
|
237
261
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}integrations/${integrationId}/webhooks`,
|
|
238
262
|
headers: {
|
|
239
263
|
"Content-Type": "application/json",
|
|
240
|
-
|
|
264
|
+
Authorization: `Bearer ${token}`,
|
|
265
|
+
Cookie: session,
|
|
241
266
|
},
|
|
242
267
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
243
268
|
});
|
|
@@ -255,8 +280,8 @@ const getConfigurationByIntegrationId = async (
|
|
|
255
280
|
integrationId
|
|
256
281
|
) => {
|
|
257
282
|
try {
|
|
258
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
259
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
283
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
284
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
260
285
|
const response = await axios({
|
|
261
286
|
method: "get",
|
|
262
287
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}integrations/${integrationId}/configuration`,
|
|
@@ -280,8 +305,8 @@ const syncIntegration = async (
|
|
|
280
305
|
integration
|
|
281
306
|
) => {
|
|
282
307
|
try {
|
|
283
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
284
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
308
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
309
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
285
310
|
const response = await axios({
|
|
286
311
|
method: "post",
|
|
287
312
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}/integrations/${integration.integration_id}/deploy`,
|
|
@@ -306,14 +331,15 @@ const sendIntegrationForReview = async (
|
|
|
306
331
|
integration
|
|
307
332
|
) => {
|
|
308
333
|
try {
|
|
309
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
334
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
310
335
|
const response = await axios({
|
|
311
336
|
method: "post",
|
|
312
337
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}/integration-reviews`,
|
|
313
338
|
data: integration,
|
|
314
339
|
headers: {
|
|
315
340
|
"Content-Type": "application/json",
|
|
316
|
-
|
|
341
|
+
Authorization: `Bearer ${token}`,
|
|
342
|
+
Cookie: session,
|
|
317
343
|
},
|
|
318
344
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
319
345
|
});
|
|
@@ -326,8 +352,8 @@ const sendIntegrationForReview = async (
|
|
|
326
352
|
const purgeCache = async (apiUrl, token, accountId, session, integration) => {
|
|
327
353
|
const { integration_id } = integration;
|
|
328
354
|
try {
|
|
329
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
330
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
355
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
356
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
331
357
|
const response = await axios({
|
|
332
358
|
method: "post",
|
|
333
359
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}/integrations/${integration_id}/cache`,
|
|
@@ -346,8 +372,8 @@ const purgeCache = async (apiUrl, token, accountId, session, integration) => {
|
|
|
346
372
|
|
|
347
373
|
const pullIntegration = async (apiUrl, token, accountId, session, id) => {
|
|
348
374
|
try {
|
|
349
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
350
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
375
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
376
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
351
377
|
const response = await axios({
|
|
352
378
|
method: "get",
|
|
353
379
|
url: `${apiUrl}/service/panel/automation/v1.0/${accountId}/integrations/${id}/pull`,
|
|
@@ -376,8 +402,8 @@ const uploadFileToCloud = async (
|
|
|
376
402
|
}
|
|
377
403
|
|
|
378
404
|
try {
|
|
379
|
-
ensureAuthenticatedOrExit(accountId, token);
|
|
380
|
-
const authHeaders = buildAuthHeaders(token, session);
|
|
405
|
+
await ensureAuthenticatedOrExit(accountId, token, session);
|
|
406
|
+
const authHeaders = await buildAuthHeaders(token, session);
|
|
381
407
|
const form = new FormData();
|
|
382
408
|
form.append("files", fs.createReadStream(filePath));
|
|
383
409
|
|
package/api/serverless.js
CHANGED
|
@@ -22,31 +22,6 @@ const getHttpsAgentForUrl = (baseUrl) => {
|
|
|
22
22
|
return undefined;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
// When session is absent the token is a PAT → use x-boltic-token header.
|
|
26
|
-
// When session is present the token is a bearer token → use Authorization + Cookie.
|
|
27
|
-
const buildAuthHeaders = (token, session) => {
|
|
28
|
-
if (session) {
|
|
29
|
-
const headers = {};
|
|
30
|
-
if (token) headers.Authorization = `Bearer ${token}`;
|
|
31
|
-
headers.Cookie = session;
|
|
32
|
-
return headers;
|
|
33
|
-
}
|
|
34
|
-
return token ? { "x-boltic-token": token } : {};
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Returns true when the user has sufficient credentials for any auth method.
|
|
38
|
-
// Pass accountId when the endpoint requires it; omit (undefined) when it doesn't.
|
|
39
|
-
// Passing null means "required but missing" → returns false.
|
|
40
|
-
const isAuthenticated = (token, session, accountId) => {
|
|
41
|
-
if (accountId !== undefined) {
|
|
42
|
-
// PAT auth: token + accountId (no session needed)
|
|
43
|
-
// Bearer auth: token + session + accountId
|
|
44
|
-
return !!(token && accountId);
|
|
45
|
-
}
|
|
46
|
-
// For endpoints that don't need accountId: PAT needs just token, bearer needs token+session
|
|
47
|
-
return !!token;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
25
|
const listAllServerless = async (
|
|
51
26
|
apiUrl,
|
|
52
27
|
token,
|
|
@@ -54,7 +29,7 @@ const listAllServerless = async (
|
|
|
54
29
|
session,
|
|
55
30
|
query = null
|
|
56
31
|
) => {
|
|
57
|
-
if (!
|
|
32
|
+
if (!token || !session || !accountId) {
|
|
58
33
|
console.error(
|
|
59
34
|
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
60
35
|
);
|
|
@@ -81,7 +56,8 @@ const listAllServerless = async (
|
|
|
81
56
|
params,
|
|
82
57
|
headers: {
|
|
83
58
|
"Content-Type": "application/json",
|
|
84
|
-
|
|
59
|
+
Authorization: `Bearer ${token}`,
|
|
60
|
+
Cookie: session,
|
|
85
61
|
},
|
|
86
62
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
87
63
|
};
|
|
@@ -99,7 +75,7 @@ const listAllServerless = async (
|
|
|
99
75
|
};
|
|
100
76
|
|
|
101
77
|
const pullServerless = async (apiUrl, token, accountId, session, id) => {
|
|
102
|
-
if (!
|
|
78
|
+
if (!token || !session || !accountId) {
|
|
103
79
|
console.error(
|
|
104
80
|
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
105
81
|
);
|
|
@@ -115,7 +91,8 @@ const pullServerless = async (apiUrl, token, accountId, session, id) => {
|
|
|
115
91
|
url,
|
|
116
92
|
headers: {
|
|
117
93
|
"Content-Type": "application/json",
|
|
118
|
-
|
|
94
|
+
Authorization: `Bearer ${token}`,
|
|
95
|
+
Cookie: session,
|
|
119
96
|
},
|
|
120
97
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
121
98
|
});
|
|
@@ -127,7 +104,7 @@ const pullServerless = async (apiUrl, token, accountId, session, id) => {
|
|
|
127
104
|
};
|
|
128
105
|
|
|
129
106
|
const publishServerless = async (apiUrl, token, session, payload) => {
|
|
130
|
-
if (!
|
|
107
|
+
if (!token || !session) {
|
|
131
108
|
console.error(
|
|
132
109
|
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
133
110
|
);
|
|
@@ -142,7 +119,8 @@ const publishServerless = async (apiUrl, token, session, payload) => {
|
|
|
142
119
|
url: `${apiUrl}/service/panel/serverless/v1.0/apps`,
|
|
143
120
|
headers: {
|
|
144
121
|
"Content-Type": "application/json",
|
|
145
|
-
|
|
122
|
+
Authorization: `Bearer ${token}`,
|
|
123
|
+
Cookie: session,
|
|
146
124
|
},
|
|
147
125
|
data: payload,
|
|
148
126
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
@@ -164,7 +142,7 @@ const updateServerless = async (
|
|
|
164
142
|
serverlessId,
|
|
165
143
|
payload
|
|
166
144
|
) => {
|
|
167
|
-
if (!
|
|
145
|
+
if (!token || !session) {
|
|
168
146
|
console.error(
|
|
169
147
|
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
170
148
|
);
|
|
@@ -179,7 +157,8 @@ const updateServerless = async (
|
|
|
179
157
|
url: `${apiUrl}/service/panel/serverless/v1.0/apps/${serverlessId}`,
|
|
180
158
|
headers: {
|
|
181
159
|
"Content-Type": "application/json",
|
|
182
|
-
|
|
160
|
+
Authorization: `Bearer ${token}`,
|
|
161
|
+
Cookie: session,
|
|
183
162
|
},
|
|
184
163
|
data: payload,
|
|
185
164
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
@@ -202,7 +181,7 @@ const getServerlessBuilds = async (
|
|
|
202
181
|
serverlessId,
|
|
203
182
|
options = {}
|
|
204
183
|
) => {
|
|
205
|
-
if (!
|
|
184
|
+
if (!token || !session || !accountId) {
|
|
206
185
|
console.error(
|
|
207
186
|
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
208
187
|
);
|
|
@@ -222,7 +201,8 @@ const getServerlessBuilds = async (
|
|
|
222
201
|
},
|
|
223
202
|
headers: {
|
|
224
203
|
"Content-Type": "application/json",
|
|
225
|
-
|
|
204
|
+
Authorization: `Bearer ${token}`,
|
|
205
|
+
Cookie: session,
|
|
226
206
|
},
|
|
227
207
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
228
208
|
};
|
|
@@ -243,7 +223,7 @@ const getServerlessLogs = async (
|
|
|
243
223
|
serverlessId,
|
|
244
224
|
options = {}
|
|
245
225
|
) => {
|
|
246
|
-
if (!
|
|
226
|
+
if (!token || !session || !accountId) {
|
|
247
227
|
console.error(
|
|
248
228
|
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
249
229
|
);
|
|
@@ -272,7 +252,8 @@ const getServerlessLogs = async (
|
|
|
272
252
|
params,
|
|
273
253
|
headers: {
|
|
274
254
|
"Content-Type": "application/json",
|
|
275
|
-
|
|
255
|
+
Authorization: `Bearer ${token}`,
|
|
256
|
+
Cookie: session,
|
|
276
257
|
},
|
|
277
258
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
278
259
|
};
|
|
@@ -293,7 +274,7 @@ const getBuildLogs = async (
|
|
|
293
274
|
serverlessId,
|
|
294
275
|
buildId
|
|
295
276
|
) => {
|
|
296
|
-
if (!
|
|
277
|
+
if (!token || !session || !accountId) {
|
|
297
278
|
console.error(
|
|
298
279
|
"\x1b[31mError:\x1b[0m Authentication credentials are required."
|
|
299
280
|
);
|
|
@@ -313,7 +294,8 @@ const getBuildLogs = async (
|
|
|
313
294
|
},
|
|
314
295
|
headers: {
|
|
315
296
|
"Content-Type": "application/json",
|
|
316
|
-
|
|
297
|
+
Authorization: `Bearer ${token}`,
|
|
298
|
+
Cookie: session,
|
|
317
299
|
},
|
|
318
300
|
httpsAgent: getHttpsAgentForUrl(apiUrl),
|
|
319
301
|
};
|
package/commands/login.js
CHANGED
|
@@ -234,17 +234,17 @@ async function handlePatLogin(patFromArg, accountIdFromArg) {
|
|
|
234
234
|
// If both values are provided via CLI flags, do not prompt at all.
|
|
235
235
|
if (pat && accountId) {
|
|
236
236
|
try {
|
|
237
|
-
await storeSecret("
|
|
237
|
+
await storeSecret("pat", pat);
|
|
238
238
|
await storeSecret("account_id", accountId);
|
|
239
239
|
console.log(
|
|
240
240
|
chalk.green(
|
|
241
|
-
"\n✅
|
|
241
|
+
"\n✅ PAT token and Account ID stored securely. They will be used for future organization-related requests.\n"
|
|
242
242
|
)
|
|
243
243
|
);
|
|
244
244
|
} catch (error) {
|
|
245
245
|
console.error(
|
|
246
246
|
chalk.red(
|
|
247
|
-
`\n❌ Failed to store credentials: ${error.message || error}\n`
|
|
247
|
+
`\n❌ Failed to store PAT credentials: ${error.message || error}\n`
|
|
248
248
|
)
|
|
249
249
|
);
|
|
250
250
|
}
|
|
@@ -271,7 +271,7 @@ async function handlePatLogin(patFromArg, accountIdFromArg) {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
try {
|
|
274
|
-
await storeSecret("
|
|
274
|
+
await storeSecret("pat", pat);
|
|
275
275
|
await storeSecret("account_id", accountId);
|
|
276
276
|
console.log(
|
|
277
277
|
chalk.green(
|
package/commands/serverless.js
CHANGED
|
@@ -267,7 +267,7 @@ async function handleCreate(args = []) {
|
|
|
267
267
|
*/
|
|
268
268
|
async function checkServerlessExists(name) {
|
|
269
269
|
const env = await getCurrentEnv();
|
|
270
|
-
if (!env || !env.token) {
|
|
270
|
+
if (!env || !env.token || !env.session) {
|
|
271
271
|
return null; // Can't check without auth, let the create call handle auth error
|
|
272
272
|
}
|
|
273
273
|
|
|
@@ -379,7 +379,7 @@ async function handleCodeTypeCreate(
|
|
|
379
379
|
|
|
380
380
|
// Get authentication credentials
|
|
381
381
|
const env = await getCurrentEnv();
|
|
382
|
-
if (!env || !env.token) {
|
|
382
|
+
if (!env || !env.token || !env.session) {
|
|
383
383
|
console.error(chalk.red("\n❌ Not authenticated. Please login first."));
|
|
384
384
|
console.log(chalk.yellow(" Run: boltic login"));
|
|
385
385
|
return;
|
|
@@ -523,7 +523,7 @@ async function handleGitTypeCreate(
|
|
|
523
523
|
|
|
524
524
|
// Get authentication credentials first
|
|
525
525
|
const env = await getCurrentEnv();
|
|
526
|
-
if (!env || !env.token) {
|
|
526
|
+
if (!env || !env.token || !env.session) {
|
|
527
527
|
console.error(chalk.red("\n❌ Not authenticated. Please login first."));
|
|
528
528
|
console.log(chalk.yellow(" Run: boltic login"));
|
|
529
529
|
// Cleanup the created directory
|
package/helper/error.js
CHANGED
|
@@ -6,10 +6,49 @@ const ErrorType = {
|
|
|
6
6
|
NETWORK_ERROR: "NETWORK_ERROR",
|
|
7
7
|
AUTH_ERROR: "AUTH_ERROR",
|
|
8
8
|
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
9
|
+
NOT_FOUND_ERROR: "NOT_FOUND_ERROR",
|
|
9
10
|
CONFIG_ERROR: "CONFIG_ERROR",
|
|
10
11
|
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
11
12
|
};
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Extract the most meaningful error message from an API response body.
|
|
16
|
+
* Checks every known backend format before giving up.
|
|
17
|
+
*/
|
|
18
|
+
const extractApiMessage = (data) => {
|
|
19
|
+
if (!data) return null;
|
|
20
|
+
if (typeof data === "string") return data;
|
|
21
|
+
|
|
22
|
+
// Backend standard: { error: { message } }
|
|
23
|
+
if (data.error?.message) return data.error.message;
|
|
24
|
+
|
|
25
|
+
// Flat: { message }
|
|
26
|
+
if (typeof data.message === "string") return data.message;
|
|
27
|
+
|
|
28
|
+
// Validation array in meta: { error: { meta: { errors: [...] } } }
|
|
29
|
+
if (data.error?.meta?.errors?.length) {
|
|
30
|
+
return data.error.meta.errors
|
|
31
|
+
.map((e) =>
|
|
32
|
+
typeof e === "string" ? e : e.message || JSON.stringify(e)
|
|
33
|
+
)
|
|
34
|
+
.join("; ");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Top-level errors array: { errors: [...] }
|
|
38
|
+
if (Array.isArray(data.errors) && data.errors.length) {
|
|
39
|
+
return data.errors
|
|
40
|
+
.map((e) =>
|
|
41
|
+
typeof e === "string" ? e : e.message || JSON.stringify(e)
|
|
42
|
+
)
|
|
43
|
+
.join("; ");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fallback: { detail } (some frameworks)
|
|
47
|
+
if (typeof data.detail === "string") return data.detail;
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
|
|
13
52
|
// Format error message based on error type and response
|
|
14
53
|
const formatErrorMessage = (error) => {
|
|
15
54
|
if (!error)
|
|
@@ -21,14 +60,14 @@ const formatErrorMessage = (error) => {
|
|
|
21
60
|
// Handle API response errors
|
|
22
61
|
if (error.response) {
|
|
23
62
|
const { status, data } = error.response;
|
|
63
|
+
const apiMessage = extractApiMessage(data);
|
|
24
64
|
|
|
25
65
|
// Authentication errors
|
|
26
66
|
if (status === 401 || status === 403) {
|
|
27
67
|
return {
|
|
28
68
|
type: ErrorType.AUTH_ERROR,
|
|
29
69
|
message:
|
|
30
|
-
|
|
31
|
-
"Authentication failed. Please login again.",
|
|
70
|
+
apiMessage || "Authentication failed. Please login again.",
|
|
32
71
|
};
|
|
33
72
|
}
|
|
34
73
|
|
|
@@ -37,7 +76,25 @@ const formatErrorMessage = (error) => {
|
|
|
37
76
|
return {
|
|
38
77
|
type: ErrorType.VALIDATION_ERROR,
|
|
39
78
|
message:
|
|
40
|
-
|
|
79
|
+
apiMessage || "Invalid request. Please check your input.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Not found errors
|
|
84
|
+
if (status === 404) {
|
|
85
|
+
return {
|
|
86
|
+
type: ErrorType.NOT_FOUND_ERROR,
|
|
87
|
+
message: apiMessage || "The requested resource was not found.",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Conflict errors
|
|
92
|
+
if (status === 409) {
|
|
93
|
+
return {
|
|
94
|
+
type: ErrorType.API_ERROR,
|
|
95
|
+
message:
|
|
96
|
+
apiMessage ||
|
|
97
|
+
"A conflict occurred with the current state of the resource.",
|
|
41
98
|
};
|
|
42
99
|
}
|
|
43
100
|
|
|
@@ -46,7 +103,7 @@ const formatErrorMessage = (error) => {
|
|
|
46
103
|
return {
|
|
47
104
|
type: ErrorType.API_ERROR,
|
|
48
105
|
message:
|
|
49
|
-
|
|
106
|
+
apiMessage ||
|
|
50
107
|
"Server error occurred. Please try again later.",
|
|
51
108
|
};
|
|
52
109
|
}
|
|
@@ -54,7 +111,7 @@ const formatErrorMessage = (error) => {
|
|
|
54
111
|
// Default API error
|
|
55
112
|
return {
|
|
56
113
|
type: ErrorType.API_ERROR,
|
|
57
|
-
message:
|
|
114
|
+
message: apiMessage || `API Error: ${status}`,
|
|
58
115
|
};
|
|
59
116
|
}
|
|
60
117
|
|
|
@@ -67,6 +124,14 @@ const formatErrorMessage = (error) => {
|
|
|
67
124
|
};
|
|
68
125
|
}
|
|
69
126
|
|
|
127
|
+
// Timeout errors
|
|
128
|
+
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
|
|
129
|
+
return {
|
|
130
|
+
type: ErrorType.NETWORK_ERROR,
|
|
131
|
+
message: "Request timed out. Please try again.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
70
135
|
// Configuration errors
|
|
71
136
|
if (error.code === "ENOENT") {
|
|
72
137
|
return {
|
|
@@ -86,37 +151,32 @@ const formatErrorMessage = (error) => {
|
|
|
86
151
|
const handleError = (error) => {
|
|
87
152
|
const formattedError = formatErrorMessage(error);
|
|
88
153
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
chalk.red("\n❌ Configuration Error:"),
|
|
114
|
-
formattedError.message
|
|
115
|
-
);
|
|
116
|
-
break;
|
|
117
|
-
default:
|
|
118
|
-
console.error(chalk.red("\n❌ Error:"), formattedError.message);
|
|
154
|
+
const labels = {
|
|
155
|
+
[ErrorType.AUTH_ERROR]: "Authentication Error",
|
|
156
|
+
[ErrorType.API_ERROR]: "API Error",
|
|
157
|
+
[ErrorType.NETWORK_ERROR]: "Network Error",
|
|
158
|
+
[ErrorType.VALIDATION_ERROR]: "Validation Error",
|
|
159
|
+
[ErrorType.NOT_FOUND_ERROR]: "Not Found",
|
|
160
|
+
[ErrorType.CONFIG_ERROR]: "Configuration Error",
|
|
161
|
+
[ErrorType.UNKNOWN_ERROR]: "Error",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const label = labels[formattedError.type] || "Error";
|
|
165
|
+
console.error(chalk.red(`\n❌ ${label}:`), formattedError.message);
|
|
166
|
+
|
|
167
|
+
// Always show API response details for debugging when there is a response
|
|
168
|
+
if (error?.response) {
|
|
169
|
+
const { status, data, config } = error.response;
|
|
170
|
+
console.error(
|
|
171
|
+
chalk.gray(
|
|
172
|
+
`\n ${config?.method?.toUpperCase() || "?"} ${config?.url || "?"} → ${status}`
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
if (data) {
|
|
176
|
+
console.error(chalk.gray(` Response: ${JSON.stringify(data)}`));
|
|
177
|
+
}
|
|
119
178
|
}
|
|
179
|
+
|
|
120
180
|
process.exit(1);
|
|
121
181
|
};
|
|
122
182
|
|
package/helper/secure-storage.js
CHANGED
|
@@ -1,86 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Mapping from credential keys to environment variable names.
|
|
4
|
-
// In CI/headless environments where keytar (OS keychain) is unavailable,
|
|
5
|
-
// these env vars are used as a fallback so commands like `boltic serverless publish`
|
|
6
|
-
// work without an interactive keychain daemon.
|
|
7
|
-
const ENV_VAR_MAP = {
|
|
8
|
-
token: "BOLTIC_TOKEN",
|
|
9
|
-
account_id: "BOLTIC_ACCOUNT_ID",
|
|
10
|
-
session: "BOLTIC_SESSION",
|
|
11
|
-
environment: "BOLTIC_ENVIRONMENT",
|
|
12
|
-
};
|
|
1
|
+
import keytar from "keytar";
|
|
13
2
|
|
|
14
|
-
|
|
15
|
-
// (e.g. libsecret on Linux CI runners) is caught at call time rather than
|
|
16
|
-
// crashing the process at startup with ERR_DLOPEN_FAILED.
|
|
17
|
-
// The result is cached after the first attempt.
|
|
18
|
-
let _keytar;
|
|
19
|
-
let _keytarAttempted = false;
|
|
20
|
-
|
|
21
|
-
const getKeytar = async () => {
|
|
22
|
-
if (_keytarAttempted) return _keytar;
|
|
23
|
-
_keytarAttempted = true;
|
|
24
|
-
try {
|
|
25
|
-
_keytar = (await import("keytar")).default;
|
|
26
|
-
} catch {
|
|
27
|
-
// Native library not available (e.g. libsecret missing on CI runners).
|
|
28
|
-
_keytar = null;
|
|
29
|
-
}
|
|
30
|
-
return _keytar;
|
|
31
|
-
};
|
|
3
|
+
const SERVICE_NAME = "boltic-cli";
|
|
32
4
|
|
|
33
5
|
/**
|
|
34
|
-
* Store a secret value securely using keytar
|
|
35
|
-
*
|
|
36
|
-
*
|
|
6
|
+
* Store a secret value securely using keytar
|
|
7
|
+
* @param {string} key - The key under which to store the secret
|
|
8
|
+
* @param {string} value - The secret value to store
|
|
9
|
+
* @returns {Promise<void>}
|
|
37
10
|
*/
|
|
38
11
|
export const storeSecret = async (key, value) => {
|
|
39
|
-
const keytar = await getKeytar();
|
|
40
|
-
if (!keytar) {
|
|
41
|
-
console.warn(
|
|
42
|
-
`Warning: System keychain is unavailable. Could not store '${key}'.`
|
|
43
|
-
);
|
|
44
|
-
console.warn(
|
|
45
|
-
`In CI environments, set credentials via environment variables (e.g. BOLTIC_TOKEN, BOLTIC_ACCOUNT_ID).`
|
|
46
|
-
);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
12
|
try {
|
|
50
13
|
await keytar.setPassword(SERVICE_NAME, key, value);
|
|
51
14
|
} catch (error) {
|
|
52
|
-
console.
|
|
53
|
-
|
|
54
|
-
);
|
|
55
|
-
console.warn(
|
|
56
|
-
`In CI environments, set credentials via environment variables (e.g. BOLTIC_TOKEN, BOLTIC_ACCOUNT_ID).`
|
|
57
|
-
);
|
|
15
|
+
console.error(`Error storing secret for ${key}:`, error.message);
|
|
16
|
+
throw error;
|
|
58
17
|
}
|
|
59
18
|
};
|
|
60
19
|
|
|
61
20
|
/**
|
|
62
|
-
* Retrieve a secret value
|
|
63
|
-
*
|
|
21
|
+
* Retrieve a secret value using keytar
|
|
22
|
+
* @param {string} key - The key of the secret to retrieve
|
|
23
|
+
* @returns {Promise<string|null>} The secret value or null if not found
|
|
64
24
|
*/
|
|
65
25
|
export const getSecret = async (key) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} catch {
|
|
72
|
-
// keytar failed — fall through to env var
|
|
73
|
-
}
|
|
26
|
+
try {
|
|
27
|
+
return await keytar.getPassword(SERVICE_NAME, key);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(`Error retrieving secret for ${key}:`, error.message);
|
|
30
|
+
return null;
|
|
74
31
|
}
|
|
75
|
-
return process.env[ENV_VAR_MAP[key]] || null;
|
|
76
32
|
};
|
|
77
33
|
|
|
78
34
|
/**
|
|
79
|
-
* Delete a secret value using keytar
|
|
35
|
+
* Delete a secret value using keytar
|
|
36
|
+
* @param {string} key - The key of the secret to delete
|
|
37
|
+
* @returns {Promise<boolean>} True if deletion was successful
|
|
80
38
|
*/
|
|
81
39
|
export const deleteSecret = async (key) => {
|
|
82
|
-
const keytar = await getKeytar();
|
|
83
|
-
if (!keytar) return false;
|
|
84
40
|
try {
|
|
85
41
|
return await keytar.deletePassword(SERVICE_NAME, key);
|
|
86
42
|
} catch (error) {
|
|
@@ -90,28 +46,17 @@ export const deleteSecret = async (key) => {
|
|
|
90
46
|
};
|
|
91
47
|
|
|
92
48
|
/**
|
|
93
|
-
* Retrieve all secrets
|
|
94
|
-
*
|
|
49
|
+
* Retrieve all secrets stored using keytar
|
|
50
|
+
* @returns {Promise<Array<{account: string, password: string}>|null>} An array of secret objects or null if an error occurs
|
|
95
51
|
*/
|
|
96
|
-
export const getAllSecrets = async () => {
|
|
97
|
-
const keytar = await getKeytar();
|
|
98
|
-
if (keytar) {
|
|
99
|
-
try {
|
|
100
|
-
const keytarSecrets = await keytar.findCredentials(SERVICE_NAME);
|
|
101
|
-
if (keytarSecrets && keytarSecrets.length > 0) return keytarSecrets;
|
|
102
|
-
} catch {
|
|
103
|
-
// keytar failed — fall through to env vars
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
52
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
53
|
+
export const getAllSecrets = async () => {
|
|
54
|
+
try {
|
|
55
|
+
return await keytar.findCredentials(SERVICE_NAME);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Error retrieving all secrets:`, error.message);
|
|
58
|
+
return null;
|
|
113
59
|
}
|
|
114
|
-
return secrets.length > 0 ? secrets : null;
|
|
115
60
|
};
|
|
116
61
|
|
|
117
62
|
export const deleteAllSecrets = async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boltic/cli",
|
|
3
|
-
"version": "1.0.44
|
|
3
|
+
"version": "1.0.44",
|
|
4
4
|
"description": "Professional CLI for interacting with the Boltic platform — create, manage, and publish integrations, serverless functions, workflows, MCPs, and more with enterprise-grade features and a seamless developer experience",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"start": "node index.js",
|
|
42
42
|
"dev": "nodemon index.js",
|
|
43
|
+
"dev:debug": "nodemon --inspect index.js",
|
|
43
44
|
"test": "jest",
|
|
44
45
|
"test:watch": "jest --watch",
|
|
45
46
|
"test:coverage": "jest --coverage",
|