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.
Files changed (49) hide show
  1. package/README.md +7 -0
  2. package/cli/commands/backup.ts +13 -11
  3. package/cli/commands/clone.ts +18 -8
  4. package/cli/commands/config.ts +29 -29
  5. package/cli/commands/connect.ts +51 -39
  6. package/cli/commands/create.ts +120 -43
  7. package/cli/commands/delete.ts +8 -8
  8. package/cli/commands/deps.ts +17 -15
  9. package/cli/commands/doctor.ts +16 -15
  10. package/cli/commands/edit.ts +115 -60
  11. package/cli/commands/engines.ts +50 -17
  12. package/cli/commands/info.ts +12 -8
  13. package/cli/commands/list.ts +34 -19
  14. package/cli/commands/logs.ts +24 -14
  15. package/cli/commands/menu/backup-handlers.ts +72 -49
  16. package/cli/commands/menu/container-handlers.ts +140 -80
  17. package/cli/commands/menu/engine-handlers.ts +145 -11
  18. package/cli/commands/menu/index.ts +4 -4
  19. package/cli/commands/menu/shell-handlers.ts +34 -31
  20. package/cli/commands/menu/sql-handlers.ts +22 -16
  21. package/cli/commands/menu/update-handlers.ts +19 -17
  22. package/cli/commands/restore.ts +105 -43
  23. package/cli/commands/run.ts +20 -18
  24. package/cli/commands/self-update.ts +5 -5
  25. package/cli/commands/start.ts +11 -9
  26. package/cli/commands/stop.ts +9 -9
  27. package/cli/commands/url.ts +12 -9
  28. package/cli/helpers.ts +49 -4
  29. package/cli/ui/prompts.ts +21 -8
  30. package/cli/ui/spinner.ts +4 -4
  31. package/cli/ui/theme.ts +4 -4
  32. package/core/binary-manager.ts +5 -1
  33. package/core/container-manager.ts +81 -30
  34. package/core/error-handler.ts +31 -0
  35. package/core/platform-service.ts +3 -3
  36. package/core/port-manager.ts +2 -0
  37. package/core/process-manager.ts +25 -3
  38. package/core/start-with-retry.ts +6 -6
  39. package/core/transaction-manager.ts +6 -6
  40. package/engines/mysql/backup.ts +53 -36
  41. package/engines/mysql/index.ts +59 -16
  42. package/engines/mysql/restore.ts +4 -4
  43. package/engines/mysql/version-validator.ts +2 -2
  44. package/engines/postgresql/binary-manager.ts +17 -17
  45. package/engines/postgresql/index.ts +13 -2
  46. package/engines/postgresql/restore.ts +2 -2
  47. package/engines/postgresql/version-validator.ts +2 -2
  48. package/engines/sqlite/index.ts +31 -9
  49. 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, error, warning, info, formatBytes } from '../../ui/theme'
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(info('No engines installed yet.'))
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(`${icon} ${engine.engine}`.padEnd(13)) +
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(`${icon} ${displayName}`.padEnd(13)) +
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
- const [, enginePath, engineName, engineVersion] = action.split(':')
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.replace('mysql-info:', '')
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
- error(
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(warning('Deletion cancelled'))
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 (err) {
200
- const e = err as Error
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
- warning(
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, error } from '../../ui/theme'
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 (err) {
160
- const e = err as Error
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(error(e.message))
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 { error, warning, info, success } from '../../ui/theme'
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(error(`Container "${containerName}" not found`))
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(success('Connection string copied to clipboard'))
42
+ console.log(uiSuccess('Connection string copied to clipboard'))
43
43
  console.log(chalk.gray(` ${connectionString}`))
44
44
  } else {
45
- console.log(warning('Could not copy to clipboard. Connection string:'))
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(error(`Container "${containerName}" not found`))
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] = await Promise.all([
73
- isUsqlInstalled(),
74
- isPgcliInstalled(),
75
- isMycliInstalled(),
76
- isLitecliInstalled(),
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<{ name: string; value: ShellChoice } | inquirer.Separator> = [
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(info('Installing pgcli for enhanced PostgreSQL shell...'))
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(success('pgcli installed successfully!'))
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(error(`Failed to install pgcli: ${result.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(error('No supported package manager found'))
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(info('Installing mycli for enhanced MySQL shell...'))
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(success('mycli installed successfully!'))
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(error(`Failed to install mycli: ${result.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(error('No supported package manager found'))
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(info('Installing usql for enhanced shell experience...'))
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(success('usql installed successfully!'))
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(error(`Failed to install usql: ${result.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(error('No supported package manager found'))
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(info('Installing litecli for enhanced SQLite shell...'))
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(success('litecli installed successfully!'))
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(error(`Failed to install litecli: ${result.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(error('No supported package manager found'))
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(info(`Connecting to ${containerName}...`))
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(warning(`${shellCmd} not found on your system.`))
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(error(`Failed to start ${shellCmd}: ${err.message}`))
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 { promptInstallDependencies, promptDatabaseSelect } from '../../ui/prompts'
11
- import { error, warning, info, success } from '../../ui/theme'
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(error(`Container "${containerName}" not found`))
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
- warning(`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`),
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
- error(
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(chalk.gray(' Drag & drop, enter path (abs or rel), or press Enter to go back'))
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(info(`Running SQL file against "${databaseName}"...`))
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(success('SQL file executed successfully'))
103
- } catch (err) {
104
- const e = err as Error
108
+ console.log(uiSuccess('SQL file executed successfully'))
109
+ } catch (error) {
110
+ const e = error as Error
105
111
  console.log()
106
- console.log(error(`SQL execution failed: ${e.message}`))
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(error(`Container "${containerName}" not found`))
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
- info(
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(info('Log file is empty'))
203
+ console.log(uiInfo('Log file is empty'))
198
204
  } else {
199
205
  const lines = content.split('\n')
200
206
  const nonEmptyLines =