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