@supatype/cli 0.1.0 → 0.1.2

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 (331) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +121 -86
  3. package/.turbo/turbo-typecheck.log +2 -2
  4. package/dist/binary-cache.d.ts +2 -0
  5. package/dist/binary-cache.d.ts.map +1 -1
  6. package/dist/binary-cache.js +36 -0
  7. package/dist/binary-cache.js.map +1 -1
  8. package/dist/binary-download-lock.d.ts +12 -0
  9. package/dist/binary-download-lock.d.ts.map +1 -0
  10. package/dist/binary-download-lock.js +106 -0
  11. package/dist/binary-download-lock.js.map +1 -0
  12. package/dist/cli-package-version.d.ts +3 -0
  13. package/dist/cli-package-version.d.ts.map +1 -0
  14. package/dist/cli-package-version.js +15 -0
  15. package/dist/cli-package-version.js.map +1 -0
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +5 -2
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/add.js +25 -19
  20. package/dist/commands/add.js.map +1 -1
  21. package/dist/commands/admin.d.ts +28 -1
  22. package/dist/commands/admin.d.ts.map +1 -1
  23. package/dist/commands/admin.js +290 -112
  24. package/dist/commands/admin.js.map +1 -1
  25. package/dist/commands/cache.d.ts +1 -1
  26. package/dist/commands/cache.d.ts.map +1 -1
  27. package/dist/commands/cache.js +98 -5
  28. package/dist/commands/cache.js.map +1 -1
  29. package/dist/commands/dev.d.ts.map +1 -1
  30. package/dist/commands/dev.js +28 -22
  31. package/dist/commands/dev.js.map +1 -1
  32. package/dist/commands/init.d.ts +8 -4
  33. package/dist/commands/init.d.ts.map +1 -1
  34. package/dist/commands/init.js +278 -139
  35. package/dist/commands/init.js.map +1 -1
  36. package/dist/commands/link-helpers.d.ts.map +1 -1
  37. package/dist/commands/link-helpers.js +26 -25
  38. package/dist/commands/link-helpers.js.map +1 -1
  39. package/dist/commands/push.js +3 -3
  40. package/dist/commands/push.js.map +1 -1
  41. package/dist/compose-local-server-image.d.ts +13 -0
  42. package/dist/compose-local-server-image.d.ts.map +1 -0
  43. package/dist/compose-local-server-image.js +57 -0
  44. package/dist/compose-local-server-image.js.map +1 -0
  45. package/dist/compose-rename.d.ts +10 -0
  46. package/dist/compose-rename.d.ts.map +1 -0
  47. package/dist/compose-rename.js +64 -0
  48. package/dist/compose-rename.js.map +1 -0
  49. package/dist/dev-compose.d.ts.map +1 -1
  50. package/dist/dev-compose.js +138 -97
  51. package/dist/dev-compose.js.map +1 -1
  52. package/dist/dev-log-bus.d.ts +5 -0
  53. package/dist/dev-log-bus.d.ts.map +1 -1
  54. package/dist/dev-log-bus.js +14 -0
  55. package/dist/dev-log-bus.js.map +1 -1
  56. package/dist/dev-log-filter.d.ts +4 -0
  57. package/dist/dev-log-filter.d.ts.map +1 -1
  58. package/dist/dev-log-filter.js +52 -0
  59. package/dist/dev-log-filter.js.map +1 -1
  60. package/dist/dev-ports.d.ts +27 -0
  61. package/dist/dev-ports.d.ts.map +1 -0
  62. package/dist/dev-ports.js +178 -0
  63. package/dist/dev-ports.js.map +1 -0
  64. package/dist/dev-ready-panel.d.ts +19 -0
  65. package/dist/dev-ready-panel.d.ts.map +1 -0
  66. package/dist/dev-ready-panel.js +49 -0
  67. package/dist/dev-ready-panel.js.map +1 -0
  68. package/dist/dev-session-lock.d.ts +25 -0
  69. package/dist/dev-session-lock.d.ts.map +1 -0
  70. package/dist/dev-session-lock.js +78 -0
  71. package/dist/dev-session-lock.js.map +1 -0
  72. package/dist/dev-session.d.ts +18 -5
  73. package/dist/dev-session.d.ts.map +1 -1
  74. package/dist/dev-session.js +91 -18
  75. package/dist/dev-session.js.map +1 -1
  76. package/dist/dev-shutdown.d.ts +20 -2
  77. package/dist/dev-shutdown.d.ts.map +1 -1
  78. package/dist/dev-shutdown.js +88 -10
  79. package/dist/dev-shutdown.js.map +1 -1
  80. package/dist/dev-task-colors.d.ts +3 -1
  81. package/dist/dev-task-colors.d.ts.map +1 -1
  82. package/dist/dev-task-colors.js +23 -3
  83. package/dist/dev-task-colors.js.map +1 -1
  84. package/dist/docker-runtime.js +1 -1
  85. package/dist/docker-runtime.js.map +1 -1
  86. package/dist/engine-push-output.d.ts +1 -0
  87. package/dist/engine-push-output.d.ts.map +1 -1
  88. package/dist/engine-push-output.js +4 -1
  89. package/dist/engine-push-output.js.map +1 -1
  90. package/dist/ensure-binary.d.ts.map +1 -1
  91. package/dist/ensure-binary.js +14 -2
  92. package/dist/ensure-binary.js.map +1 -1
  93. package/dist/ensure-component-binaries.d.ts +17 -0
  94. package/dist/ensure-component-binaries.d.ts.map +1 -0
  95. package/dist/ensure-component-binaries.js +45 -0
  96. package/dist/ensure-component-binaries.js.map +1 -0
  97. package/dist/ensure-dev-api-config.d.ts +3 -0
  98. package/dist/ensure-dev-api-config.d.ts.map +1 -0
  99. package/dist/ensure-dev-api-config.js +17 -0
  100. package/dist/ensure-dev-api-config.js.map +1 -0
  101. package/dist/env-file.d.ts +5 -0
  102. package/dist/env-file.d.ts.map +1 -0
  103. package/dist/env-file.js +33 -0
  104. package/dist/env-file.js.map +1 -0
  105. package/dist/init-dependency-versions.d.ts +13 -0
  106. package/dist/init-dependency-versions.d.ts.map +1 -0
  107. package/dist/init-dependency-versions.js +53 -0
  108. package/dist/init-dependency-versions.js.map +1 -0
  109. package/dist/init-package-json.d.ts +21 -0
  110. package/dist/init-package-json.d.ts.map +1 -0
  111. package/dist/init-package-json.js +46 -0
  112. package/dist/init-package-json.js.map +1 -0
  113. package/dist/postgres-ctl.d.ts.map +1 -1
  114. package/dist/postgres-ctl.js +2 -1
  115. package/dist/postgres-ctl.js.map +1 -1
  116. package/dist/required-host-components.d.ts +11 -0
  117. package/dist/required-host-components.d.ts.map +1 -0
  118. package/dist/required-host-components.js +25 -0
  119. package/dist/required-host-components.js.map +1 -0
  120. package/dist/resolve-api-url.d.ts +6 -0
  121. package/dist/resolve-api-url.d.ts.map +1 -0
  122. package/dist/resolve-api-url.js +39 -0
  123. package/dist/resolve-api-url.js.map +1 -0
  124. package/dist/rest-cache-admin.d.ts +33 -0
  125. package/dist/rest-cache-admin.d.ts.map +1 -0
  126. package/dist/rest-cache-admin.js +96 -0
  127. package/dist/rest-cache-admin.js.map +1 -0
  128. package/dist/scripts/postinstall.d.ts +4 -6
  129. package/dist/scripts/postinstall.d.ts.map +1 -1
  130. package/dist/scripts/postinstall.js +8 -41
  131. package/dist/scripts/postinstall.js.map +1 -1
  132. package/dist/self-host-compose.d.ts.map +1 -1
  133. package/dist/self-host-compose.js +24 -13
  134. package/dist/self-host-compose.js.map +1 -1
  135. package/dist/studio-dev-server.d.ts +4 -2
  136. package/dist/studio-dev-server.d.ts.map +1 -1
  137. package/dist/studio-dev-server.js +3 -2
  138. package/dist/studio-dev-server.js.map +1 -1
  139. package/dist/ui/brand.d.ts +2 -5
  140. package/dist/ui/brand.d.ts.map +1 -1
  141. package/dist/ui/brand.js +2 -7
  142. package/dist/ui/brand.js.map +1 -1
  143. package/dist/ui/clack-api.d.ts +44 -0
  144. package/dist/ui/clack-api.d.ts.map +1 -0
  145. package/dist/ui/clack-api.js +76 -0
  146. package/dist/ui/clack-api.js.map +1 -0
  147. package/dist/ui/clack.d.ts +14 -0
  148. package/dist/ui/clack.d.ts.map +1 -0
  149. package/dist/ui/clack.js +147 -0
  150. package/dist/ui/clack.js.map +1 -0
  151. package/dist/ui/components/FlowSpinner.d.ts +5 -0
  152. package/dist/ui/components/FlowSpinner.d.ts.map +1 -0
  153. package/dist/ui/components/FlowSpinner.js +8 -0
  154. package/dist/ui/components/FlowSpinner.js.map +1 -0
  155. package/dist/ui/components/LogoWordmark.d.ts +9 -0
  156. package/dist/ui/components/LogoWordmark.d.ts.map +1 -0
  157. package/dist/ui/components/LogoWordmark.js +17 -0
  158. package/dist/ui/components/LogoWordmark.js.map +1 -0
  159. package/dist/ui/components/PromptPanel.d.ts +5 -0
  160. package/dist/ui/components/PromptPanel.d.ts.map +1 -0
  161. package/dist/ui/components/PromptPanel.js +7 -0
  162. package/dist/ui/components/PromptPanel.js.map +1 -0
  163. package/dist/ui/components/SelectField.d.ts +11 -0
  164. package/dist/ui/components/SelectField.d.ts.map +1 -0
  165. package/dist/ui/components/SelectField.js +19 -0
  166. package/dist/ui/components/SelectField.js.map +1 -0
  167. package/dist/ui/confirm.d.ts +2 -1
  168. package/dist/ui/confirm.d.ts.map +1 -1
  169. package/dist/ui/confirm.js +4 -4
  170. package/dist/ui/confirm.js.map +1 -1
  171. package/dist/ui/dev/DevDashboard.d.ts +8 -0
  172. package/dist/ui/dev/DevDashboard.d.ts.map +1 -0
  173. package/dist/ui/dev/DevDashboard.js +151 -0
  174. package/dist/ui/dev/DevDashboard.js.map +1 -0
  175. package/dist/ui/dev/DevReadyPanelView.d.ts +7 -0
  176. package/dist/ui/dev/DevReadyPanelView.d.ts.map +1 -0
  177. package/dist/ui/dev/DevReadyPanelView.js +29 -0
  178. package/dist/ui/dev/DevReadyPanelView.js.map +1 -0
  179. package/dist/ui/fatal.d.ts.map +1 -1
  180. package/dist/ui/fatal.js +5 -11
  181. package/dist/ui/fatal.js.map +1 -1
  182. package/dist/ui/flows/FlowApp.d.ts +8 -0
  183. package/dist/ui/flows/FlowApp.d.ts.map +1 -0
  184. package/dist/ui/flows/FlowApp.js +42 -0
  185. package/dist/ui/flows/FlowApp.js.map +1 -0
  186. package/dist/ui/flows/prompt-fields.d.ts +48 -0
  187. package/dist/ui/flows/prompt-fields.d.ts.map +1 -0
  188. package/dist/ui/flows/prompt-fields.js +88 -0
  189. package/dist/ui/flows/prompt-fields.js.map +1 -0
  190. package/dist/ui/flows/types.d.ts +41 -0
  191. package/dist/ui/flows/types.d.ts.map +1 -0
  192. package/dist/ui/flows/types.js +2 -0
  193. package/dist/ui/flows/types.js.map +1 -0
  194. package/dist/ui/index.d.ts +1 -1
  195. package/dist/ui/index.js +1 -1
  196. package/dist/ui/messages.d.ts +1 -0
  197. package/dist/ui/messages.d.ts.map +1 -1
  198. package/dist/ui/messages.js +48 -8
  199. package/dist/ui/messages.js.map +1 -1
  200. package/dist/ui/progress.d.ts +5 -1
  201. package/dist/ui/progress.d.ts.map +1 -1
  202. package/dist/ui/progress.js +12 -3
  203. package/dist/ui/progress.js.map +1 -1
  204. package/dist/ui/prompts.d.ts +7 -5
  205. package/dist/ui/prompts.d.ts.map +1 -1
  206. package/dist/ui/prompts.js +15 -8
  207. package/dist/ui/prompts.js.map +1 -1
  208. package/dist/ui/runtime/cancel.d.ts +4 -0
  209. package/dist/ui/runtime/cancel.d.ts.map +1 -0
  210. package/dist/ui/runtime/cancel.js +6 -0
  211. package/dist/ui/runtime/cancel.js.map +1 -0
  212. package/dist/ui/runtime/command-chrome.d.ts +17 -0
  213. package/dist/ui/runtime/command-chrome.d.ts.map +1 -0
  214. package/dist/ui/runtime/command-chrome.js +123 -0
  215. package/dist/ui/runtime/command-chrome.js.map +1 -0
  216. package/dist/ui/runtime/dev-prompt-queue.d.ts +30 -0
  217. package/dist/ui/runtime/dev-prompt-queue.d.ts.map +1 -0
  218. package/dist/ui/runtime/dev-prompt-queue.js +57 -0
  219. package/dist/ui/runtime/dev-prompt-queue.js.map +1 -0
  220. package/dist/ui/runtime/flow-session.d.ts +4 -0
  221. package/dist/ui/runtime/flow-session.d.ts.map +1 -0
  222. package/dist/ui/runtime/flow-session.js +8 -0
  223. package/dist/ui/runtime/flow-session.js.map +1 -0
  224. package/dist/ui/runtime/run-clack-flow.d.ts +5 -0
  225. package/dist/ui/runtime/run-clack-flow.d.ts.map +1 -0
  226. package/dist/ui/runtime/run-clack-flow.js +34 -0
  227. package/dist/ui/runtime/run-clack-flow.js.map +1 -0
  228. package/dist/ui/theme.d.ts +24 -0
  229. package/dist/ui/theme.d.ts.map +1 -0
  230. package/dist/ui/theme.js +34 -0
  231. package/dist/ui/theme.js.map +1 -0
  232. package/dist/valkey-sidecar.d.ts +17 -0
  233. package/dist/valkey-sidecar.d.ts.map +1 -0
  234. package/dist/valkey-sidecar.js +94 -0
  235. package/dist/valkey-sidecar.js.map +1 -0
  236. package/package.json +11 -3
  237. package/src/binary-cache.ts +61 -0
  238. package/src/binary-download-lock.ts +124 -0
  239. package/src/cli-package-version.ts +15 -0
  240. package/src/cli.ts +6 -2
  241. package/src/commands/add.ts +26 -19
  242. package/src/commands/admin.ts +380 -137
  243. package/src/commands/cache.ts +105 -5
  244. package/src/commands/dev.ts +29 -21
  245. package/src/commands/init.ts +217 -46
  246. package/src/commands/link-helpers.ts +30 -29
  247. package/src/commands/push.ts +3 -3
  248. package/src/compose-local-server-image.ts +68 -0
  249. package/src/compose-rename.ts +73 -0
  250. package/src/dev-compose.ts +175 -96
  251. package/src/dev-log-bus.ts +18 -0
  252. package/src/dev-log-filter.ts +42 -0
  253. package/src/dev-ports.ts +223 -0
  254. package/src/dev-ready-panel.ts +63 -0
  255. package/src/dev-session-lock.ts +98 -0
  256. package/src/dev-session.ts +98 -18
  257. package/src/dev-shutdown.ts +114 -9
  258. package/src/dev-task-colors.ts +22 -3
  259. package/src/docker-runtime.ts +1 -1
  260. package/src/engine-push-output.ts +5 -1
  261. package/src/ensure-binary.ts +21 -1
  262. package/src/ensure-component-binaries.ts +71 -0
  263. package/src/ensure-dev-api-config.ts +18 -0
  264. package/src/env-file.ts +37 -0
  265. package/src/init-dependency-versions.ts +69 -0
  266. package/src/init-package-json.ts +63 -0
  267. package/src/postgres-ctl.ts +2 -1
  268. package/src/required-host-components.ts +37 -0
  269. package/src/resolve-api-url.ts +43 -0
  270. package/src/rest-cache-admin.ts +135 -0
  271. package/src/scripts/postinstall.ts +9 -46
  272. package/src/self-host-compose.ts +23 -12
  273. package/src/studio-dev-server.ts +5 -4
  274. package/src/ui/README.md +13 -9
  275. package/src/ui/brand.ts +2 -8
  276. package/src/ui/clack-api.ts +123 -0
  277. package/src/ui/clack.ts +153 -0
  278. package/src/ui/components/FlowSpinner.tsx +15 -0
  279. package/src/ui/components/LogoWordmark.tsx +36 -0
  280. package/src/ui/components/PromptPanel.tsx +17 -0
  281. package/src/ui/components/SelectField.tsx +58 -0
  282. package/src/ui/confirm.ts +5 -4
  283. package/src/ui/dev/DevDashboard.tsx +213 -0
  284. package/src/ui/dev/DevReadyPanelView.tsx +67 -0
  285. package/src/ui/fatal.ts +5 -10
  286. package/src/ui/flows/FlowApp.tsx +67 -0
  287. package/src/ui/flows/prompt-fields.tsx +192 -0
  288. package/src/ui/flows/types.ts +50 -0
  289. package/src/ui/index.ts +1 -1
  290. package/src/ui/messages.ts +50 -8
  291. package/src/ui/progress.ts +14 -3
  292. package/src/ui/prompts.ts +17 -9
  293. package/src/ui/runtime/cancel.ts +6 -0
  294. package/src/ui/runtime/command-chrome.ts +147 -0
  295. package/src/ui/runtime/dev-prompt-queue.ts +84 -0
  296. package/src/ui/runtime/flow-session.ts +11 -0
  297. package/src/ui/runtime/run-clack-flow.tsx +52 -0
  298. package/src/ui/theme.ts +41 -0
  299. package/src/valkey-sidecar.ts +126 -0
  300. package/tests/admin-ensure.test.ts +59 -0
  301. package/tests/binary-cache-cloud-overrides.test.ts +2 -2
  302. package/tests/binary-download-lock.test.ts +41 -0
  303. package/tests/cli-help.test.ts +7 -4
  304. package/tests/cli-package-version.test.ts +15 -0
  305. package/tests/command-chrome-coverage.test.ts +149 -0
  306. package/tests/command-chrome-wrap.test.ts +51 -0
  307. package/tests/config.test.ts +8 -4
  308. package/tests/dev-ports.test.ts +41 -0
  309. package/tests/dev-ready-panel.test.ts +20 -0
  310. package/tests/dev-session-lock.test.ts +54 -0
  311. package/tests/dev-ui.test.ts +66 -3
  312. package/tests/docker-runtime.test.ts +9 -20
  313. package/tests/ensure-dev-api-config.test.ts +22 -0
  314. package/tests/init-dependency-versions.test.ts +62 -0
  315. package/tests/init-package-json.test.ts +18 -0
  316. package/tests/init.test.ts +30 -3
  317. package/tests/required-host-components.test.ts +51 -0
  318. package/tests/resolve-api-url.test.ts +23 -0
  319. package/tests/rest-cache-admin.test.ts +28 -0
  320. package/tests/runtime-contract.test.ts +12 -3
  321. package/tests/tsconfig.json +8 -2
  322. package/tests/ui-confirm.test.ts +13 -9
  323. package/tests/ui-messages.test.ts +4 -18
  324. package/tsconfig.json +1 -0
  325. package/tsconfig.tsbuildinfo +1 -1
  326. package/vitest.config.ts +3 -0
  327. package/dist/dev-tui.d.ts +0 -24
  328. package/dist/dev-tui.d.ts.map +0 -1
  329. package/dist/dev-tui.js +0 -188
  330. package/dist/dev-tui.js.map +0 -1
  331. package/src/dev-tui.ts +0 -232
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Kong / Postgres host port resolution for local Docker dev.
3
+ * Persists SUPATYPE_KONG_PORT in `.env` so re-runs are stable, but re-checks
4
+ * availability so multiple projects and port collisions are surfaced clearly.
5
+ */
6
+
7
+ import { CLACK_CANCEL, isCancel, p } from "./ui/clack.js"
8
+ import { COMPOSE_DEV_KONG_PORT } from "./project-config.js"
9
+ import { isPortInUse } from "./postgres-ctl.js"
10
+ import { readEnvInt, upsertEnvFile } from "./env-file.js"
11
+ import { isInteractive } from "./ui/interactive.js"
12
+ import { fatalError } from "./ui/fatal.js"
13
+
14
+ const MIN_PORT = 1024
15
+ const MAX_PORT = 65535
16
+
17
+ export function isValidHostPort(port: number): boolean {
18
+ return Number.isInteger(port) && port >= MIN_PORT && port <= MAX_PORT
19
+ }
20
+
21
+ export function parseHostPortInput(raw: string): number | null {
22
+ const port = Number(raw.trim())
23
+ return isValidHostPort(port) ? port : null
24
+ }
25
+
26
+ /** Next free TCP port on 127.0.0.1 starting at `start`. */
27
+ export async function findNextFreePort(start: number): Promise<number> {
28
+ let port = Math.max(MIN_PORT, start)
29
+ while (port <= MAX_PORT && (await isPortInUse(port))) port++
30
+ if (port > MAX_PORT) {
31
+ throw new Error(`No free local port found in range ${start}–${MAX_PORT}.`)
32
+ }
33
+ return port
34
+ }
35
+
36
+ export function readPersistedKongPort(cwd: string): number | null {
37
+ return readEnvInt(cwd, "SUPATYPE_KONG_PORT")
38
+ }
39
+
40
+ function persistKongPort(cwd: string, port: number): void {
41
+ const apiUrl = `http://localhost:${port}`
42
+ upsertEnvFile(cwd, {
43
+ SUPATYPE_KONG_PORT: String(port),
44
+ PUBLIC_SUPATYPE_URL: apiUrl,
45
+ API_EXTERNAL_URL: apiUrl,
46
+ SITE_URL: apiUrl,
47
+ })
48
+ }
49
+
50
+ async function promptPortConflict(
51
+ cwd: string,
52
+ blockedPort: number,
53
+ reason: "in_use" | "init",
54
+ ): Promise<number> {
55
+ const port = await promptPortConflictWithoutPersist(blockedPort, reason)
56
+ persistKongPort(cwd, port)
57
+ return port
58
+ }
59
+
60
+ export interface EnsureKongPortOptions {
61
+ /** When false, fail fast instead of prompting (CI / scripts). */
62
+ interactive?: boolean
63
+ /** init wizard — slightly different copy */
64
+ context?: "dev" | "init"
65
+ }
66
+
67
+ /**
68
+ * Resolve the Kong host port for this project directory.
69
+ * - Uses `.env` when set and available.
70
+ * - Prompts (or errors) when the configured port is taken.
71
+ * - Auto-picks the next free port on first run when unset.
72
+ */
73
+ export async function ensureKongPort(
74
+ cwd: string,
75
+ opts: EnsureKongPortOptions = {},
76
+ ): Promise<number> {
77
+ const interactive = opts.interactive ?? isInteractive()
78
+ const context = opts.context ?? "dev"
79
+ const persisted = readPersistedKongPort(cwd)
80
+
81
+ if (persisted !== null) {
82
+ if (!(await isPortInUse(persisted))) return persisted
83
+
84
+ if (!interactive) {
85
+ fatalError(`Port ${persisted} is already in use (SUPATYPE_KONG_PORT in .env).`, [
86
+ "Stop the other service or set a different SUPATYPE_KONG_PORT.",
87
+ "Run `supatype dev` in a terminal to pick a new port interactively.",
88
+ ])
89
+ }
90
+
91
+ return promptPortConflict(cwd, persisted, "in_use")
92
+ }
93
+
94
+ const preferred = COMPOSE_DEV_KONG_PORT
95
+ const port =
96
+ (await isPortInUse(preferred))
97
+ ? interactive && context === "init"
98
+ ? await promptPortConflict(cwd, preferred, "init")
99
+ : await findNextFreePort(preferred)
100
+ : preferred
101
+
102
+ return port
103
+ }
104
+
105
+ /** Wizard / init — pick a Kong port without writing `.env` (scaffold writes it). */
106
+ export async function promptKongPortChoice(): Promise<number> {
107
+ const freeDefault = (await isPortInUse(COMPOSE_DEV_KONG_PORT))
108
+ ? await findNextFreePort(COMPOSE_DEV_KONG_PORT)
109
+ : COMPOSE_DEV_KONG_PORT
110
+
111
+ const value = await p.text({
112
+ message: "Local API gateway port (Kong)",
113
+ defaultValue: String(freeDefault),
114
+ placeholder: String(COMPOSE_DEV_KONG_PORT),
115
+ validate: (raw) => {
116
+ const port = parseHostPortInput(raw ?? "")
117
+ if (!port) return `Enter a port between ${MIN_PORT} and ${MAX_PORT}.`
118
+ return undefined
119
+ },
120
+ })
121
+
122
+ if (isCancel(value)) {
123
+ p.cancel("Cancelled.")
124
+ process.exit(0)
125
+ }
126
+
127
+ const port = parseHostPortInput(value)!
128
+ if (await isPortInUse(port)) {
129
+ return promptPortConflictWithoutPersist(port, "init")
130
+ }
131
+ return port
132
+ }
133
+
134
+ async function promptPortConflictWithoutPersist(
135
+ blockedPort: number,
136
+ reason: "in_use" | "init",
137
+ ): Promise<number> {
138
+ const suggested = await findNextFreePort(blockedPort + 1)
139
+ const headline =
140
+ reason === "init"
141
+ ? `Port ${blockedPort} is already in use on this machine.`
142
+ : `Port ${blockedPort} is in use — another Supatype project or service may already be bound to it.`
143
+
144
+ const choice = await p.select<"suggested" | "custom" | "cancel">({
145
+ message: headline,
146
+ options: [
147
+ {
148
+ value: "suggested",
149
+ label: `Use ${suggested} instead`,
150
+ hint: "next available port",
151
+ },
152
+ { value: "custom", label: "Enter a different port" },
153
+ { value: "cancel", label: "Cancel" },
154
+ ],
155
+ })
156
+
157
+ if (isCancel(choice) || choice === "cancel") {
158
+ p.cancel("Cancelled.")
159
+ process.exit(0)
160
+ }
161
+
162
+ if (choice === "suggested") return suggested
163
+
164
+ const custom = await p.text({
165
+ message: "Local API gateway port (Kong)",
166
+ defaultValue: String(suggested),
167
+ validate: (value) => {
168
+ const port = parseHostPortInput(value ?? "")
169
+ if (!port) return `Enter a port between ${MIN_PORT} and ${MAX_PORT}.`
170
+ return undefined
171
+ },
172
+ })
173
+
174
+ if (isCancel(custom)) {
175
+ p.cancel("Cancelled.")
176
+ process.exit(0)
177
+ }
178
+
179
+ const port = parseHostPortInput(custom)
180
+ if (!port) {
181
+ p.cancel("Invalid port.")
182
+ process.exit(1)
183
+ }
184
+
185
+ if (await isPortInUse(port)) {
186
+ fatalError(`Port ${port} is still in use.`, [
187
+ "Pick another port or stop the service using it.",
188
+ "Check Docker Desktop for other Supatype stacks.",
189
+ ])
190
+ }
191
+
192
+ return port
193
+ }
194
+
195
+ const COMPOSE_DEV_DB_PORT = 54329
196
+
197
+ function devDbConnectionUrl(port: number): string {
198
+ return `postgresql://supatype_admin:postgres@localhost:${port}/supatype?sslmode=disable`
199
+ }
200
+
201
+ export async function ensureDevDbPort(cwd: string): Promise<number> {
202
+ const persisted = readEnvInt(cwd, "SUPATYPE_DEV_DB_PORT")
203
+
204
+ const persist = (port: number): number => {
205
+ upsertEnvFile(cwd, {
206
+ SUPATYPE_DEV_DB_PORT: String(port),
207
+ DATABASE_URL: devDbConnectionUrl(port),
208
+ })
209
+ return port
210
+ }
211
+
212
+ if (persisted !== null) {
213
+ if (!(await isPortInUse(persisted))) return persisted
214
+ const next = await findNextFreePort(persisted + 1)
215
+ return persist(next)
216
+ }
217
+
218
+ let port = COMPOSE_DEV_DB_PORT
219
+ if (await isPortInUse(port)) {
220
+ port = await findNextFreePort(port + 1)
221
+ }
222
+ return persist(port)
223
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Pinned service URLs + dev keys for the Ink dashboard.
3
+ */
4
+
5
+ import { appendStackOutput, getActiveDevSession } from "./dev-session.js"
6
+
7
+ export interface DevServiceLink {
8
+ label: string
9
+ url: string
10
+ }
11
+
12
+ export interface DevReadyPanel {
13
+ title: string
14
+ links: DevServiceLink[]
15
+ anonKey?: string
16
+ serviceRoleKey?: string
17
+ hints?: string[]
18
+ }
19
+
20
+ /** Ink row budget for DevReadyPanelView (border, title, links, hints, keys, margin). */
21
+ export function devReadyPanelRowCount(panel: DevReadyPanel): number {
22
+ let rows = 2 // round border top + bottom
23
+ rows += 1 // title
24
+ rows += panel.links.length
25
+ for (const hint of panel.hints ?? []) {
26
+ if (hint.trim()) rows += 1
27
+ }
28
+ if (panel.anonKey) {
29
+ rows += 1 // "API keys (local dev)"
30
+ rows += 1 // anon
31
+ if (panel.serviceRoleKey) rows += 1 // service_role
32
+ }
33
+ rows += 1 // marginBottom on panel box
34
+ return rows
35
+ }
36
+
37
+ function formatStreamBlock(panel: DevReadyPanel): string {
38
+ const lines = [`[supatype] ${panel.title}`]
39
+ for (const link of panel.links) {
40
+ lines.push(` ${link.label.padEnd(16)} ${link.url}`)
41
+ }
42
+ for (const hint of panel.hints ?? []) {
43
+ lines.push(` ${hint}`)
44
+ }
45
+ if (panel.anonKey) {
46
+ lines.push(" API keys (local dev only):")
47
+ lines.push(` anon key ${panel.anonKey}`)
48
+ lines.push(` service_role ${panel.serviceRoleKey ?? ""}`)
49
+ }
50
+ lines.push(" Press Ctrl+C to stop.")
51
+ return lines.join("\n")
52
+ }
53
+
54
+ /** Pin links in the TUI or print the classic block in stream mode. */
55
+ export function publishDevReady(panel: DevReadyPanel): void {
56
+ const session = getActiveDevSession()
57
+ if (session?.isTui()) {
58
+ session.bus.setReadyPanel(panel)
59
+ appendStackOutput("Services running.", "log")
60
+ return
61
+ }
62
+ console.log(formatStreamBlock(panel))
63
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Tracks an active `supatype dev` session so we can recover when the CLI exits
3
+ * without graceful shutdown (terminal closed, kill signal, etc.).
4
+ */
5
+
6
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"
7
+ import { join, resolve } from "node:path"
8
+ import { spawnSync } from "node:child_process"
9
+ import { runDockerCompose } from "./self-host-compose.js"
10
+ import { confirm as uiConfirm } from "./ui/confirm.js"
11
+ import { success, warn } from "./ui/messages.js"
12
+ import { isInteractive } from "./ui/interactive.js"
13
+
14
+ const LOCK_VERSION = 1 as const
15
+
16
+ export interface DevSessionLock {
17
+ version: typeof LOCK_VERSION
18
+ composeProject: string
19
+ projectRef: string
20
+ composePath: string
21
+ kongPort: number
22
+ startedAt: string
23
+ }
24
+
25
+ export function devSessionLockPath(cwd: string): string {
26
+ return resolve(cwd, ".supatype/dev-session.json")
27
+ }
28
+
29
+ export function readDevSessionLock(cwd: string): DevSessionLock | null {
30
+ const path = devSessionLockPath(cwd)
31
+ if (!existsSync(path)) return null
32
+ try {
33
+ const data = JSON.parse(readFileSync(path, "utf8")) as DevSessionLock
34
+ if (data.version !== LOCK_VERSION) return null
35
+ return data
36
+ } catch {
37
+ return null
38
+ }
39
+ }
40
+
41
+ export function writeDevSessionLock(cwd: string, lock: Omit<DevSessionLock, "version">): void {
42
+ const dir = join(cwd, ".supatype")
43
+ mkdirSync(dir, { recursive: true })
44
+ const payload: DevSessionLock = { version: LOCK_VERSION, ...lock }
45
+ writeFileSync(devSessionLockPath(cwd), `${JSON.stringify(payload, null, 2)}\n`, "utf8")
46
+ }
47
+
48
+ export function clearDevSessionLock(cwd: string): void {
49
+ const path = devSessionLockPath(cwd)
50
+ if (existsSync(path)) unlinkSync(path)
51
+ }
52
+
53
+ export function composeStackHasContainers(composeProject: string): boolean {
54
+ const result = spawnSync(
55
+ "docker",
56
+ ["ps", "-a", "--filter", `label=com.docker.compose.project=${composeProject}`, "--format", "{{.Names}}"],
57
+ { encoding: "utf8", shell: process.platform === "win32" },
58
+ )
59
+ return Boolean(result.stdout?.trim())
60
+ }
61
+
62
+ /**
63
+ * When a previous dev session did not shut down cleanly, offer to stop its stack
64
+ * before starting a new one.
65
+ */
66
+ export async function recoverStaleDevSession(cwd: string): Promise<void> {
67
+ const lock = readDevSessionLock(cwd)
68
+ if (!lock) return
69
+ if (!composeStackHasContainers(lock.composeProject)) {
70
+ clearDevSessionLock(cwd)
71
+ return
72
+ }
73
+
74
+ const message =
75
+ `Previous dev session for "${lock.projectRef}" may not have shut down cleanly ` +
76
+ `(stack "${lock.composeProject}" is still running).`
77
+
78
+ if (!isInteractive()) {
79
+ warn(message)
80
+ warn(`Stop it manually: docker compose -p ${lock.composeProject} down`)
81
+ return
82
+ }
83
+
84
+ const stop = await uiConfirm(`${message}\n\nStop the orphaned stack before starting?`, { default: true })
85
+
86
+ if (!stop) {
87
+ warn(`Leaving "${lock.composeProject}" running.`)
88
+ return
89
+ }
90
+
91
+ const status = runDockerCompose(lock.composePath, ["down"], cwd, lock.composeProject, { quiet: true })
92
+ if (status === 0) {
93
+ clearDevSessionLock(cwd)
94
+ success(`Stopped orphaned stack "${lock.composeProject}".`)
95
+ } else {
96
+ warn(`Could not stop "${lock.composeProject}" (exit ${status}).`)
97
+ }
98
+ }
@@ -1,11 +1,15 @@
1
1
  /**
2
- * `supatype dev` output session — TUI (default) or interleaved stream mode.
2
+ * `supatype dev` output session — Ink dashboard (default) or interleaved stream mode.
3
3
  */
4
4
 
5
+ import { render, type Instance } from "ink"
6
+ import React from "react"
5
7
  import type { ProcessOptions } from "./process-manager.js"
6
- import { filterDevSubprocessLine, formatConsoleArgs } from "./dev-log-filter.js"
8
+ import { filterDevSubprocessLine, filterStackLogLine, formatConsoleArgs } from "./dev-log-filter.js"
7
9
  import { DevLogBus, type DevLogLevel } from "./dev-log-bus.js"
8
- import { DevTui } from "./dev-tui.js"
10
+ import { DevDashboard } from "./ui/dev/DevDashboard.js"
11
+ import { ensureDevShutdownHooks } from "./dev-shutdown.js"
12
+ import { clearDevPromptQueue } from "./ui/runtime/dev-prompt-queue.js"
9
13
 
10
14
  export type DevUiMode = "tui" | "stream"
11
15
 
@@ -18,26 +22,74 @@ export function resolveDevUiMode(streamFlag: boolean): DevUiMode {
18
22
  let activeSession: DevSession | null = null
19
23
 
20
24
  export function beginDevSession(mode: DevUiMode): DevSession {
25
+ ensureDevShutdownHooks()
21
26
  activeSession?.stop()
22
27
  activeSession = new DevSession(mode)
28
+ if (activeSession.isTui()) {
29
+ activeSession.enableConsoleCapture(false)
30
+ activeSession.startInk()
31
+ }
23
32
  return activeSession
24
33
  }
25
34
 
26
- /** Enter console capture + TUI. */
35
+ /** @deprecated Ink starts in beginDevSession — kept for callers that invoke late. */
27
36
  export function startDevSession(): void {
28
- activeSession?.start()
37
+ activeSession?.startInk()
38
+ }
39
+
40
+ export function isDevTuiActive(): boolean {
41
+ return activeSession?.isInkMounted() ?? false
29
42
  }
30
43
 
31
44
  export function endDevSession(): void {
45
+ clearDevPromptQueue()
32
46
  activeSession?.stop()
33
47
  activeSession = null
34
48
  }
35
49
 
50
+ /** @deprecated Dev prompts render inside the Ink dashboard — no suspend needed. */
51
+ export function suspendDevSessionForPrompt(): void {
52
+ prepareStdinForInteractivePrompt()
53
+ }
54
+
55
+ export function prepareStdinForInteractivePrompt(): void {
56
+ if (!process.stdin.isTTY) return
57
+ process.stdin.setRawMode(false)
58
+ process.stdin.resume()
59
+ }
60
+
61
+ /** @deprecated Dev prompts render inside the Ink dashboard — no resume needed. */
62
+ export function resumeDevSessionAfterPrompt(): void {
63
+ // no-op — prompts are Ink overlays
64
+ }
65
+
66
+ /** @deprecated Use Ink overlay prompts — runs fn without tearing down the dashboard. */
67
+ export async function withDevSessionSuspended<T>(fn: () => Promise<T>): Promise<T> {
68
+ return fn()
69
+ }
70
+
36
71
  export function getActiveDevSession(): DevSession | null {
37
72
  return activeSession
38
73
  }
39
74
 
40
- /** Log to a TUI task stream, or prefixed console output in stream mode. */
75
+ export function appendStackOutput(
76
+ text: string | null | undefined,
77
+ level: DevLogLevel = "log",
78
+ ): void {
79
+ if (text == null || text.trim() === "") return
80
+ const session = getActiveDevSession()
81
+ for (const line of text.split(/\r?\n/)) {
82
+ if (line.trim() === "") continue
83
+ if (session?.isTui() && !filterStackLogLine(line)) continue
84
+ if (session?.isConsoleCaptured()) {
85
+ session.bus.append("stack", line, level)
86
+ continue
87
+ }
88
+ const write = level === "warn" ? console.warn : level === "error" ? console.error : console.log
89
+ write(`[supatype] ${line}`)
90
+ }
91
+ }
92
+
41
93
  export function appendDevTaskLog(
42
94
  taskId: string,
43
95
  taskTitle: string,
@@ -58,8 +110,9 @@ export function appendDevTaskLog(
58
110
  export class DevSession {
59
111
  readonly bus = new DevLogBus()
60
112
  readonly mode: DevUiMode
61
- private tui: DevTui | null = null
113
+ private ink: Instance | null = null
62
114
  private restoreConsole: (() => void) | null = null
115
+ private consoleCaptured = false
63
116
 
64
117
  constructor(mode: DevUiMode) {
65
118
  this.mode = mode
@@ -69,18 +122,37 @@ export class DevSession {
69
122
  return this.mode === "tui"
70
123
  }
71
124
 
72
- start(): void {
73
- if (!this.isTui()) return
125
+ isInkMounted(): boolean {
126
+ return this.ink !== null
127
+ }
128
+
129
+ isConsoleCaptured(): boolean {
130
+ return this.consoleCaptured
131
+ }
132
+
133
+ needsPromptSuspend(): boolean {
134
+ return false
135
+ }
136
+
137
+ enableConsoleCapture(_tee: boolean): void {
138
+ if (!this.isTui() || this.consoleCaptured) return
74
139
  this.restoreConsole = patchConsole(this.bus)
75
- this.tui = new DevTui(this.bus)
76
- this.tui.start()
140
+ this.consoleCaptured = true
141
+ }
142
+
143
+ startInk(): void {
144
+ if (!this.isTui() || this.ink) return
145
+ this.ink = render(React.createElement(DevDashboard, { bus: this.bus }))
77
146
  }
78
147
 
79
148
  stop(): void {
80
- this.tui?.stop()
81
- this.tui = null
82
- this.restoreConsole?.()
83
- this.restoreConsole = null
149
+ this.ink?.unmount()
150
+ this.ink = null
151
+ if (this.restoreConsole) {
152
+ this.restoreConsole()
153
+ this.restoreConsole = null
154
+ this.consoleCaptured = false
155
+ }
84
156
  }
85
157
  }
86
158
 
@@ -105,6 +177,14 @@ export function enhanceProcessOptions(label: string, opts: ProcessOptions): Proc
105
177
  }
106
178
  }
107
179
 
180
+ function appendFilteredStackLine(bus: DevLogBus, line: string, level: DevLogLevel): void {
181
+ for (const part of line.split(/\r?\n/)) {
182
+ if (!part.trim()) continue
183
+ if (!filterStackLogLine(part)) continue
184
+ bus.append("stack", part, level)
185
+ }
186
+ }
187
+
108
188
  function patchConsole(bus: DevLogBus): () => void {
109
189
  const original = {
110
190
  log: console.log,
@@ -113,13 +193,13 @@ function patchConsole(bus: DevLogBus): () => void {
113
193
  }
114
194
 
115
195
  console.log = (...args: unknown[]) => {
116
- bus.append("stack", formatConsoleArgs(args), "log")
196
+ appendFilteredStackLine(bus, formatConsoleArgs(args), "log")
117
197
  }
118
198
  console.warn = (...args: unknown[]) => {
119
- bus.append("stack", formatConsoleArgs(args), "warn")
199
+ appendFilteredStackLine(bus, formatConsoleArgs(args), "warn")
120
200
  }
121
201
  console.error = (...args: unknown[]) => {
122
- bus.append("stack", formatConsoleArgs(args), "error")
202
+ appendFilteredStackLine(bus, formatConsoleArgs(args), "error")
123
203
  }
124
204
 
125
205
  return () => {