@startanaicompany/cli 1.4.17 → 1.4.19
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 +428 -29
- package/bin/saac.js +56 -4
- package/package.json +3 -2
- package/src/commands/delete.js +191 -4
- 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/logs.js +232 -4
- package/src/commands/run.js +170 -0
- package/src/commands/shell.js +403 -0
- package/src/commands/whoami.js +90 -4
- package/src/lib/api.js +54 -0
package/src/commands/env.js
CHANGED
|
@@ -1,4 +1,265 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Environment Variables Commands - Manage application environment variables
|
|
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
|
+
/**
|
|
11
|
+
* Get environment variable(s)
|
|
12
|
+
* @param {string} key - Optional specific variable key to retrieve
|
|
13
|
+
*/
|
|
14
|
+
async function get(key) {
|
|
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
|
+
const spin = logger.spinner('Fetching environment variables...').start();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const result = await api.getEnvironmentVariables(applicationUuid);
|
|
39
|
+
|
|
40
|
+
spin.succeed('Environment variables retrieved');
|
|
41
|
+
|
|
42
|
+
logger.newline();
|
|
43
|
+
|
|
44
|
+
if (!key) {
|
|
45
|
+
// Display all variables
|
|
46
|
+
if (Object.keys(result.variables).length === 0) {
|
|
47
|
+
logger.warn('No environment variables set');
|
|
48
|
+
logger.newline();
|
|
49
|
+
logger.info('Set variables with:');
|
|
50
|
+
logger.log(' saac env set KEY=value');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
logger.section(`Environment Variables: ${applicationName}`);
|
|
55
|
+
logger.newline();
|
|
56
|
+
|
|
57
|
+
// Create table data
|
|
58
|
+
const data = [
|
|
59
|
+
['Key', 'Value'],
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
for (const [envKey, value] of Object.entries(result.variables)) {
|
|
63
|
+
const maskedValue = maskSensitiveValue(envKey, value);
|
|
64
|
+
data.push([envKey, maskedValue]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(table(data));
|
|
68
|
+
|
|
69
|
+
logger.info(`Total: ${result.variable_count} / ${result.max_variables} variables`);
|
|
70
|
+
} else {
|
|
71
|
+
// Display specific variable
|
|
72
|
+
if (result.variables[key]) {
|
|
73
|
+
logger.section(`Environment Variable: ${key}`);
|
|
74
|
+
logger.newline();
|
|
75
|
+
logger.field('Key', key);
|
|
76
|
+
logger.field('Value', result.variables[key]);
|
|
77
|
+
} else {
|
|
78
|
+
logger.error(`Variable '${key}' not found`);
|
|
79
|
+
logger.newline();
|
|
80
|
+
logger.info('List all variables:');
|
|
81
|
+
logger.log(' saac env list');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
} catch (error) {
|
|
87
|
+
spin.fail('Failed to fetch environment variables');
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
logger.error(error.response?.data?.message || error.message);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* List all environment variables (alias for get with no key)
|
|
99
|
+
*/
|
|
100
|
+
async function list() {
|
|
101
|
+
return get();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Set environment variables
|
|
106
|
+
* @param {string[]} vars - Array of KEY=VALUE pairs
|
|
107
|
+
*/
|
|
108
|
+
async function set(vars) {
|
|
109
|
+
try {
|
|
110
|
+
// Check authentication
|
|
111
|
+
if (!isAuthenticated()) {
|
|
112
|
+
logger.error('Not logged in. Run: saac login');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for project config
|
|
117
|
+
const projectConfig = getProjectConfig();
|
|
118
|
+
if (!projectConfig || !projectConfig.applicationUuid) {
|
|
119
|
+
logger.error('No application found in current directory');
|
|
120
|
+
logger.info('Run this command from a project directory (must have .saac/config.json)');
|
|
121
|
+
logger.newline();
|
|
122
|
+
logger.info('Or initialize with:');
|
|
123
|
+
logger.log(' saac init');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { applicationUuid } = projectConfig;
|
|
128
|
+
|
|
129
|
+
// Validate arguments
|
|
130
|
+
if (vars.length === 0) {
|
|
131
|
+
logger.error('No variables specified');
|
|
132
|
+
logger.newline();
|
|
133
|
+
logger.info('Usage:');
|
|
134
|
+
logger.log(' saac env set KEY=VALUE [KEY2=VALUE2 ...]');
|
|
135
|
+
logger.newline();
|
|
136
|
+
logger.info('Examples:');
|
|
137
|
+
logger.log(' saac env set NODE_ENV=production');
|
|
138
|
+
logger.log(' saac env set LOG_LEVEL=debug API_URL=https://api.example.com');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Parse KEY=VALUE pairs
|
|
143
|
+
const variables = {};
|
|
144
|
+
for (const arg of vars) {
|
|
145
|
+
const equalIndex = arg.indexOf('=');
|
|
146
|
+
if (equalIndex === -1) {
|
|
147
|
+
logger.error(`Invalid format: ${arg}`);
|
|
148
|
+
logger.info('Variables must be in KEY=VALUE format');
|
|
149
|
+
logger.newline();
|
|
150
|
+
logger.info('Example:');
|
|
151
|
+
logger.log(' saac env set NODE_ENV=production');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const key = arg.substring(0, equalIndex).trim();
|
|
156
|
+
const value = arg.substring(equalIndex + 1);
|
|
157
|
+
|
|
158
|
+
if (!key) {
|
|
159
|
+
logger.error(`Empty key in: ${arg}`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Validate key format (uppercase alphanumeric + underscore)
|
|
164
|
+
const keyRegex = /^[A-Z0-9_]+$/;
|
|
165
|
+
if (!keyRegex.test(key)) {
|
|
166
|
+
logger.error(`Invalid key format: ${key}`);
|
|
167
|
+
logger.info('Keys must be uppercase alphanumeric with underscores only');
|
|
168
|
+
logger.newline();
|
|
169
|
+
logger.info('Valid examples:');
|
|
170
|
+
logger.log(' NODE_ENV, LOG_LEVEL, DATABASE_URL, API_KEY');
|
|
171
|
+
logger.newline();
|
|
172
|
+
logger.info('Invalid examples:');
|
|
173
|
+
logger.log(' node-env (lowercase/hyphen), 123ABC (starts with number)');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
variables[key] = value;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
logger.section('Updating Environment Variables');
|
|
181
|
+
logger.newline();
|
|
182
|
+
|
|
183
|
+
// Show what will be updated
|
|
184
|
+
logger.info('Variables to set:');
|
|
185
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
186
|
+
const displayValue = maskSensitiveValue(key, value);
|
|
187
|
+
logger.field(` ${key}`, displayValue);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
logger.newline();
|
|
191
|
+
|
|
192
|
+
const spin = logger.spinner('Updating environment variables...').start();
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
await api.updateEnvironmentVariables(applicationUuid, variables);
|
|
196
|
+
|
|
197
|
+
spin.succeed('Environment variables updated successfully!');
|
|
198
|
+
|
|
199
|
+
logger.newline();
|
|
200
|
+
|
|
201
|
+
logger.success(`Set ${Object.keys(variables).length} variable(s)`);
|
|
202
|
+
|
|
203
|
+
logger.newline();
|
|
204
|
+
logger.warn('Changes require redeployment to take effect');
|
|
205
|
+
logger.info('Run:');
|
|
206
|
+
logger.log(' saac deploy');
|
|
207
|
+
|
|
208
|
+
} catch (error) {
|
|
209
|
+
spin.fail('Failed to update environment variables');
|
|
210
|
+
|
|
211
|
+
if (error.response?.status === 400) {
|
|
212
|
+
logger.newline();
|
|
213
|
+
const data = error.response.data;
|
|
214
|
+
if (data.error === 'QUOTA_EXCEEDED') {
|
|
215
|
+
logger.warn('Maximum number of environment variables exceeded');
|
|
216
|
+
logger.newline();
|
|
217
|
+
logger.info('Details:');
|
|
218
|
+
logger.field(' Limit', data.details?.limit || 50);
|
|
219
|
+
logger.field(' Current', data.details?.current || 'unknown');
|
|
220
|
+
logger.field(' Requested', data.details?.requested || vars.length);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
} catch (error) {
|
|
228
|
+
logger.error(error.response?.data?.message || error.message);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Mask sensitive values for display
|
|
235
|
+
* @param {string} key - Variable key
|
|
236
|
+
* @param {string} value - Variable value
|
|
237
|
+
* @returns {string} Masked value if sensitive, original otherwise
|
|
238
|
+
*/
|
|
239
|
+
function maskSensitiveValue(key, value) {
|
|
240
|
+
const sensitivePatterns = [
|
|
241
|
+
'PASSWORD', 'SECRET', 'KEY', 'TOKEN',
|
|
242
|
+
'DATABASE_URL', 'DB_URL', 'PRIVATE', 'AUTH'
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
const isSensitive = sensitivePatterns.some(pattern =>
|
|
246
|
+
key.toUpperCase().includes(pattern)
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (!isSensitive) {
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Mask sensitive values
|
|
254
|
+
if (value.length <= 8) {
|
|
255
|
+
return '***';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return value.substring(0, 4) + '***' + value.substring(value.length - 4);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = {
|
|
262
|
+
get,
|
|
263
|
+
list,
|
|
264
|
+
set,
|
|
4
265
|
};
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exec Command - Execute commands in remote container
|
|
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
|
+
/**
|
|
11
|
+
* Execute a command in the remote container
|
|
12
|
+
* @param {string} command - Command to execute
|
|
13
|
+
* @param {object} options - Command options
|
|
14
|
+
*/
|
|
15
|
+
async function exec(command, options = {}) {
|
|
16
|
+
try {
|
|
17
|
+
// Check authentication
|
|
18
|
+
if (!isAuthenticated()) {
|
|
19
|
+
logger.error('Not logged in. Run: saac login');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for project config
|
|
24
|
+
const projectConfig = getProjectConfig();
|
|
25
|
+
if (!projectConfig || !projectConfig.applicationUuid) {
|
|
26
|
+
logger.error('No application found in current directory');
|
|
27
|
+
logger.info('Run this command from a project directory (must have .saac/config.json)');
|
|
28
|
+
logger.newline();
|
|
29
|
+
logger.info('Or initialize with:');
|
|
30
|
+
logger.log(' saac init');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { applicationUuid, applicationName } = projectConfig;
|
|
35
|
+
|
|
36
|
+
// Build exec request
|
|
37
|
+
const execRequest = {
|
|
38
|
+
command,
|
|
39
|
+
workdir: options.workdir || '/app',
|
|
40
|
+
timeout: options.timeout || 30
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Validate timeout
|
|
44
|
+
if (execRequest.timeout > 300) {
|
|
45
|
+
logger.error('Timeout cannot exceed 300 seconds (5 minutes)');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
logger.newline();
|
|
50
|
+
logger.section(`Executing Command: ${applicationName}`);
|
|
51
|
+
logger.newline();
|
|
52
|
+
logger.field(' Command', command);
|
|
53
|
+
logger.field(' Working Directory', execRequest.workdir);
|
|
54
|
+
logger.field(' Timeout', `${execRequest.timeout}s`);
|
|
55
|
+
logger.newline();
|
|
56
|
+
|
|
57
|
+
const spin = logger.spinner('Executing command in container...').start();
|
|
58
|
+
|
|
59
|
+
let result;
|
|
60
|
+
try {
|
|
61
|
+
result = await api.executeCommand(applicationUuid, execRequest);
|
|
62
|
+
spin.succeed('Command executed');
|
|
63
|
+
} catch (error) {
|
|
64
|
+
spin.fail('Command execution failed');
|
|
65
|
+
|
|
66
|
+
if (error.response?.status === 400) {
|
|
67
|
+
const data = error.response.data;
|
|
68
|
+
logger.newline();
|
|
69
|
+
|
|
70
|
+
if (data.error === 'VALIDATION_ERROR') {
|
|
71
|
+
logger.error('Command validation failed');
|
|
72
|
+
logger.newline();
|
|
73
|
+
logger.warn(data.message);
|
|
74
|
+
|
|
75
|
+
if (data.message.includes('not in allowlist')) {
|
|
76
|
+
logger.newline();
|
|
77
|
+
logger.info('Allowed commands include:');
|
|
78
|
+
logger.log(' Node.js: npm, node, npx, yarn, pnpm');
|
|
79
|
+
logger.log(' Python: python, python3, pip, poetry');
|
|
80
|
+
logger.log(' Ruby: bundle, rake, rails');
|
|
81
|
+
logger.log(' Shell: sh, bash, echo, cat, ls, pwd');
|
|
82
|
+
logger.log(' Database: psql, mysql, mongosh');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else if (error.response?.status === 408) {
|
|
86
|
+
logger.newline();
|
|
87
|
+
logger.error('Command execution timed out');
|
|
88
|
+
logger.info(`Try increasing timeout with: --timeout ${execRequest.timeout * 2}`);
|
|
89
|
+
} else if (error.response?.status === 429) {
|
|
90
|
+
logger.newline();
|
|
91
|
+
logger.error('Rate limit exceeded');
|
|
92
|
+
logger.info('Limit: 30 exec commands per 5 minutes');
|
|
93
|
+
logger.info('Please wait a few minutes and try again');
|
|
94
|
+
} else if (error.response?.status === 503) {
|
|
95
|
+
logger.newline();
|
|
96
|
+
logger.error('Container is not running');
|
|
97
|
+
logger.info('Check application status with: saac status');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
logger.newline();
|
|
104
|
+
|
|
105
|
+
// Display execution results
|
|
106
|
+
logger.success(`✓ Execution ID: ${result.execution_id}`);
|
|
107
|
+
logger.newline();
|
|
108
|
+
|
|
109
|
+
logger.field('Exit Code', result.exit_code === 0
|
|
110
|
+
? logger.chalk.green(result.exit_code)
|
|
111
|
+
: logger.chalk.red(result.exit_code)
|
|
112
|
+
);
|
|
113
|
+
logger.field('Duration', `${result.duration_ms}ms`);
|
|
114
|
+
logger.field('Started', new Date(result.started_at).toLocaleString());
|
|
115
|
+
logger.field('Completed', new Date(result.completed_at).toLocaleString());
|
|
116
|
+
|
|
117
|
+
// Display stdout
|
|
118
|
+
if (result.stdout) {
|
|
119
|
+
logger.newline();
|
|
120
|
+
logger.info('Standard Output:');
|
|
121
|
+
logger.section('─'.repeat(60));
|
|
122
|
+
console.log(result.stdout.trim());
|
|
123
|
+
logger.section('─'.repeat(60));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Display stderr
|
|
127
|
+
if (result.stderr) {
|
|
128
|
+
logger.newline();
|
|
129
|
+
logger.warn('Standard Error:');
|
|
130
|
+
logger.section('─'.repeat(60));
|
|
131
|
+
console.error(result.stderr.trim());
|
|
132
|
+
logger.section('─'.repeat(60));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If no output
|
|
136
|
+
if (!result.stdout && !result.stderr) {
|
|
137
|
+
logger.newline();
|
|
138
|
+
logger.info('(No output)');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
logger.newline();
|
|
142
|
+
|
|
143
|
+
// Exit with same code as remote command
|
|
144
|
+
process.exit(result.exit_code);
|
|
145
|
+
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.error(error.response?.data?.message || error.message);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* View execution history
|
|
154
|
+
* @param {object} options - Command options
|
|
155
|
+
*/
|
|
156
|
+
async function history(options = {}) {
|
|
157
|
+
try {
|
|
158
|
+
// Check authentication
|
|
159
|
+
if (!isAuthenticated()) {
|
|
160
|
+
logger.error('Not logged in. Run: saac login');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check for project config
|
|
165
|
+
const projectConfig = getProjectConfig();
|
|
166
|
+
if (!projectConfig || !projectConfig.applicationUuid) {
|
|
167
|
+
logger.error('No application found in current directory');
|
|
168
|
+
logger.info('Run this command from a project directory (must have .saac/config.json)');
|
|
169
|
+
logger.newline();
|
|
170
|
+
logger.info('Or initialize with:');
|
|
171
|
+
logger.log(' saac init');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const { applicationUuid, applicationName } = projectConfig;
|
|
176
|
+
|
|
177
|
+
const params = {
|
|
178
|
+
limit: options.limit || 20,
|
|
179
|
+
offset: options.offset || 0
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Validate params
|
|
183
|
+
if (params.limit > 100) {
|
|
184
|
+
logger.error('Limit cannot exceed 100');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
logger.newline();
|
|
189
|
+
const spin = logger.spinner('Fetching execution history...').start();
|
|
190
|
+
|
|
191
|
+
let result;
|
|
192
|
+
try {
|
|
193
|
+
result = await api.getExecutionHistory(applicationUuid, params);
|
|
194
|
+
spin.succeed('Execution history retrieved');
|
|
195
|
+
} catch (error) {
|
|
196
|
+
spin.fail('Failed to fetch execution history');
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
logger.newline();
|
|
201
|
+
|
|
202
|
+
if (result.executions.length === 0) {
|
|
203
|
+
logger.warn('No execution history found');
|
|
204
|
+
logger.newline();
|
|
205
|
+
logger.info('Run a command first:');
|
|
206
|
+
logger.log(' saac exec "npm run db:migrate"');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
logger.section(`Execution History: ${applicationName}`);
|
|
211
|
+
logger.newline();
|
|
212
|
+
|
|
213
|
+
// Create table data
|
|
214
|
+
const data = [
|
|
215
|
+
['ID', 'Command', 'Status', 'Exit Code', 'Duration', 'Started'],
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
for (const execution of result.executions) {
|
|
219
|
+
const shortId = execution.id.substring(0, 8);
|
|
220
|
+
const command = execution.command.length > 40
|
|
221
|
+
? execution.command.substring(0, 37) + '...'
|
|
222
|
+
: execution.command;
|
|
223
|
+
|
|
224
|
+
let statusDisplay;
|
|
225
|
+
if (execution.status === 'completed') {
|
|
226
|
+
statusDisplay = execution.exit_code === 0
|
|
227
|
+
? logger.chalk.green('✓ completed')
|
|
228
|
+
: logger.chalk.red('✗ completed');
|
|
229
|
+
} else if (execution.status === 'failed') {
|
|
230
|
+
statusDisplay = logger.chalk.red('✗ failed');
|
|
231
|
+
} else if (execution.status === 'timeout') {
|
|
232
|
+
statusDisplay = logger.chalk.yellow('⏱ timeout');
|
|
233
|
+
} else if (execution.status === 'running') {
|
|
234
|
+
statusDisplay = logger.chalk.blue('▸ running');
|
|
235
|
+
} else {
|
|
236
|
+
statusDisplay = logger.chalk.gray('○ pending');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const exitCode = execution.exit_code !== null && execution.exit_code !== undefined
|
|
240
|
+
? (execution.exit_code === 0 ? logger.chalk.green(execution.exit_code) : logger.chalk.red(execution.exit_code))
|
|
241
|
+
: logger.chalk.gray('-');
|
|
242
|
+
|
|
243
|
+
const duration = execution.duration_seconds !== null && execution.duration_seconds !== undefined
|
|
244
|
+
? `${execution.duration_seconds}s`
|
|
245
|
+
: logger.chalk.gray('-');
|
|
246
|
+
|
|
247
|
+
const startedAt = execution.started_at
|
|
248
|
+
? new Date(execution.started_at).toLocaleString()
|
|
249
|
+
: logger.chalk.gray('Not started');
|
|
250
|
+
|
|
251
|
+
data.push([shortId, command, statusDisplay, exitCode, duration, startedAt]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(table(data));
|
|
255
|
+
|
|
256
|
+
logger.info(`Showing ${result.executions.length} of ${result.total} executions`);
|
|
257
|
+
|
|
258
|
+
if (result.offset + result.limit < result.total) {
|
|
259
|
+
logger.newline();
|
|
260
|
+
logger.info('View more:');
|
|
261
|
+
logger.log(` saac exec --history --offset ${result.offset + result.limit} --limit ${result.limit}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
logger.newline();
|
|
265
|
+
logger.info('View details of a specific execution:');
|
|
266
|
+
logger.log(' saac logs # View application logs');
|
|
267
|
+
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logger.error(error.response?.data?.message || error.message);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = {
|
|
275
|
+
exec,
|
|
276
|
+
history,
|
|
277
|
+
};
|