@supatype/cli 0.1.1 → 0.1.3

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 (334) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +126 -99
  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 +12 -0
  22. package/dist/commands/admin.d.ts.map +1 -1
  23. package/dist/commands/admin.js +116 -26
  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 +26 -22
  31. package/dist/commands/dev.js.map +1 -1
  32. package/dist/commands/init.d.ts +3 -4
  33. package/dist/commands/init.d.ts.map +1 -1
  34. package/dist/commands/init.js +296 -121
  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/compose-local-server-image.d.ts +13 -0
  40. package/dist/compose-local-server-image.d.ts.map +1 -0
  41. package/dist/compose-local-server-image.js +57 -0
  42. package/dist/compose-local-server-image.js.map +1 -0
  43. package/dist/compose-rename.d.ts.map +1 -1
  44. package/dist/compose-rename.js +5 -8
  45. package/dist/compose-rename.js.map +1 -1
  46. package/dist/config.d.ts.map +1 -1
  47. package/dist/config.js +12 -1
  48. package/dist/config.js.map +1 -1
  49. package/dist/dev-compose.d.ts.map +1 -1
  50. package/dist/dev-compose.js +113 -56
  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 +16 -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.map +1 -1
  61. package/dist/dev-ports.js +19 -12
  62. package/dist/dev-ports.js.map +1 -1
  63. package/dist/dev-ready-panel.d.ts +21 -0
  64. package/dist/dev-ready-panel.d.ts.map +1 -0
  65. package/dist/dev-ready-panel.js +67 -0
  66. package/dist/dev-ready-panel.js.map +1 -0
  67. package/dist/dev-session-lock.d.ts.map +1 -1
  68. package/dist/dev-session-lock.js +5 -8
  69. package/dist/dev-session-lock.js.map +1 -1
  70. package/dist/dev-session.d.ts +18 -5
  71. package/dist/dev-session.d.ts.map +1 -1
  72. package/dist/dev-session.js +91 -18
  73. package/dist/dev-session.js.map +1 -1
  74. package/dist/dev-shutdown.d.ts +2 -0
  75. package/dist/dev-shutdown.d.ts.map +1 -1
  76. package/dist/dev-shutdown.js +29 -15
  77. package/dist/dev-shutdown.js.map +1 -1
  78. package/dist/dev-task-colors.d.ts +3 -1
  79. package/dist/dev-task-colors.d.ts.map +1 -1
  80. package/dist/dev-task-colors.js +23 -3
  81. package/dist/dev-task-colors.js.map +1 -1
  82. package/dist/docker-runtime.js +1 -1
  83. package/dist/docker-runtime.js.map +1 -1
  84. package/dist/engine-push-output.d.ts +1 -0
  85. package/dist/engine-push-output.d.ts.map +1 -1
  86. package/dist/engine-push-output.js +4 -1
  87. package/dist/engine-push-output.js.map +1 -1
  88. package/dist/ensure-binary.d.ts.map +1 -1
  89. package/dist/ensure-binary.js +14 -2
  90. package/dist/ensure-binary.js.map +1 -1
  91. package/dist/ensure-component-binaries.d.ts +17 -0
  92. package/dist/ensure-component-binaries.d.ts.map +1 -0
  93. package/dist/ensure-component-binaries.js +45 -0
  94. package/dist/ensure-component-binaries.js.map +1 -0
  95. package/dist/ensure-dev-api-config.d.ts +3 -0
  96. package/dist/ensure-dev-api-config.d.ts.map +1 -0
  97. package/dist/ensure-dev-api-config.js +17 -0
  98. package/dist/ensure-dev-api-config.js.map +1 -0
  99. package/dist/init-dependency-versions.d.ts +13 -0
  100. package/dist/init-dependency-versions.d.ts.map +1 -0
  101. package/dist/init-dependency-versions.js +53 -0
  102. package/dist/init-dependency-versions.js.map +1 -0
  103. package/dist/init-package-json.d.ts +21 -0
  104. package/dist/init-package-json.d.ts.map +1 -0
  105. package/dist/init-package-json.js +46 -0
  106. package/dist/init-package-json.js.map +1 -0
  107. package/dist/init-project-detect.d.ts +14 -0
  108. package/dist/init-project-detect.d.ts.map +1 -0
  109. package/dist/init-project-detect.js +89 -0
  110. package/dist/init-project-detect.js.map +1 -0
  111. package/dist/postgres-ctl.d.ts.map +1 -1
  112. package/dist/postgres-ctl.js +2 -1
  113. package/dist/postgres-ctl.js.map +1 -1
  114. package/dist/required-host-components.d.ts +11 -0
  115. package/dist/required-host-components.d.ts.map +1 -0
  116. package/dist/required-host-components.js +25 -0
  117. package/dist/required-host-components.js.map +1 -0
  118. package/dist/resolve-api-url.d.ts +6 -0
  119. package/dist/resolve-api-url.d.ts.map +1 -0
  120. package/dist/resolve-api-url.js +39 -0
  121. package/dist/resolve-api-url.js.map +1 -0
  122. package/dist/rest-cache-admin.d.ts +33 -0
  123. package/dist/rest-cache-admin.d.ts.map +1 -0
  124. package/dist/rest-cache-admin.js +96 -0
  125. package/dist/rest-cache-admin.js.map +1 -0
  126. package/dist/scripts/postinstall.d.ts +4 -6
  127. package/dist/scripts/postinstall.d.ts.map +1 -1
  128. package/dist/scripts/postinstall.js +8 -41
  129. package/dist/scripts/postinstall.js.map +1 -1
  130. package/dist/self-host-compose.d.ts.map +1 -1
  131. package/dist/self-host-compose.js +23 -13
  132. package/dist/self-host-compose.js.map +1 -1
  133. package/dist/studio-dev-server.d.ts +4 -2
  134. package/dist/studio-dev-server.d.ts.map +1 -1
  135. package/dist/studio-dev-server.js +3 -2
  136. package/dist/studio-dev-server.js.map +1 -1
  137. package/dist/ui/brand.d.ts +2 -5
  138. package/dist/ui/brand.d.ts.map +1 -1
  139. package/dist/ui/brand.js +2 -7
  140. package/dist/ui/brand.js.map +1 -1
  141. package/dist/ui/clack-api.d.ts +44 -0
  142. package/dist/ui/clack-api.d.ts.map +1 -0
  143. package/dist/ui/clack-api.js +76 -0
  144. package/dist/ui/clack-api.js.map +1 -0
  145. package/dist/ui/clack.d.ts +14 -0
  146. package/dist/ui/clack.d.ts.map +1 -0
  147. package/dist/ui/clack.js +147 -0
  148. package/dist/ui/clack.js.map +1 -0
  149. package/dist/ui/components/FlowSpinner.d.ts +5 -0
  150. package/dist/ui/components/FlowSpinner.d.ts.map +1 -0
  151. package/dist/ui/components/FlowSpinner.js +8 -0
  152. package/dist/ui/components/FlowSpinner.js.map +1 -0
  153. package/dist/ui/components/LogoWordmark.d.ts +9 -0
  154. package/dist/ui/components/LogoWordmark.d.ts.map +1 -0
  155. package/dist/ui/components/LogoWordmark.js +17 -0
  156. package/dist/ui/components/LogoWordmark.js.map +1 -0
  157. package/dist/ui/components/PromptPanel.d.ts +5 -0
  158. package/dist/ui/components/PromptPanel.d.ts.map +1 -0
  159. package/dist/ui/components/PromptPanel.js +7 -0
  160. package/dist/ui/components/PromptPanel.js.map +1 -0
  161. package/dist/ui/components/SelectField.d.ts +11 -0
  162. package/dist/ui/components/SelectField.d.ts.map +1 -0
  163. package/dist/ui/components/SelectField.js +19 -0
  164. package/dist/ui/components/SelectField.js.map +1 -0
  165. package/dist/ui/confirm.d.ts +2 -1
  166. package/dist/ui/confirm.d.ts.map +1 -1
  167. package/dist/ui/confirm.js +4 -4
  168. package/dist/ui/confirm.js.map +1 -1
  169. package/dist/ui/dev/DevDashboard.d.ts +8 -0
  170. package/dist/ui/dev/DevDashboard.d.ts.map +1 -0
  171. package/dist/ui/dev/DevDashboard.js +158 -0
  172. package/dist/ui/dev/DevDashboard.js.map +1 -0
  173. package/dist/ui/dev/DevReadyPanelView.d.ts +8 -0
  174. package/dist/ui/dev/DevReadyPanelView.d.ts.map +1 -0
  175. package/dist/ui/dev/DevReadyPanelView.js +48 -0
  176. package/dist/ui/dev/DevReadyPanelView.js.map +1 -0
  177. package/dist/ui/fatal.d.ts.map +1 -1
  178. package/dist/ui/fatal.js +5 -11
  179. package/dist/ui/fatal.js.map +1 -1
  180. package/dist/ui/flows/FlowApp.d.ts +8 -0
  181. package/dist/ui/flows/FlowApp.d.ts.map +1 -0
  182. package/dist/ui/flows/FlowApp.js +44 -0
  183. package/dist/ui/flows/FlowApp.js.map +1 -0
  184. package/dist/ui/flows/prompt-fields.d.ts +48 -0
  185. package/dist/ui/flows/prompt-fields.d.ts.map +1 -0
  186. package/dist/ui/flows/prompt-fields.js +92 -0
  187. package/dist/ui/flows/prompt-fields.js.map +1 -0
  188. package/dist/ui/flows/types.d.ts +41 -0
  189. package/dist/ui/flows/types.d.ts.map +1 -0
  190. package/dist/ui/flows/types.js +2 -0
  191. package/dist/ui/flows/types.js.map +1 -0
  192. package/dist/ui/index.d.ts +1 -1
  193. package/dist/ui/index.js +1 -1
  194. package/dist/ui/messages.d.ts +1 -0
  195. package/dist/ui/messages.d.ts.map +1 -1
  196. package/dist/ui/messages.js +48 -8
  197. package/dist/ui/messages.js.map +1 -1
  198. package/dist/ui/progress.d.ts +5 -1
  199. package/dist/ui/progress.d.ts.map +1 -1
  200. package/dist/ui/progress.js +12 -3
  201. package/dist/ui/progress.js.map +1 -1
  202. package/dist/ui/prompts.d.ts +7 -5
  203. package/dist/ui/prompts.d.ts.map +1 -1
  204. package/dist/ui/prompts.js +15 -8
  205. package/dist/ui/prompts.js.map +1 -1
  206. package/dist/ui/runtime/cancel.d.ts +4 -0
  207. package/dist/ui/runtime/cancel.d.ts.map +1 -0
  208. package/dist/ui/runtime/cancel.js +6 -0
  209. package/dist/ui/runtime/cancel.js.map +1 -0
  210. package/dist/ui/runtime/command-chrome.d.ts +17 -0
  211. package/dist/ui/runtime/command-chrome.d.ts.map +1 -0
  212. package/dist/ui/runtime/command-chrome.js +123 -0
  213. package/dist/ui/runtime/command-chrome.js.map +1 -0
  214. package/dist/ui/runtime/dev-prompt-queue.d.ts +30 -0
  215. package/dist/ui/runtime/dev-prompt-queue.d.ts.map +1 -0
  216. package/dist/ui/runtime/dev-prompt-queue.js +57 -0
  217. package/dist/ui/runtime/dev-prompt-queue.js.map +1 -0
  218. package/dist/ui/runtime/flow-session.d.ts +4 -0
  219. package/dist/ui/runtime/flow-session.d.ts.map +1 -0
  220. package/dist/ui/runtime/flow-session.js +8 -0
  221. package/dist/ui/runtime/flow-session.js.map +1 -0
  222. package/dist/ui/runtime/run-clack-flow.d.ts +5 -0
  223. package/dist/ui/runtime/run-clack-flow.d.ts.map +1 -0
  224. package/dist/ui/runtime/run-clack-flow.js +46 -0
  225. package/dist/ui/runtime/run-clack-flow.js.map +1 -0
  226. package/dist/ui/runtime/stdin-after-ink.d.ts +3 -0
  227. package/dist/ui/runtime/stdin-after-ink.d.ts.map +1 -0
  228. package/dist/ui/runtime/stdin-after-ink.js +16 -0
  229. package/dist/ui/runtime/stdin-after-ink.js.map +1 -0
  230. package/dist/ui/theme.d.ts +24 -0
  231. package/dist/ui/theme.d.ts.map +1 -0
  232. package/dist/ui/theme.js +34 -0
  233. package/dist/ui/theme.js.map +1 -0
  234. package/dist/valkey-sidecar.d.ts +17 -0
  235. package/dist/valkey-sidecar.d.ts.map +1 -0
  236. package/dist/valkey-sidecar.js +94 -0
  237. package/dist/valkey-sidecar.js.map +1 -0
  238. package/package.json +9 -3
  239. package/src/binary-cache.ts +61 -0
  240. package/src/binary-download-lock.ts +124 -0
  241. package/src/cli-package-version.ts +15 -0
  242. package/src/cli.ts +6 -2
  243. package/src/commands/add.ts +26 -19
  244. package/src/commands/admin.ts +156 -29
  245. package/src/commands/cache.ts +105 -5
  246. package/src/commands/dev.ts +26 -21
  247. package/src/commands/init.ts +473 -247
  248. package/src/commands/link-helpers.ts +30 -29
  249. package/src/compose-local-server-image.ts +68 -0
  250. package/src/compose-rename.ts +5 -8
  251. package/src/config.ts +13 -1
  252. package/src/dev-compose.ts +139 -54
  253. package/src/dev-log-bus.ts +20 -0
  254. package/src/dev-log-filter.ts +42 -0
  255. package/src/dev-ports.ts +22 -11
  256. package/src/dev-ready-panel.ts +80 -0
  257. package/src/dev-session-lock.ts +5 -8
  258. package/src/dev-session.ts +98 -18
  259. package/src/dev-shutdown.ts +29 -17
  260. package/src/dev-task-colors.ts +22 -3
  261. package/src/docker-runtime.ts +1 -1
  262. package/src/engine-push-output.ts +5 -1
  263. package/src/ensure-binary.ts +21 -1
  264. package/src/ensure-component-binaries.ts +71 -0
  265. package/src/ensure-dev-api-config.ts +18 -0
  266. package/src/init-dependency-versions.ts +69 -0
  267. package/src/init-package-json.ts +63 -0
  268. package/src/init-project-detect.ts +99 -0
  269. package/src/postgres-ctl.ts +2 -1
  270. package/src/required-host-components.ts +37 -0
  271. package/src/resolve-api-url.ts +43 -0
  272. package/src/rest-cache-admin.ts +135 -0
  273. package/src/scripts/postinstall.ts +9 -46
  274. package/src/self-host-compose.ts +22 -12
  275. package/src/studio-dev-server.ts +5 -4
  276. package/src/ui/README.md +13 -9
  277. package/src/ui/brand.ts +2 -8
  278. package/src/ui/clack-api.ts +123 -0
  279. package/src/ui/clack.ts +153 -0
  280. package/src/ui/components/FlowSpinner.tsx +15 -0
  281. package/src/ui/components/LogoWordmark.tsx +36 -0
  282. package/src/ui/components/PromptPanel.tsx +17 -0
  283. package/src/ui/components/SelectField.tsx +58 -0
  284. package/src/ui/confirm.ts +5 -4
  285. package/src/ui/dev/DevDashboard.tsx +223 -0
  286. package/src/ui/dev/DevReadyPanelView.tsx +134 -0
  287. package/src/ui/fatal.ts +5 -10
  288. package/src/ui/flows/FlowApp.tsx +69 -0
  289. package/src/ui/flows/prompt-fields.tsx +198 -0
  290. package/src/ui/flows/types.ts +50 -0
  291. package/src/ui/index.ts +1 -1
  292. package/src/ui/messages.ts +50 -8
  293. package/src/ui/progress.ts +14 -3
  294. package/src/ui/prompts.ts +17 -9
  295. package/src/ui/runtime/cancel.ts +6 -0
  296. package/src/ui/runtime/command-chrome.ts +147 -0
  297. package/src/ui/runtime/dev-prompt-queue.ts +84 -0
  298. package/src/ui/runtime/flow-session.ts +11 -0
  299. package/src/ui/runtime/run-clack-flow.tsx +65 -0
  300. package/src/ui/runtime/stdin-after-ink.ts +18 -0
  301. package/src/ui/theme.ts +41 -0
  302. package/src/valkey-sidecar.ts +126 -0
  303. package/tests/admin-ensure.test.ts +80 -0
  304. package/tests/binary-cache-cloud-overrides.test.ts +2 -2
  305. package/tests/binary-download-lock.test.ts +41 -0
  306. package/tests/cli-help.test.ts +7 -4
  307. package/tests/cli-package-version.test.ts +15 -0
  308. package/tests/command-chrome-coverage.test.ts +149 -0
  309. package/tests/command-chrome-wrap.test.ts +51 -0
  310. package/tests/config.test.ts +27 -4
  311. package/tests/dev-ready-panel.test.ts +37 -0
  312. package/tests/dev-ui.test.ts +42 -2
  313. package/tests/docker-runtime.test.ts +9 -20
  314. package/tests/ensure-dev-api-config.test.ts +22 -0
  315. package/tests/init-dependency-versions.test.ts +62 -0
  316. package/tests/init-package-json.test.ts +18 -0
  317. package/tests/init-project-detect.test.ts +53 -0
  318. package/tests/init.test.ts +30 -3
  319. package/tests/required-host-components.test.ts +51 -0
  320. package/tests/resolve-api-url.test.ts +23 -0
  321. package/tests/rest-cache-admin.test.ts +28 -0
  322. package/tests/runtime-contract.test.ts +12 -3
  323. package/tests/stdin-after-ink.test.ts +8 -0
  324. package/tests/tsconfig.json +8 -2
  325. package/tests/ui-confirm.test.ts +13 -9
  326. package/tests/ui-messages.test.ts +4 -18
  327. package/tsconfig.json +1 -0
  328. package/tsconfig.tsbuildinfo +1 -1
  329. package/vitest.config.ts +3 -0
  330. package/dist/dev-tui.d.ts +0 -24
  331. package/dist/dev-tui.d.ts.map +0 -1
  332. package/dist/dev-tui.js +0 -188
  333. package/dist/dev-tui.js.map +0 -1
  334. package/src/dev-tui.ts +0 -232
