@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/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: 30000,
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
- const client = createClient();
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
- const client = createClient();
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
+ };