spindb 0.7.0 → 0.7.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 +421 -294
- package/cli/commands/config.ts +7 -1
- package/cli/commands/connect.ts +1 -0
- package/cli/commands/edit.ts +10 -0
- package/cli/commands/engines.ts +10 -188
- package/cli/commands/info.ts +7 -14
- package/cli/commands/list.ts +2 -9
- package/cli/commands/logs.ts +130 -0
- package/cli/commands/menu/backup-handlers.ts +798 -0
- package/cli/commands/menu/container-handlers.ts +832 -0
- package/cli/commands/menu/engine-handlers.ts +382 -0
- package/cli/commands/menu/index.ts +184 -0
- package/cli/commands/menu/shared.ts +26 -0
- package/cli/commands/menu/shell-handlers.ts +331 -0
- package/cli/commands/menu/sql-handlers.ts +197 -0
- package/cli/commands/menu/update-handlers.ts +94 -0
- package/cli/commands/run.ts +150 -0
- package/cli/commands/url.ts +19 -5
- package/cli/constants.ts +10 -0
- package/cli/helpers.ts +152 -0
- package/cli/index.ts +5 -2
- package/cli/ui/prompts.ts +3 -11
- package/core/dependency-manager.ts +0 -163
- package/core/error-handler.ts +0 -26
- package/core/platform-service.ts +60 -40
- package/core/start-with-retry.ts +3 -28
- package/core/transaction-manager.ts +0 -8
- package/engines/base-engine.ts +10 -0
- package/engines/mysql/binary-detection.ts +1 -1
- package/engines/mysql/index.ts +78 -2
- package/engines/postgresql/index.ts +49 -0
- package/package.json +1 -1
- package/cli/commands/menu.ts +0 -2670
package/core/error-handler.ts
CHANGED
|
@@ -17,10 +17,6 @@ function getSpinDBRoot(): string {
|
|
|
17
17
|
return join(home, '.spindb')
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// =============================================================================
|
|
21
|
-
// Types
|
|
22
|
-
// =============================================================================
|
|
23
|
-
|
|
24
20
|
export type ErrorSeverity = 'fatal' | 'error' | 'warning' | 'info'
|
|
25
21
|
|
|
26
22
|
export type SpinDBErrorInfo = {
|
|
@@ -31,10 +27,6 @@ export type SpinDBErrorInfo = {
|
|
|
31
27
|
context?: Record<string, unknown>
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
// =============================================================================
|
|
35
|
-
// Error Codes
|
|
36
|
-
// =============================================================================
|
|
37
|
-
|
|
38
30
|
export const ErrorCodes = {
|
|
39
31
|
// Port errors
|
|
40
32
|
PORT_IN_USE: 'PORT_IN_USE',
|
|
@@ -83,10 +75,6 @@ export const ErrorCodes = {
|
|
|
83
75
|
|
|
84
76
|
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]
|
|
85
77
|
|
|
86
|
-
// =============================================================================
|
|
87
|
-
// SpinDBError Class
|
|
88
|
-
// =============================================================================
|
|
89
|
-
|
|
90
78
|
export class SpinDBError extends Error {
|
|
91
79
|
public readonly code: string
|
|
92
80
|
public readonly severity: ErrorSeverity
|
|
@@ -131,13 +119,6 @@ export class SpinDBError extends Error {
|
|
|
131
119
|
}
|
|
132
120
|
}
|
|
133
121
|
|
|
134
|
-
// =============================================================================
|
|
135
|
-
// Logging Functions
|
|
136
|
-
// =============================================================================
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Get the path to the log file
|
|
140
|
-
*/
|
|
141
122
|
function getLogPath(): string {
|
|
142
123
|
return join(getSpinDBRoot(), 'spindb.log')
|
|
143
124
|
}
|
|
@@ -264,13 +245,6 @@ export function logDebug(
|
|
|
264
245
|
})
|
|
265
246
|
}
|
|
266
247
|
|
|
267
|
-
// =============================================================================
|
|
268
|
-
// Error Creation Helpers
|
|
269
|
-
// =============================================================================
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Create a port-in-use error with helpful suggestion
|
|
273
|
-
*/
|
|
274
248
|
export function createPortInUseError(port: number): SpinDBError {
|
|
275
249
|
return new SpinDBError(
|
|
276
250
|
ErrorCodes.PORT_IN_USE,
|
package/core/platform-service.ts
CHANGED
|
@@ -17,13 +17,47 @@ import { existsSync } from 'fs'
|
|
|
17
17
|
|
|
18
18
|
const execAsync = promisify(exec)
|
|
19
19
|
|
|
20
|
-
// =============================================================================
|
|
21
|
-
// Types
|
|
22
|
-
// =============================================================================
|
|
23
|
-
|
|
24
20
|
export type Platform = 'darwin' | 'linux' | 'win32'
|
|
25
21
|
export type Architecture = 'arm64' | 'x64'
|
|
26
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Options for resolving home directory under sudo
|
|
25
|
+
*/
|
|
26
|
+
export type ResolveHomeDirOptions = {
|
|
27
|
+
sudoUser: string | null
|
|
28
|
+
getentResult: string | null
|
|
29
|
+
platform: 'darwin' | 'linux'
|
|
30
|
+
defaultHome: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the correct home directory, handling sudo scenarios.
|
|
35
|
+
* This is extracted as a pure function for testability.
|
|
36
|
+
*
|
|
37
|
+
* When running under sudo, we need to use the original user's home directory,
|
|
38
|
+
* not root's home. This prevents ~/.spindb from being created in /root/.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveHomeDir(options: ResolveHomeDirOptions): string {
|
|
41
|
+
const { sudoUser, getentResult, platform, defaultHome } = options
|
|
42
|
+
|
|
43
|
+
// Not running under sudo - use default
|
|
44
|
+
if (!sudoUser) {
|
|
45
|
+
return defaultHome
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Try to parse home from getent passwd output
|
|
49
|
+
// Format: username:password:uid:gid:gecos:home:shell
|
|
50
|
+
if (getentResult) {
|
|
51
|
+
const parts = getentResult.trim().split(':')
|
|
52
|
+
if (parts.length >= 6 && parts[5]) {
|
|
53
|
+
return parts[5]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback to platform-specific default
|
|
58
|
+
return platform === 'darwin' ? `/Users/${sudoUser}` : `/home/${sudoUser}`
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
export type PlatformInfo = {
|
|
28
62
|
platform: Platform
|
|
29
63
|
arch: Architecture
|
|
@@ -54,10 +88,6 @@ export type PackageManagerInfo = {
|
|
|
54
88
|
updateCommand: string
|
|
55
89
|
}
|
|
56
90
|
|
|
57
|
-
// =============================================================================
|
|
58
|
-
// Abstract Base Class
|
|
59
|
-
// =============================================================================
|
|
60
|
-
|
|
61
91
|
export abstract class BasePlatformService {
|
|
62
92
|
protected cachedPlatformInfo: PlatformInfo | null = null
|
|
63
93
|
|
|
@@ -165,33 +195,31 @@ export abstract class BasePlatformService {
|
|
|
165
195
|
}
|
|
166
196
|
}
|
|
167
197
|
|
|
168
|
-
// =============================================================================
|
|
169
|
-
// Darwin (macOS) Implementation
|
|
170
|
-
// =============================================================================
|
|
171
|
-
|
|
172
198
|
class DarwinPlatformService extends BasePlatformService {
|
|
173
199
|
getPlatformInfo(): PlatformInfo {
|
|
174
200
|
if (this.cachedPlatformInfo) return this.cachedPlatformInfo
|
|
175
201
|
|
|
176
202
|
const sudoUser = process.env.SUDO_USER || null
|
|
177
|
-
let homeDir: string
|
|
178
203
|
|
|
204
|
+
// Try to get home from getent passwd (may fail on macOS)
|
|
205
|
+
let getentResult: string | null = null
|
|
179
206
|
if (sudoUser) {
|
|
180
|
-
// Running under sudo - get original user's home
|
|
181
207
|
try {
|
|
182
|
-
|
|
208
|
+
getentResult = execSync(`getent passwd ${sudoUser}`, {
|
|
183
209
|
encoding: 'utf-8',
|
|
184
210
|
})
|
|
185
|
-
const parts = result.trim().split(':')
|
|
186
|
-
homeDir =
|
|
187
|
-
parts.length >= 6 && parts[5] ? parts[5] : `/Users/${sudoUser}`
|
|
188
211
|
} catch {
|
|
189
|
-
|
|
212
|
+
// getent may not be available on macOS
|
|
190
213
|
}
|
|
191
|
-
} else {
|
|
192
|
-
homeDir = homedir()
|
|
193
214
|
}
|
|
194
215
|
|
|
216
|
+
const homeDir = resolveHomeDir({
|
|
217
|
+
sudoUser,
|
|
218
|
+
getentResult,
|
|
219
|
+
platform: 'darwin',
|
|
220
|
+
defaultHome: homedir(),
|
|
221
|
+
})
|
|
222
|
+
|
|
195
223
|
this.cachedPlatformInfo = {
|
|
196
224
|
platform: 'darwin',
|
|
197
225
|
arch: osArch() as Architecture,
|
|
@@ -309,31 +337,31 @@ class DarwinPlatformService extends BasePlatformService {
|
|
|
309
337
|
}
|
|
310
338
|
}
|
|
311
339
|
|
|
312
|
-
// =============================================================================
|
|
313
|
-
// Linux Implementation
|
|
314
|
-
// =============================================================================
|
|
315
|
-
|
|
316
340
|
class LinuxPlatformService extends BasePlatformService {
|
|
317
341
|
getPlatformInfo(): PlatformInfo {
|
|
318
342
|
if (this.cachedPlatformInfo) return this.cachedPlatformInfo
|
|
319
343
|
|
|
320
344
|
const sudoUser = process.env.SUDO_USER || null
|
|
321
|
-
let homeDir: string
|
|
322
345
|
|
|
346
|
+
// Try to get home from getent passwd
|
|
347
|
+
let getentResult: string | null = null
|
|
323
348
|
if (sudoUser) {
|
|
324
349
|
try {
|
|
325
|
-
|
|
350
|
+
getentResult = execSync(`getent passwd ${sudoUser}`, {
|
|
326
351
|
encoding: 'utf-8',
|
|
327
352
|
})
|
|
328
|
-
const parts = result.trim().split(':')
|
|
329
|
-
homeDir = parts.length >= 6 && parts[5] ? parts[5] : `/home/${sudoUser}`
|
|
330
353
|
} catch {
|
|
331
|
-
|
|
354
|
+
// getent failed
|
|
332
355
|
}
|
|
333
|
-
} else {
|
|
334
|
-
homeDir = homedir()
|
|
335
356
|
}
|
|
336
357
|
|
|
358
|
+
const homeDir = resolveHomeDir({
|
|
359
|
+
sudoUser,
|
|
360
|
+
getentResult,
|
|
361
|
+
platform: 'linux',
|
|
362
|
+
defaultHome: homedir(),
|
|
363
|
+
})
|
|
364
|
+
|
|
337
365
|
// Check if running in WSL
|
|
338
366
|
let isWSL = false
|
|
339
367
|
try {
|
|
@@ -494,10 +522,6 @@ class LinuxPlatformService extends BasePlatformService {
|
|
|
494
522
|
}
|
|
495
523
|
}
|
|
496
524
|
|
|
497
|
-
// =============================================================================
|
|
498
|
-
// Windows Implementation (Stub for future support)
|
|
499
|
-
// =============================================================================
|
|
500
|
-
|
|
501
525
|
class Win32PlatformService extends BasePlatformService {
|
|
502
526
|
getPlatformInfo(): PlatformInfo {
|
|
503
527
|
if (this.cachedPlatformInfo) return this.cachedPlatformInfo
|
|
@@ -608,10 +632,6 @@ class Win32PlatformService extends BasePlatformService {
|
|
|
608
632
|
}
|
|
609
633
|
}
|
|
610
634
|
|
|
611
|
-
// =============================================================================
|
|
612
|
-
// Factory and Singleton
|
|
613
|
-
// =============================================================================
|
|
614
|
-
|
|
615
635
|
/**
|
|
616
636
|
* Create the appropriate platform service for the current OS
|
|
617
637
|
*/
|
package/core/start-with-retry.ts
CHANGED
|
@@ -9,12 +9,9 @@ import { portManager } from './port-manager'
|
|
|
9
9
|
import { containerManager } from './container-manager'
|
|
10
10
|
import { logWarning, logDebug } from './error-handler'
|
|
11
11
|
import type { BaseEngine } from '../engines/base-engine'
|
|
12
|
+
import { getEngineDefaults } from '../config/defaults'
|
|
12
13
|
import type { ContainerConfig } from '../types'
|
|
13
14
|
|
|
14
|
-
// =============================================================================
|
|
15
|
-
// Types
|
|
16
|
-
// =============================================================================
|
|
17
|
-
|
|
18
15
|
export type StartWithRetryOptions = {
|
|
19
16
|
engine: BaseEngine
|
|
20
17
|
config: ContainerConfig
|
|
@@ -29,13 +26,6 @@ export type StartWithRetryResult = {
|
|
|
29
26
|
error?: Error
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
// =============================================================================
|
|
33
|
-
// Port Error Detection
|
|
34
|
-
// =============================================================================
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Check if an error is a port-in-use error
|
|
38
|
-
*/
|
|
39
29
|
function isPortInUseError(err: unknown): boolean {
|
|
40
30
|
const message = (err as Error)?.message?.toLowerCase() || ''
|
|
41
31
|
return (
|
|
@@ -47,10 +37,6 @@ function isPortInUseError(err: unknown): boolean {
|
|
|
47
37
|
)
|
|
48
38
|
}
|
|
49
39
|
|
|
50
|
-
// =============================================================================
|
|
51
|
-
// Start with Retry Implementation
|
|
52
|
-
// =============================================================================
|
|
53
|
-
|
|
54
40
|
/**
|
|
55
41
|
* Start a database container with automatic port retry on conflict
|
|
56
42
|
*
|
|
@@ -129,20 +115,9 @@ export async function startWithRetry(
|
|
|
129
115
|
}
|
|
130
116
|
}
|
|
131
117
|
|
|
132
|
-
/**
|
|
133
|
-
* Get the port range for an engine
|
|
134
|
-
*/
|
|
135
118
|
function getEnginePortRange(engine: string): { start: number; end: number } {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (engine === 'postgresql') {
|
|
139
|
-
return { start: 5432, end: 5500 }
|
|
140
|
-
}
|
|
141
|
-
if (engine === 'mysql') {
|
|
142
|
-
return { start: 3306, end: 3400 }
|
|
143
|
-
}
|
|
144
|
-
// Default fallback range
|
|
145
|
-
return { start: 5432, end: 6000 }
|
|
119
|
+
const engineDefaults = getEngineDefaults(engine)
|
|
120
|
+
return engineDefaults.portRange
|
|
146
121
|
}
|
|
147
122
|
|
|
148
123
|
/**
|
|
@@ -7,19 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import { logError, logDebug, ErrorCodes } from './error-handler'
|
|
9
9
|
|
|
10
|
-
// =============================================================================
|
|
11
|
-
// Types
|
|
12
|
-
// =============================================================================
|
|
13
|
-
|
|
14
10
|
export type RollbackAction = {
|
|
15
11
|
description: string
|
|
16
12
|
execute: () => Promise<void>
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Transaction Manager Implementation
|
|
21
|
-
// =============================================================================
|
|
22
|
-
|
|
23
15
|
/**
|
|
24
16
|
* Manages a stack of rollback actions for transactional operations.
|
|
25
17
|
*
|
package/engines/base-engine.ts
CHANGED
|
@@ -151,4 +151,14 @@ export abstract class BaseEngine {
|
|
|
151
151
|
outputPath: string,
|
|
152
152
|
options: BackupOptions,
|
|
153
153
|
): Promise<BackupResult>
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Run a SQL file or inline SQL statement against the database
|
|
157
|
+
* @param container - The container configuration
|
|
158
|
+
* @param options - Options including file path or SQL statement, and target database
|
|
159
|
+
*/
|
|
160
|
+
abstract runScript(
|
|
161
|
+
container: ContainerConfig,
|
|
162
|
+
options: { file?: string; sql?: string; database?: string },
|
|
163
|
+
): Promise<void>
|
|
154
164
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { exec } from 'child_process'
|
|
7
|
+
import { existsSync } from 'fs'
|
|
7
8
|
import { promisify } from 'util'
|
|
8
9
|
import { platformService } from '../../core/platform-service'
|
|
9
10
|
|
|
@@ -129,7 +130,6 @@ export async function detectInstalledVersions(): Promise<
|
|
|
129
130
|
'/usr/local/opt/mysql@8.4/bin/mysqld',
|
|
130
131
|
]
|
|
131
132
|
|
|
132
|
-
const { existsSync } = await import('fs')
|
|
133
133
|
for (const path of homebrewPaths) {
|
|
134
134
|
if (existsSync(path)) {
|
|
135
135
|
const version = await getMysqlVersion(path)
|
package/engines/mysql/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { spawn, exec } from 'child_process'
|
|
7
7
|
import { promisify } from 'util'
|
|
8
|
-
import { existsSync } from 'fs'
|
|
8
|
+
import { existsSync, createReadStream } from 'fs'
|
|
9
9
|
import { mkdir, writeFile, readFile, unlink } from 'fs/promises'
|
|
10
10
|
import { join } from 'path'
|
|
11
11
|
import { BaseEngine } from '../base-engine'
|
|
@@ -289,8 +289,9 @@ export class MySQLEngine extends BaseEngine {
|
|
|
289
289
|
// Write PID file manually since we're running detached
|
|
290
290
|
try {
|
|
291
291
|
await writeFile(pidFile, String(proc.pid))
|
|
292
|
-
} catch {
|
|
292
|
+
} catch (error) {
|
|
293
293
|
// PID file might be written by mysqld itself
|
|
294
|
+
logDebug(`Could not write PID file (mysqld may write it): ${error}`)
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
// Wait for MySQL to be ready
|
|
@@ -842,6 +843,81 @@ export class MySQLEngine extends BaseEngine {
|
|
|
842
843
|
): Promise<BackupResult> {
|
|
843
844
|
return createBackup(container, outputPath, options)
|
|
844
845
|
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Run a SQL file or inline SQL statement against the database
|
|
849
|
+
* CLI wrapper: mysql -h 127.0.0.1 -P {port} -u root {db} < {file}
|
|
850
|
+
* CLI wrapper: mysql -h 127.0.0.1 -P {port} -u root {db} -e "{sql}"
|
|
851
|
+
*/
|
|
852
|
+
async runScript(
|
|
853
|
+
container: ContainerConfig,
|
|
854
|
+
options: { file?: string; sql?: string; database?: string },
|
|
855
|
+
): Promise<void> {
|
|
856
|
+
const { port } = container
|
|
857
|
+
const db = options.database || container.database || 'mysql'
|
|
858
|
+
|
|
859
|
+
const mysql = await getMysqlClientPath()
|
|
860
|
+
if (!mysql) {
|
|
861
|
+
throw new Error(
|
|
862
|
+
'mysql client not found. Install MySQL client tools:\n' +
|
|
863
|
+
' macOS: brew install mysql-client\n' +
|
|
864
|
+
' Ubuntu/Debian: sudo apt install mysql-client',
|
|
865
|
+
)
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const args = [
|
|
869
|
+
'-h',
|
|
870
|
+
'127.0.0.1',
|
|
871
|
+
'-P',
|
|
872
|
+
String(port),
|
|
873
|
+
'-u',
|
|
874
|
+
engineDef.superuser,
|
|
875
|
+
db,
|
|
876
|
+
]
|
|
877
|
+
|
|
878
|
+
if (options.sql) {
|
|
879
|
+
// For inline SQL, use -e flag
|
|
880
|
+
args.push('-e', options.sql)
|
|
881
|
+
return new Promise((resolve, reject) => {
|
|
882
|
+
const proc = spawn(mysql, args, { stdio: 'inherit' })
|
|
883
|
+
|
|
884
|
+
proc.on('error', reject)
|
|
885
|
+
proc.on('close', (code) => {
|
|
886
|
+
if (code === 0) {
|
|
887
|
+
resolve()
|
|
888
|
+
} else {
|
|
889
|
+
reject(new Error(`mysql exited with code ${code}`))
|
|
890
|
+
}
|
|
891
|
+
})
|
|
892
|
+
})
|
|
893
|
+
} else if (options.file) {
|
|
894
|
+
// For file input, pipe the file to mysql stdin
|
|
895
|
+
return new Promise((resolve, reject) => {
|
|
896
|
+
const fileStream = createReadStream(options.file!)
|
|
897
|
+
const proc = spawn(mysql, args, {
|
|
898
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
fileStream.pipe(proc.stdin)
|
|
902
|
+
|
|
903
|
+
fileStream.on('error', (err) => {
|
|
904
|
+
proc.kill()
|
|
905
|
+
reject(err)
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
proc.on('error', reject)
|
|
909
|
+
proc.on('close', (code) => {
|
|
910
|
+
if (code === 0) {
|
|
911
|
+
resolve()
|
|
912
|
+
} else {
|
|
913
|
+
reject(new Error(`mysql exited with code ${code}`))
|
|
914
|
+
}
|
|
915
|
+
})
|
|
916
|
+
})
|
|
917
|
+
} else {
|
|
918
|
+
throw new Error('Either file or sql option must be provided')
|
|
919
|
+
}
|
|
920
|
+
}
|
|
845
921
|
}
|
|
846
922
|
|
|
847
923
|
export const mysqlEngine = new MySQLEngine()
|
|
@@ -457,6 +457,55 @@ export class PostgreSQLEngine extends BaseEngine {
|
|
|
457
457
|
): Promise<BackupResult> {
|
|
458
458
|
return createBackup(container, outputPath, options)
|
|
459
459
|
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Run a SQL file or inline SQL statement against the database
|
|
463
|
+
* CLI wrapper: psql -h 127.0.0.1 -p {port} -U postgres -d {db} -f {file}
|
|
464
|
+
* CLI wrapper: psql -h 127.0.0.1 -p {port} -U postgres -d {db} -c "{sql}"
|
|
465
|
+
*/
|
|
466
|
+
async runScript(
|
|
467
|
+
container: ContainerConfig,
|
|
468
|
+
options: { file?: string; sql?: string; database?: string },
|
|
469
|
+
): Promise<void> {
|
|
470
|
+
const { port } = container
|
|
471
|
+
const db = options.database || container.database || 'postgres'
|
|
472
|
+
const psqlPath = await this.getPsqlPath()
|
|
473
|
+
|
|
474
|
+
const args = [
|
|
475
|
+
'-h',
|
|
476
|
+
'127.0.0.1',
|
|
477
|
+
'-p',
|
|
478
|
+
String(port),
|
|
479
|
+
'-U',
|
|
480
|
+
defaults.superuser,
|
|
481
|
+
'-d',
|
|
482
|
+
db,
|
|
483
|
+
]
|
|
484
|
+
|
|
485
|
+
if (options.file) {
|
|
486
|
+
args.push('-f', options.file)
|
|
487
|
+
} else if (options.sql) {
|
|
488
|
+
args.push('-c', options.sql)
|
|
489
|
+
} else {
|
|
490
|
+
throw new Error('Either file or sql option must be provided')
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return new Promise((resolve, reject) => {
|
|
494
|
+
const proc = spawn(psqlPath, args, { stdio: 'inherit' })
|
|
495
|
+
|
|
496
|
+
proc.on('error', (err: NodeJS.ErrnoException) => {
|
|
497
|
+
reject(err)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
proc.on('close', (code) => {
|
|
501
|
+
if (code === 0) {
|
|
502
|
+
resolve()
|
|
503
|
+
} else {
|
|
504
|
+
reject(new Error(`psql exited with code ${code}`))
|
|
505
|
+
}
|
|
506
|
+
})
|
|
507
|
+
})
|
|
508
|
+
}
|
|
460
509
|
}
|
|
461
510
|
|
|
462
511
|
export const postgresqlEngine = new PostgreSQLEngine()
|