@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,52 @@
1
+ import React, { useRef } from "react"
2
+ import { render } from "ink"
3
+ import { isInteractive } from "../interactive.js"
4
+ import { createClackApi, type ClackApi } from "../clack-api.js"
5
+ import { FlowApp } from "../flows/FlowApp.js"
6
+
7
+ interface RunClackFlowRootProps {
8
+ run: (api: ClackApi) => Promise<unknown>
9
+ onComplete: (value: unknown) => void
10
+ onError: (err: unknown) => void
11
+ }
12
+
13
+ function RunClackFlowRoot({ run, onComplete, onError }: RunClackFlowRootProps): React.ReactElement {
14
+ const started = useRef(false)
15
+
16
+ return (
17
+ <FlowApp
18
+ bind={(controller) => {
19
+ if (started.current) return
20
+ started.current = true
21
+ void run(createClackApi(controller))
22
+ .then(onComplete)
23
+ .catch(onError)
24
+ }}
25
+ />
26
+ )
27
+ }
28
+
29
+ /** Mount a single Ink flow session (init wizard, standalone confirm, etc.). */
30
+ export async function runClackFlow<T>(run: (api: ClackApi) => Promise<T>): Promise<T> {
31
+ if (!isInteractive()) {
32
+ throw new Error("Interactive Ink flow requires a TTY.")
33
+ }
34
+
35
+ return new Promise<T>((resolve, reject) => {
36
+ const instance = render(
37
+ <RunClackFlowRoot
38
+ run={run as (api: ClackApi) => Promise<unknown>}
39
+ onComplete={(value) => {
40
+ instance.unmount()
41
+ resolve(value as T)
42
+ }}
43
+ onError={(err) => {
44
+ instance.unmount()
45
+ reject(err)
46
+ }}
47
+ />,
48
+ )
49
+ })
50
+ }
51
+
52
+ export { CLACK_CANCEL } from "./cancel.js"
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Supatype terminal design tokens — shared by Ink components and plain CI output.
3
+ */
4
+
5
+ export const RESET = "\x1b[0m"
6
+ export const DIM = "\x1b[2m"
7
+ export const BOLD = "\x1b[1m"
8
+
9
+ export const theme = {
10
+ brand: "magenta" as const,
11
+ brandHex: "#c084fc",
12
+ info: "cyan" as const,
13
+ warn: "yellow" as const,
14
+ error: "red" as const,
15
+ success: "green" as const,
16
+ dim: "gray" as const,
17
+ muted: "gray" as const,
18
+ } as const
19
+
20
+ /** Legacy ANSI exports used by dev-logo / dev-task-colors. */
21
+ export const BRAND_COLOR = "\x1b[35m"
22
+
23
+ export function brandStyle(text: string): string {
24
+ return `${BRAND_COLOR}${BOLD}${text}${RESET}`
25
+ }
26
+
27
+ export function plainInfo(message: string): string {
28
+ return `${theme.info === "cyan" ? "\x1b[36m" : ""}ℹ ${message}${RESET}`
29
+ }
30
+
31
+ export function plainWarn(message: string): string {
32
+ return `\x1b[33m⚠ ${message}${RESET}`
33
+ }
34
+
35
+ export function plainError(message: string): string {
36
+ return `\x1b[31m✗ ${message}${RESET}`
37
+ }
38
+
39
+ export function plainSuccess(message: string): string {
40
+ return `\x1b[32m✓ ${message}${RESET}`
41
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Lightweight Valkey container for native `supatype dev` (REST response cache).
3
+ */
4
+
5
+ import { spawnSync } from "node:child_process"
6
+ import { createConnection } from "node:net"
7
+
8
+ const DEFAULT_HOST = "127.0.0.1"
9
+ const DEFAULT_PORT = 6379
10
+ const IMAGE = "valkey/valkey:8-alpine"
11
+
12
+ function containerName(projectName: string): string {
13
+ const safe = projectName.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 48)
14
+ return `supatype-valkey-dev-${safe}`
15
+ }
16
+
17
+ function dockerAvailable(): boolean {
18
+ const r = spawnSync("docker", ["info"], { stdio: "ignore" })
19
+ return r.status === 0
20
+ }
21
+
22
+ function isAddrReachable(host: string, port: number, timeoutMs = 800): boolean {
23
+ try {
24
+ const r = spawnSync(
25
+ process.execPath,
26
+ [
27
+ "-e",
28
+ `const n=require('net');const s=n.createConnection({host:${JSON.stringify(host)},port:${port}});` +
29
+ `s.on('connect',()=>{s.end();process.exit(0)});s.on('error',()=>process.exit(1));` +
30
+ `setTimeout(()=>process.exit(1),${timeoutMs})`,
31
+ ],
32
+ { stdio: "ignore" },
33
+ )
34
+ return r.status === 0
35
+ } catch {
36
+ return false
37
+ }
38
+ }
39
+
40
+ function sleepMs(ms: number): void {
41
+ const end = Date.now() + ms
42
+ while (Date.now() < end) {
43
+ /* spin */
44
+ }
45
+ }
46
+
47
+ export interface ValkeySidecarResult {
48
+ addr: string
49
+ containerName: string | null
50
+ started: boolean
51
+ }
52
+
53
+ /**
54
+ * Ensure Valkey is reachable at 127.0.0.1:6379 for native dev.
55
+ * Starts a named Docker container when Docker is available and nothing is listening.
56
+ */
57
+ export function ensureValkeySidecar(projectName: string): ValkeySidecarResult {
58
+ const envAddr = process.env["SUPATYPE_VALKEY_ADDR"]?.trim()
59
+ if (envAddr) {
60
+ return { addr: envAddr, containerName: null, started: false }
61
+ }
62
+
63
+ const addr = `${DEFAULT_HOST}:${DEFAULT_PORT}`
64
+
65
+ if (isAddrReachable(DEFAULT_HOST, DEFAULT_PORT)) {
66
+ return { addr, containerName: null, started: false }
67
+ }
68
+
69
+ if (!dockerAvailable()) {
70
+ console.warn(
71
+ "[supatype] ⚠ Valkey not available (Docker not running). " +
72
+ "REST server cache (`cache({ server: true })`) will bypass until Valkey is reachable.",
73
+ )
74
+ return { addr: "", containerName: null, started: false }
75
+ }
76
+
77
+ const name = containerName(projectName)
78
+ spawnSync("docker", ["rm", "-f", name], { stdio: "ignore" })
79
+
80
+ const run = spawnSync(
81
+ "docker",
82
+ ["run", "-d", "--name", name, "-p", `${DEFAULT_PORT}:6379`, IMAGE],
83
+ { encoding: "utf8" },
84
+ )
85
+
86
+ if (run.status !== 0) {
87
+ const detail = (run.stderr || run.stdout || "").trim()
88
+ console.warn(
89
+ `[supatype] ⚠ Could not start Valkey sidecar: ${detail || `exit ${run.status}`}\n` +
90
+ " REST server cache will bypass until Valkey is available.",
91
+ )
92
+ return { addr: "", containerName: null, started: false }
93
+ }
94
+
95
+ for (let i = 0; i < 30; i++) {
96
+ if (isAddrReachable(DEFAULT_HOST, DEFAULT_PORT)) {
97
+ console.log(`[supatype] Valkey sidecar running (${addr})`)
98
+ return { addr, containerName: name, started: true }
99
+ }
100
+ sleepMs(100)
101
+ }
102
+
103
+ console.warn("[supatype] ⚠ Valkey sidecar started but not reachable yet — cache may bypass initially.")
104
+ return { addr, containerName: name, started: true }
105
+ }
106
+
107
+ export function stopValkeySidecar(containerName: string | null): void {
108
+ if (!containerName) return
109
+ spawnSync("docker", ["rm", "-f", containerName], { stdio: "ignore" })
110
+ }
111
+
112
+ /** @internal Test helper — probe without subprocess. */
113
+ export function probeTcp(host: string, port: number): Promise<boolean> {
114
+ return new Promise((resolve) => {
115
+ const s = createConnection({ host, port })
116
+ s.once("connect", () => {
117
+ s.destroy()
118
+ resolve(true)
119
+ })
120
+ s.once("error", () => resolve(false))
121
+ setTimeout(() => {
122
+ s.destroy()
123
+ resolve(false)
124
+ }, 800)
125
+ })
126
+ }
@@ -98,7 +98,7 @@ describe("VERSION_PIN_LOCAL + resolveBinary", () => {
98
98
 
99
99
  it("throws when versions.engine is local without overrides.engine", async () => {
100
100
  const cfg = baseConfig()
101
- cfg.versions.engine = VERSION_PIN_LOCAL
101
+ cfg.versions!.engine = VERSION_PIN_LOCAL
102
102
  await expect(resolveBinary("engine", cfg)).rejects.toThrow(/overrides\.engine/)
103
103
  })
104
104
 
@@ -106,7 +106,7 @@ describe("VERSION_PIN_LOCAL + resolveBinary", () => {
106
106
  const fakeBin = join(tmp, "supatype-engine-local")
107
107
  writeFileSync(fakeBin, "#")
108
108
  const cfg = baseConfig({ engine: fakeBin })
109
- cfg.versions.engine = VERSION_PIN_LOCAL
109
+ cfg.versions!.engine = VERSION_PIN_LOCAL
110
110
  const p = await resolveBinary("engine", cfg)
111
111
  expect(p.replace(/\\/g, "/")).toContain("supatype-engine-local")
112
112
  })
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from "vitest"
2
+ import { existsSync, mkdirSync, rmSync } from "node:fs"
3
+ import { join } from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import {
6
+ tryAcquireDownloadLock,
7
+ releaseDownloadLock,
8
+ isDownloadInProgress,
9
+ clearDownloadLock,
10
+ } from "../src/binary-download-lock.js"
11
+ import { cachePath } from "../src/binary-cache.js"
12
+
13
+ const VERSION = "9.9.9-test"
14
+
15
+ describe("binary-download-lock", () => {
16
+ let dir: string
17
+
18
+ beforeEach(() => {
19
+ dir = cachePath("deno", VERSION)
20
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true })
21
+ mkdirSync(dir, { recursive: true })
22
+ })
23
+
24
+ afterEach(() => {
25
+ clearDownloadLock("deno", VERSION)
26
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true })
27
+ })
28
+
29
+ it("acquires and releases a download lock", () => {
30
+ expect(tryAcquireDownloadLock("deno", VERSION)).toBe(true)
31
+ expect(isDownloadInProgress("deno", VERSION)).toBe(true)
32
+ releaseDownloadLock("deno", VERSION)
33
+ expect(isDownloadInProgress("deno", VERSION)).toBe(false)
34
+ })
35
+
36
+ it("rejects a second acquire while the lock is held", () => {
37
+ expect(tryAcquireDownloadLock("deno", VERSION)).toBe(true)
38
+ expect(tryAcquireDownloadLock("deno", VERSION)).toBe(false)
39
+ releaseDownloadLock("deno", VERSION)
40
+ })
41
+ })
@@ -4,12 +4,15 @@
4
4
  */
