spindb 0.4.1 → 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 +207 -101
- package/cli/commands/clone.ts +3 -1
- package/cli/commands/connect.ts +54 -24
- package/cli/commands/create.ts +309 -189
- package/cli/commands/delete.ts +3 -1
- 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/list.ts +14 -3
- package/cli/commands/menu.ts +510 -198
- package/cli/commands/restore.ts +66 -43
- package/cli/commands/start.ts +50 -19
- package/cli/commands/stop.ts +3 -1
- package/cli/commands/url.ts +79 -0
- package/cli/index.ts +9 -3
- package/cli/ui/prompts.ts +99 -34
- package/config/defaults.ts +40 -15
- package/config/engine-defaults.ts +107 -0
- package/config/os-dependencies.ts +119 -124
- package/config/paths.ts +82 -56
- package/core/binary-manager.ts +44 -6
- package/core/config-manager.ts +17 -5
- package/core/container-manager.ts +124 -60
- 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 +51 -32
- package/core/process-manager.ts +26 -8
- package/core/start-with-retry.ts +167 -0
- package/core/transaction-manager.ts +170 -0
- package/engines/index.ts +7 -2
- package/engines/mysql/binary-detection.ts +325 -0
- package/engines/mysql/index.ts +808 -0
- 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 +17 -9
- package/engines/postgresql/restore.ts +54 -5
- package/engines/postgresql/version-validator.ts +262 -0
- package/package.json +9 -3
- package/types/index.ts +29 -5
- package/cli/commands/postgres-tools.ts +0 -216
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,7 +70,13 @@ export class BinaryManager {
|
|
|
62
70
|
platform: string,
|
|
63
71
|
arch: string,
|
|
64
72
|
): Promise<boolean> {
|
|
65
|
-
const
|
|
73
|
+
const majorVersion = this.getMajorVersion(version)
|
|
74
|
+
const binPath = paths.getBinaryPath({
|
|
75
|
+
engine: 'postgresql',
|
|
76
|
+
version: majorVersion,
|
|
77
|
+
platform,
|
|
78
|
+
arch,
|
|
79
|
+
})
|
|
66
80
|
const postgresPath = join(binPath, 'bin', 'postgres')
|
|
67
81
|
return existsSync(postgresPath)
|
|
68
82
|
}
|
|
@@ -108,9 +122,15 @@ export class BinaryManager {
|
|
|
108
122
|
arch: string,
|
|
109
123
|
onProgress?: ProgressCallback,
|
|
110
124
|
): Promise<string> {
|
|
125
|
+
const majorVersion = this.getMajorVersion(version)
|
|
111
126
|
const url = this.getDownloadUrl(version, platform, arch)
|
|
112
|
-
const binPath = paths.getBinaryPath(
|
|
113
|
-
|
|
127
|
+
const binPath = paths.getBinaryPath({
|
|
128
|
+
engine: 'postgresql',
|
|
129
|
+
version: majorVersion,
|
|
130
|
+
platform,
|
|
131
|
+
arch,
|
|
132
|
+
})
|
|
133
|
+
const tempDir = join(paths.bin, `temp-${majorVersion}-${platform}-${arch}`)
|
|
114
134
|
const jarFile = join(tempDir, 'postgres.jar')
|
|
115
135
|
|
|
116
136
|
// Ensure directories exist
|
|
@@ -190,7 +210,13 @@ export class BinaryManager {
|
|
|
190
210
|
platform: string,
|
|
191
211
|
arch: string,
|
|
192
212
|
): Promise<boolean> {
|
|
193
|
-
const
|
|
213
|
+
const majorVersion = this.getMajorVersion(version)
|
|
214
|
+
const binPath = paths.getBinaryPath({
|
|
215
|
+
engine: 'postgresql',
|
|
216
|
+
version: majorVersion,
|
|
217
|
+
platform,
|
|
218
|
+
arch,
|
|
219
|
+
})
|
|
194
220
|
const postgresPath = join(binPath, 'bin', 'postgres')
|
|
195
221
|
|
|
196
222
|
if (!existsSync(postgresPath)) {
|
|
@@ -241,7 +267,13 @@ export class BinaryManager {
|
|
|
241
267
|
arch: string,
|
|
242
268
|
binary: string,
|
|
243
269
|
): string {
|
|
244
|
-
const
|
|
270
|
+
const majorVersion = this.getMajorVersion(version)
|
|
271
|
+
const binPath = paths.getBinaryPath({
|
|
272
|
+
engine: 'postgresql',
|
|
273
|
+
version: majorVersion,
|
|
274
|
+
platform,
|
|
275
|
+
arch,
|
|
276
|
+
})
|
|
245
277
|
return join(binPath, 'bin', binary)
|
|
246
278
|
}
|
|
247
279
|
|
|
@@ -254,12 +286,18 @@ export class BinaryManager {
|
|
|
254
286
|
arch: string,
|
|
255
287
|
onProgress?: ProgressCallback,
|
|
256
288
|
): Promise<string> {
|
|
289
|
+
const majorVersion = this.getMajorVersion(version)
|
|
257
290
|
if (await this.isInstalled(version, platform, arch)) {
|
|
258
291
|
onProgress?.({
|
|
259
292
|
stage: 'cached',
|
|
260
293
|
message: 'Using cached PostgreSQL binaries',
|
|
261
294
|
})
|
|
262
|
-
return paths.getBinaryPath(
|
|
295
|
+
return paths.getBinaryPath({
|
|
296
|
+
engine: 'postgresql',
|
|
297
|
+
version: majorVersion,
|
|
298
|
+
platform,
|
|
299
|
+
arch,
|
|
300
|
+
})
|
|
263
301
|
}
|
|
264
302
|
|
|
265
303
|
return this.download(version, platform, arch, onProgress)
|
package/core/config-manager.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { exec } from 'child_process'
|
|
|
4
4
|
import { promisify } from 'util'
|
|
5
5
|
import { dirname } from 'path'
|
|
6
6
|
import { paths } from '../config/paths'
|
|
7
|
+
import { logDebug, logWarning } from './error-handler'
|
|
7
8
|
import type {
|
|
8
9
|
SpinDBConfig,
|
|
9
10
|
BinaryConfig,
|
|
@@ -41,8 +42,12 @@ export class ConfigManager {
|
|
|
41
42
|
const content = await readFile(configPath, 'utf8')
|
|
42
43
|
this.config = JSON.parse(content) as SpinDBConfig
|
|
43
44
|
return this.config
|
|
44
|
-
} catch {
|
|
45
|
+
} catch (error) {
|
|
45
46
|
// If config is corrupted, reset to default
|
|
47
|
+
logWarning('Config file corrupted, resetting to default', {
|
|
48
|
+
configPath,
|
|
49
|
+
error: error instanceof Error ? error.message : String(error),
|
|
50
|
+
})
|
|
46
51
|
this.config = { ...DEFAULT_CONFIG }
|
|
47
52
|
await this.save()
|
|
48
53
|
return this.config
|
|
@@ -110,8 +115,12 @@ export class ConfigManager {
|
|
|
110
115
|
if (match) {
|
|
111
116
|
version = match[0]
|
|
112
117
|
}
|
|
113
|
-
} catch {
|
|
114
|
-
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logDebug('Version detection failed', {
|
|
120
|
+
tool,
|
|
121
|
+
path,
|
|
122
|
+
error: error instanceof Error ? error.message : String(error),
|
|
123
|
+
})
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
config.binaries[tool] = {
|
|
@@ -142,8 +151,11 @@ export class ConfigManager {
|
|
|
142
151
|
if (path && existsSync(path)) {
|
|
143
152
|
return path
|
|
144
153
|
}
|
|
145
|
-
} catch {
|
|
146
|
-
|
|
154
|
+
} catch (error) {
|
|
155
|
+
logDebug('which command failed for binary detection', {
|
|
156
|
+
tool,
|
|
157
|
+
error: error instanceof Error ? error.message : String(error),
|
|
158
|
+
})
|
|
147
159
|
}
|
|
148
160
|
|
|
149
161
|
// Check common locations
|
|
@@ -3,10 +3,12 @@ import { mkdir, readdir, readFile, writeFile, rm, cp } from 'fs/promises'
|
|
|
3
3
|
import { paths } from '../config/paths'
|
|
4
4
|
import { processManager } from './process-manager'
|
|
5
5
|
import { portManager } from './port-manager'
|
|
6
|
-
import
|
|
6
|
+
import { getEngineDefaults, getSupportedEngines } from '../config/defaults'
|
|
7
|
+
import { getEngine } from '../engines'
|
|
8
|
+
import type { ContainerConfig, EngineName } from '../types'
|
|
7
9
|
|
|
8
10
|
export type CreateOptions = {
|
|
9
|
-
engine:
|
|
11
|
+
engine: EngineName
|
|
10
12
|
version: string
|
|
11
13
|
port: number
|
|
12
14
|
database: string
|
|
@@ -30,14 +32,14 @@ export class ContainerManager {
|
|
|
30
32
|
)
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
// Check if container already exists
|
|
34
|
-
if (await this.exists(name)) {
|
|
35
|
-
throw new Error(`Container "${name}" already exists`)
|
|
35
|
+
// Check if container already exists (for this engine)
|
|
36
|
+
if (await this.exists(name, { engine })) {
|
|
37
|
+
throw new Error(`Container "${name}" already exists for engine ${engine}`)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
// Create container directory
|
|
39
|
-
const containerPath = paths.getContainerPath(name)
|
|
40
|
-
const dataPath = paths.getContainerDataPath(name)
|
|
40
|
+
// Create container directory (engine-scoped)
|
|
41
|
+
const containerPath = paths.getContainerPath(name, { engine })
|
|
42
|
+
const dataPath = paths.getContainerDataPath(name, { engine })
|
|
41
43
|
|
|
42
44
|
await mkdir(containerPath, { recursive: true })
|
|
43
45
|
await mkdir(dataPath, { recursive: true })
|
|
@@ -53,30 +55,54 @@ export class ContainerManager {
|
|
|
53
55
|
status: 'created',
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
await this.saveConfig(name, config)
|
|
58
|
+
await this.saveConfig(name, { engine }, config)
|
|
57
59
|
|
|
58
60
|
return config
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
/**
|
|
62
64
|
* Get container configuration
|
|
65
|
+
* If engine is not provided, searches all engine directories
|
|
63
66
|
*/
|
|
64
|
-
async getConfig(
|
|
65
|
-
|
|
67
|
+
async getConfig(
|
|
68
|
+
name: string,
|
|
69
|
+
options?: { engine?: string },
|
|
70
|
+
): Promise<ContainerConfig | null> {
|
|
71
|
+
const { engine } = options || {}
|
|
72
|
+
|
|
73
|
+
if (engine) {
|
|
74
|
+
// Look in specific engine directory
|
|
75
|
+
const configPath = paths.getContainerConfigPath(name, { engine })
|
|
76
|
+
if (!existsSync(configPath)) {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
const content = await readFile(configPath, 'utf8')
|
|
80
|
+
return JSON.parse(content) as ContainerConfig
|
|
81
|
+
}
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
// Search all engine directories
|
|
84
|
+
const engines = getSupportedEngines()
|
|
85
|
+
for (const eng of engines) {
|
|
86
|
+
const configPath = paths.getContainerConfigPath(name, { engine: eng })
|
|
87
|
+
if (existsSync(configPath)) {
|
|
88
|
+
const content = await readFile(configPath, 'utf8')
|
|
89
|
+
return JSON.parse(content) as ContainerConfig
|
|
90
|
+
}
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
|
|
72
|
-
return JSON.parse(content) as ContainerConfig
|
|
93
|
+
return null
|
|
73
94
|
}
|
|
74
95
|
|
|
75
96
|
/**
|
|
76
97
|
* Save container configuration
|
|
77
98
|
*/
|
|
78
|
-
async saveConfig(
|
|
79
|
-
|
|
99
|
+
async saveConfig(
|
|
100
|
+
name: string,
|
|
101
|
+
options: { engine: string },
|
|
102
|
+
config: ContainerConfig,
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
const { engine } = options
|
|
105
|
+
const configPath = paths.getContainerConfigPath(name, { engine })
|
|
80
106
|
await writeFile(configPath, JSON.stringify(config, null, 2))
|
|
81
107
|
}
|
|
82
108
|
|
|
@@ -93,20 +119,36 @@ export class ContainerManager {
|
|
|
93
119
|
}
|
|
94
120
|
|
|
95
121
|
const updatedConfig = { ...config, ...updates }
|
|
96
|
-
await this.saveConfig(name, updatedConfig)
|
|
122
|
+
await this.saveConfig(name, { engine: config.engine }, updatedConfig)
|
|
97
123
|
return updatedConfig
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
/**
|
|
101
127
|
* Check if a container exists
|
|
128
|
+
* If engine is not provided, checks all engine directories
|
|
102
129
|
*/
|
|
103
|
-
async exists(name: string): Promise<boolean> {
|
|
104
|
-
const
|
|
105
|
-
|
|
130
|
+
async exists(name: string, options?: { engine?: string }): Promise<boolean> {
|
|
131
|
+
const { engine } = options || {}
|
|
132
|
+
|
|
133
|
+
if (engine) {
|
|
134
|
+
const configPath = paths.getContainerConfigPath(name, { engine })
|
|
135
|
+
return existsSync(configPath)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check all engine directories
|
|
139
|
+
const engines = getSupportedEngines()
|
|
140
|
+
for (const eng of engines) {
|
|
141
|
+
const configPath = paths.getContainerConfigPath(name, { engine: eng })
|
|
142
|
+
if (existsSync(configPath)) {
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return false
|
|
106
148
|
}
|
|
107
149
|
|
|
108
150
|
/**
|
|
109
|
-
* List all containers
|
|
151
|
+
* List all containers across all engines
|
|
110
152
|
*/
|
|
111
153
|
async list(): Promise<ContainerConfig[]> {
|
|
112
154
|
const containersDir = paths.containers
|
|
@@ -115,19 +157,30 @@ export class ContainerManager {
|
|
|
115
157
|
return []
|
|
116
158
|
}
|
|
117
159
|
|
|
118
|
-
const entries = await readdir(containersDir, { withFileTypes: true })
|
|
119
160
|
const containers: ContainerConfig[] = []
|
|
161
|
+
const engines = getSupportedEngines()
|
|
162
|
+
|
|
163
|
+
for (const engine of engines) {
|
|
164
|
+
const engineDir = paths.getEngineContainersPath(engine)
|
|
165
|
+
if (!existsSync(engineDir)) {
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
120
168
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
169
|
+
const entries = await readdir(engineDir, { withFileTypes: true })
|
|
170
|
+
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
if (entry.isDirectory()) {
|
|
173
|
+
const config = await this.getConfig(entry.name, { engine })
|
|
174
|
+
if (config) {
|
|
175
|
+
// Check if actually running
|
|
176
|
+
const running = await processManager.isRunning(entry.name, {
|
|
177
|
+
engine,
|
|
178
|
+
})
|
|
179
|
+
containers.push({
|
|
180
|
+
...config,
|
|
181
|
+
status: running ? 'running' : 'stopped',
|
|
182
|
+
})
|
|
183
|
+
}
|
|
131
184
|
}
|
|
132
185
|
}
|
|
133
186
|
}
|
|
@@ -141,19 +194,23 @@ export class ContainerManager {
|
|
|
141
194
|
async delete(name: string, options: DeleteOptions = {}): Promise<void> {
|
|
142
195
|
const { force = false } = options
|
|
143
196
|
|
|
144
|
-
|
|
197
|
+
// Get container config to find engine
|
|
198
|
+
const config = await this.getConfig(name)
|
|
199
|
+
if (!config) {
|
|
145
200
|
throw new Error(`Container "${name}" not found`)
|
|
146
201
|
}
|
|
147
202
|
|
|
203
|
+
const { engine } = config
|
|
204
|
+
|
|
148
205
|
// Check if running
|
|
149
|
-
const running = await processManager.isRunning(name)
|
|
206
|
+
const running = await processManager.isRunning(name, { engine })
|
|
150
207
|
if (running && !force) {
|
|
151
208
|
throw new Error(
|
|
152
209
|
`Container "${name}" is running. Stop it first or use --force`,
|
|
153
210
|
)
|
|
154
211
|
}
|
|
155
212
|
|
|
156
|
-
const containerPath = paths.getContainerPath(name)
|
|
213
|
+
const containerPath = paths.getContainerPath(name, { engine })
|
|
157
214
|
await rm(containerPath, { recursive: true, force: true })
|
|
158
215
|
}
|
|
159
216
|
|
|
@@ -171,18 +228,21 @@ export class ContainerManager {
|
|
|
171
228
|
)
|
|
172
229
|
}
|
|
173
230
|
|
|
174
|
-
//
|
|
175
|
-
|
|
231
|
+
// Get source config
|
|
232
|
+
const sourceConfig = await this.getConfig(sourceName)
|
|
233
|
+
if (!sourceConfig) {
|
|
176
234
|
throw new Error(`Source container "${sourceName}" not found`)
|
|
177
235
|
}
|
|
178
236
|
|
|
179
|
-
|
|
180
|
-
|
|
237
|
+
const { engine } = sourceConfig
|
|
238
|
+
|
|
239
|
+
// Check target doesn't exist (for this engine)
|
|
240
|
+
if (await this.exists(targetName, { engine })) {
|
|
181
241
|
throw new Error(`Target container "${targetName}" already exists`)
|
|
182
242
|
}
|
|
183
243
|
|
|
184
244
|
// Check source is not running
|
|
185
|
-
const running = await processManager.isRunning(sourceName)
|
|
245
|
+
const running = await processManager.isRunning(sourceName, { engine })
|
|
186
246
|
if (running) {
|
|
187
247
|
throw new Error(
|
|
188
248
|
`Source container "${sourceName}" is running. Stop it first`,
|
|
@@ -190,13 +250,13 @@ export class ContainerManager {
|
|
|
190
250
|
}
|
|
191
251
|
|
|
192
252
|
// Copy container directory
|
|
193
|
-
const sourcePath = paths.getContainerPath(sourceName)
|
|
194
|
-
const targetPath = paths.getContainerPath(targetName)
|
|
253
|
+
const sourcePath = paths.getContainerPath(sourceName, { engine })
|
|
254
|
+
const targetPath = paths.getContainerPath(targetName, { engine })
|
|
195
255
|
|
|
196
256
|
await cp(sourcePath, targetPath, { recursive: true })
|
|
197
257
|
|
|
198
258
|
// Update target config
|
|
199
|
-
const config = await this.getConfig(targetName)
|
|
259
|
+
const config = await this.getConfig(targetName, { engine })
|
|
200
260
|
if (!config) {
|
|
201
261
|
throw new Error('Failed to read cloned container config')
|
|
202
262
|
}
|
|
@@ -206,10 +266,13 @@ export class ContainerManager {
|
|
|
206
266
|
config.clonedFrom = sourceName
|
|
207
267
|
|
|
208
268
|
// Assign new port (excluding ports already used by other containers)
|
|
209
|
-
const
|
|
269
|
+
const engineDefaults = getEngineDefaults(engine)
|
|
270
|
+
const { port } = await portManager.findAvailablePortExcludingContainers({
|
|
271
|
+
portRange: engineDefaults.portRange,
|
|
272
|
+
})
|
|
210
273
|
config.port = port
|
|
211
274
|
|
|
212
|
-
await this.saveConfig(targetName, config)
|
|
275
|
+
await this.saveConfig(targetName, { engine }, config)
|
|
213
276
|
|
|
214
277
|
return config
|
|
215
278
|
}
|
|
@@ -225,37 +288,40 @@ export class ContainerManager {
|
|
|
225
288
|
)
|
|
226
289
|
}
|
|
227
290
|
|
|
228
|
-
//
|
|
229
|
-
|
|
291
|
+
// Get source config
|
|
292
|
+
const sourceConfig = await this.getConfig(oldName)
|
|
293
|
+
if (!sourceConfig) {
|
|
230
294
|
throw new Error(`Container "${oldName}" not found`)
|
|
231
295
|
}
|
|
232
296
|
|
|
297
|
+
const { engine } = sourceConfig
|
|
298
|
+
|
|
233
299
|
// Check target doesn't exist
|
|
234
|
-
if (await this.exists(newName)) {
|
|
300
|
+
if (await this.exists(newName, { engine })) {
|
|
235
301
|
throw new Error(`Container "${newName}" already exists`)
|
|
236
302
|
}
|
|
237
303
|
|
|
238
304
|
// Check container is not running
|
|
239
|
-
const running = await processManager.isRunning(oldName)
|
|
305
|
+
const running = await processManager.isRunning(oldName, { engine })
|
|
240
306
|
if (running) {
|
|
241
307
|
throw new Error(`Container "${oldName}" is running. Stop it first`)
|
|
242
308
|
}
|
|
243
309
|
|
|
244
310
|
// Rename directory
|
|
245
|
-
const oldPath = paths.getContainerPath(oldName)
|
|
246
|
-
const newPath = paths.getContainerPath(newName)
|
|
311
|
+
const oldPath = paths.getContainerPath(oldName, { engine })
|
|
312
|
+
const newPath = paths.getContainerPath(newName, { engine })
|
|
247
313
|
|
|
248
314
|
await cp(oldPath, newPath, { recursive: true })
|
|
249
315
|
await rm(oldPath, { recursive: true, force: true })
|
|
250
316
|
|
|
251
317
|
// Update config with new name
|
|
252
|
-
const config = await this.getConfig(newName)
|
|
318
|
+
const config = await this.getConfig(newName, { engine })
|
|
253
319
|
if (!config) {
|
|
254
320
|
throw new Error('Failed to read renamed container config')
|
|
255
321
|
}
|
|
256
322
|
|
|
257
323
|
config.name = newName
|
|
258
|
-
await this.saveConfig(newName, config)
|
|
324
|
+
await this.saveConfig(newName, { engine }, config)
|
|
259
325
|
|
|
260
326
|
return config
|
|
261
327
|
}
|
|
@@ -269,13 +335,11 @@ export class ContainerManager {
|
|
|
269
335
|
|
|
270
336
|
/**
|
|
271
337
|
* Get connection string for a container
|
|
338
|
+
* Delegates to the appropriate engine
|
|
272
339
|
*/
|
|
273
|
-
getConnectionString(
|
|
274
|
-
config
|
|
275
|
-
|
|
276
|
-
): string {
|
|
277
|
-
const { port } = config
|
|
278
|
-
return `postgresql://postgres@localhost:${port}/${database}`
|
|
340
|
+
getConnectionString(config: ContainerConfig, database?: string): string {
|
|
341
|
+
const engine = getEngine(config.engine)
|
|
342
|
+
return engine.getConnectionString(config, database)
|
|
279
343
|
}
|
|
280
344
|
}
|
|
281
345
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
getEngineDependencies,
|
|
17
17
|
getUniqueDependencies,
|
|
18
18
|
} from '../config/os-dependencies'
|
|
19
|
+
import { platformService } from './platform-service'
|
|
19
20
|
|
|
20
21
|
const execAsync = promisify(exec)
|
|
21
22
|
|
|
@@ -46,7 +47,7 @@ export type InstallResult = {
|
|
|
46
47
|
* Detect which package manager is available on the current system
|
|
47
48
|
*/
|
|
48
49
|
export async function detectPackageManager(): Promise<DetectedPackageManager | null> {
|
|
49
|
-
const platform =
|
|
50
|
+
const { platform } = platformService.getPlatformInfo()
|
|
50
51
|
|
|
51
52
|
// Filter to package managers available on this platform
|
|
52
53
|
const candidates = packageManagers.filter((pm) =>
|
|
@@ -73,7 +74,7 @@ export async function detectPackageManager(): Promise<DetectedPackageManager | n
|
|
|
73
74
|
* Get the current platform
|
|
74
75
|
*/
|
|
75
76
|
export function getCurrentPlatform(): Platform {
|
|
76
|
-
return
|
|
77
|
+
return platformService.getPlatformInfo().platform as Platform
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// =============================================================================
|
|
@@ -87,21 +88,12 @@ export async function findBinary(
|
|
|
87
88
|
binary: string,
|
|
88
89
|
): Promise<{ path: string; version?: string } | null> {
|
|
89
90
|
try {
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
const path = stdout.trim().split('\n')[0]
|
|
93
|
-
|
|
91
|
+
// Use platformService to find the binary path
|
|
92
|
+
const path = await platformService.findToolPath(binary)
|
|
94
93
|
if (!path) return null
|
|
95
94
|
|
|
96
95
|
// Try to get version
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const { stdout: versionOutput } = await execAsync(`${binary} --version`)
|
|
100
|
-
const match = versionOutput.match(/(\d+\.\d+(\.\d+)?)/)
|
|
101
|
-
version = match ? match[1] : undefined
|
|
102
|
-
} catch {
|
|
103
|
-
// Version check failed, that's ok
|
|
104
|
-
}
|
|
96
|
+
const version = (await platformService.getToolVersion(path)) || undefined
|
|
105
97
|
|
|
106
98
|
return { path, version }
|
|
107
99
|
} catch {
|
|
@@ -216,7 +208,9 @@ function execWithInheritedStdio(command: string): void {
|
|
|
216
208
|
}
|
|
217
209
|
|
|
218
210
|
if (result.status !== 0) {
|
|
219
|
-
throw new Error(
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Command failed with exit code ${result.status}: ${cmdToRun}`,
|
|
213
|
+
)
|
|
220
214
|
}
|
|
221
215
|
}
|
|
222
216
|
|