@supatype/cli 0.1.1 → 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 (316) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +114 -90
  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.map +1 -1
  22. package/dist/commands/admin.js +22 -6
  23. package/dist/commands/admin.js.map +1 -1
  24. package/dist/commands/cache.d.ts +1 -1
  25. package/dist/commands/cache.d.ts.map +1 -1
  26. package/dist/commands/cache.js +98 -5
  27. package/dist/commands/cache.js.map +1 -1
  28. package/dist/commands/dev.d.ts.map +1 -1
  29. package/dist/commands/dev.js +26 -22
  30. package/dist/commands/dev.js.map +1 -1
  31. package/dist/commands/init.d.ts +3 -4
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +238 -168
  34. package/dist/commands/init.js.map +1 -1
  35. package/dist/commands/link-helpers.d.ts.map +1 -1
  36. package/dist/commands/link-helpers.js +26 -25
  37. package/dist/commands/link-helpers.js.map +1 -1
  38. package/dist/compose-local-server-image.d.ts +13 -0
  39. package/dist/compose-local-server-image.d.ts.map +1 -0
  40. package/dist/compose-local-server-image.js +57 -0
  41. package/dist/compose-local-server-image.js.map +1 -0
  42. package/dist/compose-rename.d.ts.map +1 -1
  43. package/dist/compose-rename.js +5 -8
  44. package/dist/compose-rename.js.map +1 -1
  45. package/dist/dev-compose.d.ts.map +1 -1
  46. package/dist/dev-compose.js +111 -54
  47. package/dist/dev-compose.js.map +1 -1
  48. package/dist/dev-log-bus.d.ts +5 -0
  49. package/dist/dev-log-bus.d.ts.map +1 -1
  50. package/dist/dev-log-bus.js +14 -0
  51. package/dist/dev-log-bus.js.map +1 -1
  52. package/dist/dev-log-filter.d.ts +4 -0
  53. package/dist/dev-log-filter.d.ts.map +1 -1
  54. package/dist/dev-log-filter.js +52 -0
  55. package/dist/dev-log-filter.js.map +1 -1
  56. package/dist/dev-ports.d.ts.map +1 -1
  57. package/dist/dev-ports.js +19 -12
  58. package/dist/dev-ports.js.map +1 -1
  59. package/dist/dev-ready-panel.d.ts +19 -0
  60. package/dist/dev-ready-panel.d.ts.map +1 -0
  61. package/dist/dev-ready-panel.js +49 -0
  62. package/dist/dev-ready-panel.js.map +1 -0
  63. package/dist/dev-session-lock.d.ts.map +1 -1
  64. package/dist/dev-session-lock.js +5 -8
  65. package/dist/dev-session-lock.js.map +1 -1
  66. package/dist/dev-session.d.ts +18 -5
  67. package/dist/dev-session.d.ts.map +1 -1
  68. package/dist/dev-session.js +91 -18
  69. package/dist/dev-session.js.map +1 -1
  70. package/dist/dev-shutdown.d.ts +2 -0
  71. package/dist/dev-shutdown.d.ts.map +1 -1
  72. package/dist/dev-shutdown.js +29 -15
  73. package/dist/dev-shutdown.js.map +1 -1
  74. package/dist/dev-task-colors.d.ts +3 -1
  75. package/dist/dev-task-colors.d.ts.map +1 -1
  76. package/dist/dev-task-colors.js +23 -3
  77. package/dist/dev-task-colors.js.map +1 -1
  78. package/dist/docker-runtime.js +1 -1
  79. package/dist/docker-runtime.js.map +1 -1
  80. package/dist/engine-push-output.d.ts +1 -0
  81. package/dist/engine-push-output.d.ts.map +1 -1
  82. package/dist/engine-push-output.js +4 -1
  83. package/dist/engine-push-output.js.map +1 -1
  84. package/dist/ensure-binary.d.ts.map +1 -1
  85. package/dist/ensure-binary.js +14 -2
  86. package/dist/ensure-binary.js.map +1 -1
  87. package/dist/ensure-component-binaries.d.ts +17 -0
  88. package/dist/ensure-component-binaries.d.ts.map +1 -0
  89. package/dist/ensure-component-binaries.js +45 -0
  90. package/dist/ensure-component-binaries.js.map +1 -0
  91. package/dist/ensure-dev-api-config.d.ts +3 -0
  92. package/dist/ensure-dev-api-config.d.ts.map +1 -0
  93. package/dist/ensure-dev-api-config.js +17 -0
  94. package/dist/ensure-dev-api-config.js.map +1 -0
  95. package/dist/init-dependency-versions.d.ts +13 -0
  96. package/dist/init-dependency-versions.d.ts.map +1 -0
  97. package/dist/init-dependency-versions.js +53 -0
  98. package/dist/init-dependency-versions.js.map +1 -0
  99. package/dist/init-package-json.d.ts +21 -0
  100. package/dist/init-package-json.d.ts.map +1 -0
  101. package/dist/init-package-json.js +46 -0
  102. package/dist/init-package-json.js.map +1 -0
  103. package/dist/postgres-ctl.d.ts.map +1 -1
  104. package/dist/postgres-ctl.js +2 -1
  105. package/dist/postgres-ctl.js.map +1 -1
  106. package/dist/required-host-components.d.ts +11 -0
  107. package/dist/required-host-components.d.ts.map +1 -0
  108. package/dist/required-host-components.js +25 -0
  109. package/dist/required-host-components.js.map +1 -0
  110. package/dist/resolve-api-url.d.ts +6 -0
  111. package/dist/resolve-api-url.d.ts.map +1 -0
  112. package/dist/resolve-api-url.js +39 -0
  113. package/dist/resolve-api-url.js.map +1 -0
  114. package/dist/rest-cache-admin.d.ts +33 -0
  115. package/dist/rest-cache-admin.d.ts.map +1 -0
  116. package/dist/rest-cache-admin.js +96 -0
  117. package/dist/rest-cache-admin.js.map +1 -0
  118. package/dist/scripts/postinstall.d.ts +4 -6
  119. package/dist/scripts/postinstall.d.ts.map +1 -1
  120. package/dist/scripts/postinstall.js +8 -41
  121. package/dist/scripts/postinstall.js.map +1 -1
  122. package/dist/self-host-compose.d.ts.map +1 -1
  123. package/dist/self-host-compose.js +23 -13
  124. package/dist/self-host-compose.js.map +1 -1
  125. package/dist/studio-dev-server.d.ts +4 -2
  126. package/dist/studio-dev-server.d.ts.map +1 -1
  127. package/dist/studio-dev-server.js +3 -2
  128. package/dist/studio-dev-server.js.map +1 -1
  129. package/dist/ui/brand.d.ts +2 -5
  130. package/dist/ui/brand.d.ts.map +1 -1
  131. package/dist/ui/brand.js +2 -7
  132. package/dist/ui/brand.js.map +1 -1
  133. package/dist/ui/clack-api.d.ts +44 -0
  134. package/dist/ui/clack-api.d.ts.map +1 -0
  135. package/dist/ui/clack-api.js +76 -0
  136. package/dist/ui/clack-api.js.map +1 -0
  137. package/dist/ui/clack.d.ts +14 -0
  138. package/dist/ui/clack.d.ts.map +1 -0
  139. package/dist/ui/clack.js +147 -0
  140. package/dist/ui/clack.js.map +1 -0
  141. package/dist/ui/components/FlowSpinner.d.ts +5 -0
  142. package/dist/ui/components/FlowSpinner.d.ts.map +1 -0
  143. package/dist/ui/components/FlowSpinner.js +8 -0
  144. package/dist/ui/components/FlowSpinner.js.map +1 -0
  145. package/dist/ui/components/LogoWordmark.d.ts +9 -0
  146. package/dist/ui/components/LogoWordmark.d.ts.map +1 -0
  147. package/dist/ui/components/LogoWordmark.js +17 -0
  148. package/dist/ui/components/LogoWordmark.js.map +1 -0
  149. package/dist/ui/components/PromptPanel.d.ts +5 -0
  150. package/dist/ui/components/PromptPanel.d.ts.map +1 -0
  151. package/dist/ui/components/PromptPanel.js +7 -0
  152. package/dist/ui/components/PromptPanel.js.map +1 -0
  153. package/dist/ui/components/SelectField.d.ts +11 -0
  154. package/dist/ui/components/SelectField.d.ts.map +1 -0
  155. package/dist/ui/components/SelectField.js +19 -0
  156. package/dist/ui/components/SelectField.js.map +1 -0
  157. package/dist/ui/confirm.d.ts +2 -1
  158. package/dist/ui/confirm.d.ts.map +1 -1
  159. package/dist/ui/confirm.js +4 -4
  160. package/dist/ui/confirm.js.map +1 -1
  161. package/dist/ui/dev/DevDashboard.d.ts +8 -0
  162. package/dist/ui/dev/DevDashboard.d.ts.map +1 -0
  163. package/dist/ui/dev/DevDashboard.js +151 -0
  164. package/dist/ui/dev/DevDashboard.js.map +1 -0
  165. package/dist/ui/dev/DevReadyPanelView.d.ts +7 -0
  166. package/dist/ui/dev/DevReadyPanelView.d.ts.map +1 -0
  167. package/dist/ui/dev/DevReadyPanelView.js +29 -0
  168. package/dist/ui/dev/DevReadyPanelView.js.map +1 -0
  169. package/dist/ui/fatal.d.ts.map +1 -1
  170. package/dist/ui/fatal.js +5 -11
  171. package/dist/ui/fatal.js.map +1 -1
  172. package/dist/ui/flows/FlowApp.d.ts +8 -0
  173. package/dist/ui/flows/FlowApp.d.ts.map +1 -0
  174. package/dist/ui/flows/FlowApp.js +42 -0
  175. package/dist/ui/flows/FlowApp.js.map +1 -0
  176. package/dist/ui/flows/prompt-fields.d.ts +48 -0
  177. package/dist/ui/flows/prompt-fields.d.ts.map +1 -0
  178. package/dist/ui/flows/prompt-fields.js +88 -0
  179. package/dist/ui/flows/prompt-fields.js.map +1 -0
  180. package/dist/ui/flows/types.d.ts +41 -0
  181. package/dist/ui/flows/types.d.ts.map +1 -0
  182. package/dist/ui/flows/types.js +2 -0
  183. package/dist/ui/flows/types.js.map +1 -0
  184. package/dist/ui/index.d.ts +1 -1
  185. package/dist/ui/index.js +1 -1
  186. package/dist/ui/messages.d.ts +1 -0
  187. package/dist/ui/messages.d.ts.map +1 -1
  188. package/dist/ui/messages.js +48 -8
  189. package/dist/ui/messages.js.map +1 -1
  190. package/dist/ui/progress.d.ts +5 -1
  191. package/dist/ui/progress.d.ts.map +1 -1
  192. package/dist/ui/progress.js +12 -3
  193. package/dist/ui/progress.js.map +1 -1
  194. package/dist/ui/prompts.d.ts +7 -5
  195. package/dist/ui/prompts.d.ts.map +1 -1
  196. package/dist/ui/prompts.js +15 -8
  197. package/dist/ui/prompts.js.map +1 -1
  198. package/dist/ui/runtime/cancel.d.ts +4 -0
  199. package/dist/ui/runtime/cancel.d.ts.map +1 -0
  200. package/dist/ui/runtime/cancel.js +6 -0
  201. package/dist/ui/runtime/cancel.js.map +1 -0
  202. package/dist/ui/runtime/command-chrome.d.ts +17 -0
  203. package/dist/ui/runtime/command-chrome.d.ts.map +1 -0
  204. package/dist/ui/runtime/command-chrome.js +123 -0
  205. package/dist/ui/runtime/command-chrome.js.map +1 -0
  206. package/dist/ui/runtime/dev-prompt-queue.d.ts +30 -0
  207. package/dist/ui/runtime/dev-prompt-queue.d.ts.map +1 -0
  208. package/dist/ui/runtime/dev-prompt-queue.js +57 -0
  209. package/dist/ui/runtime/dev-prompt-queue.js.map +1 -0
  210. package/dist/ui/runtime/flow-session.d.ts +4 -0
  211. package/dist/ui/runtime/flow-session.d.ts.map +1 -0
  212. package/dist/ui/runtime/flow-session.js +8 -0
  213. package/dist/ui/runtime/flow-session.js.map +1 -0
  214. package/dist/ui/runtime/run-clack-flow.d.ts +5 -0
  215. package/dist/ui/runtime/run-clack-flow.d.ts.map +1 -0
  216. package/dist/ui/runtime/run-clack-flow.js +34 -0
  217. package/dist/ui/runtime/run-clack-flow.js.map +1 -0
  218. package/dist/ui/theme.d.ts +24 -0
  219. package/dist/ui/theme.d.ts.map +1 -0
  220. package/dist/ui/theme.js +34 -0
  221. package/dist/ui/theme.js.map +1 -0
  222. package/dist/valkey-sidecar.d.ts +17 -0
  223. package/dist/valkey-sidecar.d.ts.map +1 -0
  224. package/dist/valkey-sidecar.js +94 -0
  225. package/dist/valkey-sidecar.js.map +1 -0
  226. package/package.json +9 -3
  227. package/src/binary-cache.ts +61 -0
  228. package/src/binary-download-lock.ts +124 -0
  229. package/src/cli-package-version.ts +15 -0
  230. package/src/cli.ts +6 -2
  231. package/src/commands/add.ts +26 -19
  232. package/src/commands/admin.ts +24 -6
  233. package/src/commands/cache.ts +105 -5
  234. package/src/commands/dev.ts +26 -21
  235. package/src/commands/init.ts +124 -45
  236. package/src/commands/link-helpers.ts +30 -29
  237. package/src/compose-local-server-image.ts +68 -0
  238. package/src/compose-rename.ts +5 -8
  239. package/src/dev-compose.ts +136 -51
  240. package/src/dev-log-bus.ts +18 -0
  241. package/src/dev-log-filter.ts +42 -0
  242. package/src/dev-ports.ts +22 -11
  243. package/src/dev-ready-panel.ts +63 -0
  244. package/src/dev-session-lock.ts +5 -8
  245. package/src/dev-session.ts +98 -18
  246. package/src/dev-shutdown.ts +29 -17
  247. package/src/dev-task-colors.ts +22 -3
  248. package/src/docker-runtime.ts +1 -1
  249. package/src/engine-push-output.ts +5 -1
  250. package/src/ensure-binary.ts +21 -1
  251. package/src/ensure-component-binaries.ts +71 -0
  252. package/src/ensure-dev-api-config.ts +18 -0
  253. package/src/init-dependency-versions.ts +69 -0
  254. package/src/init-package-json.ts +63 -0
  255. package/src/postgres-ctl.ts +2 -1
  256. package/src/required-host-components.ts +37 -0
  257. package/src/resolve-api-url.ts +43 -0
  258. package/src/rest-cache-admin.ts +135 -0
  259. package/src/scripts/postinstall.ts +9 -46
  260. package/src/self-host-compose.ts +22 -12
  261. package/src/studio-dev-server.ts +5 -4
  262. package/src/ui/README.md +13 -9
  263. package/src/ui/brand.ts +2 -8
  264. package/src/ui/clack-api.ts +123 -0
  265. package/src/ui/clack.ts +153 -0
  266. package/src/ui/components/FlowSpinner.tsx +15 -0
  267. package/src/ui/components/LogoWordmark.tsx +36 -0
  268. package/src/ui/components/PromptPanel.tsx +17 -0
  269. package/src/ui/components/SelectField.tsx +58 -0
  270. package/src/ui/confirm.ts +5 -4
  271. package/src/ui/dev/DevDashboard.tsx +213 -0
  272. package/src/ui/dev/DevReadyPanelView.tsx +67 -0
  273. package/src/ui/fatal.ts +5 -10
  274. package/src/ui/flows/FlowApp.tsx +67 -0
  275. package/src/ui/flows/prompt-fields.tsx +192 -0
  276. package/src/ui/flows/types.ts +50 -0
  277. package/src/ui/index.ts +1 -1
  278. package/src/ui/messages.ts +50 -8
  279. package/src/ui/progress.ts +14 -3
  280. package/src/ui/prompts.ts +17 -9
  281. package/src/ui/runtime/cancel.ts +6 -0
  282. package/src/ui/runtime/command-chrome.ts +147 -0
  283. package/src/ui/runtime/dev-prompt-queue.ts +84 -0
  284. package/src/ui/runtime/flow-session.ts +11 -0
  285. package/src/ui/runtime/run-clack-flow.tsx +52 -0
  286. package/src/ui/theme.ts +41 -0
  287. package/src/valkey-sidecar.ts +126 -0
  288. package/tests/binary-cache-cloud-overrides.test.ts +2 -2
  289. package/tests/binary-download-lock.test.ts +41 -0
  290. package/tests/cli-help.test.ts +7 -4
  291. package/tests/cli-package-version.test.ts +15 -0
  292. package/tests/command-chrome-coverage.test.ts +149 -0
  293. package/tests/command-chrome-wrap.test.ts +51 -0
  294. package/tests/config.test.ts +8 -4
  295. package/tests/dev-ready-panel.test.ts +20 -0
  296. package/tests/dev-ui.test.ts +42 -2
  297. package/tests/docker-runtime.test.ts +9 -20
  298. package/tests/ensure-dev-api-config.test.ts +22 -0
  299. package/tests/init-dependency-versions.test.ts +62 -0
  300. package/tests/init-package-json.test.ts +18 -0
  301. package/tests/init.test.ts +30 -3
  302. package/tests/required-host-components.test.ts +51 -0
  303. package/tests/resolve-api-url.test.ts +23 -0
  304. package/tests/rest-cache-admin.test.ts +28 -0
  305. package/tests/runtime-contract.test.ts +12 -3
  306. package/tests/tsconfig.json +8 -2
  307. package/tests/ui-confirm.test.ts +13 -9
  308. package/tests/ui-messages.test.ts +4 -18
  309. package/tsconfig.json +1 -0
  310. package/tsconfig.tsbuildinfo +1 -1
  311. package/vitest.config.ts +3 -0
  312. package/dist/dev-tui.d.ts +0 -24
  313. package/dist/dev-tui.d.ts.map +0 -1
  314. package/dist/dev-tui.js +0 -188
  315. package/dist/dev-tui.js.map +0 -1
  316. package/src/dev-tui.ts +0 -232
