spindb 0.5.4 → 0.6.0
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 +46 -4
- package/cli/commands/backup.ts +269 -0
- package/cli/commands/config.ts +200 -67
- package/cli/commands/connect.ts +29 -9
- package/cli/commands/engines.ts +1 -9
- package/cli/commands/list.ts +41 -4
- package/cli/commands/menu.ts +289 -19
- package/cli/commands/restore.ts +3 -0
- package/cli/commands/self-update.ts +109 -0
- package/cli/commands/version.ts +55 -0
- package/cli/index.ts +84 -1
- package/cli/ui/prompts.ts +89 -1
- package/cli/ui/theme.ts +11 -0
- package/core/config-manager.ts +123 -37
- package/core/container-manager.ts +78 -2
- package/core/dependency-manager.ts +5 -0
- package/core/update-manager.ts +194 -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 +37 -0
- package/package.json +1 -1
- package/types/index.ts +26 -0
package/cli/commands/config.ts
CHANGED
|
@@ -1,17 +1,44 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import { existsSync } from 'fs'
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
configManager,
|
|
6
|
+
POSTGRESQL_TOOLS,
|
|
7
|
+
MYSQL_TOOLS,
|
|
8
|
+
ENHANCED_SHELLS,
|
|
9
|
+
ALL_TOOLS,
|
|
10
|
+
} from '../../core/config-manager'
|
|
11
|
+
import { updateManager } from '../../core/update-manager'
|
|
12
|
+
import { error, success, header, info } from '../ui/theme'
|
|
6
13
|
import { createSpinner } from '../ui/spinner'
|
|
7
14
|
import type { BinaryTool } from '../../types'
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Helper to display a tool's config
|
|
18
|
+
*/
|
|
19
|
+
function displayToolConfig(
|
|
20
|
+
tool: BinaryTool,
|
|
21
|
+
binaryConfig: { path: string; version?: string; source: string } | undefined,
|
|
22
|
+
): void {
|
|
23
|
+
if (binaryConfig) {
|
|
24
|
+
const sourceLabel =
|
|
25
|
+
binaryConfig.source === 'system'
|
|
26
|
+
? chalk.blue('system')
|
|
27
|
+
: binaryConfig.source === 'custom'
|
|
28
|
+
? chalk.yellow('custom')
|
|
29
|
+
: chalk.green('bundled')
|
|
30
|
+
const versionLabel = binaryConfig.version
|
|
31
|
+
? chalk.gray(` (v${binaryConfig.version})`)
|
|
32
|
+
: ''
|
|
33
|
+
console.log(
|
|
34
|
+
` ${chalk.cyan(tool.padEnd(15))} ${binaryConfig.path}${versionLabel} [${sourceLabel}]`,
|
|
35
|
+
)
|
|
36
|
+
} else {
|
|
37
|
+
console.log(
|
|
38
|
+
` ${chalk.cyan(tool.padEnd(15))} ${chalk.gray('not detected')}`,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
15
42
|
|
|
16
43
|
export const configCommand = new Command('config')
|
|
17
44
|
.description('Manage spindb configuration')
|
|
@@ -26,37 +53,38 @@ export const configCommand = new Command('config')
|
|
|
26
53
|
console.log(header('SpinDB Configuration'))
|
|
27
54
|
console.log()
|
|
28
55
|
|
|
29
|
-
|
|
30
|
-
console.log(chalk.
|
|
31
|
-
|
|
32
|
-
for (const tool of
|
|
33
|
-
|
|
34
|
-
if (binaryConfig) {
|
|
35
|
-
const sourceLabel =
|
|
36
|
-
binaryConfig.source === 'system'
|
|
37
|
-
? chalk.blue('system')
|
|
38
|
-
: binaryConfig.source === 'custom'
|
|
39
|
-
? chalk.yellow('custom')
|
|
40
|
-
: chalk.green('bundled')
|
|
41
|
-
const versionLabel = binaryConfig.version
|
|
42
|
-
? chalk.gray(` (v${binaryConfig.version})`)
|
|
43
|
-
: ''
|
|
44
|
-
console.log(
|
|
45
|
-
` ${chalk.cyan(tool.padEnd(15))} ${binaryConfig.path}${versionLabel} [${sourceLabel}]`,
|
|
46
|
-
)
|
|
47
|
-
} else {
|
|
48
|
-
console.log(
|
|
49
|
-
` ${chalk.cyan(tool.padEnd(15))} ${chalk.gray('not configured')}`,
|
|
50
|
-
)
|
|
51
|
-
}
|
|
56
|
+
// PostgreSQL tools
|
|
57
|
+
console.log(chalk.bold(' 🐘 PostgreSQL Tools:'))
|
|
58
|
+
console.log(chalk.gray(' ' + '─'.repeat(60)))
|
|
59
|
+
for (const tool of POSTGRESQL_TOOLS) {
|
|
60
|
+
displayToolConfig(tool, config.binaries[tool])
|
|
52
61
|
}
|
|
62
|
+
console.log()
|
|
53
63
|
|
|
64
|
+
// MySQL tools
|
|
65
|
+
console.log(chalk.bold(' 🐬 MySQL Tools:'))
|
|
66
|
+
console.log(chalk.gray(' ' + '─'.repeat(60)))
|
|
67
|
+
for (const tool of MYSQL_TOOLS) {
|
|
68
|
+
displayToolConfig(tool, config.binaries[tool])
|
|
69
|
+
}
|
|
70
|
+
console.log()
|
|
71
|
+
|
|
72
|
+
// Enhanced shells
|
|
73
|
+
console.log(chalk.bold(' ✨ Enhanced Shells (optional):'))
|
|
74
|
+
console.log(chalk.gray(' ' + '─'.repeat(60)))
|
|
75
|
+
for (const tool of ENHANCED_SHELLS) {
|
|
76
|
+
displayToolConfig(tool, config.binaries[tool])
|
|
77
|
+
}
|
|
54
78
|
console.log()
|
|
55
79
|
|
|
56
80
|
if (config.updatedAt) {
|
|
81
|
+
const isStale = await configManager.isStale()
|
|
82
|
+
const staleWarning = isStale
|
|
83
|
+
? chalk.yellow(' (stale - run config detect to refresh)')
|
|
84
|
+
: ''
|
|
57
85
|
console.log(
|
|
58
86
|
chalk.gray(
|
|
59
|
-
` Last updated: ${new Date(config.updatedAt).toLocaleString()}`,
|
|
87
|
+
` Last updated: ${new Date(config.updatedAt).toLocaleString()}${staleWarning}`,
|
|
60
88
|
),
|
|
61
89
|
)
|
|
62
90
|
console.log()
|
|
@@ -70,55 +98,108 @@ export const configCommand = new Command('config')
|
|
|
70
98
|
)
|
|
71
99
|
.addCommand(
|
|
72
100
|
new Command('detect')
|
|
73
|
-
.description('Auto-detect
|
|
101
|
+
.description('Auto-detect all database tools on your system')
|
|
74
102
|
.action(async () => {
|
|
75
103
|
try {
|
|
76
104
|
console.log()
|
|
77
|
-
console.log(header('Detecting
|
|
105
|
+
console.log(header('Detecting Database Tools'))
|
|
78
106
|
console.log()
|
|
79
107
|
|
|
80
|
-
const spinner = createSpinner(
|
|
81
|
-
'Searching for PostgreSQL client tools...',
|
|
82
|
-
)
|
|
108
|
+
const spinner = createSpinner('Searching for database tools...')
|
|
83
109
|
spinner.start()
|
|
84
110
|
|
|
85
111
|
// Clear existing configs to force re-detection
|
|
86
112
|
await configManager.clearAllBinaries()
|
|
87
113
|
|
|
88
|
-
const
|
|
114
|
+
const result = await configManager.initialize()
|
|
89
115
|
|
|
90
116
|
spinner.succeed('Detection complete')
|
|
91
117
|
console.log()
|
|
92
118
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
// Helper to display category results
|
|
120
|
+
async function displayCategory(
|
|
121
|
+
title: string,
|
|
122
|
+
icon: string,
|
|
123
|
+
found: BinaryTool[],
|
|
124
|
+
missing: BinaryTool[],
|
|
125
|
+
): Promise<void> {
|
|
126
|
+
console.log(chalk.bold(` ${icon} ${title}:`))
|
|
127
|
+
|
|
128
|
+
if (found.length > 0) {
|
|
129
|
+
for (const tool of found) {
|
|
130
|
+
const config = await configManager.getBinaryConfig(tool)
|
|
131
|
+
if (config) {
|
|
132
|
+
const versionLabel = config.version
|
|
133
|
+
? chalk.gray(` (v${config.version})`)
|
|
134
|
+
: ''
|
|
135
|
+
console.log(
|
|
136
|
+
` ${chalk.green('✓')} ${chalk.cyan(tool.padEnd(15))} ${config.path}${versionLabel}`,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (missing.length > 0) {
|
|
143
|
+
for (const tool of missing) {
|
|
101
144
|
console.log(
|
|
102
|
-
`
|
|
145
|
+
` ${chalk.gray('○')} ${chalk.gray(tool.padEnd(15))} not found`,
|
|
103
146
|
)
|
|
104
147
|
}
|
|
105
148
|
}
|
|
149
|
+
|
|
106
150
|
console.log()
|
|
107
151
|
}
|
|
108
152
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
153
|
+
await displayCategory(
|
|
154
|
+
'PostgreSQL Tools',
|
|
155
|
+
'🐘',
|
|
156
|
+
result.postgresql.found,
|
|
157
|
+
result.postgresql.missing,
|
|
158
|
+
)
|
|
159
|
+
await displayCategory(
|
|
160
|
+
'MySQL Tools',
|
|
161
|
+
'🐬',
|
|
162
|
+
result.mysql.found,
|
|
163
|
+
result.mysql.missing,
|
|
164
|
+
)
|
|
165
|
+
await displayCategory(
|
|
166
|
+
'Enhanced Shells (optional)',
|
|
167
|
+
'✨',
|
|
168
|
+
result.enhanced.found,
|
|
169
|
+
result.enhanced.missing,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// Show install hints for missing required tools
|
|
173
|
+
if (
|
|
174
|
+
result.postgresql.missing.length > 0 ||
|
|
175
|
+
result.mysql.missing.length > 0
|
|
176
|
+
) {
|
|
177
|
+
console.log(chalk.gray(' Install missing tools:'))
|
|
178
|
+
if (result.postgresql.missing.length > 0) {
|
|
179
|
+
console.log(
|
|
180
|
+
chalk.gray(' PostgreSQL: brew install postgresql@17'),
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
if (result.mysql.missing.length > 0) {
|
|
184
|
+
console.log(chalk.gray(' MySQL: brew install mysql'))
|
|
113
185
|
}
|
|
114
186
|
console.log()
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
)
|
|
121
|
-
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Show enhanced shell hints
|
|
190
|
+
if (result.enhanced.missing.length > 0) {
|
|
191
|
+
console.log(chalk.gray(' Optional enhanced shells:'))
|
|
192
|
+
if (result.enhanced.missing.includes('pgcli')) {
|
|
193
|
+
console.log(chalk.gray(' pgcli: brew install pgcli'))
|
|
194
|
+
}
|
|
195
|
+
if (result.enhanced.missing.includes('mycli')) {
|
|
196
|
+
console.log(chalk.gray(' mycli: brew install mycli'))
|
|
197
|
+
}
|
|
198
|
+
if (result.enhanced.missing.includes('usql')) {
|
|
199
|
+
console.log(
|
|
200
|
+
chalk.gray(' usql: brew tap xo/xo && brew install usql'),
|
|
201
|
+
)
|
|
202
|
+
}
|
|
122
203
|
console.log()
|
|
123
204
|
}
|
|
124
205
|
} catch (err) {
|
|
@@ -131,14 +212,14 @@ export const configCommand = new Command('config')
|
|
|
131
212
|
.addCommand(
|
|
132
213
|
new Command('set')
|
|
133
214
|
.description('Set a custom binary path')
|
|
134
|
-
.argument('<tool>',
|
|
215
|
+
.argument('<tool>', 'Tool name (psql, mysql, pgcli, etc.)')
|
|
135
216
|
.argument('<path>', 'Path to the binary')
|
|
136
217
|
.action(async (tool: string, path: string) => {
|
|
137
218
|
try {
|
|
138
219
|
// Validate tool name
|
|
139
|
-
if (!
|
|
220
|
+
if (!ALL_TOOLS.includes(tool as BinaryTool)) {
|
|
140
221
|
console.error(error(`Invalid tool: ${tool}`))
|
|
141
|
-
console.log(chalk.gray(` Valid tools: ${
|
|
222
|
+
console.log(chalk.gray(` Valid tools: ${ALL_TOOLS.join(', ')}`))
|
|
142
223
|
process.exit(1)
|
|
143
224
|
}
|
|
144
225
|
|
|
@@ -164,13 +245,13 @@ export const configCommand = new Command('config')
|
|
|
164
245
|
.addCommand(
|
|
165
246
|
new Command('unset')
|
|
166
247
|
.description('Remove a custom binary path')
|
|
167
|
-
.argument('<tool>',
|
|
248
|
+
.argument('<tool>', 'Tool name (psql, mysql, pgcli, etc.)')
|
|
168
249
|
.action(async (tool: string) => {
|
|
169
250
|
try {
|
|
170
251
|
// Validate tool name
|
|
171
|
-
if (!
|
|
252
|
+
if (!ALL_TOOLS.includes(tool as BinaryTool)) {
|
|
172
253
|
console.error(error(`Invalid tool: ${tool}`))
|
|
173
|
-
console.log(chalk.gray(` Valid tools: ${
|
|
254
|
+
console.log(chalk.gray(` Valid tools: ${ALL_TOOLS.join(', ')}`))
|
|
174
255
|
process.exit(1)
|
|
175
256
|
}
|
|
176
257
|
|
|
@@ -186,13 +267,13 @@ export const configCommand = new Command('config')
|
|
|
186
267
|
.addCommand(
|
|
187
268
|
new Command('path')
|
|
188
269
|
.description('Show the path for a specific tool')
|
|
189
|
-
.argument('<tool>',
|
|
270
|
+
.argument('<tool>', 'Tool name (psql, mysql, pgcli, etc.)')
|
|
190
271
|
.action(async (tool: string) => {
|
|
191
272
|
try {
|
|
192
273
|
// Validate tool name
|
|
193
|
-
if (!
|
|
274
|
+
if (!ALL_TOOLS.includes(tool as BinaryTool)) {
|
|
194
275
|
console.error(error(`Invalid tool: ${tool}`))
|
|
195
|
-
console.log(chalk.gray(` Valid tools: ${
|
|
276
|
+
console.log(chalk.gray(` Valid tools: ${ALL_TOOLS.join(', ')}`))
|
|
196
277
|
process.exit(1)
|
|
197
278
|
}
|
|
198
279
|
|
|
@@ -213,3 +294,55 @@ export const configCommand = new Command('config')
|
|
|
213
294
|
}
|
|
214
295
|
}),
|
|
215
296
|
)
|
|
297
|
+
.addCommand(
|
|
298
|
+
new Command('update-check')
|
|
299
|
+
.description('Enable or disable automatic update checks on startup')
|
|
300
|
+
.argument('[state]', 'on or off (omit to show current status)')
|
|
301
|
+
.action(async (state?: string) => {
|
|
302
|
+
try {
|
|
303
|
+
const cached = await updateManager.getCachedUpdateInfo()
|
|
304
|
+
|
|
305
|
+
if (!state) {
|
|
306
|
+
// Show current status
|
|
307
|
+
const status = cached.autoCheckEnabled
|
|
308
|
+
? chalk.green('enabled')
|
|
309
|
+
: chalk.yellow('disabled')
|
|
310
|
+
console.log()
|
|
311
|
+
console.log(` Update checks on startup: ${status}`)
|
|
312
|
+
console.log()
|
|
313
|
+
console.log(chalk.gray(' Usage:'))
|
|
314
|
+
console.log(
|
|
315
|
+
chalk.gray(' spindb config update-check on # Enable'),
|
|
316
|
+
)
|
|
317
|
+
console.log(
|
|
318
|
+
chalk.gray(' spindb config update-check off # Disable'),
|
|
319
|
+
)
|
|
320
|
+
console.log()
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (state !== 'on' && state !== 'off') {
|
|
325
|
+
console.error(error('Invalid state. Use "on" or "off"'))
|
|
326
|
+
process.exit(1)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const enabled = state === 'on'
|
|
330
|
+
await updateManager.setAutoCheckEnabled(enabled)
|
|
331
|
+
|
|
332
|
+
if (enabled) {
|
|
333
|
+
console.log(success('Update checks enabled on startup'))
|
|
334
|
+
} else {
|
|
335
|
+
console.log(info('Update checks disabled on startup'))
|
|
336
|
+
console.log(
|
|
337
|
+
chalk.gray(
|
|
338
|
+
' You can still manually check with: spindb version --check',
|
|
339
|
+
),
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
} catch (err) {
|
|
343
|
+
const e = err as Error
|
|
344
|
+
console.error(error(e.message))
|
|
345
|
+
process.exit(1)
|
|
346
|
+
}
|
|
347
|
+
}),
|
|
348
|
+
)
|
package/cli/commands/connect.ts
CHANGED
|
@@ -26,9 +26,15 @@ export const connectCommand = new Command('connect')
|
|
|
26
26
|
.option('-d, --database <name>', 'Database name')
|
|
27
27
|
.option('--tui', 'Use usql for enhanced shell experience')
|
|
28
28
|
.option('--install-tui', 'Install usql if not present, then connect')
|
|
29
|
-
.option(
|
|
29
|
+
.option(
|
|
30
|
+
'--pgcli',
|
|
31
|
+
'Use pgcli for enhanced PostgreSQL shell (dropdown auto-completion)',
|
|
32
|
+
)
|
|
30
33
|
.option('--install-pgcli', 'Install pgcli if not present, then connect')
|
|
31
|
-
.option(
|
|
34
|
+
.option(
|
|
35
|
+
'--mycli',
|
|
36
|
+
'Use mycli for enhanced MySQL shell (dropdown auto-completion)',
|
|
37
|
+
)
|
|
32
38
|
.option('--install-mycli', 'Install mycli if not present, then connect')
|
|
33
39
|
.action(
|
|
34
40
|
async (
|
|
@@ -164,7 +170,9 @@ export const connectCommand = new Command('connect')
|
|
|
164
170
|
const usePgcli = options.pgcli || options.installPgcli
|
|
165
171
|
if (usePgcli) {
|
|
166
172
|
if (engineName !== 'postgresql') {
|
|
167
|
-
console.error(
|
|
173
|
+
console.error(
|
|
174
|
+
error('pgcli is only available for PostgreSQL containers'),
|
|
175
|
+
)
|
|
168
176
|
console.log(chalk.gray('For MySQL, use: spindb connect --mycli'))
|
|
169
177
|
process.exit(1)
|
|
170
178
|
}
|
|
@@ -173,7 +181,9 @@ export const connectCommand = new Command('connect')
|
|
|
173
181
|
|
|
174
182
|
if (!pgcliInstalled) {
|
|
175
183
|
if (options.installPgcli) {
|
|
176
|
-
console.log(
|
|
184
|
+
console.log(
|
|
185
|
+
info('Installing pgcli for enhanced PostgreSQL shell...'),
|
|
186
|
+
)
|
|
177
187
|
const pm = await detectPackageManager()
|
|
178
188
|
if (pm) {
|
|
179
189
|
const result = await installPgcli(pm)
|
|
@@ -181,7 +191,9 @@ export const connectCommand = new Command('connect')
|
|
|
181
191
|
console.log(success('pgcli installed successfully!'))
|
|
182
192
|
console.log()
|
|
183
193
|
} else {
|
|
184
|
-
console.error(
|
|
194
|
+
console.error(
|
|
195
|
+
error(`Failed to install pgcli: ${result.error}`),
|
|
196
|
+
)
|
|
185
197
|
console.log()
|
|
186
198
|
console.log(chalk.gray('Manual installation:'))
|
|
187
199
|
for (const instruction of getPgcliManualInstructions()) {
|
|
@@ -201,7 +213,9 @@ export const connectCommand = new Command('connect')
|
|
|
201
213
|
} else {
|
|
202
214
|
console.error(error('pgcli is not installed'))
|
|
203
215
|
console.log()
|
|
204
|
-
console.log(
|
|
216
|
+
console.log(
|
|
217
|
+
chalk.gray('Install pgcli for enhanced PostgreSQL shell:'),
|
|
218
|
+
)
|
|
205
219
|
console.log(chalk.cyan(' spindb connect --install-pgcli'))
|
|
206
220
|
console.log()
|
|
207
221
|
console.log(chalk.gray('Or install manually:'))
|
|
@@ -218,7 +232,9 @@ export const connectCommand = new Command('connect')
|
|
|
218
232
|
if (useMycli) {
|
|
219
233
|
if (engineName !== 'mysql') {
|
|
220
234
|
console.error(error('mycli is only available for MySQL containers'))
|
|
221
|
-
console.log(
|
|
235
|
+
console.log(
|
|
236
|
+
chalk.gray('For PostgreSQL, use: spindb connect --pgcli'),
|
|
237
|
+
)
|
|
222
238
|
process.exit(1)
|
|
223
239
|
}
|
|
224
240
|
|
|
@@ -234,7 +250,9 @@ export const connectCommand = new Command('connect')
|
|
|
234
250
|
console.log(success('mycli installed successfully!'))
|
|
235
251
|
console.log()
|
|
236
252
|
} else {
|
|
237
|
-
console.error(
|
|
253
|
+
console.error(
|
|
254
|
+
error(`Failed to install mycli: ${result.error}`),
|
|
255
|
+
)
|
|
238
256
|
console.log()
|
|
239
257
|
console.log(chalk.gray('Manual installation:'))
|
|
240
258
|
for (const instruction of getMycliManualInstructions()) {
|
|
@@ -327,7 +345,9 @@ export const connectCommand = new Command('connect')
|
|
|
327
345
|
|
|
328
346
|
if (clientCmd === 'usql') {
|
|
329
347
|
console.log(chalk.gray(' Install usql:'))
|
|
330
|
-
console.log(
|
|
348
|
+
console.log(
|
|
349
|
+
chalk.cyan(' brew tap xo/xo && brew install xo/xo/usql'),
|
|
350
|
+
)
|
|
331
351
|
} else if (clientCmd === 'pgcli') {
|
|
332
352
|
console.log(chalk.gray(' Install pgcli:'))
|
|
333
353
|
console.log(chalk.cyan(' brew install pgcli'))
|
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
|
*/
|
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,12 +66,16 @@ 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')
|
|
@@ -51,12 +84,16 @@ export const listCommand = new Command('list')
|
|
|
51
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 = size !== null ? formatBytes(size) : chalk.gray('—')
|
|
89
|
+
|
|
54
90
|
console.log(
|
|
55
91
|
chalk.gray(' ') +
|
|
56
92
|
chalk.cyan(container.name.padEnd(20)) +
|
|
57
93
|
chalk.white(engineDisplay.padEnd(14)) +
|
|
58
94
|
chalk.yellow(container.version.padEnd(10)) +
|
|
59
95
|
chalk.green(String(container.port).padEnd(8)) +
|
|
96
|
+
chalk.magenta(sizeDisplay.padEnd(10)) +
|
|
60
97
|
statusDisplay,
|
|
61
98
|
)
|
|
62
99
|
}
|