spindb 0.7.3 → 0.7.5
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 +1 -30
- package/cli/commands/clone.ts +0 -6
- package/cli/commands/connect.ts +0 -16
- package/cli/commands/create.ts +4 -55
- package/cli/commands/delete.ts +0 -6
- package/cli/commands/edit.ts +0 -26
- package/cli/commands/info.ts +0 -20
- package/cli/commands/list.ts +0 -9
- package/cli/commands/logs.ts +0 -12
- package/cli/commands/menu/backup-handlers.ts +14 -63
- package/cli/commands/menu/container-handlers.ts +30 -37
- package/cli/commands/menu/engine-handlers.ts +0 -20
- package/cli/commands/menu/index.ts +2 -7
- package/cli/commands/menu/shell-handlers.ts +0 -11
- package/cli/commands/menu/sql-handlers.ts +0 -3
- package/cli/commands/restore.ts +2 -28
- package/cli/commands/run.ts +0 -11
- package/cli/commands/start.ts +2 -10
- package/cli/commands/stop.ts +0 -5
- package/cli/commands/url.ts +0 -9
- package/package.json +1 -1
|
@@ -12,7 +12,6 @@ import { getEngine } from '../../../engines'
|
|
|
12
12
|
import { defaults } from '../../../config/defaults'
|
|
13
13
|
import { getPostgresHomebrewPackage } from '../../../config/engine-defaults'
|
|
14
14
|
import { updatePostgresClientTools } from '../../../engines/postgresql/binary-manager'
|
|
15
|
-
import { type Engine } from '../../../types'
|
|
16
15
|
import {
|
|
17
16
|
promptCreateOptions,
|
|
18
17
|
promptContainerName,
|
|
@@ -33,29 +32,21 @@ import {
|
|
|
33
32
|
formatBytes,
|
|
34
33
|
} from '../../ui/theme'
|
|
35
34
|
import { getEngineIcon } from '../../constants'
|
|
35
|
+
import { type Engine } from '../../../types'
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Generate a timestamp string for backup filenames
|
|
39
|
-
*/
|
|
40
37
|
function generateBackupTimestamp(): string {
|
|
41
38
|
const now = new Date()
|
|
42
39
|
return now.toISOString().replace(/:/g, '').split('.')[0]
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
/**
|
|
46
|
-
* Get file extension for backup format
|
|
47
|
-
*/
|
|
48
42
|
function getBackupExtension(format: 'sql' | 'dump', engine: string): string {
|
|
49
43
|
if (format === 'sql') {
|
|
50
44
|
return '.sql'
|
|
51
45
|
}
|
|
46
|
+
// MySQL dump is gzipped SQL, PostgreSQL dump is custom format
|
|
52
47
|
return engine === 'mysql' ? '.sql.gz' : '.dump'
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
/**
|
|
56
|
-
* Create a new container for the restore flow
|
|
57
|
-
* Returns the container name and config if successful, null if cancelled/error
|
|
58
|
-
*/
|
|
59
50
|
export async function handleCreateForRestore(): Promise<{
|
|
60
51
|
name: string
|
|
61
52
|
config: NonNullable<Awaited<ReturnType<typeof containerManager.getConfig>>>
|
|
@@ -71,7 +62,6 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
71
62
|
|
|
72
63
|
const dbEngine = getEngine(engine)
|
|
73
64
|
|
|
74
|
-
// Check if port is currently in use
|
|
75
65
|
const portAvailable = await portManager.isPortAvailable(port)
|
|
76
66
|
if (!portAvailable) {
|
|
77
67
|
console.log(
|
|
@@ -80,7 +70,6 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
80
70
|
return null
|
|
81
71
|
}
|
|
82
72
|
|
|
83
|
-
// Ensure binaries
|
|
84
73
|
const binarySpinner = createSpinner(
|
|
85
74
|
`Checking PostgreSQL ${version} binaries...`,
|
|
86
75
|
)
|
|
@@ -97,13 +86,11 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
97
86
|
binarySpinner.succeed(`PostgreSQL ${version} binaries downloaded`)
|
|
98
87
|
}
|
|
99
88
|
|
|
100
|
-
// Check if container name already exists and prompt for new name if needed
|
|
101
89
|
while (await containerManager.exists(containerName)) {
|
|
102
90
|
console.log(chalk.yellow(` Container "${containerName}" already exists.`))
|
|
103
91
|
containerName = await promptContainerName()
|
|
104
92
|
}
|
|
105
93
|
|
|
106
|
-
// Create container
|
|
107
94
|
const createSpinnerInstance = createSpinner('Creating container...')
|
|
108
95
|
createSpinnerInstance.start()
|
|
109
96
|
|
|
@@ -116,7 +103,6 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
116
103
|
|
|
117
104
|
createSpinnerInstance.succeed('Container created')
|
|
118
105
|
|
|
119
|
-
// Initialize database cluster
|
|
120
106
|
const initSpinner = createSpinner('Initializing database cluster...')
|
|
121
107
|
initSpinner.start()
|
|
122
108
|
|
|
@@ -126,7 +112,6 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
126
112
|
|
|
127
113
|
initSpinner.succeed('Database cluster initialized')
|
|
128
114
|
|
|
129
|
-
// Start container
|
|
130
115
|
const startSpinner = createSpinner('Starting PostgreSQL...')
|
|
131
116
|
startSpinner.start()
|
|
132
117
|
|
|
@@ -141,7 +126,6 @@ export async function handleCreateForRestore(): Promise<{
|
|
|
141
126
|
|
|
142
127
|
startSpinner.succeed('PostgreSQL started')
|
|
143
128
|
|
|
144
|
-
// Create the user's database (if different from 'postgres')
|
|
145
129
|
if (database !== 'postgres') {
|
|
146
130
|
const dbSpinner = createSpinner(`Creating database "${database}"...`)
|
|
147
131
|
dbSpinner.start()
|
|
@@ -162,7 +146,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
162
146
|
const containers = await containerManager.list()
|
|
163
147
|
const running = containers.filter((c) => c.status === 'running')
|
|
164
148
|
|
|
165
|
-
// Build choices: running containers + create new option
|
|
166
149
|
const choices = [
|
|
167
150
|
...running.map((c) => ({
|
|
168
151
|
name: `${c.name} ${chalk.gray(`(${getEngineIcon(c.engine)} ${c.engine} ${c.version}, port ${c.port})`)} ${chalk.green('● running')}`,
|
|
@@ -193,9 +176,8 @@ export async function handleRestore(): Promise<void> {
|
|
|
193
176
|
let config: Awaited<ReturnType<typeof containerManager.getConfig>>
|
|
194
177
|
|
|
195
178
|
if (selectedContainer === '__create_new__') {
|
|
196
|
-
// Run the create flow first
|
|
197
179
|
const createResult = await handleCreateForRestore()
|
|
198
|
-
if (!createResult) return
|
|
180
|
+
if (!createResult) return
|
|
199
181
|
containerName = createResult.name
|
|
200
182
|
config = createResult.config
|
|
201
183
|
} else {
|
|
@@ -207,7 +189,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
207
189
|
}
|
|
208
190
|
}
|
|
209
191
|
|
|
210
|
-
// Check for required client tools BEFORE doing anything
|
|
211
192
|
const depsSpinner = createSpinner('Checking required tools...')
|
|
212
193
|
depsSpinner.start()
|
|
213
194
|
|
|
@@ -217,7 +198,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
217
198
|
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
218
199
|
)
|
|
219
200
|
|
|
220
|
-
// Offer to install
|
|
221
201
|
const installed = await promptInstallDependencies(
|
|
222
202
|
missingDeps[0].binary,
|
|
223
203
|
config.engine,
|
|
@@ -227,7 +207,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
227
207
|
return
|
|
228
208
|
}
|
|
229
209
|
|
|
230
|
-
// Verify installation worked
|
|
231
210
|
missingDeps = await getMissingDependencies(config.engine)
|
|
232
211
|
if (missingDeps.length > 0) {
|
|
233
212
|
console.log(
|
|
@@ -244,7 +223,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
244
223
|
depsSpinner.succeed('Required tools available')
|
|
245
224
|
}
|
|
246
225
|
|
|
247
|
-
// Ask for restore source
|
|
248
226
|
const { restoreSource } = await inquirer.prompt<{
|
|
249
227
|
restoreSource: 'file' | 'connection'
|
|
250
228
|
}>([
|
|
@@ -269,8 +247,9 @@ export async function handleRestore(): Promise<void> {
|
|
|
269
247
|
let isTempFile = false
|
|
270
248
|
|
|
271
249
|
if (restoreSource === 'connection') {
|
|
272
|
-
|
|
273
|
-
|
|
250
|
+
console.log(
|
|
251
|
+
chalk.gray(' Enter connection string, or press Enter to go back'),
|
|
252
|
+
)
|
|
274
253
|
const { connectionString } = await inquirer.prompt<{
|
|
275
254
|
connectionString: string
|
|
276
255
|
}>([
|
|
@@ -279,7 +258,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
279
258
|
name: 'connectionString',
|
|
280
259
|
message: 'Connection string:',
|
|
281
260
|
validate: (input: string) => {
|
|
282
|
-
if (!input) return true
|
|
261
|
+
if (!input) return true
|
|
283
262
|
if (
|
|
284
263
|
!input.startsWith('postgresql://') &&
|
|
285
264
|
!input.startsWith('postgres://')
|
|
@@ -291,20 +270,18 @@ export async function handleRestore(): Promise<void> {
|
|
|
291
270
|
},
|
|
292
271
|
])
|
|
293
272
|
|
|
294
|
-
// Empty input = go back
|
|
295
273
|
if (!connectionString.trim()) {
|
|
296
274
|
return
|
|
297
275
|
}
|
|
298
276
|
|
|
299
277
|
const engine = getEngine(config.engine)
|
|
300
278
|
|
|
301
|
-
// Create temp file for the dump
|
|
302
279
|
const timestamp = Date.now()
|
|
303
280
|
const tempDumpPath = join(tmpdir(), `spindb-dump-${timestamp}.dump`)
|
|
304
281
|
|
|
305
282
|
let dumpSuccess = false
|
|
306
283
|
let attempts = 0
|
|
307
|
-
const maxAttempts = 2
|
|
284
|
+
const maxAttempts = 2
|
|
308
285
|
|
|
309
286
|
while (!dumpSuccess && attempts < maxAttempts) {
|
|
310
287
|
attempts++
|
|
@@ -321,14 +298,12 @@ export async function handleRestore(): Promise<void> {
|
|
|
321
298
|
const e = err as Error
|
|
322
299
|
dumpSpinner.fail('Failed to create dump')
|
|
323
300
|
|
|
324
|
-
// Check if this is a missing tool error
|
|
325
301
|
if (
|
|
326
302
|
e.message.includes('pg_dump not found') ||
|
|
327
303
|
e.message.includes('ENOENT')
|
|
328
304
|
) {
|
|
329
305
|
const installed = await promptInstallDependencies('pg_dump')
|
|
330
306
|
if (installed) {
|
|
331
|
-
// Loop will retry
|
|
332
307
|
continue
|
|
333
308
|
}
|
|
334
309
|
} else {
|
|
@@ -338,14 +313,12 @@ export async function handleRestore(): Promise<void> {
|
|
|
338
313
|
console.log()
|
|
339
314
|
}
|
|
340
315
|
|
|
341
|
-
// Clean up temp file if it was created
|
|
342
316
|
try {
|
|
343
317
|
await rm(tempDumpPath, { force: true })
|
|
344
318
|
} catch {
|
|
345
319
|
// Ignore cleanup errors
|
|
346
320
|
}
|
|
347
321
|
|
|
348
|
-
// Wait for user to see the error
|
|
349
322
|
await inquirer.prompt([
|
|
350
323
|
{
|
|
351
324
|
type: 'input',
|
|
@@ -357,18 +330,19 @@ export async function handleRestore(): Promise<void> {
|
|
|
357
330
|
}
|
|
358
331
|
}
|
|
359
332
|
|
|
360
|
-
// Safety check - should never reach here without backupPath set
|
|
361
333
|
if (!dumpSuccess) {
|
|
362
334
|
console.log(error('Failed to create dump after retries'))
|
|
363
335
|
return
|
|
364
336
|
}
|
|
365
337
|
} else {
|
|
366
|
-
// Get backup file path
|
|
367
|
-
// Strip quotes that terminals add when drag-and-dropping files
|
|
368
338
|
const stripQuotes = (path: string) =>
|
|
369
339
|
path.replace(/^['"]|['"]$/g, '').trim()
|
|
370
340
|
|
|
371
|
-
console.log(
|
|
341
|
+
console.log(
|
|
342
|
+
chalk.gray(
|
|
343
|
+
' Drag & drop, enter path (abs or rel), or press Enter to go back',
|
|
344
|
+
),
|
|
345
|
+
)
|
|
372
346
|
const { backupPath: rawBackupPath } = await inquirer.prompt<{
|
|
373
347
|
backupPath: string
|
|
374
348
|
}>([
|
|
@@ -377,7 +351,7 @@ export async function handleRestore(): Promise<void> {
|
|
|
377
351
|
name: 'backupPath',
|
|
378
352
|
message: 'Backup file path:',
|
|
379
353
|
validate: (input: string) => {
|
|
380
|
-
if (!input) return true
|
|
354
|
+
if (!input) return true
|
|
381
355
|
const cleanPath = stripQuotes(input)
|
|
382
356
|
if (!existsSync(cleanPath)) return 'File not found'
|
|
383
357
|
return true
|
|
@@ -385,7 +359,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
385
359
|
},
|
|
386
360
|
])
|
|
387
361
|
|
|
388
|
-
// Empty input = go back
|
|
389
362
|
if (!rawBackupPath.trim()) {
|
|
390
363
|
return
|
|
391
364
|
}
|
|
@@ -397,21 +370,18 @@ export async function handleRestore(): Promise<void> {
|
|
|
397
370
|
|
|
398
371
|
const engine = getEngine(config.engine)
|
|
399
372
|
|
|
400
|
-
// Detect format
|
|
401
373
|
const detectSpinner = createSpinner('Detecting backup format...')
|
|
402
374
|
detectSpinner.start()
|
|
403
375
|
|
|
404
376
|
const format = await engine.detectBackupFormat(backupPath)
|
|
405
377
|
detectSpinner.succeed(`Detected: ${format.description}`)
|
|
406
378
|
|
|
407
|
-
// Create database
|
|
408
379
|
const dbSpinner = createSpinner(`Creating database "${databaseName}"...`)
|
|
409
380
|
dbSpinner.start()
|
|
410
381
|
|
|
411
382
|
await engine.createDatabase(config, databaseName)
|
|
412
383
|
dbSpinner.succeed(`Database "${databaseName}" ready`)
|
|
413
384
|
|
|
414
|
-
// Restore
|
|
415
385
|
const restoreSpinner = createSpinner('Restoring backup...')
|
|
416
386
|
restoreSpinner.start()
|
|
417
387
|
|
|
@@ -425,7 +395,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
425
395
|
} else {
|
|
426
396
|
const stderr = result.stderr || ''
|
|
427
397
|
|
|
428
|
-
// Check for version compatibility errors
|
|
429
398
|
if (
|
|
430
399
|
stderr.includes('unsupported version') ||
|
|
431
400
|
stderr.includes('Archive version') ||
|
|
@@ -438,7 +407,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
438
407
|
warning('Your pg_restore version is too old for this backup file.'),
|
|
439
408
|
)
|
|
440
409
|
|
|
441
|
-
// Clean up the failed database since restore didn't actually work
|
|
442
410
|
console.log(chalk.yellow('Cleaning up failed database...'))
|
|
443
411
|
try {
|
|
444
412
|
await engine.dropDatabase(config, databaseName)
|
|
@@ -451,7 +419,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
451
419
|
|
|
452
420
|
console.log()
|
|
453
421
|
|
|
454
|
-
// Extract version info from error message
|
|
455
422
|
const versionMatch = stderr.match(/PostgreSQL (\d+)/)
|
|
456
423
|
const requiredVersion = versionMatch ? versionMatch[1] : '17'
|
|
457
424
|
|
|
@@ -462,7 +429,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
462
429
|
)
|
|
463
430
|
console.log()
|
|
464
431
|
|
|
465
|
-
// Ask user if they want to upgrade
|
|
466
432
|
const { shouldUpgrade } = await inquirer.prompt({
|
|
467
433
|
type: 'list',
|
|
468
434
|
name: 'shouldUpgrade',
|
|
@@ -557,13 +523,10 @@ export async function handleRestore(): Promise<void> {
|
|
|
557
523
|
return
|
|
558
524
|
}
|
|
559
525
|
} else {
|
|
560
|
-
// Regular warnings/errors - show as before
|
|
561
526
|
restoreSpinner.warn('Restore completed with warnings')
|
|
562
|
-
// Show stderr output so user can see what went wrong
|
|
563
527
|
if (result.stderr) {
|
|
564
528
|
console.log()
|
|
565
529
|
console.log(chalk.yellow(' Warnings/Errors:'))
|
|
566
|
-
// Show first 20 lines of stderr to avoid overwhelming output
|
|
567
530
|
const lines = result.stderr.split('\n').filter((l) => l.trim())
|
|
568
531
|
const displayLines = lines.slice(0, 20)
|
|
569
532
|
for (const line of displayLines) {
|
|
@@ -576,7 +539,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
576
539
|
}
|
|
577
540
|
}
|
|
578
541
|
|
|
579
|
-
// Only show success message if restore actually succeeded
|
|
580
542
|
if (result.code === 0 || !result.stderr) {
|
|
581
543
|
const connectionString = engine.getConnectionString(config, databaseName)
|
|
582
544
|
console.log()
|
|
@@ -584,7 +546,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
584
546
|
console.log(chalk.gray(' Connection string:'))
|
|
585
547
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
586
548
|
|
|
587
|
-
// Copy connection string to clipboard using platform service
|
|
588
549
|
const copied = await platformService.copyToClipboard(connectionString)
|
|
589
550
|
if (copied) {
|
|
590
551
|
console.log(chalk.gray(' ✓ Connection string copied to clipboard'))
|
|
@@ -595,7 +556,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
595
556
|
console.log()
|
|
596
557
|
}
|
|
597
558
|
|
|
598
|
-
// Clean up temp file if we created one
|
|
599
559
|
if (isTempFile) {
|
|
600
560
|
try {
|
|
601
561
|
await rm(backupPath, { force: true })
|
|
@@ -604,7 +564,6 @@ export async function handleRestore(): Promise<void> {
|
|
|
604
564
|
}
|
|
605
565
|
}
|
|
606
566
|
|
|
607
|
-
// Wait for user to see the result before returning to menu
|
|
608
567
|
await inquirer.prompt([
|
|
609
568
|
{
|
|
610
569
|
type: 'input',
|
|
@@ -630,7 +589,6 @@ export async function handleBackup(): Promise<void> {
|
|
|
630
589
|
return
|
|
631
590
|
}
|
|
632
591
|
|
|
633
|
-
// Select container
|
|
634
592
|
const containerName = await promptContainerSelect(
|
|
635
593
|
running,
|
|
636
594
|
'Select container to backup:',
|
|
@@ -645,7 +603,6 @@ export async function handleBackup(): Promise<void> {
|
|
|
645
603
|
|
|
646
604
|
const engine = getEngine(config.engine)
|
|
647
605
|
|
|
648
|
-
// Check for required tools
|
|
649
606
|
const depsSpinner = createSpinner('Checking required tools...')
|
|
650
607
|
depsSpinner.start()
|
|
651
608
|
|
|
@@ -680,7 +637,6 @@ export async function handleBackup(): Promise<void> {
|
|
|
680
637
|
depsSpinner.succeed('Required tools available')
|
|
681
638
|
}
|
|
682
639
|
|
|
683
|
-
// Select database
|
|
684
640
|
const databases = config.databases || [config.database]
|
|
685
641
|
let databaseName: string
|
|
686
642
|
|
|
@@ -693,18 +649,14 @@ export async function handleBackup(): Promise<void> {
|
|
|
693
649
|
databaseName = databases[0]
|
|
694
650
|
}
|
|
695
651
|
|
|
696
|
-
// Select format
|
|
697
652
|
const format = await promptBackupFormat(config.engine)
|
|
698
653
|
|
|
699
|
-
// Get filename
|
|
700
654
|
const defaultFilename = `${containerName}-${databaseName}-backup-${generateBackupTimestamp()}`
|
|
701
655
|
const filename = await promptBackupFilename(defaultFilename)
|
|
702
656
|
|
|
703
|
-
// Build output path
|
|
704
657
|
const extension = getBackupExtension(format, config.engine)
|
|
705
658
|
const outputPath = join(process.cwd(), `${filename}${extension}`)
|
|
706
659
|
|
|
707
|
-
// Create backup
|
|
708
660
|
const backupSpinner = createSpinner(
|
|
709
661
|
`Creating ${format === 'sql' ? 'SQL' : 'dump'} backup of "${databaseName}"...`,
|
|
710
662
|
)
|
|
@@ -733,7 +685,6 @@ export async function handleBackup(): Promise<void> {
|
|
|
733
685
|
console.log()
|
|
734
686
|
}
|
|
735
687
|
|
|
736
|
-
// Wait for user to see the result
|
|
737
688
|
await inquirer.prompt([
|
|
738
689
|
{
|
|
739
690
|
type: 'input',
|
|
@@ -9,7 +9,6 @@ import { processManager } from '../../../core/process-manager'
|
|
|
9
9
|
import { getEngine } from '../../../engines'
|
|
10
10
|
import { defaults } from '../../../config/defaults'
|
|
11
11
|
import { paths } from '../../../config/paths'
|
|
12
|
-
import { type Engine } from '../../../types'
|
|
13
12
|
import {
|
|
14
13
|
promptCreateOptions,
|
|
15
14
|
promptContainerName,
|
|
@@ -28,9 +27,10 @@ import {
|
|
|
28
27
|
formatBytes,
|
|
29
28
|
} from '../../ui/theme'
|
|
30
29
|
import { getEngineIcon } from '../../constants'
|
|
31
|
-
import { type MenuChoice } from './shared'
|
|
32
30
|
import { handleOpenShell, handleCopyConnectionString } from './shell-handlers'
|
|
33
31
|
import { handleRunSql, handleViewLogs } from './sql-handlers'
|
|
32
|
+
import { type Engine } from '../../../types'
|
|
33
|
+
import { type MenuChoice } from './shared'
|
|
34
34
|
|
|
35
35
|
export async function handleCreate(): Promise<void> {
|
|
36
36
|
console.log()
|
|
@@ -44,7 +44,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
44
44
|
|
|
45
45
|
const dbEngine = getEngine(engine)
|
|
46
46
|
|
|
47
|
-
// Check for required client tools BEFORE creating anything
|
|
48
47
|
const depsSpinner = createSpinner('Checking required tools...')
|
|
49
48
|
depsSpinner.start()
|
|
50
49
|
|
|
@@ -54,7 +53,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
54
53
|
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
55
54
|
)
|
|
56
55
|
|
|
57
|
-
// Offer to install
|
|
58
56
|
const installed = await promptInstallDependencies(
|
|
59
57
|
missingDeps[0].binary,
|
|
60
58
|
engine,
|
|
@@ -64,7 +62,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
64
62
|
return
|
|
65
63
|
}
|
|
66
64
|
|
|
67
|
-
// Verify installation worked
|
|
68
65
|
missingDeps = await getMissingDependencies(engine)
|
|
69
66
|
if (missingDeps.length > 0) {
|
|
70
67
|
console.log(
|
|
@@ -81,10 +78,8 @@ export async function handleCreate(): Promise<void> {
|
|
|
81
78
|
depsSpinner.succeed('Required tools available')
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
// Check if port is currently in use
|
|
85
81
|
const portAvailable = await portManager.isPortAvailable(port)
|
|
86
82
|
|
|
87
|
-
// Ensure binaries
|
|
88
83
|
const binarySpinner = createSpinner(
|
|
89
84
|
`Checking PostgreSQL ${version} binaries...`,
|
|
90
85
|
)
|
|
@@ -101,13 +96,11 @@ export async function handleCreate(): Promise<void> {
|
|
|
101
96
|
binarySpinner.succeed(`PostgreSQL ${version} binaries downloaded`)
|
|
102
97
|
}
|
|
103
98
|
|
|
104
|
-
// Check if container name already exists and prompt for new name if needed
|
|
105
99
|
while (await containerManager.exists(containerName)) {
|
|
106
100
|
console.log(chalk.yellow(` Container "${containerName}" already exists.`))
|
|
107
101
|
containerName = await promptContainerName()
|
|
108
102
|
}
|
|
109
103
|
|
|
110
|
-
// Create container
|
|
111
104
|
const createSpinnerInstance = createSpinner('Creating container...')
|
|
112
105
|
createSpinnerInstance.start()
|
|
113
106
|
|
|
@@ -120,7 +113,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
120
113
|
|
|
121
114
|
createSpinnerInstance.succeed('Container created')
|
|
122
115
|
|
|
123
|
-
// Initialize database cluster
|
|
124
116
|
const initSpinner = createSpinner('Initializing database cluster...')
|
|
125
117
|
initSpinner.start()
|
|
126
118
|
|
|
@@ -130,7 +122,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
130
122
|
|
|
131
123
|
initSpinner.succeed('Database cluster initialized')
|
|
132
124
|
|
|
133
|
-
// Start container (only if port is available)
|
|
134
125
|
if (portAvailable) {
|
|
135
126
|
const startSpinner = createSpinner('Starting PostgreSQL...')
|
|
136
127
|
startSpinner.start()
|
|
@@ -143,7 +134,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
143
134
|
|
|
144
135
|
startSpinner.succeed('PostgreSQL started')
|
|
145
136
|
|
|
146
|
-
// Create the user's database (if different from 'postgres')
|
|
147
137
|
if (config && database !== 'postgres') {
|
|
148
138
|
const dbSpinner = createSpinner(`Creating database "${database}"...`)
|
|
149
139
|
dbSpinner.start()
|
|
@@ -153,7 +143,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
153
143
|
dbSpinner.succeed(`Database "${database}" created`)
|
|
154
144
|
}
|
|
155
145
|
|
|
156
|
-
// Show success
|
|
157
146
|
if (config) {
|
|
158
147
|
const connectionString = dbEngine.getConnectionString(config)
|
|
159
148
|
console.log()
|
|
@@ -169,7 +158,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
169
158
|
console.log(chalk.gray(' Connection string:'))
|
|
170
159
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
171
160
|
|
|
172
|
-
// Copy connection string to clipboard using platform service
|
|
173
161
|
try {
|
|
174
162
|
const copied = await platformService.copyToClipboard(connectionString)
|
|
175
163
|
if (copied) {
|
|
@@ -183,7 +171,6 @@ export async function handleCreate(): Promise<void> {
|
|
|
183
171
|
|
|
184
172
|
console.log()
|
|
185
173
|
|
|
186
|
-
// Wait for user to see the result before returning to menu
|
|
187
174
|
await inquirer.prompt([
|
|
188
175
|
{
|
|
189
176
|
type: 'input',
|
|
@@ -231,7 +218,6 @@ export async function handleList(
|
|
|
231
218
|
return
|
|
232
219
|
}
|
|
233
220
|
|
|
234
|
-
// Fetch sizes for running containers in parallel
|
|
235
221
|
const sizes = await Promise.all(
|
|
236
222
|
containers.map(async (container) => {
|
|
237
223
|
if (container.status !== 'running') return null
|
|
@@ -244,7 +230,6 @@ export async function handleList(
|
|
|
244
230
|
}),
|
|
245
231
|
)
|
|
246
232
|
|
|
247
|
-
// Table header
|
|
248
233
|
console.log()
|
|
249
234
|
console.log(
|
|
250
235
|
chalk.gray(' ') +
|
|
@@ -257,7 +242,6 @@ export async function handleList(
|
|
|
257
242
|
)
|
|
258
243
|
console.log(chalk.gray(' ' + '─'.repeat(70)))
|
|
259
244
|
|
|
260
|
-
// Table rows
|
|
261
245
|
for (let i = 0; i < containers.length; i++) {
|
|
262
246
|
const container = containers[i]
|
|
263
247
|
const size = sizes[i]
|
|
@@ -290,7 +274,6 @@ export async function handleList(
|
|
|
290
274
|
),
|
|
291
275
|
)
|
|
292
276
|
|
|
293
|
-
// Container selection with submenu
|
|
294
277
|
console.log()
|
|
295
278
|
const containerChoices = [
|
|
296
279
|
...containers.map((c, i) => {
|
|
@@ -340,7 +323,6 @@ export async function showContainerSubmenu(
|
|
|
340
323
|
return
|
|
341
324
|
}
|
|
342
325
|
|
|
343
|
-
// Check actual running state
|
|
344
326
|
const isRunning = await processManager.isRunning(containerName, {
|
|
345
327
|
engine: config.engine,
|
|
346
328
|
})
|
|
@@ -357,10 +339,15 @@ export async function showContainerSubmenu(
|
|
|
357
339
|
console.log()
|
|
358
340
|
|
|
359
341
|
const actionChoices: MenuChoice[] = [
|
|
360
|
-
// Start or Stop depending on current state
|
|
361
342
|
!isRunning
|
|
362
|
-
? {
|
|
363
|
-
|
|
343
|
+
? {
|
|
344
|
+
name: `${chalk.green('▶')} Start container`,
|
|
345
|
+
value: 'start',
|
|
346
|
+
}
|
|
347
|
+
: {
|
|
348
|
+
name: `${chalk.red('■')} Stop container`,
|
|
349
|
+
value: 'stop',
|
|
350
|
+
},
|
|
364
351
|
{
|
|
365
352
|
name: isRunning
|
|
366
353
|
? `${chalk.blue('⌘')} Open shell`
|
|
@@ -375,10 +362,6 @@ export async function showContainerSubmenu(
|
|
|
375
362
|
value: 'run-sql',
|
|
376
363
|
disabled: isRunning ? false : 'Start container first',
|
|
377
364
|
},
|
|
378
|
-
{
|
|
379
|
-
name: `${chalk.gray('📋')} View logs`,
|
|
380
|
-
value: 'logs',
|
|
381
|
-
},
|
|
382
365
|
{
|
|
383
366
|
name: !isRunning
|
|
384
367
|
? `${chalk.white('⚙')} Edit container`
|
|
@@ -394,6 +377,10 @@ export async function showContainerSubmenu(
|
|
|
394
377
|
disabled: !isRunning ? false : 'Stop container first',
|
|
395
378
|
},
|
|
396
379
|
{ name: `${chalk.magenta('⎘')} Copy connection string`, value: 'copy' },
|
|
380
|
+
{
|
|
381
|
+
name: `${chalk.gray('☰')} View logs`,
|
|
382
|
+
value: 'logs',
|
|
383
|
+
},
|
|
397
384
|
{
|
|
398
385
|
name: !isRunning
|
|
399
386
|
? `${chalk.red('✕')} Delete container`
|
|
@@ -402,8 +389,14 @@ export async function showContainerSubmenu(
|
|
|
402
389
|
disabled: !isRunning ? false : 'Stop container first',
|
|
403
390
|
},
|
|
404
391
|
new inquirer.Separator(),
|
|
405
|
-
{
|
|
406
|
-
|
|
392
|
+
{
|
|
393
|
+
name: `${chalk.blue('←')} Back to containers`,
|
|
394
|
+
value: 'back',
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: `${chalk.blue('⌂')} Back to main menu`,
|
|
398
|
+
value: 'main',
|
|
399
|
+
},
|
|
407
400
|
]
|
|
408
401
|
|
|
409
402
|
const { action } = await inquirer.prompt<{ action: string }>([
|
|
@@ -490,7 +483,6 @@ export async function handleStart(): Promise<void> {
|
|
|
490
483
|
return
|
|
491
484
|
}
|
|
492
485
|
|
|
493
|
-
// Check port availability
|
|
494
486
|
const portAvailable = await portManager.isPortAvailable(config.port)
|
|
495
487
|
if (!portAvailable) {
|
|
496
488
|
const { port: newPort } = await portManager.findAvailablePort()
|
|
@@ -556,7 +548,6 @@ async function handleStartContainer(containerName: string): Promise<void> {
|
|
|
556
548
|
return
|
|
557
549
|
}
|
|
558
550
|
|
|
559
|
-
// Check port availability
|
|
560
551
|
const portAvailable = await portManager.isPortAvailable(config.port)
|
|
561
552
|
if (!portAvailable) {
|
|
562
553
|
console.log(
|
|
@@ -599,7 +590,6 @@ async function handleStartContainer(containerName: string): Promise<void> {
|
|
|
599
590
|
console.log()
|
|
600
591
|
console.log(error(e.message))
|
|
601
592
|
|
|
602
|
-
// Check if there's a log file with more details
|
|
603
593
|
const logPath = paths.getContainerLogPath(containerName, {
|
|
604
594
|
engine: config.engine,
|
|
605
595
|
})
|
|
@@ -651,8 +641,14 @@ async function handleEditContainer(
|
|
|
651
641
|
value: 'port',
|
|
652
642
|
},
|
|
653
643
|
new inquirer.Separator(),
|
|
654
|
-
{
|
|
655
|
-
|
|
644
|
+
{
|
|
645
|
+
name: `${chalk.blue('←')} Back to container`,
|
|
646
|
+
value: 'back',
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: `${chalk.blue('⌂')} Back to main menu`,
|
|
650
|
+
value: 'main',
|
|
651
|
+
},
|
|
656
652
|
]
|
|
657
653
|
|
|
658
654
|
const { field } = await inquirer.prompt<{ field: string }>([
|
|
@@ -695,7 +691,6 @@ async function handleEditContainer(
|
|
|
695
691
|
return await handleEditContainer(containerName)
|
|
696
692
|
}
|
|
697
693
|
|
|
698
|
-
// Check if new name already exists
|
|
699
694
|
if (await containerManager.exists(newName)) {
|
|
700
695
|
console.log(error(`Container "${newName}" already exists`))
|
|
701
696
|
return await handleEditContainer(containerName)
|
|
@@ -735,7 +730,6 @@ async function handleEditContainer(
|
|
|
735
730
|
return await handleEditContainer(containerName)
|
|
736
731
|
}
|
|
737
732
|
|
|
738
|
-
// Check if port is in use
|
|
739
733
|
const portAvailable = await portManager.isPortAvailable(newPort)
|
|
740
734
|
if (!portAvailable) {
|
|
741
735
|
console.log(
|
|
@@ -788,7 +782,6 @@ async function handleCloneFromSubmenu(
|
|
|
788
782
|
console.log()
|
|
789
783
|
console.log(connectionBox(targetName, connectionString, newConfig.port))
|
|
790
784
|
|
|
791
|
-
// Go to the new container's submenu
|
|
792
785
|
await showContainerSubmenu(targetName, showMainMenu)
|
|
793
786
|
}
|
|
794
787
|
|