spindb 0.4.1 → 0.5.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/README.md +207 -101
- package/cli/commands/clone.ts +3 -1
- package/cli/commands/connect.ts +54 -24
- package/cli/commands/create.ts +309 -189
- package/cli/commands/delete.ts +3 -1
- package/cli/commands/deps.ts +19 -4
- package/cli/commands/edit.ts +245 -0
- package/cli/commands/engines.ts +434 -0
- package/cli/commands/info.ts +279 -0
- package/cli/commands/list.ts +14 -3
- package/cli/commands/menu.ts +510 -198
- package/cli/commands/restore.ts +66 -43
- package/cli/commands/start.ts +50 -19
- package/cli/commands/stop.ts +3 -1
- package/cli/commands/url.ts +79 -0
- package/cli/index.ts +9 -3
- package/cli/ui/prompts.ts +99 -34
- package/config/defaults.ts +40 -15
- package/config/engine-defaults.ts +107 -0
- package/config/os-dependencies.ts +119 -124
- package/config/paths.ts +82 -56
- package/core/binary-manager.ts +44 -6
- package/core/config-manager.ts +17 -5
- package/core/container-manager.ts +124 -60
- package/core/dependency-manager.ts +9 -15
- package/core/error-handler.ts +336 -0
- package/core/platform-service.ts +634 -0
- package/core/port-manager.ts +51 -32
- package/core/process-manager.ts +26 -8
- package/core/start-with-retry.ts +167 -0
- package/core/transaction-manager.ts +170 -0
- package/engines/index.ts +7 -2
- package/engines/mysql/binary-detection.ts +325 -0
- package/engines/mysql/index.ts +808 -0
- package/engines/mysql/restore.ts +257 -0
- package/engines/mysql/version-validator.ts +373 -0
- package/{core/postgres-binary-manager.ts → engines/postgresql/binary-manager.ts} +63 -23
- package/engines/postgresql/binary-urls.ts +5 -3
- package/engines/postgresql/index.ts +17 -9
- package/engines/postgresql/restore.ts +54 -5
- package/engines/postgresql/version-validator.ts +262 -0
- package/package.json +9 -3
- package/types/index.ts +29 -5
- package/cli/commands/postgres-tools.ts +0 -216
package/cli/commands/menu.ts
CHANGED
|
@@ -22,14 +22,24 @@ import {
|
|
|
22
22
|
} from '../ui/theme'
|
|
23
23
|
import { existsSync } from 'fs'
|
|
24
24
|
import { readdir, rm, lstat } from 'fs/promises'
|
|
25
|
-
import { spawn } from 'child_process'
|
|
26
|
-
import {
|
|
25
|
+
import { spawn, exec } from 'child_process'
|
|
26
|
+
import { promisify } from 'util'
|
|
27
|
+
import { tmpdir } from 'os'
|
|
27
28
|
import { join } from 'path'
|
|
28
29
|
import { paths } from '../../config/paths'
|
|
30
|
+
import { platformService } from '../../core/platform-service'
|
|
29
31
|
import { portManager } from '../../core/port-manager'
|
|
30
32
|
import { defaults } from '../../config/defaults'
|
|
33
|
+
import { getPostgresHomebrewPackage } from '../../config/engine-defaults'
|
|
34
|
+
import type { EngineName } from '../../types'
|
|
31
35
|
import inquirer from 'inquirer'
|
|
32
36
|
import { getMissingDependencies } from '../../core/dependency-manager'
|
|
37
|
+
import {
|
|
38
|
+
getMysqldPath,
|
|
39
|
+
getMysqlVersion,
|
|
40
|
+
isMariaDB,
|
|
41
|
+
getMysqlInstallInfo,
|
|
42
|
+
} from '../../engines/mysql/binary-detection'
|
|
33
43
|
|
|
34
44
|
type MenuChoice =
|
|
35
45
|
| {
|
|
@@ -39,6 +49,14 @@ type MenuChoice =
|
|
|
39
49
|
}
|
|
40
50
|
| inquirer.Separator
|
|
41
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Engine icons for display
|
|
54
|
+
*/
|
|
55
|
+
const engineIcons: Record<string, string> = {
|
|
56
|
+
postgresql: '🐘',
|
|
57
|
+
mysql: '🐬',
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
async function showMainMenu(): Promise<void> {
|
|
43
61
|
console.clear()
|
|
44
62
|
console.log(header('SpinDB - Local Database Manager'))
|
|
@@ -57,7 +75,6 @@ async function showMainMenu(): Promise<void> {
|
|
|
57
75
|
|
|
58
76
|
const canStart = stopped > 0
|
|
59
77
|
const canStop = running > 0
|
|
60
|
-
const canConnect = running > 0
|
|
61
78
|
const canRestore = running > 0
|
|
62
79
|
const canClone = containers.length > 0
|
|
63
80
|
|
|
@@ -87,18 +104,11 @@ async function showMainMenu(): Promise<void> {
|
|
|
87
104
|
},
|
|
88
105
|
{
|
|
89
106
|
name: canStop
|
|
90
|
-
? `${chalk.
|
|
107
|
+
? `${chalk.red('■')} Stop a container`
|
|
91
108
|
: chalk.gray('■ Stop a container'),
|
|
92
109
|
value: 'stop',
|
|
93
110
|
disabled: canStop ? false : 'No running containers',
|
|
94
111
|
},
|
|
95
|
-
{
|
|
96
|
-
name: canConnect
|
|
97
|
-
? `${chalk.blue('⌘')} Open psql shell`
|
|
98
|
-
: chalk.gray('⌘ Open psql shell'),
|
|
99
|
-
value: 'connect',
|
|
100
|
-
disabled: canConnect ? false : 'No running containers',
|
|
101
|
-
},
|
|
102
112
|
{
|
|
103
113
|
name: canRestore
|
|
104
114
|
? `${chalk.magenta('↓')} Restore backup`
|
|
@@ -147,9 +157,6 @@ async function showMainMenu(): Promise<void> {
|
|
|
147
157
|
case 'stop':
|
|
148
158
|
await handleStop()
|
|
149
159
|
break
|
|
150
|
-
case 'connect':
|
|
151
|
-
await handleConnect()
|
|
152
|
-
break
|
|
153
160
|
case 'restore':
|
|
154
161
|
await handleRestore()
|
|
155
162
|
break
|
|
@@ -204,7 +211,9 @@ async function handleCreate(): Promise<void> {
|
|
|
204
211
|
missingDeps = await getMissingDependencies(engine)
|
|
205
212
|
if (missingDeps.length > 0) {
|
|
206
213
|
console.log(
|
|
207
|
-
error(
|
|
214
|
+
error(
|
|
215
|
+
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
216
|
+
),
|
|
208
217
|
)
|
|
209
218
|
return
|
|
210
219
|
}
|
|
@@ -246,7 +255,7 @@ async function handleCreate(): Promise<void> {
|
|
|
246
255
|
createSpinnerInstance.start()
|
|
247
256
|
|
|
248
257
|
await containerManager.create(containerName, {
|
|
249
|
-
engine: dbEngine.name,
|
|
258
|
+
engine: dbEngine.name as EngineName,
|
|
250
259
|
version,
|
|
251
260
|
port,
|
|
252
261
|
database,
|
|
@@ -303,25 +312,14 @@ async function handleCreate(): Promise<void> {
|
|
|
303
312
|
console.log(chalk.gray(' Connection string:'))
|
|
304
313
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
305
314
|
|
|
306
|
-
// Copy connection string to clipboard using platform
|
|
315
|
+
// Copy connection string to clipboard using platform service
|
|
307
316
|
try {
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
})
|
|
315
|
-
proc.stdin?.write(connectionString)
|
|
316
|
-
proc.stdin?.end()
|
|
317
|
-
proc.on('close', (code) => {
|
|
318
|
-
if (code === 0) resolve()
|
|
319
|
-
else reject(new Error(`Clipboard command exited with code ${code}`))
|
|
320
|
-
})
|
|
321
|
-
proc.on('error', reject)
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
console.log(chalk.gray(' ✓ Connection string copied to clipboard'))
|
|
317
|
+
const copied = await platformService.copyToClipboard(connectionString)
|
|
318
|
+
if (copied) {
|
|
319
|
+
console.log(chalk.gray(' ✓ Connection string copied to clipboard'))
|
|
320
|
+
} else {
|
|
321
|
+
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
322
|
+
}
|
|
325
323
|
} catch {
|
|
326
324
|
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
327
325
|
}
|
|
@@ -362,6 +360,15 @@ async function handleList(): Promise<void> {
|
|
|
362
360
|
console.log(
|
|
363
361
|
info('No containers found. Create one with the "Create" option.'),
|
|
364
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
|
+
])
|
|
365
372
|
return
|
|
366
373
|
}
|
|
367
374
|
|
|
@@ -408,7 +415,7 @@ async function handleList(): Promise<void> {
|
|
|
408
415
|
console.log()
|
|
409
416
|
const containerChoices = [
|
|
410
417
|
...containers.map((c) => ({
|
|
411
|
-
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})`)} ${
|
|
412
419
|
c.status === 'running'
|
|
413
420
|
? chalk.green('● running')
|
|
414
421
|
: chalk.gray('○ stopped')
|
|
@@ -447,7 +454,9 @@ async function showContainerSubmenu(containerName: string): Promise<void> {
|
|
|
447
454
|
}
|
|
448
455
|
|
|
449
456
|
// Check actual running state
|
|
450
|
-
const isRunning = await processManager.isRunning(containerName
|
|
457
|
+
const isRunning = await processManager.isRunning(containerName, {
|
|
458
|
+
engine: config.engine,
|
|
459
|
+
})
|
|
451
460
|
const status = isRunning ? 'running' : 'stopped'
|
|
452
461
|
|
|
453
462
|
console.clear()
|
|
@@ -465,6 +474,13 @@ async function showContainerSubmenu(containerName: string): Promise<void> {
|
|
|
465
474
|
!isRunning
|
|
466
475
|
? { name: `${chalk.green('▶')} Start container`, value: 'start' }
|
|
467
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
|
+
},
|
|
468
484
|
{
|
|
469
485
|
name: !isRunning
|
|
470
486
|
? `${chalk.white('⚙')} Edit container`
|
|
@@ -504,6 +520,10 @@ async function showContainerSubmenu(containerName: string): Promise<void> {
|
|
|
504
520
|
await handleStopContainer(containerName)
|
|
505
521
|
await showContainerSubmenu(containerName)
|
|
506
522
|
return
|
|
523
|
+
case 'shell':
|
|
524
|
+
await handleOpenShell(containerName)
|
|
525
|
+
await showContainerSubmenu(containerName)
|
|
526
|
+
return
|
|
507
527
|
case 'edit': {
|
|
508
528
|
const newName = await handleEditContainer(containerName)
|
|
509
529
|
if (newName === null) {
|
|
@@ -628,67 +648,29 @@ async function handleCopyConnectionString(
|
|
|
628
648
|
const engine = getEngine(config.engine)
|
|
629
649
|
const connectionString = engine.getConnectionString(config)
|
|
630
650
|
|
|
631
|
-
// Copy to clipboard using platform
|
|
632
|
-
const
|
|
633
|
-
const cmd = platform() === 'darwin' ? 'pbcopy' : 'xclip'
|
|
634
|
-
const args = platform() === 'darwin' ? [] : ['-selection', 'clipboard']
|
|
651
|
+
// Copy to clipboard using platform service
|
|
652
|
+
const copied = await platformService.copyToClipboard(connectionString)
|
|
635
653
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const proc = spawn(cmd, args, { stdio: ['pipe', 'inherit', 'inherit'] })
|
|
639
|
-
proc.stdin?.write(connectionString)
|
|
640
|
-
proc.stdin?.end()
|
|
641
|
-
proc.on('close', (code) => {
|
|
642
|
-
if (code === 0) resolve()
|
|
643
|
-
else reject(new Error(`Clipboard command exited with code ${code}`))
|
|
644
|
-
})
|
|
645
|
-
proc.on('error', reject)
|
|
646
|
-
})
|
|
647
|
-
|
|
648
|
-
console.log()
|
|
654
|
+
console.log()
|
|
655
|
+
if (copied) {
|
|
649
656
|
console.log(success('Connection string copied to clipboard'))
|
|
650
657
|
console.log(chalk.gray(` ${connectionString}`))
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
await inquirer.prompt([
|
|
654
|
-
{
|
|
655
|
-
type: 'input',
|
|
656
|
-
name: 'continue',
|
|
657
|
-
message: chalk.gray('Press Enter to continue...'),
|
|
658
|
-
},
|
|
659
|
-
])
|
|
660
|
-
} catch {
|
|
661
|
-
// Fallback: just display the string
|
|
662
|
-
console.log()
|
|
658
|
+
} else {
|
|
663
659
|
console.log(warning('Could not copy to clipboard. Connection string:'))
|
|
664
660
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
665
|
-
console.log()
|
|
666
|
-
|
|
667
|
-
await inquirer.prompt([
|
|
668
|
-
{
|
|
669
|
-
type: 'input',
|
|
670
|
-
name: 'continue',
|
|
671
|
-
message: chalk.gray('Press Enter to continue...'),
|
|
672
|
-
},
|
|
673
|
-
])
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
async function handleConnect(): Promise<void> {
|
|
678
|
-
const containers = await containerManager.list()
|
|
679
|
-
const running = containers.filter((c) => c.status === 'running')
|
|
680
|
-
|
|
681
|
-
if (running.length === 0) {
|
|
682
|
-
console.log(warning('No running containers'))
|
|
683
|
-
return
|
|
684
661
|
}
|
|
662
|
+
console.log()
|
|
685
663
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
664
|
+
await inquirer.prompt([
|
|
665
|
+
{
|
|
666
|
+
type: 'input',
|
|
667
|
+
name: 'continue',
|
|
668
|
+
message: chalk.gray('Press Enter to continue...'),
|
|
669
|
+
},
|
|
670
|
+
])
|
|
671
|
+
}
|
|
691
672
|
|
|
673
|
+
async function handleOpenShell(containerName: string): Promise<void> {
|
|
692
674
|
const config = await containerManager.getConfig(containerName)
|
|
693
675
|
if (!config) {
|
|
694
676
|
console.error(error(`Container "${containerName}" not found`))
|
|
@@ -701,25 +683,49 @@ async function handleConnect(): Promise<void> {
|
|
|
701
683
|
console.log(info(`Connecting to ${containerName}...`))
|
|
702
684
|
console.log()
|
|
703
685
|
|
|
704
|
-
//
|
|
705
|
-
|
|
686
|
+
// Determine shell command based on engine
|
|
687
|
+
let shellCmd: string
|
|
688
|
+
let shellArgs: string[]
|
|
689
|
+
let installHint: string
|
|
690
|
+
|
|
691
|
+
if (config.engine === 'mysql') {
|
|
692
|
+
shellCmd = 'mysql'
|
|
693
|
+
// MySQL connection: mysql -u root -h 127.0.0.1 -P port database
|
|
694
|
+
shellArgs = [
|
|
695
|
+
'-u',
|
|
696
|
+
'root',
|
|
697
|
+
'-h',
|
|
698
|
+
'127.0.0.1',
|
|
699
|
+
'-P',
|
|
700
|
+
String(config.port),
|
|
701
|
+
config.database,
|
|
702
|
+
]
|
|
703
|
+
installHint = 'brew install mysql-client'
|
|
704
|
+
} else {
|
|
705
|
+
// PostgreSQL (default)
|
|
706
|
+
shellCmd = 'psql'
|
|
707
|
+
shellArgs = [connectionString]
|
|
708
|
+
installHint = 'brew install libpq && brew link --force libpq'
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const shellProcess = spawn(shellCmd, shellArgs, {
|
|
706
712
|
stdio: 'inherit',
|
|
707
713
|
})
|
|
708
714
|
|
|
709
|
-
|
|
715
|
+
shellProcess.on('error', (err: NodeJS.ErrnoException) => {
|
|
710
716
|
if (err.code === 'ENOENT') {
|
|
711
|
-
console.log(warning(
|
|
717
|
+
console.log(warning(`${shellCmd} not found on your system.`))
|
|
712
718
|
console.log()
|
|
713
719
|
console.log(chalk.gray(' Connect manually with:'))
|
|
714
720
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
715
721
|
console.log()
|
|
716
|
-
console.log(chalk.gray(
|
|
717
|
-
console.log(chalk.cyan(
|
|
722
|
+
console.log(chalk.gray(` Install ${config.engine} client:`))
|
|
723
|
+
console.log(chalk.cyan(` ${installHint}`))
|
|
718
724
|
}
|
|
719
725
|
})
|
|
720
726
|
|
|
721
727
|
await new Promise<void>((resolve) => {
|
|
722
|
-
|
|
728
|
+
shellProcess.on('close', () => resolve())
|
|
723
729
|
})
|
|
724
730
|
}
|
|
725
731
|
|
|
@@ -779,7 +785,7 @@ async function handleCreateForRestore(): Promise<{
|
|
|
779
785
|
createSpinnerInstance.start()
|
|
780
786
|
|
|
781
787
|
await containerManager.create(containerName, {
|
|
782
|
-
engine: dbEngine.name,
|
|
788
|
+
engine: dbEngine.name as EngineName,
|
|
783
789
|
version,
|
|
784
790
|
port,
|
|
785
791
|
database,
|
|
@@ -836,7 +842,7 @@ async function handleRestore(): Promise<void> {
|
|
|
836
842
|
// Build choices: running containers + create new option
|
|
837
843
|
const choices = [
|
|
838
844
|
...running.map((c) => ({
|
|
839
|
-
name: `${c.name} ${chalk.gray(`(${c.engine} ${c.version}, port ${c.port})`)} ${chalk.green('● running')}`,
|
|
845
|
+
name: `${c.name} ${chalk.gray(`(${engineIcons[c.engine] || '🗄️'} ${c.engine} ${c.version}, port ${c.port})`)} ${chalk.green('● running')}`,
|
|
840
846
|
value: c.name,
|
|
841
847
|
short: c.name,
|
|
842
848
|
})),
|
|
@@ -901,7 +907,9 @@ async function handleRestore(): Promise<void> {
|
|
|
901
907
|
missingDeps = await getMissingDependencies(config.engine)
|
|
902
908
|
if (missingDeps.length > 0) {
|
|
903
909
|
console.log(
|
|
904
|
-
error(
|
|
910
|
+
error(
|
|
911
|
+
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
912
|
+
),
|
|
905
913
|
)
|
|
906
914
|
return
|
|
907
915
|
}
|
|
@@ -1138,7 +1146,7 @@ async function handleRestore(): Promise<void> {
|
|
|
1138
1146
|
|
|
1139
1147
|
try {
|
|
1140
1148
|
const { updatePostgresClientTools } = await import(
|
|
1141
|
-
'../../
|
|
1149
|
+
'../../engines/postgresql/binary-manager'
|
|
1142
1150
|
)
|
|
1143
1151
|
const updateSuccess = await updatePostgresClientTools()
|
|
1144
1152
|
|
|
@@ -1159,24 +1167,26 @@ async function handleRestore(): Promise<void> {
|
|
|
1159
1167
|
console.log(
|
|
1160
1168
|
error('Automatic upgrade failed. Please upgrade manually:'),
|
|
1161
1169
|
)
|
|
1170
|
+
const pgPackage = getPostgresHomebrewPackage()
|
|
1171
|
+
const latestMajor = pgPackage.split('@')[1]
|
|
1162
1172
|
console.log(
|
|
1163
1173
|
warning(
|
|
1164
|
-
|
|
1174
|
+
` macOS: brew install ${pgPackage} && brew link --force ${pgPackage}`,
|
|
1165
1175
|
),
|
|
1166
1176
|
)
|
|
1167
1177
|
console.log(
|
|
1168
1178
|
chalk.gray(
|
|
1169
|
-
|
|
1179
|
+
` This installs PostgreSQL ${latestMajor} client tools: pg_restore, pg_dump, psql, and libpq`,
|
|
1170
1180
|
),
|
|
1171
1181
|
)
|
|
1172
1182
|
console.log(
|
|
1173
1183
|
warning(
|
|
1174
|
-
|
|
1184
|
+
` Ubuntu/Debian: sudo apt update && sudo apt install postgresql-client-${latestMajor}`,
|
|
1175
1185
|
),
|
|
1176
1186
|
)
|
|
1177
1187
|
console.log(
|
|
1178
1188
|
chalk.gray(
|
|
1179
|
-
|
|
1189
|
+
` This installs PostgreSQL ${latestMajor} client tools: pg_restore, pg_dump, psql, and libpq`,
|
|
1180
1190
|
),
|
|
1181
1191
|
)
|
|
1182
1192
|
await new Promise((resolve) => {
|
|
@@ -1240,24 +1250,11 @@ async function handleRestore(): Promise<void> {
|
|
|
1240
1250
|
console.log(chalk.gray(' Connection string:'))
|
|
1241
1251
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
1242
1252
|
|
|
1243
|
-
// Copy connection string to clipboard using platform
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
const args = platform() === 'darwin' ? [] : ['-selection', 'clipboard']
|
|
1247
|
-
|
|
1248
|
-
await new Promise<void>((resolve, reject) => {
|
|
1249
|
-
const proc = spawn(cmd, args, { stdio: ['pipe', 'inherit', 'inherit'] })
|
|
1250
|
-
proc.stdin?.write(connectionString)
|
|
1251
|
-
proc.stdin?.end()
|
|
1252
|
-
proc.on('close', (code) => {
|
|
1253
|
-
if (code === 0) resolve()
|
|
1254
|
-
else reject(new Error(`Clipboard command exited with code ${code}`))
|
|
1255
|
-
})
|
|
1256
|
-
proc.on('error', reject)
|
|
1257
|
-
})
|
|
1258
|
-
|
|
1253
|
+
// Copy connection string to clipboard using platform service
|
|
1254
|
+
const copied = await platformService.copyToClipboard(connectionString)
|
|
1255
|
+
if (copied) {
|
|
1259
1256
|
console.log(chalk.gray(' ✓ Connection string copied to clipboard'))
|
|
1260
|
-
}
|
|
1257
|
+
} else {
|
|
1261
1258
|
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
1262
1259
|
}
|
|
1263
1260
|
|
|
@@ -1352,6 +1349,17 @@ async function handleStartContainer(containerName: string): Promise<void> {
|
|
|
1352
1349
|
`Port ${config.port} is in use. Stop the process using it or change this container's port.`,
|
|
1353
1350
|
),
|
|
1354
1351
|
)
|
|
1352
|
+
console.log()
|
|
1353
|
+
console.log(
|
|
1354
|
+
info(
|
|
1355
|
+
'Tip: If you installed MariaDB via apt, it may have started a system service.',
|
|
1356
|
+
),
|
|
1357
|
+
)
|
|
1358
|
+
console.log(
|
|
1359
|
+
info(
|
|
1360
|
+
'Run: sudo systemctl stop mariadb && sudo systemctl disable mariadb',
|
|
1361
|
+
),
|
|
1362
|
+
)
|
|
1355
1363
|
return
|
|
1356
1364
|
}
|
|
1357
1365
|
|
|
@@ -1360,15 +1368,31 @@ async function handleStartContainer(containerName: string): Promise<void> {
|
|
|
1360
1368
|
const spinner = createSpinner(`Starting ${containerName}...`)
|
|
1361
1369
|
spinner.start()
|
|
1362
1370
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1371
|
+
try {
|
|
1372
|
+
await engine.start(config)
|
|
1373
|
+
await containerManager.updateConfig(containerName, { status: 'running' })
|
|
1365
1374
|
|
|
1366
|
-
|
|
1375
|
+
spinner.succeed(`Container "${containerName}" started`)
|
|
1367
1376
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1377
|
+
const connectionString = engine.getConnectionString(config)
|
|
1378
|
+
console.log()
|
|
1379
|
+
console.log(chalk.gray(' Connection string:'))
|
|
1380
|
+
console.log(chalk.cyan(` ${connectionString}`))
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
spinner.fail(`Failed to start "${containerName}"`)
|
|
1383
|
+
const e = err as Error
|
|
1384
|
+
console.log()
|
|
1385
|
+
console.log(error(e.message))
|
|
1386
|
+
|
|
1387
|
+
// Check if there's a log file with more details
|
|
1388
|
+
const logPath = paths.getContainerLogPath(containerName, {
|
|
1389
|
+
engine: config.engine,
|
|
1390
|
+
})
|
|
1391
|
+
if (existsSync(logPath)) {
|
|
1392
|
+
console.log()
|
|
1393
|
+
console.log(info(`Check the log file for details: ${logPath}`))
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1372
1396
|
}
|
|
1373
1397
|
|
|
1374
1398
|
async function handleStopContainer(containerName: string): Promise<void> {
|
|
@@ -1566,7 +1590,9 @@ async function handleDelete(containerName: string): Promise<void> {
|
|
|
1566
1590
|
return
|
|
1567
1591
|
}
|
|
1568
1592
|
|
|
1569
|
-
const isRunning = await processManager.isRunning(containerName
|
|
1593
|
+
const isRunning = await processManager.isRunning(containerName, {
|
|
1594
|
+
engine: config.engine,
|
|
1595
|
+
})
|
|
1570
1596
|
|
|
1571
1597
|
if (isRunning) {
|
|
1572
1598
|
const stopSpinner = createSpinner(`Stopping ${containerName}...`)
|
|
@@ -1586,72 +1612,135 @@ async function handleDelete(containerName: string): Promise<void> {
|
|
|
1586
1612
|
deleteSpinner.succeed(`Container "${containerName}" deleted`)
|
|
1587
1613
|
}
|
|
1588
1614
|
|
|
1589
|
-
type
|
|
1590
|
-
engine:
|
|
1615
|
+
type InstalledPostgresEngine = {
|
|
1616
|
+
engine: 'postgresql'
|
|
1591
1617
|
version: string
|
|
1592
1618
|
platform: string
|
|
1593
1619
|
arch: string
|
|
1594
1620
|
path: string
|
|
1595
1621
|
sizeBytes: number
|
|
1622
|
+
source: 'downloaded'
|
|
1596
1623
|
}
|
|
1597
1624
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1625
|
+
type InstalledMysqlEngine = {
|
|
1626
|
+
engine: 'mysql'
|
|
1627
|
+
version: string
|
|
1628
|
+
path: string
|
|
1629
|
+
source: 'system'
|
|
1630
|
+
isMariaDB: boolean
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
type InstalledEngine = InstalledPostgresEngine | InstalledMysqlEngine
|
|
1600
1634
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1635
|
+
const execAsync = promisify(exec)
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* Get the actual PostgreSQL version from the binary
|
|
1639
|
+
*/
|
|
1640
|
+
async function getPostgresVersionFromBinary(
|
|
1641
|
+
binPath: string,
|
|
1642
|
+
): Promise<string | null> {
|
|
1643
|
+
const postgresPath = join(binPath, 'bin', 'postgres')
|
|
1644
|
+
if (!existsSync(postgresPath)) {
|
|
1645
|
+
return null
|
|
1603
1646
|
}
|
|
1604
1647
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1648
|
+
try {
|
|
1649
|
+
const { stdout } = await execAsync(`"${postgresPath}" --version`)
|
|
1650
|
+
// Output: postgres (PostgreSQL) 17.7
|
|
1651
|
+
const match = stdout.match(/\(PostgreSQL\)\s+([\d.]+)/)
|
|
1652
|
+
return match ? match[1] : null
|
|
1653
|
+
} catch {
|
|
1654
|
+
return null
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1607
1657
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
// Parse directory name: postgresql-17-darwin-arm64
|
|
1611
|
-
const match = entry.name.match(/^(\w+)-(.+)-(\w+)-(\w+)$/)
|
|
1612
|
-
if (match) {
|
|
1613
|
-
const [, engine, version, platform, arch] = match
|
|
1614
|
-
const dirPath = join(binDir, entry.name)
|
|
1658
|
+
async function getInstalledEngines(): Promise<InstalledEngine[]> {
|
|
1659
|
+
const engines: InstalledEngine[] = []
|
|
1615
1660
|
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1661
|
+
// Get PostgreSQL engines from ~/.spindb/bin/
|
|
1662
|
+
const binDir = paths.bin
|
|
1663
|
+
if (existsSync(binDir)) {
|
|
1664
|
+
const entries = await readdir(binDir, { withFileTypes: true })
|
|
1665
|
+
|
|
1666
|
+
for (const entry of entries) {
|
|
1667
|
+
if (entry.isDirectory()) {
|
|
1668
|
+
// Parse directory name: postgresql-17-darwin-arm64
|
|
1669
|
+
const match = entry.name.match(/^(\w+)-(.+)-(\w+)-(\w+)$/)
|
|
1670
|
+
if (match && match[1] === 'postgresql') {
|
|
1671
|
+
const [, , majorVersion, platform, arch] = match
|
|
1672
|
+
const dirPath = join(binDir, entry.name)
|
|
1673
|
+
|
|
1674
|
+
// Get actual version from the binary
|
|
1675
|
+
const actualVersion =
|
|
1676
|
+
(await getPostgresVersionFromBinary(dirPath)) || majorVersion
|
|
1677
|
+
|
|
1678
|
+
// Get directory size (using lstat to avoid following symlinks)
|
|
1679
|
+
let sizeBytes = 0
|
|
1680
|
+
try {
|
|
1681
|
+
const files = await readdir(dirPath, { recursive: true })
|
|
1682
|
+
for (const file of files) {
|
|
1683
|
+
try {
|
|
1684
|
+
const filePath = join(dirPath, file.toString())
|
|
1685
|
+
const fileStat = await lstat(filePath)
|
|
1686
|
+
// Only count regular files (not symlinks or directories)
|
|
1687
|
+
if (fileStat.isFile()) {
|
|
1688
|
+
sizeBytes += fileStat.size
|
|
1689
|
+
}
|
|
1690
|
+
} catch {
|
|
1691
|
+
// Skip files we can't stat
|
|
1627
1692
|
}
|
|
1628
|
-
} catch {
|
|
1629
|
-
// Skip files we can't stat
|
|
1630
1693
|
}
|
|
1694
|
+
} catch {
|
|
1695
|
+
// Skip directories we can't read
|
|
1631
1696
|
}
|
|
1632
|
-
} catch {
|
|
1633
|
-
// Skip directories we can't read
|
|
1634
|
-
}
|
|
1635
1697
|
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1698
|
+
engines.push({
|
|
1699
|
+
engine: 'postgresql',
|
|
1700
|
+
version: actualVersion,
|
|
1701
|
+
platform,
|
|
1702
|
+
arch,
|
|
1703
|
+
path: dirPath,
|
|
1704
|
+
sizeBytes,
|
|
1705
|
+
source: 'downloaded',
|
|
1706
|
+
})
|
|
1707
|
+
}
|
|
1644
1708
|
}
|
|
1645
1709
|
}
|
|
1646
1710
|
}
|
|
1647
1711
|
|
|
1648
|
-
//
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1712
|
+
// Detect system-installed MySQL
|
|
1713
|
+
const mysqldPath = await getMysqldPath()
|
|
1714
|
+
if (mysqldPath) {
|
|
1715
|
+
const version = await getMysqlVersion(mysqldPath)
|
|
1716
|
+
if (version) {
|
|
1717
|
+
const mariadb = await isMariaDB()
|
|
1718
|
+
engines.push({
|
|
1719
|
+
engine: 'mysql',
|
|
1720
|
+
version,
|
|
1721
|
+
path: mysqldPath,
|
|
1722
|
+
source: 'system',
|
|
1723
|
+
isMariaDB: mariadb,
|
|
1724
|
+
})
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// Sort PostgreSQL by version (descending), MySQL stays at end
|
|
1729
|
+
const pgEngines = engines.filter(
|
|
1730
|
+
(e): e is InstalledPostgresEngine => e.engine === 'postgresql',
|
|
1731
|
+
)
|
|
1732
|
+
const mysqlEngine = engines.find(
|
|
1733
|
+
(e): e is InstalledMysqlEngine => e.engine === 'mysql',
|
|
1734
|
+
)
|
|
1735
|
+
|
|
1736
|
+
pgEngines.sort((a, b) => compareVersions(b.version, a.version))
|
|
1653
1737
|
|
|
1654
|
-
|
|
1738
|
+
const result: InstalledEngine[] = [...pgEngines]
|
|
1739
|
+
if (mysqlEngine) {
|
|
1740
|
+
result.push(mysqlEngine)
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
return result
|
|
1655
1744
|
}
|
|
1656
1745
|
|
|
1657
1746
|
function compareVersions(a: string, b: string): number {
|
|
@@ -1685,54 +1774,104 @@ async function handleEngines(): Promise<void> {
|
|
|
1685
1774
|
console.log(info('No engines installed yet.'))
|
|
1686
1775
|
console.log(
|
|
1687
1776
|
chalk.gray(
|
|
1688
|
-
'
|
|
1777
|
+
' PostgreSQL engines are downloaded automatically when you create a container.',
|
|
1778
|
+
),
|
|
1779
|
+
)
|
|
1780
|
+
console.log(
|
|
1781
|
+
chalk.gray(
|
|
1782
|
+
' MySQL requires system installation (brew install mysql or apt install mysql-server).',
|
|
1689
1783
|
),
|
|
1690
1784
|
)
|
|
1691
1785
|
return
|
|
1692
1786
|
}
|
|
1693
1787
|
|
|
1694
|
-
//
|
|
1695
|
-
const
|
|
1788
|
+
// Separate PostgreSQL and MySQL
|
|
1789
|
+
const pgEngines = engines.filter(
|
|
1790
|
+
(e): e is InstalledPostgresEngine => e.engine === 'postgresql',
|
|
1791
|
+
)
|
|
1792
|
+
const mysqlEngine = engines.find(
|
|
1793
|
+
(e): e is InstalledMysqlEngine => e.engine === 'mysql',
|
|
1794
|
+
)
|
|
1795
|
+
|
|
1796
|
+
// Calculate total size for PostgreSQL
|
|
1797
|
+
const totalPgSize = pgEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
|
|
1696
1798
|
|
|
1697
1799
|
// Table header
|
|
1698
1800
|
console.log()
|
|
1699
1801
|
console.log(
|
|
1700
1802
|
chalk.gray(' ') +
|
|
1701
|
-
chalk.bold.white('ENGINE'.padEnd(
|
|
1803
|
+
chalk.bold.white('ENGINE'.padEnd(14)) +
|
|
1702
1804
|
chalk.bold.white('VERSION'.padEnd(12)) +
|
|
1703
|
-
chalk.bold.white('
|
|
1805
|
+
chalk.bold.white('SOURCE'.padEnd(18)) +
|
|
1704
1806
|
chalk.bold.white('SIZE'),
|
|
1705
1807
|
)
|
|
1706
1808
|
console.log(chalk.gray(' ' + '─'.repeat(55)))
|
|
1707
1809
|
|
|
1708
|
-
//
|
|
1709
|
-
for (const engine of
|
|
1810
|
+
// PostgreSQL rows
|
|
1811
|
+
for (const engine of pgEngines) {
|
|
1812
|
+
const icon = engineIcons[engine.engine] || '🗄️'
|
|
1813
|
+
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
1814
|
+
|
|
1710
1815
|
console.log(
|
|
1711
1816
|
chalk.gray(' ') +
|
|
1712
|
-
chalk.cyan(engine.engine
|
|
1817
|
+
chalk.cyan(`${icon} ${engine.engine}`.padEnd(13)) +
|
|
1713
1818
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
1714
|
-
chalk.gray(
|
|
1819
|
+
chalk.gray(platformInfo.padEnd(18)) +
|
|
1715
1820
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
1716
1821
|
)
|
|
1717
1822
|
}
|
|
1718
1823
|
|
|
1824
|
+
// MySQL row
|
|
1825
|
+
if (mysqlEngine) {
|
|
1826
|
+
const icon = engineIcons.mysql
|
|
1827
|
+
const displayName = mysqlEngine.isMariaDB ? 'mariadb' : 'mysql'
|
|
1828
|
+
|
|
1829
|
+
console.log(
|
|
1830
|
+
chalk.gray(' ') +
|
|
1831
|
+
chalk.cyan(`${icon} ${displayName}`.padEnd(13)) +
|
|
1832
|
+
chalk.yellow(mysqlEngine.version.padEnd(12)) +
|
|
1833
|
+
chalk.gray('system'.padEnd(18)) +
|
|
1834
|
+
chalk.gray('(system-installed)'),
|
|
1835
|
+
)
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1719
1838
|
console.log(chalk.gray(' ' + '─'.repeat(55)))
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1839
|
+
|
|
1840
|
+
// Summary
|
|
1841
|
+
console.log()
|
|
1842
|
+
if (pgEngines.length > 0) {
|
|
1843
|
+
console.log(
|
|
1844
|
+
chalk.gray(
|
|
1845
|
+
` PostgreSQL: ${pgEngines.length} version(s), ${formatBytes(totalPgSize)}`,
|
|
1846
|
+
),
|
|
1847
|
+
)
|
|
1848
|
+
}
|
|
1849
|
+
if (mysqlEngine) {
|
|
1850
|
+
console.log(chalk.gray(` MySQL: system-installed at ${mysqlEngine.path}`))
|
|
1851
|
+
}
|
|
1725
1852
|
console.log()
|
|
1726
1853
|
|
|
1727
|
-
// Menu options
|
|
1728
|
-
const choices: MenuChoice[] = [
|
|
1729
|
-
|
|
1854
|
+
// Menu options - only allow deletion of PostgreSQL engines
|
|
1855
|
+
const choices: MenuChoice[] = []
|
|
1856
|
+
|
|
1857
|
+
for (const e of pgEngines) {
|
|
1858
|
+
choices.push({
|
|
1730
1859
|
name: `${chalk.red('✕')} Delete ${e.engine} ${e.version} ${chalk.gray(`(${formatBytes(e.sizeBytes)})`)}`,
|
|
1731
1860
|
value: `delete:${e.path}:${e.engine}:${e.version}`,
|
|
1732
|
-
})
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1861
|
+
})
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// MySQL info option (not disabled, shows info icon)
|
|
1865
|
+
if (mysqlEngine) {
|
|
1866
|
+
const displayName = mysqlEngine.isMariaDB ? 'MariaDB' : 'MySQL'
|
|
1867
|
+
choices.push({
|
|
1868
|
+
name: `${chalk.blue('ℹ')} ${displayName} ${mysqlEngine.version} ${chalk.gray('(system-installed)')}`,
|
|
1869
|
+
value: `mysql-info:${mysqlEngine.path}`,
|
|
1870
|
+
})
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
choices.push(new inquirer.Separator())
|
|
1874
|
+
choices.push({ name: `${chalk.blue('←')} Back to main menu`, value: 'back' })
|
|
1736
1875
|
|
|
1737
1876
|
const { action } = await inquirer.prompt<{ action: string }>([
|
|
1738
1877
|
{
|
|
@@ -1754,6 +1893,13 @@ async function handleEngines(): Promise<void> {
|
|
|
1754
1893
|
// Return to engines menu
|
|
1755
1894
|
await handleEngines()
|
|
1756
1895
|
}
|
|
1896
|
+
|
|
1897
|
+
if (action.startsWith('mysql-info:')) {
|
|
1898
|
+
const mysqldPath = action.replace('mysql-info:', '')
|
|
1899
|
+
await handleMysqlInfo(mysqldPath)
|
|
1900
|
+
// Return to engines menu
|
|
1901
|
+
await handleEngines()
|
|
1902
|
+
}
|
|
1757
1903
|
}
|
|
1758
1904
|
|
|
1759
1905
|
async function handleDeleteEngine(
|
|
@@ -1812,6 +1958,174 @@ async function handleDeleteEngine(
|
|
|
1812
1958
|
}
|
|
1813
1959
|
}
|
|
1814
1960
|
|
|
1961
|
+
async function handleMysqlInfo(mysqldPath: string): Promise<void> {
|
|
1962
|
+
console.clear()
|
|
1963
|
+
|
|
1964
|
+
// Get install info
|
|
1965
|
+
const installInfo = await getMysqlInstallInfo(mysqldPath)
|
|
1966
|
+
const displayName = installInfo.isMariaDB ? 'MariaDB' : 'MySQL'
|
|
1967
|
+
|
|
1968
|
+
// Get version
|
|
1969
|
+
const version = await getMysqlVersion(mysqldPath)
|
|
1970
|
+
|
|
1971
|
+
console.log(header(`${displayName} Information`))
|
|
1972
|
+
console.log()
|
|
1973
|
+
|
|
1974
|
+
// Check for containers using MySQL
|
|
1975
|
+
const containers = await containerManager.list()
|
|
1976
|
+
const mysqlContainers = containers.filter((c) => c.engine === 'mysql')
|
|
1977
|
+
|
|
1978
|
+
// Track running containers for uninstall instructions
|
|
1979
|
+
const runningContainers: string[] = []
|
|
1980
|
+
|
|
1981
|
+
if (mysqlContainers.length > 0) {
|
|
1982
|
+
console.log(
|
|
1983
|
+
warning(
|
|
1984
|
+
`${mysqlContainers.length} container(s) are using ${displayName}:`,
|
|
1985
|
+
),
|
|
1986
|
+
)
|
|
1987
|
+
console.log()
|
|
1988
|
+
for (const c of mysqlContainers) {
|
|
1989
|
+
const isRunning = await processManager.isRunning(c.name, {
|
|
1990
|
+
engine: c.engine,
|
|
1991
|
+
})
|
|
1992
|
+
if (isRunning) {
|
|
1993
|
+
runningContainers.push(c.name)
|
|
1994
|
+
}
|
|
1995
|
+
const status = isRunning
|
|
1996
|
+
? chalk.green('● running')
|
|
1997
|
+
: chalk.gray('○ stopped')
|
|
1998
|
+
console.log(chalk.gray(` • ${c.name} ${status}`))
|
|
1999
|
+
}
|
|
2000
|
+
console.log()
|
|
2001
|
+
console.log(
|
|
2002
|
+
chalk.yellow(
|
|
2003
|
+
' Uninstalling will break these containers. Delete them first.',
|
|
2004
|
+
),
|
|
2005
|
+
)
|
|
2006
|
+
console.log()
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// Show installation details
|
|
2010
|
+
console.log(chalk.white(' Installation Details:'))
|
|
2011
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
2012
|
+
console.log(
|
|
2013
|
+
chalk.gray(' ') +
|
|
2014
|
+
chalk.white('Version:'.padEnd(18)) +
|
|
2015
|
+
chalk.yellow(version || 'unknown'),
|
|
2016
|
+
)
|
|
2017
|
+
console.log(
|
|
2018
|
+
chalk.gray(' ') +
|
|
2019
|
+
chalk.white('Binary Path:'.padEnd(18)) +
|
|
2020
|
+
chalk.gray(mysqldPath),
|
|
2021
|
+
)
|
|
2022
|
+
console.log(
|
|
2023
|
+
chalk.gray(' ') +
|
|
2024
|
+
chalk.white('Package Manager:'.padEnd(18)) +
|
|
2025
|
+
chalk.cyan(installInfo.packageManager),
|
|
2026
|
+
)
|
|
2027
|
+
console.log(
|
|
2028
|
+
chalk.gray(' ') +
|
|
2029
|
+
chalk.white('Package Name:'.padEnd(18)) +
|
|
2030
|
+
chalk.cyan(installInfo.packageName),
|
|
2031
|
+
)
|
|
2032
|
+
console.log()
|
|
2033
|
+
|
|
2034
|
+
// Uninstall instructions
|
|
2035
|
+
console.log(chalk.white(' To uninstall:'))
|
|
2036
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
2037
|
+
|
|
2038
|
+
let stepNum = 1
|
|
2039
|
+
|
|
2040
|
+
// Step: Stop running containers first
|
|
2041
|
+
if (runningContainers.length > 0) {
|
|
2042
|
+
console.log(chalk.gray(` # ${stepNum}. Stop running SpinDB containers`))
|
|
2043
|
+
console.log(chalk.cyan(' spindb stop <container-name>'))
|
|
2044
|
+
console.log()
|
|
2045
|
+
stepNum++
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
// Step: Delete SpinDB containers
|
|
2049
|
+
if (mysqlContainers.length > 0) {
|
|
2050
|
+
console.log(chalk.gray(` # ${stepNum}. Delete SpinDB containers`))
|
|
2051
|
+
console.log(chalk.cyan(' spindb delete <container-name>'))
|
|
2052
|
+
console.log()
|
|
2053
|
+
stepNum++
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
if (installInfo.packageManager === 'homebrew') {
|
|
2057
|
+
console.log(
|
|
2058
|
+
chalk.gray(
|
|
2059
|
+
` # ${stepNum}. Stop Homebrew service (if running separately)`,
|
|
2060
|
+
),
|
|
2061
|
+
)
|
|
2062
|
+
console.log(chalk.cyan(` brew services stop ${installInfo.packageName}`))
|
|
2063
|
+
console.log()
|
|
2064
|
+
console.log(chalk.gray(` # ${stepNum + 1}. Uninstall the package`))
|
|
2065
|
+
console.log(chalk.cyan(` ${installInfo.uninstallCommand}`))
|
|
2066
|
+
} else if (installInfo.packageManager === 'apt') {
|
|
2067
|
+
console.log(chalk.gray(` # ${stepNum}. Stop the system service`))
|
|
2068
|
+
console.log(
|
|
2069
|
+
chalk.cyan(
|
|
2070
|
+
` sudo systemctl stop ${installInfo.isMariaDB ? 'mariadb' : 'mysql'}`,
|
|
2071
|
+
),
|
|
2072
|
+
)
|
|
2073
|
+
console.log()
|
|
2074
|
+
console.log(chalk.gray(` # ${stepNum + 1}. Disable auto-start on boot`))
|
|
2075
|
+
console.log(
|
|
2076
|
+
chalk.cyan(
|
|
2077
|
+
` sudo systemctl disable ${installInfo.isMariaDB ? 'mariadb' : 'mysql'}`,
|
|
2078
|
+
),
|
|
2079
|
+
)
|
|
2080
|
+
console.log()
|
|
2081
|
+
console.log(chalk.gray(` # ${stepNum + 2}. Uninstall the package`))
|
|
2082
|
+
console.log(chalk.cyan(` ${installInfo.uninstallCommand}`))
|
|
2083
|
+
console.log()
|
|
2084
|
+
console.log(chalk.gray(` # ${stepNum + 3}. Remove data files (optional)`))
|
|
2085
|
+
console.log(
|
|
2086
|
+
chalk.cyan(' sudo apt purge mysql-server mysql-client mysql-common'),
|
|
2087
|
+
)
|
|
2088
|
+
console.log(chalk.cyan(' sudo rm -rf /var/lib/mysql /etc/mysql'))
|
|
2089
|
+
} else if (
|
|
2090
|
+
installInfo.packageManager === 'yum' ||
|
|
2091
|
+
installInfo.packageManager === 'dnf'
|
|
2092
|
+
) {
|
|
2093
|
+
console.log(chalk.gray(` # ${stepNum}. Stop the system service`))
|
|
2094
|
+
console.log(
|
|
2095
|
+
chalk.cyan(
|
|
2096
|
+
` sudo systemctl stop ${installInfo.isMariaDB ? 'mariadb' : 'mysqld'}`,
|
|
2097
|
+
),
|
|
2098
|
+
)
|
|
2099
|
+
console.log()
|
|
2100
|
+
console.log(chalk.gray(` # ${stepNum + 1}. Uninstall the package`))
|
|
2101
|
+
console.log(chalk.cyan(` ${installInfo.uninstallCommand}`))
|
|
2102
|
+
} else if (installInfo.packageManager === 'pacman') {
|
|
2103
|
+
console.log(chalk.gray(` # ${stepNum}. Stop the system service`))
|
|
2104
|
+
console.log(
|
|
2105
|
+
chalk.cyan(
|
|
2106
|
+
` sudo systemctl stop ${installInfo.isMariaDB ? 'mariadb' : 'mysqld'}`,
|
|
2107
|
+
),
|
|
2108
|
+
)
|
|
2109
|
+
console.log()
|
|
2110
|
+
console.log(chalk.gray(` # ${stepNum + 1}. Uninstall the package`))
|
|
2111
|
+
console.log(chalk.cyan(` ${installInfo.uninstallCommand}`))
|
|
2112
|
+
} else {
|
|
2113
|
+
console.log(chalk.gray(' Use your system package manager to uninstall.'))
|
|
2114
|
+
console.log(chalk.gray(` The binary is located at: ${mysqldPath}`))
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
console.log()
|
|
2118
|
+
|
|
2119
|
+
// Wait for user
|
|
2120
|
+
await inquirer.prompt([
|
|
2121
|
+
{
|
|
2122
|
+
type: 'input',
|
|
2123
|
+
name: 'continue',
|
|
2124
|
+
message: chalk.gray('Press Enter to go back...'),
|
|
2125
|
+
},
|
|
2126
|
+
])
|
|
2127
|
+
}
|
|
2128
|
+
|
|
1815
2129
|
export const menuCommand = new Command('menu')
|
|
1816
2130
|
.description('Interactive menu for managing containers')
|
|
1817
2131
|
.action(async () => {
|
|
@@ -1833,9 +2147,7 @@ export const menuCommand = new Command('menu')
|
|
|
1833
2147
|
: 'psql'
|
|
1834
2148
|
const installed = await promptInstallDependencies(missingTool)
|
|
1835
2149
|
if (installed) {
|
|
1836
|
-
console.log(
|
|
1837
|
-
chalk.yellow(' Please re-run spindb to continue.'),
|
|
1838
|
-
)
|
|
2150
|
+
console.log(chalk.yellow(' Please re-run spindb to continue.'))
|
|
1839
2151
|
}
|
|
1840
2152
|
process.exit(1)
|
|
1841
2153
|
}
|