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
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import inquirer from 'inquirer'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
statSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
copyFileSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
} from 'fs'
|
|
4
11
|
import { dirname, basename, join, resolve } from 'path'
|
|
5
12
|
import { homedir } from 'os'
|
|
6
13
|
import { containerManager } from '../../../core/container-manager'
|
|
@@ -22,10 +29,10 @@ import {
|
|
|
22
29
|
import { createSpinner } from '../../ui/spinner'
|
|
23
30
|
import {
|
|
24
31
|
header,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
uiSuccess,
|
|
33
|
+
uiError,
|
|
34
|
+
uiWarning,
|
|
35
|
+
uiInfo,
|
|
29
36
|
connectionBox,
|
|
30
37
|
formatBytes,
|
|
31
38
|
} from '../../ui/theme'
|
|
@@ -69,7 +76,7 @@ export async function handleCreate(): Promise<void> {
|
|
|
69
76
|
missingDeps = await getMissingDependencies(engine)
|
|
70
77
|
if (missingDeps.length > 0) {
|
|
71
78
|
console.log(
|
|
72
|
-
|
|
79
|
+
uiError(
|
|
73
80
|
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
74
81
|
),
|
|
75
82
|
)
|
|
@@ -94,13 +101,17 @@ export async function handleCreate(): Promise<void> {
|
|
|
94
101
|
|
|
95
102
|
const isInstalled = await dbEngine.isBinaryInstalled(version)
|
|
96
103
|
if (isInstalled) {
|
|
97
|
-
binarySpinner.succeed(
|
|
104
|
+
binarySpinner.succeed(
|
|
105
|
+
`${dbEngine.displayName} ${version} binaries ready (cached)`,
|
|
106
|
+
)
|
|
98
107
|
} else {
|
|
99
108
|
binarySpinner.text = `Downloading ${dbEngine.displayName} ${version} binaries...`
|
|
100
109
|
await dbEngine.ensureBinaries(version, ({ message }) => {
|
|
101
110
|
binarySpinner.text = message
|
|
102
111
|
})
|
|
103
|
-
binarySpinner.succeed(
|
|
112
|
+
binarySpinner.succeed(
|
|
113
|
+
`${dbEngine.displayName} ${version} binaries downloaded`,
|
|
114
|
+
)
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
|
|
@@ -131,7 +142,9 @@ export async function handleCreate(): Promise<void> {
|
|
|
131
142
|
path: sqlitePath, // SQLite file path (undefined for server databases)
|
|
132
143
|
})
|
|
133
144
|
|
|
134
|
-
initSpinner.succeed(
|
|
145
|
+
initSpinner.succeed(
|
|
146
|
+
isSQLite ? 'Database file created' : 'Database cluster initialized',
|
|
147
|
+
)
|
|
135
148
|
|
|
136
149
|
// SQLite: show file path, no start needed
|
|
137
150
|
if (isSQLite) {
|
|
@@ -139,13 +152,13 @@ export async function handleCreate(): Promise<void> {
|
|
|
139
152
|
if (config) {
|
|
140
153
|
const connectionString = dbEngine.getConnectionString(config)
|
|
141
154
|
console.log()
|
|
142
|
-
console.log(
|
|
155
|
+
console.log(uiSuccess('Database Created'))
|
|
143
156
|
console.log()
|
|
144
157
|
console.log(chalk.gray(` Container: ${containerName}`))
|
|
145
158
|
console.log(chalk.gray(` Engine: ${dbEngine.displayName} ${version}`))
|
|
146
159
|
console.log(chalk.gray(` File: ${config.database}`))
|
|
147
160
|
console.log()
|
|
148
|
-
console.log(
|
|
161
|
+
console.log(uiSuccess(`Available at ${config.database}`))
|
|
149
162
|
console.log()
|
|
150
163
|
console.log(chalk.gray(' Connection string:'))
|
|
151
164
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
@@ -199,14 +212,14 @@ export async function handleCreate(): Promise<void> {
|
|
|
199
212
|
if (config) {
|
|
200
213
|
const connectionString = dbEngine.getConnectionString(config)
|
|
201
214
|
console.log()
|
|
202
|
-
console.log(
|
|
215
|
+
console.log(uiSuccess('Database Created'))
|
|
203
216
|
console.log()
|
|
204
217
|
console.log(chalk.gray(` Container: ${containerName}`))
|
|
205
218
|
console.log(chalk.gray(` Engine: ${dbEngine.displayName} ${version}`))
|
|
206
219
|
console.log(chalk.gray(` Database: ${database}`))
|
|
207
220
|
console.log(chalk.gray(` Port: ${port}`))
|
|
208
221
|
console.log()
|
|
209
|
-
console.log(
|
|
222
|
+
console.log(uiSuccess(`Running on port ${port}`))
|
|
210
223
|
console.log()
|
|
211
224
|
console.log(chalk.gray(' Connection string:'))
|
|
212
225
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
@@ -235,12 +248,12 @@ export async function handleCreate(): Promise<void> {
|
|
|
235
248
|
} else {
|
|
236
249
|
console.log()
|
|
237
250
|
console.log(
|
|
238
|
-
|
|
251
|
+
uiWarning(
|
|
239
252
|
`Port ${port} is currently in use. Container created but not started.`,
|
|
240
253
|
),
|
|
241
254
|
)
|
|
242
255
|
console.log(
|
|
243
|
-
|
|
256
|
+
uiInfo(
|
|
244
257
|
`Start it later with: ${chalk.cyan(`spindb start ${containerName}`)}`,
|
|
245
258
|
),
|
|
246
259
|
)
|
|
@@ -257,7 +270,7 @@ export async function handleList(
|
|
|
257
270
|
|
|
258
271
|
if (containers.length === 0) {
|
|
259
272
|
console.log(
|
|
260
|
-
|
|
273
|
+
uiInfo('No containers found. Create one with the "Create" option.'),
|
|
261
274
|
)
|
|
262
275
|
console.log()
|
|
263
276
|
|
|
@@ -302,19 +315,20 @@ export async function handleList(
|
|
|
302
315
|
|
|
303
316
|
// SQLite uses available/missing, server databases use running/stopped
|
|
304
317
|
const statusDisplay = isSQLite
|
|
305
|
-
?
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
:
|
|
309
|
-
|
|
310
|
-
|
|
318
|
+
? container.status === 'running'
|
|
319
|
+
? chalk.blue('● available')
|
|
320
|
+
: chalk.gray('○ missing')
|
|
321
|
+
: container.status === 'running'
|
|
322
|
+
? chalk.green('● running')
|
|
323
|
+
: chalk.gray('○ stopped')
|
|
311
324
|
|
|
312
325
|
const sizeDisplay = size !== null ? formatBytes(size) : chalk.gray('—')
|
|
313
326
|
|
|
314
327
|
// Truncate name if too long
|
|
315
|
-
const displayName =
|
|
316
|
-
|
|
317
|
-
|
|
328
|
+
const displayName =
|
|
329
|
+
container.name.length > 15
|
|
330
|
+
? container.name.slice(0, 14) + '…'
|
|
331
|
+
: container.name
|
|
318
332
|
|
|
319
333
|
// SQLite shows dash instead of port
|
|
320
334
|
const portDisplay = isSQLite ? '—' : String(container.port)
|
|
@@ -338,7 +352,9 @@ export async function handleList(
|
|
|
338
352
|
|
|
339
353
|
const running = serverContainers.filter((c) => c.status === 'running').length
|
|
340
354
|
const stopped = serverContainers.filter((c) => c.status !== 'running').length
|
|
341
|
-
const available = sqliteContainers.filter(
|
|
355
|
+
const available = sqliteContainers.filter(
|
|
356
|
+
(c) => c.status === 'running',
|
|
357
|
+
).length
|
|
342
358
|
const missing = sqliteContainers.filter((c) => c.status !== 'running').length
|
|
343
359
|
|
|
344
360
|
const parts: string[] = []
|
|
@@ -346,13 +362,13 @@ export async function handleList(
|
|
|
346
362
|
parts.push(`${running} running, ${stopped} stopped`)
|
|
347
363
|
}
|
|
348
364
|
if (sqliteContainers.length > 0) {
|
|
349
|
-
parts.push(
|
|
365
|
+
parts.push(
|
|
366
|
+
`${available} SQLite available${missing > 0 ? `, ${missing} missing` : ''}`,
|
|
367
|
+
)
|
|
350
368
|
}
|
|
351
369
|
|
|
352
370
|
console.log(
|
|
353
|
-
chalk.gray(
|
|
354
|
-
` ${containers.length} container(s): ${parts.join('; ')}`,
|
|
355
|
-
),
|
|
371
|
+
chalk.gray(` ${containers.length} container(s): ${parts.join('; ')}`),
|
|
356
372
|
)
|
|
357
373
|
|
|
358
374
|
console.log()
|
|
@@ -361,8 +377,12 @@ export async function handleList(
|
|
|
361
377
|
// Simpler selector - table already shows details
|
|
362
378
|
const statusLabel =
|
|
363
379
|
c.engine === Engine.SQLite
|
|
364
|
-
?
|
|
365
|
-
|
|
380
|
+
? c.status === 'running'
|
|
381
|
+
? chalk.blue('● available')
|
|
382
|
+
: chalk.gray('○ missing')
|
|
383
|
+
: c.status === 'running'
|
|
384
|
+
? chalk.green('● running')
|
|
385
|
+
: chalk.gray('○ stopped')
|
|
366
386
|
|
|
367
387
|
return {
|
|
368
388
|
name: `${c.name} ${statusLabel}`,
|
|
@@ -400,7 +420,7 @@ export async function showContainerSubmenu(
|
|
|
400
420
|
): Promise<void> {
|
|
401
421
|
const config = await containerManager.getConfig(containerName)
|
|
402
422
|
if (!config) {
|
|
403
|
-
console.error(
|
|
423
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
404
424
|
return
|
|
405
425
|
}
|
|
406
426
|
|
|
@@ -458,7 +478,11 @@ export async function showContainerSubmenu(
|
|
|
458
478
|
? `${chalk.blue('⌘')} Open shell`
|
|
459
479
|
: chalk.gray('⌘ Open shell'),
|
|
460
480
|
value: 'shell',
|
|
461
|
-
disabled: canOpenShell
|
|
481
|
+
disabled: canOpenShell
|
|
482
|
+
? false
|
|
483
|
+
: isSQLite
|
|
484
|
+
? 'Database file missing'
|
|
485
|
+
: 'Start container first',
|
|
462
486
|
})
|
|
463
487
|
|
|
464
488
|
// Run SQL - always enabled for SQLite (if file exists), server databases need to be running
|
|
@@ -468,7 +492,11 @@ export async function showContainerSubmenu(
|
|
|
468
492
|
? `${chalk.yellow('▷')} Run SQL file`
|
|
469
493
|
: chalk.gray('▷ Run SQL file'),
|
|
470
494
|
value: 'run-sql',
|
|
471
|
-
disabled: canRunSql
|
|
495
|
+
disabled: canRunSql
|
|
496
|
+
? false
|
|
497
|
+
: isSQLite
|
|
498
|
+
? 'Database file missing'
|
|
499
|
+
: 'Start container first',
|
|
472
500
|
})
|
|
473
501
|
|
|
474
502
|
// Edit container - SQLite can always edit (no running state), server databases must be stopped
|
|
@@ -491,9 +519,10 @@ export async function showContainerSubmenu(
|
|
|
491
519
|
disabled: canClone ? false : 'Stop container first',
|
|
492
520
|
})
|
|
493
521
|
|
|
494
|
-
actionChoices.push(
|
|
495
|
-
|
|
496
|
-
|
|
522
|
+
actionChoices.push({
|
|
523
|
+
name: `${chalk.magenta('⎘')} Copy connection string`,
|
|
524
|
+
value: 'copy',
|
|
525
|
+
})
|
|
497
526
|
|
|
498
527
|
// View logs - not available for SQLite (no log file)
|
|
499
528
|
if (!isSQLite) {
|
|
@@ -596,7 +625,7 @@ export async function handleStart(): Promise<void> {
|
|
|
596
625
|
)
|
|
597
626
|
|
|
598
627
|
if (stopped.length === 0) {
|
|
599
|
-
console.log(
|
|
628
|
+
console.log(uiWarning('All containers are already running'))
|
|
600
629
|
return
|
|
601
630
|
}
|
|
602
631
|
|
|
@@ -609,7 +638,7 @@ export async function handleStart(): Promise<void> {
|
|
|
609
638
|
|
|
610
639
|
const config = await containerManager.getConfig(containerName)
|
|
611
640
|
if (!config) {
|
|
612
|
-
console.error(
|
|
641
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
613
642
|
return
|
|
614
643
|
}
|
|
615
644
|
|
|
@@ -617,7 +646,7 @@ export async function handleStart(): Promise<void> {
|
|
|
617
646
|
if (!portAvailable) {
|
|
618
647
|
const { port: newPort } = await portManager.findAvailablePort()
|
|
619
648
|
console.log(
|
|
620
|
-
|
|
649
|
+
uiWarning(`Port ${config.port} is in use, switching to port ${newPort}`),
|
|
621
650
|
)
|
|
622
651
|
config.port = newPort
|
|
623
652
|
await containerManager.updateConfig(containerName, { port: newPort })
|
|
@@ -647,7 +676,7 @@ export async function handleStop(): Promise<void> {
|
|
|
647
676
|
)
|
|
648
677
|
|
|
649
678
|
if (running.length === 0) {
|
|
650
|
-
console.log(
|
|
679
|
+
console.log(uiWarning('No running containers'))
|
|
651
680
|
return
|
|
652
681
|
}
|
|
653
682
|
|
|
@@ -660,7 +689,7 @@ export async function handleStop(): Promise<void> {
|
|
|
660
689
|
|
|
661
690
|
const config = await containerManager.getConfig(containerName)
|
|
662
691
|
if (!config) {
|
|
663
|
-
console.error(
|
|
692
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
664
693
|
return
|
|
665
694
|
}
|
|
666
695
|
|
|
@@ -678,25 +707,25 @@ export async function handleStop(): Promise<void> {
|
|
|
678
707
|
async function handleStartContainer(containerName: string): Promise<void> {
|
|
679
708
|
const config = await containerManager.getConfig(containerName)
|
|
680
709
|
if (!config) {
|
|
681
|
-
console.error(
|
|
710
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
682
711
|
return
|
|
683
712
|
}
|
|
684
713
|
|
|
685
714
|
const portAvailable = await portManager.isPortAvailable(config.port)
|
|
686
715
|
if (!portAvailable) {
|
|
687
716
|
console.log(
|
|
688
|
-
|
|
717
|
+
uiWarning(
|
|
689
718
|
`Port ${config.port} is in use. Stop the process using it or change this container's port.`,
|
|
690
719
|
),
|
|
691
720
|
)
|
|
692
721
|
console.log()
|
|
693
722
|
console.log(
|
|
694
|
-
|
|
723
|
+
uiInfo(
|
|
695
724
|
'Tip: If you installed MariaDB via apt, it may have started a system service.',
|
|
696
725
|
),
|
|
697
726
|
)
|
|
698
727
|
console.log(
|
|
699
|
-
|
|
728
|
+
uiInfo(
|
|
700
729
|
'Run: sudo systemctl stop mariadb && sudo systemctl disable mariadb',
|
|
701
730
|
),
|
|
702
731
|
)
|
|
@@ -718,18 +747,18 @@ async function handleStartContainer(containerName: string): Promise<void> {
|
|
|
718
747
|
console.log()
|
|
719
748
|
console.log(chalk.gray(' Connection string:'))
|
|
720
749
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
721
|
-
} catch (
|
|
750
|
+
} catch (error) {
|
|
722
751
|
spinner.fail(`Failed to start "${containerName}"`)
|
|
723
|
-
const e =
|
|
752
|
+
const e = error as Error
|
|
724
753
|
console.log()
|
|
725
|
-
console.log(
|
|
754
|
+
console.log(uiError(e.message))
|
|
726
755
|
|
|
727
756
|
const logPath = paths.getContainerLogPath(containerName, {
|
|
728
757
|
engine: config.engine,
|
|
729
758
|
})
|
|
730
759
|
if (existsSync(logPath)) {
|
|
731
760
|
console.log()
|
|
732
|
-
console.log(
|
|
761
|
+
console.log(uiInfo(`Check the log file for details: ${logPath}`))
|
|
733
762
|
}
|
|
734
763
|
}
|
|
735
764
|
}
|
|
@@ -737,7 +766,7 @@ async function handleStartContainer(containerName: string): Promise<void> {
|
|
|
737
766
|
async function handleStopContainer(containerName: string): Promise<void> {
|
|
738
767
|
const config = await containerManager.getConfig(containerName)
|
|
739
768
|
if (!config) {
|
|
740
|
-
console.error(
|
|
769
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
741
770
|
return
|
|
742
771
|
}
|
|
743
772
|
|
|
@@ -757,7 +786,7 @@ async function handleEditContainer(
|
|
|
757
786
|
): Promise<string | null> {
|
|
758
787
|
const config = await containerManager.getConfig(containerName)
|
|
759
788
|
if (!config) {
|
|
760
|
-
console.error(
|
|
789
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
761
790
|
return null
|
|
762
791
|
}
|
|
763
792
|
|
|
@@ -767,7 +796,9 @@ async function handleEditContainer(
|
|
|
767
796
|
console.log(header(`Edit: ${containerName}`))
|
|
768
797
|
console.log()
|
|
769
798
|
|
|
770
|
-
const editChoices: Array<
|
|
799
|
+
const editChoices: Array<
|
|
800
|
+
{ name: string; value: string } | inquirer.Separator
|
|
801
|
+
> = [
|
|
771
802
|
{
|
|
772
803
|
name: `Name: ${chalk.white(containerName)}`,
|
|
773
804
|
value: 'name',
|
|
@@ -833,12 +864,12 @@ async function handleEditContainer(
|
|
|
833
864
|
])
|
|
834
865
|
|
|
835
866
|
if (newName === containerName) {
|
|
836
|
-
console.log(
|
|
867
|
+
console.log(uiInfo('Name unchanged'))
|
|
837
868
|
return await handleEditContainer(containerName)
|
|
838
869
|
}
|
|
839
870
|
|
|
840
871
|
if (await containerManager.exists(newName)) {
|
|
841
|
-
console.log(
|
|
872
|
+
console.log(uiError(`Container "${newName}" already exists`))
|
|
842
873
|
return await handleEditContainer(containerName)
|
|
843
874
|
}
|
|
844
875
|
|
|
@@ -872,21 +903,21 @@ async function handleEditContainer(
|
|
|
872
903
|
])
|
|
873
904
|
|
|
874
905
|
if (newPort === config.port) {
|
|
875
|
-
console.log(
|
|
906
|
+
console.log(uiInfo('Port unchanged'))
|
|
876
907
|
return await handleEditContainer(containerName)
|
|
877
908
|
}
|
|
878
909
|
|
|
879
910
|
const portAvailable = await portManager.isPortAvailable(newPort)
|
|
880
911
|
if (!portAvailable) {
|
|
881
912
|
console.log(
|
|
882
|
-
|
|
913
|
+
uiWarning(
|
|
883
914
|
`Port ${newPort} is currently in use. You'll need to stop the process using it before starting this container.`,
|
|
884
915
|
),
|
|
885
916
|
)
|
|
886
917
|
}
|
|
887
918
|
|
|
888
919
|
await containerManager.updateConfig(containerName, { port: newPort })
|
|
889
|
-
console.log(
|
|
920
|
+
console.log(uiSuccess(`Changed port from ${config.port} to ${newPort}`))
|
|
890
921
|
|
|
891
922
|
// Continue editing
|
|
892
923
|
return await handleEditContainer(containerName)
|
|
@@ -928,40 +959,43 @@ async function handleEditContainer(
|
|
|
928
959
|
// - ends with /
|
|
929
960
|
// - exists and is a directory
|
|
930
961
|
// - doesn't have a database file extension (assume it's a directory path)
|
|
931
|
-
const isDirectory =
|
|
962
|
+
const isDirectory =
|
|
963
|
+
expandedPath.endsWith('/') ||
|
|
932
964
|
(existsSync(expandedPath) && statSync(expandedPath).isDirectory()) ||
|
|
933
965
|
!hasDbExtension
|
|
934
966
|
|
|
935
967
|
let finalPath: string
|
|
936
968
|
if (isDirectory) {
|
|
937
969
|
// Remove trailing slash if present, then append filename
|
|
938
|
-
const dirPath = expandedPath.endsWith('/')
|
|
970
|
+
const dirPath = expandedPath.endsWith('/')
|
|
971
|
+
? expandedPath.slice(0, -1)
|
|
972
|
+
: expandedPath
|
|
939
973
|
finalPath = join(dirPath, currentFileName)
|
|
940
974
|
} else {
|
|
941
975
|
finalPath = expandedPath
|
|
942
976
|
}
|
|
943
977
|
|
|
944
978
|
if (finalPath === config.database) {
|
|
945
|
-
console.log(
|
|
979
|
+
console.log(uiInfo('Location unchanged'))
|
|
946
980
|
return await handleEditContainer(containerName)
|
|
947
981
|
}
|
|
948
982
|
|
|
949
983
|
// Check if source file exists
|
|
950
984
|
if (!existsSync(config.database)) {
|
|
951
|
-
console.log(
|
|
985
|
+
console.log(uiError(`Source file not found: ${config.database}`))
|
|
952
986
|
return await handleEditContainer(containerName)
|
|
953
987
|
}
|
|
954
988
|
|
|
955
989
|
// Check if destination already exists
|
|
956
990
|
if (existsSync(finalPath)) {
|
|
957
|
-
console.log(
|
|
991
|
+
console.log(uiError(`Destination file already exists: ${finalPath}`))
|
|
958
992
|
return await handleEditContainer(containerName)
|
|
959
993
|
}
|
|
960
994
|
|
|
961
995
|
// Check if destination directory exists
|
|
962
996
|
const destDir = dirname(finalPath)
|
|
963
997
|
if (!existsSync(destDir)) {
|
|
964
|
-
console.log(
|
|
998
|
+
console.log(uiWarning(`Directory does not exist: ${destDir}`))
|
|
965
999
|
const { createDir } = await inquirer.prompt<{ createDir: string }>([
|
|
966
1000
|
{
|
|
967
1001
|
type: 'list',
|
|
@@ -980,9 +1014,13 @@ async function handleEditContainer(
|
|
|
980
1014
|
|
|
981
1015
|
try {
|
|
982
1016
|
mkdirSync(destDir, { recursive: true })
|
|
983
|
-
console.log(
|
|
984
|
-
} catch (
|
|
985
|
-
console.log(
|
|
1017
|
+
console.log(uiSuccess(`Created directory: ${destDir}`))
|
|
1018
|
+
} catch (mkdirError) {
|
|
1019
|
+
console.log(
|
|
1020
|
+
uiError(
|
|
1021
|
+
`Failed to create directory: ${(mkdirError as Error).message}`,
|
|
1022
|
+
),
|
|
1023
|
+
)
|
|
986
1024
|
return await handleEditContainer(containerName)
|
|
987
1025
|
}
|
|
988
1026
|
}
|
|
@@ -1020,15 +1058,17 @@ async function handleEditContainer(
|
|
|
1020
1058
|
}
|
|
1021
1059
|
|
|
1022
1060
|
// Update the container config and SQLite registry
|
|
1023
|
-
await containerManager.updateConfig(containerName, {
|
|
1061
|
+
await containerManager.updateConfig(containerName, {
|
|
1062
|
+
database: finalPath,
|
|
1063
|
+
})
|
|
1024
1064
|
await sqliteRegistry.update(containerName, { filePath: finalPath })
|
|
1025
1065
|
spinner.succeed(`Moved database to ${finalPath}`)
|
|
1026
1066
|
|
|
1027
1067
|
// Wait for user to see success message before refreshing
|
|
1028
1068
|
await pressEnterToContinue()
|
|
1029
|
-
} catch (
|
|
1069
|
+
} catch (error) {
|
|
1030
1070
|
spinner.fail('Failed to move database file')
|
|
1031
|
-
console.log(
|
|
1071
|
+
console.log(uiError((error as Error).message))
|
|
1032
1072
|
await pressEnterToContinue()
|
|
1033
1073
|
}
|
|
1034
1074
|
|
|
@@ -1045,7 +1085,7 @@ async function handleCloneFromSubmenu(
|
|
|
1045
1085
|
): Promise<void> {
|
|
1046
1086
|
const sourceConfig = await containerManager.getConfig(sourceName)
|
|
1047
1087
|
if (!sourceConfig) {
|
|
1048
|
-
console.log(
|
|
1088
|
+
console.log(uiError(`Container "${sourceName}" not found`))
|
|
1049
1089
|
return
|
|
1050
1090
|
}
|
|
1051
1091
|
|
|
@@ -1066,8 +1106,10 @@ async function handleCloneFromSubmenu(
|
|
|
1066
1106
|
])
|
|
1067
1107
|
|
|
1068
1108
|
// Check if target container already exists
|
|
1069
|
-
if (
|
|
1070
|
-
|
|
1109
|
+
if (
|
|
1110
|
+
await containerManager.exists(targetName, { engine: sourceConfig.engine })
|
|
1111
|
+
) {
|
|
1112
|
+
console.log(uiError(`Container "${targetName}" already exists`))
|
|
1071
1113
|
return
|
|
1072
1114
|
}
|
|
1073
1115
|
|
|
@@ -1086,9 +1128,9 @@ async function handleCloneFromSubmenu(
|
|
|
1086
1128
|
console.log(connectionBox(targetName, connectionString, newConfig.port))
|
|
1087
1129
|
|
|
1088
1130
|
await showContainerSubmenu(targetName, showMainMenu)
|
|
1089
|
-
} catch (
|
|
1131
|
+
} catch (error) {
|
|
1090
1132
|
spinner.fail(`Failed to clone "${sourceName}"`)
|
|
1091
|
-
console.log(
|
|
1133
|
+
console.log(uiError((error as Error).message))
|
|
1092
1134
|
await pressEnterToContinue()
|
|
1093
1135
|
}
|
|
1094
1136
|
}
|
|
@@ -1096,7 +1138,7 @@ async function handleCloneFromSubmenu(
|
|
|
1096
1138
|
async function handleDelete(containerName: string): Promise<void> {
|
|
1097
1139
|
const config = await containerManager.getConfig(containerName)
|
|
1098
1140
|
if (!config) {
|
|
1099
|
-
console.error(
|
|
1141
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
1100
1142
|
return
|
|
1101
1143
|
}
|
|
1102
1144
|
|
|
@@ -1106,7 +1148,7 @@ async function handleDelete(containerName: string): Promise<void> {
|
|
|
1106
1148
|
)
|
|
1107
1149
|
|
|
1108
1150
|
if (!confirmed) {
|
|
1109
|
-
console.log(
|
|
1151
|
+
console.log(uiWarning('Deletion cancelled'))
|
|
1110
1152
|
return
|
|
1111
1153
|
}
|
|
1112
1154
|
|
|
@@ -4,7 +4,7 @@ import { rm } from 'fs/promises'
|
|
|
4
4
|
import { containerManager } from '../../../core/container-manager'
|
|
5
5
|
import { processManager } from '../../../core/process-manager'
|
|
6
6
|
import { createSpinner } from '../../ui/spinner'
|
|
7
|
-
import { header,
|
|
7
|
+
import { header, uiError, uiWarning, uiInfo, formatBytes } from '../../ui/theme'
|
|
8
8
|
import { promptConfirm } from '../../ui/prompts'
|
|
9
9
|
import { getEngineIcon, ENGINE_ICONS } from '../../constants'
|
|
10
10
|
import {
|
|
@@ -37,7 +37,7 @@ export async function handleEngines(): Promise<void> {
|
|
|
37
37
|
const engines = await getInstalledEngines()
|
|
38
38
|
|
|
39
39
|
if (engines.length === 0) {
|
|
40
|
-
console.log(
|
|
40
|
+
console.log(uiInfo('No engines installed yet.'))
|
|
41
41
|
console.log(
|
|
42
42
|
chalk.gray(
|
|
43
43
|
' PostgreSQL engines are downloaded automatically when you create a container.',
|
|
@@ -128,7 +128,9 @@ export async function handleEngines(): Promise<void> {
|
|
|
128
128
|
console.log(chalk.gray(` MySQL: system-installed at ${mysqlEngine.path}`))
|
|
129
129
|
}
|
|
130
130
|
if (sqliteEngine) {
|
|
131
|
-
console.log(
|
|
131
|
+
console.log(
|
|
132
|
+
chalk.gray(` SQLite: system-installed at ${sqliteEngine.path}`),
|
|
133
|
+
)
|
|
132
134
|
}
|
|
133
135
|
console.log()
|
|
134
136
|
|
|
@@ -212,7 +214,7 @@ async function handleDeleteEngine(
|
|
|
212
214
|
if (usingContainers.length > 0) {
|
|
213
215
|
console.log()
|
|
214
216
|
console.log(
|
|
215
|
-
|
|
217
|
+
uiError(
|
|
216
218
|
`Cannot delete: ${usingContainers.length} container(s) are using ${engineName} ${engineVersion}`,
|
|
217
219
|
),
|
|
218
220
|
)
|
|
@@ -238,7 +240,7 @@ async function handleDeleteEngine(
|
|
|
238
240
|
)
|
|
239
241
|
|
|
240
242
|
if (!confirmed) {
|
|
241
|
-
console.log(
|
|
243
|
+
console.log(uiWarning('Deletion cancelled'))
|
|
242
244
|
return
|
|
243
245
|
}
|
|
244
246
|
|
|
@@ -248,8 +250,8 @@ async function handleDeleteEngine(
|
|
|
248
250
|
try {
|
|
249
251
|
await rm(enginePath, { recursive: true, force: true })
|
|
250
252
|
spinner.succeed(`Deleted ${engineName} ${engineVersion}`)
|
|
251
|
-
} catch (
|
|
252
|
-
const e =
|
|
253
|
+
} catch (error) {
|
|
254
|
+
const e = error as Error
|
|
253
255
|
spinner.fail(`Failed to delete: ${e.message}`)
|
|
254
256
|
}
|
|
255
257
|
}
|
|
@@ -272,7 +274,7 @@ async function handleMysqlInfo(mysqldPath: string): Promise<void> {
|
|
|
272
274
|
|
|
273
275
|
if (mysqlContainers.length > 0) {
|
|
274
276
|
console.log(
|
|
275
|
-
|
|
277
|
+
uiWarning(
|
|
276
278
|
`${mysqlContainers.length} container(s) are using ${displayName}:`,
|
|
277
279
|
),
|
|
278
280
|
)
|
|
@@ -438,7 +440,9 @@ async function handleSqliteInfo(sqlitePath: string): Promise<void> {
|
|
|
438
440
|
const sqliteContainers = containers.filter((c) => c.engine === 'sqlite')
|
|
439
441
|
|
|
440
442
|
if (sqliteContainers.length > 0) {
|
|
441
|
-
console.log(
|
|
443
|
+
console.log(
|
|
444
|
+
uiInfo(`${sqliteContainers.length} SQLite database(s) registered:`),
|
|
445
|
+
)
|
|
442
446
|
console.log()
|
|
443
447
|
for (const c of sqliteContainers) {
|
|
444
448
|
const status =
|
|
@@ -471,9 +475,15 @@ async function handleSqliteInfo(sqlitePath: string): Promise<void> {
|
|
|
471
475
|
|
|
472
476
|
console.log(chalk.white(' Notes:'))
|
|
473
477
|
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
474
|
-
console.log(
|
|
478
|
+
console.log(
|
|
479
|
+
chalk.gray(
|
|
480
|
+
' • SQLite is typically pre-installed on macOS and most Linux distributions',
|
|
481
|
+
),
|
|
482
|
+
)
|
|
475
483
|
console.log(chalk.gray(' • No server process - databases are just files'))
|
|
476
|
-
console.log(
|
|
484
|
+
console.log(
|
|
485
|
+
chalk.gray(' • Use "spindb delete <name>" to unregister a database'),
|
|
486
|
+
)
|
|
477
487
|
console.log()
|
|
478
488
|
|
|
479
489
|
await inquirer.prompt([
|
|
@@ -3,7 +3,7 @@ import chalk from 'chalk'
|
|
|
3
3
|
import inquirer from 'inquirer'
|
|
4
4
|
import { containerManager } from '../../../core/container-manager'
|
|
5
5
|
import { promptInstallDependencies } from '../../ui/prompts'
|
|
6
|
-
import { header,
|
|
6
|
+
import { header, uiError } from '../../ui/theme'
|
|
7
7
|
import { getInstalledEngines } from '../../helpers'
|
|
8
8
|
import {
|
|
9
9
|
handleCreate,
|
|
@@ -156,8 +156,8 @@ export const menuCommand = new Command('menu')
|
|
|
156
156
|
.action(async () => {
|
|
157
157
|
try {
|
|
158
158
|
await showMainMenu()
|
|
159
|
-
} catch (
|
|
160
|
-
const e =
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const e = error as Error
|
|
161
161
|
|
|
162
162
|
// Check if this is a missing tool error
|
|
163
163
|
if (
|
|
@@ -177,7 +177,7 @@ export const menuCommand = new Command('menu')
|
|
|
177
177
|
process.exit(1)
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
console.error(
|
|
180
|
+
console.error(uiError(e.message))
|
|
181
181
|
process.exit(1)
|
|
182
182
|
}
|
|
183
183
|
})
|