spindb 0.5.3 → 0.5.5
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/README.md +78 -2
- package/cli/commands/backup.ts +263 -0
- package/cli/commands/config.ts +144 -66
- package/cli/commands/connect.ts +336 -111
- package/cli/commands/engines.ts +2 -10
- package/cli/commands/info.ts +3 -3
- package/cli/commands/list.ts +43 -5
- package/cli/commands/menu.ts +447 -33
- package/cli/commands/restore.ts +4 -1
- package/cli/index.ts +2 -0
- package/cli/ui/prompts.ts +99 -6
- package/cli/ui/theme.ts +12 -1
- package/config/os-dependencies.ts +92 -0
- package/core/binary-manager.ts +12 -19
- package/core/config-manager.ts +133 -37
- package/core/container-manager.ts +76 -2
- package/core/dependency-manager.ts +140 -0
- package/engines/base-engine.ts +20 -0
- package/engines/mysql/backup.ts +159 -0
- package/engines/mysql/index.ts +39 -0
- package/engines/mysql/restore.ts +16 -2
- package/engines/postgresql/backup.ts +93 -0
- package/engines/postgresql/index.ts +68 -1
- package/package.json +1 -1
- package/types/index.ts +20 -0
package/cli/commands/connect.ts
CHANGED
|
@@ -3,134 +3,359 @@ import { spawn } from 'child_process'
|
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
import { containerManager } from '../../core/container-manager'
|
|
5
5
|
import { processManager } from '../../core/process-manager'
|
|
6
|
+
import {
|
|
7
|
+
isUsqlInstalled,
|
|
8
|
+
isPgcliInstalled,
|
|
9
|
+
isMycliInstalled,
|
|
10
|
+
detectPackageManager,
|
|
11
|
+
installUsql,
|
|
12
|
+
installPgcli,
|
|
13
|
+
installMycli,
|
|
14
|
+
getUsqlManualInstructions,
|
|
15
|
+
getPgcliManualInstructions,
|
|
16
|
+
getMycliManualInstructions,
|
|
17
|
+
} from '../../core/dependency-manager'
|
|
6
18
|
import { getEngine } from '../../engines'
|
|
7
19
|
import { getEngineDefaults } from '../../config/defaults'
|
|
8
20
|
import { promptContainerSelect } from '../ui/prompts'
|
|
9
|
-
import { error, warning, info } from '../ui/theme'
|
|
21
|
+
import { error, warning, info, success } from '../ui/theme'
|
|
10
22
|
|
|
11
23
|
export const connectCommand = new Command('connect')
|
|
12
24
|
.description('Connect to a container with database client')
|
|
13
25
|
.argument('[name]', 'Container name')
|
|
14
26
|
.option('-d, --database <name>', 'Database name')
|
|
15
|
-
.
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
.option('--tui', 'Use usql for enhanced shell experience')
|
|
28
|
+
.option('--install-tui', 'Install usql if not present, then connect')
|
|
29
|
+
.option('--pgcli', 'Use pgcli for enhanced PostgreSQL shell (dropdown auto-completion)')
|
|
30
|
+
.option('--install-pgcli', 'Install pgcli if not present, then connect')
|
|
31
|
+
.option('--mycli', 'Use mycli for enhanced MySQL shell (dropdown auto-completion)')
|
|
32
|
+
.option('--install-mycli', 'Install mycli if not present, then connect')
|
|
33
|
+
.action(
|
|
34
|
+
async (
|
|
35
|
+
name: string | undefined,
|
|
36
|
+
options: {
|
|
37
|
+
database?: string
|
|
38
|
+
tui?: boolean
|
|
39
|
+
installTui?: boolean
|
|
40
|
+
pgcli?: boolean
|
|
41
|
+
installPgcli?: boolean
|
|
42
|
+
mycli?: boolean
|
|
43
|
+
installMycli?: boolean
|
|
44
|
+
},
|
|
45
|
+
) => {
|
|
46
|
+
try {
|
|
47
|
+
let containerName = name
|
|
18
48
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
49
|
+
// Interactive selection if no name provided
|
|
50
|
+
if (!containerName) {
|
|
51
|
+
const containers = await containerManager.list()
|
|
52
|
+
const running = containers.filter((c) => c.status === 'running')
|
|
23
53
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
if (running.length === 0) {
|
|
55
|
+
if (containers.length === 0) {
|
|
56
|
+
console.log(
|
|
57
|
+
warning('No containers found. Create one with: spindb create'),
|
|
58
|
+
)
|
|
59
|
+
} else {
|
|
60
|
+
console.log(
|
|
61
|
+
warning(
|
|
62
|
+
'No running containers. Start one first with: spindb start',
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
return
|
|
35
67
|
}
|
|
36
|
-
|
|
68
|
+
|
|
69
|
+
const selected = await promptContainerSelect(
|
|
70
|
+
running,
|
|
71
|
+
'Select container to connect to:',
|
|
72
|
+
)
|
|
73
|
+
if (!selected) return
|
|
74
|
+
containerName = selected
|
|
37
75
|
}
|
|
38
76
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
77
|
+
// Get container config
|
|
78
|
+
const config = await containerManager.getConfig(containerName)
|
|
79
|
+
if (!config) {
|
|
80
|
+
console.error(error(`Container "${containerName}" not found`))
|
|
81
|
+
process.exit(1)
|
|
82
|
+
}
|
|
46
83
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (!config) {
|
|
50
|
-
console.error(error(`Container "${containerName}" not found`))
|
|
51
|
-
process.exit(1)
|
|
52
|
-
}
|
|
84
|
+
const { engine: engineName } = config
|
|
85
|
+
const engineDefaults = getEngineDefaults(engineName)
|
|
53
86
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// Default database: container's database or superuser
|
|
58
|
-
const database =
|
|
59
|
-
options.database ?? config.database ?? engineDefaults.superuser
|
|
60
|
-
|
|
61
|
-
// Check if running
|
|
62
|
-
const running = await processManager.isRunning(containerName, {
|
|
63
|
-
engine: engineName,
|
|
64
|
-
})
|
|
65
|
-
if (!running) {
|
|
66
|
-
console.error(
|
|
67
|
-
error(`Container "${containerName}" is not running. Start it first.`),
|
|
68
|
-
)
|
|
69
|
-
process.exit(1)
|
|
70
|
-
}
|
|
87
|
+
// Default database: container's database or superuser
|
|
88
|
+
const database =
|
|
89
|
+
options.database ?? config.database ?? engineDefaults.superuser
|
|
71
90
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// MySQL: mysql -h 127.0.0.1 -P port -u root database
|
|
85
|
-
clientCmd = 'mysql'
|
|
86
|
-
clientArgs = [
|
|
87
|
-
'-h',
|
|
88
|
-
'127.0.0.1',
|
|
89
|
-
'-P',
|
|
90
|
-
String(config.port),
|
|
91
|
-
'-u',
|
|
92
|
-
engineDefaults.superuser,
|
|
93
|
-
database,
|
|
94
|
-
]
|
|
95
|
-
} else {
|
|
96
|
-
// PostgreSQL: psql connection_string
|
|
97
|
-
clientCmd = 'psql'
|
|
98
|
-
clientArgs = [connectionString]
|
|
99
|
-
}
|
|
91
|
+
// Check if running
|
|
92
|
+
const running = await processManager.isRunning(containerName, {
|
|
93
|
+
engine: engineName,
|
|
94
|
+
})
|
|
95
|
+
if (!running) {
|
|
96
|
+
console.error(
|
|
97
|
+
error(
|
|
98
|
+
`Container "${containerName}" is not running. Start it first.`,
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
process.exit(1)
|
|
102
|
+
}
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
104
|
+
// Get engine
|
|
105
|
+
const engine = getEngine(engineName)
|
|
106
|
+
const connectionString = engine.getConnectionString(config, database)
|
|
107
|
+
|
|
108
|
+
// Handle --tui and --install-tui flags (usql)
|
|
109
|
+
const useUsql = options.tui || options.installTui
|
|
110
|
+
if (useUsql) {
|
|
111
|
+
const usqlInstalled = await isUsqlInstalled()
|
|
112
|
+
|
|
113
|
+
if (!usqlInstalled) {
|
|
114
|
+
if (options.installTui) {
|
|
115
|
+
// Try to install usql
|
|
116
|
+
console.log(
|
|
117
|
+
info('Installing usql for enhanced shell experience...'),
|
|
118
|
+
)
|
|
119
|
+
const pm = await detectPackageManager()
|
|
120
|
+
if (pm) {
|
|
121
|
+
const result = await installUsql(pm)
|
|
122
|
+
if (result.success) {
|
|
123
|
+
console.log(success('usql installed successfully!'))
|
|
124
|
+
console.log()
|
|
125
|
+
} else {
|
|
126
|
+
console.error(
|
|
127
|
+
error(`Failed to install usql: ${result.error}`),
|
|
128
|
+
)
|
|
129
|
+
console.log()
|
|
130
|
+
console.log(chalk.gray('Manual installation:'))
|
|
131
|
+
for (const instruction of getUsqlManualInstructions()) {
|
|
132
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
133
|
+
}
|
|
134
|
+
process.exit(1)
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
console.error(error('No supported package manager found'))
|
|
138
|
+
console.log()
|
|
139
|
+
console.log(chalk.gray('Manual installation:'))
|
|
140
|
+
for (const instruction of getUsqlManualInstructions()) {
|
|
141
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
142
|
+
}
|
|
143
|
+
process.exit(1)
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// --tui flag but usql not installed
|
|
147
|
+
console.error(error('usql is not installed'))
|
|
148
|
+
console.log()
|
|
149
|
+
console.log(
|
|
150
|
+
chalk.gray('Install usql for enhanced shell experience:'),
|
|
151
|
+
)
|
|
152
|
+
console.log(chalk.cyan(' spindb connect --install-tui'))
|
|
153
|
+
console.log()
|
|
154
|
+
console.log(chalk.gray('Or install manually:'))
|
|
155
|
+
for (const instruction of getUsqlManualInstructions()) {
|
|
156
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
157
|
+
}
|
|
158
|
+
process.exit(1)
|
|
159
|
+
}
|
|
121
160
|
}
|
|
122
|
-
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Handle --pgcli and --install-pgcli flags
|
|
164
|
+
const usePgcli = options.pgcli || options.installPgcli
|
|
165
|
+
if (usePgcli) {
|
|
166
|
+
if (engineName !== 'postgresql') {
|
|
167
|
+
console.error(error('pgcli is only available for PostgreSQL containers'))
|
|
168
|
+
console.log(chalk.gray('For MySQL, use: spindb connect --mycli'))
|
|
169
|
+
process.exit(1)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const pgcliInstalled = await isPgcliInstalled()
|
|
173
|
+
|
|
174
|
+
if (!pgcliInstalled) {
|
|
175
|
+
if (options.installPgcli) {
|
|
176
|
+
console.log(info('Installing pgcli for enhanced PostgreSQL shell...'))
|
|
177
|
+
const pm = await detectPackageManager()
|
|
178
|
+
if (pm) {
|
|
179
|
+
const result = await installPgcli(pm)
|
|
180
|
+
if (result.success) {
|
|
181
|
+
console.log(success('pgcli installed successfully!'))
|
|
182
|
+
console.log()
|
|
183
|
+
} else {
|
|
184
|
+
console.error(error(`Failed to install pgcli: ${result.error}`))
|
|
185
|
+
console.log()
|
|
186
|
+
console.log(chalk.gray('Manual installation:'))
|
|
187
|
+
for (const instruction of getPgcliManualInstructions()) {
|
|
188
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
189
|
+
}
|
|
190
|
+
process.exit(1)
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
console.error(error('No supported package manager found'))
|
|
194
|
+
console.log()
|
|
195
|
+
console.log(chalk.gray('Manual installation:'))
|
|
196
|
+
for (const instruction of getPgcliManualInstructions()) {
|
|
197
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
198
|
+
}
|
|
199
|
+
process.exit(1)
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
console.error(error('pgcli is not installed'))
|
|
203
|
+
console.log()
|
|
204
|
+
console.log(chalk.gray('Install pgcli for enhanced PostgreSQL shell:'))
|
|
205
|
+
console.log(chalk.cyan(' spindb connect --install-pgcli'))
|
|
206
|
+
console.log()
|
|
207
|
+
console.log(chalk.gray('Or install manually:'))
|
|
208
|
+
for (const instruction of getPgcliManualInstructions()) {
|
|
209
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
210
|
+
}
|
|
211
|
+
process.exit(1)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Handle --mycli and --install-mycli flags
|
|
217
|
+
const useMycli = options.mycli || options.installMycli
|
|
218
|
+
if (useMycli) {
|
|
219
|
+
if (engineName !== 'mysql') {
|
|
220
|
+
console.error(error('mycli is only available for MySQL containers'))
|
|
221
|
+
console.log(chalk.gray('For PostgreSQL, use: spindb connect --pgcli'))
|
|
222
|
+
process.exit(1)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const mycliInstalled = await isMycliInstalled()
|
|
226
|
+
|
|
227
|
+
if (!mycliInstalled) {
|
|
228
|
+
if (options.installMycli) {
|
|
229
|
+
console.log(info('Installing mycli for enhanced MySQL shell...'))
|
|
230
|
+
const pm = await detectPackageManager()
|
|
231
|
+
if (pm) {
|
|
232
|
+
const result = await installMycli(pm)
|
|
233
|
+
if (result.success) {
|
|
234
|
+
console.log(success('mycli installed successfully!'))
|
|
235
|
+
console.log()
|
|
236
|
+
} else {
|
|
237
|
+
console.error(error(`Failed to install mycli: ${result.error}`))
|
|
238
|
+
console.log()
|
|
239
|
+
console.log(chalk.gray('Manual installation:'))
|
|
240
|
+
for (const instruction of getMycliManualInstructions()) {
|
|
241
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
242
|
+
}
|
|
243
|
+
process.exit(1)
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
console.error(error('No supported package manager found'))
|
|
247
|
+
console.log()
|
|
248
|
+
console.log(chalk.gray('Manual installation:'))
|
|
249
|
+
for (const instruction of getMycliManualInstructions()) {
|
|
250
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
251
|
+
}
|
|
252
|
+
process.exit(1)
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
console.error(error('mycli is not installed'))
|
|
256
|
+
console.log()
|
|
257
|
+
console.log(chalk.gray('Install mycli for enhanced MySQL shell:'))
|
|
258
|
+
console.log(chalk.cyan(' spindb connect --install-mycli'))
|
|
259
|
+
console.log()
|
|
260
|
+
console.log(chalk.gray('Or install manually:'))
|
|
261
|
+
for (const instruction of getMycliManualInstructions()) {
|
|
262
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
263
|
+
}
|
|
264
|
+
process.exit(1)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(info(`Connecting to ${containerName}:${database}...`))
|
|
270
|
+
console.log()
|
|
271
|
+
|
|
272
|
+
// Build client command based on engine and shell preference
|
|
273
|
+
let clientCmd: string
|
|
274
|
+
let clientArgs: string[]
|
|
275
|
+
|
|
276
|
+
if (usePgcli) {
|
|
277
|
+
// pgcli accepts connection strings
|
|
278
|
+
clientCmd = 'pgcli'
|
|
279
|
+
clientArgs = [connectionString]
|
|
280
|
+
} else if (useMycli) {
|
|
281
|
+
// mycli: mycli -h host -P port -u user database
|
|
282
|
+
clientCmd = 'mycli'
|
|
283
|
+
clientArgs = [
|
|
284
|
+
'-h',
|
|
285
|
+
'127.0.0.1',
|
|
286
|
+
'-P',
|
|
287
|
+
String(config.port),
|
|
288
|
+
'-u',
|
|
289
|
+
engineDefaults.superuser,
|
|
290
|
+
database,
|
|
291
|
+
]
|
|
292
|
+
} else if (useUsql) {
|
|
293
|
+
// usql accepts connection strings directly for both PostgreSQL and MySQL
|
|
294
|
+
clientCmd = 'usql'
|
|
295
|
+
clientArgs = [connectionString]
|
|
296
|
+
} else if (engineName === 'mysql') {
|
|
297
|
+
// MySQL: mysql -h 127.0.0.1 -P port -u root database
|
|
298
|
+
clientCmd = 'mysql'
|
|
299
|
+
clientArgs = [
|
|
300
|
+
'-h',
|
|
301
|
+
'127.0.0.1',
|
|
302
|
+
'-P',
|
|
303
|
+
String(config.port),
|
|
304
|
+
'-u',
|
|
305
|
+
engineDefaults.superuser,
|
|
306
|
+
database,
|
|
307
|
+
]
|
|
123
308
|
} else {
|
|
124
|
-
|
|
309
|
+
// PostgreSQL: psql connection_string
|
|
310
|
+
clientCmd = 'psql'
|
|
311
|
+
clientArgs = [connectionString]
|
|
125
312
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
313
|
+
|
|
314
|
+
const clientProcess = spawn(clientCmd, clientArgs, {
|
|
315
|
+
stdio: 'inherit',
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
clientProcess.on('error', (err: NodeJS.ErrnoException) => {
|
|
319
|
+
if (err.code === 'ENOENT') {
|
|
320
|
+
console.log(warning(`${clientCmd} not found on your system.`))
|
|
321
|
+
console.log()
|
|
322
|
+
console.log(
|
|
323
|
+
chalk.gray(' Install client tools or connect manually:'),
|
|
324
|
+
)
|
|
325
|
+
console.log(chalk.cyan(` ${connectionString}`))
|
|
326
|
+
console.log()
|
|
327
|
+
|
|
328
|
+
if (clientCmd === 'usql') {
|
|
329
|
+
console.log(chalk.gray(' Install usql:'))
|
|
330
|
+
console.log(chalk.cyan(' brew tap xo/xo && brew install xo/xo/usql'))
|
|
331
|
+
} else if (clientCmd === 'pgcli') {
|
|
332
|
+
console.log(chalk.gray(' Install pgcli:'))
|
|
333
|
+
console.log(chalk.cyan(' brew install pgcli'))
|
|
334
|
+
} else if (clientCmd === 'mycli') {
|
|
335
|
+
console.log(chalk.gray(' Install mycli:'))
|
|
336
|
+
console.log(chalk.cyan(' brew install mycli'))
|
|
337
|
+
} else if (engineName === 'mysql') {
|
|
338
|
+
console.log(chalk.gray(' On macOS with Homebrew:'))
|
|
339
|
+
console.log(chalk.cyan(' brew install mysql-client'))
|
|
340
|
+
} else {
|
|
341
|
+
console.log(chalk.gray(' On macOS with Homebrew:'))
|
|
342
|
+
console.log(
|
|
343
|
+
chalk.cyan(' brew install libpq && brew link --force libpq'),
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
console.log()
|
|
347
|
+
} else {
|
|
348
|
+
console.error(error(err.message))
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
await new Promise<void>((resolve) => {
|
|
353
|
+
clientProcess.on('close', () => resolve())
|
|
354
|
+
})
|
|
355
|
+
} catch (err) {
|
|
356
|
+
const e = err as Error
|
|
357
|
+
console.error(error(e.message))
|
|
358
|
+
process.exit(1)
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
)
|
package/cli/commands/engines.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { paths } from '../../config/paths'
|
|
|
10
10
|
import { containerManager } from '../../core/container-manager'
|
|
11
11
|
import { promptConfirm } from '../ui/prompts'
|
|
12
12
|
import { createSpinner } from '../ui/spinner'
|
|
13
|
-
import { error, warning, info } from '../ui/theme'
|
|
13
|
+
import { error, warning, info, formatBytes } from '../ui/theme'
|
|
14
14
|
import {
|
|
15
15
|
getMysqldPath,
|
|
16
16
|
getMysqlVersion,
|
|
@@ -185,14 +185,6 @@ function compareVersions(a: string, b: string): number {
|
|
|
185
185
|
return 0
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
function formatBytes(bytes: number): string {
|
|
189
|
-
if (bytes === 0) return '0 B'
|
|
190
|
-
const k = 1024
|
|
191
|
-
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
192
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
193
|
-
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`
|
|
194
|
-
}
|
|
195
|
-
|
|
196
188
|
/**
|
|
197
189
|
* Engine icons
|
|
198
190
|
*/
|
|
@@ -251,7 +243,7 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
251
243
|
|
|
252
244
|
// PostgreSQL rows
|
|
253
245
|
for (const engine of pgEngines) {
|
|
254
|
-
const icon = engineIcons[engine.engine] || '
|
|
246
|
+
const icon = engineIcons[engine.engine] || '▣'
|
|
255
247
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
256
248
|
|
|
257
249
|
console.log(
|
package/cli/commands/info.ts
CHANGED
|
@@ -65,7 +65,7 @@ async function displayContainerInfo(
|
|
|
65
65
|
return
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const icon = engineIcons[config.engine] || '
|
|
68
|
+
const icon = engineIcons[config.engine] || '▣'
|
|
69
69
|
const statusDisplay =
|
|
70
70
|
actualStatus === 'running'
|
|
71
71
|
? chalk.green('● running')
|
|
@@ -168,7 +168,7 @@ async function displayAllContainersInfo(
|
|
|
168
168
|
? chalk.green('● running')
|
|
169
169
|
: chalk.gray('○ stopped')
|
|
170
170
|
|
|
171
|
-
const icon = engineIcons[container.engine] || '
|
|
171
|
+
const icon = engineIcons[container.engine] || '▣'
|
|
172
172
|
const engineDisplay = `${icon} ${container.engine}`
|
|
173
173
|
|
|
174
174
|
console.log(
|
|
@@ -251,7 +251,7 @@ export const infoCommand = new Command('info')
|
|
|
251
251
|
choices: [
|
|
252
252
|
{ name: 'All containers', value: 'all' },
|
|
253
253
|
...containers.map((c) => ({
|
|
254
|
-
name: `${c.name} ${chalk.gray(`(${engineIcons[c.engine] || '
|
|
254
|
+
name: `${c.name} ${chalk.gray(`(${engineIcons[c.engine] || '▣'} ${c.engine})`)}`,
|
|
255
255
|
value: c.name,
|
|
256
256
|
})),
|
|
257
257
|
],
|
package/cli/commands/list.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import { containerManager } from '../../core/container-manager'
|
|
4
|
-
import {
|
|
4
|
+
import { getEngine } from '../../engines'
|
|
5
|
+
import { info, error, formatBytes } from '../ui/theme'
|
|
6
|
+
import type { ContainerConfig } from '../../types'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Engine icons for display
|
|
@@ -11,6 +13,23 @@ const engineIcons: Record<string, string> = {
|
|
|
11
13
|
mysql: '🐬',
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Get database size for a container (only if running)
|
|
18
|
+
*/
|
|
19
|
+
async function getContainerSize(
|
|
20
|
+
container: ContainerConfig,
|
|
21
|
+
): Promise<number | null> {
|
|
22
|
+
if (container.status !== 'running') {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const engine = getEngine(container.engine)
|
|
27
|
+
return await engine.getDatabaseSize(container)
|
|
28
|
+
} catch {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
export const listCommand = new Command('list')
|
|
15
34
|
.alias('ls')
|
|
16
35
|
.description('List all containers')
|
|
@@ -20,7 +39,14 @@ export const listCommand = new Command('list')
|
|
|
20
39
|
const containers = await containerManager.list()
|
|
21
40
|
|
|
22
41
|
if (options.json) {
|
|
23
|
-
|
|
42
|
+
// Include sizes in JSON output
|
|
43
|
+
const containersWithSize = await Promise.all(
|
|
44
|
+
containers.map(async (container) => ({
|
|
45
|
+
...container,
|
|
46
|
+
sizeBytes: await getContainerSize(container),
|
|
47
|
+
})),
|
|
48
|
+
)
|
|
49
|
+
console.log(JSON.stringify(containersWithSize, null, 2))
|
|
24
50
|
return
|
|
25
51
|
}
|
|
26
52
|
|
|
@@ -29,6 +55,9 @@ export const listCommand = new Command('list')
|
|
|
29
55
|
return
|
|
30
56
|
}
|
|
31
57
|
|
|
58
|
+
// Fetch sizes for running containers in parallel
|
|
59
|
+
const sizes = await Promise.all(containers.map(getContainerSize))
|
|
60
|
+
|
|
32
61
|
// Table header
|
|
33
62
|
console.log()
|
|
34
63
|
console.log(
|
|
@@ -37,26 +66,35 @@ export const listCommand = new Command('list')
|
|
|
37
66
|
chalk.bold.white('ENGINE'.padEnd(15)) +
|
|
38
67
|
chalk.bold.white('VERSION'.padEnd(10)) +
|
|
39
68
|
chalk.bold.white('PORT'.padEnd(8)) +
|
|
69
|
+
chalk.bold.white('SIZE'.padEnd(10)) +
|
|
40
70
|
chalk.bold.white('STATUS'),
|
|
41
71
|
)
|
|
42
|
-
console.log(chalk.gray(' ' + '─'.repeat(
|
|
72
|
+
console.log(chalk.gray(' ' + '─'.repeat(73)))
|
|
43
73
|
|
|
44
74
|
// Table rows
|
|
45
|
-
for (
|
|
75
|
+
for (let i = 0; i < containers.length; i++) {
|
|
76
|
+
const container = containers[i]
|
|
77
|
+
const size = sizes[i]
|
|
78
|
+
|
|
46
79
|
const statusDisplay =
|
|
47
80
|
container.status === 'running'
|
|
48
81
|
? chalk.green('● running')
|
|
49
82
|
: chalk.gray('○ stopped')
|
|
50
83
|
|
|
51
|
-
const engineIcon = engineIcons[container.engine] || '
|
|
84
|
+
const engineIcon = engineIcons[container.engine] || '▣'
|
|
52
85
|
const engineDisplay = `${engineIcon} ${container.engine}`
|
|
53
86
|
|
|
87
|
+
// Format size: show value if running, dash if stopped
|
|
88
|
+
const sizeDisplay =
|
|
89
|
+
size !== null ? formatBytes(size) : chalk.gray('—')
|
|
90
|
+
|
|
54
91
|
console.log(
|
|
55
92
|
chalk.gray(' ') +
|
|
56
93
|
chalk.cyan(container.name.padEnd(20)) +
|
|
57
94
|
chalk.white(engineDisplay.padEnd(14)) +
|
|
58
95
|
chalk.yellow(container.version.padEnd(10)) +
|
|
59
96
|
chalk.green(String(container.port).padEnd(8)) +
|
|
97
|
+
chalk.magenta(sizeDisplay.padEnd(10)) +
|
|
60
98
|
statusDisplay,
|
|
61
99
|
)
|
|
62
100
|
}
|