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
package/engines/redis/index.ts
DELETED
|
@@ -1,1583 +0,0 @@
|
|
|
1
|
-
import { spawn, exec, type SpawnOptions } from 'child_process'
|
|
2
|
-
import { promisify } from 'util'
|
|
3
|
-
import { existsSync } from 'fs'
|
|
4
|
-
import { mkdir, writeFile, readFile, unlink } from 'fs/promises'
|
|
5
|
-
import { join } from 'path'
|
|
6
|
-
import { BaseEngine } from '../base-engine'
|
|
7
|
-
import { paths } from '../../config/paths'
|
|
8
|
-
import { getEngineDefaults } from '../../config/defaults'
|
|
9
|
-
import { platformService, isWindows } from '../../core/platform-service'
|
|
10
|
-
import { configManager } from '../../core/config-manager'
|
|
11
|
-
import {
|
|
12
|
-
logDebug,
|
|
13
|
-
logWarning,
|
|
14
|
-
assertValidUsername,
|
|
15
|
-
} from '../../core/error-handler'
|
|
16
|
-
import { processManager } from '../../core/process-manager'
|
|
17
|
-
import { redisBinaryManager } from './binary-manager'
|
|
18
|
-
import { getBinaryUrl } from './binary-urls'
|
|
19
|
-
import {
|
|
20
|
-
normalizeVersion,
|
|
21
|
-
SUPPORTED_MAJOR_VERSIONS,
|
|
22
|
-
REDIS_VERSION_MAP,
|
|
23
|
-
} from './version-maps'
|
|
24
|
-
import { fetchAvailableVersions as fetchHostdbVersions } from './hostdb-releases'
|
|
25
|
-
import {
|
|
26
|
-
detectBackupFormat as detectBackupFormatImpl,
|
|
27
|
-
restoreBackup,
|
|
28
|
-
} from './restore'
|
|
29
|
-
import { createBackup } from './backup'
|
|
30
|
-
import { getRedisCliPath, REDIS_CLI_NOT_FOUND_ERROR } from './cli-utils'
|
|
31
|
-
import {
|
|
32
|
-
type Platform,
|
|
33
|
-
type Arch,
|
|
34
|
-
type ContainerConfig,
|
|
35
|
-
type ProgressCallback,
|
|
36
|
-
type BackupFormat,
|
|
37
|
-
type BackupOptions,
|
|
38
|
-
type BackupResult,
|
|
39
|
-
type RestoreResult,
|
|
40
|
-
type DumpResult,
|
|
41
|
-
type StatusResult,
|
|
42
|
-
type QueryResult,
|
|
43
|
-
type QueryOptions,
|
|
44
|
-
type CreateUserOptions,
|
|
45
|
-
type UserCredentials,
|
|
46
|
-
} from '../../types'
|
|
47
|
-
import { parseRedisResult } from '../../core/query-parser'
|
|
48
|
-
import { getLibraryEnv, detectLibraryError } from '../../core/library-env'
|
|
49
|
-
|
|
50
|
-
const execAsync = promisify(exec)
|
|
51
|
-
|
|
52
|
-
const ENGINE = 'redis'
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Escape a Redis key for use in CLI commands.
|
|
56
|
-
* Escapes backslashes, double quotes, and control characters to prevent
|
|
57
|
-
* command injection and ensure keys are parsed correctly by the CLI.
|
|
58
|
-
*/
|
|
59
|
-
function escapeKeyForCommand(key: string): string {
|
|
60
|
-
return key
|
|
61
|
-
.replace(/\\/g, '\\\\') // Backslashes first to prevent double-escaping
|
|
62
|
-
.replace(/"/g, '\\"') // Double quotes
|
|
63
|
-
.replace(/\n/g, '\\n') // Newline
|
|
64
|
-
.replace(/\r/g, '\\r') // Carriage return
|
|
65
|
-
.replace(/\t/g, '\\t') // Tab
|
|
66
|
-
}
|
|
67
|
-
const engineDef = getEngineDefaults(ENGINE)
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Shell metacharacters that indicate potential command injection
|
|
71
|
-
* These patterns shouldn't appear in valid Redis commands
|
|
72
|
-
*/
|
|
73
|
-
const SHELL_INJECTION_PATTERNS = [
|
|
74
|
-
/;\s*\S/, // Command chaining: ; followed by another command
|
|
75
|
-
/\$\(/, // Command substitution: $(...)
|
|
76
|
-
/\$\{/, // Variable substitution: ${...}
|
|
77
|
-
/`/, // Backtick command substitution
|
|
78
|
-
/&&/, // Logical AND chaining
|
|
79
|
-
/\|\|/, // Logical OR chaining
|
|
80
|
-
/\|\s*\S/, // Pipe to another command
|
|
81
|
-
]
|
|
82
|
-
|
|
83
|
-
// Validate that a command doesn't contain shell injection patterns
|
|
84
|
-
function validateCommand(command: string): void {
|
|
85
|
-
for (const pattern of SHELL_INJECTION_PATTERNS) {
|
|
86
|
-
if (pattern.test(command)) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
`Command contains shell metacharacters that are not valid in Redis commands. ` +
|
|
89
|
-
`If you need complex commands, use a script file instead.`,
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Convert a Windows path to Cygwin path format.
|
|
97
|
-
* Redis Windows binaries (from redis-windows) are built with MSYS2/Cygwin runtime
|
|
98
|
-
* and expect paths in /cygdrive/c/... format when passed as command-line arguments.
|
|
99
|
-
*
|
|
100
|
-
* Example: C:\Users\foo\config.conf -> /cygdrive/c/Users/foo/config.conf
|
|
101
|
-
*/
|
|
102
|
-
function toCygwinPath(windowsPath: string): string {
|
|
103
|
-
// Match drive letter at start (e.g., C:\ or D:/)
|
|
104
|
-
const driveMatch = windowsPath.match(/^([A-Za-z]):[/\\]/)
|
|
105
|
-
if (!driveMatch) {
|
|
106
|
-
// Not a Windows absolute path, return as-is with forward slashes
|
|
107
|
-
return windowsPath.replace(/\\/g, '/')
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const driveLetter = driveMatch[1].toLowerCase()
|
|
111
|
-
const restOfPath = windowsPath.slice(3).replace(/\\/g, '/')
|
|
112
|
-
return `/cygdrive/${driveLetter}/${restOfPath}`
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Parse a Redis connection string
|
|
117
|
-
* Supported schemes:
|
|
118
|
-
* - redis:// (plain, no TLS)
|
|
119
|
-
* - rediss:// (TLS enabled)
|
|
120
|
-
*
|
|
121
|
-
* Format: scheme://[user:password@]host[:port][/database]
|
|
122
|
-
*
|
|
123
|
-
* Examples:
|
|
124
|
-
* - redis://localhost:6379
|
|
125
|
-
* - rediss://secure.host:6379/0 (TLS)
|
|
126
|
-
* - redis://:password@localhost:6379/0
|
|
127
|
-
* - redis://user:password@remote.host:6380/5
|
|
128
|
-
*/
|
|
129
|
-
function parseRedisConnectionString(connectionString: string): {
|
|
130
|
-
host: string
|
|
131
|
-
port: number
|
|
132
|
-
username: string | undefined
|
|
133
|
-
password: string | undefined
|
|
134
|
-
database: number
|
|
135
|
-
tls: boolean
|
|
136
|
-
} {
|
|
137
|
-
let url: URL
|
|
138
|
-
|
|
139
|
-
const normalized = connectionString.trim()
|
|
140
|
-
|
|
141
|
-
// Check for valid schemes
|
|
142
|
-
const validSchemes = ['redis://', 'rediss://']
|
|
143
|
-
const hasValidScheme = validSchemes.some((scheme) =>
|
|
144
|
-
normalized.startsWith(scheme),
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
if (!hasValidScheme) {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`Invalid Redis connection string: ${connectionString}\n` +
|
|
150
|
-
'Expected format: scheme://[user:password@]host:port[/database]\n' +
|
|
151
|
-
'Supported schemes: redis://, rediss://\n' +
|
|
152
|
-
'(Use rediss:// for TLS connections)',
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
url = new URL(normalized)
|
|
158
|
-
} catch {
|
|
159
|
-
throw new Error(
|
|
160
|
-
`Invalid Redis connection string: ${connectionString}\n` +
|
|
161
|
-
'Expected format: scheme://[user:password@]host:port[/database]',
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Determine TLS based on scheme
|
|
166
|
-
const tls = normalized.startsWith('rediss://')
|
|
167
|
-
|
|
168
|
-
const host = url.hostname || 'localhost'
|
|
169
|
-
const port = parseInt(url.port, 10) || 6379
|
|
170
|
-
|
|
171
|
-
// Redis 6.0+ supports ACL with usernames
|
|
172
|
-
// Format: redis://username:password@host:port/db
|
|
173
|
-
const username = url.username || undefined
|
|
174
|
-
const password = url.password || undefined
|
|
175
|
-
|
|
176
|
-
// Database is in the path (e.g., /5 means database 5)
|
|
177
|
-
let database = 0
|
|
178
|
-
if (url.pathname && url.pathname !== '/') {
|
|
179
|
-
const dbNum = parseInt(url.pathname.replace('/', ''), 10)
|
|
180
|
-
if (!isNaN(dbNum)) {
|
|
181
|
-
if (dbNum < 0 || dbNum > 15) {
|
|
182
|
-
throw new RangeError(
|
|
183
|
-
`Invalid Redis database number: ${dbNum} (from path "${url.pathname}").\n` +
|
|
184
|
-
'Redis databases must be 0-15 by default.\n' +
|
|
185
|
-
'If your server is configured with more databases (via the "databases" setting),\n' +
|
|
186
|
-
'you may need to increase the limit in server configuration.',
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
database = dbNum
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return { host, port, username, password, database, tls }
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Build a redis-cli command for inline command execution
|
|
197
|
-
export function buildRedisCliCommand(
|
|
198
|
-
redisCliPath: string,
|
|
199
|
-
port: number,
|
|
200
|
-
command: string,
|
|
201
|
-
options?: { database?: string },
|
|
202
|
-
): string {
|
|
203
|
-
// Validate command doesn't contain shell injection patterns
|
|
204
|
-
validateCommand(command)
|
|
205
|
-
|
|
206
|
-
const db = options?.database || '0'
|
|
207
|
-
// Escape double quotes consistently on all platforms to prevent shell interpretation issues
|
|
208
|
-
const escaped = command.replace(/"/g, '\\"')
|
|
209
|
-
return `"${redisCliPath}" -h 127.0.0.1 -p ${port} -n ${db} ${escaped}`
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Generate Redis configuration file content
|
|
213
|
-
function generateRedisConfig(options: {
|
|
214
|
-
port: number
|
|
215
|
-
dataDir: string
|
|
216
|
-
logFile: string
|
|
217
|
-
pidFile: string
|
|
218
|
-
daemonize?: boolean
|
|
219
|
-
}): string {
|
|
220
|
-
// Windows Redis doesn't support daemonize natively, use detached spawn instead
|
|
221
|
-
const daemonizeValue = options.daemonize ?? true
|
|
222
|
-
|
|
223
|
-
// Redis config requires forward slashes even on Windows
|
|
224
|
-
const normalizePathForRedis = (p: string) => p.replace(/\\/g, '/')
|
|
225
|
-
|
|
226
|
-
return `# SpinDB generated Redis configuration
|
|
227
|
-
port ${options.port}
|
|
228
|
-
bind 127.0.0.1
|
|
229
|
-
dir ${normalizePathForRedis(options.dataDir)}
|
|
230
|
-
daemonize ${daemonizeValue ? 'yes' : 'no'}
|
|
231
|
-
logfile ${normalizePathForRedis(options.logFile)}
|
|
232
|
-
pidfile ${normalizePathForRedis(options.pidFile)}
|
|
233
|
-
|
|
234
|
-
# Persistence - RDB snapshots
|
|
235
|
-
save 900 1
|
|
236
|
-
save 300 10
|
|
237
|
-
save 60 10000
|
|
238
|
-
dbfilename dump.rdb
|
|
239
|
-
|
|
240
|
-
# Append Only File (disabled for local dev)
|
|
241
|
-
appendonly no
|
|
242
|
-
|
|
243
|
-
# Suppress ARM64 copy-on-write warning with Transparent Huge Pages.
|
|
244
|
-
# Redis refuses to start on ARM64 with THP enabled unless this is set.
|
|
245
|
-
# Safe for local development (SpinDB's use case).
|
|
246
|
-
ignore-warnings ARM64-COW-BUG
|
|
247
|
-
`
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export class RedisEngine extends BaseEngine {
|
|
251
|
-
name = ENGINE
|
|
252
|
-
displayName = 'Redis'
|
|
253
|
-
defaultPort = engineDef.defaultPort
|
|
254
|
-
supportedVersions = SUPPORTED_MAJOR_VERSIONS
|
|
255
|
-
|
|
256
|
-
// Get platform info for binary operations
|
|
257
|
-
getPlatformInfo(): { platform: Platform; arch: Arch } {
|
|
258
|
-
return platformService.getPlatformInfo()
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Fetch available versions from hostdb (dynamically or from cache/fallback)
|
|
262
|
-
async fetchAvailableVersions(): Promise<Record<string, string[]>> {
|
|
263
|
-
return fetchHostdbVersions()
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Get binary download URL from hostdb
|
|
267
|
-
getBinaryUrl(version: string, platform: Platform, arch: Arch): string {
|
|
268
|
-
return getBinaryUrl(version, platform, arch)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Resolves version string to full version (e.g., '8' -> '8.4.0')
|
|
272
|
-
resolveFullVersion(version: string): string {
|
|
273
|
-
// Check if already a full version (has at least two dots)
|
|
274
|
-
if (/^\d+\.\d+\.\d+$/.test(version)) {
|
|
275
|
-
return version
|
|
276
|
-
}
|
|
277
|
-
// It's a major version, resolve using version map
|
|
278
|
-
return REDIS_VERSION_MAP[version] || `${version}.0.0`
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Get the path where binaries for a version would be installed
|
|
282
|
-
getBinaryPath(version: string): string {
|
|
283
|
-
const fullVersion = this.resolveFullVersion(version)
|
|
284
|
-
const { platform: p, arch: a } = this.getPlatformInfo()
|
|
285
|
-
return paths.getBinaryPath({
|
|
286
|
-
engine: 'redis',
|
|
287
|
-
version: fullVersion,
|
|
288
|
-
platform: p,
|
|
289
|
-
arch: a,
|
|
290
|
-
})
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Verify that Redis binaries are available
|
|
294
|
-
async verifyBinary(binPath: string): Promise<boolean> {
|
|
295
|
-
const ext = platformService.getExecutableExtension()
|
|
296
|
-
const serverPath = join(binPath, 'bin', `redis-server${ext}`)
|
|
297
|
-
return existsSync(serverPath)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
//Check if a specific Redis version is installed (downloaded)
|
|
301
|
-
async isBinaryInstalled(version: string): Promise<boolean> {
|
|
302
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
303
|
-
return redisBinaryManager.isInstalled(version, platform, arch)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Ensure Redis binaries are available for a specific version
|
|
308
|
-
* Downloads from hostdb if not already installed
|
|
309
|
-
* Returns the path to the bin directory
|
|
310
|
-
*/
|
|
311
|
-
async ensureBinaries(
|
|
312
|
-
version: string,
|
|
313
|
-
onProgress?: ProgressCallback,
|
|
314
|
-
): Promise<string> {
|
|
315
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
316
|
-
|
|
317
|
-
const binPath = await redisBinaryManager.ensureInstalled(
|
|
318
|
-
version,
|
|
319
|
-
platform,
|
|
320
|
-
arch,
|
|
321
|
-
onProgress,
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
// Register binaries in config
|
|
325
|
-
const ext = platformService.getExecutableExtension()
|
|
326
|
-
const tools = ['redis-server', 'redis-cli'] as const
|
|
327
|
-
|
|
328
|
-
for (const tool of tools) {
|
|
329
|
-
const toolPath = join(binPath, 'bin', `${tool}${ext}`)
|
|
330
|
-
if (existsSync(toolPath)) {
|
|
331
|
-
await configManager.setBinaryPath(tool, toolPath, 'bundled')
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return binPath
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Initialize a new Redis data directory
|
|
340
|
-
* Creates the directory and generates redis.conf
|
|
341
|
-
*/
|
|
342
|
-
async initDataDir(
|
|
343
|
-
containerName: string,
|
|
344
|
-
_version: string,
|
|
345
|
-
options: Record<string, unknown> = {},
|
|
346
|
-
): Promise<string> {
|
|
347
|
-
const dataDir = paths.getContainerDataPath(containerName, {
|
|
348
|
-
engine: ENGINE,
|
|
349
|
-
})
|
|
350
|
-
const containerDir = paths.getContainerPath(containerName, {
|
|
351
|
-
engine: ENGINE,
|
|
352
|
-
})
|
|
353
|
-
const logFile = paths.getContainerLogPath(containerName, { engine: ENGINE })
|
|
354
|
-
const pidFile = join(containerDir, 'redis.pid')
|
|
355
|
-
const port = (options.port as number) || engineDef.defaultPort
|
|
356
|
-
|
|
357
|
-
// Create data directory if it doesn't exist
|
|
358
|
-
if (!existsSync(dataDir)) {
|
|
359
|
-
await mkdir(dataDir, { recursive: true })
|
|
360
|
-
logDebug(`Created Redis data directory: ${dataDir}`)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Generate redis.conf
|
|
364
|
-
const configPath = join(containerDir, 'redis.conf')
|
|
365
|
-
const configContent = generateRedisConfig({
|
|
366
|
-
port,
|
|
367
|
-
dataDir,
|
|
368
|
-
logFile,
|
|
369
|
-
pidFile,
|
|
370
|
-
})
|
|
371
|
-
await writeFile(configPath, configContent)
|
|
372
|
-
logDebug(`Generated Redis config: ${configPath}`)
|
|
373
|
-
|
|
374
|
-
return dataDir
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Get the path to redis-server for a version
|
|
378
|
-
async getRedisServerPath(version: string): Promise<string> {
|
|
379
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
380
|
-
const fullVersion = normalizeVersion(version)
|
|
381
|
-
const binPath = paths.getBinaryPath({
|
|
382
|
-
engine: 'redis',
|
|
383
|
-
version: fullVersion,
|
|
384
|
-
platform,
|
|
385
|
-
arch,
|
|
386
|
-
})
|
|
387
|
-
const ext = platformService.getExecutableExtension()
|
|
388
|
-
const serverPath = join(binPath, 'bin', `redis-server${ext}`)
|
|
389
|
-
if (existsSync(serverPath)) {
|
|
390
|
-
return serverPath
|
|
391
|
-
}
|
|
392
|
-
throw new Error(
|
|
393
|
-
`Redis ${version} is not installed. Run: spindb engines download redis ${version}`,
|
|
394
|
-
)
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Get the path to redis-cli for a version
|
|
398
|
-
override async getRedisCliPath(version?: string): Promise<string> {
|
|
399
|
-
// Check config cache first
|
|
400
|
-
const cached = await configManager.getBinaryPath('redis-cli')
|
|
401
|
-
if (cached && existsSync(cached)) {
|
|
402
|
-
return cached
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// If version provided, use downloaded binary
|
|
406
|
-
if (version) {
|
|
407
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
408
|
-
const fullVersion = normalizeVersion(version)
|
|
409
|
-
const binPath = paths.getBinaryPath({
|
|
410
|
-
engine: 'redis',
|
|
411
|
-
version: fullVersion,
|
|
412
|
-
platform,
|
|
413
|
-
arch,
|
|
414
|
-
})
|
|
415
|
-
const ext = platformService.getExecutableExtension()
|
|
416
|
-
const cliPath = join(binPath, 'bin', `redis-cli${ext}`)
|
|
417
|
-
if (existsSync(cliPath)) {
|
|
418
|
-
return cliPath
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
throw new Error(
|
|
423
|
-
'redis-cli not found. Run: spindb engines download redis <version>',
|
|
424
|
-
)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Start Redis server
|
|
429
|
-
* CLI wrapper: redis-server /path/to/redis.conf
|
|
430
|
-
*/
|
|
431
|
-
async start(
|
|
432
|
-
container: ContainerConfig,
|
|
433
|
-
onProgress?: ProgressCallback,
|
|
434
|
-
): Promise<{ port: number; connectionString: string }> {
|
|
435
|
-
const { name, port, version, binaryPath } = container
|
|
436
|
-
|
|
437
|
-
// Check if already running (idempotent behavior)
|
|
438
|
-
const alreadyRunning = await processManager.isRunning(name, {
|
|
439
|
-
engine: ENGINE,
|
|
440
|
-
})
|
|
441
|
-
if (alreadyRunning) {
|
|
442
|
-
return {
|
|
443
|
-
port,
|
|
444
|
-
connectionString: this.getConnectionString(container),
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Use stored binary path if available (from container creation)
|
|
449
|
-
// This ensures version consistency - the container uses the same binary it was created with
|
|
450
|
-
let redisServer: string | null = null
|
|
451
|
-
|
|
452
|
-
if (binaryPath && existsSync(binaryPath)) {
|
|
453
|
-
// binaryPath is the directory (e.g., ~/.spindb/bin/redis-8.4.0-linux-arm64)
|
|
454
|
-
// We need to construct the full path to redis-server
|
|
455
|
-
const ext = platformService.getExecutableExtension()
|
|
456
|
-
const serverPath = join(binaryPath, 'bin', `redis-server${ext}`)
|
|
457
|
-
if (existsSync(serverPath)) {
|
|
458
|
-
redisServer = serverPath
|
|
459
|
-
logDebug(`Using stored binary path: ${redisServer}`)
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// If we didn't find the binary above, fall back to normal path
|
|
464
|
-
if (!redisServer) {
|
|
465
|
-
// Get binary from downloaded hostdb binaries
|
|
466
|
-
try {
|
|
467
|
-
redisServer = await this.getRedisServerPath(version)
|
|
468
|
-
} catch (error) {
|
|
469
|
-
// Binary not downloaded yet - this is an orphaned container situation
|
|
470
|
-
const originalMessage =
|
|
471
|
-
error instanceof Error ? error.message : String(error)
|
|
472
|
-
throw new Error(
|
|
473
|
-
`Redis ${version} is not installed. Run: spindb engines download redis ${version}\n` +
|
|
474
|
-
` Original error: ${originalMessage}`,
|
|
475
|
-
)
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
logDebug(`Using redis-server for version ${version}: ${redisServer}`)
|
|
480
|
-
|
|
481
|
-
// Compute library fallback paths from the binary directory
|
|
482
|
-
// redisServer is e.g. /path/to/redis-8.4.0-darwin-arm64/bin/redis-server
|
|
483
|
-
// We need the parent directory (without /bin/redis-server)
|
|
484
|
-
const binBaseDir = binaryPath || this.getBinaryPath(version)
|
|
485
|
-
const libraryEnv = getLibraryEnv(binBaseDir)
|
|
486
|
-
|
|
487
|
-
const containerDir = paths.getContainerPath(name, { engine: ENGINE })
|
|
488
|
-
const configPath = join(containerDir, 'redis.conf')
|
|
489
|
-
const dataDir = paths.getContainerDataPath(name, { engine: ENGINE })
|
|
490
|
-
const logFile = paths.getContainerLogPath(name, { engine: ENGINE })
|
|
491
|
-
const pidFile = join(containerDir, 'redis.pid')
|
|
492
|
-
|
|
493
|
-
// Windows Redis doesn't support daemonize natively
|
|
494
|
-
// Use detached spawn on Windows instead, similar to MongoDB
|
|
495
|
-
const useDetachedSpawn = isWindows()
|
|
496
|
-
|
|
497
|
-
// Regenerate config with current port (in case it changed)
|
|
498
|
-
const configContent = generateRedisConfig({
|
|
499
|
-
port,
|
|
500
|
-
dataDir,
|
|
501
|
-
logFile,
|
|
502
|
-
pidFile,
|
|
503
|
-
daemonize: !useDetachedSpawn, // Disable daemonize on Windows
|
|
504
|
-
})
|
|
505
|
-
await writeFile(configPath, configContent)
|
|
506
|
-
|
|
507
|
-
onProgress?.({ stage: 'starting', message: 'Starting Redis...' })
|
|
508
|
-
|
|
509
|
-
logDebug(`Starting redis-server with config: ${configPath}`)
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Check log file for port binding errors
|
|
513
|
-
* Returns error message if found, null otherwise
|
|
514
|
-
*/
|
|
515
|
-
const checkLogForPortError = async (): Promise<string | null> => {
|
|
516
|
-
try {
|
|
517
|
-
const logContent = await readFile(logFile, 'utf-8')
|
|
518
|
-
const recentLog = logContent.slice(-2000) // Last 2KB
|
|
519
|
-
|
|
520
|
-
if (
|
|
521
|
-
recentLog.includes('Address already in use') ||
|
|
522
|
-
recentLog.includes('bind: Address already in use')
|
|
523
|
-
) {
|
|
524
|
-
return `Port ${port} is already in use (address already in use)`
|
|
525
|
-
}
|
|
526
|
-
if (recentLog.includes('Failed listening on port')) {
|
|
527
|
-
return `Port ${port} is already in use`
|
|
528
|
-
}
|
|
529
|
-
} catch {
|
|
530
|
-
// Log file might not exist yet
|
|
531
|
-
}
|
|
532
|
-
return null
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (useDetachedSpawn) {
|
|
536
|
-
// Windows: spawn detached process with proper error handling
|
|
537
|
-
// This follows the pattern used by MySQL which works on Windows
|
|
538
|
-
return new Promise((resolve, reject) => {
|
|
539
|
-
const spawnOpts: SpawnOptions = {
|
|
540
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
541
|
-
detached: true,
|
|
542
|
-
windowsHide: true,
|
|
543
|
-
env: { ...process.env, ...libraryEnv },
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Convert Windows path to Cygwin format for MSYS2/Cygwin-built binaries
|
|
547
|
-
const cygwinConfigPath = toCygwinPath(configPath)
|
|
548
|
-
const proc = spawn(redisServer, [cygwinConfigPath], spawnOpts)
|
|
549
|
-
let settled = false
|
|
550
|
-
let stderrOutput = ''
|
|
551
|
-
let stdoutOutput = ''
|
|
552
|
-
|
|
553
|
-
// Handle spawn errors (binary not found, DLL issues, etc.)
|
|
554
|
-
proc.on('error', (err) => {
|
|
555
|
-
if (settled) return
|
|
556
|
-
settled = true
|
|
557
|
-
reject(new Error(`Failed to spawn Redis server: ${err.message}`))
|
|
558
|
-
})
|
|
559
|
-
|
|
560
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
561
|
-
const str = data.toString()
|
|
562
|
-
stdoutOutput += str
|
|
563
|
-
logDebug(`redis-server stdout: ${str}`)
|
|
564
|
-
})
|
|
565
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
566
|
-
const str = data.toString()
|
|
567
|
-
stderrOutput += str
|
|
568
|
-
logDebug(`redis-server stderr: ${str}`)
|
|
569
|
-
})
|
|
570
|
-
|
|
571
|
-
// Detach the process so it continues running after parent exits
|
|
572
|
-
proc.unref()
|
|
573
|
-
|
|
574
|
-
// Give spawn a moment to fail if it's going to, then check readiness
|
|
575
|
-
setTimeout(async () => {
|
|
576
|
-
if (settled) return
|
|
577
|
-
|
|
578
|
-
// Verify process actually started
|
|
579
|
-
if (!proc.pid) {
|
|
580
|
-
settled = true
|
|
581
|
-
reject(new Error('Redis server process failed to start (no PID)'))
|
|
582
|
-
return
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Write PID file for consistency with other engines
|
|
586
|
-
try {
|
|
587
|
-
await writeFile(pidFile, String(proc.pid))
|
|
588
|
-
} catch {
|
|
589
|
-
// Non-fatal - process is running, PID file is for convenience
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// Wait for Redis to be ready
|
|
593
|
-
const ready = await this.waitForReady(port, version)
|
|
594
|
-
if (settled) return
|
|
595
|
-
|
|
596
|
-
if (ready) {
|
|
597
|
-
// On Windows, Cygwin binaries may fork internally, making proc.pid stale.
|
|
598
|
-
// Find the actual PID by port and update the PID file (same pattern as QuestDB).
|
|
599
|
-
try {
|
|
600
|
-
const pids = await platformService.findProcessByPort(port)
|
|
601
|
-
if (pids.length > 0 && pids[0] !== proc.pid) {
|
|
602
|
-
logDebug(
|
|
603
|
-
`Redis actual PID ${pids[0]} differs from spawn PID ${proc.pid}, updating PID file`,
|
|
604
|
-
)
|
|
605
|
-
await writeFile(pidFile, String(pids[0]))
|
|
606
|
-
}
|
|
607
|
-
} catch {
|
|
608
|
-
// Non-fatal - PID file already has proc.pid from earlier write
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
settled = true
|
|
612
|
-
resolve({
|
|
613
|
-
port,
|
|
614
|
-
connectionString: this.getConnectionString(container),
|
|
615
|
-
})
|
|
616
|
-
} else {
|
|
617
|
-
settled = true
|
|
618
|
-
const portError = await checkLogForPortError()
|
|
619
|
-
|
|
620
|
-
// Read log file content for better error diagnostics
|
|
621
|
-
let logContent = ''
|
|
622
|
-
try {
|
|
623
|
-
logContent = await readFile(logFile, 'utf-8')
|
|
624
|
-
} catch {
|
|
625
|
-
logContent = '(log file not found or empty)'
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Check for library loading errors first
|
|
629
|
-
const libError = detectLibraryError(
|
|
630
|
-
stderrOutput + logContent,
|
|
631
|
-
'Redis',
|
|
632
|
-
)
|
|
633
|
-
if (libError) {
|
|
634
|
-
reject(new Error(libError))
|
|
635
|
-
return
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const errorDetails = [
|
|
639
|
-
portError || 'Redis failed to start within timeout.',
|
|
640
|
-
`Binary: ${redisServer}`,
|
|
641
|
-
`Config: ${configPath}`,
|
|
642
|
-
`Log file: ${logFile}`,
|
|
643
|
-
`Log content:\n${logContent || '(empty)'}`,
|
|
644
|
-
stderrOutput ? `Stderr:\n${stderrOutput}` : '',
|
|
645
|
-
stdoutOutput ? `Stdout:\n${stdoutOutput}` : '',
|
|
646
|
-
]
|
|
647
|
-
.filter(Boolean)
|
|
648
|
-
.join('\n')
|
|
649
|
-
|
|
650
|
-
reject(new Error(errorDetails))
|
|
651
|
-
}
|
|
652
|
-
}, 500)
|
|
653
|
-
})
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Unix: Redis with daemonize: yes handles its own forking
|
|
657
|
-
return new Promise((resolve, reject) => {
|
|
658
|
-
const proc = spawn(redisServer, [configPath], {
|
|
659
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
660
|
-
env: { ...process.env, ...libraryEnv },
|
|
661
|
-
})
|
|
662
|
-
|
|
663
|
-
let stdout = ''
|
|
664
|
-
let stderr = ''
|
|
665
|
-
|
|
666
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
667
|
-
stdout += data.toString()
|
|
668
|
-
logDebug(`redis-server stdout: ${data.toString()}`)
|
|
669
|
-
})
|
|
670
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
671
|
-
stderr += data.toString()
|
|
672
|
-
logDebug(`redis-server stderr: ${data.toString()}`)
|
|
673
|
-
})
|
|
674
|
-
|
|
675
|
-
proc.on('error', reject)
|
|
676
|
-
|
|
677
|
-
proc.on('close', async (code) => {
|
|
678
|
-
// Redis with daemonize: yes exits immediately after forking
|
|
679
|
-
// Exit code 0 means the parent forked successfully, but the child may still fail
|
|
680
|
-
if (code === 0 || code === null) {
|
|
681
|
-
// Give the child process a moment to start (or fail)
|
|
682
|
-
await new Promise((r) => setTimeout(r, 500))
|
|
683
|
-
|
|
684
|
-
// Check log for early startup failures (like port conflicts)
|
|
685
|
-
const earlyError = await checkLogForPortError()
|
|
686
|
-
if (earlyError) {
|
|
687
|
-
reject(new Error(earlyError))
|
|
688
|
-
return
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Wait for Redis to be ready
|
|
692
|
-
const ready = await this.waitForReady(port, version)
|
|
693
|
-
if (ready) {
|
|
694
|
-
resolve({
|
|
695
|
-
port,
|
|
696
|
-
connectionString: this.getConnectionString(container),
|
|
697
|
-
})
|
|
698
|
-
} else {
|
|
699
|
-
// Check log again for errors if not ready
|
|
700
|
-
const portError = await checkLogForPortError()
|
|
701
|
-
if (portError) {
|
|
702
|
-
reject(new Error(portError))
|
|
703
|
-
return
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Include log content in error for CI debugging
|
|
707
|
-
let logContent = ''
|
|
708
|
-
try {
|
|
709
|
-
logContent = await readFile(logFile, 'utf-8')
|
|
710
|
-
} catch {
|
|
711
|
-
logContent = '(log file not found or empty)'
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// Check for library loading errors
|
|
715
|
-
const libError = detectLibraryError(stderr + logContent, 'Redis')
|
|
716
|
-
if (libError) {
|
|
717
|
-
reject(new Error(libError))
|
|
718
|
-
return
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
const errorDetails = [
|
|
722
|
-
'Redis failed to start within timeout.',
|
|
723
|
-
`Binary: ${redisServer}`,
|
|
724
|
-
`Log file: ${logFile}`,
|
|
725
|
-
`Log content:\n${logContent || '(empty)'}`,
|
|
726
|
-
stderr ? `Stderr:\n${stderr}` : '',
|
|
727
|
-
stdout ? `Stdout:\n${stdout}` : '',
|
|
728
|
-
]
|
|
729
|
-
.filter(Boolean)
|
|
730
|
-
.join('\n')
|
|
731
|
-
|
|
732
|
-
reject(new Error(errorDetails))
|
|
733
|
-
}
|
|
734
|
-
} else {
|
|
735
|
-
// Include log content for non-zero exit codes too
|
|
736
|
-
let logContent = ''
|
|
737
|
-
try {
|
|
738
|
-
logContent = await readFile(logFile, 'utf-8')
|
|
739
|
-
} catch {
|
|
740
|
-
logContent = ''
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// Check for library loading errors on non-zero exit
|
|
744
|
-
const libError = detectLibraryError(
|
|
745
|
-
stderr + stdout + logContent,
|
|
746
|
-
'Redis',
|
|
747
|
-
)
|
|
748
|
-
if (libError) {
|
|
749
|
-
reject(new Error(libError))
|
|
750
|
-
return
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const errorDetails = [
|
|
754
|
-
stderr || stdout || `redis-server exited with code ${code}`,
|
|
755
|
-
logContent ? `Log content:\n${logContent}` : '',
|
|
756
|
-
]
|
|
757
|
-
.filter(Boolean)
|
|
758
|
-
.join('\n')
|
|
759
|
-
|
|
760
|
-
reject(new Error(errorDetails))
|
|
761
|
-
}
|
|
762
|
-
})
|
|
763
|
-
})
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// Wait for Redis to be ready to accept connections
|
|
767
|
-
private async waitForReady(
|
|
768
|
-
port: number,
|
|
769
|
-
version: string,
|
|
770
|
-
timeoutMs = 60000,
|
|
771
|
-
): Promise<boolean> {
|
|
772
|
-
const startTime = Date.now()
|
|
773
|
-
const checkInterval = 500
|
|
774
|
-
|
|
775
|
-
let redisCli: string
|
|
776
|
-
try {
|
|
777
|
-
redisCli = await this.getRedisCliPathForVersion(version)
|
|
778
|
-
} catch {
|
|
779
|
-
logWarning('redis-cli not found, cannot verify Redis is ready')
|
|
780
|
-
return false
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
784
|
-
try {
|
|
785
|
-
const cmd = `"${redisCli}" -h 127.0.0.1 -p ${port} PING`
|
|
786
|
-
const { stdout } = await execAsync(cmd, { timeout: 5000 })
|
|
787
|
-
if (stdout.trim() === 'PONG') {
|
|
788
|
-
logDebug(`Redis ready on port ${port}`)
|
|
789
|
-
return true
|
|
790
|
-
}
|
|
791
|
-
} catch {
|
|
792
|
-
await new Promise((resolve) => setTimeout(resolve, checkInterval))
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
logWarning(`Redis did not become ready within ${timeoutMs}ms`)
|
|
797
|
-
return false
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Stop Redis server
|
|
802
|
-
* Uses SHUTDOWN SAVE via redis-cli to persist data before stopping
|
|
803
|
-
*/
|
|
804
|
-
async stop(container: ContainerConfig): Promise<void> {
|
|
805
|
-
const { name, port, version } = container
|
|
806
|
-
const containerDir = paths.getContainerPath(name, { engine: ENGINE })
|
|
807
|
-
const pidFile = join(containerDir, 'redis.pid')
|
|
808
|
-
|
|
809
|
-
logDebug(`Stopping Redis container "${name}" on port ${port}`)
|
|
810
|
-
|
|
811
|
-
// Try graceful shutdown via redis-cli
|
|
812
|
-
const redisCli = await this.getRedisCliPathForVersion(version)
|
|
813
|
-
if (redisCli) {
|
|
814
|
-
try {
|
|
815
|
-
const cmd = `"${redisCli}" -h 127.0.0.1 -p ${port} SHUTDOWN SAVE`
|
|
816
|
-
await execAsync(cmd, { timeout: 10000 })
|
|
817
|
-
logDebug('Redis shutdown command sent')
|
|
818
|
-
// Wait a bit for process to exit
|
|
819
|
-
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
820
|
-
} catch (error) {
|
|
821
|
-
logDebug(`redis-cli shutdown failed: ${error}`)
|
|
822
|
-
// Continue to PID-based shutdown
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// Get PID and force kill if needed
|
|
827
|
-
let pid: number | null = null
|
|
828
|
-
|
|
829
|
-
if (existsSync(pidFile)) {
|
|
830
|
-
try {
|
|
831
|
-
const content = await readFile(pidFile, 'utf8')
|
|
832
|
-
pid = parseInt(content.trim(), 10)
|
|
833
|
-
} catch {
|
|
834
|
-
// Ignore
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Kill process if still running
|
|
839
|
-
if (pid && platformService.isProcessRunning(pid)) {
|
|
840
|
-
logDebug(`Killing Redis process ${pid}`)
|
|
841
|
-
try {
|
|
842
|
-
await platformService.terminateProcess(pid, false)
|
|
843
|
-
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
844
|
-
|
|
845
|
-
if (platformService.isProcessRunning(pid)) {
|
|
846
|
-
logWarning(`Graceful termination failed, force killing ${pid}`)
|
|
847
|
-
await platformService.terminateProcess(pid, true)
|
|
848
|
-
}
|
|
849
|
-
} catch (error) {
|
|
850
|
-
logDebug(`Process termination error: ${error}`)
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// Cleanup PID file
|
|
855
|
-
if (existsSync(pidFile)) {
|
|
856
|
-
try {
|
|
857
|
-
await unlink(pidFile)
|
|
858
|
-
} catch {
|
|
859
|
-
// Ignore
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
logDebug('Redis stopped')
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// Get Redis server status
|
|
867
|
-
async status(container: ContainerConfig): Promise<StatusResult> {
|
|
868
|
-
const { name, port, version } = container
|
|
869
|
-
const containerDir = paths.getContainerPath(name, { engine: ENGINE })
|
|
870
|
-
const pidFile = join(containerDir, 'redis.pid')
|
|
871
|
-
|
|
872
|
-
// Try pinging with redis-cli
|
|
873
|
-
const redisCli = await this.getRedisCliPathForVersion(version)
|
|
874
|
-
if (redisCli) {
|
|
875
|
-
try {
|
|
876
|
-
const cmd = `"${redisCli}" -h 127.0.0.1 -p ${port} PING`
|
|
877
|
-
const { stdout } = await execAsync(cmd, { timeout: 5000 })
|
|
878
|
-
if (stdout.trim() === 'PONG') {
|
|
879
|
-
return { running: true, message: 'Redis is running' }
|
|
880
|
-
}
|
|
881
|
-
} catch {
|
|
882
|
-
// Not responding, check PID
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// Check PID file
|
|
887
|
-
if (existsSync(pidFile)) {
|
|
888
|
-
try {
|
|
889
|
-
const content = await readFile(pidFile, 'utf8')
|
|
890
|
-
const pid = parseInt(content.trim(), 10)
|
|
891
|
-
if (!isNaN(pid) && pid > 0 && platformService.isProcessRunning(pid)) {
|
|
892
|
-
return {
|
|
893
|
-
running: true,
|
|
894
|
-
message: `Redis is running (PID: ${pid})`,
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
} catch {
|
|
898
|
-
// Ignore
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
return { running: false, message: 'Redis is not running' }
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
// Detect backup format
|
|
906
|
-
async detectBackupFormat(filePath: string): Promise<BackupFormat> {
|
|
907
|
-
return detectBackupFormatImpl(filePath)
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Restore a backup
|
|
912
|
-
* IMPORTANT: Redis must be stopped before restore
|
|
913
|
-
*/
|
|
914
|
-
async restore(
|
|
915
|
-
container: ContainerConfig,
|
|
916
|
-
backupPath: string,
|
|
917
|
-
options: { database?: string; flush?: boolean } = {},
|
|
918
|
-
): Promise<RestoreResult> {
|
|
919
|
-
const { name, port } = container
|
|
920
|
-
const dataDir = paths.getContainerDataPath(name, { engine: ENGINE })
|
|
921
|
-
|
|
922
|
-
return restoreBackup(backupPath, {
|
|
923
|
-
containerName: name,
|
|
924
|
-
dataDir,
|
|
925
|
-
port,
|
|
926
|
-
database: options.database || container.database || '0',
|
|
927
|
-
flush: options.flush,
|
|
928
|
-
})
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
/**
|
|
932
|
-
* Get connection string
|
|
933
|
-
* Format: redis://127.0.0.1:PORT/DATABASE
|
|
934
|
-
*/
|
|
935
|
-
getConnectionString(container: ContainerConfig, database?: string): string {
|
|
936
|
-
const { port } = container
|
|
937
|
-
const db = database || container.database || '0'
|
|
938
|
-
return `redis://127.0.0.1:${port}/${db}`
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
/**
|
|
942
|
-
* Get path to redis-cli for a specific version
|
|
943
|
-
* @param version - Optional version (e.g., "8", "7"). If not provided, uses cached path.
|
|
944
|
-
* @deprecated Use getRedisCliPath() instead
|
|
945
|
-
*/
|
|
946
|
-
async getRedisCliPathForVersion(version?: string): Promise<string> {
|
|
947
|
-
return this.getRedisCliPath(version)
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// Open redis-cli interactive shell
|
|
951
|
-
async connect(container: ContainerConfig, database?: string): Promise<void> {
|
|
952
|
-
const { port, version } = container
|
|
953
|
-
const db = database || container.database || '0'
|
|
954
|
-
|
|
955
|
-
const redisCli = await this.getRedisCliPathForVersion(version)
|
|
956
|
-
|
|
957
|
-
const spawnOptions: SpawnOptions = {
|
|
958
|
-
stdio: 'inherit',
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
return new Promise((resolve, reject) => {
|
|
962
|
-
const proc = spawn(
|
|
963
|
-
redisCli,
|
|
964
|
-
['-h', '127.0.0.1', '-p', String(port), '-n', db],
|
|
965
|
-
spawnOptions,
|
|
966
|
-
)
|
|
967
|
-
|
|
968
|
-
proc.on('error', reject)
|
|
969
|
-
proc.on('close', () => resolve())
|
|
970
|
-
})
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// Get path to iredis (enhanced CLI) if installed
|
|
974
|
-
private async getIredisPath(): Promise<string | null> {
|
|
975
|
-
// Check config cache first
|
|
976
|
-
const cached = await configManager.getBinaryPath('iredis')
|
|
977
|
-
if (cached && existsSync(cached)) {
|
|
978
|
-
return cached
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Check system PATH
|
|
982
|
-
const systemPath = await platformService.findToolPath('iredis')
|
|
983
|
-
if (systemPath) {
|
|
984
|
-
return systemPath
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
return null
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
// Connect with iredis (enhanced CLI)
|
|
991
|
-
async connectWithIredis(
|
|
992
|
-
container: ContainerConfig,
|
|
993
|
-
database?: string,
|
|
994
|
-
): Promise<void> {
|
|
995
|
-
const { port } = container
|
|
996
|
-
const db = database || container.database || '0'
|
|
997
|
-
|
|
998
|
-
const iredis = await this.getIredisPath()
|
|
999
|
-
if (!iredis) {
|
|
1000
|
-
throw new Error(
|
|
1001
|
-
'iredis not found. Install it with:\n' +
|
|
1002
|
-
' macOS: brew install iredis\n' +
|
|
1003
|
-
' pip: pip install iredis',
|
|
1004
|
-
)
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
const spawnOptions: SpawnOptions = {
|
|
1008
|
-
stdio: 'inherit',
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
return new Promise((resolve, reject) => {
|
|
1012
|
-
const proc = spawn(
|
|
1013
|
-
iredis,
|
|
1014
|
-
['-h', '127.0.0.1', '-p', String(port), '-n', db],
|
|
1015
|
-
spawnOptions,
|
|
1016
|
-
)
|
|
1017
|
-
|
|
1018
|
-
proc.on('error', reject)
|
|
1019
|
-
proc.on('close', () => resolve())
|
|
1020
|
-
})
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
/**
|
|
1024
|
-
* Create a new database
|
|
1025
|
-
* Redis uses numbered databases (0-15), they always exist
|
|
1026
|
-
* This is effectively a no-op
|
|
1027
|
-
*/
|
|
1028
|
-
async createDatabase(
|
|
1029
|
-
_container: ContainerConfig,
|
|
1030
|
-
database: string,
|
|
1031
|
-
): Promise<void> {
|
|
1032
|
-
const dbNum = parseInt(database, 10)
|
|
1033
|
-
if (isNaN(dbNum) || dbNum < 0 || dbNum > 15) {
|
|
1034
|
-
throw new Error(
|
|
1035
|
-
`Invalid Redis database number: ${database}. Must be 0-15.`,
|
|
1036
|
-
)
|
|
1037
|
-
}
|
|
1038
|
-
// No-op - Redis databases always exist
|
|
1039
|
-
logDebug(
|
|
1040
|
-
`Redis database ${database} is available (databases 0-15 always exist)`,
|
|
1041
|
-
)
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* Drop a database
|
|
1046
|
-
* Uses FLUSHDB to clear all keys in the specified database
|
|
1047
|
-
*/
|
|
1048
|
-
async dropDatabase(
|
|
1049
|
-
container: ContainerConfig,
|
|
1050
|
-
database: string,
|
|
1051
|
-
): Promise<void> {
|
|
1052
|
-
const { port, version } = container
|
|
1053
|
-
const dbNum = parseInt(database, 10)
|
|
1054
|
-
if (isNaN(dbNum) || dbNum < 0 || dbNum > 15) {
|
|
1055
|
-
throw new Error(
|
|
1056
|
-
`Invalid Redis database number: ${database}. Must be 0-15.`,
|
|
1057
|
-
)
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
const redisCli = await this.getRedisCliPathForVersion(version)
|
|
1061
|
-
|
|
1062
|
-
// SELECT the database and FLUSHDB
|
|
1063
|
-
const cmd = `"${redisCli}" -h 127.0.0.1 -p ${port} -n ${database} FLUSHDB`
|
|
1064
|
-
|
|
1065
|
-
try {
|
|
1066
|
-
await execAsync(cmd, { timeout: 10000 })
|
|
1067
|
-
logDebug(`Flushed Redis database ${database}`)
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
const err = error as Error
|
|
1070
|
-
logDebug(`FLUSHDB failed: ${err.message}`)
|
|
1071
|
-
throw new Error(
|
|
1072
|
-
`Failed to flush Redis database ${database}: ${err.message}`,
|
|
1073
|
-
)
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
/**
|
|
1078
|
-
* Get the memory usage of the Redis server in bytes
|
|
1079
|
-
*
|
|
1080
|
-
* NOTE: Redis does not provide per-database memory statistics.
|
|
1081
|
-
* This returns the total server memory (used_memory from INFO memory),
|
|
1082
|
-
* not the size of a specific numbered database (0-15).
|
|
1083
|
-
* This is acceptable for SpinDB since each container runs one Redis server.
|
|
1084
|
-
*/
|
|
1085
|
-
async getDatabaseSize(container: ContainerConfig): Promise<number | null> {
|
|
1086
|
-
const { port, version } = container
|
|
1087
|
-
|
|
1088
|
-
try {
|
|
1089
|
-
const redisCli = await this.getRedisCliPathForVersion(version)
|
|
1090
|
-
// INFO memory returns server-wide stats (database selection has no effect)
|
|
1091
|
-
const cmd = `"${redisCli}" -h 127.0.0.1 -p ${port} INFO memory`
|
|
1092
|
-
|
|
1093
|
-
const { stdout } = await execAsync(cmd, { timeout: 10000 })
|
|
1094
|
-
|
|
1095
|
-
// Parse used_memory (total server memory) from INFO output
|
|
1096
|
-
const match = stdout.match(/used_memory:(\d+)/)
|
|
1097
|
-
if (match) {
|
|
1098
|
-
return parseInt(match[1], 10)
|
|
1099
|
-
}
|
|
1100
|
-
return null
|
|
1101
|
-
} catch {
|
|
1102
|
-
return null
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Dump from a remote Redis connection
|
|
1108
|
-
* Creates a text-format backup by scanning all keys from the remote server
|
|
1109
|
-
*
|
|
1110
|
-
* Connection string format: redis://[user:password@]host:port[/db]
|
|
1111
|
-
*/
|
|
1112
|
-
async dumpFromConnectionString(
|
|
1113
|
-
connectionString: string,
|
|
1114
|
-
outputPath: string,
|
|
1115
|
-
): Promise<DumpResult> {
|
|
1116
|
-
const redisCli = await getRedisCliPath()
|
|
1117
|
-
if (!redisCli) {
|
|
1118
|
-
throw new Error(REDIS_CLI_NOT_FOUND_ERROR)
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// Parse connection string
|
|
1122
|
-
const { host, port, username, password, database, tls } =
|
|
1123
|
-
parseRedisConnectionString(connectionString)
|
|
1124
|
-
|
|
1125
|
-
logDebug(
|
|
1126
|
-
`Connecting to remote Redis at ${host}:${port} (db: ${database}, tls: ${tls})`,
|
|
1127
|
-
)
|
|
1128
|
-
|
|
1129
|
-
// Build CLI args for remote connection (password passed via env var for security)
|
|
1130
|
-
const buildArgs = (): string[] => {
|
|
1131
|
-
const args = ['-h', host, '-p', String(port)]
|
|
1132
|
-
// Redis 6.0+ ACL: pass username via --user flag
|
|
1133
|
-
if (username) {
|
|
1134
|
-
args.push('--user', username)
|
|
1135
|
-
}
|
|
1136
|
-
// Enable TLS for rediss:// scheme
|
|
1137
|
-
if (tls) {
|
|
1138
|
-
args.push('--tls')
|
|
1139
|
-
}
|
|
1140
|
-
// Note: password is passed via REDISCLI_AUTH env var, not command line
|
|
1141
|
-
args.push('-n', String(database))
|
|
1142
|
-
return args
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
// Execute a Redis command on the remote server with timeout
|
|
1146
|
-
const execRemote = async (
|
|
1147
|
-
command: string,
|
|
1148
|
-
timeoutMs = 30000,
|
|
1149
|
-
): Promise<string> => {
|
|
1150
|
-
return new Promise((resolve, reject) => {
|
|
1151
|
-
const args = buildArgs()
|
|
1152
|
-
// Pass password via REDISCLI_AUTH env var to avoid exposing it in process listings
|
|
1153
|
-
const env = password
|
|
1154
|
-
? { ...process.env, REDISCLI_AUTH: password }
|
|
1155
|
-
: process.env
|
|
1156
|
-
const proc = spawn(redisCli, args, {
|
|
1157
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1158
|
-
env,
|
|
1159
|
-
})
|
|
1160
|
-
|
|
1161
|
-
let stdout = ''
|
|
1162
|
-
let stderr = ''
|
|
1163
|
-
let settled = false
|
|
1164
|
-
|
|
1165
|
-
// Timeout handler to prevent hanging
|
|
1166
|
-
const timeoutId = setTimeout(() => {
|
|
1167
|
-
if (settled) return
|
|
1168
|
-
settled = true
|
|
1169
|
-
proc.kill()
|
|
1170
|
-
reject(
|
|
1171
|
-
new Error(
|
|
1172
|
-
`Command timed out after ${timeoutMs}ms: ${command.slice(0, 50)}...`,
|
|
1173
|
-
),
|
|
1174
|
-
)
|
|
1175
|
-
}, timeoutMs)
|
|
1176
|
-
|
|
1177
|
-
proc.stdout.on('data', (data: Buffer) => {
|
|
1178
|
-
stdout += data.toString()
|
|
1179
|
-
})
|
|
1180
|
-
proc.stderr.on('data', (data: Buffer) => {
|
|
1181
|
-
stderr += data.toString()
|
|
1182
|
-
})
|
|
1183
|
-
|
|
1184
|
-
proc.on('error', (err) => {
|
|
1185
|
-
if (settled) return
|
|
1186
|
-
settled = true
|
|
1187
|
-
clearTimeout(timeoutId)
|
|
1188
|
-
reject(err)
|
|
1189
|
-
})
|
|
1190
|
-
|
|
1191
|
-
proc.on('close', (code) => {
|
|
1192
|
-
if (settled) return
|
|
1193
|
-
settled = true
|
|
1194
|
-
clearTimeout(timeoutId)
|
|
1195
|
-
// Ignore auth-related warnings in stderr (password provided via REDISCLI_AUTH)
|
|
1196
|
-
if (code === 0 || code === null) {
|
|
1197
|
-
resolve(stdout)
|
|
1198
|
-
} else {
|
|
1199
|
-
reject(new Error(stderr || `redis-cli exited with code ${code}`))
|
|
1200
|
-
}
|
|
1201
|
-
})
|
|
1202
|
-
|
|
1203
|
-
proc.stdin.write(command + '\n')
|
|
1204
|
-
proc.stdin.end()
|
|
1205
|
-
})
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
// Test connectivity
|
|
1209
|
-
try {
|
|
1210
|
-
const pingResult = await execRemote('PING')
|
|
1211
|
-
if (!pingResult.trim().includes('PONG')) {
|
|
1212
|
-
throw new Error(`Unexpected PING response: ${pingResult.trim()}`)
|
|
1213
|
-
}
|
|
1214
|
-
} catch (error) {
|
|
1215
|
-
throw new Error(
|
|
1216
|
-
`Failed to connect to Redis at ${host}:${port}: ${(error as Error).message}`,
|
|
1217
|
-
)
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
// Build text backup from remote keys
|
|
1221
|
-
const commands: string[] = []
|
|
1222
|
-
commands.push('# Redis backup generated by SpinDB')
|
|
1223
|
-
commands.push(`# Source: ${host}:${port}`)
|
|
1224
|
-
commands.push(`# Date: ${new Date().toISOString()}`)
|
|
1225
|
-
commands.push('')
|
|
1226
|
-
|
|
1227
|
-
// WARNING: KEYS * blocks the Redis server during execution.
|
|
1228
|
-
// This is acceptable for small datasets but will cause performance issues
|
|
1229
|
-
// on large databases. For production use with large datasets, consider
|
|
1230
|
-
// implementing SCAN-based iteration instead.
|
|
1231
|
-
// TODO: Replace with SCAN iterator for large dataset support
|
|
1232
|
-
const keysOutput = await execRemote('KEYS *')
|
|
1233
|
-
const keys = keysOutput
|
|
1234
|
-
.trim()
|
|
1235
|
-
.split(/\r?\n/)
|
|
1236
|
-
.map((k) => k.trim())
|
|
1237
|
-
.filter((k) => k)
|
|
1238
|
-
|
|
1239
|
-
logDebug(`Found ${keys.length} keys on remote Redis`)
|
|
1240
|
-
|
|
1241
|
-
for (const key of keys) {
|
|
1242
|
-
// Get key type
|
|
1243
|
-
const typeOutput = await execRemote(`TYPE "${escapeKeyForCommand(key)}"`)
|
|
1244
|
-
const keyType = typeOutput.trim()
|
|
1245
|
-
|
|
1246
|
-
// Get TTL
|
|
1247
|
-
const ttlOutput = await execRemote(`TTL "${escapeKeyForCommand(key)}"`)
|
|
1248
|
-
const ttl = parseInt(ttlOutput.trim(), 10)
|
|
1249
|
-
|
|
1250
|
-
// Quote the key for output commands if it contains special chars
|
|
1251
|
-
const quotedKey =
|
|
1252
|
-
key.includes(' ') || /[*?[\]{}$`"'\\!<>|;&()]/.test(key)
|
|
1253
|
-
? `"${key.replace(/"/g, '\\"')}"`
|
|
1254
|
-
: key
|
|
1255
|
-
|
|
1256
|
-
// Redis-cli compatible double-quote escaping for values
|
|
1257
|
-
// Escapes backslashes and double quotes, converts newlines to \n sequences
|
|
1258
|
-
// Note: This approach doesn't handle binary data.
|
|
1259
|
-
// For binary-safe backups, consider using DUMP/RESTORE commands instead.
|
|
1260
|
-
const escapeValue = (value: string): string => {
|
|
1261
|
-
const escaped = value
|
|
1262
|
-
.replace(/\\/g, '\\\\')
|
|
1263
|
-
.replace(/"/g, '\\"')
|
|
1264
|
-
.replace(/\n/g, '\\n')
|
|
1265
|
-
.replace(/\r/g, '\\r')
|
|
1266
|
-
return `"${escaped}"`
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
// Strip only trailing newline from execRemote output, preserving intentional whitespace
|
|
1270
|
-
const stripTrailingNewline = (s: string): string =>
|
|
1271
|
-
s.replace(/\r?\n$/, '')
|
|
1272
|
-
|
|
1273
|
-
switch (keyType) {
|
|
1274
|
-
case 'string': {
|
|
1275
|
-
const value = await execRemote(`GET "${escapeKeyForCommand(key)}"`)
|
|
1276
|
-
commands.push(
|
|
1277
|
-
`SET ${quotedKey} ${escapeValue(stripTrailingNewline(value))}`,
|
|
1278
|
-
)
|
|
1279
|
-
break
|
|
1280
|
-
}
|
|
1281
|
-
case 'hash': {
|
|
1282
|
-
const hashData = await execRemote(
|
|
1283
|
-
`HGETALL "${escapeKeyForCommand(key)}"`,
|
|
1284
|
-
)
|
|
1285
|
-
const lines = stripTrailingNewline(hashData)
|
|
1286
|
-
.split(/\r?\n/)
|
|
1287
|
-
.filter((l) => l)
|
|
1288
|
-
if (lines.length >= 2) {
|
|
1289
|
-
const pairs: string[] = []
|
|
1290
|
-
// Handle odd number of lines (incomplete field/value pair)
|
|
1291
|
-
const completeCount = lines.length - (lines.length % 2)
|
|
1292
|
-
if (lines.length % 2 !== 0) {
|
|
1293
|
-
logWarning(
|
|
1294
|
-
`Hash ${quotedKey} has incomplete field/value pair, skipping last field`,
|
|
1295
|
-
)
|
|
1296
|
-
}
|
|
1297
|
-
for (let i = 0; i < completeCount; i += 2) {
|
|
1298
|
-
const field = lines[i]
|
|
1299
|
-
const value = lines[i + 1]
|
|
1300
|
-
const quotedField =
|
|
1301
|
-
field.includes(' ') || /[*?[\]{}$`"'\\!<>|;&()]/.test(field)
|
|
1302
|
-
? `"${field.replace(/"/g, '\\"')}"`
|
|
1303
|
-
: field
|
|
1304
|
-
pairs.push(`${quotedField} ${escapeValue(value)}`)
|
|
1305
|
-
}
|
|
1306
|
-
if (pairs.length > 0) {
|
|
1307
|
-
commands.push(`HSET ${quotedKey} ${pairs.join(' ')}`)
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
break
|
|
1311
|
-
}
|
|
1312
|
-
case 'list': {
|
|
1313
|
-
const listData = await execRemote(
|
|
1314
|
-
`LRANGE "${escapeKeyForCommand(key)}" 0 -1`,
|
|
1315
|
-
)
|
|
1316
|
-
const items = stripTrailingNewline(listData)
|
|
1317
|
-
.split(/\r?\n/)
|
|
1318
|
-
.filter((l) => l)
|
|
1319
|
-
if (items.length > 0) {
|
|
1320
|
-
const escapedItems = items.map((item) => escapeValue(item))
|
|
1321
|
-
commands.push(`RPUSH ${quotedKey} ${escapedItems.join(' ')}`)
|
|
1322
|
-
}
|
|
1323
|
-
break
|
|
1324
|
-
}
|
|
1325
|
-
case 'set': {
|
|
1326
|
-
const setData = await execRemote(
|
|
1327
|
-
`SMEMBERS "${escapeKeyForCommand(key)}"`,
|
|
1328
|
-
)
|
|
1329
|
-
const members = stripTrailingNewline(setData)
|
|
1330
|
-
.split(/\r?\n/)
|
|
1331
|
-
.filter((l) => l)
|
|
1332
|
-
if (members.length > 0) {
|
|
1333
|
-
const escapedMembers = members.map((m) => escapeValue(m))
|
|
1334
|
-
commands.push(`SADD ${quotedKey} ${escapedMembers.join(' ')}`)
|
|
1335
|
-
}
|
|
1336
|
-
break
|
|
1337
|
-
}
|
|
1338
|
-
case 'zset': {
|
|
1339
|
-
const zsetData = await execRemote(
|
|
1340
|
-
`ZRANGE "${escapeKeyForCommand(key)}" 0 -1 WITHSCORES`,
|
|
1341
|
-
)
|
|
1342
|
-
const lines = stripTrailingNewline(zsetData)
|
|
1343
|
-
.split(/\r?\n/)
|
|
1344
|
-
.filter((l) => l)
|
|
1345
|
-
if (lines.length >= 2) {
|
|
1346
|
-
const pairs: string[] = []
|
|
1347
|
-
// Handle odd number of lines (incomplete member/score pair)
|
|
1348
|
-
const completeCount = lines.length - (lines.length % 2)
|
|
1349
|
-
if (lines.length % 2 !== 0) {
|
|
1350
|
-
logWarning(
|
|
1351
|
-
`ZSet ${quotedKey} has odd line count, skipping incomplete entry: ${lines[lines.length - 1]}`,
|
|
1352
|
-
)
|
|
1353
|
-
}
|
|
1354
|
-
for (let i = 0; i < completeCount; i += 2) {
|
|
1355
|
-
const member = lines[i]
|
|
1356
|
-
const score = lines[i + 1]
|
|
1357
|
-
pairs.push(`${score} ${escapeValue(member)}`)
|
|
1358
|
-
}
|
|
1359
|
-
if (pairs.length > 0) {
|
|
1360
|
-
commands.push(`ZADD ${quotedKey} ${pairs.join(' ')}`)
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
break
|
|
1364
|
-
}
|
|
1365
|
-
// TODO: Add Redis Streams support (XRANGE/XADD commands)
|
|
1366
|
-
// Streams are a complex data type that would require special handling
|
|
1367
|
-
// for the message IDs and fields. Consider implementing if there's demand.
|
|
1368
|
-
default:
|
|
1369
|
-
logWarning(`Skipping key ${key} with unsupported type: ${keyType}`)
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
// Add EXPIRE if key has TTL
|
|
1373
|
-
if (ttl > 0) {
|
|
1374
|
-
commands.push(`EXPIRE ${quotedKey} ${ttl}`)
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
// Write commands to file
|
|
1379
|
-
const content = commands.join('\n') + '\n'
|
|
1380
|
-
await writeFile(outputPath, content, 'utf-8')
|
|
1381
|
-
|
|
1382
|
-
return {
|
|
1383
|
-
filePath: outputPath,
|
|
1384
|
-
warnings:
|
|
1385
|
-
keys.length === 0 ? ['Remote Redis database is empty'] : undefined,
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
// Create a backup
|
|
1390
|
-
async backup(
|
|
1391
|
-
container: ContainerConfig,
|
|
1392
|
-
outputPath: string,
|
|
1393
|
-
options: BackupOptions,
|
|
1394
|
-
): Promise<BackupResult> {
|
|
1395
|
-
return createBackup(container, outputPath, options)
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// Run a Redis command file or inline command
|
|
1399
|
-
async runScript(
|
|
1400
|
-
container: ContainerConfig,
|
|
1401
|
-
options: { file?: string; sql?: string; database?: string },
|
|
1402
|
-
): Promise<void> {
|
|
1403
|
-
const { port, version } = container
|
|
1404
|
-
const db = options.database || container.database || '0'
|
|
1405
|
-
|
|
1406
|
-
const redisCli = await this.getRedisCliPathForVersion(version)
|
|
1407
|
-
|
|
1408
|
-
if (options.file) {
|
|
1409
|
-
// Read file and pipe to redis-cli via stdin (avoids shell interpolation issues)
|
|
1410
|
-
const fileContent = await readFile(options.file, 'utf-8')
|
|
1411
|
-
const args = ['-h', '127.0.0.1', '-p', String(port), '-n', db]
|
|
1412
|
-
|
|
1413
|
-
await new Promise<void>((resolve, reject) => {
|
|
1414
|
-
const proc = spawn(redisCli, args, {
|
|
1415
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
1416
|
-
})
|
|
1417
|
-
|
|
1418
|
-
let rejected = false
|
|
1419
|
-
|
|
1420
|
-
proc.on('error', (err) => {
|
|
1421
|
-
rejected = true
|
|
1422
|
-
reject(err)
|
|
1423
|
-
})
|
|
1424
|
-
|
|
1425
|
-
proc.on('close', (code) => {
|
|
1426
|
-
if (rejected) return
|
|
1427
|
-
if (code === 0 || code === null) {
|
|
1428
|
-
resolve()
|
|
1429
|
-
} else {
|
|
1430
|
-
reject(new Error(`redis-cli exited with code ${code}`))
|
|
1431
|
-
}
|
|
1432
|
-
})
|
|
1433
|
-
|
|
1434
|
-
// Write file content to stdin and close it
|
|
1435
|
-
proc.stdin?.write(fileContent)
|
|
1436
|
-
proc.stdin?.end()
|
|
1437
|
-
})
|
|
1438
|
-
} else if (options.sql) {
|
|
1439
|
-
// Run inline command by piping to redis-cli stdin (avoids shell quoting issues on Windows)
|
|
1440
|
-
const args = ['-h', '127.0.0.1', '-p', String(port), '-n', db]
|
|
1441
|
-
|
|
1442
|
-
await new Promise<void>((resolve, reject) => {
|
|
1443
|
-
const proc = spawn(redisCli, args, {
|
|
1444
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
1445
|
-
})
|
|
1446
|
-
|
|
1447
|
-
let rejected = false
|
|
1448
|
-
|
|
1449
|
-
proc.on('error', (err) => {
|
|
1450
|
-
rejected = true
|
|
1451
|
-
reject(err)
|
|
1452
|
-
})
|
|
1453
|
-
|
|
1454
|
-
proc.on('close', (code) => {
|
|
1455
|
-
if (rejected) return
|
|
1456
|
-
if (code === 0 || code === null) {
|
|
1457
|
-
resolve()
|
|
1458
|
-
} else {
|
|
1459
|
-
reject(new Error(`redis-cli exited with code ${code}`))
|
|
1460
|
-
}
|
|
1461
|
-
})
|
|
1462
|
-
|
|
1463
|
-
// Write command to stdin and close it
|
|
1464
|
-
proc.stdin?.write(options.sql + '\n')
|
|
1465
|
-
proc.stdin?.end()
|
|
1466
|
-
})
|
|
1467
|
-
} else {
|
|
1468
|
-
throw new Error('Either file or sql option must be provided')
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
async executeQuery(
|
|
1473
|
-
container: ContainerConfig,
|
|
1474
|
-
query: string,
|
|
1475
|
-
options?: QueryOptions,
|
|
1476
|
-
): Promise<QueryResult> {
|
|
1477
|
-
const { port, version } = container
|
|
1478
|
-
const db = options?.database || container.database || '0'
|
|
1479
|
-
|
|
1480
|
-
const redisCli = await this.getRedisCliPathForVersion(version)
|
|
1481
|
-
|
|
1482
|
-
return new Promise((resolve, reject) => {
|
|
1483
|
-
const args = ['-h', '127.0.0.1', '-p', String(port), '-n', db, '--raw']
|
|
1484
|
-
|
|
1485
|
-
const proc = spawn(redisCli, args, {
|
|
1486
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1487
|
-
})
|
|
1488
|
-
|
|
1489
|
-
let stdout = ''
|
|
1490
|
-
let stderr = ''
|
|
1491
|
-
|
|
1492
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
1493
|
-
stdout += data.toString()
|
|
1494
|
-
})
|
|
1495
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
1496
|
-
stderr += data.toString()
|
|
1497
|
-
})
|
|
1498
|
-
|
|
1499
|
-
proc.on('error', reject)
|
|
1500
|
-
|
|
1501
|
-
proc.on('close', (code) => {
|
|
1502
|
-
if (code === 0 || code === null) {
|
|
1503
|
-
resolve(parseRedisResult(stdout, query))
|
|
1504
|
-
} else {
|
|
1505
|
-
reject(new Error(stderr || `redis-cli exited with code ${code}`))
|
|
1506
|
-
}
|
|
1507
|
-
})
|
|
1508
|
-
|
|
1509
|
-
// Write command to stdin and close it
|
|
1510
|
-
proc.stdin?.write(query + '\n')
|
|
1511
|
-
proc.stdin?.end()
|
|
1512
|
-
})
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
/**
|
|
1516
|
-
* List databases for Redis.
|
|
1517
|
-
* Redis uses numbered databases (0-15 by default), not named databases.
|
|
1518
|
-
* Returns the configured database number as a single-item array.
|
|
1519
|
-
*/
|
|
1520
|
-
async listDatabases(container: ContainerConfig): Promise<string[]> {
|
|
1521
|
-
// Redis has numbered databases, not named ones
|
|
1522
|
-
// Return the container's configured database
|
|
1523
|
-
return [container.database]
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
async createUser(
|
|
1527
|
-
container: ContainerConfig,
|
|
1528
|
-
options: CreateUserOptions,
|
|
1529
|
-
): Promise<UserCredentials> {
|
|
1530
|
-
const { username, password } = options
|
|
1531
|
-
assertValidUsername(username)
|
|
1532
|
-
const { port } = container
|
|
1533
|
-
const db = options.database ?? container.database ?? '0'
|
|
1534
|
-
const redisCli = await this.getRedisCliPath(container.version)
|
|
1535
|
-
|
|
1536
|
-
// Reject passwords with characters that break ACL SETUSER syntax:
|
|
1537
|
-
// '>' sets password, '#' sets hash, '<' removes password — all are ACL delimiters.
|
|
1538
|
-
// Whitespace and newlines would split the command unexpectedly.
|
|
1539
|
-
if (/[>#<\s\n\r]/.test(password)) {
|
|
1540
|
-
throw new Error(
|
|
1541
|
-
'Password contains invalid characters for Redis ACL. Passwords must not contain ">", "#", "<", whitespace, or newlines.',
|
|
1542
|
-
)
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
// ACL SETUSER is idempotent - sets user with full access
|
|
1546
|
-
// Pass the full ACL command via stdin to avoid exposing the password in argv
|
|
1547
|
-
const connArgs = ['-h', '127.0.0.1', '-p', String(port), '-n', db]
|
|
1548
|
-
|
|
1549
|
-
await new Promise<void>((resolve, reject) => {
|
|
1550
|
-
const proc = spawn(redisCli, connArgs, {
|
|
1551
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1552
|
-
})
|
|
1553
|
-
|
|
1554
|
-
let stderr = ''
|
|
1555
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
1556
|
-
stderr += data.toString()
|
|
1557
|
-
})
|
|
1558
|
-
|
|
1559
|
-
proc.stdin?.write(`ACL SETUSER ${username} on >${password} ~* &* +@all\n`)
|
|
1560
|
-
proc.stdin?.end()
|
|
1561
|
-
|
|
1562
|
-
proc.on('close', (code) => {
|
|
1563
|
-
if (code === 0) resolve()
|
|
1564
|
-
else reject(new Error(`Failed to create user: ${stderr}`))
|
|
1565
|
-
})
|
|
1566
|
-
proc.on('error', reject)
|
|
1567
|
-
})
|
|
1568
|
-
logDebug(`Created Redis user: ${username}`)
|
|
1569
|
-
|
|
1570
|
-
const connectionString = `redis://${encodeURIComponent(username)}:${encodeURIComponent(password)}@127.0.0.1:${port}/${db}`
|
|
1571
|
-
|
|
1572
|
-
return {
|
|
1573
|
-
username,
|
|
1574
|
-
password,
|
|
1575
|
-
connectionString,
|
|
1576
|
-
engine: container.engine,
|
|
1577
|
-
container: container.name,
|
|
1578
|
-
database: db,
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
export const redisEngine = new RedisEngine()
|