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.
- package/README.md +77 -100
- package/cli/commands/clone.ts +3 -1
- package/cli/commands/connect.ts +50 -24
- package/cli/commands/create.ts +265 -112
- package/cli/commands/delete.ts +3 -1
- package/cli/commands/list.ts +14 -3
- package/cli/commands/menu.ts +250 -84
- package/cli/commands/restore.ts +142 -38
- package/cli/commands/start.ts +30 -4
- package/cli/commands/stop.ts +3 -1
- package/cli/ui/prompts.ts +95 -32
- package/config/defaults.ts +40 -15
- package/config/engine-defaults.ts +84 -0
- package/config/os-dependencies.ts +68 -19
- package/config/paths.ts +116 -23
- package/core/binary-manager.ts +30 -5
- package/core/container-manager.ts +124 -60
- package/core/dependency-manager.ts +44 -22
- package/core/port-manager.ts +42 -31
- package/core/postgres-binary-manager.ts +10 -9
- package/core/process-manager.ts +14 -6
- package/engines/index.ts +7 -2
- package/engines/mysql/binary-detection.ts +248 -0
- package/engines/mysql/index.ts +699 -0
- package/engines/postgresql/index.ts +13 -6
- package/package.json +4 -2
- package/types/index.ts +29 -5
|
@@ -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
|
|
252
|
-
apt: { package: '
|
|
253
|
-
yum: { package: '
|
|
254
|
-
dnf: { package: '
|
|
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
|
|
285
|
+
'Then run: brew install mysql',
|
|
261
286
|
],
|
|
262
287
|
linux: [
|
|
263
|
-
'Ubuntu
|
|
264
|
-
'CentOS/RHEL: sudo yum install
|
|
265
|
-
'Fedora: sudo dnf install
|
|
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
|
|
276
|
-
apt: { package: '
|
|
277
|
-
yum: { package: '
|
|
278
|
-
dnf: { package: '
|
|
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
|
|
333
|
+
'Then run: brew install mysql',
|
|
285
334
|
],
|
|
286
335
|
linux: [
|
|
287
|
-
'Ubuntu
|
|
288
|
-
'CentOS/RHEL: sudo yum install
|
|
289
|
-
'Fedora: sudo dnf install
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
platform
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
}
|
package/core/binary-manager.ts
CHANGED
|
@@ -62,7 +62,12 @@ export class BinaryManager {
|
|
|
62
62
|
platform: string,
|
|
63
63
|
arch: string,
|
|
64
64
|
): Promise<boolean> {
|
|
65
|
-
const binPath = paths.getBinaryPath(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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)
|