@supatype/cli 0.1.0-alpha.8 → 0.1.0

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 (388) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-test.log +274 -69
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/assets/supatype-logo-wordmark.ascii.txt +6 -0
  5. package/bin/dev-entry.ts +2 -1
  6. package/dist/app/framework.js +1 -3
  7. package/dist/app/framework.js.map +1 -1
  8. package/dist/app/proxy-dev-app.d.ts +14 -0
  9. package/dist/app/proxy-dev-app.d.ts.map +1 -1
  10. package/dist/app/proxy-dev-app.js +110 -6
  11. package/dist/app/proxy-dev-app.js.map +1 -1
  12. package/dist/app-config.d.ts +10 -0
  13. package/dist/app-config.d.ts.map +1 -1
  14. package/dist/app-config.js +72 -0
  15. package/dist/app-config.js.map +1 -1
  16. package/dist/assets/supatype-logo-wordmark.ascii.txt +6 -0
  17. package/dist/binary-cache.d.ts +19 -7
  18. package/dist/binary-cache.d.ts.map +1 -1
  19. package/dist/binary-cache.js +92 -46
  20. package/dist/binary-cache.js.map +1 -1
  21. package/dist/cli.d.ts +1 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +17 -2
  24. package/dist/cli.js.map +1 -1
  25. package/dist/commands/add.d.ts +3 -0
  26. package/dist/commands/add.d.ts.map +1 -0
  27. package/dist/commands/add.js +86 -0
  28. package/dist/commands/add.js.map +1 -0
  29. package/dist/commands/admin.d.ts.map +1 -1
  30. package/dist/commands/admin.js +39 -53
  31. package/dist/commands/admin.js.map +1 -1
  32. package/dist/commands/adopt.d.ts +3 -0
  33. package/dist/commands/adopt.d.ts.map +1 -0
  34. package/dist/commands/adopt.js +55 -0
  35. package/dist/commands/adopt.js.map +1 -0
  36. package/dist/commands/app.d.ts.map +1 -1
  37. package/dist/commands/app.js +20 -17
  38. package/dist/commands/app.js.map +1 -1
  39. package/dist/commands/cache.d.ts.map +1 -1
  40. package/dist/commands/cache.js +11 -10
  41. package/dist/commands/cache.js.map +1 -1
  42. package/dist/commands/cloud.d.ts +4 -9
  43. package/dist/commands/cloud.d.ts.map +1 -1
  44. package/dist/commands/cloud.js +75 -125
  45. package/dist/commands/cloud.js.map +1 -1
  46. package/dist/commands/db.d.ts.map +1 -1
  47. package/dist/commands/db.js +37 -58
  48. package/dist/commands/db.js.map +1 -1
  49. package/dist/commands/deploy.d.ts.map +1 -1
  50. package/dist/commands/deploy.js +140 -96
  51. package/dist/commands/deploy.js.map +1 -1
  52. package/dist/commands/dev.d.ts.map +1 -1
  53. package/dist/commands/dev.js +72 -39
  54. package/dist/commands/dev.js.map +1 -1
  55. package/dist/commands/diff.d.ts.map +1 -1
  56. package/dist/commands/diff.js +39 -39
  57. package/dist/commands/diff.js.map +1 -1
  58. package/dist/commands/doctor.d.ts +3 -0
  59. package/dist/commands/doctor.d.ts.map +1 -0
  60. package/dist/commands/doctor.js +78 -0
  61. package/dist/commands/doctor.js.map +1 -0
  62. package/dist/commands/engine.d.ts.map +1 -1
  63. package/dist/commands/engine.js +5 -4
  64. package/dist/commands/engine.js.map +1 -1
  65. package/dist/commands/functions.d.ts.map +1 -1
  66. package/dist/commands/functions.js +172 -119
  67. package/dist/commands/functions.js.map +1 -1
  68. package/dist/commands/generate.d.ts.map +1 -1
  69. package/dist/commands/generate.js +5 -4
  70. package/dist/commands/generate.js.map +1 -1
  71. package/dist/commands/init.d.ts +30 -1
  72. package/dist/commands/init.d.ts.map +1 -1
  73. package/dist/commands/init.js +814 -107
  74. package/dist/commands/init.js.map +1 -1
  75. package/dist/commands/introspect.d.ts +3 -0
  76. package/dist/commands/introspect.d.ts.map +1 -0
  77. package/dist/commands/introspect.js +35 -0
  78. package/dist/commands/introspect.js.map +1 -0
  79. package/dist/commands/keys.d.ts +15 -1
  80. package/dist/commands/keys.d.ts.map +1 -1
  81. package/dist/commands/keys.js +46 -10
  82. package/dist/commands/keys.js.map +1 -1
  83. package/dist/commands/link-helpers.d.ts +15 -0
  84. package/dist/commands/link-helpers.d.ts.map +1 -0
  85. package/dist/commands/link-helpers.js +225 -0
  86. package/dist/commands/link-helpers.js.map +1 -0
  87. package/dist/commands/logs.d.ts.map +1 -1
  88. package/dist/commands/logs.js +5 -4
  89. package/dist/commands/logs.js.map +1 -1
  90. package/dist/commands/migrate-from-v1.d.ts.map +1 -1
  91. package/dist/commands/migrate-from-v1.js +3 -2
  92. package/dist/commands/migrate-from-v1.js.map +1 -1
  93. package/dist/commands/migrate.d.ts.map +1 -1
  94. package/dist/commands/migrate.js +119 -26
  95. package/dist/commands/migrate.js.map +1 -1
  96. package/dist/commands/pg.d.ts.map +1 -1
  97. package/dist/commands/pg.js +11 -12
  98. package/dist/commands/pg.js.map +1 -1
  99. package/dist/commands/plugins.d.ts.map +1 -1
  100. package/dist/commands/plugins.js +55 -46
  101. package/dist/commands/plugins.js.map +1 -1
  102. package/dist/commands/pull.d.ts.map +1 -1
  103. package/dist/commands/pull.js +33 -5
  104. package/dist/commands/pull.js.map +1 -1
  105. package/dist/commands/push.d.ts.map +1 -1
  106. package/dist/commands/push.js +110 -137
  107. package/dist/commands/push.js.map +1 -1
  108. package/dist/commands/seed.d.ts.map +1 -1
  109. package/dist/commands/seed.js +4 -3
  110. package/dist/commands/seed.js.map +1 -1
  111. package/dist/commands/self-host.d.ts +2 -2
  112. package/dist/commands/self-host.d.ts.map +1 -1
  113. package/dist/commands/self-host.js +65 -50
  114. package/dist/commands/self-host.js.map +1 -1
  115. package/dist/commands/self-update.d.ts.map +1 -1
  116. package/dist/commands/self-update.js +3 -2
  117. package/dist/commands/self-update.js.map +1 -1
  118. package/dist/commands/status.d.ts +1 -1
  119. package/dist/commands/status.d.ts.map +1 -1
  120. package/dist/commands/status.js +95 -29
  121. package/dist/commands/status.js.map +1 -1
  122. package/dist/commands/types.d.ts.map +1 -1
  123. package/dist/commands/types.js +3 -2
  124. package/dist/commands/types.js.map +1 -1
  125. package/dist/commands/update.d.ts.map +1 -1
  126. package/dist/commands/update.js +54 -21
  127. package/dist/commands/update.js.map +1 -1
  128. package/dist/config.d.ts +2 -1
  129. package/dist/config.d.ts.map +1 -1
  130. package/dist/config.js.map +1 -1
  131. package/dist/dev-compose.d.ts +26 -0
  132. package/dist/dev-compose.d.ts.map +1 -1
  133. package/dist/dev-compose.js +328 -34
  134. package/dist/dev-compose.js.map +1 -1
  135. package/dist/dev-log-bus.d.ts +30 -0
  136. package/dist/dev-log-bus.d.ts.map +1 -0
  137. package/dist/dev-log-bus.js +87 -0
  138. package/dist/dev-log-bus.js.map +1 -0
  139. package/dist/dev-log-filter.d.ts +10 -0
  140. package/dist/dev-log-filter.d.ts.map +1 -0
  141. package/dist/dev-log-filter.js +36 -0
  142. package/dist/dev-log-filter.js.map +1 -0
  143. package/dist/dev-logo.d.ts +12 -0
  144. package/dist/dev-logo.d.ts.map +1 -0
  145. package/dist/dev-logo.js +56 -0
  146. package/dist/dev-logo.js.map +1 -0
  147. package/dist/dev-session.d.ts +26 -0
  148. package/dist/dev-session.d.ts.map +1 -0
  149. package/dist/dev-session.js +106 -0
  150. package/dist/dev-session.js.map +1 -0
  151. package/dist/dev-shutdown.d.ts +9 -0
  152. package/dist/dev-shutdown.d.ts.map +1 -0
  153. package/dist/dev-shutdown.js +50 -0
  154. package/dist/dev-shutdown.js.map +1 -0
  155. package/dist/dev-task-colors.d.ts +13 -0
  156. package/dist/dev-task-colors.d.ts.map +1 -0
  157. package/dist/dev-task-colors.js +43 -0
  158. package/dist/dev-task-colors.js.map +1 -0
  159. package/dist/dev-tui.d.ts +24 -0
  160. package/dist/dev-tui.d.ts.map +1 -0
  161. package/dist/dev-tui.js +188 -0
  162. package/dist/dev-tui.js.map +1 -0
  163. package/dist/diff-output.d.ts +5 -1
  164. package/dist/diff-output.d.ts.map +1 -1
  165. package/dist/diff-output.js +69 -0
  166. package/dist/diff-output.js.map +1 -1
  167. package/dist/docker-runtime.d.ts +30 -0
  168. package/dist/docker-runtime.d.ts.map +1 -0
  169. package/dist/docker-runtime.js +118 -0
  170. package/dist/docker-runtime.js.map +1 -0
  171. package/dist/engine-client.d.ts +10 -1
  172. package/dist/engine-client.d.ts.map +1 -1
  173. package/dist/engine-client.js +76 -17
  174. package/dist/engine-client.js.map +1 -1
  175. package/dist/engine-push-output.d.ts +17 -0
  176. package/dist/engine-push-output.d.ts.map +1 -0
  177. package/dist/engine-push-output.js +64 -0
  178. package/dist/engine-push-output.js.map +1 -0
  179. package/dist/ensure-binary.js +2 -2
  180. package/dist/ensure-binary.js.map +1 -1
  181. package/dist/gitignore.d.ts +8 -0
  182. package/dist/gitignore.d.ts.map +1 -0
  183. package/dist/gitignore.js +41 -0
  184. package/dist/gitignore.js.map +1 -0
  185. package/dist/kong-config.d.ts +9 -0
  186. package/dist/kong-config.d.ts.map +1 -1
  187. package/dist/kong-config.js +18 -1
  188. package/dist/kong-config.js.map +1 -1
  189. package/dist/link.d.ts +66 -0
  190. package/dist/link.d.ts.map +1 -0
  191. package/dist/link.js +160 -0
  192. package/dist/link.js.map +1 -0
  193. package/dist/process-manager.d.ts +8 -0
  194. package/dist/process-manager.d.ts.map +1 -1
  195. package/dist/process-manager.js +53 -9
  196. package/dist/process-manager.js.map +1 -1
  197. package/dist/project-config.d.ts +30 -3
  198. package/dist/project-config.d.ts.map +1 -1
  199. package/dist/project-config.js +37 -4
  200. package/dist/project-config.js.map +1 -1
  201. package/dist/prompts.d.ts +3 -0
  202. package/dist/prompts.d.ts.map +1 -0
  203. package/dist/prompts.js +3 -0
  204. package/dist/prompts.js.map +1 -0
  205. package/dist/pull-utils.d.ts +50 -14
  206. package/dist/pull-utils.d.ts.map +1 -1
  207. package/dist/pull-utils.js +152 -12
  208. package/dist/pull-utils.js.map +1 -1
  209. package/dist/resolve-target.d.ts +86 -0
  210. package/dist/resolve-target.d.ts.map +1 -0
  211. package/dist/resolve-target.js +291 -0
  212. package/dist/resolve-target.js.map +1 -0
  213. package/dist/restore-system-relation-targets.d.ts +3 -0
  214. package/dist/restore-system-relation-targets.d.ts.map +1 -0
  215. package/dist/restore-system-relation-targets.js +45 -0
  216. package/dist/restore-system-relation-targets.js.map +1 -0
  217. package/dist/runtime-routes.d.ts.map +1 -1
  218. package/dist/runtime-routes.js +7 -0
  219. package/dist/runtime-routes.js.map +1 -1
  220. package/dist/schema-ast-v2.d.ts +1 -1
  221. package/dist/schema-ast-v2.d.ts.map +1 -1
  222. package/dist/schema-ast-v2.js +2 -2
  223. package/dist/schema-ast-v2.js.map +1 -1
  224. package/dist/schema-sources.d.ts +40 -0
  225. package/dist/schema-sources.d.ts.map +1 -0
  226. package/dist/schema-sources.js +183 -0
  227. package/dist/schema-sources.js.map +1 -0
  228. package/dist/scripts/postinstall.js +5 -1
  229. package/dist/scripts/postinstall.js.map +1 -1
  230. package/dist/seed.d.ts +8 -0
  231. package/dist/seed.d.ts.map +1 -0
  232. package/dist/seed.js +32 -0
  233. package/dist/seed.js.map +1 -0
  234. package/dist/self-host-compose.d.ts +37 -1
  235. package/dist/self-host-compose.d.ts.map +1 -1
  236. package/dist/self-host-compose.js +233 -43
  237. package/dist/self-host-compose.js.map +1 -1
  238. package/dist/storage-provision.d.ts +4 -0
  239. package/dist/storage-provision.d.ts.map +1 -1
  240. package/dist/storage-provision.js +24 -2
  241. package/dist/storage-provision.js.map +1 -1
  242. package/dist/supatype-eval-1781522769253.d.mts +2 -0
  243. package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
  244. package/dist/supatype-eval-1781522769253.mjs +3 -0
  245. package/dist/supatype-eval-1781522769253.mjs.map +1 -0
  246. package/dist/systemd.js +2 -2
  247. package/dist/systemd.js.map +1 -1
  248. package/dist/target-client.d.ts +10 -0
  249. package/dist/target-client.d.ts.map +1 -0
  250. package/dist/target-client.js +22 -0
  251. package/dist/target-client.js.map +1 -0
  252. package/dist/type-extractor.d.ts +11 -0
  253. package/dist/type-extractor.d.ts.map +1 -1
  254. package/dist/type-extractor.js +95 -8
  255. package/dist/type-extractor.js.map +1 -1
  256. package/dist/ui/brand.d.ts +9 -0
  257. package/dist/ui/brand.d.ts.map +1 -0
  258. package/dist/ui/brand.js +11 -0
  259. package/dist/ui/brand.js.map +1 -0
  260. package/dist/ui/confirm.d.ts +12 -0
  261. package/dist/ui/confirm.d.ts.map +1 -0
  262. package/dist/ui/confirm.js +28 -0
  263. package/dist/ui/confirm.js.map +1 -0
  264. package/dist/ui/fatal.d.ts +10 -0
  265. package/dist/ui/fatal.d.ts.map +1 -0
  266. package/dist/ui/fatal.js +34 -0
  267. package/dist/ui/fatal.js.map +1 -0
  268. package/dist/ui/index.d.ts +9 -0
  269. package/dist/ui/index.d.ts.map +1 -0
  270. package/dist/ui/index.js +9 -0
  271. package/dist/ui/index.js.map +1 -0
  272. package/dist/ui/interactive.d.ts +3 -0
  273. package/dist/ui/interactive.d.ts.map +1 -0
  274. package/dist/ui/interactive.js +5 -0
  275. package/dist/ui/interactive.js.map +1 -0
  276. package/dist/ui/messages.d.ts +10 -0
  277. package/dist/ui/messages.d.ts.map +1 -0
  278. package/dist/ui/messages.js +35 -0
  279. package/dist/ui/messages.js.map +1 -0
  280. package/dist/ui/next-steps.d.ts +3 -0
  281. package/dist/ui/next-steps.d.ts.map +1 -0
  282. package/dist/ui/next-steps.js +10 -0
  283. package/dist/ui/next-steps.js.map +1 -0
  284. package/dist/ui/progress.d.ts +5 -0
  285. package/dist/ui/progress.d.ts.map +1 -0
  286. package/dist/ui/progress.js +24 -0
  287. package/dist/ui/progress.js.map +1 -0
  288. package/dist/ui/prompts.d.ts +14 -0
  289. package/dist/ui/prompts.d.ts.map +1 -0
  290. package/dist/ui/prompts.js +34 -0
  291. package/dist/ui/prompts.js.map +1 -0
  292. package/package.json +9 -4
  293. package/src/app/framework.ts +1 -3
  294. package/src/app/proxy-dev-app.ts +114 -6
  295. package/src/app-config.ts +80 -0
  296. package/src/binary-cache.ts +102 -52
  297. package/src/cli.ts +16 -2
  298. package/src/commands/add.ts +97 -0
  299. package/src/commands/admin.ts +39 -73
  300. package/src/commands/adopt.ts +82 -0
  301. package/src/commands/app.ts +20 -17
  302. package/src/commands/cache.ts +11 -10
  303. package/src/commands/cloud.ts +91 -142
  304. package/src/commands/db.ts +40 -63
  305. package/src/commands/deploy.ts +186 -126
  306. package/src/commands/dev.ts +95 -55
  307. package/src/commands/diff.ts +52 -43
  308. package/src/commands/doctor.ts +103 -0
  309. package/src/commands/engine.ts +5 -4
  310. package/src/commands/functions.ts +187 -123
  311. package/src/commands/generate.ts +5 -4
  312. package/src/commands/init.ts +996 -105
  313. package/src/commands/introspect.ts +48 -0
  314. package/src/commands/keys.ts +56 -14
  315. package/src/commands/link-helpers.ts +273 -0
  316. package/src/commands/logs.ts +5 -4
  317. package/src/commands/migrate-from-v1.ts +3 -2
  318. package/src/commands/migrate.ts +167 -27
  319. package/src/commands/pg.ts +13 -18
  320. package/src/commands/plugins.ts +55 -46
  321. package/src/commands/pull.ts +38 -9
  322. package/src/commands/push.ts +147 -174
  323. package/src/commands/seed.ts +5 -4
  324. package/src/commands/self-host.ts +85 -54
  325. package/src/commands/self-update.ts +3 -2
  326. package/src/commands/status.ts +102 -33
  327. package/src/commands/types.ts +3 -2
  328. package/src/commands/update.ts +59 -23
  329. package/src/config.ts +2 -1
  330. package/src/dev-compose.ts +426 -34
  331. package/src/dev-log-bus.ts +101 -0
  332. package/src/dev-log-filter.ts +32 -0
  333. package/src/dev-logo.ts +61 -0
  334. package/src/dev-session.ts +130 -0
  335. package/src/dev-shutdown.ts +54 -0
  336. package/src/dev-task-colors.ts +47 -0
  337. package/src/dev-tui.ts +232 -0
  338. package/src/diff-output.ts +79 -1
  339. package/src/docker-runtime.ts +151 -0
  340. package/src/engine-client.ts +81 -17
  341. package/src/engine-push-output.ts +75 -0
  342. package/src/ensure-binary.ts +2 -2
  343. package/src/gitignore.ts +48 -0
  344. package/src/kong-config.ts +24 -1
  345. package/src/link.ts +243 -0
  346. package/src/process-manager.ts +66 -10
  347. package/src/project-config.ts +62 -7
  348. package/src/prompts.ts +2 -0
  349. package/src/pull-utils.ts +217 -23
  350. package/src/resolve-target.ts +419 -0
  351. package/src/restore-system-relation-targets.ts +45 -0
  352. package/src/runtime-routes.ts +7 -0
  353. package/src/schema-ast-v2.ts +2 -1
  354. package/src/schema-sources.ts +248 -0
  355. package/src/scripts/postinstall.ts +7 -1
  356. package/src/seed.ts +43 -0
  357. package/src/self-host-compose.ts +261 -46
  358. package/src/storage-provision.ts +33 -1
  359. package/src/supatype-eval-1781522769253.mts +1 -0
  360. package/src/systemd.ts +2 -2
  361. package/src/target-client.ts +40 -0
  362. package/src/type-extractor.ts +124 -11
  363. package/src/ui/README.md +17 -0
  364. package/src/ui/brand.ts +12 -0
  365. package/src/ui/confirm.ts +38 -0
  366. package/src/ui/fatal.ts +43 -0
  367. package/src/ui/index.ts +8 -0
  368. package/src/ui/interactive.ts +4 -0
  369. package/src/ui/messages.ts +43 -0
  370. package/src/ui/next-steps.ts +10 -0
  371. package/src/ui/progress.ts +28 -0
  372. package/src/ui/prompts.ts +40 -0
  373. package/tests/cli-help.test.ts +27 -2
  374. package/tests/config.test.ts +29 -2
  375. package/tests/dev-ui.test.ts +139 -0
  376. package/tests/docker-runtime.test.ts +236 -0
  377. package/tests/engine-push-output.test.ts +67 -0
  378. package/tests/init.test.ts +197 -18
  379. package/tests/link.test.ts +148 -0
  380. package/tests/minisign.test.ts +102 -0
  381. package/tests/proxy-dev-app.test.ts +45 -1
  382. package/tests/pull-utils.test.ts +5 -4
  383. package/tests/runtime-contract.test.ts +186 -2
  384. package/tests/schema-sources.test.ts +119 -0
  385. package/tests/storage-provision.test.ts +100 -0
  386. package/tests/ui-confirm.test.ts +41 -0
  387. package/tests/ui-messages.test.ts +66 -0
  388. package/tsconfig.tsbuildinfo +1 -1