@@ -0,0 +1,36 @@
1
+ import React from "react"
2
+ import { Box, Text } from "ink"
3
+ import { layoutLogoBlock, pickLogoLines } from "../../dev-logo.js"
4
+ import { theme } from "../theme.js"
5
+
6
+ function truncatePlain(text: string, width: number): string {
7
+ if (text.length <= width) return text
8
+ if (width <= 1) return text.slice(0, width)
9
+ return `${text.slice(0, width - 1)}…`
10
+ }
11
+
12
+ export interface LogoWordmarkProps {
13
+ maxWidth?: number
14
+ /** Small caption under the figlet wordmark. */
15
+ tagline?: string
16
+ }
17
+
18
+ /** Figlet slant wordmark — native Ink colours (no embedded ANSI). */
19
+ export function LogoWordmark({ maxWidth, tagline }: LogoWordmarkProps): React.ReactElement {
20
+ const lines = layoutLogoBlock(pickLogoLines())
21
+
22
+ return (
23
+ <Box flexDirection="column" marginBottom={tagline ? 0 : 1}>
24
+ {lines.map((line, index) => (
25
+ <Text key={`logo-${index}`} bold color={theme.brand}>
26
+ {maxWidth !== undefined ? truncatePlain(line, maxWidth) : line}
27
+ </Text>
28
+ ))}
29
+ {tagline ? (
30
+ <Text dimColor color={theme.brand}>
31
+ {tagline}
32
+ </Text>
33
+ ) : null}
34
+ </Box>
35
+ )
36
+ }
@@ -0,0 +1,17 @@
1
+ import React, { type ReactNode } from "react"
2
+ import { Box } from "ink"
3
+ import { theme } from "../theme.js"
4
+
5
+ export function PromptPanel({ children }: { children: ReactNode }): React.ReactElement {
6
+ return (
7
+ <Box
8
+ marginTop={1}
9
+ borderStyle="round"
10
+ borderColor={theme.brand}
11
+ paddingX={1}
12
+ flexDirection="column"
13
+ >
14
+ {children}
15
+ </Box>
16
+ )
17
+ }
@@ -0,0 +1,58 @@
1
+ import React, { type FC } from "react"
2
+ import { Box, Text, useInput } from "ink"
3
+ import SelectInput from "ink-select-input"
4
+ import { theme } from "../theme.js"
5
+ import type { DevSelectOption } from "../runtime/dev-prompt-queue.js"
6
+
7
+ export interface SelectFieldProps {
8
+ message: string
9
+ options: DevSelectOption[]
10
+ initialValue?: string
11
+ onSubmit: (value: string) => void
12
+ onCancel: () => void
13
+ }
14
+
15
+ const BrandIndicator: FC<{ isSelected?: boolean }> = ({ isSelected }) => (
16
+ <Text {...(isSelected ? { color: theme.brand, bold: true } : { dimColor: true })}>
17
+ {isSelected ? "▸ " : " "}
18
+ </Text>
19
+ )
20
+
21
+ const BrandItem: FC<{ isSelected?: boolean; label: string }> = ({ isSelected, label }) => (
22
+ <Text {...(isSelected ? { color: theme.brand, bold: true } : {})}>{label}</Text>
23
+ )
24
+
25
+ export function SelectField({
26
+ message,
27
+ options,
28
+ initialValue,
29
+ onSubmit,
30
+ onCancel,
31
+ }: SelectFieldProps): React.ReactElement {
32
+ useInput((_input, key) => {
33
+ if (key.escape) onCancel()
34
+ })
35
+
36
+ const items = options.map((opt) => ({
37
+ label: opt.hint ? `${opt.label} — ${opt.hint}` : opt.label,
38
+ value: opt.value,
39
+ }))
40
+
41
+ const foundIndex = items.findIndex((item) => item.value === initialValue)
42
+
43
+ return (
44
+ <Box flexDirection="column">
45
+ <Text bold color={theme.brand}>
46
+ {message}
47
+ </Text>
48
+ <SelectInput
49
+ items={items}
50
+ {...(foundIndex >= 0 ? { initialIndex: foundIndex } : {})}
51
+ indicatorComponent={BrandIndicator}
52
+ itemComponent={BrandItem}
53
+ onSelect={(item) => onSubmit(item.value)}
54
+ />
55
+ <Text dimColor>↑/↓ or j/k · enter · esc cancel · 1-9 quick pick</Text>
56
+ </Box>
57
+ )
58
+ }
package/src/ui/confirm.ts CHANGED
@@ -1,5 +1,5 @@
1
- import * as p from "@clack/prompts"
2
1
  import { isInteractive } from "./interactive.js"
