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,1571 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import inquirer from 'inquirer'
|
|
3
|
-
import { existsSync } from 'fs'
|
|
4
|
-
import { rm, mkdir } from 'fs/promises'
|
|
5
|
-
import { join } from 'path'
|
|
6
|
-
import { tmpdir } from 'os'
|
|
7
|
-
import { containerManager } from '../../../core/container-manager'
|
|
8
|
-
import { getMissingDependencies } from '../../../core/dependency-manager'
|
|
9
|
-
import { platformService } from '../../../core/platform-service'
|
|
10
|
-
import { portManager } from '../../../core/port-manager'
|
|
11
|
-
import { getEngine } from '../../../engines'
|
|
12
|
-
import { defaults } from '../../../config/defaults'
|
|
13
|
-
import {
|
|
14
|
-
getBackupExtension,
|
|
15
|
-
getBackupSpinnerLabel,
|
|
16
|
-
getDefaultFormat,
|
|
17
|
-
} from '../../../config/backup-formats'
|
|
18
|
-
import {
|
|
19
|
-
generateBackupTimestamp,
|
|
20
|
-
estimateBackupSize,
|
|
21
|
-
checkBackupSize,
|
|
22
|
-
} from '../../../core/backup-restore'
|
|
23
|
-
import {
|
|
24
|
-
promptCreateOptions,
|
|
25
|
-
promptContainerName,
|
|
26
|
-
promptContainerSelect,
|
|
27
|
-
promptDatabaseName,
|
|
28
|
-
promptDatabaseSelect,
|
|
29
|
-
promptBackupFormat,
|
|
30
|
-
promptBackupFilename,
|
|
31
|
-
promptBackupDirectory,
|
|
32
|
-
promptInstallDependencies,
|
|
33
|
-
promptConfirm,
|
|
34
|
-
escapeablePrompt,
|
|
35
|
-
filterableListPrompt,
|
|
36
|
-
type FilterableChoice,
|
|
37
|
-
BACK_VALUE,
|
|
38
|
-
MAIN_MENU_VALUE,
|
|
39
|
-
ESCAPE_VALUE,
|
|
40
|
-
} from '../../ui/prompts'
|
|
41
|
-
import { createSpinner } from '../../ui/spinner'
|
|
42
|
-
import {
|
|
43
|
-
header,
|
|
44
|
-
uiSuccess,
|
|
45
|
-
uiError,
|
|
46
|
-
uiWarning,
|
|
47
|
-
connectionBox,
|
|
48
|
-
formatBytes,
|
|
49
|
-
} from '../../ui/theme'
|
|
50
|
-
import { getEngineIcon, getPageSize } from '../../constants'
|
|
51
|
-
import { Engine, assertExhaustive } from '../../../types'
|
|
52
|
-
import { pressEnterToContinue } from './shared'
|
|
53
|
-
import { SpinDBError, ErrorCodes } from '../../../core/error-handler'
|
|
54
|
-
import { validateTypedbConnectionString } from './validators'
|
|
55
|
-
|
|
56
|
-
// Strip surrounding quotes from paths (handles drag-and-drop paths)
|
|
57
|
-
function stripQuotes(path: string): string {
|
|
58
|
-
return path.replace(/^['"]|['"]$/g, '').trim()
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Mask the password portion of a connection string for display.
|
|
63
|
-
* Example: postgresql://user:secretpass@host:5432/db → postgresql://user:****@host:5432/db
|
|
64
|
-
* Handles passwords containing '@' characters by using URL parsing.
|
|
65
|
-
*/
|
|
66
|
-
function maskConnectionStringPassword(connectionString: string): string {
|
|
67
|
-
if (!connectionString) return connectionString
|
|
68
|
-
try {
|
|
69
|
-
// Use URL constructor for robust parsing (handles @ in passwords)
|
|
70
|
-
const url = new URL(connectionString)
|
|
71
|
-
if (url.password) {
|
|
72
|
-
url.password = '****'
|
|
73
|
-
return url.toString()
|
|
74
|
-
}
|
|
75
|
-
return connectionString
|
|
76
|
-
} catch {
|
|
77
|
-
// Fallback for malformed URLs - use greedy regex that captures everything up to last @
|
|
78
|
-
try {
|
|
79
|
-
return connectionString.replace(
|
|
80
|
-
/^([a-z+]+:\/\/[^:]*:)(.+)(@[^@]+)$/i,
|
|
81
|
-
(_, prefix, _password, suffix) => `${prefix}****${suffix}`,
|
|
82
|
-
)
|
|
83
|
-
} catch {
|
|
84
|
-
return connectionString
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Validate a connection string for the given engine.
|
|
91
|
-
* Returns true if valid, or an error message string if invalid.
|
|
92
|
-
*
|
|
93
|
-
* Empty input is intentionally allowed (returns true) to support the
|
|
94
|
-
* "press Enter to go back" UX pattern. Callers must check for empty
|
|
95
|
-
* input after the prompt returns (e.g., `if (!connectionString.trim()) return`).
|
|
96
|
-
*/
|
|
97
|
-
function validateConnectionString(
|
|
98
|
-
input: string,
|
|
99
|
-
engine: Engine,
|
|
100
|
-
): true | string {
|
|
101
|
-
// Allow empty input for "press Enter to go back" UX - callers handle this case
|
|
102
|
-
if (!input) return true
|
|
103
|
-
|
|
104
|
-
switch (engine) {
|
|
105
|
-
case Engine.PostgreSQL:
|
|
106
|
-
if (
|
|
107
|
-
!input.startsWith('postgresql://') &&
|
|
108
|
-
!input.startsWith('postgres://')
|
|
109
|
-
) {
|
|
110
|
-
return 'Connection string must start with postgresql:// or postgres://'
|
|
111
|
-
}
|
|
112
|
-
break
|
|
113
|
-
case Engine.MySQL:
|
|
114
|
-
if (!input.startsWith('mysql://')) {
|
|
115
|
-
return 'Connection string must start with mysql://'
|
|
116
|
-
}
|
|
117
|
-
break
|
|
118
|
-
case Engine.MariaDB:
|
|
119
|
-
if (!input.startsWith('mysql://') && !input.startsWith('mariadb://')) {
|
|
120
|
-
return 'Connection string must start with mysql:// or mariadb://'
|
|
121
|
-
}
|
|
122
|
-
break
|
|
123
|
-
case Engine.MongoDB:
|
|
124
|
-
case Engine.FerretDB:
|
|
125
|
-
if (
|
|
126
|
-
!input.startsWith('mongodb://') &&
|
|
127
|
-
!input.startsWith('mongodb+srv://')
|
|
128
|
-
) {
|
|
129
|
-
return 'Connection string must start with mongodb:// or mongodb+srv://'
|
|
130
|
-
}
|
|
131
|
-
break
|
|
132
|
-
case Engine.Redis:
|
|
133
|
-
if (!input.startsWith('redis://') && !input.startsWith('rediss://')) {
|
|
134
|
-
return 'Connection string must start with redis:// or rediss://'
|
|
135
|
-
}
|
|
136
|
-
break
|
|
137
|
-
case Engine.Valkey:
|
|
138
|
-
if (
|
|
139
|
-
!input.startsWith('redis://') &&
|
|
140
|
-
!input.startsWith('rediss://') &&
|
|
141
|
-
!input.startsWith('valkey://') &&
|
|
142
|
-
!input.startsWith('valkeys://')
|
|
143
|
-
) {
|
|
144
|
-
return 'Connection string must start with redis://, rediss://, valkey://, or valkeys://'
|
|
145
|
-
}
|
|
146
|
-
break
|
|
147
|
-
case Engine.ClickHouse:
|
|
148
|
-
if (
|
|
149
|
-
!input.startsWith('clickhouse://') &&
|
|
150
|
-
!input.startsWith('http://') &&
|
|
151
|
-
!input.startsWith('https://')
|
|
152
|
-
) {
|
|
153
|
-
return 'Connection string must start with clickhouse://, http://, or https://'
|
|
154
|
-
}
|
|
155
|
-
break
|
|
156
|
-
case Engine.Qdrant:
|
|
157
|
-
if (
|
|
158
|
-
!input.startsWith('qdrant://') &&
|
|
159
|
-
!input.startsWith('http://') &&
|
|
160
|
-
!input.startsWith('https://')
|
|
161
|
-
) {
|
|
162
|
-
return 'Connection string must start with qdrant://, http://, or https://'
|
|
163
|
-
}
|
|
164
|
-
break
|
|
165
|
-
case Engine.Meilisearch:
|
|
166
|
-
if (
|
|
167
|
-
!input.startsWith('meilisearch://') &&
|
|
168
|
-
!input.startsWith('http://') &&
|
|
169
|
-
!input.startsWith('https://')
|
|
170
|
-
) {
|
|
171
|
-
return 'Connection string must start with meilisearch://, http://, or https://'
|
|
172
|
-
}
|
|
173
|
-
break
|
|
174
|
-
case Engine.CouchDB:
|
|
175
|
-
if (
|
|
176
|
-
!input.startsWith('couchdb://') &&
|
|
177
|
-
!input.startsWith('http://') &&
|
|
178
|
-
!input.startsWith('https://')
|
|
179
|
-
) {
|
|
180
|
-
return 'Connection string must start with couchdb://, http://, or https://'
|
|
181
|
-
}
|
|
182
|
-
break
|
|
183
|
-
case Engine.CockroachDB:
|
|
184
|
-
if (
|
|
185
|
-
!input.startsWith('postgresql://') &&
|
|
186
|
-
!input.startsWith('postgres://')
|
|
187
|
-
) {
|
|
188
|
-
return 'Connection string must start with postgresql:// or postgres://'
|
|
189
|
-
}
|
|
190
|
-
break
|
|
191
|
-
case Engine.SurrealDB:
|
|
192
|
-
if (
|
|
193
|
-
!input.startsWith('surrealdb://') &&
|
|
194
|
-
!input.startsWith('ws://') &&
|
|
195
|
-
!input.startsWith('wss://') &&
|
|
196
|
-
!input.startsWith('http://') &&
|
|
197
|
-
!input.startsWith('https://')
|
|
198
|
-
) {
|
|
199
|
-
return 'Connection string must start with surrealdb://, ws://, wss://, http://, or https://'
|
|
200
|
-
}
|
|
201
|
-
break
|
|
202
|
-
case Engine.QuestDB:
|
|
203
|
-
// QuestDB uses PostgreSQL wire protocol
|
|
204
|
-
if (
|
|
205
|
-
!input.startsWith('postgresql://') &&
|
|
206
|
-
!input.startsWith('postgres://')
|
|
207
|
-
) {
|
|
208
|
-
return 'Connection string must start with postgresql:// or postgres://'
|
|
209
|
-
}
|
|
210
|
-
break
|
|
211
|
-
case Engine.TypeDB:
|
|
212
|
-
{
|
|
213
|
-
const typedbError = validateTypedbConnectionString(input)
|
|
214
|
-
if (typedbError) return typedbError
|
|
215
|
-
}
|
|
216
|
-
break
|
|
217
|
-
case Engine.InfluxDB:
|
|
218
|
-
if (
|
|
219
|
-
!input.startsWith('influxdb://') &&
|
|
220
|
-
!input.startsWith('http://') &&
|
|
221
|
-
!input.startsWith('https://')
|
|
222
|
-
) {
|
|
223
|
-
return 'Connection string must start with influxdb://, http://, or https://'
|
|
224
|
-
}
|
|
225
|
-
break
|
|
226
|
-
case Engine.Weaviate:
|
|
227
|
-
if (!input.startsWith('http://') && !input.startsWith('https://')) {
|
|
228
|
-
return 'Connection string must start with http:// or https://'
|
|
229
|
-
}
|
|
230
|
-
break
|
|
231
|
-
case Engine.TigerBeetle:
|
|
232
|
-
return 'TigerBeetle does not support remote dumps (custom binary protocol)'
|
|
233
|
-
case Engine.SQLite:
|
|
234
|
-
case Engine.DuckDB:
|
|
235
|
-
return 'File-based engines do not support remote connection strings'
|
|
236
|
-
default:
|
|
237
|
-
assertExhaustive(engine)
|
|
238
|
-
}
|
|
239
|
-
return true
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Prompt for a connection string with validation and password masking.
|
|
244
|
-
* Shows a hint about pressing Enter to go back and Escape for main menu.
|
|
245
|
-
*
|
|
246
|
-
* @param engine - The database engine for connection string validation
|
|
247
|
-
* @returns The connection string, or null if empty/escaped
|
|
248
|
-
*/
|
|
249
|
-
async function promptConnectionString(engine: Engine): Promise<string | null> {
|
|
250
|
-
console.log(
|
|
251
|
-
chalk.gray(
|
|
252
|
-
' Enter connection string, or press Enter to go back (esc - main menu)',
|
|
253
|
-
),
|
|
254
|
-
)
|
|
255
|
-
const { connectionString } = await escapeablePrompt<{
|
|
256
|
-
connectionString: string
|
|
257
|
-
}>([
|
|
258
|
-
{
|
|
259
|
-
type: 'input',
|
|
260
|
-
name: 'connectionString',
|
|
261
|
-
message: 'Connection string:',
|
|
262
|
-
transformer: (input: string) =>
|
|
263
|
-
maskConnectionStringPassword(input.trim()),
|
|
264
|
-
validate: (input: string) =>
|
|
265
|
-
validateConnectionString(input.trim(), engine),
|
|
266
|
-
},
|
|
267
|
-
])
|
|
268
|
-
|
|
269
|
-
const trimmed = connectionString.trim()
|
|
270
|
-
return trimmed || null
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export async function handleCreateForRestore(): Promise<{
|
|
274
|
-
name: string
|
|
275
|
-
config: NonNullable<Awaited<ReturnType<typeof containerManager.getConfig>>>
|
|
276
|
-
} | null> {
|
|
277
|
-
console.log()
|
|
278
|
-
const answers = await promptCreateOptions()
|
|
279
|
-
let { name: containerName } = answers
|
|
280
|
-
const { engine, version, port, database } = answers
|
|
281
|
-
|
|
282
|
-
console.log()
|
|
283
|
-
console.log(header('Creating Database Container'))
|
|
284
|
-
console.log()
|
|
285
|
-
|
|
286
|
-
const dbEngine = getEngine(engine)
|
|
287
|
-
|
|
288
|
-
const portAvailable = await portManager.isPortAvailable(port)
|
|
289
|
-
if (!portAvailable) {
|
|
290
|
-
console.log(
|
|
291
|
-
uiError(`Port ${port} is in use. Please choose a different port.`),
|
|
292
|
-
)
|
|
293
|
-
return null
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const binarySpinner = createSpinner(
|
|
297
|
-
`Checking ${dbEngine.displayName} ${version} binaries...`,
|
|
298
|
-
)
|
|
299
|
-
binarySpinner.start()
|
|
300
|
-
|
|
301
|
-
const isInstalled = await dbEngine.isBinaryInstalled(version)
|
|
302
|
-
if (isInstalled) {
|
|
303
|
-
binarySpinner.succeed(
|
|
304
|
-
`${dbEngine.displayName} ${version} binaries ready (cached)`,
|
|
305
|
-
)
|
|
306
|
-
} else {
|
|
307
|
-
binarySpinner.text = `Downloading ${dbEngine.displayName} ${version} binaries...`
|
|
308
|
-
await dbEngine.ensureBinaries(version, ({ message }) => {
|
|
309
|
-
binarySpinner.text = message
|
|
310
|
-
})
|
|
311
|
-
binarySpinner.succeed(
|
|
312
|
-
`${dbEngine.displayName} ${version} binaries downloaded`,
|
|
313
|
-
)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
while (await containerManager.exists(containerName)) {
|
|
317
|
-
console.log(chalk.yellow(` Container "${containerName}" already exists.`))
|
|
318
|
-
containerName = await promptContainerName()
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const createSpinnerInstance = createSpinner('Creating container...')
|
|
322
|
-
createSpinnerInstance.start()
|
|
323
|
-
|
|
324
|
-
await containerManager.create(containerName, {
|
|
325
|
-
engine: dbEngine.name as Engine,
|
|
326
|
-
version,
|
|
327
|
-
port,
|
|
328
|
-
database,
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
createSpinnerInstance.succeed('Container created')
|
|
332
|
-
|
|
333
|
-
const initSpinner = createSpinner('Initializing database cluster...')
|
|
334
|
-
initSpinner.start()
|
|
335
|
-
|
|
336
|
-
await dbEngine.initDataDir(containerName, version, {
|
|
337
|
-
superuser: defaults.superuser,
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
initSpinner.succeed('Database cluster initialized')
|
|
341
|
-
|
|
342
|
-
const startSpinner = createSpinner(`Starting ${dbEngine.displayName}...`)
|
|
343
|
-
startSpinner.start()
|
|
344
|
-
|
|
345
|
-
const config = await containerManager.getConfig(containerName)
|
|
346
|
-
if (!config) {
|
|
347
|
-
startSpinner.fail('Failed to get container config')
|
|
348
|
-
return null
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
await dbEngine.start(config)
|
|
352
|
-
await containerManager.updateConfig(containerName, { status: 'running' })
|
|
353
|
-
|
|
354
|
-
startSpinner.succeed(`${dbEngine.displayName} started`)
|
|
355
|
-
|
|
356
|
-
if (database !== 'postgres') {
|
|
357
|
-
const dbSpinner = createSpinner(`Creating database "${database}"...`)
|
|
358
|
-
dbSpinner.start()
|
|
359
|
-
|
|
360
|
-
await dbEngine.createDatabase(config, database)
|
|
361
|
-
|
|
362
|
-
dbSpinner.succeed(`Database "${database}" created`)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
console.log()
|
|
366
|
-
console.log(uiSuccess('Container ready for restore'))
|
|
367
|
-
console.log()
|
|
368
|
-
|
|
369
|
-
return { name: containerName, config }
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export async function handleRestore(): Promise<void> {
|
|
373
|
-
// Use a loop instead of recursion for "back" navigation
|
|
374
|
-
while (true) {
|
|
375
|
-
const containers = await containerManager.list()
|
|
376
|
-
const running = containers.filter((c) => c.status === 'running')
|
|
377
|
-
|
|
378
|
-
// Build filterable container choices
|
|
379
|
-
const containerChoices: FilterableChoice[] = running.map((c) => ({
|
|
380
|
-
name: `${c.name} ${chalk.gray(`(${getEngineIcon(c.engine)}${c.engine} ${c.version}, port ${c.port})`)} ${chalk.green('● running')}`,
|
|
381
|
-
value: c.name,
|
|
382
|
-
short: c.name,
|
|
383
|
-
}))
|
|
384
|
-
|
|
385
|
-
// Build footer with action and navigation options
|
|
386
|
-
const footerChoices: (FilterableChoice | inquirer.Separator)[] = [
|
|
387
|
-
new inquirer.Separator(),
|
|
388
|
-
{
|
|
389
|
-
name: `${chalk.green('➕')} Create new container`,
|
|
390
|
-
value: '__create_new__',
|
|
391
|
-
short: 'Create new',
|
|
392
|
-
},
|
|
393
|
-
new inquirer.Separator(),
|
|
394
|
-
{ name: `${chalk.blue('←')} Back`, value: BACK_VALUE },
|
|
395
|
-
{
|
|
396
|
-
name: `${chalk.blue('⌂')} Back to main menu ${chalk.gray('(esc)')}`,
|
|
397
|
-
value: MAIN_MENU_VALUE,
|
|
398
|
-
},
|
|
399
|
-
new inquirer.Separator(),
|
|
400
|
-
]
|
|
401
|
-
|
|
402
|
-
const allChoices = [...containerChoices, ...footerChoices]
|
|
403
|
-
|
|
404
|
-
const selectedContainer = await filterableListPrompt(
|
|
405
|
-
allChoices,
|
|
406
|
-
'Select container to restore to:',
|
|
407
|
-
{
|
|
408
|
-
filterableCount: containerChoices.length,
|
|
409
|
-
pageSize: getPageSize(),
|
|
410
|
-
emptyText: 'No containers match filter',
|
|
411
|
-
},
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
// Handle navigation (including escape)
|
|
415
|
-
if (
|
|
416
|
-
selectedContainer === ESCAPE_VALUE ||
|
|
417
|
-
selectedContainer === BACK_VALUE ||
|
|
418
|
-
selectedContainer === MAIN_MENU_VALUE
|
|
419
|
-
) {
|
|
420
|
-
return
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
let containerName: string
|
|
424
|
-
let config: Awaited<ReturnType<typeof containerManager.getConfig>>
|
|
425
|
-
|
|
426
|
-
if (selectedContainer === '__create_new__') {
|
|
427
|
-
const createResult = await handleCreateForRestore()
|
|
428
|
-
if (!createResult) return
|
|
429
|
-
containerName = createResult.name
|
|
430
|
-
config = createResult.config
|
|
431
|
-
} else {
|
|
432
|
-
containerName = selectedContainer
|
|
433
|
-
config = await containerManager.getConfig(containerName)
|
|
434
|
-
if (!config) {
|
|
435
|
-
console.error(uiError(`Container "${containerName}" not found`))
|
|
436
|
-
return
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const depsSpinner = createSpinner('Checking required tools...')
|
|
441
|
-
depsSpinner.start()
|
|
442
|
-
|
|
443
|
-
let missingDeps = await getMissingDependencies(config.engine)
|
|
444
|
-
if (missingDeps.length > 0) {
|
|
445
|
-
depsSpinner.warn(
|
|
446
|
-
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
const installed = await promptInstallDependencies(
|
|
450
|
-
missingDeps[0].binary,
|
|
451
|
-
config.engine,
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
if (!installed) {
|
|
455
|
-
return
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
missingDeps = await getMissingDependencies(config.engine)
|
|
459
|
-
if (missingDeps.length > 0) {
|
|
460
|
-
console.log(
|
|
461
|
-
uiError(
|
|
462
|
-
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
463
|
-
),
|
|
464
|
-
)
|
|
465
|
-
return
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
console.log(chalk.green(' ✓ All required tools are now available'))
|
|
469
|
-
console.log()
|
|
470
|
-
} else {
|
|
471
|
-
depsSpinner.succeed('Required tools available')
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// All engines now support dumpFromConnectionString
|
|
475
|
-
const restoreChoices: Array<
|
|
476
|
-
{ name: string; value: string } | inquirer.Separator
|
|
477
|
-
> = [
|
|
478
|
-
{
|
|
479
|
-
name: `${chalk.magenta('☰')} Dump file (drag and drop or enter path)`,
|
|
480
|
-
value: 'file',
|
|
481
|
-
},
|
|
482
|
-
]
|
|
483
|
-
|
|
484
|
-
restoreChoices.push({
|
|
485
|
-
name: `${chalk.cyan('↗')} Connection string ${chalk.gray('(pull from remote database)')}`,
|
|
486
|
-
value: 'connection',
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
restoreChoices.push(new inquirer.Separator(), {
|
|
490
|
-
name: `${chalk.blue('←')} Back`,
|
|
491
|
-
value: '__back__',
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
const { restoreSource } = await escapeablePrompt<{
|
|
495
|
-
restoreSource: 'file' | 'connection' | '__back__'
|
|
496
|
-
}>([
|
|
497
|
-
{
|
|
498
|
-
type: 'list',
|
|
499
|
-
name: 'restoreSource',
|
|
500
|
-
message: 'Restore from:',
|
|
501
|
-
choices: restoreChoices,
|
|
502
|
-
},
|
|
503
|
-
])
|
|
504
|
-
|
|
505
|
-
if (restoreSource === '__back__') {
|
|
506
|
-
continue // Go back to container selection
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
let backupPath = ''
|
|
510
|
-
let isTempFile = false
|
|
511
|
-
|
|
512
|
-
if (restoreSource === 'connection') {
|
|
513
|
-
const connectionString = await promptConnectionString(config.engine)
|
|
514
|
-
if (!connectionString) {
|
|
515
|
-
continue // Return to container selection
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const engine = getEngine(config.engine)
|
|
519
|
-
|
|
520
|
-
const timestamp = Date.now()
|
|
521
|
-
const defaultFormat = getDefaultFormat(config.engine as Engine)
|
|
522
|
-
const dumpExtension = getBackupExtension(
|
|
523
|
-
config.engine as Engine,
|
|
524
|
-
defaultFormat,
|
|
525
|
-
)
|
|
526
|
-
const tempDumpPath = join(
|
|
527
|
-
tmpdir(),
|
|
528
|
-
`spindb-dump-${timestamp}${dumpExtension}`,
|
|
529
|
-
)
|
|
530
|
-
|
|
531
|
-
let dumpSuccess = false
|
|
532
|
-
let attempts = 0
|
|
533
|
-
const maxAttempts = 2
|
|
534
|
-
|
|
535
|
-
while (!dumpSuccess && attempts < maxAttempts) {
|
|
536
|
-
attempts++
|
|
537
|
-
const dumpSpinner = createSpinner(
|
|
538
|
-
'Creating dump from remote database...',
|
|
539
|
-
)
|
|
540
|
-
dumpSpinner.start()
|
|
541
|
-
|
|
542
|
-
try {
|
|
543
|
-
const dumpResult = await engine.dumpFromConnectionString(
|
|
544
|
-
connectionString,
|
|
545
|
-
tempDumpPath,
|
|
546
|
-
)
|
|
547
|
-
dumpSpinner.succeed('Dump created from remote database')
|
|
548
|
-
if (dumpResult.warnings?.length) {
|
|
549
|
-
for (const warning of dumpResult.warnings) {
|
|
550
|
-
console.log(chalk.yellow(` ${warning}`))
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
backupPath = tempDumpPath
|
|
554
|
-
isTempFile = true
|
|
555
|
-
dumpSuccess = true
|
|
556
|
-
} catch (error) {
|
|
557
|
-
const e = error as Error
|
|
558
|
-
dumpSpinner.fail('Failed to create dump')
|
|
559
|
-
|
|
560
|
-
// Handle version mismatch errors with helpful message
|
|
561
|
-
if (
|
|
562
|
-
e instanceof SpinDBError &&
|
|
563
|
-
e.code === ErrorCodes.VERSION_MISMATCH
|
|
564
|
-
) {
|
|
565
|
-
console.log()
|
|
566
|
-
console.log(uiError('PostgreSQL version mismatch:'))
|
|
567
|
-
console.log(chalk.gray(` ${e.message}`))
|
|
568
|
-
if (e.suggestion) {
|
|
569
|
-
console.log()
|
|
570
|
-
console.log(uiWarning('To fix this:'))
|
|
571
|
-
console.log(chalk.yellow(` ${e.suggestion}`))
|
|
572
|
-
}
|
|
573
|
-
console.log()
|
|
574
|
-
|
|
575
|
-
try {
|
|
576
|
-
await rm(tempDumpPath, { force: true })
|
|
577
|
-
} catch {
|
|
578
|
-
// Ignore cleanup errors
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
await pressEnterToContinue()
|
|
582
|
-
return
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Handle connection errors
|
|
586
|
-
if (
|
|
587
|
-
e instanceof SpinDBError &&
|
|
588
|
-
e.code === ErrorCodes.CONNECTION_FAILED
|
|
589
|
-
) {
|
|
590
|
-
console.log()
|
|
591
|
-
console.log(uiError('Connection failed:'))
|
|
592
|
-
console.log(chalk.gray(` ${e.message}`))
|
|
593
|
-
if (e.suggestion) {
|
|
594
|
-
console.log(chalk.yellow(` ${e.suggestion}`))
|
|
595
|
-
}
|
|
596
|
-
console.log()
|
|
597
|
-
|
|
598
|
-
await pressEnterToContinue()
|
|
599
|
-
return
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// Handle missing tool errors
|
|
603
|
-
if (
|
|
604
|
-
e.message.includes('pg_dump not found') ||
|
|
605
|
-
e.message.includes('mysqldump not found') ||
|
|
606
|
-
e.message.includes('ENOENT')
|
|
607
|
-
) {
|
|
608
|
-
const missingTool = e.message.includes('mysqldump')
|
|
609
|
-
? 'mysqldump'
|
|
610
|
-
: 'pg_dump'
|
|
611
|
-
const toolEngine =
|
|
612
|
-
missingTool === 'mysqldump' ? 'mysql' : 'postgresql'
|
|
613
|
-
const installed = await promptInstallDependencies(
|
|
614
|
-
missingTool,
|
|
615
|
-
toolEngine as Engine,
|
|
616
|
-
)
|
|
617
|
-
if (installed) {
|
|
618
|
-
// Installation counts toward maxAttempts - retry with newly installed tools
|
|
619
|
-
continue
|
|
620
|
-
}
|
|
621
|
-
} else {
|
|
622
|
-
const dumpTool = config.engine === 'mysql' ? 'mysqldump' : 'pg_dump'
|
|
623
|
-
console.log()
|
|
624
|
-
console.log(uiError(`${dumpTool} error:`))
|
|
625
|
-
console.log(chalk.gray(` ${e.message}`))
|
|
626
|
-
console.log()
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
try {
|
|
630
|
-
await rm(tempDumpPath, { force: true })
|
|
631
|
-
} catch {
|
|
632
|
-
// Ignore cleanup errors
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
await pressEnterToContinue()
|
|
636
|
-
return
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
if (!dumpSuccess) {
|
|
641
|
-
console.log(uiError('Failed to create dump after retries'))
|
|
642
|
-
return
|
|
643
|
-
}
|
|
644
|
-
} else {
|
|
645
|
-
console.log(
|
|
646
|
-
chalk.gray(
|
|
647
|
-
' Drag & drop, enter path (abs or rel), or press Enter to go back (esc - main menu)',
|
|
648
|
-
),
|
|
649
|
-
)
|
|
650
|
-
const { backupPath: rawBackupPath } = await escapeablePrompt<{
|
|
651
|
-
backupPath: string
|
|
652
|
-
}>([
|
|
653
|
-
{
|
|
654
|
-
type: 'input',
|
|
655
|
-
name: 'backupPath',
|
|
656
|
-
message: 'Backup file path:',
|
|
657
|
-
validate: (input: string) => {
|
|
658
|
-
if (!input) return true
|
|
659
|
-
const cleanPath = stripQuotes(input)
|
|
660
|
-
if (!existsSync(cleanPath)) return 'File not found'
|
|
661
|
-
return true
|
|
662
|
-
},
|
|
663
|
-
},
|
|
664
|
-
])
|
|
665
|
-
|
|
666
|
-
if (!rawBackupPath.trim()) {
|
|
667
|
-
continue // Return to container selection
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
backupPath = stripQuotes(rawBackupPath)
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const engine = getEngine(config.engine)
|
|
674
|
-
|
|
675
|
-
// Get existing databases in this container
|
|
676
|
-
const existingDatabases = config.databases || [config.database]
|
|
677
|
-
|
|
678
|
-
// Redis uses numbered databases 0-15, so "create new" doesn't apply
|
|
679
|
-
const isRedis = config.engine === 'redis'
|
|
680
|
-
|
|
681
|
-
// Restore mode selection
|
|
682
|
-
type RestoreMode = 'new' | 'replace' | '__back__'
|
|
683
|
-
let restoreMode: RestoreMode
|
|
684
|
-
|
|
685
|
-
if (isRedis) {
|
|
686
|
-
// Redis: Always restore to existing database (0-15)
|
|
687
|
-
restoreMode = 'replace'
|
|
688
|
-
} else {
|
|
689
|
-
const result = await escapeablePrompt<{ restoreMode: RestoreMode }>([
|
|
690
|
-
{
|
|
691
|
-
type: 'list',
|
|
692
|
-
name: 'restoreMode',
|
|
693
|
-
message: 'How would you like to restore?',
|
|
694
|
-
choices: [
|
|
695
|
-
{
|
|
696
|
-
name: `${chalk.green('➕')} Create new database ${chalk.gray('(keeps existing databases intact)')}`,
|
|
697
|
-
value: 'new',
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
name: `${chalk.yellow('↻')} Replace existing database ${chalk.gray('(overwrites data)')}`,
|
|
701
|
-
value: 'replace',
|
|
702
|
-
disabled:
|
|
703
|
-
existingDatabases.length === 0
|
|
704
|
-
? 'No existing databases'
|
|
705
|
-
: false,
|
|
706
|
-
},
|
|
707
|
-
new inquirer.Separator(),
|
|
708
|
-
{
|
|
709
|
-
name: `${chalk.blue('←')} Back`,
|
|
710
|
-
value: '__back__',
|
|
711
|
-
},
|
|
712
|
-
],
|
|
713
|
-
},
|
|
714
|
-
])
|
|
715
|
-
restoreMode = result.restoreMode
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (restoreMode === '__back__') {
|
|
719
|
-
continue // Return to container selection
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
let databaseName: string
|
|
723
|
-
|
|
724
|
-
if (restoreMode === 'new') {
|
|
725
|
-
// Show existing databases for context
|
|
726
|
-
if (existingDatabases.length > 0) {
|
|
727
|
-
console.log()
|
|
728
|
-
console.log(chalk.gray(' Existing databases in this container:'))
|
|
729
|
-
for (const db of existingDatabases) {
|
|
730
|
-
console.log(chalk.gray(` • ${db}`))
|
|
731
|
-
}
|
|
732
|
-
console.log()
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// Prompt for new database name (must not already exist)
|
|
736
|
-
const result = await promptDatabaseName(containerName, config.engine, {
|
|
737
|
-
allowBack: true,
|
|
738
|
-
existingDatabases,
|
|
739
|
-
disallowExisting: true,
|
|
740
|
-
})
|
|
741
|
-
|
|
742
|
-
if (result === null) {
|
|
743
|
-
continue // Return to container selection
|
|
744
|
-
}
|
|
745
|
-
databaseName = result
|
|
746
|
-
} else {
|
|
747
|
-
// Replace existing database - show selection
|
|
748
|
-
if (existingDatabases.length === 1) {
|
|
749
|
-
databaseName = existingDatabases[0]
|
|
750
|
-
} else {
|
|
751
|
-
const result = await promptDatabaseSelect(
|
|
752
|
-
existingDatabases,
|
|
753
|
-
'Select database to replace:',
|
|
754
|
-
{ includeBack: true },
|
|
755
|
-
)
|
|
756
|
-
if (result === null) {
|
|
757
|
-
continue // Return to container selection
|
|
758
|
-
}
|
|
759
|
-
databaseName = result
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// Confirm overwrite
|
|
763
|
-
const confirmed = await promptConfirm(
|
|
764
|
-
`This will overwrite all data in "${databaseName}". Continue?`,
|
|
765
|
-
false,
|
|
766
|
-
)
|
|
767
|
-
|
|
768
|
-
if (!confirmed) {
|
|
769
|
-
continue // Return to container selection
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
// Redis doesn't need drop/create - databases 0-15 always exist
|
|
773
|
-
if (!isRedis) {
|
|
774
|
-
// Drop the existing database before restore
|
|
775
|
-
console.log()
|
|
776
|
-
const dropSpinner = createSpinner(
|
|
777
|
-
`Dropping existing database "${databaseName}"...`,
|
|
778
|
-
)
|
|
779
|
-
dropSpinner.start()
|
|
780
|
-
|
|
781
|
-
try {
|
|
782
|
-
await engine.dropDatabase(config, databaseName)
|
|
783
|
-
dropSpinner.succeed(`Dropped database "${databaseName}"`)
|
|
784
|
-
} catch (error) {
|
|
785
|
-
dropSpinner.fail(`Failed to drop database "${databaseName}"`)
|
|
786
|
-
console.log(uiError((error as Error).message))
|
|
787
|
-
await pressEnterToContinue()
|
|
788
|
-
return
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
const detectSpinner = createSpinner('Detecting backup format...')
|
|
794
|
-
detectSpinner.start()
|
|
795
|
-
|
|
796
|
-
const format = await engine.detectBackupFormat(backupPath)
|
|
797
|
-
detectSpinner.succeed(`Detected: ${format.description}`)
|
|
798
|
-
|
|
799
|
-
// For Redis .redis text files, ask about merge vs replace behavior
|
|
800
|
-
let flushBeforeRestore = false
|
|
801
|
-
if (isRedis && format.format === 'redis') {
|
|
802
|
-
const { restoreBehavior } = await escapeablePrompt<{
|
|
803
|
-
restoreBehavior: 'replace' | 'merge'
|
|
804
|
-
}>([
|
|
805
|
-
{
|
|
806
|
-
type: 'list',
|
|
807
|
-
name: 'restoreBehavior',
|
|
808
|
-
message: 'How should existing data be handled?',
|
|
809
|
-
choices: [
|
|
810
|
-
{
|
|
811
|
-
name: `${chalk.yellow('↻')} Replace all ${chalk.gray('(FLUSHDB - clear database first)')}`,
|
|
812
|
-
value: 'replace',
|
|
813
|
-
},
|
|
814
|
-
{
|
|
815
|
-
name: `${chalk.green('➕')} Merge ${chalk.gray('(add/update keys, keep others)')}`,
|
|
816
|
-
value: 'merge',
|
|
817
|
-
},
|
|
818
|
-
],
|
|
819
|
-
},
|
|
820
|
-
])
|
|
821
|
-
flushBeforeRestore = restoreBehavior === 'replace'
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Redis doesn't need createDatabase - databases 0-15 always exist
|
|
825
|
-
if (!isRedis) {
|
|
826
|
-
const dbSpinner = createSpinner(`Creating database "${databaseName}"...`)
|
|
827
|
-
dbSpinner.start()
|
|
828
|
-
|
|
829
|
-
await engine.createDatabase(config, databaseName)
|
|
830
|
-
dbSpinner.succeed(`Database "${databaseName}" ready`)
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
const restoreSpinner = createSpinner('Restoring backup...')
|
|
834
|
-
restoreSpinner.start()
|
|
835
|
-
|
|
836
|
-
const result = await engine.restore(config, backupPath, {
|
|
837
|
-
database: databaseName,
|
|
838
|
-
createDatabase: false,
|
|
839
|
-
flush: flushBeforeRestore,
|
|
840
|
-
})
|
|
841
|
-
|
|
842
|
-
if (result.code === 0) {
|
|
843
|
-
restoreSpinner.succeed('Backup restored successfully')
|
|
844
|
-
} else {
|
|
845
|
-
const stderr = result.stderr || ''
|
|
846
|
-
|
|
847
|
-
if (
|
|
848
|
-
stderr.includes('unsupported version') ||
|
|
849
|
-
stderr.includes('Archive version') ||
|
|
850
|
-
stderr.includes('too old')
|
|
851
|
-
) {
|
|
852
|
-
restoreSpinner.fail('Version compatibility detected')
|
|
853
|
-
console.log()
|
|
854
|
-
console.log(uiError('PostgreSQL version incompatibility detected:'))
|
|
855
|
-
console.log(
|
|
856
|
-
uiWarning('Your pg_restore version is too old for this backup file.'),
|
|
857
|
-
)
|
|
858
|
-
|
|
859
|
-
console.log(chalk.yellow('Cleaning up failed database...'))
|
|
860
|
-
try {
|
|
861
|
-
await engine.dropDatabase(config, databaseName)
|
|
862
|
-
console.log(chalk.gray(`✓ Removed database "${databaseName}"`))
|
|
863
|
-
} catch {
|
|
864
|
-
console.log(
|
|
865
|
-
chalk.yellow(
|
|
866
|
-
`Warning: Could not remove database "${databaseName}"`,
|
|
867
|
-
),
|
|
868
|
-
)
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
console.log()
|
|
872
|
-
|
|
873
|
-
const versionMatch = stderr.match(/PostgreSQL (\d+)/)
|
|
874
|
-
const requiredVersion = versionMatch ? versionMatch[1] : '17'
|
|
875
|
-
|
|
876
|
-
console.log(
|
|
877
|
-
chalk.gray(
|
|
878
|
-
`This backup was created with PostgreSQL ${requiredVersion}`,
|
|
879
|
-
),
|
|
880
|
-
)
|
|
881
|
-
console.log()
|
|
882
|
-
|
|
883
|
-
console.log()
|
|
884
|
-
console.log(
|
|
885
|
-
uiWarning(
|
|
886
|
-
`To restore this backup, download PostgreSQL ${requiredVersion} binaries:`,
|
|
887
|
-
),
|
|
888
|
-
)
|
|
889
|
-
console.log(
|
|
890
|
-
chalk.cyan(` spindb engines download postgresql ${requiredVersion}`),
|
|
891
|
-
)
|
|
892
|
-
console.log()
|
|
893
|
-
console.log(
|
|
894
|
-
chalk.gray(
|
|
895
|
-
'Then create a new container with that version and try the restore again.',
|
|
896
|
-
),
|
|
897
|
-
)
|
|
898
|
-
await pressEnterToContinue()
|
|
899
|
-
return
|
|
900
|
-
} else {
|
|
901
|
-
// Other restore errors - show warnings
|
|
902
|
-
restoreSpinner.warn('Restore completed with warnings')
|
|
903
|
-
if (result.stderr) {
|
|
904
|
-
console.log()
|
|
905
|
-
console.log(chalk.yellow(' Warnings/Errors:'))
|
|
906
|
-
const lines = result.stderr.split('\n').filter((l) => l.trim())
|
|
907
|
-
const displayLines = lines.slice(0, 20)
|
|
908
|
-
for (const line of displayLines) {
|
|
909
|
-
console.log(chalk.gray(` ${line}`))
|
|
910
|
-
}
|
|
911
|
-
if (lines.length > 20) {
|
|
912
|
-
console.log(chalk.gray(` ... and ${lines.length - 20} more lines`))
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (result.code === 0) {
|
|
919
|
-
const connectionString = engine.getConnectionString(config, databaseName)
|
|
920
|
-
console.log()
|
|
921
|
-
console.log(uiSuccess(`Database "${databaseName}" restored`))
|
|
922
|
-
console.log(chalk.gray(' Connection string:'))
|
|
923
|
-
console.log(chalk.cyan(` ${connectionString}`))
|
|
924
|
-
|
|
925
|
-
const copied = await platformService.copyToClipboard(connectionString)
|
|
926
|
-
if (copied) {
|
|
927
|
-
console.log(chalk.gray(' ✓ Connection string copied to clipboard'))
|
|
928
|
-
} else {
|
|
929
|
-
console.log(chalk.gray(' (Could not copy to clipboard)'))
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
console.log()
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
if (isTempFile) {
|
|
936
|
-
try {
|
|
937
|
-
await rm(backupPath, { force: true })
|
|
938
|
-
} catch {
|
|
939
|
-
// Ignore cleanup errors
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
await pressEnterToContinue()
|
|
944
|
-
|
|
945
|
-
return // Exit the wizard loop after successful restore
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
/**
|
|
950
|
-
* Shared backup flow for both main menu and container submenu
|
|
951
|
-
* Reduces code duplication between handleBackup and handleBackupForContainer
|
|
952
|
-
*
|
|
953
|
-
* @param containerName - The container to backup
|
|
954
|
-
* @param database - Optional database name (skips database selection if provided)
|
|
955
|
-
*/
|
|
956
|
-
async function performBackupFlow(
|
|
957
|
-
containerName: string,
|
|
958
|
-
database?: string,
|
|
959
|
-
): Promise<void> {
|
|
960
|
-
const config = await containerManager.getConfig(containerName)
|
|
961
|
-
if (!config) {
|
|
962
|
-
console.log(uiError(`Container "${containerName}" not found`))
|
|
963
|
-
return
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
const engine = getEngine(config.engine)
|
|
967
|
-
|
|
968
|
-
// Check dependencies
|
|
969
|
-
const depsSpinner = createSpinner('Checking required tools...')
|
|
970
|
-
depsSpinner.start()
|
|
971
|
-
|
|
972
|
-
let missingDeps = await getMissingDependencies(config.engine)
|
|
973
|
-
if (missingDeps.length > 0) {
|
|
974
|
-
depsSpinner.warn(
|
|
975
|
-
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
976
|
-
)
|
|
977
|
-
|
|
978
|
-
const installed = await promptInstallDependencies(
|
|
979
|
-
missingDeps[0].binary,
|
|
980
|
-
config.engine,
|
|
981
|
-
)
|
|
982
|
-
|
|
983
|
-
if (!installed) {
|
|
984
|
-
return
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
missingDeps = await getMissingDependencies(config.engine)
|
|
988
|
-
if (missingDeps.length > 0) {
|
|
989
|
-
console.log(
|
|
990
|
-
uiError(
|
|
991
|
-
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
992
|
-
),
|
|
993
|
-
)
|
|
994
|
-
return
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
console.log(chalk.green(' ✓ All required tools are now available'))
|
|
998
|
-
console.log()
|
|
999
|
-
} else {
|
|
1000
|
-
depsSpinner.succeed('Required tools available')
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
// Use provided database or select from available databases
|
|
1004
|
-
const databases = config.databases || [config.database]
|
|
1005
|
-
let databaseName: string
|
|
1006
|
-
|
|
1007
|
-
if (database) {
|
|
1008
|
-
// Use the pre-selected database from the container submenu
|
|
1009
|
-
databaseName = database
|
|
1010
|
-
} else if (databases.length > 1) {
|
|
1011
|
-
databaseName = await promptDatabaseSelect(
|
|
1012
|
-
databases,
|
|
1013
|
-
'Select database to backup:',
|
|
1014
|
-
)
|
|
1015
|
-
} else {
|
|
1016
|
-
databaseName = databases[0]
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
// Show estimated size
|
|
1020
|
-
const estimatedSize = await estimateBackupSize(config)
|
|
1021
|
-
if (estimatedSize !== null) {
|
|
1022
|
-
console.log(
|
|
1023
|
-
chalk.gray(` Estimated database size: ${formatBytes(estimatedSize)}`),
|
|
1024
|
-
)
|
|
1025
|
-
console.log()
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// Select format
|
|
1029
|
-
const format = await promptBackupFormat(config.engine)
|
|
1030
|
-
|
|
1031
|
-
// Select output directory
|
|
1032
|
-
const outputDir = await promptBackupDirectory()
|
|
1033
|
-
if (!outputDir) return
|
|
1034
|
-
|
|
1035
|
-
// Ensure directory exists
|
|
1036
|
-
if (!existsSync(outputDir)) {
|
|
1037
|
-
await mkdir(outputDir, { recursive: true })
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
// Get filename
|
|
1041
|
-
const defaultFilename = `${containerName}-${databaseName}-backup-${generateBackupTimestamp()}`
|
|
1042
|
-
const filename = await promptBackupFilename(defaultFilename)
|
|
1043
|
-
|
|
1044
|
-
const extension = getBackupExtension(config.engine, format)
|
|
1045
|
-
const outputPath = join(outputDir, `${filename}${extension}`)
|
|
1046
|
-
|
|
1047
|
-
const spinnerLabel = getBackupSpinnerLabel(config.engine, format)
|
|
1048
|
-
const backupSpinner = createSpinner(
|
|
1049
|
-
`Creating ${spinnerLabel} backup of "${databaseName}"...`,
|
|
1050
|
-
)
|
|
1051
|
-
backupSpinner.start()
|
|
1052
|
-
|
|
1053
|
-
try {
|
|
1054
|
-
const result = await engine.backup(config, outputPath, {
|
|
1055
|
-
database: databaseName,
|
|
1056
|
-
format,
|
|
1057
|
-
})
|
|
1058
|
-
|
|
1059
|
-
backupSpinner.succeed('Backup created successfully')
|
|
1060
|
-
|
|
1061
|
-
console.log()
|
|
1062
|
-
console.log(uiSuccess('Backup complete'))
|
|
1063
|
-
console.log()
|
|
1064
|
-
console.log(chalk.gray(' Saved to:'), chalk.cyan(result.path))
|
|
1065
|
-
console.log(chalk.gray(' Size:'), chalk.white(formatBytes(result.size)))
|
|
1066
|
-
console.log(chalk.gray(' Format:'), chalk.white(result.format))
|
|
1067
|
-
console.log()
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
const e = error as Error
|
|
1070
|
-
backupSpinner.fail('Backup failed')
|
|
1071
|
-
console.log()
|
|
1072
|
-
console.log(uiError(e.message))
|
|
1073
|
-
console.log()
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
export async function handleBackup(): Promise<void> {
|
|
1078
|
-
const containers = await containerManager.list()
|
|
1079
|
-
const running = containers.filter((c) => c.status === 'running')
|
|
1080
|
-
|
|
1081
|
-
if (running.length === 0) {
|
|
1082
|
-
console.log(uiWarning('No running containers. Start a container first.'))
|
|
1083
|
-
await pressEnterToContinue()
|
|
1084
|
-
return
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
const containerName = await promptContainerSelect(
|
|
1088
|
-
running,
|
|
1089
|
-
'Select container to backup:',
|
|
1090
|
-
{ includeBack: true },
|
|
1091
|
-
)
|
|
1092
|
-
if (!containerName) return
|
|
1093
|
-
|
|
1094
|
-
await performBackupFlow(containerName)
|
|
1095
|
-
await pressEnterToContinue()
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
/**
|
|
1099
|
-
* Handle backup for a specific container (used from container submenu)
|
|
1100
|
-
* Skips container selection since we already know which container
|
|
1101
|
-
*/
|
|
1102
|
-
export async function handleBackupForContainer(
|
|
1103
|
-
containerName: string,
|
|
1104
|
-
database?: string,
|
|
1105
|
-
): Promise<void> {
|
|
1106
|
-
await performBackupFlow(containerName, database)
|
|
1107
|
-
await pressEnterToContinue()
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
/**
|
|
1111
|
-
* Handle restore for a specific container (used from container submenu)
|
|
1112
|
-
* Skips container selection since we already know which container
|
|
1113
|
-
*
|
|
1114
|
-
* @param containerName - The container to restore to
|
|
1115
|
-
* @param database - Optional database name (pre-selects target database if provided)
|
|
1116
|
-
*/
|
|
1117
|
-
export async function handleRestoreForContainer(
|
|
1118
|
-
containerName: string,
|
|
1119
|
-
database?: string,
|
|
1120
|
-
): Promise<void> {
|
|
1121
|
-
const config = await containerManager.getConfig(containerName)
|
|
1122
|
-
if (!config) {
|
|
1123
|
-
console.log(uiError(`Container "${containerName}" not found`))
|
|
1124
|
-
await pressEnterToContinue()
|
|
1125
|
-
return
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
const engine = getEngine(config.engine)
|
|
1129
|
-
|
|
1130
|
-
// Check dependencies
|
|
1131
|
-
const depsSpinner = createSpinner('Checking required tools...')
|
|
1132
|
-
depsSpinner.start()
|
|
1133
|
-
|
|
1134
|
-
let missingDeps = await getMissingDependencies(config.engine)
|
|
1135
|
-
if (missingDeps.length > 0) {
|
|
1136
|
-
depsSpinner.warn(
|
|
1137
|
-
`Missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
1138
|
-
)
|
|
1139
|
-
|
|
1140
|
-
const installed = await promptInstallDependencies(
|
|
1141
|
-
missingDeps[0].binary,
|
|
1142
|
-
config.engine,
|
|
1143
|
-
)
|
|
1144
|
-
|
|
1145
|
-
if (!installed) {
|
|
1146
|
-
return
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
missingDeps = await getMissingDependencies(config.engine)
|
|
1150
|
-
if (missingDeps.length > 0) {
|
|
1151
|
-
console.log(
|
|
1152
|
-
uiError(
|
|
1153
|
-
`Still missing tools: ${missingDeps.map((d) => d.name).join(', ')}`,
|
|
1154
|
-
),
|
|
1155
|
-
)
|
|
1156
|
-
return
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
console.log(chalk.green(' ✓ All required tools are now available'))
|
|
1160
|
-
console.log()
|
|
1161
|
-
} else {
|
|
1162
|
-
depsSpinner.succeed('Required tools available')
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
// Restore source selection (file or connection string)
|
|
1166
|
-
// All engines now support dumpFromConnectionString
|
|
1167
|
-
const restoreChoices: Array<
|
|
1168
|
-
{ name: string; value: string } | inquirer.Separator
|
|
1169
|
-
> = [
|
|
1170
|
-
{
|
|
1171
|
-
name: `${chalk.magenta('☰')} Dump file (drag and drop or enter path)`,
|
|
1172
|
-
value: 'file',
|
|
1173
|
-
},
|
|
1174
|
-
{
|
|
1175
|
-
name: `${chalk.cyan('↗')} Connection string ${chalk.gray('(pull from remote database)')}`,
|
|
1176
|
-
value: 'connection',
|
|
1177
|
-
},
|
|
1178
|
-
]
|
|
1179
|
-
|
|
1180
|
-
restoreChoices.push(new inquirer.Separator(), {
|
|
1181
|
-
name: `${chalk.blue('←')} Back`,
|
|
1182
|
-
value: '__back__',
|
|
1183
|
-
})
|
|
1184
|
-
|
|
1185
|
-
const { restoreSource } = await escapeablePrompt<{
|
|
1186
|
-
restoreSource: 'file' | 'connection' | '__back__'
|
|
1187
|
-
}>([
|
|
1188
|
-
{
|
|
1189
|
-
type: 'list',
|
|
1190
|
-
name: 'restoreSource',
|
|
1191
|
-
message: 'Restore from:',
|
|
1192
|
-
choices: restoreChoices,
|
|
1193
|
-
},
|
|
1194
|
-
])
|
|
1195
|
-
|
|
1196
|
-
if (restoreSource === '__back__') {
|
|
1197
|
-
return
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
let backupPath = ''
|
|
1201
|
-
let isTempFile = false
|
|
1202
|
-
|
|
1203
|
-
if (restoreSource === 'connection') {
|
|
1204
|
-
const connectionString = await promptConnectionString(config.engine)
|
|
1205
|
-
if (!connectionString) {
|
|
1206
|
-
return
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
const timestamp = Date.now()
|
|
1210
|
-
const defaultFormat = getDefaultFormat(config.engine as Engine)
|
|
1211
|
-
const dumpExtension = getBackupExtension(
|
|
1212
|
-
config.engine as Engine,
|
|
1213
|
-
defaultFormat,
|
|
1214
|
-
)
|
|
1215
|
-
const tempDumpPath = join(
|
|
1216
|
-
tmpdir(),
|
|
1217
|
-
`spindb-dump-${timestamp}${dumpExtension}`,
|
|
1218
|
-
)
|
|
1219
|
-
|
|
1220
|
-
const dumpSpinner = createSpinner('Creating dump from remote database...')
|
|
1221
|
-
dumpSpinner.start()
|
|
1222
|
-
|
|
1223
|
-
try {
|
|
1224
|
-
const dumpResult = await engine.dumpFromConnectionString(
|
|
1225
|
-
connectionString,
|
|
1226
|
-
tempDumpPath,
|
|
1227
|
-
)
|
|
1228
|
-
dumpSpinner.succeed('Dump created from remote database')
|
|
1229
|
-
if (dumpResult.warnings?.length) {
|
|
1230
|
-
for (const warning of dumpResult.warnings) {
|
|
1231
|
-
console.log(chalk.yellow(` ${warning}`))
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
backupPath = tempDumpPath
|
|
1235
|
-
isTempFile = true
|
|
1236
|
-
} catch (error) {
|
|
1237
|
-
const e = error as Error
|
|
1238
|
-
dumpSpinner.fail('Failed to create dump')
|
|
1239
|
-
console.log(uiError(e.message))
|
|
1240
|
-
await pressEnterToContinue()
|
|
1241
|
-
return
|
|
1242
|
-
}
|
|
1243
|
-
} else {
|
|
1244
|
-
// Handle file restore
|
|
1245
|
-
console.log(
|
|
1246
|
-
chalk.gray(
|
|
1247
|
-
' Drag & drop, enter path (abs or rel), or press Enter to go back (esc - main menu)',
|
|
1248
|
-
),
|
|
1249
|
-
)
|
|
1250
|
-
const { backupPath: rawBackupPath } = await escapeablePrompt<{
|
|
1251
|
-
backupPath: string
|
|
1252
|
-
}>([
|
|
1253
|
-
{
|
|
1254
|
-
type: 'input',
|
|
1255
|
-
name: 'backupPath',
|
|
1256
|
-
message: 'Backup file path:',
|
|
1257
|
-
validate: (input: string) => {
|
|
1258
|
-
if (!input) return true
|
|
1259
|
-
const cleanPath = stripQuotes(input)
|
|
1260
|
-
if (!existsSync(cleanPath)) return 'File not found'
|
|
1261
|
-
return true
|
|
1262
|
-
},
|
|
1263
|
-
},
|
|
1264
|
-
])
|
|
1265
|
-
|
|
1266
|
-
if (!rawBackupPath.trim()) {
|
|
1267
|
-
return
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
backupPath = stripQuotes(rawBackupPath)
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// Check backup file size and warn if large
|
|
1274
|
-
const sizeCheck = checkBackupSize(backupPath)
|
|
1275
|
-
if (sizeCheck.level === 'very_large') {
|
|
1276
|
-
console.log()
|
|
1277
|
-
console.log(
|
|
1278
|
-
chalk.yellow(` ⚠ Large backup file: ${formatBytes(sizeCheck.size)}`),
|
|
1279
|
-
)
|
|
1280
|
-
console.log(chalk.gray(' This restore may take a while.'))
|
|
1281
|
-
console.log()
|
|
1282
|
-
const confirmed = await promptConfirm('Continue with restore?', true)
|
|
1283
|
-
if (!confirmed) {
|
|
1284
|
-
if (isTempFile) {
|
|
1285
|
-
await rm(backupPath, { force: true }).catch(() => {})
|
|
1286
|
-
}
|
|
1287
|
-
return
|
|
1288
|
-
}
|
|
1289
|
-
} else if (sizeCheck.level === 'large') {
|
|
1290
|
-
console.log(
|
|
1291
|
-
chalk.gray(` Backup file size: ${formatBytes(sizeCheck.size)}`),
|
|
1292
|
-
)
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// Detect backup format
|
|
1296
|
-
const format = await engine.detectBackupFormat(backupPath)
|
|
1297
|
-
console.log(chalk.gray(` Detected format: ${format.description}`))
|
|
1298
|
-
console.log()
|
|
1299
|
-
|
|
1300
|
-
// Get existing databases in this container
|
|
1301
|
-
const existingDatabases = config.databases || [config.database]
|
|
1302
|
-
|
|
1303
|
-
// Redis uses numbered databases 0-15, so "create new" doesn't apply
|
|
1304
|
-
const isRedis = config.engine === 'redis'
|
|
1305
|
-
|
|
1306
|
-
// Restore mode selection
|
|
1307
|
-
type RestoreMode = 'new' | 'replace' | '__back__'
|
|
1308
|
-
let restoreMode: RestoreMode
|
|
1309
|
-
|
|
1310
|
-
if (isRedis) {
|
|
1311
|
-
// Redis: Always restore to existing database (0-15)
|
|
1312
|
-
restoreMode = 'replace'
|
|
1313
|
-
} else {
|
|
1314
|
-
const result = await escapeablePrompt<{ restoreMode: RestoreMode }>([
|
|
1315
|
-
{
|
|
1316
|
-
type: 'list',
|
|
1317
|
-
name: 'restoreMode',
|
|
1318
|
-
message: 'How would you like to restore?',
|
|
1319
|
-
choices: [
|
|
1320
|
-
{
|
|
1321
|
-
name: `${chalk.green('➕')} Create new database ${chalk.gray('(keeps existing databases intact)')}`,
|
|
1322
|
-
value: 'new',
|
|
1323
|
-
},
|
|
1324
|
-
{
|
|
1325
|
-
name: `${chalk.yellow('↻')} Replace existing database ${chalk.gray('(overwrites data)')}`,
|
|
1326
|
-
value: 'replace',
|
|
1327
|
-
disabled:
|
|
1328
|
-
existingDatabases.length === 0 ? 'No existing databases' : false,
|
|
1329
|
-
},
|
|
1330
|
-
new inquirer.Separator(),
|
|
1331
|
-
{
|
|
1332
|
-
name: `${chalk.blue('←')} Back`,
|
|
1333
|
-
value: '__back__',
|
|
1334
|
-
},
|
|
1335
|
-
],
|
|
1336
|
-
},
|
|
1337
|
-
])
|
|
1338
|
-
restoreMode = result.restoreMode
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
if (restoreMode === '__back__') {
|
|
1342
|
-
if (isTempFile) {
|
|
1343
|
-
await rm(backupPath, { force: true }).catch(() => {})
|
|
1344
|
-
}
|
|
1345
|
-
return
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
let databaseName: string
|
|
1349
|
-
|
|
1350
|
-
if (restoreMode === 'new') {
|
|
1351
|
-
// Show existing databases for context
|
|
1352
|
-
if (existingDatabases.length > 0) {
|
|
1353
|
-
console.log()
|
|
1354
|
-
console.log(chalk.gray(' Existing databases in this container:'))
|
|
1355
|
-
for (const db of existingDatabases) {
|
|
1356
|
-
console.log(chalk.gray(` • ${db}`))
|
|
1357
|
-
}
|
|
1358
|
-
console.log()
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
// Prompt for new database name
|
|
1362
|
-
const result = await promptDatabaseName(containerName, config.engine, {
|
|
1363
|
-
existingDatabases,
|
|
1364
|
-
})
|
|
1365
|
-
if (!result) {
|
|
1366
|
-
if (isTempFile) {
|
|
1367
|
-
await rm(backupPath, { force: true }).catch(() => {})
|
|
1368
|
-
}
|
|
1369
|
-
return
|
|
1370
|
-
}
|
|
1371
|
-
databaseName = result
|
|
1372
|
-
|
|
1373
|
-
// Create the new database
|
|
1374
|
-
const createDbSpinner = createSpinner(
|
|
1375
|
-
`Creating database "${databaseName}"...`,
|
|
1376
|
-
)
|
|
1377
|
-
createDbSpinner.start()
|
|
1378
|
-
try {
|
|
1379
|
-
await engine.createDatabase(config, databaseName)
|
|
1380
|
-
createDbSpinner.succeed(`Database "${databaseName}" created`)
|
|
1381
|
-
|
|
1382
|
-
// Update container config with new database
|
|
1383
|
-
const updatedDbs = [...existingDatabases, databaseName]
|
|
1384
|
-
await containerManager.updateConfig(containerName, {
|
|
1385
|
-
databases: updatedDbs,
|
|
1386
|
-
})
|
|
1387
|
-
} catch (error) {
|
|
1388
|
-
const e = error as Error
|
|
1389
|
-
createDbSpinner.fail('Failed to create database')
|
|
1390
|
-
console.log(uiError(e.message))
|
|
1391
|
-
if (isTempFile) {
|
|
1392
|
-
await rm(backupPath, { force: true }).catch(() => {})
|
|
1393
|
-
}
|
|
1394
|
-
await pressEnterToContinue()
|
|
1395
|
-
return
|
|
1396
|
-
}
|
|
1397
|
-
} else {
|
|
1398
|
-
// Replace existing database - use pre-selected or auto-select if only one
|
|
1399
|
-
if (database) {
|
|
1400
|
-
// Use the pre-selected database from the container submenu
|
|
1401
|
-
databaseName = database
|
|
1402
|
-
console.log(chalk.gray(` Target database: ${databaseName}`))
|
|
1403
|
-
} else if (existingDatabases.length === 1) {
|
|
1404
|
-
databaseName = existingDatabases[0]
|
|
1405
|
-
console.log(chalk.gray(` Using database: ${databaseName}`))
|
|
1406
|
-
} else {
|
|
1407
|
-
const { database: selectedDb } = await escapeablePrompt<{
|
|
1408
|
-
database: string
|
|
1409
|
-
}>([
|
|
1410
|
-
{
|
|
1411
|
-
type: 'list',
|
|
1412
|
-
name: 'database',
|
|
1413
|
-
message: 'Select database to replace:',
|
|
1414
|
-
choices: existingDatabases.map((db) => ({ name: db, value: db })),
|
|
1415
|
-
},
|
|
1416
|
-
])
|
|
1417
|
-
databaseName = selectedDb
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
// For Redis .redis text files, ask about merge vs replace behavior
|
|
1422
|
-
let flushBeforeRestore = false
|
|
1423
|
-
if (isRedis && format.format === 'redis') {
|
|
1424
|
-
const { restoreBehavior } = await escapeablePrompt<{
|
|
1425
|
-
restoreBehavior: 'replace' | 'merge'
|
|
1426
|
-
}>([
|
|
1427
|
-
{
|
|
1428
|
-
type: 'list',
|
|
1429
|
-
name: 'restoreBehavior',
|
|
1430
|
-
message: 'How should existing data be handled?',
|
|
1431
|
-
choices: [
|
|
1432
|
-
{
|
|
1433
|
-
name: `${chalk.yellow('↻')} Replace all ${chalk.gray('(FLUSHDB - clear database first)')}`,
|
|
1434
|
-
value: 'replace',
|
|
1435
|
-
},
|
|
1436
|
-
{
|
|
1437
|
-
name: `${chalk.green('➕')} Merge ${chalk.gray('(add/update keys, keep others)')}`,
|
|
1438
|
-
value: 'merge',
|
|
1439
|
-
},
|
|
1440
|
-
],
|
|
1441
|
-
},
|
|
1442
|
-
])
|
|
1443
|
-
flushBeforeRestore = restoreBehavior === 'replace'
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
// Perform restore
|
|
1447
|
-
const restoreSpinner = createSpinner(
|
|
1448
|
-
`Restoring to "${databaseName}" in ${containerName}...`,
|
|
1449
|
-
)
|
|
1450
|
-
restoreSpinner.start()
|
|
1451
|
-
|
|
1452
|
-
try {
|
|
1453
|
-
const result = await engine.restore(config, backupPath, {
|
|
1454
|
-
database: databaseName,
|
|
1455
|
-
flush: flushBeforeRestore,
|
|
1456
|
-
})
|
|
1457
|
-
|
|
1458
|
-
if (result.code === 0) {
|
|
1459
|
-
restoreSpinner.succeed('Restore completed successfully')
|
|
1460
|
-
|
|
1461
|
-
const connectionString = engine.getConnectionString(config, databaseName)
|
|
1462
|
-
console.log()
|
|
1463
|
-
console.log(uiSuccess(`Database "${databaseName}" restored`))
|
|
1464
|
-
console.log(chalk.gray(' Connection string:'))
|
|
1465
|
-
console.log(chalk.cyan(` ${connectionString}`))
|
|
1466
|
-
|
|
1467
|
-
const copied = await platformService.copyToClipboard(connectionString)
|
|
1468
|
-
if (copied) {
|
|
1469
|
-
console.log(chalk.gray(' ✓ Connection string copied to clipboard'))
|
|
1470
|
-
}
|
|
1471
|
-
console.log()
|
|
1472
|
-
} else {
|
|
1473
|
-
restoreSpinner.warn('Restore completed with warnings')
|
|
1474
|
-
if (result.stderr) {
|
|
1475
|
-
console.log()
|
|
1476
|
-
console.log(chalk.yellow(' Warnings/Errors:'))
|
|
1477
|
-
const lines = result.stderr.split('\n').filter((l) => l.trim())
|
|
1478
|
-
const displayLines = lines.slice(0, 10)
|
|
1479
|
-
for (const line of displayLines) {
|
|
1480
|
-
console.log(chalk.gray(` ${line}`))
|
|
1481
|
-
}
|
|
1482
|
-
if (lines.length > 10) {
|
|
1483
|
-
console.log(chalk.gray(` ... and ${lines.length - 10} more lines`))
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
} catch (error) {
|
|
1488
|
-
const e = error as Error
|
|
1489
|
-
restoreSpinner.fail('Restore failed')
|
|
1490
|
-
console.log()
|
|
1491
|
-
console.log(uiError(e.message))
|
|
1492
|
-
console.log()
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
await pressEnterToContinue()
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
export async function handleClone(): Promise<void> {
|
|
1499
|
-
const containers = await containerManager.list()
|
|
1500
|
-
const stopped = containers.filter((c) => c.status !== 'running')
|
|
1501
|
-
|
|
1502
|
-
if (containers.length === 0) {
|
|
1503
|
-
console.log(uiWarning('No containers found'))
|
|
1504
|
-
return
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
if (stopped.length === 0) {
|
|
1508
|
-
console.log(
|
|
1509
|
-
uiWarning(
|
|
1510
|
-
'All containers are running. Stop a container first to clone it.',
|
|
1511
|
-
),
|
|
1512
|
-
)
|
|
1513
|
-
return
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
const sourceName = await promptContainerSelect(
|
|
1517
|
-
stopped,
|
|
1518
|
-
'Select container to clone:',
|
|
1519
|
-
{ includeBack: true },
|
|
1520
|
-
)
|
|
1521
|
-
if (!sourceName) return
|
|
1522
|
-
|
|
1523
|
-
const sourceConfig = await containerManager.getConfig(sourceName)
|
|
1524
|
-
if (!sourceConfig) {
|
|
1525
|
-
console.log(uiError(`Container "${sourceName}" not found`))
|
|
1526
|
-
return
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
const { targetName } = await escapeablePrompt<{ targetName: string }>([
|
|
1530
|
-
{
|
|
1531
|
-
type: 'input',
|
|
1532
|
-
name: 'targetName',
|
|
1533
|
-
message: 'Name for the cloned container:',
|
|
1534
|
-
default: `${sourceName}-copy`,
|
|
1535
|
-
validate: (input: string) => {
|
|
1536
|
-
if (!input) return 'Name is required'
|
|
1537
|
-
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(input)) {
|
|
1538
|
-
return 'Name must start with a letter and contain only letters, numbers, hyphens, and underscores'
|
|
1539
|
-
}
|
|
1540
|
-
return true
|
|
1541
|
-
},
|
|
1542
|
-
},
|
|
1543
|
-
])
|
|
1544
|
-
|
|
1545
|
-
// Check if target container already exists
|
|
1546
|
-
if (
|
|
1547
|
-
await containerManager.exists(targetName, { engine: sourceConfig.engine })
|
|
1548
|
-
) {
|
|
1549
|
-
console.log(uiError(`Container "${targetName}" already exists`))
|
|
1550
|
-
return
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
const spinner = createSpinner(`Cloning ${sourceName} to ${targetName}...`)
|
|
1554
|
-
spinner.start()
|
|
1555
|
-
|
|
1556
|
-
try {
|
|
1557
|
-
const newConfig = await containerManager.clone(sourceName, targetName)
|
|
1558
|
-
|
|
1559
|
-
spinner.succeed(`Cloned "${sourceName}" to "${targetName}"`)
|
|
1560
|
-
|
|
1561
|
-
const engine = getEngine(newConfig.engine)
|
|
1562
|
-
const connectionString = engine.getConnectionString(newConfig)
|
|
1563
|
-
|
|
1564
|
-
console.log()
|
|
1565
|
-
console.log(connectionBox(targetName, connectionString, newConfig.port))
|
|
1566
|
-
} catch (error) {
|
|
1567
|
-
const e = error as Error
|
|
1568
|
-
spinner.fail(`Failed to clone "${sourceName}"`)
|
|
1569
|
-
console.log(uiError(e.message))
|
|
1570
|
-
}
|
|
1571
|
-
}
|