@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/commands/create.js
CHANGED
|
@@ -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
|
-
|
|
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('
|
|
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
|
package/src/commands/delete.js
CHANGED
|
@@ -1,4 +1,191 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Delete Command - Permanently delete application
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const api = require('../lib/api');
|
|
6
|
+
const { getProjectConfig, isAuthenticated } = require('../lib/config');
|
|
7
|
+
const logger = require('../lib/logger');
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Delete application with confirmation
|
|
14
|
+
* @param {object} options - Command options
|
|
15
|
+
*/
|
|
16
|
+
async function deleteApp(options) {
|
|
17
|
+
try {
|
|
18
|
+
// Check authentication
|
|
19
|
+
if (!isAuthenticated()) {
|
|
20
|
+
logger.error('Not logged in. Run: saac login');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check for project config
|
|
25
|
+
const projectConfig = getProjectConfig();
|
|
26
|
+
if (!projectConfig || !projectConfig.applicationUuid) {
|
|
27
|
+
logger.error('No application found in current directory');
|
|
28
|
+
logger.info('Run this command from a project directory (must have .saac/config.json)');
|
|
29
|
+
logger.newline();
|
|
30
|
+
logger.info('Or initialize with:');
|
|
31
|
+
logger.log(' saac init');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { applicationUuid } = projectConfig;
|
|
36
|
+
|
|
37
|
+
// Fetch application details for confirmation
|
|
38
|
+
const fetchSpin = logger.spinner('Fetching application details...').start();
|
|
39
|
+
|
|
40
|
+
let app;
|
|
41
|
+
try {
|
|
42
|
+
app = await api.getApplication(applicationUuid);
|
|
43
|
+
fetchSpin.succeed('Application details retrieved');
|
|
44
|
+
} catch (error) {
|
|
45
|
+
fetchSpin.fail('Failed to fetch application details');
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
logger.newline();
|
|
50
|
+
|
|
51
|
+
// Show confirmation prompt (unless --yes flag)
|
|
52
|
+
if (!options.yes) {
|
|
53
|
+
logger.warn('WARNING: This will permanently delete your application!');
|
|
54
|
+
logger.newline();
|
|
55
|
+
|
|
56
|
+
logger.field('Application', app.name);
|
|
57
|
+
logger.field('Domain', app.domain || `https://${app.subdomain}.startanaicompany.com`);
|
|
58
|
+
logger.field('UUID', app.uuid);
|
|
59
|
+
logger.field('Status', app.status);
|
|
60
|
+
|
|
61
|
+
logger.newline();
|
|
62
|
+
|
|
63
|
+
logger.error('This action cannot be undone. All data will be lost:');
|
|
64
|
+
logger.log(' • Application configuration');
|
|
65
|
+
logger.log(' • All deployments');
|
|
66
|
+
logger.log(' • Environment variables');
|
|
67
|
+
logger.log(' • All logs');
|
|
68
|
+
logger.log(' • DNS records');
|
|
69
|
+
|
|
70
|
+
logger.newline();
|
|
71
|
+
|
|
72
|
+
const answers = await inquirer.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: 'input',
|
|
75
|
+
name: 'confirmation',
|
|
76
|
+
message: 'Type \'yes\' to confirm deletion:',
|
|
77
|
+
validate: (input) => {
|
|
78
|
+
if (input === 'yes') {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return 'Please type \'yes\' to confirm';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
if (answers.confirmation !== 'yes') {
|
|
87
|
+
logger.error('Deletion cancelled');
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
logger.newline();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Delete application
|
|
95
|
+
const deleteSpin = logger.spinner('Deleting application...').start();
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await api.deleteApplication(app.uuid);
|
|
99
|
+
|
|
100
|
+
deleteSpin.succeed('Application deleted successfully!');
|
|
101
|
+
|
|
102
|
+
logger.newline();
|
|
103
|
+
|
|
104
|
+
logger.info('Resources deleted:');
|
|
105
|
+
for (const [resource, deleted] of Object.entries(result.resources_deleted || {})) {
|
|
106
|
+
const icon = deleted ? '✓' : '✗';
|
|
107
|
+
const name = resource.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
108
|
+
const color = deleted ? logger.chalk.green : logger.chalk.red;
|
|
109
|
+
logger.log(` ${color(icon)} ${name}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
logger.newline();
|
|
113
|
+
|
|
114
|
+
logger.success(`Application '${result.application_name}' has been permanently deleted.`);
|
|
115
|
+
|
|
116
|
+
logger.newline();
|
|
117
|
+
|
|
118
|
+
logger.info('Next steps:');
|
|
119
|
+
logger.log(' • Remove local project: rm -rf .saac/');
|
|
120
|
+
logger.log(' • Create new application: saac create');
|
|
121
|
+
logger.log(' • View other applications: saac list');
|
|
122
|
+
|
|
123
|
+
// Remove local config
|
|
124
|
+
const configDir = path.join(process.cwd(), '.saac');
|
|
125
|
+
if (fs.existsSync(configDir)) {
|
|
126
|
+
fs.rmSync(configDir, { recursive: true, force: true });
|
|
127
|
+
logger.newline();
|
|
128
|
+
logger.success('Local configuration removed');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
} catch (error) {
|
|
132
|
+
deleteSpin.fail('Failed to delete application');
|
|
133
|
+
|
|
134
|
+
if (error.response?.status === 404) {
|
|
135
|
+
logger.newline();
|
|
136
|
+
logger.warn('Application not found or already deleted');
|
|
137
|
+
} else if (error.response?.status === 409) {
|
|
138
|
+
logger.newline();
|
|
139
|
+
const data = error.response.data;
|
|
140
|
+
|
|
141
|
+
if (data.error === 'DEPLOYMENT_IN_PROGRESS') {
|
|
142
|
+
logger.warn('Cannot delete application while deployment is in progress');
|
|
143
|
+
logger.newline();
|
|
144
|
+
logger.info('Details:');
|
|
145
|
+
logger.field(' Deployment Status', data.current_deployment_status);
|
|
146
|
+
if (data.deployment_uuid) {
|
|
147
|
+
logger.field(' Deployment UUID', data.deployment_uuid);
|
|
148
|
+
}
|
|
149
|
+
logger.newline();
|
|
150
|
+
logger.info('Suggestion:');
|
|
151
|
+
logger.log(' Wait for deployment to finish or fail before deleting');
|
|
152
|
+
logger.newline();
|
|
153
|
+
logger.info('Check deployment status:');
|
|
154
|
+
logger.log(' saac deployments');
|
|
155
|
+
}
|
|
156
|
+
} else if (error.response?.status === 500) {
|
|
157
|
+
logger.newline();
|
|
158
|
+
const data = error.response.data;
|
|
159
|
+
|
|
160
|
+
if (data.error === 'PARTIAL_DELETION') {
|
|
161
|
+
logger.warn('Application partially deleted. Some resources may remain.');
|
|
162
|
+
logger.newline();
|
|
163
|
+
logger.info('Cleanup status:');
|
|
164
|
+
if (data.details) {
|
|
165
|
+
for (const [resource, deleted] of Object.entries(data.details)) {
|
|
166
|
+
if (resource !== 'error') {
|
|
167
|
+
const icon = deleted ? '✓' : '✗';
|
|
168
|
+
const name = resource.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
169
|
+
logger.log(` ${icon} ${name}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (data.details.error) {
|
|
173
|
+
logger.newline();
|
|
174
|
+
logger.error('Error: ' + data.details.error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
logger.newline();
|
|
178
|
+
logger.info('Please contact support to complete cleanup');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
} catch (error) {
|
|
186
|
+
logger.error(error.response?.data?.message || error.message);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = deleteApp;
|
package/src/commands/deploy.js
CHANGED
|
@@ -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('
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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) {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployments command - List deployment history
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const api = require('../lib/api');
|
|
6
|
+
const { getProjectConfig, isAuthenticated } = require('../lib/config');
|
|
7
|
+
const logger = require('../lib/logger');
|
|
8
|
+
const { table } = require('table');
|
|
9
|
+
|
|
10
|
+
async function deployments(options) {
|
|
11
|
+
try {
|
|
12
|
+
// Check authentication
|
|
13
|
+
if (!isAuthenticated()) {
|
|
14
|
+
logger.error('Not logged in. Run: saac login');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check for project config
|
|
19
|
+
const projectConfig = getProjectConfig();
|
|
20
|
+
if (!projectConfig || !projectConfig.applicationUuid) {
|
|
21
|
+
logger.error('No application found in current directory');
|
|
22
|
+
logger.info('Run this command from a project directory (must have .saac/config.json)');
|
|
23
|
+
logger.newline();
|
|
24
|
+
logger.info('Or initialize with:');
|
|
25
|
+
logger.log(' saac init');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { applicationUuid, applicationName } = projectConfig;
|
|
30
|
+
|
|
31
|
+
logger.section(`Deployment History: ${applicationName}`);
|
|
32
|
+
logger.newline();
|
|
33
|
+
|
|
34
|
+
const spin = logger.spinner('Fetching deployment history...').start();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// Build query parameters
|
|
38
|
+
const params = {};
|
|
39
|
+
if (options.limit) {
|
|
40
|
+
params.limit = parseInt(options.limit, 10);
|
|
41
|
+
}
|
|
42
|
+
if (options.offset) {
|
|
43
|
+
params.offset = parseInt(options.offset, 10);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = await api.getDeployments(applicationUuid, params);
|
|
47
|
+
|
|
48
|
+
spin.succeed('Deployment history retrieved');
|
|
49
|
+
|
|
50
|
+
if (!result.deployments || result.deployments.length === 0) {
|
|
51
|
+
logger.newline();
|
|
52
|
+
logger.warn('No deployments found for this application');
|
|
53
|
+
logger.newline();
|
|
54
|
+
logger.info('Deploy your application with:');
|
|
55
|
+
logger.log(' saac deploy');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
logger.newline();
|
|
60
|
+
|
|
61
|
+
// Build table data
|
|
62
|
+
const data = [
|
|
63
|
+
['UUID', 'Status', 'Branch', 'Commit', 'Duration', 'Trigger', 'Date'],
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
result.deployments.forEach((d) => {
|
|
67
|
+
const uuid = d.deployment_uuid ? d.deployment_uuid.substring(0, 23) + '...' : 'N/A';
|
|
68
|
+
|
|
69
|
+
// Colorize status
|
|
70
|
+
let statusDisplay;
|
|
71
|
+
if (d.status === 'finished') {
|
|
72
|
+
statusDisplay = logger.chalk.green('finished');
|
|
73
|
+
} else if (d.status === 'failed') {
|
|
74
|
+
statusDisplay = logger.chalk.red('failed');
|
|
75
|
+
} else if (d.status === 'running' || d.status === 'queued') {
|
|
76
|
+
statusDisplay = logger.chalk.yellow(d.status);
|
|
77
|
+
} else {
|
|
78
|
+
statusDisplay = logger.chalk.gray(d.status || 'unknown');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const branch = d.git_branch || 'N/A';
|
|
82
|
+
const commit = d.git_commit ? d.git_commit.substring(0, 7) : 'N/A';
|
|
83
|
+
const duration = d.duration_seconds ? `${d.duration_seconds}s` : '-';
|
|
84
|
+
const trigger = d.triggered_by || 'api';
|
|
85
|
+
const date = d.started_at ? new Date(d.started_at).toLocaleString() : 'N/A';
|
|
86
|
+
|
|
87
|
+
data.push([
|
|
88
|
+
uuid,
|
|
89
|
+
statusDisplay,
|
|
90
|
+
branch,
|
|
91
|
+
commit,
|
|
92
|
+
duration,
|
|
93
|
+
trigger,
|
|
94
|
+
date,
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
console.log(table(data, {
|
|
99
|
+
header: {
|
|
100
|
+
alignment: 'center',
|
|
101
|
+
content: `Showing ${result.deployments.length} of ${result.total} deployments`,
|
|
102
|
+
},
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
// Show pagination info
|
|
106
|
+
if (result.total > result.deployments.length) {
|
|
107
|
+
const remaining = result.total - result.deployments.length;
|
|
108
|
+
logger.info(`${remaining} more deployment(s) available`);
|
|
109
|
+
logger.newline();
|
|
110
|
+
logger.info('View more with:');
|
|
111
|
+
logger.log(` saac deployments --limit ${options.limit || 20} --offset ${(options.offset || 0) + (options.limit || 20)}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
logger.newline();
|
|
115
|
+
logger.info('View deployment logs:');
|
|
116
|
+
logger.log(' saac logs --deployment # Latest deployment');
|
|
117
|
+
logger.log(' saac logs --deployment <uuid> # Specific deployment');
|
|
118
|
+
|
|
119
|
+
} catch (error) {
|
|
120
|
+
spin.fail('Failed to fetch deployment history');
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
} catch (error) {
|
|
125
|
+
logger.error(error.response?.data?.message || error.message);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = deployments;
|
package/src/commands/domain.js
CHANGED
|
@@ -1,4 +1,205 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Domain Management Commands - Manage application domain and subdomain
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const api = require('../lib/api');
|
|
6
|
+
const { getProjectConfig, isAuthenticated, saveProjectConfig } = require('../lib/config');
|
|
7
|
+
const logger = require('../lib/logger');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set/change application subdomain
|
|
11
|
+
* @param {string} subdomain - New subdomain
|
|
12
|
+
* @param {object} options - Command options
|
|
13
|
+
*/
|
|
14
|
+
async function set(subdomain, options) {
|
|
15
|
+
try {
|
|
16
|
+
// Check authentication
|
|
17
|
+
if (!isAuthenticated()) {
|
|
18
|
+
logger.error('Not logged in. Run: saac login');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check for project config
|
|
23
|
+
const projectConfig = getProjectConfig();
|
|
24
|
+
if (!projectConfig || !projectConfig.applicationUuid) {
|
|
25
|
+
logger.error('No application found in current directory');
|
|
26
|
+
logger.info('Run this command from a project directory (must have .saac/config.json)');
|
|
27
|
+
logger.newline();
|
|
28
|
+
logger.info('Or initialize with:');
|
|
29
|
+
logger.log(' saac init');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { applicationUuid, applicationName } = projectConfig;
|
|
34
|
+
|
|
35
|
+
// Validate subdomain format
|
|
36
|
+
const subdomainRegex = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
|
|
37
|
+
if (!subdomainRegex.test(subdomain)) {
|
|
38
|
+
logger.error('Invalid subdomain format');
|
|
39
|
+
logger.newline();
|
|
40
|
+
logger.info('Subdomain must:');
|
|
41
|
+
logger.log(' • Be 3-63 characters long');
|
|
42
|
+
logger.log(' • Contain only lowercase letters, numbers, and hyphens');
|
|
43
|
+
logger.log(' • Not start or end with a hyphen');
|
|
44
|
+
logger.newline();
|
|
45
|
+
logger.info('Valid examples:');
|
|
46
|
+
logger.log(' my-app, johnsmith, app-123');
|
|
47
|
+
logger.newline();
|
|
48
|
+
logger.info('Invalid examples:');
|
|
49
|
+
logger.log(' My-App (uppercase), -myapp (starts with hyphen), a (too short)');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const domainSuffix = options.domainSuffix || 'startanaicompany.com';
|
|
54
|
+
|
|
55
|
+
logger.section('Updating Domain');
|
|
56
|
+
logger.newline();
|
|
57
|
+
|
|
58
|
+
logger.info('Configuration:');
|
|
59
|
+
logger.field(' Application', applicationName);
|
|
60
|
+
logger.field(' New Subdomain', subdomain);
|
|
61
|
+
logger.field(' Domain Suffix', domainSuffix);
|
|
62
|
+
logger.field(' New Domain', `https://${subdomain}.${domainSuffix}`);
|
|
63
|
+
|
|
64
|
+
logger.newline();
|
|
65
|
+
|
|
66
|
+
const spin = logger.spinner('Updating domain...').start();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = await api.updateDomain(applicationUuid, subdomain, domainSuffix);
|
|
70
|
+
|
|
71
|
+
spin.succeed('Domain updated successfully!');
|
|
72
|
+
|
|
73
|
+
logger.newline();
|
|
74
|
+
|
|
75
|
+
logger.field('Old Domain', result.old_domain);
|
|
76
|
+
logger.field('New Domain', result.new_domain);
|
|
77
|
+
|
|
78
|
+
logger.newline();
|
|
79
|
+
logger.info('DNS propagation may take 1-5 minutes');
|
|
80
|
+
logger.info('Your application will be accessible at the new domain shortly.');
|
|
81
|
+
|
|
82
|
+
// Update local config
|
|
83
|
+
projectConfig.subdomain = subdomain;
|
|
84
|
+
projectConfig.domain = result.new_domain;
|
|
85
|
+
saveProjectConfig(projectConfig);
|
|
86
|
+
|
|
87
|
+
logger.newline();
|
|
88
|
+
logger.success('Local configuration updated');
|
|
89
|
+
|
|
90
|
+
} catch (error) {
|
|
91
|
+
spin.fail('Failed to update domain');
|
|
92
|
+
|
|
93
|
+
if (error.response?.status === 409) {
|
|
94
|
+
logger.newline();
|
|
95
|
+
const data = error.response.data;
|
|
96
|
+
|
|
97
|
+
if (data.error === 'SUBDOMAIN_TAKEN') {
|
|
98
|
+
logger.warn('Subdomain is already taken');
|
|
99
|
+
logger.newline();
|
|
100
|
+
|
|
101
|
+
if (data.suggestions && data.suggestions.length > 0) {
|
|
102
|
+
logger.info('Try these alternatives:');
|
|
103
|
+
data.suggestions.forEach(suggestion => {
|
|
104
|
+
logger.log(` • ${suggestion}`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
} else if (data.error === 'SUBDOMAIN_BLOCKED') {
|
|
108
|
+
logger.warn('Subdomain is reserved and cannot be used');
|
|
109
|
+
logger.newline();
|
|
110
|
+
logger.info('Reserved subdomains:');
|
|
111
|
+
if (data.blocklist) {
|
|
112
|
+
data.blocklist.forEach(blocked => {
|
|
113
|
+
logger.log(` • ${blocked}`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} else if (error.response?.status === 403) {
|
|
118
|
+
logger.newline();
|
|
119
|
+
const data = error.response.data;
|
|
120
|
+
|
|
121
|
+
if (data.error === 'DOMAIN_SUFFIX_NOT_ALLOWED') {
|
|
122
|
+
logger.warn('Custom domain suffix not available on your plan');
|
|
123
|
+
logger.newline();
|
|
124
|
+
logger.info('Allowed suffixes:');
|
|
125
|
+
if (data.allowed_suffixes) {
|
|
126
|
+
data.allowed_suffixes.forEach(suffix => {
|
|
127
|
+
logger.log(` • ${suffix}`);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
logger.newline();
|
|
131
|
+
if (data.upgrade_required) {
|
|
132
|
+
logger.info('Upgrade to Pro plan for custom domain support');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.error(error.response?.data?.message || error.message);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Show current domain information
|
|
148
|
+
*/
|
|
149
|
+
async function show() {
|
|
150
|
+
try {
|
|
151
|
+
// Check authentication
|
|
152
|
+
if (!isAuthenticated()) {
|
|
153
|
+
logger.error('Not logged in. Run: saac login');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check for project config
|
|
158
|
+
const projectConfig = getProjectConfig();
|
|
159
|
+
if (!projectConfig || !projectConfig.applicationUuid) {
|
|
160
|
+
logger.error('No application found in current directory');
|
|
161
|
+
logger.info('Run this command from a project directory (must have .saac/config.json)');
|
|
162
|
+
logger.newline();
|
|
163
|
+
logger.info('Or initialize with:');
|
|
164
|
+
logger.log(' saac init');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const { applicationUuid } = projectConfig;
|
|
169
|
+
|
|
170
|
+
const spin = logger.spinner('Fetching application details...').start();
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const app = await api.getApplication(applicationUuid);
|
|
174
|
+
|
|
175
|
+
spin.succeed('Application details retrieved');
|
|
176
|
+
|
|
177
|
+
logger.newline();
|
|
178
|
+
|
|
179
|
+
logger.section(`Domain Information: ${app.name}`);
|
|
180
|
+
logger.newline();
|
|
181
|
+
|
|
182
|
+
logger.field('Domain', app.domain || `https://${app.subdomain}.startanaicompany.com`);
|
|
183
|
+
logger.field('Subdomain', app.subdomain);
|
|
184
|
+
logger.field('Domain Suffix', 'startanaicompany.com');
|
|
185
|
+
logger.field('Status', app.status);
|
|
186
|
+
|
|
187
|
+
logger.newline();
|
|
188
|
+
logger.info('To change domain:');
|
|
189
|
+
logger.log(' saac domain set <new-subdomain>');
|
|
190
|
+
|
|
191
|
+
} catch (error) {
|
|
192
|
+
spin.fail('Failed to fetch application details');
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
} catch (error) {
|
|
197
|
+
logger.error(error.response?.data?.message || error.message);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = {
|
|
203
|
+
set,
|
|
204
|
+
show,
|
|
4
205
|
};
|