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/restore.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
promptInstallDependencies,
|
|
12
12
|
} from '../ui/prompts'
|
|
13
13
|
import { createSpinner } from '../ui/spinner'
|
|
14
|
-
import {
|
|
14
|
+
import { uiSuccess, uiError, uiWarning } from '../ui/theme'
|
|
15
15
|
import { tmpdir } from 'os'
|
|
16
16
|
import { join } from 'path'
|
|
17
17
|
import { getMissingDependencies } from '../../core/dependency-manager'
|
|
@@ -50,11 +50,13 @@ export const restoreCommand = new Command('restore')
|
|
|
50
50
|
if (running.length === 0) {
|
|
51
51
|
if (containers.length === 0) {
|
|
52
52
|
console.log(
|
|
53
|
-
|
|
53
|
+
uiWarning(
|
|
54
|
+
'No containers found. Create one with: spindb create',
|
|
55
|
+
),
|
|
54
56
|
)
|
|
55
57
|
} else {
|
|
56
58
|
console.log(
|
|
57
|
-
|
|
59
|
+
uiWarning(
|
|
58
60
|
'No running containers. Start one first with: spindb start',
|
|
59
61
|
),
|
|
60
62
|
)
|
|
@@ -72,7 +74,7 @@ export const restoreCommand = new Command('restore')
|
|
|
72
74
|
|
|
73
75
|
const config = await containerManager.getConfig(containerName)
|
|
74
76
|
if (!config) {
|
|
75
|
-
console.error(
|
|
77
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
76
78
|
process.exit(1)
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -83,7 +85,7 @@ export const restoreCommand = new Command('restore')
|
|
|
83
85
|
})
|
|
84
86
|
if (!running) {
|
|
85
87
|
console.error(
|
|
86
|
-
|
|
88
|
+
uiError(
|
|
87
89
|
`Container "${containerName}" is not running. Start it first.`,
|
|
88
90
|
),
|
|
89
91
|
)
|
|
@@ -113,7 +115,7 @@ export const restoreCommand = new Command('restore')
|
|
|
113
115
|
missingDeps = await getMissingDependencies(config.engine)
|
|
114
116
|
if (missingDeps.length > 0) {
|
|
115
117
|
console.error(
|
|
116
|
-
|
|
118
|
+
uiError(
|
|
117
119
|
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
118
120
|
),
|
|
119
121
|
)
|
|
@@ -134,7 +136,7 @@ export const restoreCommand = new Command('restore')
|
|
|
134
136
|
|
|
135
137
|
if (engineName === 'postgresql' && !isPgUrl) {
|
|
136
138
|
console.error(
|
|
137
|
-
|
|
139
|
+
uiError(
|
|
138
140
|
'Connection string must start with postgresql:// or postgres:// for PostgreSQL containers',
|
|
139
141
|
),
|
|
140
142
|
)
|
|
@@ -143,7 +145,7 @@ export const restoreCommand = new Command('restore')
|
|
|
143
145
|
|
|
144
146
|
if (engineName === 'mysql' && !isMysqlUrl) {
|
|
145
147
|
console.error(
|
|
146
|
-
|
|
148
|
+
uiError(
|
|
147
149
|
'Connection string must start with mysql:// for MySQL containers',
|
|
148
150
|
),
|
|
149
151
|
)
|
|
@@ -152,7 +154,7 @@ export const restoreCommand = new Command('restore')
|
|
|
152
154
|
|
|
153
155
|
if (!isPgUrl && !isMysqlUrl) {
|
|
154
156
|
console.error(
|
|
155
|
-
|
|
157
|
+
uiError(
|
|
156
158
|
'Connection string must start with postgresql://, postgres://, or mysql://',
|
|
157
159
|
),
|
|
158
160
|
)
|
|
@@ -181,8 +183,8 @@ export const restoreCommand = new Command('restore')
|
|
|
181
183
|
dumpSpinner.succeed('Dump created from remote database')
|
|
182
184
|
backupPath = tempDumpPath
|
|
183
185
|
dumpSuccess = true
|
|
184
|
-
} catch (
|
|
185
|
-
const e =
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const e = error as Error
|
|
186
188
|
dumpSpinner.fail('Failed to create dump')
|
|
187
189
|
|
|
188
190
|
const dumpTool = engineName === 'mysql' ? 'mysqldump' : 'pg_dump'
|
|
@@ -201,19 +203,19 @@ export const restoreCommand = new Command('restore')
|
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
console.log()
|
|
204
|
-
console.error(
|
|
206
|
+
console.error(uiError(`${dumpTool} error:`))
|
|
205
207
|
console.log(chalk.gray(` ${e.message}`))
|
|
206
208
|
process.exit(1)
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
if (!dumpSuccess) {
|
|
211
|
-
console.error(
|
|
213
|
+
console.error(uiError('Failed to create dump after retries'))
|
|
212
214
|
process.exit(1)
|
|
213
215
|
}
|
|
214
216
|
} else {
|
|
215
217
|
if (!backupPath) {
|
|
216
|
-
console.error(
|
|
218
|
+
console.error(uiError('Backup file path is required'))
|
|
217
219
|
console.log(
|
|
218
220
|
chalk.gray(' Usage: spindb restore <container> <backup-file>'),
|
|
219
221
|
)
|
|
@@ -226,7 +228,7 @@ export const restoreCommand = new Command('restore')
|
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
if (!existsSync(backupPath)) {
|
|
229
|
-
console.error(
|
|
231
|
+
console.error(uiError(`Backup file not found: ${backupPath}`))
|
|
230
232
|
process.exit(1)
|
|
231
233
|
}
|
|
232
234
|
}
|
|
@@ -237,7 +239,7 @@ export const restoreCommand = new Command('restore')
|
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
if (!backupPath) {
|
|
240
|
-
console.error(
|
|
242
|
+
console.error(uiError('No backup path specified'))
|
|
241
243
|
process.exit(1)
|
|
242
244
|
}
|
|
243
245
|
|
|
@@ -347,7 +349,7 @@ export const restoreCommand = new Command('restore')
|
|
|
347
349
|
databaseName,
|
|
348
350
|
)
|
|
349
351
|
console.log()
|
|
350
|
-
console.log(
|
|
352
|
+
console.log(uiSuccess(`Database "${databaseName}" restored`))
|
|
351
353
|
console.log()
|
|
352
354
|
console.log(chalk.gray(' Connection string:'))
|
|
353
355
|
console.log(chalk.cyan(` ${connectionString}`))
|
|
@@ -365,8 +367,8 @@ export const restoreCommand = new Command('restore')
|
|
|
365
367
|
chalk.cyan(` spindb connect ${containerName} -d ${databaseName}`),
|
|
366
368
|
)
|
|
367
369
|
console.log()
|
|
368
|
-
} catch (
|
|
369
|
-
const e =
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const e = error as Error
|
|
370
372
|
|
|
371
373
|
const missingToolPatterns = [
|
|
372
374
|
'pg_restore not found',
|
|
@@ -391,7 +393,7 @@ export const restoreCommand = new Command('restore')
|
|
|
391
393
|
process.exit(1)
|
|
392
394
|
}
|
|
393
395
|
|
|
394
|
-
console.error(
|
|
396
|
+
console.error(uiError(e.message))
|
|
395
397
|
process.exit(1)
|
|
396
398
|
} finally {
|
|
397
399
|
if (tempDumpPath) {
|
package/cli/commands/run.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { containerManager } from '../../core/container-manager'
|
|
|
5
5
|
import { processManager } from '../../core/process-manager'
|
|
6
6
|
import { getEngine } from '../../engines'
|
|
7
7
|
import { promptInstallDependencies } from '../ui/prompts'
|
|
8
|
-
import {
|
|
8
|
+
import { uiError, uiWarning } from '../ui/theme'
|
|
9
9
|
import { getMissingDependencies } from '../../core/dependency-manager'
|
|
10
10
|
import { Engine } from '../../types'
|
|
11
11
|
|
|
@@ -26,7 +26,7 @@ export const runCommand = new Command('run')
|
|
|
26
26
|
|
|
27
27
|
const config = await containerManager.getConfig(containerName)
|
|
28
28
|
if (!config) {
|
|
29
|
-
console.error(
|
|
29
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
30
30
|
process.exit(1)
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -36,9 +36,7 @@ export const runCommand = new Command('run')
|
|
|
36
36
|
if (engineName === Engine.SQLite) {
|
|
37
37
|
if (!existsSync(config.database)) {
|
|
38
38
|
console.error(
|
|
39
|
-
|
|
40
|
-
`SQLite database file not found: ${config.database}`,
|
|
41
|
-
),
|
|
39
|
+
uiError(`SQLite database file not found: ${config.database}`),
|
|
42
40
|
)
|
|
43
41
|
process.exit(1)
|
|
44
42
|
}
|
|
@@ -49,7 +47,7 @@ export const runCommand = new Command('run')
|
|
|
49
47
|
})
|
|
50
48
|
if (!running) {
|
|
51
49
|
console.error(
|
|
52
|
-
|
|
50
|
+
uiError(
|
|
53
51
|
`Container "${containerName}" is not running. Start it first with: spindb start ${containerName}`,
|
|
54
52
|
),
|
|
55
53
|
)
|
|
@@ -59,16 +57,16 @@ export const runCommand = new Command('run')
|
|
|
59
57
|
|
|
60
58
|
if (file && options.sql) {
|
|
61
59
|
console.error(
|
|
62
|
-
|
|
60
|
+
uiError('Cannot specify both a file and --sql option. Choose one.'),
|
|
63
61
|
)
|
|
64
62
|
process.exit(1)
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
if (!file && !options.sql) {
|
|
68
|
-
console.error(
|
|
69
|
-
|
|
70
|
-
chalk.gray(' Usage: spindb run <container> <file.sql>'),
|
|
66
|
+
console.error(
|
|
67
|
+
uiError('Must provide either a SQL file or --sql option'),
|
|
71
68
|
)
|
|
69
|
+
console.log(chalk.gray(' Usage: spindb run <container> <file.sql>'))
|
|
72
70
|
console.log(
|
|
73
71
|
chalk.gray(' or: spindb run <container> --sql "SELECT ..."'),
|
|
74
72
|
)
|
|
@@ -76,7 +74,7 @@ export const runCommand = new Command('run')
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
if (file && !existsSync(file)) {
|
|
79
|
-
console.error(
|
|
77
|
+
console.error(uiError(`SQL file not found: ${file}`))
|
|
80
78
|
process.exit(1)
|
|
81
79
|
}
|
|
82
80
|
|
|
@@ -85,7 +83,7 @@ export const runCommand = new Command('run')
|
|
|
85
83
|
let missingDeps = await getMissingDependencies(engineName)
|
|
86
84
|
if (missingDeps.length > 0) {
|
|
87
85
|
console.log(
|
|
88
|
-
|
|
86
|
+
uiWarning(
|
|
89
87
|
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
90
88
|
),
|
|
91
89
|
)
|
|
@@ -102,7 +100,7 @@ export const runCommand = new Command('run')
|
|
|
102
100
|
missingDeps = await getMissingDependencies(engineName)
|
|
103
101
|
if (missingDeps.length > 0) {
|
|
104
102
|
console.error(
|
|
105
|
-
|
|
103
|
+
uiError(
|
|
106
104
|
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
107
105
|
),
|
|
108
106
|
)
|
|
@@ -120,8 +118,8 @@ export const runCommand = new Command('run')
|
|
|
120
118
|
sql: options.sql,
|
|
121
119
|
database,
|
|
122
120
|
})
|
|
123
|
-
} catch (
|
|
124
|
-
const e =
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const e = error as Error
|
|
125
123
|
|
|
126
124
|
const missingToolPatterns = [
|
|
127
125
|
'psql not found',
|
|
@@ -138,8 +136,12 @@ export const runCommand = new Command('run')
|
|
|
138
136
|
.replace(' not found', '')
|
|
139
137
|
.replace(' client', '')
|
|
140
138
|
// Determine engine from the missing tool name
|
|
141
|
-
const toolEngine =
|
|
142
|
-
|
|
139
|
+
const toolEngine =
|
|
140
|
+
missingTool === 'mysql' ? Engine.MySQL : Engine.PostgreSQL
|
|
141
|
+
const installed = await promptInstallDependencies(
|
|
142
|
+
missingTool,
|
|
143
|
+
toolEngine,
|
|
144
|
+
)
|
|
143
145
|
if (installed) {
|
|
144
146
|
console.log(
|
|
145
147
|
chalk.yellow(' Please re-run your command to continue.'),
|
|
@@ -148,7 +150,7 @@ export const runCommand = new Command('run')
|
|
|
148
150
|
process.exit(1)
|
|
149
151
|
}
|
|
150
152
|
|
|
151
|
-
console.error(
|
|
153
|
+
console.error(uiError(e.message))
|
|
152
154
|
process.exit(1)
|
|
153
155
|
}
|
|
154
156
|
},
|
|
@@ -3,7 +3,7 @@ import chalk from 'chalk'
|
|
|
3
3
|
import inquirer from 'inquirer'
|
|
4
4
|
import { updateManager } from '../../core/update-manager'
|
|
5
5
|
import { createSpinner } from '../ui/spinner'
|
|
6
|
-
import {
|
|
6
|
+
import { uiSuccess, uiError, uiInfo, header } from '../ui/theme'
|
|
7
7
|
|
|
8
8
|
export const selfUpdateCommand = new Command('self-update')
|
|
9
9
|
.alias('update')
|
|
@@ -24,7 +24,7 @@ export const selfUpdateCommand = new Command('self-update')
|
|
|
24
24
|
if (!result) {
|
|
25
25
|
checkSpinner.fail('Could not reach npm registry')
|
|
26
26
|
console.log()
|
|
27
|
-
console.log(
|
|
27
|
+
console.log(uiInfo('Check your internet connection and try again.'))
|
|
28
28
|
console.log(chalk.gray(' Manual update: npm install -g spindb@latest'))
|
|
29
29
|
process.exit(1)
|
|
30
30
|
}
|
|
@@ -84,7 +84,7 @@ export const selfUpdateCommand = new Command('self-update')
|
|
|
84
84
|
updateSpinner.succeed('Update complete')
|
|
85
85
|
console.log()
|
|
86
86
|
console.log(
|
|
87
|
-
|
|
87
|
+
uiSuccess(
|
|
88
88
|
`Updated from ${updateResult.previousVersion} to ${updateResult.newVersion}`,
|
|
89
89
|
),
|
|
90
90
|
)
|
|
@@ -100,9 +100,9 @@ export const selfUpdateCommand = new Command('self-update')
|
|
|
100
100
|
} else {
|
|
101
101
|
updateSpinner.fail('Update failed')
|
|
102
102
|
console.log()
|
|
103
|
-
console.log(
|
|
103
|
+
console.log(uiError(updateResult.error || 'Unknown error'))
|
|
104
104
|
console.log()
|
|
105
|
-
console.log(
|
|
105
|
+
console.log(uiInfo('Manual update: npm install -g spindb@latest'))
|
|
106
106
|
process.exit(1)
|
|
107
107
|
}
|
|
108
108
|
},
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { existsSync } from 'fs'
|
|
4
|
+
import { resolve, basename } from 'path'
|
|
5
|
+
import { sqliteRegistry } from '../../engines/sqlite/registry'
|
|
6
|
+
import {
|
|
7
|
+
scanForUnregisteredSqliteFiles,
|
|
8
|
+
deriveContainerName,
|
|
9
|
+
} from '../../engines/sqlite/scanner'
|
|
10
|
+
import { containerManager } from '../../core/container-manager'
|
|
11
|
+
import { uiSuccess, uiError, uiInfo } from '../ui/theme'
|
|
12
|
+
|
|
13
|
+
export const sqliteCommand = new Command('sqlite').description(
|
|
14
|
+
'SQLite-specific operations',
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// sqlite scan
|
|
18
|
+
sqliteCommand
|
|
19
|
+
.command('scan')
|
|
20
|
+
.description('Scan folder for unregistered SQLite files')
|
|
21
|
+
.option(
|
|
22
|
+
'-p, --path <dir>',
|
|
23
|
+
'Directory to scan (default: current directory)',
|
|
24
|
+
)
|
|
25
|
+
.option('--json', 'Output as JSON')
|
|
26
|
+
.action(async (options: { path?: string; json?: boolean }): Promise<void> => {
|
|
27
|
+
const dir = options.path ? resolve(options.path) : process.cwd()
|
|
28
|
+
|
|
29
|
+
if (!existsSync(dir)) {
|
|
30
|
+
if (options.json) {
|
|
31
|
+
console.log(
|
|
32
|
+
JSON.stringify({ error: 'Directory not found', directory: dir }),
|
|
33
|
+
)
|
|
34
|
+
} else {
|
|
35
|
+
console.error(uiError(`Directory not found: ${dir}`))
|
|
36
|
+
}
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const unregistered = await scanForUnregisteredSqliteFiles(dir)
|
|
41
|
+
|
|
42
|
+
if (options.json) {
|
|
43
|
+
console.log(JSON.stringify({ directory: dir, files: unregistered }))
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (unregistered.length === 0) {
|
|
48
|
+
console.log(uiInfo(`No unregistered SQLite files found in ${dir}`))
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(
|
|
53
|
+
chalk.cyan(`Found ${unregistered.length} unregistered SQLite file(s):`),
|
|
54
|
+
)
|
|
55
|
+
for (const file of unregistered) {
|
|
56
|
+
console.log(chalk.gray(` ${file.fileName}`))
|
|
57
|
+
}
|
|
58
|
+
console.log()
|
|
59
|
+
console.log(chalk.gray(' Register with: spindb attach <path>'))
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// sqlite ignore
|
|
63
|
+
sqliteCommand
|
|
64
|
+
.command('ignore')
|
|
65
|
+
.description('Add folder to ignore list for CWD scanning')
|
|
66
|
+
.argument('[folder]', 'Folder path to ignore (default: current directory)')
|
|
67
|
+
.option('--json', 'Output as JSON')
|
|
68
|
+
.action(
|
|
69
|
+
async (folder: string | undefined, options: { json?: boolean }): Promise<void> => {
|
|
70
|
+
const absolutePath = resolve(folder || process.cwd())
|
|
71
|
+
await sqliteRegistry.addIgnoreFolder(absolutePath)
|
|
72
|
+
|
|
73
|
+
if (options.json) {
|
|
74
|
+
console.log(JSON.stringify({ success: true, folder: absolutePath }))
|
|
75
|
+
} else {
|
|
76
|
+
console.log(uiSuccess(`Added to ignore list: ${absolutePath}`))
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
// sqlite unignore
|
|
82
|
+
sqliteCommand
|
|
83
|
+
.command('unignore')
|
|
84
|
+
.description('Remove folder from ignore list')
|
|
85
|
+
.argument('[folder]', 'Folder path to unignore (default: current directory)')
|
|
86
|
+
.option('--json', 'Output as JSON')
|
|
87
|
+
.action(
|
|
88
|
+
async (folder: string | undefined, options: { json?: boolean }): Promise<void> => {
|
|
89
|
+
const absolutePath = resolve(folder || process.cwd())
|
|
90
|
+
const removed = await sqliteRegistry.removeIgnoreFolder(absolutePath)
|
|
91
|
+
|
|
92
|
+
if (options.json) {
|
|
93
|
+
console.log(JSON.stringify({ success: removed, folder: absolutePath }))
|
|
94
|
+
} else {
|
|
95
|
+
if (removed) {
|
|
96
|
+
console.log(uiSuccess(`Removed from ignore list: ${absolutePath}`))
|
|
97
|
+
} else {
|
|
98
|
+
console.log(uiInfo(`Folder was not in ignore list: ${absolutePath}`))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// sqlite ignored (list ignored folders)
|
|
105
|
+
sqliteCommand
|
|
106
|
+
.command('ignored')
|
|
107
|
+
.description('List ignored folders')
|
|
108
|
+
.option('--json', 'Output as JSON')
|
|
109
|
+
.action(async (options: { json?: boolean }): Promise<void> => {
|
|
110
|
+
const folders = await sqliteRegistry.listIgnoredFolders()
|
|
111
|
+
|
|
112
|
+
if (options.json) {
|
|
113
|
+
console.log(JSON.stringify({ folders }))
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (folders.length === 0) {
|
|
118
|
+
console.log(uiInfo('No folders are being ignored'))
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(chalk.cyan('Ignored folders:'))
|
|
123
|
+
for (const folder of folders) {
|
|
124
|
+
console.log(chalk.gray(` ${folder}`))
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// sqlite attach (alias to top-level attach)
|
|
129
|
+
sqliteCommand
|
|
130
|
+
.command('attach')
|
|
131
|
+
.description('Register an existing SQLite database (alias for "spindb attach")')
|
|
132
|
+
.argument('<path>', 'Path to SQLite database file')
|
|
133
|
+
.option('-n, --name <name>', 'Container name')
|
|
134
|
+
.option('--json', 'Output as JSON')
|
|
135
|
+
.action(
|
|
136
|
+
async (
|
|
137
|
+
path: string,
|
|
138
|
+
options: { name?: string; json?: boolean },
|
|
139
|
+
): Promise<void> => {
|
|
140
|
+
try {
|
|
141
|
+
const absolutePath = resolve(path)
|
|
142
|
+
|
|
143
|
+
if (!existsSync(absolutePath)) {
|
|
144
|
+
if (options.json) {
|
|
145
|
+
console.log(
|
|
146
|
+
JSON.stringify({ success: false, error: 'File not found' }),
|
|
147
|
+
)
|
|
148
|
+
} else {
|
|
149
|
+
console.error(uiError(`File not found: ${absolutePath}`))
|
|
150
|
+
}
|
|
151
|
+
process.exit(1)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (await sqliteRegistry.isPathRegistered(absolutePath)) {
|
|
155
|
+
const entry = await sqliteRegistry.getByPath(absolutePath)
|
|
156
|
+
if (options.json) {
|
|
157
|
+
console.log(
|
|
158
|
+
JSON.stringify({
|
|
159
|
+
success: false,
|
|
160
|
+
error: 'Already registered',
|
|
161
|
+
existingName: entry?.name,
|
|
162
|
+
}),
|
|
163
|
+
)
|
|
164
|
+
} else {
|
|
165
|
+
console.error(
|
|
166
|
+
uiError(`File is already registered as "${entry?.name}"`),
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
process.exit(1)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const containerName =
|
|
173
|
+
options.name || deriveContainerName(basename(absolutePath))
|
|
174
|
+
|
|
175
|
+
if (await containerManager.exists(containerName)) {
|
|
176
|
+
if (options.json) {
|
|
177
|
+
console.log(
|
|
178
|
+
JSON.stringify({
|
|
179
|
+
success: false,
|
|
180
|
+
error: 'Container name already exists',
|
|
181
|
+
}),
|
|
182
|
+
)
|
|
183
|
+
} else {
|
|
184
|
+
console.error(uiError(`Container "${containerName}" already exists`))
|
|
185
|
+
}
|
|
186
|
+
process.exit(1)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await sqliteRegistry.add({
|
|
190
|
+
name: containerName,
|
|
191
|
+
filePath: absolutePath,
|
|
192
|
+
created: new Date().toISOString(),
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
if (options.json) {
|
|
196
|
+
console.log(
|
|
197
|
+
JSON.stringify({
|
|
198
|
+
success: true,
|
|
199
|
+
name: containerName,
|
|
200
|
+
filePath: absolutePath,
|
|
201
|
+
}),
|
|
202
|
+
)
|
|
203
|
+
} else {
|
|
204
|
+
console.log(
|
|
205
|
+
uiSuccess(
|
|
206
|
+
`Registered "${basename(absolutePath)}" as "${containerName}"`,
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
console.log()
|
|
210
|
+
console.log(chalk.gray(' Connect with:'))
|
|
211
|
+
console.log(chalk.cyan(` spindb connect ${containerName}`))
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const e = error as Error
|
|
215
|
+
if (options.json) {
|
|
216
|
+
console.log(JSON.stringify({ success: false, error: e.message }))
|
|
217
|
+
} else {
|
|
218
|
+
console.error(uiError(e.message))
|
|
219
|
+
}
|
|
220
|
+
process.exit(1)
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
// sqlite detach (alias to top-level detach)
|
|
226
|
+
sqliteCommand
|
|
227
|
+
.command('detach')
|
|
228
|
+
.description('Unregister a SQLite database (alias for "spindb detach")')
|
|
229
|
+
.argument('<name>', 'Container name')
|
|
230
|
+
.option('-f, --force', 'Skip confirmation')
|
|
231
|
+
.option('--json', 'Output as JSON')
|
|
232
|
+
.action(
|
|
233
|
+
async (
|
|
234
|
+
name: string,
|
|
235
|
+
options: { force?: boolean; json?: boolean },
|
|
236
|
+
): Promise<void> => {
|
|
237
|
+
// Import dynamically to avoid circular dependency issues
|
|
238
|
+
const { detachCommand } = await import('./detach')
|
|
239
|
+
|
|
240
|
+
// Build args array
|
|
241
|
+
const args = ['node', 'detach', name]
|
|
242
|
+
if (options.force) args.push('-f')
|
|
243
|
+
if (options.json) args.push('--json')
|
|
244
|
+
|
|
245
|
+
await detachCommand.parseAsync(args, { from: 'node' })
|
|
246
|
+
},
|
|
247
|
+
)
|
package/cli/commands/start.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { getEngine } from '../../engines'
|
|
|
7
7
|
import { getEngineDefaults } from '../../config/defaults'
|
|
8
8
|
import { promptContainerSelect } from '../ui/prompts'
|
|
9
9
|
import { createSpinner } from '../ui/spinner'
|
|
10
|
-
import {
|
|
10
|
+
import { uiError, uiWarning } from '../ui/theme'
|
|
11
11
|
|
|
12
12
|
export const startCommand = new Command('start')
|
|
13
13
|
.description('Start a container')
|
|
@@ -23,10 +23,10 @@ export const startCommand = new Command('start')
|
|
|
23
23
|
if (stopped.length === 0) {
|
|
24
24
|
if (containers.length === 0) {
|
|
25
25
|
console.log(
|
|
26
|
-
|
|
26
|
+
uiWarning('No containers found. Create one with: spindb create'),
|
|
27
27
|
)
|
|
28
28
|
} else {
|
|
29
|
-
console.log(
|
|
29
|
+
console.log(uiWarning('All containers are already running'))
|
|
30
30
|
}
|
|
31
31
|
return
|
|
32
32
|
}
|
|
@@ -41,7 +41,7 @@ export const startCommand = new Command('start')
|
|
|
41
41
|
|
|
42
42
|
const config = await containerManager.getConfig(containerName)
|
|
43
43
|
if (!config) {
|
|
44
|
-
console.error(
|
|
44
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
45
45
|
process.exit(1)
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -51,7 +51,9 @@ export const startCommand = new Command('start')
|
|
|
51
51
|
engine: engineName,
|
|
52
52
|
})
|
|
53
53
|
if (running) {
|
|
54
|
-
console.log(
|
|
54
|
+
console.log(
|
|
55
|
+
uiWarning(`Container "${containerName}" is already running`),
|
|
56
|
+
)
|
|
55
57
|
return
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -72,7 +74,7 @@ export const startCommand = new Command('start')
|
|
|
72
74
|
if (!result.success) {
|
|
73
75
|
spinner.fail(`Failed to start "${containerName}"`)
|
|
74
76
|
if (result.error) {
|
|
75
|
-
console.error(
|
|
77
|
+
console.error(uiError(result.error.message))
|
|
76
78
|
}
|
|
77
79
|
process.exit(1)
|
|
78
80
|
}
|
|
@@ -110,9 +112,9 @@ export const startCommand = new Command('start')
|
|
|
110
112
|
console.log(chalk.gray(' Connect with:'))
|
|
111
113
|
console.log(chalk.cyan(` spindb connect ${containerName}`))
|
|
112
114
|
console.log()
|
|
113
|
-
} catch (
|
|
114
|
-
const e =
|
|
115
|
-
console.error(
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const e = error as Error
|
|
117
|
+
console.error(uiError(e.message))
|
|
116
118
|
process.exit(1)
|
|
117
119
|
}
|
|
118
120
|
})
|
package/cli/commands/stop.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { processManager } from '../../core/process-manager'
|
|
|
4
4
|
import { getEngine } from '../../engines'
|
|
5
5
|
import { promptContainerSelect } from '../ui/prompts'
|
|
6
6
|
import { createSpinner } from '../ui/spinner'
|
|
7
|
-
import {
|
|
7
|
+
import { uiSuccess, uiError, uiWarning } from '../ui/theme'
|
|
8
8
|
|
|
9
9
|
export const stopCommand = new Command('stop')
|
|
10
10
|
.description('Stop a container')
|
|
@@ -17,7 +17,7 @@ export const stopCommand = new Command('stop')
|
|
|
17
17
|
const running = containers.filter((c) => c.status === 'running')
|
|
18
18
|
|
|
19
19
|
if (running.length === 0) {
|
|
20
|
-
console.log(
|
|
20
|
+
console.log(uiWarning('No running containers found'))
|
|
21
21
|
return
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -34,7 +34,7 @@ export const stopCommand = new Command('stop')
|
|
|
34
34
|
spinner.succeed(`Stopped "${container.name}"`)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
console.log(
|
|
37
|
+
console.log(uiSuccess(`Stopped ${running.length} container(s)`))
|
|
38
38
|
return
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -45,7 +45,7 @@ export const stopCommand = new Command('stop')
|
|
|
45
45
|
const running = containers.filter((c) => c.status === 'running')
|
|
46
46
|
|
|
47
47
|
if (running.length === 0) {
|
|
48
|
-
console.log(
|
|
48
|
+
console.log(uiWarning('No running containers found'))
|
|
49
49
|
return
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -59,7 +59,7 @@ export const stopCommand = new Command('stop')
|
|
|
59
59
|
|
|
60
60
|
const config = await containerManager.getConfig(containerName)
|
|
61
61
|
if (!config) {
|
|
62
|
-
console.error(
|
|
62
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
63
63
|
process.exit(1)
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -67,7 +67,7 @@ export const stopCommand = new Command('stop')
|
|
|
67
67
|
engine: config.engine,
|
|
68
68
|
})
|
|
69
69
|
if (!running) {
|
|
70
|
-
console.log(
|
|
70
|
+
console.log(uiWarning(`Container "${containerName}" is not running`))
|
|
71
71
|
return
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -80,9 +80,9 @@ export const stopCommand = new Command('stop')
|
|
|
80
80
|
await containerManager.updateConfig(containerName, { status: 'stopped' })
|
|
81
81
|
|
|
82
82
|
spinner.succeed(`Container "${containerName}" stopped`)
|
|
83
|
-
} catch (
|
|
84
|
-
const e =
|
|
85
|
-
console.error(
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const e = error as Error
|
|
85
|
+
console.error(uiError(e.message))
|
|
86
86
|
process.exit(1)
|
|
87
87
|
}
|
|
88
88
|
})
|