@@ -1,36 +1,26 @@
1
1
  import type { Command } from "commander"
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
3
- import { resolve, join, dirname, basename } from "node:path"
4
- import { fileURLToPath } from "node:url"
3
+ import { resolve, join, basename } from "node:path"
5
4
  import { spawnSync } from "node:child_process"
6
- import * as p from "@clack/prompts"
7
- import { ensureNotCancelled, printLogo } from "../ui/prompts.js"
5
+ import { p, runClackFlow } from "../ui/clack.js"
6
+ import { ensureNotCancelled } from "../ui/prompts.js"
8
7
  import { generateAndWriteKeys } from "./keys.js"
9
8
  import { file, error, info, plain, warn } from "../ui/messages.js"
10
9
  import { nextSteps } from "../ui/next-steps.js"
11
10
  import { probeDockerDaemon, reportDockerUnavailable } from "../docker-runtime.js"
12
-
13
- export { scaffold }
14
-
15
- // ─── Markers used by `supatype app add / remove` (app.ts) ────────────────────
16
- export const APP_COMPOSE_MARKER = " # ─── App service (run: supatype app add) ───"
17
- export const KONG_APP_MARKER = " # ─── App fallback route (run: supatype app add) ───"
18
-
19
- const CLI_PACKAGE_JSON = join(
20
- dirname(fileURLToPath(import.meta.url)),
21
- "..",
22
- "..",
23
- "package.json",
24
- )
25
-
26
- function cliPackageVersion(): string {
27
- try {
28
- const pkg = JSON.parse(readFileSync(CLI_PACKAGE_JSON, "utf8")) as { version?: string }
29
- return pkg.version ?? "0.1.0"
30
- } catch {
31
- return "0.1.0"
32
- }
33
- }
11
+ import { loadConfig } from "../config.js"
12
+ import {
13
+ ensureComponentBinaries,
14
+ reportComponentBinaryFailures,
15
+ } from "../ensure-component-binaries.js"
16
+ import { mergeSupatypePackageJson } from "../init-package-json.js"
17
+ import { cliPackageVersion } from "../cli-package-version.js"
18
+ import {
19
+ initDependencyVersionsFallback,
20
+ resolveInitDependencyVersions,
21
+ type InitDependencyVersions,
22
+ } from "../init-dependency-versions.js"
23
+ import { detectProjectSetup, type DetectedProjectSetup } from "../init-project-detect.js"
34
24
 
