@startanaicompany/cli 1.6.0 → 1.9.1
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/settings.local.json +19 -1
- package/CLAUDE.md +143 -1300
- package/README.md +263 -2
- package/bin/saac.js +32 -2
- package/package.json +1 -1
- package/src/commands/db.js +322 -0
- package/src/commands/deploy.js +194 -30
- package/src/commands/exec.js +77 -16
- package/src/lib/api.js +66 -2
package/src/commands/deploy.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deploy command
|
|
2
|
+
* Deploy command with streaming support
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated, getUser } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const errorDisplay = require('../lib/errorDisplay');
|
|
9
9
|
|
|
@@ -30,57 +30,55 @@ async function deploy(options) {
|
|
|
30
30
|
logger.section(`Deploying ${applicationName}`);
|
|
31
31
|
logger.newline();
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Default to streaming mode (agents and users need visibility)
|
|
34
|
+
// Use fire-and-forget mode only if --no-stream is explicitly set
|
|
35
|
+
if (options.stream !== false) {
|
|
36
|
+
return await deployWithStreaming(applicationUuid, applicationName, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Fire-and-forget mode (only when --no-stream is used)
|
|
40
|
+
const spin = logger.spinner('Queueing deployment...').start();
|
|
34
41
|
|
|
35
42
|
try {
|
|
36
|
-
const
|
|
43
|
+
const deployOptions = {};
|
|
44
|
+
if (options.noCache) {
|
|
45
|
+
deployOptions.no_cache = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = await api.deployApplication(applicationUuid, deployOptions);
|
|
37
49
|
|
|
38
50
|
// Check if deployment failed
|
|
39
51
|
if (result.success === false) {
|
|
40
52
|
spin.fail('Deployment failed');
|
|
41
|
-
|
|
42
|
-
// Display detailed error information
|
|
43
53
|
errorDisplay.displayDeploymentError(result, logger);
|
|
44
|
-
|
|
45
|
-
// Handle timeout specifically
|
|
46
|
-
if (result.status === 'timeout') {
|
|
47
|
-
errorDisplay.displayTimeoutInstructions(logger);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
54
|
process.exit(1);
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
// SUCCESS: Deployment
|
|
54
|
-
spin.succeed('Deployment
|
|
57
|
+
// SUCCESS: Deployment queued
|
|
58
|
+
spin.succeed('Deployment queued');
|
|
55
59
|
|
|
56
60
|
logger.newline();
|
|
57
|
-
logger.success('
|
|
61
|
+
logger.success('Deployment has been queued!');
|
|
58
62
|
logger.newline();
|
|
59
63
|
logger.field('Application', applicationName);
|
|
60
|
-
logger.field('Status',
|
|
64
|
+
logger.field('Status', 'queued (daemon will build within 30 seconds)');
|
|
61
65
|
if (result.git_branch) {
|
|
62
66
|
logger.field('Branch', result.git_branch);
|
|
63
67
|
}
|
|
64
68
|
if (result.domain) {
|
|
65
69
|
logger.field('Domain', result.domain);
|
|
66
70
|
}
|
|
67
|
-
if (
|
|
68
|
-
logger.field('
|
|
71
|
+
if (options.noCache) {
|
|
72
|
+
logger.field('Build Mode', 'No cache (full rebuild)');
|
|
69
73
|
}
|
|
70
74
|
logger.newline();
|
|
71
75
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
logger.newline();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
logger.info('Useful commands:');
|
|
82
|
-
logger.log(` saac logs --follow View live deployment logs`);
|
|
83
|
-
logger.log(` saac status Check application status`);
|
|
76
|
+
logger.info('The daemon will pick up this deployment shortly and begin building.');
|
|
77
|
+
logger.newline();
|
|
78
|
+
logger.info('Monitor deployment progress:');
|
|
79
|
+
logger.log(` saac deploy Stream build logs in real-time (default)`);
|
|
80
|
+
logger.log(` saac logs --deployment View deployment logs after completion`);
|
|
81
|
+
logger.log(` saac status Check application status`);
|
|
84
82
|
|
|
85
83
|
} catch (error) {
|
|
86
84
|
spin.fail('Deployment request failed');
|
|
@@ -92,4 +90,170 @@ async function deploy(options) {
|
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Deploy with SSE streaming
|
|
95
|
+
*/
|
|
96
|
+
async function deployWithStreaming(applicationUuid, applicationName, options) {
|
|
97
|
+
const user = getUser();
|
|
98
|
+
const config = require('../lib/config');
|
|
99
|
+
const baseUrl = config.getApiUrl();
|
|
100
|
+
|
|
101
|
+
logger.info('Initiating deployment with build log streaming...');
|
|
102
|
+
logger.newline();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const headers = {
|
|
106
|
+
'Accept': 'text/event-stream',
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Add authentication header
|
|
111
|
+
if (process.env.SAAC_API_KEY) {
|
|
112
|
+
headers['X-API-Key'] = process.env.SAAC_API_KEY;
|
|
113
|
+
} else if (user.sessionToken) {
|
|
114
|
+
headers['X-Session-Token'] = user.sessionToken;
|
|
115
|
+
} else if (user.apiKey) {
|
|
116
|
+
headers['X-API-Key'] = user.apiKey;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const body = { stream: true };
|
|
120
|
+
if (options.noCache) {
|
|
121
|
+
body.no_cache = true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const url = `${baseUrl}/applications/${applicationUuid}/deploy`;
|
|
125
|
+
const response = await fetch(url, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers,
|
|
128
|
+
body: JSON.stringify(body),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
const errorText = await response.text();
|
|
133
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!response.body) {
|
|
137
|
+
throw new Error('Response body is null');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const reader = response.body.getReader();
|
|
141
|
+
const decoder = new TextDecoder();
|
|
142
|
+
let buffer = '';
|
|
143
|
+
let deploymentQueued = false;
|
|
144
|
+
|
|
145
|
+
// Handle Ctrl+C gracefully
|
|
146
|
+
const cleanup = () => {
|
|
147
|
+
reader.cancel();
|
|
148
|
+
logger.newline();
|
|
149
|
+
logger.info('Stream closed');
|
|
150
|
+
process.exit(0);
|
|
151
|
+
};
|
|
152
|
+
process.on('SIGINT', cleanup);
|
|
153
|
+
process.on('SIGTERM', cleanup);
|
|
154
|
+
|
|
155
|
+
while (true) {
|
|
156
|
+
const { done, value } = await reader.read();
|
|
157
|
+
|
|
158
|
+
if (done) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
buffer += decoder.decode(value, { stream: true });
|
|
163
|
+
const lines = buffer.split('\n');
|
|
164
|
+
buffer = lines.pop() || '';
|
|
165
|
+
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
// Skip empty lines and comments (keepalive)
|
|
168
|
+
if (!line.trim() || line.startsWith(':')) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Parse SSE data lines
|
|
173
|
+
if (line.startsWith('data: ')) {
|
|
174
|
+
try {
|
|
175
|
+
const data = JSON.parse(line.slice(6));
|
|
176
|
+
|
|
177
|
+
// Handle deploy_queued event
|
|
178
|
+
if (data.event === 'deploy_queued') {
|
|
179
|
+
logger.success('✓ Deployment queued');
|
|
180
|
+
logger.newline();
|
|
181
|
+
logger.field('Application', applicationName);
|
|
182
|
+
logger.field('Branch', data.git_branch || 'master');
|
|
183
|
+
if (data.domain) {
|
|
184
|
+
logger.field('Domain', data.domain);
|
|
185
|
+
}
|
|
186
|
+
if (options.noCache) {
|
|
187
|
+
logger.field('Build Mode', 'No cache (full rebuild)');
|
|
188
|
+
}
|
|
189
|
+
logger.newline();
|
|
190
|
+
logger.info('Waiting for daemon to start build...');
|
|
191
|
+
logger.newline();
|
|
192
|
+
deploymentQueued = true;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Handle deploy_finished event
|
|
197
|
+
if (data.event === 'deploy_finished') {
|
|
198
|
+
logger.newline();
|
|
199
|
+
logger.log('─'.repeat(60));
|
|
200
|
+
logger.newline();
|
|
201
|
+
|
|
202
|
+
if (data.status === 'running') {
|
|
203
|
+
logger.success('✓ Deployment completed successfully!');
|
|
204
|
+
} else if (data.status === 'failed') {
|
|
205
|
+
logger.error('✗ Deployment failed');
|
|
206
|
+
} else {
|
|
207
|
+
logger.info(`Deployment status: ${data.status}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
logger.newline();
|
|
211
|
+
logger.field('Final Status', data.status);
|
|
212
|
+
if (data.deployment_uuid) {
|
|
213
|
+
logger.field('Deployment UUID', data.deployment_uuid);
|
|
214
|
+
}
|
|
215
|
+
logger.newline();
|
|
216
|
+
|
|
217
|
+
if (data.status === 'running') {
|
|
218
|
+
logger.info('Your application is now running!');
|
|
219
|
+
logger.newline();
|
|
220
|
+
logger.info('Next steps:');
|
|
221
|
+
logger.log(` saac status Check application status`);
|
|
222
|
+
logger.log(` saac logs --follow View live application logs`);
|
|
223
|
+
} else if (data.status === 'failed') {
|
|
224
|
+
logger.info('View full deployment logs:');
|
|
225
|
+
logger.log(` saac logs --deployment View complete build logs`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Clean exit
|
|
229
|
+
process.removeListener('SIGINT', cleanup);
|
|
230
|
+
process.removeListener('SIGTERM', cleanup);
|
|
231
|
+
process.exit(data.status === 'running' ? 0 : 1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Handle build log messages
|
|
235
|
+
if (data.type === 'build' && data.message) {
|
|
236
|
+
const timestamp = new Date(data.timestamp).toLocaleTimeString();
|
|
237
|
+
const service = logger.chalk.cyan(`[${data.service}]`);
|
|
238
|
+
console.log(`${logger.chalk.gray(timestamp)} ${service} ${data.message}`);
|
|
239
|
+
}
|
|
240
|
+
} catch (parseError) {
|
|
241
|
+
logger.warn(`Failed to parse event: ${line}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Stream ended without deploy_finished event
|
|
248
|
+
logger.newline();
|
|
249
|
+
logger.warn('Build stream ended unexpectedly');
|
|
250
|
+
logger.info('Check deployment status with: saac status');
|
|
251
|
+
|
|
252
|
+
} catch (error) {
|
|
253
|
+
logger.error('Failed to stream deployment');
|
|
254
|
+
logger.error(error.message);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
95
259
|
module.exports = deploy;
|
package/src/commands/exec.js
CHANGED
|
@@ -7,6 +7,45 @@ const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const { table } = require('table');
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Poll for command result with timeout
|
|
12
|
+
*/
|
|
13
|
+
async function pollForResult(applicationUuid, commandId, maxWaitSeconds = 120) {
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
const pollInterval = 1000; // 1 second
|
|
16
|
+
|
|
17
|
+
while (true) {
|
|
18
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
19
|
+
|
|
20
|
+
if (elapsed > maxWaitSeconds) {
|
|
21
|
+
throw new Error(`Command timed out after ${maxWaitSeconds} seconds`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const result = await api.getDbCommandResult(applicationUuid, 'exec', commandId);
|
|
26
|
+
|
|
27
|
+
if (result.status === 'completed') {
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (result.status === 'failed') {
|
|
32
|
+
const errorMsg = result.result?.error || result.error || 'Command failed';
|
|
33
|
+
throw new Error(errorMsg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Still pending, wait and retry
|
|
37
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// If error is not a 404 (command not found yet), rethrow
|
|
40
|
+
if (error.response?.status !== 404) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
// 404 means command not processed yet, keep polling
|
|
44
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
10
49
|
/**
|
|
11
50
|
* Execute a command in the remote container
|
|
12
51
|
* @param {string} command - Command to execute
|
|
@@ -39,7 +78,7 @@ async function exec(command, options = {}) {
|
|
|
39
78
|
const execRequest = {
|
|
40
79
|
command,
|
|
41
80
|
workdir: options.workdir || '/app',
|
|
42
|
-
timeout: options.timeout || 30
|
|
81
|
+
timeout: parseInt(options.timeout) || 30
|
|
43
82
|
};
|
|
44
83
|
|
|
45
84
|
// Validate timeout
|
|
@@ -56,11 +95,20 @@ async function exec(command, options = {}) {
|
|
|
56
95
|
logger.field(' Timeout', `${execRequest.timeout}s`);
|
|
57
96
|
logger.newline();
|
|
58
97
|
|
|
59
|
-
const spin = logger.spinner('
|
|
98
|
+
const spin = logger.spinner('Queueing command...').start();
|
|
60
99
|
|
|
100
|
+
let response;
|
|
61
101
|
let result;
|
|
62
102
|
try {
|
|
63
|
-
|
|
103
|
+
// Queue the command
|
|
104
|
+
response = await api.executeCommand(applicationUuid, execRequest);
|
|
105
|
+
const commandId = response.command_id;
|
|
106
|
+
|
|
107
|
+
spin.text = 'Waiting for daemon to execute command...';
|
|
108
|
+
|
|
109
|
+
// Poll for result with timeout buffer
|
|
110
|
+
result = await pollForResult(applicationUuid, commandId, execRequest.timeout + 30);
|
|
111
|
+
|
|
64
112
|
spin.succeed('Command executed');
|
|
65
113
|
} catch (error) {
|
|
66
114
|
spin.fail('Command execution failed');
|
|
@@ -105,37 +153,50 @@ async function exec(command, options = {}) {
|
|
|
105
153
|
logger.newline();
|
|
106
154
|
|
|
107
155
|
// Display execution results
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
const execResult = result.result || {};
|
|
157
|
+
|
|
158
|
+
// Calculate duration
|
|
159
|
+
let duration = 'N/A';
|
|
160
|
+
if (result.created_at && result.completed_at) {
|
|
161
|
+
const start = new Date(result.created_at);
|
|
162
|
+
const end = new Date(result.completed_at);
|
|
163
|
+
duration = `${end - start}ms`;
|
|
164
|
+
}
|
|
110
165
|
|
|
111
|
-
logger.field('Exit Code',
|
|
112
|
-
?
|
|
113
|
-
|
|
166
|
+
logger.field('Exit Code', execResult.exit_code !== undefined
|
|
167
|
+
? (execResult.exit_code === 0
|
|
168
|
+
? logger.chalk.green(execResult.exit_code)
|
|
169
|
+
: logger.chalk.red(execResult.exit_code))
|
|
170
|
+
: 'N/A'
|
|
114
171
|
);
|
|
115
|
-
logger.field('Duration',
|
|
116
|
-
|
|
117
|
-
|
|
172
|
+
logger.field('Duration', duration);
|
|
173
|
+
if (result.created_at) {
|
|
174
|
+
logger.field('Started', new Date(result.created_at).toLocaleString());
|
|
175
|
+
}
|
|
176
|
+
if (result.completed_at) {
|
|
177
|
+
logger.field('Completed', new Date(result.completed_at).toLocaleString());
|
|
178
|
+
}
|
|
118
179
|
|
|
119
180
|
// Display stdout
|
|
120
|
-
if (
|
|
181
|
+
if (execResult.stdout) {
|
|
121
182
|
logger.newline();
|
|
122
183
|
logger.info('Standard Output:');
|
|
123
184
|
logger.section('─'.repeat(60));
|
|
124
|
-
console.log(
|
|
185
|
+
console.log(execResult.stdout.trim());
|
|
125
186
|
logger.section('─'.repeat(60));
|
|
126
187
|
}
|
|
127
188
|
|
|
128
189
|
// Display stderr
|
|
129
|
-
if (
|
|
190
|
+
if (execResult.stderr) {
|
|
130
191
|
logger.newline();
|
|
131
192
|
logger.warn('Standard Error:');
|
|
132
193
|
logger.section('─'.repeat(60));
|
|
133
|
-
console.error(
|
|
194
|
+
console.error(execResult.stderr.trim());
|
|
134
195
|
logger.section('─'.repeat(60));
|
|
135
196
|
}
|
|
136
197
|
|
|
137
198
|
// If no output
|
|
138
|
-
if (!
|
|
199
|
+
if (!execResult.stdout && !execResult.stderr) {
|
|
139
200
|
logger.newline();
|
|
140
201
|
logger.info('(No output)');
|
|
141
202
|
}
|
package/src/lib/api.js
CHANGED
|
@@ -120,10 +120,10 @@ async function getApplication(uuid) {
|
|
|
120
120
|
* Deploy application
|
|
121
121
|
* Note: This waits for deployment to complete (up to 5 minutes)
|
|
122
122
|
*/
|
|
123
|
-
async function deployApplication(uuid) {
|
|
123
|
+
async function deployApplication(uuid, options = {}) {
|
|
124
124
|
// Use 5-minute timeout for deployment waiting
|
|
125
125
|
const client = createClient(300000); // 5 minutes
|
|
126
|
-
const response = await client.post(`/applications/${uuid}/deploy
|
|
126
|
+
const response = await client.post(`/applications/${uuid}/deploy`, options);
|
|
127
127
|
return response.data;
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -319,6 +319,65 @@ async function listGitRepositories(gitHost, options = {}) {
|
|
|
319
319
|
return response.data;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
/**
|
|
323
|
+
* List database containers for an application
|
|
324
|
+
* @param {string} uuid - Application UUID
|
|
325
|
+
* @returns {Promise<object>} - { command_id, status }
|
|
326
|
+
*/
|
|
327
|
+
async function listDbContainers(uuid) {
|
|
328
|
+
const client = createClient();
|
|
329
|
+
const response = await client.get(`/applications/${uuid}/db/containers`);
|
|
330
|
+
return response.data;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Execute SQL query on application database
|
|
335
|
+
* @param {string} uuid - Application UUID
|
|
336
|
+
* @param {object} queryData - { query, db_name?, allow_writes? }
|
|
337
|
+
* @returns {Promise<object>} - { command_id, status }
|
|
338
|
+
*/
|
|
339
|
+
async function executeSql(uuid, queryData) {
|
|
340
|
+
const client = createClient();
|
|
341
|
+
const response = await client.post(`/applications/${uuid}/db/sql`, queryData);
|
|
342
|
+
return response.data;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get result of a database command (universal endpoint for all command types)
|
|
347
|
+
* @param {string} uuid - Application UUID
|
|
348
|
+
* @param {string} commandType - 'sql', 'redis', 'containers' (not used, kept for API compatibility)
|
|
349
|
+
* @param {string} commandId - Command ID
|
|
350
|
+
* @returns {Promise<object>} - { status, result, command_type, created_at, completed_at }
|
|
351
|
+
*/
|
|
352
|
+
async function getDbCommandResult(uuid, commandType, commandId) {
|
|
353
|
+
const client = createClient();
|
|
354
|
+
const response = await client.get(`/applications/${uuid}/db/result/${commandId}`);
|
|
355
|
+
return response.data;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Execute Redis command on application database
|
|
360
|
+
* @param {string} uuid - Application UUID
|
|
361
|
+
* @param {object} commandData - { command }
|
|
362
|
+
* @returns {Promise<object>} - { command_id, status }
|
|
363
|
+
*/
|
|
364
|
+
async function executeRedis(uuid, commandData) {
|
|
365
|
+
const client = createClient();
|
|
366
|
+
const response = await client.post(`/applications/${uuid}/db/redis`, commandData);
|
|
367
|
+
return response.data;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get database connection information
|
|
372
|
+
* @param {string} uuid - Application UUID
|
|
373
|
+
* @returns {Promise<object>} - { postgres: {...}, redis: {...} }
|
|
374
|
+
*/
|
|
375
|
+
async function getDbInfo(uuid) {
|
|
376
|
+
const client = createClient();
|
|
377
|
+
const response = await client.get(`/applications/${uuid}/db/info`);
|
|
378
|
+
return response.data;
|
|
379
|
+
}
|
|
380
|
+
|
|
322
381
|
module.exports = {
|
|
323
382
|
createClient,
|
|
324
383
|
login,
|
|
@@ -345,4 +404,9 @@ module.exports = {
|
|
|
345
404
|
executeCommand,
|
|
346
405
|
getExecutionHistory,
|
|
347
406
|
listGitRepositories,
|
|
407
|
+
listDbContainers,
|
|
408
|
+
executeSql,
|
|
409
|
+
getDbCommandResult,
|
|
410
|
+
executeRedis,
|
|
411
|
+
getDbInfo,
|
|
348
412
|
};
|