spindb 0.37.2 → 0.38.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/dist/cli/bin.js +9 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/attach.js +102 -0
- package/dist/cli/commands/attach.js.map +1 -0
- package/dist/cli/commands/backup.js +197 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/backups.js +190 -0
- package/dist/cli/commands/backups.js.map +1 -0
- package/dist/cli/commands/clone.js +119 -0
- package/dist/cli/commands/clone.js.map +1 -0
- package/dist/cli/commands/config.js +276 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/connect.js +559 -0
- package/dist/cli/commands/connect.js.map +1 -0
- package/dist/cli/commands/create.js +952 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/databases.js +485 -0
- package/dist/cli/commands/databases.js.map +1 -0
- package/dist/cli/commands/delete.js +106 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/deps.js +238 -0
- package/dist/cli/commands/deps.js.map +1 -0
- package/dist/cli/commands/detach.js +81 -0
- package/dist/cli/commands/detach.js.map +1 -0
- package/dist/cli/commands/doctor.js +567 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/duckdb.js +207 -0
- package/dist/cli/commands/duckdb.js.map +1 -0
- package/dist/cli/commands/edit.js +524 -0
- package/dist/cli/commands/edit.js.map +1 -0
- package/dist/cli/commands/engines.js +1414 -0
- package/dist/cli/commands/engines.js.map +1 -0
- package/dist/cli/commands/export.js +383 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/info.js +270 -0
- package/dist/cli/commands/info.js.map +1 -0
- package/dist/cli/commands/list.js +215 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/logs.js +81 -0
- package/dist/cli/commands/logs.js.map +1 -0
- package/dist/cli/commands/menu/backup-handlers.js +1202 -0
- package/dist/cli/commands/menu/backup-handlers.js.map +1 -0
- package/dist/cli/commands/menu/container-handlers.js +1788 -0
- package/dist/cli/commands/menu/container-handlers.js.map +1 -0
- package/dist/cli/commands/menu/engine-handlers.js +235 -0
- package/dist/cli/commands/menu/engine-handlers.js.map +1 -0
- package/dist/cli/commands/menu/index.js +266 -0
- package/dist/cli/commands/menu/index.js.map +1 -0
- package/dist/cli/commands/menu/settings-handlers.js +320 -0
- package/dist/cli/commands/menu/settings-handlers.js.map +1 -0
- package/dist/cli/commands/menu/shared.js +13 -0
- package/dist/cli/commands/menu/shared.js.map +1 -0
- package/dist/cli/commands/menu/shell-handlers.js +1573 -0
- package/dist/cli/commands/menu/shell-handlers.js.map +1 -0
- package/dist/cli/commands/menu/sql-handlers.js +185 -0
- package/dist/cli/commands/menu/sql-handlers.js.map +1 -0
- package/dist/cli/commands/menu/update-handlers.js +322 -0
- package/dist/cli/commands/menu/update-handlers.js.map +1 -0
- package/dist/cli/commands/menu/validators.js +9 -0
- package/dist/cli/commands/menu/validators.js.map +1 -0
- package/dist/cli/commands/ports.js +166 -0
- package/dist/cli/commands/ports.js.map +1 -0
- package/dist/cli/commands/pull.js +166 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/query.js +180 -0
- package/dist/cli/commands/query.js.map +1 -0
- package/dist/cli/commands/restore.js +428 -0
- package/dist/cli/commands/restore.js.map +1 -0
- package/dist/cli/commands/run.js +115 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/self-update.js +99 -0
- package/dist/cli/commands/self-update.js.map +1 -0
- package/dist/cli/commands/sqlite.js +207 -0
- package/dist/cli/commands/sqlite.js.map +1 -0
- package/dist/cli/commands/start.js +196 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/stop.js +182 -0
- package/dist/cli/commands/stop.js.map +1 -0
- package/dist/cli/commands/url.js +88 -0
- package/dist/cli/commands/url.js.map +1 -0
- package/dist/cli/commands/users.js +189 -0
- package/dist/cli/commands/users.js.map +1 -0
- package/dist/cli/commands/version.js +52 -0
- package/dist/cli/commands/version.js.map +1 -0
- package/dist/cli/commands/which.js +258 -0
- package/dist/cli/commands/which.js.map +1 -0
- package/dist/cli/constants.js +212 -0
- package/dist/cli/constants.js.map +1 -0
- package/dist/cli/helpers.js +1120 -0
- package/dist/cli/helpers.js.map +1 -0
- package/dist/cli/index.js +146 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ui/prompts.js +1002 -0
- package/dist/cli/ui/prompts.js.map +1 -0
- package/dist/cli/ui/spinner.js +74 -0
- package/dist/cli/ui/spinner.js.map +1 -0
- package/dist/cli/ui/theme.js +99 -0
- package/dist/cli/ui/theme.js.map +1 -0
- package/dist/cli/utils/file-follower.js +79 -0
- package/dist/cli/utils/file-follower.js.map +1 -0
- package/dist/config/backup-formats.js +363 -0
- package/dist/config/backup-formats.js.map +1 -0
- package/dist/config/defaults.js +25 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/engine-defaults.js +303 -0
- package/dist/config/engine-defaults.js.map +1 -0
- package/dist/config/engines-registry.js +103 -0
- package/dist/config/engines-registry.js.map +1 -0
- package/dist/config/os-dependencies.js +767 -0
- package/dist/config/os-dependencies.js.map +1 -0
- package/dist/config/paths.js +156 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/version.js +3 -0
- package/dist/config/version.js.map +1 -0
- package/dist/core/backup-restore.js +219 -0
- package/dist/core/backup-restore.js.map +1 -0
- package/dist/core/base-binary-manager.js +403 -0
- package/dist/core/base-binary-manager.js.map +1 -0
- package/dist/core/base-document-binary-manager.js +364 -0
- package/dist/core/base-document-binary-manager.js.map +1 -0
- package/dist/core/base-embedded-binary-manager.js +364 -0
- package/dist/core/base-embedded-binary-manager.js.map +1 -0
- package/dist/core/base-server-binary-manager.js +368 -0
- package/dist/core/base-server-binary-manager.js.map +1 -0
- package/dist/core/config-manager.js +495 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/container-manager.js +609 -0
- package/dist/core/container-manager.js.map +1 -0
- package/dist/core/credential-generator.js +67 -0
- package/dist/core/credential-generator.js.map +1 -0
- package/dist/core/credential-manager.js +211 -0
- package/dist/core/credential-manager.js.map +1 -0
- package/dist/core/dblab-utils.js +105 -0
- package/dist/core/dblab-utils.js.map +1 -0
- package/dist/core/dependency-manager.js +359 -0
- package/dist/core/dependency-manager.js.map +1 -0
- package/dist/core/docker-exporter.js +1077 -0
- package/dist/core/docker-exporter.js.map +1 -0
- package/dist/core/error-handler.js +295 -0
- package/dist/core/error-handler.js.map +1 -0
- package/dist/core/fs-error-utils.js +74 -0
- package/dist/core/fs-error-utils.js.map +1 -0
- package/dist/core/homebrew-version-manager.js +280 -0
- package/dist/core/homebrew-version-manager.js.map +1 -0
- package/dist/core/hostdb-client.js +252 -0
- package/dist/core/hostdb-client.js.map +1 -0
- package/dist/core/hostdb-metadata.js +243 -0
- package/dist/core/hostdb-metadata.js.map +1 -0
- package/dist/core/hostdb-releases-factory.js +161 -0
- package/dist/core/hostdb-releases-factory.js.map +1 -0
- package/dist/core/library-env.js +88 -0
- package/dist/core/library-env.js.map +1 -0
- package/dist/core/pgweb-utils.js +53 -0
- package/dist/core/pgweb-utils.js.map +1 -0
- package/dist/core/platform-service.js +632 -0
- package/dist/core/platform-service.js.map +1 -0
- package/dist/core/port-manager.js +136 -0
- package/dist/core/port-manager.js.map +1 -0
- package/dist/core/process-manager.js +445 -0
- package/dist/core/process-manager.js.map +1 -0
- package/dist/core/pull-manager.js +418 -0
- package/dist/core/pull-manager.js.map +1 -0
- package/dist/core/query-parser.js +449 -0
- package/dist/core/query-parser.js.map +1 -0
- package/dist/core/spawn-utils.js +90 -0
- package/dist/core/spawn-utils.js.map +1 -0
- package/dist/core/start-with-retry.js +90 -0
- package/dist/core/start-with-retry.js.map +1 -0
- package/dist/core/test-cleanup.js +85 -0
- package/dist/core/test-cleanup.js.map +1 -0
- package/dist/core/tls-generator.js +84 -0
- package/dist/core/tls-generator.js.map +1 -0
- package/dist/core/transaction-manager.js +139 -0
- package/dist/core/transaction-manager.js.map +1 -0
- package/dist/core/update-manager.js +241 -0
- package/dist/core/update-manager.js.map +1 -0
- package/dist/core/version-migration.js +260 -0
- package/dist/core/version-migration.js.map +1 -0
- package/dist/core/version-utils.js +91 -0
- package/dist/core/version-utils.js.map +1 -0
- package/dist/engines/base-engine.js +179 -0
- package/dist/engines/base-engine.js.map +1 -0
- package/dist/engines/clickhouse/backup.js +289 -0
- package/dist/engines/clickhouse/backup.js.map +1 -0
- package/dist/engines/clickhouse/binary-manager.js +145 -0
- package/dist/engines/clickhouse/binary-manager.js.map +1 -0
- package/dist/engines/clickhouse/binary-urls.js +100 -0
- package/dist/engines/clickhouse/binary-urls.js.map +1 -0
- package/dist/engines/clickhouse/cli-utils.js +143 -0
- package/dist/engines/clickhouse/cli-utils.js.map +1 -0
- package/dist/engines/clickhouse/hostdb-releases.js +24 -0
- package/dist/engines/clickhouse/hostdb-releases.js.map +1 -0
- package/dist/engines/clickhouse/index.js +1077 -0
- package/dist/engines/clickhouse/index.js.map +1 -0
- package/dist/engines/clickhouse/restore.js +335 -0
- package/dist/engines/clickhouse/restore.js.map +1 -0
- package/dist/engines/clickhouse/version-maps.js +83 -0
- package/dist/engines/clickhouse/version-maps.js.map +1 -0
- package/dist/engines/clickhouse/version-validator.js +133 -0
- package/dist/engines/clickhouse/version-validator.js.map +1 -0
- package/dist/engines/cockroachdb/backup.js +261 -0
- package/dist/engines/cockroachdb/backup.js.map +1 -0
- package/dist/engines/cockroachdb/binary-manager.js +33 -0
- package/dist/engines/cockroachdb/binary-manager.js.map +1 -0
- package/dist/engines/cockroachdb/binary-urls.js +33 -0
- package/dist/engines/cockroachdb/binary-urls.js.map +1 -0
- package/dist/engines/cockroachdb/cli-utils.js +338 -0
- package/dist/engines/cockroachdb/cli-utils.js.map +1 -0
- package/dist/engines/cockroachdb/hostdb-releases.js +21 -0
- package/dist/engines/cockroachdb/hostdb-releases.js.map +1 -0
- package/dist/engines/cockroachdb/index.js +1016 -0
- package/dist/engines/cockroachdb/index.js.map +1 -0
- package/dist/engines/cockroachdb/restore.js +323 -0
- package/dist/engines/cockroachdb/restore.js.map +1 -0
- package/dist/engines/cockroachdb/version-maps.js +37 -0
- package/dist/engines/cockroachdb/version-maps.js.map +1 -0
- package/dist/engines/couchdb/api-client.js +64 -0
- package/dist/engines/couchdb/api-client.js.map +1 -0
- package/dist/engines/couchdb/backup.js +90 -0
- package/dist/engines/couchdb/backup.js.map +1 -0
- package/dist/engines/couchdb/binary-manager.js +62 -0
- package/dist/engines/couchdb/binary-manager.js.map +1 -0
- package/dist/engines/couchdb/binary-urls.js +92 -0
- package/dist/engines/couchdb/binary-urls.js.map +1 -0
- package/dist/engines/couchdb/hostdb-releases.js +21 -0
- package/dist/engines/couchdb/hostdb-releases.js.map +1 -0
- package/dist/engines/couchdb/index.js +1043 -0
- package/dist/engines/couchdb/index.js.map +1 -0
- package/dist/engines/couchdb/restore.js +198 -0
- package/dist/engines/couchdb/restore.js.map +1 -0
- package/dist/engines/couchdb/version-maps.js +67 -0
- package/dist/engines/couchdb/version-maps.js.map +1 -0
- package/dist/engines/couchdb/version-validator.js +88 -0
- package/dist/engines/couchdb/version-validator.js.map +1 -0
- package/dist/engines/duckdb/binary-manager.js +33 -0
- package/dist/engines/duckdb/binary-manager.js.map +1 -0
- package/{engines/duckdb/binary-urls.ts → dist/engines/duckdb/binary-urls.js} +11 -16
- package/dist/engines/duckdb/binary-urls.js.map +1 -0
- package/dist/engines/duckdb/hostdb-releases.js +21 -0
- package/dist/engines/duckdb/hostdb-releases.js.map +1 -0
- package/dist/engines/duckdb/index.js +594 -0
- package/dist/engines/duckdb/index.js.map +1 -0
- package/dist/engines/duckdb/registry.js +265 -0
- package/dist/engines/duckdb/registry.js.map +1 -0
- package/dist/engines/duckdb/scanner.js +12 -0
- package/dist/engines/duckdb/scanner.js.map +1 -0
- package/dist/engines/duckdb/version-maps.js +67 -0
- package/dist/engines/duckdb/version-maps.js.map +1 -0
- package/dist/engines/duckdb/version-validator.js +62 -0
- package/dist/engines/duckdb/version-validator.js.map +1 -0
- package/dist/engines/ferretdb/backup.js +170 -0
- package/dist/engines/ferretdb/backup.js.map +1 -0
- package/dist/engines/ferretdb/binary-manager.js +765 -0
- package/dist/engines/ferretdb/binary-manager.js.map +1 -0
- package/dist/engines/ferretdb/binary-urls.js +135 -0
- package/dist/engines/ferretdb/binary-urls.js.map +1 -0
- package/dist/engines/ferretdb/index.js +1517 -0
- package/dist/engines/ferretdb/index.js.map +1 -0
- package/dist/engines/ferretdb/restore.js +310 -0
- package/dist/engines/ferretdb/restore.js.map +1 -0
- package/{engines/ferretdb/version-maps.ts → dist/engines/ferretdb/version-maps.js} +62 -79
- package/dist/engines/ferretdb/version-maps.js.map +1 -0
- package/dist/engines/file-based-utils.js +184 -0
- package/dist/engines/file-based-utils.js.map +1 -0
- package/dist/engines/index.js +124 -0
- package/dist/engines/index.js.map +1 -0
- package/dist/engines/influxdb/api-client.js +54 -0
- package/dist/engines/influxdb/api-client.js.map +1 -0
- package/dist/engines/influxdb/backup.js +119 -0
- package/dist/engines/influxdb/backup.js.map +1 -0
- package/dist/engines/influxdb/binary-manager.js +87 -0
- package/dist/engines/influxdb/binary-manager.js.map +1 -0
- package/dist/engines/influxdb/binary-urls.js +56 -0
- package/dist/engines/influxdb/binary-urls.js.map +1 -0
- package/dist/engines/influxdb/hostdb-releases.js +21 -0
- package/dist/engines/influxdb/hostdb-releases.js.map +1 -0
- package/dist/engines/influxdb/index.js +962 -0
- package/dist/engines/influxdb/index.js.map +1 -0
- package/dist/engines/influxdb/restore.js +329 -0
- package/dist/engines/influxdb/restore.js.map +1 -0
- package/dist/engines/influxdb/version-maps.js +64 -0
- package/dist/engines/influxdb/version-maps.js.map +1 -0
- package/dist/engines/influxdb/version-validator.js +109 -0
- package/dist/engines/influxdb/version-validator.js.map +1 -0
- package/dist/engines/mariadb/backup.js +178 -0
- package/dist/engines/mariadb/backup.js.map +1 -0
- package/dist/engines/mariadb/binary-manager.js +33 -0
- package/dist/engines/mariadb/binary-manager.js.map +1 -0
- package/{engines/mariadb/binary-urls.ts → dist/engines/mariadb/binary-urls.js} +38 -55
- package/dist/engines/mariadb/binary-urls.js.map +1 -0
- package/dist/engines/mariadb/hostdb-releases.js +21 -0
- package/dist/engines/mariadb/hostdb-releases.js.map +1 -0
- package/dist/engines/mariadb/index.js +1011 -0
- package/dist/engines/mariadb/index.js.map +1 -0
- package/dist/engines/mariadb/restore.js +322 -0
- package/dist/engines/mariadb/restore.js.map +1 -0
- package/dist/engines/mariadb/version-maps.js +63 -0
- package/dist/engines/mariadb/version-maps.js.map +1 -0
- package/dist/engines/mariadb/version-validator.js +143 -0
- package/dist/engines/mariadb/version-validator.js.map +1 -0
- package/dist/engines/meilisearch/api-client.js +50 -0
- package/dist/engines/meilisearch/api-client.js.map +1 -0
- package/dist/engines/meilisearch/backup.js +167 -0
- package/dist/engines/meilisearch/backup.js.map +1 -0
- package/dist/engines/meilisearch/binary-manager.js +31 -0
- package/dist/engines/meilisearch/binary-manager.js.map +1 -0
- package/dist/engines/meilisearch/binary-urls.js +56 -0
- package/dist/engines/meilisearch/binary-urls.js.map +1 -0
- package/dist/engines/meilisearch/hostdb-releases.js +21 -0
- package/dist/engines/meilisearch/hostdb-releases.js.map +1 -0
- package/dist/engines/meilisearch/index.js +992 -0
- package/dist/engines/meilisearch/index.js.map +1 -0
- package/dist/engines/meilisearch/restore.js +167 -0
- package/dist/engines/meilisearch/restore.js.map +1 -0
- package/dist/engines/meilisearch/version-maps.js +67 -0
- package/dist/engines/meilisearch/version-maps.js.map +1 -0
- package/dist/engines/meilisearch/version-validator.js +109 -0
- package/dist/engines/meilisearch/version-validator.js.map +1 -0
- package/dist/engines/mongodb/backup.js +109 -0
- package/dist/engines/mongodb/backup.js.map +1 -0
- package/dist/engines/mongodb/binary-manager.js +36 -0
- package/dist/engines/mongodb/binary-manager.js.map +1 -0
- package/dist/engines/mongodb/binary-urls.js +46 -0
- package/dist/engines/mongodb/binary-urls.js.map +1 -0
- package/dist/engines/mongodb/cli-utils.js +131 -0
- package/dist/engines/mongodb/cli-utils.js.map +1 -0
- package/dist/engines/mongodb/hostdb-releases.js +77 -0
- package/dist/engines/mongodb/hostdb-releases.js.map +1 -0
- package/dist/engines/mongodb/index.js +873 -0
- package/dist/engines/mongodb/index.js.map +1 -0
- package/dist/engines/mongodb/restore.js +276 -0
- package/dist/engines/mongodb/restore.js.map +1 -0
- package/dist/engines/mongodb/version-maps.js +79 -0
- package/dist/engines/mongodb/version-maps.js.map +1 -0
- package/dist/engines/mongodb/version-validator.js +133 -0
- package/dist/engines/mongodb/version-validator.js.map +1 -0
- package/dist/engines/mysql/backup.js +210 -0
- package/dist/engines/mysql/backup.js.map +1 -0
- package/dist/engines/mysql/binary-detection.js +325 -0
- package/dist/engines/mysql/binary-detection.js.map +1 -0
- package/dist/engines/mysql/binary-manager.js +30 -0
- package/dist/engines/mysql/binary-manager.js.map +1 -0
- package/dist/engines/mysql/binary-urls.js +87 -0
- package/dist/engines/mysql/binary-urls.js.map +1 -0
- package/{engines/mysql/hostdb-releases.ts → dist/engines/mysql/hostdb-releases.js} +20 -23
- package/dist/engines/mysql/hostdb-releases.js.map +1 -0
- package/dist/engines/mysql/index.js +1066 -0
- package/dist/engines/mysql/index.js.map +1 -0
- package/dist/engines/mysql/restore.js +361 -0
- package/dist/engines/mysql/restore.js.map +1 -0
- package/dist/engines/mysql/version-maps.js +79 -0
- package/dist/engines/mysql/version-maps.js.map +1 -0
- package/dist/engines/mysql/version-validator.js +266 -0
- package/dist/engines/mysql/version-validator.js.map +1 -0
- package/dist/engines/postgresql/backup.js +118 -0
- package/dist/engines/postgresql/backup.js.map +1 -0
- package/dist/engines/postgresql/binary-manager.js +85 -0
- package/dist/engines/postgresql/binary-manager.js.map +1 -0
- package/dist/engines/postgresql/binary-urls.js +80 -0
- package/dist/engines/postgresql/binary-urls.js.map +1 -0
- package/dist/engines/postgresql/hostdb-releases.js +21 -0
- package/dist/engines/postgresql/hostdb-releases.js.map +1 -0
- package/dist/engines/postgresql/index.js +852 -0
- package/dist/engines/postgresql/index.js.map +1 -0
- package/dist/engines/postgresql/remote-version.js +109 -0
- package/dist/engines/postgresql/remote-version.js.map +1 -0
- package/dist/engines/postgresql/restore.js +254 -0
- package/dist/engines/postgresql/restore.js.map +1 -0
- package/dist/engines/postgresql/version-maps.js +73 -0
- package/dist/engines/postgresql/version-maps.js.map +1 -0
- package/dist/engines/postgresql/version-validator.js +286 -0
- package/dist/engines/postgresql/version-validator.js.map +1 -0
- package/dist/engines/qdrant/api-client.js +50 -0
- package/dist/engines/qdrant/api-client.js.map +1 -0
- package/dist/engines/qdrant/backup.js +115 -0
- package/dist/engines/qdrant/backup.js.map +1 -0
- package/dist/engines/qdrant/binary-manager.js +31 -0
- package/dist/engines/qdrant/binary-manager.js.map +1 -0
- package/dist/engines/qdrant/binary-urls.js +92 -0
- package/dist/engines/qdrant/binary-urls.js.map +1 -0
- package/dist/engines/qdrant/cli-utils.js +39 -0
- package/dist/engines/qdrant/cli-utils.js.map +1 -0
- package/dist/engines/qdrant/hostdb-releases.js +21 -0
- package/dist/engines/qdrant/hostdb-releases.js.map +1 -0
- package/dist/engines/qdrant/index.js +1002 -0
- package/dist/engines/qdrant/index.js.map +1 -0
- package/dist/engines/qdrant/restore.js +154 -0
- package/dist/engines/qdrant/restore.js.map +1 -0
- package/dist/engines/qdrant/version-maps.js +67 -0
- package/dist/engines/qdrant/version-maps.js.map +1 -0
- package/dist/engines/qdrant/version-validator.js +109 -0
- package/dist/engines/qdrant/version-validator.js.map +1 -0
- package/dist/engines/questdb/backup.js +191 -0
- package/dist/engines/questdb/backup.js.map +1 -0
- package/dist/engines/questdb/binary-manager.js +247 -0
- package/dist/engines/questdb/binary-manager.js.map +1 -0
- package/dist/engines/questdb/binary-urls.js +27 -0
- package/dist/engines/questdb/binary-urls.js.map +1 -0
- package/dist/engines/questdb/hostdb-releases.js +21 -0
- package/dist/engines/questdb/hostdb-releases.js.map +1 -0
- package/dist/engines/questdb/index.js +814 -0
- package/dist/engines/questdb/index.js.map +1 -0
- package/dist/engines/questdb/restore.js +202 -0
- package/dist/engines/questdb/restore.js.map +1 -0
- package/dist/engines/questdb/version-maps.js +33 -0
- package/dist/engines/questdb/version-maps.js.map +1 -0
- package/dist/engines/questdb/version-validator.js +99 -0
- package/dist/engines/questdb/version-validator.js.map +1 -0
- package/dist/engines/redis/backup.js +292 -0
- package/dist/engines/redis/backup.js.map +1 -0
- package/dist/engines/redis/binary-manager.js +32 -0
- package/dist/engines/redis/binary-manager.js.map +1 -0
- package/dist/engines/redis/binary-urls.js +96 -0
- package/dist/engines/redis/binary-urls.js.map +1 -0
- package/dist/engines/redis/cli-utils.js +38 -0
- package/dist/engines/redis/cli-utils.js.map +1 -0
- package/dist/engines/redis/hostdb-releases.js +21 -0
- package/dist/engines/redis/hostdb-releases.js.map +1 -0
- package/dist/engines/redis/index.js +1263 -0
- package/dist/engines/redis/index.js.map +1 -0
- package/dist/engines/redis/restore.js +338 -0
- package/dist/engines/redis/restore.js.map +1 -0
- package/dist/engines/redis/version-maps.js +70 -0
- package/dist/engines/redis/version-maps.js.map +1 -0
- package/dist/engines/redis/version-validator.js +109 -0
- package/dist/engines/redis/version-validator.js.map +1 -0
- package/dist/engines/sqlite/binary-manager.js +39 -0
- package/dist/engines/sqlite/binary-manager.js.map +1 -0
- package/{engines/sqlite/binary-urls.ts → dist/engines/sqlite/binary-urls.js} +11 -16
- package/dist/engines/sqlite/binary-urls.js.map +1 -0
- package/dist/engines/sqlite/hostdb-releases.js +21 -0
- package/dist/engines/sqlite/hostdb-releases.js.map +1 -0
- package/dist/engines/sqlite/index.js +493 -0
- package/dist/engines/sqlite/index.js.map +1 -0
- package/dist/engines/sqlite/registry.js +163 -0
- package/dist/engines/sqlite/registry.js.map +1 -0
- package/dist/engines/sqlite/scanner.js +12 -0
- package/dist/engines/sqlite/scanner.js.map +1 -0
- package/dist/engines/sqlite/version-maps.js +57 -0
- package/dist/engines/sqlite/version-maps.js.map +1 -0
- package/dist/engines/surrealdb/backup.js +97 -0
- package/dist/engines/surrealdb/backup.js.map +1 -0
- package/dist/engines/surrealdb/binary-manager.js +33 -0
- package/dist/engines/surrealdb/binary-manager.js.map +1 -0
- package/dist/engines/surrealdb/binary-urls.js +33 -0
- package/dist/engines/surrealdb/binary-urls.js.map +1 -0
- package/dist/engines/surrealdb/cli-utils.js +147 -0
- package/dist/engines/surrealdb/cli-utils.js.map +1 -0
- package/dist/engines/surrealdb/hostdb-releases.js +21 -0
- package/dist/engines/surrealdb/hostdb-releases.js.map +1 -0
- package/dist/engines/surrealdb/index.js +1022 -0
- package/dist/engines/surrealdb/index.js.map +1 -0
- package/dist/engines/surrealdb/restore.js +224 -0
- package/dist/engines/surrealdb/restore.js.map +1 -0
- package/dist/engines/surrealdb/version-maps.js +36 -0
- package/dist/engines/surrealdb/version-maps.js.map +1 -0
- package/dist/engines/tigerbeetle/backup.js +36 -0
- package/dist/engines/tigerbeetle/backup.js.map +1 -0
- package/dist/engines/tigerbeetle/binary-manager.js +72 -0
- package/dist/engines/tigerbeetle/binary-manager.js.map +1 -0
- package/dist/engines/tigerbeetle/binary-urls.js +49 -0
- package/dist/engines/tigerbeetle/binary-urls.js.map +1 -0
- package/dist/engines/tigerbeetle/hostdb-releases.js +21 -0
- package/dist/engines/tigerbeetle/hostdb-releases.js.map +1 -0
- package/dist/engines/tigerbeetle/index.js +559 -0
- package/dist/engines/tigerbeetle/index.js.map +1 -0
- package/dist/engines/tigerbeetle/restore.js +91 -0
- package/dist/engines/tigerbeetle/restore.js.map +1 -0
- package/{engines/tigerbeetle/version-maps.ts → dist/engines/tigerbeetle/version-maps.js} +22 -31
- package/dist/engines/tigerbeetle/version-maps.js.map +1 -0
- package/dist/engines/tigerbeetle/version-validator.js +108 -0
- package/dist/engines/tigerbeetle/version-validator.js.map +1 -0
- package/dist/engines/typedb/backup.js +129 -0
- package/dist/engines/typedb/backup.js.map +1 -0
- package/dist/engines/typedb/binary-manager.js +151 -0
- package/dist/engines/typedb/binary-manager.js.map +1 -0
- package/dist/engines/typedb/binary-urls.js +33 -0
- package/dist/engines/typedb/binary-urls.js.map +1 -0
- package/dist/engines/typedb/cli-utils.js +163 -0
- package/dist/engines/typedb/cli-utils.js.map +1 -0
- package/dist/engines/typedb/hostdb-releases.js +21 -0
- package/dist/engines/typedb/hostdb-releases.js.map +1 -0
- package/dist/engines/typedb/index.js +1003 -0
- package/dist/engines/typedb/index.js.map +1 -0
- package/dist/engines/typedb/restore.js +279 -0
- package/dist/engines/typedb/restore.js.map +1 -0
- package/dist/engines/typedb/version-maps.js +40 -0
- package/dist/engines/typedb/version-maps.js.map +1 -0
- package/dist/engines/typedb/version-validator.js +103 -0
- package/dist/engines/typedb/version-validator.js.map +1 -0
- package/dist/engines/valkey/backup.js +292 -0
- package/dist/engines/valkey/backup.js.map +1 -0
- package/dist/engines/valkey/binary-manager.js +33 -0
- package/dist/engines/valkey/binary-manager.js.map +1 -0
- package/dist/engines/valkey/binary-urls.js +98 -0
- package/dist/engines/valkey/binary-urls.js.map +1 -0
- package/dist/engines/valkey/cli-utils.js +38 -0
- package/dist/engines/valkey/cli-utils.js.map +1 -0
- package/dist/engines/valkey/hostdb-releases.js +21 -0
- package/dist/engines/valkey/hostdb-releases.js.map +1 -0
- package/dist/engines/valkey/index.js +1257 -0
- package/dist/engines/valkey/index.js.map +1 -0
- package/dist/engines/valkey/restore.js +340 -0
- package/dist/engines/valkey/restore.js.map +1 -0
- package/dist/engines/valkey/version-maps.js +70 -0
- package/dist/engines/valkey/version-maps.js.map +1 -0
- package/dist/engines/valkey/version-validator.js +112 -0
- package/dist/engines/valkey/version-validator.js.map +1 -0
- package/dist/engines/weaviate/api-client.js +50 -0
- package/dist/engines/weaviate/api-client.js.map +1 -0
- package/dist/engines/weaviate/backup.js +95 -0
- package/dist/engines/weaviate/backup.js.map +1 -0
- package/dist/engines/weaviate/binary-manager.js +58 -0
- package/dist/engines/weaviate/binary-manager.js.map +1 -0
- package/dist/engines/weaviate/binary-urls.js +92 -0
- package/dist/engines/weaviate/binary-urls.js.map +1 -0
- package/dist/engines/weaviate/cli-utils.js +39 -0
- package/dist/engines/weaviate/cli-utils.js.map +1 -0
- package/dist/engines/weaviate/hostdb-releases.js +21 -0
- package/dist/engines/weaviate/hostdb-releases.js.map +1 -0
- package/dist/engines/weaviate/index.js +871 -0
- package/dist/engines/weaviate/index.js.map +1 -0
- package/dist/engines/weaviate/restore.js +185 -0
- package/dist/engines/weaviate/restore.js.map +1 -0
- package/dist/engines/weaviate/version-maps.js +67 -0
- package/dist/engines/weaviate/version-maps.js.map +1 -0
- package/dist/engines/weaviate/version-validator.js +109 -0
- package/dist/engines/weaviate/version-validator.js.map +1 -0
- package/dist/types/index.js +102 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +12 -9
- package/bin/cli.js +0 -68
- package/cli/bin.ts +0 -10
- package/cli/commands/attach.ts +0 -139
- package/cli/commands/backup.ts +0 -290
- package/cli/commands/backups.ts +0 -247
- package/cli/commands/clone.ts +0 -159
- package/cli/commands/config.ts +0 -367
- package/cli/commands/connect.ts +0 -684
- package/cli/commands/create.ts +0 -1201
- package/cli/commands/databases.ts +0 -630
- package/cli/commands/delete.ts +0 -133
- package/cli/commands/deps.ts +0 -342
- package/cli/commands/detach.ts +0 -107
- package/cli/commands/doctor.ts +0 -689
- package/cli/commands/duckdb.ts +0 -273
- package/cli/commands/edit.ts +0 -683
- package/cli/commands/engines.ts +0 -1914
- package/cli/commands/export.ts +0 -544
- package/cli/commands/info.ts +0 -340
- package/cli/commands/list.ts +0 -284
- package/cli/commands/logs.ts +0 -102
- package/cli/commands/menu/backup-handlers.ts +0 -1571
- package/cli/commands/menu/container-handlers.ts +0 -2288
- package/cli/commands/menu/engine-handlers.ts +0 -355
- package/cli/commands/menu/index.ts +0 -342
- package/cli/commands/menu/settings-handlers.ts +0 -365
- package/cli/commands/menu/shared.ts +0 -23
- package/cli/commands/menu/shell-handlers.ts +0 -1811
- package/cli/commands/menu/sql-handlers.ts +0 -231
- package/cli/commands/menu/update-handlers.ts +0 -378
- package/cli/commands/menu/validators.ts +0 -8
- package/cli/commands/ports.ts +0 -211
- package/cli/commands/pull.ts +0 -223
- package/cli/commands/query.ts +0 -241
- package/cli/commands/restore.ts +0 -587
- package/cli/commands/run.ts +0 -178
- package/cli/commands/self-update.ts +0 -121
- package/cli/commands/sqlite.ts +0 -273
- package/cli/commands/start.ts +0 -218
- package/cli/commands/stop.ts +0 -241
- package/cli/commands/url.ts +0 -104
- package/cli/commands/users.ts +0 -264
- package/cli/commands/version.ts +0 -55
- package/cli/commands/which.ts +0 -290
- package/cli/constants.ts +0 -233
- package/cli/helpers.ts +0 -1593
- package/cli/index.ts +0 -162
- package/cli/ui/prompts.ts +0 -1525
- package/cli/ui/spinner.ts +0 -88
- package/cli/ui/theme.ts +0 -128
- package/cli/utils/file-follower.ts +0 -93
- package/config/backup-formats.ts +0 -446
- package/config/defaults.ts +0 -56
- package/config/engine-defaults.ts +0 -336
- package/config/engines-registry.ts +0 -150
- package/config/engines.schema.json +0 -135
- package/config/os-dependencies.ts +0 -888
- package/config/paths.ts +0 -200
- package/core/backup-restore.ts +0 -330
- package/core/base-binary-manager.ts +0 -562
- package/core/base-document-binary-manager.ts +0 -523
- package/core/base-embedded-binary-manager.ts +0 -547
- package/core/base-server-binary-manager.ts +0 -523
- package/core/config-manager.ts +0 -652
- package/core/container-manager.ts +0 -787
- package/core/credential-generator.ts +0 -93
- package/core/credential-manager.ts +0 -259
- package/core/dblab-utils.ts +0 -113
- package/core/dependency-manager.ts +0 -512
- package/core/docker-exporter.ts +0 -1345
- package/core/error-handler.ts +0 -419
- package/core/fs-error-utils.ts +0 -82
- package/core/homebrew-version-manager.ts +0 -352
- package/core/hostdb-client.ts +0 -344
- package/core/hostdb-metadata.ts +0 -350
- package/core/hostdb-releases-factory.ts +0 -237
- package/core/library-env.ts +0 -118
- package/core/pgweb-utils.ts +0 -62
- package/core/platform-service.ts +0 -829
- package/core/port-manager.ts +0 -165
- package/core/process-manager.ts +0 -576
- package/core/pull-manager.ts +0 -511
- package/core/query-parser.ts +0 -514
- package/core/spawn-utils.ts +0 -122
- package/core/start-with-retry.ts +0 -130
- package/core/test-cleanup.ts +0 -108
- package/core/tls-generator.ts +0 -116
- package/core/transaction-manager.ts +0 -158
- package/core/update-manager.ts +0 -308
- package/core/version-migration.ts +0 -346
- package/core/version-utils.ts +0 -104
- package/engines/base-engine.ts +0 -340
- package/engines/clickhouse/README.md +0 -231
- package/engines/clickhouse/backup.ts +0 -398
- package/engines/clickhouse/binary-manager.ts +0 -201
- package/engines/clickhouse/binary-urls.ts +0 -125
- package/engines/clickhouse/cli-utils.ts +0 -176
- package/engines/clickhouse/hostdb-releases.ts +0 -30
- package/engines/clickhouse/index.ts +0 -1345
- package/engines/clickhouse/restore.ts +0 -466
- package/engines/clickhouse/version-maps.ts +0 -95
- package/engines/clickhouse/version-validator.ts +0 -154
- package/engines/cockroachdb/README.md +0 -170
- package/engines/cockroachdb/backup.ts +0 -376
- package/engines/cockroachdb/binary-manager.ts +0 -45
- package/engines/cockroachdb/binary-urls.ts +0 -40
- package/engines/cockroachdb/cli-utils.ts +0 -384
- package/engines/cockroachdb/hostdb-releases.ts +0 -26
- package/engines/cockroachdb/index.ts +0 -1276
- package/engines/cockroachdb/restore.ts +0 -455
- package/engines/cockroachdb/version-maps.ts +0 -42
- package/engines/couchdb/README.md +0 -257
- package/engines/couchdb/api-client.ts +0 -81
- package/engines/couchdb/backup.ts +0 -137
- package/engines/couchdb/binary-manager.ts +0 -86
- package/engines/couchdb/binary-urls.ts +0 -115
- package/engines/couchdb/hostdb-releases.ts +0 -23
- package/engines/couchdb/index.ts +0 -1429
- package/engines/couchdb/restore.ts +0 -290
- package/engines/couchdb/version-maps.ts +0 -78
- package/engines/couchdb/version-validator.ts +0 -111
- package/engines/duckdb/README.md +0 -154
- package/engines/duckdb/binary-manager.ts +0 -45
- package/engines/duckdb/hostdb-releases.ts +0 -23
- package/engines/duckdb/index.ts +0 -749
- package/engines/duckdb/registry.ts +0 -303
- package/engines/duckdb/scanner.ts +0 -22
- package/engines/duckdb/version-maps.ts +0 -78
- package/engines/duckdb/version-validator.ts +0 -78
- package/engines/ferretdb/README.md +0 -262
- package/engines/ferretdb/backup.ts +0 -173
- package/engines/ferretdb/binary-manager.ts +0 -1095
- package/engines/ferretdb/binary-urls.ts +0 -183
- package/engines/ferretdb/index.ts +0 -1907
- package/engines/ferretdb/restore.ts +0 -357
- package/engines/file-based-utils.ts +0 -262
- package/engines/index.ts +0 -131
- package/engines/influxdb/README.md +0 -180
- package/engines/influxdb/api-client.ts +0 -64
- package/engines/influxdb/backup.ts +0 -160
- package/engines/influxdb/binary-manager.ts +0 -110
- package/engines/influxdb/binary-urls.ts +0 -69
- package/engines/influxdb/hostdb-releases.ts +0 -23
- package/engines/influxdb/index.ts +0 -1272
- package/engines/influxdb/restore.ts +0 -417
- package/engines/influxdb/version-maps.ts +0 -75
- package/engines/influxdb/version-validator.ts +0 -128
- package/engines/mariadb/README.md +0 -141
- package/engines/mariadb/backup.ts +0 -233
- package/engines/mariadb/binary-manager.ts +0 -45
- package/engines/mariadb/hostdb-releases.ts +0 -23
- package/engines/mariadb/index.ts +0 -1300
- package/engines/mariadb/restore.ts +0 -447
- package/engines/mariadb/version-maps.ts +0 -72
- package/engines/mariadb/version-validator.ts +0 -181
- package/engines/meilisearch/README.md +0 -255
- package/engines/meilisearch/api-client.ts +0 -61
- package/engines/meilisearch/backup.ts +0 -233
- package/engines/meilisearch/binary-manager.ts +0 -43
- package/engines/meilisearch/binary-urls.ts +0 -69
- package/engines/meilisearch/hostdb-releases.ts +0 -26
- package/engines/meilisearch/index.ts +0 -1292
- package/engines/meilisearch/restore.ts +0 -219
- package/engines/meilisearch/version-maps.ts +0 -78
- package/engines/meilisearch/version-validator.ts +0 -128
- package/engines/mongodb/README.md +0 -162
- package/engines/mongodb/backup.ts +0 -127
- package/engines/mongodb/binary-manager.ts +0 -48
- package/engines/mongodb/binary-urls.ts +0 -63
- package/engines/mongodb/cli-utils.ts +0 -171
- package/engines/mongodb/hostdb-releases.ts +0 -91
- package/engines/mongodb/index.ts +0 -1118
- package/engines/mongodb/restore.ts +0 -361
- package/engines/mongodb/version-maps.ts +0 -91
- package/engines/mongodb/version-validator.ts +0 -160
- package/engines/mysql/README.md +0 -142
- package/engines/mysql/backup.ts +0 -270
- package/engines/mysql/binary-detection.ts +0 -408
- package/engines/mysql/binary-manager.ts +0 -42
- package/engines/mysql/binary-urls.ts +0 -104
- package/engines/mysql/index.ts +0 -1361
- package/engines/mysql/restore.ts +0 -500
- package/engines/mysql/version-maps.ts +0 -91
- package/engines/mysql/version-validator.ts +0 -369
- package/engines/postgresql/README.md +0 -158
- package/engines/postgresql/backup.ts +0 -151
- package/engines/postgresql/binary-manager.ts +0 -114
- package/engines/postgresql/binary-urls.ts +0 -99
- package/engines/postgresql/hostdb-releases.ts +0 -26
- package/engines/postgresql/index.ts +0 -1143
- package/engines/postgresql/remote-version.ts +0 -161
- package/engines/postgresql/restore.ts +0 -342
- package/engines/postgresql/version-maps.ts +0 -83
- package/engines/postgresql/version-validator.ts +0 -413
- package/engines/qdrant/README.md +0 -222
- package/engines/qdrant/api-client.ts +0 -61
- package/engines/qdrant/backup.ts +0 -165
- package/engines/qdrant/binary-manager.ts +0 -43
- package/engines/qdrant/binary-urls.ts +0 -115
- package/engines/qdrant/cli-utils.ts +0 -43
- package/engines/qdrant/hostdb-releases.ts +0 -23
- package/engines/qdrant/index.ts +0 -1312
- package/engines/qdrant/restore.ts +0 -203
- package/engines/qdrant/version-maps.ts +0 -78
- package/engines/qdrant/version-validator.ts +0 -128
- package/engines/questdb/README.md +0 -334
- package/engines/questdb/backup.ts +0 -220
- package/engines/questdb/binary-manager.ts +0 -310
- package/engines/questdb/binary-urls.ts +0 -34
- package/engines/questdb/hostdb-releases.ts +0 -23
- package/engines/questdb/index.ts +0 -1023
- package/engines/questdb/restore.ts +0 -260
- package/engines/questdb/version-maps.ts +0 -37
- package/engines/questdb/version-validator.ts +0 -121
- package/engines/redis/README.md +0 -173
- package/engines/redis/backup.ts +0 -389
- package/engines/redis/binary-manager.ts +0 -44
- package/engines/redis/binary-urls.ts +0 -117
- package/engines/redis/cli-utils.ts +0 -42
- package/engines/redis/hostdb-releases.ts +0 -23
- package/engines/redis/index.ts +0 -1583
- package/engines/redis/restore.ts +0 -443
- package/engines/redis/version-maps.ts +0 -81
- package/engines/redis/version-validator.ts +0 -131
- package/engines/sqlite/README.md +0 -162
- package/engines/sqlite/binary-manager.ts +0 -52
- package/engines/sqlite/hostdb-releases.ts +0 -23
- package/engines/sqlite/index.ts +0 -641
- package/engines/sqlite/registry.ts +0 -198
- package/engines/sqlite/scanner.ts +0 -22
- package/engines/sqlite/version-maps.ts +0 -64
- package/engines/surrealdb/README.md +0 -218
- package/engines/surrealdb/backup.ts +0 -131
- package/engines/surrealdb/binary-manager.ts +0 -45
- package/engines/surrealdb/binary-urls.ts +0 -40
- package/engines/surrealdb/cli-utils.ts +0 -173
- package/engines/surrealdb/hostdb-releases.ts +0 -23
- package/engines/surrealdb/index.ts +0 -1246
- package/engines/surrealdb/restore.ts +0 -302
- package/engines/surrealdb/version-maps.ts +0 -41
- package/engines/tigerbeetle/README.md +0 -61
- package/engines/tigerbeetle/backup.ts +0 -49
- package/engines/tigerbeetle/binary-manager.ts +0 -95
- package/engines/tigerbeetle/binary-urls.ts +0 -62
- package/engines/tigerbeetle/hostdb-releases.ts +0 -26
- package/engines/tigerbeetle/index.ts +0 -746
- package/engines/tigerbeetle/restore.ts +0 -130
- package/engines/tigerbeetle/version-validator.ts +0 -126
- package/engines/typedb/backup.ts +0 -167
- package/engines/typedb/binary-manager.ts +0 -200
- package/engines/typedb/binary-urls.ts +0 -40
- package/engines/typedb/cli-utils.ts +0 -210
- package/engines/typedb/hostdb-releases.ts +0 -23
- package/engines/typedb/index.ts +0 -1275
- package/engines/typedb/restore.ts +0 -377
- package/engines/typedb/version-maps.ts +0 -48
- package/engines/typedb/version-validator.ts +0 -127
- package/engines/valkey/README.md +0 -219
- package/engines/valkey/backup.ts +0 -389
- package/engines/valkey/binary-manager.ts +0 -45
- package/engines/valkey/binary-urls.ts +0 -122
- package/engines/valkey/cli-utils.ts +0 -42
- package/engines/valkey/hostdb-releases.ts +0 -23
- package/engines/valkey/index.ts +0 -1585
- package/engines/valkey/restore.ts +0 -446
- package/engines/valkey/version-maps.ts +0 -81
- package/engines/valkey/version-validator.ts +0 -131
- package/engines/weaviate/README.md +0 -302
- package/engines/weaviate/api-client.ts +0 -61
- package/engines/weaviate/backup.ts +0 -145
- package/engines/weaviate/binary-manager.ts +0 -80
- package/engines/weaviate/binary-urls.ts +0 -115
- package/engines/weaviate/cli-utils.ts +0 -43
- package/engines/weaviate/hostdb-releases.ts +0 -23
- package/engines/weaviate/index.ts +0 -1139
- package/engines/weaviate/restore.ts +0 -235
- package/engines/weaviate/version-maps.ts +0 -78
- package/engines/weaviate/version-validator.ts +0 -128
- package/types/index.ts +0 -624
- /package/{config → dist/config}/engines.json +0 -0
|
@@ -1,2288 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import inquirer from 'inquirer'
|
|
3
|
-
import {
|
|
4
|
-
existsSync,
|
|
5
|
-
renameSync,
|
|
6
|
-
statSync,
|
|
7
|
-
mkdirSync,
|
|
8
|
-
copyFileSync,
|
|
9
|
-
unlinkSync,
|
|
10
|
-
} from 'fs'
|
|
11
|
-
import { stat, mkdir, rm } from 'fs/promises'
|
|
12
|
-
import { dirname, basename, join, resolve } from 'path'
|
|
13
|
-
import { homedir } from 'os'
|
|
14
|
-
import { containerManager } from '../../../core/container-manager'
|
|
15
|
-
import { getMissingDependencies } from '../../../core/dependency-manager'
|
|
16
|
-
import { platformService } from '../../../core/platform-service'
|
|
17
|
-
import { portManager } from '../../../core/port-manager'
|
|
18
|
-
import { processManager } from '../../../core/process-manager'
|
|
19
|
-
import { getEngine } from '../../../engines'
|
|
20
|
-
import { BaseEngine } from '../../../engines/base-engine'
|
|
21
|
-
import { sqliteRegistry } from '../../../engines/sqlite/registry'
|
|
22
|
-
import { duckdbRegistry } from '../../../engines/duckdb/registry'
|
|
23
|
-
import { defaults } from '../../../config/defaults'
|
|
24
|
-
import { getEngineConfig } from '../../../config/engines-registry'
|
|
25
|
-
import { getPageSize } from '../../constants'
|
|
26
|
-
import { paths } from '../../../config/paths'
|
|
27
|
-
import {
|
|
28
|
-
promptContainerName,
|
|
29
|
-
promptContainerSelect,
|
|
30
|
-
promptInstallDependencies,
|
|
31
|
-
promptConfirm,
|
|
32
|
-
promptEngine,
|
|
33
|
-
promptVersion,
|
|
34
|
-
promptPort,
|
|
35
|
-
promptDatabaseName,
|
|
36
|
-
promptFileDatabasePath,
|
|
37
|
-
escapeablePrompt,
|
|
38
|
-
filterableListPrompt,
|
|
39
|
-
type FilterableChoice,
|
|
40
|
-
BACK_VALUE,
|
|
41
|
-
MAIN_MENU_VALUE,
|
|
42
|
-
TOGGLE_PREFIX,
|
|
43
|
-
} from '../../ui/prompts'
|
|
44
|
-
import { getEngineDefaults } from '../../../config/defaults'
|
|
45
|
-
import { createSpinner } from '../../ui/spinner'
|
|
46
|
-
import {
|
|
47
|
-
header,
|
|
48
|
-
uiSuccess,
|
|
49
|
-
uiError,
|
|
50
|
-
uiWarning,
|
|
51
|
-
uiInfo,
|
|
52
|
-
connectionBox,
|
|
53
|
-
formatBytes,
|
|
54
|
-
box,
|
|
55
|
-
} from '../../ui/theme'
|
|
56
|
-
import {
|
|
57
|
-
handleOpenShell,
|
|
58
|
-
handleCopyConnectionString,
|
|
59
|
-
stopPgwebProcess,
|
|
60
|
-
} from './shell-handlers'
|
|
61
|
-
import { getPgwebStatus } from '../../../core/pgweb-utils'
|
|
62
|
-
import { generatePassword } from '../../../core/credential-generator'
|
|
63
|
-
import {
|
|
64
|
-
saveCredentials,
|
|
65
|
-
credentialsExist,
|
|
66
|
-
getDefaultUsername,
|
|
67
|
-
} from '../../../core/credential-manager'
|
|
68
|
-
import {
|
|
69
|
-
UnsupportedOperationError,
|
|
70
|
-
isValidUsername,
|
|
71
|
-
} from '../../../core/error-handler'
|
|
72
|
-
import { handleRunSql, handleViewLogs } from './sql-handlers'
|
|
73
|
-
import {
|
|
74
|
-
handleBackupForContainer,
|
|
75
|
-
handleRestoreForContainer,
|
|
76
|
-
} from './backup-handlers'
|
|
77
|
-
import {
|
|
78
|
-
exportToDocker,
|
|
79
|
-
getExportBackupPath,
|
|
80
|
-
dockerExportExists,
|
|
81
|
-
getDockerConnectionString,
|
|
82
|
-
} from '../../../core/docker-exporter'
|
|
83
|
-
import { getDefaultFormat } from '../../../config/backup-formats'
|
|
84
|
-
import { Engine, isFileBasedEngine } from '../../../types'
|
|
85
|
-
import { type MenuChoice, pressEnterToContinue } from './shared'
|
|
86
|
-
import { getEngineIcon } from '../../constants'
|
|
87
|
-
|
|
88
|
-
export async function handleCreate(): Promise<'main' | string | void> {
|
|
89
|
-
console.log()
|
|
90
|
-
console.log(header('Create New Database Container'))
|
|
91
|
-
console.log()
|
|
92
|
-
|
|
93
|
-
// Wizard state - all values start as null
|
|
94
|
-
let selectedEngine: string | null = null
|
|
95
|
-
let selectedVersion: string | null = null
|
|
96
|
-
let containerName: string | null = null
|
|
97
|
-
|
|
98
|
-
// Step 1: Engine selection (back returns to main menu)
|
|
99
|
-
while (selectedEngine === null) {
|
|
100
|
-
const result = await promptEngine({ includeBack: true })
|
|
101
|
-
if (result === MAIN_MENU_VALUE) return 'main'
|
|
102
|
-
if (result === BACK_VALUE) return // Back to parent menu
|
|
103
|
-
selectedEngine = result
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Step 2: Version selection (back returns to engine)
|
|
107
|
-
while (selectedVersion === null) {
|
|
108
|
-
const result = await promptVersion(selectedEngine!, { includeBack: true })
|
|
109
|
-
if (result === MAIN_MENU_VALUE) return 'main'
|
|
110
|
-
if (result === BACK_VALUE) {
|
|
111
|
-
selectedEngine = null
|
|
112
|
-
continue
|
|
113
|
-
}
|
|
114
|
-
selectedVersion = result
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Step 3: Container name (back returns to version)
|
|
118
|
-
while (containerName === null) {
|
|
119
|
-
const result = await promptContainerName(undefined, { allowBack: true })
|
|
120
|
-
if (result === null) {
|
|
121
|
-
selectedVersion = null
|
|
122
|
-
continue
|
|
123
|
-
}
|
|
124
|
-
containerName = result
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// At this point, all wizard values are guaranteed to be set
|
|
128
|
-
const engine = selectedEngine!
|
|
129
|
-
const version = selectedVersion!
|
|
130
|
-
const name = containerName!
|
|
131
|
-
|
|
132
|
-
// Step 4: Database name (defaults to container name, sanitized)
|
|
133
|
-
// Redis and Valkey use numbered databases 0-15, so skip prompt and default to "0"
|
|
134
|
-
// Qdrant uses collections (not databases), so default to "default"
|
|
135
|
-
// Meilisearch uses indexes (not databases), so default to "default"
|
|
136
|
-
let database: string
|
|
137
|
-
if (engine === 'redis' || engine === 'valkey') {
|
|
138
|
-
database = '0'
|
|
139
|
-
} else if (engine === 'qdrant' || engine === 'meilisearch') {
|
|
140
|
-
database = 'default'
|
|
141
|
-
} else if (engine === 'influxdb') {
|
|
142
|
-
database = 'mydb'
|
|
143
|
-
} else {
|
|
144
|
-
database = await promptDatabaseName(name, engine)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Step 5: Port or file path (SQLite/DuckDB)
|
|
148
|
-
const isSQLite = engine === 'sqlite'
|
|
149
|
-
const isDuckDB = engine === 'duckdb'
|
|
150
|
-
const isFileBasedDB = isSQLite || isDuckDB
|
|
151
|
-
let port: number
|
|
152
|
-
let filePath: string | undefined = undefined
|
|
153
|
-
if (isFileBasedDB) {
|
|
154
|
-
// File-based databases don't need a port, but need a path
|
|
155
|
-
const defaultExtension = isDuckDB ? '.duckdb' : '.sqlite'
|
|
156
|
-
filePath = await promptFileDatabasePath(name, defaultExtension)
|
|
157
|
-
port = 0
|
|
158
|
-
} else {
|
|
159
|
-
const engineDefaults = getEngineDefaults(engine)
|
|
160
|
-
port = await promptPort(engineDefaults.defaultPort, engine)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Now we have all values - proceed with container creation
|
|
164
|
-
let containerNameFinal = name
|
|
165
|
-
|
|
166
|
-
console.log()
|
|
167
|
-
console.log(header('Creating Database Container'))
|
|
168
|
-
console.log()
|
|
169
|
-
|
|
170
|
-
const dbEngine = getEngine(engine)
|
|
171
|
-
const isPostgreSQL = engine === 'postgresql'
|
|
172
|
-
|
|
173
|
-
// For PostgreSQL and file-based DBs, download binaries FIRST
|
|
174
|
-
// They include client tools needed for subsequent operations
|
|
175
|
-
let portAvailable = true
|
|
176
|
-
if (isPostgreSQL || isFileBasedDB) {
|
|
177
|
-
if (!isFileBasedDB) {
|
|
178
|
-
portAvailable = await portManager.isPortAvailable(port)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const binarySpinner = createSpinner(
|
|
182
|
-
`Checking ${dbEngine.displayName} ${version} binaries...`,
|
|
183
|
-
)
|
|
184
|
-
binarySpinner.start()
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
const isInstalled = await dbEngine.isBinaryInstalled(version)
|
|
188
|
-
if (isInstalled) {
|
|
189
|
-
binarySpinner.succeed(
|
|
190
|
-
`${dbEngine.displayName} ${version} binaries ready (cached)`,
|
|
191
|
-
)
|
|
192
|
-
} else {
|
|
193
|
-
binarySpinner.text = `Downloading ${dbEngine.displayName} ${version} binaries...`
|
|
194
|
-
await dbEngine.ensureBinaries(version, ({ message }) => {
|
|
195
|
-
binarySpinner.text = message
|
|
196
|
-
})
|
|
197
|
-
binarySpinner.succeed(
|
|
198
|
-
`${dbEngine.displayName} ${version} binaries downloaded`,
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
} catch (error) {
|
|
202
|
-
binarySpinner.fail(`Failed to download ${dbEngine.displayName} binaries`)
|
|
203
|
-
const e = error as Error
|
|
204
|
-
console.log()
|
|
205
|
-
console.log(uiError(e.message))
|
|
206
|
-
console.log()
|
|
207
|
-
await pressEnterToContinue()
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Check dependencies (all engines need this)
|
|
213
|
-
// For PostgreSQL, this runs AFTER binary download so client tools are available
|
|
214
|
-
const depsSpinner = createSpinner('Checking required tools...')
|
|
215
|
-
depsSpinner.start()
|
|
216
|
-
|
|
217
|
-
let missingDeps = await getMissingDependencies(engine)
|
|
218
|
-
if (missingDeps.length > 0) {
|
|
219
|
-
depsSpinner.warn(
|
|
220
|
-
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
const installed = await promptInstallDependencies(
|
|
224
|
-
missingDeps[0].binary,
|
|
225
|
-
engine,
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
if (!installed) {
|
|
229
|
-
console.log()
|
|
230
|
-
console.log(
|
|
231
|
-
uiWarning(
|
|
232
|
-
'Container creation cancelled - required tools not installed.',
|
|
233
|
-
),
|
|
234
|
-
)
|
|
235
|
-
await pressEnterToContinue()
|
|
236
|
-
return
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
missingDeps = await getMissingDependencies(engine)
|
|
240
|
-
if (missingDeps.length > 0) {
|
|
241
|
-
console.log(
|
|
242
|
-
uiError(
|
|
243
|
-
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
244
|
-
),
|
|
245
|
-
)
|
|
246
|
-
await pressEnterToContinue()
|
|
247
|
-
return
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
console.log(chalk.green(' ✓ All required tools are now available'))
|
|
251
|
-
console.log()
|
|
252
|
-
} else {
|
|
253
|
-
depsSpinner.succeed('Required tools available')
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Server databases (MySQL): check port and binaries
|
|
257
|
-
// PostgreSQL already handled above
|
|
258
|
-
if (!isFileBasedDB && !isPostgreSQL) {
|
|
259
|
-
portAvailable = await portManager.isPortAvailable(port)
|
|
260
|
-
|
|
261
|
-
const binarySpinner = createSpinner(
|
|
262
|
-
`Checking ${dbEngine.displayName} ${version} binaries...`,
|
|
263
|
-
)
|
|
264
|
-
binarySpinner.start()
|
|
265
|
-
|
|
266
|
-
try {
|
|
267
|
-
const isInstalled = await dbEngine.isBinaryInstalled(version)
|
|
268
|
-
if (isInstalled) {
|
|
269
|
-
binarySpinner.succeed(
|
|
270
|
-
`${dbEngine.displayName} ${version} binaries ready (cached)`,
|
|
271
|
-
)
|
|
272
|
-
} else {
|
|
273
|
-
binarySpinner.text = `Downloading ${dbEngine.displayName} ${version} binaries...`
|
|
274
|
-
await dbEngine.ensureBinaries(version, ({ message }) => {
|
|
275
|
-
binarySpinner.text = message
|
|
276
|
-
})
|
|
277
|
-
binarySpinner.succeed(
|
|
278
|
-
`${dbEngine.displayName} ${version} binaries downloaded`,
|
|
279
|
-
)
|
|
280
|
-
}
|
|
281
|
-
} catch (error) {
|
|
282
|
-
binarySpinner.fail(`Failed to download ${dbEngine.displayName} binaries`)
|
|
283
|
-
const e = error as Error
|
|
284
|
-
console.log()
|
|
285
|
-
console.log(uiError(e.message))
|
|
286
|
-
console.log()
|
|
287
|
-
await pressEnterToContinue()
|
|
288
|
-
return
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
while (await containerManager.exists(containerNameFinal)) {
|
|
293
|
-
console.log(
|
|
294
|
-
chalk.yellow(` Container "${containerNameFinal}" already exists.`),
|
|
295
|
-
)
|
|
296
|
-
const newName = await promptContainerName(undefined, { allowBack: true })
|
|
297
|
-
if (!newName) {
|
|
298
|
-
console.log(chalk.blue(' Container creation cancelled.'))
|
|
299
|
-
return
|
|
300
|
-
}
|
|
301
|
-
containerNameFinal = newName
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const createSpinnerInstance = createSpinner('Creating container...')
|
|
305
|
-
createSpinnerInstance.start()
|
|
306
|
-
|
|
307
|
-
await containerManager.create(containerNameFinal, {
|
|
308
|
-
engine: dbEngine.name as Engine,
|
|
309
|
-
version,
|
|
310
|
-
port,
|
|
311
|
-
database,
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
createSpinnerInstance.succeed('Container created')
|
|
315
|
-
|
|
316
|
-
const initSpinner = createSpinner(
|
|
317
|
-
isFileBasedDB
|
|
318
|
-
? 'Creating database file...'
|
|
319
|
-
: 'Initializing database cluster...',
|
|
320
|
-
)
|
|
321
|
-
initSpinner.start()
|
|
322
|
-
|
|
323
|
-
await dbEngine.initDataDir(containerNameFinal, version, {
|
|
324
|
-
superuser: defaults.superuser,
|
|
325
|
-
path: filePath, // File-based DB path (undefined for server databases)
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
initSpinner.succeed(
|
|
329
|
-
isFileBasedDB ? 'Database file created' : 'Database cluster initialized',
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
// File-based databases (SQLite/DuckDB): show file path, no start needed
|
|
333
|
-
if (isFileBasedDB) {
|
|
334
|
-
const config = await containerManager.getConfig(containerNameFinal)
|
|
335
|
-
if (config) {
|
|
336
|
-
const connectionString = dbEngine.getConnectionString(config)
|
|
337
|
-
console.log()
|
|
338
|
-
console.log(uiSuccess('Database Created'))
|
|
339
|
-
console.log()
|
|
340
|
-
console.log(chalk.gray(` Container: ${containerNameFinal}`))
|
|
341
|
-
console.log(chalk.gray(` Engine: ${dbEngine.displayName} ${version}`))
|
|
342
|
-
console.log(chalk.gray(` File: ${config.database}`))
|
|
343
|
-
console.log()
|
|
344
|
-
console.log(uiSuccess(`Available at ${config.database}`))
|
|
345
|
-
console.log()
|
|
346
|
-
console.log(chalk.gray(' Connection string:'))
|
|
347
|
-
console.log(chalk.cyan(` ${connectionString}`))
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const copied = await platformService.copyToClipboard(connectionString)
|
|
351
|
-
if (copied) {
|
|
352
|
-
console.log(chalk.gray(' ✓ Connection string copied to clipboard'))
|
|
353
|
-
} else {
|
|
354
|
-
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
355
|
-
}
|
|
356
|
-
} catch {
|
|
357
|
-
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
console.log()
|
|
361
|
-
|
|
362
|
-
await escapeablePrompt([
|
|
363
|
-
{
|
|
364
|
-
type: 'input',
|
|
365
|
-
name: 'continue',
|
|
366
|
-
message: chalk.gray('Press Enter to continue...'),
|
|
367
|
-
},
|
|
368
|
-
])
|
|
369
|
-
}
|
|
370
|
-
return containerNameFinal
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Server databases: start and create database
|
|
374
|
-
if (portAvailable) {
|
|
375
|
-
const startSpinner = createSpinner(`Starting ${dbEngine.displayName}...`)
|
|
376
|
-
startSpinner.start()
|
|
377
|
-
|
|
378
|
-
const config = await containerManager.getConfig(containerNameFinal)
|
|
379
|
-
if (config) {
|
|
380
|
-
try {
|
|
381
|
-
await dbEngine.start(config)
|
|
382
|
-
} catch (error) {
|
|
383
|
-
startSpinner.fail(`${dbEngine.displayName} failed to start`)
|
|
384
|
-
const e = error as Error
|
|
385
|
-
console.log()
|
|
386
|
-
console.log(uiError(e.message))
|
|
387
|
-
console.log()
|
|
388
|
-
// Clean up the container that was created but failed to start
|
|
389
|
-
try {
|
|
390
|
-
await containerManager.delete(containerNameFinal, { force: true })
|
|
391
|
-
} catch {
|
|
392
|
-
// Ignore cleanup errors
|
|
393
|
-
}
|
|
394
|
-
await pressEnterToContinue()
|
|
395
|
-
return
|
|
396
|
-
}
|
|
397
|
-
await containerManager.updateConfig(containerNameFinal, {
|
|
398
|
-
status: 'running',
|
|
399
|
-
})
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
startSpinner.succeed(`${dbEngine.displayName} started`)
|
|
403
|
-
|
|
404
|
-
// Skip creating 'postgres' database for PostgreSQL - it's created by initdb
|
|
405
|
-
// For other engines (MySQL, SQLite), allow creating a database named 'postgres'
|
|
406
|
-
if (
|
|
407
|
-
config &&
|
|
408
|
-
!(config.engine === Engine.PostgreSQL && database === 'postgres')
|
|
409
|
-
) {
|
|
410
|
-
const dbSpinner = createSpinner(`Creating database "${database}"...`)
|
|
411
|
-
dbSpinner.start()
|
|
412
|
-
|
|
413
|
-
await dbEngine.createDatabase(config, database)
|
|
414
|
-
|
|
415
|
-
dbSpinner.succeed(`Database "${database}" created`)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (config) {
|
|
419
|
-
const connectionString = dbEngine.getConnectionString(config)
|
|
420
|
-
console.log()
|
|
421
|
-
console.log(uiSuccess('Database Created'))
|
|
422
|
-
console.log()
|
|
423
|
-
console.log(chalk.gray(` Container: ${containerNameFinal}`))
|
|
424
|
-
console.log(chalk.gray(` Engine: ${dbEngine.displayName} ${version}`))
|
|
425
|
-
console.log(chalk.gray(` Database: ${database}`))
|
|
426
|
-
console.log(chalk.gray(` Port: ${port}`))
|
|
427
|
-
console.log()
|
|
428
|
-
console.log(uiSuccess(`Running on port ${port}`))
|
|
429
|
-
console.log()
|
|
430
|
-
console.log(chalk.gray(' Connection string:'))
|
|
431
|
-
console.log(chalk.cyan(` ${connectionString}`))
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
|
-
const copied = await platformService.copyToClipboard(connectionString)
|
|
435
|
-
if (copied) {
|
|
436
|
-
console.log(chalk.gray(' ✓ Connection string copied to clipboard'))
|
|
437
|
-
} else {
|
|
438
|
-
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
439
|
-
}
|
|
440
|
-
} catch {
|
|
441
|
-
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
console.log()
|
|
445
|
-
|
|
446
|
-
await escapeablePrompt([
|
|
447
|
-
{
|
|
448
|
-
type: 'input',
|
|
449
|
-
name: 'continue',
|
|
450
|
-
message: chalk.gray('Press Enter to continue...'),
|
|
451
|
-
},
|
|
452
|
-
])
|
|
453
|
-
}
|
|
454
|
-
} else {
|
|
455
|
-
console.log()
|
|
456
|
-
console.log(
|
|
457
|
-
uiWarning(
|
|
458
|
-
`Port ${port} is currently in use. Container created but not started.`,
|
|
459
|
-
),
|
|
460
|
-
)
|
|
461
|
-
console.log(
|
|
462
|
-
uiInfo(
|
|
463
|
-
`Start it later with: ${chalk.cyan(`spindb start ${containerNameFinal}`)}`,
|
|
464
|
-
),
|
|
465
|
-
)
|
|
466
|
-
console.log()
|
|
467
|
-
|
|
468
|
-
await escapeablePrompt([
|
|
469
|
-
{
|
|
470
|
-
type: 'input',
|
|
471
|
-
name: 'continue',
|
|
472
|
-
message: chalk.gray('Press Enter to continue...'),
|
|
473
|
-
},
|
|
474
|
-
])
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return containerNameFinal
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
export async function handleList(
|
|
481
|
-
showMainMenu: () => Promise<void>,
|
|
482
|
-
options?: { focusContainer?: string },
|
|
483
|
-
): Promise<void> {
|
|
484
|
-
console.clear()
|
|
485
|
-
console.log(header('Containers'))
|
|
486
|
-
console.log()
|
|
487
|
-
|
|
488
|
-
const spinner = createSpinner('Loading containers...')
|
|
489
|
-
spinner.start()
|
|
490
|
-
|
|
491
|
-
const containers = await containerManager.list()
|
|
492
|
-
|
|
493
|
-
if (containers.length === 0) {
|
|
494
|
-
spinner.stop()
|
|
495
|
-
console.log(
|
|
496
|
-
uiInfo('No containers found. Create one with the "Create" option.'),
|
|
497
|
-
)
|
|
498
|
-
console.log()
|
|
499
|
-
|
|
500
|
-
await escapeablePrompt([
|
|
501
|
-
{
|
|
502
|
-
type: 'input',
|
|
503
|
-
name: 'continue',
|
|
504
|
-
message: chalk.gray('Press Enter to return to the main menu...'),
|
|
505
|
-
},
|
|
506
|
-
])
|
|
507
|
-
return
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Fetch sizes for running containers
|
|
511
|
-
const sizes = await Promise.all(
|
|
512
|
-
containers.map(async (container) => {
|
|
513
|
-
if (container.status !== 'running') return null
|
|
514
|
-
try {
|
|
515
|
-
const engine = getEngine(container.engine)
|
|
516
|
-
return await engine.getDatabaseSize(container)
|
|
517
|
-
} catch {
|
|
518
|
-
return null
|
|
519
|
-
}
|
|
520
|
-
}),
|
|
521
|
-
)
|
|
522
|
-
|
|
523
|
-
spinner.stop()
|
|
524
|
-
|
|
525
|
-
// Column widths for formatting
|
|
526
|
-
const COL_NAME = 16
|
|
527
|
-
const COL_ENGINE = 13
|
|
528
|
-
const COL_VERSION = 8
|
|
529
|
-
const COL_PORT = 6
|
|
530
|
-
const COL_SIZE = 9
|
|
531
|
-
|
|
532
|
-
// Build selectable choices with formatted display (like engines menu)
|
|
533
|
-
const containerChoices: FilterableChoice[] = containers.map((c, i) => {
|
|
534
|
-
const size = sizes[i]
|
|
535
|
-
const isFileBased = isFileBasedEngine(c.engine)
|
|
536
|
-
|
|
537
|
-
// Status display
|
|
538
|
-
const statusDisplay = isFileBased
|
|
539
|
-
? c.status === 'running'
|
|
540
|
-
? chalk.blue('● available')
|
|
541
|
-
: chalk.gray('○ missing')
|
|
542
|
-
: c.status === 'running'
|
|
543
|
-
? chalk.green('● running')
|
|
544
|
-
: chalk.gray('○ stopped')
|
|
545
|
-
|
|
546
|
-
// Truncate name if too long
|
|
547
|
-
const displayName =
|
|
548
|
-
c.name.length > COL_NAME - 1
|
|
549
|
-
? c.name.slice(0, COL_NAME - 2) + '…'
|
|
550
|
-
: c.name
|
|
551
|
-
|
|
552
|
-
// Port or dash for file-based
|
|
553
|
-
const portDisplay = isFileBased ? '—' : String(c.port)
|
|
554
|
-
|
|
555
|
-
// Size display
|
|
556
|
-
const sizeDisplay = size !== null ? formatBytes(size) : '—'
|
|
557
|
-
|
|
558
|
-
// Build formatted row
|
|
559
|
-
// Pad icon and engine name separately to avoid emoji width calculation issues
|
|
560
|
-
// (padEnd counts code points, not visual width)
|
|
561
|
-
const icon = getEngineIcon(c.engine)
|
|
562
|
-
const engineName = c.engine.padEnd(COL_ENGINE)
|
|
563
|
-
const isRunning = c.status === 'running'
|
|
564
|
-
const row =
|
|
565
|
-
(isRunning
|
|
566
|
-
? chalk.cyan.bold(displayName.padEnd(COL_NAME))
|
|
567
|
-
: chalk.cyan(displayName.padEnd(COL_NAME))) +
|
|
568
|
-
chalk.white(`${icon}${engineName}`) +
|
|
569
|
-
chalk.yellow(c.version.padEnd(COL_VERSION)) +
|
|
570
|
-
chalk.green(portDisplay.padEnd(COL_PORT)) +
|
|
571
|
-
chalk.magenta(sizeDisplay.padEnd(COL_SIZE)) +
|
|
572
|
-
statusDisplay
|
|
573
|
-
|
|
574
|
-
return {
|
|
575
|
-
name: row,
|
|
576
|
-
value: c.name,
|
|
577
|
-
short: c.name,
|
|
578
|
-
}
|
|
579
|
-
})
|
|
580
|
-
|
|
581
|
-
// Calculate summary
|
|
582
|
-
const serverContainers = containers.filter(
|
|
583
|
-
(c) => !isFileBasedEngine(c.engine),
|
|
584
|
-
)
|
|
585
|
-
const fileBasedContainers = containers.filter((c) =>
|
|
586
|
-
isFileBasedEngine(c.engine),
|
|
587
|
-
)
|
|
588
|
-
const running = serverContainers.filter((c) => c.status === 'running').length
|
|
589
|
-
const stopped = serverContainers.filter((c) => c.status !== 'running').length
|
|
590
|
-
const available = fileBasedContainers.filter(
|
|
591
|
-
(c) => c.status === 'running',
|
|
592
|
-
).length
|
|
593
|
-
const missing = fileBasedContainers.filter(
|
|
594
|
-
(c) => c.status !== 'running',
|
|
595
|
-
).length
|
|
596
|
-
|
|
597
|
-
const parts: string[] = []
|
|
598
|
-
if (serverContainers.length > 0) {
|
|
599
|
-
parts.push(`${running} running, ${stopped} stopped`)
|
|
600
|
-
}
|
|
601
|
-
if (fileBasedContainers.length > 0) {
|
|
602
|
-
parts.push(
|
|
603
|
-
`${available} file-based available${missing > 0 ? `, ${missing} missing` : ''}`,
|
|
604
|
-
)
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Check if there are any server-based (toggleable) containers
|
|
608
|
-
const hasServerContainers = containers.some(
|
|
609
|
-
(c) => !isFileBasedEngine(c.engine),
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
// Build the full choice list with footer items
|
|
613
|
-
// IMPORTANT: Containers must come FIRST because filterableCount slices from index 0
|
|
614
|
-
const summary = `${containers.length} container(s): ${parts.join('; ')}`
|
|
615
|
-
const headerItems = hasServerContainers
|
|
616
|
-
? [
|
|
617
|
-
new inquirer.Separator(
|
|
618
|
-
chalk.cyan('── [Shift+Tab] toggle start/stop ──'),
|
|
619
|
-
),
|
|
620
|
-
]
|
|
621
|
-
: []
|
|
622
|
-
const allChoices: (FilterableChoice | inquirer.Separator)[] = [
|
|
623
|
-
...containerChoices,
|
|
624
|
-
new inquirer.Separator(),
|
|
625
|
-
new inquirer.Separator(summary),
|
|
626
|
-
new inquirer.Separator(),
|
|
627
|
-
{ name: `${chalk.green('+')} Create new`, value: 'create' },
|
|
628
|
-
{
|
|
629
|
-
name: `${chalk.blue('←')} Back to main menu ${chalk.gray('(esc)')}`,
|
|
630
|
-
value: 'back',
|
|
631
|
-
},
|
|
632
|
-
new inquirer.Separator(),
|
|
633
|
-
]
|
|
634
|
-
|
|
635
|
-
const selectedContainer = await filterableListPrompt(
|
|
636
|
-
allChoices,
|
|
637
|
-
`Select a container: ${chalk.gray('↑↓ pick, type to filter')}`,
|
|
638
|
-
{
|
|
639
|
-
filterableCount: containerChoices.length,
|
|
640
|
-
pageSize: getPageSize(),
|
|
641
|
-
emptyText: 'No containers match filter',
|
|
642
|
-
enableToggle: hasServerContainers,
|
|
643
|
-
defaultValue: options?.focusContainer,
|
|
644
|
-
headerItems,
|
|
645
|
-
},
|
|
646
|
-
)
|
|
647
|
-
|
|
648
|
-
// Handle toggle (Shift+Tab) - start/stop the container and refresh list
|
|
649
|
-
if (selectedContainer.startsWith(TOGGLE_PREFIX)) {
|
|
650
|
-
const containerName = selectedContainer.slice(TOGGLE_PREFIX.length)
|
|
651
|
-
const config = await containerManager.getConfig(containerName)
|
|
652
|
-
|
|
653
|
-
if (config && !isFileBasedEngine(config.engine)) {
|
|
654
|
-
const isRunning = await processManager.isRunning(containerName, {
|
|
655
|
-
engine: config.engine,
|
|
656
|
-
})
|
|
657
|
-
|
|
658
|
-
// Show inline status without clearing screen
|
|
659
|
-
console.log()
|
|
660
|
-
if (isRunning) {
|
|
661
|
-
await handleStopContainer(containerName)
|
|
662
|
-
} else {
|
|
663
|
-
const result = await handleStartContainer(containerName)
|
|
664
|
-
if (result === 'home') {
|
|
665
|
-
await showMainMenu()
|
|
666
|
-
return
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Refresh the container list with cursor on the same container
|
|
672
|
-
await handleList(showMainMenu, { focusContainer: containerName })
|
|
673
|
-
return
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// Back returns to main menu (escape is handled globally)
|
|
677
|
-
if (selectedContainer === 'back') {
|
|
678
|
-
return
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
if (selectedContainer === 'create') {
|
|
682
|
-
const result = await handleCreate()
|
|
683
|
-
if (result === 'main') {
|
|
684
|
-
await showMainMenu()
|
|
685
|
-
} else if (result) {
|
|
686
|
-
await showContainerSubmenu(result, showMainMenu)
|
|
687
|
-
} else {
|
|
688
|
-
await handleList(showMainMenu)
|
|
689
|
-
}
|
|
690
|
-
return
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
await showContainerSubmenu(selectedContainer, showMainMenu)
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
export async function showContainerSubmenu(
|
|
697
|
-
containerName: string,
|
|
698
|
-
showMainMenu: () => Promise<void>,
|
|
699
|
-
selectedDatabase?: string,
|
|
700
|
-
): Promise<void> {
|
|
701
|
-
const config = await containerManager.getConfig(containerName)
|
|
702
|
-
if (!config) {
|
|
703
|
-
console.error(uiError(`Container "${containerName}" not found`))
|
|
704
|
-
return
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// File-based databases: Check file existence instead of running status
|
|
708
|
-
const isSQLite = config.engine === Engine.SQLite
|
|
709
|
-
const isDuckDB = config.engine === Engine.DuckDB
|
|
710
|
-
const isFileBasedDB = isSQLite || isDuckDB
|
|
711
|
-
let isRunning: boolean
|
|
712
|
-
let status: string
|
|
713
|
-
let locationInfo: string
|
|
714
|
-
|
|
715
|
-
if (isFileBasedDB) {
|
|
716
|
-
const fileExists = existsSync(config.database)
|
|
717
|
-
isRunning = fileExists // For file-based DBs, "running" means "file exists"
|
|
718
|
-
status = fileExists ? 'available' : 'missing'
|
|
719
|
-
locationInfo = `at ${config.database}`
|
|
720
|
-
} else {
|
|
721
|
-
isRunning = await processManager.isRunning(containerName, {
|
|
722
|
-
engine: config.engine,
|
|
723
|
-
})
|
|
724
|
-
status = isRunning ? 'running' : 'stopped'
|
|
725
|
-
locationInfo = `on port ${config.port}`
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// Get list of databases in this container
|
|
729
|
-
const databases = config.databases || [config.database]
|
|
730
|
-
|
|
731
|
-
// Auto-select: use provided selection, or default to primary database from config
|
|
732
|
-
const activeDatabase = selectedDatabase || config.database
|
|
733
|
-
|
|
734
|
-
// Header shows icon + container → database
|
|
735
|
-
const engineIcon = getEngineIcon(config.engine)
|
|
736
|
-
const headerText = `${engineIcon} ${containerName} ${chalk.gray('→')} ${activeDatabase}`
|
|
737
|
-
|
|
738
|
-
console.clear()
|
|
739
|
-
console.log(header(headerText))
|
|
740
|
-
console.log(
|
|
741
|
-
chalk.gray(
|
|
742
|
-
`${config.engine} ${config.version} ${locationInfo} - ${status}`,
|
|
743
|
-
),
|
|
744
|
-
)
|
|
745
|
-
console.log()
|
|
746
|
-
|
|
747
|
-
// Build action choices based on engine type
|
|
748
|
-
const actionChoices: MenuChoice[] = []
|
|
749
|
-
|
|
750
|
-
// Helper for disabled menu items (hint shown in separator, not on each item)
|
|
751
|
-
function disabledItem(icon: string, label: string) {
|
|
752
|
-
return {
|
|
753
|
-
name: chalk.gray(`${icon} ${label}`),
|
|
754
|
-
value: '_disabled_',
|
|
755
|
-
disabled: '', // Empty string hides the "(Disabled)" text
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// Determine if database-specific actions can be performed
|
|
760
|
-
// Requires: database selected + (running for server DBs OR file exists for file-based DBs)
|
|
761
|
-
const containerReady = isFileBasedDB ? existsSync(config.database) : isRunning
|
|
762
|
-
const hasMultipleDatabases = databases.length > 1
|
|
763
|
-
const canDoDbAction = !!activeDatabase && containerReady
|
|
764
|
-
|
|
765
|
-
// Label for data section separator - shows state or required action
|
|
766
|
-
function getDataSectionLabel(): string {
|
|
767
|
-
if (!containerReady) {
|
|
768
|
-
return isFileBasedDB ? 'Database file missing' : 'Start container first'
|
|
769
|
-
}
|
|
770
|
-
if (!activeDatabase && hasMultipleDatabases) {
|
|
771
|
-
return 'Select database first'
|
|
772
|
-
}
|
|
773
|
-
// Show positive state when actions are available
|
|
774
|
-
return isFileBasedDB ? 'Available' : 'Running'
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// Label for management section separator - shows state or required action
|
|
778
|
-
function getManageSectionLabel(): string {
|
|
779
|
-
if (!isFileBasedDB && isRunning) {
|
|
780
|
-
return 'Stop container first'
|
|
781
|
-
}
|
|
782
|
-
// Show positive state when actions are available
|
|
783
|
-
return isFileBasedDB ? 'Available' : 'Stopped'
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
787
|
-
// SECTION 1: Container State
|
|
788
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
789
|
-
|
|
790
|
-
// Start/Stop buttons only for server databases (not file-based)
|
|
791
|
-
if (!isFileBasedDB) {
|
|
792
|
-
if (!isRunning) {
|
|
793
|
-
actionChoices.push({
|
|
794
|
-
name: `${chalk.green('▶')} Start container`,
|
|
795
|
-
value: 'start',
|
|
796
|
-
})
|
|
797
|
-
} else {
|
|
798
|
-
actionChoices.push({
|
|
799
|
-
name: `${chalk.red('■')} Stop container`,
|
|
800
|
-
value: 'stop',
|
|
801
|
-
})
|
|
802
|
-
|
|
803
|
-
// Stop pgweb - only for PG-wire-protocol engines when pgweb is running
|
|
804
|
-
if (
|
|
805
|
-
config.engine === 'postgresql' ||
|
|
806
|
-
config.engine === 'cockroachdb' ||
|
|
807
|
-
config.engine === 'ferretdb'
|
|
808
|
-
) {
|
|
809
|
-
const pgwebStatus = await getPgwebStatus(containerName, config.engine)
|
|
810
|
-
if (pgwebStatus.running) {
|
|
811
|
-
actionChoices.push({
|
|
812
|
-
name: `${chalk.redBright('■')} Stop pgweb (port ${pgwebStatus.port})`,
|
|
813
|
-
value: 'stop-pgweb',
|
|
814
|
-
})
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// View logs - available anytime for server-based DBs
|
|
820
|
-
actionChoices.push({
|
|
821
|
-
name: `${chalk.gray('☰')} View logs`,
|
|
822
|
-
value: 'logs',
|
|
823
|
-
})
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// Database selection - show current selection or prompt to select
|
|
827
|
-
// Only show if there are multiple databases
|
|
828
|
-
if (hasMultipleDatabases) {
|
|
829
|
-
const dbIndex = activeDatabase ? databases.indexOf(activeDatabase) + 1 : 0
|
|
830
|
-
const dbLabel = activeDatabase
|
|
831
|
-
? `${chalk.cyan('◉')} Set database ${chalk.gray('|')} Current: ${chalk.white(activeDatabase)} ${chalk.gray(`(${dbIndex} of ${databases.length})`)}`
|
|
832
|
-
: `${chalk.yellow('◉')} Set database`
|
|
833
|
-
actionChoices.push({
|
|
834
|
-
name: dbLabel,
|
|
835
|
-
value: 'select-database',
|
|
836
|
-
})
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
840
|
-
// SECTION 2: Data Operations
|
|
841
|
-
// Separator shows current state or required action
|
|
842
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
843
|
-
const dataSectionLabel = getDataSectionLabel()
|
|
844
|
-
actionChoices.push(
|
|
845
|
-
new inquirer.Separator(chalk.gray(`── ${dataSectionLabel} ──`)),
|
|
846
|
-
)
|
|
847
|
-
|
|
848
|
-
// Open console - requires database selection for multi-db containers
|
|
849
|
-
actionChoices.push(
|
|
850
|
-
canDoDbAction
|
|
851
|
-
? { name: `${chalk.blue('>')} Open console`, value: 'shell' }
|
|
852
|
-
: disabledItem('>', 'Open console'),
|
|
853
|
-
)
|
|
854
|
-
|
|
855
|
-
// Run script file - requires database selection for multi-db containers
|
|
856
|
-
// Label comes from engines.json scriptFileLabel; null means no script support (REST API engines)
|
|
857
|
-
const engineConfig = await getEngineConfig(config.engine)
|
|
858
|
-
if (engineConfig.scriptFileLabel) {
|
|
859
|
-
const runScriptLabel = engineConfig.scriptFileLabel
|
|
860
|
-
actionChoices.push(
|
|
861
|
-
canDoDbAction
|
|
862
|
-
? { name: `${chalk.yellow('▷')} ${runScriptLabel}`, value: 'run-sql' }
|
|
863
|
-
: disabledItem('▷', runScriptLabel),
|
|
864
|
-
)
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// Copy connection string - requires database selection for multi-db containers
|
|
868
|
-
actionChoices.push(
|
|
869
|
-
canDoDbAction
|
|
870
|
-
? { name: `${chalk.green('⎘')} Copy connection string`, value: 'copy' }
|
|
871
|
-
: disabledItem('⎘', 'Copy connection string'),
|
|
872
|
-
)
|
|
873
|
-
|
|
874
|
-
// Create user - only for engines that override createUser from BaseEngine
|
|
875
|
-
const engine = getEngine(config.engine)
|
|
876
|
-
const supportsUsers = engine.createUser !== BaseEngine.prototype.createUser
|
|
877
|
-
if (supportsUsers) {
|
|
878
|
-
actionChoices.push(
|
|
879
|
-
containerReady
|
|
880
|
-
? {
|
|
881
|
-
name: `${chalk.yellow('+')} Create user`,
|
|
882
|
-
value: 'create_user',
|
|
883
|
-
}
|
|
884
|
-
: disabledItem('+', 'Create user'),
|
|
885
|
-
)
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// Backup - requires database selection for multi-db containers
|
|
889
|
-
actionChoices.push(
|
|
890
|
-
canDoDbAction
|
|
891
|
-
? { name: `${chalk.magenta('↓')} Backup database`, value: 'backup' }
|
|
892
|
-
: disabledItem('↓', 'Backup database'),
|
|
893
|
-
)
|
|
894
|
-
|
|
895
|
-
// Restore - requires database selection for multi-db containers
|
|
896
|
-
actionChoices.push(
|
|
897
|
-
canDoDbAction
|
|
898
|
-
? { name: `${chalk.magenta('↑')} Restore from backup`, value: 'restore' }
|
|
899
|
-
: disabledItem('↑', 'Restore from backup'),
|
|
900
|
-
)
|
|
901
|
-
|
|
902
|
-
// Export - server-based DBs must be running, file-based must have the file
|
|
903
|
-
actionChoices.push(
|
|
904
|
-
containerReady
|
|
905
|
-
? { name: `${chalk.cyan('⬆')} Export`, value: 'export' }
|
|
906
|
-
: disabledItem('⬆', 'Export'),
|
|
907
|
-
)
|
|
908
|
-
|
|
909
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
910
|
-
// SECTION 3: Container Management (requires stopped for server-based)
|
|
911
|
-
// Separator shows current state or required action
|
|
912
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
913
|
-
const manageSectionLabel = getManageSectionLabel()
|
|
914
|
-
actionChoices.push(
|
|
915
|
-
new inquirer.Separator(chalk.gray(`── ${manageSectionLabel} ──`)),
|
|
916
|
-
)
|
|
917
|
-
|
|
918
|
-
// Edit container - file-based DBs can always edit (no running state), server databases must be stopped
|
|
919
|
-
const canEdit = isFileBasedDB || !isRunning
|
|
920
|
-
actionChoices.push(
|
|
921
|
-
canEdit
|
|
922
|
-
? { name: `${chalk.yellow('⚙')} Edit container`, value: 'edit' }
|
|
923
|
-
: disabledItem('⚙', 'Edit container'),
|
|
924
|
-
)
|
|
925
|
-
|
|
926
|
-
// Clone container - file-based DBs can always clone, server databases must be stopped
|
|
927
|
-
const canClone = isFileBasedDB || !isRunning
|
|
928
|
-
actionChoices.push(
|
|
929
|
-
canClone
|
|
930
|
-
? { name: `${chalk.cyan('◇')} Clone container`, value: 'clone' }
|
|
931
|
-
: disabledItem('◇', 'Clone container'),
|
|
932
|
-
)
|
|
933
|
-
|
|
934
|
-
// Detach - only for file-based DBs (unregisters without deleting file)
|
|
935
|
-
if (isFileBasedDB) {
|
|
936
|
-
actionChoices.push({
|
|
937
|
-
name: `${chalk.yellow('⊘')} Detach from SpinDB`,
|
|
938
|
-
value: 'detach',
|
|
939
|
-
})
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// Delete container - file-based DBs can always delete, server databases must be stopped
|
|
943
|
-
const canDelete = isFileBasedDB || !isRunning
|
|
944
|
-
actionChoices.push(
|
|
945
|
-
canDelete
|
|
946
|
-
? { name: `${chalk.red('✕')} Delete container`, value: 'delete' }
|
|
947
|
-
: disabledItem('✕', 'Delete container'),
|
|
948
|
-
)
|
|
949
|
-
|
|
950
|
-
actionChoices.push(new inquirer.Separator())
|
|
951
|
-
|
|
952
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
953
|
-
// SECTION 4: Navigation
|
|
954
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
955
|
-
|
|
956
|
-
actionChoices.push(
|
|
957
|
-
{
|
|
958
|
-
name: `${chalk.blue('←')} Back to containers`,
|
|
959
|
-
value: 'back',
|
|
960
|
-
},
|
|
961
|
-
{
|
|
962
|
-
name: `${chalk.blue('⌂')} Back to main menu ${chalk.gray('(esc)')}`,
|
|
963
|
-
value: 'main',
|
|
964
|
-
},
|
|
965
|
-
new inquirer.Separator(),
|
|
966
|
-
)
|
|
967
|
-
|
|
968
|
-
const { action } = await escapeablePrompt<{ action: string }>([
|
|
969
|
-
{
|
|
970
|
-
type: 'list',
|
|
971
|
-
name: 'action',
|
|
972
|
-
message: 'What would you like to do?',
|
|
973
|
-
choices: actionChoices,
|
|
974
|
-
pageSize: getPageSize(),
|
|
975
|
-
},
|
|
976
|
-
])
|
|
977
|
-
|
|
978
|
-
// Escape is handled globally by the menu loop
|
|
979
|
-
|
|
980
|
-
switch (action) {
|
|
981
|
-
case 'start': {
|
|
982
|
-
const result = await handleStartContainer(containerName)
|
|
983
|
-
if (result === 'home') {
|
|
984
|
-
await showMainMenu()
|
|
985
|
-
return
|
|
986
|
-
}
|
|
987
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
988
|
-
return
|
|
989
|
-
}
|
|
990
|
-
case 'stop':
|
|
991
|
-
await handleStopContainer(containerName)
|
|
992
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
993
|
-
return
|
|
994
|
-
case 'select-database': {
|
|
995
|
-
const result = await handleSelectDatabase(
|
|
996
|
-
containerName,
|
|
997
|
-
databases,
|
|
998
|
-
config.database,
|
|
999
|
-
)
|
|
1000
|
-
if (result.action === 'home') {
|
|
1001
|
-
await showMainMenu()
|
|
1002
|
-
return
|
|
1003
|
-
}
|
|
1004
|
-
if (result.action === 'back') {
|
|
1005
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1006
|
-
return
|
|
1007
|
-
}
|
|
1008
|
-
if (result.action === 'change-default') {
|
|
1009
|
-
await handleChangeDefaultDatabase(
|
|
1010
|
-
containerName,
|
|
1011
|
-
databases,
|
|
1012
|
-
config.database,
|
|
1013
|
-
)
|
|
1014
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1015
|
-
return
|
|
1016
|
-
}
|
|
1017
|
-
// action === 'select'
|
|
1018
|
-
await showContainerSubmenu(containerName, showMainMenu, result.database)
|
|
1019
|
-
return
|
|
1020
|
-
}
|
|
1021
|
-
case 'shell':
|
|
1022
|
-
await handleOpenShell(containerName, activeDatabase)
|
|
1023
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1024
|
-
return
|
|
1025
|
-
case 'run-sql':
|
|
1026
|
-
await handleRunSql(containerName, activeDatabase)
|
|
1027
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1028
|
-
return
|
|
1029
|
-
case 'logs':
|
|
1030
|
-
await handleViewLogs(containerName)
|
|
1031
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1032
|
-
return
|
|
1033
|
-
case 'stop-pgweb':
|
|
1034
|
-
await stopPgwebProcess(containerName, config.engine)
|
|
1035
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1036
|
-
return
|
|
1037
|
-
case 'edit': {
|
|
1038
|
-
const newName = await handleEditContainer(containerName)
|
|
1039
|
-
if (newName === null) {
|
|
1040
|
-
// User chose to go back to main menu
|
|
1041
|
-
return
|
|
1042
|
-
}
|
|
1043
|
-
if (newName !== containerName) {
|
|
1044
|
-
// Container was renamed, show submenu with new name
|
|
1045
|
-
await showContainerSubmenu(newName, showMainMenu, activeDatabase)
|
|
1046
|
-
} else {
|
|
1047
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1048
|
-
}
|
|
1049
|
-
return
|
|
1050
|
-
}
|
|
1051
|
-
case 'clone':
|
|
1052
|
-
await handleCloneFromSubmenu(containerName, showMainMenu)
|
|
1053
|
-
return
|
|
1054
|
-
case 'copy':
|
|
1055
|
-
await handleCopyConnectionString(containerName, activeDatabase)
|
|
1056
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1057
|
-
return
|
|
1058
|
-
case 'create_user':
|
|
1059
|
-
await handleCreateUser(containerName, activeDatabase)
|
|
1060
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1061
|
-
return
|
|
1062
|
-
case 'backup':
|
|
1063
|
-
await handleBackupForContainer(containerName, activeDatabase)
|
|
1064
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1065
|
-
return
|
|
1066
|
-
case 'restore':
|
|
1067
|
-
await handleRestoreForContainer(containerName, activeDatabase)
|
|
1068
|
-
await showContainerSubmenu(containerName, showMainMenu, activeDatabase)
|
|
1069
|
-
return
|
|
1070
|
-
case 'detach':
|
|
1071
|
-
await handleDetachContainer(containerName, showMainMenu)
|
|
1072
|
-
return // Return to list after detach
|
|
1073
|
-
case 'delete':
|
|
1074
|
-
await handleDelete(containerName)
|
|
1075
|
-
return // Don't show submenu again after delete
|
|
1076
|
-
case 'export':
|
|
1077
|
-
await handleExportSubmenu(containerName, databases, showMainMenu)
|
|
1078
|
-
return
|
|
1079
|
-
case 'back':
|
|
1080
|
-
await handleList(showMainMenu)
|
|
1081
|
-
return
|
|
1082
|
-
case 'main':
|
|
1083
|
-
return // Return to main menu
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
export async function handleStart(): Promise<void> {
|
|
1088
|
-
const containers = await containerManager.list()
|
|
1089
|
-
// Filter for stopped containers, excluding file-based DBs (no server process to start)
|
|
1090
|
-
const stopped = containers.filter(
|
|
1091
|
-
(c) => c.status !== 'running' && !isFileBasedEngine(c.engine),
|
|
1092
|
-
)
|
|
1093
|
-
|
|
1094
|
-
if (stopped.length === 0) {
|
|
1095
|
-
console.log(uiWarning('All containers are already running'))
|
|
1096
|
-
return
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
const containerName = await promptContainerSelect(
|
|
1100
|
-
stopped,
|
|
1101
|
-
'Select container to start:',
|
|
1102
|
-
{ includeBack: true },
|
|
1103
|
-
)
|
|
1104
|
-
if (!containerName) return
|
|
1105
|
-
|
|
1106
|
-
// Reuse handleStartContainer for consistent port conflict handling
|
|
1107
|
-
await handleStartContainer(containerName)
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
export async function handleStop(): Promise<void> {
|
|
1111
|
-
const containers = await containerManager.list()
|
|
1112
|
-
// Filter for running containers, excluding file-based DBs (no server process to stop)
|
|
1113
|
-
const running = containers.filter(
|
|
1114
|
-
(c) => c.status === 'running' && !isFileBasedEngine(c.engine),
|
|
1115
|
-
)
|
|
1116
|
-
|
|
1117
|
-
if (running.length === 0) {
|
|
1118
|
-
console.log(uiWarning('No running containers'))
|
|
1119
|
-
return
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
const containerName = await promptContainerSelect(
|
|
1123
|
-
running,
|
|
1124
|
-
'Select container to stop:',
|
|
1125
|
-
{ includeBack: true },
|
|
1126
|
-
)
|
|
1127
|
-
if (!containerName) return
|
|
1128
|
-
|
|
1129
|
-
const config = await containerManager.getConfig(containerName)
|
|
1130
|
-
if (!config) {
|
|
1131
|
-
console.error(uiError(`Container "${containerName}" not found`))
|
|
1132
|
-
return
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
const engine = getEngine(config.engine)
|
|
1136
|
-
|
|
1137
|
-
const spinner = createSpinner(`Stopping ${containerName}...`)
|
|
1138
|
-
spinner.start()
|
|
1139
|
-
|
|
1140
|
-
await engine.stop(config)
|
|
1141
|
-
await containerManager.updateConfig(containerName, { status: 'stopped' })
|
|
1142
|
-
|
|
1143
|
-
spinner.succeed(`Container "${containerName}" stopped`)
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
type StartResult = 'started' | 'back' | 'home'
|
|
1147
|
-
|
|
1148
|
-
async function handleStartContainer(
|
|
1149
|
-
containerName: string,
|
|
1150
|
-
): Promise<StartResult> {
|
|
1151
|
-
const config = await containerManager.getConfig(containerName)
|
|
1152
|
-
if (!config) {
|
|
1153
|
-
console.error(uiError(`Container "${containerName}" not found`))
|
|
1154
|
-
return 'back'
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
const portAvailable = await portManager.isPortAvailable(config.port)
|
|
1158
|
-
if (!portAvailable) {
|
|
1159
|
-
// Find next available port
|
|
1160
|
-
let newPort: number
|
|
1161
|
-
try {
|
|
1162
|
-
const result = await portManager.findAvailablePort({
|
|
1163
|
-
preferredPort: config.port,
|
|
1164
|
-
})
|
|
1165
|
-
newPort = result.port
|
|
1166
|
-
} catch {
|
|
1167
|
-
console.log()
|
|
1168
|
-
console.log(
|
|
1169
|
-
uiError('No available ports found. Free up a port and try again.'),
|
|
1170
|
-
)
|
|
1171
|
-
return 'back'
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// Check if another SpinDB container is using this port
|
|
1175
|
-
const allContainers = await containerManager.list()
|
|
1176
|
-
const conflictingContainer = allContainers.find(
|
|
1177
|
-
(c) =>
|
|
1178
|
-
c.name !== containerName &&
|
|
1179
|
-
c.port === config.port &&
|
|
1180
|
-
c.status === 'running',
|
|
1181
|
-
)
|
|
1182
|
-
|
|
1183
|
-
const conflictReason = conflictingContainer
|
|
1184
|
-
? `in use by "${conflictingContainer.name}"`
|
|
1185
|
-
: 'in use by another process'
|
|
1186
|
-
|
|
1187
|
-
console.log()
|
|
1188
|
-
console.log(uiWarning(`Port ${config.port} is ${conflictReason}`))
|
|
1189
|
-
console.log()
|
|
1190
|
-
|
|
1191
|
-
const { action } = await escapeablePrompt<{ action: string }>([
|
|
1192
|
-
{
|
|
1193
|
-
type: 'list',
|
|
1194
|
-
name: 'action',
|
|
1195
|
-
message: 'What would you like to do?',
|
|
1196
|
-
choices: [
|
|
1197
|
-
{
|
|
1198
|
-
name: `${chalk.green('▶')} Update to port ${newPort} and start ${chalk.gray('(recommended)')}`,
|
|
1199
|
-
value: 'update',
|
|
1200
|
-
},
|
|
1201
|
-
{ name: `${chalk.blue('←')} Go back`, value: 'back' },
|
|
1202
|
-
{
|
|
1203
|
-
name: `${chalk.blue('⌂')} Back to main menu`,
|
|
1204
|
-
value: 'home',
|
|
1205
|
-
},
|
|
1206
|
-
],
|
|
1207
|
-
},
|
|
1208
|
-
])
|
|
1209
|
-
|
|
1210
|
-
if (action === 'back') {
|
|
1211
|
-
return 'back'
|
|
1212
|
-
}
|
|
1213
|
-
if (action === 'home') {
|
|
1214
|
-
return 'home'
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
// Update port and continue to start
|
|
1218
|
-
config.port = newPort
|
|
1219
|
-
await containerManager.updateConfig(containerName, { port: newPort })
|
|
1220
|
-
console.log(uiSuccess(`Updated port to ${newPort}`))
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
const engine = getEngine(config.engine)
|
|
1224
|
-
|
|
1225
|
-
const spinner = createSpinner(`Starting ${containerName}...`)
|
|
1226
|
-
spinner.start()
|
|
1227
|
-
|
|
1228
|
-
try {
|
|
1229
|
-
await engine.start(config)
|
|
1230
|
-
await containerManager.updateConfig(containerName, { status: 'running' })
|
|
1231
|
-
|
|
1232
|
-
spinner.succeed(`Container "${containerName}" started`)
|
|
1233
|
-
|
|
1234
|
-
const connectionString = engine.getConnectionString(config)
|
|
1235
|
-
console.log()
|
|
1236
|
-
console.log(chalk.gray(' Connection string:'))
|
|
1237
|
-
console.log(chalk.cyan(` ${connectionString}`))
|
|
1238
|
-
return 'started'
|
|
1239
|
-
} catch (error) {
|
|
1240
|
-
spinner.fail(`Failed to start "${containerName}"`)
|
|
1241
|
-
const e = error as Error
|
|
1242
|
-
console.log()
|
|
1243
|
-
console.log(uiError(e.message))
|
|
1244
|
-
|
|
1245
|
-
const logPath = paths.getContainerLogPath(containerName, {
|
|
1246
|
-
engine: config.engine,
|
|
1247
|
-
})
|
|
1248
|
-
if (existsSync(logPath)) {
|
|
1249
|
-
console.log()
|
|
1250
|
-
console.log(uiInfo(`Check the log file for details: ${logPath}`))
|
|
1251
|
-
}
|
|
1252
|
-
return 'back'
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
async function handleStopContainer(containerName: string): Promise<void> {
|
|
1257
|
-
const config = await containerManager.getConfig(containerName)
|
|
1258
|
-
if (!config) {
|
|
1259
|
-
console.error(uiError(`Container "${containerName}" not found`))
|
|
1260
|
-
return
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
const engine = getEngine(config.engine)
|
|
1264
|
-
|
|
1265
|
-
const spinner = createSpinner(`Stopping ${containerName}...`)
|
|
1266
|
-
spinner.start()
|
|
1267
|
-
|
|
1268
|
-
await engine.stop(config)
|
|
1269
|
-
await containerManager.updateConfig(containerName, { status: 'stopped' })
|
|
1270
|
-
|
|
1271
|
-
spinner.succeed(`Container "${containerName}" stopped`)
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
async function handleEditContainer(
|
|
1275
|
-
containerName: string,
|
|
1276
|
-
): Promise<string | null> {
|
|
1277
|
-
const config = await containerManager.getConfig(containerName)
|
|
1278
|
-
if (!config) {
|
|
1279
|
-
console.error(uiError(`Container "${containerName}" not found`))
|
|
1280
|
-
return null
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
const isSQLite = config.engine === Engine.SQLite
|
|
1284
|
-
const isDuckDB = config.engine === Engine.DuckDB
|
|
1285
|
-
const isFileBasedDB = isSQLite || isDuckDB
|
|
1286
|
-
|
|
1287
|
-
console.clear()
|
|
1288
|
-
console.log(header(`Edit: ${containerName}`))
|
|
1289
|
-
console.log()
|
|
1290
|
-
|
|
1291
|
-
const editChoices: Array<
|
|
1292
|
-
{ name: string; value: string } | inquirer.Separator
|
|
1293
|
-
> = [
|
|
1294
|
-
{
|
|
1295
|
-
name: `Name: ${chalk.white(containerName)}`,
|
|
1296
|
-
value: 'name',
|
|
1297
|
-
},
|
|
1298
|
-
]
|
|
1299
|
-
|
|
1300
|
-
// File-based DBs: show relocate option with file path; others: show port
|
|
1301
|
-
if (isFileBasedDB) {
|
|
1302
|
-
editChoices.push({
|
|
1303
|
-
name: `Location: ${chalk.white(config.database)}`,
|
|
1304
|
-
value: 'relocate',
|
|
1305
|
-
})
|
|
1306
|
-
} else {
|
|
1307
|
-
editChoices.push({
|
|
1308
|
-
name: `Port: ${chalk.white(String(config.port))}`,
|
|
1309
|
-
value: 'port',
|
|
1310
|
-
})
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
editChoices.push(new inquirer.Separator())
|
|
1314
|
-
editChoices.push({
|
|
1315
|
-
name: `${chalk.blue('←')} Back to container`,
|
|
1316
|
-
value: 'back',
|
|
1317
|
-
})
|
|
1318
|
-
editChoices.push({
|
|
1319
|
-
name: `${chalk.blue('⌂')} Back to main menu ${chalk.gray('(esc)')}`,
|
|
1320
|
-
value: 'main',
|
|
1321
|
-
})
|
|
1322
|
-
|
|
1323
|
-
const { field } = await escapeablePrompt<{ field: string }>([
|
|
1324
|
-
{
|
|
1325
|
-
type: 'list',
|
|
1326
|
-
name: 'field',
|
|
1327
|
-
message: 'Select field to edit:',
|
|
1328
|
-
choices: editChoices,
|
|
1329
|
-
pageSize: getPageSize(),
|
|
1330
|
-
},
|
|
1331
|
-
])
|
|
1332
|
-
|
|
1333
|
-
if (field === 'back') {
|
|
1334
|
-
return containerName
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
if (field === 'main') {
|
|
1338
|
-
return null // Signal to go back to main menu
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
if (field === 'name') {
|
|
1342
|
-
const { newName } = await escapeablePrompt<{ newName: string }>([
|
|
1343
|
-
{
|
|
1344
|
-
type: 'input',
|
|
1345
|
-
name: 'newName',
|
|
1346
|
-
message: 'New name:',
|
|
1347
|
-
default: containerName,
|
|
1348
|
-
validate: (input: string) => {
|
|
1349
|
-
if (!input) return 'Name is required'
|
|
1350
|
-
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(input)) {
|
|
1351
|
-
return 'Name must start with a letter and contain only letters, numbers, hyphens, and underscores'
|
|
1352
|
-
}
|
|
1353
|
-
return true
|
|
1354
|
-
},
|
|
1355
|
-
},
|
|
1356
|
-
])
|
|
1357
|
-
|
|
1358
|
-
if (newName === containerName) {
|
|
1359
|
-
console.log(uiInfo('Name unchanged'))
|
|
1360
|
-
return await handleEditContainer(containerName)
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
if (await containerManager.exists(newName)) {
|
|
1364
|
-
console.log(uiError(`Container "${newName}" already exists`))
|
|
1365
|
-
return await handleEditContainer(containerName)
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
const spinner = createSpinner('Renaming container...')
|
|
1369
|
-
spinner.start()
|
|
1370
|
-
|
|
1371
|
-
await containerManager.rename(containerName, newName)
|
|
1372
|
-
|
|
1373
|
-
spinner.succeed(`Renamed "${containerName}" to "${newName}"`)
|
|
1374
|
-
|
|
1375
|
-
// Continue editing with new name
|
|
1376
|
-
return await handleEditContainer(newName)
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
if (field === 'port') {
|
|
1380
|
-
const { newPort } = await escapeablePrompt<{ newPort: number }>([
|
|
1381
|
-
{
|
|
1382
|
-
type: 'input',
|
|
1383
|
-
name: 'newPort',
|
|
1384
|
-
message: 'New port:',
|
|
1385
|
-
default: String(config.port),
|
|
1386
|
-
validate: (input: string) => {
|
|
1387
|
-
const num = parseInt(input, 10)
|
|
1388
|
-
if (isNaN(num) || num < 1 || num > 65535) {
|
|
1389
|
-
return 'Port must be a number between 1 and 65535'
|
|
1390
|
-
}
|
|
1391
|
-
return true
|
|
1392
|
-
},
|
|
1393
|
-
filter: (input: string) => parseInt(input, 10),
|
|
1394
|
-
},
|
|
1395
|
-
])
|
|
1396
|
-
|
|
1397
|
-
if (newPort === config.port) {
|
|
1398
|
-
console.log(uiInfo('Port unchanged'))
|
|
1399
|
-
return await handleEditContainer(containerName)
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
const portAvailable = await portManager.isPortAvailable(newPort)
|
|
1403
|
-
if (!portAvailable) {
|
|
1404
|
-
console.log(
|
|
1405
|
-
uiWarning(
|
|
1406
|
-
`Port ${newPort} is currently in use. You'll need to stop the process using it before starting this container.`,
|
|
1407
|
-
),
|
|
1408
|
-
)
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
await containerManager.updateConfig(containerName, { port: newPort })
|
|
1412
|
-
console.log(uiSuccess(`Changed port from ${config.port} to ${newPort}`))
|
|
1413
|
-
|
|
1414
|
-
// Continue editing
|
|
1415
|
-
return await handleEditContainer(containerName)
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
if (field === 'relocate') {
|
|
1419
|
-
const currentFileName = basename(config.database)
|
|
1420
|
-
|
|
1421
|
-
const { inputPath } = await escapeablePrompt<{ inputPath: string }>([
|
|
1422
|
-
{
|
|
1423
|
-
type: 'input',
|
|
1424
|
-
name: 'inputPath',
|
|
1425
|
-
message: 'New file path:',
|
|
1426
|
-
default: config.database,
|
|
1427
|
-
validate: (input: string) => {
|
|
1428
|
-
if (!input) return 'Path is required'
|
|
1429
|
-
return true
|
|
1430
|
-
},
|
|
1431
|
-
},
|
|
1432
|
-
])
|
|
1433
|
-
|
|
1434
|
-
// Expand ~ to home directory
|
|
1435
|
-
let expandedPath = inputPath
|
|
1436
|
-
if (inputPath === '~') {
|
|
1437
|
-
expandedPath = homedir()
|
|
1438
|
-
} else if (inputPath.startsWith('~/')) {
|
|
1439
|
-
expandedPath = join(homedir(), inputPath.slice(2))
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
// Convert relative paths to absolute
|
|
1443
|
-
if (!expandedPath.startsWith('/')) {
|
|
1444
|
-
expandedPath = resolve(process.cwd(), expandedPath)
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
// Check if path looks like a file (has db extension) or directory
|
|
1448
|
-
const hasDbExtension = /\.(sqlite3?|db|duckdb|ddb)$/i.test(expandedPath)
|
|
1449
|
-
|
|
1450
|
-
// Treat as directory if:
|
|
1451
|
-
// - ends with /
|
|
1452
|
-
// - exists and is a directory
|
|
1453
|
-
// - doesn't have a database file extension (assume it's a directory path)
|
|
1454
|
-
const isDirectory =
|
|
1455
|
-
expandedPath.endsWith('/') ||
|
|
1456
|
-
(existsSync(expandedPath) && statSync(expandedPath).isDirectory()) ||
|
|
1457
|
-
!hasDbExtension
|
|
1458
|
-
|
|
1459
|
-
let finalPath: string
|
|
1460
|
-
if (isDirectory) {
|
|
1461
|
-
// Remove trailing slash if present, then append filename
|
|
1462
|
-
const dirPath = expandedPath.endsWith('/')
|
|
1463
|
-
? expandedPath.slice(0, -1)
|
|
1464
|
-
: expandedPath
|
|
1465
|
-
finalPath = join(dirPath, currentFileName)
|
|
1466
|
-
} else {
|
|
1467
|
-
finalPath = expandedPath
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
if (finalPath === config.database) {
|
|
1471
|
-
console.log(uiInfo('Location unchanged'))
|
|
1472
|
-
return await handleEditContainer(containerName)
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// Check if source file exists
|
|
1476
|
-
if (!existsSync(config.database)) {
|
|
1477
|
-
console.log(uiError(`Source file not found: ${config.database}`))
|
|
1478
|
-
return await handleEditContainer(containerName)
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
// Check if destination already exists
|
|
1482
|
-
if (existsSync(finalPath)) {
|
|
1483
|
-
console.log(uiError(`Destination file already exists: ${finalPath}`))
|
|
1484
|
-
return await handleEditContainer(containerName)
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
// Check if destination directory exists
|
|
1488
|
-
const destDir = dirname(finalPath)
|
|
1489
|
-
if (!existsSync(destDir)) {
|
|
1490
|
-
console.log(uiWarning(`Directory does not exist: ${destDir}`))
|
|
1491
|
-
const { createDir } = await escapeablePrompt<{ createDir: string }>([
|
|
1492
|
-
{
|
|
1493
|
-
type: 'list',
|
|
1494
|
-
name: 'createDir',
|
|
1495
|
-
message: 'Create this directory?',
|
|
1496
|
-
choices: [
|
|
1497
|
-
{ name: 'Yes, create it', value: 'yes' },
|
|
1498
|
-
{ name: 'No, cancel', value: 'no' },
|
|
1499
|
-
],
|
|
1500
|
-
},
|
|
1501
|
-
])
|
|
1502
|
-
|
|
1503
|
-
if (createDir !== 'yes') {
|
|
1504
|
-
return await handleEditContainer(containerName)
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
try {
|
|
1508
|
-
mkdirSync(destDir, { recursive: true })
|
|
1509
|
-
console.log(uiSuccess(`Created directory: ${destDir}`))
|
|
1510
|
-
} catch (mkdirError) {
|
|
1511
|
-
console.log(
|
|
1512
|
-
uiError(
|
|
1513
|
-
`Failed to create directory: ${(mkdirError as Error).message}`,
|
|
1514
|
-
),
|
|
1515
|
-
)
|
|
1516
|
-
return await handleEditContainer(containerName)
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
const spinner = createSpinner('Moving database file...')
|
|
1521
|
-
spinner.start()
|
|
1522
|
-
|
|
1523
|
-
try {
|
|
1524
|
-
// Try rename first (fast, same filesystem)
|
|
1525
|
-
try {
|
|
1526
|
-
renameSync(config.database, finalPath)
|
|
1527
|
-
} catch (renameErr) {
|
|
1528
|
-
const e = renameErr as NodeJS.ErrnoException
|
|
1529
|
-
// EXDEV = cross-device link, need to copy+delete
|
|
1530
|
-
if (e.code === 'EXDEV') {
|
|
1531
|
-
try {
|
|
1532
|
-
// Copy file preserving mode/permissions
|
|
1533
|
-
copyFileSync(config.database, finalPath)
|
|
1534
|
-
// Only delete source after successful copy
|
|
1535
|
-
unlinkSync(config.database)
|
|
1536
|
-
} catch (copyErr) {
|
|
1537
|
-
// Clean up partial target on failure
|
|
1538
|
-
if (existsSync(finalPath)) {
|
|
1539
|
-
try {
|
|
1540
|
-
unlinkSync(finalPath)
|
|
1541
|
-
} catch {
|
|
1542
|
-
// Ignore cleanup errors
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
throw copyErr
|
|
1546
|
-
}
|
|
1547
|
-
} else {
|
|
1548
|
-
throw renameErr
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
// Update the container config and registry
|
|
1553
|
-
await containerManager.updateConfig(containerName, {
|
|
1554
|
-
database: finalPath,
|
|
1555
|
-
})
|
|
1556
|
-
// Use appropriate registry based on engine
|
|
1557
|
-
if (isSQLite) {
|
|
1558
|
-
await sqliteRegistry.update(containerName, { filePath: finalPath })
|
|
1559
|
-
} else if (isDuckDB) {
|
|
1560
|
-
await duckdbRegistry.update(containerName, { filePath: finalPath })
|
|
1561
|
-
}
|
|
1562
|
-
spinner.succeed(`Moved database to ${finalPath}`)
|
|
1563
|
-
|
|
1564
|
-
// Wait for user to see success message before refreshing
|
|
1565
|
-
await pressEnterToContinue()
|
|
1566
|
-
} catch (error) {
|
|
1567
|
-
spinner.fail('Failed to move database file')
|
|
1568
|
-
console.log(uiError((error as Error).message))
|
|
1569
|
-
await pressEnterToContinue()
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
// Continue editing (will fetch fresh config)
|
|
1573
|
-
return await handleEditContainer(containerName)
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
return containerName
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
type SelectDatabaseResult =
|
|
1580
|
-
| { action: 'select'; database: string }
|
|
1581
|
-
| { action: 'change-default' }
|
|
1582
|
-
| { action: 'back' }
|
|
1583
|
-
| { action: 'home' }
|
|
1584
|
-
|
|
1585
|
-
async function handleSelectDatabase(
|
|
1586
|
-
containerName: string,
|
|
1587
|
-
databases: string[],
|
|
1588
|
-
primaryDatabase: string,
|
|
1589
|
-
): Promise<SelectDatabaseResult> {
|
|
1590
|
-
console.clear()
|
|
1591
|
-
console.log(header(`${containerName} - Select Database`))
|
|
1592
|
-
console.log()
|
|
1593
|
-
|
|
1594
|
-
const choices: MenuChoice[] = databases.map((db) => ({
|
|
1595
|
-
name: db === primaryDatabase ? `${db} ${chalk.gray('(default)')}` : db,
|
|
1596
|
-
value: db,
|
|
1597
|
-
}))
|
|
1598
|
-
|
|
1599
|
-
choices.push(new inquirer.Separator())
|
|
1600
|
-
choices.push({
|
|
1601
|
-
name: `${chalk.yellow('★')} Change default database`,
|
|
1602
|
-
value: '_change-default',
|
|
1603
|
-
})
|
|
1604
|
-
choices.push({
|
|
1605
|
-
name: `${chalk.blue('←')} Back`,
|
|
1606
|
-
value: '_back',
|
|
1607
|
-
})
|
|
1608
|
-
choices.push({
|
|
1609
|
-
name: `${chalk.blue('⌂')} Home ${chalk.gray('(esc)')}`,
|
|
1610
|
-
value: '_home',
|
|
1611
|
-
})
|
|
1612
|
-
|
|
1613
|
-
const { database } = await escapeablePrompt<{ database: string }>([
|
|
1614
|
-
{
|
|
1615
|
-
type: 'list',
|
|
1616
|
-
name: 'database',
|
|
1617
|
-
message: 'Select a database:',
|
|
1618
|
-
choices,
|
|
1619
|
-
pageSize: getPageSize(),
|
|
1620
|
-
},
|
|
1621
|
-
])
|
|
1622
|
-
|
|
1623
|
-
if (database === '_back') {
|
|
1624
|
-
return { action: 'back' }
|
|
1625
|
-
}
|
|
1626
|
-
if (database === '_home') {
|
|
1627
|
-
return { action: 'home' }
|
|
1628
|
-
}
|
|
1629
|
-
if (database === '_change-default') {
|
|
1630
|
-
return { action: 'change-default' }
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
return { action: 'select', database }
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
async function handleChangeDefaultDatabase(
|
|
1637
|
-
containerName: string,
|
|
1638
|
-
databases: string[],
|
|
1639
|
-
currentDefault: string,
|
|
1640
|
-
): Promise<void> {
|
|
1641
|
-
console.clear()
|
|
1642
|
-
console.log(header(`${containerName} - Change Default Database`))
|
|
1643
|
-
console.log()
|
|
1644
|
-
|
|
1645
|
-
const choices: MenuChoice[] = databases.map((db) => ({
|
|
1646
|
-
name:
|
|
1647
|
-
db === currentDefault ? `${db} ${chalk.gray('(current default)')}` : db,
|
|
1648
|
-
value: db,
|
|
1649
|
-
}))
|
|
1650
|
-
|
|
1651
|
-
choices.push(new inquirer.Separator())
|
|
1652
|
-
choices.push({
|
|
1653
|
-
name: `${chalk.blue('←')} Cancel`,
|
|
1654
|
-
value: '_cancel',
|
|
1655
|
-
})
|
|
1656
|
-
|
|
1657
|
-
const { database } = await escapeablePrompt<{ database: string }>([
|
|
1658
|
-
{
|
|
1659
|
-
type: 'list',
|
|
1660
|
-
name: 'database',
|
|
1661
|
-
message: 'Select new default database:',
|
|
1662
|
-
choices,
|
|
1663
|
-
pageSize: getPageSize(),
|
|
1664
|
-
},
|
|
1665
|
-
])
|
|
1666
|
-
|
|
1667
|
-
if (database === '_cancel' || database === currentDefault) {
|
|
1668
|
-
return
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
// Update the container config
|
|
1672
|
-
await containerManager.updateConfig(containerName, { database })
|
|
1673
|
-
console.log()
|
|
1674
|
-
console.log(uiSuccess(`Default database changed to "${database}"`))
|
|
1675
|
-
await pressEnterToContinue()
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
async function handleCloneFromSubmenu(
|
|
1679
|
-
sourceName: string,
|
|
1680
|
-
showMainMenu: () => Promise<void>,
|
|
1681
|
-
): Promise<void> {
|
|
1682
|
-
const sourceConfig = await containerManager.getConfig(sourceName)
|
|
1683
|
-
if (!sourceConfig) {
|
|
1684
|
-
console.log(uiError(`Container "${sourceName}" not found`))
|
|
1685
|
-
return
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
const { targetName } = await escapeablePrompt<{ targetName: string }>([
|
|
1689
|
-
{
|
|
1690
|
-
type: 'input',
|
|
1691
|
-
name: 'targetName',
|
|
1692
|
-
message: 'Name for the cloned container:',
|
|
1693
|
-
default: `${sourceName}-copy`,
|
|
1694
|
-
validate: (input: string) => {
|
|
1695
|
-
if (!input) return 'Name is required'
|
|
1696
|
-
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(input)) {
|
|
1697
|
-
return 'Name must start with a letter and contain only letters, numbers, hyphens, and underscores'
|
|
1698
|
-
}
|
|
1699
|
-
return true
|
|
1700
|
-
},
|
|
1701
|
-
},
|
|
1702
|
-
])
|
|
1703
|
-
|
|
1704
|
-
// Check if target container already exists
|
|
1705
|
-
if (
|
|
1706
|
-
await containerManager.exists(targetName, { engine: sourceConfig.engine })
|
|
1707
|
-
) {
|
|
1708
|
-
console.log(uiError(`Container "${targetName}" already exists`))
|
|
1709
|
-
return
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
const spinner = createSpinner(`Cloning ${sourceName} to ${targetName}...`)
|
|
1713
|
-
spinner.start()
|
|
1714
|
-
|
|
1715
|
-
try {
|
|
1716
|
-
const newConfig = await containerManager.clone(sourceName, targetName)
|
|
1717
|
-
|
|
1718
|
-
spinner.succeed(`Cloned "${sourceName}" to "${targetName}"`)
|
|
1719
|
-
|
|
1720
|
-
const engine = getEngine(newConfig.engine)
|
|
1721
|
-
const connectionString = engine.getConnectionString(newConfig)
|
|
1722
|
-
|
|
1723
|
-
console.log()
|
|
1724
|
-
console.log(connectionBox(targetName, connectionString, newConfig.port))
|
|
1725
|
-
|
|
1726
|
-
await showContainerSubmenu(targetName, showMainMenu)
|
|
1727
|
-
} catch (error) {
|
|
1728
|
-
spinner.fail(`Failed to clone "${sourceName}"`)
|
|
1729
|
-
console.log(uiError((error as Error).message))
|
|
1730
|
-
await pressEnterToContinue()
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
async function handleDetachContainer(
|
|
1735
|
-
containerName: string,
|
|
1736
|
-
showMainMenu: () => Promise<void>,
|
|
1737
|
-
): Promise<void> {
|
|
1738
|
-
const config = await containerManager.getConfig(containerName)
|
|
1739
|
-
if (!config) {
|
|
1740
|
-
console.log(uiError(`Container "${containerName}" not found`))
|
|
1741
|
-
await pressEnterToContinue()
|
|
1742
|
-
return
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
const confirmed = await promptConfirm(
|
|
1746
|
-
`Detach "${containerName}" from SpinDB? (file will be kept on disk)`,
|
|
1747
|
-
true,
|
|
1748
|
-
)
|
|
1749
|
-
|
|
1750
|
-
if (!confirmed) {
|
|
1751
|
-
console.log(uiWarning('Cancelled'))
|
|
1752
|
-
await pressEnterToContinue()
|
|
1753
|
-
await showContainerSubmenu(containerName, showMainMenu)
|
|
1754
|
-
return
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
let filePath: string | undefined
|
|
1758
|
-
// Use appropriate registry based on engine
|
|
1759
|
-
if (config.engine === Engine.SQLite) {
|
|
1760
|
-
const entry = await sqliteRegistry.get(containerName)
|
|
1761
|
-
filePath = entry?.filePath
|
|
1762
|
-
await sqliteRegistry.remove(containerName)
|
|
1763
|
-
} else if (config.engine === Engine.DuckDB) {
|
|
1764
|
-
const entry = await duckdbRegistry.get(containerName)
|
|
1765
|
-
filePath = entry?.filePath
|
|
1766
|
-
await duckdbRegistry.remove(containerName)
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
console.log(uiSuccess(`Detached "${containerName}" from SpinDB`))
|
|
1770
|
-
if (filePath) {
|
|
1771
|
-
console.log(chalk.gray(` File remains at: ${filePath}`))
|
|
1772
|
-
console.log()
|
|
1773
|
-
console.log(chalk.gray(' Re-attach with:'))
|
|
1774
|
-
console.log(chalk.cyan(` spindb attach ${filePath}`))
|
|
1775
|
-
}
|
|
1776
|
-
await pressEnterToContinue()
|
|
1777
|
-
await handleList(showMainMenu)
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
async function handleDelete(containerName: string): Promise<void> {
|
|
1781
|
-
const config = await containerManager.getConfig(containerName)
|
|
1782
|
-
if (!config) {
|
|
1783
|
-
console.error(uiError(`Container "${containerName}" not found`))
|
|
1784
|
-
return
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
const confirmed = await promptConfirm(
|
|
1788
|
-
`Are you sure you want to delete "${containerName}"? This cannot be undone.`,
|
|
1789
|
-
false,
|
|
1790
|
-
)
|
|
1791
|
-
|
|
1792
|
-
if (!confirmed) {
|
|
1793
|
-
console.log(uiWarning('Deletion cancelled'))
|
|
1794
|
-
return
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
const isRunning = await processManager.isRunning(containerName, {
|
|
1798
|
-
engine: config.engine,
|
|
1799
|
-
})
|
|
1800
|
-
|
|
1801
|
-
if (isRunning) {
|
|
1802
|
-
const stopSpinner = createSpinner(`Stopping ${containerName}...`)
|
|
1803
|
-
stopSpinner.start()
|
|
1804
|
-
|
|
1805
|
-
const engine = getEngine(config.engine)
|
|
1806
|
-
await engine.stop(config)
|
|
1807
|
-
|
|
1808
|
-
stopSpinner.succeed(`Stopped "${containerName}"`)
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
const deleteSpinner = createSpinner(`Deleting ${containerName}...`)
|
|
1812
|
-
deleteSpinner.start()
|
|
1813
|
-
|
|
1814
|
-
await containerManager.delete(containerName, { force: true })
|
|
1815
|
-
|
|
1816
|
-
deleteSpinner.succeed(`Container "${containerName}" deleted`)
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
async function isDockerContainerRunning(
|
|
1820
|
-
containerName: string,
|
|
1821
|
-
): Promise<boolean> {
|
|
1822
|
-
try {
|
|
1823
|
-
const { execSync } = await import('child_process')
|
|
1824
|
-
const result = execSync(
|
|
1825
|
-
`docker ps --filter "name=spindb-${containerName}" --format "{{.Names}}"`,
|
|
1826
|
-
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
|
|
1827
|
-
)
|
|
1828
|
-
return result.trim().includes(`spindb-${containerName}`)
|
|
1829
|
-
} catch {
|
|
1830
|
-
return false
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
async function handleExportSubmenu(
|
|
1835
|
-
containerName: string,
|
|
1836
|
-
databases: string[],
|
|
1837
|
-
showMainMenu: () => Promise<void>,
|
|
1838
|
-
): Promise<void> {
|
|
1839
|
-
const config = await containerManager.getConfig(containerName)
|
|
1840
|
-
if (!config) {
|
|
1841
|
-
console.log(uiError(`Container "${containerName}" not found`))
|
|
1842
|
-
await pressEnterToContinue()
|
|
1843
|
-
return
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
// Check if Docker export already exists
|
|
1847
|
-
const hasDockerExport = dockerExportExists(containerName, config.engine)
|
|
1848
|
-
|
|
1849
|
-
// Check if Docker container is running (only if export exists)
|
|
1850
|
-
let dockerRunning = false
|
|
1851
|
-
if (hasDockerExport) {
|
|
1852
|
-
dockerRunning = await isDockerContainerRunning(containerName)
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
console.log()
|
|
1856
|
-
console.log(header('Export'))
|
|
1857
|
-
console.log()
|
|
1858
|
-
|
|
1859
|
-
// Build choices based on whether export exists
|
|
1860
|
-
const choices: MenuChoice[] = []
|
|
1861
|
-
|
|
1862
|
-
if (hasDockerExport) {
|
|
1863
|
-
// Export exists: show option to get connection string with running status
|
|
1864
|
-
const runningStatus = dockerRunning
|
|
1865
|
-
? chalk.green('running')
|
|
1866
|
-
: chalk.gray('not running')
|
|
1867
|
-
choices.push({
|
|
1868
|
-
name: `${chalk.green('⎘')} Get Docker connection string ${chalk.gray(`(${runningStatus})`)}`,
|
|
1869
|
-
value: 'docker-url',
|
|
1870
|
-
})
|
|
1871
|
-
choices.push({
|
|
1872
|
-
name: `${chalk.cyan('▣')} Docker ${chalk.gray('(Re-export - invalidates original credentials)')}`,
|
|
1873
|
-
value: 'docker',
|
|
1874
|
-
})
|
|
1875
|
-
} else {
|
|
1876
|
-
// No export: just show Docker option
|
|
1877
|
-
choices.push({ name: `${chalk.cyan('▣')} Docker`, value: 'docker' })
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
choices.push(new inquirer.Separator())
|
|
1881
|
-
choices.push({ name: `${chalk.blue('←')} Back`, value: 'back' })
|
|
1882
|
-
choices.push({ name: `${chalk.blue('⌂')} Back to main menu`, value: 'home' })
|
|
1883
|
-
|
|
1884
|
-
const { action } = await escapeablePrompt<{ action: string }>([
|
|
1885
|
-
{
|
|
1886
|
-
type: 'list',
|
|
1887
|
-
name: 'action',
|
|
1888
|
-
message: 'Export format:',
|
|
1889
|
-
choices,
|
|
1890
|
-
},
|
|
1891
|
-
])
|
|
1892
|
-
|
|
1893
|
-
switch (action) {
|
|
1894
|
-
case 'docker-url':
|
|
1895
|
-
await handleGetDockerConnectionString(containerName, config.engine)
|
|
1896
|
-
await handleExportSubmenu(containerName, databases, showMainMenu)
|
|
1897
|
-
return
|
|
1898
|
-
case 'docker':
|
|
1899
|
-
await handleExportDocker(containerName, databases, showMainMenu)
|
|
1900
|
-
return
|
|
1901
|
-
case 'back':
|
|
1902
|
-
await showContainerSubmenu(containerName, showMainMenu, undefined)
|
|
1903
|
-
return
|
|
1904
|
-
case 'home':
|
|
1905
|
-
await showMainMenu()
|
|
1906
|
-
return
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
async function handleGetDockerConnectionString(
|
|
1911
|
-
containerName: string,
|
|
1912
|
-
engine: Engine,
|
|
1913
|
-
): Promise<void> {
|
|
1914
|
-
const connectionString = await getDockerConnectionString(
|
|
1915
|
-
containerName,
|
|
1916
|
-
engine,
|
|
1917
|
-
)
|
|
1918
|
-
|
|
1919
|
-
if (!connectionString) {
|
|
1920
|
-
console.log()
|
|
1921
|
-
console.log(uiError('Could not read Docker export credentials'))
|
|
1922
|
-
await pressEnterToContinue()
|
|
1923
|
-
return
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
// Copy to clipboard
|
|
1927
|
-
const copied = await platformService.copyToClipboard(connectionString)
|
|
1928
|
-
|
|
1929
|
-
console.log()
|
|
1930
|
-
if (copied) {
|
|
1931
|
-
console.log(uiSuccess('Connection string copied to clipboard'))
|
|
1932
|
-
} else {
|
|
1933
|
-
console.log(uiWarning('Could not copy to clipboard'))
|
|
1934
|
-
}
|
|
1935
|
-
console.log()
|
|
1936
|
-
console.log(chalk.gray(' Connection string:'))
|
|
1937
|
-
console.log(chalk.cyan(` ${connectionString}`))
|
|
1938
|
-
console.log()
|
|
1939
|
-
|
|
1940
|
-
await pressEnterToContinue()
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
async function handleExportDocker(
|
|
1944
|
-
containerName: string,
|
|
1945
|
-
databases: string[],
|
|
1946
|
-
showMainMenu: () => Promise<void>,
|
|
1947
|
-
): Promise<void> {
|
|
1948
|
-
const config = await containerManager.getConfig(containerName)
|
|
1949
|
-
if (!config) {
|
|
1950
|
-
console.log(uiError(`Container "${containerName}" not found`))
|
|
1951
|
-
await pressEnterToContinue()
|
|
1952
|
-
await showContainerSubmenu(containerName, showMainMenu, undefined)
|
|
1953
|
-
return
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
const engine = getEngine(config.engine)
|
|
1957
|
-
const engineDefaultPort = getEngineDefaults(config.engine).defaultPort
|
|
1958
|
-
|
|
1959
|
-
// Determine output directory
|
|
1960
|
-
const outputDir = join(
|
|
1961
|
-
paths.getContainerPath(containerName, { engine: config.engine }),
|
|
1962
|
-
'docker',
|
|
1963
|
-
)
|
|
1964
|
-
|
|
1965
|
-
// Check if output directory already exists
|
|
1966
|
-
if (existsSync(outputDir)) {
|
|
1967
|
-
console.log()
|
|
1968
|
-
console.log(uiWarning(`Output directory already exists: ${outputDir}`))
|
|
1969
|
-
const shouldOverwrite = await promptConfirm(
|
|
1970
|
-
'Do you want to overwrite it?',
|
|
1971
|
-
false,
|
|
1972
|
-
)
|
|
1973
|
-
if (!shouldOverwrite) {
|
|
1974
|
-
console.log(uiInfo('Export cancelled'))
|
|
1975
|
-
await pressEnterToContinue()
|
|
1976
|
-
await showContainerSubmenu(containerName, showMainMenu, undefined)
|
|
1977
|
-
return
|
|
1978
|
-
}
|
|
1979
|
-
// Remove existing directory
|
|
1980
|
-
try {
|
|
1981
|
-
await rm(outputDir, { recursive: true, force: true })
|
|
1982
|
-
} catch (error) {
|
|
1983
|
-
console.log(
|
|
1984
|
-
uiError(
|
|
1985
|
-
`Failed to remove existing directory: ${(error as Error).message}`,
|
|
1986
|
-
),
|
|
1987
|
-
)
|
|
1988
|
-
await pressEnterToContinue()
|
|
1989
|
-
await showContainerSubmenu(containerName, showMainMenu, undefined)
|
|
1990
|
-
return
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
// Determine target port
|
|
1995
|
-
let targetPort = engineDefaultPort
|
|
1996
|
-
if (config.port !== engineDefaultPort) {
|
|
1997
|
-
console.log()
|
|
1998
|
-
console.log(
|
|
1999
|
-
chalk.yellow(
|
|
2000
|
-
`Local container uses port ${chalk.cyan(String(config.port))}, but ${engine.displayName}'s standard port is ${chalk.cyan(String(engineDefaultPort))}.`,
|
|
2001
|
-
),
|
|
2002
|
-
)
|
|
2003
|
-
const { selectedPort } = await escapeablePrompt<{ selectedPort: number }>([
|
|
2004
|
-
{
|
|
2005
|
-
type: 'list',
|
|
2006
|
-
name: 'selectedPort',
|
|
2007
|
-
message: 'Which port should the Docker container use?',
|
|
2008
|
-
choices: [
|
|
2009
|
-
{
|
|
2010
|
-
name: `${engineDefaultPort} ${chalk.gray('(standard port - recommended)')}`,
|
|
2011
|
-
value: engineDefaultPort,
|
|
2012
|
-
},
|
|
2013
|
-
{
|
|
2014
|
-
name: `${config.port} ${chalk.gray('(same as local container)')}`,
|
|
2015
|
-
value: config.port,
|
|
2016
|
-
},
|
|
2017
|
-
],
|
|
2018
|
-
default: engineDefaultPort,
|
|
2019
|
-
},
|
|
2020
|
-
])
|
|
2021
|
-
targetPort = selectedPort
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
console.log()
|
|
2025
|
-
console.log(chalk.bold(`Exporting ${chalk.cyan(containerName)} to Docker...`))
|
|
2026
|
-
console.log()
|
|
2027
|
-
|
|
2028
|
-
// Create backups for all databases (or copy file for file-based DBs)
|
|
2029
|
-
const backupPaths: Array<{ database: string; path: string }> = []
|
|
2030
|
-
const isFileBased = isFileBasedEngine(config.engine)
|
|
2031
|
-
|
|
2032
|
-
if (isFileBased) {
|
|
2033
|
-
// File-based database: copy the database file directly
|
|
2034
|
-
const copySpinner = createSpinner('Copying database file...')
|
|
2035
|
-
copySpinner.start()
|
|
2036
|
-
|
|
2037
|
-
try {
|
|
2038
|
-
await mkdir(join(outputDir, 'data'), { recursive: true })
|
|
2039
|
-
|
|
2040
|
-
// Get the database file path from config.database
|
|
2041
|
-
const dbFilePath = config.database
|
|
2042
|
-
if (!existsSync(dbFilePath)) {
|
|
2043
|
-
throw new Error(`Database file not found: ${dbFilePath}`)
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
// Copy to data directory with original filename
|
|
2047
|
-
const destPath = join(outputDir, 'data', basename(dbFilePath))
|
|
2048
|
-
copyFileSync(dbFilePath, destPath)
|
|
2049
|
-
|
|
2050
|
-
const fileSize = (await stat(destPath)).size
|
|
2051
|
-
backupPaths.push({ database: config.database, path: destPath })
|
|
2052
|
-
|
|
2053
|
-
copySpinner.succeed(`Database file copied (${formatBytes(fileSize)})`)
|
|
2054
|
-
} catch (error) {
|
|
2055
|
-
copySpinner.fail('Failed to copy database file')
|
|
2056
|
-
console.log(uiError((error as Error).message))
|
|
2057
|
-
await pressEnterToContinue()
|
|
2058
|
-
await showContainerSubmenu(containerName, showMainMenu, undefined)
|
|
2059
|
-
return
|
|
2060
|
-
}
|
|
2061
|
-
} else {
|
|
2062
|
-
// Server-based database: create backups using engine's backup method
|
|
2063
|
-
const backupSpinner = createSpinner(
|
|
2064
|
-
databases.length > 1
|
|
2065
|
-
? `Creating backups for ${databases.length} databases...`
|
|
2066
|
-
: 'Creating database backup...',
|
|
2067
|
-
)
|
|
2068
|
-
backupSpinner.start()
|
|
2069
|
-
|
|
2070
|
-
try {
|
|
2071
|
-
await mkdir(join(outputDir, 'data'), { recursive: true })
|
|
2072
|
-
|
|
2073
|
-
for (const db of databases) {
|
|
2074
|
-
const backupPath = getExportBackupPath(
|
|
2075
|
-
outputDir,
|
|
2076
|
-
containerName,
|
|
2077
|
-
db,
|
|
2078
|
-
config.engine,
|
|
2079
|
-
)
|
|
2080
|
-
const format = getDefaultFormat(config.engine)
|
|
2081
|
-
const result = await engine.backup(config, backupPath, {
|
|
2082
|
-
database: db,
|
|
2083
|
-
format,
|
|
2084
|
-
})
|
|
2085
|
-
backupPaths.push({ database: db, path: result.path })
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
const totalSize = await Promise.all(
|
|
2089
|
-
backupPaths.map(async (bp) => (await stat(bp.path)).size),
|
|
2090
|
-
).then((sizes) => sizes.reduce((a, b) => a + b, 0))
|
|
2091
|
-
|
|
2092
|
-
backupSpinner.succeed(
|
|
2093
|
-
databases.length > 1
|
|
2094
|
-
? `Backups created for ${databases.length} databases (${formatBytes(totalSize)})`
|
|
2095
|
-
: `Backup created (${formatBytes(totalSize)})`,
|
|
2096
|
-
)
|
|
2097
|
-
} catch (error) {
|
|
2098
|
-
backupSpinner.fail('Backup failed')
|
|
2099
|
-
console.log(uiError((error as Error).message))
|
|
2100
|
-
await pressEnterToContinue()
|
|
2101
|
-
await showContainerSubmenu(containerName, showMainMenu, undefined)
|
|
2102
|
-
return
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
// Generate Docker artifacts
|
|
2107
|
-
const exportSpinner = createSpinner('Generating Docker artifacts...')
|
|
2108
|
-
exportSpinner.start()
|
|
2109
|
-
|
|
2110
|
-
try {
|
|
2111
|
-
const result = await exportToDocker(config, {
|
|
2112
|
-
outputDir,
|
|
2113
|
-
port: targetPort,
|
|
2114
|
-
includeData: true,
|
|
2115
|
-
backupPaths: backupPaths.length > 0 ? backupPaths : undefined,
|
|
2116
|
-
skipTLS: isFileBased, // Skip TLS for file-based DBs (no network connection)
|
|
2117
|
-
})
|
|
2118
|
-
|
|
2119
|
-
exportSpinner.succeed('Docker artifacts generated')
|
|
2120
|
-
|
|
2121
|
-
console.log()
|
|
2122
|
-
console.log(uiSuccess(`Exported ${chalk.cyan(containerName)} to Docker`))
|
|
2123
|
-
console.log()
|
|
2124
|
-
|
|
2125
|
-
// Display summary
|
|
2126
|
-
const lines = [
|
|
2127
|
-
`${chalk.bold(engine.displayName)} ${config.version}`,
|
|
2128
|
-
`Port: ${chalk.green(String(targetPort))}`,
|
|
2129
|
-
databases.length > 1
|
|
2130
|
-
? `Databases: ${chalk.cyan(databases.join(', '))}`
|
|
2131
|
-
: `Database: ${chalk.cyan(config.database)}`,
|
|
2132
|
-
'',
|
|
2133
|
-
chalk.bold('Generated Credentials'),
|
|
2134
|
-
chalk.gray('────────────────────────'),
|
|
2135
|
-
`Username: ${chalk.white(result.credentials.username)}`,
|
|
2136
|
-
`Password: ${chalk.white(result.credentials.password)}`,
|
|
2137
|
-
chalk.gray('────────────────────────'),
|
|
2138
|
-
'',
|
|
2139
|
-
chalk.yellow('Save these credentials now - stored in .env'),
|
|
2140
|
-
]
|
|
2141
|
-
|
|
2142
|
-
// Simple box display using the theme's box function
|
|
2143
|
-
console.log(box(lines))
|
|
2144
|
-
|
|
2145
|
-
console.log()
|
|
2146
|
-
console.log(chalk.gray(' Output:'), chalk.cyan(result.outputDir))
|
|
2147
|
-
console.log()
|
|
2148
|
-
console.log(chalk.bold(' To run:'))
|
|
2149
|
-
console.log(
|
|
2150
|
-
chalk.cyan(` cd "${result.outputDir}" && docker compose up -d`),
|
|
2151
|
-
)
|
|
2152
|
-
console.log()
|
|
2153
|
-
} catch (error) {
|
|
2154
|
-
exportSpinner.fail('Export failed')
|
|
2155
|
-
console.log(uiError((error as Error).message))
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
await pressEnterToContinue()
|
|
2159
|
-
await showContainerSubmenu(containerName, showMainMenu, undefined)
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
async function handleCreateUser(
|
|
2163
|
-
containerName: string,
|
|
2164
|
-
activeDatabase?: string,
|
|
2165
|
-
): Promise<void> {
|
|
2166
|
-
const config = await containerManager.getConfig(containerName)
|
|
2167
|
-
if (!config) {
|
|
2168
|
-
console.log(uiError(`Container "${containerName}" not found`))
|
|
2169
|
-
await pressEnterToContinue()
|
|
2170
|
-
return
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
try {
|
|
2174
|
-
// Prompt for username
|
|
2175
|
-
const defaultUser = getDefaultUsername(config.engine)
|
|
2176
|
-
const { username } = await escapeablePrompt<{ username: string }>([
|
|
2177
|
-
{
|
|
2178
|
-
type: 'input',
|
|
2179
|
-
name: 'username',
|
|
2180
|
-
message: 'Username:',
|
|
2181
|
-
default: defaultUser,
|
|
2182
|
-
validate: (input: string) => {
|
|
2183
|
-
if (!input.trim()) return 'Username is required'
|
|
2184
|
-
if (!isValidUsername(input)) {
|
|
2185
|
-
return 'Must start with a letter, contain only letters/numbers/underscores'
|
|
2186
|
-
}
|
|
2187
|
-
return true
|
|
2188
|
-
},
|
|
2189
|
-
},
|
|
2190
|
-
])
|
|
2191
|
-
|
|
2192
|
-
// Check for existing credentials
|
|
2193
|
-
if (credentialsExist(containerName, config.engine, username)) {
|
|
2194
|
-
const overwrite = await promptConfirm(
|
|
2195
|
-
`Credentials for "${username}" already exist. Overwrite?`,
|
|
2196
|
-
false,
|
|
2197
|
-
)
|
|
2198
|
-
if (!overwrite) {
|
|
2199
|
-
console.log(chalk.yellow('Credential creation cancelled.'))
|
|
2200
|
-
await pressEnterToContinue()
|
|
2201
|
-
return
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
const password = generatePassword({ length: 20, alphanumericOnly: true })
|
|
2206
|
-
const engine = getEngine(config.engine)
|
|
2207
|
-
|
|
2208
|
-
const spinner = createSpinner(`Creating user "${username}"...`)
|
|
2209
|
-
spinner.start()
|
|
2210
|
-
|
|
2211
|
-
let credentials
|
|
2212
|
-
try {
|
|
2213
|
-
credentials = await engine.createUser(config, {
|
|
2214
|
-
username,
|
|
2215
|
-
password,
|
|
2216
|
-
database: activeDatabase || config.database,
|
|
2217
|
-
})
|
|
2218
|
-
spinner.succeed(`Created user "${username}"`)
|
|
2219
|
-
} catch (error) {
|
|
2220
|
-
spinner.fail(`Failed to create user "${username}"`)
|
|
2221
|
-
throw error
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
// Save credentials (non-fatal — credentials are already created)
|
|
2225
|
-
let credentialFile: string | undefined
|
|
2226
|
-
try {
|
|
2227
|
-
credentialFile = await saveCredentials(
|
|
2228
|
-
containerName,
|
|
2229
|
-
config.engine,
|
|
2230
|
-
credentials,
|
|
2231
|
-
)
|
|
2232
|
-
} catch (error) {
|
|
2233
|
-
console.log(
|
|
2234
|
-
uiWarning(
|
|
2235
|
-
`Could not save credentials to disk: ${(error as Error).message}`,
|
|
2236
|
-
),
|
|
2237
|
-
)
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
console.log()
|
|
2241
|
-
if (credentials.apiKey) {
|
|
2242
|
-
console.log(` ${chalk.gray('Key name:')} ${credentials.username}`)
|
|
2243
|
-
console.log(` ${chalk.gray('API key:')} ${credentials.apiKey}`)
|
|
2244
|
-
console.log(
|
|
2245
|
-
` ${chalk.gray('API URL:')} ${credentials.connectionString}`,
|
|
2246
|
-
)
|
|
2247
|
-
} else {
|
|
2248
|
-
console.log(` ${chalk.gray('Username:')} ${credentials.username}`)
|
|
2249
|
-
console.log(` ${chalk.gray('Password:')} ${credentials.password}`)
|
|
2250
|
-
if (credentials.database) {
|
|
2251
|
-
console.log(` ${chalk.gray('Database:')} ${credentials.database}`)
|
|
2252
|
-
}
|
|
2253
|
-
console.log(
|
|
2254
|
-
` ${chalk.gray('URL:')} ${credentials.connectionString}`,
|
|
2255
|
-
)
|
|
2256
|
-
}
|
|
2257
|
-
if (credentialFile) {
|
|
2258
|
-
console.log()
|
|
2259
|
-
console.log(` ${chalk.gray('Saved to:')} ${credentialFile}`)
|
|
2260
|
-
}
|
|
2261
|
-
console.log()
|
|
2262
|
-
|
|
2263
|
-
// Offer to copy to clipboard
|
|
2264
|
-
try {
|
|
2265
|
-
const copyText = credentials.apiKey || credentials.connectionString
|
|
2266
|
-
const copied = await platformService.copyToClipboard(copyText)
|
|
2267
|
-
if (copied) {
|
|
2268
|
-
console.log(
|
|
2269
|
-
uiSuccess(
|
|
2270
|
-
credentials.apiKey
|
|
2271
|
-
? 'API key copied to clipboard'
|
|
2272
|
-
: 'Connection string copied to clipboard',
|
|
2273
|
-
),
|
|
2274
|
-
)
|
|
2275
|
-
}
|
|
2276
|
-
} catch {
|
|
2277
|
-
// Clipboard failure is non-critical — credentials are already displayed above
|
|
2278
|
-
}
|
|
2279
|
-
} catch (error) {
|
|
2280
|
-
if (error instanceof UnsupportedOperationError) {
|
|
2281
|
-
console.log(uiError('User management is not supported for this engine'))
|
|
2282
|
-
} else {
|
|
2283
|
-
console.log(uiError((error as Error).message))
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
|
|
2287
|
-
await pressEnterToContinue()
|
|
2288
|
-
}
|