@startanaicompany/cli 1.4.16 → 1.4.17

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 CHANGED
@@ -275,6 +275,71 @@ saac create web -s web -r git@git... -t abc123 \
275
275
  - Saves project config to `.saac/config.json` after successful creation
276
276
  - Displays next steps and useful commands
277
277
 
278
+ **Deployment Behavior (NEW):**
279
+
280
+ The `create` command now **waits for the initial deployment to complete** (up to 5 minutes) before returning.
281
+
282
+ **Response Time:**
283
+ - Typical: 30-120 seconds
284
+ - Maximum: 5 minutes (timeout)
285
+
286
+ **Success Response:**
287
+ ```json
288
+ {
289
+ "success": true,
290
+ "coolify_app_uuid": "...",
291
+ "app_name": "my-app",
292
+ "domain": "https://myapp.startanaicompany.com",
293
+ "deployment_status": "finished",
294
+ "deployment_uuid": "...",
295
+ "git_branch": "master"
296
+ }
297
+ ```
298
+
299
+ **Failure Response (HTTP 200 with success: false):**
300
+ ```json
301
+ {
302
+ "success": false,
303
+ "coolify_app_uuid": "...",
304
+ "app_name": "my-app",
305
+ "deployment_status": "failed",
306
+ "message": "Port 8080 is already in use. Remove host port bindings...",
307
+ "errors": [
308
+ {
309
+ "type": "PORT_CONFLICT",
310
+ "message": "Port 8080 is already in use...",
311
+ "detail": "Bind for 0.0.0.0:8080 failed..."
312
+ }
313
+ ],
314
+ "relevant_logs": [...],
315
+ "last_logs": [...]
316
+ }
317
+ ```
318
+
319
+ **Error Types:**
320
+ - `PORT_CONFLICT` - Host port binding conflict in docker-compose.yml
321
+ - `BUILD_FAILED` - Build process returned non-zero exit code
322
+ - `TIMEOUT` - Deployment didn't complete in 5 minutes
323
+ - `UNKNOWN` - Generic deployment failure
324
+
325
+ **Important Notes:**
326
+ 1. **Application is created even if deployment fails** - the UUID is saved to `.saac/config.json`
327
+ 2. Failed deployments return HTTP 200 (not 4xx) with `success: false`
328
+ 3. CLI must check the `success` field, not just HTTP status code
329
+ 4. Detailed error information is displayed with actionable advice
330
+ 5. User can fix the issue and run `saac deploy` to retry
331
+
332
+ **Error Display:**
333
+ The CLI displays comprehensive error information:
334
+ - Error summary message
335
+ - Structured error details with types
336
+ - Relevant error logs (filtered)
337
+ - Last log lines for context
338
+ - Actionable advice based on error type:
339
+ - `PORT_CONFLICT`: Remove host port bindings from docker-compose.yml
340
+ - `BUILD_FAILED`: Check Dockerfile, run `docker build .` locally
341
+ - `TIMEOUT`: Check `saac status` and `saac logs`, may still be running
342
+
278
343
  ### Update Command Implementation
279
344
 
280
345
  The `update` command allows modifying application configuration after deployment using `PATCH /api/v1/applications/:uuid`.
@@ -373,7 +438,7 @@ saac git disconnect git.startanaicompany.com
373
438
 
374
439
  ### Deploy Command Implementation
375
440
 
376
- The `deploy` command triggers deployment for the current application.
441
+ The `deploy` command triggers deployment and **waits for completion** (up to 5 minutes).
377
442
 
378
443
  **Usage:**
