spindb 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/cli/commands/backup.ts +13 -11
  2. package/cli/commands/clone.ts +14 -10
  3. package/cli/commands/config.ts +29 -29
  4. package/cli/commands/connect.ts +51 -39
  5. package/cli/commands/create.ts +59 -32
  6. package/cli/commands/delete.ts +8 -8
  7. package/cli/commands/deps.ts +17 -15
  8. package/cli/commands/doctor.ts +11 -11
  9. package/cli/commands/edit.ts +108 -55
  10. package/cli/commands/engines.ts +17 -15
  11. package/cli/commands/info.ts +8 -6
  12. package/cli/commands/list.ts +31 -16
  13. package/cli/commands/logs.ts +15 -11
  14. package/cli/commands/menu/backup-handlers.ts +52 -47
  15. package/cli/commands/menu/container-handlers.ts +120 -78
  16. package/cli/commands/menu/engine-handlers.ts +21 -11
  17. package/cli/commands/menu/index.ts +4 -4
  18. package/cli/commands/menu/shell-handlers.ts +34 -31
  19. package/cli/commands/menu/sql-handlers.ts +22 -16
  20. package/cli/commands/menu/update-handlers.ts +19 -17
  21. package/cli/commands/restore.ts +22 -20
  22. package/cli/commands/run.ts +20 -18
  23. package/cli/commands/self-update.ts +5 -5
  24. package/cli/commands/start.ts +11 -9
  25. package/cli/commands/stop.ts +9 -9
  26. package/cli/commands/url.ts +12 -9
  27. package/cli/helpers.ts +9 -4
  28. package/cli/ui/prompts.ts +12 -5
  29. package/cli/ui/spinner.ts +4 -4
  30. package/cli/ui/theme.ts +4 -4
  31. package/core/binary-manager.ts +5 -1
  32. package/core/container-manager.ts +5 -5
  33. package/core/platform-service.ts +3 -3
  34. package/core/start-with-retry.ts +6 -6
  35. package/core/transaction-manager.ts +6 -6
  36. package/engines/mysql/index.ts +11 -11
  37. package/engines/mysql/restore.ts +4 -4
  38. package/engines/mysql/version-validator.ts +2 -2
  39. package/engines/postgresql/binary-manager.ts +17 -17
  40. package/engines/postgresql/index.ts +7 -2
  41. package/engines/postgresql/restore.ts +2 -2
  42. package/engines/postgresql/version-validator.ts +2 -2
  43. package/engines/sqlite/index.ts +21 -8
  44. package/package.json +1 -1
@@ -5,7 +5,7 @@ import { containerManager } from '../../core/container-manager'
5
5
  import { processManager } from '../../core/process-manager'
6
6
  import { getEngine } from '../../engines'
7
7
  import { promptInstallDependencies } from '../ui/prompts'
8
- import { error, warning } from '../ui/theme'
8
+ import { uiError, uiWarning } from '../ui/theme'
9
9
  import { getMissingDependencies } from '../../core/dependency-manager'
10
10
  import { Engine } from '../../types'
11
11
 
@@ -26,7 +26,7 @@ export const runCommand = new Command('run')
26
26
 
27
27
  const config = await containerManager.getConfig(containerName)
28
28
  if (!config) {
29
- console.error(error(`Container "${containerName}" not found`))
29
+ console.error(uiError(`Container "${containerName}" not found`))
30
30
  process.exit(1)
31
31
  }
32
32
 
@@ -36,9 +36,7 @@ export const runCommand = new Command('run')
36
36
  if (engineName === Engine.SQLite) {
37
37
  if (!existsSync(config.database)) {
38
38
  console.error(
39
- error(
40
- `SQLite database file not found: ${config.database}`,
41
- ),
39
+ uiError(`SQLite database file not found: ${config.database}`),
42
40
  )
43
41
  process.exit(1)
44
42
  }
@@ -49,7 +47,7 @@ export const runCommand = new Command('run')
49
47
  })