35
25
  // ─── Options model ─────────────────────────────────────────────────────────--
36
26
 
@@ -166,8 +156,14 @@ export function registerInit(program: Command): void {
166
156
 
167
157
  let result: WizardResult
168
158
  if (interactive) {
169
- printLogo()
170
- result = await runWizard(defaultName, modeTarget)
159
+ await runInteractiveInit({
160
+ name,
161
+ dir,
162
+ defaultName,
163
+ modeTarget,
164
+ opts,
165
+ })
166
+ return
171
167
  } else {
172
168
  result = {
173
169
  ...defaultScaffoldOptions(defaultName, modeTarget),
@@ -200,220 +196,346 @@ export function registerInit(program: Command): void {
200
196
 
201
197
  if (name) mkdirSync(dir, { recursive: true })
202
198
 
203
- scaffold(dir, result)
199
+ const deps = await resolveInitDependencyVersions()
200
+ const runningCli = cliPackageVersion()
201
+ if (deps.cli !== runningCli || deps.types !== runningCli) {
202
+ info(
203
+ `Using published npm versions: @supatype/cli ^${deps.cli}, @supatype/types ^${deps.types}`,
204
+ )
205
+ }
206
+
207
+ scaffold(dir, result, deps)
204
208
 
205
- if (doInstall) runInstall(dir, result.packageManager)
209
+ const installOk = doInstall ? runInstall(dir, result.packageManager) : false
206
210
  const keysGenerated = doKeys ? writeKeys(dir) : false
211
+ const binariesReady =
212
+ doInstall && installOk ? await ensureInitBinaries(dir) : false
213
+
214
+ if (doInstall && !installOk) {
215
+ error(
216
+ `Dependency install failed. Run "${result.packageManager} install" in the project directory, then "supatype update".`,
217
+ )
218
+ process.exit(1)
219
+ }
220
+ if (doKeys && !keysGenerated) {
221
+ error("Could not generate API keys. Check .env has JWT_SECRET, then run `supatype keys`.")
222
+ process.exit(1)
223
+ }
224
+ if (doInstall && installOk && !binariesReady) {
225
+ error("Component binaries could not be downloaded. Run `supatype update` in the project directory.")
226
+ process.exit(1)
227
+ }
207
228
 
208
229
  warnDockerUnavailableForProvider(result.provider)
209
230
 
210
231
  printNextSteps({
211
232
  name,
212
233
  result,
213
- installed: doInstall,
234
+ installed: doInstall && installOk,
214
235
  keysGenerated,
236
+ binariesReady,
215
237
  })
216
238
  })
217
239
  }
218
240
 
219
- // ─── Wizard ──────────────────────────────────────────────────────────────────
241
+ interface InteractiveInitArgs {
242
+ name: string | undefined
243
+ dir: string
244
+ defaultName: string
245
+ modeTarget: ProductionTarget
246
+ opts: InitCliOptions
247
+ }
220
248
 
221
- async function runWizard(
222
- defaultName: string,
223
- defaultTarget: ProductionTarget,
224
- ): Promise<WizardResult> {
225
- p.intro("Create a new Supatype project")
249
+ /** Interactive init — one Ink session for wizard + scaffold + install + finish. */
250
+ async function runInteractiveInit(args: InteractiveInitArgs): Promise<void> {
251
+ const { name, dir, defaultName, modeTarget, opts } = args
252
+
253
+ await runClackFlow(async () => {
254
+ p.intro("Create a new Supatype project")
255
+ const detected = detectProjectSetup(dir)
256
+ if (detected.hasExistingFiles && detected.summaryLines.length > 0) {
257
+ p.note(
258
+ ["Existing project detected:", ...detected.summaryLines.map((line) => ` • ${line}`)].join(
259
+ "\n",
260
+ ),
261
+ )
262
+ }
263
+ let result = await collectWizardAnswers(defaultName, modeTarget, dir, detected)
264
+
265
+ if (opts.admin === false) {
266
+ delete result.adminEmail
267
+ delete result.adminPassword
268
+ } else if (opts.adminEmail && opts.adminPassword) {
269
+ result.adminEmail = opts.adminEmail
270
+ result.adminPassword = opts.adminPassword
271
+ }
226
272
 
227
- const projectName = ensureNotCancelled(
228
- await p.text({
229
- message: "Project name",
230
- defaultValue: defaultName,
231
- placeholder: defaultName,
232
- }),
233
- ).trim() || defaultName
273
+ const doInstall = opts.install !== false && result.install
274
+ const doKeys = opts.keys !== false && result.generateKeys
234
275
 
235
- const packageManager = ensureNotCancelled(
236
- await p.select<PackageManager>({
237
- message: "Package manager",
238
- initialValue: detectInvokingPackageManager(),
239
- options: [
240
- { value: "npm", label: "npm" },
241
- { value: "pnpm", label: "pnpm" },
242
- { value: "yarn", label: "yarn" },
243
- { value: "bun", label: "bun" },
244
- ],
245
- }),
246
- )
276
+ if (name) mkdirSync(dir, { recursive: true })
247
277
 
248
- const productionTarget = ensureNotCancelled(
249
- await p.select<ProductionTarget>({
250
- message: "Where will this run in production?",
251
- initialValue: defaultTarget,
252
- options: [
253
- { value: "cloud", label: "Supatype Cloud", hint: "managed; deploy via supatype link" },
254
- { value: "self-host", label: "Self-host", hint: "your own server with TLS" },
255
- { value: "later", label: "Decide later", hint: "local development only for now" },
256
- ],
257
- }),
258
- )
278
+ const deps = await resolveInitDependencyVersions()
279
+ const runningCli = cliPackageVersion()
280
+ if (deps.cli !== runningCli || deps.types !== runningCli) {
281
+ info(
282
+ `Using published npm versions: @supatype/cli ^${deps.cli}, @supatype/types ^${deps.types}`,
283
+ )
284
+ }
259
285
 
260
- let domain: string | undefined
261
- let tlsEmail: string | undefined
262
- if (productionTarget === "self-host") {
263
- domain = ensureNotCancelled(
264
- await p.text({
265
- message: "Production domain for ACME TLS (optional, can set later)",
266
- placeholder: "api.example.com",
267
- defaultValue: "",
268
- }),
269
- ).trim()
270
- if (domain) {
271
- tlsEmail =
272
- ensureNotCancelled(
273
- await p.text({
274
- message: "Email for Let's Encrypt (HTTPS) certificates",
275
- placeholder: "you@example.com",
276
- defaultValue: "",
277
- }),
278
- ).trim() || undefined
286
+ const setupSpinner = p.spinner()
287
+ setupSpinner.start("Setting up your project...")
288
+
289
+ scaffold(dir, result, deps)
290
+
291
+ let installOk = !doInstall
292
+ if (doInstall) {
293
+ setupSpinner.start(`Installing dependencies with ${result.packageManager}...`)
294
+ installOk = runInstall(dir, result.packageManager)
295
+ setupSpinner.stop(installOk ? "Dependencies installed." : "Dependency install failed.")
279
296
  }
280
- }
281
297
 
282
- const provider = ensureNotCancelled(
283
- await p.select<ScaffoldOptions["provider"]>({
284
- message: "How should Postgres and the server run for local development?",
285
- initialValue: "docker",
286
- options: [
287
- { value: "docker", label: "Docker", hint: "Docker Compose stack (recommended)" },
288
- { value: "native", label: "Native", hint: "host Postgres + server binaries, no Docker" },
289
- ],
290
- }),
291
- )
298
+ let keysGenerated = !doKeys
299
+ if (doKeys) {
300
+ setupSpinner.start("Generating API keys...")
301
+ keysGenerated = writeKeys(dir)
302
+ setupSpinner.stop(keysGenerated ? "API keys generated." : "Could not generate API keys.")
303
+ }
292
304
 
293
- let kongPort: number | undefined
294
- if (provider === "docker") {
295
- const { promptKongPortChoice } = await import("../dev-ports.js")
296
- kongPort = await promptKongPortChoice()
297
- }
305
+ let binariesReady = false
306
+ if (doInstall && installOk) {
307
+ setupSpinner.start("Preparing component binaries...")
308
+ binariesReady = await ensureInitBinaries(dir)
309
+ setupSpinner.stop(binariesReady ? "Component binaries ready." : "Some binaries are missing.")
310
+ }
298
311
 
299
- const schemaPath = ensureNotCancelled(
300
- await p.text({
301
- message: "Where should your schema live?",
302
- defaultValue: "schema/index.ts",
303
- placeholder: "schema/index.ts",
304
- }),
305
- ).trim() || "schema/index.ts"
312
+ if (doInstall && !installOk) {
313
+ error(
314
+ `Dependency install failed. Run "${result.packageManager} install" in the project directory, then "supatype update".`,
315
+ )
316
+ process.exit(1)
317
+ }
318
+ if (doKeys && !keysGenerated) {
319
+ error("Could not generate API keys. Check .env has JWT_SECRET, then run `supatype keys`.")
320
+ process.exit(1)
321
+ }
322
+ if (doInstall && installOk && !binariesReady) {
323
+ error("Component binaries could not be downloaded. Run `supatype update` in the project directory.")
324
+ process.exit(1)
325
+ }
306
326
 
307
- const app = await promptApp()
327
+ warnDockerUnavailableForProvider(result.provider)
308
328
 
309
- const email = ensureNotCancelled(
310
- await p.select<ScaffoldOptions["email"]>({
311
- message: "Email provider",
312
- initialValue: "console",
313
- options: [
314
- { value: "console", label: "console", hint: "log emails to the terminal (dev)" },
315
- { value: "smtp", label: "SMTP" },
316
- { value: "resend", label: "Resend" },
317
- { value: "ses", label: "Amazon SES" },
318
- ],
319
- }),
320
- )
329
+ p.outro(`Supatype project ready${name ? ` in ${name}/` : ""}.`)
330
+ printNextSteps({
331
+ name,
332
+ result,
333
+ installed: doInstall && installOk,
334
+ keysGenerated,
335
+ binariesReady,
336
+ })
337
+ })
321
338
 
322
- const storageLocal = ensureNotCancelled(
323
- await p.select<StorageProvider>({
324
- message: "Local storage (for development)?",
325
- initialValue: "local",
326
- options: STORAGE_PROVIDER_OPTIONS,
327
- }),
328
- )
339
+ process.exit(0)
340
+ }
329
341
 
330
- const storageProduction = ensureNotCancelled(
331
- await p.select<StorageProvider>({
332
- message: "Production storage?",
333
- initialValue: "local",
334
- options: STORAGE_PROVIDER_OPTIONS,
335
- }),
336
- )
342
+ // ─── Wizard ──────────────────────────────────────────────────────────────────
337
343
 
338
- const helloFunction = ensureNotCancelled(
339
- await p.confirm({
340
- message: "Create a hello-world edge function?",
341
- initialValue: false,
342
- }),
343
- )
344
+ async function collectWizardAnswers(
345
+ defaultName: string,
346
+ defaultTarget: ProductionTarget,
347
+ dir: string,
348
+ detected: DetectedProjectSetup,
349
+ ): Promise<WizardResult> {
350
+ const projectName = ensureNotCancelled(
351
+ await p.text({
352
+ message: "Project name",
353
+ defaultValue: defaultName,
354
+ placeholder: defaultName,
355
+ }),
356
+ ).trim() || defaultName
357
+
358
+ const admin = await promptAdminUser()
359
+
360
+ const packageManager = ensureNotCancelled(
361
+ await p.select<PackageManager>({
362
+ message: "Package manager",
363
+ initialValue: detectInvokingPackageManager(),
364
+ options: [
365
+ { value: "npm", label: "npm" },
366
+ { value: "pnpm", label: "pnpm" },
367
+ { value: "yarn", label: "yarn" },
368
+ { value: "bun", label: "bun" },
369
+ ],
370
+ }),
371
+ )
344
372
 
345
- const install = ensureNotCancelled(
346
- await p.confirm({
347
- message: `Install dependencies with ${packageManager} now?`,
348
- initialValue: true,
349
- }),
350
- )
373
+ const productionTarget = ensureNotCancelled(
374
+ await p.select<ProductionTarget>({
375
+ message: "Where will this run in production?",
376
+ initialValue: defaultTarget,
377
+ options: [
378
+ { value: "cloud", label: "Supatype Cloud", hint: "managed; deploy via supatype link" },
379
+ { value: "self-host", label: "Self-host", hint: "your own server with TLS" },
380
+ { value: "later", label: "Decide later", hint: "local development only for now" },
381
+ ],
382
+ }),
383
+ )
351
384
 
352
- const generateKeys = ensureNotCancelled(
353
- await p.confirm({
354
- message: "Generate ANON_KEY and SERVICE_ROLE_KEY now?",
355
- initialValue: true,
356
- }),
357
- )
385
+ let domain: string | undefined
386
+ let tlsEmail: string | undefined
387
+ if (productionTarget === "self-host") {
388
+ domain = ensureNotCancelled(
389
+ await p.text({
390
+ message: "Production domain for ACME TLS (optional, can set later)",
391
+ placeholder: "api.example.com",
392
+ defaultValue: "",
393
+ }),
394
+ ).trim()
395
+ if (domain) {
396
+ tlsEmail =
397
+ ensureNotCancelled(
398
+ await p.text({
399
+ message: "Email for Let's Encrypt (HTTPS) certificates",
400
+ placeholder: "you@example.com",
401
+ defaultValue: "",
402
+ }),
403
+ ).trim() || undefined
404
+ }
405
+ }
406
+
407
+ const provider = ensureNotCancelled(
408
+ await p.select<ScaffoldOptions["provider"]>({
409
+ message: "How should Postgres and the server run for local development?",
410
+ initialValue: "docker",
411
+ options: [
412
+ { value: "docker", label: "Docker", hint: "Docker Compose stack (recommended)" },
413
+ { value: "native", label: "Native", hint: "host Postgres + server binaries, no Docker" },
414
+ ],
415
+ }),
416
+ )
417
+
418
+ let kongPort: number | undefined
419
+ if (provider === "docker") {
420
+ const { promptKongPortChoice } = await import("../dev-ports.js")
421
+ kongPort = await promptKongPortChoice()
422
+ }
423
+
424
+ const defaultSchemaPath = existsSync(join(dir, "schema/index.ts"))
425
+ ? "schema/index.ts"
426
+ : "schema/index.ts"
427
+ const schemaPath = ensureNotCancelled(
428
+ await p.text({
429
+ message: "Where should your schema live?",
430
+ defaultValue: defaultSchemaPath,
431
+ placeholder: "schema/index.ts",
432
+ }),
433
+ ).trim() || defaultSchemaPath
434
+
435
+ const app = await promptApp(detected, productionTarget)
436
+
437
+ const email = ensureNotCancelled(
438
+ await p.select<ScaffoldOptions["email"]>({
439
+ message: "Email provider",
440
+ initialValue: "console",
441
+ options: [
442
+ { value: "console", label: "console", hint: "log emails to the terminal (dev)" },
443
+ { value: "smtp", label: "SMTP" },
444
+ { value: "resend", label: "Resend" },
445
+ { value: "ses", label: "Amazon SES" },
446
+ ],
447
+ }),
448
+ )
449
+
450
+ const storageLocal = ensureNotCancelled(
451
+ await p.select<StorageProvider>({
452
+ message: "Local storage (for development)?",
453
+ initialValue: "local",
454
+ options: STORAGE_PROVIDER_OPTIONS,
455
+ }),
456
+ )
457
+
458
+ const storageProduction = ensureNotCancelled(
459
+ await p.select<StorageProvider>({
460
+ message: "Production storage?",
461
+ initialValue: "local",
462
+ options: STORAGE_PROVIDER_OPTIONS,
463
+ }),
464
+ )
358
465
 
359
- let adminEmail: string | undefined
360
- let adminPassword: string | undefined
466
+ const helloFunction = ensureNotCancelled(
467
+ await p.confirm({
468
+ message: "Create a hello-world edge function?",
469
+ initialValue: false,
470
+ }),
471
+ )
472
+
473
+ return {
474
+ projectName,
475
+ provider,
476
+ ...(kongPort !== undefined ? { kongPort } : {}),
477
+ productionTarget,
478
+ ...(domain !== undefined ? { domain } : {}),
479
+ ...(tlsEmail !== undefined ? { tlsEmail } : {}),
480
+ schemaPath,
481
+ app,
482
+ email,
483
+ storageLocal,
484
+ storageProduction,
485
+ helloFunction,
486
+ packageManager,
487
+ install: true,
488
+ generateKeys: true,
489
+ ...admin,
490
+ }
491
+ }
492
+
493
+ async function promptAdminUser(): Promise<{ adminEmail?: string; adminPassword?: string }> {
361
494
  const createAdmin = ensureNotCancelled(
362
495
  await p.confirm({
363
496
  message: "Create an admin user for /admin?",
364
497
  initialValue: true,
365
498
  }),
366
499
  )
367
- if (createAdmin) {
368
- adminEmail =
369
- ensureNotCancelled(
370
- await p.text({
371
- message: "Admin email",
372
- placeholder: "you@example.com",
373
- defaultValue: "",
374
- }),
375
- ).trim() || undefined
376
- if (adminEmail) {
377
- adminPassword =
378
- ensureNotCancelled(
379
- await p.text({
380
- message: "Admin password (min 8 chars)",
381
- defaultValue: "",
382
- }),
383
- ).trim() || undefined
384
- if (adminPassword && adminPassword.length < 8) {
385
- adminPassword = undefined
386
- }
387
- }
388
- }
500
+ if (!createAdmin) return {}
389
501
 
390
- p.outro("Setting up your project...")
502
+ p.note("First sign-in at /admin — created automatically on supatype dev or push.")
391
503
 
392
- return {
393
- projectName,
394
- provider,
395
- ...(kongPort !== undefined ? { kongPort } : {}),
396
- productionTarget,
397
- ...(domain !== undefined ? { domain } : {}),
398
- ...(tlsEmail !== undefined ? { tlsEmail } : {}),
399
- schemaPath,
400
- app,
401
- email,
402
- storageLocal,
403
- storageProduction,
404
- helloFunction,
405
- packageManager,
406
- install,
407
- generateKeys,
408
- ...(adminEmail && adminPassword ? { adminEmail, adminPassword } : {}),
504
+ const adminEmail = ensureNotCancelled(
505
+ await p.text({
506
+ message: "Admin email",
507
+ placeholder: "you@example.com",
508
+ validate: (value) => (value.trim() ? undefined : "Email is required"),
509
+ }),
510
+ ).trim()
511
+
512
+ let adminPassword = ""
513
+ while (true) {
514
+ adminPassword = ensureNotCancelled(
515
+ await p.password({ message: "Admin password (min 8 characters)" }),
516
+ ).trim()
517
+ if (adminPassword.length >= 8) break
518
+ p.log.warn("Password must be at least 8 characters.")
409
519
  }
520
+
521
+ return { adminEmail, adminPassword }
410
522
  }
411
523
 
412
- async function promptApp(): Promise<ScaffoldAppOptions> {
524
+ async function promptApp(
525
+ detected: DetectedProjectSetup,
526
+ productionTarget: ProductionTarget,
527
+ ): Promise<ScaffoldAppOptions> {
528
+ const initialMode: ScaffoldAppOptions["mode"] =
529
+ detected.hasVite && productionTarget !== "later"
530
+ ? "static"
531
+ : detected.hasVite
532
+ ? "proxy"
533
+ : "none"
534
+
413
535
  const mode = ensureNotCancelled(
414
536
  await p.select<ScaffoldAppOptions["mode"]>({
415
537
  message: "Host a frontend app at /?",
416
- initialValue: "none",
538
+ initialValue: initialMode,
417
539
  options: [
418
540
  { value: "none", label: "No app", hint: "API only" },
419
541
  { value: "static", label: "Static site", hint: "serve a built directory" },
@@ -426,11 +548,11 @@ async function promptApp(): Promise<ScaffoldAppOptions> {
426
548
  const staticDir = ensureNotCancelled(
427
549
  await p.text({
428
550
  message: "Directory to serve",
429
- defaultValue: "./public",
430
- placeholder: "./public",
551
+ defaultValue: detected.staticDir,
552
+ placeholder: detected.staticDir,
431
553
  }),
432
- ).trim() || "./public"
433
- const viteDevUrl = await promptViteDevUrl()
554
+ ).trim() || detected.staticDir
555
+ const viteDevUrl = await promptViteDevUrl(detected, productionTarget)
434
556
  return { mode, staticDir, ...(viteDevUrl ? { viteDevUrl } : {}) }
435
557
  }
436
558
 
@@ -438,40 +560,49 @@ async function promptApp(): Promise<ScaffoldAppOptions> {
438
560
  const upstream = ensureNotCancelled(
439
561
  await p.text({
440
562
  message: "URL of your running dev server",
441
- defaultValue: "http://localhost:3000",
442
- placeholder: "http://localhost:3000",
563
+ defaultValue: detected.hasVite ? detected.viteDevUrl : "http://localhost:3000",
564
+ placeholder: detected.hasVite ? detected.viteDevUrl : "http://localhost:3000",
443
565
  }),
444
- ).trim() || "http://localhost:3000"
566
+ ).trim() || (detected.hasVite ? detected.viteDevUrl : "http://localhost:3000")
445
567
  const start = ensureNotCancelled(
446
568
  await p.text({
447
569
  message: "package.json script that starts your dev server",
448
- defaultValue: "dev",
449
- placeholder: "dev",
570
+ defaultValue: detected.hasVite ? "vite" : "dev",
571
+ placeholder: detected.hasVite ? "vite" : "dev",
450
572
  }),
451
- ).trim() || "dev"
452
- const viteDevUrl = await promptViteDevUrl()
573
+ ).trim() || (detected.hasVite ? "vite" : "dev")
574
+ const viteDevUrl = await promptViteDevUrl(detected, productionTarget)
453
575
  return { mode, upstream, start, ...(viteDevUrl ? { viteDevUrl } : {}) }
454
576
  }
455
577
 
456
578
  return { mode: "none" }
457
579
  }
458
580
 
459
- async function promptViteDevUrl(): Promise<string | undefined> {
581
+ async function promptViteDevUrl(
582
+ detected: DetectedProjectSetup,
583
+ productionTarget: ProductionTarget,
584
+ ): Promise<string | undefined> {
585
+ const defaultYes = detected.hasVite || productionTarget !== "later"
460
586
  const useVite = ensureNotCancelled(
461
587
  await p.confirm({
462
- message: "Enable live reload from a separate Vite dev server?",
463
- initialValue: false,
588
+ message: detected.hasVite
589
+ ? "Use Vite for local development?"
590
+ : "Develop locally with Vite (live reload)?",
591
+ initialValue: defaultYes,
464
592
  }),
465
593
  )
466
594
  if (!useVite) return undefined
595
+
596
+ p.note(`Press Enter for ${detected.viteDevUrl}, or type a custom URL.`)
597
+
467
598
  return (
468
599
  ensureNotCancelled(
469
600
  await p.text({
470
601
  message: "Vite dev server URL",
471
- defaultValue: "http://127.0.0.1:5173",
472
- placeholder: "http://127.0.0.1:5173",
602
+ defaultValue: detected.viteDevUrl,
603
+ placeholder: detected.viteDevUrl,
473
604
  }),
474
- ).trim() || "http://127.0.0.1:5173"
605
+ ).trim() || detected.viteDevUrl
475
606
  )
476
607
  }
477
608
 
@@ -485,15 +616,36 @@ function detectInvokingPackageManager(): PackageManager {
485
616
  return "npm"
486
617
  }
487
618
 
488
- function runInstall(dir: string, pm: PackageManager): void {
489
- info(`Installing dependencies with ${pm}...`)
619
+ function runInstall(dir: string, pm: PackageManager): boolean {
490
620
  const res = spawnSync(pm, ["install"], {
491
621
  cwd: dir,
492
622
  stdio: "inherit",
493
623
  shell: process.platform === "win32",
494
624
  })
495
625
  if (res.status !== 0 || res.error) {
496
- warn(`Dependency install did not complete (run "${pm} install" manually).`)
626
+ return false
627
+ }
628
+ return true
629
+ }
630
+
631
+ async function ensureInitBinaries(dir: string): Promise<boolean> {
632
+ const previousCwd = process.cwd()
633
+ try {
634
+ process.chdir(dir)
635
+ const config = loadConfig(dir)
636
+ const result = await ensureComponentBinaries(config, dir)
637
+ if (!result.ok) {
638
+ reportComponentBinaryFailures(result.failures)
639
+ return false
640
+ }
641
+ return true
642
+ } catch (err) {
643
+ const message = err instanceof Error ? err.message : String(err)
644
+ warn(`Could not verify component binaries: ${message}`)
645
+ warn("Run manually from your project directory: supatype update")
646
+ return false
647
+ } finally {
648
+ process.chdir(previousCwd)
497
649
  }
498
650
  }
499
651
 
@@ -508,7 +660,11 @@ function writeKeys(dir: string): boolean {
508
660
 
509
661
  // ─── Scaffold ──────────────────────────────────────────────────────────────--
510
662
 
511
- function scaffold(dir: string, optsOrName: ScaffoldOptions | string): void {
663
+ export function scaffold(
664
+ dir: string,
665
+ optsOrName: ScaffoldOptions | string,
666
+ deps: InitDependencyVersions = initDependencyVersionsFallback(),
667
+ ): void {
512
668
  const opts =
513
669
  typeof optsOrName === "string" ? defaultScaffoldOptions(optsOrName) : optsOrName
514
670
  const write = (rel: string, content: string) => {
@@ -520,9 +676,17 @@ function scaffold(dir: string, optsOrName: ScaffoldOptions | string): void {
520
676
 
521
677
  const pkgPath = join(dir, "package.json")
522
678
  if (!existsSync(pkgPath)) {
523
- write("package.json", packageJsonTemplate(opts, cliPackageVersion()))
679
+ write("package.json", packageJsonTemplate(opts, deps))
524
680
  } else {
525
- file("skipped", "package.json (already exists)")
681
+ try {
682
+ const existing = JSON.parse(readFileSync(pkgPath, "utf8")) as Record<string, unknown>
683
+ const merged = mergeSupatypePackageJson(existing, opts, deps)
684
+ writeFileSync(pkgPath, `${JSON.stringify(merged, null, 2)}\n`, "utf8")
685
+ file("updated", "package.json (added Supatype dependencies)")
686
+ } catch {
687
+ warn("Could not merge into package.json — add @supatype/cli and @supatype/types manually.")
688
+ file("skipped", "package.json (invalid JSON)")
689
+ }
526
690
  }
527
691
 
528
692
  write("supatype.config.ts", tsConfigTemplate(opts))
@@ -533,7 +697,7 @@ function scaffold(dir: string, optsOrName: ScaffoldOptions | string): void {
533
697
  write(".env", envTemplate(opts))
534
698
  write("seed.ts", seedTemplate(opts.projectName))
535
699
  write("seeds/.gitkeep", "")
536
- scaffoldAppAssets(opts, write)
700
+ scaffoldAppAssets(dir, opts, write)
537
701
 
538
702
  if (opts.helloFunction) scaffoldHelloFunction(dir, write)
539
703
 
@@ -557,40 +721,63 @@ function staticDirRelative(staticDir?: string): string {
557
721
  }
558
722
 
559
723
  function scaffoldAppAssets(
724
+ dir: string,
560
725
  opts: ScaffoldOptions,
561
726
  write: (rel: string, content: string) => void,
562
727
  ): void {
728
+ const writeUnlessExists = (rel: string, content: string) => {
729
+ const full = join(dir, rel)
730
+ if (existsSync(full)) {
731
+ file("skipped", `${rel} (already exists)`)
732
+ return
733
+ }
734
+ write(rel, content)
735
+ }
736
+
563
737
  const holding = holdingPageTemplate(opts.projectName)
564
738
  const hasVite = Boolean(opts.app.viteDevUrl)
565
739
 
566
740
  if (opts.app.mode === "static") {
567
741
  const staticRel = staticDirRelative(opts.app.staticDir)
568
- write(`${staticRel}/index.html`, holding)
569
- if (hasVite) scaffoldVite(opts, write, holding)
742
+ if (opts.app.viteDevUrl && opts.productionTarget !== "later") {
743
+ writeUnlessExists("dist/index.html", holding)
744
+ scaffoldVite(dir, opts, writeUnlessExists, holding)
745
+ return
746
+ }
747
+ writeUnlessExists(`${staticRel}/index.html`, holding)
748
+ if (opts.app.viteDevUrl) scaffoldVite(dir, opts, writeUnlessExists, holding)
570
749
  return
571
750
  }
572
751
 
573
752
  if (opts.app.mode === "proxy" && opts.productionTarget !== "later") {
574
- write("dist/index.html", holding)
575
- if (hasVite) scaffoldVite(opts, write, holding)
753
+ writeUnlessExists("dist/index.html", holding)
754
+ if (hasVite) scaffoldVite(dir, opts, writeUnlessExists, holding)
576
755
  return
577
756
  }
578
757
 
579
758
  if (opts.app.mode === "proxy" && hasVite) {
580
- scaffoldVite(opts, write, holding)
759
+ scaffoldVite(dir, opts, writeUnlessExists, holding)
581
760
  return
582
761
  }
583
762
 
584
- write("public/.gitkeep", "")
763
+ writeUnlessExists("public/.gitkeep", "")
585
764
  }
586
765
 
587
766
  function scaffoldVite(
767
+ dir: string,
588
768
  opts: ScaffoldOptions,
589
769
  write: (rel: string, content: string) => void,
590
770
  holding: string,
591
771
  ): void {
592
772
  if (!opts.app.viteDevUrl) return
593
773
  write("index.html", holding)
774
+ const viteConfigRel = ["vite.config.ts", "vite.config.js", "vite.config.mjs", "vite.config.cjs"].find(
775
+ (name) => existsSync(join(dir, name)),
776
+ )
777
+ if (viteConfigRel) {
778
+ file("skipped", `${viteConfigRel} (already exists)`)
779
+ return
780
+ }
594
781
  write("vite.config.ts", viteConfigTemplate(opts.app.viteDevUrl))
595
782
  }
596
783
 
@@ -609,7 +796,7 @@ function scaffoldHelloFunction(
609
796
 
610
797
  // ─── Templates ───────────────────────────────────────────────────────────────
611
798
 
612
- function packageJsonTemplate(opts: ScaffoldOptions, cliVersion: string): string {
799
+ function packageJsonTemplate(opts: ScaffoldOptions, deps: InitDependencyVersions): string {
613
800
  const scripts: string[] = [
614
801
  ` "dev": "supatype dev"`,
615
802
  ` "push": "supatype push"`,
@@ -633,8 +820,8 @@ function packageJsonTemplate(opts: ScaffoldOptions, cliVersion: string): string
633
820
  ${scripts.join(",\n")}
634
821
  },
635
822
  "dependencies": {
636
- "@supatype/cli": "^${cliVersion}",
637
- "@supatype/types": "^${cliVersion}"
823
+ "@supatype/cli": "^${deps.cli}",
824
+ "@supatype/types": "^${deps.types}"
638
825
  },
639
826
  "devDependencies": {
640
827
  ${devDeps.join(",\n")}
@@ -701,20 +888,49 @@ function localConfigTemplate(app: ScaffoldAppOptions): string {
701
888
  `const localConfig: Partial<SupatypeConfig> = {`,
702
889
  ` server: { mode: "dev" },`,
703
890
  ]
704
- if (app.mode === "proxy") {
705
- lines.push(` app: {`)
706
- lines.push(` mode: "proxy",`)
707
- lines.push(` upstream: "${app.upstream ?? "http://localhost:3000"}",`)
708
- lines.push(` start: "${app.start ?? "dev"}",`)
709
- if (app.viteDevUrl) lines.push(` vite_dev_url: "${app.viteDevUrl}",`)
710
- lines.push(` },`)
891
+ const localApp = localDevAppConfigLines(app)
892
+ if (localApp.length > 0) {
893
+ lines.push(...localApp)
711
894
  }
712
895
  lines.push(`}`, ``, `export default localConfig`, ``)
713
896
  return lines.join("\n")
714
897
  }
715
898
 
899
+ /** App block for supatype.local.config.ts (proxy + Vite during local dev). */
900
+ function localDevAppConfigLines(app: ScaffoldAppOptions): string[] {
901
+ if (app.mode === "proxy") {
902
+ return [
903
+ ` app: {`,
904
+ ` mode: "proxy",`,
905
+ ` upstream: "${app.upstream ?? "http://localhost:3000"}",`,
906
+ ` start: "${app.start ?? "dev"}",`,
907
+ ...(app.viteDevUrl ? [` vite_dev_url: "${app.viteDevUrl}",`] : []),
908
+ ` },`,
909
+ ]
910
+ }
911
+ if (app.viteDevUrl) {
912
+ return [
913
+ ` app: {`,
914
+ ` mode: "proxy",`,
915
+ ` upstream: "${app.viteDevUrl}",`,
916
+ ` start: "vite",`,
917
+ ` vite_dev_url: "${app.viteDevUrl}",`,
918
+ ` },`,
919
+ ]
920
+ }
921
+ return []
922
+ }
923
+
716
924
  function appConfigLines(app: ScaffoldAppOptions, productionTarget: ProductionTarget): string[] {
717
925
  if (app.mode === "static") {
926
+ if (productionTarget !== "later" && app.viteDevUrl) {
927
+ return [
928
+ ` app: {`,
929
+ ` mode: "static",`,
930
+ ` static_dir: "./dist", // production build output`,
931
+ ` },`,
932
+ ]
933
+ }
718
934
  const out = [
719
935
  ` app: {`,
720
936
  ` mode: "static",`,
@@ -774,7 +990,7 @@ function holdingPageTemplate(projectName: string): string {
774
990
  <meta charset="UTF-8" />
775
991
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
776
992
  <title>${name} — Supatype</title>
777
- <meta name="description" content="A Supatype project. Define your types — we generate your backend." />
993
+ <meta name="description" content="A Supatype project. Define your types — we generate your platform." />
778
994
  <link rel="preconnect" href="https://fonts.googleapis.com" />
779
995
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
780
996
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
@@ -909,6 +1125,8 @@ export default defineConfig({
909
1125
  host: "127.0.0.1",
910
1126
  port: ${port},
911
1127
  strictPort: true,
1128
+ // Docker Compose app proxy reaches the host as host.docker.internal.
1129
+ allowedHosts: ["127.0.0.1", "localhost", "host.docker.internal"],
912
1130
  },
913
1131
  })
914
1132
  `
@@ -1185,27 +1403,35 @@ function printNextSteps(args: {
1185
1403
  result: WizardResult
1186
1404
  installed: boolean
1187
1405
  keysGenerated: boolean
1406
+ binariesReady: boolean
1188
1407
  }): void {
1189
- const { name, result, installed, keysGenerated } = args
1408
+ const { name, result, installed, keysGenerated, binariesReady } = args
1409
+ const setupComplete = installed && keysGenerated && binariesReady
1190
1410
  const steps: string[] = []
1191
1411
  if (name) steps.push(`cd ${name}`)
1192
- if (!installed) steps.push(`${result.packageManager} install`)
1193
- if (!keysGenerated) steps.push("supatype keys")
1194
1412
  const kongHint =
1195
1413
  result.provider === "docker" && result.kongPort !== undefined
1196
1414
  ? `supatype dev # Docker Compose stack (Kong :${result.kongPort})`
1197
1415
  : "supatype dev # Docker Compose stack (Kong :18473)"
1198
- steps.push(kongHint)
1199
- steps.push("supatype push # apply schema + generate types")
1200
- if (result.adminEmail) {
1201
- steps.push(" # first admin user is created on dev or push")
1416
+ if (setupComplete) {
1417
+ steps.push(kongHint)
1418
+ steps.push("supatype push # apply schema + generate types")
1419
+ if (result.adminEmail) {
1420
+ steps.push(" # first admin user is created on dev or push")
1421
+ }
1422
+ } else {
1423
+ if (!installed) steps.push(`${result.packageManager} install`)
1424
+ if (!binariesReady) steps.push("supatype update # download component binaries")
1425
+ if (!keysGenerated) steps.push("supatype keys")
1426
+ steps.push(kongHint)
1427
+ steps.push("supatype push # apply schema + generate types")
1202
1428
  }
1203
1429
  if (result.helloFunction) {
1204
1430
  steps.push("supatype functions serve # run edge functions locally")
1205
1431
  }
1206
1432
 
1207
1433
  info(`Supatype project ready${name ? ` in ${name}/` : ""}.`)
1208
- nextSteps("Next steps:", steps)
1434
+ nextSteps(setupComplete ? "Next steps:" : "Finish setup:", steps)
1209
1435
 
1210
1436
  if (result.app.mode === "none") {
1211
1437
  nextSteps("Static frontend (self-host):", [