379
444
  ```bash
@@ -385,13 +450,57 @@ saac deploy --force
385
450
  1. Validates authentication (session token not expired)
386
451
  2. Checks for project config (`.saac/config.json`)
387
452
  3. Makes POST request to `/api/v1/applications/:uuid/deploy`
388
- 4. Displays deployment status and deployment ID
389
- 5. Shows command to follow logs: `saac logs --follow`
453
+ 4. **Waits for deployment to complete** (up to 5 minutes)
454
+ 5. Displays deployment status with detailed error information on failure
455
+
456
+ **Response Time:**
457
+ - Typical: 30-120 seconds
458
+ - Maximum: 5 minutes (timeout)
459
+
460
+ **Success Response:**
461
+ ```json
462
+ {
463
+ "success": true,
464
+ "status": "finished",
465
+ "deployment_uuid": "...",
466
+ "git_branch": "master",
467
+ "domain": "https://myapp.startanaicompany.com",
468
+ "traefik_status": "queued"
469
+ }
470
+ ```
471
+
472
+ **Failure Response:**
473
+ ```json
474
+ {
475
+ "success": false,
476
+ "status": "failed",
477
+ "message": "Build failed with exit code 1",
478
+ "errors": [
479
+ {
480
+ "type": "BUILD_FAILED",
481
+ "message": "Build failed with exit code 1",
482
+ "detail": "npm ERR! code ELIFECYCLE..."
483
+ }
484
+ ],
485
+ "relevant_logs": [...],
486
+ "last_logs": [...]
487
+ }
488
+ ```
390
489
 
391
- **Response Fields:**
392
- - `status` - Application status after deployment triggered
393
- - `domain` - Application domain (if available)
394
- - `deployment_id` - Unique ID for this deployment
490
+ **Error Types:**
491
+ - `PORT_CONFLICT` - Host port binding conflict
492
+ - `BUILD_FAILED` - Build process failed
493
+ - `TIMEOUT` - Deployment didn't complete in 5 minutes
494
+ - `UNKNOWN` - Generic failure
495
+
496
+ **Error Display:**
497
+ The CLI displays:
498
+ 1. Error summary message
499
+ 2. Structured error details with types
500
+ 3. Relevant logs (filtered error logs)
501
+ 4. Last 5 log lines for context
502
+ 5. Actionable advice based on error type
503
+ 6. Suggestion to view full logs with `saac logs --follow`
395
504
 
396
505
  **Note:** The `--force` flag is defined in the CLI but not currently used by the API.
397
506
 
@@ -588,7 +697,7 @@ The wrapper API expects Git repositories to be hosted on the StartAnAiCompany Gi
588
697
  - During registration, Gitea username can be auto-detected or manually provided
589
698
  - Applications reference repositories in the format: `git@git.startanaicompany.com:user/repo.git`
590
699
 
591
- ## Current Status - Version 1.4.16
700
+ ## Current Status - Version 1.4.17
592
701
 
593
702
  ### Completed Features
594
703
 
@@ -808,4 +917,4 @@ Before publishing to npm:
808
917
  - `dotenv` - Environment variables
809
918
  - `open` - Open browser for OAuth (v8.4.2 for compatibility with chalk v4)
810
919
 
811
- **Version:** 1.4.16 (current)
920
+ **Version:** 1.4.17 (current)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startanaicompany/cli",
3
- "version": "1.4.16",
3
+ "version": "1.4.17",
4
4
  "description": "Official CLI for StartAnAiCompany.com - Deploy AI recruitment sites with ease",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -8,6 +8,7 @@ const logger = require('../lib/logger');
8
8
  const oauth = require('../lib/oauth');
9
9
  const inquirer = require('inquirer');
10
10
  const { execSync } = require('child_process');
11
+ const errorDisplay = require('../lib/errorDisplay');
11
12
 
12
13
  async function create(name, options) {
13
14
  try {
@@ -281,14 +282,12 @@ async function create(name, options) {
281
282
 
282
283
  logger.newline();
283
284
 
284
- const spin = logger.spinner('Creating application...').start();
285
+ const spin = logger.spinner('Creating application and deploying (this may take up to 5 minutes)...').start();
285
286
 
286
287
  try {
287
288
  const result = await api.createApplication(appData);
288
289
 
289
- spin.succeed('Application created successfully!');
290
-
291
- // Save project configuration
290
+ // Always save project configuration (even if deployment failed)
292
291
  saveProjectConfig({
293
292
  applicationUuid: result.coolify_app_uuid,
294
293
  applicationName: result.app_name,
@@ -297,13 +296,35 @@ async function create(name, options) {
297
296
  gitRepository: appData.git_repository,
298
297
  });
299
298
 
299
+ // Check if deployment failed
300
+ if (result.success === false) {
301
+ spin.fail('Deployment failed');
302
+
303
+ // Display detailed error information
304
+ errorDisplay.displayDeploymentError(result, logger);
305
+
306
+ // Show recovery instructions
307
+ errorDisplay.displayCreateRecoveryInstructions(result, logger);
308
+
309
+ process.exit(1);
310
+ }
311
+
312
+ // SUCCESS: Application created and deployed
313
+ spin.succeed('Application created and deployed successfully!');
314
+
300
315
  logger.newline();
301
- logger.success('Application created!');
316
+ logger.success('Your application is live!');
302
317
  logger.newline();
303
318
  logger.field('Name', result.app_name);
304
319
  logger.field('Domain', result.domain);
305
320
  logger.field('UUID', result.coolify_app_uuid);
306
- logger.field('Status', result.deployment_status);
321
+ logger.field('Status', result.deployment_status || 'finished');
322
+ if (result.git_branch) {
323
+ logger.field('Branch', result.git_branch);
324
+ }
325
+ if (result.deployment_uuid) {
326
+ logger.field('Deployment ID', result.deployment_uuid);
327
+ }
307
328
  logger.newline();
308
329
 
309
330
  // Show next steps
@@ -5,6 +5,7 @@
5
5
  const api = require('../lib/api');
6
6
  const { getProjectConfig, isAuthenticated } = require('../lib/config');
7
7
  const logger = require('../lib/logger');
8
+ const errorDisplay = require('../lib/errorDisplay');
8
9
 
9
10
  async function deploy(options) {
10
11
  try {
@@ -25,30 +26,62 @@ async function deploy(options) {
25
26
  const { applicationUuid, applicationName } = projectConfig;
26
27
 
27
28
  logger.section(`Deploying ${applicationName}`);
29
+ logger.newline();
28
30
 
29
- const spin = logger.spinner('Triggering deployment...').start();
31
+ const spin = logger.spinner('Deploying application (waiting for completion, up to 5 minutes)...').start();
30
32
 
31
33
  try {
32
34
  const result = await api.deployApplication(applicationUuid);
33
35
 
34
- spin.succeed('Deployment triggered!');
36
+ // Check if deployment failed
37
+ if (result.success === false) {
38
+ spin.fail('Deployment failed');
39
+
40
+ // Display detailed error information
41
+ errorDisplay.displayDeploymentError(result, logger);
42
+
43
+ // Handle timeout specifically
44
+ if (result.status === 'timeout') {
45
+ errorDisplay.displayTimeoutInstructions(logger);
46
+ }
47
+
48
+ process.exit(1);
49
+ }
50
+
51
+ // SUCCESS: Deployment completed
52
+ spin.succeed('Deployment completed successfully!');
35
53
 
36
54
  logger.newline();
37
- logger.success('Deployment started successfully');
55
+ logger.success('Your application has been deployed!');
38
56
  logger.newline();
39
57
  logger.field('Application', applicationName);
40
58
  logger.field('Status', result.status);
59
+ if (result.git_branch) {
60
+ logger.field('Branch', result.git_branch);
61
+ }
41
62
  if (result.domain) {
42
63
  logger.field('Domain', result.domain);
43
64
  }
44
- logger.field('Deployment ID', result.deployment_id);
65
+ if (result.deployment_uuid || result.deployment_id) {
66
+ logger.field('Deployment ID', result.deployment_uuid || result.deployment_id);
67
+ }
45
68
  logger.newline();
46
- logger.info(
47
- `View logs with: ${logger.chalk.yellow('saac logs --follow')}`
48
- );
69
+
70
+ // Show Traefik status if present
71
+ if (result.traefik_status === 'queued') {
72
+ logger.info('Routing configuration is being applied (may take a few seconds)');
73
+ logger.newline();
74
+ } else if (result.traefik_status === 'failed') {
75
+ logger.warn('Routing configuration failed - application may not be accessible');
76
+ logger.newline();
77
+ }
78
+
79
+ logger.info('Useful commands:');
80
+ logger.log(` saac logs --follow View live deployment logs`);
81
+ logger.log(` saac status Check application status`);
49
82
 
50
83
  } catch (error) {
51
- spin.fail('Deployment failed');
84
+ spin.fail('Deployment request failed');
52
85
  throw error;
53
86
  }
54
87
  } catch (error) {
File without changes
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
  }
@@ -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
+ };