spindb 0.8.1 → 0.9.0
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 +80 -7
- package/cli/commands/connect.ts +115 -14
- package/cli/commands/create.ts +113 -1
- package/cli/commands/doctor.ts +319 -0
- package/cli/commands/edit.ts +203 -5
- package/cli/commands/info.ts +79 -26
- package/cli/commands/list.ts +64 -9
- package/cli/commands/menu/backup-handlers.ts +28 -13
- package/cli/commands/menu/container-handlers.ts +410 -120
- 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/run.ts +27 -11
- package/cli/commands/url.ts +17 -9
- package/cli/constants.ts +1 -0
- package/cli/index.ts +2 -0
- package/cli/ui/prompts.ts +165 -14
- 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 +119 -11
- package/core/dependency-manager.ts +18 -0
- package/engines/index.ts +4 -0
- package/engines/sqlite/index.ts +597 -0
- package/engines/sqlite/registry.ts +185 -0
- package/package.json +3 -2
- package/types/index.ts +26 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**Local databases without the Docker baggage.**
|
|
8
8
|
|
|
9
|
-
Spin up PostgreSQL and
|
|
9
|
+
Spin up PostgreSQL, MySQL, and SQLite instances for local development. No Docker daemon, no container networking, no volume mounts. Just databases running on localhost, ready in seconds.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -176,11 +176,37 @@ spindb deps check --engine mysql
|
|
|
176
176
|
|
|
177
177
|
**Linux users:** MariaDB works as a drop-in replacement for MySQL. If you have MariaDB installed, SpinDB will detect and use it automatically. In a future release, MariaDB will be available as its own engine with support for MariaDB-specific features.
|
|
178
178
|
|
|
179
|
+
#### SQLite
|
|
180
|
+
|
|
181
|
+
| | |
|
|
182
|
+
|---|---|
|
|
183
|
+
| Version | 3 (system) |
|
|
184
|
+
| Default port | N/A (file-based) |
|
|
185
|
+
| Data location | Project directory (CWD) |
|
|
186
|
+
| Binary source | System installation |
|
|
187
|
+
|
|
188
|
+
SQLite is a file-based database—no server process, no ports. Databases are stored in your project directory by default, not `~/.spindb/`. SpinDB tracks registered SQLite databases in a registry file.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Create in current directory
|
|
192
|
+
spindb create mydb --engine sqlite
|
|
193
|
+
|
|
194
|
+
# Create with custom path
|
|
195
|
+
spindb create mydb --engine sqlite --path ./data/mydb.sqlite
|
|
196
|
+
|
|
197
|
+
# Connect to it
|
|
198
|
+
spindb connect mydb
|
|
199
|
+
|
|
200
|
+
# Use litecli for enhanced experience
|
|
201
|
+
spindb connect mydb --litecli
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Note:** Unlike server databases, SQLite databases don't need to be "started" or "stopped"—they're always available as long as the file exists.
|
|
205
|
+
|
|
179
206
|
### Planned Engines
|
|
180
207
|
|
|
181
208
|
| Engine | Type | Status |
|
|
182
209
|
|--------|------|--------|
|
|
183
|
-
| SQLite | File-based | Planned for v1.2 |
|
|
184
210
|
| Redis | In-memory key-value | Planned for v1.2 |
|
|
185
211
|
| MongoDB | Document database | Planned for v1.2 |
|
|
186
212
|
|
|
@@ -195,10 +221,14 @@ spindb deps check --engine mysql
|
|
|
195
221
|
```bash
|
|
196
222
|
spindb create mydb # PostgreSQL (default)
|
|
197
223
|
spindb create mydb --engine mysql # MySQL
|
|
224
|
+
spindb create mydb --engine sqlite # SQLite (file-based)
|
|
198
225
|
spindb create mydb --version 16 # Specific PostgreSQL version
|
|
199
226
|
spindb create mydb --port 5433 # Custom port
|
|
200
227
|
spindb create mydb --database my_app # Custom database name
|
|
201
228
|
spindb create mydb --no-start # Create without starting
|
|
229
|
+
|
|
230
|
+
# SQLite with custom path
|
|
231
|
+
spindb create mydb --engine sqlite --path ./data/app.sqlite
|
|
202
232
|
```
|
|
203
233
|
|
|
204
234
|
Create and restore in one command:
|
|
@@ -213,10 +243,11 @@ spindb create mydb --from "postgresql://user:pass@host:5432/production"
|
|
|
213
243
|
|
|
214
244
|
| Option | Description |
|
|
215
245
|
|--------|-------------|
|
|
216
|
-
| `--engine`, `-e` | Database engine (`postgresql`, `mysql`) |
|
|
246
|
+
| `--engine`, `-e` | Database engine (`postgresql`, `mysql`, `sqlite`) |
|
|
217
247
|
| `--version`, `-v` | Engine version |
|
|
218
|
-
| `--port`, `-p` | Port number |
|
|
248
|
+
| `--port`, `-p` | Port number (not applicable for SQLite) |
|
|
219
249
|
| `--database`, `-d` | Primary database name |
|
|
250
|
+
| `--path` | File path for SQLite databases |
|
|
220
251
|
| `--max-connections` | Maximum database connections (default: 200) |
|
|
221
252
|
| `--from` | Restore from backup file or connection string |
|
|
222
253
|
| `--no-start` | Create without starting |
|
|
@@ -333,11 +364,12 @@ spindb clone source-db new-db
|
|
|
333
364
|
spindb start new-db
|
|
334
365
|
```
|
|
335
366
|
|
|
336
|
-
#### `edit` - Rename, change port, or edit database config
|
|
367
|
+
#### `edit` - Rename, change port, relocate, or edit database config
|
|
337
368
|
|
|
338
369
|
```bash
|
|
339
370
|
spindb edit mydb --name newname # Must be stopped
|
|
340
371
|
spindb edit mydb --port 5433
|
|
372
|
+
spindb edit mydb --relocate ~/new/path # Move SQLite database file
|
|
341
373
|
spindb edit mydb --set-config max_connections=300 # PostgreSQL config
|
|
342
374
|
spindb edit mydb # Interactive mode
|
|
343
375
|
```
|
|
@@ -405,6 +437,43 @@ spindb version --check # Check for updates
|
|
|
405
437
|
spindb self-update
|
|
406
438
|
```
|
|
407
439
|
|
|
440
|
+
#### `doctor` - System health check
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
spindb doctor # Interactive health check
|
|
444
|
+
spindb doctor --json # JSON output for scripting
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Checks performed:
|
|
448
|
+
- Configuration file validity and binary cache freshness
|
|
449
|
+
- Container status across all engines
|
|
450
|
+
- SQLite registry for orphaned entries (files deleted outside SpinDB)
|
|
451
|
+
- Database tool availability
|
|
452
|
+
|
|
453
|
+
Example output:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
SpinDB Health Check
|
|
457
|
+
═══════════════════
|
|
458
|
+
|
|
459
|
+
✓ Configuration
|
|
460
|
+
└─ Configuration valid, 12 tools cached
|
|
461
|
+
|
|
462
|
+
✓ Containers
|
|
463
|
+
└─ 4 container(s)
|
|
464
|
+
postgresql: 2 running, 0 stopped
|
|
465
|
+
mysql: 0 running, 1 stopped
|
|
466
|
+
sqlite: 1 exist, 0 missing
|
|
467
|
+
|
|
468
|
+
⚠ SQLite Registry
|
|
469
|
+
└─ 1 orphaned entry found
|
|
470
|
+
"old-project" → /path/to/missing.sqlite
|
|
471
|
+
|
|
472
|
+
? What would you like to do?
|
|
473
|
+
❯ Remove orphaned entries from registry
|
|
474
|
+
Skip (do nothing)
|
|
475
|
+
```
|
|
476
|
+
|
|
408
477
|
---
|
|
409
478
|
|
|
410
479
|
## Enhanced CLI Tools
|
|
@@ -415,7 +484,7 @@ SpinDB supports enhanced database shells that provide features like auto-complet
|
|
|
415
484
|
|--------|----------|----------|-----------|
|
|
416
485
|
| PostgreSQL | `psql` | `pgcli` | `usql` |
|
|
417
486
|
| MySQL | `mysql` | `mycli` | `usql` |
|
|
418
|
-
| SQLite
|
|
487
|
+
| SQLite | `sqlite3` | `litecli` | `usql` |
|
|
419
488
|
| Redis (planned) | `redis-cli` | `iredis` | - |
|
|
420
489
|
| MongoDB (planned) | `mongosh` | - | - |
|
|
421
490
|
|
|
@@ -456,8 +525,13 @@ spindb connect mydb --install-tui # usql
|
|
|
456
525
|
│ ├── container.json
|
|
457
526
|
│ ├── data/
|
|
458
527
|
│ └── mysql.log
|
|
528
|
+
├── sqlite-registry.json # Tracks SQLite file locations
|
|
459
529
|
├── logs/ # Error logs
|
|
460
530
|
└── config.json # Tool paths cache
|
|
531
|
+
|
|
532
|
+
# SQLite databases are stored in project directories, not ~/.spindb/
|
|
533
|
+
./myproject/
|
|
534
|
+
└── mydb.sqlite # Created with: spindb create mydb -e sqlite
|
|
461
535
|
```
|
|
462
536
|
|
|
463
537
|
### How Data Persists
|
|
@@ -521,7 +595,6 @@ See [TODO.md](TODO.md) for the full roadmap.
|
|
|
521
595
|
- Secrets management (macOS Keychain)
|
|
522
596
|
|
|
523
597
|
### v1.2 - Additional Engines
|
|
524
|
-
- SQLite (file-based, no server)
|
|
525
598
|
- Redis (in-memory key-value)
|
|
526
599
|
- MongoDB (document database)
|
|
527
600
|
- MariaDB as standalone engine
|
package/cli/commands/connect.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import { spawn } from 'child_process'
|
|
3
|
+
import { existsSync } from 'fs'
|
|
3
4
|
import chalk from 'chalk'
|
|
4
5
|
import { containerManager } from '../../core/container-manager'
|
|
5
6
|
import { processManager } from '../../core/process-manager'
|
|
@@ -7,18 +8,22 @@ import {
|
|
|
7
8
|
isUsqlInstalled,
|
|
8
9
|
isPgcliInstalled,
|
|
9
10
|
isMycliInstalled,
|
|
11
|
+
isLitecliInstalled,
|
|
10
12
|
detectPackageManager,
|
|
11
13
|
installUsql,
|
|
12
14
|
installPgcli,
|
|
13
15
|
installMycli,
|
|
16
|
+
installLitecli,
|
|
14
17
|
getUsqlManualInstructions,
|
|
15
18
|
getPgcliManualInstructions,
|
|
16
19
|
getMycliManualInstructions,
|
|
20
|
+
getLitecliManualInstructions,
|
|
17
21
|
} from '../../core/dependency-manager'
|
|
18
22
|
import { getEngine } from '../../engines'
|
|
19
23
|
import { getEngineDefaults } from '../../config/defaults'
|
|
20
24
|
import { promptContainerSelect } from '../ui/prompts'
|
|
21
25
|
import { error, warning, info, success } from '../ui/theme'
|
|
26
|
+
import { Engine } from '../../types'
|
|
22
27
|
|
|
23
28
|
export const connectCommand = new Command('connect')
|
|
24
29
|
.alias('shell')
|
|
@@ -37,6 +42,11 @@ export const connectCommand = new Command('connect')
|
|
|
37
42
|
'Use mycli for enhanced MySQL shell (dropdown auto-completion)',
|
|
38
43
|
)
|
|
39
44
|
.option('--install-mycli', 'Install mycli if not present, then connect')
|
|
45
|
+
.option(
|
|
46
|
+
'--litecli',
|
|
47
|
+
'Use litecli for enhanced SQLite shell (auto-completion, syntax highlighting)',
|
|
48
|
+
)
|
|
49
|
+
.option('--install-litecli', 'Install litecli if not present, then connect')
|
|
40
50
|
.action(
|
|
41
51
|
async (
|
|
42
52
|
name: string | undefined,
|
|
@@ -48,6 +58,8 @@ export const connectCommand = new Command('connect')
|
|
|
48
58
|
installPgcli?: boolean
|
|
49
59
|
mycli?: boolean
|
|
50
60
|
installMycli?: boolean
|
|
61
|
+
litecli?: boolean
|
|
62
|
+
installLitecli?: boolean
|
|
51
63
|
},
|
|
52
64
|
) => {
|
|
53
65
|
try {
|
|
@@ -55,9 +67,15 @@ export const connectCommand = new Command('connect')
|
|
|
55
67
|
|
|
56
68
|
if (!containerName) {
|
|
57
69
|
const containers = await containerManager.list()
|
|
58
|
-
|
|
70
|
+
// SQLite containers are always "available" if file exists, server containers need to be running
|
|
71
|
+
const connectable = containers.filter((c) => {
|
|
72
|
+
if (c.engine === Engine.SQLite) {
|
|
73
|
+
return existsSync(c.database)
|
|
74
|
+
}
|
|
75
|
+
return c.status === 'running'
|
|
76
|
+
})
|
|
59
77
|
|
|
60
|
-
if (
|
|
78
|
+
if (connectable.length === 0) {
|
|
61
79
|
if (containers.length === 0) {
|
|
62
80
|
console.log(
|
|
63
81
|
warning('No containers found. Create one with: spindb create'),
|
|
@@ -73,7 +91,7 @@ export const connectCommand = new Command('connect')
|
|
|
73
91
|
}
|
|
74
92
|
|
|
75
93
|
const selected = await promptContainerSelect(
|
|
76
|
-
|
|
94
|
+
connectable,
|
|
77
95
|
'Select container to connect to:',
|
|
78
96
|
)
|
|
79
97
|
if (!selected) return
|
|
@@ -92,16 +110,29 @@ export const connectCommand = new Command('connect')
|
|
|
92
110
|
const database =
|
|
93
111
|
options.database ?? config.database ?? engineDefaults.superuser
|
|
94
112
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
// SQLite: check file exists instead of running status
|
|
114
|
+
if (engineName === Engine.SQLite) {
|
|
115
|
+
if (!existsSync(config.database)) {
|
|
116
|
+
console.error(
|
|
117
|
+
error(
|
|
118
|
+
`SQLite database file not found: ${config.database}`,
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
process.exit(1)
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Server databases need to be running
|
|
125
|
+
const running = await processManager.isRunning(containerName, {
|
|
126
|
+
engine: engineName,
|
|
127
|
+
})
|
|
128
|
+
if (!running) {
|
|
129
|
+
console.error(
|
|
130
|
+
error(
|
|
131
|
+
`Container "${containerName}" is not running. Start it first.`,
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
process.exit(1)
|
|
135
|
+
}
|
|
105
136
|
}
|
|
106
137
|
|
|
107
138
|
const engine = getEngine(engineName)
|
|
@@ -275,13 +306,74 @@ export const connectCommand = new Command('connect')
|
|
|
275
306
|
}
|
|
276
307
|
}
|
|
277
308
|
|
|
309
|
+
const useLitecli = options.litecli || options.installLitecli
|
|
310
|
+
if (useLitecli) {
|
|
311
|
+
if (engineName !== Engine.SQLite) {
|
|
312
|
+
console.error(error('litecli is only available for SQLite containers'))
|
|
313
|
+
if (engineName === 'postgresql') {
|
|
314
|
+
console.log(chalk.gray('For PostgreSQL, use: spindb connect --pgcli'))
|
|
315
|
+
} else if (engineName === 'mysql') {
|
|
316
|
+
console.log(chalk.gray('For MySQL, use: spindb connect --mycli'))
|
|
317
|
+
}
|
|
318
|
+
process.exit(1)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const litecliInstalled = await isLitecliInstalled()
|
|
322
|
+
|
|
323
|
+
if (!litecliInstalled) {
|
|
324
|
+
if (options.installLitecli) {
|
|
325
|
+
console.log(info('Installing litecli for enhanced SQLite shell...'))
|
|
326
|
+
const pm = await detectPackageManager()
|
|
327
|
+
if (pm) {
|
|
328
|
+
const result = await installLitecli(pm)
|
|
329
|
+
if (result.success) {
|
|
330
|
+
console.log(success('litecli installed successfully!'))
|
|
331
|
+
console.log()
|
|
332
|
+
} else {
|
|
333
|
+
console.error(
|
|
334
|
+
error(`Failed to install litecli: ${result.error}`),
|
|
335
|
+
)
|
|
336
|
+
console.log()
|
|
337
|
+
console.log(chalk.gray('Manual installation:'))
|
|
338
|
+
for (const instruction of getLitecliManualInstructions()) {
|
|
339
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
340
|
+
}
|
|
341
|
+
process.exit(1)
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
console.error(error('No supported package manager found'))
|
|
345
|
+
console.log()
|
|
346
|
+
console.log(chalk.gray('Manual installation:'))
|
|
347
|
+
for (const instruction of getLitecliManualInstructions()) {
|
|
348
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
349
|
+
}
|
|
350
|
+
process.exit(1)
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
console.error(error('litecli is not installed'))
|
|
354
|
+
console.log()
|
|
355
|
+
console.log(chalk.gray('Install litecli for enhanced SQLite shell:'))
|
|
356
|
+
console.log(chalk.cyan(' spindb connect --install-litecli'))
|
|
357
|
+
console.log()
|
|
358
|
+
console.log(chalk.gray('Or install manually:'))
|
|
359
|
+
for (const instruction of getLitecliManualInstructions()) {
|
|
360
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
361
|
+
}
|
|
362
|
+
process.exit(1)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
278
367
|
console.log(info(`Connecting to ${containerName}:${database}...`))
|
|
279
368
|
console.log()
|
|
280
369
|
|
|
281
370
|
let clientCmd: string
|
|
282
371
|
let clientArgs: string[]
|
|
283
372
|
|
|
284
|
-
if (
|
|
373
|
+
if (useLitecli) {
|
|
374
|
+
clientCmd = 'litecli'
|
|
375
|
+
clientArgs = [config.database]
|
|
376
|
+
} else if (usePgcli) {
|
|
285
377
|
clientCmd = 'pgcli'
|
|
286
378
|
clientArgs = [connectionString]
|
|
287
379
|
} else if (useMycli) {
|
|
@@ -298,6 +390,9 @@ export const connectCommand = new Command('connect')
|
|
|
298
390
|
} else if (useUsql) {
|
|
299
391
|
clientCmd = 'usql'
|
|
300
392
|
clientArgs = [connectionString]
|
|
393
|
+
} else if (engineName === Engine.SQLite) {
|
|
394
|
+
clientCmd = 'sqlite3'
|
|
395
|
+
clientArgs = [config.database]
|
|
301
396
|
} else if (engineName === 'mysql') {
|
|
302
397
|
clientCmd = 'mysql'
|
|
303
398
|
clientArgs = [
|
|
@@ -339,6 +434,12 @@ export const connectCommand = new Command('connect')
|
|
|
339
434
|
} else if (clientCmd === 'mycli') {
|
|
340
435
|
console.log(chalk.gray(' Install mycli:'))
|
|
341
436
|
console.log(chalk.cyan(' brew install mycli'))
|
|
437
|
+
} else if (clientCmd === 'litecli') {
|
|
438
|
+
console.log(chalk.gray(' Install litecli:'))
|
|
439
|
+
console.log(chalk.cyan(' brew install litecli'))
|
|
440
|
+
} else if (clientCmd === 'sqlite3') {
|
|
441
|
+
console.log(chalk.gray(' sqlite3 comes with macOS.'))
|
|
442
|
+
console.log(chalk.gray(' If not available, check your PATH.'))
|
|
342
443
|
} else if (engineName === 'mysql') {
|
|
343
444
|
console.log(chalk.gray(' On macOS with Homebrew:'))
|
|
344
445
|
console.log(chalk.cyan(' brew install mysql-client'))
|
package/cli/commands/create.ts
CHANGED
|
@@ -21,6 +21,96 @@ import { platformService } from '../../core/platform-service'
|
|
|
21
21
|
import { startWithRetry } from '../../core/start-with-retry'
|
|
22
22
|
import { TransactionManager } from '../../core/transaction-manager'
|
|
23
23
|
import { Engine } from '../../types'
|
|
24
|
+
import type { BaseEngine } from '../../engines/base-engine'
|
|
25
|
+
import { resolve } from 'path'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Simplified SQLite container creation flow
|
|
29
|
+
* SQLite is file-based, so no port, start/stop, or server management needed
|
|
30
|
+
*/
|
|
31
|
+
async function createSqliteContainer(
|
|
32
|
+
containerName: string,
|
|
33
|
+
dbEngine: BaseEngine,
|
|
34
|
+
version: string,
|
|
35
|
+
options: { path?: string; from?: string | null },
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const { path: filePath, from: restoreLocation } = options
|
|
38
|
+
|
|
39
|
+
// Check dependencies
|
|
40
|
+
const depsSpinner = createSpinner('Checking required tools...')
|
|
41
|
+
depsSpinner.start()
|
|
42
|
+
|
|
43
|
+
const missingDeps = await getMissingDependencies('sqlite')
|
|
44
|
+
if (missingDeps.length > 0) {
|
|
45
|
+
depsSpinner.warn(`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`)
|
|
46
|
+
const installed = await promptInstallDependencies(missingDeps[0].binary, 'sqlite')
|
|
47
|
+
if (!installed) {
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
depsSpinner.succeed('Required tools available')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if container already exists
|
|
55
|
+
while (await containerManager.exists(containerName)) {
|
|
56
|
+
console.log(chalk.yellow(` Container "${containerName}" already exists.`))
|
|
57
|
+
containerName = await promptContainerName()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Determine file path
|
|
61
|
+
const defaultPath = `./${containerName}.sqlite`
|
|
62
|
+
const absolutePath = resolve(filePath || defaultPath)
|
|
63
|
+
|
|
64
|
+
// Check if file already exists
|
|
65
|
+
if (existsSync(absolutePath)) {
|
|
66
|
+
console.error(error(`File already exists: ${absolutePath}`))
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const createSpinnerInstance = createSpinner('Creating SQLite database...')
|
|
71
|
+
createSpinnerInstance.start()
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Initialize the SQLite database file and register in registry
|
|
75
|
+
await dbEngine.initDataDir(containerName, version, { path: absolutePath })
|
|
76
|
+
createSpinnerInstance.succeed('SQLite database created')
|
|
77
|
+
} catch (err) {
|
|
78
|
+
createSpinnerInstance.fail('Failed to create SQLite database')
|
|
79
|
+
throw err
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Handle --from restore
|
|
83
|
+
if (restoreLocation) {
|
|
84
|
+
const config = await containerManager.getConfig(containerName)
|
|
85
|
+
if (config) {
|
|
86
|
+
const format = await dbEngine.detectBackupFormat(restoreLocation)
|
|
87
|
+
const restoreSpinner = createSpinner(`Restoring from ${format.description}...`)
|
|
88
|
+
restoreSpinner.start()
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await dbEngine.restore(config, restoreLocation)
|
|
92
|
+
restoreSpinner.succeed('Backup restored successfully')
|
|
93
|
+
} catch (err) {
|
|
94
|
+
restoreSpinner.fail('Failed to restore backup')
|
|
95
|
+
throw err
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Display success
|
|
101
|
+
console.log()
|
|
102
|
+
console.log(chalk.green(' ✓ SQLite database ready'))
|
|
103
|
+
console.log()
|
|
104
|
+
console.log(chalk.gray(' File path:'))
|
|
105
|
+
console.log(chalk.cyan(` ${absolutePath}`))
|
|
106
|
+
console.log()
|
|
107
|
+
console.log(chalk.gray(' Connection string:'))
|
|
108
|
+
console.log(chalk.cyan(` sqlite:///${absolutePath}`))
|
|
109
|
+
console.log()
|
|
110
|
+
console.log(chalk.gray(' Connect with:'))
|
|
111
|
+
console.log(chalk.cyan(` spindb connect ${containerName}`))
|
|
112
|
+
console.log()
|
|
113
|
+
}
|
|
24
114
|
|
|
25
115
|
function detectLocationType(location: string): {
|
|
26
116
|
type: 'connection' | 'file' | 'not_found'
|
|
@@ -37,7 +127,15 @@ function detectLocationType(location: string): {
|
|
|
37
127
|
return { type: 'connection', inferredEngine: Engine.MySQL }
|
|
38
128
|
}
|
|
39
129
|
|
|
130
|
+
if (location.startsWith('sqlite://')) {
|
|
131
|
+
return { type: 'connection', inferredEngine: Engine.SQLite }
|
|
132
|
+
}
|
|
133
|
+
|
|
40
134
|
if (existsSync(location)) {
|
|
135
|
+
// Check if it's a SQLite file
|
|
136
|
+
if (location.endsWith('.sqlite') || location.endsWith('.db') || location.endsWith('.sqlite3')) {
|
|
137
|
+
return { type: 'file', inferredEngine: Engine.SQLite }
|
|
138
|
+
}
|
|
41
139
|
return { type: 'file' }
|
|
42
140
|
}
|
|
43
141
|
|
|
@@ -47,10 +145,14 @@ function detectLocationType(location: string): {
|
|
|
47
145
|
export const createCommand = new Command('create')
|
|
48
146
|
.description('Create a new database container')
|
|
49
147
|
.argument('[name]', 'Container name')
|
|
50
|
-
.option('-e, --engine <engine>', 'Database engine (postgresql, mysql)')
|
|
148
|
+
.option('-e, --engine <engine>', 'Database engine (postgresql, mysql, sqlite)')
|
|
51
149
|
.option('-v, --version <version>', 'Database version')
|
|
52
150
|
.option('-d, --database <database>', 'Database name')
|
|
53
151
|
.option('-p, --port <port>', 'Port number')
|
|
152
|
+
.option(
|
|
153
|
+
'--path <path>',
|
|
154
|
+
'Path for SQLite database file (default: ./<name>.sqlite)',
|
|
155
|
+
)
|
|
54
156
|
.option(
|
|
55
157
|
'--max-connections <number>',
|
|
56
158
|
'Maximum number of database connections (default: 200)',
|
|
@@ -68,6 +170,7 @@ export const createCommand = new Command('create')
|
|
|
68
170
|
version?: string
|
|
69
171
|
database?: string
|
|
70
172
|
port?: string
|
|
173
|
+
path?: string
|
|
71
174
|
maxConnections?: string
|
|
72
175
|
start: boolean
|
|
73
176
|
from?: string
|
|
@@ -140,6 +243,15 @@ export const createCommand = new Command('create')
|
|
|
140
243
|
|
|
141
244
|
const dbEngine = getEngine(engine)
|
|
142
245
|
|
|
246
|
+
// SQLite has a simplified flow (no port, no start/stop)
|
|
247
|
+
if (engine === Engine.SQLite) {
|
|
248
|
+
await createSqliteContainer(containerName, dbEngine, version, {
|
|
249
|
+
path: options.path,
|
|
250
|
+
from: restoreLocation,
|
|
251
|
+
})
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
143
255
|
const depsSpinner = createSpinner('Checking required tools...')
|
|
144
256
|
depsSpinner.start()
|
|
145
257
|
|