@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
@@ -2,7 +2,7 @@
2
2
  * `supatype dev` when `provider: docker` — full self-host Compose stack (Kong gateway).
3
3
  */
4
4
 
5
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
5
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs"
6
6
  import { homedir } from "node:os"
7
7
  import { dirname, join, resolve } from "node:path"
8
8
  import { spawnSync } from "node:child_process"
@@ -10,24 +10,59 @@ import { startProxyDevApp } from "./app/proxy-dev-app.js"
10
10
  import { loadSchemaAst } from "./config.js"
11
11
  import {
12
12
  COMPOSE_DEV_KONG_PORT,
13
+ connectionString,
13
14
  projectRootFromConfig,
14
15
  resolveRuntimeProvider,
15
16
  schemaPathFromProject,
16
17
  type SupatypeProjectConfig,
17
18
  } from "./project-config.js"
18
19
  import { signJwt } from "./jwt.js"
19
- import { isPortInUse } from "./postgres-ctl.js"
20
- import { composeProjectName, runDockerCompose, writeSelfHostCompose, type SelfHostComposePaths } from "./self-host-compose.js"
20
+ import { ensureDevDbPort, ensureKongPort } from "./dev-ports.js"
21
+ import { handleComposeProjectRename } from "./compose-rename.js"
22
+ import { recoverStaleDevSession, writeDevSessionLock } from "./dev-session-lock.js"
23
+ import { readEnvValue, upsertEnvFile } from "./env-file.js"
24
+ import {
25
+ COMPOSE_PINNED_IMAGE_ENV_KEYS,
26
+ composeDockerImageEnv,
27
+ composeProjectName,
28
+ exitComposeFailed,
29
+ runDockerCompose,
30
+ schemaEngineImageForPush,
31
+ writeSelfHostCompose,
32
+ type SelfHostComposePaths,
33
+ } from "./self-host-compose.js"
34
+ import type { DockerBrandOptions } from "./docker-runtime.js"
21
35
  import { hasEngineOverride } from "./binary-cache.js"
22
36
  import { startStudioViteDevServer } from "./studio-dev-server.js"
23
- import { ensureEngine, engineRequest } from "./engine-client.js"
37
+ import { ensureEngine, engineRequest, type DiffResult } from "./engine-client.js"
38
+ import { writeSchemaSourcePushArtifacts, type SchemaSourcePushArtifacts } from "./schema-sources.js"
39
+ import { endDevSession } from "./dev-session.js"
40
+ import { writeLocalEnvironment } from "./link.js"
41
+ import { registerDevShutdown } from "./dev-shutdown.js"
42
+ import {
43
+ filterComposeNoise,
44
+ formatEnginePushMessage,
45
+ parseEngineJsonOutput,
46
+ parseEnginePushOutput,
47
+ } from "./engine-push-output.js"
24
48
  import { withAdminRoles } from "./studio-admin-roles.js"
49
+ import { restoreSystemRelationTargets } from "./restore-system-relation-targets.js"
50
+ import { provisionBucketsFromAst } from "./storage-provision.js"
51
+ import type { ExtractedSchemaAstV2 } from "./schema-ast-v2.js"
52
+ import { ensureFirstAdminUserForProject } from "./commands/admin.js"
25
53
 
26
54
  const LOCAL_JWT_SECRET = "super-secret-jwt-token-with-at-least-32-characters-long"
27
55
 
28
56
  /** Default host port for compose Postgres when `overrides.engine` is set (devLocal). */
29
57
  const COMPOSE_DEV_DB_PORT = 54329
30
58
 
59
+ /** Sync optional Docker image pins from config into `.env` (no JWT rotation). */
60
+ export function syncComposeImagePins(cwd: string, config: SupatypeProjectConfig): void {
61
+ const imagePins = composeDockerImageEnv(config)
62
+ const removeImageKeys = COMPOSE_PINNED_IMAGE_ENV_KEYS.filter((key) => !(key in imagePins))
63
+ upsertEnvFile(cwd, imagePins, removeImageKeys)
64
+ }
65
+
31
66
  export interface DevComposeOptions {
32
67
  watch: boolean
33
68
  }
