spindb 0.4.0 → 0.5.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.
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Engine-specific default configurations
3
+ * Extracted for dependency injection pattern - allows each engine to define its own defaults
4
+ */
5
+
6
+ export type EngineDefaults = {
7
+ /** Default version to use when not specified */
8
+ defaultVersion: string
9
+ /** Default port for this engine */
10
+ defaultPort: number
11
+ /** Port range to scan if default is busy */
12
+ portRange: { start: number; end: number }
13
+ /** Supported major versions */
14
+ supportedVersions: string[]
15
+ /** Default superuser name */
16
+ superuser: string
17
+ /** Connection string scheme (e.g., 'postgresql', 'mysql') */
18
+ connectionScheme: string
19
+ /** Log file name within container directory */
20
+ logFileName: string
21
+ /** PID file name (relative to data directory or container) */
22
+ pidFileName: string
23
+ /** Subdirectory for data files within container */
24
+ dataSubdir: string
25
+ /** Client tools required for this engine */
26
+ clientTools: string[]
27
+ }
28
+
29
+ export const engineDefaults: Record<string, EngineDefaults> = {
30
+ postgresql: {
31
+ defaultVersion: '16',
32
+ defaultPort: 5432,
33
+ portRange: { start: 5432, end: 5500 },
34
+ supportedVersions: ['14', '15', '16', '17'],
35
+ superuser: 'postgres',
36
+ connectionScheme: 'postgresql',
37
+ logFileName: 'postgres.log',
38
+ pidFileName: 'postmaster.pid',
39
+ dataSubdir: 'data',
40
+ clientTools: ['psql', 'pg_dump', 'pg_restore', 'pg_basebackup'],
41
+ },
42
+ mysql: {
43
+ defaultVersion: '9.0',
44
+ defaultPort: 3306,
45
+ portRange: { start: 3306, end: 3400 },
46
+ supportedVersions: ['5.7', '8.0', '8.4', '9.0'],
47
+ superuser: 'root',
48
+ connectionScheme: 'mysql',
49
+ logFileName: 'mysql.log',
50
+ pidFileName: 'mysql.pid',
51
+ dataSubdir: 'data',
52
+ clientTools: ['mysql', 'mysqldump', 'mysqlpump'],
53
+ },
54
+ }
55
+
56
+ /**
57
+ * Get engine defaults by name
58
+ * @throws Error if engine is not found
59
+ */
60
+ export function getEngineDefaults(engine: string): EngineDefaults {
61
+ const normalized = engine.toLowerCase()
62
+ const defaults = engineDefaults[normalized]
63
+ if (!defaults) {
64
+ const available = Object.keys(engineDefaults).join(', ')
65
+ throw new Error(
66
+ `Unknown engine "${engine}". Available engines: ${available}`,
67
+ )
68
+ }
69
+ return defaults
70
+ }
71
+
72
+ /**
73
+ * Check if an engine is supported
74
+ */
75
+ export function isEngineSupported(engine: string): boolean {
76
+ return engine.toLowerCase() in engineDefaults
77
+ }
78
+
79
+ /**
80
+ * Get list of all supported engine names
81
+ */
82
+ export function getSupportedEngines(): string[] {
83
+ return Object.keys(engineDefaults)
84
+ }
@@ -241,28 +241,53 @@ const postgresqlDependencies: EngineDependencies = {
241
241
 
242
242
  const mysqlDependencies: EngineDependencies = {
243
243
  engine: 'mysql',
244
- displayName: 'MySQL',
244
+ displayName: 'MySQL/MariaDB',
245
245
  dependencies: [
246
+ {
247
+ name: 'mysqld',
248
+ binary: 'mysqld',
249
+ description: 'MySQL/MariaDB server daemon',
250
+ packages: {
251
+ brew: { package: 'mysql' },
252
+ // Modern Debian/Ubuntu use mariadb-server (MySQL-compatible)
253
+ apt: { package: 'mariadb-server' },
254
+ yum: { package: 'mariadb-server' },
255
+ dnf: { package: 'mariadb-server' },
256
+ pacman: { package: 'mariadb' },
257
+ },
258
+ manualInstall: {
259
+ darwin: [
260
+ 'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
261
+ 'Then run: brew install mysql',
262
+ ],
263
+ linux: [
264
+ 'Debian/Ubuntu: sudo apt install mariadb-server',
265
+ 'CentOS/RHEL: sudo yum install mariadb-server',
266
+ 'Fedora: sudo dnf install mariadb-server',
267
+ 'Arch: sudo pacman -S mariadb',
268
+ ],
269
+ },
270
+ },
246
271
  {
247
272
  name: 'mysql',
248
273
  binary: 'mysql',
249
- description: 'MySQL command-line client',
274
+ description: 'MySQL/MariaDB command-line client',
250
275
  packages: {
251
- brew: { package: 'mysql-client' },
252
- apt: { package: 'mysql-client' },
253
- yum: { package: 'mysql' },
254
- dnf: { package: 'mysql' },
276
+ brew: { package: 'mysql' },
277
+ apt: { package: 'mariadb-client' },
278
+ yum: { package: 'mariadb' },
279
+ dnf: { package: 'mariadb' },
255
280
  pacman: { package: 'mariadb-clients' },
256
281
  },
257
282
  manualInstall: {
258
283
  darwin: [
259
284
  'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
260
- 'Then run: brew install mysql-client',
285
+ 'Then run: brew install mysql',
261
286
  ],
262
287
  linux: [
263
- 'Ubuntu/Debian: sudo apt install mysql-client',
264
- 'CentOS/RHEL: sudo yum install mysql',
265
- 'Fedora: sudo dnf install mysql',
288
+ 'Debian/Ubuntu: sudo apt install mariadb-client',
289
+ 'CentOS/RHEL: sudo yum install mariadb',
290
+ 'Fedora: sudo dnf install mariadb',
266
291
  'Arch: sudo pacman -S mariadb-clients',
267
292
  ],
268
293
  },
@@ -270,23 +295,47 @@ const mysqlDependencies: EngineDependencies = {
270
295
  {
271
296
  name: 'mysqldump',
272
297
  binary: 'mysqldump',
273
- description: 'MySQL database backup utility',
298
+ description: 'MySQL/MariaDB database backup utility',
299
+ packages: {
300
+ brew: { package: 'mysql' },
301
+ apt: { package: 'mariadb-client' },
302
+ yum: { package: 'mariadb' },
303
+ dnf: { package: 'mariadb' },
304
+ pacman: { package: 'mariadb-clients' },
305
+ },
306
+ manualInstall: {
307
+ darwin: [
308
+ 'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
309
+ 'Then run: brew install mysql',
310
+ ],
311
+ linux: [
312
+ 'Debian/Ubuntu: sudo apt install mariadb-client',
313
+ 'CentOS/RHEL: sudo yum install mariadb',
314
+ 'Fedora: sudo dnf install mariadb',
315
+ 'Arch: sudo pacman -S mariadb-clients',
316
+ ],
317
+ },
318
+ },
319
+ {
320
+ name: 'mysqladmin',
321
+ binary: 'mysqladmin',
322
+ description: 'MySQL/MariaDB server administration utility',
274
323
  packages: {
275
- brew: { package: 'mysql-client' },
276
- apt: { package: 'mysql-client' },
277
- yum: { package: 'mysql' },
278
- dnf: { package: 'mysql' },
324
+ brew: { package: 'mysql' },
325
+ apt: { package: 'mariadb-client' },
326
+ yum: { package: 'mariadb' },
327
+ dnf: { package: 'mariadb' },
279
328
  pacman: { package: 'mariadb-clients' },
280
329
  },
281
330
  manualInstall: {
282
331
  darwin: [
283
332
  'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
284
- 'Then run: brew install mysql-client',
333
+ 'Then run: brew install mysql',
285
334
  ],
286
335
  linux: [
287
- 'Ubuntu/Debian: sudo apt install mysql-client',
288
- 'CentOS/RHEL: sudo yum install mysql',
289
- 'Fedora: sudo dnf install mysql',
336
+ 'Debian/Ubuntu: sudo apt install mariadb-client',
337
+ 'CentOS/RHEL: sudo yum install mariadb',
338
+ 'Fedora: sudo dnf install mariadb',
290
339
  'Arch: sudo pacman -S mariadb-clients',
291
340
  ],
292
341
  },
package/config/paths.ts CHANGED
@@ -1,7 +1,63 @@
1
1
  import { homedir } from 'os'
2
2
  import { join } from 'path'
3
+ import { execSync } from 'child_process'
4
+ import { getEngineDefaults } from './engine-defaults'
3
5
 
4
- const SPINDB_HOME = join(homedir(), '.spindb')
6
+ /**
7
+ * Get the real user's home directory, even when running under sudo.
8
+ * When a user runs `sudo spindb`, we want to use their home directory,
9
+ * not root's home directory.
10
+ */
11
+ function getRealHomeDir(): string {
12
+ // Check if running under sudo
13
+ const sudoUser = process.env.SUDO_USER
14
+
15
+ if (sudoUser) {
16
+ // Get the original user's home directory
17
+ try {
18
+ // Use getent to reliably get the home directory for the sudo user
19
+ const result = execSync(`getent passwd ${sudoUser}`, {
20
+ encoding: 'utf-8',
21
+ })
22
+ const parts = result.trim().split(':')
23
+ if (parts.length >= 6 && parts[5]) {
24
+ return parts[5]
25
+ }
26
+ } catch {
27
+ // Fall back to constructing the path
28
+ // On most Linux systems, home dirs are /home/username
29
+ // On macOS, they're /Users/username
30
+ const platform = process.platform
31
+ if (platform === 'darwin') {
32
+ return `/Users/${sudoUser}`
33
+ } else {
34
+ return `/home/${sudoUser}`
35
+ }
36
+ }
37
+ }
38
+
39
+ // Not running under sudo, use normal homedir
40
+ return homedir()
41
+ }
42
+
43
+ const SPINDB_HOME = join(getRealHomeDir(), '.spindb')
44
+
45
+ /**
46
+ * Options for container path functions
47
+ */
48
+ type ContainerPathOptions = {
49
+ engine: string
50
+ }
51
+
52
+ /**
53
+ * Options for binary path functions
54
+ */
55
+ type BinaryPathOptions = {
56
+ engine: string
57
+ version: string
58
+ platform: string
59
+ arch: string
60
+ }
5
61
 
6
62
  export const paths = {
7
63
  // Root directory for all spindb data
@@ -16,38 +72,75 @@ export const paths = {
16
72
  // Global config file
17
73
  config: join(SPINDB_HOME, 'config.json'),
18
74
 
19
- // Get path for a specific binary version
20
- getBinaryPath(
21
- engine: string,
22
- version: string,
23
- platform: string,
24
- arch: string,
25
- ): string {
75
+ /**
76
+ * Get path for a specific binary version
77
+ */
78
+ getBinaryPath(options: BinaryPathOptions): string {
79
+ const { engine, version, platform, arch } = options
26
80
  return join(this.bin, `${engine}-${version}-${platform}-${arch}`)
27
81
  },
28
82
 
29
- // Get path for a specific container
30
- getContainerPath(name: string): string {
31
- return join(this.containers, name)
83
+ /**
84
+ * Get path for a specific container
85
+ * New structure: ~/.spindb/containers/{engine}/{name}/
86
+ */
87
+ getContainerPath(name: string, options: ContainerPathOptions): string {
88
+ const { engine } = options
89
+ return join(this.containers, engine, name)
90
+ },
91
+
92
+ /**
93
+ * Get path for container config file
94
+ */
95
+ getContainerConfigPath(name: string, options: ContainerPathOptions): string {
96
+ const { engine } = options
97
+ return join(this.containers, engine, name, 'container.json')
32
98
  },
33
99
 
34
- // Get path for container config
35
- getContainerConfigPath(name: string): string {
36
- return join(this.containers, name, 'container.json')
100
+ /**
101
+ * Get path for container data directory
102
+ */
103
+ getContainerDataPath(name: string, options: ContainerPathOptions): string {
104
+ const { engine } = options
105
+ const engineDef = getEngineDefaults(engine)
106
+ return join(this.containers, engine, name, engineDef.dataSubdir)
37
107
  },
38
108
 
39
- // Get path for container data directory
40
- getContainerDataPath(name: string): string {
41
- return join(this.containers, name, 'data')
109
+ /**
110
+ * Get path for container log file
111
+ */
112
+ getContainerLogPath(name: string, options: ContainerPathOptions): string {
113
+ const { engine } = options
114
+ const engineDef = getEngineDefaults(engine)
115
+ return join(this.containers, engine, name, engineDef.logFileName)
42
116
  },
43
117
 
44
- // Get path for container log file
45
- getContainerLogPath(name: string): string {
46
- return join(this.containers, name, 'postgres.log')
118
+ /**
119
+ * Get path for container PID file
120
+ * Note: For PostgreSQL, PID is inside data dir. For MySQL, it may differ.
121
+ */
122
+ getContainerPidPath(name: string, options: ContainerPathOptions): string {
123
+ const { engine } = options
124
+ const engineDef = getEngineDefaults(engine)
125
+ // PostgreSQL: data/postmaster.pid
126
+ // MySQL: data/mysql.pid (or just mysql.pid depending on config)
127
+ if (engine === 'postgresql') {
128
+ return join(
129
+ this.containers,
130
+ engine,
131
+ name,
132
+ engineDef.dataSubdir,
133
+ engineDef.pidFileName,
134
+ )
135
+ }
136
+ // MySQL and others: PID file at container level
137
+ return join(this.containers, engine, name, engineDef.pidFileName)
47
138
  },
48
139
 
49
- // Get path for container PID file
50
- getContainerPidPath(name: string): string {
51
- return join(this.containers, name, 'data', 'postmaster.pid')
140
+ /**
141
+ * Get path for engine-specific containers directory
142
+ */
143
+ getEngineContainersPath(engine: string): string {
144
+ return join(this.containers, engine)
52
145
  },
53
146
  }
@@ -62,7 +62,12 @@ export class BinaryManager {
62
62
  platform: string,
63
63
  arch: string,
64
64
  ): Promise<boolean> {
65
- const binPath = paths.getBinaryPath('postgresql', version, platform, arch)
65
+ const binPath = paths.getBinaryPath({
66
+ engine: 'postgresql',
67
+ version,
68
+ platform,
69
+ arch,
70
+ })
66
71
  const postgresPath = join(binPath, 'bin', 'postgres')
67
72
  return existsSync(postgresPath)
68
73
  }
@@ -109,7 +114,12 @@ export class BinaryManager {
109
114
  onProgress?: ProgressCallback,
110
115
  ): Promise<string> {
111
116
  const url = this.getDownloadUrl(version, platform, arch)
112
- const binPath = paths.getBinaryPath('postgresql', version, platform, arch)
117
+ const binPath = paths.getBinaryPath({
118
+ engine: 'postgresql',
119
+ version,
120
+ platform,
121
+ arch,
122
+ })
113
123
  const tempDir = join(paths.bin, `temp-${version}-${platform}-${arch}`)
114
124
  const jarFile = join(tempDir, 'postgres.jar')
115
125
 
@@ -190,7 +200,12 @@ export class BinaryManager {
190
200
  platform: string,
191
201
  arch: string,
192
202
  ): Promise<boolean> {
193
- const binPath = paths.getBinaryPath('postgresql', version, platform, arch)
203
+ const binPath = paths.getBinaryPath({
204
+ engine: 'postgresql',
205
+ version,
206
+ platform,
207
+ arch,
208
+ })
194
209
  const postgresPath = join(binPath, 'bin', 'postgres')
195
210
 
196
211
  if (!existsSync(postgresPath)) {
@@ -241,7 +256,12 @@ export class BinaryManager {
241
256
  arch: string,
242
257
  binary: string,
243
258
  ): string {
244
- const binPath = paths.getBinaryPath('postgresql', version, platform, arch)
259
+ const binPath = paths.getBinaryPath({
260
+ engine: 'postgresql',
261
+ version,
262
+ platform,
263
+ arch,
264
+ })
245
265
  return join(binPath, 'bin', binary)
246
266
  }
247
267
 
@@ -259,7 +279,12 @@ export class BinaryManager {
259
279
  stage: 'cached',
260
280
  message: 'Using cached PostgreSQL binaries',
261
281
  })
262
- return paths.getBinaryPath('postgresql', version, platform, arch)
282
+ return paths.getBinaryPath({
283
+ engine: 'postgresql',
284
+ version,
285
+ platform,
286
+ arch,
287
+ })
263
288
  }
264
289
 
265
290
  return this.download(version, platform, arch, onProgress)