50
48
  if (!running) {
51
49
  console.error(
52
- error(
50
+ uiError(
53
51
  `Container "${containerName}" is not running. Start it first with: spindb start ${containerName}`,
54
52
  ),
55
53
  )
@@ -59,16 +57,16 @@ export const runCommand = new Command('run')
59
57
 
60
58
  if (file && options.sql) {
61
59
  console.error(
62
- error('Cannot specify both a file and --sql option. Choose one.'),
60
+ uiError('Cannot specify both a file and --sql option. Choose one.'),
63
61
  )
64
62
  process.exit(1)
65
63
  }
66
64
 
67
65
  if (!file && !options.sql) {
68
- console.error(error('Must provide either a SQL file or --sql option'))
69
- console.log(
70
- chalk.gray(' Usage: spindb run <container> <file.sql>'),
66
+ console.error(
67
+ uiError('Must provide either a SQL file or --sql option'),
71
68
  )
69
+ console.log(chalk.gray(' Usage: spindb run <container> <file.sql>'))
72
70
  console.log(
73
71
  chalk.gray(' or: spindb run <container> --sql "SELECT ..."'),
74
72
  )
@@ -76,7 +74,7 @@ export const runCommand = new Command('run')
76
74
  }
77
75
 
78
76
  if (file && !existsSync(file)) {
79
- console.error(error(`SQL file not found: ${file}`))
77
+ console.error(uiError(`SQL file not found: ${file}`))
80
78
  process.exit(1)
81
79
  }
82
80
 
@@ -85,7 +83,7 @@ export const runCommand = new Command('run')
85
83
  let missingDeps = await getMissingDependencies(engineName)
86
84
  if (missingDeps.length > 0) {
87
85
  console.log(
88
- warning(
86
+ uiWarning(
89
87
  `Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
90
88
  ),
91
89
  )
@@ -102,7 +100,7 @@ export const runCommand = new Command('run')
102
100
  missingDeps = await getMissingDependencies(engineName)
103
101
  if (missingDeps.length > 0) {
104
102
  console.error(
105
- error(
103
+ uiError(
106
104
  `Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
107
105
  ),
108
106
  )
@@ -120,8 +118,8 @@ export const runCommand = new Command('run')
120
118
  sql: options.sql,
121
119
  database,
122
120
  })
123
- } catch (err) {
124
- const e = err as Error
121
+ } catch (error) {
122
+ const e = error as Error
125
123
 
126
124
  const missingToolPatterns = [
127
125
  'psql not found',
@@ -138,8 +136,12 @@ export const runCommand = new Command('run')
138
136
  .replace(' not found', '')
139
137
  .replace(' client', '')
140
138
  // Determine engine from the missing tool name
141
- const toolEngine = missingTool === 'mysql' ? Engine.MySQL : Engine.PostgreSQL
142
- const installed = await promptInstallDependencies(missingTool, toolEngine)
139
+ const toolEngine =
140
+ missingTool === 'mysql' ? Engine.MySQL : Engine.PostgreSQL
141
+ const installed = await promptInstallDependencies(
142
+ missingTool,
143
+ toolEngine,
144
+ )
143
145
  if (installed) {
144
146
  console.log(
145
147
  chalk.yellow(' Please re-run your command to continue.'),
@@ -148,7 +150,7 @@ export const runCommand = new Command('run')
148
150
  process.exit(1)
149
151
  }
150
152
 
151
- console.error(error(e.message))
153
+ console.error(uiError(e.message))
152
154
  process.exit(1)
153
155
  }
154
156
  },
@@ -3,7 +3,7 @@ import chalk from 'chalk'
3
3
  import inquirer from 'inquirer'
4
4
  import { updateManager } from '../../core/update-manager'
5
5
  import { createSpinner } from '../ui/spinner'
6
- import { success, error, info, header } from '../ui/theme'
6
+ import { uiSuccess, uiError, uiInfo, header } from '../ui/theme'
7
7
 
8
8
  export const selfUpdateCommand = new Command('self-update')
9
9
  .alias('update')
@@ -24,7 +24,7 @@ export const selfUpdateCommand = new Command('self-update')
24
24
  if (!result) {
25
25
  checkSpinner.fail('Could not reach npm registry')
26
26
  console.log()
27
- console.log(info('Check your internet connection and try again.'))
27
+ console.log(uiInfo('Check your internet connection and try again.'))
28
28
  console.log(chalk.gray(' Manual update: npm install -g spindb@latest'))
29
29
  process.exit(1)
30
30
  }
@@ -84,7 +84,7 @@ export const selfUpdateCommand = new Command('self-update')
84
84
  updateSpinner.succeed('Update complete')
85
85
  console.log()
86
86
  console.log(
87
- success(
87
+ uiSuccess(
88
88
  `Updated from ${updateResult.previousVersion} to ${updateResult.newVersion}`,
89
89
  ),
90
90
  )
@@ -100,9 +100,9 @@ export const selfUpdateCommand = new Command('self-update')
100
100
  } else {
101
101
  updateSpinner.fail('Update failed')
102
102
  console.log()
103
- console.log(error(updateResult.error || 'Unknown error'))
103
+ console.log(uiError(updateResult.error || 'Unknown error'))
104
104
  console.log()
105
- console.log(info('Manual update: npm install -g spindb@latest'))
105
+ console.log(uiInfo('Manual update: npm install -g spindb@latest'))
106
106
  process.exit(1)
107
107
  }
108
108
  },
@@ -7,7 +7,7 @@ import { getEngine } from '../../engines'
7
7
  import { getEngineDefaults } from '../../config/defaults'
8
8
  import { promptContainerSelect } from '../ui/prompts'
9
9
  import { createSpinner } from '../ui/spinner'
10
- import { error, warning } from '../ui/theme'
10
+ import { uiError, uiWarning } from '../ui/theme'
11
11
 
12
12
  export const startCommand = new Command('start')
13
13
  .description('Start a container')
@@ -23,10 +23,10 @@ export const startCommand = new Command('start')
23
23
  if (stopped.length === 0) {
24
24
  if (containers.length === 0) {
25
25
  console.log(
26
- warning('No containers found. Create one with: spindb create'),
26
+ uiWarning('No containers found. Create one with: spindb create'),
27
27
  )
28
28
  } else {
29
- console.log(warning('All containers are already running'))
29
+ console.log(uiWarning('All containers are already running'))
30
30
  }
31
31
  return
32
32
  }
@@ -41,7 +41,7 @@ export const startCommand = new Command('start')
41
41
 
42
42
  const config = await containerManager.getConfig(containerName)
43
43
  if (!config) {
44
- console.error(error(`Container "${containerName}" not found`))
44
+ console.error(uiError(`Container "${containerName}" not found`))
45
45
  process.exit(1)
46
46
  }
47
47
 
@@ -51,7 +51,9 @@ export const startCommand = new Command('start')
51
51
  engine: engineName,
52
52
  })
