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/README.md
CHANGED
|
@@ -653,16 +653,13 @@ rm -rf ~/.spindb
|
|
|
653
653
|
|
|
654
654
|
## Contributing
|
|
655
655
|
|
|
656
|
-
See [
|
|
656
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and distribution info.
|
|
657
657
|
|
|
658
|
-
|
|
658
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for project architecture and comprehensive CLI command examples.
|
|
659
659
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
pnpm test:pg # PostgreSQL integration
|
|
664
|
-
pnpm test:mysql # MySQL integration
|
|
665
|
-
```
|
|
660
|
+
See [CLAUDE.md](CLAUDE.md) for AI-assisted development context.
|
|
661
|
+
|
|
662
|
+
See [ENGINES.md](ENGINES.md) for detailed engine documentation (backup formats, planned engines, etc.).
|
|
666
663
|
|
|
667
664
|
---
|
|
668
665
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { existsSync } from 'fs'
|
|
3
|
+
import { resolve, basename } from 'path'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import { sqliteRegistry } from '../../engines/sqlite/registry'
|
|
6
|
+
import { containerManager } from '../../core/container-manager'
|
|
7
|
+
import { deriveContainerName } from '../../engines/sqlite/scanner'
|
|
8
|
+
import { uiSuccess, uiError } from '../ui/theme'
|
|
9
|
+
|
|
10
|
+
export const attachCommand = new Command('attach')
|
|
11
|
+
.description('Register an existing SQLite database with SpinDB')
|
|
12
|
+
.argument('<path>', 'Path to SQLite database file')
|
|
13
|
+
.option('-n, --name <name>', 'Container name (defaults to filename)')
|
|
14
|
+
.option('--json', 'Output as JSON')
|
|
15
|
+
.action(
|
|
16
|
+
async (
|
|
17
|
+
path: string,
|
|
18
|
+
options: { name?: string; json?: boolean },
|
|
19
|
+
): Promise<void> => {
|
|
20
|
+
try {
|
|
21
|
+
const absolutePath = resolve(path)
|
|
22
|
+
|
|
23
|
+
// Verify file exists
|
|
24
|
+
if (!existsSync(absolutePath)) {
|
|
25
|
+
if (options.json) {
|
|
26
|
+
console.log(
|
|
27
|
+
JSON.stringify({ success: false, error: 'File not found' }),
|
|
28
|
+
)
|
|
29
|
+
} else {
|
|
30
|
+
console.error(uiError(`File not found: ${absolutePath}`))
|
|
31
|
+
}
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if already registered
|
|
36
|
+
if (await sqliteRegistry.isPathRegistered(absolutePath)) {
|
|
37
|
+
const entry = await sqliteRegistry.getByPath(absolutePath)
|
|
38
|
+
if (options.json) {
|
|
39
|
+
console.log(
|
|
40
|
+
JSON.stringify({
|
|
41
|
+
success: false,
|
|
42
|
+
error: 'Already registered',
|
|
43
|
+
existingName: entry?.name,
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
} else {
|
|
47
|
+
console.error(
|
|
48
|
+
uiError(`File is already registered as "${entry?.name}"`),
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Determine container name
|
|
55
|
+
const containerName =
|
|
56
|
+
options.name || deriveContainerName(basename(absolutePath))
|
|
57
|
+
|
|
58
|
+
// Check if container name exists
|
|
59
|
+
if (await containerManager.exists(containerName)) {
|
|
60
|
+
if (options.json) {
|
|
61
|
+
console.log(
|
|
62
|
+
JSON.stringify({
|
|
63
|
+
success: false,
|
|
64
|
+
error: 'Container name already exists',
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
67
|
+
} else {
|
|
68
|
+
console.error(uiError(`Container "${containerName}" already exists`))
|
|
69
|
+
}
|
|
70
|
+
process.exit(1)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Register the file
|
|
74
|
+
await sqliteRegistry.add({
|
|
75
|
+
name: containerName,
|
|
76
|
+
filePath: absolutePath,
|
|
77
|
+
created: new Date().toISOString(),
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
if (options.json) {
|
|
81
|
+
console.log(
|
|
82
|
+
JSON.stringify({
|
|
83
|
+
success: true,
|
|
84
|
+
name: containerName,
|
|
85
|
+
filePath: absolutePath,
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
} else {
|
|
89
|
+
console.log(
|
|
90
|
+
uiSuccess(
|
|
91
|
+
`Registered "${basename(absolutePath)}" as "${containerName}"`,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
console.log()
|
|
95
|
+
console.log(chalk.gray(' Connect with:'))
|
|
96
|
+
console.log(chalk.cyan(` spindb connect ${containerName}`))
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const e = error as Error
|
|
100
|
+
if (options.json) {
|
|
101
|
+
console.log(JSON.stringify({ success: false, error: e.message }))
|
|
102
|
+
} else {
|
|
103
|
+
console.error(uiError(e.message))
|
|
104
|
+
}
|
|
105
|
+
process.exit(1)
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
)
|
package/cli/commands/backup.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
promptInstallDependencies,
|
|
13
13
|
} from '../ui/prompts'
|
|
14
14
|
import { createSpinner } from '../ui/spinner'
|
|
15
|
-
import {
|
|
15
|
+
import { uiSuccess, uiError, uiWarning, formatBytes } from '../ui/theme'
|
|
16
16
|
import { getMissingDependencies } from '../../core/dependency-manager'
|
|
17
17
|
|
|
18
18
|
function generateTimestamp(): string {
|
|
@@ -70,11 +70,13 @@ export const backupCommand = new Command('backup')
|
|
|
70
70
|
if (running.length === 0) {
|
|
71
71
|
if (containers.length === 0) {
|
|
72
72
|
console.log(
|
|
73
|
-
|
|
73
|
+
uiWarning(
|
|
74
|
+
'No containers found. Create one with: spindb create',
|
|
75
|
+
),
|
|
74
76
|
)
|
|
75
77
|
} else {
|
|
76
78
|
console.log(
|
|
77
|
-
|
|
79
|
+
uiWarning(
|
|
78
80
|
'No running containers. Start one first with: spindb start',
|
|
79
81
|
),
|
|
80
82
|
)
|
|
@@ -92,7 +94,7 @@ export const backupCommand = new Command('backup')
|
|
|
92
94
|
|
|
93
95
|
const config = await containerManager.getConfig(containerName)
|
|
94
96
|
if (!config) {
|
|
95
|
-
console.error(
|
|
97
|
+
console.error(uiError(`Container "${containerName}" not found`))
|
|
96
98
|
process.exit(1)
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -103,7 +105,7 @@ export const backupCommand = new Command('backup')
|
|
|
103
105
|
})
|
|
104
106
|
if (!running) {
|
|
105
107
|
console.error(
|
|
106
|
-
|
|
108
|
+
uiError(
|
|
107
109
|
`Container "${containerName}" is not running. Start it first.`,
|
|
108
110
|
),
|
|
109
111
|
)
|
|
@@ -133,7 +135,7 @@ export const backupCommand = new Command('backup')
|
|
|
133
135
|
missingDeps = await getMissingDependencies(config.engine)
|
|
134
136
|
if (missingDeps.length > 0) {
|
|
135
137
|
console.error(
|
|
136
|
-
|
|
138
|
+
uiError(
|
|
137
139
|
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
138
140
|
),
|
|
139
141
|
)
|
|
@@ -169,7 +171,7 @@ export const backupCommand = new Command('backup')
|
|
|
169
171
|
format = 'dump'
|
|
170
172
|
} else if (options.format) {
|
|
171
173
|
if (options.format !== 'sql' && options.format !== 'dump') {
|
|
172
|
-
console.error(
|
|
174
|
+
console.error(uiError('Format must be "sql" or "dump"'))
|
|
173
175
|
process.exit(1)
|
|
174
176
|
}
|
|
175
177
|
format = options.format as 'sql' | 'dump'
|
|
@@ -204,7 +206,7 @@ export const backupCommand = new Command('backup')
|
|
|
204
206
|
backupSpinner.succeed('Backup created successfully')
|
|
205
207
|
|
|
206
208
|
console.log()
|
|
207
|
-
console.log(
|
|
209
|
+
console.log(uiSuccess('Backup complete'))
|
|
208
210
|
console.log()
|
|
209
211
|
console.log(chalk.gray(' File:'), chalk.cyan(result.path))
|
|
210
212
|
console.log(
|
|
@@ -213,8 +215,8 @@ export const backupCommand = new Command('backup')
|
|
|
213
215
|
)
|
|
214
216
|
console.log(chalk.gray(' Format:'), chalk.white(result.format))
|
|
215
217
|
console.log()
|
|
216
|
-
} catch (
|
|
217
|
-
const e =
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const e = error as Error
|
|
218
220
|
|
|
219
221
|
const missingToolPatterns = ['pg_dump not found', 'mysqldump not found']
|
|
220
222
|
|
|
@@ -233,7 +235,7 @@ export const backupCommand = new Command('backup')
|
|
|
233
235
|
process.exit(1)
|
|
234
236
|
}
|
|
235
237
|
|
|
236
|
-
console.error(
|
|
238
|
+
console.error(uiError(e.message))
|
|
237
239
|
process.exit(1)
|
|
238
240
|
}
|
|
239
241
|
},
|
package/cli/commands/clone.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { processManager } from '../../core/process-manager'
|
|
|
5
5
|
import { getEngine } from '../../engines'
|
|
6
6
|
import { promptContainerSelect, promptContainerName } from '../ui/prompts'
|
|
7
7
|
import { createSpinner } from '../ui/spinner'
|
|
8
|
-
import {
|
|
8
|
+
import { uiError, uiWarning, connectionBox } from '../ui/theme'
|
|
9
9
|
|
|
10
10
|
export const cloneCommand = new Command('clone')
|
|
11
11
|
.description('Clone a container with all its data')
|
|
@@ -22,14 +22,14 @@ export const cloneCommand = new Command('clone')
|
|
|
22
22
|
|
|
23
23
|
if (containers.length === 0) {
|
|
24
24
|
console.log(
|
|
25
|
-
|
|
25
|
+
uiWarning('No containers found. Create one with: spindb create'),
|
|
26
26
|
)
|
|
27
27
|
return
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (stopped.length === 0) {
|
|
31
31
|
console.log(
|
|
32
|
-
|
|
32
|
+
uiWarning(
|
|
33
33
|
'All containers are running. Stop a container first to clone it.',
|
|
34
34
|
),
|
|
35
35
|
)
|
|
@@ -51,7 +51,7 @@ export const cloneCommand = new Command('clone')
|
|
|
51
51
|
|
|
52
52
|
const sourceConfig = await containerManager.getConfig(sourceName)
|
|
53
53
|
if (!sourceConfig) {
|
|
54
|
-
console.error(
|
|
54
|
+
console.error(uiError(`Container "${sourceName}" not found`))
|
|
55
55
|
process.exit(1)
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -60,7 +60,7 @@ export const cloneCommand = new Command('clone')
|
|
|
60
60
|
})
|
|
61
61
|
if (running) {
|
|
62
62
|
console.error(
|
|
63
|
-
|
|
63
|
+
uiError(
|
|
64
64
|
`Container "${sourceName}" is running. Stop it first to clone.`,
|
|
65
65
|
),
|
|
66
66
|
)
|
|
@@ -72,8 +72,12 @@ export const cloneCommand = new Command('clone')
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// Check if target container already exists
|
|
75
|
-
if (
|
|
76
|
-
|
|
75
|
+
if (
|
|
76
|
+
await containerManager.exists(targetName, {
|
|
77
|
+
engine: sourceConfig.engine,
|
|
78
|
+
})
|
|
79
|
+
) {
|
|
80
|
+
console.error(uiError(`Container "${targetName}" already exists`))
|
|
77
81
|
process.exit(1)
|
|
78
82
|
}
|
|
79
83
|
|
|
@@ -95,9 +99,9 @@ export const cloneCommand = new Command('clone')
|
|
|
95
99
|
console.log(chalk.gray(' Start the cloned container:'))
|
|
96
100
|
console.log(chalk.cyan(` spindb start ${targetName}`))
|
|
97
101
|
console.log()
|
|
98
|
-
} catch (
|
|
99
|
-
const e =
|
|
100
|
-
console.error(
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const e = error as Error
|
|
104
|
+
console.error(uiError(e.message))
|
|
101
105
|
process.exit(1)
|
|
102
106
|
}
|
|
103
107
|
})
|
package/cli/commands/config.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
ALL_TOOLS,
|
|
10
10
|
} from '../../core/config-manager'
|
|
11
11
|
import { updateManager } from '../../core/update-manager'
|
|
12
|
-
import {
|
|
12
|
+
import { uiError, uiSuccess, header, uiInfo } from '../ui/theme'
|
|
13
13
|
import { createSpinner } from '../ui/spinner'
|
|
14
14
|
import type { BinaryTool } from '../../types'
|
|
15
15
|
|
|
@@ -95,9 +95,9 @@ export const configCommand = new Command('config')
|
|
|
95
95
|
)
|
|
96
96
|
console.log()
|
|
97
97
|
}
|
|
98
|
-
} catch (
|
|
99
|
-
const e =
|
|
100
|
-
console.error(
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const e = error as Error
|
|
100
|
+
console.error(uiError(e.message))
|
|
101
101
|
process.exit(1)
|
|
102
102
|
}
|
|
103
103
|
}),
|
|
@@ -208,9 +208,9 @@ export const configCommand = new Command('config')
|
|
|
208
208
|
}
|
|
209
209
|
console.log()
|
|
210
210
|
}
|
|
211
|
-
} catch (
|
|
212
|
-
const e =
|
|
213
|
-
console.error(
|
|
211
|
+
} catch (error) {
|
|
212
|
+
const e = error as Error
|
|
213
|
+
console.error(uiError(e.message))
|
|
214
214
|
process.exit(1)
|
|
215
215
|
}
|
|
216
216
|
}),
|
|
@@ -224,14 +224,14 @@ export const configCommand = new Command('config')
|
|
|
224
224
|
try {
|
|
225
225
|
// Validate tool name
|
|
226
226
|
if (!ALL_TOOLS.includes(tool as BinaryTool)) {
|
|
227
|
-
console.error(
|
|
227
|
+
console.error(uiError(`Invalid tool: ${tool}`))
|
|
228
228
|
console.log(chalk.gray(` Valid tools: ${ALL_TOOLS.join(', ')}`))
|
|
229
229
|
process.exit(1)
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
// Validate path exists
|
|
233
233
|
if (!existsSync(path)) {
|
|
234
|
-
console.error(
|
|
234
|
+
console.error(uiError(`File not found: ${path}`))
|
|
235
235
|
process.exit(1)
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -240,10 +240,10 @@ export const configCommand = new Command('config')
|
|
|
240
240
|
const config = await configManager.getBinaryConfig(tool as BinaryTool)
|
|
241
241
|
const versionLabel = config?.version ? ` (v${config.version})` : ''
|
|
242
242
|
|
|
243
|
-
console.log(
|
|
244
|
-
} catch (
|
|
245
|
-
const e =
|
|
246
|
-
console.error(
|
|
243
|
+
console.log(uiSuccess(`Set ${tool} to: ${path}${versionLabel}`))
|
|
244
|
+
} catch (error) {
|
|
245
|
+
const e = error as Error
|
|
246
|
+
console.error(uiError(e.message))
|
|
247
247
|
process.exit(1)
|
|
248
248
|
}
|
|
249
249
|
}),
|
|
@@ -256,16 +256,16 @@ export const configCommand = new Command('config')
|
|
|
256
256
|
try {
|
|
257
257
|
// Validate tool name
|
|
258
258
|
if (!ALL_TOOLS.includes(tool as BinaryTool)) {
|
|
259
|
-
console.error(
|
|
259
|
+
console.error(uiError(`Invalid tool: ${tool}`))
|
|
260
260
|
console.log(chalk.gray(` Valid tools: ${ALL_TOOLS.join(', ')}`))
|
|
261
261
|
process.exit(1)
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
await configManager.clearBinaryPath(tool as BinaryTool)
|
|
265
|
-
console.log(
|
|
266
|
-
} catch (
|
|
267
|
-
const e =
|
|
268
|
-
console.error(
|
|
265
|
+
console.log(uiSuccess(`Removed ${tool} configuration`))
|
|
266
|
+
} catch (error) {
|
|
267
|
+
const e = error as Error
|
|
268
|
+
console.error(uiError(e.message))
|
|
269
269
|
process.exit(1)
|
|
270
270
|
}
|
|
271
271
|
}),
|
|
@@ -278,7 +278,7 @@ export const configCommand = new Command('config')
|
|
|
278
278
|
try {
|
|
279
279
|
// Validate tool name
|
|
280
280
|
if (!ALL_TOOLS.includes(tool as BinaryTool)) {
|
|
281
|
-
console.error(
|
|
281
|
+
console.error(uiError(`Invalid tool: ${tool}`))
|
|
282
282
|
console.log(chalk.gray(` Valid tools: ${ALL_TOOLS.join(', ')}`))
|
|
283
283
|
process.exit(1)
|
|
284
284
|
}
|
|
@@ -287,15 +287,15 @@ export const configCommand = new Command('config')
|
|
|
287
287
|
if (path) {
|
|
288
288
|
console.log(path)
|
|
289
289
|
} else {
|
|
290
|
-
console.error(
|
|
290
|
+
console.error(uiError(`${tool} not found`))
|
|
291
291
|
console.log(
|
|
292
292
|
chalk.gray(` Run 'spindb config detect' to auto-detect tools`),
|
|
293
293
|
)
|
|
294
294
|
process.exit(1)
|
|
295
295
|
}
|
|
296
|
-
} catch (
|
|
297
|
-
const e =
|
|
298
|
-
console.error(
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const e = error as Error
|
|
298
|
+
console.error(uiError(e.message))
|
|
299
299
|
process.exit(1)
|
|
300
300
|
}
|
|
301
301
|
}),
|
|
@@ -328,7 +328,7 @@ export const configCommand = new Command('config')
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
if (state !== 'on' && state !== 'off') {
|
|
331
|
-
console.error(
|
|
331
|
+
console.error(uiError('Invalid state. Use "on" or "off"'))
|
|
332
332
|
process.exit(1)
|
|
333
333
|
}
|
|
334
334
|
|
|
@@ -336,18 +336,18 @@ export const configCommand = new Command('config')
|
|
|
336
336
|
await updateManager.setAutoCheckEnabled(enabled)
|
|
337
337
|
|
|
338
338
|
if (enabled) {
|
|
339
|
-
console.log(
|
|
339
|
+
console.log(uiSuccess('Update checks enabled on startup'))
|
|
340
340
|
} else {
|
|
341
|
-
console.log(
|
|
341
|
+
console.log(uiInfo('Update checks disabled on startup'))
|
|
342
342
|
console.log(
|
|
343
343
|
chalk.gray(
|
|
344
344
|
' You can still manually check with: spindb version --check',
|
|
345
345
|
),
|
|
346
346
|
)
|
|
347
347
|
}
|
|
348
|
-
} catch (
|
|
349
|
-
const e =
|
|
350
|
-
console.error(
|
|
348
|
+
} catch (error) {
|
|
349
|
+
const e = error as Error
|
|
350
|
+
console.error(uiError(e.message))
|
|
351
351
|
process.exit(1)
|
|
352
352
|
}
|
|
353
353
|
}),
|