@startanaicompany/cli 1.4.16 → 1.4.18
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/CLAUDE.md +495 -36
- package/bin/saac.js +58 -4
- package/package.json +1 -1
- package/src/commands/create.js +27 -6
- package/src/commands/delete.js +191 -4
- package/src/commands/deploy.js +41 -8
- package/src/commands/deployments.js +130 -0
- package/src/commands/domain.js +204 -3
- package/src/commands/env.js +264 -3
- package/src/commands/exec.js +277 -0
- package/src/commands/keys.js +0 -0
- package/src/commands/logs.js +232 -4
- package/src/commands/run.js +170 -0
- package/src/commands/shell.js +166 -0
- package/src/commands/whoami.js +90 -4
- package/src/lib/api.js +63 -4
- package/src/lib/errorDisplay.js +170 -0
package/src/lib/api.js
CHANGED
|
@@ -9,8 +9,9 @@ const pkg = require('../../package.json');
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Create axios instance with base configuration
|
|
12
|
+
* @param {number} timeout - Timeout in milliseconds (default: 30000)
|
|
12
13
|
*/
|
|
13
|
-
function createClient() {
|
|
14
|
+
function createClient(timeout = 30000) {
|
|
14
15
|
const user = getUser();
|
|
15
16
|
const envApiKey = process.env.SAAC_API_KEY; // For CI/CD
|
|
16
17
|
|
|
@@ -33,7 +34,7 @@ function createClient() {
|
|
|
33
34
|
|
|
34
35
|
return axios.create({
|
|
35
36
|
baseURL: getApiUrl(),
|
|
36
|
-
timeout:
|
|
37
|
+
timeout: timeout,
|
|
37
38
|
headers,
|
|
38
39
|
});
|
|
39
40
|
}
|
|
@@ -88,9 +89,11 @@ async function getUserInfo() {
|
|
|
88
89
|
|
|
89
90
|
/**
|
|
90
91
|
* Create a new application
|
|
92
|
+
* Note: This waits for deployment to complete (up to 5 minutes)
|
|
91
93
|
*/
|
|
92
94
|
async function createApplication(appData) {
|
|
93
|
-
|
|
95
|
+
// Use 5-minute timeout for deployment waiting
|
|
96
|
+
const client = createClient(300000); // 5 minutes
|
|
94
97
|
const response = await client.post('/applications', appData);
|
|
95
98
|
return response.data;
|
|
96
99
|
}
|
|
@@ -115,9 +118,11 @@ async function getApplication(uuid) {
|
|
|
115
118
|
|
|
116
119
|
/**
|
|
117
120
|
* Deploy application
|
|
121
|
+
* Note: This waits for deployment to complete (up to 5 minutes)
|
|
118
122
|
*/
|
|
119
123
|
async function deployApplication(uuid) {
|
|
120
|
-
|
|
124
|
+
// Use 5-minute timeout for deployment waiting
|
|
125
|
+
const client = createClient(300000); // 5 minutes
|
|
121
126
|
const response = await client.post(`/applications/${uuid}/deploy`);
|
|
122
127
|
return response.data;
|
|
123
128
|
}
|
|
@@ -181,6 +186,24 @@ async function healthCheck() {
|
|
|
181
186
|
return response.data;
|
|
182
187
|
}
|
|
183
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Get deployment history for an application
|
|
191
|
+
*/
|
|
192
|
+
async function getDeployments(uuid, params = {}) {
|
|
193
|
+
const client = createClient();
|
|
194
|
+
const response = await client.get(`/applications/${uuid}/deployments`, { params });
|
|
195
|
+
return response.data;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get deployment logs (build logs, not runtime logs)
|
|
200
|
+
*/
|
|
201
|
+
async function getDeploymentLogs(uuid, params = {}) {
|
|
202
|
+
const client = createClient();
|
|
203
|
+
const response = await client.get(`/applications/${uuid}/deployment-logs`, { params });
|
|
204
|
+
return response.data;
|
|
205
|
+
}
|
|
206
|
+
|
|
184
207
|
/**
|
|
185
208
|
* Request login OTP (no API key required)
|
|
186
209
|
*/
|
|
@@ -236,6 +259,37 @@ async function getApiKeyInfo() {
|
|
|
236
259
|
return response.data;
|
|
237
260
|
}
|
|
238
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Get environment variables for an application
|
|
264
|
+
*/
|
|
265
|
+
async function getEnvironmentVariables(uuid) {
|
|
266
|
+
const client = createClient();
|
|
267
|
+
const response = await client.get(`/applications/${uuid}/env`);
|
|
268
|
+
return response.data;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Execute a command in the application container
|
|
273
|
+
* @param {string} uuid - Application UUID
|
|
274
|
+
* @param {object} execRequest - { command, workdir, timeout }
|
|
275
|
+
*/
|
|
276
|
+
async function executeCommand(uuid, execRequest) {
|
|
277
|
+
const client = createClient();
|
|
278
|
+
const response = await client.post(`/applications/${uuid}/exec`, execRequest);
|
|
279
|
+
return response.data;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get execution history for an application
|
|
284
|
+
* @param {string} uuid - Application UUID
|
|
285
|
+
* @param {object} params - { limit, offset }
|
|
286
|
+
*/
|
|
287
|
+
async function getExecutionHistory(uuid, params = {}) {
|
|
288
|
+
const client = createClient();
|
|
289
|
+
const response = await client.get(`/applications/${uuid}/exec/history`, { params });
|
|
290
|
+
return response.data;
|
|
291
|
+
}
|
|
292
|
+
|
|
239
293
|
module.exports = {
|
|
240
294
|
createClient,
|
|
241
295
|
login,
|
|
@@ -249,6 +303,7 @@ module.exports = {
|
|
|
249
303
|
deployApplication,
|
|
250
304
|
getApplicationLogs,
|
|
251
305
|
updateEnvironmentVariables,
|
|
306
|
+
getEnvironmentVariables,
|
|
252
307
|
updateDomain,
|
|
253
308
|
deleteApplication,
|
|
254
309
|
healthCheck,
|
|
@@ -256,4 +311,8 @@ module.exports = {
|
|
|
256
311
|
verifyLoginOtp,
|
|
257
312
|
regenerateApiKey,
|
|
258
313
|
getApiKeyInfo,
|
|
314
|
+
getDeployments,
|
|
315
|
+
getDeploymentLogs,
|
|
316
|
+
executeCommand,
|
|
317
|
+
getExecutionHistory,
|
|
259
318
|
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Display Utilities
|
|
3
|
+
* Formats and displays deployment errors with actionable advice
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get actionable advice for specific error types
|
|
10
|
+
*/
|
|
11
|
+
function getErrorAdvice(errorType) {
|
|
12
|
+
const advice = {
|
|
13
|
+
PORT_CONFLICT: [
|
|
14
|
+
'Fix: Remove host port bindings from your docker-compose.yml',
|
|
15
|
+
' Change "8080:8080" to just "8080"',
|
|
16
|
+
' Traefik will handle external routing automatically',
|
|
17
|
+
],
|
|
18
|
+
BUILD_FAILED: [
|
|
19
|
+
'Fix: Check your Dockerfile and build configuration',
|
|
20
|
+
' Run "docker build ." locally to debug',
|
|
21
|
+
' Verify all dependencies are properly specified',
|
|
22
|
+
],
|
|
23
|
+
TIMEOUT: [
|
|
24
|
+
'Note: Deployment may still be running in the background',
|
|
25
|
+
' Check status with: saac status',
|
|
26
|
+
' View logs with: saac logs --follow',
|
|
27
|
+
],
|
|
28
|
+
UNKNOWN: [
|
|
29
|
+
'Tip: Check the deployment logs for more details',
|
|
30
|
+
' Run: saac logs --follow',
|
|
31
|
+
],
|
|
32
|
+
PARSE_ERROR: [
|
|
33
|
+
'Note: Could not parse deployment logs',
|
|
34
|
+
' Contact support if this persists',
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return advice[errorType] || advice.UNKNOWN;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Format log lines for display
|
|
43
|
+
*/
|
|
44
|
+
function formatLogs(logs, maxLines = 10) {
|
|
45
|
+
if (!logs || logs.length === 0) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If logs is an array of objects with 'output' field
|
|
50
|
+
if (logs[0] && typeof logs[0] === 'object' && logs[0].output) {
|
|
51
|
+
return logs.slice(-maxLines).map(log => log.output);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If logs is an array of strings
|
|
55
|
+
return logs.slice(-maxLines);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Display detailed deployment error information
|
|
60
|
+
*/
|
|
61
|
+
function displayDeploymentError(result, logger) {
|
|
62
|
+
logger.newline();
|
|
63
|
+
logger.error(`Deployment Error: ${result.message || 'Deployment failed'}`);
|
|
64
|
+
logger.newline();
|
|
65
|
+
|
|
66
|
+
// Display deployment info
|
|
67
|
+
if (result.deployment_status || result.status) {
|
|
68
|
+
logger.field('Status', result.deployment_status || result.status);
|
|
69
|
+
}
|
|
70
|
+
if (result.git_branch) {
|
|
71
|
+
logger.field('Branch', result.git_branch);
|
|
72
|
+
}
|
|
73
|
+
if (result.deployment_uuid) {
|
|
74
|
+
logger.field('Deployment ID', result.deployment_uuid);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
logger.newline();
|
|
78
|
+
|
|
79
|
+
// Display structured errors
|
|
80
|
+
if (result.errors && result.errors.length > 0) {
|
|
81
|
+
logger.info('Error Details:');
|
|
82
|
+
result.errors.forEach(err => {
|
|
83
|
+
logger.log(` ${chalk.yellow(`[${err.type}]`)} ${err.message}`);
|
|
84
|
+
if (err.detail) {
|
|
85
|
+
const detailLines = err.detail.split('\n').slice(0, 3); // First 3 lines
|
|
86
|
+
detailLines.forEach(line => {
|
|
87
|
+
logger.log(` ${chalk.gray(line)}`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
logger.newline();
|
|
92
|
+
|
|
93
|
+
// Display actionable advice for the first error
|
|
94
|
+
const firstError = result.errors[0];
|
|
95
|
+
if (firstError && firstError.type) {
|
|
96
|
+
const advice = getErrorAdvice(firstError.type);
|
|
97
|
+
if (advice && advice.length > 0) {
|
|
98
|
+
logger.info('Suggested Fix:');
|
|
99
|
+
advice.forEach(line => {
|
|
100
|
+
logger.log(` ${chalk.cyan(line)}`);
|
|
101
|
+
});
|
|
102
|
+
logger.newline();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Display relevant logs (filtered error logs)
|
|
108
|
+
if (result.relevant_logs && result.relevant_logs.length > 0) {
|
|
109
|
+
logger.info('Relevant Logs:');
|
|
110
|
+
const logLines = formatLogs(result.relevant_logs, 5);
|
|
111
|
+
logLines.forEach(line => {
|
|
112
|
+
logger.log(` ${chalk.gray(line)}`);
|
|
113
|
+
});
|
|
114
|
+
logger.newline();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Display last logs (context)
|
|
118
|
+
if (result.last_logs && result.last_logs.length > 0) {
|
|
119
|
+
logger.info('Recent Log Output:');
|
|
120
|
+
const logLines = formatLogs(result.last_logs, 5);
|
|
121
|
+
logLines.forEach(line => {
|
|
122
|
+
logger.log(` ${chalk.gray(line)}`);
|
|
123
|
+
});
|
|
124
|
+
logger.newline();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Suggest viewing full logs
|
|
128
|
+
if (result.coolify_app_uuid || result.deployment_uuid) {
|
|
129
|
+
const uuid = result.coolify_app_uuid || result.deployment_uuid;
|
|
130
|
+
logger.info('View full logs:');
|
|
131
|
+
logger.log(` ${chalk.yellow('saac logs --follow')}`);
|
|
132
|
+
logger.newline();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Display recovery instructions after failed create
|
|
138
|
+
*/
|
|
139
|
+
function displayCreateRecoveryInstructions(result, logger) {
|
|
140
|
+
if (result.coolify_app_uuid) {
|
|
141
|
+
logger.warn(`Application "${result.app_name}" was created but deployment failed.`);
|
|
142
|
+
logger.info('Fix the issue in your repository, then redeploy:');
|
|
143
|
+
logger.log(` ${chalk.yellow('saac deploy')}`);
|
|
144
|
+
logger.newline();
|
|
145
|
+
logger.info('Or delete and recreate:');
|
|
146
|
+
logger.log(` ${chalk.yellow(`saac delete ${result.coolify_app_uuid}`)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Display timeout-specific instructions
|
|
152
|
+
*/
|
|
153
|
+
function displayTimeoutInstructions(logger) {
|
|
154
|
+
logger.warn('Deployment timed out after 5 minutes');
|
|
155
|
+
logger.newline();
|
|
156
|
+
logger.info('The deployment may still be running in the background.');
|
|
157
|
+
logger.info('Check the status:');
|
|
158
|
+
logger.log(` ${chalk.yellow('saac status')}`);
|
|
159
|
+
logger.newline();
|
|
160
|
+
logger.info('Or view live logs:');
|
|
161
|
+
logger.log(` ${chalk.yellow('saac logs --follow')}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
getErrorAdvice,
|
|
166
|
+
formatLogs,
|
|
167
|
+
displayDeploymentError,
|
|
168
|
+
displayCreateRecoveryInstructions,
|
|
169
|
+
displayTimeoutInstructions,
|
|
170
|
+
};
|