spindb 0.13.4 → 0.15.1
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 +63 -24
- package/cli/commands/engines.ts +102 -17
- package/cli/commands/menu/engine-handlers.ts +34 -2
- package/cli/commands/menu/shell-handlers.ts +21 -0
- package/cli/commands/start.ts +47 -1
- package/cli/commands/stop.ts +76 -2
- package/cli/constants.ts +4 -1
- package/cli/helpers.ts +94 -0
- package/config/defaults.ts +7 -7
- package/config/engine-defaults.ts +15 -1
- package/core/binary-manager.ts +130 -73
- package/core/process-manager.ts +66 -0
- package/core/transaction-manager.ts +2 -2
- package/engines/base-engine.ts +8 -0
- package/engines/index.ts +4 -1
- package/engines/mariadb/backup.ts +232 -0
- package/engines/mariadb/binary-manager.ts +456 -0
- package/engines/mariadb/binary-urls.ts +127 -0
- package/engines/mariadb/hostdb-releases.ts +271 -0
- package/engines/mariadb/index.ts +1009 -0
- package/engines/mariadb/restore.ts +344 -0
- package/engines/mariadb/version-maps.ts +65 -0
- package/engines/mariadb/version-validator.ts +193 -0
- package/engines/postgresql/binary-urls.ts +84 -111
- package/engines/postgresql/hostdb-releases.ts +256 -0
- package/engines/postgresql/version-maps.ts +7 -10
- package/package.json +8 -4
- package/types/index.ts +11 -0
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, MongoDB, and Redis instances for local development. No Docker daemon, no container networking, no volume mounts. Just databases running on localhost, ready in seconds.
|
|
10
|
+
Spin up PostgreSQL, MySQL, MariaDB, 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
|
|
|
@@ -139,20 +139,40 @@ You'll get an interactive menu with arrow-key navigation:
|
|
|
139
139
|
| Versions | 14, 15, 16, 17, 18 |
|
|
140
140
|
| Default port | 5432 |
|
|
141
141
|
| Default user | `postgres` |
|
|
142
|
-
| Binary source | [
|
|
142
|
+
| Binary source | [hostdb](https://github.com/robertjbass/hostdb) (macOS/Linux), [EDB](https://www.enterprisedb.com/) (Windows) |
|
|
143
143
|
|
|
144
144
|
SpinDB downloads PostgreSQL server binaries automatically:
|
|
145
|
-
- **macOS/Linux:** Pre-compiled binaries from
|
|
145
|
+
- **macOS/Linux:** Pre-compiled binaries from [hostdb](https://github.com/robertjbass/hostdb) on GitHub Releases
|
|
146
146
|
- **Windows:** Official binaries from EnterpriseDB (EDB)
|
|
147
147
|
|
|
148
|
-
**Why download binaries instead of using system PostgreSQL?** The
|
|
148
|
+
**Why download binaries instead of using system PostgreSQL?** The hostdb project 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.
|
|
149
149
|
|
|
150
|
-
**Client tools
|
|
150
|
+
**Client tools included:** PostgreSQL binaries include `psql`, `pg_dump`, and `pg_restore` for all operations.
|
|
151
|
+
|
|
152
|
+
#### MariaDB
|
|
153
|
+
|
|
154
|
+
| | |
|
|
155
|
+
|---|---|
|
|
156
|
+
| Versions | 10.11, 11.4, 11.8 |
|
|
157
|
+
| Default port | 3307 |
|
|
158
|
+
| Default user | `root` |
|
|
159
|
+
| Binary source | [hostdb](https://github.com/robertjbass/hostdb) |
|
|
160
|
+
|
|
161
|
+
SpinDB downloads MariaDB server binaries automatically from [hostdb](https://github.com/robertjbass/hostdb) on GitHub Releases—just like PostgreSQL. This provides multi-version support and works across all platforms.
|
|
151
162
|
|
|
152
163
|
```bash
|
|
153
|
-
|
|
164
|
+
# Create a MariaDB container
|
|
165
|
+
spindb create mydb --engine mariadb
|
|
166
|
+
|
|
167
|
+
# Or using the alias
|
|
168
|
+
spindb create mydb -e maria
|
|
169
|
+
|
|
170
|
+
# Check what's available
|
|
171
|
+
spindb deps check --engine mariadb
|
|
154
172
|
```
|
|
155
173
|
|
|
174
|
+
MariaDB is MySQL-compatible, so most MySQL tools and clients work seamlessly. If you need MySQL-specific features, use the `mysql` engine instead.
|
|
175
|
+
|
|
156
176
|
#### MySQL
|
|
157
177
|
|
|
158
178
|
| | |
|
|
@@ -181,7 +201,7 @@ winget install Oracle.MySQL
|
|
|
181
201
|
spindb deps check --engine mysql
|
|
182
202
|
```
|
|
183
203
|
|
|
184
|
-
**Linux users:** MariaDB
|
|
204
|
+
**Linux users:** MariaDB is also available as a standalone engine with downloadable binaries. Use `spindb create mydb --engine mariadb` for the dedicated MariaDB engine.
|
|
185
205
|
|
|
186
206
|
#### SQLite
|
|
187
207
|
|
|
@@ -302,6 +322,7 @@ spindb connect myredis --iredis
|
|
|
302
322
|
|
|
303
323
|
```bash
|
|
304
324
|
spindb create mydb # PostgreSQL (default)
|
|
325
|
+
spindb create mydb --engine mariadb # MariaDB
|
|
305
326
|
spindb create mydb --engine mysql # MySQL
|
|
306
327
|
spindb create mydb --engine sqlite # SQLite (file-based)
|
|
307
328
|
spindb create mydb --db-version 16 # Specific PostgreSQL version
|
|
@@ -328,8 +349,8 @@ spindb create mydb --from "postgresql://user:pass@host:5432/production"
|
|
|
328
349
|
|
|
329
350
|
| Option | Description |
|
|
330
351
|
|--------|-------------|
|
|
331
|
-
| `--engine`, `-e` | Database engine (`postgresql`, `mysql`, `sqlite`, `mongodb`, `redis`) |
|
|
332
|
-
| `--db-version` | Engine version (e.g., 17 for PostgreSQL, 8 for Redis) |
|
|
352
|
+
| `--engine`, `-e` | Database engine (`postgresql`, `mariadb`, `mysql`, `sqlite`, `mongodb`, `redis`) |
|
|
353
|
+
| `--db-version` | Engine version (e.g., 17 for PostgreSQL, 11.8 for MariaDB, 8 for Redis) |
|
|
333
354
|
| `--port`, `-p` | Port number (not applicable for SQLite) |
|
|
334
355
|
| `--database`, `-d` | Primary database name (Redis uses 0-15) |
|
|
335
356
|
| `--path` | File path for SQLite databases |
|
|
@@ -442,6 +463,7 @@ spindb backup mydb --dump
|
|
|
442
463
|
|
|
443
464
|
Format by engine:
|
|
444
465
|
- PostgreSQL: `.sql` (plain SQL) / `.dump` (pg_dump custom)
|
|
466
|
+
- MariaDB: `.sql` (plain SQL) / `.sql.gz` (compressed SQL)
|
|
445
467
|
- MySQL: `.sql` (plain SQL) / `.sql.gz` (compressed SQL)
|
|
446
468
|
- SQLite: `.sql` (plain SQL) / `.sqlite` (binary copy)
|
|
447
469
|
- MongoDB: `.bson` (BSON dump) / `.archive` (compressed archive)
|
|
@@ -532,6 +554,18 @@ Each engine has specific backup formats and restore behaviors:
|
|
|
532
554
|
|
|
533
555
|
</details>
|
|
534
556
|
|
|
557
|
+
<details>
|
|
558
|
+
<summary>MariaDB</summary>
|
|
559
|
+
|
|
560
|
+
| Format | Extension | Tool | Notes |
|
|
561
|
+
|--------|-----------|------|-------|
|
|
562
|
+
| SQL | `.sql` | mariadb-dump | Plain text SQL |
|
|
563
|
+
| Compressed | `.sql.gz` | mariadb-dump + gzip | Gzip compressed SQL |
|
|
564
|
+
|
|
565
|
+
**Restore behavior:** Creates new database or replaces existing. Pipes to `mariadb` client.
|
|
566
|
+
|
|
567
|
+
</details>
|
|
568
|
+
|
|
535
569
|
<details>
|
|
536
570
|
<summary>MySQL</summary>
|
|
537
571
|
|
|
@@ -734,6 +768,7 @@ SpinDB supports enhanced database shells that provide features like auto-complet
|
|
|
734
768
|
| Engine | Standard | Enhanced | Universal |
|
|
735
769
|
|--------|----------|----------|-----------|
|
|
736
770
|
| PostgreSQL | `psql` | `pgcli` | `usql` |
|
|
771
|
+
| MariaDB | `mariadb` | `mycli` | `usql` |
|
|
737
772
|
| MySQL | `mysql` | `mycli` | `usql` |
|
|
738
773
|
| SQLite | `sqlite3` | `litecli` | `usql` |
|
|
739
774
|
| MongoDB | `mongosh` | - | `usql` |
|
|
@@ -824,12 +859,13 @@ Each database engine has its own persistence mechanism:
|
|
|
824
859
|
| Engine | Mechanism | Durability |
|
|
825
860
|
|--------|-----------|------------|
|
|
826
861
|
| PostgreSQL | Write-Ahead Logging (WAL) | Every commit is immediately durable |
|
|
862
|
+
| MariaDB | InnoDB transaction logs | Every commit is immediately durable |
|
|
827
863
|
| MySQL | InnoDB transaction logs | Every commit is immediately durable |
|
|
828
864
|
| SQLite | File-based transactions | Every commit is immediately durable |
|
|
829
865
|
| MongoDB | WiredTiger with journaling | Writes journaled before acknowledged |
|
|
830
866
|
| Redis | RDB snapshots | Periodic snapshots (see below) |
|
|
831
867
|
|
|
832
|
-
**PostgreSQL, MySQL, MongoDB:** These engines use transaction logs or journaling. Every committed write is guaranteed to survive a crash or unexpected shutdown.
|
|
868
|
+
**PostgreSQL, MariaDB, MySQL, MongoDB:** These engines use transaction logs or journaling. Every committed write is guaranteed to survive a crash or unexpected shutdown.
|
|
833
869
|
|
|
834
870
|
**SQLite:** As a file-based database, SQLite writes directly to disk on each commit. No server process means no risk of losing in-flight data.
|
|
835
871
|
|
|
@@ -843,41 +879,42 @@ This means Redis may lose up to ~60 seconds of writes on an unexpected crash. Fo
|
|
|
843
879
|
### Binary Sources
|
|
844
880
|
|
|
845
881
|
**PostgreSQL:** Server binaries are downloaded automatically:
|
|
846
|
-
- **macOS/Linux:** From [
|
|
847
|
-
|
|
882
|
+
- **All platforms (macOS/Linux/Windows for macOS/Linux via hostdb, Windows via EDB):** From [hostdb](https://github.com/robertjbass/hostdb) on GitHub Releases (macOS/Linux) or [EnterpriseDB (EDB)](https://www.enterprisedb.com/download-postgresql-binaries) (Windows)
|
|
883
|
+
|
|
884
|
+
**MariaDB:** Server binaries are downloaded automatically from [hostdb](https://github.com/robertjbass/hostdb) on GitHub Releases for all platforms.
|
|
848
885
|
|
|
849
886
|
**MySQL/MongoDB/Redis:** Uses your system installation. SpinDB detects binaries from Homebrew (macOS), apt/pacman (Linux), or Chocolatey/winget/Scoop (Windows).
|
|
850
887
|
|
|
851
|
-
### Why Precompiled Binaries for PostgreSQL, but System Installs for Others?
|
|
888
|
+
### Why Precompiled Binaries for PostgreSQL and MariaDB, but System Installs for Others?
|
|
852
889
|
|
|
853
890
|
This isn't a preference—it's a practical reality of what's available.
|
|
854
891
|
|
|
855
|
-
**PostgreSQL
|
|
892
|
+
**PostgreSQL and MariaDB have excellent embedded binary distributions.** The [hostdb](https://github.com/robertjbass/hostdb) project provides pre-compiled, portable database binaries:
|
|
856
893
|
|
|
857
894
|
- Cross-platform (macOS Intel/ARM, Linux x64/ARM, Windows)
|
|
858
|
-
- Hosted on
|
|
859
|
-
- ~45 MB per version
|
|
860
|
-
- Actively maintained with new
|
|
895
|
+
- Hosted on GitHub Releases (highly reliable CDN)
|
|
896
|
+
- ~45-100 MB per version
|
|
897
|
+
- Actively maintained with new database releases
|
|
861
898
|
|
|
862
|
-
This makes multi-version support trivial: need PostgreSQL 14 for a legacy project and 18 for a new one? SpinDB downloads
|
|
899
|
+
This makes multi-version support trivial: need PostgreSQL 14 for a legacy project and 18 for a new one? Need MariaDB 11.8? SpinDB downloads them all, and they run side-by-side without conflicts.
|
|
863
900
|
|
|
864
|
-
**No equivalent exists for MySQL, MongoDB, or Redis.** None of these databases have a comparable embedded binary
|
|
901
|
+
**No equivalent exists for MySQL, MongoDB, or Redis (yet).** None of these databases have a comparable embedded binary distribution:
|
|
865
902
|
|
|
866
|
-
- **MySQL:** Oracle distributes MySQL as large installers with system dependencies, not embeddable binaries.
|
|
903
|
+
- **MySQL:** Oracle distributes MySQL as large installers with system dependencies, not embeddable binaries.
|
|
867
904
|
- **MongoDB:** Server binaries are several hundred MB and aren't designed for portable distribution.
|
|
868
905
|
- **Redis:** While Redis is small (~6-12 MB), there's no official portable distribution. Community Windows ports exist, but macOS/Linux rely on system packages.
|
|
869
906
|
|
|
870
907
|
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.
|
|
871
908
|
|
|
872
|
-
**Does this limit multi-version support?** Yes, for MySQL/MongoDB/Redis you get whatever version your package manager provides. In practice, this is rarely a problem—developers seldom need multiple versions of these databases simultaneously.
|
|
909
|
+
**Does this limit multi-version support?** Yes, for MySQL/MongoDB/Redis you get whatever version your package manager provides. In practice, this is rarely a problem—developers seldom need multiple versions of these databases simultaneously. As hostdb expands to support more databases, SpinDB will adopt them for multi-version support.
|
|
873
910
|
|
|
874
911
|
---
|
|
875
912
|
|
|
876
913
|
## Limitations
|
|
877
914
|
|
|
878
|
-
- **Client tools required** - `
|
|
915
|
+
- **Client tools required** - `mysql`, `mongosh`, and `redis-cli` must be installed separately for some operations (connecting, backups, restores) for system-installed engines
|
|
879
916
|
- **Local only** - Databases bind to `127.0.0.1`; remote connections planned for v1.1
|
|
880
|
-
- **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))
|
|
917
|
+
- **Single version for MySQL/MongoDB/Redis** - Unlike PostgreSQL and MariaDB, MySQL, MongoDB, and Redis use system installations, so you're limited to one version per machine (see [Why Precompiled Binaries for PostgreSQL and MariaDB?](#why-precompiled-binaries-for-postgresql-and-mariadb-but-system-installs-for-others))
|
|
881
918
|
- **Redis remote dump not supported** - Redis doesn't support creating containers from remote connection strings. Use backup/restore for data migration.
|
|
882
919
|
|
|
883
920
|
---
|
|
@@ -892,7 +929,6 @@ See [TODO.md](TODO.md) for the full roadmap.
|
|
|
892
929
|
- Secrets management (macOS Keychain)
|
|
893
930
|
|
|
894
931
|
### v1.2 - Additional Engines
|
|
895
|
-
- MariaDB as standalone engine
|
|
896
932
|
- CockroachDB (distributed SQL)
|
|
897
933
|
|
|
898
934
|
### v1.3 - Advanced Features
|
|
@@ -900,6 +936,9 @@ See [TODO.md](TODO.md) for the full roadmap.
|
|
|
900
936
|
- Scheduled backups
|
|
901
937
|
- Import from Docker
|
|
902
938
|
|
|
939
|
+
### Future Infrastructure
|
|
940
|
+
- **hostdb npm package**: Available database versions will be published as an npm package from [hostdb](https://github.com/robertjbass/hostdb) and imported into SpinDB, eliminating the need to manually sync version-maps.ts with releases.json
|
|
941
|
+
|
|
903
942
|
### Possible Future Engines
|
|
904
943
|
|
|
905
944
|
These engines are under consideration but not yet on the roadmap. Community interest and feasibility will determine priority:
|
package/cli/commands/engines.ts
CHANGED
|
@@ -3,6 +3,7 @@ import chalk from 'chalk'
|
|
|
3
3
|
import { rm } from 'fs/promises'
|
|
4
4
|
import inquirer from 'inquirer'
|
|
5
5
|
import { containerManager } from '../../core/container-manager'
|
|
6
|
+
import { processManager } from '../../core/process-manager'
|
|
6
7
|
import { getEngine } from '../../engines'
|
|
7
8
|
import { binaryManager } from '../../core/binary-manager'
|
|
8
9
|
import { paths } from '../../config/paths'
|
|
@@ -281,30 +282,46 @@ async function deleteEngine(
|
|
|
281
282
|
process.exit(1)
|
|
282
283
|
}
|
|
283
284
|
|
|
284
|
-
// Check if any containers are using this engine version
|
|
285
|
+
// Check if any containers are using this engine version (for warning only)
|
|
285
286
|
const containers = await containerManager.list()
|
|
286
287
|
const usingContainers = containers.filter(
|
|
287
288
|
(c) => c.engine === engineName && c.version === engineVersion,
|
|
288
289
|
)
|
|
289
290
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
uiError(
|
|
293
|
-
`Cannot delete: ${usingContainers.length} container(s) are using ${engineName} ${engineVersion}`,
|
|
294
|
-
),
|
|
295
|
-
)
|
|
296
|
-
console.log(
|
|
297
|
-
chalk.gray(
|
|
298
|
-
` Containers: ${usingContainers.map((c) => c.name).join(', ')}`,
|
|
299
|
-
),
|
|
300
|
-
)
|
|
301
|
-
console.log()
|
|
302
|
-
console.log(chalk.gray(' Delete these containers first, then try again.'))
|
|
303
|
-
process.exit(1)
|
|
304
|
-
}
|
|
291
|
+
// Check for running containers using this engine
|
|
292
|
+
const runningContainers = usingContainers.filter((c) => c.status === 'running')
|
|
305
293
|
|
|
306
|
-
// Confirm deletion
|
|
294
|
+
// Confirm deletion (warn about containers)
|
|
307
295
|
if (!options.yes) {
|
|
296
|
+
if (usingContainers.length > 0) {
|
|
297
|
+
const runningCount = runningContainers.length
|
|
298
|
+
const stoppedCount = usingContainers.length - runningCount
|
|
299
|
+
|
|
300
|
+
if (runningCount > 0) {
|
|
301
|
+
console.log(
|
|
302
|
+
uiWarning(
|
|
303
|
+
`${runningCount} running container(s) will be stopped: ${runningContainers.map((c) => c.name).join(', ')}`,
|
|
304
|
+
),
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
if (stoppedCount > 0) {
|
|
308
|
+
const stoppedContainers = usingContainers.filter(
|
|
309
|
+
(c) => c.status !== 'running',
|
|
310
|
+
)
|
|
311
|
+
console.log(
|
|
312
|
+
chalk.gray(
|
|
313
|
+
` ${stoppedCount} stopped container(s) will be orphaned: ${stoppedContainers.map((c) => c.name).join(', ')}`,
|
|
314
|
+
),
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
console.log(
|
|
318
|
+
chalk.gray(
|
|
319
|
+
' You can re-download the engine later to use these containers.',
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
console.log()
|
|
323
|
+
}
|
|
324
|
+
|
|
308
325
|
const confirmed = await promptConfirm(
|
|
309
326
|
`Delete ${engineName} ${engineVersion}? This cannot be undone.`,
|
|
310
327
|
false,
|
|
@@ -316,6 +333,74 @@ async function deleteEngine(
|
|
|
316
333
|
}
|
|
317
334
|
}
|
|
318
335
|
|
|
336
|
+
// Stop any running containers first (while we still have the binary)
|
|
337
|
+
if (runningContainers.length > 0) {
|
|
338
|
+
const stopSpinner = createSpinner(
|
|
339
|
+
`Stopping ${runningContainers.length} running container(s)...`,
|
|
340
|
+
)
|
|
341
|
+
stopSpinner.start()
|
|
342
|
+
|
|
343
|
+
const engine = getEngine(Engine.PostgreSQL)
|
|
344
|
+
const failedToStop: string[] = []
|
|
345
|
+
|
|
346
|
+
for (const container of runningContainers) {
|
|
347
|
+
stopSpinner.text = `Stopping ${container.name}...`
|
|
348
|
+
try {
|
|
349
|
+
await engine.stop(container)
|
|
350
|
+
await containerManager.updateConfig(container.name, {
|
|
351
|
+
status: 'stopped',
|
|
352
|
+
})
|
|
353
|
+
} catch (error) {
|
|
354
|
+
// Log the original failure before attempting fallback
|
|
355
|
+
const err = error as Error
|
|
356
|
+
console.error(
|
|
357
|
+
chalk.gray(
|
|
358
|
+
` Failed to stop ${container.name} via engine.stop: ${err.message}`,
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
// Try fallback kill
|
|
362
|
+
const killed = await processManager.killProcess(container.name, {
|
|
363
|
+
engine: container.engine,
|
|
364
|
+
})
|
|
365
|
+
if (killed) {
|
|
366
|
+
await containerManager.updateConfig(container.name, {
|
|
367
|
+
status: 'stopped',
|
|
368
|
+
})
|
|
369
|
+
} else {
|
|
370
|
+
failedToStop.push(container.name)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (failedToStop.length > 0) {
|
|
376
|
+
stopSpinner.warn(
|
|
377
|
+
`Could not stop ${failedToStop.length} container(s): ${failedToStop.join(', ')}`,
|
|
378
|
+
)
|
|
379
|
+
console.log(
|
|
380
|
+
chalk.yellow(
|
|
381
|
+
' These containers may still be running. Deleting the engine could leave them in a broken state.',
|
|
382
|
+
),
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
if (!options.yes) {
|
|
386
|
+
const continueAnyway = await promptConfirm(
|
|
387
|
+
'Continue with engine deletion anyway?',
|
|
388
|
+
false,
|
|
389
|
+
)
|
|
390
|
+
if (!continueAnyway) {
|
|
391
|
+
console.log(uiWarning('Deletion cancelled'))
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
console.log(chalk.yellow(' Proceeding with deletion (--yes specified)'))
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
stopSpinner.succeed(
|
|
399
|
+
`Stopped ${runningContainers.length} container(s)`,
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
319
404
|
// Delete the engine
|
|
320
405
|
const spinner = createSpinner(`Deleting ${engineName} ${engineVersion}...`)
|
|
321
406
|
spinner.start()
|
|
@@ -11,6 +11,7 @@ import { getEngineIcon, ENGINE_ICONS } from '../../constants'
|
|
|
11
11
|
import {
|
|
12
12
|
getInstalledEngines,
|
|
13
13
|
type InstalledPostgresEngine,
|
|
14
|
+
type InstalledMariadbEngine,
|
|
14
15
|
type InstalledMysqlEngine,
|
|
15
16
|
type InstalledSqliteEngine,
|
|
16
17
|
type InstalledMongodbEngine,
|
|
@@ -54,6 +55,9 @@ export async function handleEngines(): Promise<void> {
|
|
|
54
55
|
const pgEngines = engines.filter(
|
|
55
56
|
(e): e is InstalledPostgresEngine => e.engine === 'postgresql',
|
|
56
57
|
)
|
|
58
|
+
const mariadbEngines = engines.filter(
|
|
59
|
+
(e): e is InstalledMariadbEngine => e.engine === 'mariadb',
|
|
60
|
+
)
|
|
57
61
|
const mysqlEngines = engines.filter(
|
|
58
62
|
(e): e is InstalledMysqlEngine => e.engine === 'mysql',
|
|
59
63
|
)
|
|
@@ -68,6 +72,7 @@ export async function handleEngines(): Promise<void> {
|
|
|
68
72
|
)
|
|
69
73
|
|
|
70
74
|
const totalPgSize = pgEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
|
|
75
|
+
const totalMariadbSize = mariadbEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
|
|
71
76
|
|
|
72
77
|
const COL_ENGINE = 14
|
|
73
78
|
const COL_VERSION = 12
|
|
@@ -97,6 +102,20 @@ export async function handleEngines(): Promise<void> {
|
|
|
97
102
|
)
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
for (const engine of mariadbEngines) {
|
|
106
|
+
const icon = getEngineIcon(engine.engine)
|
|
107
|
+
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
108
|
+
const engineDisplay = `${icon} ${engine.engine}`
|
|
109
|
+
|
|
110
|
+
console.log(
|
|
111
|
+
chalk.gray(' ') +
|
|
112
|
+
chalk.cyan(padToWidth(engineDisplay, COL_ENGINE)) +
|
|
113
|
+
chalk.yellow(engine.version.padEnd(COL_VERSION)) +
|
|
114
|
+
chalk.gray(platformInfo.padEnd(COL_SOURCE)) +
|
|
115
|
+
chalk.white(formatBytes(engine.sizeBytes)),
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
for (const mysqlEngine of mysqlEngines) {
|
|
101
120
|
const icon = ENGINE_ICONS.mysql
|
|
102
121
|
const displayName = mysqlEngine.isMariaDB ? 'mariadb' : 'mysql'
|
|
@@ -113,8 +132,7 @@ export async function handleEngines(): Promise<void> {
|
|
|
113
132
|
|
|
114
133
|
if (sqliteEngine) {
|
|
115
134
|
const icon = ENGINE_ICONS.sqlite
|
|
116
|
-
|
|
117
|
-
const engineDisplay = `${icon} sqlite`
|
|
135
|
+
const engineDisplay = `${icon} sqlite`
|
|
118
136
|
|
|
119
137
|
console.log(
|
|
120
138
|
chalk.gray(' ') +
|
|
@@ -161,6 +179,13 @@ export async function handleEngines(): Promise<void> {
|
|
|
161
179
|
),
|
|
162
180
|
)
|
|
163
181
|
}
|
|
182
|
+
if (mariadbEngines.length > 0) {
|
|
183
|
+
console.log(
|
|
184
|
+
chalk.gray(
|
|
185
|
+
` MariaDB: ${mariadbEngines.length} version(s), ${formatBytes(totalMariadbSize)}`,
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
}
|
|
164
189
|
if (mysqlEngines.length > 0) {
|
|
165
190
|
const versionCount = mysqlEngines.length
|
|
166
191
|
const versionText = versionCount === 1 ? 'version' : 'versions'
|
|
@@ -198,6 +223,13 @@ export async function handleEngines(): Promise<void> {
|
|
|
198
223
|
})
|
|
199
224
|
}
|
|
200
225
|
|
|
226
|
+
for (const e of mariadbEngines) {
|
|
227
|
+
choices.push({
|
|
228
|
+
name: `${chalk.red('✕')} Delete ${e.engine} ${e.version} ${chalk.gray(`(${formatBytes(e.sizeBytes)})`)}`,
|
|
229
|
+
value: `delete:${e.path}:${e.engine}:${e.version}`,
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
201
233
|
for (const mysqlEngine of mysqlEngines) {
|
|
202
234
|
const displayName = mysqlEngine.isMariaDB ? 'MariaDB' : 'MySQL'
|
|
203
235
|
// Show formula name if it's a versioned install
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getIredisManualInstructions,
|
|
22
22
|
} from '../../../core/dependency-manager'
|
|
23
23
|
import { platformService } from '../../../core/platform-service'
|
|
24
|
+
import { configManager } from '../../../core/config-manager'
|
|
24
25
|
import { getEngine } from '../../../engines'
|
|
25
26
|
import { createSpinner } from '../../ui/spinner'
|
|
26
27
|
import { uiError, uiWarning, uiInfo, uiSuccess } from '../../ui/theme'
|
|
@@ -118,6 +119,12 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
118
119
|
engineSpecificInstalled = mycliInstalled
|
|
119
120
|
engineSpecificValue = 'mycli'
|
|
120
121
|
engineSpecificInstallValue = 'install-mycli'
|
|
122
|
+
} else if (config.engine === 'mariadb') {
|
|
123
|
+
defaultShellName = 'mariadb'
|
|
124
|
+
engineSpecificCli = 'mycli'
|
|
125
|
+
engineSpecificInstalled = mycliInstalled
|
|
126
|
+
engineSpecificValue = 'mycli'
|
|
127
|
+
engineSpecificInstallValue = 'install-mycli'
|
|
121
128
|
} else if (config.engine === 'mongodb') {
|
|
122
129
|
defaultShellName = 'mongosh'
|
|
123
130
|
// mongosh IS the enhanced shell for MongoDB (no separate enhanced CLI like pgcli/mycli)
|
|
@@ -425,6 +432,20 @@ async function launchShell(
|
|
|
425
432
|
config.database,
|
|
426
433
|
]
|
|
427
434
|
installHint = 'brew install mysql-client'
|
|
435
|
+
} else if (config.engine === 'mariadb') {
|
|
436
|
+
// MariaDB uses downloaded binaries, not system PATH - get the actual path
|
|
437
|
+
const mariadbPath = await configManager.getBinaryPath('mariadb')
|
|
438
|
+
shellCmd = mariadbPath || 'mariadb'
|
|
439
|
+
shellArgs = [
|
|
440
|
+
'-u',
|
|
441
|
+
'root',
|
|
442
|
+
'-h',
|
|
443
|
+
'127.0.0.1',
|
|
444
|
+
'-P',
|
|
445
|
+
String(config.port),
|
|
446
|
+
config.database,
|
|
447
|
+
]
|
|
448
|
+
installHint = 'spindb engines download mariadb'
|
|
428
449
|
} else if (config.engine === 'mongodb') {
|
|
429
450
|
shellCmd = 'mongosh'
|
|
430
451
|
shellArgs = [connectionString]
|
package/cli/commands/start.ts
CHANGED
|
@@ -5,9 +5,10 @@ import { processManager } from '../../core/process-manager'
|
|
|
5
5
|
import { startWithRetry } from '../../core/start-with-retry'
|
|
6
6
|
import { getEngine } from '../../engines'
|
|
7
7
|
import { getEngineDefaults } from '../../config/defaults'
|
|
8
|
-
import { promptContainerSelect } from '../ui/prompts'
|
|
8
|
+
import { promptContainerSelect, promptConfirm } from '../ui/prompts'
|
|
9
9
|
import { createSpinner } from '../ui/spinner'
|
|
10
10
|
import { uiError, uiWarning } from '../ui/theme'
|
|
11
|
+
import { Engine } from '../../types'
|
|
11
12
|
|
|
12
13
|
export const startCommand = new Command('start')
|
|
13
14
|
.description('Start a container')
|
|
@@ -61,6 +62,51 @@ export const startCommand = new Command('start')
|
|
|
61
62
|
const engineDefaults = getEngineDefaults(engineName)
|
|
62
63
|
const engine = getEngine(engineName)
|
|
63
64
|
|
|
65
|
+
// For PostgreSQL, check if the engine binary is installed
|
|
66
|
+
if (engineName === Engine.PostgreSQL) {
|
|
67
|
+
const isInstalled = await engine.isBinaryInstalled(config.version)
|
|
68
|
+
if (!isInstalled) {
|
|
69
|
+
console.log(
|
|
70
|
+
uiWarning(
|
|
71
|
+
`PostgreSQL ${config.version} engine is not installed (required by "${containerName}")`,
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
const confirmed = await promptConfirm(
|
|
75
|
+
`Download PostgreSQL ${config.version} now?`,
|
|
76
|
+
true,
|
|
77
|
+
)
|
|
78
|
+
if (!confirmed) {
|
|
79
|
+
console.log(
|
|
80
|
+
chalk.gray(
|
|
81
|
+
` Run "spindb engines download postgresql ${config.version}" to download manually.`,
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const downloadSpinner = createSpinner(
|
|
88
|
+
`Downloading PostgreSQL ${config.version}...`,
|
|
89
|
+
)
|
|
90
|
+
downloadSpinner.start()
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await engine.ensureBinaries(config.version, ({ stage, message }) => {
|
|
94
|
+
if (stage === 'cached') {
|
|
95
|
+
downloadSpinner.text = `PostgreSQL ${config.version} ready`
|
|
96
|
+
} else {
|
|
97
|
+
downloadSpinner.text = message
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
downloadSpinner.succeed(`PostgreSQL ${config.version} downloaded`)
|
|
101
|
+
} catch (downloadError) {
|
|
102
|
+
downloadSpinner.fail(
|
|
103
|
+
`Failed to download PostgreSQL ${config.version} for "${containerName}"`,
|
|
104
|
+
)
|
|
105
|
+
throw downloadError
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
64
110
|
const spinner = createSpinner(`Starting ${containerName}...`)
|
|
65
111
|
spinner.start()
|
|
66
112
|
|
package/cli/commands/stop.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
2
3
|
import { containerManager } from '../../core/container-manager'
|
|
3
4
|
import { processManager } from '../../core/process-manager'
|
|
4
5
|
import { getEngine } from '../../engines'
|
|
5
6
|
import { promptContainerSelect } from '../ui/prompts'
|
|
6
7
|
import { createSpinner } from '../ui/spinner'
|
|
7
8
|
import { uiSuccess, uiError, uiWarning } from '../ui/theme'
|
|
9
|
+
import { Engine } from '../../types'
|
|
8
10
|
|
|
9
11
|
export const stopCommand = new Command('stop')
|
|
10
12
|
.description('Stop a container')
|
|
@@ -35,7 +37,46 @@ export const stopCommand = new Command('stop')
|
|
|
35
37
|
spinner?.start()
|
|
36
38
|
|
|
37
39
|
const engine = getEngine(container.engine)
|
|
38
|
-
|
|
40
|
+
|
|
41
|
+
// For PostgreSQL, check if engine binary is installed
|
|
42
|
+
let usedFallback = false
|
|
43
|
+
let stopFailed = false
|
|
44
|
+
if (container.engine === Engine.PostgreSQL) {
|
|
45
|
+
const isInstalled = await engine.isBinaryInstalled(container.version)
|
|
46
|
+
if (!isInstalled) {
|
|
47
|
+
if (spinner) {
|
|
48
|
+
spinner.text = `Stopping ${container.name} (engine missing, using fallback)...`
|
|
49
|
+
}
|
|
50
|
+
const killed = await processManager.killProcess(container.name, {
|
|
51
|
+
engine: container.engine,
|
|
52
|
+
})
|
|
53
|
+
if (!killed) {
|
|
54
|
+
spinner?.fail(`Failed to stop "${container.name}"`)
|
|
55
|
+
console.log(
|
|
56
|
+
chalk.gray(
|
|
57
|
+
` The PostgreSQL ${container.version} engine is not installed.`,
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
console.log(
|
|
61
|
+
chalk.gray(
|
|
62
|
+
` Run "spindb engines download postgresql ${container.version.split('.')[0]}" to reinstall.`,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
stopFailed = true
|
|
66
|
+
} else {
|
|
67
|
+
usedFallback = true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (stopFailed) {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!usedFallback) {
|
|
77
|
+
await engine.stop(container)
|
|
78
|
+
}
|
|
79
|
+
|
|
39
80
|
await containerManager.updateConfig(container.name, {
|
|
40
81
|
status: 'stopped',
|
|
41
82
|
})
|
|
@@ -98,7 +139,40 @@ export const stopCommand = new Command('stop')
|
|
|
98
139
|
: createSpinner(`Stopping ${containerName}...`)
|
|
99
140
|
spinner?.start()
|
|
100
141
|
|
|
101
|
-
|
|
142
|
+
// For PostgreSQL, check if engine binary is installed
|
|
143
|
+
// If not, use fallback process kill
|
|
144
|
+
let usedFallback = false
|
|
145
|
+
if (config.engine === Engine.PostgreSQL) {
|
|
146
|
+
const isInstalled = await engine.isBinaryInstalled(config.version)
|
|
147
|
+
if (!isInstalled) {
|
|
148
|
+
if (spinner) {
|
|
149
|
+
spinner.text = `Stopping ${containerName} (engine missing, using fallback)...`
|
|
150
|
+
}
|
|
151
|
+
const killed = await processManager.killProcess(containerName, {
|
|
152
|
+
engine: config.engine,
|
|
153
|
+
})
|
|
154
|
+
if (!killed) {
|
|
155
|
+
spinner?.fail(`Failed to stop "${containerName}"`)
|
|
156
|
+
console.log(
|
|
157
|
+
chalk.gray(
|
|
158
|
+
` The PostgreSQL ${config.version} engine is not installed.`,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
console.log(
|
|
162
|
+
chalk.gray(
|
|
163
|
+
` Run "spindb engines download postgresql ${config.version.split('.')[0]}" to reinstall.`,
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
process.exit(1)
|
|
167
|
+
}
|
|
168
|
+
usedFallback = true
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!usedFallback) {
|
|
173
|
+
await engine.stop(config)
|
|
174
|
+
}
|
|
175
|
+
|
|
102
176
|
await containerManager.updateConfig(containerName, {
|
|
103
177
|
status: 'stopped',
|
|
104
178
|
})
|
package/cli/constants.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
// Note: Some emojis render narrower than others in terminals.
|
|
2
|
+
// Add extra space after narrow emojis (🦭, 🪶) for visual alignment.
|
|
1
3
|
export const ENGINE_ICONS: Record<string, string> = {
|
|
2
4
|
postgresql: '🐘',
|
|
3
5
|
mysql: '🐬',
|
|
4
|
-
|
|
6
|
+
mariadb: '🦭 ', // Extra space - seal emoji renders narrow
|
|
7
|
+
sqlite: '🪶 ', // Extra space - feather emoji renders narrow
|
|
5
8
|
mongodb: '🍃',
|
|
6
9
|
redis: '🔴',
|
|
7
10
|
}
|