spindb 0.7.0 → 0.7.5
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/backup.ts +1 -30
- package/cli/commands/clone.ts +0 -6
- package/cli/commands/config.ts +7 -1
- package/cli/commands/connect.ts +1 -16
- package/cli/commands/create.ts +4 -55
- package/cli/commands/delete.ts +0 -6
- package/cli/commands/edit.ts +9 -25
- package/cli/commands/engines.ts +10 -188
- package/cli/commands/info.ts +7 -34
- package/cli/commands/list.ts +2 -18
- package/cli/commands/logs.ts +118 -0
- package/cli/commands/menu/backup-handlers.ts +749 -0
- package/cli/commands/menu/container-handlers.ts +825 -0
- package/cli/commands/menu/engine-handlers.ts +362 -0
- package/cli/commands/menu/index.ts +179 -0
- package/cli/commands/menu/shared.ts +26 -0
- package/cli/commands/menu/shell-handlers.ts +320 -0
- package/cli/commands/menu/sql-handlers.ts +194 -0
- package/cli/commands/menu/update-handlers.ts +94 -0
- package/cli/commands/restore.ts +2 -28
- package/cli/commands/run.ts +139 -0
- package/cli/commands/start.ts +2 -10
- package/cli/commands/stop.ts +0 -5
- package/cli/commands/url.ts +18 -13
- 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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import inquirer from 'inquirer'
|
|
3
|
+
import { rm } from 'fs/promises'
|
|
4
|
+
import { containerManager } from '../../../core/container-manager'
|
|
5
|
+
import { processManager } from '../../../core/process-manager'
|
|
6
|
+
import { createSpinner } from '../../ui/spinner'
|
|
7
|
+
import { header, error, warning, info, formatBytes } from '../../ui/theme'
|
|
8
|
+
import { promptConfirm } from '../../ui/prompts'
|
|
9
|
+
import { getEngineIcon, ENGINE_ICONS } from '../../constants'
|
|
10
|
+
import {
|
|
11
|
+
getInstalledEngines,
|
|
12
|
+
type InstalledPostgresEngine,
|
|
13
|
+
type InstalledMysqlEngine,
|
|
14
|
+
} from '../../helpers'
|
|
15
|
+
import {
|
|
16
|
+
getMysqlVersion,
|
|
17
|
+
getMysqlInstallInfo,
|
|
18
|
+
} from '../../../engines/mysql/binary-detection'
|
|
19
|
+
import { type MenuChoice } from './shared'
|
|
20
|
+
|
|
21
|
+
export async function handleEngines(): Promise<void> {
|
|
22
|
+
console.clear()
|
|
23
|
+
console.log(header('Installed Engines'))
|
|
24
|
+
console.log()
|
|
25
|
+
|
|
26
|
+
const engines = await getInstalledEngines()
|
|
27
|
+
|
|
28
|
+
if (engines.length === 0) {
|
|
29
|
+
console.log(info('No engines installed yet.'))
|
|
30
|
+
console.log(
|
|
31
|
+
chalk.gray(
|
|
32
|
+
' PostgreSQL engines are downloaded automatically when you create a container.',
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
console.log(
|
|
36
|
+
chalk.gray(
|
|
37
|
+
' MySQL requires system installation (brew install mysql or apt install mysql-server).',
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pgEngines = engines.filter(
|
|
44
|
+
(e): e is InstalledPostgresEngine => e.engine === 'postgresql',
|
|
45
|
+
)
|
|
46
|
+
const mysqlEngine = engines.find(
|
|
47
|
+
(e): e is InstalledMysqlEngine => e.engine === 'mysql',
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const totalPgSize = pgEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
|
|
51
|
+
|
|
52
|
+
console.log()
|
|
53
|
+
console.log(
|
|
54
|
+
chalk.gray(' ') +
|
|
55
|
+
chalk.bold.white('ENGINE'.padEnd(14)) +
|
|
56
|
+
chalk.bold.white('VERSION'.padEnd(12)) +
|
|
57
|
+
chalk.bold.white('SOURCE'.padEnd(18)) +
|
|
58
|
+
chalk.bold.white('SIZE'),
|
|
59
|
+
)
|
|
60
|
+
console.log(chalk.gray(' ' + '─'.repeat(55)))
|
|
61
|
+
|
|
62
|
+
for (const engine of pgEngines) {
|
|
63
|
+
const icon = getEngineIcon(engine.engine)
|
|
64
|
+
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
65
|
+
|
|
66
|
+
console.log(
|
|
67
|
+
chalk.gray(' ') +
|
|
68
|
+
chalk.cyan(`${icon} ${engine.engine}`.padEnd(13)) +
|
|
69
|
+
chalk.yellow(engine.version.padEnd(12)) +
|
|
70
|
+
chalk.gray(platformInfo.padEnd(18)) +
|
|
71
|
+
chalk.white(formatBytes(engine.sizeBytes)),
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (mysqlEngine) {
|
|
76
|
+
const icon = ENGINE_ICONS.mysql
|
|
77
|
+
const displayName = mysqlEngine.isMariaDB ? 'mariadb' : 'mysql'
|
|
78
|
+
|
|
79
|
+
console.log(
|
|
80
|
+
chalk.gray(' ') +
|
|
81
|
+
chalk.cyan(`${icon} ${displayName}`.padEnd(13)) +
|
|
82
|
+
chalk.yellow(mysqlEngine.version.padEnd(12)) +
|
|
83
|
+
chalk.gray('system'.padEnd(18)) +
|
|
84
|
+
chalk.gray('(system-installed)'),
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(chalk.gray(' ' + '─'.repeat(55)))
|
|
89
|
+
|
|
90
|
+
console.log()
|
|
91
|
+
if (pgEngines.length > 0) {
|
|
92
|
+
console.log(
|
|
93
|
+
chalk.gray(
|
|
94
|
+
` PostgreSQL: ${pgEngines.length} version(s), ${formatBytes(totalPgSize)}`,
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
if (mysqlEngine) {
|
|
99
|
+
console.log(chalk.gray(` MySQL: system-installed at ${mysqlEngine.path}`))
|
|
100
|
+
}
|
|
101
|
+
console.log()
|
|
102
|
+
|
|
103
|
+
const choices: MenuChoice[] = []
|
|
104
|
+
|
|
105
|
+
for (const e of pgEngines) {
|
|
106
|
+
choices.push({
|
|
107
|
+
name: `${chalk.red('✕')} Delete ${e.engine} ${e.version} ${chalk.gray(`(${formatBytes(e.sizeBytes)})`)}`,
|
|
108
|
+
value: `delete:${e.path}:${e.engine}:${e.version}`,
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (mysqlEngine) {
|
|
113
|
+
const displayName = mysqlEngine.isMariaDB ? 'MariaDB' : 'MySQL'
|
|
114
|
+
choices.push({
|
|
115
|
+
name: `${chalk.blue('ℹ')} ${displayName} ${mysqlEngine.version} ${chalk.gray('(system-installed)')}`,
|
|
116
|
+
value: `mysql-info:${mysqlEngine.path}`,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
choices.push(new inquirer.Separator())
|
|
121
|
+
choices.push({ name: `${chalk.blue('←')} Back to main menu`, value: 'back' })
|
|
122
|
+
|
|
123
|
+
const { action } = await inquirer.prompt<{ action: string }>([
|
|
124
|
+
{
|
|
125
|
+
type: 'list',
|
|
126
|
+
name: 'action',
|
|
127
|
+
message: 'Manage engines:',
|
|
128
|
+
choices,
|
|
129
|
+
pageSize: 15,
|
|
130
|
+
},
|
|
131
|
+
])
|
|
132
|
+
|
|
133
|
+
if (action === 'back') {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (action.startsWith('delete:')) {
|
|
138
|
+
const [, enginePath, engineName, engineVersion] = action.split(':')
|
|
139
|
+
await handleDeleteEngine(enginePath, engineName, engineVersion)
|
|
140
|
+
await handleEngines()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (action.startsWith('mysql-info:')) {
|
|
144
|
+
const mysqldPath = action.replace('mysql-info:', '')
|
|
145
|
+
await handleMysqlInfo(mysqldPath)
|
|
146
|
+
await handleEngines()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function handleDeleteEngine(
|
|
151
|
+
enginePath: string,
|
|
152
|
+
engineName: string,
|
|
153
|
+
engineVersion: string,
|
|
154
|
+
): Promise<void> {
|
|
155
|
+
const containers = await containerManager.list()
|
|
156
|
+
const usingContainers = containers.filter(
|
|
157
|
+
(c) => c.engine === engineName && c.version === engineVersion,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if (usingContainers.length > 0) {
|
|
161
|
+
console.log()
|
|
162
|
+
console.log(
|
|
163
|
+
error(
|
|
164
|
+
`Cannot delete: ${usingContainers.length} container(s) are using ${engineName} ${engineVersion}`,
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
console.log(
|
|
168
|
+
chalk.gray(
|
|
169
|
+
` Containers: ${usingContainers.map((c) => c.name).join(', ')}`,
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
console.log()
|
|
173
|
+
await inquirer.prompt([
|
|
174
|
+
{
|
|
175
|
+
type: 'input',
|
|
176
|
+
name: 'continue',
|
|
177
|
+
message: chalk.gray('Press Enter to continue...'),
|
|
178
|
+
},
|
|
179
|
+
])
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const confirmed = await promptConfirm(
|
|
184
|
+
`Delete ${engineName} ${engineVersion}? This cannot be undone.`,
|
|
185
|
+
false,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if (!confirmed) {
|
|
189
|
+
console.log(warning('Deletion cancelled'))
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const spinner = createSpinner(`Deleting ${engineName} ${engineVersion}...`)
|
|
194
|
+
spinner.start()
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await rm(enginePath, { recursive: true, force: true })
|
|
198
|
+
spinner.succeed(`Deleted ${engineName} ${engineVersion}`)
|
|
199
|
+
} catch (err) {
|
|
200
|
+
const e = err as Error
|
|
201
|
+
spinner.fail(`Failed to delete: ${e.message}`)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function handleMysqlInfo(mysqldPath: string): Promise<void> {
|
|
206
|
+
console.clear()
|
|
207
|
+
|
|
208
|
+
const installInfo = await getMysqlInstallInfo(mysqldPath)
|
|
209
|
+
const displayName = installInfo.isMariaDB ? 'MariaDB' : 'MySQL'
|
|
210
|
+
|
|
211
|
+
const version = await getMysqlVersion(mysqldPath)
|
|
212
|
+
|
|
213
|
+
console.log(header(`${displayName} Information`))
|
|
214
|
+
console.log()
|
|
215
|
+
|
|
216
|
+
const containers = await containerManager.list()
|
|
217
|
+
const mysqlContainers = containers.filter((c) => c.engine === 'mysql')
|
|
218
|
+
|
|
219
|
+
const runningContainers: string[] = []
|
|
220
|
+
|
|
221
|
+
if (mysqlContainers.length > 0) {
|
|
222
|
+
console.log(
|
|
223
|
+
warning(
|
|
224
|
+
`${mysqlContainers.length} container(s) are using ${displayName}:`,
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
console.log()
|
|
228
|
+
for (const c of mysqlContainers) {
|
|
229
|
+
const isRunning = await processManager.isRunning(c.name, {
|
|
230
|
+
engine: c.engine,
|
|
231
|
+
})
|
|
232
|
+
if (isRunning) {
|
|
233
|
+
runningContainers.push(c.name)
|
|
234
|
+
}
|
|
235
|
+
const status = isRunning
|
|
236
|
+
? chalk.green('● running')
|
|
237
|
+
: chalk.gray('○ stopped')
|
|
238
|
+
console.log(chalk.gray(` • ${c.name} ${status}`))
|
|
239
|
+
}
|
|
240
|
+
console.log()
|
|
241
|
+
console.log(
|
|
242
|
+
chalk.yellow(
|
|
243
|
+
' Uninstalling will break these containers. Delete them first.',
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
console.log()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(chalk.white(' Installation Details:'))
|
|
250
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
251
|
+
console.log(
|
|
252
|
+
chalk.gray(' ') +
|
|
253
|
+
chalk.white('Version:'.padEnd(18)) +
|
|
254
|
+
chalk.yellow(version || 'unknown'),
|
|
255
|
+
)
|
|
256
|
+
console.log(
|
|
257
|
+
chalk.gray(' ') +
|
|
258
|
+
chalk.white('Binary Path:'.padEnd(18)) +
|
|
259
|
+
chalk.gray(mysqldPath),
|
|
260
|
+
)
|
|
261
|
+
console.log(
|
|
262
|
+
chalk.gray(' ') +
|
|
263
|
+
chalk.white('Package Manager:'.padEnd(18)) +
|
|
264
|
+
chalk.cyan(installInfo.packageManager),
|
|
265
|
+
)
|
|
266
|
+
console.log(
|
|
267
|
+
chalk.gray(' ') +
|
|
268
|
+
chalk.white('Package Name:'.padEnd(18)) +
|
|
269
|
+
chalk.cyan(installInfo.packageName),
|
|
270
|
+
)
|
|
271
|
+
console.log()
|
|
272
|
+
|
|
273
|
+
console.log(chalk.white(' To uninstall:'))
|
|
274
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
275
|
+
|
|
276
|
+
let stepNum = 1
|
|
277
|
+
|
|
278
|
+
if (runningContainers.length > 0) {
|
|
279
|
+
console.log(chalk.gray(` # ${stepNum}. Stop running SpinDB containers`))
|
|
280
|
+
console.log(chalk.cyan(' spindb stop <container-name>'))
|
|
281
|
+
console.log()
|
|
282
|
+
stepNum++
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (mysqlContainers.length > 0) {
|
|
286
|
+
console.log(chalk.gray(` # ${stepNum}. Delete SpinDB containers`))
|
|
287
|
+
console.log(chalk.cyan(' spindb delete <container-name>'))
|
|
288
|
+
console.log()
|
|
289
|
+
stepNum++
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (installInfo.packageManager === 'homebrew') {
|
|
293
|
+
console.log(
|
|
294
|
+
chalk.gray(
|
|
295
|
+
` # ${stepNum}. Stop Homebrew service (if running separately)`,
|
|
296
|
+
),
|
|
297
|
+
)
|
|
298
|
+
console.log(chalk.cyan(` brew services stop ${installInfo.packageName}`))
|
|
299
|
+
console.log()
|
|
300
|
+
console.log(chalk.gray(` # ${stepNum + 1}. Uninstall the package`))
|
|
301
|
+
console.log(chalk.cyan(` ${installInfo.uninstallCommand}`))
|
|
302
|
+
} else if (installInfo.packageManager === 'apt') {
|
|
303
|
+
console.log(chalk.gray(` # ${stepNum}. Stop the system service`))
|
|
304
|
+
console.log(
|
|
305
|
+
chalk.cyan(
|
|
306
|
+
` sudo systemctl stop ${installInfo.isMariaDB ? 'mariadb' : 'mysql'}`,
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
console.log()
|
|
310
|
+
console.log(chalk.gray(` # ${stepNum + 1}. Disable auto-start on boot`))
|
|
311
|
+
console.log(
|
|
312
|
+
chalk.cyan(
|
|
313
|
+
` sudo systemctl disable ${installInfo.isMariaDB ? 'mariadb' : 'mysql'}`,
|
|
314
|
+
),
|
|
315
|
+
)
|
|
316
|
+
console.log()
|
|
317
|
+
console.log(chalk.gray(` # ${stepNum + 2}. Uninstall the package`))
|
|
318
|
+
console.log(chalk.cyan(` ${installInfo.uninstallCommand}`))
|
|
319
|
+
console.log()
|
|
320
|
+
console.log(chalk.gray(` # ${stepNum + 3}. Remove data files (optional)`))
|
|
321
|
+
console.log(
|
|
322
|
+
chalk.cyan(' sudo apt purge mysql-server mysql-client mysql-common'),
|
|
323
|
+
)
|
|
324
|
+
console.log(chalk.cyan(' sudo rm -rf /var/lib/mysql /etc/mysql'))
|
|
325
|
+
} else if (
|
|
326
|
+
installInfo.packageManager === 'yum' ||
|
|
327
|
+
installInfo.packageManager === 'dnf'
|
|
328
|
+
) {
|
|
329
|
+
console.log(chalk.gray(` # ${stepNum}. Stop the system service`))
|
|
330
|
+
console.log(
|
|
331
|
+
chalk.cyan(
|
|
332
|
+
` sudo systemctl stop ${installInfo.isMariaDB ? 'mariadb' : 'mysqld'}`,
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
console.log()
|
|
336
|
+
console.log(chalk.gray(` # ${stepNum + 1}. Uninstall the package`))
|
|
337
|
+
console.log(chalk.cyan(` ${installInfo.uninstallCommand}`))
|
|
338
|
+
} else if (installInfo.packageManager === 'pacman') {
|
|
339
|
+
console.log(chalk.gray(` # ${stepNum}. Stop the system service`))
|
|
340
|
+
console.log(
|
|
341
|
+
chalk.cyan(
|
|
342
|
+
` sudo systemctl stop ${installInfo.isMariaDB ? 'mariadb' : 'mysqld'}`,
|
|
343
|
+
),
|
|
344
|
+
)
|
|
345
|
+
console.log()
|
|
346
|
+
console.log(chalk.gray(` # ${stepNum + 1}. Uninstall the package`))
|
|
347
|
+
console.log(chalk.cyan(` ${installInfo.uninstallCommand}`))
|
|
348
|
+
} else {
|
|
349
|
+
console.log(chalk.gray(' Use your system package manager to uninstall.'))
|
|
350
|
+
console.log(chalk.gray(` The binary is located at: ${mysqldPath}`))
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
console.log()
|
|
354
|
+
|
|
355
|
+
await inquirer.prompt([
|
|
356
|
+
{
|
|
357
|
+
type: 'input',
|
|
358
|
+
name: 'continue',
|
|
359
|
+
message: chalk.gray('Press Enter to go back...'),
|
|
360
|
+
},
|
|
361
|
+
])
|
|
362
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import inquirer from 'inquirer'
|
|
4
|
+
import { containerManager } from '../../../core/container-manager'
|
|
5
|
+
import { promptInstallDependencies } from '../../ui/prompts'
|
|
6
|
+
import { header, error } from '../../ui/theme'
|
|
7
|
+
import { getInstalledEngines } from '../../helpers'
|
|
8
|
+
import {
|
|
9
|
+
handleCreate,
|
|
10
|
+
handleList,
|
|
11
|
+
handleStart,
|
|
12
|
+
handleStop,
|
|
13
|
+
} from './container-handlers'
|
|
14
|
+
import { handleBackup, handleRestore, handleClone } from './backup-handlers'
|
|
15
|
+
import { handleEngines } from './engine-handlers'
|
|
16
|
+
import { handleCheckUpdate } from './update-handlers'
|
|
17
|
+
import { type MenuChoice } from './shared'
|
|
18
|
+
|
|
19
|
+
async function showMainMenu(): Promise<void> {
|
|
20
|
+
console.clear()
|
|
21
|
+
console.log(header('SpinDB - Local Database Manager'))
|
|
22
|
+
console.log()
|
|
23
|
+
|
|
24
|
+
const containers = await containerManager.list()
|
|
25
|
+
const running = containers.filter((c) => c.status === 'running').length
|
|
26
|
+
const stopped = containers.filter((c) => c.status !== 'running').length
|
|
27
|
+
|
|
28
|
+
console.log(
|
|
29
|
+
chalk.gray(
|
|
30
|
+
` ${containers.length} container(s): ${running} running, ${stopped} stopped`,
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
console.log()
|
|
34
|
+
|
|
35
|
+
const canStart = stopped > 0
|
|
36
|
+
const canStop = running > 0
|
|
37
|
+
const canRestore = running > 0
|
|
38
|
+
const canClone = containers.length > 0
|
|
39
|
+
|
|
40
|
+
// Check if any engines are installed
|
|
41
|
+
const engines = await getInstalledEngines()
|
|
42
|
+
const hasEngines = engines.length > 0
|
|
43
|
+
|
|
44
|
+
// If containers exist, show List first; otherwise show Create first
|
|
45
|
+
const hasContainers = containers.length > 0
|
|
46
|
+
|
|
47
|
+
const choices: MenuChoice[] = [
|
|
48
|
+
...(hasContainers
|
|
49
|
+
? [
|
|
50
|
+
{ name: `${chalk.cyan('◉')} Containers`, value: 'list' },
|
|
51
|
+
{ name: `${chalk.green('+')} Create new container`, value: 'create' },
|
|
52
|
+
]
|
|
53
|
+
: [
|
|
54
|
+
{ name: `${chalk.green('+')} Create new container`, value: 'create' },
|
|
55
|
+
{ name: `${chalk.cyan('◉')} Containers`, value: 'list' },
|
|
56
|
+
]),
|
|
57
|
+
{
|
|
58
|
+
name: canStart
|
|
59
|
+
? `${chalk.green('▶')} Start a container`
|
|
60
|
+
: chalk.gray('▶ Start a container'),
|
|
61
|
+
value: 'start',
|
|
62
|
+
disabled: canStart ? false : 'No stopped containers',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: canStop
|
|
66
|
+
? `${chalk.red('■')} Stop a container`
|
|
67
|
+
: chalk.gray('■ Stop a container'),
|
|
68
|
+
value: 'stop',
|
|
69
|
+
disabled: canStop ? false : 'No running containers',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: canRestore
|
|
73
|
+
? `${chalk.magenta('↓')} Restore backup`
|
|
74
|
+
: chalk.gray('↓ Restore backup'),
|
|
75
|
+
value: 'restore',
|
|
76
|
+
disabled: canRestore ? false : 'No running containers',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: canRestore
|
|
80
|
+
? `${chalk.magenta('↑')} Backup database`
|
|
81
|
+
: chalk.gray('↑ Backup database'),
|
|
82
|
+
value: 'backup',
|
|
83
|
+
disabled: canRestore ? false : 'No running containers',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: canClone
|
|
87
|
+
? `${chalk.cyan('⧉')} Clone a container`
|
|
88
|
+
: chalk.gray('⧉ Clone a container'),
|
|
89
|
+
value: 'clone',
|
|
90
|
+
disabled: canClone ? false : 'No containers',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: hasEngines
|
|
94
|
+
? `${chalk.yellow('⚙')} List installed engines`
|
|
95
|
+
: chalk.gray('⚙ List installed engines'),
|
|
96
|
+
value: 'engines',
|
|
97
|
+
disabled: hasEngines ? false : 'No engines installed',
|
|
98
|
+
},
|
|
99
|
+
new inquirer.Separator(),
|
|
100
|
+
{ name: `${chalk.cyan('↑')} Check for updates`, value: 'check-update' },
|
|
101
|
+
{ name: `${chalk.gray('⏻')} Exit`, value: 'exit' },
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
const { action } = await inquirer.prompt<{ action: string }>([
|
|
105
|
+
{
|
|
106
|
+
type: 'list',
|
|
107
|
+
name: 'action',
|
|
108
|
+
message: 'What would you like to do?',
|
|
109
|
+
choices,
|
|
110
|
+
pageSize: 12,
|
|
111
|
+
},
|
|
112
|
+
])
|
|
113
|
+
|
|
114
|
+
switch (action) {
|
|
115
|
+
case 'create':
|
|
116
|
+
await handleCreate()
|
|
117
|
+
break
|
|
118
|
+
case 'list':
|
|
119
|
+
await handleList(showMainMenu)
|
|
120
|
+
break
|
|
121
|
+
case 'start':
|
|
122
|
+
await handleStart()
|
|
123
|
+
break
|
|
124
|
+
case 'stop':
|
|
125
|
+
await handleStop()
|
|
126
|
+
break
|
|
127
|
+
case 'restore':
|
|
128
|
+
await handleRestore()
|
|
129
|
+
break
|
|
130
|
+
case 'backup':
|
|
131
|
+
await handleBackup()
|
|
132
|
+
break
|
|
133
|
+
case 'clone':
|
|
134
|
+
await handleClone()
|
|
135
|
+
break
|
|
136
|
+
case 'engines':
|
|
137
|
+
await handleEngines()
|
|
138
|
+
break
|
|
139
|
+
case 'check-update':
|
|
140
|
+
await handleCheckUpdate()
|
|
141
|
+
break
|
|
142
|
+
case 'exit':
|
|
143
|
+
console.log(chalk.gray('\n Goodbye!\n'))
|
|
144
|
+
process.exit(0)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await showMainMenu()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const menuCommand = new Command('menu')
|
|
151
|
+
.description('Interactive menu for managing containers')
|
|
152
|
+
.action(async () => {
|
|
153
|
+
try {
|
|
154
|
+
await showMainMenu()
|
|
155
|
+
} catch (err) {
|
|
156
|
+
const e = err as Error
|
|
157
|
+
|
|
158
|
+
// Check if this is a missing tool error
|
|
159
|
+
if (
|
|
160
|
+
e.message.includes('pg_restore not found') ||
|
|
161
|
+
e.message.includes('psql not found') ||
|
|
162
|
+
e.message.includes('pg_dump not found')
|
|
163
|
+
) {
|
|
164
|
+
const missingTool = e.message.includes('pg_restore')
|
|
165
|
+
? 'pg_restore'
|
|
166
|
+
: e.message.includes('pg_dump')
|
|
167
|
+
? 'pg_dump'
|
|
168
|
+
: 'psql'
|
|
169
|
+
const installed = await promptInstallDependencies(missingTool)
|
|
170
|
+
if (installed) {
|
|
171
|
+
console.log(chalk.yellow(' Please re-run spindb to continue.'))
|
|
172
|
+
}
|
|
173
|
+
process.exit(1)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.error(error(e.message))
|
|
177
|
+
process.exit(1)
|
|
178
|
+
}
|
|
179
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import inquirer from 'inquirer'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Menu choice type for inquirer list prompts
|
|
6
|
+
*/
|
|
7
|
+
export type MenuChoice =
|
|
8
|
+
| {
|
|
9
|
+
name: string
|
|
10
|
+
value: string
|
|
11
|
+
disabled?: boolean | string
|
|
12
|
+
}
|
|
13
|
+
| inquirer.Separator
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Helper to pause and wait for user to press Enter
|
|
17
|
+
*/
|
|
18
|
+
export async function pressEnterToContinue(): Promise<void> {
|
|
19
|
+
await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'input',
|
|
22
|
+
name: 'continue',
|
|
23
|
+
message: chalk.gray('Press Enter to continue...'),
|
|
24
|
+
},
|
|
25
|
+
])
|
|
26
|
+
}
|