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
|
@@ -4,20 +4,31 @@ 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 {
|
|
11
11
|
getInstalledEngines,
|
|
12
12
|
type InstalledPostgresEngine,
|
|
13
13
|
type InstalledMysqlEngine,
|
|
14
|
+
type InstalledSqliteEngine,
|
|
14
15
|
} from '../../helpers'
|
|
15
16
|
import {
|
|
16
17
|
getMysqlVersion,
|
|
17
18
|
getMysqlInstallInfo,
|
|
18
19
|
} from '../../../engines/mysql/binary-detection'
|
|
20
|
+
|
|
19
21
|
import { type MenuChoice } from './shared'
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Pad string to width, accounting for emoji taking 2 display columns
|
|
25
|
+
*/
|
|
26
|
+
function padWithEmoji(str: string, width: number): string {
|
|
27
|
+
// Count emojis using Extended_Pictographic (excludes digits/symbols that \p{Emoji} matches)
|
|
28
|
+
const emojiCount = (str.match(/\p{Extended_Pictographic}/gu) || []).length
|
|
29
|
+
return str.padEnd(width + emojiCount)
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export async function handleEngines(): Promise<void> {
|
|
22
33
|
console.clear()
|
|
23
34
|
console.log(header('Installed Engines'))
|
|
@@ -26,7 +37,7 @@ export async function handleEngines(): Promise<void> {
|
|
|
26
37
|
const engines = await getInstalledEngines()
|
|
27
38
|
|
|
28
39
|
if (engines.length === 0) {
|
|
29
|
-
console.log(
|
|
40
|
+
console.log(uiInfo('No engines installed yet.'))
|
|
30
41
|
console.log(
|
|
31
42
|
chalk.gray(
|
|
32
43
|
' PostgreSQL engines are downloaded automatically when you create a container.',
|
|
@@ -46,6 +57,9 @@ export async function handleEngines(): Promise<void> {
|
|
|
46
57
|
const mysqlEngine = engines.find(
|
|
47
58
|
(e): e is InstalledMysqlEngine => e.engine === 'mysql',
|
|
48
59
|
)
|
|
60
|
+
const sqliteEngine = engines.find(
|
|
61
|
+
(e): e is InstalledSqliteEngine => e.engine === 'sqlite',
|
|
62
|
+
)
|
|
49
63
|
|
|
50
64
|
const totalPgSize = pgEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
|
|
51
65
|
|
|
@@ -62,10 +76,11 @@ export async function handleEngines(): Promise<void> {
|
|
|
62
76
|
for (const engine of pgEngines) {
|
|
63
77
|
const icon = getEngineIcon(engine.engine)
|
|
64
78
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
79
|
+
const engineDisplay = `${icon} ${engine.engine}`
|
|
65
80
|
|
|
66
81
|
console.log(
|
|
67
82
|
chalk.gray(' ') +
|
|
68
|
-
chalk.cyan(
|
|
83
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
69
84
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
70
85
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
71
86
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -75,16 +90,30 @@ export async function handleEngines(): Promise<void> {
|
|
|
75
90
|
if (mysqlEngine) {
|
|
76
91
|
const icon = ENGINE_ICONS.mysql
|
|
77
92
|
const displayName = mysqlEngine.isMariaDB ? 'mariadb' : 'mysql'
|
|
93
|
+
const engineDisplay = `${icon} ${displayName}`
|
|
78
94
|
|
|
79
95
|
console.log(
|
|
80
96
|
chalk.gray(' ') +
|
|
81
|
-
chalk.cyan(
|
|
97
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
82
98
|
chalk.yellow(mysqlEngine.version.padEnd(12)) +
|
|
83
99
|
chalk.gray('system'.padEnd(18)) +
|
|
84
100
|
chalk.gray('(system-installed)'),
|
|
85
101
|
)
|
|
86
102
|
}
|
|
87
103
|
|
|
104
|
+
if (sqliteEngine) {
|
|
105
|
+
const icon = ENGINE_ICONS.sqlite
|
|
106
|
+
const engineDisplay = `${icon} sqlite`
|
|
107
|
+
|
|
108
|
+
console.log(
|
|
109
|
+
chalk.gray(' ') +
|
|
110
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
111
|
+
chalk.yellow(sqliteEngine.version.padEnd(12)) +
|
|
112
|
+
chalk.gray('system'.padEnd(18)) +
|
|
113
|
+
chalk.gray('(system-installed)'),
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
88
117
|
console.log(chalk.gray(' ' + '─'.repeat(55)))
|
|
89
118
|
|
|
90
119
|
console.log()
|
|
@@ -98,6 +127,11 @@ export async function handleEngines(): Promise<void> {
|
|
|
98
127
|
if (mysqlEngine) {
|
|
99
128
|
console.log(chalk.gray(` MySQL: system-installed at ${mysqlEngine.path}`))
|
|
100
129
|
}
|
|
130
|
+
if (sqliteEngine) {
|
|
131
|
+
console.log(
|
|
132
|
+
chalk.gray(` SQLite: system-installed at ${sqliteEngine.path}`),
|
|
133
|
+
)
|
|
134
|
+
}
|
|
101
135
|
console.log()
|
|
102
136
|
|
|
103
137
|
const choices: MenuChoice[] = []
|
|
@@ -117,6 +151,13 @@ export async function handleEngines(): Promise<void> {
|
|
|
117
151
|
})
|
|
118
152
|
}
|
|
119
153
|
|
|
154
|
+
if (sqliteEngine) {
|
|
155
|
+
choices.push({
|
|
156
|
+
name: `${chalk.blue('ℹ')} SQLite ${sqliteEngine.version} ${chalk.gray('(system-installed)')}`,
|
|
157
|
+
value: `sqlite-info:${sqliteEngine.path}`,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
120
161
|
choices.push(new inquirer.Separator())
|
|
121
162
|
choices.push({ name: `${chalk.blue('←')} Back to main menu`, value: 'back' })
|
|
122
163
|
|
|
@@ -135,16 +176,29 @@ export async function handleEngines(): Promise<void> {
|
|
|
135
176
|
}
|
|
136
177
|
|
|
137
178
|
if (action.startsWith('delete:')) {
|
|
138
|
-
|
|
179
|
+
// Parse from the end to preserve colons in path
|
|
180
|
+
// Format: delete:path:engineName:engineVersion
|
|
181
|
+
const withoutPrefix = action.slice('delete:'.length)
|
|
182
|
+
const lastColon = withoutPrefix.lastIndexOf(':')
|
|
183
|
+
const secondLastColon = withoutPrefix.lastIndexOf(':', lastColon - 1)
|
|
184
|
+
const enginePath = withoutPrefix.slice(0, secondLastColon)
|
|
185
|
+
const engineName = withoutPrefix.slice(secondLastColon + 1, lastColon)
|
|
186
|
+
const engineVersion = withoutPrefix.slice(lastColon + 1)
|
|
139
187
|
await handleDeleteEngine(enginePath, engineName, engineVersion)
|
|
140
188
|
await handleEngines()
|
|
141
189
|
}
|
|
142
190
|
|
|
143
191
|
if (action.startsWith('mysql-info:')) {
|
|
144
|
-
const mysqldPath = action.
|
|
192
|
+
const mysqldPath = action.slice('mysql-info:'.length)
|
|
145
193
|
await handleMysqlInfo(mysqldPath)
|
|
146
194
|
await handleEngines()
|
|
147
195
|
}
|
|
196
|
+
|
|
197
|
+
if (action.startsWith('sqlite-info:')) {
|
|
198
|
+
const sqlitePath = action.slice('sqlite-info:'.length)
|
|
199
|
+
await handleSqliteInfo(sqlitePath)
|
|
200
|
+
await handleEngines()
|
|
201
|
+
}
|
|
148
202
|
}
|
|
149
203
|
|
|
150
204
|
async function handleDeleteEngine(
|
|
@@ -160,7 +214,7 @@ async function handleDeleteEngine(
|
|
|
160
214
|
if (usingContainers.length > 0) {
|
|
161
215
|
console.log()
|
|
162
216
|
console.log(
|
|
163
|
-
|
|
217
|
+
uiError(
|
|
164
218
|
`Cannot delete: ${usingContainers.length} container(s) are using ${engineName} ${engineVersion}`,
|
|
165
219
|
),
|
|
166
220
|
)
|
|
@@ -186,7 +240,7 @@ async function handleDeleteEngine(
|
|
|
186
240
|
)
|
|
187
241
|
|
|
188
242
|
if (!confirmed) {
|
|
189
|
-
console.log(
|
|
243
|
+
console.log(uiWarning('Deletion cancelled'))
|
|
190
244
|
return
|
|
191
245
|
}
|
|
192
246
|
|
|
@@ -196,8 +250,8 @@ async function handleDeleteEngine(
|
|
|
196
250
|
try {
|
|
197
251
|
await rm(enginePath, { recursive: true, force: true })
|
|
198
252
|
spinner.succeed(`Deleted ${engineName} ${engineVersion}`)
|
|
199
|
-
} catch (
|
|
200
|
-
const e =
|
|
253
|
+
} catch (error) {
|
|
254
|
+
const e = error as Error
|
|
201
255
|
spinner.fail(`Failed to delete: ${e.message}`)
|
|
202
256
|
}
|
|
203
257
|
}
|
|
@@ -220,7 +274,7 @@ async function handleMysqlInfo(mysqldPath: string): Promise<void> {
|
|
|
220
274
|
|
|
221
275
|
if (mysqlContainers.length > 0) {
|
|
222
276
|
console.log(
|
|
223
|
-
|
|
277
|
+
uiWarning(
|
|
224
278
|
`${mysqlContainers.length} container(s) are using ${displayName}:`,
|
|
225
279
|
),
|
|
226
280
|
)
|
|
@@ -360,3 +414,83 @@ async function handleMysqlInfo(mysqldPath: string): Promise<void> {
|
|
|
360
414
|
},
|
|
361
415
|
])
|
|
362
416
|
}
|
|
417
|
+
|
|
418
|
+
async function handleSqliteInfo(sqlitePath: string): Promise<void> {
|
|
419
|
+
console.clear()
|
|
420
|
+
|
|
421
|
+
console.log(header('SQLite Information'))
|
|
422
|
+
console.log()
|
|
423
|
+
|
|
424
|
+
// Get version
|
|
425
|
+
let version = 'unknown'
|
|
426
|
+
try {
|
|
427
|
+
const { exec } = await import('child_process')
|
|
428
|
+
const { promisify } = await import('util')
|
|
429
|
+
const execAsync = promisify(exec)
|
|
430
|
+
const { stdout } = await execAsync(`"${sqlitePath}" --version`)
|
|
431
|
+
const match = stdout.match(/^([\d.]+)/)
|
|
432
|
+
if (match) {
|
|
433
|
+
version = match[1]
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
// Ignore
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const containers = await containerManager.list()
|
|
440
|
+
const sqliteContainers = containers.filter((c) => c.engine === 'sqlite')
|
|
441
|
+
|
|
442
|
+
if (sqliteContainers.length > 0) {
|
|
443
|
+
console.log(
|
|
444
|
+
uiInfo(`${sqliteContainers.length} SQLite database(s) registered:`),
|
|
445
|
+
)
|
|
446
|
+
console.log()
|
|
447
|
+
for (const c of sqliteContainers) {
|
|
448
|
+
const status =
|
|
449
|
+
c.status === 'running'
|
|
450
|
+
? chalk.blue('🔵 available')
|
|
451
|
+
: chalk.gray('⚪ missing')
|
|
452
|
+
console.log(chalk.gray(` • ${c.name} ${status}`))
|
|
453
|
+
}
|
|
454
|
+
console.log()
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
console.log(chalk.white(' Installation Details:'))
|
|
458
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
459
|
+
console.log(
|
|
460
|
+
chalk.gray(' ') +
|
|
461
|
+
chalk.white('Version:'.padEnd(18)) +
|
|
462
|
+
chalk.yellow(version),
|
|
463
|
+
)
|
|
464
|
+
console.log(
|
|
465
|
+
chalk.gray(' ') +
|
|
466
|
+
chalk.white('Binary Path:'.padEnd(18)) +
|
|
467
|
+
chalk.gray(sqlitePath),
|
|
468
|
+
)
|
|
469
|
+
console.log(
|
|
470
|
+
chalk.gray(' ') +
|
|
471
|
+
chalk.white('Type:'.padEnd(18)) +
|
|
472
|
+
chalk.cyan('Embedded (file-based)'),
|
|
473
|
+
)
|
|
474
|
+
console.log()
|
|
475
|
+
|
|
476
|
+
console.log(chalk.white(' Notes:'))
|
|
477
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
478
|
+
console.log(
|
|
479
|
+
chalk.gray(
|
|
480
|
+
' • SQLite is typically pre-installed on macOS and most Linux distributions',
|
|
481
|
+
),
|
|
482
|
+
)
|
|
483
|
+
console.log(chalk.gray(' • No server process - databases are just files'))
|
|
484
|
+
console.log(
|
|
485
|
+
chalk.gray(' • Use "spindb delete <name>" to unregister a database'),
|
|
486
|
+
)
|
|
487
|
+
console.log()
|
|
488
|
+
|
|
489
|
+
await inquirer.prompt([
|
|
490
|
+
{
|
|
491
|
+
type: 'input',
|
|
492
|
+
name: 'continue',
|
|
493
|
+
message: chalk.gray('Press Enter to go back...'),
|
|
494
|
+
},
|
|
495
|
+
])
|
|
496
|
+
}
|
|
@@ -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
|
})
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { platformService } from '../../../core/platform-service'
|
|
21
21
|
import { getEngine } from '../../../engines'
|
|
22
22
|
import { createSpinner } from '../../ui/spinner'
|
|
23
|
-
import {
|
|
23
|
+
import { uiError, uiWarning, uiInfo, uiSuccess } from '../../ui/theme'
|
|
24
24
|
import { pressEnterToContinue } from './shared'
|
|
25
25
|
|
|
26
26
|
export async function handleCopyConnectionString(
|
|
@@ -28,7 +28,7 @@ export async function handleCopyConnectionString(
|
|
|
28
28
|
): Promise<void> {
|
|
29
29
|
const config = await containerManager.getConfig(containerName)
|
|
30
30
|
if (!config) {
|
|
31
|
-
console.error(
|
|
31
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
32
32
|
return
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -39,10 +39,10 @@ export async function handleCopyConnectionString(
|
|
|
39
39
|
|
|
40
40
|
console.log()
|
|
41
41
|
if (copied) {
|
|
42
|
-
console.log(
|
|
42
|
+
console.log(uiSuccess('Connection string copied to clipboard'))
|
|
43
43
|
console.log(chalk.gray(` ${connectionString}`))
|
|
44
44
|
} else {
|
|
45
|
-
console.log(
|
|
45
|
+
console.log(uiWarning('Could not copy to clipboard. Connection string:'))
|
|
46
46
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
47
47
|
}
|
|
48
48
|
console.log()
|
|
@@ -59,7 +59,7 @@ export async function handleCopyConnectionString(
|
|
|
59
59
|
export async function handleOpenShell(containerName: string): Promise<void> {
|
|
60
60
|
const config = await containerManager.getConfig(containerName)
|
|
61
61
|
if (!config) {
|
|
62
|
-
console.error(
|
|
62
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
63
63
|
return
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -69,12 +69,13 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
69
69
|
const shellCheckSpinner = createSpinner('Checking available shells...')
|
|
70
70
|
shellCheckSpinner.start()
|
|
71
71
|
|
|
72
|
-
const [usqlInstalled, pgcliInstalled, mycliInstalled, litecliInstalled] =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
const [usqlInstalled, pgcliInstalled, mycliInstalled, litecliInstalled] =
|
|
73
|
+
await Promise.all([
|
|
74
|
+
isUsqlInstalled(),
|
|
75
|
+
isPgcliInstalled(),
|
|
76
|
+
isMycliInstalled(),
|
|
77
|
+
isLitecliInstalled(),
|
|
78
|
+
])
|
|
78
79
|
|
|
79
80
|
shellCheckSpinner.stop()
|
|
80
81
|
// Clear the spinner line
|
|
@@ -119,7 +120,9 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
119
120
|
engineSpecificInstallValue = 'install-pgcli'
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
const choices: Array<
|
|
123
|
+
const choices: Array<
|
|
124
|
+
{ name: string; value: ShellChoice } | inquirer.Separator
|
|
125
|
+
> = [
|
|
123
126
|
{
|
|
124
127
|
name: `>_ Use default shell (${defaultShellName})`,
|
|
125
128
|
value: 'default',
|
|
@@ -173,16 +176,16 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
173
176
|
|
|
174
177
|
if (shellChoice === 'install-pgcli') {
|
|
175
178
|
console.log()
|
|
176
|
-
console.log(
|
|
179
|
+
console.log(uiInfo('Installing pgcli for enhanced PostgreSQL shell...'))
|
|
177
180
|
const pm = await detectPackageManager()
|
|
178
181
|
if (pm) {
|
|
179
182
|
const result = await installPgcli(pm)
|
|
180
183
|
if (result.success) {
|
|
181
|
-
console.log(
|
|
184
|
+
console.log(uiSuccess('pgcli installed successfully!'))
|
|
182
185
|
console.log()
|
|
183
186
|
await launchShell(containerName, config, connectionString, 'pgcli')
|
|
184
187
|
} else {
|
|
185
|
-
console.error(
|
|
188
|
+
console.error(uiError(`Failed to install pgcli: ${result.error}`))
|
|
186
189
|
console.log()
|
|
187
190
|
console.log(chalk.gray('Manual installation:'))
|
|
188
191
|
for (const instruction of getPgcliManualInstructions()) {
|
|
@@ -192,7 +195,7 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
192
195
|
await pressEnterToContinue()
|
|
193
196
|
}
|
|
194
197
|
} else {
|
|
195
|
-
console.error(
|
|
198
|
+
console.error(uiError('No supported package manager found'))
|
|
196
199
|
console.log()
|
|
197
200
|
console.log(chalk.gray('Manual installation:'))
|
|
198
201
|
for (const instruction of getPgcliManualInstructions()) {
|
|
@@ -206,16 +209,16 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
206
209
|
|
|
207
210
|
if (shellChoice === 'install-mycli') {
|
|
208
211
|
console.log()
|
|
209
|
-
console.log(
|
|
212
|
+
console.log(uiInfo('Installing mycli for enhanced MySQL shell...'))
|
|
210
213
|
const pm = await detectPackageManager()
|
|
211
214
|
if (pm) {
|
|
212
215
|
const result = await installMycli(pm)
|
|
213
216
|
if (result.success) {
|
|
214
|
-
console.log(
|
|
217
|
+
console.log(uiSuccess('mycli installed successfully!'))
|
|
215
218
|
console.log()
|
|
216
219
|
await launchShell(containerName, config, connectionString, 'mycli')
|
|
217
220
|
} else {
|
|
218
|
-
console.error(
|
|
221
|
+
console.error(uiError(`Failed to install mycli: ${result.error}`))
|
|
219
222
|
console.log()
|
|
220
223
|
console.log(chalk.gray('Manual installation:'))
|
|
221
224
|
for (const instruction of getMycliManualInstructions()) {
|
|
@@ -225,7 +228,7 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
225
228
|
await pressEnterToContinue()
|
|
226
229
|
}
|
|
227
230
|
} else {
|
|
228
|
-
console.error(
|
|
231
|
+
console.error(uiError('No supported package manager found'))
|
|
229
232
|
console.log()
|
|
230
233
|
console.log(chalk.gray('Manual installation:'))
|
|
231
234
|
for (const instruction of getMycliManualInstructions()) {
|
|
@@ -239,16 +242,16 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
239
242
|
|
|
240
243
|
if (shellChoice === 'install-usql') {
|
|
241
244
|
console.log()
|
|
242
|
-
console.log(
|
|
245
|
+
console.log(uiInfo('Installing usql for enhanced shell experience...'))
|
|
243
246
|
const pm = await detectPackageManager()
|
|
244
247
|
if (pm) {
|
|
245
248
|
const result = await installUsql(pm)
|
|
246
249
|
if (result.success) {
|
|
247
|
-
console.log(
|
|
250
|
+
console.log(uiSuccess('usql installed successfully!'))
|
|
248
251
|
console.log()
|
|
249
252
|
await launchShell(containerName, config, connectionString, 'usql')
|
|
250
253
|
} else {
|
|
251
|
-
console.error(
|
|
254
|
+
console.error(uiError(`Failed to install usql: ${result.error}`))
|
|
252
255
|
console.log()
|
|
253
256
|
console.log(chalk.gray('Manual installation:'))
|
|
254
257
|
for (const instruction of getUsqlManualInstructions()) {
|
|
@@ -258,7 +261,7 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
258
261
|
await pressEnterToContinue()
|
|
259
262
|
}
|
|
260
263
|
} else {
|
|
261
|
-
console.error(
|
|
264
|
+
console.error(uiError('No supported package manager found'))
|
|
262
265
|
console.log()
|
|
263
266
|
console.log(chalk.gray('Manual installation:'))
|
|
264
267
|
for (const instruction of getUsqlManualInstructions()) {
|
|
@@ -272,16 +275,16 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
272
275
|
|
|
273
276
|
if (shellChoice === 'install-litecli') {
|
|
274
277
|
console.log()
|
|
275
|
-
console.log(
|
|
278
|
+
console.log(uiInfo('Installing litecli for enhanced SQLite shell...'))
|
|
276
279
|
const pm = await detectPackageManager()
|
|
277
280
|
if (pm) {
|
|
278
281
|
const result = await installLitecli(pm)
|
|
279
282
|
if (result.success) {
|
|
280
|
-
console.log(
|
|
283
|
+
console.log(uiSuccess('litecli installed successfully!'))
|
|
281
284
|
console.log()
|
|
282
285
|
await launchShell(containerName, config, connectionString, 'litecli')
|
|
283
286
|
} else {
|
|
284
|
-
console.error(
|
|
287
|
+
console.error(uiError(`Failed to install litecli: ${result.error}`))
|
|
285
288
|
console.log()
|
|
286
289
|
console.log(chalk.gray('Manual installation:'))
|
|
287
290
|
for (const instruction of getLitecliManualInstructions()) {
|
|
@@ -291,7 +294,7 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
291
294
|
await pressEnterToContinue()
|
|
292
295
|
}
|
|
293
296
|
} else {
|
|
294
|
-
console.error(
|
|
297
|
+
console.error(uiError('No supported package manager found'))
|
|
295
298
|
console.log()
|
|
296
299
|
console.log(chalk.gray('Manual installation:'))
|
|
297
300
|
for (const instruction of getLitecliManualInstructions()) {
|
|
@@ -312,7 +315,7 @@ async function launchShell(
|
|
|
312
315
|
connectionString: string,
|
|
313
316
|
shellType: 'default' | 'usql' | 'pgcli' | 'mycli' | 'litecli',
|
|
314
317
|
): Promise<void> {
|
|
315
|
-
console.log(
|
|
318
|
+
console.log(uiInfo(`Connecting to ${containerName}...`))
|
|
316
319
|
console.log()
|
|
317
320
|
|
|
318
321
|
let shellCmd: string
|
|
@@ -386,7 +389,7 @@ async function launchShell(
|
|
|
386
389
|
|
|
387
390
|
shellProcess.on('error', (err: NodeJS.ErrnoException) => {
|
|
388
391
|
if (err.code === 'ENOENT') {
|
|
389
|
-
console.log(
|
|
392
|
+
console.log(uiWarning(`${shellCmd} not found on your system.`))
|
|
390
393
|
console.log()
|
|
391
394
|
console.log(chalk.gray(' Connect manually with:'))
|
|
392
395
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
@@ -394,7 +397,7 @@ async function launchShell(
|
|
|
394
397
|
console.log(chalk.gray(` Install ${shellCmd}:`))
|
|
395
398
|
console.log(chalk.cyan(` ${installHint}`))
|
|
396
399
|
} else {
|
|
397
|
-
console.log(
|
|
400
|
+
console.log(uiError(`Failed to start ${shellCmd}: ${err.message}`))
|
|
398
401
|
}
|
|
399
402
|
settle()
|
|
400
403
|
})
|
|
@@ -7,14 +7,17 @@ import { containerManager } from '../../../core/container-manager'
|
|
|
7
7
|
import { getMissingDependencies } from '../../../core/dependency-manager'
|
|
8
8
|
import { getEngine } from '../../../engines'
|
|
9
9
|
import { paths } from '../../../config/paths'
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import {
|
|
11
|
+
promptInstallDependencies,
|
|
12
|
+
promptDatabaseSelect,
|
|
13
|
+
} from '../../ui/prompts'
|
|
14
|
+
import { uiError, uiWarning, uiInfo, uiSuccess } from '../../ui/theme'
|
|
12
15
|
import { pressEnterToContinue } from './shared'
|
|
13
16
|
|
|
14
17
|
export async function handleRunSql(containerName: string): Promise<void> {
|
|
15
18
|
const config = await containerManager.getConfig(containerName)
|
|
16
19
|
if (!config) {
|
|
17
|
-
console.error(
|
|
20
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
18
21
|
return
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -23,7 +26,7 @@ export async function handleRunSql(containerName: string): Promise<void> {
|
|
|
23
26
|
let missingDeps = await getMissingDependencies(config.engine)
|
|
24
27
|
if (missingDeps.length > 0) {
|
|
25
28
|
console.log(
|
|
26
|
-
|
|
29
|
+
uiWarning(`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`),
|
|
27
30
|
)
|
|
28
31
|
|
|
29
32
|
const installed = await promptInstallDependencies(
|
|
@@ -38,7 +41,7 @@ export async function handleRunSql(containerName: string): Promise<void> {
|
|
|
38
41
|
missingDeps = await getMissingDependencies(config.engine)
|
|
39
42
|
if (missingDeps.length > 0) {
|
|
40
43
|
console.log(
|
|
41
|
-
|
|
44
|
+
uiError(
|
|
42
45
|
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
43
46
|
),
|
|
44
47
|
)
|
|
@@ -50,11 +53,14 @@ export async function handleRunSql(containerName: string): Promise<void> {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
// Strip quotes that terminals add when drag-and-dropping files
|
|
53
|
-
const stripQuotes = (path: string) =>
|
|
54
|
-
path.replace(/^['"]|['"]$/g, '').trim()
|
|
56
|
+
const stripQuotes = (path: string) => path.replace(/^['"]|['"]$/g, '').trim()
|
|
55
57
|
|
|
56
58
|
// Prompt for file path (empty input = go back)
|
|
57
|
-
console.log(
|
|
59
|
+
console.log(
|
|
60
|
+
chalk.gray(
|
|
61
|
+
' Drag & drop, enter path (abs or rel), or press Enter to go back',
|
|
62
|
+
),
|
|
63
|
+
)
|
|
58
64
|
const { filePath: rawFilePath } = await inquirer.prompt<{
|
|
59
65
|
filePath: string
|
|
60
66
|
}>([
|
|
@@ -90,7 +96,7 @@ export async function handleRunSql(containerName: string): Promise<void> {
|
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
console.log()
|
|
93
|
-
console.log(
|
|
99
|
+
console.log(uiInfo(`Running SQL file against "${databaseName}"...`))
|
|
94
100
|
console.log()
|
|
95
101
|
|
|
96
102
|
try {
|
|
@@ -99,11 +105,11 @@ export async function handleRunSql(containerName: string): Promise<void> {
|
|
|
99
105
|
database: databaseName,
|
|
100
106
|
})
|
|
101
107
|
console.log()
|
|
102
|
-
console.log(
|
|
103
|
-
} catch (
|
|
104
|
-
const e =
|
|
108
|
+
console.log(uiSuccess('SQL file executed successfully'))
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const e = error as Error
|
|
105
111
|
console.log()
|
|
106
|
-
console.log(
|
|
112
|
+
console.log(uiError(`SQL execution failed: ${e.message}`))
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
console.log()
|
|
@@ -116,7 +122,7 @@ export async function handleRunSql(containerName: string): Promise<void> {
|
|
|
116
122
|
export async function handleViewLogs(containerName: string): Promise<void> {
|
|
117
123
|
const config = await containerManager.getConfig(containerName)
|
|
118
124
|
if (!config) {
|
|
119
|
-
console.error(
|
|
125
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
120
126
|
return
|
|
121
127
|
}
|
|
122
128
|
|
|
@@ -126,7 +132,7 @@ export async function handleViewLogs(containerName: string): Promise<void> {
|
|
|
126
132
|
|
|
127
133
|
if (!existsSync(logPath)) {
|
|
128
134
|
console.log(
|
|
129
|
-
|
|
135
|
+
uiInfo(
|
|
130
136
|
`No log file found for "${containerName}". The container may not have been started yet.`,
|
|
131
137
|
),
|
|
132
138
|
)
|
|
@@ -194,7 +200,7 @@ export async function handleViewLogs(containerName: string): Promise<void> {
|
|
|
194
200
|
const lineCount = action === 'tail-100' ? 100 : 50
|
|
195
201
|
const content = await readFile(logPath, 'utf-8')
|
|
196
202
|
if (content.trim() === '') {
|
|
197
|
-
console.log(
|
|
203
|
+
console.log(uiInfo('Log file is empty'))
|
|
198
204
|
} else {
|
|
199
205
|
const lines = content.split('\n')
|
|
200
206
|
const nonEmptyLines =
|