@supatype/cli 0.1.0-alpha.9 → 0.1.1

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