@supatype/cli 0.1.0-alpha.9 → 0.1.1

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 (407) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-test.log +285 -69
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/assets/supatype-logo-wordmark.ascii.txt +6 -0
  5. package/bin/dev-entry.ts +2 -1
  6. package/dist/app/framework.js +1 -3
  7. package/dist/app/framework.js.map +1 -1
  8. package/dist/app/proxy-dev-app.d.ts +14 -0
  9. package/dist/app/proxy-dev-app.d.ts.map +1 -1
  10. package/dist/app/proxy-dev-app.js +110 -6
  11. package/dist/app/proxy-dev-app.js.map +1 -1
  12. package/dist/app-config.d.ts +10 -0
  13. package/dist/app-config.d.ts.map +1 -1
  14. package/dist/app-config.js +72 -0
  15. package/dist/app-config.js.map +1 -1
  16. package/dist/assets/supatype-logo-wordmark.ascii.txt +6 -0
  17. package/dist/binary-cache.d.ts +19 -7
  18. package/dist/binary-cache.d.ts.map +1 -1
  19. package/dist/binary-cache.js +92 -46
  20. package/dist/binary-cache.js.map +1 -1
  21. package/dist/cli.d.ts +1 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +17 -2
  24. package/dist/cli.js.map +1 -1
  25. package/dist/commands/add.d.ts +3 -0
  26. package/dist/commands/add.d.ts.map +1 -0
  27. package/dist/commands/add.js +86 -0
  28. package/dist/commands/add.js.map +1 -0
  29. package/dist/commands/admin.d.ts +28 -1
  30. package/dist/commands/admin.d.ts.map +1 -1
  31. package/dist/commands/admin.js +297 -149
  32. package/dist/commands/admin.js.map +1 -1
  33. package/dist/commands/adopt.d.ts +3 -0
  34. package/dist/commands/adopt.d.ts.map +1 -0
  35. package/dist/commands/adopt.js +55 -0
  36. package/dist/commands/adopt.js.map +1 -0
  37. package/dist/commands/app.d.ts.map +1 -1
  38. package/dist/commands/app.js +20 -17
  39. package/dist/commands/app.js.map +1 -1
  40. package/dist/commands/cache.d.ts.map +1 -1
  41. package/dist/commands/cache.js +11 -10
  42. package/dist/commands/cache.js.map +1 -1
  43. package/dist/commands/cloud.d.ts +4 -9
  44. package/dist/commands/cloud.d.ts.map +1 -1
  45. package/dist/commands/cloud.js +75 -125
  46. package/dist/commands/cloud.js.map +1 -1
  47. package/dist/commands/db.d.ts.map +1 -1
  48. package/dist/commands/db.js +37 -58
  49. package/dist/commands/db.js.map +1 -1
  50. package/dist/commands/deploy.d.ts.map +1 -1
  51. package/dist/commands/deploy.js +140 -96
  52. package/dist/commands/deploy.js.map +1 -1
  53. package/dist/commands/dev.d.ts.map +1 -1
  54. package/dist/commands/dev.js +74 -39
  55. package/dist/commands/dev.js.map +1 -1
  56. package/dist/commands/diff.d.ts.map +1 -1
  57. package/dist/commands/diff.js +39 -39
  58. package/dist/commands/diff.js.map +1 -1
  59. package/dist/commands/doctor.d.ts +3 -0
  60. package/dist/commands/doctor.d.ts.map +1 -0
  61. package/dist/commands/doctor.js +78 -0
  62. package/dist/commands/doctor.js.map +1 -0
  63. package/dist/commands/engine.d.ts.map +1 -1
  64. package/dist/commands/engine.js +5 -4
  65. package/dist/commands/engine.js.map +1 -1
  66. package/dist/commands/functions.d.ts.map +1 -1
  67. package/dist/commands/functions.js +172 -119
  68. package/dist/commands/functions.js.map +1 -1
  69. package/dist/commands/generate.d.ts.map +1 -1
  70. package/dist/commands/generate.js +5 -4
  71. package/dist/commands/generate.js.map +1 -1
  72. package/dist/commands/init.d.ts +35 -1
  73. package/dist/commands/init.d.ts.map +1 -1
  74. package/dist/commands/init.js +883 -107
  75. package/dist/commands/init.js.map +1 -1
  76. package/dist/commands/introspect.d.ts +3 -0
  77. package/dist/commands/introspect.d.ts.map +1 -0
  78. package/dist/commands/introspect.js +35 -0
  79. package/dist/commands/introspect.js.map +1 -0
  80. package/dist/commands/keys.d.ts +15 -1
  81. package/dist/commands/keys.d.ts.map +1 -1
  82. package/dist/commands/keys.js +46 -10
  83. package/dist/commands/keys.js.map +1 -1
  84. package/dist/commands/link-helpers.d.ts +15 -0
  85. package/dist/commands/link-helpers.d.ts.map +1 -0
  86. package/dist/commands/link-helpers.js +225 -0
  87. package/dist/commands/link-helpers.js.map +1 -0
  88. package/dist/commands/logs.d.ts.map +1 -1
  89. package/dist/commands/logs.js +5 -4
  90. package/dist/commands/logs.js.map +1 -1
  91. package/dist/commands/migrate-from-v1.d.ts.map +1 -1
  92. package/dist/commands/migrate-from-v1.js +3 -2
  93. package/dist/commands/migrate-from-v1.js.map +1 -1
  94. package/dist/commands/migrate.d.ts.map +1 -1
  95. package/dist/commands/migrate.js +119 -26
  96. package/dist/commands/migrate.js.map +1 -1
  97. package/dist/commands/pg.d.ts.map +1 -1
  98. package/dist/commands/pg.js +11 -12
  99. package/dist/commands/pg.js.map +1 -1
  100. package/dist/commands/plugins.d.ts.map +1 -1
  101. package/dist/commands/plugins.js +55 -46
  102. package/dist/commands/plugins.js.map +1 -1
  103. package/dist/commands/pull.d.ts.map +1 -1
  104. package/dist/commands/pull.js +33 -5
  105. package/dist/commands/pull.js.map +1 -1
  106. package/dist/commands/push.d.ts.map +1 -1
  107. package/dist/commands/push.js +111 -138
  108. package/dist/commands/push.js.map +1 -1
  109. package/dist/commands/seed.d.ts.map +1 -1
  110. package/dist/commands/seed.js +4 -3
  111. package/dist/commands/seed.js.map +1 -1
  112. package/dist/commands/self-host.d.ts +2 -2
  113. package/dist/commands/self-host.d.ts.map +1 -1
  114. package/dist/commands/self-host.js +65 -50
  115. package/dist/commands/self-host.js.map +1 -1
  116. package/dist/commands/self-update.d.ts.map +1 -1
  117. package/dist/commands/self-update.js +3 -2
  118. package/dist/commands/self-update.js.map +1 -1
  119. package/dist/commands/status.d.ts +1 -1
  120. package/dist/commands/status.d.ts.map +1 -1
  121. package/dist/commands/status.js +95 -29
  122. package/dist/commands/status.js.map +1 -1
  123. package/dist/commands/types.d.ts.map +1 -1
  124. package/dist/commands/types.js +3 -2
  125. package/dist/commands/types.js.map +1 -1
  126. package/dist/commands/update.d.ts.map +1 -1
  127. package/dist/commands/update.js +54 -21
  128. package/dist/commands/update.js.map +1 -1
  129. package/dist/compose-rename.d.ts +10 -0
  130. package/dist/compose-rename.d.ts.map +1 -0
  131. package/dist/compose-rename.js +67 -0
  132. package/dist/compose-rename.js.map +1 -0
  133. package/dist/config.d.ts +2 -1
  134. package/dist/config.d.ts.map +1 -1
  135. package/dist/config.js.map +1 -1
  136. package/dist/dev-compose.d.ts +26 -0
  137. package/dist/dev-compose.d.ts.map +1 -1
  138. package/dist/dev-compose.js +357 -79
  139. package/dist/dev-compose.js.map +1 -1
  140. package/dist/dev-log-bus.d.ts +30 -0
  141. package/dist/dev-log-bus.d.ts.map +1 -0
  142. package/dist/dev-log-bus.js +87 -0
  143. package/dist/dev-log-bus.js.map +1 -0
  144. package/dist/dev-log-filter.d.ts +10 -0
  145. package/dist/dev-log-filter.d.ts.map +1 -0
  146. package/dist/dev-log-filter.js +36 -0
  147. package/dist/dev-log-filter.js.map +1 -0
  148. package/dist/dev-logo.d.ts +12 -0
  149. package/dist/dev-logo.d.ts.map +1 -0
  150. package/dist/dev-logo.js +56 -0
  151. package/dist/dev-logo.js.map +1 -0
  152. package/dist/dev-ports.d.ts +27 -0
  153. package/dist/dev-ports.d.ts.map +1 -0
  154. package/dist/dev-ports.js +171 -0
  155. package/dist/dev-ports.js.map +1 -0
  156. package/dist/dev-session-lock.d.ts +25 -0
  157. package/dist/dev-session-lock.d.ts.map +1 -0
  158. package/dist/dev-session-lock.js +81 -0
  159. package/dist/dev-session-lock.js.map +1 -0
  160. package/dist/dev-session.d.ts +26 -0
  161. package/dist/dev-session.d.ts.map +1 -0
  162. package/dist/dev-session.js +106 -0
  163. package/dist/dev-session.js.map +1 -0
  164. package/dist/dev-shutdown.d.ts +25 -0
  165. package/dist/dev-shutdown.d.ts.map +1 -0
  166. package/dist/dev-shutdown.js +114 -0
  167. package/dist/dev-shutdown.js.map +1 -0
  168. package/dist/dev-task-colors.d.ts +13 -0
  169. package/dist/dev-task-colors.d.ts.map +1 -0
  170. package/dist/dev-task-colors.js +43 -0
  171. package/dist/dev-task-colors.js.map +1 -0
  172. package/dist/dev-tui.d.ts +24 -0
  173. package/dist/dev-tui.d.ts.map +1 -0
  174. package/dist/dev-tui.js +188 -0
  175. package/dist/dev-tui.js.map +1 -0
  176. package/dist/diff-output.d.ts +5 -1
  177. package/dist/diff-output.d.ts.map +1 -1
  178. package/dist/diff-output.js +69 -0
  179. package/dist/diff-output.js.map +1 -1
  180. package/dist/docker-runtime.d.ts +30 -0
  181. package/dist/docker-runtime.d.ts.map +1 -0
  182. package/dist/docker-runtime.js +118 -0
  183. package/dist/docker-runtime.js.map +1 -0
  184. package/dist/engine-client.d.ts +10 -1
  185. package/dist/engine-client.d.ts.map +1 -1
  186. package/dist/engine-client.js +76 -17
  187. package/dist/engine-client.js.map +1 -1
  188. package/dist/engine-push-output.d.ts +17 -0
  189. package/dist/engine-push-output.d.ts.map +1 -0
  190. package/dist/engine-push-output.js +64 -0
  191. package/dist/engine-push-output.js.map +1 -0
  192. package/dist/ensure-binary.js +2 -2
  193. package/dist/ensure-binary.js.map +1 -1
  194. package/dist/env-file.d.ts +5 -0
  195. package/dist/env-file.d.ts.map +1 -0
  196. package/dist/env-file.js +33 -0
  197. package/dist/env-file.js.map +1 -0
  198. package/dist/gitignore.d.ts +8 -0
  199. package/dist/gitignore.d.ts.map +1 -0
  200. package/dist/gitignore.js +41 -0
  201. package/dist/gitignore.js.map +1 -0
  202. package/dist/kong-config.d.ts +9 -0
  203. package/dist/kong-config.d.ts.map +1 -1
  204. package/dist/kong-config.js +18 -1
  205. package/dist/kong-config.js.map +1 -1
  206. package/dist/link.d.ts +66 -0
  207. package/dist/link.d.ts.map +1 -0
  208. package/dist/link.js +160 -0
  209. package/dist/link.js.map +1 -0
  210. package/dist/process-manager.d.ts +8 -0
  211. package/dist/process-manager.d.ts.map +1 -1
  212. package/dist/process-manager.js +53 -9
  213. package/dist/process-manager.js.map +1 -1
  214. package/dist/project-config.d.ts +30 -3
  215. package/dist/project-config.d.ts.map +1 -1
  216. package/dist/project-config.js +37 -4
  217. package/dist/project-config.js.map +1 -1
  218. package/dist/prompts.d.ts +3 -0
  219. package/dist/prompts.d.ts.map +1 -0
  220. package/dist/prompts.js +3 -0
  221. package/dist/prompts.js.map +1 -0
  222. package/dist/pull-utils.d.ts +50 -14
  223. package/dist/pull-utils.d.ts.map +1 -1
  224. package/dist/pull-utils.js +152 -12
  225. package/dist/pull-utils.js.map +1 -1
  226. package/dist/resolve-target.d.ts +86 -0
  227. package/dist/resolve-target.d.ts.map +1 -0
  228. package/dist/resolve-target.js +291 -0
  229. package/dist/resolve-target.js.map +1 -0
  230. package/dist/restore-system-relation-targets.d.ts +3 -0
  231. package/dist/restore-system-relation-targets.d.ts.map +1 -0
  232. package/dist/restore-system-relation-targets.js +45 -0
  233. package/dist/restore-system-relation-targets.js.map +1 -0
  234. package/dist/runtime-routes.d.ts.map +1 -1
  235. package/dist/runtime-routes.js +7 -0
  236. package/dist/runtime-routes.js.map +1 -1
  237. package/dist/schema-ast-v2.d.ts +1 -1
  238. package/dist/schema-ast-v2.d.ts.map +1 -1
  239. package/dist/schema-ast-v2.js +2 -2
  240. package/dist/schema-ast-v2.js.map +1 -1
  241. package/dist/schema-sources.d.ts +40 -0
  242. package/dist/schema-sources.d.ts.map +1 -0
  243. package/dist/schema-sources.js +183 -0
  244. package/dist/schema-sources.js.map +1 -0
  245. package/dist/scripts/postinstall.js +5 -1
  246. package/dist/scripts/postinstall.js.map +1 -1
  247. package/dist/self-host-compose.d.ts +37 -1
  248. package/dist/self-host-compose.d.ts.map +1 -1
  249. package/dist/self-host-compose.js +234 -43
  250. package/dist/self-host-compose.js.map +1 -1
  251. package/dist/storage-provision.d.ts +4 -0
  252. package/dist/storage-provision.d.ts.map +1 -1
  253. package/dist/storage-provision.js +24 -2
  254. package/dist/storage-provision.js.map +1 -1
  255. package/dist/supatype-eval-1781522769253.d.mts +2 -0
  256. package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
  257. package/dist/supatype-eval-1781522769253.mjs +3 -0
  258. package/dist/supatype-eval-1781522769253.mjs.map +1 -0
  259. package/dist/systemd.js +2 -2
  260. package/dist/systemd.js.map +1 -1
  261. package/dist/target-client.d.ts +10 -0
  262. package/dist/target-client.d.ts.map +1 -0
  263. package/dist/target-client.js +22 -0
  264. package/dist/target-client.js.map +1 -0
  265. package/dist/type-extractor.d.ts +11 -0
  266. package/dist/type-extractor.d.ts.map +1 -1
  267. package/dist/type-extractor.js +95 -8
  268. package/dist/type-extractor.js.map +1 -1
  269. package/dist/ui/brand.d.ts +9 -0
  270. package/dist/ui/brand.d.ts.map +1 -0
  271. package/dist/ui/brand.js +11 -0
  272. package/dist/ui/brand.js.map +1 -0
  273. package/dist/ui/confirm.d.ts +12 -0
  274. package/dist/ui/confirm.d.ts.map +1 -0
  275. package/dist/ui/confirm.js +28 -0
  276. package/dist/ui/confirm.js.map +1 -0
  277. package/dist/ui/fatal.d.ts +10 -0
  278. package/dist/ui/fatal.d.ts.map +1 -0
  279. package/dist/ui/fatal.js +34 -0
  280. package/dist/ui/fatal.js.map +1 -0
  281. package/dist/ui/index.d.ts +9 -0
  282. package/dist/ui/index.d.ts.map +1 -0
  283. package/dist/ui/index.js +9 -0
  284. package/dist/ui/index.js.map +1 -0
  285. package/dist/ui/interactive.d.ts +3 -0
  286. package/dist/ui/interactive.d.ts.map +1 -0
  287. package/dist/ui/interactive.js +5 -0
  288. package/dist/ui/interactive.js.map +1 -0
  289. package/dist/ui/messages.d.ts +10 -0
  290. package/dist/ui/messages.d.ts.map +1 -0
  291. package/dist/ui/messages.js +35 -0
  292. package/dist/ui/messages.js.map +1 -0
  293. package/dist/ui/next-steps.d.ts +3 -0
  294. package/dist/ui/next-steps.d.ts.map +1 -0
  295. package/dist/ui/next-steps.js +10 -0
  296. package/dist/ui/next-steps.js.map +1 -0
  297. package/dist/ui/progress.d.ts +5 -0
  298. package/dist/ui/progress.d.ts.map +1 -0
  299. package/dist/ui/progress.js +24 -0
  300. package/dist/ui/progress.js.map +1 -0
  301. package/dist/ui/prompts.d.ts +14 -0
  302. package/dist/ui/prompts.d.ts.map +1 -0
  303. package/dist/ui/prompts.js +34 -0
  304. package/dist/ui/prompts.js.map +1 -0
  305. package/package.json +5 -2
  306. package/src/app/framework.ts +1 -3
  307. package/src/app/proxy-dev-app.ts +114 -6
  308. package/src/app-config.ts +80 -0
  309. package/src/binary-cache.ts +102 -52
  310. package/src/cli.ts +16 -2
  311. package/src/commands/add.ts +97 -0
  312. package/src/commands/admin.ts +381 -190
  313. package/src/commands/adopt.ts +82 -0
  314. package/src/commands/app.ts +20 -17
  315. package/src/commands/cache.ts +11 -10
  316. package/src/commands/cloud.ts +91 -142
  317. package/src/commands/db.ts +40 -63
  318. package/src/commands/deploy.ts +186 -126
  319. package/src/commands/dev.ts +98 -55
  320. package/src/commands/diff.ts +52 -43
  321. package/src/commands/doctor.ts +103 -0
  322. package/src/commands/engine.ts +5 -4
  323. package/src/commands/functions.ts +187 -123
  324. package/src/commands/generate.ts +5 -4
  325. package/src/commands/init.ts +1087 -104
  326. package/src/commands/introspect.ts +48 -0
  327. package/src/commands/keys.ts +56 -14
  328. package/src/commands/link-helpers.ts +273 -0
  329. package/src/commands/logs.ts +5 -4
  330. package/src/commands/migrate-from-v1.ts +3 -2
  331. package/src/commands/migrate.ts +167 -27
  332. package/src/commands/pg.ts +13 -18
  333. package/src/commands/plugins.ts +55 -46
  334. package/src/commands/pull.ts +38 -9
  335. package/src/commands/push.ts +148 -175
  336. package/src/commands/seed.ts +5 -4
  337. package/src/commands/self-host.ts +85 -54
  338. package/src/commands/self-update.ts +3 -2
  339. package/src/commands/status.ts +102 -33
  340. package/src/commands/types.ts +3 -2
  341. package/src/commands/update.ts +59 -23
  342. package/src/compose-rename.ts +76 -0
  343. package/src/config.ts +2 -1
  344. package/src/dev-compose.ts +462 -76
  345. package/src/dev-log-bus.ts +101 -0
  346. package/src/dev-log-filter.ts +32 -0
  347. package/src/dev-logo.ts +61 -0
  348. package/src/dev-ports.ts +212 -0
  349. package/src/dev-session-lock.ts +101 -0
  350. package/src/dev-session.ts +130 -0
  351. package/src/dev-shutdown.ts +147 -0
  352. package/src/dev-task-colors.ts +47 -0
  353. package/src/dev-tui.ts +232 -0
  354. package/src/diff-output.ts +79 -1
  355. package/src/docker-runtime.ts +151 -0
  356. package/src/engine-client.ts +81 -17
  357. package/src/engine-push-output.ts +75 -0
  358. package/src/ensure-binary.ts +2 -2
  359. package/src/env-file.ts +37 -0
  360. package/src/gitignore.ts +48 -0
  361. package/src/kong-config.ts +24 -1
  362. package/src/link.ts +243 -0
  363. package/src/process-manager.ts +66 -10
  364. package/src/project-config.ts +62 -7
  365. package/src/prompts.ts +2 -0
  366. package/src/pull-utils.ts +217 -23
  367. package/src/resolve-target.ts +419 -0
  368. package/src/restore-system-relation-targets.ts +45 -0
  369. package/src/runtime-routes.ts +7 -0
  370. package/src/schema-ast-v2.ts +2 -1
  371. package/src/schema-sources.ts +248 -0
  372. package/src/scripts/postinstall.ts +7 -1
  373. package/src/self-host-compose.ts +262 -46
  374. package/src/storage-provision.ts +33 -1
  375. package/src/supatype-eval-1781522769253.mts +1 -0
  376. package/src/systemd.ts +2 -2
  377. package/src/target-client.ts +40 -0
  378. package/src/type-extractor.ts +124 -11
  379. package/src/ui/README.md +17 -0
  380. package/src/ui/brand.ts +12 -0
  381. package/src/ui/confirm.ts +38 -0
  382. package/src/ui/fatal.ts +43 -0
  383. package/src/ui/index.ts +8 -0
  384. package/src/ui/interactive.ts +4 -0
  385. package/src/ui/messages.ts +43 -0
  386. package/src/ui/next-steps.ts +10 -0
  387. package/src/ui/progress.ts +28 -0
  388. package/src/ui/prompts.ts +40 -0
  389. package/tests/admin-ensure.test.ts +59 -0
  390. package/tests/cli-help.test.ts +27 -2
  391. package/tests/config.test.ts +29 -2
  392. package/tests/dev-ports.test.ts +41 -0
  393. package/tests/dev-session-lock.test.ts +54 -0
  394. package/tests/dev-ui.test.ts +162 -0
  395. package/tests/docker-runtime.test.ts +236 -0
  396. package/tests/engine-push-output.test.ts +67 -0
  397. package/tests/init.test.ts +197 -18
  398. package/tests/link.test.ts +148 -0
  399. package/tests/minisign.test.ts +102 -0
  400. package/tests/proxy-dev-app.test.ts +45 -1
  401. package/tests/pull-utils.test.ts +5 -4
  402. package/tests/runtime-contract.test.ts +186 -2
  403. package/tests/schema-sources.test.ts +119 -0
  404. package/tests/storage-provision.test.ts +100 -0
  405. package/tests/ui-confirm.test.ts +41 -0
  406. package/tests/ui-messages.test.ts +66 -0
  407. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Graceful shutdown for `supatype dev` — SIGINT, TUI Ctrl+C, terminal close,
