spindb 0.8.2 → 0.9.1
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 +87 -7
- package/cli/commands/clone.ts +6 -0
- package/cli/commands/connect.ts +115 -14
- package/cli/commands/create.ts +170 -8
- package/cli/commands/doctor.ts +320 -0
- package/cli/commands/edit.ts +209 -9
- package/cli/commands/engines.ts +34 -3
- package/cli/commands/info.ts +81 -26
- package/cli/commands/list.ts +64 -9
- package/cli/commands/logs.ts +9 -3
- package/cli/commands/menu/backup-handlers.ts +52 -21
- package/cli/commands/menu/container-handlers.ts +433 -127
- package/cli/commands/menu/engine-handlers.ts +128 -4
- package/cli/commands/menu/index.ts +5 -1
- package/cli/commands/menu/shell-handlers.ts +105 -21
- package/cli/commands/menu/sql-handlers.ts +16 -4
- package/cli/commands/menu/update-handlers.ts +278 -0
- package/cli/commands/restore.ts +83 -23
- package/cli/commands/run.ts +27 -11
- package/cli/commands/url.ts +17 -9
- package/cli/constants.ts +1 -0
- package/cli/helpers.ts +41 -1
- package/cli/index.ts +2 -0
- package/cli/ui/prompts.ts +148 -7
- package/config/engine-defaults.ts +14 -0
- package/config/os-dependencies.ts +66 -0
- package/config/paths.ts +8 -0
- package/core/container-manager.ts +191 -32
- package/core/dependency-manager.ts +18 -0
- package/core/error-handler.ts +31 -0
- package/core/port-manager.ts +2 -0
- package/core/process-manager.ts +25 -3
- package/engines/index.ts +4 -0
- package/engines/mysql/backup.ts +53 -36
- package/engines/mysql/index.ts +48 -5
- package/engines/postgresql/index.ts +6 -0
- package/engines/sqlite/index.ts +606 -0
- package/engines/sqlite/registry.ts +185 -0
- package/package.json +1 -1
- package/types/index.ts +26 -0
|
@@ -11,13 +11,24 @@ import {
|
|
|
11
11
|
getInstalledEngines,
|
|
12
12
|
type InstalledPostgresEngine,
|
|
13
13
|
type InstalledMysqlEngine,
|
|
14
|
+
type InstalledSqliteEngine,
|
|
14
15
|
} from '../../helpers'
|
|
15
16
|
import {
|
|
16
17
|
getMysqlVersion,
|
|
17
18
|
getMysqlInstallInfo,
|
|
18
19
|
} from '../../../engines/mysql/binary-detection'
|
|
20
|
+
|
|
19
21
|
import { type MenuChoice } from './shared'
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Pad string to width, accounting for emoji taking 2 display columns
|
|
25
|
+
*/
|
|
26
|
+
function padWithEmoji(str: string, width: number): string {
|
|
27
|
+
// Count emojis using Extended_Pictographic (excludes digits/symbols that \p{Emoji} matches)
|
|
28
|
+
const emojiCount = (str.match(/\p{Extended_Pictographic}/gu) || []).length
|
|
29
|
+
return str.padEnd(width + emojiCount)
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export async function handleEngines(): Promise<void> {
|
|
22
33
|
console.clear()
|
|
23
34
|
console.log(header('Installed Engines'))
|
|
@@ -46,6 +57,9 @@ export async function handleEngines(): Promise<void> {
|
|
|
46
57
|
const mysqlEngine = engines.find(
|
|
47
58
|
(e): e is InstalledMysqlEngine => e.engine === 'mysql',
|
|
48
59
|
)
|
|
60
|
+
const sqliteEngine = engines.find(
|
|
61
|
+
(e): e is InstalledSqliteEngine => e.engine === 'sqlite',
|
|
62
|
+
)
|
|
49
63
|
|
|
50
64
|
const totalPgSize = pgEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
|
|
51
65
|
|
|
@@ -62,10 +76,11 @@ export async function handleEngines(): Promise<void> {
|
|
|
62
76
|
for (const engine of pgEngines) {
|
|
63
77
|
const icon = getEngineIcon(engine.engine)
|
|
64
78
|
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
79
|
+
const engineDisplay = `${icon} ${engine.engine}`
|
|
65
80
|
|
|
66
81
|
console.log(
|
|
67
82
|
chalk.gray(' ') +
|
|
68
|
-
chalk.cyan(
|
|
83
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
69
84
|
chalk.yellow(engine.version.padEnd(12)) +
|
|
70
85
|
chalk.gray(platformInfo.padEnd(18)) +
|
|
71
86
|
chalk.white(formatBytes(engine.sizeBytes)),
|
|
@@ -75,16 +90,30 @@ export async function handleEngines(): Promise<void> {
|
|
|
75
90
|
if (mysqlEngine) {
|
|
76
91
|
const icon = ENGINE_ICONS.mysql
|
|
77
92
|
const displayName = mysqlEngine.isMariaDB ? 'mariadb' : 'mysql'
|
|
93
|
+
const engineDisplay = `${icon} ${displayName}`
|
|
78
94
|
|
|
79
95
|
console.log(
|
|
80
96
|
chalk.gray(' ') +
|
|
81
|
-
chalk.cyan(
|
|
97
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
82
98
|
chalk.yellow(mysqlEngine.version.padEnd(12)) +
|
|
83
99
|
chalk.gray('system'.padEnd(18)) +
|
|
84
100
|
chalk.gray('(system-installed)'),
|
|
85
101
|
)
|
|
86
102
|
}
|
|
87
103
|
|
|
104
|
+
if (sqliteEngine) {
|
|
105
|
+
const icon = ENGINE_ICONS.sqlite
|
|
106
|
+
const engineDisplay = `${icon} sqlite`
|
|
107
|
+
|
|
108
|
+
console.log(
|
|
109
|
+
chalk.gray(' ') +
|
|
110
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
111
|
+
chalk.yellow(sqliteEngine.version.padEnd(12)) +
|
|
112
|
+
chalk.gray('system'.padEnd(18)) +
|
|
113
|
+
chalk.gray('(system-installed)'),
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
88
117
|
console.log(chalk.gray(' ' + '─'.repeat(55)))
|
|
89
118
|
|
|
90
119
|
console.log()
|
|
@@ -98,6 +127,9 @@ export async function handleEngines(): Promise<void> {
|
|
|
98
127
|
if (mysqlEngine) {
|
|
99
128
|
console.log(chalk.gray(` MySQL: system-installed at ${mysqlEngine.path}`))
|
|
100
129
|
}
|
|
130
|
+
if (sqliteEngine) {
|
|
131
|
+
console.log(chalk.gray(` SQLite: system-installed at ${sqliteEngine.path}`))
|
|
132
|
+
}
|
|
101
133
|
console.log()
|
|
102
134
|
|
|
103
135
|
const choices: MenuChoice[] = []
|
|
@@ -117,6 +149,13 @@ export async function handleEngines(): Promise<void> {
|
|
|
117
149
|
})
|
|
118
150
|
}
|
|
119
151
|
|
|
152
|
+
if (sqliteEngine) {
|
|
153
|
+
choices.push({
|
|
154
|
+
name: `${chalk.blue('ℹ')} SQLite ${sqliteEngine.version} ${chalk.gray('(system-installed)')}`,
|
|
155
|
+
value: `sqlite-info:${sqliteEngine.path}`,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
120
159
|
choices.push(new inquirer.Separator())
|
|
121
160
|
choices.push({ name: `${chalk.blue('←')} Back to main menu`, value: 'back' })
|
|
122
161
|
|
|
@@ -135,16 +174,29 @@ export async function handleEngines(): Promise<void> {
|
|
|
135
174
|
}
|
|
136
175
|
|
|
137
176
|
if (action.startsWith('delete:')) {
|
|
138
|
-
|
|
177
|
+
// Parse from the end to preserve colons in path
|
|
178
|
+
// Format: delete:path:engineName:engineVersion
|
|
179
|
+
const withoutPrefix = action.slice('delete:'.length)
|
|
180
|
+
const lastColon = withoutPrefix.lastIndexOf(':')
|
|
181
|
+
const secondLastColon = withoutPrefix.lastIndexOf(':', lastColon - 1)
|
|
182
|
+
const enginePath = withoutPrefix.slice(0, secondLastColon)
|
|
183
|
+
const engineName = withoutPrefix.slice(secondLastColon + 1, lastColon)
|
|
184
|
+
const engineVersion = withoutPrefix.slice(lastColon + 1)
|
|
139
185
|
await handleDeleteEngine(enginePath, engineName, engineVersion)
|
|
140
186
|
await handleEngines()
|
|
141
187
|
}
|
|
142
188
|
|
|
143
189
|
if (action.startsWith('mysql-info:')) {
|
|
144
|
-
const mysqldPath = action.
|
|
190
|
+
const mysqldPath = action.slice('mysql-info:'.length)
|
|
145
191
|
await handleMysqlInfo(mysqldPath)
|
|
146
192
|
await handleEngines()
|
|
147
193
|
}
|
|
194
|
+
|
|
195
|
+
if (action.startsWith('sqlite-info:')) {
|
|
196
|
+
const sqlitePath = action.slice('sqlite-info:'.length)
|
|
197
|
+
await handleSqliteInfo(sqlitePath)
|
|
198
|
+
await handleEngines()
|
|
199
|
+
}
|
|
148
200
|
}
|
|
149
201
|
|
|
150
202
|
async function handleDeleteEngine(
|
|
@@ -360,3 +412,75 @@ async function handleMysqlInfo(mysqldPath: string): Promise<void> {
|
|
|
360
412
|
},
|
|
361
413
|
])
|
|
362
414
|
}
|
|
415
|
+
|
|
416
|
+
async function handleSqliteInfo(sqlitePath: string): Promise<void> {
|
|
417
|
+
console.clear()
|
|
418
|
+
|
|
419
|
+
console.log(header('SQLite Information'))
|
|
420
|
+
console.log()
|
|
421
|
+
|
|
422
|
+
// Get version
|
|
423
|
+
let version = 'unknown'
|
|
424
|
+
try {
|
|
425
|
+
const { exec } = await import('child_process')
|
|
426
|
+
const { promisify } = await import('util')
|
|
427
|
+
const execAsync = promisify(exec)
|
|
428
|
+
const { stdout } = await execAsync(`"${sqlitePath}" --version`)
|
|
429
|
+
const match = stdout.match(/^([\d.]+)/)
|
|
430
|
+
if (match) {
|
|
431
|
+
version = match[1]
|
|
432
|
+
}
|
|
433
|
+
} catch {
|
|
434
|
+
// Ignore
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const containers = await containerManager.list()
|
|
438
|
+
const sqliteContainers = containers.filter((c) => c.engine === 'sqlite')
|
|
439
|
+
|
|
440
|
+
if (sqliteContainers.length > 0) {
|
|
441
|
+
console.log(info(`${sqliteContainers.length} SQLite database(s) registered:`))
|
|
442
|
+
console.log()
|
|
443
|
+
for (const c of sqliteContainers) {
|
|
444
|
+
const status =
|
|
445
|
+
c.status === 'running'
|
|
446
|
+
? chalk.blue('🔵 available')
|
|
447
|
+
: chalk.gray('⚪ missing')
|
|
448
|
+
console.log(chalk.gray(` • ${c.name} ${status}`))
|
|
449
|
+
}
|
|
450
|
+
console.log()
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log(chalk.white(' Installation Details:'))
|
|
454
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
455
|
+
console.log(
|
|
456
|
+
chalk.gray(' ') +
|
|
457
|
+
chalk.white('Version:'.padEnd(18)) +
|
|
458
|
+
chalk.yellow(version),
|
|
459
|
+
)
|
|
460
|
+
console.log(
|
|
461
|
+
chalk.gray(' ') +
|
|
462
|
+
chalk.white('Binary Path:'.padEnd(18)) +
|
|
463
|
+
chalk.gray(sqlitePath),
|
|
464
|
+
)
|
|
465
|
+
console.log(
|
|
466
|
+
chalk.gray(' ') +
|
|
467
|
+
chalk.white('Type:'.padEnd(18)) +
|
|
468
|
+
chalk.cyan('Embedded (file-based)'),
|
|
469
|
+
)
|
|
470
|
+
console.log()
|
|
471
|
+
|
|
472
|
+
console.log(chalk.white(' Notes:'))
|
|
473
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
474
|
+
console.log(chalk.gray(' • SQLite is typically pre-installed on macOS and most Linux distributions'))
|
|
475
|
+
console.log(chalk.gray(' • No server process - databases are just files'))
|
|
476
|
+
console.log(chalk.gray(' • Use "spindb delete <name>" to unregister a database'))
|
|
477
|
+
console.log()
|
|
478
|
+
|
|
479
|
+
await inquirer.prompt([
|
|
480
|
+
{
|
|
481
|
+
type: 'input',
|
|
482
|
+
name: 'continue',
|
|
483
|
+
message: chalk.gray('Press Enter to go back...'),
|
|
484
|
+
},
|
|
485
|
+
])
|
|
486
|
+
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from './container-handlers'
|
|
14
14
|
import { handleBackup, handleRestore, handleClone } from './backup-handlers'
|
|
15
15
|
import { handleEngines } from './engine-handlers'
|
|
16
|
-
import { handleCheckUpdate } from './update-handlers'
|
|
16
|
+
import { handleCheckUpdate, handleDoctor } from './update-handlers'
|
|
17
17
|
import { type MenuChoice } from './shared'
|
|
18
18
|
|
|
19
19
|
async function showMainMenu(): Promise<void> {
|
|
@@ -97,6 +97,7 @@ async function showMainMenu(): Promise<void> {
|
|
|
97
97
|
disabled: hasEngines ? false : 'No engines installed',
|
|
98
98
|
},
|
|
99
99
|
new inquirer.Separator(),
|
|
100
|
+
{ name: `${chalk.bgRed.white('+')} System health check`, value: 'doctor' },
|
|
100
101
|
{ name: `${chalk.cyan('↑')} Check for updates`, value: 'check-update' },
|
|
101
102
|
{ name: `${chalk.gray('⏻')} Exit`, value: 'exit' },
|
|
102
103
|
]
|
|
@@ -136,6 +137,9 @@ async function showMainMenu(): Promise<void> {
|
|
|
136
137
|
case 'engines':
|
|
137
138
|
await handleEngines()
|
|
138
139
|
break
|
|
140
|
+
case 'doctor':
|
|
141
|
+
await handleDoctor()
|
|
142
|
+
break
|
|
139
143
|
case 'check-update':
|
|
140
144
|
await handleCheckUpdate()
|
|
141
145
|
break
|
|
@@ -6,13 +6,16 @@ import {
|
|
|
6
6
|
isUsqlInstalled,
|
|
7
7
|
isPgcliInstalled,
|
|
8
8
|
isMycliInstalled,
|
|
9
|
+
isLitecliInstalled,
|
|
9
10
|
detectPackageManager,
|
|
10
11
|
installUsql,
|
|
11
12
|
installPgcli,
|
|
12
13
|
installMycli,
|
|
14
|
+
installLitecli,
|
|
13
15
|
getUsqlManualInstructions,
|
|
14
16
|
getPgcliManualInstructions,
|
|
15
17
|
getMycliManualInstructions,
|
|
18
|
+
getLitecliManualInstructions,
|
|
16
19
|
} from '../../../core/dependency-manager'
|
|
17
20
|
import { platformService } from '../../../core/platform-service'
|
|
18
21
|
import { getEngine } from '../../../engines'
|
|
@@ -66,10 +69,11 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
66
69
|
const shellCheckSpinner = createSpinner('Checking available shells...')
|
|
67
70
|
shellCheckSpinner.start()
|
|
68
71
|
|
|
69
|
-
const [usqlInstalled, pgcliInstalled, mycliInstalled] = await Promise.all([
|
|
72
|
+
const [usqlInstalled, pgcliInstalled, mycliInstalled, litecliInstalled] = await Promise.all([
|
|
70
73
|
isUsqlInstalled(),
|
|
71
74
|
isPgcliInstalled(),
|
|
72
75
|
isMycliInstalled(),
|
|
76
|
+
isLitecliInstalled(),
|
|
73
77
|
])
|
|
74
78
|
|
|
75
79
|
shellCheckSpinner.stop()
|
|
@@ -84,12 +88,36 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
84
88
|
| 'install-pgcli'
|
|
85
89
|
| 'mycli'
|
|
86
90
|
| 'install-mycli'
|
|
91
|
+
| 'litecli'
|
|
92
|
+
| 'install-litecli'
|
|
87
93
|
| 'back'
|
|
88
94
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
// Engine-specific shell names
|
|
96
|
+
let defaultShellName: string
|
|
97
|
+
let engineSpecificCli: string
|
|
98
|
+
let engineSpecificInstalled: boolean
|
|
99
|
+
let engineSpecificValue: ShellChoice
|
|
100
|
+
let engineSpecificInstallValue: ShellChoice
|
|
101
|
+
|
|
102
|
+
if (config.engine === 'sqlite') {
|
|
103
|
+
defaultShellName = 'sqlite3'
|
|
104
|
+
engineSpecificCli = 'litecli'
|
|
105
|
+
engineSpecificInstalled = litecliInstalled
|
|
106
|
+
engineSpecificValue = 'litecli'
|
|
107
|
+
engineSpecificInstallValue = 'install-litecli'
|
|
108
|
+
} else if (config.engine === 'mysql') {
|
|
109
|
+
defaultShellName = 'mysql'
|
|
110
|
+
engineSpecificCli = 'mycli'
|
|
111
|
+
engineSpecificInstalled = mycliInstalled
|
|
112
|
+
engineSpecificValue = 'mycli'
|
|
113
|
+
engineSpecificInstallValue = 'install-mycli'
|
|
114
|
+
} else {
|
|
115
|
+
defaultShellName = 'psql'
|
|
116
|
+
engineSpecificCli = 'pgcli'
|
|
117
|
+
engineSpecificInstalled = pgcliInstalled
|
|
118
|
+
engineSpecificValue = 'pgcli'
|
|
119
|
+
engineSpecificInstallValue = 'install-pgcli'
|
|
120
|
+
}
|
|
93
121
|
|
|
94
122
|
const choices: Array<{ name: string; value: ShellChoice } | inquirer.Separator> = [
|
|
95
123
|
{
|
|
@@ -101,15 +129,16 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
101
129
|
if (engineSpecificInstalled) {
|
|
102
130
|
choices.push({
|
|
103
131
|
name: `⚡ Use ${engineSpecificCli} (enhanced features, recommended)`,
|
|
104
|
-
value:
|
|
132
|
+
value: engineSpecificValue,
|
|
105
133
|
})
|
|
106
134
|
} else {
|
|
107
135
|
choices.push({
|
|
108
136
|
name: `↓ Install ${engineSpecificCli} (enhanced features, recommended)`,
|
|
109
|
-
value:
|
|
137
|
+
value: engineSpecificInstallValue,
|
|
110
138
|
})
|
|
111
139
|
}
|
|
112
140
|
|
|
141
|
+
// usql supports SQLite too
|
|
113
142
|
if (usqlInstalled) {
|
|
114
143
|
choices.push({
|
|
115
144
|
name: '⚡ Use usql (universal SQL client)',
|
|
@@ -241,6 +270,39 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
241
270
|
return
|
|
242
271
|
}
|
|
243
272
|
|
|
273
|
+
if (shellChoice === 'install-litecli') {
|
|
274
|
+
console.log()
|
|
275
|
+
console.log(info('Installing litecli for enhanced SQLite shell...'))
|
|
276
|
+
const pm = await detectPackageManager()
|
|
277
|
+
if (pm) {
|
|
278
|
+
const result = await installLitecli(pm)
|
|
279
|
+
if (result.success) {
|
|
280
|
+
console.log(success('litecli installed successfully!'))
|
|
281
|
+
console.log()
|
|
282
|
+
await launchShell(containerName, config, connectionString, 'litecli')
|
|
283
|
+
} else {
|
|
284
|
+
console.error(error(`Failed to install litecli: ${result.error}`))
|
|
285
|
+
console.log()
|
|
286
|
+
console.log(chalk.gray('Manual installation:'))
|
|
287
|
+
for (const instruction of getLitecliManualInstructions()) {
|
|
288
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
289
|
+
}
|
|
290
|
+
console.log()
|
|
291
|
+
await pressEnterToContinue()
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
console.error(error('No supported package manager found'))
|
|
295
|
+
console.log()
|
|
296
|
+
console.log(chalk.gray('Manual installation:'))
|
|
297
|
+
for (const instruction of getLitecliManualInstructions()) {
|
|
298
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
299
|
+
}
|
|
300
|
+
console.log()
|
|
301
|
+
await pressEnterToContinue()
|
|
302
|
+
}
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
244
306
|
await launchShell(containerName, config, connectionString, shellChoice)
|
|
245
307
|
}
|
|
246
308
|
|
|
@@ -248,7 +310,7 @@ async function launchShell(
|
|
|
248
310
|
containerName: string,
|
|
249
311
|
config: NonNullable<Awaited<ReturnType<typeof containerManager.getConfig>>>,
|
|
250
312
|
connectionString: string,
|
|
251
|
-
shellType: 'default' | 'usql' | 'pgcli' | 'mycli',
|
|
313
|
+
shellType: 'default' | 'usql' | 'pgcli' | 'mycli' | 'litecli',
|
|
252
314
|
): Promise<void> {
|
|
253
315
|
console.log(info(`Connecting to ${containerName}...`))
|
|
254
316
|
console.log()
|
|
@@ -275,11 +337,21 @@ async function launchShell(
|
|
|
275
337
|
config.database,
|
|
276
338
|
]
|
|
277
339
|
installHint = 'brew install mycli'
|
|
340
|
+
} else if (shellType === 'litecli') {
|
|
341
|
+
// litecli takes the database file path directly
|
|
342
|
+
shellCmd = 'litecli'
|
|
343
|
+
shellArgs = [config.database]
|
|
344
|
+
installHint = 'brew install litecli'
|
|
278
345
|
} else if (shellType === 'usql') {
|
|
279
|
-
// usql accepts connection strings directly for
|
|
346
|
+
// usql accepts connection strings directly for PostgreSQL, MySQL, and SQLite
|
|
280
347
|
shellCmd = 'usql'
|
|
281
348
|
shellArgs = [connectionString]
|
|
282
349
|
installHint = 'brew tap xo/xo && brew install xo/xo/usql'
|
|
350
|
+
} else if (config.engine === 'sqlite') {
|
|
351
|
+
// Default SQLite shell
|
|
352
|
+
shellCmd = 'sqlite3'
|
|
353
|
+
shellArgs = [config.database]
|
|
354
|
+
installHint = 'brew install sqlite3'
|
|
283
355
|
} else if (config.engine === 'mysql') {
|
|
284
356
|
shellCmd = 'mysql'
|
|
285
357
|
shellArgs = [
|
|
@@ -302,19 +374,31 @@ async function launchShell(
|
|
|
302
374
|
stdio: 'inherit',
|
|
303
375
|
})
|
|
304
376
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
console.log(chalk.cyan(` ${installHint}`))
|
|
377
|
+
await new Promise<void>((resolve) => {
|
|
378
|
+
let settled = false
|
|
379
|
+
|
|
380
|
+
const settle = () => {
|
|
381
|
+
if (!settled) {
|
|
382
|
+
settled = true
|
|
383
|
+
resolve()
|
|
384
|
+
}
|
|
314
385
|
}
|
|
315
|
-
})
|
|
316
386
|
|
|
317
|
-
|
|
318
|
-
|
|
387
|
+
shellProcess.on('error', (err: NodeJS.ErrnoException) => {
|
|
388
|
+
if (err.code === 'ENOENT') {
|
|
389
|
+
console.log(warning(`${shellCmd} not found on your system.`))
|
|
390
|
+
console.log()
|
|
391
|
+
console.log(chalk.gray(' Connect manually with:'))
|
|
392
|
+
console.log(chalk.cyan(` ${connectionString}`))
|
|
393
|
+
console.log()
|
|
394
|
+
console.log(chalk.gray(` Install ${shellCmd}:`))
|
|
395
|
+
console.log(chalk.cyan(` ${installHint}`))
|
|
396
|
+
} else {
|
|
397
|
+
console.log(error(`Failed to start ${shellCmd}: ${err.message}`))
|
|
398
|
+
}
|
|
399
|
+
settle()
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
shellProcess.on('close', settle)
|
|
319
403
|
})
|
|
320
404
|
}
|
|
@@ -169,11 +169,23 @@ export async function handleViewLogs(containerName: string): Promise<void> {
|
|
|
169
169
|
stdio: 'inherit',
|
|
170
170
|
})
|
|
171
171
|
await new Promise<void>((resolve) => {
|
|
172
|
-
|
|
172
|
+
let settled = false
|
|
173
|
+
|
|
174
|
+
const cleanup = () => {
|
|
175
|
+
if (!settled) {
|
|
176
|
+
settled = true
|
|
177
|
+
process.off('SIGINT', handleSigint)
|
|
178
|
+
resolve()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const handleSigint = () => {
|
|
173
183
|
child.kill('SIGTERM')
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
184
|
+
cleanup()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
process.on('SIGINT', handleSigint)
|
|
188
|
+
child.on('close', cleanup)
|
|
177
189
|
})
|
|
178
190
|
return
|
|
179
191
|
}
|