53
53
  if (running) {
54
- console.log(warning(`Container "${containerName}" is already running`))
54
+ console.log(
55
+ uiWarning(`Container "${containerName}" is already running`),
56
+ )
55
57
  return
56
58
  }
57
59
 
@@ -72,7 +74,7 @@ export const startCommand = new Command('start')
72
74
  if (!result.success) {
73
75
  spinner.fail(`Failed to start "${containerName}"`)
74
76
  if (result.error) {
75
- console.error(error(result.error.message))
77
+ console.error(uiError(result.error.message))
76
78
  }
77
79
  process.exit(1)
78
80
  }
@@ -110,9 +112,9 @@ export const startCommand = new Command('start')
110
112
  console.log(chalk.gray(' Connect with:'))
111
113
  console.log(chalk.cyan(` spindb connect ${containerName}`))
112
114
  console.log()
113
- } catch (err) {
114
- const e = err as Error
115
- console.error(error(e.message))
115
+ } catch (error) {
116
+ const e = error as Error
117
+ console.error(uiError(e.message))
116
118
  process.exit(1)
117
119
  }
118
120
  })
@@ -4,7 +4,7 @@ import { processManager } from '../../core/process-manager'
4
4
  import { getEngine } from '../../engines'
5
5
  import { promptContainerSelect } from '../ui/prompts'
6
6
  import { createSpinner } from '../ui/spinner'
7
- import { success, error, warning } from '../ui/theme'
7
+ import { uiSuccess, uiError, uiWarning } from '../ui/theme'
8
8
 
9
9
  export const stopCommand = new Command('stop')