@@ -1,8 +1,14 @@
1
1
  import type { Command } from "commander"
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
3
- import { resolve, join, dirname } from "node:path"
3
+ import { resolve, join, dirname, basename } from "node:path"
4
4
  import { fileURLToPath } from "node:url"
5
- import { fetchAllLatestVersions } from "../binary-cache.js"
5
+ import { spawnSync } from "node:child_process"
6
+ import * as p from "@clack/prompts"
7
+ import { ensureNotCancelled, printLogo } from "../ui/prompts.js"
8
+ import { generateAndWriteKeys } from "./keys.js"
9
+ import { file, error, info, plain, warn } from "../ui/messages.js"
10
+ import { nextSteps } from "../ui/next-steps.js"
11
+ import { probeDockerDaemon, reportDockerUnavailable } from "../docker-runtime.js"
6
12
 
7
13
  export { scaffold }
8
14
 
@@ -26,158 +32,852 @@ function cliPackageVersion(): string {
26
32
  }
27
33
  }
28
34
 
35
+ // ─── Options model ─────────────────────────────────────────────────────────--
36
+
37
+ type PackageManager = "npm" | "pnpm" | "yarn" | "bun"
38
+
39
+ /** Where the project runs in production (drives committed config + local override). */
40
+ type ProductionTarget = "cloud" | "self-host" | "later"
41
+
42
+ export interface ScaffoldAppOptions {
43
+ mode: "none" | "static" | "proxy"
44
+ staticDir?: string
45
+ upstream?: string
46
+ start?: string
47
+ viteDevUrl?: string
48
+ }
49
+
50
+ /** File-affecting answers that drive what `scaffold()` writes. */
51
+ export interface ScaffoldOptions {
52
+ projectName: string
53
+ /** Local development runtime (docker recommended). */
54
+ provider: "docker" | "native"
55
+ productionTarget: ProductionTarget
56
+ domain?: string
57
+ /** ACME contact email for Let's Encrypt HTTPS (self-host + domain). */
58
+ tlsEmail?: string
59
+ schemaPath: string
60
+ app: ScaffoldAppOptions
61
+ email: "console" | "smtp" | "resend" | "ses"
62
+ /** Object storage while developing locally (`supatype dev`). */
63
+ storageLocal: "local" | "s3"
64
+ /** Object storage when deployed to production. */
65
+ storageProduction: "local" | "s3"
66
+ helloFunction: boolean
67
+ }
68
+
69
+ type StorageProvider = ScaffoldOptions["storageLocal"]
70
+
71
+ const STORAGE_PROVIDER_OPTIONS: {
72
+ value: StorageProvider
73
+ label: string
74
+ hint: string
75
+ }[] = [
76
+ { value: "local", label: "Local", hint: "storage you host yourself (MinIO)" },
77
+ { value: "s3", label: "S3", hint: "external bucket (AWS S3 or compatible)" },
78
+ ]
79
+
80
+ /** Wizard result = scaffold options plus runtime actions (install / keys). */
81
+ interface WizardResult extends ScaffoldOptions {
82
+ packageManager: PackageManager
83
+ install: boolean
84
+ generateKeys: boolean
85
+ }
86
+
87
+ /** `--mode dev|standalone` is mapped onto a production target for back-compat. */
88
+ function productionTargetFromMode(mode: string): ProductionTarget {
89
+ return mode === "standalone" ? "self-host" : "later"
90
+ }
91
+
92
+ /** supatype-server mode written to the committed config for a production target. */
93
+ function serverModeForTarget(target: ProductionTarget): "dev" | "standalone" | "managed" {
94
+ switch (target) {
95
+ case "cloud":
96
+ return "managed"
97
+ case "self-host":
98
+ return "standalone"
99
+ case "later":
100
+ return "dev"
101
+ }
102
+ }
103
+
104
+ export function defaultScaffoldOptions(
105
+ projectName: string,
106
+ productionTarget: ProductionTarget = "later",
107
+ ): ScaffoldOptions {
108
+ return {
109
+ projectName,
110
+ provider: "docker",
111
+ productionTarget,
112
+ ...(productionTarget === "self-host" ? { domain: "" } : {}),
113
+ schemaPath: "schema/index.ts",
114
+ app: { mode: "none" },
115
+ email: "console",
116
+ storageLocal: "local",
117
+ storageProduction: "local",
118
+ helloFunction: false,
119
+ }
120
+ }
121
+
122
+ // ─── Registration ──────────────────────────────────────────────────────────--
123
+
124
+ interface InitCliOptions {
125
+ mode: string
126
+ defaults?: boolean
127
+ install: boolean
128
+ keys: boolean
129
+ }
130
+
29
131
  export function registerInit(program: Command): void {
30
132
  program
31
133
  .command("init [name]")
32
134
  .description("Scaffold a new Supatype project")
33
135
  .option(
34
136
  "--mode <mode>",
35
- "Server mode in supatype.config.ts: dev (default) | standalone (native ACME — not Compose self-host)",
137
+ "Back-compat: dev (default, local only) | standalone (self-host production target)",
36
138
  "dev",
37
139
  )
38
- .action(async (name?: string, opts: { mode: string } = { mode: "dev" }) => {
39
- const projectName = name ?? "my-project"
140
+ .option("-y, --defaults", "Skip all prompts and use sensible defaults")
141
+ .option("--no-install", "Do not run the package manager install step")
142
+ .option("--no-keys", "Do not generate ANON_KEY / SERVICE_ROLE_KEY")
143
+ .action(async (name: string | undefined, opts: InitCliOptions) => {
40
144
  const dir = name ? resolve(process.cwd(), name) : process.cwd()
41
145
 
42
146
  if (name && existsSync(dir)) {
43
- console.error(`Directory already exists: ${dir}`)
147
+ error(`Directory already exists: ${dir}`)
44
148
  process.exit(1)
45
149
  }
46
150
 
47
- if (name) mkdirSync(dir, { recursive: true })
151
+ const defaultName = name ?? basename(dir) ?? "my-project"
152
+ const interactive = !opts.defaults && Boolean(process.stdin.isTTY)
48
153
 
49
- let versions: Record<string, string> = {}
50
- try {
51
- console.log("Fetching latest component versions from CDN...")
52
- versions = await fetchAllLatestVersions()
53
- } catch {
54
- // Non-fatal: scaffold with placeholder versions; user can run `supatype update`.
55
- }
154
+ const modeTarget = productionTargetFromMode(opts.mode)
56
155
 
57
- scaffold(dir, projectName, opts.mode as "dev" | "standalone", versions)
58
-
59
- console.log(`\nSupatype project ready${name ? ` in ${name}/` : ""}.\n`)
60
- console.log("Next steps:")
61
- if (name) console.log(` cd ${name}`)
62
- console.log(" npm install")
63
- console.log(" supatype keys")
64
- console.log(" supatype dev # native Postgres + supatype-server")
65
- console.log(" supatype push # apply schema + generate types")
66
- console.log("\nStatic frontend (self-host):")
67
- console.log(" supatype app add --static ./public")
68
- console.log(" npm run build # write files into public/")
69
- console.log(" supatype self-host compose up -d")
70
- if (opts.mode === "standalone") {
71
- console.log("\nStandalone (native TLS with ACME):")
72
- console.log(" Edit supatype.config.ts — set server.domain")
73
- console.log(" supatype dev # or run supatype-server with your TLS setup")
156
+ let result: WizardResult
157
+ if (interactive) {
158
+ printLogo()
159
+ result = await runWizard(defaultName, modeTarget)
160
+ } else {
161
+ result = {
162
+ ...defaultScaffoldOptions(defaultName, modeTarget),
163
+ packageManager: detectInvokingPackageManager(),
164
+ install: true,
165
+ generateKeys: true,
166
+ }
74
167
  }
75
- console.log()
168
+
169
+ // CLI flags override wizard / default action choices.
170
+ const doInstall = opts.install !== false && result.install
171
+ const doKeys = opts.keys !== false && result.generateKeys
172
+
173
+ if (name) mkdirSync(dir, { recursive: true })
174
+
175
+ scaffold(dir, result)
176
+
177
+ if (doInstall) runInstall(dir, result.packageManager)
178
+ const keysGenerated = doKeys ? writeKeys(dir) : false
179
+
180
+ warnDockerUnavailableForProvider(result.provider)
181
+
182
+ printNextSteps({
183
+ name,
184
+ result,
185
+ installed: doInstall,
186
+ keysGenerated,
187
+ })
76
188
  })
77
189
  }
78
190
 
79
- function scaffold(dir: string, projectName: string, mode: "dev" | "standalone" = "dev", versions: Record<string, string> = {}): void {
191
+ // ─── Wizard ──────────────────────────────────────────────────────────────────
192
+
193
+ async function runWizard(
194
+ defaultName: string,
195
+ defaultTarget: ProductionTarget,
196
+ ): Promise<WizardResult> {
197
+ p.intro("Create a new Supatype project")
198
+
199
+ const projectName = ensureNotCancelled(
200
+ await p.text({
201
+ message: "Project name",
202
+ defaultValue: defaultName,
203
+ placeholder: defaultName,
204
+ }),
205
+ ).trim() || defaultName
206
+
207
+ const packageManager = ensureNotCancelled(
208
+ await p.select<PackageManager>({
209
+ message: "Package manager",
210
+ initialValue: detectInvokingPackageManager(),
211
+ options: [
212
+ { value: "npm", label: "npm" },
213
+ { value: "pnpm", label: "pnpm" },
214
+ { value: "yarn", label: "yarn" },
215
+ { value: "bun", label: "bun" },
216
+ ],
217
+ }),
218
+ )
219
+
220
+ const productionTarget = ensureNotCancelled(
221
+ await p.select<ProductionTarget>({
222
+ message: "Where will this run in production?",
223
+ initialValue: defaultTarget,
224
+ options: [
225
+ { value: "cloud", label: "Supatype Cloud", hint: "managed; deploy via supatype link" },
226
+ { value: "self-host", label: "Self-host", hint: "your own server with TLS" },
227
+ { value: "later", label: "Decide later", hint: "local development only for now" },
228
+ ],
229
+ }),
230
+ )
231
+
232
+ let domain: string | undefined
233
+ let tlsEmail: string | undefined
234
+ if (productionTarget === "self-host") {
235
+ domain = ensureNotCancelled(
236
+ await p.text({
237
+ message: "Production domain for ACME TLS (optional, can set later)",
238
+ placeholder: "api.example.com",
239
+ defaultValue: "",
240
+ }),
241
+ ).trim()
242
+ if (domain) {
243
+ tlsEmail =
244
+ ensureNotCancelled(
245
+ await p.text({
246
+ message: "Email for Let's Encrypt (HTTPS) certificates",
247
+ placeholder: "you@example.com",
248
+ defaultValue: "",
249
+ }),
250
+ ).trim() || undefined
251
+ }
252
+ }
253
+
254
+ const provider = ensureNotCancelled(
255
+ await p.select<ScaffoldOptions["provider"]>({
256
+ message: "How should Postgres and the server run for local development?",
257
+ initialValue: "docker",
258
+ options: [
259
+ { value: "docker", label: "Docker", hint: "Docker Compose stack (recommended)" },
260
+ { value: "native", label: "Native", hint: "host Postgres + server binaries, no Docker" },
261
+ ],
262
+ }),
263
+ )
264
+
265
+ const schemaPath = ensureNotCancelled(
266
+ await p.text({
267
+ message: "Where should your schema live?",
268
+ defaultValue: "schema/index.ts",
269
+ placeholder: "schema/index.ts",
270
+ }),
271
+ ).trim() || "schema/index.ts"
272
+
273
+ const app = await promptApp()
274
+
275
+ const email = ensureNotCancelled(
276
+ await p.select<ScaffoldOptions["email"]>({
277
+ message: "Email provider",
278
+ initialValue: "console",
279
+ options: [
280
+ { value: "console", label: "console", hint: "log emails to the terminal (dev)" },
281
+ { value: "smtp", label: "SMTP" },
282
+ { value: "resend", label: "Resend" },
283
+ { value: "ses", label: "Amazon SES" },
284
+ ],
285
+ }),
286
+ )
287
+
288
+ const storageLocal = ensureNotCancelled(
289
+ await p.select<StorageProvider>({
290
+ message: "Local storage (for development)?",
291
+ initialValue: "local",
292
+ options: STORAGE_PROVIDER_OPTIONS,
293
+ }),
294
+ )
295
+
296
+ const storageProduction = ensureNotCancelled(
297
+ await p.select<StorageProvider>({
298
+ message: "Production storage?",
299
+ initialValue: "local",
300
+ options: STORAGE_PROVIDER_OPTIONS,
301
+ }),
302
+ )
303
+
304
+ const helloFunction = ensureNotCancelled(
305
+ await p.confirm({
306
+ message: "Create a hello-world edge function?",
307
+ initialValue: false,
308
+ }),
309
+ )
310
+
311
+ const install = ensureNotCancelled(
312
+ await p.confirm({
313
+ message: `Install dependencies with ${packageManager} now?`,
314
+ initialValue: true,
315
+ }),
316
+ )
317
+
318
+ const generateKeys = ensureNotCancelled(
319
+ await p.confirm({
320
+ message: "Generate ANON_KEY and SERVICE_ROLE_KEY now?",
321
+ initialValue: true,
322
+ }),
323
+ )
324
+
325
+ p.outro("Setting up your project...")
326
+
327
+ return {
328
+ projectName,
329
+ provider,
330
+ productionTarget,
331
+ ...(domain !== undefined ? { domain } : {}),
332
+ ...(tlsEmail !== undefined ? { tlsEmail } : {}),
333
+ schemaPath,
334
+ app,
335
+ email,
336
+ storageLocal,
337
+ storageProduction,
338
+ helloFunction,
339
+ packageManager,
340
+ install,
341
+ generateKeys,
342
+ }
343
+ }
344
+
345
+ async function promptApp(): Promise<ScaffoldAppOptions> {
346
+ const mode = ensureNotCancelled(
347
+ await p.select<ScaffoldAppOptions["mode"]>({
348
+ message: "Host a frontend app at /?",
349
+ initialValue: "none",
350
+ options: [
351
+ { value: "none", label: "No app", hint: "API only" },
352
+ { value: "static", label: "Static site", hint: "serve a built directory" },
353
+ { value: "proxy", label: "Local dev server", hint: "forward requests to a dev server you run" },
354
+ ],
355
+ }),
356
+ )
357
+
358
+ if (mode === "static") {
359
+ const staticDir = ensureNotCancelled(
360
+ await p.text({
361
+ message: "Directory to serve",
362
+ defaultValue: "./public",
363
+ placeholder: "./public",
364
+ }),
365
+ ).trim() || "./public"
366
+ const viteDevUrl = await promptViteDevUrl()
367
+ return { mode, staticDir, ...(viteDevUrl ? { viteDevUrl } : {}) }
368
+ }
369
+
370
+ if (mode === "proxy") {
371
+ const upstream = ensureNotCancelled(
372
+ await p.text({
373
+ message: "URL of your running dev server",
374
+ defaultValue: "http://localhost:3000",
375
+ placeholder: "http://localhost:3000",
376
+ }),
377
+ ).trim() || "http://localhost:3000"
378
+ const start = ensureNotCancelled(
379
+ await p.text({
380
+ message: "package.json script that starts your dev server",
381
+ defaultValue: "dev",
382
+ placeholder: "dev",
383
+ }),
384
+ ).trim() || "dev"
385
+ const viteDevUrl = await promptViteDevUrl()
386
+ return { mode, upstream, start, ...(viteDevUrl ? { viteDevUrl } : {}) }
387
+ }
388
+
389
+ return { mode: "none" }
390
+ }
391
+
392
+ async function promptViteDevUrl(): Promise<string | undefined> {
393
+ const useVite = ensureNotCancelled(
394
+ await p.confirm({
395
+ message: "Enable live reload from a separate Vite dev server?",
396
+ initialValue: false,
397
+ }),
398
+ )
399
+ if (!useVite) return undefined
400
+ return (
401
+ ensureNotCancelled(
402
+ await p.text({
403
+ message: "Vite dev server URL",
404
+ defaultValue: "http://127.0.0.1:5173",
405
+ placeholder: "http://127.0.0.1:5173",
406
+ }),
407
+ ).trim() || "http://127.0.0.1:5173"
408
+ )
409
+ }
410
+
411
+ // ─── Package manager ───────────────────────────────────────────────────────--
412
+
413
+ function detectInvokingPackageManager(): PackageManager {
414
+ const ua = process.env["npm_config_user_agent"] ?? ""
415
+ if (ua.startsWith("pnpm")) return "pnpm"
416
+ if (ua.startsWith("yarn")) return "yarn"
417
+ if (ua.startsWith("bun")) return "bun"
418
+ return "npm"
419
+ }
420
+
421
+ function runInstall(dir: string, pm: PackageManager): void {
422
+ info(`Installing dependencies with ${pm}...`)
423
+ const res = spawnSync(pm, ["install"], {
424
+ cwd: dir,
425
+ stdio: "inherit",
426
+ shell: process.platform === "win32",
427
+ })
428
+ if (res.status !== 0 || res.error) {
429
+ warn(`Dependency install did not complete (run "${pm} install" manually).`)
430
+ }
431
+ }
432
+
433
+ function writeKeys(dir: string): boolean {
434
+ const keys = generateAndWriteKeys(dir)
435
+ if (!keys) {
436
+ warn("Could not generate keys (JWT_SECRET missing). Run `supatype keys` manually.")
437
+ return false
438
+ }
439
+ return true
440
+ }
441
+
442
+ // ─── Scaffold ──────────────────────────────────────────────────────────────--
443
+
444
+ function scaffold(dir: string, optsOrName: ScaffoldOptions | string): void {
445
+ const opts =
446
+ typeof optsOrName === "string" ? defaultScaffoldOptions(optsOrName) : optsOrName
80
447
  const write = (rel: string, content: string) => {
81
448
  const full = join(dir, rel)
82
449
  mkdirSync(resolve(full, ".."), { recursive: true })
83
450
  writeFileSync(full, content, "utf8")
84
- console.log(` created ${rel}`)
451
+ file("created", rel)
85
452
  }
86
453
 
87
454
  const pkgPath = join(dir, "package.json")
88
455
  if (!existsSync(pkgPath)) {
89
- write("package.json", packageJsonTemplate(projectName, cliPackageVersion()))
456
+ write("package.json", packageJsonTemplate(opts, cliPackageVersion()))
90
457
  } else {
91
- console.log(" skipped package.json (already exists)")
458
+ file("skipped", "package.json (already exists)")
92
459
  }
93
460
 
94
- write("supatype.config.ts", tsConfigTemplate(projectName, mode, versions))
95
- write("schema/index.ts", schemaTemplate())
96
- write(".env", envTemplate(projectName))
97
- write("seed.ts", seedTemplate(projectName))
461
+ write("supatype.config.ts", tsConfigTemplate(opts))
462
+ if (opts.productionTarget !== "later") {
463
+ write("supatype.local.config.ts", localConfigTemplate(opts.app))
464
+ }
465
+ write(opts.schemaPath, schemaTemplate())
466
+ write(".env", envTemplate(opts))
467
+ write("seed.ts", seedTemplate(opts.projectName))
98
468
  write("seeds/.gitkeep", "")
469
+ scaffoldAppAssets(opts, write)
470
+
471
+ if (opts.helloFunction) scaffoldHelloFunction(dir, write)
472
+
473
+ const gitignorePath = join(dir, ".gitignore")
474
+ if (existsSync(gitignorePath)) {
475
+ const merged = mergeGitignoreTemplate(readFileSync(gitignorePath, "utf8"))
476
+ if (merged !== readFileSync(gitignorePath, "utf8")) {
477
+ writeFileSync(gitignorePath, merged, "utf8")
478
+ file("updated", ".gitignore (added .supatype/)")
479
+ } else {
480
+ file("skipped", ".gitignore (already exists)")
481
+ }
482
+ } else {
483
+ write(".gitignore", gitignoreTemplate())
484
+ }
485
+ }
486
+
487
+ function staticDirRelative(staticDir?: string): string {
488
+ const raw = (staticDir ?? "./public").trim()
489
+ return raw.replace(/^\.\//, "").replace(/\/+$/, "") || "public"
490
+ }
491
+
492
+ function scaffoldAppAssets(
493
+ opts: ScaffoldOptions,
494
+ write: (rel: string, content: string) => void,
495
+ ): void {
496
+ const holding = holdingPageTemplate(opts.projectName)
497
+ const hasVite = Boolean(opts.app.viteDevUrl)
498
+
499
+ if (opts.app.mode === "static") {
500
+ const staticRel = staticDirRelative(opts.app.staticDir)
501
+ write(`${staticRel}/index.html`, holding)
502
+ if (hasVite) scaffoldVite(opts, write, holding)
503
+ return
504
+ }
505
+
506
+ if (opts.app.mode === "proxy" && opts.productionTarget !== "later") {
507
+ write("dist/index.html", holding)
508
+ if (hasVite) scaffoldVite(opts, write, holding)
509
+ return
510
+ }
511
+
512
+ if (opts.app.mode === "proxy" && hasVite) {
513
+ scaffoldVite(opts, write, holding)
514
+ return
515
+ }
516
+
99
517
  write("public/.gitkeep", "")
100
- write(".gitignore", gitignoreTemplate())
518
+ }
519
+
520
+ function scaffoldVite(
521
+ opts: ScaffoldOptions,
522
+ write: (rel: string, content: string) => void,
523
+ holding: string,
524
+ ): void {
525
+ if (!opts.app.viteDevUrl) return
526
+ write("index.html", holding)
527
+ write("vite.config.ts", viteConfigTemplate(opts.app.viteDevUrl))
528
+ }
529
+
530
+ function scaffoldHelloFunction(
531
+ dir: string,
532
+ write: (rel: string, content: string) => void,
533
+ ): void {
534
+ write("functions/hello/index.ts", helloFunctionTemplate())
535
+ if (!existsSync(join(dir, "functions/_shared/README.md"))) {
536
+ write("functions/_shared/README.md", sharedFunctionsReadme())
537
+ }
538
+ if (!existsSync(join(dir, "functions/.env.local"))) {
539
+ write("functions/.env.local", functionsEnvLocalTemplate())
540
+ }
101
541
  }
102
542
 
103
543
  // ─── Templates ───────────────────────────────────────────────────────────────
104
544
 
105
- function packageJsonTemplate(projectName: string, cliVersion: string): string {
545
+ function packageJsonTemplate(opts: ScaffoldOptions, cliVersion: string): string {
546
+ const scripts: string[] = [
547
+ ` "dev": "supatype dev"`,
548
+ ` "push": "supatype push"`,
549
+ ` "seed": "tsx seed.ts"`,
550
+ ]
551
+ if (opts.app.viteDevUrl) {
552
+ scripts.push(` "vite": "vite"`)
553
+ }
554
+ if (opts.helloFunction) {
555
+ scripts.push(` "functions": "supatype functions serve"`)
556
+ }
557
+ const devDeps = [` "tsx": "^4.19.2"`, ` "typescript": "^5"`]
558
+ if (opts.app.viteDevUrl) {
559
+ devDeps.push(` "vite": "^6"`)
560
+ }
106
561
  return `{
107
- "name": "${projectName}",
562
+ "name": "${opts.projectName}",
108
563
  "private": true,
109
564
  "type": "module",
110
565
  "scripts": {
111
- "dev": "supatype dev",
112
- "push": "supatype push",
113
- "seed": "tsx seed.ts"
566
+ ${scripts.join(",\n")}
114
567
  },
115
568
  "dependencies": {
116
569
  "@supatype/cli": "^${cliVersion}",
117
570
  "@supatype/types": "^${cliVersion}"
118
571
  },
119
572
  "devDependencies": {
120
- "tsx": "^4.19.2",
121
- "typescript": "^5"
573
+ ${devDeps.join(",\n")}
122
574
  }
123
575
  }
124
576
  `
125
577
  }
126
578
 
127
- function tsConfigTemplate(projectName: string, mode: "dev" | "standalone", versions: Record<string, string> = {}): string {
128
- const domainField =
129
- mode === "standalone"
130
- ? ` domain: "", // e.g. "api.example.com" for ACME TLS\n`
131
- : ""
132
- const v = (key: string, fallback: string) => versions[key] ?? fallback
133
- return `import { defineConfig } from "@supatype/cli"
579
+ function tsConfigTemplate(opts: ScaffoldOptions): string {
580
+ const serverMode = serverModeForTarget(opts.productionTarget)
581
+ const hasLocalOverride = opts.productionTarget !== "later"
582
+ const lines: string[] = []
583
+ lines.push(`import { defineConfig } from "@supatype/cli"`)
584
+ lines.push("")
585
+ if (hasLocalOverride) {
586
+ lines.push(`// Committed config = ${opts.productionTarget} production target.`)
587
+ lines.push(`// Local development overrides live in supatype.local.config.ts (gitignored).`)
588
+ }
589
+ lines.push(`export default defineConfig({`)
590
+ lines.push(` project: { name: "${opts.projectName}" },`)
591
+ lines.push(` provider: "${opts.provider}",`)
592
+ if (opts.provider === "docker") {
593
+ lines.push(` // provider: "native" // host Postgres + supatype-server binaries (no Docker)`)
594
+ }
595
+ lines.push(` database: {`)
596
+ lines.push(` provider: "${opts.provider}",`)
597
+ lines.push(` },`)
598
+ lines.push(` server: {`)
599
+ lines.push(` mode: "${serverMode}",`)
600
+ lines.push(` port: 54321,`)
601
+ if (serverMode === "standalone") {
602
+ lines.push(` domain: "${opts.domain ?? ""}", // e.g. "api.example.com" for ACME TLS`)
603
+ if (opts.tlsEmail) {
604
+ lines.push(` tls: { email: "${opts.tlsEmail}" }, // automatic HTTPS via Let's Encrypt`)
605
+ } else {
606
+ lines.push(` // tls: { email: "you@example.com" }, // set to enable automatic HTTPS (Let's Encrypt)`)
607
+ }
608
+ }
609
+ lines.push(` },`)
610
+ lines.push(...appConfigLines(opts.app, opts.productionTarget))
611
+ if (opts.productionTarget !== "later") {
612
+ lines.push(` environments: { default: "production" }, // supatype link --env production ...`)
613
+ }
614
+ lines.push(
615
+ ` // Optional: pin component versions (native cache + Docker images synced to .env on dev/push)`,
616
+ )
617
+ lines.push(` // versions: { engine: "0.1.2", server: "1.0.5", postgres: "17.2", deno: "2.2.0" },`)
618
+ lines.push(` email: { provider: "${opts.email}" },`)
619
+ lines.push(...storageConfigLines(opts.storageLocal, opts.storageProduction))
620
+ lines.push(` schema: { path: "${opts.schemaPath}", pg_schema: "public" },`)
621
+ lines.push(
622
+ ` // Self-host production: supatype self-host compose (Docker only). Standalone + domain = native ACME dev.`,
623
+ )
624
+ lines.push(`})`)
625
+ return lines.join("\n") + "\n"
626
+ }
627
+
628
+ function localConfigTemplate(app: ScaffoldAppOptions): string {
629
+ const lines: string[] = [
630
+ `import type { SupatypeConfig } from "@supatype/cli"`,
631
+ ``,
632
+ `// Local development overrides — gitignored, deep-merged over supatype.config.ts.`,
633
+ `// Keeps \`supatype dev\` in local mode while the committed config targets production.`,
634
+ `const localConfig: Partial<SupatypeConfig> = {`,
635
+ ` server: { mode: "dev" },`,
636
+ ]
637
+ if (app.mode === "proxy") {
638
+ lines.push(` app: {`)
639
+ lines.push(` mode: "proxy",`)
640
+ lines.push(` upstream: "${app.upstream ?? "http://localhost:3000"}",`)
641
+ lines.push(` start: "${app.start ?? "dev"}",`)
642
+ if (app.viteDevUrl) lines.push(` vite_dev_url: "${app.viteDevUrl}",`)
643
+ lines.push(` },`)
644
+ }
645
+ lines.push(`}`, ``, `export default localConfig`, ``)
646
+ return lines.join("\n")
647
+ }
648
+
649
+ function appConfigLines(app: ScaffoldAppOptions, productionTarget: ProductionTarget): string[] {
650
+ if (app.mode === "static") {
651
+ const out = [
652
+ ` app: {`,
653
+ ` mode: "static",`,
654
+ ` static_dir: "${app.staticDir ?? "./public"}",`,
655
+ ]
656
+ if (app.viteDevUrl) out.push(` vite_dev_url: "${app.viteDevUrl}",`)
657
+ out.push(` },`)
658
+ return out
659
+ }
660
+ if (app.mode === "proxy") {
661
+ if (productionTarget !== "later") {
662
+ return [
663
+ ` app: {`,
664
+ ` mode: "static",`,
665
+ ` static_dir: "./dist", // production build output`,
666
+ ` },`,
667
+ ]
668
+ }
669
+ const out = [
670
+ ` app: {`,
671
+ ` mode: "proxy",`,
672
+ ` upstream: "${app.upstream ?? "http://localhost:3000"}",`,
673
+ ` start: "${app.start ?? "dev"}",`,
674
+ ]
675
+ if (app.viteDevUrl) out.push(` vite_dev_url: "${app.viteDevUrl}",`)
676
+ out.push(` },`)
677
+ return out
678
+ }
679
+ return [
680
+ ` app: {`,
681
+ ` mode: "none",`,
682
+ ` // mode: "static", static_dir: "./public", // supatype app add --static ./public`,
683
+ ` // mode: "proxy", upstream: "http://localhost:3000", start: "dev",`,
684
+ ` // vite_dev_url: "http://127.0.0.1:5173", // live reload from a separate Vite dev server`,
685
+ ` },`,
686
+ ]
687
+ }
688
+
689
+ const HOLDING_PAGE_LOGO_URL = "https://supatype.github.io/supatype/supatype.svg"
690
+ const HOLDING_PAGE_DOCS_URL = "https://supatype.github.io/supatype/"
691
+ const HOLDING_PAGE_GITHUB_URL = "https://github.com/supatype"
692
+ const HOLDING_PAGE_DISCORD_URL = "https://discord.gg/yaQrjQD4"
693
+
694
+ function escapeHtml(text: string): string {
695
+ return text
696
+ .replace(/&/g, "&amp;")
697
+ .replace(/</g, "&lt;")
698
+ .replace(/>/g, "&gt;")
699
+ .replace(/"/g, "&quot;")
700
+ }
701
+
702
+ function holdingPageTemplate(projectName: string): string {
703
+ const name = escapeHtml(projectName)
704
+ return `<!doctype html>
705
+ <html lang="en">
706
+ <head>
707
+ <meta charset="UTF-8" />
708
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
709
+ <title>${name} — Supatype</title>
710
+ <meta name="description" content="A Supatype project. Define your types — we generate your backend." />
711
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
712
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
713
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
714
+ <style>
715
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
716
+ :root {
717
+ --bg: #0a0a0f;
718
+ --border: rgba(255, 255, 255, 0.08);
719
+ --text: #e8e8f0;
720
+ --text-muted: #8888a8;
721
+ --purple: #7c3aed;
722
+ --purple-light: #a855f7;
723
+ }
724
+ body {
725
+ min-height: 100vh;
726
+ font-family: Inter, system-ui, sans-serif;
727
+ background: var(--bg);
728
+ color: var(--text);
729
+ line-height: 1.6;
730
+ display: flex;
731
+ align-items: center;
732
+ justify-content: center;
733
+ padding: 2rem;
734
+ }
735
+ body::before {
736
+ content: "";
737
+ position: fixed;
738
+ inset: 0;
739
+ background: radial-gradient(ellipse 80% 50% at 50% -20%, rgba(124, 58, 237, 0.18), transparent);
740
+ pointer-events: none;
741
+ }
742
+ main {
743
+ position: relative;
744
+ max-width: 32rem;
745
+ width: 100%;
746
+ text-align: center;
747
+ }
748
+ .logo {
749
+ display: block;
750
+ height: 2rem;
751
+ width: auto;
752
+ margin: 0 auto 2rem;
753
+ }
754
+ h1 {
755
+ font-size: 1.5rem;
756
+ font-weight: 700;
757
+ letter-spacing: -0.02em;
758
+ margin-bottom: 0.75rem;
759
+ }
760
+ .tagline {
761
+ color: var(--text-muted);
762
+ font-size: 1rem;
763
+ margin-bottom: 2rem;
764
+ }
765
+ .links {
766
+ display: flex;
767
+ flex-wrap: wrap;
768
+ gap: 0.75rem;
769
+ justify-content: center;
770
+ margin-bottom: 2.5rem;
771
+ }
772
+ .links a {
773
+ display: inline-flex;
774
+ align-items: center;
775
+ padding: 0.6rem 1.1rem;
776
+ border-radius: 10px;
777
+ border: 1px solid var(--border);
778
+ color: var(--text);
779
+ text-decoration: none;
780
+ font-size: 0.9rem;
781
+ font-weight: 500;
782
+ transition: border-color 0.15s, background 0.15s;
783
+ }
784
+ .links a:hover {
785
+ border-color: rgba(168, 85, 247, 0.45);
786
+ background: rgba(124, 58, 237, 0.08);
787
+ }
788
+ .links a.primary {
789
+ background: linear-gradient(135deg, var(--purple), var(--purple-light));
790
+ border-color: transparent;
791
+ color: #fff;
792
+ }
793
+ .links a.primary:hover {
794
+ opacity: 0.92;
795
+ }
796
+ .hint {
797
+ font-size: 0.85rem;
798
+ color: var(--text-muted);
799
+ }
800
+ .hint code {
801
+ font-family: ui-monospace, "JetBrains Mono", monospace;
802
+ font-size: 0.8rem;
803
+ background: rgba(255, 255, 255, 0.06);
804
+ padding: 0.15rem 0.4rem;
805
+ border-radius: 4px;
806
+ }
807
+ </style>
808
+ </head>
809
+ <body>
810
+ <main>
811
+ <img class="logo" src="${HOLDING_PAGE_LOGO_URL}" alt="Supatype" width="160" height="30" />
812
+ <h1>${name}</h1>
813
+ <p class="tagline">Your Supatype project is ready. Replace this page when you build your app.</p>
814
+ <nav class="links" aria-label="Supatype resources">
815
+ <a class="primary" href="${HOLDING_PAGE_DOCS_URL}" target="_blank" rel="noopener noreferrer">Documentation</a>
816
+ <a href="${HOLDING_PAGE_GITHUB_URL}" target="_blank" rel="noopener noreferrer">GitHub</a>
817
+ <a href="${HOLDING_PAGE_DISCORD_URL}" target="_blank" rel="noopener noreferrer">Discord</a>
818
+ </nav>
819
+ <p class="hint">Run <code>supatype dev</code> then open <code>http://localhost:18473/</code></p>
820
+ </main>
821
+ </body>
822
+ </html>
823
+ `
824
+ }
825
+
826
+ function vitePortFromDevUrl(viteDevUrl: string): number {
827
+ try {
828
+ const url = new URL(viteDevUrl)
829
+ if (url.port) return Number.parseInt(url.port, 10)
830
+ return url.protocol === "https:" ? 443 : 80
831
+ } catch {
832
+ return 5173
833
+ }
834
+ }
835
+
836
+ function viteConfigTemplate(viteDevUrl: string): string {
837
+ const port = vitePortFromDevUrl(viteDevUrl)
838
+ return `import { defineConfig } from "vite"
134
839
 
135
840
  export default defineConfig({
136
- project: { name: "${projectName}" },
137
- provider: "native",
138
- // provider: "docker" // full self-host Compose stack (Kong :18473)
139
- database: {
140
- provider: "native",
141
- },
142
841
  server: {
143
- mode: "${mode}",
144
- port: 54321,
145
- ${domainField} },
146
- app: {
147
- mode: "none",
148
- // mode: "static", static_dir: "./public", // supatype app add --static ./public
149
- // mode: "proxy", upstream: "http://localhost:3000", start: "dev",
150
- // vite_dev_url: "http://127.0.0.1:5173", // dev HMR at /_vite (when using a separate Vite server)
151
- },
152
- versions: {
153
- engine: "${v("engine", "latest")}",
154
- server: "${v("server", "latest")}",
155
- postgres: "${v("postgres", "latest")}",
156
- deno: "${v("deno", "latest")}",
842
+ host: "127.0.0.1",
843
+ port: ${port},
844
+ strictPort: true,
157
845
  },
158
- email: { provider: "console" },
159
- storage: { provider: "local", local_path: ".supatype/storage" },
160
- schema: { path: "schema/index.ts", pg_schema: "public" },
161
- // Self-host production: supatype self-host compose (Docker only). Standalone + domain = native ACME dev.
162
846
  })
163
847
  `
164
848
  }
165
849
 
850
+ function storageConfigLines(
851
+ storageLocal: StorageProvider,
852
+ storageProduction: StorageProvider,
853
+ ): string[] {
854
+ const lines: string[] = []
855
+ if (storageLocal === "s3") {
856
+ lines.push(` storage: { provider: "s3" }, // dev — configure S3_* in .env`)
857
+ } else {
858
+ lines.push(` storage: { provider: "local", local_path: ".supatype/storage" },`)
859
+ }
860
+ if (storageProduction === "s3" && storageLocal !== "s3") {
861
+ lines.push(` // Production storage: external S3 bucket — set production S3_* in .env`)
862
+ } else if (storageProduction === "local" && storageLocal === "s3") {
863
+ lines.push(` // Production storage: MinIO on your server (included in self-host compose)`)
864
+ }
865
+ return lines
866
+ }
867
+
166
868
  function schemaTemplate(): string {
167
- return `import type { Model, Public, Owner, Role, SupatypeAuthUserId, Unique, Email, UUID } from "@supatype/types"
869
+ return `import type { Model, LoggedIn, Owner, Public, Role, SupatypeAuthUserId, UUID } from "@supatype/types"
168
870
 
169
- export type User = Model<{
871
+ /** App profile for a signed-in user. \`id\` matches the Supatype auth user id. */
872
+ export type Profile = Model<{
170
873
  id: SupatypeAuthUserId
171
- email: Unique<Email>
172
- name: string
173
- created_at: string
174
- updated_at: string
874
+ display_name: string
175
875
  }, {
176
876
  access: {
177
- read: Public
178
- create: Public
877
+ read: LoggedIn
878
+ create: Owner<"id">
179
879
  update: Owner<"id">
180
- delete: Role<"admin">
880
+ delete: Owner<"id">
181
881
  }
182
882
  }>
183
883
 
@@ -195,34 +895,112 @@ export type SiteSettings = Model<{
195
895
  `
196
896
  }
197
897
 
198
- function envTemplate(projectName: string): string {
199
- return `DATABASE_URL=postgresql://supatype_admin:postgres@localhost:5432/${projectName}
898
+ function envTemplate(opts: ScaffoldOptions): string {
899
+ const sections: string[] = []
900
+ sections.push(`DATABASE_URL=postgresql://supatype_admin:postgres@localhost:5432/${opts.projectName}
200
901
  POSTGRES_USER=supatype_admin
201
902
  POSTGRES_PASSWORD=postgres
202
- POSTGRES_DB=${projectName}
903
+ POSTGRES_DB=${opts.projectName}`)
203
904
 
204
- # JWT — run \`supatype keys\` to generate ANON_KEY and SERVICE_ROLE_KEY
905
+ sections.push(`# JWT — run \`supatype keys\` to generate ANON_KEY and SERVICE_ROLE_KEY
205
906
  JWT_SECRET=super-secret-jwt-token-change-in-production
206
907
  ANON_KEY=
207
- SERVICE_ROLE_KEY=
908
+ SERVICE_ROLE_KEY=`)
208
909
 
209
- # Site URL (used by GoTrue for email redirects)
210
- SITE_URL=http://localhost:3000
910
+ sections.push(`# Site URL (used by GoTrue for email redirects)
911
+ SITE_URL=http://localhost:3000`)
211
912
 
212
- # SMTP — leave empty to use email autoconfirm in dev (no emails sent)
913
+ sections.push(emailEnvSection(opts.email, opts.projectName))
914
+ sections.push(storageEnvSections(opts.storageLocal, opts.storageProduction))
915
+
916
+ sections.push(
917
+ `# Self-host compose uses the same DATABASE_URL when Postgres is published on localhost:5432`,
918
+ )
919
+
920
+ return sections.join("\n\n") + "\n"
921
+ }
922
+
923
+ function emailEnvSection(email: ScaffoldOptions["email"], projectName: string): string {
924
+ switch (email) {
925
+ case "resend":
926
+ return `# Email (Resend)
927
+ RESEND_API_KEY=
928
+ RESEND_FROM=onboarding@resend.dev`
929
+ case "ses":
930
+ return `# Email (Amazon SES)
931
+ SES_FROM=
932
+ AWS_REGION=us-east-1
933
+ AWS_ACCESS_KEY_ID=
934
+ AWS_SECRET_ACCESS_KEY=`
935
+ case "smtp":
936
+ return `# Email (SMTP)
937
+ SMTP_HOST=
938
+ SMTP_PORT=587
939
+ SMTP_USER=
940
+ SMTP_PASS=
941
+ SMTP_SENDER_NAME=${projectName}`
942
+ case "console":
943
+ default:
944
+ return `# SMTP — leave empty to use email autoconfirm in dev (no emails sent)
213
945
  SMTP_HOST=
214
946
  SMTP_PORT=
215
947
  SMTP_USER=
216
948
  SMTP_PASS=
217
- SMTP_SENDER_NAME=${projectName}
949
+ SMTP_SENDER_NAME=${projectName}`
950
+ }
951
+ }
952
+
953
+ function storageEnvSections(
954
+ storageLocal: StorageProvider,
955
+ storageProduction: StorageProvider,
956
+ ): string {
957
+ if (storageLocal === storageProduction) {
958
+ if (storageLocal === "s3") {
959
+ return `# Storage (local development and production — external bucket)
960
+ # Use separate buckets for dev and production in your provider.
961
+ S3_ENDPOINT=
962
+ S3_REGION=us-east-1
963
+ S3_BUCKET=
964
+ S3_ACCESS_KEY=
965
+ S3_SECRET_KEY=`
966
+ }
967
+ return `${localStorageEnvSection("local")}
968
+
969
+ # Production storage (MinIO on your server)
970
+ # Included in the self-host compose stack — no extra configuration needed.`
971
+ }
218
972
 
219
- # Storage (MinIO for local dev)
973
+ return [localStorageEnvSection(storageLocal), productionStorageEnvSection(storageProduction)].join(
974
+ "\n\n",
975
+ )
976
+ }
977
+
978
+ function localStorageEnvSection(storage: StorageProvider): string {
979
+ if (storage === "s3") {
980
+ return `# Storage (local development — external bucket)
981
+ S3_ENDPOINT=
982
+ S3_REGION=us-east-1
983
+ S3_BUCKET=
984
+ S3_ACCESS_KEY=
985
+ S3_SECRET_KEY=`
986
+ }
987
+ return `# Storage (local development — MinIO)
220
988
  S3_ENDPOINT=http://localhost:9000
221
989
  S3_ACCESS_KEY=supatype
222
- S3_SECRET_KEY=supatype-secret
990
+ S3_SECRET_KEY=supatype-secret`
991
+ }
223
992
 
224
- # Self-host compose uses the same DATABASE_URL when Postgres is published on localhost:5432
225
- `
993
+ function productionStorageEnvSection(storage: StorageProvider): string {
994
+ if (storage === "s3") {
995
+ return `# Storage (production — external bucket)
996
+ S3_ENDPOINT=
997
+ S3_REGION=us-east-1
998
+ S3_BUCKET=
999
+ S3_ACCESS_KEY=
1000
+ S3_SECRET_KEY=`
1001
+ }
1002
+ return `# Storage (production — MinIO on your server)
1003
+ # Included in the self-host compose stack — no extra configuration needed.`
226
1004
  }
227
1005
 
228
1006
  function seedTemplate(projectName: string): string {
@@ -238,7 +1016,7 @@ async function seed() {
238
1016
  console.log("Seeding ${projectName}...")
239
1017
 
240
1018
  // TODO: insert seed data
241
- // await db\`INSERT INTO users (email, name) VALUES ('admin@example.com', 'Admin')\`
1019
+ // await db\`INSERT INTO profile (id, display_name) VALUES ('...', 'Admin')\`
242
1020
 
243
1021
  await db.end()
244
1022
  console.log("Done.")
@@ -251,17 +1029,130 @@ seed().catch((e) => {
251
1029
  `
252
1030
  }
253
1031
 
1032
+ function helloFunctionTemplate(): string {
1033
+ return `// hello — Supatype Edge Function
1034
+ // Docs: https://supatype.com/docs/edge-functions
1035
+
1036
+ export default async function handler(req: Request): Promise<Response> {
1037
+ const { method } = req
1038
+
1039
+ if (method === "POST") {
1040
+ const body = await req.json()
1041
+ return new Response(JSON.stringify({ message: "Hello from hello!", received: body }), {
1042
+ status: 200,
1043
+ headers: { "Content-Type": "application/json" },
1044
+ })
1045
+ }
1046
+
1047
+ return new Response(JSON.stringify({ message: "Hello from hello!" }), {
1048
+ status: 200,
1049
+ headers: { "Content-Type": "application/json" },
1050
+ })
1051
+ }
1052
+ `
1053
+ }
1054
+
1055
+ function sharedFunctionsReadme(): string {
1056
+ return "# Shared Code\n\nFiles in `_shared/` are available to all functions via relative imports.\nThis directory is not deployed as a function.\n\nExample: `import { sendEmail } from '../_shared/email.ts'`\n"
1057
+ }
1058
+
1059
+ function functionsEnvLocalTemplate(): string {
1060
+ return "# Local environment variables for edge functions\n# These are NOT committed to git\n# Set production env vars via: npx supatype functions env set KEY=value\n"
1061
+ }
1062
+
254
1063
  function gitignoreTemplate(): string {
255
1064
  return `.env
256
1065
  node_modules/
257
1066
  dist/
258
- .supatype/engine/
259
- # Local overrides — never commit
1067
+ .supatype/
260
1068
  supatype.local.config.ts
261
1069
  supatype.local.config.js
262
1070
  supatype.local.config.mjs
263
- # Generated by supatype push
1071
+ # Generated by supatype push (legacy paths — prefer output.types in config)
264
1072
  src/types/supatype.d.ts
265
1073
  src/lib/supatype.ts
266
1074
  `
267
1075
  }
1076
+
1077
+ export function mergeGitignoreTemplate(existingContent: string): string {
1078
+ if (existingContent.includes(".supatype/") || existingContent.includes(".supatype\n")) {
1079
+ return existingContent
1080
+ }
1081
+ const block = `
1082
+ # Supatype — local runtime (contains secrets in link.json)
1083
+ .supatype/
1084
+ `
1085
+ return existingContent.endsWith("\n") ? `${existingContent}${block}` : `${existingContent}\n${block}`
1086
+ }
1087
+
1088
+ // ─── Next steps ────────────────────────────────────────────────────────────--
1089
+
1090
+ function warnDockerUnavailableForProvider(provider: ScaffoldOptions["provider"]): void {
1091
+ if (provider !== "docker") return
1092
+ const probe = probeDockerDaemon()
1093
+ if (probe.ok) return
1094
+ reportDockerUnavailable(probe)
1095
+ plain()
1096
+ }
1097
+
1098
+ function printNextSteps(args: {
1099
+ name: string | undefined
1100
+ result: WizardResult
1101
+ installed: boolean
1102
+ keysGenerated: boolean
1103
+ }): void {
1104
+ const { name, result, installed, keysGenerated } = args
1105
+ const steps: string[] = []
1106
+ if (name) steps.push(`cd ${name}`)
1107
+ if (!installed) steps.push(`${result.packageManager} install`)
1108
+ if (!keysGenerated) steps.push("supatype keys")
1109
+ steps.push("supatype dev # Docker Compose stack (Kong :18473)")
1110
+ steps.push("supatype push # apply schema + generate types")
1111
+ if (result.helloFunction) {
1112
+ steps.push("supatype functions serve # run edge functions locally")
1113
+ }
1114
+
1115
+ info(`Supatype project ready${name ? ` in ${name}/` : ""}.`)
1116
+ nextSteps("Next steps:", steps)
1117
+
1118
+ if (result.app.mode === "none") {
1119
+ nextSteps("Static frontend (self-host):", [
1120
+ "supatype app add --static ./public",
1121
+ "npm run build # write files into public/",
1122
+ "supatype self-host compose up -d",
1123
+ ])
1124
+ }
1125
+
1126
+ if (result.productionTarget === "cloud") {
1127
+ nextSteps("Deploy to Supatype Cloud:", [
1128
+ "supatype login",
1129
+ "supatype link --env production --project <ref>",
1130
+ "supatype push --env production",
1131
+ ])
1132
+ info("supatype.local.config.ts keeps `supatype dev` local while the committed config targets cloud.")
1133
+ } else if (result.productionTarget === "self-host") {
1134
+ const selfHostSteps: string[] = []
1135
+ const domain = result.domain?.trim()
1136
+ if (domain) {
1137
+ selfHostSteps.push(`1. Point DNS: an A record for ${domain} -> your server's public IP`)
1138
+ selfHostSteps.push("2. Open ports 80 and 443 on the server firewall")
1139
+ if (!result.tlsEmail) {
1140
+ selfHostSteps.push("3. Set server.tls.email in supatype.config.ts (required for HTTPS)")
1141
+ }
1142
+ selfHostSteps.push("supatype self-host compose up -d # Kong provisions HTTPS automatically")
1143
+ selfHostSteps.push(`Your Supatype platform goes live at https://${domain}`)
1144
+ selfHostSteps.push(
1145
+ "Your app, REST, Auth, Storage, Realtime, Functions, and Studio — all behind one HTTPS domain (certs persist in valkey-data)",
1146
+ )
1147
+ } else {
1148
+ selfHostSteps.push(
1149
+ "Set server.domain + server.tls.email in supatype.config.ts to enable automatic HTTPS",
1150
+ )
1151
+ selfHostSteps.push("supatype self-host compose up -d # Docker stack")
1152
+ }
1153
+ selfHostSteps.push("supatype link --env production ... # then: supatype push --env production")
1154
+ nextSteps("Self-host production (your own server):", selfHostSteps)
1155
+ info("supatype.local.config.ts keeps `supatype dev` local while the committed config targets self-host.")
1156
+ }
1157
+ plain()
1158
+ }