5
5
  import { describe, it, expect } from "vitest"
6
6
  import { spawnSync } from "node:child_process"
7
- import { resolve, dirname } from "node:path"
7
+ import { readFileSync } from "node:fs"
8
+ import { resolve, dirname, join } from "node:path"
8
9
  import { fileURLToPath } from "node:url"
9
10
 
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url))
11
12
  const CLI_BIN = resolve(__dirname, "../bin/supatype.js")
12
- const DIST_CLI = resolve(__dirname, "../dist/cli.js")
13
+ const PACKAGE_VERSION = (
14
+ JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8")) as { version: string }
15
+ ).version
13
16
 
14
17
  function runCli(args: string[]): { stdout: string; stderr: string; exitCode: number } {
15
18
  const result = spawnSync(process.execPath, [CLI_BIN, ...args], {
@@ -51,10 +54,10 @@ describe("CLI binary (requires built dist/)", () => {
51
54
  }
52
55
  })
53
56
 
54
- it("--version prints a semver string", () => {
57
+ it("--version prints package.json version", () => {
55
58
  const { stdout, exitCode } = runCli(["--version"])
56
59
  expect(exitCode).toBe(0)
57
- expect(stdout.trim()).toMatch(/^\d+\.\d+\.\d+/)
60
+ expect(stdout.trim()).toBe(PACKAGE_VERSION)
58
61
  })
59
62
 
60
63
  it("self-update --help describes the command", () => {
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { readFileSync } from "node:fs"
3
+ import { join, dirname } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+ import { cliPackageVersion } from "../src/cli-package-version.js"
6
+
7
+ const packageJson = JSON.parse(
8
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), "../package.json"), "utf8"),
9
+ ) as { version: string }
10
+
11
+ describe("cliPackageVersion", () => {
12
+ it("matches packages/cli/package.json version", () => {
13
+ expect(cliPackageVersion()).toBe(packageJson.version)
14
+ })
15
+ })
@@ -0,0 +1,149 @@
1
+ import { Command } from "commander"
2
+ import { describe, expect, it } from "vitest"
3
+ import { registerInit } from "../src/commands/init.js"
4
+ import { registerDev } from "../src/commands/dev.js"
5
+ import { registerCache } from "../src/commands/cache.js"
6
+ import { registerSelfUpdate } from "../src/commands/self-update.js"
7
+ import { registerUpdate } from "../src/commands/update.js"
8
+ import { registerPg } from "../src/commands/pg.js"
9
+ import { registerPush } from "../src/commands/push.js"
10
+ import { registerDiff } from "../src/commands/diff.js"
11
+ import { registerPull } from "../src/commands/pull.js"
12
+ import { registerDoctor } from "../src/commands/doctor.js"
13
+ import { registerIntrospect } from "../src/commands/introspect.js"
14
+ import { registerAdopt } from "../src/commands/adopt.js"
15
+ import { registerGenerate } from "../src/commands/generate.js"
16
+ import { registerMigrate } from "../src/commands/migrate.js"
17
+ import { registerSeed } from "../src/commands/seed.js"
18
+ import { registerKeys } from "../src/commands/keys.js"
19
+ import { registerApp } from "../src/commands/app.js"
20
+ import { registerAdd } from "../src/commands/add.js"
21
+ import { registerSelfHost } from "../src/commands/self-host.js"
22
+ import { registerCloud } from "../src/commands/cloud.js"
23
+ import { registerEngine } from "../src/commands/engine.js"
24
+ import { registerDb } from "../src/commands/db.js"
25
+ import { registerDeploy } from "../src/commands/deploy.js"
26
+ import { registerStatus } from "../src/commands/status.js"
27
+ import { registerLogs } from "../src/commands/logs.js"
28
+ import { registerAdmin } from "../src/commands/admin.js"
29
+ import { registerFunctions } from "../src/commands/functions.js"
30
+ import { registerPlugins } from "../src/commands/plugins.js"
31
+ import { registerTypes } from "../src/commands/types.js"
32
+ import { registerMigrateFromV1 } from "../src/commands/migrate-from-v1.js"
33
+ import {
34
+ shouldExcludeCommandChrome,
35
+ wrapProgramActionsWithChrome,
36
+ } from "../src/ui/runtime/command-chrome.js"
37
+
38
+ type CommandWithAction = Command & {
39
+ parent?: Command | null
40
+ _actionHandler?: ((args: unknown) => unknown) | null
41
+ }
42
+
43
+ function commandPath(cmd: CommandWithAction, root: Command): string {
44
+ const segments: string[] = []
45
+ let current: CommandWithAction | null = cmd
46
+ while (current && current !== root) {
47
+ segments.unshift(current.name())
48
+ current = current.parent ?? null
49
+ }
50
+ return segments.join(" ")
51
+ }
52
+
53
+ function collectActionCommands(cmd: CommandWithAction, root: Command): CommandWithAction[] {
54
+ const out: CommandWithAction[] = []
55
+ if (cmd._actionHandler) out.push(cmd)
56
+ for (const sub of cmd.commands) {
57
+ out.push(...collectActionCommands(sub as CommandWithAction, root))
58
+ }
59
+ return out
60
+ }
61
+
62
+ function buildProgram(): Command {
63
+ const program = new Command().name("supatype")
64
+ registerInit(program)
65
+ registerDev(program)
66
+ registerCache(program)
67
+ registerSelfUpdate(program)
68
+ registerUpdate(program)
69
+ registerPg(program)
70
+ registerPush(program)
71
+ registerDiff(program)
72
+ registerPull(program)
73
+ registerDoctor(program)
74
+ registerIntrospect(program)
75
+ registerAdopt(program)
76
+ registerGenerate(program)
77
+ registerMigrate(program)
78
+ registerSeed(program)
79
+ registerKeys(program)
80
+ registerApp(program)
81
+ registerAdd(program)
82
+ registerSelfHost(program)
83
+ registerCloud(program)
84
+ registerEngine(program)
85
+ registerDb(program)
86
+ registerDeploy(program)
87
+ registerStatus(program)
88
+ registerLogs(program)
89
+ registerAdmin(program)
90
+ registerFunctions(program)
91
+ registerPlugins(program)
92
+ registerTypes(program)
93
+ registerMigrateFromV1(program)
94
+ wrapProgramActionsWithChrome(program)
95
+ return program
96
+ }
97
+
98
+ describe("CLI command chrome coverage", () => {
99
+ it("wraps every non-excluded leaf command", () => {
100
+ const program = buildProgram()
101
+ const root = program as CommandWithAction
102
+ const commands = collectActionCommands(root, program)
103
+
104
+ expect(commands.length).toBeGreaterThan(50)
105
+
106
+ const excluded: string[] = []
107
+ const wrapped: string[] = []
108
+
109
+ for (const cmd of commands) {
110
+ const path = commandPath(cmd, program)
111
+ const handlerBefore = cmd._actionHandler
112
+ // Re-wrap is idempotent only when already wrapped; detect first-wrap by path policy.
113
+ if (shouldExcludeCommandChrome(path)) {
114
+ excluded.push(path)
115
+ continue
116
+ }
117
+ wrapped.push(path)
118
+ expect(handlerBefore, `${path} should be wrapped`).toBeDefined()
119
+ }
120
+
121
+ expect(excluded).toEqual(
122
+ expect.arrayContaining([
123
+ "dev",
124
+ "init",
125
+ "link",
126
+ "add domain",
127
+ "logs",
128
+ "pg psql",
129
+ "functions serve",
130
+ "self-host compose logs",
131
+ ]),
132
+ )
133
+
134
+ expect(wrapped).toEqual(
135
+ expect.arrayContaining([
136
+ "push",
137
+ "diff",
138
+ "doctor",
139
+ "pull",
140
+ "cache list",
141
+ "cache rest list",
142
+ "deploy",
143
+ "deploy rollback",
144
+ "keys",
145
+ "generate",
146
+ ]),
147
+ )
148
+ })
149
+ })
@@ -0,0 +1,51 @@
1
+ import { Command } from "commander"
2
+ import { describe, expect, it, vi } from "vitest"
3
+ import {
4
+ shouldExcludeCommandChrome,
5
+ wrapProgramActionsWithChrome,
6
+ } from "../src/ui/runtime/command-chrome.js"
7
+
8
+ describe("shouldExcludeCommandChrome", () => {
9
+ it("excludes wizard and streaming commands", () => {
10
+ expect(shouldExcludeCommandChrome("dev")).toBe(true)
11
+ expect(shouldExcludeCommandChrome("init")).toBe(true)
12
+ expect(shouldExcludeCommandChrome("link")).toBe(true)
13
+ expect(shouldExcludeCommandChrome("add domain")).toBe(true)
14
+ expect(shouldExcludeCommandChrome("logs")).toBe(true)
15
+ expect(shouldExcludeCommandChrome("pg psql")).toBe(true)
16
+ expect(shouldExcludeCommandChrome("functions serve")).toBe(true)
17
+ expect(shouldExcludeCommandChrome("self-host compose logs")).toBe(true)
18
+ })
19
+
20
+ it("includes one-shot commands", () => {
21
+ expect(shouldExcludeCommandChrome("push")).toBe(false)
22
+ expect(shouldExcludeCommandChrome("doctor")).toBe(false)
23
+ expect(shouldExcludeCommandChrome("cache rest list")).toBe(false)
24
+ expect(shouldExcludeCommandChrome("deploy rollback")).toBe(false)
25
+ })
26
+ })
27
+
28
+ describe("wrapProgramActionsWithChrome", () => {
29
+ it("wraps leaf actions but skips excluded paths", () => {
30
+ const program = new Command()
31
+ const pushFn = vi.fn()
32
+ const devFn = vi.fn()
33
+
34
+ program.command("push").action(pushFn)
35
+ program.command("dev").action(devFn)
36
+
37
+ type CommandWithAction = Command & {
38
+ _actionHandler?: ((args: unknown) => unknown) | null
39
+ }
40
+
41
+ const pushCmd = program.commands.find((c) => c.name() === "push") as CommandWithAction
42
+ const devCmd = program.commands.find((c) => c.name() === "dev") as CommandWithAction
43
+ const pushHandlerBefore = pushCmd._actionHandler
44
+ const devHandlerBefore = devCmd._actionHandler
45
+
46
+ wrapProgramActionsWithChrome(program)
47
+
48
+ expect(pushCmd._actionHandler).not.toBe(pushHandlerBefore)
49
+ expect(devCmd._actionHandler).toBe(devHandlerBefore)
50
+ })
51
+ })
@@ -176,7 +176,7 @@ describe("loadConfig()", () => {
176
176
  })}`,
177
177
  )
178
178
  const cfg = loadConfig(tmpDir)
179
- expect(cfg.versions.engine).toBe("0.4.2")
179
+ expect(cfg.versions!.engine).toBe("0.4.2")
180
180
  expect(cfg.schema?.path).toBe("./a.ts")
181
181
  })
182
182
  })
@@ -207,7 +207,9 @@ describe("mergeProjectConfig()", () => {
207
207
  ...minimalProject("p"),
208
208
  email: { provider: "smtp", smtp: { host: "h1", port: 587, user: "u0" } },
209
209
  })
210
- const merged = mergeProjectConfig(base, { email: { smtp: { host: "h2", pass: "x" } } })
210
+ const merged = mergeProjectConfig(base, {
211
+ email: { smtp: { host: "h2", pass: "x" } },
212
+ } as Partial<typeof base>)
211
213
  expect(merged.email?.provider).toBe("smtp")
212
214
  expect(merged.email?.smtp?.host).toBe("h2")
213
215
  expect(merged.email?.smtp?.port).toBe(587)
@@ -222,7 +224,7 @@ describe("mergeProjectConfig()", () => {
222
224
  })
223
225
  const merged = mergeProjectConfig(base, {
224
226
  email: { send_email_hook_secrets: "v1,whsec_customsecret0000000000000000" },
225
- })
227
+ } as Partial<typeof base>)
226
228
  expect(merged.email?.send_email_hook).toBe(true)
227
229
  expect(merged.email?.send_email_hook_uri).toBe("http://old/hook")
228
230
  expect(merged.email?.send_email_hook_secrets).toBe("v1,whsec_customsecret0000000000000000")
@@ -233,7 +235,9 @@ describe("mergeProjectConfig()", () => {
233
235
  ...minimalProject("p"),
234
236
  app: { mode: "static", static_dir: "./dist", vite_dev_url: "http://127.0.0.1:1111" },
235
237
  })
236
- const merged = mergeProjectConfig(base, { app: { vite_dev_url: "http://127.0.0.1:5173" } })
238
+ const merged = mergeProjectConfig(base, {
239
+ app: { vite_dev_url: "http://127.0.0.1:5173" },
240
+ } as Partial<typeof base>)
237
241
  expect(merged.app?.vite_dev_url).toBe("http://127.0.0.1:5173")
238
242
  })
239
243
 
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { devReadyPanelRowCount } from "../src/dev-ready-panel.js"
3
+
4
+ describe("devReadyPanelRowCount", () => {
5
+ it("accounts for border, keys block, and hints", () => {
6
+ const rows = devReadyPanelRowCount({
7
+ title: "Services running",
8
+ links: [
9
+ { label: "API", url: "http://localhost:1" },
10
+ { label: "Studio", url: "http://localhost:1/studio/" },
11
+ ],
12
+ hints: ["Demo data: pnpm seed"],
13
+ anonKey: "anon-jwt",
14
+ serviceRoleKey: "svc-jwt",
15
+ })
16
+
17
+ // border(2) + title(1) + links(2) + hint(1) + keys header(1) + anon(1) + svc(1) + margin(1)
18
+ expect(rows).toBe(10)
19
+ })
20
+ })
@@ -2,9 +2,13 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
2
2
  import { mkdtempSync, writeFileSync } from "node:fs"
3
3
  import { join } from "node:path"
4
4
  import { tmpdir } from "node:os"
5
+
6
+ vi.mock("ink", () => ({
7
+ render: vi.fn(() => ({ unmount: vi.fn() })),
8
+ }))
5
9
  import { DevLogBus } from "../src/dev-log-bus.js"
6
- import { filterDevSubprocessLine, formatConsoleArgs, stripAnsi } from "../src/dev-log-filter.js"
7
- import { appendDevTaskLog, beginDevSession, endDevSession, resolveDevUiMode } from "../src/dev-session.js"
10
+ import { filterCommandLogLine, filterDevSubprocessLine, filterStackLogLine, formatConsoleArgs, stripAnsi } from "../src/dev-log-filter.js"
11
+ import { appendDevTaskLog, beginDevSession, endDevSession, getActiveDevSession, resolveDevUiMode, withDevSessionSuspended } from "../src/dev-session.js"
8
12
  import {
9
13
  layoutLogoBlock,
10
14
  logoRowCount,
@@ -25,6 +29,28 @@ describe("filterDevSubprocessLine()", () => {
25
29
  })
26
30
  })
27
31
 
32
+ describe("filterStackLogLine()", () => {
33
+ it("drops docker build progress but keeps supatype messages", () => {
34
+ expect(filterStackLogLine("#1 [internal] load build definition from Dockerfile")).toBe(false)
35
+ expect(filterStackLogLine(" Container supatype-demo-db-1 Running")).toBe(false)
36
+ expect(filterStackLogLine("[supatype] Waiting for Postgres (compose)...")).toBe(true)
37
+ expect(filterStackLogLine("Services running.")).toBe(true)
38
+ expect(filterStackLogLine(" API (Kong) http://localhost:18473")).toBe(true)
39
+ })
40
+
41
+ it("keeps override banner lines", () => {
42
+ expect(filterStackLogLine(" engine → ../supatype-schema-engine/bin/engine")).toBe(true)
43
+ })
44
+ })
45
+
46
+ describe("filterCommandLogLine()", () => {
47
+ it("keeps diff output but drops docker container progress", () => {
48
+ expect(filterCommandLogLine(" [+] add_column users.email")).toBe(true)
49
+ expect(filterCommandLogLine(" Container supatype-demo-db-1 Running")).toBe(false)
50
+ expect(filterCommandLogLine("[supatype] Schema pushed.")).toBe(true)
51
+ })
52
+ })
53
+
28
54
  describe("stripAnsi()", () => {
29
55
  it("removes colour codes", () => {
30
56
  expect(stripAnsi("\x1b[33mhello\x1b[0m")).toBe("hello")
@@ -78,6 +104,20 @@ describe("appendDevTaskLog()", () => {
78
104
  expect(session.bus.getTask("app")?.lines).toEqual(["Proxy mode: running npm run vite (/tmp/app)"])
79
105
  expect(session.bus.getTask("stack")?.lines ?? []).toEqual([])
80
106
  })
107
+
108
+ it("captures console.log to the bus before ink mounts", () => {
109
+ beginDevSession("tui")
110
+ console.log("[supatype] bootstrap line")
111
+ expect(getActiveDevSession()?.bus.getTask("stack")?.lines).toEqual(["bootstrap line"])
112
+ expect(getActiveDevSession()?.isInkMounted()).toBe(true)
113
+ })
114
+
115
+ it("withDevSessionSuspended is a no-op during bootstrap", async () => {
116
+ beginDevSession("tui")
117
+ await withDevSessionSuspended(async () => "ok")
118
+ expect(getActiveDevSession()?.isConsoleCaptured()).toBe(true)
119
+ expect(getActiveDevSession()?.isInkMounted()).toBe(true)
120
+ })
81
121
  })
82
122
 
83
123
  describe("dev-logo", () => {