10
10
  .description('Stop a container')
@@ -17,7 +17,7 @@ export const stopCommand = new Command('stop')
17
17
  const running = containers.filter((c) => c.status === 'running')
18
18
 
19
19
  if (running.length === 0) {
20
- console.log(warning('No running containers found'))
20
+ console.log(uiWarning('No running containers found'))
21
21
  return
22
22
  }
23
23
 
@@ -34,7 +34,7 @@ export const stopCommand = new Command('stop')
34
34
  spinner.succeed(`Stopped "${container.name}"`)
35
35
  }
36
36
 
37
- console.log(success(`Stopped ${running.length} container(s)`))
37
+ console.log(uiSuccess(`Stopped ${running.length} container(s)`))
38
38
  return
39
39
  }
40
40
 
@@ -45,7 +45,7 @@ export const stopCommand = new Command('stop')
45
45
  const running = containers.filter((c) => c.status === 'running')
46
46
 
47
47
  if (running.length === 0) {
48
- console.log(warning('No running containers found'))
48
+ console.log(uiWarning('No running containers found'))
49
49
  return
50
50
  }
51
51
 
@@ -59,7 +59,7 @@ export const stopCommand = new Command('stop')
59
59
 
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
  process.exit(1)
64
64
  }
65
65
 
@@ -67,7 +67,7 @@ export const stopCommand = new Command('stop')
67
67
  engine: config.engine,
68
68
  })
69
69
  if (!running) {
70
- console.log(warning(`Container "${containerName}" is not running`))
70
+ console.log(uiWarning(`Container "${containerName}" is not running`))
71
71
  return
72
72
  }
73
73
 
@@ -80,9 +80,9 @@ export const stopCommand = new Command('stop')
80
80
  await containerManager.updateConfig(containerName, { status: 'stopped' })
81
81
 
82
82
  spinner.succeed(`Container "${containerName}" stopped`)
83
- } catch (err) {
84
- const e = err as Error
85
- console.error(error(e.message))
83
+ } catch (error) {
84
+ const e = error as Error
85
+ console.error(uiError(e.message))
86
86
  process.exit(1)
87
87
  }
88
88
  })
@@ -3,7 +3,7 @@ import { containerManager } from '../../core/container-manager'
3
3
  import { platformService } from '../../core/platform-service'
4
4
  import { getEngine } from '../../engines'
5
5
  import { promptContainerSelect } from '../ui/prompts'
6
- import { error, warning, success } from '../ui/theme'
6
+ import { uiError, uiWarning, uiSuccess } from '../ui/theme'
7
7
 
8
8
  export const urlCommand = new Command('url')
9
9
  .alias('connection-string')
@@ -24,7 +24,7 @@ export const urlCommand = new Command('url')
24
24
  const containers = await containerManager.list()
25
25
 
26
26
  if (containers.length === 0) {
27
- console.log(warning('No containers found'))
27
+ console.log(uiWarning('No containers found'))
28
28
  return
29
29
  }
30
30
 
@@ -38,13 +38,16 @@ export const urlCommand = new Command('url')
38
38
 
39
39
  const config = await containerManager.getConfig(containerName)
40
40
  if (!config) {
41
- console.error(error(`Container "${containerName}" not found`))
41
+ console.error(uiError(`Container "${containerName}" not found`))
42
42
  process.exit(1)
43
43
  }
44
44
 
45
45
  const engine = getEngine(config.engine)
46
46
  const databaseName = options.database || config.database
47
- const connectionString = engine.getConnectionString(config, databaseName)
47
+ const connectionString = engine.getConnectionString(
48
+ config,
49
+ databaseName,
50
+ )
48
51
 
49
52
  if (options.json) {
50
53
  const jsonOutput =
@@ -72,10 +75,10 @@ export const urlCommand = new Command('url')
72
75
  const copied = await platformService.copyToClipboard(connectionString)
73
76
  if (copied) {
74
77
  console.log(connectionString)
75
- console.error(success('Copied to clipboard'))
78
+ console.error(uiSuccess('Copied to clipboard'))
76
79
  } else {
77
80
  console.log(connectionString)
78
- console.error(warning('Could not copy to clipboard'))
81
+ console.error(uiWarning('Could not copy to clipboard'))
79
82
  }
80
83
  } else {
81
84
  process.stdout.write(connectionString)
@@ -83,9 +86,9 @@ export const urlCommand = new Command('url')
83
86
  console.log()
84
87
  }
