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,320 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import inquirer from 'inquirer'
|
|
3
|
+
import { spawn } from 'child_process'
|
|
4
|
+
import { containerManager } from '../../../core/container-manager'
|
|
5
|
+
import {
|
|
6
|
+
isUsqlInstalled,
|
|
7
|
+
isPgcliInstalled,
|
|
8
|
+
isMycliInstalled,
|
|
9
|
+
detectPackageManager,
|
|
10
|
+
installUsql,
|
|
11
|
+
installPgcli,
|
|
12
|
+
installMycli,
|
|
13
|
+
getUsqlManualInstructions,
|
|
14
|
+
getPgcliManualInstructions,
|
|
15
|
+
getMycliManualInstructions,
|
|
16
|
+
} from '../../../core/dependency-manager'
|
|
17
|
+
import { platformService } from '../../../core/platform-service'
|
|
18
|
+
import { getEngine } from '../../../engines'
|
|
19
|
+
import { createSpinner } from '../../ui/spinner'
|
|
20
|
+
import { error, warning, info, success } from '../../ui/theme'
|
|
21
|
+
import { pressEnterToContinue } from './shared'
|
|
22
|
+
|
|
23
|
+
export async function handleCopyConnectionString(
|
|
24
|
+
containerName: string,
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
const config = await containerManager.getConfig(containerName)
|
|
27
|
+
if (!config) {
|
|
28
|
+
console.error(error(`Container "${containerName}" not found`))
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const engine = getEngine(config.engine)
|
|
33
|
+
const connectionString = engine.getConnectionString(config)
|
|
34
|
+
|
|
35
|
+
const copied = await platformService.copyToClipboard(connectionString)
|
|
36
|
+
|
|
37
|
+
console.log()
|
|
38
|
+
if (copied) {
|
|
39
|
+
console.log(success('Connection string copied to clipboard'))
|
|
40
|
+
console.log(chalk.gray(` ${connectionString}`))
|
|
41
|
+
} else {
|
|
42
|
+
console.log(warning('Could not copy to clipboard. Connection string:'))
|
|
43
|
+
console.log(chalk.cyan(` ${connectionString}`))
|
|
44
|
+
}
|
|
45
|
+
console.log()
|
|
46
|
+
|
|
47
|
+
await inquirer.prompt([
|
|
48
|
+
{
|
|
49
|
+
type: 'input',
|
|
50
|
+
name: 'continue',
|
|
51
|
+
message: chalk.gray('Press Enter to continue...'),
|
|
52
|
+
},
|
|
53
|
+
])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function handleOpenShell(containerName: string): Promise<void> {
|
|
57
|
+
const config = await containerManager.getConfig(containerName)
|
|
58
|
+
if (!config) {
|
|
59
|
+
console.error(error(`Container "${containerName}" not found`))
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const engine = getEngine(config.engine)
|
|
64
|
+
const connectionString = engine.getConnectionString(config)
|
|
65
|
+
|
|
66
|
+
const shellCheckSpinner = createSpinner('Checking available shells...')
|
|
67
|
+
shellCheckSpinner.start()
|
|
68
|
+
|
|
69
|
+
const [usqlInstalled, pgcliInstalled, mycliInstalled] = await Promise.all([
|
|
70
|
+
isUsqlInstalled(),
|
|
71
|
+
isPgcliInstalled(),
|
|
72
|
+
isMycliInstalled(),
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
shellCheckSpinner.stop()
|
|
76
|
+
// Clear the spinner line
|
|
77
|
+
process.stdout.write('\x1b[1A\x1b[2K')
|
|
78
|
+
|
|
79
|
+
type ShellChoice =
|
|
80
|
+
| 'default'
|
|
81
|
+
| 'usql'
|
|
82
|
+
| 'install-usql'
|
|
83
|
+
| 'pgcli'
|
|
84
|
+
| 'install-pgcli'
|
|
85
|
+
| 'mycli'
|
|
86
|
+
| 'install-mycli'
|
|
87
|
+
| 'back'
|
|
88
|
+
|
|
89
|
+
const defaultShellName = config.engine === 'mysql' ? 'mysql' : 'psql'
|
|
90
|
+
const engineSpecificCli = config.engine === 'mysql' ? 'mycli' : 'pgcli'
|
|
91
|
+
const engineSpecificInstalled =
|
|
92
|
+
config.engine === 'mysql' ? mycliInstalled : pgcliInstalled
|
|
93
|
+
|
|
94
|
+
const choices: Array<{ name: string; value: ShellChoice } | inquirer.Separator> = [
|
|
95
|
+
{
|
|
96
|
+
name: `>_ Use default shell (${defaultShellName})`,
|
|
97
|
+
value: 'default',
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
if (engineSpecificInstalled) {
|
|
102
|
+
choices.push({
|
|
103
|
+
name: `⚡ Use ${engineSpecificCli} (enhanced features, recommended)`,
|
|
104
|
+
value: config.engine === 'mysql' ? 'mycli' : 'pgcli',
|
|
105
|
+
})
|
|
106
|
+
} else {
|
|
107
|
+
choices.push({
|
|
108
|
+
name: `↓ Install ${engineSpecificCli} (enhanced features, recommended)`,
|
|
109
|
+
value: config.engine === 'mysql' ? 'install-mycli' : 'install-pgcli',
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (usqlInstalled) {
|
|
114
|
+
choices.push({
|
|
115
|
+
name: '⚡ Use usql (universal SQL client)',
|
|
116
|
+
value: 'usql',
|
|
117
|
+
})
|
|
118
|
+
} else {
|
|
119
|
+
choices.push({
|
|
120
|
+
name: '↓ Install usql (universal SQL client)',
|
|
121
|
+
value: 'install-usql',
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
choices.push(new inquirer.Separator())
|
|
126
|
+
choices.push({
|
|
127
|
+
name: `${chalk.blue('←')} Back`,
|
|
128
|
+
value: 'back',
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const { shellChoice } = await inquirer.prompt<{ shellChoice: ShellChoice }>([
|
|
132
|
+
{
|
|
133
|
+
type: 'list',
|
|
134
|
+
name: 'shellChoice',
|
|
135
|
+
message: 'Select shell option:',
|
|
136
|
+
choices,
|
|
137
|
+
pageSize: 10,
|
|
138
|
+
},
|
|
139
|
+
])
|
|
140
|
+
|
|
141
|
+
if (shellChoice === 'back') {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (shellChoice === 'install-pgcli') {
|
|
146
|
+
console.log()
|
|
147
|
+
console.log(info('Installing pgcli for enhanced PostgreSQL shell...'))
|
|
148
|
+
const pm = await detectPackageManager()
|
|
149
|
+
if (pm) {
|
|
150
|
+
const result = await installPgcli(pm)
|
|
151
|
+
if (result.success) {
|
|
152
|
+
console.log(success('pgcli installed successfully!'))
|
|
153
|
+
console.log()
|
|
154
|
+
await launchShell(containerName, config, connectionString, 'pgcli')
|
|
155
|
+
} else {
|
|
156
|
+
console.error(error(`Failed to install pgcli: ${result.error}`))
|
|
157
|
+
console.log()
|
|
158
|
+
console.log(chalk.gray('Manual installation:'))
|
|
159
|
+
for (const instruction of getPgcliManualInstructions()) {
|
|
160
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
161
|
+
}
|
|
162
|
+
console.log()
|
|
163
|
+
await pressEnterToContinue()
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
console.error(error('No supported package manager found'))
|
|
167
|
+
console.log()
|
|
168
|
+
console.log(chalk.gray('Manual installation:'))
|
|
169
|
+
for (const instruction of getPgcliManualInstructions()) {
|
|
170
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
171
|
+
}
|
|
172
|
+
console.log()
|
|
173
|
+
await pressEnterToContinue()
|
|
174
|
+
}
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (shellChoice === 'install-mycli') {
|
|
179
|
+
console.log()
|
|
180
|
+
console.log(info('Installing mycli for enhanced MySQL shell...'))
|
|
181
|
+
const pm = await detectPackageManager()
|
|
182
|
+
if (pm) {
|
|
183
|
+
const result = await installMycli(pm)
|
|
184
|
+
if (result.success) {
|
|
185
|
+
console.log(success('mycli installed successfully!'))
|
|
186
|
+
console.log()
|
|
187
|
+
await launchShell(containerName, config, connectionString, 'mycli')
|
|
188
|
+
} else {
|
|
189
|
+
console.error(error(`Failed to install mycli: ${result.error}`))
|
|
190
|
+
console.log()
|
|
191
|
+
console.log(chalk.gray('Manual installation:'))
|
|
192
|
+
for (const instruction of getMycliManualInstructions()) {
|
|
193
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
194
|
+
}
|
|
195
|
+
console.log()
|
|
196
|
+
await pressEnterToContinue()
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
console.error(error('No supported package manager found'))
|
|
200
|
+
console.log()
|
|
201
|
+
console.log(chalk.gray('Manual installation:'))
|
|
202
|
+
for (const instruction of getMycliManualInstructions()) {
|
|
203
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
204
|
+
}
|
|
205
|
+
console.log()
|
|
206
|
+
await pressEnterToContinue()
|
|
207
|
+
}
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (shellChoice === 'install-usql') {
|
|
212
|
+
console.log()
|
|
213
|
+
console.log(info('Installing usql for enhanced shell experience...'))
|
|
214
|
+
const pm = await detectPackageManager()
|
|
215
|
+
if (pm) {
|
|
216
|
+
const result = await installUsql(pm)
|
|
217
|
+
if (result.success) {
|
|
218
|
+
console.log(success('usql installed successfully!'))
|
|
219
|
+
console.log()
|
|
220
|
+
await launchShell(containerName, config, connectionString, 'usql')
|
|
221
|
+
} else {
|
|
222
|
+
console.error(error(`Failed to install usql: ${result.error}`))
|
|
223
|
+
console.log()
|
|
224
|
+
console.log(chalk.gray('Manual installation:'))
|
|
225
|
+
for (const instruction of getUsqlManualInstructions()) {
|
|
226
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
227
|
+
}
|
|
228
|
+
console.log()
|
|
229
|
+
await pressEnterToContinue()
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
console.error(error('No supported package manager found'))
|
|
233
|
+
console.log()
|
|
234
|
+
console.log(chalk.gray('Manual installation:'))
|
|
235
|
+
for (const instruction of getUsqlManualInstructions()) {
|
|
236
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
237
|
+
}
|
|
238
|
+
console.log()
|
|
239
|
+
await pressEnterToContinue()
|
|
240
|
+
}
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
await launchShell(containerName, config, connectionString, shellChoice)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function launchShell(
|
|
248
|
+
containerName: string,
|
|
249
|
+
config: NonNullable<Awaited<ReturnType<typeof containerManager.getConfig>>>,
|
|
250
|
+
connectionString: string,
|
|
251
|
+
shellType: 'default' | 'usql' | 'pgcli' | 'mycli',
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
console.log(info(`Connecting to ${containerName}...`))
|
|
254
|
+
console.log()
|
|
255
|
+
|
|
256
|
+
let shellCmd: string
|
|
257
|
+
let shellArgs: string[]
|
|
258
|
+
let installHint: string
|
|
259
|
+
|
|
260
|
+
if (shellType === 'pgcli') {
|
|
261
|
+
// pgcli accepts connection strings
|
|
262
|
+
shellCmd = 'pgcli'
|
|
263
|
+
shellArgs = [connectionString]
|
|
264
|
+
installHint = 'brew install pgcli'
|
|
265
|
+
} else if (shellType === 'mycli') {
|
|
266
|
+
// mycli: mycli -h host -P port -u user database
|
|
267
|
+
shellCmd = 'mycli'
|
|
268
|
+
shellArgs = [
|
|
269
|
+
'-h',
|
|
270
|
+
'127.0.0.1',
|
|
271
|
+
'-P',
|
|
272
|
+
String(config.port),
|
|
273
|
+
'-u',
|
|
274
|
+
'root',
|
|
275
|
+
config.database,
|
|
276
|
+
]
|
|
277
|
+
installHint = 'brew install mycli'
|
|
278
|
+
} else if (shellType === 'usql') {
|
|
279
|
+
// usql accepts connection strings directly for both PostgreSQL and MySQL
|
|
280
|
+
shellCmd = 'usql'
|
|
281
|
+
shellArgs = [connectionString]
|
|
282
|
+
installHint = 'brew tap xo/xo && brew install xo/xo/usql'
|
|
283
|
+
} else if (config.engine === 'mysql') {
|
|
284
|
+
shellCmd = 'mysql'
|
|
285
|
+
shellArgs = [
|
|
286
|
+
'-u',
|
|
287
|
+
'root',
|
|
288
|
+
'-h',
|
|
289
|
+
'127.0.0.1',
|
|
290
|
+
'-P',
|
|
291
|
+
String(config.port),
|
|
292
|
+
config.database,
|
|
293
|
+
]
|
|
294
|
+
installHint = 'brew install mysql-client'
|
|
295
|
+
} else {
|
|
296
|
+
shellCmd = 'psql'
|
|
297
|
+
shellArgs = [connectionString]
|
|
298
|
+
installHint = 'brew install libpq && brew link --force libpq'
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const shellProcess = spawn(shellCmd, shellArgs, {
|
|
302
|
+
stdio: 'inherit',
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
shellProcess.on('error', (err: NodeJS.ErrnoException) => {
|
|
306
|
+
if (err.code === 'ENOENT') {
|
|
307
|
+
console.log(warning(`${shellCmd} not found on your system.`))
|
|
308
|
+
console.log()
|
|
309
|
+
console.log(chalk.gray(' Connect manually with:'))
|
|
310
|
+
console.log(chalk.cyan(` ${connectionString}`))
|
|
311
|
+
console.log()
|
|
312
|
+
console.log(chalk.gray(` Install ${shellCmd}:`))
|
|
313
|
+
console.log(chalk.cyan(` ${installHint}`))
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
await new Promise<void>((resolve) => {
|
|
318
|
+
shellProcess.on('close', () => resolve())
|
|
319
|
+
})
|
|
320
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import inquirer from 'inquirer'
|
|
3
|
+
import { existsSync } from 'fs'
|
|
4
|
+
import { readFile } from 'fs/promises'
|
|
5
|
+
import { spawn } from 'child_process'
|
|
6
|
+
import { containerManager } from '../../../core/container-manager'
|
|
7
|
+
import { getMissingDependencies } from '../../../core/dependency-manager'
|
|
8
|
+
import { getEngine } from '../../../engines'
|
|
9
|
+
import { paths } from '../../../config/paths'
|
|
10
|
+
import { promptInstallDependencies, promptDatabaseSelect } from '../../ui/prompts'
|
|
11
|
+
import { error, warning, info, success } from '../../ui/theme'
|
|
12
|
+
import { pressEnterToContinue } from './shared'
|
|
13
|
+
|
|
14
|
+
export async function handleRunSql(containerName: string): Promise<void> {
|
|
15
|
+
const config = await containerManager.getConfig(containerName)
|
|
16
|
+
if (!config) {
|
|
17
|
+
console.error(error(`Container "${containerName}" not found`))
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const engine = getEngine(config.engine)
|
|
22
|
+
|
|
23
|
+
let missingDeps = await getMissingDependencies(config.engine)
|
|
24
|
+
if (missingDeps.length > 0) {
|
|
25
|
+
console.log(
|
|
26
|
+
warning(`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const installed = await promptInstallDependencies(
|
|
30
|
+
missingDeps[0].binary,
|
|
31
|
+
config.engine,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if (!installed) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
missingDeps = await getMissingDependencies(config.engine)
|
|
39
|
+
if (missingDeps.length > 0) {
|
|
40
|
+
console.log(
|
|
41
|
+
error(
|
|
42
|
+
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green(' ✓ All required tools are now available'))
|
|
49
|
+
console.log()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Strip quotes that terminals add when drag-and-dropping files
|
|
53
|
+
const stripQuotes = (path: string) =>
|
|
54
|
+
path.replace(/^['"]|['"]$/g, '').trim()
|
|
55
|
+
|
|
56
|
+
// Prompt for file path (empty input = go back)
|
|
57
|
+
console.log(chalk.gray(' Drag & drop, enter path (abs or rel), or press Enter to go back'))
|
|
58
|
+
const { filePath: rawFilePath } = await inquirer.prompt<{
|
|
59
|
+
filePath: string
|
|
60
|
+
}>([
|
|
61
|
+
{
|
|
62
|
+
type: 'input',
|
|
63
|
+
name: 'filePath',
|
|
64
|
+
message: 'SQL file path:',
|
|
65
|
+
validate: (input: string) => {
|
|
66
|
+
if (!input) return true // Empty = go back
|
|
67
|
+
const cleanPath = stripQuotes(input)
|
|
68
|
+
if (!existsSync(cleanPath)) return 'File not found'
|
|
69
|
+
return true
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
if (!rawFilePath.trim()) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const filePath = stripQuotes(rawFilePath)
|
|
79
|
+
|
|
80
|
+
const databases = config.databases || [config.database]
|
|
81
|
+
let databaseName: string
|
|
82
|
+
|
|
83
|
+
if (databases.length > 1) {
|
|
84
|
+
databaseName = await promptDatabaseSelect(
|
|
85
|
+
databases,
|
|
86
|
+
'Select database to run SQL against:',
|
|
87
|
+
)
|
|
88
|
+
} else {
|
|
89
|
+
databaseName = databases[0]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log()
|
|
93
|
+
console.log(info(`Running SQL file against "${databaseName}"...`))
|
|
94
|
+
console.log()
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await engine.runScript(config, {
|
|
98
|
+
file: filePath,
|
|
99
|
+
database: databaseName,
|
|
100
|
+
})
|
|
101
|
+
console.log()
|
|
102
|
+
console.log(success('SQL file executed successfully'))
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const e = err as Error
|
|
105
|
+
console.log()
|
|
106
|
+
console.log(error(`SQL execution failed: ${e.message}`))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log()
|
|
110
|
+
await pressEnterToContinue()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* View container logs with interactive options
|
|
115
|
+
*/
|
|
116
|
+
export async function handleViewLogs(containerName: string): Promise<void> {
|
|
117
|
+
const config = await containerManager.getConfig(containerName)
|
|
118
|
+
if (!config) {
|
|
119
|
+
console.error(error(`Container "${containerName}" not found`))
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const logPath = paths.getContainerLogPath(config.name, {
|
|
124
|
+
engine: config.engine,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
if (!existsSync(logPath)) {
|
|
128
|
+
console.log(
|
|
129
|
+
info(
|
|
130
|
+
`No log file found for "${containerName}". The container may not have been started yet.`,
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
await pressEnterToContinue()
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const { action } = await inquirer.prompt<{ action: string }>([
|
|
138
|
+
{
|
|
139
|
+
type: 'list',
|
|
140
|
+
name: 'action',
|
|
141
|
+
message: 'How would you like to view logs?',
|
|
142
|
+
choices: [
|
|
143
|
+
{ name: 'View last 50 lines', value: 'tail-50' },
|
|
144
|
+
{ name: 'View last 100 lines', value: 'tail-100' },
|
|
145
|
+
{ name: 'Follow logs (live)', value: 'follow' },
|
|
146
|
+
{ name: 'Open in editor', value: 'editor' },
|
|
147
|
+
{ name: `${chalk.blue('←')} Back`, value: 'back' },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
])
|
|
151
|
+
|
|
152
|
+
if (action === 'back') {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (action === 'editor') {
|
|
157
|
+
const editorCmd = process.env.EDITOR || 'vi'
|
|
158
|
+
const child = spawn(editorCmd, [logPath], { stdio: 'inherit' })
|
|
159
|
+
await new Promise<void>((resolve) => {
|
|
160
|
+
child.on('close', () => resolve())
|
|
161
|
+
})
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (action === 'follow') {
|
|
166
|
+
console.log(chalk.gray(' Press Ctrl+C to stop following logs'))
|
|
167
|
+
console.log()
|
|
168
|
+
const child = spawn('tail', ['-n', '50', '-f', logPath], {
|
|
169
|
+
stdio: 'inherit',
|
|
170
|
+
})
|
|
171
|
+
await new Promise<void>((resolve) => {
|
|
172
|
+
process.on('SIGINT', () => {
|
|
173
|
+
child.kill('SIGTERM')
|
|
174
|
+
resolve()
|
|
175
|
+
})
|
|
176
|
+
child.on('close', () => resolve())
|
|
177
|
+
})
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// tail-50 or tail-100
|
|
182
|
+
const lineCount = action === 'tail-100' ? 100 : 50
|
|
183
|
+
const content = await readFile(logPath, 'utf-8')
|
|
184
|
+
if (content.trim() === '') {
|
|
185
|
+
console.log(info('Log file is empty'))
|
|
186
|
+
} else {
|
|
187
|
+
const lines = content.split('\n')
|
|
188
|
+
const nonEmptyLines =
|
|
189
|
+
lines[lines.length - 1] === '' ? lines.slice(0, -1) : lines
|
|
190
|
+
console.log(nonEmptyLines.slice(-lineCount).join('\n'))
|
|
191
|
+
}
|
|
192
|
+
console.log()
|
|
193
|
+
await pressEnterToContinue()
|
|
194
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import inquirer from 'inquirer'
|
|
3
|
+
import { updateManager } from '../../../core/update-manager'
|
|
4
|
+
import { createSpinner } from '../../ui/spinner'
|
|
5
|
+
import { header, success, error, warning, info } from '../../ui/theme'
|
|
6
|
+
import { pressEnterToContinue } from './shared'
|
|
7
|
+
|
|
8
|
+
export async function handleCheckUpdate(): Promise<void> {
|
|
9
|
+
console.clear()
|
|
10
|
+
console.log(header('Check for Updates'))
|
|
11
|
+
console.log()
|
|
12
|
+
|
|
13
|
+
const spinner = createSpinner('Checking for updates...')
|
|
14
|
+
spinner.start()
|
|
15
|
+
|
|
16
|
+
const result = await updateManager.checkForUpdate(true)
|
|
17
|
+
|
|
18
|
+
if (!result) {
|
|
19
|
+
spinner.fail('Could not reach npm registry')
|
|
20
|
+
console.log()
|
|
21
|
+
console.log(info('Check your internet connection and try again.'))
|
|
22
|
+
console.log(chalk.gray(' Manual update: npm install -g spindb@latest'))
|
|
23
|
+
console.log()
|
|
24
|
+
await pressEnterToContinue()
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (result.updateAvailable) {
|
|
29
|
+
spinner.succeed('Update available')
|
|
30
|
+
console.log()
|
|
31
|
+
console.log(chalk.gray(` Current version: ${result.currentVersion}`))
|
|
32
|
+
console.log(
|
|
33
|
+
chalk.gray(` Latest version: ${chalk.green(result.latestVersion)}`),
|
|
34
|
+
)
|
|
35
|
+
console.log()
|
|
36
|
+
|
|
37
|
+
const { action } = await inquirer.prompt<{ action: string }>([
|
|
38
|
+
{
|
|
39
|
+
type: 'list',
|
|
40
|
+
name: 'action',
|
|
41
|
+
message: 'What would you like to do?',
|
|
42
|
+
choices: [
|
|
43
|
+
{ name: 'Update now', value: 'update' },
|
|
44
|
+
{ name: 'Remind me later', value: 'later' },
|
|
45
|
+
{ name: "Don't check for updates on startup", value: 'disable' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
if (action === 'update') {
|
|
51
|
+
console.log()
|
|
52
|
+
const updateSpinner = createSpinner('Updating spindb...')
|
|
53
|
+
updateSpinner.start()
|
|
54
|
+
|
|
55
|
+
const updateResult = await updateManager.performUpdate()
|
|
56
|
+
|
|
57
|
+
if (updateResult.success) {
|
|
58
|
+
updateSpinner.succeed('Update complete')
|
|
59
|
+
console.log()
|
|
60
|
+
console.log(
|
|
61
|
+
success(
|
|
62
|
+
`Updated from ${updateResult.previousVersion} to ${updateResult.newVersion}`,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
console.log()
|
|
66
|
+
if (updateResult.previousVersion !== updateResult.newVersion) {
|
|
67
|
+
console.log(warning('Please restart spindb to use the new version.'))
|
|
68
|
+
console.log()
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
updateSpinner.fail('Update failed')
|
|
72
|
+
console.log()
|
|
73
|
+
console.log(error(updateResult.error || 'Unknown error'))
|
|
74
|
+
console.log()
|
|
75
|
+
console.log(info('Manual update: npm install -g spindb@latest'))
|
|
76
|
+
}
|
|
77
|
+
await pressEnterToContinue()
|
|
78
|
+
} else if (action === 'disable') {
|
|
79
|
+
await updateManager.setAutoCheckEnabled(false)
|
|
80
|
+
console.log()
|
|
81
|
+
console.log(info('Update checks disabled on startup.'))
|
|
82
|
+
console.log(chalk.gray(' Re-enable with: spindb config update-check on'))
|
|
83
|
+
console.log()
|
|
84
|
+
await pressEnterToContinue()
|
|
85
|
+
}
|
|
86
|
+
// 'later' just returns to menu
|
|
87
|
+
} else {
|
|
88
|
+
spinner.succeed('You are on the latest version')
|
|
89
|
+
console.log()
|
|
90
|
+
console.log(chalk.gray(` Version: ${result.currentVersion}`))
|
|
91
|
+
console.log()
|
|
92
|
+
await pressEnterToContinue()
|
|
93
|
+
}
|
|
94
|
+
}
|