spindb 0.9.0 → 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/README.md +7 -0
- package/cli/commands/backup.ts +13 -11
- package/cli/commands/clone.ts +18 -8
- package/cli/commands/config.ts +29 -29
- package/cli/commands/connect.ts +51 -39
- package/cli/commands/create.ts +120 -43
- package/cli/commands/delete.ts +8 -8
- package/cli/commands/deps.ts +17 -15
- package/cli/commands/doctor.ts +16 -15
- package/cli/commands/edit.ts +115 -60
- package/cli/commands/engines.ts +50 -17
- package/cli/commands/info.ts +12 -8
- package/cli/commands/list.ts +34 -19
- package/cli/commands/logs.ts +24 -14
- package/cli/commands/menu/backup-handlers.ts +72 -49
- package/cli/commands/menu/container-handlers.ts +140 -80
- package/cli/commands/menu/engine-handlers.ts +145 -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 +105 -43
- 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 +49 -4
- package/cli/ui/prompts.ts +21 -8
- 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 +81 -30
- package/core/error-handler.ts +31 -0
- package/core/platform-service.ts +3 -3
- package/core/port-manager.ts +2 -0
- package/core/process-manager.ts +25 -3
- package/core/start-with-retry.ts +6 -6
- package/core/transaction-manager.ts +6 -6
- package/engines/mysql/backup.ts +53 -36
- package/engines/mysql/index.ts +59 -16
- 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 +13 -2
- package/engines/postgresql/restore.ts +2 -2
- package/engines/postgresql/version-validator.ts +2 -2
- package/engines/sqlite/index.ts +31 -9
- 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
|
|
|
@@ -1043,6 +1083,12 @@ async function handleCloneFromSubmenu(
|
|
|
1043
1083
|
sourceName: string,
|
|
1044
1084
|
showMainMenu: () => Promise<void>,
|
|
1045
1085
|
): Promise<void> {
|
|
1086
|
+
const sourceConfig = await containerManager.getConfig(sourceName)
|
|
1087
|
+
if (!sourceConfig) {
|
|
1088
|
+
console.log(uiError(`Container "${sourceName}" not found`))
|
|
1089
|
+
return
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1046
1092
|
const { targetName } = await inquirer.prompt<{ targetName: string }>([
|
|
1047
1093
|
{
|
|
1048
1094
|
type: 'input',
|
|
@@ -1059,26 +1105,40 @@ async function handleCloneFromSubmenu(
|
|
|
1059
1105
|
},
|
|
1060
1106
|
])
|
|
1061
1107
|
|
|
1108
|
+
// Check if target container already exists
|
|
1109
|
+
if (
|
|
1110
|
+
await containerManager.exists(targetName, { engine: sourceConfig.engine })
|
|
1111
|
+
) {
|
|
1112
|
+
console.log(uiError(`Container "${targetName}" already exists`))
|
|
1113
|
+
return
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1062
1116
|
const spinner = createSpinner(`Cloning ${sourceName} to ${targetName}...`)
|
|
1063
1117
|
spinner.start()
|
|
1064
1118
|
|
|
1065
|
-
|
|
1119
|
+
try {
|
|
1120
|
+
const newConfig = await containerManager.clone(sourceName, targetName)
|
|
1066
1121
|
|
|
1067
|
-
|
|
1122
|
+
spinner.succeed(`Cloned "${sourceName}" to "${targetName}"`)
|
|
1068
1123
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1124
|
+
const engine = getEngine(newConfig.engine)
|
|
1125
|
+
const connectionString = engine.getConnectionString(newConfig)
|
|
1071
1126
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1127
|
+
console.log()
|
|
1128
|
+
console.log(connectionBox(targetName, connectionString, newConfig.port))
|
|
1074
1129
|
|
|
1075
|
-
|
|
1130
|
+
await showContainerSubmenu(targetName, showMainMenu)
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
spinner.fail(`Failed to clone "${sourceName}"`)
|
|
1133
|
+
console.log(uiError((error as Error).message))
|
|
1134
|
+
await pressEnterToContinue()
|
|
1135
|
+
}
|
|
1076
1136
|
}
|
|
1077
1137
|
|
|
1078
1138
|
async function handleDelete(containerName: string): Promise<void> {
|
|
1079
1139
|
const config = await containerManager.getConfig(containerName)
|
|
1080
1140
|
if (!config) {
|
|
1081
|
-
console.error(
|
|
1141
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
1082
1142
|
return
|
|
1083
1143
|
}
|
|
1084
1144
|
|
|
@@ -1088,7 +1148,7 @@ async function handleDelete(containerName: string): Promise<void> {
|
|
|
1088
1148
|
)
|
|
1089
1149
|
|
|
1090
1150
|
if (!confirmed) {
|
|
1091
|
-
console.log(
|
|
1151
|
+
console.log(uiWarning('Deletion cancelled'))
|
|
1092
1152
|
return
|
|
1093
1153
|
}
|
|
1094
1154
|
|