3
+ * and a synchronous compose-down fallback on process exit.
4
+ */
5
+
6
+ import { existsSync } from "node:fs"
7
+ import { endDevSession } from "./dev-session.js"
8
+ import { clearDevSessionLock } from "./dev-session-lock.js"
9
+ import { runDockerCompose } from "./self-host-compose.js"
10
+
11
+ export interface DevComposeShutdownFallback {
12
+ cwd: string
13
+ composePath: string
14
+ composeProject: string
15
+ }
16
+
17
+ let shutdownWork: (() => Promise<void>) | null = null
18
+ let composeFallback: DevComposeShutdownFallback | null = null
19
+ let shutdownCwd: string | null = null
20
+ let shuttingDown = false
21
+ let shutdownCompleted = false
22
+ let forceQuitRequested = false
23
+ let hooksRegistered = false
24
+
25
+ function onSignal(): void {
26
+ void runDevShutdown()
27
+ }
28
+
29
+ function onStdinClose(): void {
30
+ if (!process.stdin.isTTY) return
31
+ void runDevShutdown()
32
+ }
33
+
34
+ function syncComposeDownFallback(): void {
35
+ if (shutdownCompleted || !composeFallback) return
36
+ try {
37
+ runDockerCompose(
38
+ composeFallback.composePath,
39
+ ["down"],
40
+ composeFallback.cwd,
41
+ composeFallback.composeProject,
42
+ { quiet: true },
43
+ )
44
+ } catch {
45
+ // best-effort — process is exiting
46
+ }
47
+ if (shutdownCwd) clearDevSessionLock(shutdownCwd)
48
+ }
49
+
50
+ function onProcessExit(): void {
51
+ syncComposeDownFallback()
52
+ }
53
+
54
+ export interface RegisterDevShutdownOptions {
55
+ /** Sync `docker compose down` when async teardown cannot finish (terminal close, kill). */
56
+ compose?: DevComposeShutdownFallback
57
+ /** Project root — clears `.supatype/dev-session.json` after shutdown. */
58
+ cwd?: string
59
+ }
60
+
61
+ /** Register async teardown (stop children, compose down, etc.). Call once per dev session. */
62
+ export function registerDevShutdown(
63
+ work: () => Promise<void>,
64
+ opts?: RegisterDevShutdownOptions,
65
+ ): void {
66
+ shutdownWork = work
67
+ composeFallback = opts?.compose ?? null
68
+ shutdownCwd = opts?.cwd ?? opts?.compose?.cwd ?? null
69
+
70
+ if (hooksRegistered) return
71
+ hooksRegistered = true
72
+
73
+ process.on("SIGINT", onSignal)
74
+ process.on("SIGTERM", onSignal)
75
+ if (process.platform === "win32") {
76
+ process.on("SIGBREAK", onSignal)
77
+ }
78
+ process.on("exit", onProcessExit)
79
+
80
+ if (process.stdin.isTTY) {
81
+ process.stdin.on("end", onStdinClose)
82
+ process.stdin.on("close", onStdinClose)
83
+ }
84
+ }
85
+
86
+ /** TUI Ctrl+C — do not re-emit SIGINT (avoids double-fire on Windows raw mode). */
87
+ export function requestDevShutdown(): void {
88
+ void runDevShutdown()
89
+ }
90
+
91
+ export function isDevShuttingDown(): boolean {
92
+ return shuttingDown
93
+ }
94
+
95
+ /** @internal Tests — reset module state between cases. */
96
+ export function resetDevShutdownForTests(): void {
97
+ shutdownWork = null
98
+ composeFallback = null
99
+ shutdownCwd = null
100
+ shuttingDown = false
101
+ shutdownCompleted = false
102
+ forceQuitRequested = false
103
+ hooksRegistered = false
104
+ }
105
+
106
+ async function runDevShutdown(): Promise<void> {
107
+ if (shuttingDown) {
108
+ if (!forceQuitRequested) {
109
+ forceQuitRequested = true
110
+ process.stderr.write(
111
+ "\n[supatype] Still shutting down (stopping Docker)… press Ctrl+C again to force quit.\n",
112
+ )
113
+ return
114
+ }
115
+ try {
116
+ endDevSession()
117
+ } catch {
118
+ // best-effort terminal restore
119
+ }
120
+ process.stderr.write("\n[supatype] Forced quit — Docker containers may still be running.\n")
121
+ process.stdout.write("\n")
122
+ process.exit(130)
123
+ }
124
+ shuttingDown = true
125
+
126
+ try {
127
+ endDevSession()
128
+ process.stdout.write("\n")
129
+ await shutdownWork?.()
130
+ shutdownCompleted = true
131
+ if (shutdownCwd) clearDevSessionLock(shutdownCwd)
132
+ process.exit(0)
133
+ } catch (err) {
134
+ process.stderr.write(`[supatype] Shutdown failed: ${(err as Error).message}\n`)
135
+ process.stderr.write(
136
+ "[supatype] Docker containers may still be running — try: supatype self-host compose down\n",
137
+ )
138
+ syncComposeDownFallback()
139
+ shutdownCompleted = true
140
+ process.exit(1)
141
+ }
142
+ }
143
+
144
+ /** Whether a compose fallback was registered (tests). */
145
+ export function hasComposeShutdownFallback(): boolean {
146
+ return composeFallback !== null && existsSync(composeFallback.composePath)
147
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * ANSI colours for `supatype dev` TUI task list and log panes.
3
+ */
4
+
5
+ import { BOLD, DIM, RESET } from "./ui/brand.js"
6
+
7
+ export { RESET, DIM, BOLD }
8
+
9
+ /** Purple for orchestrator logs, green for Vite/app, etc. */
10
+ export function taskColor(taskId: string): string {
11
+ switch (taskId) {
12
+ case "stack":
13
+ return "\x1b[35m"
14
+ case "app":
15
+ return "\x1b[32m"
16
+ case "studio":
17
+ return "\x1b[96m"
18
+ case "server":
19
+ return "\x1b[32m"
20
+ case "postgrest":
21
+ return "\x1b[36m"
22
+ default:
23
+ return "\x1b[37m"
24
+ }
25
+ }
26
+
27
+ export function levelColor(line: string): string | null {
28
+ if (line.startsWith("✗ ")) return "\x1b[31m"
29
+ if (line.startsWith("⚠ ")) return "\x1b[33m"
30
+ return null
31
+ }
32
+
33
+ /** Drop redundant prefix — task pane is already labelled supatype. */
34
+ export function normalizeStackLogLine(line: string): string {
35
+ return line.replace(/^\[supatype\]\s*/, "")
36
+ }
37
+
38
+ export function colorizeLogLine(taskId: string, line: string): string {
39
+ const level = levelColor(line)
40
+ const base = level ?? taskColor(taskId)
41
+ return `${base}${line}${RESET}`
42
+ }
43
+
44
+ export function colorizeTaskLabel(taskId: string, label: string, focused: boolean): string {
45
+ const style = focused ? BOLD + taskColor(taskId) : taskColor(taskId)
46
+ return `${style}${label}${RESET}`
47
+ }
package/src/dev-tui.ts ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Interactive terminal UI for `supatype dev` — task list + focused log pane.
3
+ */
4
+
5
+ import { stripAnsi } from "./dev-log-filter.js"
6
+ import type { DevLogBus } from "./dev-log-bus.js"
7
+ import {
8
+ colorizeLogLine,
9
+ colorizeTaskLabel,
10
+ DIM,
11
+ RESET,
12
+ taskColor,
13
+ } from "./dev-task-colors.js"
14
+ import {
15
+ colorLogoLines,
16
+ layoutLogoBlock,
17
+ logoRowCount,
18
+ pickLogoLines,
19
+ } from "./dev-logo.js"
20
+ import { requestDevShutdown } from "./dev-shutdown.js"
21
+
22
+ const ENTER_ALT_SCREEN = "\x1b[?1049h"
23
+ const LEAVE_ALT_SCREEN = "\x1b[?1049l"
24
+ const HIDE_CURSOR = "\x1b[?25l"
25
+ const SHOW_CURSOR = "\x1b[?25h"
26
+ const CLEAR_SCREEN = "\x1b[2J\x1b[H"
27
+
28
+ const TASK_COL_WIDTH = 22
29
+ const MIN_WIDTH = 60
30
+ const MIN_HEIGHT = 14
31
+
32
+ export class DevTui {
33
+ private active = false
34
+ private scrollFromBottom = 0
35
+ private renderPending = false
36
+ private unsubscribeBus: (() => void) | null = null
37
+ private readonly onResize = (): void => this.scheduleRender()
38
+ private readonly onData = (chunk: Buffer): void => this.handleInput(chunk)
39
+
40
+ constructor(private readonly bus: DevLogBus) {}
41
+
42
+ start(): void {
43
+ if (this.active) return
44
+ this.active = true
45
+ this.scrollFromBottom = 0
46
+
47
+ if (process.stdin.isTTY) {
48
+ process.stdin.setRawMode(true)
49
+ process.stdin.resume()
50
+ process.stdin.on("data", this.onData)
51
+ }
52
+ process.stdout.on("resize", this.onResize)
53
+
54
+ process.stdout.write(ENTER_ALT_SCREEN + HIDE_CURSOR)
55
+ this.unsubscribeBus = this.bus.onUpdate(() => this.scheduleRender())
56
+ this.scheduleRender()
57
+ }
58
+
59
+ stop(): void {
60
+ if (!this.active) return
61
+ this.active = false
62
+
63
+ this.unsubscribeBus?.()
64
+ this.unsubscribeBus = null
65
+
66
+ process.stdout.off("resize", this.onResize)
67
+ if (process.stdin.isTTY) {
68
+ process.stdin.off("data", this.onData)
69
+ process.stdin.setRawMode(false)
70
+ process.stdin.pause()
71
+ }
72
+
73
+ process.stdout.write(SHOW_CURSOR + LEAVE_ALT_SCREEN)
74
+ }
75
+
76
+ private scheduleRender(): void {
77
+ if (!this.active || this.renderPending) return
78
+ this.renderPending = true
79
+ setImmediate(() => {
80
+ this.renderPending = false
81
+ if (this.active) this.render()
82
+ })
83
+ }
84
+
85
+ private handleInput(chunk: Buffer): void {
86
+ const key = chunk.toString()
87
+
88
+ if (key === "\x03") {
89
+ requestDevShutdown()
90
+ return
91
+ }
92
+
93
+ switch (key) {
94
+ case "j":
95
+ case "\x1b[B":
96
+ this.bus.focusNext()
97
+ this.scrollFromBottom = 0
98
+ break
99
+ case "k":
100
+ case "\x1b[A":
101
+ this.bus.focusPrevious()
102
+ this.scrollFromBottom = 0
103
+ break
104
+ case "u":
105
+ this.scrollFromBottom = Math.min(
106
+ this.scrollFromBottom + 3,
107
+ this.maxScroll(this.bus.getFocusedTaskId()),
108
+ )
109
+ break
110
+ case "d":
111
+ this.scrollFromBottom = Math.max(this.scrollFromBottom - 3, 0)
112
+ break
113
+ case "g":
114
+ this.scrollFromBottom = this.maxScroll(this.bus.getFocusedTaskId())
115
+ break
116
+ case "G":
117
+ this.scrollFromBottom = 0
118
+ break
119
+ default:
120
+ return
121
+ }
122
+ this.scheduleRender()
123
+ }
124
+
125
+ private maxScroll(taskId: string): number {
126
+ const task = this.bus.getTask(taskId)
127
+ if (!task) return 0
128
+ const logHeight = this.splitPaneHeight()
129
+ return Math.max(0, task.lines.length - logHeight)
130
+ }
131
+
132
+ private chromeRowCount(): number {
133
+ // logo + separator + keybind hint + footer
134
+ return logoRowCount() + 3
135
+ }
136
+
137
+ private splitPaneHeight(): number {
138
+ const rows = Math.max(MIN_HEIGHT, process.stdout.rows ?? 24)
139
+ return Math.max(4, rows - this.chromeRowCount())
140
+ }
141
+
142
+ private render(): void {
143
+ const cols = Math.max(MIN_WIDTH, process.stdout.columns ?? 80)
144
+ const logWidth = cols - TASK_COL_WIDTH - 1
145
+ const logHeight = this.splitPaneHeight()
146
+
147
+ const lines: string[] = []
148
+
149
+ const logoPlain = layoutLogoBlock(pickLogoLines())
150
+ for (const colored of colorLogoLines(logoPlain)) {
151
+ lines.push(formatLogoRow(colored, cols))
152
+ }
153
+
154
+ lines.push(`${DIM}${"─".repeat(cols)}${RESET}`)
155
+ lines.push(
156
+ `${DIM}${truncate(
157
+ " ↑/k ↓/j task u/d scroll g/G top/bottom Ctrl+C quit",
158
+ cols,
159
+ )}${RESET}`,
160
+ )
161
+
162
+ const taskIds = this.bus.getTaskOrder()
163
+ const focusedId = this.bus.getFocusedTaskId()
164
+
165
+ for (let row = 0; row < logHeight; row++) {
166
+ let left = " ".repeat(TASK_COL_WIDTH)
167
+ const taskId = taskIds[row]
168
+ if (taskId) {
169
+ const task = this.bus.getTask(taskId)
170
+ if (task) {
171
+ const marker = taskId === focusedId ? "▶" : " "
172
+ const unread = task.unread ? " •" : " "
173
+ const label = truncate(`${marker} ${task.title}${unread}`, TASK_COL_WIDTH - 1)
174
+ left = padVisible(
175
+ colorizeTaskLabel(taskId, label, taskId === focusedId),
176
+ TASK_COL_WIDTH,
177
+ )
178
+ }
179
+ }
180
+
181
+ const logLine = this.logLineAt(focusedId, row, logHeight, logWidth)
182
+ lines.push(left + `${DIM}│${RESET}` + logLine)
183
+ }
184
+
185
+ const focusedTitle = this.bus.getTask(focusedId)?.title ?? focusedId
186
+ lines.push(`${DIM} focused:${RESET} ${colorizeTaskLabel(focusedId, focusedTitle, true)}`)
187
+
188
+ process.stdout.write(CLEAR_SCREEN + lines.join("\n"))
189
+ }
190
+
191
+ private logLineAt(taskId: string, row: number, logHeight: number, width: number): string {
192
+ const task = this.bus.getTask(taskId)
193
+ if (!task || task.lines.length === 0) {
194
+ return " ".repeat(width)
195
+ }
196
+
197
+ const total = task.lines.length
198
+ const end = total - this.scrollFromBottom
199
+ const start = Math.max(0, end - logHeight)
200
+ const index = start + row
201
+ if (index >= end || index >= total) {
202
+ return " ".repeat(width)
203
+ }
204
+
205
+ const raw = stripAnsi(task.lines[index] ?? "")
206
+ const clipped = truncate(raw, width)
207
+ return padVisible(colorizeLogLine(taskId, clipped), width)
208
+ }
209
+ }
210
+
211
+ function truncate(text: string, width: number): string {
212
+ if (width <= 0) return ""
213
+ if (text.length <= width) return text
214
+ if (width <= 1) return text.slice(0, width)
215
+ return `${text.slice(0, width - 1)}…`
216
+ }
217
+
218
+ /** Pad to visible width (ignores ANSI sequences already stripped from input). */
219
+ function padVisible(text: string, width: number): string {
220
+ const visible = stripAnsi(text)
221
+ if (visible.length >= width) return text
222
+ return text + " ".repeat(width - visible.length)
223
+ }
224
+
225
+ function formatLogoRow(colored: string, cols: number): string {
226
+ const plain = stripAnsi(colored)
227
+ if (plain.length <= cols) {
228
+ return plain.length < cols ? `${colored}${" ".repeat(cols - plain.length)}` : colored
229
+ }
230
+ const purple = taskColor("stack")
231
+ return `${purple}\x1b[1m${plain.slice(0, cols)}${RESET}`
232
+ }
@@ -1,4 +1,44 @@
1
- import type { DiffResult } from "./engine-client.js"
1
+ import type { DiffResult, Operation } from "./engine-client.js"
2
+
3
+ /** Human-readable label for a single schema operation. */
4
+ export function formatOperation(op: Operation): string {
5
+ if (typeof op.description === "string" && op.description.trim().length > 0) {
6
+ return op.description
7
+ }
8
+
9
+ const kind = typeof op.type === "string" ? op.type : typeof op.kind === "string" ? op.kind : "operation"
10
+ const raw = op as unknown as Record<string, unknown>
11
+ const table = raw["table"]
12
+ const column = raw["column"]
13
+ const index = raw["index"]
14
+ const sql = typeof op.sql === "string" ? op.sql.trim() : ""
15
+
16
+ if (kind === "add_unique_constraint" || kind === "drop_unique_constraint") {
17
+ const constraint = typeof raw["constraint"] === "string" ? raw["constraint"] : null
18
+ if (typeof table === "string" && constraint) return `${kind} ${table}.${constraint}`
19
+ }
20
+
21
+ if (kind === "create_index" || kind === "drop_index" || kind === "add_index") {
22
+ const indexName = typeof index === "string" ? index : typeof raw["name"] === "string" ? raw["name"] : null
23
+ const fields = Array.isArray(raw["fields"]) ? raw["fields"].join(", ") : null
24
+ if (indexName && fields) return `${kind} ${table}.${indexName} (${fields})`
25
+ if (indexName) return `${kind} ${table}.${indexName}`
26
+ }
27
+
28
+ if (typeof table === "string" && typeof column === "string") {
29
+ return `${kind} ${table}.${column}`
30
+ }
31
+ if (typeof table === "string") {
32
+ return `${kind} ${table}`
33
+ }
34
+
35
+ if (sql) {
36
+ const oneLine = sql.replace(/\s+/g, " ").slice(0, 120)
37
+ return `${kind}: ${oneLine}${sql.length > 120 ? "…" : ""}`
38
+ }
39
+
40
+ return kind
41
+ }
2
42
 
3
43
  /** Print engine diff warnings before the operation list. */
4
44
  export function printDiffWarnings(diff: DiffResult): void {
@@ -10,3 +50,41 @@ export function printDiffWarnings(diff: DiffResult): void {
10
50
  }
11
51
  console.log()
12
52
  }
53
+
54
+ const RISK_SYMBOL: Record<NonNullable<DiffResult["operations"][number]["risk"]>, string> = {
55
+ safe: "+",
56
+ warn: "~",
57
+ cautious: "~",
58
+ danger: "!",
59
+ destructive: "!",
60
+ }
61
+
62
+ const RISK_LEGEND: Record<NonNullable<DiffResult["operations"][number]["risk"]>, string> = {
63
+ safe: "safe",
64
+ warn: "caution",
65
+ cautious: "caution",
66
+ danger: "DANGER",
67
+ destructive: "DANGER",
68
+ }
69
+
70
+ /** Print planned schema operations from a diff result. */
71
+ export function printDiffOperations(diff: DiffResult): void {
72
+ const ops = diff.operations ?? []
73
+ if (ops.length === 0) {
74
+ console.log("No changes.")
75
+ return
76
+ }
77
+
78
+ console.log(`\n${ops.length} change(s):\n`)
79
+ for (const op of ops) {
80
+ const r = op.risk ?? "safe"
81
+ const label = op.warning ?? formatOperation(op)
82
+ console.log(` [${RISK_SYMBOL[r]}] ${label} (${RISK_LEGEND[r]})`)
83
+ }
84
+
85
+ const dangerous = ops.filter((o) => o.risk === "danger").length
86
+ if (dangerous > 0) {
87
+ console.log(`\n ${dangerous} dangerous operation(s). Review before pushing.`)
88
+ }
89
+ console.log()
90
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Docker daemon availability — used before `docker compose` and other docker CLI calls.
3
+ */
4
+
5
+ import { spawnSync } from "node:child_process"
6
+ import { platform } from "node:os"
7
+ import { endDevSession, getActiveDevSession } from "./dev-session.js"
8
+ import { isInteractive } from "./ui/interactive.js"
9
+ import { error, plain } from "./ui/messages.js"
10
+ import { clack as p, printLogo } from "./ui/prompts.js"
11
+
12
+ export type DockerDaemonProbe =
13
+ | { ok: true }
14
+ | { ok: false; reason: "cli_missing" | "daemon_unavailable"; detail?: string }
15
+
16
+ export type DockerBrandOptions = {
17
+ intro: string
18
+ }
19
+
20
+ export type DockerReportOptions = {
21
+ /** Logo + Clack intro before the error (e.g. `supatype dev` entry). */
22
+ brand?: DockerBrandOptions
23
+ }
24
+
25
+ function dockerSpawn(args: string[]) {
26
+ return spawnSync("docker", args, {
27
+ encoding: "utf8",
28
+ stdio: "pipe",
29
+ shell: process.platform === "win32",
30
+ })
31
+ }
32
+
33
+ /** Returns whether the Docker CLI is on PATH and the daemon accepts connections. */
34
+ export function probeDockerDaemon(): DockerDaemonProbe {
35
+ const version = dockerSpawn(["version", "--format", "{{.Client.Version}}"])
36
+ if (version.error && "code" in version.error && version.error.code === "ENOENT") {
37
+ return { ok: false, reason: "cli_missing" }
38
+ }
39
+
40
+ const clientVersion = (version.stdout ?? "").trim()
41
+ const versionStderr = (version.stderr ?? "").trim()
42
+ const versionDetail = `${versionStderr}${version.stdout ?? ""}`.trim()
43
+
44
+ // No client version — CLI missing or broken.
45
+ if (!clientVersion) {
46
+ return {
47
+ ok: false,
48
+ reason: "cli_missing",
49
+ ...(versionDetail ? { detail: versionDetail } : {}),
50
+ }
51
+ }
52
+
53
+ // Client is present but daemon refused (paused/stopped). `docker info` can hang
54
+ // while paused on Docker Desktop — use the version stderr and skip info.
55
+ if (version.status !== 0 && versionStderr) {
56
+ return { ok: false, reason: "daemon_unavailable", detail: versionStderr }
57
+ }
58
+
59
+ const info = dockerSpawn(["info"])
60
+ if (info.status === 0) return { ok: true }
61
+
62
+ const infoDetail = `${info.stderr ?? ""}${info.stdout ?? ""}`.trim()
63
+ const detail = infoDetail || versionDetail
64
+ return { ok: false, reason: "daemon_unavailable", ...(detail ? { detail } : {}) }
65
+ }
66
+
67
+ function shouldShowDockerDetail(probe: Extract<DockerDaemonProbe, { ok: false }>): boolean {
68
+ if (!probe.detail) return false
69
+ const lower = probe.detail.toLowerCase()
70
+ if (probe.reason !== "daemon_unavailable") return true
71
+ // Skip raw daemon stderr when we already print a clearer hint.
72
+ return !lower.includes("paused") && !lower.includes("cannot connect")
73
+ }
74
+
75
+ function dockerUnavailableHeadline(probe: Extract<DockerDaemonProbe, { ok: false }>): string {
76
+ if (probe.reason === "cli_missing") {
77
+ return "Docker is not installed or not on your PATH."
78
+ }
79
+ return "Docker is installed but the daemon is not running."
80
+ }
81
+
82
+ function dockerUnavailableHints(probe: Extract<DockerDaemonProbe, { ok: false }>): string[] {
83
+ const hints: string[] = []
84
+
85
+ if (probe.reason === "cli_missing") {
86
+ hints.push(
87
+ "Install Docker Desktop (https://www.docker.com/products/docker-desktop/) or add the docker CLI to PATH.",
88
+ )
89
+ } else if (probe.detail?.toLowerCase().includes("paused")) {
90
+ hints.push("Unpause Docker Desktop from the whale menu or Dashboard, then try again.")
91
+ } else if (platform() === "win32" || platform() === "darwin") {
92
+ hints.push("Start Docker Desktop, then try again.")
93
+ } else {
94
+ hints.push("Start the Docker daemon (e.g. sudo systemctl start docker), then try again.")
95
+ }
96
+
97
+ hints.push('To develop without Docker, set provider: "native" in supatype.config.ts.')
98
+
99
+ if (shouldShowDockerDetail(probe)) {
100
+ const firstLine = probe.detail!.split(/\r?\n/).find((line) => line.trim().length > 0)
101
+ if (firstLine) hints.push(`docker: ${firstLine.trim()}`)
102
+ }
103
+
104
+ return hints
105
+ }
106
+
107
+ /** @deprecated Tests only — use `reportDockerUnavailable`. */
108
+ export function formatDockerUnavailableMessage(
109
+ probe: Extract<DockerDaemonProbe, { ok: false }>,
110
+ ): string {
111
+ const lines: string[] = [dockerUnavailableHeadline(probe), ...dockerUnavailableHints(probe)]
112
+ return lines.join("\n")
113
+ }
114
+
115
+ /** Print Docker-unavailable guidance via the shared CLI message layer. */
116
+ export function reportDockerUnavailable(
117
+ probe: Extract<DockerDaemonProbe, { ok: false }>,
118
+ opts?: DockerReportOptions,
119
+ ): void {
120
+ if (opts?.brand && isInteractive()) {
121
+ printLogo()
122
+ p.intro(opts.brand.intro)
123
+ }
124
+
125
+ const headline = dockerUnavailableHeadline(probe)
126
+ const hints = dockerUnavailableHints(probe)
127
+
128
+ error(headline)
129
+
130
+ if (isInteractive()) {
131
+ p.note(hints.join("\n\n"))
132
+ return
133
+ }
134
+
135
+ for (const hint of hints) {
136
+ plain()
137
+ plain(` ${hint}`)
138
+ }
139
+ }
140
+
141
+ /** Exit 1 with a friendly message when Docker is not usable. */
142
+ export function requireDockerDaemon(opts?: DockerReportOptions): void {
143
+ const probe = probeDockerDaemon()
144
+ if (probe.ok) return
145
+ // Dev TUI patches console — restore stderr before printing a fatal message.
146
+ if (getActiveDevSession()) {
147
+ endDevSession()
148
+ }
149
+ reportDockerUnavailable(probe, opts)
150
+ process.exit(1)
151
+ }