spindb 0.12.2 → 0.13.2
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 +89 -26
- package/cli/commands/connect.ts +92 -0
- package/cli/commands/create.ts +17 -6
- package/cli/commands/engines.ts +95 -12
- package/cli/commands/menu/container-handlers.ts +13 -3
- package/cli/commands/menu/shell-handlers.ts +71 -13
- package/cli/commands/menu/sql-handlers.ts +4 -3
- package/cli/commands/run.ts +37 -21
- package/cli/constants.ts +2 -0
- package/cli/helpers.ts +72 -0
- package/config/engine-defaults.ts +14 -0
- package/config/os-dependencies.ts +95 -0
- package/core/dependency-manager.ts +21 -0
- package/core/port-manager.ts +2 -2
- package/engines/base-engine.ts +9 -0
- package/engines/index.ts +3 -0
- package/engines/mongodb/index.ts +12 -0
- package/engines/mongodb/restore.ts +7 -3
- package/engines/mongodb/version-validator.ts +5 -3
- package/engines/mysql/index.ts +12 -0
- package/engines/postgresql/index.ts +12 -0
- package/engines/redis/backup.ts +169 -0
- package/engines/redis/binary-detection.ts +386 -0
- package/engines/redis/index.ts +858 -0
- package/engines/redis/restore.ts +189 -0
- package/engines/redis/version-validator.ts +133 -0
- package/engines/sqlite/index.ts +1 -1
- package/package.json +5 -3
- package/types/index.ts +10 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
**The first npm CLI for running local databases without Docker.**
|
|
9
9
|
|
|
10
|
-
Spin up PostgreSQL, MySQL, SQLite, and
|
|
10
|
+
Spin up PostgreSQL, MySQL, SQLite, MongoDB, and Redis instances for local development. No Docker daemon, no container networking, no volume mounts. Just databases running on localhost, ready in seconds.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -145,7 +145,7 @@ SpinDB downloads PostgreSQL server binaries automatically:
|
|
|
145
145
|
- **macOS/Linux:** Pre-compiled binaries from the zonky.io project, hosted on Maven Central
|
|
146
146
|
- **Windows:** Official binaries from EnterpriseDB (EDB)
|
|
147
147
|
|
|
148
|
-
**Why download binaries instead of using system PostgreSQL?**
|
|
148
|
+
**Why download binaries instead of using system PostgreSQL?** The [zonky.io project](https://github.com/zonkyio/embedded-postgres-binaries) provides pre-configured, portable PostgreSQL binaries—just extract and run. This lets you run PostgreSQL 14 for one project and 18 for another, side-by-side, without conflicts. No other database engine has an equivalent portable distribution.
|
|
149
149
|
|
|
150
150
|
**Client tools required:** You still need `psql`, `pg_dump`, and `pg_restore` installed on your system for some operations (connecting, backups, restores). SpinDB can install these for you:
|
|
151
151
|
|
|
@@ -162,7 +162,7 @@ spindb deps install --engine postgresql
|
|
|
162
162
|
| Default user | `root` |
|
|
163
163
|
| Binary source | System installation |
|
|
164
164
|
|
|
165
|
-
Unlike PostgreSQL, SpinDB uses your system's MySQL installation.
|
|
165
|
+
Unlike PostgreSQL, SpinDB uses your system's MySQL installation. While Oracle provides MySQL binary downloads, they require system libraries and configuration—there's no "unzip and run" distribution like zonky.io provides for PostgreSQL. For most local development, a single system-installed MySQL version works well.
|
|
166
166
|
|
|
167
167
|
```bash
|
|
168
168
|
# macOS
|
|
@@ -219,7 +219,7 @@ spindb connect mydb --litecli
|
|
|
219
219
|
| Default user | None (no auth by default) |
|
|
220
220
|
| Binary source | System installation |
|
|
221
221
|
|
|
222
|
-
Like MySQL, SpinDB uses your system's MongoDB installation. MongoDB
|
|
222
|
+
Like MySQL, SpinDB uses your system's MongoDB installation. While MongoDB provides official binary downloads, they require additional configuration and system dependencies. SpinDB relies on your package manager to handle this setup.
|
|
223
223
|
|
|
224
224
|
```bash
|
|
225
225
|
# macOS
|
|
@@ -240,20 +240,57 @@ MongoDB uses JavaScript for queries instead of SQL. When using `spindb run`, pas
|
|
|
240
240
|
|
|
241
241
|
```bash
|
|
242
242
|
# Insert a document
|
|
243
|
-
spindb run mydb
|
|
243
|
+
spindb run mydb -c "db.users.insertOne({name: 'Alice', email: 'alice@example.com'})"
|
|
244
244
|
|
|
245
245
|
# Query documents
|
|
246
|
-
spindb run mydb
|
|
246
|
+
spindb run mydb -c "db.users.find().pretty()"
|
|
247
247
|
|
|
248
248
|
# Run a JavaScript file
|
|
249
249
|
spindb run mydb --file ./scripts/seed.js
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
#### Redis
|
|
253
253
|
|
|
254
|
-
|
|
|
255
|
-
|
|
256
|
-
|
|
|
254
|
+
| | |
|
|
255
|
+
|---|---|
|
|
256
|
+
| Versions | 6, 7, 8 |
|
|
257
|
+
| Default port | 6379 |
|
|
258
|
+
| Default user | None (no auth by default) |
|
|
259
|
+
| Binary source | System installation |
|
|
260
|
+
|
|
261
|
+
Like MySQL and MongoDB, SpinDB uses your system's Redis installation. Redis provides embeddable binaries, but system packages are more reliable for handling dependencies and platform-specific setup.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# macOS
|
|
265
|
+
brew install redis
|
|
266
|
+
|
|
267
|
+
# Ubuntu/Debian
|
|
268
|
+
sudo apt install redis-server redis-tools
|
|
269
|
+
|
|
270
|
+
# Windows (Chocolatey)
|
|
271
|
+
choco install redis
|
|
272
|
+
|
|
273
|
+
# Check if SpinDB can find Redis
|
|
274
|
+
spindb deps check --engine redis
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Redis uses numbered databases (0-15) instead of named databases. When using `spindb run`, pass Redis commands:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
# Set a key
|
|
281
|
+
spindb run myredis -c "SET mykey myvalue"
|
|
282
|
+
|
|
283
|
+
# Get a key
|
|
284
|
+
spindb run myredis -c "GET mykey"
|
|
285
|
+
|
|
286
|
+
# Run a Redis command file
|
|
287
|
+
spindb run myredis --file ./scripts/seed.redis
|
|
288
|
+
|
|
289
|
+
# Use iredis for enhanced shell experience
|
|
290
|
+
spindb connect myredis --iredis
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Note:** Redis doesn't support remote dump/restore. Creating containers from remote Redis connection strings is not supported. Use `backup` and `restore` commands for data migration.
|
|
257
294
|
|
|
258
295
|
---
|
|
259
296
|
|
|
@@ -267,7 +304,7 @@ spindb run mydb --file ./scripts/seed.js
|
|
|
267
304
|
spindb create mydb # PostgreSQL (default)
|
|
268
305
|
spindb create mydb --engine mysql # MySQL
|
|
269
306
|
spindb create mydb --engine sqlite # SQLite (file-based)
|
|
270
|
-
spindb create mydb --version 16
|
|
307
|
+
spindb create mydb --db-version 16 # Specific PostgreSQL version
|
|
271
308
|
spindb create mydb --port 5433 # Custom port
|
|
272
309
|
spindb create mydb --database my_app # Custom database name
|
|
273
310
|
spindb create mydb --no-start # Create without starting
|
|
@@ -291,10 +328,10 @@ spindb create mydb --from "postgresql://user:pass@host:5432/production"
|
|
|
291
328
|
|
|
292
329
|
| Option | Description |
|
|
293
330
|
|--------|-------------|
|
|
294
|
-
| `--engine`, `-e` | Database engine (`postgresql`, `mysql`, `sqlite`) |
|
|
295
|
-
| `--version
|
|
331
|
+
| `--engine`, `-e` | Database engine (`postgresql`, `mysql`, `sqlite`, `mongodb`, `redis`) |
|
|
332
|
+
| `--db-version` | Engine version (e.g., 17 for PostgreSQL, 8 for Redis) |
|
|
296
333
|
| `--port`, `-p` | Port number (not applicable for SQLite) |
|
|
297
|
-
| `--database`, `-d` | Primary database name |
|
|
334
|
+
| `--database`, `-d` | Primary database name (Redis uses 0-15) |
|
|
298
335
|
| `--path` | File path for SQLite databases |
|
|
299
336
|
| `--max-connections` | Maximum database connections (default: 200) |
|
|
300
337
|
| `--from` | Restore from backup file or connection string |
|
|
@@ -355,16 +392,20 @@ spindb connect mydb --install-mycli
|
|
|
355
392
|
spindb connect mydb --install-tui
|
|
356
393
|
```
|
|
357
394
|
|
|
358
|
-
#### `run` - Execute SQL/scripts
|
|
395
|
+
#### `run` - Execute SQL/scripts/commands
|
|
359
396
|
|
|
360
397
|
```bash
|
|
361
|
-
spindb run mydb script.sql
|
|
362
|
-
spindb run mydb
|
|
363
|
-
spindb run mydb seed.sql --database my_app
|
|
398
|
+
spindb run mydb script.sql # Run a SQL file
|
|
399
|
+
spindb run mydb -c "SELECT * FROM users" # Run inline SQL
|
|
400
|
+
spindb run mydb seed.sql --database my_app # Target specific database
|
|
364
401
|
|
|
365
402
|
# MongoDB uses JavaScript instead of SQL
|
|
366
|
-
spindb run mydb seed.js
|
|
367
|
-
spindb run mydb
|
|
403
|
+
spindb run mydb seed.js # Run a JavaScript file
|
|
404
|
+
spindb run mydb -c "db.users.find().pretty()" # Run inline JavaScript
|
|
405
|
+
|
|
406
|
+
# Redis uses Redis commands
|
|
407
|
+
spindb run myredis -c "SET foo bar" # Run inline command
|
|
408
|
+
spindb run myredis seed.redis # Run command file
|
|
368
409
|
```
|
|
369
410
|
|
|
370
411
|
#### `url` - Get connection string
|
|
@@ -596,7 +637,7 @@ SpinDB supports enhanced database shells that provide features like auto-complet
|
|
|
596
637
|
| MySQL | `mysql` | `mycli` | `usql` |
|
|
597
638
|
| SQLite | `sqlite3` | `litecli` | `usql` |
|
|
598
639
|
| MongoDB | `mongosh` | - | `usql` |
|
|
599
|
-
| Redis
|
|
640
|
+
| Redis | `redis-cli` | `iredis` | - |
|
|
600
641
|
|
|
601
642
|
**pgcli / mycli** provide:
|
|
602
643
|
- Intelligent auto-completion (tables, columns, keywords)
|
|
@@ -682,15 +723,38 @@ When you stop a container:
|
|
|
682
723
|
- **macOS/Linux:** From [zonky.io/embedded-postgres-binaries](https://github.com/zonkyio/embedded-postgres-binaries), hosted on Maven Central
|
|
683
724
|
- **Windows:** From [EnterpriseDB (EDB)](https://www.enterprisedb.com/download-postgresql-binaries), official PostgreSQL distributions
|
|
684
725
|
|
|
685
|
-
**MySQL:** Uses your system
|
|
726
|
+
**MySQL/MongoDB/Redis:** Uses your system installation. SpinDB detects binaries from Homebrew (macOS), apt/pacman (Linux), or Chocolatey/winget/Scoop (Windows).
|
|
727
|
+
|
|
728
|
+
### Why Precompiled Binaries for PostgreSQL, but System Installs for Others?
|
|
729
|
+
|
|
730
|
+
This isn't a preference—it's a practical reality of what's available.
|
|
731
|
+
|
|
732
|
+
**PostgreSQL has an excellent embedded binary distribution.** The [zonky.io](https://github.com/zonkyio/embedded-postgres-binaries) project maintains minimal, self-contained PostgreSQL server binaries specifically designed for embedding:
|
|
733
|
+
|
|
734
|
+
- Cross-platform (macOS Intel/ARM, Linux x64/ARM, Windows)
|
|
735
|
+
- Hosted on Maven Central (highly reliable CDN)
|
|
736
|
+
- ~45 MB per version
|
|
737
|
+
- Actively maintained with new PostgreSQL releases
|
|
738
|
+
|
|
739
|
+
This makes multi-version support trivial: need PostgreSQL 14 for a legacy project and 18 for a new one? SpinDB downloads both, and they run side-by-side without conflicts.
|
|
740
|
+
|
|
741
|
+
**No equivalent exists for MySQL or MongoDB.** Neither database has a comparable embedded binary project:
|
|
742
|
+
|
|
743
|
+
- **MySQL:** Oracle distributes MySQL as large installers with system dependencies, not embeddable binaries. There's no "zonky.io for MySQL."
|
|
744
|
+
- **MongoDB:** Server binaries are several hundred MB with complex licensing around redistribution. MongoDB Inc. doesn't provide an embedded distribution.
|
|
745
|
+
|
|
746
|
+
For these databases, system packages (Homebrew, apt, choco) are the most reliable option. They handle dependencies, platform quirks, and security updates. SpinDB simply orchestrates what's already installed.
|
|
747
|
+
|
|
748
|
+
**Does this limit multi-version support?** Yes, for MySQL/MongoDB you get whatever version your package manager provides. In practice, this is rarely a problem—developers seldom need multiple MySQL versions simultaneously. If zonky.io-style distributions emerged for other databases, SpinDB could adopt them.
|
|
686
749
|
|
|
687
750
|
---
|
|
688
751
|
|
|
689
752
|
## Limitations
|
|
690
753
|
|
|
691
|
-
- **Client tools required** - `psql` and `
|
|
754
|
+
- **Client tools required** - `psql`, `mysql`, `mongosh`, and `redis-cli` must be installed separately for some operations (connecting, backups, restores)
|
|
692
755
|
- **Local only** - Databases bind to `127.0.0.1`; remote connections planned for v1.1
|
|
693
|
-
- **
|
|
756
|
+
- **Single version for MySQL/MongoDB/Redis** - Unlike PostgreSQL, MySQL, MongoDB, and Redis use system installations, so you're limited to one version per machine (see [Why Precompiled Binaries for PostgreSQL?](#why-precompiled-binaries-for-postgresql-but-system-installs-for-others))
|
|
757
|
+
- **Redis remote dump not supported** - Redis doesn't support creating containers from remote connection strings. Use backup/restore for data migration.
|
|
694
758
|
|
|
695
759
|
---
|
|
696
760
|
|
|
@@ -704,9 +768,8 @@ See [TODO.md](TODO.md) for the full roadmap.
|
|
|
704
768
|
- Secrets management (macOS Keychain)
|
|
705
769
|
|
|
706
770
|
### v1.2 - Additional Engines
|
|
707
|
-
- Redis (in-memory key-value)
|
|
708
|
-
- MongoDB (document database)
|
|
709
771
|
- MariaDB as standalone engine
|
|
772
|
+
- CockroachDB (distributed SQL)
|
|
710
773
|
|
|
711
774
|
### v1.3 - Advanced Features
|
|
712
775
|
- Container templates
|
package/cli/commands/connect.ts
CHANGED
|
@@ -9,15 +9,18 @@ import {
|
|
|
9
9
|
isPgcliInstalled,
|
|
10
10
|
isMycliInstalled,
|
|
11
11
|
isLitecliInstalled,
|
|
12
|
+
isIredisInstalled,
|
|
12
13
|
detectPackageManager,
|
|
13
14
|
installUsql,
|
|
14
15
|
installPgcli,
|
|
15
16
|
installMycli,
|
|
16
17
|
installLitecli,
|
|
18
|
+
installIredis,
|
|
17
19
|
getUsqlManualInstructions,
|
|
18
20
|
getPgcliManualInstructions,
|
|
19
21
|
getMycliManualInstructions,
|
|
20
22
|
getLitecliManualInstructions,
|
|
23
|
+
getIredisManualInstructions,
|
|
21
24
|
} from '../../core/dependency-manager'
|
|
22
25
|
import { getEngine } from '../../engines'
|
|
23
26
|
import { getEngineDefaults } from '../../config/defaults'
|
|
@@ -47,6 +50,11 @@ export const connectCommand = new Command('connect')
|
|
|
47
50
|
'Use litecli for enhanced SQLite shell (auto-completion, syntax highlighting)',
|
|
48
51
|
)
|
|
49
52
|
.option('--install-litecli', 'Install litecli if not present, then connect')
|
|
53
|
+
.option(
|
|
54
|
+
'--iredis',
|
|
55
|
+
'Use iredis for enhanced Redis shell (auto-completion, syntax highlighting)',
|
|
56
|
+
)
|
|
57
|
+
.option('--install-iredis', 'Install iredis if not present, then connect')
|
|
50
58
|
.action(
|
|
51
59
|
async (
|
|
52
60
|
name: string | undefined,
|
|
@@ -60,6 +68,8 @@ export const connectCommand = new Command('connect')
|
|
|
60
68
|
installMycli?: boolean
|
|
61
69
|
litecli?: boolean
|
|
62
70
|
installLitecli?: boolean
|
|
71
|
+
iredis?: boolean
|
|
72
|
+
installIredis?: boolean
|
|
63
73
|
},
|
|
64
74
|
) => {
|
|
65
75
|
try {
|
|
@@ -376,6 +386,76 @@ export const connectCommand = new Command('connect')
|
|
|
376
386
|
}
|
|
377
387
|
}
|
|
378
388
|
|
|
389
|
+
const useIredis = options.iredis || options.installIredis
|
|
390
|
+
if (useIredis) {
|
|
391
|
+
if (engineName !== Engine.Redis) {
|
|
392
|
+
console.error(
|
|
393
|
+
uiError('iredis is only available for Redis containers'),
|
|
394
|
+
)
|
|
395
|
+
if (engineName === 'postgresql') {
|
|
396
|
+
console.log(
|
|
397
|
+
chalk.gray('For PostgreSQL, use: spindb connect --pgcli'),
|
|
398
|
+
)
|
|
399
|
+
} else if (engineName === 'mysql') {
|
|
400
|
+
console.log(chalk.gray('For MySQL, use: spindb connect --mycli'))
|
|
401
|
+
} else if (engineName === Engine.SQLite) {
|
|
402
|
+
console.log(
|
|
403
|
+
chalk.gray('For SQLite, use: spindb connect --litecli'),
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
process.exit(1)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const iredisInstalled = await isIredisInstalled()
|
|
410
|
+
|
|
411
|
+
if (!iredisInstalled) {
|
|
412
|
+
if (options.installIredis) {
|
|
413
|
+
console.log(
|
|
414
|
+
uiInfo('Installing iredis for enhanced Redis shell...'),
|
|
415
|
+
)
|
|
416
|
+
const pm = await detectPackageManager()
|
|
417
|
+
if (pm) {
|
|
418
|
+
const result = await installIredis(pm)
|
|
419
|
+
if (result.success) {
|
|
420
|
+
console.log(uiSuccess('iredis installed successfully!'))
|
|
421
|
+
console.log()
|
|
422
|
+
} else {
|
|
423
|
+
console.error(
|
|
424
|
+
uiError(`Failed to install iredis: ${result.error}`),
|
|
425
|
+
)
|
|
426
|
+
console.log()
|
|
427
|
+
console.log(chalk.gray('Manual installation:'))
|
|
428
|
+
for (const instruction of getIredisManualInstructions()) {
|
|
429
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
430
|
+
}
|
|
431
|
+
process.exit(1)
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
console.error(uiError('No supported package manager found'))
|
|
435
|
+
console.log()
|
|
436
|
+
console.log(chalk.gray('Manual installation:'))
|
|
437
|
+
for (const instruction of getIredisManualInstructions()) {
|
|
438
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
439
|
+
}
|
|
440
|
+
process.exit(1)
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
console.error(uiError('iredis is not installed'))
|
|
444
|
+
console.log()
|
|
445
|
+
console.log(
|
|
446
|
+
chalk.gray('Install iredis for enhanced Redis shell:'),
|
|
447
|
+
)
|
|
448
|
+
console.log(chalk.cyan(' spindb connect --install-iredis'))
|
|
449
|
+
console.log()
|
|
450
|
+
console.log(chalk.gray('Or install manually:'))
|
|
451
|
+
for (const instruction of getIredisManualInstructions()) {
|
|
452
|
+
console.log(chalk.cyan(` ${instruction}`))
|
|
453
|
+
}
|
|
454
|
+
process.exit(1)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
379
459
|
console.log(uiInfo(`Connecting to ${containerName}:${database}...`))
|
|
380
460
|
console.log()
|
|
381
461
|
|
|
@@ -385,6 +465,9 @@ export const connectCommand = new Command('connect')
|
|
|
385
465
|
if (useLitecli) {
|
|
386
466
|
clientCmd = 'litecli'
|
|
387
467
|
clientArgs = [config.database]
|
|
468
|
+
} else if (useIredis) {
|
|
469
|
+
clientCmd = 'iredis'
|
|
470
|
+
clientArgs = ['-h', '127.0.0.1', '-p', String(config.port), '-n', database]
|
|
388
471
|
} else if (usePgcli) {
|
|
389
472
|
clientCmd = 'pgcli'
|
|
390
473
|
clientArgs = [connectionString]
|
|
@@ -405,6 +488,9 @@ export const connectCommand = new Command('connect')
|
|
|
405
488
|
} else if (engineName === Engine.SQLite) {
|
|
406
489
|
clientCmd = 'sqlite3'
|
|
407
490
|
clientArgs = [config.database]
|
|
491
|
+
} else if (engineName === Engine.Redis) {
|
|
492
|
+
clientCmd = 'redis-cli'
|
|
493
|
+
clientArgs = ['-h', '127.0.0.1', '-p', String(config.port), '-n', database]
|
|
408
494
|
} else if (engineName === 'mysql') {
|
|
409
495
|
clientCmd = 'mysql'
|
|
410
496
|
clientArgs = [
|
|
@@ -449,6 +535,12 @@ export const connectCommand = new Command('connect')
|
|
|
449
535
|
} else if (clientCmd === 'litecli') {
|
|
450
536
|
console.log(chalk.gray(' Install litecli:'))
|
|
451
537
|
console.log(chalk.cyan(' brew install litecli'))
|
|
538
|
+
} else if (clientCmd === 'iredis') {
|
|
539
|
+
console.log(chalk.gray(' Install iredis:'))
|
|
540
|
+
console.log(chalk.cyan(' pip install iredis'))
|
|
541
|
+
} else if (clientCmd === 'redis-cli') {
|
|
542
|
+
console.log(chalk.gray(' Install Redis:'))
|
|
543
|
+
console.log(chalk.cyan(' brew install redis'))
|
|
452
544
|
} else if (clientCmd === 'sqlite3') {
|
|
453
545
|
console.log(chalk.gray(' sqlite3 comes with macOS.'))
|
|
454
546
|
console.log(chalk.gray(' If not available, check your PATH.'))
|
package/cli/commands/create.ts
CHANGED
|
@@ -178,6 +178,10 @@ function detectLocationType(location: string): {
|
|
|
178
178
|
return { type: 'connection', inferredEngine: Engine.SQLite }
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
if (location.startsWith('redis://') || location.startsWith('rediss://')) {
|
|
182
|
+
return { type: 'connection', inferredEngine: Engine.Redis }
|
|
183
|
+
}
|
|
184
|
+
|
|
181
185
|
if (existsSync(location)) {
|
|
182
186
|
// Check if it's a SQLite file (case-insensitive)
|
|
183
187
|
const lowerLocation = location.toLowerCase()
|
|
@@ -199,9 +203,9 @@ export const createCommand = new Command('create')
|
|
|
199
203
|
.argument('[name]', 'Container name')
|
|
200
204
|
.option(
|
|
201
205
|
'-e, --engine <engine>',
|
|
202
|
-
'Database engine (postgresql, mysql, sqlite)',
|
|
206
|
+
'Database engine (postgresql, mysql, sqlite, mongodb, redis)',
|
|
203
207
|
)
|
|
204
|
-
.option('-
|
|
208
|
+
.option('--db-version <version>', 'Database version (e.g., 17, 8.0)')
|
|
205
209
|
.option('-d, --database <database>', 'Database name')
|
|
206
210
|
.option('-p, --port <port>', 'Port number')
|
|
207
211
|
.option(
|
|
@@ -225,7 +229,7 @@ export const createCommand = new Command('create')
|
|
|
225
229
|
name: string | undefined,
|
|
226
230
|
options: {
|
|
227
231
|
engine?: string
|
|
228
|
-
|
|
232
|
+
dbVersion?: string
|
|
229
233
|
database?: string
|
|
230
234
|
port?: string
|
|
231
235
|
path?: string
|
|
@@ -241,7 +245,7 @@ export const createCommand = new Command('create')
|
|
|
241
245
|
try {
|
|
242
246
|
let containerName = name
|
|
243
247
|
let engine: Engine = (options.engine as Engine) || Engine.PostgreSQL
|
|
244
|
-
let version = options.
|
|
248
|
+
let version = options.dbVersion
|
|
245
249
|
let database = options.database
|
|
246
250
|
|
|
247
251
|
let restoreLocation: string | null = null
|
|
@@ -296,10 +300,17 @@ export const createCommand = new Command('create')
|
|
|
296
300
|
database = answers.database
|
|
297
301
|
}
|
|
298
302
|
|
|
299
|
-
|
|
303
|
+
// Redis uses numbered databases (0-15), default to "0"
|
|
304
|
+
// Other engines default to container name
|
|
305
|
+
if (engine === Engine.Redis) {
|
|
306
|
+
database = database ?? '0'
|
|
307
|
+
} else {
|
|
308
|
+
database = database ?? containerName
|
|
309
|
+
}
|
|
300
310
|
|
|
301
311
|
// Validate database name to prevent SQL injection
|
|
302
|
-
|
|
312
|
+
// Skip for Redis which uses numbered databases (0-15)
|
|
313
|
+
if (engine !== Engine.Redis && !isValidDatabaseName(database)) {
|
|
303
314
|
console.error(
|
|
304
315
|
uiError(
|
|
305
316
|
'Database name must start with a letter and contain only letters, numbers, hyphens, and underscores',
|
package/cli/commands/engines.ts
CHANGED
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
type InstalledPostgresEngine,
|
|
25
25
|
type InstalledMysqlEngine,
|
|
26
26
|
type InstalledSqliteEngine,
|
|
27
|
+
type InstalledMongodbEngine,
|
|
28
|
+
type InstalledRedisEngine,
|
|
27
29
|
} from '../helpers'
|
|
28
30
|
import { Engine } from '../../types'
|
|
29
31
|
|
|
@@ -36,6 +38,25 @@ function padWithEmoji(str: string, width: number): string {
|
|
|
36
38
|
return str.padEnd(width + emojiCount)
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Display manual installation instructions for missing dependencies
|
|
43
|
+
*/
|
|
44
|
+
function displayManualInstallInstructions(
|
|
45
|
+
missingDeps: Array<{ dependency: { name: string }; installed: boolean }>,
|
|
46
|
+
): void {
|
|
47
|
+
const platform = getCurrentPlatform()
|
|
48
|
+
for (const status of missingDeps) {
|
|
49
|
+
const instructions = getManualInstallInstructions(
|
|
50
|
+
status.dependency as Parameters<typeof getManualInstallInstructions>[0],
|
|
51
|
+
platform,
|
|
52
|
+
)
|
|
53
|
+
console.log(chalk.gray(` ${status.dependency.name}:`))
|
|
54
|
+
for (const instruction of instructions) {
|
|
55
|
+
console.log(chalk.gray(` ${instruction}`))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
/**
|
|
40
61
|
* List subcommand action
|
|
41
62
|
*/
|
|
@@ -72,6 +93,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
72
93
|
const sqliteEngine = engines.find(
|
|
73
94
|
(e): e is InstalledSqliteEngine => e.engine === 'sqlite',
|
|
74
95
|
)
|
|
96
|
+
const mongodbEngine = engines.find(
|
|
97
|
+
(e): e is InstalledMongodbEngine => e.engine === 'mongodb',
|
|
98
|
+
)
|
|
99
|
+
const redisEngine = engines.find(
|
|
100
|
+
(e): e is InstalledRedisEngine => e.engine === 'redis',
|
|
101
|
+
)
|
|
75
102
|
|
|
76
103
|
// Calculate total size for PostgreSQL
|
|
77
104
|
const totalPgSize = pgEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
|
|
@@ -131,6 +158,34 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
131
158
|
)
|
|
132
159
|
}
|
|
133
160
|
|
|
161
|
+
// MongoDB row
|
|
162
|
+
if (mongodbEngine) {
|
|
163
|
+
const icon = ENGINE_ICONS.mongodb
|
|
164
|
+
const engineDisplay = `${icon} mongodb`
|
|
165
|
+
|
|
166
|
+
console.log(
|
|
167
|
+
chalk.gray(' ') +
|
|
168
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
169
|
+
chalk.yellow(mongodbEngine.version.padEnd(12)) +
|
|
170
|
+
chalk.gray('system'.padEnd(18)) +
|
|
171
|
+
chalk.gray('(system-installed)'),
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Redis row
|
|
176
|
+
if (redisEngine) {
|
|
177
|
+
const icon = ENGINE_ICONS.redis
|
|
178
|
+
const engineDisplay = `${icon} redis`
|
|
179
|
+
|
|
180
|
+
console.log(
|
|
181
|
+
chalk.gray(' ') +
|
|
182
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
183
|
+
chalk.yellow(redisEngine.version.padEnd(12)) +
|
|
184
|
+
chalk.gray('system'.padEnd(18)) +
|
|
185
|
+
chalk.gray('(system-installed)'),
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
134
189
|
console.log(chalk.gray(' ' + '─'.repeat(55)))
|
|
135
190
|
|
|
136
191
|
// Summary
|
|
@@ -150,6 +205,16 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
150
205
|
chalk.gray(` SQLite: system-installed at ${sqliteEngine.path}`),
|
|
151
206
|
)
|
|
152
207
|
}
|
|
208
|
+
if (mongodbEngine) {
|
|
209
|
+
console.log(
|
|
210
|
+
chalk.gray(` MongoDB: system-installed at ${mongodbEngine.path}`),
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
if (redisEngine) {
|
|
214
|
+
console.log(
|
|
215
|
+
chalk.gray(` Redis: system-installed at ${redisEngine.path}`),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
153
218
|
console.log()
|
|
154
219
|
}
|
|
155
220
|
|
|
@@ -283,18 +348,8 @@ async function installEngineViaPackageManager(
|
|
|
283
348
|
console.error(uiError('No supported package manager found.'))
|
|
284
349
|
console.log()
|
|
285
350
|
console.log(chalk.yellow('Manual installation instructions:'))
|
|
286
|
-
const platform = getCurrentPlatform()
|
|
287
351
|
const missingDeps = statuses.filter((s) => !s.installed)
|
|
288
|
-
|
|
289
|
-
const instructions = getManualInstallInstructions(
|
|
290
|
-
status.dependency,
|
|
291
|
-
platform,
|
|
292
|
-
)
|
|
293
|
-
console.log(chalk.gray(` ${status.dependency.name}:`))
|
|
294
|
-
for (const instruction of instructions) {
|
|
295
|
-
console.log(chalk.gray(` ${instruction}`))
|
|
296
|
-
}
|
|
297
|
-
}
|
|
352
|
+
displayManualInstallInstructions(missingDeps)
|
|
298
353
|
process.exit(1)
|
|
299
354
|
}
|
|
300
355
|
|
|
@@ -329,6 +384,24 @@ async function installEngineViaPackageManager(
|
|
|
329
384
|
}
|
|
330
385
|
process.exit(1)
|
|
331
386
|
}
|
|
387
|
+
|
|
388
|
+
// Check if some dependencies couldn't be installed because the package manager
|
|
389
|
+
// doesn't have a package definition for them (e.g., Redis on Windows with Chocolatey)
|
|
390
|
+
if (results.length === 0) {
|
|
391
|
+
const stillMissing = statuses.filter((s) => !s.installed)
|
|
392
|
+
if (stillMissing.length > 0) {
|
|
393
|
+
console.log()
|
|
394
|
+
console.log(
|
|
395
|
+
uiWarning(
|
|
396
|
+
`${packageManager.name} doesn't have packages for ${displayName}.`,
|
|
397
|
+
),
|
|
398
|
+
)
|
|
399
|
+
console.log()
|
|
400
|
+
console.log(chalk.yellow('Manual installation required:'))
|
|
401
|
+
displayManualInstallInstructions(stillMissing)
|
|
402
|
+
process.exit(1)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
332
405
|
}
|
|
333
406
|
|
|
334
407
|
// Main engines command
|
|
@@ -430,9 +503,19 @@ enginesCommand
|
|
|
430
503
|
return
|
|
431
504
|
}
|
|
432
505
|
|
|
506
|
+
if (['mongodb', 'mongo'].includes(normalizedEngine)) {
|
|
507
|
+
await installEngineViaPackageManager('mongodb', 'MongoDB')
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (normalizedEngine === 'redis') {
|
|
512
|
+
await installEngineViaPackageManager('redis', 'Redis')
|
|
513
|
+
return
|
|
514
|
+
}
|
|
515
|
+
|
|
433
516
|
console.error(
|
|
434
517
|
uiError(
|
|
435
|
-
`Unknown engine "${engineName}". Supported: postgresql, mysql, sqlite`,
|
|
518
|
+
`Unknown engine "${engineName}". Supported: postgresql, mysql, sqlite, mongodb, redis`,
|
|
436
519
|
),
|
|
437
520
|
)
|
|
438
521
|
process.exit(1)
|
|
@@ -94,7 +94,13 @@ export async function handleCreate(): Promise<'main' | void> {
|
|
|
94
94
|
const name = containerName!
|
|
95
95
|
|
|
96
96
|
// Step 4: Database name (defaults to container name, sanitized)
|
|
97
|
-
|
|
97
|
+
// Redis uses numbered databases 0-15, so skip prompt and default to "0"
|
|
98
|
+
let database: string
|
|
99
|
+
if (engine === 'redis') {
|
|
100
|
+
database = '0'
|
|
101
|
+
} else {
|
|
102
|
+
database = await promptDatabaseName(name, engine)
|
|
103
|
+
}
|
|
98
104
|
|
|
99
105
|
// Step 5: Port or SQLite path
|
|
100
106
|
const isSQLite = engine === 'sqlite'
|
|
@@ -601,9 +607,13 @@ export async function showContainerSubmenu(
|
|
|
601
607
|
|
|
602
608
|
// Run SQL/script - always enabled for SQLite (if file exists), server databases need to be running
|
|
603
609
|
const canRunSql = isSQLite ? existsSync(config.database) : isRunning
|
|
604
|
-
// MongoDB uses
|
|
610
|
+
// Engine-specific terminology: Redis uses commands, MongoDB uses scripts, others use SQL
|
|
605
611
|
const runScriptLabel =
|
|
606
|
-
config.engine === '
|
|
612
|
+
config.engine === 'redis'
|
|
613
|
+
? 'Run command file'
|
|
614
|
+
: config.engine === 'mongodb'
|
|
615
|
+
? 'Run script file'
|
|
616
|
+
: 'Run SQL file'
|
|
607
617
|
actionChoices.push({
|
|
608
618
|
name: canRunSql
|
|
609
619
|
? `${chalk.yellow('▷')} ${runScriptLabel}`
|