spindb 0.5.2 → 0.5.3
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 +137 -8
- package/cli/commands/connect.ts +8 -4
- package/cli/commands/create.ts +106 -67
- package/cli/commands/deps.ts +19 -4
- package/cli/commands/edit.ts +245 -0
- package/cli/commands/engines.ts +434 -0
- package/cli/commands/info.ts +279 -0
- package/cli/commands/menu.ts +408 -153
- package/cli/commands/restore.ts +10 -24
- package/cli/commands/start.ts +25 -20
- package/cli/commands/url.ts +79 -0
- package/cli/index.ts +9 -3
- package/cli/ui/prompts.ts +8 -6
- package/config/engine-defaults.ts +24 -1
- package/config/os-dependencies.ts +59 -113
- package/config/paths.ts +7 -36
- package/core/binary-manager.ts +19 -6
- package/core/config-manager.ts +17 -5
- package/core/dependency-manager.ts +9 -15
- package/core/error-handler.ts +336 -0
- package/core/platform-service.ts +634 -0
- package/core/port-manager.ts +11 -3
- package/core/process-manager.ts +12 -2
- package/core/start-with-retry.ts +167 -0
- package/core/transaction-manager.ts +170 -0
- package/engines/mysql/binary-detection.ts +177 -100
- package/engines/mysql/index.ts +240 -131
- package/engines/mysql/restore.ts +257 -0
- package/engines/mysql/version-validator.ts +373 -0
- package/{core/postgres-binary-manager.ts → engines/postgresql/binary-manager.ts} +63 -23
- package/engines/postgresql/binary-urls.ts +5 -3
- package/engines/postgresql/index.ts +4 -3
- package/engines/postgresql/restore.ts +54 -5
- package/engines/postgresql/version-validator.ts +262 -0
- package/package.json +6 -2
- package/cli/commands/postgres-tools.ts +0 -216
package/cli/commands/restore.ts
CHANGED
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
} from '../ui/prompts'
|
|
13
13
|
import { createSpinner } from '../ui/spinner'
|
|
14
14
|
import { success, error, warning } from '../ui/theme'
|
|
15
|
-
import {
|
|
16
|
-
import { spawn } from 'child_process'
|
|
15
|
+
import { tmpdir } from 'os'
|
|
17
16
|
import { join } from 'path'
|
|
18
17
|
import { getMissingDependencies } from '../../core/dependency-manager'
|
|
18
|
+
import { platformService } from '../../core/platform-service'
|
|
19
19
|
|
|
20
20
|
export const restoreCommand = new Command('restore')
|
|
21
21
|
.description('Restore a backup to a container')
|
|
@@ -182,7 +182,10 @@ export const restoreCommand = new Command('restore')
|
|
|
182
182
|
dumpSpinner.start()
|
|
183
183
|
|
|
184
184
|
try {
|
|
185
|
-
await engine.dumpFromConnectionString(
|
|
185
|
+
await engine.dumpFromConnectionString(
|
|
186
|
+
options.fromUrl,
|
|
187
|
+
tempDumpPath,
|
|
188
|
+
)
|
|
186
189
|
dumpSpinner.succeed('Dump created from remote database')
|
|
187
190
|
backupPath = tempDumpPath
|
|
188
191
|
dumpSuccess = true
|
|
@@ -307,28 +310,11 @@ export const restoreCommand = new Command('restore')
|
|
|
307
310
|
console.log(chalk.gray(' Connection string:'))
|
|
308
311
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
309
312
|
|
|
310
|
-
// Copy connection string to clipboard using platform
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const args =
|
|
314
|
-
platform() === 'darwin' ? [] : ['-selection', 'clipboard']
|
|
315
|
-
|
|
316
|
-
await new Promise<void>((resolve, reject) => {
|
|
317
|
-
const proc = spawn(cmd, args, {
|
|
318
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
319
|
-
})
|
|
320
|
-
proc.stdin?.write(connectionString)
|
|
321
|
-
proc.stdin?.end()
|
|
322
|
-
proc.on('close', (code) => {
|
|
323
|
-
if (code === 0) resolve()
|
|
324
|
-
else
|
|
325
|
-
reject(new Error(`Clipboard command exited with code ${code}`))
|
|
326
|
-
})
|
|
327
|
-
proc.on('error', reject)
|
|
328
|
-
})
|
|
329
|
-
|
|
313
|
+
// Copy connection string to clipboard using platform service
|
|
314
|
+
const copied = await platformService.copyToClipboard(connectionString)
|
|
315
|
+
if (copied) {
|
|
330
316
|
console.log(chalk.gray(' Connection string copied to clipboard'))
|
|
331
|
-
}
|
|
317
|
+
} else {
|
|
332
318
|
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
333
319
|
}
|
|
334
320
|
|
package/cli/commands/start.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import { containerManager } from '../../core/container-manager'
|
|
4
|
-
import { portManager } from '../../core/port-manager'
|
|
5
4
|
import { processManager } from '../../core/process-manager'
|
|
5
|
+
import { startWithRetry } from '../../core/start-with-retry'
|
|
6
6
|
import { getEngine } from '../../engines'
|
|
7
7
|
import { getEngineDefaults } from '../../config/defaults'
|
|
8
8
|
import { promptContainerSelect } from '../ui/prompts'
|
|
@@ -61,32 +61,37 @@ export const startCommand = new Command('start')
|
|
|
61
61
|
// Get engine defaults for port range and database name
|
|
62
62
|
const engineDefaults = getEngineDefaults(engineName)
|
|
63
63
|
|
|
64
|
-
//
|
|
65
|
-
const portAvailable = await portManager.isPortAvailable(config.port)
|
|
66
|
-
if (!portAvailable) {
|
|
67
|
-
// Try to find a new port (using engine-specific port range)
|
|
68
|
-
const { port: newPort } = await portManager.findAvailablePort({
|
|
69
|
-
portRange: engineDefaults.portRange,
|
|
70
|
-
})
|
|
71
|
-
console.log(
|
|
72
|
-
warning(
|
|
73
|
-
`Port ${config.port} is in use, switching to port ${newPort}`,
|
|
74
|
-
),
|
|
75
|
-
)
|
|
76
|
-
config.port = newPort
|
|
77
|
-
await containerManager.updateConfig(containerName, { port: newPort })
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Get engine and start
|
|
64
|
+
// Get engine and start with retry (handles port race conditions)
|
|
81
65
|
const engine = getEngine(engineName)
|
|
82
66
|
|
|
83
67
|
const spinner = createSpinner(`Starting ${containerName}...`)
|
|
84
68
|
spinner.start()
|
|
85
69
|
|
|
86
|
-
await
|
|
70
|
+
const result = await startWithRetry({
|
|
71
|
+
engine,
|
|
72
|
+
config,
|
|
73
|
+
onPortChange: (oldPort, newPort) => {
|
|
74
|
+
spinner.text = `Port ${oldPort} was in use, retrying with port ${newPort}...`
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
spinner.fail(`Failed to start "${containerName}"`)
|
|
80
|
+
if (result.error) {
|
|
81
|
+
console.error(error(result.error.message))
|
|
82
|
+
}
|
|
83
|
+
process.exit(1)
|
|
84
|
+
}
|
|
85
|
+
|
|
87
86
|
await containerManager.updateConfig(containerName, { status: 'running' })
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
if (result.retriesUsed > 0) {
|
|
89
|
+
spinner.warn(
|
|
90
|
+
`Container "${containerName}" started on port ${result.finalPort} (original port was in use)`,
|
|
91
|
+
)
|
|
92
|
+
} else {
|
|
93
|
+
spinner.succeed(`Container "${containerName}" started`)
|
|
94
|
+
}
|
|
90
95
|
|
|
91
96
|
// Ensure the user's database exists (if different from default)
|
|
92
97
|
const defaultDb = engineDefaults.superuser // postgres or root
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { containerManager } from '../../core/container-manager'
|
|
3
|
+
import { platformService } from '../../core/platform-service'
|
|
4
|
+
import { getEngine } from '../../engines'
|
|
5
|
+
import { promptContainerSelect } from '../ui/prompts'
|
|
6
|
+
import { error, warning, success } from '../ui/theme'
|
|
7
|
+
|
|
8
|
+
export const urlCommand = new Command('url')
|
|
9
|
+
.alias('connection-string')
|
|
10
|
+
.description('Output connection string for a container')
|
|
11
|
+
.argument('[name]', 'Container name')
|
|
12
|
+
.option('-c, --copy', 'Copy to clipboard')
|
|
13
|
+
.option('-d, --database <database>', 'Use different database name')
|
|
14
|
+
.action(
|
|
15
|
+
async (
|
|
16
|
+
name: string | undefined,
|
|
17
|
+
options: { copy?: boolean; database?: string },
|
|
18
|
+
) => {
|
|
19
|
+
try {
|
|
20
|
+
let containerName = name
|
|
21
|
+
|
|
22
|
+
// Interactive selection if no name provided
|
|
23
|
+
if (!containerName) {
|
|
24
|
+
const containers = await containerManager.list()
|
|
25
|
+
|
|
26
|
+
if (containers.length === 0) {
|
|
27
|
+
console.log(warning('No containers found'))
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const selected = await promptContainerSelect(
|
|
32
|
+
containers,
|
|
33
|
+
'Select container:',
|
|
34
|
+
)
|
|
35
|
+
if (!selected) return
|
|
36
|
+
containerName = selected
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Get container config
|
|
40
|
+
const config = await containerManager.getConfig(containerName)
|
|
41
|
+
if (!config) {
|
|
42
|
+
console.error(error(`Container "${containerName}" not found`))
|
|
43
|
+
process.exit(1)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get connection string
|
|
47
|
+
const engine = getEngine(config.engine)
|
|
48
|
+
const connectionString = engine.getConnectionString(
|
|
49
|
+
config,
|
|
50
|
+
options.database,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
// Copy to clipboard if requested
|
|
54
|
+
if (options.copy) {
|
|
55
|
+
const copied = await platformService.copyToClipboard(connectionString)
|
|
56
|
+
if (copied) {
|
|
57
|
+
// Output the string AND confirmation
|
|
58
|
+
console.log(connectionString)
|
|
59
|
+
console.error(success('Copied to clipboard'))
|
|
60
|
+
} else {
|
|
61
|
+
// Output the string but warn about clipboard
|
|
62
|
+
console.log(connectionString)
|
|
63
|
+
console.error(warning('Could not copy to clipboard'))
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// Just output the connection string (no newline formatting for easy piping)
|
|
67
|
+
process.stdout.write(connectionString)
|
|
68
|
+
// Add newline if stdout is a TTY (interactive terminal)
|
|
69
|
+
if (process.stdout.isTTY) {
|
|
70
|
+
console.log()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const e = err as Error
|
|
75
|
+
console.error(error(e.message))
|
|
76
|
+
process.exit(1)
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
)
|
package/cli/index.ts
CHANGED
|
@@ -9,14 +9,17 @@ import { connectCommand } from './commands/connect'
|
|
|
9
9
|
import { cloneCommand } from './commands/clone'
|
|
10
10
|
import { menuCommand } from './commands/menu'
|
|
11
11
|
import { configCommand } from './commands/config'
|
|
12
|
-
import { postgresToolsCommand } from './commands/postgres-tools'
|
|
13
12
|
import { depsCommand } from './commands/deps'
|
|
13
|
+
import { enginesCommand } from './commands/engines'
|
|
14
|
+
import { editCommand } from './commands/edit'
|
|
15
|
+
import { urlCommand } from './commands/url'
|
|
16
|
+
import { infoCommand } from './commands/info'
|
|
14
17
|
|
|
15
18
|
export async function run(): Promise<void> {
|
|
16
19
|
program
|
|
17
20
|
.name('spindb')
|
|
18
21
|
.description('Spin up local database containers without Docker')
|
|
19
|
-
.version('0.1.0')
|
|
22
|
+
.version('0.1.0', '-v, --version', 'output the version number')
|
|
20
23
|
|
|
21
24
|
program.addCommand(createCommand)
|
|
22
25
|
program.addCommand(listCommand)
|
|
@@ -28,8 +31,11 @@ export async function run(): Promise<void> {
|
|
|
28
31
|
program.addCommand(cloneCommand)
|
|
29
32
|
program.addCommand(menuCommand)
|
|
30
33
|
program.addCommand(configCommand)
|
|
31
|
-
program.addCommand(postgresToolsCommand)
|
|
32
34
|
program.addCommand(depsCommand)
|
|
35
|
+
program.addCommand(enginesCommand)
|
|
36
|
+
program.addCommand(editCommand)
|
|
37
|
+
program.addCommand(urlCommand)
|
|
38
|
+
program.addCommand(infoCommand)
|
|
33
39
|
|
|
34
40
|
// If no arguments provided, show interactive menu
|
|
35
41
|
if (process.argv.length <= 2) {
|
package/cli/ui/prompts.ts
CHANGED
|
@@ -3,7 +3,7 @@ import chalk from 'chalk'
|
|
|
3
3
|
import ora from 'ora'
|
|
4
4
|
import { listEngines, getEngine } from '../../engines'
|
|
5
5
|
import { defaults, getEngineDefaults } from '../../config/defaults'
|
|
6
|
-
import { installPostgresBinaries } from '../../
|
|
6
|
+
import { installPostgresBinaries } from '../../engines/postgresql/binary-manager'
|
|
7
7
|
import {
|
|
8
8
|
detectPackageManager,
|
|
9
9
|
getManualInstallInstructions,
|
|
@@ -333,7 +333,9 @@ export async function promptInstallDependencies(
|
|
|
333
333
|
if (dep) {
|
|
334
334
|
const instructions = getManualInstallInstructions(dep, platform)
|
|
335
335
|
console.log(
|
|
336
|
-
chalk.gray(
|
|
336
|
+
chalk.gray(
|
|
337
|
+
` Please install ${engineDeps.displayName} client tools:`,
|
|
338
|
+
),
|
|
337
339
|
)
|
|
338
340
|
console.log()
|
|
339
341
|
for (const instruction of instructions) {
|
|
@@ -346,7 +348,9 @@ export async function promptInstallDependencies(
|
|
|
346
348
|
}
|
|
347
349
|
|
|
348
350
|
console.log(
|
|
349
|
-
chalk.gray(
|
|
351
|
+
chalk.gray(
|
|
352
|
+
` Detected package manager: ${chalk.white(packageManager.name)}`,
|
|
353
|
+
),
|
|
350
354
|
)
|
|
351
355
|
console.log()
|
|
352
356
|
|
|
@@ -425,9 +429,7 @@ export async function promptInstallDependencies(
|
|
|
425
429
|
|
|
426
430
|
if (allSuccess) {
|
|
427
431
|
console.log()
|
|
428
|
-
console.log(
|
|
429
|
-
chalk.green(` ${engineName} tools installed successfully!`),
|
|
430
|
-
)
|
|
432
|
+
console.log(chalk.green(` ${engineName} tools installed successfully!`))
|
|
431
433
|
console.log(chalk.gray(' Continuing with your operation...'))
|
|
432
434
|
console.log()
|
|
433
435
|
return true
|
|
@@ -12,6 +12,8 @@ export type EngineDefaults = {
|
|
|
12
12
|
portRange: { start: number; end: number }
|
|
13
13
|
/** Supported major versions */
|
|
14
14
|
supportedVersions: string[]
|
|
15
|
+
/** Latest major version (used for Homebrew package names like postgresql@17) */
|
|
16
|
+
latestVersion: string
|
|
15
17
|
/** Default superuser name */
|
|
16
18
|
superuser: string
|
|
17
19
|
/** Connection string scheme (e.g., 'postgresql', 'mysql') */
|
|
@@ -28,10 +30,11 @@ export type EngineDefaults = {
|
|
|
28
30
|
|
|
29
31
|
export const engineDefaults: Record<string, EngineDefaults> = {
|
|
30
32
|
postgresql: {
|
|
31
|
-
defaultVersion: '
|
|
33
|
+
defaultVersion: '17',
|
|
32
34
|
defaultPort: 5432,
|
|
33
35
|
portRange: { start: 5432, end: 5500 },
|
|
34
36
|
supportedVersions: ['14', '15', '16', '17'],
|
|
37
|
+
latestVersion: '17', // Update when PostgreSQL 18 is released
|
|
35
38
|
superuser: 'postgres',
|
|
36
39
|
connectionScheme: 'postgresql',
|
|
37
40
|
logFileName: 'postgres.log',
|
|
@@ -44,6 +47,7 @@ export const engineDefaults: Record<string, EngineDefaults> = {
|
|
|
44
47
|
defaultPort: 3306,
|
|
45
48
|
portRange: { start: 3306, end: 3400 },
|
|
46
49
|
supportedVersions: ['5.7', '8.0', '8.4', '9.0'],
|
|
50
|
+
latestVersion: '9.0', // MySQL doesn't use versioned Homebrew packages, but kept for consistency
|
|
47
51
|
superuser: 'root',
|
|
48
52
|
connectionScheme: 'mysql',
|
|
49
53
|
logFileName: 'mysql.log',
|
|
@@ -82,3 +86,22 @@ export function isEngineSupported(engine: string): boolean {
|
|
|
82
86
|
export function getSupportedEngines(): string[] {
|
|
83
87
|
return Object.keys(engineDefaults)
|
|
84
88
|
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get Homebrew package name for PostgreSQL client tools
|
|
92
|
+
* Returns 'postgresql@17' format for versioned installs
|
|
93
|
+
*/
|
|
94
|
+
export function getPostgresHomebrewPackage(): string {
|
|
95
|
+
const version = engineDefaults.postgresql.latestVersion
|
|
96
|
+
return `postgresql@${version}`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get the PostgreSQL Homebrew bin path for a given architecture
|
|
101
|
+
* @param arch - 'arm64' or 'x64'
|
|
102
|
+
*/
|
|
103
|
+
export function getPostgresHomebrewBinPath(arch: 'arm64' | 'x64'): string {
|
|
104
|
+
const pkg = getPostgresHomebrewPackage()
|
|
105
|
+
const prefix = arch === 'arm64' ? '/opt/homebrew' : '/usr/local'
|
|
106
|
+
return `${prefix}/opt/${pkg}/bin`
|
|
107
|
+
}
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* across different operating systems and package managers.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { getPostgresHomebrewPackage } from './engine-defaults'
|
|
9
|
+
|
|
8
10
|
export type PackageManagerId = 'brew' | 'apt' | 'yum' | 'dnf' | 'pacman'
|
|
9
11
|
|
|
10
|
-
export type Platform = 'darwin' | 'linux'
|
|
12
|
+
export type Platform = 'darwin' | 'linux' | 'win32'
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Package definition for a specific package manager
|
|
@@ -116,122 +118,66 @@ export const packageManagers: PackageManagerConfig[] = [
|
|
|
116
118
|
// PostgreSQL Dependencies
|
|
117
119
|
// =============================================================================
|
|
118
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Helper to create PostgreSQL client tool dependency
|
|
123
|
+
* Uses getPostgresHomebrewPackage() to get the current latest version
|
|
124
|
+
*/
|
|
125
|
+
function createPostgresDependency(
|
|
126
|
+
name: string,
|
|
127
|
+
binary: string,
|
|
128
|
+
description: string,
|
|
129
|
+
): Dependency {
|
|
130
|
+
const pgPackage = getPostgresHomebrewPackage()
|
|
131
|
+
return {
|
|
132
|
+
name,
|
|
133
|
+
binary,
|
|
134
|
+
description,
|
|
135
|
+
packages: {
|
|
136
|
+
brew: {
|
|
137
|
+
package: pgPackage,
|
|
138
|
+
postInstall: [`brew link --overwrite ${pgPackage}`],
|
|
139
|
+
},
|
|
140
|
+
apt: { package: 'postgresql-client' },
|
|
141
|
+
yum: { package: 'postgresql' },
|
|
142
|
+
dnf: { package: 'postgresql' },
|
|
143
|
+
pacman: { package: 'postgresql-libs' },
|
|
144
|
+
},
|
|
145
|
+
manualInstall: {
|
|
146
|
+
darwin: [
|
|
147
|
+
'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
148
|
+
`Then run: brew install ${pgPackage} && brew link --overwrite ${pgPackage}`,
|
|
149
|
+
'Or install Postgres.app: https://postgresapp.com/downloads.html',
|
|
150
|
+
],
|
|
151
|
+
linux: [
|
|
152
|
+
'Ubuntu/Debian: sudo apt install postgresql-client',
|
|
153
|
+
'CentOS/RHEL: sudo yum install postgresql',
|
|
154
|
+
'Fedora: sudo dnf install postgresql',
|
|
155
|
+
'Arch: sudo pacman -S postgresql-libs',
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
119
161
|
const postgresqlDependencies: EngineDependencies = {
|
|
120
162
|
engine: 'postgresql',
|
|
121
163
|
displayName: 'PostgreSQL',
|
|
122
164
|
dependencies: [
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
140
|
-
'Then run: brew install postgresql@17 && brew link --overwrite postgresql@17',
|
|
141
|
-
'Or install Postgres.app: https://postgresapp.com/downloads.html',
|
|
142
|
-
],
|
|
143
|
-
linux: [
|
|
144
|
-
'Ubuntu/Debian: sudo apt install postgresql-client',
|
|
145
|
-
'CentOS/RHEL: sudo yum install postgresql',
|
|
146
|
-
'Fedora: sudo dnf install postgresql',
|
|
147
|
-
'Arch: sudo pacman -S postgresql-libs',
|
|
148
|
-
],
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
name: 'pg_dump',
|
|
153
|
-
binary: 'pg_dump',
|
|
154
|
-
description: 'PostgreSQL database backup utility',
|
|
155
|
-
packages: {
|
|
156
|
-
brew: {
|
|
157
|
-
package: 'postgresql@17',
|
|
158
|
-
postInstall: ['brew link --overwrite postgresql@17'],
|
|
159
|
-
},
|
|
160
|
-
apt: { package: 'postgresql-client' },
|
|
161
|
-
yum: { package: 'postgresql' },
|
|
162
|
-
dnf: { package: 'postgresql' },
|
|
163
|
-
pacman: { package: 'postgresql-libs' },
|
|
164
|
-
},
|
|
165
|
-
manualInstall: {
|
|
166
|
-
darwin: [
|
|
167
|
-
'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
168
|
-
'Then run: brew install postgresql@17 && brew link --overwrite postgresql@17',
|
|
169
|
-
'Or install Postgres.app: https://postgresapp.com/downloads.html',
|
|
170
|
-
],
|
|
171
|
-
linux: [
|
|
172
|
-
'Ubuntu/Debian: sudo apt install postgresql-client',
|
|
173
|
-
'CentOS/RHEL: sudo yum install postgresql',
|
|
174
|
-
'Fedora: sudo dnf install postgresql',
|
|
175
|
-
'Arch: sudo pacman -S postgresql-libs',
|
|
176
|
-
],
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
name: 'pg_restore',
|
|
181
|
-
binary: 'pg_restore',
|
|
182
|
-
description: 'PostgreSQL database restore utility',
|
|
183
|
-
packages: {
|
|
184
|
-
brew: {
|
|
185
|
-
package: 'postgresql@17',
|
|
186
|
-
postInstall: ['brew link --overwrite postgresql@17'],
|
|
187
|
-
},
|
|
188
|
-
apt: { package: 'postgresql-client' },
|
|
189
|
-
yum: { package: 'postgresql' },
|
|
190
|
-
dnf: { package: 'postgresql' },
|
|
191
|
-
pacman: { package: 'postgresql-libs' },
|
|
192
|
-
},
|
|
193
|
-
manualInstall: {
|
|
194
|
-
darwin: [
|
|
195
|
-
'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
196
|
-
'Then run: brew install postgresql@17 && brew link --overwrite postgresql@17',
|
|
197
|
-
'Or install Postgres.app: https://postgresapp.com/downloads.html',
|
|
198
|
-
],
|
|
199
|
-
linux: [
|
|
200
|
-
'Ubuntu/Debian: sudo apt install postgresql-client',
|
|
201
|
-
'CentOS/RHEL: sudo yum install postgresql',
|
|
202
|
-
'Fedora: sudo dnf install postgresql',
|
|
203
|
-
'Arch: sudo pacman -S postgresql-libs',
|
|
204
|
-
],
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
name: 'pg_basebackup',
|
|
209
|
-
binary: 'pg_basebackup',
|
|
210
|
-
description: 'PostgreSQL base backup utility for physical backups',
|
|
211
|
-
packages: {
|
|
212
|
-
brew: {
|
|
213
|
-
package: 'postgresql@17',
|
|
214
|
-
postInstall: ['brew link --overwrite postgresql@17'],
|
|
215
|
-
},
|
|
216
|
-
apt: { package: 'postgresql-client' },
|
|
217
|
-
yum: { package: 'postgresql' },
|
|
218
|
-
dnf: { package: 'postgresql' },
|
|
219
|
-
pacman: { package: 'postgresql-libs' },
|
|
220
|
-
},
|
|
221
|
-
manualInstall: {
|
|
222
|
-
darwin: [
|
|
223
|
-
'Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
224
|
-
'Then run: brew install postgresql@17 && brew link --overwrite postgresql@17',
|
|
225
|
-
'Or install Postgres.app: https://postgresapp.com/downloads.html',
|
|
226
|
-
],
|
|
227
|
-
linux: [
|
|
228
|
-
'Ubuntu/Debian: sudo apt install postgresql-client',
|
|
229
|
-
'CentOS/RHEL: sudo yum install postgresql',
|
|
230
|
-
'Fedora: sudo dnf install postgresql',
|
|
231
|
-
'Arch: sudo pacman -S postgresql-libs',
|
|
232
|
-
],
|
|
233
|
-
},
|
|
234
|
-
},
|
|
165
|
+
createPostgresDependency('psql', 'psql', 'PostgreSQL interactive terminal'),
|
|
166
|
+
createPostgresDependency(
|
|
167
|
+
'pg_dump',
|
|
168
|
+
'pg_dump',
|
|
169
|
+
'PostgreSQL database backup utility',
|
|
170
|
+
),
|
|
171
|
+
createPostgresDependency(
|
|
172
|
+
'pg_restore',
|
|
173
|
+
'pg_restore',
|
|
174
|
+
'PostgreSQL database restore utility',
|
|
175
|
+
),
|
|
176
|
+
createPostgresDependency(
|
|
177
|
+
'pg_basebackup',
|
|
178
|
+
'pg_basebackup',
|
|
179
|
+
'PostgreSQL base backup utility for physical backups',
|
|
180
|
+
),
|
|
235
181
|
],
|
|
236
182
|
}
|
|
237
183
|
|
package/config/paths.ts
CHANGED
|
@@ -1,46 +1,17 @@
|
|
|
1
|
-
import { homedir } from 'os'
|
|
2
1
|
import { join } from 'path'
|
|
3
|
-
import { execSync } from 'child_process'
|
|
4
2
|
import { getEngineDefaults } from './engine-defaults'
|
|
3
|
+
import { platformService } from '../core/platform-service'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* Get the
|
|
8
|
-
*
|
|
9
|
-
* not root's home directory.
|
|
6
|
+
* Get the SpinDB home directory using the platform service.
|
|
7
|
+
* This handles sudo detection and platform-specific home directories.
|
|
10
8
|
*/
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
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()
|
|
9
|
+
function getSpinDBHome(): string {
|
|
10
|
+
const platformInfo = platformService.getPlatformInfo()
|
|
11
|
+
return join(platformInfo.homeDir, '.spindb')
|
|
41
12
|
}
|
|
42
13
|
|
|
43
|
-
const SPINDB_HOME =
|
|
14
|
+
const SPINDB_HOME = getSpinDBHome()
|
|
44
15
|
|
|
45
16
|
/**
|
|
46
17
|
* Options for container path functions
|
package/core/binary-manager.ts
CHANGED
|
@@ -54,6 +54,14 @@ export class BinaryManager {
|
|
|
54
54
|
return version
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Get major version from any version string (e.g., "17.7.0" -> "17", "16" -> "16")
|
|
59
|
+
* Used for directory naming to ensure one directory per major version.
|
|
60
|
+
*/
|
|
61
|
+
getMajorVersion(version: string): string {
|
|
62
|
+
return version.split('.')[0]
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
/**
|
|
58
66
|
* Check if binaries for a specific version are already installed
|
|
59
67
|
*/
|
|
@@ -62,9 +70,10 @@ export class BinaryManager {
|
|
|
62
70
|
platform: string,
|
|
63
71
|
arch: string,
|
|
64
72
|
): Promise<boolean> {
|
|
73
|
+
const majorVersion = this.getMajorVersion(version)
|
|
65
74
|
const binPath = paths.getBinaryPath({
|
|
66
75
|
engine: 'postgresql',
|
|
67
|
-
version,
|
|
76
|
+
version: majorVersion,
|
|
68
77
|
platform,
|
|
69
78
|
arch,
|
|
70
79
|
})
|
|
@@ -113,14 +122,15 @@ export class BinaryManager {
|
|
|
113
122
|
arch: string,
|
|
114
123
|
onProgress?: ProgressCallback,
|
|
115
124
|
): Promise<string> {
|
|
125
|
+
const majorVersion = this.getMajorVersion(version)
|
|
116
126
|
const url = this.getDownloadUrl(version, platform, arch)
|
|
117
127
|
const binPath = paths.getBinaryPath({
|
|
118
128
|
engine: 'postgresql',
|
|
119
|
-
version,
|
|
129
|
+
version: majorVersion,
|
|
120
130
|
platform,
|
|
121
131
|
arch,
|
|
122
132
|
})
|
|
123
|
-
const tempDir = join(paths.bin, `temp-${
|
|
133
|
+
const tempDir = join(paths.bin, `temp-${majorVersion}-${platform}-${arch}`)
|
|
124
134
|
const jarFile = join(tempDir, 'postgres.jar')
|
|
125
135
|
|
|
126
136
|
// Ensure directories exist
|
|
@@ -200,9 +210,10 @@ export class BinaryManager {
|
|
|
200
210
|
platform: string,
|
|
201
211
|
arch: string,
|
|
202
212
|
): Promise<boolean> {
|
|
213
|
+
const majorVersion = this.getMajorVersion(version)
|
|
203
214
|
const binPath = paths.getBinaryPath({
|
|
204
215
|
engine: 'postgresql',
|
|
205
|
-
version,
|
|
216
|
+
version: majorVersion,
|
|
206
217
|
platform,
|
|
207
218
|
arch,
|
|
208
219
|
})
|
|
@@ -256,9 +267,10 @@ export class BinaryManager {
|
|
|
256
267
|
arch: string,
|
|
257
268
|
binary: string,
|
|
258
269
|
): string {
|
|
270
|
+
const majorVersion = this.getMajorVersion(version)
|
|
259
271
|
const binPath = paths.getBinaryPath({
|
|
260
272
|
engine: 'postgresql',
|
|
261
|
-
version,
|
|
273
|
+
version: majorVersion,
|
|
262
274
|
platform,
|
|
263
275
|
arch,
|
|
264
276
|
})
|
|
@@ -274,6 +286,7 @@ export class BinaryManager {
|
|
|
274
286
|
arch: string,
|
|
275
287
|
onProgress?: ProgressCallback,
|
|
276
288
|
): Promise<string> {
|
|
289
|
+
const majorVersion = this.getMajorVersion(version)
|
|
277
290
|
if (await this.isInstalled(version, platform, arch)) {
|
|
278
291
|
onProgress?.({
|
|
279
292
|
stage: 'cached',
|
|
@@ -281,7 +294,7 @@ export class BinaryManager {
|
|
|
281
294
|
})
|
|
282
295
|
return paths.getBinaryPath({
|
|
283
296
|
engine: 'postgresql',
|
|
284
|
-
version,
|
|
297
|
+
version: majorVersion,
|
|
285
298
|
platform,
|
|
286
299
|
arch,
|
|
287
300
|
})
|