85
88
  }
86
- } catch (err) {
87
- const e = err as Error
88
- console.error(error(e.message))
89
+ } catch (error) {
90
+ const e = error as Error
91
+ console.error(uiError(e.message))
89
92
  process.exit(1)
90
93
  }
91
94
  },
package/cli/helpers.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'fs'
2
2
  import { readdir, lstat } from 'fs/promises'
3
3
  import { join } from 'path'
4
- import { exec } from 'child_process'
4
+ import { exec, execFile } from 'child_process'
5
5
  import { promisify } from 'util'
6
6
  import { paths } from '../config/paths'
7
7
  import {
@@ -11,6 +11,7 @@ import {
11
11
  } from '../engines/mysql/binary-detection'
12
12
 
13
13
  const execAsync = promisify(exec)
14
+ const execFileAsync = promisify(execFile)
14
15
 
15
16
  export type InstalledPostgresEngine = {
16
17
  engine: 'postgresql'
@@ -49,7 +50,7 @@ async function getPostgresVersion(binPath: string): Promise<string | null> {
49
50
  }
50
51
 
51
52
  try {
52
- const { stdout } = await execAsync(`"${postgresPath}" --version`)
53
+ const { stdout } = await execFileAsync(postgresPath, ['--version'])
53
54
  const match = stdout.match(/\(PostgreSQL\)\s+([\d.]+)/)
54
55
  return match ? match[1] : null
55
56
  } catch {
@@ -57,7 +58,9 @@ async function getPostgresVersion(binPath: string): Promise<string | null> {
57
58
  }
58
59
  }
59
60
 
60
- export async function getInstalledPostgresEngines(): Promise<InstalledPostgresEngine[]> {
61
+ export async function getInstalledPostgresEngines(): Promise<
62
+ InstalledPostgresEngine[]
63
+ > {
61
64
  const binDir = paths.bin
62
65
 
63
66
  if (!existsSync(binDir)) {
@@ -144,7 +147,9 @@ async function getInstalledSqliteEngine(): Promise<InstalledSqliteEngine | null>
144
147
  return null
145
148
  }
146
149
 
147
- const { stdout: versionOutput } = await execAsync(`"${sqlitePath}" --version`)
150
+ const { stdout: versionOutput } = await execFileAsync(sqlitePath, [
151
+ '--version',
152
+ ])
148
153
  // sqlite3 --version outputs: "3.43.2 2023-10-10 12:14:04 ..."
149
154
  const versionMatch = versionOutput.match(/^([\d.]+)/)
150
155
  const version = versionMatch ? versionMatch[1] : 'unknown'
package/cli/ui/prompts.ts CHANGED
@@ -289,7 +289,9 @@ export async function promptDatabaseName(
289
289
  engine === 'mysql' ? 'Database (schema) name:' : 'Database name:'
290
290
 
291
291
  // Sanitize the default name to ensure it's valid
292
- const sanitizedDefault = defaultName ? sanitizeDatabaseName(defaultName) : undefined
292
+ const sanitizedDefault = defaultName
293
+ ? sanitizeDatabaseName(defaultName)
294
+ : undefined
293
295
 
294
296
  const { database } = await inquirer.prompt<{ database: string }>([
295
297
  {
@@ -420,7 +422,11 @@ export async function promptSqlitePath(
420
422
  ): Promise<string | undefined> {
421
423
  const defaultPath = `./${containerName}.sqlite`
422
424
 
423
- console.log(chalk.gray(' SQLite databases are stored as files in your project directory.'))
425
+ console.log(
426
+ chalk.gray(
427
+ ' SQLite databases are stored as files in your project directory.',
428
+ ),
429
+ )
424
430
  console.log(chalk.gray(` Default: ${defaultPath}`))
425
431
  console.log()
426
432
 
@@ -496,7 +502,8 @@ export async function promptSqlitePath(
496
502
  {
497
503
  type: 'list',
498
504
  name: 'overwrite',
499
- message: 'A file already exists at this location. What would you like to do?',
505
+ message:
506
+ 'A file already exists at this location. What would you like to do?',
500
507
  choices: [
501
508
  { name: 'Choose a different path', value: 'different' },
502
509
  { name: 'Cancel', value: 'cancel' },
@@ -702,8 +709,8 @@ export async function promptInstallDependencies(
702
709
 
703
710
  return false
704
711
  }
705
- } catch (err) {
706
- const e = err as Error
712
+ } catch (error) {
713
+ const e = error as Error
707
714
  console.log()
708
715
  console.log(chalk.red(` Installation failed: ${e.message}`))
709
716
  console.log()
package/cli/ui/spinner.ts CHANGED
@@ -27,10 +27,10 @@ export async function withSpinner<T>(
27
27
  })
28
28
  spinner.succeed()
29
29
  return result
30
- } catch (err) {
31
- const error = err as Error
32
- spinner.fail(error.message)
33
- throw error
30
+ } catch (error) {
31
+ const e = error as Error
32
+ spinner.fail(e.message)
33
+ throw e
34
34
  }
35
35
  }
36
36
 
package/cli/ui/theme.ts CHANGED
@@ -60,28 +60,28 @@ ${chalk.cyan('└' + line + '┘')}
60
60
  /**
61
61
  * Format a success message
62
62
  */
63
- export function success(message: string): string {
63
+ export function uiSuccess(message: string): string {
64
64
  return `${theme.icons.success} ${message}`
65
65
  }
66
66
 
67
67
  /**
68
68
  * Format an error message
69
69
  */
70
- export function error(message: string): string {
70
+ export function uiError(message: string): string {
71
71
  return `${theme.icons.error} ${chalk.red(message)}`
72
72
  }
73
73
 
74
74
  /**
75
75
  * Format a warning message
76
76
  */
77
- export function warning(message: string): string {
77
+ export function uiWarning(message: string): string {
78
78
  return `${theme.icons.warning} ${chalk.yellow(message)}`
79
79
  }
80
80
 
81
81
  /**
82
82
  * Format an info message
83
83
  */
84
- export function info(message: string): string {
84
+ export function uiInfo(message: string): string {
85
85
  return `${theme.icons.info} ${message}`
86
86
  }
87
87
 
@@ -6,7 +6,11 @@ import { exec } from 'child_process'
6
6
  import { promisify } from 'util'
7
7
  import { paths } from '../config/paths'
8
8
  import { defaults } from '../config/defaults'
9
- import { Engine, type ProgressCallback, type InstalledBinary } from '../types'
9
+ import {
10
+ type Engine,
11
+ type ProgressCallback,
12
+ type InstalledBinary,
13
+ } from '../types'
10
14
 
11
15
  const execAsync = promisify(exec)
12
16
 
@@ -397,12 +397,12 @@ export class ContainerManager {
397
397
  await this.saveConfig(targetName, { engine }, config)
398
398
 
399
399
  return config
400
- } catch (err) {
400
+ } catch (error) {
401
401
  // Clean up the copied directory on failure
402
402
  await rm(targetPath, { recursive: true, force: true }).catch(() => {
403
403
  // Ignore cleanup errors
404
404
  })
405
- throw err
405
+ throw error
406
406
  }
407
407
  }
408
408
 
@@ -497,8 +497,8 @@ export class ContainerManager {
497
497
  try {
498
498
  // Try atomic rename first (only works on same filesystem)
499
499
  await fsRename(sourcePath, targetPath)
500
- } catch (err) {
501
- const e = err as NodeJS.ErrnoException
500
+ } catch (error) {
501
+ const e = error as NodeJS.ErrnoException
502
502
  if (e.code === 'EXDEV') {
503
503
  // Cross-filesystem move - fall back to copy+delete
504
504
  await cp(sourcePath, targetPath, { recursive: true })
@@ -514,7 +514,7 @@ export class ContainerManager {
514
514
  )
515
515
  }
516
516
  } else {
517
- throw err
517
+ throw error
518
518
  }
519
519
  }
520
520
  }
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { homedir, platform as osPlatform, arch as osArch } from 'os'
14
- import { execSync, exec, spawn } from 'child_process'
14
+ import { execSync, execFileSync, exec, spawn } from 'child_process'
15
15
  import { promisify } from 'util'
16
16
  import { existsSync } from 'fs'
17
17
 
@@ -205,7 +205,7 @@ class DarwinPlatformService extends BasePlatformService {
205
205
  let getentResult: string | null = null
206
206
  if (sudoUser) {
207
207
  try {
208
- getentResult = execSync(`getent passwd ${sudoUser}`, {
208
+ getentResult = execFileSync('getent', ['passwd', sudoUser], {
209
209
  encoding: 'utf-8',
210
210
  })
211
211
  } catch {
@@ -347,7 +347,7 @@ class LinuxPlatformService extends BasePlatformService {
347
347
  let getentResult: string | null = null
348
348
  if (sudoUser) {
349
349
  try {
350
- getentResult = execSync(`getent passwd ${sudoUser}`, {
350
+ getentResult = execFileSync('getent', ['passwd', sudoUser], {
351
351
  encoding: 'utf-8',
352
352
  })
353
353
  } catch {
@@ -26,8 +26,8 @@ export type StartWithRetryResult = {
26
26
  error?: Error
27
27
  }
28
28
 
29
- function isPortInUseError(err: unknown): boolean {
30
- const message = (err as Error)?.message?.toLowerCase() || ''
29
+ function isPortInUseError(error: unknown): boolean {
30
+ const message = (error as Error)?.message?.toLowerCase() || ''
31
31
  return (
32
32
  message.includes('address already in use') ||
33
33
  message.includes('eaddrinuse') ||
@@ -62,14 +62,14 @@ export async function startWithRetry(
62
62
  finalPort: config.port,
63
63
  retriesUsed: attempt - 1,
64
64
  }
65
- } catch (err) {
66
- const isPortError = isPortInUseError(err)
65
+ } catch (error) {
66
+ const isPortError = isPortInUseError(error)
67
67
 
68
68
  logDebug(`Start attempt ${attempt} failed`, {
69
69
  containerName: config.name,
70
70
  port: config.port,
71
71
  isPortError,
72
- error: err instanceof Error ? err.message : String(err),
72
+ error: error instanceof Error ? error.message : String(error),
73
73
  })
74
74
 
75
75
  if (isPortError && attempt < maxRetries) {
@@ -101,7 +101,7 @@ export async function startWithRetry(
101
101
  success: false,
102
102
  finalPort: config.port,
103
103
  retriesUsed: attempt - 1,
104
- error: err instanceof Error ? err : new Error(String(err)),
104
+ error: error instanceof Error ? error : new Error(String(error)),
105
105
  }
106
106
  }
107
107
  }
@@ -36,9 +36,9 @@ export type RollbackAction = {
36
36
  * })
37
37
  *
38
38
  * tx.commit() // Success - clear rollback stack
39
- * } catch (err) {
39
+ * } catch (error) {
40
40
  * await tx.rollback() // Error - undo everything
41
- * throw err
41
+ * throw error
42
42
  * }
43
43
  * ```
44
44
  */
@@ -85,14 +85,14 @@ export class TransactionManager {
85
85
  logDebug(`Executing rollback: ${action.description}`)
86
86
  await action.execute()
87
87
  logDebug(`Rollback successful: ${action.description}`)
88
- } catch (err) {
88
+ } catch (error) {
89
89
  // Log error but continue with other rollbacks
90
90
  logError({
91
91
  code: ErrorCodes.ROLLBACK_FAILED,
92
92
  message: `Failed to rollback: ${action.description}`,
93
93
  severity: 'warning',
94
94
  context: {
95
- error: err instanceof Error ? err.message : String(err),
95
+ error: error instanceof Error ? error.message : String(error),
96
96
  },
97
97
  })
98
98
  }
@@ -155,8 +155,8 @@ export async function withTransaction<T>(
155
155
  const result = await operation(tx)
156
156
  tx.commit()
157
157
  return result
158
- } catch (err) {
158
+ } catch (error) {
159
159
  await tx.rollback()
160
- throw err
160
+ throw error
161
161
  }
162
162
  }