spindb 0.9.1 → 0.9.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/cli/commands/backup.ts +13 -11
- package/cli/commands/clone.ts +14 -10
- package/cli/commands/config.ts +29 -29
- package/cli/commands/connect.ts +51 -39
- package/cli/commands/create.ts +59 -32
- package/cli/commands/delete.ts +8 -8
- package/cli/commands/deps.ts +17 -15
- package/cli/commands/doctor.ts +11 -11
- package/cli/commands/edit.ts +108 -55
- package/cli/commands/engines.ts +17 -15
- package/cli/commands/info.ts +8 -6
- package/cli/commands/list.ts +31 -16
- package/cli/commands/logs.ts +15 -11
- package/cli/commands/menu/backup-handlers.ts +52 -47
- package/cli/commands/menu/container-handlers.ts +120 -78
- package/cli/commands/menu/engine-handlers.ts +21 -11
- package/cli/commands/menu/index.ts +4 -4
- package/cli/commands/menu/shell-handlers.ts +34 -31
- package/cli/commands/menu/sql-handlers.ts +22 -16
- package/cli/commands/menu/update-handlers.ts +19 -17
- package/cli/commands/restore.ts +22 -20
- package/cli/commands/run.ts +20 -18
- package/cli/commands/self-update.ts +5 -5
- package/cli/commands/start.ts +11 -9
- package/cli/commands/stop.ts +9 -9
- package/cli/commands/url.ts +12 -9
- package/cli/helpers.ts +9 -4
- package/cli/ui/prompts.ts +12 -5
- package/cli/ui/spinner.ts +4 -4
- package/cli/ui/theme.ts +4 -4
- package/core/binary-manager.ts +5 -1
- package/core/container-manager.ts +5 -5
- package/core/platform-service.ts +3 -3
- package/core/start-with-retry.ts +6 -6
- package/core/transaction-manager.ts +6 -6
- package/engines/mysql/index.ts +11 -11
- package/engines/mysql/restore.ts +4 -4
- package/engines/mysql/version-validator.ts +2 -2
- package/engines/postgresql/binary-manager.ts +17 -17
- package/engines/postgresql/index.ts +7 -2
- package/engines/postgresql/restore.ts +2 -2
- package/engines/postgresql/version-validator.ts +2 -2
- package/engines/sqlite/index.ts +21 -8
- package/package.json +1 -1
package/cli/commands/logs.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { readFile } from 'fs/promises'
|
|
|
5
5
|
import { containerManager } from '../../core/container-manager'
|
|
6
6
|
import { paths } from '../../config/paths'
|
|
7
7
|
import { promptContainerSelect } from '../ui/prompts'
|
|
8
|
-
import {
|
|
8
|
+
import { uiError, uiWarning, uiInfo } from '../ui/theme'
|
|
9
9
|
|
|
10
10
|
function getLastNLines(content: string, n: number): string {
|
|
11
11
|
const lines = content.split('\n')
|
|
@@ -32,7 +32,7 @@ export const logsCommand = new Command('logs')
|
|
|
32
32
|
const containers = await containerManager.list()
|
|
33
33
|
|
|
34
34
|
if (containers.length === 0) {
|
|
35
|
-
console.log(
|
|
35
|
+
console.log(uiWarning('No containers found'))
|
|
36
36
|
return
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -46,7 +46,7 @@ export const logsCommand = new Command('logs')
|
|
|
46
46
|
|
|
47
47
|
const config = await containerManager.getConfig(containerName)
|
|
48
48
|
if (!config) {
|
|
49
|
-
console.error(
|
|
49
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
50
50
|
process.exit(1)
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -56,7 +56,7 @@ export const logsCommand = new Command('logs')
|
|
|
56
56
|
|
|
57
57
|
if (!existsSync(logPath)) {
|
|
58
58
|
console.log(
|
|
59
|
-
|
|
59
|
+
uiInfo(
|
|
60
60
|
`No log file found for "${containerName}". The container may not have been started yet.`,
|
|
61
61
|
),
|
|
62
62
|
)
|
|
@@ -84,9 +84,13 @@ export const logsCommand = new Command('logs')
|
|
|
84
84
|
|
|
85
85
|
if (options.follow) {
|
|
86
86
|
const lineCount = parseInt(options.lines || '50', 10)
|
|
87
|
-
const child = spawn(
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
const child = spawn(
|
|
88
|
+
'tail',
|
|
89
|
+
['-n', String(lineCount), '-f', logPath],
|
|
90
|
+
{
|
|
91
|
+
stdio: 'inherit',
|
|
92
|
+
},
|
|
93
|
+
)
|
|
90
94
|
|
|
91
95
|
// Use named handler so we can remove it to prevent listener leaks
|
|
92
96
|
const sigintHandler = () => {
|
|
@@ -109,15 +113,15 @@ export const logsCommand = new Command('logs')
|
|
|
109
113
|
const content = await readFile(logPath, 'utf-8')
|
|
110
114
|
|
|
111
115
|
if (content.trim() === '') {
|
|
112
|
-
console.log(
|
|
116
|
+
console.log(uiInfo('Log file is empty'))
|
|
113
117
|
return
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
const output = getLastNLines(content, lineCount)
|
|
117
121
|
console.log(output)
|
|
118
|
-
} catch (
|
|
119
|
-
const e =
|
|
120
|
-
console.error(
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const e = error as Error
|
|
124
|
+
console.error(uiError(e.message))
|
|
121
125
|
process.exit(1)
|
|
122
126
|
}
|
|
123
127
|
},
|
|
@@ -25,14 +25,15 @@ import {
|
|
|
25
25
|
import { createSpinner } from '../../ui/spinner'
|
|
26
26
|
import {
|
|
27
27
|
header,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
uiSuccess,
|
|
29
|
+
uiError,
|
|
30
|
+
uiWarning,
|
|
31
31
|
connectionBox,
|
|
32
32
|
formatBytes,
|
|
33
33
|
} from '../../ui/theme'
|
|
34
34
|
import { getEngineIcon } from '../../constants'
|
|
35
35
|
import { type Engine } from '../../../types'
|
|
36
|
+
import { pressEnterToContinue } from './shared'
|
|
36
37
|
|
|
37
38
|
function generateBackupTimestamp(): string {
|
|
38
39
|
const now = new Date()
|
|
@@ -65,7 +66,7 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
65
66
|
const portAvailable = await portManager.isPortAvailable(port)
|
|
66
67
|
if (!portAvailable) {
|
|
67
68
|
console.log(
|
|
68
|
-
|
|
69
|
+
uiError(`Port ${port} is in use. Please choose a different port.`),
|
|
69
70
|
)
|
|
70
71
|
return null
|
|
71
72
|
}
|
|
@@ -77,13 +78,17 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
77
78
|
|
|
78
79
|
const isInstalled = await dbEngine.isBinaryInstalled(version)
|
|
79
80
|
if (isInstalled) {
|
|
80
|
-
binarySpinner.succeed(
|
|
81
|
+
binarySpinner.succeed(
|
|
82
|
+
`${dbEngine.displayName} ${version} binaries ready (cached)`,
|
|
83
|
+
)
|
|
81
84
|
} else {
|
|
82
85
|
binarySpinner.text = `Downloading ${dbEngine.displayName} ${version} binaries...`
|
|
83
86
|
await dbEngine.ensureBinaries(version, ({ message }) => {
|
|
84
87
|
binarySpinner.text = message
|
|
85
88
|
})
|
|
86
|
-
binarySpinner.succeed(
|
|
89
|
+
binarySpinner.succeed(
|
|
90
|
+
`${dbEngine.displayName} ${version} binaries downloaded`,
|
|
91
|
+
)
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
while (await containerManager.exists(containerName)) {
|
|
@@ -136,7 +141,7 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
console.log()
|
|
139
|
-
console.log(
|
|
144
|
+
console.log(uiSuccess('Container ready for restore'))
|
|
140
145
|
console.log()
|
|
141
146
|
|
|
142
147
|
return { name: containerName, config }
|
|
@@ -184,7 +189,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
184
189
|
containerName = selectedContainer
|
|
185
190
|
config = await containerManager.getConfig(containerName)
|
|
186
191
|
if (!config) {
|
|
187
|
-
console.error(
|
|
192
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
188
193
|
return
|
|
189
194
|
}
|
|
190
195
|
}
|
|
@@ -210,7 +215,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
210
215
|
missingDeps = await getMissingDependencies(config.engine)
|
|
211
216
|
if (missingDeps.length > 0) {
|
|
212
217
|
console.log(
|
|
213
|
-
|
|
218
|
+
uiError(
|
|
214
219
|
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
215
220
|
),
|
|
216
221
|
)
|
|
@@ -301,8 +306,8 @@ export async function handleRestore(): Promise<void> {
|
|
|
301
306
|
backupPath = tempDumpPath
|
|
302
307
|
isTempFile = true
|
|
303
308
|
dumpSuccess = true
|
|
304
|
-
} catch (
|
|
305
|
-
const e =
|
|
309
|
+
} catch (error) {
|
|
310
|
+
const e = error as Error
|
|
306
311
|
dumpSpinner.fail('Failed to create dump')
|
|
307
312
|
|
|
308
313
|
if (
|
|
@@ -313,15 +318,19 @@ export async function handleRestore(): Promise<void> {
|
|
|
313
318
|
const missingTool = e.message.includes('mysqldump')
|
|
314
319
|
? 'mysqldump'
|
|
315
320
|
: 'pg_dump'
|
|
316
|
-
const toolEngine =
|
|
317
|
-
|
|
321
|
+
const toolEngine =
|
|
322
|
+
missingTool === 'mysqldump' ? 'mysql' : 'postgresql'
|
|
323
|
+
const installed = await promptInstallDependencies(
|
|
324
|
+
missingTool,
|
|
325
|
+
toolEngine as Engine,
|
|
326
|
+
)
|
|
318
327
|
if (installed) {
|
|
319
328
|
continue
|
|
320
329
|
}
|
|
321
330
|
} else {
|
|
322
331
|
const dumpTool = config.engine === 'mysql' ? 'mysqldump' : 'pg_dump'
|
|
323
332
|
console.log()
|
|
324
|
-
console.log(
|
|
333
|
+
console.log(uiError(`${dumpTool} error:`))
|
|
325
334
|
console.log(chalk.gray(` ${e.message}`))
|
|
326
335
|
console.log()
|
|
327
336
|
}
|
|
@@ -344,7 +353,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
344
353
|
}
|
|
345
354
|
|
|
346
355
|
if (!dumpSuccess) {
|
|
347
|
-
console.log(
|
|
356
|
+
console.log(uiError('Failed to create dump after retries'))
|
|
348
357
|
return
|
|
349
358
|
}
|
|
350
359
|
} else {
|
|
@@ -415,9 +424,9 @@ export async function handleRestore(): Promise<void> {
|
|
|
415
424
|
) {
|
|
416
425
|
restoreSpinner.fail('Version compatibility detected')
|
|
417
426
|
console.log()
|
|
418
|
-
console.log(
|
|
427
|
+
console.log(uiError('PostgreSQL version incompatibility detected:'))
|
|
419
428
|
console.log(
|
|
420
|
-
|
|
429
|
+
uiWarning('Your pg_restore version is too old for this backup file.'),
|
|
421
430
|
)
|
|
422
431
|
|
|
423
432
|
console.log(chalk.yellow('Cleaning up failed database...'))
|
|
@@ -467,23 +476,20 @@ export async function handleRestore(): Promise<void> {
|
|
|
467
476
|
upgradeSpinner.succeed('PostgreSQL client tools upgraded')
|
|
468
477
|
console.log()
|
|
469
478
|
console.log(
|
|
470
|
-
|
|
479
|
+
uiSuccess('Please try the restore again with the updated tools.'),
|
|
471
480
|
)
|
|
472
|
-
await
|
|
473
|
-
console.log(chalk.gray('Press Enter to continue...'))
|
|
474
|
-
process.stdin.once('data', resolve)
|
|
475
|
-
})
|
|
481
|
+
await pressEnterToContinue()
|
|
476
482
|
return
|
|
477
483
|
} else {
|
|
478
484
|
upgradeSpinner.fail('Upgrade failed')
|
|
479
485
|
console.log()
|
|
480
486
|
console.log(
|
|
481
|
-
|
|
487
|
+
uiError('Automatic upgrade failed. Please upgrade manually:'),
|
|
482
488
|
)
|
|
483
489
|
const pgPackage = getPostgresHomebrewPackage()
|
|
484
490
|
const latestMajor = pgPackage.split('@')[1]
|
|
485
491
|
console.log(
|
|
486
|
-
|
|
492
|
+
uiWarning(
|
|
487
493
|
` macOS: brew install ${pgPackage} && brew link --force ${pgPackage}`,
|
|
488
494
|
),
|
|
489
495
|
)
|
|
@@ -493,7 +499,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
493
499
|
),
|
|
494
500
|
)
|
|
495
501
|
console.log(
|
|
496
|
-
|
|
502
|
+
uiWarning(
|
|
497
503
|
` Ubuntu/Debian: sudo apt update && sudo apt install postgresql-client-${latestMajor}`,
|
|
498
504
|
),
|
|
499
505
|
)
|
|
@@ -502,15 +508,12 @@ export async function handleRestore(): Promise<void> {
|
|
|
502
508
|
` This installs PostgreSQL ${latestMajor} client tools: pg_restore, pg_dump, psql, and libpq`,
|
|
503
509
|
),
|
|
504
510
|
)
|
|
505
|
-
await
|
|
506
|
-
console.log(chalk.gray('Press Enter to continue...'))
|
|
507
|
-
process.stdin.once('data', resolve)
|
|
508
|
-
})
|
|
511
|
+
await pressEnterToContinue()
|
|
509
512
|
return
|
|
510
513
|
}
|
|
511
514
|
} catch {
|
|
512
515
|
upgradeSpinner.fail('Upgrade failed')
|
|
513
|
-
console.log(
|
|
516
|
+
console.log(uiError('Failed to upgrade PostgreSQL client tools'))
|
|
514
517
|
console.log(
|
|
515
518
|
chalk.gray(
|
|
516
519
|
'Manual upgrade may be required for pg_restore, pg_dump, and psql',
|
|
@@ -525,7 +528,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
525
528
|
} else {
|
|
526
529
|
console.log()
|
|
527
530
|
console.log(
|
|
528
|
-
|
|
531
|
+
uiWarning(
|
|
529
532
|
'Restore cancelled. Please upgrade PostgreSQL client tools manually and try again.',
|
|
530
533
|
),
|
|
531
534
|
)
|
|
@@ -555,7 +558,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
555
558
|
if (result.code === 0) {
|
|
556
559
|
const connectionString = engine.getConnectionString(config, databaseName)
|
|
557
560
|
console.log()
|
|
558
|
-
console.log(
|
|
561
|
+
console.log(uiSuccess(`Database "${databaseName}" restored`))
|
|
559
562
|
console.log(chalk.gray(' Connection string:'))
|
|
560
563
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
561
564
|
|
|
@@ -591,7 +594,7 @@ export async function handleBackup(): Promise<void> {
|
|
|
591
594
|
const running = containers.filter((c) => c.status === 'running')
|
|
592
595
|
|
|
593
596
|
if (running.length === 0) {
|
|
594
|
-
console.log(
|
|
597
|
+
console.log(uiWarning('No running containers. Start a container first.'))
|
|
595
598
|
await inquirer.prompt([
|
|
596
599
|
{
|
|
597
600
|
type: 'input',
|
|
@@ -611,7 +614,7 @@ export async function handleBackup(): Promise<void> {
|
|
|
611
614
|
|
|
612
615
|
const config = await containerManager.getConfig(containerName)
|
|
613
616
|
if (!config) {
|
|
614
|
-
console.log(
|
|
617
|
+
console.log(uiError(`Container "${containerName}" not found`))
|
|
615
618
|
return
|
|
616
619
|
}
|
|
617
620
|
|
|
@@ -638,7 +641,7 @@ export async function handleBackup(): Promise<void> {
|
|
|
638
641
|
missingDeps = await getMissingDependencies(config.engine)
|
|
639
642
|
if (missingDeps.length > 0) {
|
|
640
643
|
console.log(
|
|
641
|
-
|
|
644
|
+
uiError(
|
|
642
645
|
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
643
646
|
),
|
|
644
647
|
)
|
|
@@ -685,17 +688,17 @@ export async function handleBackup(): Promise<void> {
|
|
|
685
688
|
backupSpinner.succeed('Backup created successfully')
|
|
686
689
|
|
|
687
690
|
console.log()
|
|
688
|
-
console.log(
|
|
691
|
+
console.log(uiSuccess('Backup complete'))
|
|
689
692
|
console.log()
|
|
690
693
|
console.log(chalk.gray(' File:'), chalk.cyan(result.path))
|
|
691
694
|
console.log(chalk.gray(' Size:'), chalk.white(formatBytes(result.size)))
|
|
692
695
|
console.log(chalk.gray(' Format:'), chalk.white(result.format))
|
|
693
696
|
console.log()
|
|
694
|
-
} catch (
|
|
695
|
-
const e =
|
|
697
|
+
} catch (error) {
|
|
698
|
+
const e = error as Error
|
|
696
699
|
backupSpinner.fail('Backup failed')
|
|
697
700
|
console.log()
|
|
698
|
-
console.log(
|
|
701
|
+
console.log(uiError(e.message))
|
|
699
702
|
console.log()
|
|
700
703
|
}
|
|
701
704
|
|
|
@@ -713,13 +716,13 @@ export async function handleClone(): Promise<void> {
|
|
|
713
716
|
const stopped = containers.filter((c) => c.status !== 'running')
|
|
714
717
|
|
|
715
718
|
if (containers.length === 0) {
|
|
716
|
-
console.log(
|
|
719
|
+
console.log(uiWarning('No containers found'))
|
|
717
720
|
return
|
|
718
721
|
}
|
|
719
722
|
|
|
720
723
|
if (stopped.length === 0) {
|
|
721
724
|
console.log(
|
|
722
|
-
|
|
725
|
+
uiWarning(
|
|
723
726
|
'All containers are running. Stop a container first to clone it.',
|
|
724
727
|
),
|
|
725
728
|
)
|
|
@@ -735,7 +738,7 @@ export async function handleClone(): Promise<void> {
|
|
|
735
738
|
|
|
736
739
|
const sourceConfig = await containerManager.getConfig(sourceName)
|
|
737
740
|
if (!sourceConfig) {
|
|
738
|
-
console.log(
|
|
741
|
+
console.log(uiError(`Container "${sourceName}" not found`))
|
|
739
742
|
return
|
|
740
743
|
}
|
|
741
744
|
|
|
@@ -756,8 +759,10 @@ export async function handleClone(): Promise<void> {
|
|
|
756
759
|
])
|
|
757
760
|
|
|
758
761
|
// Check if target container already exists
|
|
759
|
-
if (
|
|
760
|
-
|
|
762
|
+
if (
|
|
763
|
+
await containerManager.exists(targetName, { engine: sourceConfig.engine })
|
|
764
|
+
) {
|
|
765
|
+
console.log(uiError(`Container "${targetName}" already exists`))
|
|
761
766
|
return
|
|
762
767
|
}
|
|
763
768
|
|
|
@@ -774,9 +779,9 @@ export async function handleClone(): Promise<void> {
|
|
|
774
779
|
|
|
775
780
|
console.log()
|
|
776
781
|
console.log(connectionBox(targetName, connectionString, newConfig.port))
|
|
777
|
-
} catch (
|
|
778
|
-
const e =
|
|
782
|
+
} catch (error) {
|
|
783
|
+
const e = error as Error
|
|
779
784
|
spinner.fail(`Failed to clone "${sourceName}"`)
|
|
780
|
-
console.log(
|
|
785
|
+
console.log(uiError(e.message))
|
|
781
786
|
}
|
|
782
787
|
}
|