2
+ import { CLACK_CANCEL, isCancel, p } from "./clack.js"
3
3
  import { plain } from "./messages.js"
4
4
 
5
5
  export interface ConfirmOptions {
@@ -9,7 +9,7 @@ export interface ConfirmOptions {
9
9
  }
10
10
 
11
11
  /**
12
- * Yes/no confirmation — Clack in TTY, plain prompt or default otherwise.
12
+ * Yes/no confirmation — Ink overlay in dev, Ink flow in TTY, default otherwise.
13
13
  */
14
14
  export async function confirm(message: string, opts: ConfirmOptions = {}): Promise<boolean> {
15
15
  const fallback = opts.nonInteractive ?? opts.default ?? false
@@ -24,9 +24,8 @@ export async function confirm(message: string, opts: ConfirmOptions = {}): Promi
24
24
  initialValue: opts.default ?? false,
25
25
  })
26
26
 
27
- if (p.isCancel(value)) {
27
+ if (isCancel(value)) {
28
28
  p.cancel("Cancelled.")
29
- process.exit(0)
30
29
  }
31
30
 
32
31
  return value
@@ -36,3 +35,5 @@ export async function confirm(message: string, opts: ConfirmOptions = {}): Promi
36
35
  export function logSkippedConfirm(reason: string): void {
37
36
  plain(`${reason} (use --yes to skip confirmation)`)
38
37
  }
38
+
39
+ export { isCancel, CLACK_CANCEL } from "./clack.js"
@@ -0,0 +1,213 @@
1
+ import React, { useEffect, useState } from "react"
2
+ import { Box, Text, useInput, useStdout } from "ink"
3
+ import type { DevLogBus } from "../../dev-log-bus.js"
4
+ import { stripAnsi } from "../../dev-log-filter.js"
5
+ import { logoRowCount } from "../../dev-logo.js"
6
+ import { logInkColor, taskInkColor } from "../../dev-task-colors.js"
7
+ import { LogoWordmark } from "../components/LogoWordmark.js"
8
+ import { PromptPanel } from "../components/PromptPanel.js"
9
+ import { DevReadyPanelView } from "./DevReadyPanelView.js"
10
+ import { theme } from "../theme.js"
11
+ import { requestDevShutdown } from "../../dev-shutdown.js"
12
+ import {
13
+ getActiveDevPrompt,
14
+ resolveDevPrompt,
15
+ subscribeDevPrompts,
16
+ } from "../runtime/dev-prompt-queue.js"
17
+ import { CLACK_CANCEL } from "../runtime/cancel.js"
18
+ import { ActiveFlowPromptView } from "../flows/prompt-fields.js"
19
+ import type { ActiveFlowPrompt } from "../flows/types.js"
20
+
21
+ const TASK_COL_WIDTH = 22
22
+ const MIN_WIDTH = 60
23
+ const MIN_HEIGHT = 14
24
+ const TAGLINE = "local development"
25
+
26
+ function truncate(text: string, width: number): string {
27
+ if (width <= 0) return ""
28
+ if (text.length <= width) return text
29
+ if (width <= 1) return text.slice(0, width)
30
+ return `${text.slice(0, width - 1)}…`
31
+ }
32
+
33
+ function padPlain(text: string, width: number): string {
34
+ if (text.length >= width) return text
35
+ return text + " ".repeat(width - text.length)
36
+ }
37
+
38
+ function devPromptToFlowPrompt(prompt: NonNullable<ReturnType<typeof getActiveDevPrompt>>): ActiveFlowPrompt {
39
+ switch (prompt.kind) {
40
+ case "confirm":
41
+ return {
42
+ kind: "confirm",
43
+ message: prompt.message,
44
+ initialValue: typeof prompt.initialValue === "boolean" ? prompt.initialValue : false,
45
+ }
46
+ case "text":
47
+ return {
48
+ kind: "text",
49
+ message: prompt.message,
50
+ ...(prompt.defaultValue !== undefined ? { defaultValue: prompt.defaultValue } : {}),
51
+ ...(prompt.placeholder !== undefined ? { placeholder: prompt.placeholder } : {}),
52
+ ...(prompt.validate !== undefined ? { validate: prompt.validate } : {}),
53
+ }
54
+ case "password":
55
+ return { kind: "password", message: prompt.message }
56
+ case "select":
57
+ return {
58
+ kind: "select",
59
+ message: prompt.message,
60
+ options: prompt.options ?? [],
61
+ ...(typeof prompt.initialValue === "string" ? { initialValue: prompt.initialValue } : {}),
62
+ }
63
+ default:
64
+ return { kind: "confirm", message: prompt.message, initialValue: false }
65
+ }
66
+ }
67
+
68
+ function scrollHint(scrollFromBottom: number, total: number, visible: number): string {
69
+ if (total <= visible) return "all lines visible"
70
+ if (scrollFromBottom === 0) return "latest"
71
+ if (scrollFromBottom >= total - visible) return "oldest"
72
+ return `${scrollFromBottom} lines up from latest`
73
+ }
74
+
75
+ interface DevDashboardProps {
76
+ bus: DevLogBus
77
+ }
78
+
79
+ export function DevDashboard({ bus }: DevDashboardProps): React.ReactElement {
80
+ const { stdout } = useStdout()
81
+ const cols = Math.max(MIN_WIDTH, stdout.columns ?? 80)
82
+ const rows = Math.max(MIN_HEIGHT, stdout.rows ?? 24)
83
+ const [, tick] = useState(0)
84
+ const [scrollFromBottom, setScrollFromBottom] = useState(0)
85
+ const [activePrompt, setActivePrompt] = useState(getActiveDevPrompt())
86
+
87
+ useEffect(() => {
88
+ const offBus = bus.onUpdate(() => tick((n) => n + 1))
89
+ const offPrompt = subscribeDevPrompts(() => setActivePrompt(getActiveDevPrompt()))
90
+ return () => {
91
+ offBus()
92
+ offPrompt()
93
+ }
94
+ }, [bus])
95
+
96
+ const promptOpen = activePrompt !== null
97
+ const focusedId = bus.getFocusedTaskId()
98
+ const taskIds = bus.getTaskOrder()
99
+ const readyPanel = bus.getReadyPanel()
100
+ const readyRows = readyPanel ? bus.readyPanelRowCount() : 0
101
+ const logoRows = logoRowCount() + 1 // figlet + tagline
102
+ const chromeRows = logoRows + readyRows + 2 // separator + keybindings line
103
+ const logHeight = Math.max(4, rows - chromeRows - (promptOpen ? 6 : 0) - 1)
104
+ const logWidth = cols - TASK_COL_WIDTH - 1
105
+
106
+ useInput((input, key) => {
107
+ if (promptOpen) return
108
+
109
+ if ((key.ctrl && input === "c") || input === "\x03") {
110
+ requestDevShutdown()
111
+ return
112
+ }
113
+
114
+ if (key.downArrow || input === "j") {
115
+ bus.focusNext()
116
+ setScrollFromBottom(0)
117
+ return
118
+ }
119
+ if (key.upArrow || input === "k") {
120
+ bus.focusPrevious()
121
+ setScrollFromBottom(0)
122
+ return
123
+ }
124
+ if (input === "u") {
125
+ setScrollFromBottom((s) =>
126
+ Math.min(s + 3, Math.max(0, (bus.getTask(focusedId)?.lines.length ?? 0) - logHeight)),
127
+ )
128
+ return
129
+ }
130
+ if (input === "d") {
131
+ setScrollFromBottom((s) => Math.max(s - 3, 0))
132
+ return
133
+ }
134
+ if (input === "g") {
135
+ setScrollFromBottom(Math.max(0, (bus.getTask(focusedId)?.lines.length ?? 0) - logHeight))
136
+ return
137
+ }
138
+ if (input === "G") {
139
+ setScrollFromBottom(0)
140
+ }
141
+ })
142
+
143
+ const taskLines = bus.getTask(focusedId)?.lines ?? []
144
+ const totalLines = taskLines.length
145
+ const end = totalLines - scrollFromBottom
146
+ const start = Math.max(0, end - logHeight)
147
+
148
+ return (
149
+ <Box flexDirection="column" width={cols} height={rows}>
150
+ <LogoWordmark maxWidth={cols} tagline={TAGLINE} />
151
+ {readyPanel ? <DevReadyPanelView panel={readyPanel} width={cols} /> : null}
152
+ <Text dimColor>{"─".repeat(cols)}</Text>
153
+ <Text dimColor>
154
+ {truncate(" ↑/k ↓/j task u/d scroll g/G top/bottom Ctrl+C quit", cols)}
155
+ </Text>
156
+
157
+ {Array.from({ length: logHeight }, (_, row) => {
158
+ const taskId = taskIds[row]
159
+ const task = taskId ? bus.getTask(taskId) : undefined
160
+ let left = " ".repeat(TASK_COL_WIDTH)
161
+ if (task && taskId) {
162
+ const marker = taskId === focusedId ? "▶" : " "
163
+ const unread = task.unread ? " •" : " "
164
+ const label = truncate(`${marker} ${task.title}${unread}`, TASK_COL_WIDTH - 1)
165
+ left = padPlain(label, TASK_COL_WIDTH)
166
+ }
167
+
168
+ const index = start + row
169
+ const rawFull = index >= end || index >= totalLines ? "" : (taskLines[index] ?? "")
170
+ const raw = stripAnsi(rawFull)
171
+ const logLine = padPlain(truncate(raw, logWidth), logWidth)
172
+
173
+ return (
174
+ <Box key={`row-${row}`}>
175
+ <Text
176
+ {...(taskId === focusedId ? { bold: true } : {})}
177
+ color={taskId ? taskInkColor(taskId) : theme.dim}
178
+ >
179
+ {left}
180
+ </Text>
181
+ <Text dimColor>│</Text>
182
+ <Text
183
+ {...(raw
184
+ ? { color: logInkColor(focusedId, raw) }
185
+ : { dimColor: true })}
186
+ >
187
+ {logLine}
188
+ </Text>
189
+ </Box>
190
+ )
191
+ })}
192
+
193
+ <Text dimColor>{"─".repeat(cols)}</Text>
194
+ <Text>
195
+ <Text dimColor> focused </Text>
196
+ <Text color={taskInkColor(focusedId)} bold>
197
+ {bus.getTask(focusedId)?.title ?? focusedId}
198
+ </Text>
199
+ <Text dimColor>{` · ${scrollHint(scrollFromBottom, totalLines, logHeight)}`}</Text>
200
+ </Text>
201
+
202
+ {promptOpen && activePrompt ? (
203
+ <PromptPanel>
204
+ <ActiveFlowPromptView
205
+ prompt={devPromptToFlowPrompt(activePrompt)}
206
+ onSubmit={(value) => resolveDevPrompt(value)}
207
+ onCancel={() => resolveDevPrompt(CLACK_CANCEL)}
208
+ />
209
+ </PromptPanel>
210
+ ) : null}
211
+ </Box>
212
+ )
213
+ }
@@ -0,0 +1,67 @@
1
+ import React from "react"
2
+ import { Box, Text } from "ink"
3
+ import type { DevReadyPanel } from "../../dev-ready-panel.js"
4
+ import { theme } from "../theme.js"
5
+
6
+ function truncate(text: string, width: number): string {
7
+ if (width <= 0) return ""
8
+ if (text.length <= width) return text
9
+ if (width <= 1) return text.slice(0, width)
10
+ return `${text.slice(0, width - 1)}…`
11
+ }
12
+
13
+ function shortenKey(key: string, width: number): string {
14
+ if (key.length <= width) return key
15
+ if (width <= 12) return truncate(key, width)
16
+ return `${key.slice(0, width - 10)}…${key.slice(-8)}`
17
+ }
18
+
19
+ /** Inner text width inside the round border + horizontal padding. */
20
+ function panelInnerWidth(outerWidth: number): number {
21
+ return Math.max(20, outerWidth - 4)
22
+ }
23
+
24
+ export function DevReadyPanelView({
25
+ panel,
26
+ width,
27
+ }: {
28
+ panel: DevReadyPanel
29
+ width: number
30
+ }): React.ReactElement {
31
+ const innerWidth = panelInnerWidth(width)
32
+ const labelWidth = 14
33
+
34
+ return (
35
+ <Box
36
+ flexDirection="column"
37
+ marginBottom={1}
38
+ borderStyle="round"
39
+ borderColor={theme.brand}
40
+ paddingX={1}
41
+ >
42
+ <Text bold color={theme.brand}>
43
+ {truncate(panel.title, innerWidth)}
44
+ </Text>
45
+ {panel.links.map((link) => (
46
+ <Box key={`${link.label}-${link.url}`}>
47
+ <Text dimColor>{truncate(link.label.padEnd(labelWidth), labelWidth)}</Text>
48
+ <Text color={theme.info}>{truncate(link.url, innerWidth - labelWidth)}</Text>
49
+ </Box>
50
+ ))}
51
+ {panel.hints?.map((hint) => (
52
+ <Text key={hint} dimColor>
53
+ {truncate(hint, innerWidth)}
54
+ </Text>
55
+ ))}
56
+ {panel.anonKey ? (
57
+ <>
58
+ <Text dimColor>API keys (local dev)</Text>
59
+ <Text dimColor>{`anon ${shortenKey(panel.anonKey, innerWidth - 5)}`}</Text>
60
+ {panel.serviceRoleKey ? (
61
+ <Text dimColor>{`svc ${shortenKey(panel.serviceRoleKey, innerWidth - 5)}`}</Text>
62
+ ) : null}
63
+ </>
64
+ ) : null}
65
+ </Box>
66
+ )
67
+ }
package/src/ui/fatal.ts CHANGED
@@ -1,8 +1,7 @@
1
- import * as p from "@clack/prompts"
2
1
  import type { DockerBrandOptions } from "../docker-runtime.js"
3
2
  import { isInteractive } from "./interactive.js"
4
3
  import { error, plain } from "./messages.js"
5
- import { clack, printLogo } from "./prompts.js"
4
+ import { printLogo } from "./prompts.js"
6
5
 
7
6
  export type FatalOptions = {
8
7
  brand?: DockerBrandOptions
@@ -13,19 +12,15 @@ export type FatalOptions = {
13
12
  export function fatalError(message: string, hints: string[] = [], opts?: FatalOptions): never {
14
13
  if (opts?.brand && isInteractive()) {
15
14
  printLogo()
16
- clack.intro(opts.brand.intro)
15
+ plain(opts.brand.intro)
17
16
  }
18
17
 
19
18
  error(message)
20
19
 
21
20
  if (hints.length > 0) {
22
- if (isInteractive()) {
23
- p.note(hints.join("\n\n"))
24
- } else {
25
- for (const hint of hints) {
26
- plain()
27
- plain(` ${hint}`)
28
- }
21
+ for (const hint of hints) {
22
+ plain()
23
+ plain(` ${hint}`)
29
24
  }
30
25
  }
31
26
 
@@ -0,0 +1,67 @@
1
+ import React, { useEffect, useRef, useState } from "react"
2
+ import { Box } from "ink"
3
+ import { PromptPanel } from "../components/PromptPanel.js"
4
+ import { CLACK_CANCEL } from "../runtime/cancel.js"
5
+ import {
6
+ ActiveFlowPromptView,
7
+ FlowLogPane,
8
+ FlowLogoHeader,
9
+ FlowSpinnerLine,
10
+ } from "./prompt-fields.js"
11
+ import type { ActiveFlowPrompt, FlowController, FlowLogLine } from "./types.js"
12
+
13
+ interface FlowAppProps {
14
+ bind: (controller: FlowController) => void
15
+ }
16
+
17
+ export function FlowApp({ bind }: FlowAppProps): React.ReactElement {
18
+ const [logLines, setLogLines] = useState<FlowLogLine[]>([])
19
+ const [spinner, setSpinner] = useState<string | null>(null)
20
+ const [prompt, setPrompt] = useState<ActiveFlowPrompt | null>(null)
21
+ const [promptResolver, setPromptResolver] = useState<((value: unknown) => void) | null>(null)
22
+ const bound = useRef(false)
23
+
24
+ useEffect(() => {
25
+ if (bound.current) return
26
+ bound.current = true
27
+ const controller: FlowController = {
28
+ appendLog(level, text) {
29
+ setLogLines((lines) => [...lines, { level, text }])
30
+ },
31
+ appendPlain(text) {
32
+ setLogLines((lines) => [...lines, { level: "plain", text }])
33
+ },
34
+ setSpinner(message) {
35
+ setSpinner(message)
36
+ },
37
+ waitForPrompt<T>(spec: ActiveFlowPrompt): Promise<T> {
38
+ return new Promise<T>((resolve) => {
39
+ setPrompt(spec)
40
+ setPromptResolver(() => (value: unknown) => {
41
+ setPrompt(null)
42
+ setPromptResolver(null)
43
+ resolve(value as T)
44
+ })
45
+ })
46
+ },
47
+ }
48
+ bind(controller)
49
+ }, [bind])
50
+
51
+ return (
52
+ <Box flexDirection="column" paddingX={1}>
53
+ <FlowLogoHeader />
54
+ <FlowLogPane lines={logLines} />
55
+ {spinner ? <FlowSpinnerLine message={spinner} /> : null}
56
+ {prompt && promptResolver ? (
57
+ <PromptPanel>
58
+ <ActiveFlowPromptView
59
+ prompt={prompt}
60
+ onSubmit={(value) => promptResolver(value)}
61
+ onCancel={() => promptResolver(CLACK_CANCEL)}
62
+ />
63
+ </PromptPanel>
64
+ ) : null}
65
+ </Box>
66
+ )
67
+ }