spindb 0.27.1 â 0.27.3
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/cli/commands/backups.ts +6 -17
- package/cli/commands/config.ts +5 -4
- package/cli/commands/engines.ts +25 -42
- package/cli/commands/info.ts +4 -4
- package/cli/commands/list.ts +3 -9
- package/cli/commands/menu/backup-handlers.ts +3 -3
- package/cli/commands/menu/container-handlers.ts +64 -80
- package/cli/commands/menu/engine-handlers.ts +22 -16
- package/cli/commands/menu/index.ts +12 -12
- package/cli/commands/menu/shell-handlers.ts +9 -1
- package/cli/constants.ts +39 -39
- package/cli/ui/prompts.ts +112 -1
- package/cli/ui/theme.ts +0 -2
- package/engines/mariadb/restore.ts +133 -57
- package/engines/mysql/restore.ts +160 -60
- package/package.json +3 -1
package/cli/commands/backups.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { join, extname } from 'path'
|
|
|
11
11
|
import { homedir } from 'os'
|
|
12
12
|
import chalk from 'chalk'
|
|
13
13
|
import { formatBytes } from '../ui/theme'
|
|
14
|
+
import { getEngineIcon } from '../constants'
|
|
14
15
|
|
|
15
16
|
type BackupInfo = {
|
|
16
17
|
filename: string
|
|
@@ -132,22 +133,10 @@ function formatRelativeTime(date: Date): string {
|
|
|
132
133
|
return date.toLocaleDateString()
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
// Get engine icon
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return 'ð'
|
|
140
|
-
case 'mysql':
|
|
141
|
-
return 'ðŽ'
|
|
142
|
-
case 'sqlite':
|
|
143
|
-
return 'ðïļ'
|
|
144
|
-
case 'mongodb':
|
|
145
|
-
return 'ð'
|
|
146
|
-
case 'redis':
|
|
147
|
-
return 'ðī'
|
|
148
|
-
default:
|
|
149
|
-
return 'ðĶ'
|
|
150
|
-
}
|
|
136
|
+
// Get engine icon - wraps the shared function with fallback for null/unknown engines
|
|
137
|
+
function getBackupEngineIcon(engine: string | null): string {
|
|
138
|
+
if (!engine) return 'ðĶ '
|
|
139
|
+
return getEngineIcon(engine)
|
|
151
140
|
}
|
|
152
141
|
|
|
153
142
|
export const backupsCommand = new Command('backups')
|
|
@@ -230,7 +219,7 @@ export const backupsCommand = new Command('backups')
|
|
|
230
219
|
)
|
|
231
220
|
|
|
232
221
|
for (const backup of limitedBackups) {
|
|
233
|
-
const icon =
|
|
222
|
+
const icon = getBackupEngineIcon(backup.engine)
|
|
234
223
|
const filename =
|
|
235
224
|
backup.filename.length > maxFilename
|
|
236
225
|
? backup.filename.slice(0, maxFilename - 3) + '...'
|
package/cli/commands/config.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '../../core/config-manager'
|
|
11
11
|
import { updateManager } from '../../core/update-manager'
|
|
12
12
|
import { uiError, uiSuccess, header, uiInfo } from '../ui/theme'
|
|
13
|
+
import { getEngineIcon } from '../constants'
|
|
13
14
|
import { createSpinner } from '../ui/spinner'
|
|
14
15
|
import type { BinaryTool } from '../../types'
|
|
15
16
|
|
|
@@ -58,7 +59,7 @@ export const configCommand = new Command('config')
|
|
|
58
59
|
console.log()
|
|
59
60
|
|
|
60
61
|
// PostgreSQL tools
|
|
61
|
-
console.log(chalk.bold('
|
|
62
|
+
console.log(chalk.bold(` ${getEngineIcon('postgresql')}PostgreSQL Tools:`))
|
|
62
63
|
console.log(chalk.gray(' ' + 'â'.repeat(60)))
|
|
63
64
|
for (const tool of POSTGRESQL_TOOLS) {
|
|
64
65
|
displayToolConfig(tool, config.binaries[tool])
|
|
@@ -66,7 +67,7 @@ export const configCommand = new Command('config')
|
|
|
66
67
|
console.log()
|
|
67
68
|
|
|
68
69
|
// MySQL tools
|
|
69
|
-
console.log(chalk.bold('
|
|
70
|
+
console.log(chalk.bold(` ${getEngineIcon('mysql')}MySQL Tools:`))
|
|
70
71
|
console.log(chalk.gray(' ' + 'â'.repeat(60)))
|
|
71
72
|
for (const tool of MYSQL_TOOLS) {
|
|
72
73
|
displayToolConfig(tool, config.binaries[tool])
|
|
@@ -156,13 +157,13 @@ export const configCommand = new Command('config')
|
|
|
156
157
|
|
|
157
158
|
await displayCategory(
|
|
158
159
|
'PostgreSQL Tools',
|
|
159
|
-
'
|
|
160
|
+
getEngineIcon('postgresql'),
|
|
160
161
|
result.postgresql.found,
|
|
161
162
|
result.postgresql.missing,
|
|
162
163
|
)
|
|
163
164
|
await displayCategory(
|
|
164
165
|
'MySQL Tools',
|
|
165
|
-
'
|
|
166
|
+
getEngineIcon('mysql'),
|
|
166
167
|
result.mysql.found,
|
|
167
168
|
result.mysql.missing,
|
|
168
169
|
)
|
package/cli/commands/engines.ts
CHANGED
|
@@ -29,7 +29,7 @@ import type { BinaryTool } from '../../types'
|
|
|
29
29
|
import { promptConfirm } from '../ui/prompts'
|
|
30
30
|
import { createSpinner } from '../ui/spinner'
|
|
31
31
|
import { uiError, uiWarning, uiInfo, uiSuccess, formatBytes } from '../ui/theme'
|
|
32
|
-
import { getEngineIcon
|
|
32
|
+
import { getEngineIcon } from '../constants'
|
|
33
33
|
import {
|
|
34
34
|
getInstalledEngines,
|
|
35
35
|
getInstalledPostgresEngines,
|
|
@@ -70,13 +70,6 @@ import {
|
|
|
70
70
|
normalizeDocumentDBVersion,
|
|
71
71
|
} from '../../engines/ferretdb/version-maps'
|
|
72
72
|
|
|
73
|
-
// Pad string to width, accounting for emoji taking 2 display columns
|
|
74
|
-
function padWithEmoji(str: string, width: number): string {
|
|
75
|
-
// Count emojis using Extended_Pictographic (excludes digits/symbols that \p{Emoji} matches)
|
|
76
|
-
const emojiCount = (str.match(/\p{Extended_Pictographic}/gu) || []).length
|
|
77
|
-
return str.padEnd(width + emojiCount)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
73
|
// Display manual installation instructions for missing dependencies
|
|
81
74
|
function displayManualInstallInstructions(
|
|
82
75
|
missingDeps: Array<{ dependency: { name: string }; installed: boolean }>,
|
|
@@ -482,13 +475,13 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
482
475
|
|
|
483
476
|
// PostgreSQL rows
|
|
484
477
|
for (const engine of pgEngines) {
|
|
485
|
-
const icon = getEngineIcon(engine.engine)
|
|
486
478
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
487
|
-
|
|
479
|
+
// getEngineIcon() includes trailing space for consistent alignment
|
|
480
|
+
const engineDisplay = `${getEngineIcon(engine.engine)}${engine.engine}`
|
|
488
481
|
|
|
489
482
|
console.log(
|
|
490
483
|
chalk.gray(' ') +
|
|
491
|
-
chalk.cyan(
|
|
484
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
492
485
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
493
486
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
494
487
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -497,13 +490,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
497
490
|
|
|
498
491
|
// MySQL rows
|
|
499
492
|
for (const mysqlEngine of mysqlEngines) {
|
|
500
|
-
const icon = ENGINE_ICONS.mysql
|
|
501
493
|
const platformInfo = `${mysqlEngine.platform}-${mysqlEngine.arch}`
|
|
502
|
-
const engineDisplay = `${
|
|
494
|
+
const engineDisplay = `${getEngineIcon('mysql')}mysql`
|
|
503
495
|
|
|
504
496
|
console.log(
|
|
505
497
|
chalk.gray(' ') +
|
|
506
|
-
chalk.cyan(
|
|
498
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
507
499
|
chalk.yellow(mysqlEngine.version.padEnd(12)) +
|
|
508
500
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
509
501
|
chalk.white(formatBytes(mysqlEngine.sizeBytes)),
|
|
@@ -512,12 +504,11 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
512
504
|
|
|
513
505
|
// SQLite row
|
|
514
506
|
if (sqliteEngine) {
|
|
515
|
-
const
|
|
516
|
-
const engineDisplay = `${icon} sqlite`
|
|
507
|
+
const engineDisplay = `${getEngineIcon('sqlite')}sqlite`
|
|
517
508
|
|
|
518
509
|
console.log(
|
|
519
510
|
chalk.gray(' ') +
|
|
520
|
-
chalk.cyan(
|
|
511
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
521
512
|
chalk.yellow(sqliteEngine.version.padEnd(12)) +
|
|
522
513
|
chalk.gray('system'.padEnd(18)) +
|
|
523
514
|
chalk.gray('(system-installed)'),
|
|
@@ -526,13 +517,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
526
517
|
|
|
527
518
|
// DuckDB rows
|
|
528
519
|
for (const engine of duckdbEngines) {
|
|
529
|
-
const icon = ENGINE_ICONS.duckdb
|
|
530
520
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
531
|
-
const engineDisplay = `${
|
|
521
|
+
const engineDisplay = `${getEngineIcon('duckdb')}duckdb`
|
|
532
522
|
|
|
533
523
|
console.log(
|
|
534
524
|
chalk.gray(' ') +
|
|
535
|
-
chalk.cyan(
|
|
525
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
536
526
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
537
527
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
538
528
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -541,13 +531,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
541
531
|
|
|
542
532
|
// MongoDB rows
|
|
543
533
|
for (const engine of mongodbEngines) {
|
|
544
|
-
const icon = ENGINE_ICONS.mongodb
|
|
545
534
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
546
|
-
const engineDisplay = `${
|
|
535
|
+
const engineDisplay = `${getEngineIcon('mongodb')}mongodb`
|
|
547
536
|
|
|
548
537
|
console.log(
|
|
549
538
|
chalk.gray(' ') +
|
|
550
|
-
chalk.cyan(
|
|
539
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
551
540
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
552
541
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
553
542
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -556,13 +545,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
556
545
|
|
|
557
546
|
// FerretDB rows
|
|
558
547
|
for (const engine of ferretdbEngines) {
|
|
559
|
-
const icon = ENGINE_ICONS.ferretdb
|
|
560
548
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
561
|
-
const engineDisplay = `${
|
|
549
|
+
const engineDisplay = `${getEngineIcon('ferretdb')}ferretdb`
|
|
562
550
|
|
|
563
551
|
console.log(
|
|
564
552
|
chalk.gray(' ') +
|
|
565
|
-
chalk.cyan(
|
|
553
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
566
554
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
567
555
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
568
556
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -571,13 +559,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
571
559
|
|
|
572
560
|
// Redis rows
|
|
573
561
|
for (const engine of redisEngines) {
|
|
574
|
-
const icon = ENGINE_ICONS.redis
|
|
575
562
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
576
|
-
const engineDisplay = `${
|
|
563
|
+
const engineDisplay = `${getEngineIcon('redis')}redis`
|
|
577
564
|
|
|
578
565
|
console.log(
|
|
579
566
|
chalk.gray(' ') +
|
|
580
|
-
chalk.cyan(
|
|
567
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
581
568
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
582
569
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
583
570
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -586,13 +573,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
586
573
|
|
|
587
574
|
// Valkey rows
|
|
588
575
|
for (const engine of valkeyEngines) {
|
|
589
|
-
const icon = ENGINE_ICONS.valkey
|
|
590
576
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
591
|
-
const engineDisplay = `${
|
|
577
|
+
const engineDisplay = `${getEngineIcon('valkey')}valkey`
|
|
592
578
|
|
|
593
579
|
console.log(
|
|
594
580
|
chalk.gray(' ') +
|
|
595
|
-
chalk.cyan(
|
|
581
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
596
582
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
597
583
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
598
584
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -601,13 +587,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
601
587
|
|
|
602
588
|
// Qdrant rows
|
|
603
589
|
for (const engine of qdrantEngines) {
|
|
604
|
-
const icon = ENGINE_ICONS.qdrant
|
|
605
590
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
606
|
-
const engineDisplay = `${
|
|
591
|
+
const engineDisplay = `${getEngineIcon('qdrant')}qdrant`
|
|
607
592
|
|
|
608
593
|
console.log(
|
|
609
594
|
chalk.gray(' ') +
|
|
610
|
-
chalk.cyan(
|
|
595
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
611
596
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
612
597
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
613
598
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -616,13 +601,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
616
601
|
|
|
617
602
|
// Meilisearch rows
|
|
618
603
|
for (const engine of meilisearchEngines) {
|
|
619
|
-
const icon = ENGINE_ICONS.meilisearch
|
|
620
604
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
621
|
-
const engineDisplay = `${
|
|
605
|
+
const engineDisplay = `${getEngineIcon('meilisearch')}meilisearch`
|
|
622
606
|
|
|
623
607
|
console.log(
|
|
624
608
|
chalk.gray(' ') +
|
|
625
|
-
chalk.cyan(
|
|
609
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
626
610
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
627
611
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
628
612
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -631,13 +615,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
631
615
|
|
|
632
616
|
// CouchDB rows
|
|
633
617
|
for (const engine of couchdbEngines) {
|
|
634
|
-
const icon = ENGINE_ICONS.couchdb
|
|
635
618
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
636
|
-
const engineDisplay = `${
|
|
619
|
+
const engineDisplay = `${getEngineIcon('couchdb')}couchdb`
|
|
637
620
|
|
|
638
621
|
console.log(
|
|
639
622
|
chalk.gray(' ') +
|
|
640
|
-
chalk.cyan(
|
|
623
|
+
chalk.cyan(engineDisplay.padEnd(14)) +
|
|
641
624
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
642
625
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
643
626
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -778,7 +761,7 @@ async function deleteEngine(
|
|
|
778
761
|
// Interactive selection if not provided
|
|
779
762
|
if (!engineName || !engineVersion) {
|
|
780
763
|
const choices = pgEngines.map((e) => ({
|
|
781
|
-
name: `${getEngineIcon(e.engine)}
|
|
764
|
+
name: `${getEngineIcon(e.engine)}${e.engine} ${e.version} ${chalk.gray(`(${formatBytes(e.sizeBytes)})`)}`,
|
|
782
765
|
value: `${e.engine}:${e.version}:${e.path}`,
|
|
783
766
|
}))
|
|
784
767
|
|
package/cli/commands/info.ts
CHANGED
|
@@ -81,7 +81,7 @@ async function displayContainerInfo(
|
|
|
81
81
|
console.log(
|
|
82
82
|
chalk.gray(' ') +
|
|
83
83
|
chalk.white('Engine:'.padEnd(14)) +
|
|
84
|
-
chalk.cyan(`${icon}
|
|
84
|
+
chalk.cyan(`${icon}${config.engine} ${config.version}`),
|
|
85
85
|
)
|
|
86
86
|
console.log(
|
|
87
87
|
chalk.gray(' ') + chalk.white('Status:'.padEnd(14)) + statusDisplay,
|
|
@@ -192,8 +192,8 @@ async function displayAllContainersInfo(
|
|
|
192
192
|
: chalk.gray('â stopped')
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
const engineDisplay = `${
|
|
195
|
+
// getEngineIcon() includes trailing space for consistent alignment
|
|
196
|
+
const engineDisplay = `${getEngineIcon(container.engine)}${container.engine}`
|
|
197
197
|
|
|
198
198
|
// Show truncated file path for SQLite instead of port
|
|
199
199
|
let portOrPath: string
|
|
@@ -290,7 +290,7 @@ export const infoCommand = new Command('info')
|
|
|
290
290
|
choices: [
|
|
291
291
|
{ name: 'All containers', value: 'all' },
|
|
292
292
|
...containers.map((c) => ({
|
|
293
|
-
name: `${c.name} ${chalk.gray(`(${getEngineIcon(c.engine)}
|
|
293
|
+
name: `${c.name} ${chalk.gray(`(${getEngineIcon(c.engine)}${c.engine})`)}`,
|
|
294
294
|
value: c.name,
|
|
295
295
|
})),
|
|
296
296
|
],
|
package/cli/commands/list.ts
CHANGED
|
@@ -14,12 +14,6 @@ import {
|
|
|
14
14
|
deriveContainerName,
|
|
15
15
|
} from '../../engines/sqlite/scanner'
|
|
16
16
|
|
|
17
|
-
// Pad string to width, accounting for emoji taking 2 display columns
|
|
18
|
-
function padWithEmoji(str: string, width: number): string {
|
|
19
|
-
// Count emojis using Extended_Pictographic (excludes digits/symbols that \p{Emoji} matches)
|
|
20
|
-
const emojiCount = (str.match(/\p{Extended_Pictographic}/gu) || []).length
|
|
21
|
-
return str.padEnd(width + emojiCount)
|
|
22
|
-
}
|
|
23
17
|
|
|
24
18
|
/**
|
|
25
19
|
* Prompt user about unregistered SQLite files in CWD
|
|
@@ -194,8 +188,8 @@ export const listCommand = new Command('list')
|
|
|
194
188
|
: chalk.gray('â stopped')
|
|
195
189
|
}
|
|
196
190
|
|
|
197
|
-
|
|
198
|
-
const engineDisplay = `${
|
|
191
|
+
// getEngineIcon() includes trailing space for consistent alignment
|
|
192
|
+
const engineDisplay = `${getEngineIcon(container.engine)}${container.engine}`
|
|
199
193
|
|
|
200
194
|
const sizeDisplay = size !== null ? formatBytes(size) : chalk.gray('â')
|
|
201
195
|
|
|
@@ -213,7 +207,7 @@ export const listCommand = new Command('list')
|
|
|
213
207
|
console.log(
|
|
214
208
|
chalk.gray(' ') +
|
|
215
209
|
chalk.cyan(container.name.padEnd(20)) +
|
|
216
|
-
chalk.white(
|
|
210
|
+
chalk.white(engineDisplay.padEnd(15)) +
|
|
217
211
|
chalk.yellow(container.version.padEnd(10)) +
|
|
218
212
|
chalk.green(portOrPath.padEnd(8)) +
|
|
219
213
|
chalk.magenta(sizeDisplay.padEnd(10)) +
|
|
@@ -347,7 +347,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
347
347
|
|
|
348
348
|
const choices = [
|
|
349
349
|
...running.map((c) => ({
|
|
350
|
-
name: `${c.name} ${chalk.gray(`(${getEngineIcon(c.engine)}
|
|
350
|
+
name: `${c.name} ${chalk.gray(`(${getEngineIcon(c.engine)}${c.engine} ${c.version}, port ${c.port})`)} ${chalk.green('â running')}`,
|
|
351
351
|
value: c.name,
|
|
352
352
|
short: c.name,
|
|
353
353
|
})),
|
|
@@ -442,7 +442,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
442
442
|
]
|
|
443
443
|
|
|
444
444
|
restoreChoices.push({
|
|
445
|
-
name: `${chalk.cyan('
|
|
445
|
+
name: `${chalk.cyan('â')} Connection string ${chalk.gray('(pull from remote database)')}`,
|
|
446
446
|
value: 'connection',
|
|
447
447
|
})
|
|
448
448
|
|
|
@@ -1118,7 +1118,7 @@ export async function handleRestoreForContainer(
|
|
|
1118
1118
|
value: 'file',
|
|
1119
1119
|
},
|
|
1120
1120
|
{
|
|
1121
|
-
name: `${chalk.cyan('
|
|
1121
|
+
name: `${chalk.cyan('â')} Connection string ${chalk.gray('(pull from remote database)')}`,
|
|
1122
1122
|
value: 'connection',
|
|
1123
1123
|
},
|
|
1124
1124
|
]
|
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
promptDatabaseName,
|
|
32
32
|
promptFileDatabasePath,
|
|
33
33
|
escapeablePrompt,
|
|
34
|
+
filterableListPrompt,
|
|
35
|
+
type FilterableChoice,
|
|
34
36
|
BACK_VALUE,
|
|
35
37
|
MAIN_MENU_VALUE,
|
|
36
38
|
} from '../../ui/prompts'
|
|
@@ -468,7 +470,7 @@ export async function handleList(
|
|
|
468
470
|
const COL_SIZE = 9
|
|
469
471
|
|
|
470
472
|
// Build selectable choices with formatted display (like engines menu)
|
|
471
|
-
const containerChoices:
|
|
473
|
+
const containerChoices: FilterableChoice[] = containers.map((c, i) => {
|
|
472
474
|
const size = sizes[i]
|
|
473
475
|
const isFileBased = isFileBasedEngine(c.engine)
|
|
474
476
|
|
|
@@ -532,26 +534,27 @@ export async function handleList(
|
|
|
532
534
|
)
|
|
533
535
|
}
|
|
534
536
|
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
|
|
537
|
+
// Build the full choice list with footer items
|
|
538
|
+
const allChoices: (FilterableChoice | inquirer.Separator)[] = [
|
|
539
|
+
...containerChoices,
|
|
540
|
+
new inquirer.Separator(chalk.gray('â'.repeat(60))),
|
|
538
541
|
new inquirer.Separator(
|
|
539
|
-
|
|
542
|
+
`${containers.length} container(s): ${parts.join('; ')} ${chalk.gray('â type to filter')}`,
|
|
540
543
|
),
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
544
|
+
new inquirer.Separator(),
|
|
545
|
+
{ name: `${chalk.green('+')} Create new`, value: 'create' },
|
|
546
|
+
{ name: `${chalk.blue('â')} Back to main menu ${chalk.gray('(esc)')}`, value: 'back' },
|
|
547
|
+
]
|
|
545
548
|
|
|
546
|
-
const
|
|
549
|
+
const selectedContainer = await filterableListPrompt(
|
|
550
|
+
allChoices,
|
|
551
|
+
'Select a container:',
|
|
547
552
|
{
|
|
548
|
-
|
|
549
|
-
name: 'selectedContainer',
|
|
550
|
-
message: 'Select a container:',
|
|
551
|
-
choices: containerChoices,
|
|
553
|
+
filterableCount: containerChoices.length,
|
|
552
554
|
pageSize: 15,
|
|
555
|
+
emptyText: 'No containers match filter',
|
|
553
556
|
},
|
|
554
|
-
|
|
557
|
+
)
|
|
555
558
|
|
|
556
559
|
// Back returns to main menu (escape is handled globally)
|
|
557
560
|
if (selectedContainer === 'back') {
|
|
@@ -630,19 +633,21 @@ export async function showContainerSubmenu(
|
|
|
630
633
|
}
|
|
631
634
|
}
|
|
632
635
|
|
|
636
|
+
// Helper for disabled menu items - includes grayed hint in the name
|
|
637
|
+
const disabledItem = (icon: string, label: string, hint: string) => ({
|
|
638
|
+
name: chalk.gray(`${icon} ${label}`) + chalk.gray(` (${hint})`),
|
|
639
|
+
value: '_disabled_',
|
|
640
|
+
disabled: true, // true hides inquirer's default reason text
|
|
641
|
+
})
|
|
642
|
+
|
|
633
643
|
// Open shell - always enabled for file-based DBs (if file exists), server databases need to be running
|
|
634
644
|
const canOpenShell = isFileBasedDB ? existsSync(config.database) : isRunning
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
: chalk.
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
? false
|
|
642
|
-
: isFileBasedDB
|
|
643
|
-
? 'Database file missing'
|
|
644
|
-
: 'Start container first',
|
|
645
|
-
})
|
|
645
|
+
const shellHint = isFileBasedDB ? 'Database file missing' : 'Start container first'
|
|
646
|
+
actionChoices.push(
|
|
647
|
+
canOpenShell
|
|
648
|
+
? { name: `${chalk.blue('>')} Open shell`, value: 'shell' }
|
|
649
|
+
: disabledItem('>', 'Open shell', shellHint),
|
|
650
|
+
)
|
|
646
651
|
|
|
647
652
|
// Run SQL/script - always enabled for file-based DBs (if file exists), server databases need to be running
|
|
648
653
|
// REST API engines (Qdrant, Meilisearch, CouchDB) don't support script files - hide the option entirely
|
|
@@ -657,38 +662,29 @@ export async function showContainerSubmenu(
|
|
|
657
662
|
: config.engine === Engine.SurrealDB
|
|
658
663
|
? 'Run SurrealQL file'
|
|
659
664
|
: 'Run SQL file'
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
: chalk.
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
? false
|
|
667
|
-
: isFileBasedDB
|
|
668
|
-
? 'Database file missing'
|
|
669
|
-
: 'Start container first',
|
|
670
|
-
})
|
|
665
|
+
const runSqlHint = isFileBasedDB ? 'Database file missing' : 'Start container first'
|
|
666
|
+
actionChoices.push(
|
|
667
|
+
canRunSql
|
|
668
|
+
? { name: `${chalk.yellow('â·')} ${runScriptLabel}`, value: 'run-sql' }
|
|
669
|
+
: disabledItem('â·', runScriptLabel, runSqlHint),
|
|
670
|
+
)
|
|
671
671
|
}
|
|
672
672
|
|
|
673
673
|
// Edit container - file-based DBs can always edit (no running state), server databases must be stopped
|
|
674
674
|
const canEdit = isFileBasedDB ? true : !isRunning
|
|
675
|
-
actionChoices.push(
|
|
676
|
-
|
|
677
|
-
? `${chalk.
|
|
678
|
-
:
|
|
679
|
-
|
|
680
|
-
disabled: canEdit ? false : 'Stop container first',
|
|
681
|
-
})
|
|
675
|
+
actionChoices.push(
|
|
676
|
+
canEdit
|
|
677
|
+
? { name: `${chalk.yellow('â')} Edit container`, value: 'edit' }
|
|
678
|
+
: disabledItem('â', 'Edit container', 'Stop container first'),
|
|
679
|
+
)
|
|
682
680
|
|
|
683
681
|
// Clone container - file-based DBs can always clone, server databases must be stopped
|
|
684
682
|
const canClone = isFileBasedDB ? true : !isRunning
|
|
685
|
-
actionChoices.push(
|
|
686
|
-
|
|
687
|
-
? `${chalk.cyan('
|
|
688
|
-
:
|
|
689
|
-
|
|
690
|
-
disabled: canClone ? false : 'Stop container first',
|
|
691
|
-
})
|
|
683
|
+
actionChoices.push(
|
|
684
|
+
canClone
|
|
685
|
+
? { name: `${chalk.cyan('â')} Clone container`, value: 'clone' }
|
|
686
|
+
: disabledItem('â', 'Clone container', 'Stop container first'),
|
|
687
|
+
)
|
|
692
688
|
|
|
693
689
|
actionChoices.push({
|
|
694
690
|
name: `${chalk.magenta('â')} Copy connection string`,
|
|
@@ -697,31 +693,21 @@ export async function showContainerSubmenu(
|
|
|
697
693
|
|
|
698
694
|
// Backup - requires running for server databases, file exists for file-based DBs
|
|
699
695
|
const canBackup = isFileBasedDB ? existsSync(config.database) : isRunning
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
: chalk.
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
? false
|
|
707
|
-
: isFileBasedDB
|
|
708
|
-
? 'Database file missing'
|
|
709
|
-
: 'Start container first',
|
|
710
|
-
})
|
|
696
|
+
const backupHint = isFileBasedDB ? 'Database file missing' : 'Start container first'
|
|
697
|
+
actionChoices.push(
|
|
698
|
+
canBackup
|
|
699
|
+
? { name: `${chalk.magenta('â')} Backup database`, value: 'backup' }
|
|
700
|
+
: disabledItem('â', 'Backup database', backupHint),
|
|
701
|
+
)
|
|
711
702
|
|
|
712
703
|
// Restore - requires running for server databases, file exists for file-based DBs
|
|
713
704
|
const canRestore = isFileBasedDB ? existsSync(config.database) : isRunning
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
: chalk.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
? false
|
|
721
|
-
: isFileBasedDB
|
|
722
|
-
? 'Database file missing'
|
|
723
|
-
: 'Start container first',
|
|
724
|
-
})
|
|
705
|
+
const restoreHint = isFileBasedDB ? 'Database file missing' : 'Start container first'
|
|
706
|
+
actionChoices.push(
|
|
707
|
+
canRestore
|
|
708
|
+
? { name: `${chalk.magenta('â')} Restore from backup`, value: 'restore' }
|
|
709
|
+
: disabledItem('â', 'Restore from backup', restoreHint),
|
|
710
|
+
)
|
|
725
711
|
|
|
726
712
|
// View logs - not available for file-based DBs (no log file)
|
|
727
713
|
if (!isFileBasedDB) {
|
|
@@ -741,13 +727,11 @@ export async function showContainerSubmenu(
|
|
|
741
727
|
|
|
742
728
|
// Delete container - file-based DBs can always delete, server databases must be stopped
|
|
743
729
|
const canDelete = isFileBasedDB ? true : !isRunning
|
|
744
|
-
actionChoices.push(
|
|
745
|
-
|
|
746
|
-
? `${chalk.red('â')} Delete container
|
|
747
|
-
:
|
|
748
|
-
|
|
749
|
-
disabled: canDelete ? false : 'Stop container first',
|
|
750
|
-
})
|
|
730
|
+
actionChoices.push(
|
|
731
|
+
canDelete
|
|
732
|
+
? { name: `${chalk.red('â')} Delete container`, value: 'delete' }
|
|
733
|
+
: disabledItem('â', 'Delete container', 'Stop container first'),
|
|
734
|
+
)
|
|
751
735
|
|
|
752
736
|
actionChoices.push(
|
|
753
737
|
new inquirer.Separator(),
|
|
@@ -5,7 +5,12 @@ import { join, dirname, basename } from 'path'
|
|
|
5
5
|
import { containerManager } from '../../../core/container-manager'
|
|
6
6
|
import { createSpinner } from '../../ui/spinner'
|
|
7
7
|
import { header, uiError, uiWarning, uiInfo, formatBytes } from '../../ui/theme'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
promptConfirm,
|
|
10
|
+
escapeablePrompt,
|
|
11
|
+
filterableListPrompt,
|
|
12
|
+
type FilterableChoice,
|
|
13
|
+
} from '../../ui/prompts'
|
|
9
14
|
import { getEngineIcon, getEngineIconPadded } from '../../constants'
|
|
10
15
|
import {
|
|
11
16
|
getInstalledEngines,
|
|
@@ -93,7 +98,7 @@ export async function handleEngines(): Promise<void> {
|
|
|
93
98
|
const COL_SIZE = 10
|
|
94
99
|
|
|
95
100
|
// Build selectable choices with formatted display
|
|
96
|
-
const
|
|
101
|
+
const engineChoices: FilterableChoice[] = allEnginesSorted.map((e) => {
|
|
97
102
|
// Use getEngineIconPadded to handle emoji width inconsistencies
|
|
98
103
|
// Icons like ðĶ and ðŠķ render at width 1, others at width 2
|
|
99
104
|
const icon = getEngineIconPadded(e.engine)
|
|
@@ -114,25 +119,26 @@ export async function handleEngines(): Promise<void> {
|
|
|
114
119
|
}
|
|
115
120
|
})
|
|
116
121
|
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
// Build full choice list with footer
|
|
123
|
+
const allChoices: (FilterableChoice | inquirer.Separator)[] = [
|
|
124
|
+
...engineChoices,
|
|
125
|
+
new inquirer.Separator(chalk.gray('â'.repeat(52))),
|
|
119
126
|
new inquirer.Separator(
|
|
120
|
-
|
|
127
|
+
`Total: ${engines.length} engine(s), ${formatBytes(totalSize)} ${chalk.gray('â type to filter')}`,
|
|
121
128
|
),
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
choices.push(new inquirer.Separator()) // Separator for when list wraps around
|
|
129
|
+
new inquirer.Separator(),
|
|
130
|
+
{ name: `${chalk.blue('â')} Back to main menu ${chalk.gray('(esc)')}`, value: 'back' },
|
|
131
|
+
]
|
|
126
132
|
|
|
127
|
-
const
|
|
133
|
+
const action = await filterableListPrompt(
|
|
134
|
+
allChoices,
|
|
135
|
+
'Select an engine:',
|
|
128
136
|
{
|
|
129
|
-
|
|
130
|
-
name: 'action',
|
|
131
|
-
message: 'Select an engine:',
|
|
132
|
-
choices,
|
|
137
|
+
filterableCount: engineChoices.length,
|
|
133
138
|
pageSize: 18,
|
|
139
|
+
emptyText: 'No engines match filter',
|
|
134
140
|
},
|
|
135
|
-
|
|
141
|
+
)
|
|
136
142
|
|
|
137
143
|
// Back returns to main menu (escape is handled globally)
|
|
138
144
|
if (action === 'back') {
|
|
@@ -176,7 +182,7 @@ async function showEngineSubmenu(
|
|
|
176
182
|
console.log()
|
|
177
183
|
console.log(
|
|
178
184
|
chalk.cyan(
|
|
179
|
-
` ${getEngineIcon(engineName)}
|
|
185
|
+
` ${getEngineIcon(engineName)}${engineName} ${engineVersion} ${chalk.gray(`(${formatBytes(sizeBytes)})`)}`,
|
|
180
186
|
),
|
|
181
187
|
)
|
|
182
188
|
console.log()
|