@startanaicompany/cli 1.4.17 → 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.
@@ -1,4 +1,191 @@
1
- // TODO: Implement delete command
2
- module.exports = async function() {
3
- console.log('delete command - Coming soon!');
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;
@@ -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;
@@ -1,4 +1,205 @@
1
- // TODO: Implement domain command
2
- module.exports = async function() {
3
- console.log('domain command - Coming soon!');
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
  };