spindb 0.4.0 → 0.5.2
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 +77 -100
- package/cli/commands/clone.ts +3 -1
- package/cli/commands/connect.ts +50 -24
- package/cli/commands/create.ts +265 -112
- package/cli/commands/delete.ts +3 -1
- package/cli/commands/list.ts +14 -3
- package/cli/commands/menu.ts +250 -84
- package/cli/commands/restore.ts +142 -38
- package/cli/commands/start.ts +30 -4
- package/cli/commands/stop.ts +3 -1
- package/cli/ui/prompts.ts +95 -32
- package/config/defaults.ts +40 -15
- package/config/engine-defaults.ts +84 -0
- package/config/os-dependencies.ts +68 -19
- package/config/paths.ts +116 -23
- package/core/binary-manager.ts +30 -5
- package/core/container-manager.ts +124 -60
- package/core/dependency-manager.ts +44 -22
- package/core/port-manager.ts +42 -31
- package/core/postgres-binary-manager.ts +10 -9
- package/core/process-manager.ts +14 -6
- package/engines/index.ts +7 -2
- package/engines/mysql/binary-detection.ts +248 -0
- package/engines/mysql/index.ts +699 -0
- package/engines/postgresql/index.ts +13 -6
- package/package.json +4 -2
- package/types/index.ts +29 -5
package/cli/commands/menu.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { processManager } from '../../core/process-manager'
|
|
|
5
5
|
import { getEngine } from '../../engines'
|
|
6
6
|
import {
|
|
7
7
|
promptContainerSelect,
|
|
8
|
+
promptContainerName,
|
|
8
9
|
promptDatabaseName,
|
|
9
10
|
promptCreateOptions,
|
|
10
11
|
promptConfirm,
|
|
@@ -27,7 +28,9 @@ import { join } from 'path'
|
|
|
27
28
|
import { paths } from '../../config/paths'
|
|
28
29
|
import { portManager } from '../../core/port-manager'
|
|
29
30
|
import { defaults } from '../../config/defaults'
|
|
31
|
+
import type { EngineName } from '../../types'
|
|
30
32
|
import inquirer from 'inquirer'
|
|
33
|
+
import { getMissingDependencies } from '../../core/dependency-manager'
|
|
31
34
|
|
|
32
35
|
type MenuChoice =
|
|
33
36
|
| {
|
|
@@ -37,6 +40,14 @@ type MenuChoice =
|
|
|
37
40
|
}
|
|
38
41
|
| inquirer.Separator
|
|
39
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Engine icons for display
|
|
45
|
+
*/
|
|
46
|
+
const engineIcons: Record<string, string> = {
|
|
47
|
+
postgresql: '🐘',
|
|
48
|
+
mysql: '🐬',
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
async function showMainMenu(): Promise<void> {
|
|
41
52
|
console.clear()
|
|
42
53
|
console.log(header('SpinDB - Local Database Manager'))
|
|
@@ -55,7 +66,6 @@ async function showMainMenu(): Promise<void> {
|
|
|
55
66
|
|
|
56
67
|
const canStart = stopped > 0
|
|
57
68
|
const canStop = running > 0
|
|
58
|
-
const canConnect = running > 0
|
|
59
69
|
const canRestore = running > 0
|
|
60
70
|
const canClone = containers.length > 0
|
|
61
71
|
|
|
@@ -90,13 +100,6 @@ async function showMainMenu(): Promise<void> {
|
|
|
90
100
|
value: 'stop',
|
|
91
101
|
disabled: canStop ? false : 'No running containers',
|
|
92
102
|
},
|
|
93
|
-
{
|
|
94
|
-
name: canConnect
|
|
95
|
-
? `${chalk.blue('⌘')} Open psql shell`
|
|
96
|
-
: chalk.gray('⌘ Open psql shell'),
|
|
97
|
-
value: 'connect',
|
|
98
|
-
disabled: canConnect ? false : 'No running containers',
|
|
99
|
-
},
|
|
100
103
|
{
|
|
101
104
|
name: canRestore
|
|
102
105
|
? `${chalk.magenta('↓')} Restore backup`
|
|
@@ -145,9 +148,6 @@ async function showMainMenu(): Promise<void> {
|
|
|
145
148
|
case 'stop':
|
|
146
149
|
await handleStop()
|
|
147
150
|
break
|
|
148
|
-
case 'connect':
|
|
149
|
-
await handleConnect()
|
|
150
|
-
break
|
|
151
151
|
case 'restore':
|
|
152
152
|
await handleRestore()
|
|
153
153
|
break
|
|
@@ -169,7 +169,8 @@ async function showMainMenu(): Promise<void> {
|
|
|
169
169
|
async function handleCreate(): Promise<void> {
|
|
170
170
|
console.log()
|
|
171
171
|
const answers = await promptCreateOptions()
|
|
172
|
-
|
|
172
|
+
let { name: containerName } = answers
|
|
173
|
+
const { engine, version, port, database } = answers
|
|
173
174
|
|
|
174
175
|
console.log()
|
|
175
176
|
console.log(header('Creating Database Container'))
|
|
@@ -177,6 +178,41 @@ async function handleCreate(): Promise<void> {
|
|
|
177
178
|
|
|
178
179
|
const dbEngine = getEngine(engine)
|
|
179
180
|
|
|
181
|
+
// Check for required client tools BEFORE creating anything
|
|
182
|
+
const depsSpinner = createSpinner('Checking required tools...')
|
|
183
|
+
depsSpinner.start()
|
|
184
|
+
|
|
185
|
+
let missingDeps = await getMissingDependencies(engine)
|
|
186
|
+
if (missingDeps.length > 0) {
|
|
187
|
+
depsSpinner.warn(
|
|
188
|
+
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// Offer to install
|
|
192
|
+
const installed = await promptInstallDependencies(
|
|
193
|
+
missingDeps[0].binary,
|
|
194
|
+
engine,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if (!installed) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Verify installation worked
|
|
202
|
+
missingDeps = await getMissingDependencies(engine)
|
|
203
|
+
if (missingDeps.length > 0) {
|
|
204
|
+
console.log(
|
|
205
|
+
error(`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`),
|
|
206
|
+
)
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(chalk.green(' ✓ All required tools are now available'))
|
|
211
|
+
console.log()
|
|
212
|
+
} else {
|
|
213
|
+
depsSpinner.succeed('Required tools available')
|
|
214
|
+
}
|
|
215
|
+
|
|
180
216
|
// Check if port is currently in use
|
|
181
217
|
const portAvailable = await portManager.isPortAvailable(port)
|
|
182
218
|
|
|
@@ -197,12 +233,18 @@ async function handleCreate(): Promise<void> {
|
|
|
197
233
|
binarySpinner.succeed(`PostgreSQL ${version} binaries downloaded`)
|
|
198
234
|
}
|
|
199
235
|
|
|
236
|
+
// Check if container name already exists and prompt for new name if needed
|
|
237
|
+
while (await containerManager.exists(containerName)) {
|
|
238
|
+
console.log(chalk.yellow(` Container "${containerName}" already exists.`))
|
|
239
|
+
containerName = await promptContainerName()
|
|
240
|
+
}
|
|
241
|
+
|
|
200
242
|
// Create container
|
|
201
243
|
const createSpinnerInstance = createSpinner('Creating container...')
|
|
202
244
|
createSpinnerInstance.start()
|
|
203
245
|
|
|
204
246
|
await containerManager.create(containerName, {
|
|
205
|
-
engine: dbEngine.name,
|
|
247
|
+
engine: dbEngine.name as EngineName,
|
|
206
248
|
version,
|
|
207
249
|
port,
|
|
208
250
|
database,
|
|
@@ -318,6 +360,15 @@ async function handleList(): Promise<void> {
|
|
|
318
360
|
console.log(
|
|
319
361
|
info('No containers found. Create one with the "Create" option.'),
|
|
320
362
|
)
|
|
363
|
+
console.log()
|
|
364
|
+
|
|
365
|
+
await inquirer.prompt([
|
|
366
|
+
{
|
|
367
|
+
type: 'input',
|
|
368
|
+
name: 'continue',
|
|
369
|
+
message: chalk.gray('Press Enter to return to the main menu...'),
|
|
370
|
+
},
|
|
371
|
+
])
|
|
321
372
|
return
|
|
322
373
|
}
|
|
323
374
|
|
|
@@ -364,7 +415,7 @@ async function handleList(): Promise<void> {
|
|
|
364
415
|
console.log()
|
|
365
416
|
const containerChoices = [
|
|
366
417
|
...containers.map((c) => ({
|
|
367
|
-
name: `${c.name} ${chalk.gray(`(${c.engine} ${c.version}, port ${c.port})`)} ${
|
|
418
|
+
name: `${c.name} ${chalk.gray(`(${engineIcons[c.engine] || '🗄️'} ${c.engine} ${c.version}, port ${c.port})`)} ${
|
|
368
419
|
c.status === 'running'
|
|
369
420
|
? chalk.green('● running')
|
|
370
421
|
: chalk.gray('○ stopped')
|
|
@@ -403,7 +454,9 @@ async function showContainerSubmenu(containerName: string): Promise<void> {
|
|
|
403
454
|
}
|
|
404
455
|
|
|
405
456
|
// Check actual running state
|
|
406
|
-
const isRunning = await processManager.isRunning(containerName
|
|
457
|
+
const isRunning = await processManager.isRunning(containerName, {
|
|
458
|
+
engine: config.engine,
|
|
459
|
+
})
|
|
407
460
|
const status = isRunning ? 'running' : 'stopped'
|
|
408
461
|
|
|
409
462
|
console.clear()
|
|
@@ -421,6 +474,13 @@ async function showContainerSubmenu(containerName: string): Promise<void> {
|
|
|
421
474
|
!isRunning
|
|
422
475
|
? { name: `${chalk.green('▶')} Start container`, value: 'start' }
|
|
423
476
|
: { name: `${chalk.yellow('■')} Stop container`, value: 'stop' },
|
|
477
|
+
{
|
|
478
|
+
name: isRunning
|
|
479
|
+
? `${chalk.blue('⌘')} Open shell`
|
|
480
|
+
: chalk.gray('⌘ Open shell'),
|
|
481
|
+
value: 'shell',
|
|
482
|
+
disabled: isRunning ? false : 'Start container first',
|
|
483
|
+
},
|
|
424
484
|
{
|
|
425
485
|
name: !isRunning
|
|
426
486
|
? `${chalk.white('⚙')} Edit container`
|
|
@@ -460,6 +520,10 @@ async function showContainerSubmenu(containerName: string): Promise<void> {
|
|
|
460
520
|
await handleStopContainer(containerName)
|
|
461
521
|
await showContainerSubmenu(containerName)
|
|
462
522
|
return
|
|
523
|
+
case 'shell':
|
|
524
|
+
await handleOpenShell(containerName)
|
|
525
|
+
await showContainerSubmenu(containerName)
|
|
526
|
+
return
|
|
463
527
|
case 'edit': {
|
|
464
528
|
const newName = await handleEditContainer(containerName)
|
|
465
529
|
if (newName === null) {
|
|
@@ -630,21 +694,7 @@ async function handleCopyConnectionString(
|
|
|
630
694
|
}
|
|
631
695
|
}
|
|
632
696
|
|
|
633
|
-
async function
|
|
634
|
-
const containers = await containerManager.list()
|
|
635
|
-
const running = containers.filter((c) => c.status === 'running')
|
|
636
|
-
|
|
637
|
-
if (running.length === 0) {
|
|
638
|
-
console.log(warning('No running containers'))
|
|
639
|
-
return
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const containerName = await promptContainerSelect(
|
|
643
|
-
running,
|
|
644
|
-
'Select container to connect to:',
|
|
645
|
-
)
|
|
646
|
-
if (!containerName) return
|
|
647
|
-
|
|
697
|
+
async function handleOpenShell(containerName: string): Promise<void> {
|
|
648
698
|
const config = await containerManager.getConfig(containerName)
|
|
649
699
|
if (!config) {
|
|
650
700
|
console.error(error(`Container "${containerName}" not found`))
|
|
@@ -657,25 +707,49 @@ async function handleConnect(): Promise<void> {
|
|
|
657
707
|
console.log(info(`Connecting to ${containerName}...`))
|
|
658
708
|
console.log()
|
|
659
709
|
|
|
660
|
-
//
|
|
661
|
-
|
|
710
|
+
// Determine shell command based on engine
|
|
711
|
+
let shellCmd: string
|
|
712
|
+
let shellArgs: string[]
|
|
713
|
+
let installHint: string
|
|
714
|
+
|
|
715
|
+
if (config.engine === 'mysql') {
|
|
716
|
+
shellCmd = 'mysql'
|
|
717
|
+
// MySQL connection: mysql -u root -h 127.0.0.1 -P port database
|
|
718
|
+
shellArgs = [
|
|
719
|
+
'-u',
|
|
720
|
+
'root',
|
|
721
|
+
'-h',
|
|
722
|
+
'127.0.0.1',
|
|
723
|
+
'-P',
|
|
724
|
+
String(config.port),
|
|
725
|
+
config.database,
|
|
726
|
+
]
|
|
727
|
+
installHint = 'brew install mysql-client'
|
|
728
|
+
} else {
|
|
729
|
+
// PostgreSQL (default)
|
|
730
|
+
shellCmd = 'psql'
|
|
731
|
+
shellArgs = [connectionString]
|
|
732
|
+
installHint = 'brew install libpq && brew link --force libpq'
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const shellProcess = spawn(shellCmd, shellArgs, {
|
|
662
736
|
stdio: 'inherit',
|
|
663
737
|
})
|
|
664
738
|
|
|
665
|
-
|
|
739
|
+
shellProcess.on('error', (err: NodeJS.ErrnoException) => {
|
|
666
740
|
if (err.code === 'ENOENT') {
|
|
667
|
-
console.log(warning(
|
|
741
|
+
console.log(warning(`${shellCmd} not found on your system.`))
|
|
668
742
|
console.log()
|
|
669
743
|
console.log(chalk.gray(' Connect manually with:'))
|
|
670
744
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
671
745
|
console.log()
|
|
672
|
-
console.log(chalk.gray(
|
|
673
|
-
console.log(chalk.cyan(
|
|
746
|
+
console.log(chalk.gray(` Install ${config.engine} client:`))
|
|
747
|
+
console.log(chalk.cyan(` ${installHint}`))
|
|
674
748
|
}
|
|
675
749
|
})
|
|
676
750
|
|
|
677
751
|
await new Promise<void>((resolve) => {
|
|
678
|
-
|
|
752
|
+
shellProcess.on('close', () => resolve())
|
|
679
753
|
})
|
|
680
754
|
}
|
|
681
755
|
|
|
@@ -689,7 +763,8 @@ async function handleCreateForRestore(): Promise<{
|
|
|
689
763
|
} | null> {
|
|
690
764
|
console.log()
|
|
691
765
|
const answers = await promptCreateOptions()
|
|
692
|
-
|
|
766
|
+
let { name: containerName } = answers
|
|
767
|
+
const { engine, version, port, database } = answers
|
|
693
768
|
|
|
694
769
|
console.log()
|
|
695
770
|
console.log(header('Creating Database Container'))
|
|
@@ -723,12 +798,18 @@ async function handleCreateForRestore(): Promise<{
|
|
|
723
798
|
binarySpinner.succeed(`PostgreSQL ${version} binaries downloaded`)
|
|
724
799
|
}
|
|
725
800
|
|
|
801
|
+
// Check if container name already exists and prompt for new name if needed
|
|
802
|
+
while (await containerManager.exists(containerName)) {
|
|
803
|
+
console.log(chalk.yellow(` Container "${containerName}" already exists.`))
|
|
804
|
+
containerName = await promptContainerName()
|
|
805
|
+
}
|
|
806
|
+
|
|
726
807
|
// Create container
|
|
727
808
|
const createSpinnerInstance = createSpinner('Creating container...')
|
|
728
809
|
createSpinnerInstance.start()
|
|
729
810
|
|
|
730
811
|
await containerManager.create(containerName, {
|
|
731
|
-
engine: dbEngine.name,
|
|
812
|
+
engine: dbEngine.name as EngineName,
|
|
732
813
|
version,
|
|
733
814
|
port,
|
|
734
815
|
database,
|
|
@@ -785,7 +866,7 @@ async function handleRestore(): Promise<void> {
|
|
|
785
866
|
// Build choices: running containers + create new option
|
|
786
867
|
const choices = [
|
|
787
868
|
...running.map((c) => ({
|
|
788
|
-
name: `${c.name} ${chalk.gray(`(${c.engine} ${c.version}, port ${c.port})`)} ${chalk.green('● running')}`,
|
|
869
|
+
name: `${c.name} ${chalk.gray(`(${engineIcons[c.engine] || '🗄️'} ${c.engine} ${c.version}, port ${c.port})`)} ${chalk.green('● running')}`,
|
|
789
870
|
value: c.name,
|
|
790
871
|
short: c.name,
|
|
791
872
|
})),
|
|
@@ -826,6 +907,41 @@ async function handleRestore(): Promise<void> {
|
|
|
826
907
|
}
|
|
827
908
|
}
|
|
828
909
|
|
|
910
|
+
// Check for required client tools BEFORE doing anything
|
|
911
|
+
const depsSpinner = createSpinner('Checking required tools...')
|
|
912
|
+
depsSpinner.start()
|
|
913
|
+
|
|
914
|
+
let missingDeps = await getMissingDependencies(config.engine)
|
|
915
|
+
if (missingDeps.length > 0) {
|
|
916
|
+
depsSpinner.warn(
|
|
917
|
+
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
// Offer to install
|
|
921
|
+
const installed = await promptInstallDependencies(
|
|
922
|
+
missingDeps[0].binary,
|
|
923
|
+
config.engine,
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
if (!installed) {
|
|
927
|
+
return
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// Verify installation worked
|
|
931
|
+
missingDeps = await getMissingDependencies(config.engine)
|
|
932
|
+
if (missingDeps.length > 0) {
|
|
933
|
+
console.log(
|
|
934
|
+
error(`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`),
|
|
935
|
+
)
|
|
936
|
+
return
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
console.log(chalk.green(' ✓ All required tools are now available'))
|
|
940
|
+
console.log()
|
|
941
|
+
} else {
|
|
942
|
+
depsSpinner.succeed('Required tools available')
|
|
943
|
+
}
|
|
944
|
+
|
|
829
945
|
// Ask for restore source
|
|
830
946
|
const { restoreSource } = await inquirer.prompt<{
|
|
831
947
|
restoreSource: 'file' | 'connection'
|
|
@@ -847,7 +963,7 @@ async function handleRestore(): Promise<void> {
|
|
|
847
963
|
},
|
|
848
964
|
])
|
|
849
965
|
|
|
850
|
-
let backupPath
|
|
966
|
+
let backupPath = ''
|
|
851
967
|
let isTempFile = false
|
|
852
968
|
|
|
853
969
|
if (restoreSource === 'connection') {
|
|
@@ -878,46 +994,64 @@ async function handleRestore(): Promise<void> {
|
|
|
878
994
|
const timestamp = Date.now()
|
|
879
995
|
const tempDumpPath = join(tmpdir(), `spindb-dump-${timestamp}.dump`)
|
|
880
996
|
|
|
881
|
-
|
|
882
|
-
|
|
997
|
+
let dumpSuccess = false
|
|
998
|
+
let attempts = 0
|
|
999
|
+
const maxAttempts = 2 // Allow one retry after installing deps
|
|
883
1000
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
dumpSpinner
|
|
887
|
-
|
|
888
|
-
isTempFile = true
|
|
889
|
-
} catch (err) {
|
|
890
|
-
const e = err as Error
|
|
891
|
-
dumpSpinner.fail('Failed to create dump')
|
|
1001
|
+
while (!dumpSuccess && attempts < maxAttempts) {
|
|
1002
|
+
attempts++
|
|
1003
|
+
const dumpSpinner = createSpinner('Creating dump from remote database...')
|
|
1004
|
+
dumpSpinner.start()
|
|
892
1005
|
|
|
893
|
-
// Clean up temp file if it was created
|
|
894
1006
|
try {
|
|
895
|
-
await
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1007
|
+
await engine.dumpFromConnectionString(connectionString, tempDumpPath)
|
|
1008
|
+
dumpSpinner.succeed('Dump created from remote database')
|
|
1009
|
+
backupPath = tempDumpPath
|
|
1010
|
+
isTempFile = true
|
|
1011
|
+
dumpSuccess = true
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
const e = err as Error
|
|
1014
|
+
dumpSpinner.fail('Failed to create dump')
|
|
1015
|
+
|
|
1016
|
+
// Check if this is a missing tool error
|
|
1017
|
+
if (
|
|
1018
|
+
e.message.includes('pg_dump not found') ||
|
|
1019
|
+
e.message.includes('ENOENT')
|
|
1020
|
+
) {
|
|
1021
|
+
const installed = await promptInstallDependencies('pg_dump')
|
|
1022
|
+
if (installed) {
|
|
1023
|
+
// Loop will retry
|
|
1024
|
+
continue
|
|
1025
|
+
}
|
|
1026
|
+
} else {
|
|
1027
|
+
console.log()
|
|
1028
|
+
console.log(error('pg_dump error:'))
|
|
1029
|
+
console.log(chalk.gray(` ${e.message}`))
|
|
1030
|
+
console.log()
|
|
1031
|
+
}
|
|
899
1032
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1033
|
+
// Clean up temp file if it was created
|
|
1034
|
+
try {
|
|
1035
|
+
await rm(tempDumpPath, { force: true })
|
|
1036
|
+
} catch {
|
|
1037
|
+
// Ignore cleanup errors
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Wait for user to see the error
|
|
1041
|
+
await inquirer.prompt([
|
|
1042
|
+
{
|
|
1043
|
+
type: 'input',
|
|
1044
|
+
name: 'continue',
|
|
1045
|
+
message: chalk.gray('Press Enter to continue...'),
|
|
1046
|
+
},
|
|
1047
|
+
])
|
|
1048
|
+
return
|
|
911
1049
|
}
|
|
1050
|
+
}
|
|
912
1051
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
type: 'input',
|
|
917
|
-
name: 'continue',
|
|
918
|
-
message: chalk.gray('Press Enter to continue...'),
|
|
919
|
-
},
|
|
920
|
-
])
|
|
1052
|
+
// Safety check - should never reach here without backupPath set
|
|
1053
|
+
if (!dumpSuccess) {
|
|
1054
|
+
console.log(error('Failed to create dump after retries'))
|
|
921
1055
|
return
|
|
922
1056
|
}
|
|
923
1057
|
} else {
|
|
@@ -1248,6 +1382,15 @@ async function handleStartContainer(containerName: string): Promise<void> {
|
|
|
1248
1382
|
`Port ${config.port} is in use. Stop the process using it or change this container's port.`,
|
|
1249
1383
|
),
|
|
1250
1384
|
)
|
|
1385
|
+
console.log()
|
|
1386
|
+
console.log(
|
|
1387
|
+
info(
|
|
1388
|
+
'Tip: If you installed MariaDB via apt, it may have started a system service.',
|
|
1389
|
+
),
|
|
1390
|
+
)
|
|
1391
|
+
console.log(
|
|
1392
|
+
info('Run: sudo systemctl stop mariadb && sudo systemctl disable mariadb'),
|
|
1393
|
+
)
|
|
1251
1394
|
return
|
|
1252
1395
|
}
|
|
1253
1396
|
|
|
@@ -1256,15 +1399,31 @@ async function handleStartContainer(containerName: string): Promise<void> {
|
|
|
1256
1399
|
const spinner = createSpinner(`Starting ${containerName}...`)
|
|
1257
1400
|
spinner.start()
|
|
1258
1401
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1402
|
+
try {
|
|
1403
|
+
await engine.start(config)
|
|
1404
|
+
await containerManager.updateConfig(containerName, { status: 'running' })
|
|
1261
1405
|
|
|
1262
|
-
|
|
1406
|
+
spinner.succeed(`Container "${containerName}" started`)
|
|
1263
1407
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1408
|
+
const connectionString = engine.getConnectionString(config)
|
|
1409
|
+
console.log()
|
|
1410
|
+
console.log(chalk.gray(' Connection string:'))
|
|
1411
|
+
console.log(chalk.cyan(` ${connectionString}`))
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
spinner.fail(`Failed to start "${containerName}"`)
|
|
1414
|
+
const e = err as Error
|
|
1415
|
+
console.log()
|
|
1416
|
+
console.log(error(e.message))
|
|
1417
|
+
|
|
1418
|
+
// Check if there's a log file with more details
|
|
1419
|
+
const logPath = paths.getContainerLogPath(containerName, {
|
|
1420
|
+
engine: config.engine,
|
|
1421
|
+
})
|
|
1422
|
+
if (existsSync(logPath)) {
|
|
1423
|
+
console.log()
|
|
1424
|
+
console.log(info(`Check the log file for details: ${logPath}`))
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1268
1427
|
}
|
|
1269
1428
|
|
|
1270
1429
|
async function handleStopContainer(containerName: string): Promise<void> {
|
|
@@ -1462,7 +1621,9 @@ async function handleDelete(containerName: string): Promise<void> {
|
|
|
1462
1621
|
return
|
|
1463
1622
|
}
|
|
1464
1623
|
|
|
1465
|
-
const isRunning = await processManager.isRunning(containerName
|
|
1624
|
+
const isRunning = await processManager.isRunning(containerName, {
|
|
1625
|
+
engine: config.engine,
|
|
1626
|
+
})
|
|
1466
1627
|
|
|
1467
1628
|
if (isRunning) {
|
|
1468
1629
|
const stopSpinner = createSpinner(`Stopping ${containerName}...`)
|
|
@@ -1727,7 +1888,12 @@ export const menuCommand = new Command('menu')
|
|
|
1727
1888
|
: e.message.includes('pg_dump')
|
|
1728
1889
|
? 'pg_dump'
|
|
1729
1890
|
: 'psql'
|
|
1730
|
-
await promptInstallDependencies(missingTool)
|
|
1891
|
+
const installed = await promptInstallDependencies(missingTool)
|
|
1892
|
+
if (installed) {
|
|
1893
|
+
console.log(
|
|
1894
|
+
chalk.yellow(' Please re-run spindb to continue.'),
|
|
1895
|
+
)
|
|
1896
|
+
}
|
|
1731
1897
|
process.exit(1)
|
|
1732
1898
|
}
|
|
1733
1899
|
|