@@ -39,30 +74,12 @@ export function composeDbUrl(): string {
39
74
 
40
75
  /**
41
76
  * Resolve the host Kong port for this project. Persisted in `.env` as
42
- * SUPATYPE_KONG_PORT so re-runs are stable; on first run it picks the default
43
- * (18473) or the next free port, so multiple projects can run concurrently.
77
+ * SUPATYPE_KONG_PORT; prompts when the configured port is already taken.
44
78
  */
45
79
  async function resolveDevDbPort(cwd: string): Promise<number> {
46
- const envPath = join(cwd, ".env")
47
- if (existsSync(envPath)) {
48
- const m = readFileSync(envPath, "utf8").match(/^SUPATYPE_DEV_DB_PORT=(\d+)/m)
49
- if (m && m[1]) return Number(m[1])
50
- }
51
- let port = COMPOSE_DEV_DB_PORT
52
- while (await isPortInUse(port)) port++
53
- return port
54
- }
55
-
56
- function readEnvValue(cwd: string, key: string, fallback: string): string {
57
- const envPath = join(cwd, ".env")
58
- if (existsSync(envPath)) {
59
- const m = readFileSync(envPath, "utf8").match(new RegExp(`^${key}=(.+)$`, "m"))
60
- if (m?.[1]) return m[1].trim()
61
- }
62
- return fallback
80
+ return ensureDevDbPort(cwd)
63
81
  }
64
82
 
65
- /** Postgres DSN for compose db when published to the host (local engine push). */
66
83
  function hostComposeDbUrl(cwd: string): string {
67
84
  const port = readEnvValue(cwd, "SUPATYPE_DEV_DB_PORT", String(COMPOSE_DEV_DB_PORT))
68
85
  const user = readEnvValue(cwd, "POSTGRES_USER", "supatype_admin")
@@ -71,40 +88,92 @@ function hostComposeDbUrl(cwd: string): string {
71
88
  return `postgresql://${user}:${pass}@127.0.0.1:${port}/${db}?sslmode=disable`
72
89
  }
73
90
 
74
- async function resolveKongPort(cwd: string): Promise<number> {
75
- const envPath = join(cwd, ".env")
76
- if (existsSync(envPath)) {
77
- const m = readFileSync(envPath, "utf8").match(/^SUPATYPE_KONG_PORT=(\d+)/m)
78
- if (m && m[1]) return Number(m[1])
79
- }
80
- let port = COMPOSE_DEV_KONG_PORT
81
- while (await isPortInUse(port)) port++
82
- return port
91
+ /**
92
+ * When `provider: docker` and `overrides.engine` is set, ensure Postgres is published
93
+ * on the host (SUPATYPE_DEV_DB_PORT) so the local engine binary can connect.
94
+ */
95
+ export async function ensureDockerDbPublishedForHostEngine(
96
+ cwd: string,
97
+ config: SupatypeProjectConfig,
98
+ brand?: DockerBrandOptions,
99
+ ): Promise<void> {
100
+ if (resolveRuntimeProvider(config) !== "docker") {
101
+ throw new Error("ensureDockerDbPublishedForHostEngine requires provider: docker")
102
+ }
103
+ if (!hasEngineOverride(config)) {
104
+ throw new Error(
105
+ "Docker Postgres is not published to the host without overrides.engine. " +
106
+ "Set overrides.engine in supatype.local.config.ts or pass --connection.",
107
+ )
108
+ }
109
+
110
+ const project = composeProjectName(config.project.name)
111
+ const kongPort = await resolveKongPort(cwd)
112
+ const devDbPort = await resolveDevDbPort(cwd)
113
+
114
+ const now = Math.floor(Date.now() / 1000)
115
+ const jwtBase = { iss: "supatype", iat: now, exp: now + 315_360_000 }
116
+ const anonKey = signJwt({ ...jwtBase, role: "anon" }, LOCAL_JWT_SECRET)
117
+ const serviceRoleKey = signJwt({ ...jwtBase, role: "service_role" }, LOCAL_JWT_SECRET)
118
+ ensureDevComposeEnv(cwd, config, anonKey, serviceRoleKey, kongPort, devDbPort)
119
+
120
+ const paths = writeSelfHostCompose(cwd, config, { devLocal: true })
121
+ const up = runDockerCompose(paths.composePath, ["up", "-d", "db"], cwd, project, {
122
+ quiet: true,
123
+ ...(brand !== undefined && { brand }),
124
+ })
125
+ if (up !== 0) {
126
+ exitComposeFailed(up, "Could not start Postgres (compose db service).", brand)
127
+ }
128
+ await waitComposeHealthy(paths, cwd, 120_000, project)
83
129
  }
84
130
 
85
- function upsertEnvFile(cwd: string, updates: Record<string, string>): void {
86
- const envPath = join(cwd, ".env")
87
- const existing = existsSync(envPath) ? readFileSync(envPath, "utf8") : ""
88
- const keys = new Set(Object.keys(updates))
89
- const kept = existing
90
- .split("\n")
91
- .filter((line) => {
92
- const key = line.split("=")[0]?.trim()
93
- return key && line.includes("=") && !keys.has(key)
94
- })
95
- const merged = [...kept, ...Object.entries(updates).map(([key, value]) => `${key}=${value}`)]
96
- writeFileSync(envPath, `${merged.join("\n").trimEnd()}\n`, "utf8")
131
+ /**
132
+ * True when CLI should publish local Compose Postgres for the host-side engine
133
+ * (local dev with overrides.engine). False for remote DB URLs via config or --connection.
134
+ */
135
+ export function usesLocalDockerEngineDb(
136
+ config: SupatypeProjectConfig,
137
+ explicitConnection?: string,
138
+ ): boolean {
139
+ if (explicitConnection?.trim()) return false
140
+ if (config.connection?.trim()) return false
141
+ return resolveRuntimeProvider(config) === "docker" && hasEngineOverride(config)
97
142
  }
98
143
 
99
- /** Keep compose + Studio + Astro on the same freshly signed dev JWTs every `dev` / `push`. */
100
- function ensureDevComposeEnv(
144
+ /**
145
+ * Resolve a Postgres URL reachable from the host-side engine binary.
146
+ * Local docker + overrides.engine → SUPATYPE_DEV_DB_PORT on localhost.
147
+ * Remote self-host → set `connection` in config or pass `--connection`.
148
+ */
149
+ export async function resolveHostEngineDatabaseUrl(
150
+ cwd: string,
151
+ config: SupatypeProjectConfig,
152
+ explicit?: string,
153
+ ): Promise<string> {
154
+ if (explicit?.trim()) return explicit
155
+ if (config.connection?.trim()) return config.connection
156
+ if (usesLocalDockerEngineDb(config)) {
157
+ await ensureDockerDbPublishedForHostEngine(cwd, config)
158
+ return hostComposeDbUrl(cwd)
159
+ }
160
+ return connectionString(config)
161
+ }
162
+
163
+ async function resolveKongPort(cwd: string): Promise<number> {
164
+ return ensureKongPort(cwd, { context: "dev" })
165
+ }
166
+
167
+ function upsertDevComposeEnv(
101
168
  cwd: string,
169
+ config: SupatypeProjectConfig,
102
170
  anonKey: string,
103
171
  serviceRoleKey: string,
104
172
  kongPort: number,
105
173
  devDbPort?: number,
106
174
  ): void {
107
175
  const apiUrl = `http://localhost:${kongPort}`
176
+ const imagePins = composeDockerImageEnv(config)
108
177
  const updates: Record<string, string> = {
109
178
  POSTGRES_USER: "supatype_admin",
110
179
  POSTGRES_PASSWORD: "postgres",
@@ -113,16 +182,33 @@ function ensureDevComposeEnv(
113
182
  ANON_KEY: anonKey,
114
183
  SERVICE_ROLE_KEY: serviceRoleKey,
115
184
  PUBLIC_SUPATYPE_ANON_KEY: anonKey,
185
+ VITE_SUPATYPE_ANON_KEY: anonKey,
116
186
  PUBLIC_SUPATYPE_URL: apiUrl,
117
187
  SUPATYPE_KONG_PORT: String(kongPort),
118
188
  API_EXTERNAL_URL: apiUrl,
119
189
  SITE_URL: apiUrl,
120
190
  GOTRUE_MAILER_AUTOCONFIRM: "true",
191
+ ...imagePins,
121
192
  }
122
193
  if (devDbPort !== undefined) {
123
194
  updates.SUPATYPE_DEV_DB_PORT = String(devDbPort)
195
+ updates.DATABASE_URL =
196
+ `postgresql://supatype_admin:postgres@localhost:${devDbPort}/supatype?sslmode=disable`
124
197
  }
125
- upsertEnvFile(cwd, updates)
198
+ const removeImageKeys = COMPOSE_PINNED_IMAGE_ENV_KEYS.filter((key) => !(key in imagePins))
199
+ upsertEnvFile(cwd, updates, removeImageKeys)
200
+ }
201
+
202
+ /** Keep compose + Studio on the same freshly signed dev JWTs; sync optional image pins from config. */
203
+ function ensureDevComposeEnv(
204
+ cwd: string,
205
+ config: SupatypeProjectConfig,
206
+ anonKey: string,
207
+ serviceRoleKey: string,
208
+ kongPort: number,
209
+ devDbPort?: number,
210
+ ): void {
211
+ upsertDevComposeEnv(cwd, config, anonKey, serviceRoleKey, kongPort, devDbPort)
126
212
  }
127
213
 
128
214
  async function waitComposeHealthy(paths: SelfHostComposePaths, cwd: string, maxMs: number, composeProject: string): Promise<void> {
@@ -158,8 +244,18 @@ async function waitKongReady(kongPort: number, maxSec: number): Promise<void> {
158
244
  throw new Error(`Kong gateway at ${base} did not become ready within ${maxSec}s`)
159
245
  }
160
246
 
247
+ async function provisionDockerStorageBuckets(
248
+ ast: ExtractedSchemaAstV2,
249
+ kongPort: number,
250
+ serviceRoleKey: string,
251
+ ): Promise<void> {
252
+ await provisionBucketsFromAst(ast, `http://localhost:${kongPort}/storage/v1`, serviceRoleKey)
253
+ }
254
+
161
255
  let _lastPushedAst: string | null = null
162
256
  let _lastFailedAst: string | null = null
257
+ let _composePushInFlight = false
258
+ let _composePushQueued = false
163
259
 
164
260
  /**
165
261
  * Regenerate admin-config + TypeScript types from the AST using the **host** engine.
@@ -207,6 +303,7 @@ async function refreshSchemaArtifacts(
207
303
 
208
304
  try {
209
305
  const admin = withAdminRoles(await engineRequest<unknown>("/admin", { ast }), config)
306
+ restoreSystemRelationTargets(admin, ast)
210
307
  writeFileSync(adminConfigPath, `${JSON.stringify(admin, null, 2)}\n`)
211
308
  console.log("[supatype] Admin config written to .supatype/admin-config.json")
212
309
  } catch (err) {
@@ -245,12 +342,19 @@ async function runComposeSchemaPush(
245
342
  console.log("[supatype] Applying schema via local engine (overrides.engine)...")
246
343
  await ensureEngine()
247
344
  const pgSchema = config.schema?.pg_schema ?? "public"
345
+ const sources = writeSchemaSourcePushArtifacts(cwd)
248
346
  try {
249
347
  await engineRequest("/push", {
250
348
  ast,
251
349
  database_url: hostComposeDbUrl(cwd),
252
350
  schema: pgSchema,
253
351
  force: true,
352
+ ...(sources
353
+ ? {
354
+ schema_sources_gz_base64: sources.payload.dataBase64,
355
+ schema_sources_manifest: sources.payload.manifest,
356
+ }
357
+ : {}),
254
358
  })
255
359
  } catch (err) {
256
360
  _lastFailedAst = astJson
@@ -258,34 +362,66 @@ async function runComposeSchemaPush(
258
362
  }
259
363
  _lastPushedAst = astJson
260
364
  _lastFailedAst = null
365
+ if (astHasSystemAuthRelation(ast)) {
366
+ grantAuthSchemaAccess(paths, cwd, composeProject)
367
+ }
261
368
  console.log("[supatype] Schema applied.")
262
369
  return
263
370
  }
264
371
 
265
372
  console.log("[supatype] Applying schema via compose schema-engine...")
266
- let push = runComposeEnginePush(paths, cwd, composeProject)
373
+ const sources = writeSchemaSourcePushArtifacts(cwd)
374
+ let push = await runComposeEnginePush(paths, cwd, composeProject, config, sources)
267
375
  // Windows Docker bind mounts can lag briefly after the host write.
268
376
  if (push.status !== 0) {
269
377
  await new Promise((r) => setTimeout(r, 250))
270
- push = runComposeEnginePush(paths, cwd, composeProject)
378
+ push = await runComposeEnginePush(paths, cwd, composeProject, config, sources)
271
379
  }
272
380
  if (push.status !== 0) {
273
381
  _lastFailedAst = astJson
274
- throw new Error(push.output || `Engine schema push failed (exit ${push.status})`)
382
+ const detail = filterComposeNoise(push.output) || push.output
383
+ throw new Error(detail || `Engine schema push failed (exit ${push.status})`)
275
384
  }
276
385
  _lastPushedAst = astJson
277
386
  _lastFailedAst = null
278
387
 
279
- console.log("[supatype] Schema applied.")
388
+ if (astHasSystemAuthRelation(ast)) {
389
+ grantAuthSchemaAccess(paths, cwd, composeProject)
390
+ }
391
+ }
392
+
393
+ /** Serialize watch-triggered pushes so docker output cannot interleave. */
394
+ async function runComposeSchemaPushQueued(
395
+ cwd: string,
396
+ config: SupatypeProjectConfig,
397
+ paths: SelfHostComposePaths,
398
+ schemaPath: string,
399
+ composeProject: string,
400
+ ): Promise<void> {
401
+ if (_composePushInFlight) {
402
+ _composePushQueued = true
403
+ return
404
+ }
405
+ _composePushInFlight = true
406
+ try {
407
+ do {
408
+ _composePushQueued = false
409
+ await runComposeSchemaPush(cwd, config, paths, schemaPath, composeProject)
410
+ } while (_composePushQueued)
411
+ } finally {
412
+ _composePushInFlight = false
413
+ }
280
414
  }
281
415
 
282
- function runComposeEnginePush(
416
+ async function runComposeEnginePush(
283
417
  paths: SelfHostComposePaths,
284
418
  cwd: string,
285
419
  composeProject: string,
286
- ): { status: number; output: string } {
420
+ config: SupatypeProjectConfig,
421
+ sources?: SchemaSourcePushArtifacts | null,
422
+ ): Promise<{ status: number; output: string }> {
287
423
  const envFile = resolve(cwd, ".env")
288
- const composeArgs = ["compose"]
424
+ const composeArgs = ["compose", "--progress", "quiet"]
289
425
  if (composeProject) composeArgs.push("-p", composeProject)
290
426
  composeArgs.push("--project-directory", cwd)
291
427
  composeArgs.push("-f", paths.composePath)
@@ -306,16 +442,159 @@ function runComposeEnginePush(
306
442
  "--force",
307
443
  "--non-interactive",
308
444
  )
445
+ if (sources) {
446
+ composeArgs.push(
447
+ "--schema-sources-gz",
448
+ sources.dockerGzPath,
449
+ "--schema-sources-manifest",
450
+ sources.dockerManifestPath,
451
+ )
452
+ }
453
+ const pushEnv: NodeJS.ProcessEnv = {
454
+ ...process.env,
455
+ COMPOSE_PROGRESS: "quiet",
456
+ }
457
+ const engineImage = await schemaEngineImageForPush(config)
458
+ if (engineImage) {
459
+ pushEnv.SUPATYPE_ENGINE_IMAGE = engineImage
460
+ }
309
461
  const result = spawnSync("docker", composeArgs, {
310
462
  cwd,
311
463
  encoding: "utf8",
312
464
  maxBuffer: 10 * 1024 * 1024,
465
+ env: pushEnv,
313
466
  })
314
467
  const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim()
315
- if (output.length > 0) {
316
- console.error(output)
468
+ const exitStatus = result.status ?? 1
469
+ const pushResult = parseEnginePushOutput(output)
470
+
471
+ if (exitStatus === 0) {
472
+ if (pushResult) {
473
+ console.log(`[supatype] ${formatEnginePushMessage(pushResult)}`)
474
+ } else {
475
+ console.log("[supatype] Schema applied.")
476
+ }
317
477
  }
318
- return { status: result.status ?? 1, output }
478
+
479
+ return { status: exitStatus, output }
480
+ }
481
+
482
+ async function runComposeEngineDiff(
483
+ paths: SelfHostComposePaths,
484
+ cwd: string,
485
+ composeProject: string,
486
+ config: SupatypeProjectConfig,
487
+ pgSchema: string,
488
+ ): Promise<{ status: number; output: string; diff: DiffResult | null }> {
489
+ const envFile = resolve(cwd, ".env")
490
+ const composeArgs = ["compose", "--progress", "quiet"]
491
+ if (composeProject) composeArgs.push("-p", composeProject)
492
+ composeArgs.push("--project-directory", cwd)
493
+ composeArgs.push("-f", paths.composePath)
494
+ if (existsSync(envFile)) {
495
+ composeArgs.push("--env-file", envFile)
496
+ }
497
+ composeArgs.push(
498
+ "--profile",
499
+ "tools",
500
+ "run",
501
+ "--rm",
502
+ "schema-engine",
503
+ "diff",
504
+ "-i",
505
+ "/project/.supatype/schema.ast.json",
506
+ "--database-url",
507
+ composeDbUrl(),
508
+ "--schema",
509
+ pgSchema,
510
+ )
511
+ const diffEnv: NodeJS.ProcessEnv = {
512
+ ...process.env,
513
+ COMPOSE_PROGRESS: "quiet",
514
+ }
515
+ const engineImage = await schemaEngineImageForPush(config)
516
+ if (engineImage) {
517
+ diffEnv.SUPATYPE_ENGINE_IMAGE = engineImage
518
+ }
519
+ const result = spawnSync("docker", composeArgs, {
520
+ cwd,
521
+ encoding: "utf8",
522
+ maxBuffer: 10 * 1024 * 1024,
523
+ env: diffEnv,
524
+ })
525
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim()
526
+ const exitStatus = result.status ?? 1
527
+ const diff = parseEngineJsonOutput<DiffResult>(output)
528
+
529
+ return { status: exitStatus, output, diff }
530
+ }
531
+
532
+ /**
533
+ * `supatype diff` when `provider: docker`. Uses in-compose schema-engine unless
534
+ * `overrides.engine` is set — then Postgres is published to the host and diff runs
535
+ * through the local engine binary.
536
+ */
537
+ export async function diffSchemaDocker(cwd: string, config: SupatypeProjectConfig): Promise<DiffResult> {
538
+ if (resolveRuntimeProvider(config) !== "docker") {
539
+ throw new Error("diffSchemaDocker requires provider: docker")
540
+ }
541
+ const project = composeProjectName(config.project.name)
542
+ const pgSchema = config.schema?.pg_schema ?? "public"
543
+
544
+ if (hasEngineOverride(config)) {
545
+ const brand = { intro: "Schema diff" }
546
+ await ensureDockerDbPublishedForHostEngine(cwd, config, brand)
547
+ const schemaPath = schemaPathFromProject(config, cwd)
548
+ const ast = loadSchemaAst(schemaPath, cwd)
549
+ await ensureEngine()
550
+ return engineRequest<DiffResult>("/diff", {
551
+ ast,
552
+ database_url: hostComposeDbUrl(cwd),
553
+ schema: pgSchema,
554
+ })
555
+ }
556
+
557
+ const kongPort = await resolveKongPort(cwd)
558
+ const now = Math.floor(Date.now() / 1000)
559
+ const jwtBase = { iss: "supatype", iat: now, exp: now + 315_360_000 }
560
+ const anonKey = signJwt({ ...jwtBase, role: "anon" }, LOCAL_JWT_SECRET)
561
+ const serviceRoleKey = signJwt({ ...jwtBase, role: "service_role" }, LOCAL_JWT_SECRET)
562
+ ensureDevComposeEnv(cwd, config, anonKey, serviceRoleKey, kongPort, undefined)
563
+
564
+ const paths = writeSelfHostCompose(cwd, config, { devLocal: true })
565
+ const diffBrand = { intro: "Schema diff" }
566
+
567
+ const up = runDockerCompose(paths.composePath, ["up", "-d", "db"], cwd, project, {
568
+ quiet: true,
569
+ brand: diffBrand,
570
+ })
571
+ if (up !== 0) {
572
+ exitComposeFailed(up, "Could not start Postgres (compose db service).", diffBrand)
573
+ }
574
+ await waitComposeHealthy(paths, cwd, 120_000, project)
575
+
576
+ const schemaPath = schemaPathFromProject(config, cwd)
577
+ const ast = loadSchemaAst(schemaPath, cwd)
578
+
579
+ const supatypeDir = join(cwd, ".supatype")
580
+ mkdirSync(supatypeDir, { recursive: true })
581
+ const astPath = join(supatypeDir, "schema.ast.json")
582
+ writeFileSync(astPath, JSON.stringify(ast))
583
+
584
+ let result = await runComposeEngineDiff(paths, cwd, project, config, pgSchema)
585
+ // Windows Docker bind mounts can lag briefly after the host write.
586
+ if (result.status !== 0) {
587
+ await new Promise((r) => setTimeout(r, 250))
588
+ result = await runComposeEngineDiff(paths, cwd, project, config, pgSchema)
589
+ }
590
+ if (result.status !== 0) {
591
+ const detail = filterComposeNoise(result.output) || result.output
592
+ throw new Error(detail || `Engine schema diff failed (exit ${result.status})`)
593
+ }
594
+ if (!result.diff) {
595
+ throw new Error("Engine diff returned no result")
596
+ }
597
+ return result.diff
319
598
  }
320
599
 
321
600
  /**
@@ -335,17 +614,39 @@ export async function pushSchemaDocker(cwd: string, config: SupatypeProjectConfi
335
614
  const jwtBase = { iss: "supatype", iat: now, exp: now + 315_360_000 }
336
615
  const anonKey = signJwt({ ...jwtBase, role: "anon" }, LOCAL_JWT_SECRET)
337
616
  const serviceRoleKey = signJwt({ ...jwtBase, role: "service_role" }, LOCAL_JWT_SECRET)
338
- ensureDevComposeEnv(cwd, anonKey, serviceRoleKey, kongPort, devDbPort)
617
+ ensureDevComposeEnv(cwd, config, anonKey, serviceRoleKey, kongPort, devDbPort)
339
618
 
340
619
  const paths = writeSelfHostCompose(cwd, config, { devLocal: true })
620
+ const pushBrand = { intro: "Push schema" }
341
621
 
342
622
  console.log(`[supatype] provider: docker — applying schema via compose (project ${project})...`)
343
- const up = runDockerCompose(paths.composePath, ["up", "-d", "db"], cwd, project)
344
- if (up !== 0) process.exit(up)
623
+ const up = runDockerCompose(paths.composePath, ["up", "-d", "db"], cwd, project, {
624
+ quiet: true,
625
+ brand: pushBrand,
626
+ })
627
+ if (up !== 0) {
628
+ exitComposeFailed(up, "Could not start Postgres (compose db service).", pushBrand)
629
+ }
345
630
  await waitComposeHealthy(paths, cwd, 120_000, project)
346
631
 
347
632
  const schemaPath = schemaPathFromProject(config, cwd)
633
+ const ast = loadSchemaAst(schemaPath, cwd)
348
634
  await runComposeSchemaPush(cwd, config, paths, schemaPath, project)
635
+
636
+ const upGateway = runDockerCompose(paths.composePath, ["up", "-d"], cwd, project, {
637
+ quiet: true,
638
+ brand: pushBrand,
639
+ })
640
+ if (upGateway !== 0) {
641
+ exitComposeFailed(upGateway, "Could not start the Compose gateway stack.", pushBrand)
642
+ }
643
+ await waitKongReady(kongPort, 120)
644
+ await provisionDockerStorageBuckets(ast, kongPort, serviceRoleKey)
645
+
646
+ await ensureFirstAdminUserForProject(cwd, config, {
647
+ compose: { project, composePath: paths.composePath },
648
+ })
649
+
349
650
  console.log("[supatype] Schema pushed.")
350
651
  }
351
652
 
@@ -365,13 +666,22 @@ export async function runDevCompose(cwd: string, config: SupatypeProjectConfig,
365
666
  const anonKey = signJwt({ ...jwtBase, role: "anon" }, LOCAL_JWT_SECRET)
366
667
  const serviceRoleKey = signJwt({ ...jwtBase, role: "service_role" }, LOCAL_JWT_SECRET)
367
668
 
368
- ensureDevComposeEnv(cwd, anonKey, serviceRoleKey, kongPort, devDbPort)
669
+ ensureDevComposeEnv(cwd, config, anonKey, serviceRoleKey, kongPort, devDbPort)
369
670
 
370
671
  console.log(`[supatype] provider: docker — starting self-host Compose stack (project ${project}, gateway :${kongPort})...`)
371
672
  const paths = writeSelfHostCompose(cwd, config, { devLocal: true })
673
+ await recoverStaleDevSession(cwd)
674
+ await handleComposeProjectRename(cwd, config.project.name, paths)
675
+ const devBrand = { intro: "Local development" }
372
676
 
373
- const upStatus = runDockerCompose(paths.composePath, ["up", "-d"], cwd, project)
374
- if (upStatus !== 0) process.exit(upStatus)
677
+ const upStatus = runDockerCompose(paths.composePath, ["up", "-d"], cwd, project, {
678
+ quiet: true,
679
+ brand: devBrand,
680
+ })
681
+ if (upStatus !== 0) {
682
+ endDevSession()
683
+ exitComposeFailed(upStatus, "Could not start the local Compose stack.", devBrand)
684
+ }
375
685
 
376
686
  console.log("[supatype] Waiting for Postgres (compose)...")
377
687
  await waitComposeHealthy(paths, cwd, 180_000, project)
@@ -381,9 +691,33 @@ export async function runDevCompose(cwd: string, config: SupatypeProjectConfig,
381
691
  console.error("[supatype] Initial schema push failed:", (e as Error).message),
382
692
  )
383
693
 
694
+ await ensureFirstAdminUserForProject(cwd, config, {
695
+ compose: { project, composePath: paths.composePath },
696
+ })
697
+
384
698
  console.log("[supatype] Waiting for API gateway...")
385
699
  await waitKongReady(kongPort, 120)
386
700
 
701
+ writeLocalEnvironment(cwd, {
702
+ target: "local",
703
+ apiUrl: `http://localhost:${kongPort}`,
704
+ databaseUrl: hasEngineOverride(config) ? hostComposeDbUrl(cwd) : composeDbUrl(),
705
+ projectRef: config.project.name,
706
+ kongPort,
707
+ provider: "docker",
708
+ })
709
+
710
+ writeDevSessionLock(cwd, {
711
+ composeProject: project,
712
+ projectRef: config.project.name,
713
+ composePath: paths.composePath,
714
+ kongPort,
715
+ startedAt: new Date().toISOString(),
716
+ })
717
+
718
+ const ast = loadSchemaAst(schemaPath, cwd)
719
+ await provisionDockerStorageBuckets(ast, kongPort, serviceRoleKey)
720
+
387
721
  const pidDir = join(homedir(), ".supatype", "projects", config.project.name, "pid")
388
722
  mkdirSync(pidDir, { recursive: true })
389
723
 
@@ -423,33 +757,85 @@ export async function runDevCompose(cwd: string, config: SupatypeProjectConfig,
423
757
 
424
758
  const appProc = startProxyDevApp(cwd, config, pidDir)
425
759
 
426
- const cleanup = async () => {
427
- console.log("\n[supatype] Shutting down compose...")
760
+ let schemaWatcher: import("node:fs").FSWatcher | null = null
761
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null
762
+
763
+ registerDevShutdown(async () => {
764
+ schemaWatcher?.close()
765
+ schemaWatcher = null
766
+ if (debounceTimer) {
767
+ clearTimeout(debounceTimer)
768
+ debounceTimer = null
769
+ }
770
+ console.log("[supatype] Shutting down compose...")
428
771
  await studioProc?.stop()
429
772
  await appProc?.stop()
430
- runDockerCompose(paths.composePath, ["down"], cwd, project)
431
- process.exit(0)
432
- }
433
- process.once("SIGINT", () => void cleanup())
434
- process.once("SIGTERM", () => void cleanup())
773
+ const downStatus = runDockerCompose(paths.composePath, ["down"], cwd, project, { quiet: true })
774
+ if (downStatus === 0) {
775
+ console.log("[supatype] Compose stack stopped.")
776
+ } else {
777
+ console.warn(`[supatype] Compose down exited with status ${downStatus}.`)
778
+ }
779
+ }, {
780
+ cwd,
781
+ compose: { cwd, composePath: paths.composePath, composeProject: project },
782
+ })
435
783
 
436
784
  if (opts.watch) {
437
785
  const schemaDir = join(projectRootFromConfig(config, cwd), config.schema?.path ?? "schema/index.ts", "..")
438
786
  console.log(`[supatype] Watching ${schemaDir} for changes...`)
439
787
  const { watch } = await import("node:fs")
440
- let debounceTimer: ReturnType<typeof setTimeout> | null = null
441
- watch(schemaDir, { recursive: true }, (_eventType, filename) => {
788
+ schemaWatcher = watch(schemaDir, { recursive: true }, (_eventType, filename) => {
442
789
  if (!filename?.endsWith(".ts")) return
443
790
  if (debounceTimer) clearTimeout(debounceTimer)
444
791
  debounceTimer = setTimeout(() => {
445
792
  debounceTimer = null
446
793
  console.log(`\n[supatype] Change detected in ${filename}, pushing schema...`)
447
- runComposeSchemaPush(cwd, config, paths, schemaPath, project).catch((e: unknown) =>
448
- console.error("[supatype] Schema push failed:", (e as Error).message),
449
- )
794
+ runComposeSchemaPushQueued(cwd, config, paths, schemaPath, project)
795
+ .then(async () => {
796
+ const updatedAst = loadSchemaAst(schemaPath, cwd)
797
+ await provisionDockerStorageBuckets(updatedAst, kongPort, serviceRoleKey)
798
+ })
799
+ .catch((e: unknown) =>
800
+ console.error("[supatype] Schema push failed:", (e as Error).message),
801
+ )
450
802
  }, 300)
451
803
  })
452
804
  }
453
805
 
454
806
  await new Promise<never>(() => undefined)
455
807
  }
808
+
809
+ function astHasSystemAuthRelation(ast: unknown): boolean {
810
+ const obj = ast as { models?: Array<{ fields?: Record<string, { kind?: string; target?: string }> }> }
811
+ if (!obj?.models) return false
812
+ for (const model of obj.models) {
813
+ if (!model.fields) continue
814
+ for (const field of Object.values(model.fields)) {
815
+ if (field.kind === "relation" && field.target === "supatype:user") return true
816
+ }
817
+ }
818
+ return false
819
+ }
820
+
821
+ function grantAuthSchemaAccess(
822
+ paths: SelfHostComposePaths,
823
+ cwd: string,
824
+ composeProject: string,
825
+ ): void {
826
+ const composeDir = dirname(paths.composePath)
827
+ const baseArgs = [
828
+ "compose", "-p", composeProject,
829
+ "-f", paths.composePath,
830
+ ]
831
+ const sql = "GRANT USAGE ON SCHEMA auth TO service_role; GRANT SELECT ON auth.users TO service_role;"
832
+ const result = spawnSync(
833
+ "docker",
834
+ [...baseArgs, "exec", "-T", "-e", "PGPASSWORD=postgres", "db",
835
+ "psql", "-U", "supatype_admin", "-d", "supatype", "-c", sql],
836
+ { cwd: composeDir, encoding: "utf8", timeout: 10_000 },
837
+ )
838
+ if (result.status !== 0) {
839
+ console.warn("[supatype] Could not grant service_role access to auth.users — Studio relation preview may fail.")
840
+ }
841
+ }