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