spindb 0.9.1 → 0.9.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 +5 -8
- package/cli/commands/attach.ts +108 -0
- package/cli/commands/backup.ts +13 -11
- package/cli/commands/clone.ts +14 -10
- package/cli/commands/config.ts +29 -29
- package/cli/commands/connect.ts +51 -39
- package/cli/commands/create.ts +65 -32
- package/cli/commands/delete.ts +8 -8
- package/cli/commands/deps.ts +17 -15
- package/cli/commands/detach.ts +100 -0
- package/cli/commands/doctor.ts +27 -13
- package/cli/commands/edit.ts +120 -57
- package/cli/commands/engines.ts +17 -15
- package/cli/commands/info.ts +8 -6
- package/cli/commands/list.ts +127 -18
- package/cli/commands/logs.ts +15 -11
- package/cli/commands/menu/backup-handlers.ts +52 -47
- package/cli/commands/menu/container-handlers.ts +164 -79
- package/cli/commands/menu/engine-handlers.ts +21 -11
- package/cli/commands/menu/index.ts +4 -4
- package/cli/commands/menu/shell-handlers.ts +34 -31
- package/cli/commands/menu/sql-handlers.ts +22 -16
- package/cli/commands/menu/update-handlers.ts +19 -17
- package/cli/commands/restore.ts +22 -20
- package/cli/commands/run.ts +20 -18
- package/cli/commands/self-update.ts +5 -5
- package/cli/commands/sqlite.ts +247 -0
- package/cli/commands/start.ts +11 -9
- package/cli/commands/stop.ts +9 -9
- package/cli/commands/url.ts +12 -9
- package/cli/helpers.ts +9 -4
- package/cli/index.ts +6 -0
- package/cli/ui/prompts.ts +12 -5
- package/cli/ui/spinner.ts +4 -4
- package/cli/ui/theme.ts +4 -4
- package/config/paths.ts +0 -8
- package/core/binary-manager.ts +5 -1
- package/core/config-manager.ts +32 -0
- package/core/container-manager.ts +5 -5
- package/core/platform-service.ts +3 -3
- package/core/start-with-retry.ts +6 -6
- package/core/transaction-manager.ts +6 -6
- package/engines/mysql/backup.ts +37 -13
- package/engines/mysql/index.ts +11 -11
- package/engines/mysql/restore.ts +4 -4
- package/engines/mysql/version-validator.ts +2 -2
- package/engines/postgresql/binary-manager.ts +17 -17
- package/engines/postgresql/index.ts +7 -2
- package/engines/postgresql/restore.ts +2 -2
- package/engines/postgresql/version-validator.ts +2 -2
- package/engines/sqlite/index.ts +30 -15
- package/engines/sqlite/registry.ts +64 -33
- package/engines/sqlite/scanner.ts +99 -0
- package/package.json +4 -3
- package/types/index.ts +21 -1
package/cli/commands/connect.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import { getEngine } from '../../engines'
|
|
23
23
|
import { getEngineDefaults } from '../../config/defaults'
|
|
24
24
|
import { promptContainerSelect } from '../ui/prompts'
|
|
25
|
-
import {
|
|
25
|
+
import { uiError, uiWarning, uiInfo, uiSuccess } from '../ui/theme'
|
|
26
26
|
import { Engine } from '../../types'
|
|
27
27
|
|
|
28
28
|
export const connectCommand = new Command('connect')
|
|
@@ -78,11 +78,13 @@ export const connectCommand = new Command('connect')
|
|
|
78
78
|
if (connectable.length === 0) {
|
|
79
79
|
if (containers.length === 0) {
|
|
80
80
|
console.log(
|
|
81
|
-
|
|
81
|
+
uiWarning(
|
|
82
|
+
'No containers found. Create one with: spindb create',
|
|
83
|
+
),
|
|
82
84
|
)
|
|
83
85
|
} else {
|
|
84
86
|
console.log(
|
|
85
|
-
|
|
87
|
+
uiWarning(
|
|
86
88
|
'No running containers. Start one first with: spindb start',
|
|
87
89
|
),
|
|
88
90
|
)
|
|
@@ -100,7 +102,7 @@ export const connectCommand = new Command('connect')
|
|
|
100
102
|
|
|
101
103
|
const config = await containerManager.getConfig(containerName)
|
|
102
104
|
if (!config) {
|
|
103
|
-
console.error(
|
|
105
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
104
106
|
process.exit(1)
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -114,9 +116,7 @@ export const connectCommand = new Command('connect')
|
|
|
114
116
|
if (engineName === Engine.SQLite) {
|
|
115
117
|
if (!existsSync(config.database)) {
|
|
116
118
|
console.error(
|
|
117
|
-
|
|
118
|
-
`SQLite database file not found: ${config.database}`,
|
|
119
|
-
),
|
|
119
|
+
uiError(`SQLite database file not found: ${config.database}`),
|
|
120
120
|
)
|
|
121
121
|
process.exit(1)
|
|
122
122
|
}
|
|
@@ -127,7 +127,7 @@ export const connectCommand = new Command('connect')
|
|
|
127
127
|
})
|
|
128
128
|
if (!running) {
|
|
129
129
|
console.error(
|
|
130
|
-
|
|
130
|
+
uiError(
|
|
131
131
|
`Container "${containerName}" is not running. Start it first.`,
|
|
132
132
|
),
|
|
133
133
|
)
|
|
@@ -145,17 +145,17 @@ export const connectCommand = new Command('connect')
|
|
|
145
145
|
if (!usqlInstalled) {
|
|
146
146
|
if (options.installTui) {
|
|
147
147
|
console.log(
|
|
148
|
-
|
|
148
|
+
uiInfo('Installing usql for enhanced shell experience...'),
|
|
149
149
|
)
|
|
150
150
|
const pm = await detectPackageManager()
|
|
151
151
|
if (pm) {
|
|
152
152
|
const result = await installUsql(pm)
|
|
153
153
|
if (result.success) {
|
|
154
|
-
console.log(
|
|
154
|
+
console.log(uiSuccess('usql installed successfully!'))
|
|
155
155
|
console.log()
|
|
156
156
|
} else {
|
|
157
157
|
console.error(
|
|
158
|
-
|
|
158
|
+
uiError(`Failed to install usql: ${result.error}`),
|
|
159
159
|
)
|
|
160
160
|
console.log()
|
|
161
161
|
console.log(chalk.gray('Manual installation:'))
|
|
@@ -165,7 +165,7 @@ export const connectCommand = new Command('connect')
|
|
|
165
165
|
process.exit(1)
|
|
166
166
|
}
|
|
167
167
|
} else {
|
|
168
|
-
console.error(
|
|
168
|
+
console.error(uiError('No supported package manager found'))
|
|
169
169
|
console.log()
|
|
170
170
|
console.log(chalk.gray('Manual installation:'))
|
|
171
171
|
for (const instruction of getUsqlManualInstructions()) {
|
|
@@ -174,7 +174,7 @@ export const connectCommand = new Command('connect')
|
|
|
174
174
|
process.exit(1)
|
|
175
175
|
}
|
|
176
176
|
} else {
|
|
177
|
-
console.error(
|
|
177
|
+
console.error(uiError('usql is not installed'))
|
|
178
178
|
console.log()
|
|
179
179
|
console.log(
|
|
180
180
|
chalk.gray('Install usql for enhanced shell experience:'),
|
|
@@ -194,7 +194,7 @@ export const connectCommand = new Command('connect')
|
|
|
194
194
|
if (usePgcli) {
|
|
195
195
|
if (engineName !== 'postgresql') {
|
|
196
196
|
console.error(
|
|
197
|
-
|
|
197
|
+
uiError('pgcli is only available for PostgreSQL containers'),
|
|
198
198
|
)
|
|
199
199
|
console.log(chalk.gray('For MySQL, use: spindb connect --mycli'))
|
|
200
200
|
process.exit(1)
|
|
@@ -205,17 +205,17 @@ export const connectCommand = new Command('connect')
|
|
|
205
205
|
if (!pgcliInstalled) {
|
|
206
206
|
if (options.installPgcli) {
|
|
207
207
|
console.log(
|
|
208
|
-
|
|
208
|
+
uiInfo('Installing pgcli for enhanced PostgreSQL shell...'),
|
|
209
209
|
)
|
|
210
210
|
const pm = await detectPackageManager()
|
|
211
211
|
if (pm) {
|
|
212
212
|
const result = await installPgcli(pm)
|
|
213
213
|
if (result.success) {
|
|
214
|
-
console.log(
|
|
214
|
+
console.log(uiSuccess('pgcli installed successfully!'))
|
|
215
215
|
console.log()
|
|
216
216
|
} else {
|
|
217
217
|
console.error(
|
|
218
|
-
|
|
218
|
+
uiError(`Failed to install pgcli: ${result.error}`),
|
|
219
219
|
)
|
|
220
220
|
console.log()
|
|
221
221
|
console.log(chalk.gray('Manual installation:'))
|
|
@@ -225,7 +225,7 @@ export const connectCommand = new Command('connect')
|
|
|
225
225
|
process.exit(1)
|
|
226
226
|
}
|
|
227
227
|
} else {
|
|
228
|
-
console.error(
|
|
228
|
+
console.error(uiError('No supported package manager found'))
|
|
229
229
|
console.log()
|
|
230
230
|
console.log(chalk.gray('Manual installation:'))
|
|
231
231
|
for (const instruction of getPgcliManualInstructions()) {
|
|
@@ -234,7 +234,7 @@ export const connectCommand = new Command('connect')
|
|
|
234
234
|
process.exit(1)
|
|
235
235
|
}
|
|
236
236
|
} else {
|
|
237
|
-
console.error(
|
|
237
|
+
console.error(uiError('pgcli is not installed'))
|
|
238
238
|
console.log()
|
|
239
239
|
console.log(
|
|
240
240
|
chalk.gray('Install pgcli for enhanced PostgreSQL shell:'),
|
|
@@ -253,7 +253,9 @@ export const connectCommand = new Command('connect')
|
|
|
253
253
|
const useMycli = options.mycli || options.installMycli
|
|
254
254
|
if (useMycli) {
|
|
255
255
|
if (engineName !== 'mysql') {
|
|
256
|
-
console.error(
|
|
256
|
+
console.error(
|
|
257
|
+
uiError('mycli is only available for MySQL containers'),
|
|
258
|
+
)
|
|
257
259
|
console.log(
|
|
258
260
|
chalk.gray('For PostgreSQL, use: spindb connect --pgcli'),
|
|
259
261
|
)
|
|
@@ -264,16 +266,18 @@ export const connectCommand = new Command('connect')
|
|
|
264
266
|
|
|
265
267
|
if (!mycliInstalled) {
|
|
266
268
|
if (options.installMycli) {
|
|
267
|
-
console.log(
|
|
269
|
+
console.log(
|
|
270
|
+
uiInfo('Installing mycli for enhanced MySQL shell...'),
|
|
271
|
+
)
|
|
268
272
|
const pm = await detectPackageManager()
|
|
269
273
|
if (pm) {
|
|
270
274
|
const result = await installMycli(pm)
|
|
271
275
|
if (result.success) {
|
|
272
|
-
console.log(
|
|
276
|
+
console.log(uiSuccess('mycli installed successfully!'))
|
|
273
277
|
console.log()
|
|
274
278
|
} else {
|
|
275
279
|
console.error(
|
|
276
|
-
|
|
280
|
+
uiError(`Failed to install mycli: ${result.error}`),
|
|
277
281
|
)
|
|
278
282
|
console.log()
|
|
279
283
|
console.log(chalk.gray('Manual installation:'))
|
|
@@ -283,7 +287,7 @@ export const connectCommand = new Command('connect')
|
|
|
283
287
|
process.exit(1)
|
|
284
288
|
}
|
|
285
289
|
} else {
|
|
286
|
-
console.error(
|
|
290
|
+
console.error(uiError('No supported package manager found'))
|
|
287
291
|
console.log()
|
|
288
292
|
console.log(chalk.gray('Manual installation:'))
|
|
289
293
|
for (const instruction of getMycliManualInstructions()) {
|
|
@@ -292,7 +296,7 @@ export const connectCommand = new Command('connect')
|
|
|
292
296
|
process.exit(1)
|
|
293
297
|
}
|
|
294
298
|
} else {
|
|
295
|
-
console.error(
|
|
299
|
+
console.error(uiError('mycli is not installed'))
|
|
296
300
|
console.log()
|
|
297
301
|
console.log(chalk.gray('Install mycli for enhanced MySQL shell:'))
|
|
298
302
|
console.log(chalk.cyan(' spindb connect --install-mycli'))
|
|
@@ -309,9 +313,13 @@ export const connectCommand = new Command('connect')
|
|
|
309
313
|
const useLitecli = options.litecli || options.installLitecli
|
|
310
314
|
if (useLitecli) {
|
|
311
315
|
if (engineName !== Engine.SQLite) {
|
|
312
|
-
console.error(
|
|
316
|
+
console.error(
|
|
317
|
+
uiError('litecli is only available for SQLite containers'),
|
|
318
|
+
)
|
|
313
319
|
if (engineName === 'postgresql') {
|
|
314
|
-
console.log(
|
|
320
|
+
console.log(
|
|
321
|
+
chalk.gray('For PostgreSQL, use: spindb connect --pgcli'),
|
|
322
|
+
)
|
|
315
323
|
} else if (engineName === 'mysql') {
|
|
316
324
|
console.log(chalk.gray('For MySQL, use: spindb connect --mycli'))
|
|
317
325
|
}
|
|
@@ -322,16 +330,18 @@ export const connectCommand = new Command('connect')
|
|
|
322
330
|
|
|
323
331
|
if (!litecliInstalled) {
|
|
324
332
|
if (options.installLitecli) {
|
|
325
|
-
console.log(
|
|
333
|
+
console.log(
|
|
334
|
+
uiInfo('Installing litecli for enhanced SQLite shell...'),
|
|
335
|
+
)
|
|
326
336
|
const pm = await detectPackageManager()
|
|
327
337
|
if (pm) {
|
|
328
338
|
const result = await installLitecli(pm)
|
|
329
339
|
if (result.success) {
|
|
330
|
-
console.log(
|
|
340
|
+
console.log(uiSuccess('litecli installed successfully!'))
|
|
331
341
|
console.log()
|
|
332
342
|
} else {
|
|
333
343
|
console.error(
|
|
334
|
-
|
|
344
|
+
uiError(`Failed to install litecli: ${result.error}`),
|
|
335
345
|
)
|
|
336
346
|
console.log()
|
|
337
347
|
console.log(chalk.gray('Manual installation:'))
|
|
@@ -341,7 +351,7 @@ export const connectCommand = new Command('connect')
|
|
|
341
351
|
process.exit(1)
|
|
342
352
|
}
|
|
343
353
|
} else {
|
|
344
|
-
console.error(
|
|
354
|
+
console.error(uiError('No supported package manager found'))
|
|
345
355
|
console.log()
|
|
346
356
|
console.log(chalk.gray('Manual installation:'))
|
|
347
357
|
for (const instruction of getLitecliManualInstructions()) {
|
|
@@ -350,9 +360,11 @@ export const connectCommand = new Command('connect')
|
|
|
350
360
|
process.exit(1)
|
|
351
361
|
}
|
|
352
362
|
} else {
|
|
353
|
-
console.error(
|
|
363
|
+
console.error(uiError('litecli is not installed'))
|
|
354
364
|
console.log()
|
|
355
|
-
console.log(
|
|
365
|
+
console.log(
|
|
366
|
+
chalk.gray('Install litecli for enhanced SQLite shell:'),
|
|
367
|
+
)
|
|
356
368
|
console.log(chalk.cyan(' spindb connect --install-litecli'))
|
|
357
369
|
console.log()
|
|
358
370
|
console.log(chalk.gray('Or install manually:'))
|
|
@@ -364,7 +376,7 @@ export const connectCommand = new Command('connect')
|
|
|
364
376
|
}
|
|
365
377
|
}
|
|
366
378
|
|
|
367
|
-
console.log(
|
|
379
|
+
console.log(uiInfo(`Connecting to ${containerName}:${database}...`))
|
|
368
380
|
console.log()
|
|
369
381
|
|
|
370
382
|
let clientCmd: string
|
|
@@ -415,7 +427,7 @@ export const connectCommand = new Command('connect')
|
|
|
415
427
|
|
|
416
428
|
clientProcess.on('error', (err: NodeJS.ErrnoException) => {
|
|
417
429
|
if (err.code === 'ENOENT') {
|
|
418
|
-
console.log(
|
|
430
|
+
console.log(uiWarning(`${clientCmd} not found on your system.`))
|
|
419
431
|
console.log()
|
|
420
432
|
console.log(
|
|
421
433
|
chalk.gray(' Install client tools or connect manually:'),
|
|
@@ -451,16 +463,16 @@ export const connectCommand = new Command('connect')
|
|
|
451
463
|
}
|
|
452
464
|
console.log()
|
|
453
465
|
} else {
|
|
454
|
-
console.error(
|
|
466
|
+
console.error(uiError(err.message))
|
|
455
467
|
}
|
|
456
468
|
})
|
|
457
469
|
|
|
458
470
|
await new Promise<void>((resolve) => {
|
|
459
471
|
clientProcess.on('close', () => resolve())
|
|
460
472
|
})
|
|
461
|
-
} catch (
|
|
462
|
-
const e =
|
|
463
|
-
console.error(
|
|
473
|
+
} catch (error) {
|
|
474
|
+
const e = error as Error
|
|
475
|
+
console.error(uiError(e.message))
|
|
464
476
|
process.exit(1)
|
|
465
477
|
}
|
|
466
478
|
},
|
package/cli/commands/create.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
promptConfirm,
|
|
14
14
|
} from '../ui/prompts'
|
|
15
15
|
import { createSpinner } from '../ui/spinner'
|
|
16
|
-
import { header,
|
|
16
|
+
import { header, uiError, connectionBox } from '../ui/theme'
|
|
17
17
|
import { tmpdir } from 'os'
|
|
18
18
|
import { join } from 'path'
|
|
19
19
|
import { getMissingDependencies } from '../../core/dependency-manager'
|
|
@@ -21,9 +21,9 @@ import { platformService } from '../../core/platform-service'
|
|
|
21
21
|
import { startWithRetry } from '../../core/start-with-retry'
|
|
22
22
|
import { TransactionManager } from '../../core/transaction-manager'
|
|
23
23
|
import { isValidDatabaseName } from '../../core/error-handler'
|
|
24
|
+
import { resolve } from 'path'
|
|
24
25
|
import { Engine } from '../../types'
|
|
25
26
|
import type { BaseEngine } from '../../engines/base-engine'
|
|
26
|
-
import { resolve } from 'path'
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Simplified SQLite container creation flow
|
|
@@ -43,8 +43,13 @@ async function createSqliteContainer(
|
|
|
43
43
|
|
|
44
44
|
const missingDeps = await getMissingDependencies('sqlite')
|
|
45
45
|
if (missingDeps.length > 0) {
|
|
46
|
-
depsSpinner.warn(
|
|
47
|
-
|
|
46
|
+
depsSpinner.warn(
|
|
47
|
+
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
48
|
+
)
|
|
49
|
+
const installed = await promptInstallDependencies(
|
|
50
|
+
missingDeps[0].binary,
|
|
51
|
+
'sqlite',
|
|
52
|
+
)
|
|
48
53
|
if (!installed) {
|
|
49
54
|
process.exit(1)
|
|
50
55
|
}
|
|
@@ -64,7 +69,7 @@ async function createSqliteContainer(
|
|
|
64
69
|
|
|
65
70
|
// Check if file already exists
|
|
66
71
|
if (existsSync(absolutePath)) {
|
|
67
|
-
console.error(
|
|
72
|
+
console.error(uiError(`File already exists: ${absolutePath}`))
|
|
68
73
|
process.exit(1)
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -75,9 +80,9 @@ async function createSqliteContainer(
|
|
|
75
80
|
// Initialize the SQLite database file and register in registry
|
|
76
81
|
await dbEngine.initDataDir(containerName, version, { path: absolutePath })
|
|
77
82
|
createSpinnerInstance.succeed('SQLite database created')
|
|
78
|
-
} catch (
|
|
83
|
+
} catch (error) {
|
|
79
84
|
createSpinnerInstance.fail('Failed to create SQLite database')
|
|
80
|
-
throw
|
|
85
|
+
throw error
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
// Handle --from restore
|
|
@@ -85,15 +90,23 @@ async function createSqliteContainer(
|
|
|
85
90
|
const config = await containerManager.getConfig(containerName)
|
|
86
91
|
if (config) {
|
|
87
92
|
const format = await dbEngine.detectBackupFormat(restoreLocation)
|
|
88
|
-
const restoreSpinner = createSpinner(
|
|
93
|
+
const restoreSpinner = createSpinner(
|
|
94
|
+
`Restoring from ${format.description}...`,
|
|
95
|
+
)
|
|
89
96
|
restoreSpinner.start()
|
|
90
97
|
|
|
91
98
|
try {
|
|
92
99
|
await dbEngine.restore(config, restoreLocation)
|
|
93
100
|
restoreSpinner.succeed('Backup restored successfully')
|
|
94
|
-
} catch (
|
|
101
|
+
} catch (error) {
|
|
95
102
|
restoreSpinner.fail('Failed to restore backup')
|
|
96
|
-
|
|
103
|
+
// Clean up the created container on restore failure
|
|
104
|
+
try {
|
|
105
|
+
await containerManager.delete(containerName, { force: true })
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore cleanup errors - still throw the original restore error
|
|
108
|
+
}
|
|
109
|
+
throw error
|
|
97
110
|
}
|
|
98
111
|
}
|
|
99
112
|
}
|
|
@@ -146,7 +159,11 @@ function detectLocationType(location: string): {
|
|
|
146
159
|
if (existsSync(location)) {
|
|
147
160
|
// Check if it's a SQLite file (case-insensitive)
|
|
148
161
|
const lowerLocation = location.toLowerCase()
|
|
149
|
-
if (
|
|
162
|
+
if (
|
|
163
|
+
lowerLocation.endsWith('.sqlite') ||
|
|
164
|
+
lowerLocation.endsWith('.db') ||
|
|
165
|
+
lowerLocation.endsWith('.sqlite3')
|
|
166
|
+
) {
|
|
150
167
|
return { type: 'file', inferredEngine: Engine.SQLite }
|
|
151
168
|
}
|
|
152
169
|
return { type: 'file' }
|
|
@@ -158,7 +175,10 @@ function detectLocationType(location: string): {
|
|
|
158
175
|
export const createCommand = new Command('create')
|
|
159
176
|
.description('Create a new database container')
|
|
160
177
|
.argument('[name]', 'Container name')
|
|
161
|
-
.option(
|
|
178
|
+
.option(
|
|
179
|
+
'-e, --engine <engine>',
|
|
180
|
+
'Database engine (postgresql, mysql, sqlite)',
|
|
181
|
+
)
|
|
162
182
|
.option('-v, --version <version>', 'Database version')
|
|
163
183
|
.option('-d, --database <database>', 'Database name')
|
|
164
184
|
.option('-p, --port <port>', 'Port number')
|
|
@@ -207,7 +227,7 @@ export const createCommand = new Command('create')
|
|
|
207
227
|
const locationInfo = detectLocationType(options.from)
|
|
208
228
|
|
|
209
229
|
if (locationInfo.type === 'not_found') {
|
|
210
|
-
console.error(
|
|
230
|
+
console.error(uiError(`Location not found: ${options.from}`))
|
|
211
231
|
console.log(
|
|
212
232
|
chalk.gray(
|
|
213
233
|
' Provide a valid file path or connection string (postgresql://, mysql://)',
|
|
@@ -230,7 +250,7 @@ export const createCommand = new Command('create')
|
|
|
230
250
|
|
|
231
251
|
if (options.start === false) {
|
|
232
252
|
console.error(
|
|
233
|
-
|
|
253
|
+
uiError(
|
|
234
254
|
'Cannot use --no-start with --from (restore requires running container)',
|
|
235
255
|
),
|
|
236
256
|
)
|
|
@@ -257,7 +277,7 @@ export const createCommand = new Command('create')
|
|
|
257
277
|
// Validate database name to prevent SQL injection
|
|
258
278
|
if (!isValidDatabaseName(database)) {
|
|
259
279
|
console.error(
|
|
260
|
-
|
|
280
|
+
uiError(
|
|
261
281
|
'Database name must start with a letter and contain only letters, numbers, hyphens, and underscores',
|
|
262
282
|
),
|
|
263
283
|
)
|
|
@@ -282,13 +302,26 @@ export const createCommand = new Command('create')
|
|
|
282
302
|
// For server databases, validate --connect with --no-start
|
|
283
303
|
if (options.connect && options.start === false) {
|
|
284
304
|
console.error(
|
|
285
|
-
|
|
305
|
+
uiError(
|
|
286
306
|
'Cannot use --no-start with --connect (connection requires running container)',
|
|
287
307
|
),
|
|
288
308
|
)
|
|
289
309
|
process.exit(1)
|
|
290
310
|
}
|
|
291
311
|
|
|
312
|
+
// Validate --max-connections if provided
|
|
313
|
+
if (options.maxConnections) {
|
|
314
|
+
const parsed = parseInt(options.maxConnections, 10)
|
|
315
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
316
|
+
console.error(
|
|
317
|
+
uiError(
|
|
318
|
+
'Invalid --max-connections value: must be a positive integer',
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
process.exit(1)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
292
325
|
const depsSpinner = createSpinner('Checking required tools...')
|
|
293
326
|
depsSpinner.start()
|
|
294
327
|
|
|
@@ -310,7 +343,7 @@ export const createCommand = new Command('create')
|
|
|
310
343
|
missingDeps = await getMissingDependencies(engine)
|
|
311
344
|
if (missingDeps.length > 0) {
|
|
312
345
|
console.error(
|
|
313
|
-
|
|
346
|
+
uiError(
|
|
314
347
|
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
315
348
|
),
|
|
316
349
|
)
|
|
@@ -399,9 +432,9 @@ export const createCommand = new Command('create')
|
|
|
399
432
|
})
|
|
400
433
|
|
|
401
434
|
createSpinnerInstance.succeed('Container created')
|
|
402
|
-
} catch (
|
|
435
|
+
} catch (error) {
|
|
403
436
|
createSpinnerInstance.fail('Failed to create container')
|
|
404
|
-
throw
|
|
437
|
+
throw error
|
|
405
438
|
}
|
|
406
439
|
|
|
407
440
|
const initSpinner = createSpinner('Initializing database cluster...')
|
|
@@ -415,10 +448,10 @@ export const createCommand = new Command('create')
|
|
|
415
448
|
: undefined,
|
|
416
449
|
})
|
|
417
450
|
initSpinner.succeed('Database cluster initialized')
|
|
418
|
-
} catch (
|
|
451
|
+
} catch (error) {
|
|
419
452
|
initSpinner.fail('Failed to initialize database cluster')
|
|
420
453
|
await tx.rollback()
|
|
421
|
-
throw
|
|
454
|
+
throw error
|
|
422
455
|
}
|
|
423
456
|
|
|
424
457
|
// --from requires start, --start forces start, --no-start skips, otherwise ask user
|
|
@@ -484,14 +517,14 @@ export const createCommand = new Command('create')
|
|
|
484
517
|
} else {
|
|
485
518
|
startSpinner.succeed(`${dbEngine.displayName} started`)
|
|
486
519
|
}
|
|
487
|
-
} catch (
|
|
520
|
+
} catch (error) {
|
|
488
521
|
if (!startSpinner.isSpinning) {
|
|
489
522
|
// Error was already handled above
|
|
490
523
|
} else {
|
|
491
524
|
startSpinner.fail(`Failed to start ${dbEngine.displayName}`)
|
|
492
525
|
}
|
|
493
526
|
await tx.rollback()
|
|
494
|
-
throw
|
|
527
|
+
throw error
|
|
495
528
|
}
|
|
496
529
|
|
|
497
530
|
const defaultDb = engineDefaults.superuser
|
|
@@ -504,10 +537,10 @@ export const createCommand = new Command('create')
|
|
|
504
537
|
try {
|
|
505
538
|
await dbEngine.createDatabase(config, database)
|
|
506
539
|
dbSpinner.succeed(`Database "${database}" created`)
|
|
507
|
-
} catch (
|
|
540
|
+
} catch (error) {
|
|
508
541
|
dbSpinner.fail(`Failed to create database "${database}"`)
|
|
509
542
|
await tx.rollback()
|
|
510
|
-
throw
|
|
543
|
+
throw error
|
|
511
544
|
}
|
|
512
545
|
}
|
|
513
546
|
}
|
|
@@ -538,8 +571,8 @@ export const createCommand = new Command('create')
|
|
|
538
571
|
dumpSpinner.succeed('Dump created from remote database')
|
|
539
572
|
backupPath = tempDumpPath
|
|
540
573
|
dumpSuccess = true
|
|
541
|
-
} catch (
|
|
542
|
-
const e =
|
|
574
|
+
} catch (error) {
|
|
575
|
+
const e = error as Error
|
|
543
576
|
dumpSpinner.fail('Failed to create dump')
|
|
544
577
|
|
|
545
578
|
if (
|
|
@@ -554,14 +587,14 @@ export const createCommand = new Command('create')
|
|
|
554
587
|
}
|
|
555
588
|
|
|
556
589
|
console.log()
|
|
557
|
-
console.error(
|
|
590
|
+
console.error(uiError('pg_dump error:'))
|
|
558
591
|
console.log(chalk.gray(` ${e.message}`))
|
|
559
592
|
process.exit(1)
|
|
560
593
|
}
|
|
561
594
|
}
|
|
562
595
|
|
|
563
596
|
if (!dumpSuccess) {
|
|
564
|
-
console.error(
|
|
597
|
+
console.error(uiError('Failed to create dump after retries'))
|
|
565
598
|
process.exit(1)
|
|
566
599
|
}
|
|
567
600
|
} else {
|
|
@@ -639,8 +672,8 @@ export const createCommand = new Command('create')
|
|
|
639
672
|
console.log()
|
|
640
673
|
}
|
|
641
674
|
}
|
|
642
|
-
} catch (
|
|
643
|
-
const e =
|
|
675
|
+
} catch (error) {
|
|
676
|
+
const e = error as Error
|
|
644
677
|
|
|
645
678
|
const missingToolPatterns = [
|
|
646
679
|
'pg_restore not found',
|
|
@@ -666,7 +699,7 @@ export const createCommand = new Command('create')
|
|
|
666
699
|
process.exit(1)
|
|
667
700
|
}
|
|
668
701
|
|
|
669
|
-
console.error(
|
|
702
|
+
console.error(uiError(e.message))
|
|
670
703
|
process.exit(1)
|
|
671
704
|
} finally {
|
|
672
705
|
if (tempDumpPath) {
|
package/cli/commands/delete.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { processManager } from '../../core/process-manager'
|
|
|
4
4
|
import { getEngine } from '../../engines'
|
|
5
5
|
import { promptContainerSelect, promptConfirm } from '../ui/prompts'
|
|
6
6
|
import { createSpinner } from '../ui/spinner'
|
|
7
|
-
import {
|
|
7
|
+
import { uiError, uiWarning } from '../ui/theme'
|
|
8
8
|
|
|
9
9
|
export const deleteCommand = new Command('delete')
|
|
10
10
|
.alias('rm')
|
|
@@ -24,7 +24,7 @@ export const deleteCommand = new Command('delete')
|
|
|
24
24
|
const containers = await containerManager.list()
|
|
25
25
|
|
|
26
26
|
if (containers.length === 0) {
|
|
27
|
-
console.log(
|
|
27
|
+
console.log(uiWarning('No containers found'))
|
|
28
28
|
return
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -38,7 +38,7 @@ export const deleteCommand = new Command('delete')
|
|
|
38
38
|
|
|
39
39
|
const config = await containerManager.getConfig(containerName)
|
|
40
40
|
if (!config) {
|
|
41
|
-
console.error(
|
|
41
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
42
42
|
process.exit(1)
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -48,7 +48,7 @@ export const deleteCommand = new Command('delete')
|
|
|
48
48
|
false,
|
|
49
49
|
)
|
|
50
50
|
if (!confirmed) {
|
|
51
|
-
console.log(
|
|
51
|
+
console.log(uiWarning('Deletion cancelled'))
|
|
52
52
|
return
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -67,7 +67,7 @@ export const deleteCommand = new Command('delete')
|
|
|
67
67
|
stopSpinner.succeed(`Stopped "${containerName}"`)
|
|
68
68
|
} else {
|
|
69
69
|
console.error(
|
|
70
|
-
|
|
70
|
+
uiError(
|
|
71
71
|
`Container "${containerName}" is running. Stop it first or use --force`,
|
|
72
72
|
),
|
|
73
73
|
)
|
|
@@ -81,9 +81,9 @@ export const deleteCommand = new Command('delete')
|
|
|
81
81
|
await containerManager.delete(containerName, { force: true })
|
|
82
82
|
|
|
83
83
|
deleteSpinner.succeed(`Container "${containerName}" deleted`)
|
|
84
|
-
} catch (
|
|
85
|
-
const e =
|
|
86
|
-
console.error(
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const e = error as Error
|
|
86
|
+
console.error(uiError(e.message))
|
|
87
87
|
process.exit(1)
|
|
88
88
|
}
|
|
89
89
|
},
|