@supatype/cli 0.1.6 → 0.1.9

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 (477) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +152 -102
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/LICENSE +202 -0
  5. package/deno.d.ts +52 -0
  6. package/dist/app/framework.js +1 -1
  7. package/dist/app/framework.js.map +1 -1
  8. package/dist/augmentation-generator.d.ts +22 -0
  9. package/dist/augmentation-generator.d.ts.map +1 -1
  10. package/dist/augmentation-generator.js +19 -5
  11. package/dist/augmentation-generator.js.map +1 -1
  12. package/dist/binary-cache.d.ts +42 -6
  13. package/dist/binary-cache.d.ts.map +1 -1
  14. package/dist/binary-cache.js +76 -44
  15. package/dist/binary-cache.js.map +1 -1
  16. package/dist/cache-clean.d.ts +15 -0
  17. package/dist/cache-clean.d.ts.map +1 -0
  18. package/dist/cache-clean.js +76 -0
  19. package/dist/cache-clean.js.map +1 -0
  20. package/dist/cache-pins.d.ts +51 -0
  21. package/dist/cache-pins.d.ts.map +1 -0
  22. package/dist/cache-pins.js +184 -0
  23. package/dist/cache-pins.js.map +1 -0
  24. package/dist/cli-artifacts.d.ts +32 -0
  25. package/dist/cli-artifacts.d.ts.map +1 -0
  26. package/dist/cli-artifacts.js +49 -0
  27. package/dist/cli-artifacts.js.map +1 -0
  28. package/dist/cli-install-method.d.ts +27 -0
  29. package/dist/cli-install-method.d.ts.map +1 -0
  30. package/dist/cli-install-method.js +78 -0
  31. package/dist/cli-install-method.js.map +1 -0
  32. package/dist/cli-package-version.d.ts +1 -1
  33. package/dist/cli-package-version.d.ts.map +1 -1
  34. package/dist/cli-package-version.js +6 -1
  35. package/dist/cli-package-version.js.map +1 -1
  36. package/dist/cli-standalone.d.ts +13 -4
  37. package/dist/cli-standalone.d.ts.map +1 -1
  38. package/dist/cli-standalone.js +86 -31
  39. package/dist/cli-standalone.js.map +1 -1
  40. package/dist/cli-version-embedded.d.ts +10 -0
  41. package/dist/cli-version-embedded.d.ts.map +1 -0
  42. package/dist/cli-version-embedded.js +13 -0
  43. package/dist/cli-version-embedded.js.map +1 -0
  44. package/dist/cli.d.ts.map +1 -1
  45. package/dist/cli.js +7 -1
  46. package/dist/cli.js.map +1 -1
  47. package/dist/cloud-credentials.d.ts +34 -0
  48. package/dist/cloud-credentials.d.ts.map +1 -0
  49. package/dist/cloud-credentials.js +139 -0
  50. package/dist/cloud-credentials.js.map +1 -0
  51. package/dist/commands/add.js +1 -1
  52. package/dist/commands/add.js.map +1 -1
  53. package/dist/commands/admin.d.ts +11 -0
  54. package/dist/commands/admin.d.ts.map +1 -1
  55. package/dist/commands/admin.js +123 -21
  56. package/dist/commands/admin.js.map +1 -1
  57. package/dist/commands/adopt.js +1 -1
  58. package/dist/commands/adopt.js.map +1 -1
  59. package/dist/commands/app.js +2 -2
  60. package/dist/commands/app.js.map +1 -1
  61. package/dist/commands/cache.d.ts +1 -1
  62. package/dist/commands/cache.d.ts.map +1 -1
  63. package/dist/commands/cache.js +69 -73
  64. package/dist/commands/cache.js.map +1 -1
  65. package/dist/commands/cloud.d.ts.map +1 -1
  66. package/dist/commands/cloud.js +32 -8
  67. package/dist/commands/cloud.js.map +1 -1
  68. package/dist/commands/db.d.ts +3 -2
  69. package/dist/commands/db.d.ts.map +1 -1
  70. package/dist/commands/db.js +149 -3
  71. package/dist/commands/db.js.map +1 -1
  72. package/dist/commands/deploy.d.ts +9 -9
  73. package/dist/commands/deploy.js +12 -12
  74. package/dist/commands/deploy.js.map +1 -1
  75. package/dist/commands/dev.d.ts +3 -3
  76. package/dist/commands/dev.d.ts.map +1 -1
  77. package/dist/commands/dev.js +63 -23
  78. package/dist/commands/dev.js.map +1 -1
  79. package/dist/commands/doctor.d.ts +28 -0
  80. package/dist/commands/doctor.d.ts.map +1 -1
  81. package/dist/commands/doctor.js +60 -3
  82. package/dist/commands/doctor.js.map +1 -1
  83. package/dist/commands/engine.d.ts +1 -1
  84. package/dist/commands/engine.js +1 -1
  85. package/dist/commands/functions.d.ts +7 -0
  86. package/dist/commands/functions.d.ts.map +1 -1
  87. package/dist/commands/functions.js +45 -11
  88. package/dist/commands/functions.js.map +1 -1
  89. package/dist/commands/generate.d.ts.map +1 -1
  90. package/dist/commands/generate.js +5 -1
  91. package/dist/commands/generate.js.map +1 -1
  92. package/dist/commands/hooks.d.ts +12 -0
  93. package/dist/commands/hooks.d.ts.map +1 -0
  94. package/dist/commands/hooks.js +118 -0
  95. package/dist/commands/hooks.js.map +1 -0
  96. package/dist/commands/init.d.ts.map +1 -1
  97. package/dist/commands/init.js +64 -30
  98. package/dist/commands/init.js.map +1 -1
  99. package/dist/commands/internal.d.ts +6 -0
  100. package/dist/commands/internal.d.ts.map +1 -0
  101. package/dist/commands/internal.js +22 -0
  102. package/dist/commands/internal.js.map +1 -0
  103. package/dist/commands/link-helpers.d.ts.map +1 -1
  104. package/dist/commands/link-helpers.js +8 -2
  105. package/dist/commands/link-helpers.js.map +1 -1
  106. package/dist/commands/login.d.ts +7 -0
  107. package/dist/commands/login.d.ts.map +1 -0
  108. package/dist/commands/login.js +110 -0
  109. package/dist/commands/login.js.map +1 -0
  110. package/dist/commands/logs.d.ts +1 -1
  111. package/dist/commands/migrate.js +5 -5
  112. package/dist/commands/migrate.js.map +1 -1
  113. package/dist/commands/pg.d.ts +1 -1
  114. package/dist/commands/pg.js +1 -1
  115. package/dist/commands/plugins.js +1 -1
  116. package/dist/commands/plugins.js.map +1 -1
  117. package/dist/commands/pull.js +2 -2
  118. package/dist/commands/pull.js.map +1 -1
  119. package/dist/commands/push.d.ts.map +1 -1
  120. package/dist/commands/push.js +110 -7
  121. package/dist/commands/push.js.map +1 -1
  122. package/dist/commands/self-host.d.ts +1 -1
  123. package/dist/commands/self-host.js +1 -1
  124. package/dist/commands/self-update.d.ts +3 -2
  125. package/dist/commands/self-update.d.ts.map +1 -1
  126. package/dist/commands/self-update.js +31 -16
  127. package/dist/commands/self-update.js.map +1 -1
  128. package/dist/commands/status.d.ts +1 -1
  129. package/dist/commands/update.d.ts +1 -1
  130. package/dist/commands/update.js +1 -1
  131. package/dist/config.d.ts +1 -1
  132. package/dist/config.d.ts.map +1 -1
  133. package/dist/config.js +19 -4
  134. package/dist/config.js.map +1 -1
  135. package/dist/db-preflight.d.ts +78 -0
  136. package/dist/db-preflight.d.ts.map +1 -0
  137. package/dist/db-preflight.js +452 -0
  138. package/dist/db-preflight.js.map +1 -0
  139. package/dist/dev-compose.d.ts +4 -4
  140. package/dist/dev-compose.d.ts.map +1 -1
  141. package/dist/dev-compose.js +143 -88
  142. package/dist/dev-compose.js.map +1 -1
  143. package/dist/dev-log-filter.d.ts +1 -1
  144. package/dist/dev-log-filter.d.ts.map +1 -1
  145. package/dist/dev-log-filter.js +1 -1
  146. package/dist/dev-log-filter.js.map +1 -1
  147. package/dist/dev-ports.d.ts +2 -2
  148. package/dist/dev-ports.d.ts.map +1 -1
  149. package/dist/dev-ports.js +2 -2
  150. package/dist/dev-ports.js.map +1 -1
  151. package/dist/dev-session.d.ts +5 -5
  152. package/dist/dev-session.d.ts.map +1 -1
  153. package/dist/dev-session.js +6 -6
  154. package/dist/dev-session.js.map +1 -1
  155. package/dist/dev-shutdown.d.ts +5 -5
  156. package/dist/dev-shutdown.d.ts.map +1 -1
  157. package/dist/dev-shutdown.js +7 -7
  158. package/dist/dev-shutdown.js.map +1 -1
  159. package/dist/dev-task-colors.d.ts +1 -1
  160. package/dist/dev-task-colors.d.ts.map +1 -1
  161. package/dist/dev-task-colors.js +1 -1
  162. package/dist/dev-task-colors.js.map +1 -1
  163. package/dist/docker-postgres.d.ts +5 -5
  164. package/dist/docker-postgres.d.ts.map +1 -1
  165. package/dist/docker-postgres.js +5 -5
  166. package/dist/docker-postgres.js.map +1 -1
  167. package/dist/docker-runtime.d.ts +2 -2
  168. package/dist/docker-runtime.d.ts.map +1 -1
  169. package/dist/docker-runtime.js +5 -5
  170. package/dist/docker-runtime.js.map +1 -1
  171. package/dist/engine-client.d.ts +1 -1
  172. package/dist/engine-client.js +3 -3
  173. package/dist/engine-client.js.map +1 -1
  174. package/dist/engine-push-output.js +1 -1
  175. package/dist/engine-push-output.js.map +1 -1
  176. package/dist/ensure-binary.d.ts.map +1 -1
  177. package/dist/ensure-binary.js +17 -0
  178. package/dist/ensure-binary.js.map +1 -1
  179. package/dist/ensure-component-binaries.js +1 -1
  180. package/dist/ensure-component-binaries.js.map +1 -1
  181. package/dist/env-file.d.ts +40 -2
  182. package/dist/env-file.d.ts.map +1 -1
  183. package/dist/env-file.js +132 -11
  184. package/dist/env-file.js.map +1 -1
  185. package/dist/field-masking-tier.d.ts +50 -0
  186. package/dist/field-masking-tier.d.ts.map +1 -0
  187. package/dist/field-masking-tier.js +91 -0
  188. package/dist/field-masking-tier.js.map +1 -0
  189. package/dist/functions-deno-types.d.ts +24 -0
  190. package/dist/functions-deno-types.d.ts.map +1 -0
  191. package/dist/functions-deno-types.js +155 -0
  192. package/dist/functions-deno-types.js.map +1 -0
  193. package/dist/functions-router-gen.js +1 -1
  194. package/dist/gitignore.d.ts +2 -2
  195. package/dist/gitignore.d.ts.map +1 -1
  196. package/dist/gitignore.js +3 -3
  197. package/dist/gitignore.js.map +1 -1
  198. package/dist/hook-upload.d.ts +30 -0
  199. package/dist/hook-upload.d.ts.map +1 -0
  200. package/dist/hook-upload.js +86 -0
  201. package/dist/hook-upload.js.map +1 -0
  202. package/dist/hooks-generator.d.ts +8 -0
  203. package/dist/hooks-generator.d.ts.map +1 -0
  204. package/dist/hooks-generator.js +332 -0
  205. package/dist/hooks-generator.js.map +1 -0
  206. package/dist/index.d.ts +1 -1
  207. package/dist/index.js +1 -1
  208. package/dist/init-project-detect.d.ts +1 -1
  209. package/dist/init-project-detect.d.ts.map +1 -1
  210. package/dist/init-project-detect.js +1 -1
  211. package/dist/init-project-detect.js.map +1 -1
  212. package/dist/link.d.ts +4 -0
  213. package/dist/link.d.ts.map +1 -1
  214. package/dist/link.js +2 -0
  215. package/dist/link.js.map +1 -1
  216. package/dist/local-secrets.d.ts +71 -0
  217. package/dist/local-secrets.d.ts.map +1 -0
  218. package/dist/local-secrets.js +100 -0
  219. package/dist/local-secrets.js.map +1 -0
  220. package/dist/main.d.ts +2 -0
  221. package/dist/main.d.ts.map +1 -0
  222. package/dist/main.js +20 -0
  223. package/dist/main.js.map +1 -0
  224. package/dist/model-hooks.d.ts +73 -0
  225. package/dist/model-hooks.d.ts.map +1 -0
  226. package/dist/model-hooks.js +211 -0
  227. package/dist/model-hooks.js.map +1 -0
  228. package/dist/pgbouncer-userlist.d.ts +1 -1
  229. package/dist/pgbouncer-userlist.d.ts.map +1 -1
  230. package/dist/pgbouncer-userlist.js +2 -2
  231. package/dist/pgbouncer-userlist.js.map +1 -1
  232. package/dist/postgres-ctl.d.ts +12 -1
  233. package/dist/postgres-ctl.d.ts.map +1 -1
  234. package/dist/postgres-ctl.js +28 -5
  235. package/dist/postgres-ctl.js.map +1 -1
  236. package/dist/process-manager.d.ts +2 -2
  237. package/dist/process-manager.d.ts.map +1 -1
  238. package/dist/process-manager.js +4 -4
  239. package/dist/process-manager.js.map +1 -1
  240. package/dist/project-config.d.ts +122 -2
  241. package/dist/project-config.d.ts.map +1 -1
  242. package/dist/project-config.js +150 -1
  243. package/dist/project-config.js.map +1 -1
  244. package/dist/pull-utils.d.ts +2 -2
  245. package/dist/pull-utils.d.ts.map +1 -1
  246. package/dist/pull-utils.js +4 -4
  247. package/dist/pull-utils.js.map +1 -1
  248. package/dist/realtime-launch.js +1 -1
  249. package/dist/realtime-launch.js.map +1 -1
  250. package/dist/release-pins.d.ts +1 -1
  251. package/dist/release-pins.js +2 -2
  252. package/dist/release-pins.js.map +1 -1
  253. package/dist/resolve-api-url.js +1 -1
  254. package/dist/resolve-api-url.js.map +1 -1
  255. package/dist/resolve-target.d.ts +4 -0
  256. package/dist/resolve-target.d.ts.map +1 -1
  257. package/dist/resolve-target.js +69 -73
  258. package/dist/resolve-target.js.map +1 -1
  259. package/dist/rest-cache-admin.js +2 -2
  260. package/dist/rest-cache-admin.js.map +1 -1
  261. package/dist/runtime-routes.d.ts +1 -1
  262. package/dist/runtime-routes.js +1 -1
  263. package/dist/schema-ast-v2.d.ts +5 -4
  264. package/dist/schema-ast-v2.d.ts.map +1 -1
  265. package/dist/schema-ast-v2.js +5 -3
  266. package/dist/schema-ast-v2.js.map +1 -1
  267. package/dist/schema-push-lock.d.ts.map +1 -1
  268. package/dist/schema-push-lock.js +43 -16
  269. package/dist/schema-push-lock.js.map +1 -1
  270. package/dist/scripts/postinstall.d.ts +1 -1
  271. package/dist/scripts/postinstall.js +1 -1
  272. package/dist/self-host-compose.d.ts +26 -2
  273. package/dist/self-host-compose.d.ts.map +1 -1
  274. package/dist/self-host-compose.js +305 -73
  275. package/dist/self-host-compose.js.map +1 -1
  276. package/dist/service-role-check.d.ts +21 -0
  277. package/dist/service-role-check.d.ts.map +1 -0
  278. package/dist/service-role-check.js +85 -0
  279. package/dist/service-role-check.js.map +1 -0
  280. package/dist/storage-provision.d.ts +1 -1
  281. package/dist/storage-provision.js +3 -3
  282. package/dist/storage-provision.js.map +1 -1
  283. package/dist/studio-dev-server.d.ts +1 -1
  284. package/dist/studio-dev-server.d.ts.map +1 -1
  285. package/dist/systemd.d.ts +1 -1
  286. package/dist/systemd.js +1 -1
  287. package/dist/target-client.d.ts +17 -0
  288. package/dist/target-client.d.ts.map +1 -1
  289. package/dist/target-client.js +69 -17
  290. package/dist/target-client.js.map +1 -1
  291. package/dist/tsx-runner.js +1 -1
  292. package/dist/tsx-runner.js.map +1 -1
  293. package/dist/type-extractor.d.ts.map +1 -1
  294. package/dist/type-extractor.js +738 -34
  295. package/dist/type-extractor.js.map +1 -1
  296. package/dist/type-resolver.js +1 -1
  297. package/dist/type-resolver.js.map +1 -1
  298. package/dist/ui/brand.d.ts +1 -1
  299. package/dist/ui/brand.js +1 -1
  300. package/dist/ui/components/LogoWordmark.d.ts +1 -1
  301. package/dist/ui/components/LogoWordmark.d.ts.map +1 -1
  302. package/dist/ui/components/LogoWordmark.js +1 -1
  303. package/dist/ui/components/LogoWordmark.js.map +1 -1
  304. package/dist/ui/components/SelectField.js +1 -1
  305. package/dist/ui/components/SelectField.js.map +1 -1
  306. package/dist/ui/confirm.d.ts +1 -1
  307. package/dist/ui/confirm.js +1 -1
  308. package/dist/ui/fatal.d.ts +1 -1
  309. package/dist/ui/fatal.d.ts.map +1 -1
  310. package/dist/ui/fatal.js +1 -1
  311. package/dist/ui/fatal.js.map +1 -1
  312. package/dist/ui/flows/prompt-fields.js +1 -1
  313. package/dist/ui/flows/prompt-fields.js.map +1 -1
  314. package/dist/ui/interactive.d.ts +1 -1
  315. package/dist/ui/interactive.d.ts.map +1 -1
  316. package/dist/ui/interactive.js +1 -1
  317. package/dist/ui/interactive.js.map +1 -1
  318. package/dist/ui/progress.d.ts +1 -1
  319. package/dist/ui/progress.js +1 -1
  320. package/dist/ui/runtime/command-chrome.js +2 -2
  321. package/dist/ui/runtime/command-chrome.js.map +1 -1
  322. package/dist/ui/runtime/run-clack-flow.js +1 -1
  323. package/dist/ui/runtime/run-clack-flow.js.map +1 -1
  324. package/dist/ui/runtime/stdin-after-ink.d.ts.map +1 -1
  325. package/dist/ui/runtime/stdin-after-ink.js +4 -2
  326. package/dist/ui/runtime/stdin-after-ink.js.map +1 -1
  327. package/dist/ui/theme.d.ts +1 -1
  328. package/dist/ui/theme.js +1 -1
  329. package/dist/valkey-sidecar.d.ts +1 -1
  330. package/dist/valkey-sidecar.d.ts.map +1 -1
  331. package/dist/valkey-sidecar.js +2 -2
  332. package/dist/valkey-sidecar.js.map +1 -1
  333. package/package.json +6 -2
  334. package/scripts/build-release-artifacts.sh +112 -0
  335. package/scripts/generate-homebrew-formula.mjs +123 -0
  336. package/scripts/mirror-deno-release.sh +1 -1
  337. package/src/TYPE-RESOLUTION.md +31 -31
  338. package/src/app/framework.ts +1 -1
  339. package/src/augmentation-generator.ts +19 -6
  340. package/src/binary-cache.ts +94 -47
  341. package/src/cache-clean.ts +81 -0
  342. package/src/cache-pins.ts +243 -0
  343. package/src/cli-artifacts.ts +65 -0
  344. package/src/cli-install-method.ts +87 -0
  345. package/src/cli-package-version.ts +5 -1
  346. package/src/cli-standalone.ts +116 -34
  347. package/src/cli-version-embedded.ts +12 -0
  348. package/src/cli.ts +7 -1
  349. package/src/cloud-credentials.ts +183 -0
  350. package/src/commands/add.ts +1 -1
  351. package/src/commands/admin.ts +142 -27
  352. package/src/commands/adopt.ts +1 -1
  353. package/src/commands/app.ts +2 -2
  354. package/src/commands/cache.ts +92 -77
  355. package/src/commands/cloud.ts +32 -8
  356. package/src/commands/db.ts +164 -3
  357. package/src/commands/deploy.ts +12 -12
  358. package/src/commands/dev.ts +72 -23
  359. package/src/commands/doctor.ts +69 -3
  360. package/src/commands/engine.ts +1 -1
  361. package/src/commands/functions.ts +47 -10
  362. package/src/commands/generate.ts +5 -1
  363. package/src/commands/hooks.ts +138 -0
  364. package/src/commands/init.ts +65 -31
  365. package/src/commands/internal.ts +23 -0
  366. package/src/commands/link-helpers.ts +9 -2
  367. package/src/commands/login.ts +127 -0
  368. package/src/commands/logs.ts +1 -1
  369. package/src/commands/migrate.ts +5 -5
  370. package/src/commands/pg.ts +1 -1
  371. package/src/commands/plugins.ts +1 -1
  372. package/src/commands/pull.ts +2 -2
  373. package/src/commands/push.ts +124 -7
  374. package/src/commands/self-host.ts +1 -1
  375. package/src/commands/self-update.ts +41 -19
  376. package/src/commands/status.ts +1 -1
  377. package/src/commands/update.ts +1 -1
  378. package/src/config.ts +23 -4
  379. package/src/db-preflight.ts +575 -0
  380. package/src/dev-compose.ts +158 -89
  381. package/src/dev-log-filter.ts +1 -1
  382. package/src/dev-ports.ts +3 -3
  383. package/src/dev-session.ts +6 -6
  384. package/src/dev-shutdown.ts +8 -8
  385. package/src/dev-task-colors.ts +1 -1
  386. package/src/docker-postgres.ts +6 -6
  387. package/src/docker-runtime.ts +5 -5
  388. package/src/engine-client.ts +3 -3
  389. package/src/engine-push-output.ts +1 -1
  390. package/src/ensure-binary.ts +20 -0
  391. package/src/ensure-component-binaries.ts +1 -1
  392. package/src/env-file.ts +157 -11
  393. package/src/field-masking-tier.ts +107 -0
  394. package/src/functions-deno-types.ts +188 -0
  395. package/src/functions-router-gen.ts +1 -1
  396. package/src/gitignore.ts +3 -3
  397. package/src/hook-upload.ts +107 -0
  398. package/src/hooks-generator.ts +352 -0
  399. package/src/index.ts +1 -1
  400. package/src/init-project-detect.ts +1 -1
  401. package/src/link.ts +7 -0
  402. package/src/local-secrets.ts +104 -0
  403. package/src/main.ts +20 -0
  404. package/src/model-hooks.ts +260 -0
  405. package/src/pgbouncer-userlist.ts +2 -2
  406. package/src/postgres-ctl.ts +32 -5
  407. package/src/process-manager.ts +4 -4
  408. package/src/project-config.ts +237 -3
  409. package/src/pull-utils.ts +4 -4
  410. package/src/realtime-launch.ts +1 -1
  411. package/src/release-pins.ts +2 -2
  412. package/src/resolve-api-url.ts +1 -1
  413. package/src/resolve-target.ts +113 -68
  414. package/src/rest-cache-admin.ts +2 -2
  415. package/src/runtime-routes.ts +2 -2
  416. package/src/schema-ast-v2.ts +8 -5
  417. package/src/schema-push-lock.ts +56 -17
  418. package/src/scripts/postinstall.ts +1 -1
  419. package/src/self-host-compose.ts +348 -72
  420. package/src/service-role-check.ts +111 -0
  421. package/src/storage-provision.ts +3 -3
  422. package/src/studio-dev-server.ts +1 -1
  423. package/src/systemd.ts +1 -1
  424. package/src/target-client.ts +105 -18
  425. package/src/tsx-runner.ts +1 -1
  426. package/src/type-extractor.ts +885 -30
  427. package/src/type-resolver.ts +1 -1
  428. package/src/ui/README.md +1 -1
  429. package/src/ui/brand.ts +1 -1
  430. package/src/ui/components/LogoWordmark.tsx +1 -1
  431. package/src/ui/components/SelectField.tsx +1 -1
  432. package/src/ui/confirm.ts +1 -1
  433. package/src/ui/fatal.ts +1 -1
  434. package/src/ui/flows/prompt-fields.tsx +1 -1
  435. package/src/ui/interactive.ts +1 -1
  436. package/src/ui/progress.ts +1 -1
  437. package/src/ui/runtime/command-chrome.ts +2 -2
  438. package/src/ui/runtime/run-clack-flow.tsx +1 -1
  439. package/src/ui/runtime/stdin-after-ink.ts +4 -2
  440. package/src/ui/theme.ts +1 -1
  441. package/src/valkey-sidecar.ts +2 -2
  442. package/tests/access-composition.test.ts +667 -0
  443. package/tests/admin-studio-membership.test.ts +121 -0
  444. package/tests/api-schemas.test.ts +83 -0
  445. package/tests/archive-name-alias.test.ts +42 -0
  446. package/tests/augmentation-generator.test.ts +28 -0
  447. package/tests/cache-clean.test.ts +206 -0
  448. package/tests/cli-help.test.ts +11 -2
  449. package/tests/cli-install-method.test.ts +66 -0
  450. package/tests/command-chrome-coverage.test.ts +31 -26
  451. package/tests/command-chrome-wrap.test.ts +1 -0
  452. package/tests/db-preflight.test.ts +72 -0
  453. package/tests/doctor-render.test.ts +62 -0
  454. package/tests/doctor-service-role.test.ts +55 -0
  455. package/tests/engine-push-output.test.ts +1 -1
  456. package/tests/env-file.test.ts +121 -0
  457. package/tests/external-database-compose.test.ts +184 -0
  458. package/tests/external-database-config.test.ts +154 -0
  459. package/tests/field-masking-tier.test.ts +184 -0
  460. package/tests/functions-deno-types.test.ts +197 -0
  461. package/tests/homebrew-formula.test.ts +100 -0
  462. package/tests/hook-upload.test.ts +125 -0
  463. package/tests/hooks-generator.test.ts +212 -0
  464. package/tests/init.test.ts +5 -2
  465. package/tests/keys.test.ts +8 -2
  466. package/tests/local-secrets.test.ts +92 -0
  467. package/tests/model-hooks.test.ts +322 -0
  468. package/tests/runtime-contract.test.ts +113 -1
  469. package/tests/service-role-check.test.ts +109 -0
  470. package/tests/service-role-routes.test.ts +51 -0
  471. package/tests/storage-provision.test.ts +1 -1
  472. package/tests/target-client-refresh.test.ts +94 -0
  473. package/tests/type-extractor.test.ts +187 -0
  474. package/tsconfig.tsbuildinfo +1 -1
  475. package/vitest.config.ts +7 -1
  476. package/scripts/build-release-bundle.mjs +0 -27
  477. package/tests/engine-distribution.test.ts +0 -418
@@ -3,9 +3,21 @@ import { dirname, join, relative, resolve } from "node:path"
3
3
  import { spawnSync } from "node:child_process"
4
4
  import { requireDockerDaemon, type DockerBrandOptions } from "./docker-runtime.js"
5
5
  import { fatalError } from "./ui/fatal.js"
6
- import { preferredFunctionsPathFromProject, selfHostTlsEnabled, type SupatypeProjectConfig } from "./project-config.js"
6
+ import {
7
+ apiSchemaList,
8
+ externalDatabaseUrl,
9
+ hooksPathFromProject,
10
+ preferredFunctionsPathFromProject,
11
+ realtimeEnabled,
12
+ serviceRoleRoutes,
13
+ selfHostTlsEnabled,
14
+ usesExternalDatabase,
15
+ type SupatypeProjectConfig,
16
+ } from "./project-config.js"
7
17
  import { hasEngineOverride, hasStudioOverride, pinnedVersion, fetchLatestVersion, VERSION_PIN_LOCAL } from "./binary-cache.js"
8
18
  import { buildKongDeclarative } from "./kong-config.js"
19
+ import { readEnvFile } from "./env-file.js"
20
+ import { fieldMaskingTierFromProject, type FieldMaskingTier } from "./field-masking-tier.js"
9
21
 
10
22
  /** Env keys written when `versions` pins exist in supatype.config.ts. */
11
23
  export const COMPOSE_PINNED_IMAGE_ENV_KEYS = [
@@ -118,7 +130,7 @@ export function composePullNeedsIgnoreFailures(
118
130
  /**
119
131
  * Schema-engine image for a one-off `docker compose run` when pushing schema.
120
132
  * Uses config pin when set; otherwise CDN engine semver (Docker Hub `:latest` can lag).
121
- * Does not touch `.env` server/postgres still use compose `:latest` defaults.
133
+ * Does not touch `.env`, server/postgres still use compose `:latest` defaults.
122
134
  */
123
135
  export async function schemaEngineImageForPush(
124
136
  config: SupatypeProjectConfig,
@@ -162,7 +174,7 @@ export function staticDirForCompose(config: SupatypeProjectConfig): string | und
162
174
  /**
163
175
  * Bind-mount source for `/project` in generated compose files.
164
176
  * Paths are resolved from `--project-directory` (always the project root in `runDockerCompose`),
165
- * not from the compose file directory use `.` not `../..`.
177
+ * not from the compose file directory, use `.` not `../..`.
166
178
  */
167
179
  function projectMountPath(_cwd: string): string {
168
180
  return "."
@@ -181,10 +193,51 @@ function kongMountPath(_cwd: string): string {
181
193
  return ".supatype/self-host/kong.yml"
182
194
  }
183
195
 
196
+ /**
197
+ * The DSN every service uses for the schema-owning role.
198
+ *
199
+ * One expression instead of six identical constructions of `@db:5432`. With an external database it
200
+ * becomes `${DATABASE_URL:?…}`: the URL is the operator's, so there is nothing for the generator to
201
+ * build: and interpolating the value into the compose file would write a password into a generated
202
+ * file. Compose resolves the variable from `.env` at up-time, the same place the config's
203
+ * `process.env.DATABASE_URL` read it from.
204
+ */
205
+ function ownerDatabaseUrl(config: SupatypeProjectConfig, scheme = "postgresql"): string {
206
+ if (usesExternalDatabase(config)) {
207
+ return "${DATABASE_URL:?DATABASE_URL is missing from .env, required by database.external}"
208
+ }
209
+ return (
210
+ `${scheme}://\${POSTGRES_USER:-supatype_admin}:` +
211
+ "\${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is missing from .env}" +
212
+ "@db:5432/\${POSTGRES_DB:-supatype}"
213
+ )
214
+ }
215
+
216
+ /**
217
+ * PostgREST's DSN, which connects as `authenticator` rather than the owner.
218
+ *
219
+ * For an external database the host, port, database and query string are taken from the operator's
220
+ * URL and only the password stays a variable. The alternative, a second full URL in `.env`, is one
221
+ * more thing to keep in step with the first, and two URLs that disagree about *which database* is a
222
+ * split-brain nobody notices until the API is serving one and migrations are landing in the other.
223
+ * A hostname is not the secret here; the password is, and it stays out of the generated file.
224
+ */
225
+ function postgrestDatabaseUrl(config: SupatypeProjectConfig): string {
226
+ const password = "${AUTHENTICATOR_PASSWORD:?AUTHENTICATOR_PASSWORD is missing from .env}"
227
+ const externalUrl = externalDatabaseUrl(config)
228
+ if (externalUrl === undefined) {
229
+ return `postgresql://authenticator:${password}@db:5432/\${POSTGRES_DB:-supatype}`
230
+ }
231
+
232
+ const parsed = new URL(externalUrl)
233
+ const port = parsed.port ? `:${parsed.port}` : ""
234
+ return `postgresql://authenticator:${password}@${parsed.hostname}${port}${parsed.pathname}${parsed.search}`
235
+ }
236
+
184
237
  /** Host Vite dev server as seen from Kong inside Docker Compose. */
185
238
  export const COMPOSE_STUDIO_HOST_URL = "http://host.docker.internal:3002"
186
239
 
187
- /** Studio container always Docker Hub unless SUPATYPE_STUDIO_IMAGE is set in .env. */
240
+ /** Studio container: always Docker Hub unless SUPATYPE_STUDIO_IMAGE is set in .env. */
188
241
  function studioServiceBlock(): string {
189
242
  return ` image: \${SUPATYPE_STUDIO_IMAGE:-supatype/studio:latest}`
190
243
  }
@@ -225,6 +278,14 @@ function serverAppEnvForCompose(config: SupatypeProjectConfig, devLocal: boolean
225
278
  export interface SelfHostComposeOptions {
226
279
  /** `supatype dev` with provider docker: internal-only db/server; Kong on host :18473. */
227
280
  devLocal?: boolean
281
+ /**
282
+ * Which mechanism enforces per-column rules, when the caller knows.
283
+ *
284
+ * Only the call sites that have loaded the schema can say, see `field-masking-tier.ts`. Left
285
+ * unset, the exposed schema list is today's, which is correct for every project without field
286
+ * rules and for every project on `supatype/postgres`.
287
+ */
288
+ fieldMaskingTier?: FieldMaskingTier
228
289
  }
229
290
 
230
291
  export function renderSelfHostCompose(
@@ -234,6 +295,14 @@ export function renderSelfHostCompose(
234
295
  ): string {
235
296
  const projectMount = projectMountPath(cwd)
236
297
  const kongMount = kongMountPath(cwd)
298
+ const external = usesExternalDatabase(config)
299
+ const ownerUrl = ownerDatabaseUrl(config)
300
+ // GoTrue's driver wants the `postgres://` spelling; an external URL is used as given.
301
+ const gotrueUrl = external ? ownerUrl : ownerDatabaseUrl(config, "postgres")
302
+ // An external URL may already carry query parameters (`?sslmode=require` is common on managed
303
+ // providers), and appending a second `?` produces a DSN that fails to parse.
304
+ const gotrueSearchPathSeparator = externalDatabaseUrl(config)?.includes("?") ? "&" : "?"
305
+ const postgrestUrl = postgrestDatabaseUrl(config)
237
306
  const devLocal = options?.devLocal === true
238
307
  const tlsEnabled = selfHostTlsEnabled(config, devLocal)
239
308
  const domain = config.server.domain?.trim() ?? ""
@@ -308,61 +377,126 @@ ${studioService}
308
377
  # server: { mode: "standalone", domain: "your.domain", tls: { email: "you@example.com" } }
309
378
  # then re-run \`supatype self-host compose up -d\`. Kong publishes :80/:443 and provisions certs automatically.
310
379
  `
380
+ // An external database is not ours to declare a volume for.
311
381
  const volumesBlock = `volumes:
312
- db-data:
313
- minio-data:
382
+ ${external ? "" : " db-data:\n"} minio-data:
314
383
  valkey-data:
315
384
  `
316
385
 
317
- return `# Generated by supatype self-host compose
318
- # Kong supatype-server (unified gateway) internal PostgREST / storage / etc.
319
- services:
320
- db:
386
+ // `depends_on` for the services that talk to Postgres. With an external database there is no
387
+ // container to wait on, so the clause disappears entirely and each service retries on connect
388
+ // instead: which is also what covers a database that restarts *after* boot, something no
389
+ // healthcheck ever did.
390
+ const dbDependencyClause = external
391
+ ? ""
392
+ : ` db:
393
+ condition: service_healthy
394
+ `
395
+ const dbDependency = external ? "" : ` depends_on:\n${dbDependencyClause}`
396
+
397
+ // Realtime, omitted entirely when the project has turned it off.
398
+ //
399
+ // Not started-and-disabled: the service degrades gracefully on its own (it reports the reason on
400
+ // /health/ready rather than crash-looping), so this switch is for the operator who has read
401
+ // `supatype db check`, knows their database cannot do logical decoding, and would rather not run a
402
+ // container that can only report that. The server then has no realtime URL, which is what makes
403
+ // subscription requests fail with a clear route error instead of hanging against a dead upstream.
404
+ const realtime = realtimeEnabled(config)
405
+ const realtimeBlock = realtime
406
+ ? ` realtime:
407
+ image: \${SUPATYPE_REALTIME_IMAGE:-supatype/realtime:latest}
408
+ expose:
409
+ - "4000"
410
+ environment:
411
+ PORT: "4000"
412
+ DATABASE_URL: "${ownerUrl}"
413
+ JWT_SECRET: \${JWT_SECRET:?JWT_SECRET is missing from .env}
414
+ SLOT_NAME: supatype_realtime
415
+ # Matches the publication the supatype/postgres image creates. Read by nothing today,
416
+ # wal2json decodes from the slot, and kept for a future pgoutput decoder.
417
+ PUBLICATION_NAME: supatype_realtime
418
+ ${dbDependency}`
419
+ : ` # No \`realtime\` service: database.external.realtime is false. Subscriptions are
420
+ # unavailable; REST, storage, auth and functions are unaffected.
421
+ `
422
+ const realtimeDependency = realtime
423
+ ? ` realtime:
424
+ condition: service_started
425
+ `
426
+ : ""
427
+ const realtimeServerEnv = realtime ? " SUPATYPE_REALTIME_URL: http://realtime:4000" : ""
428
+
429
+ const dbServiceBlock = external
430
+ ? ` # No \`db\` service: database.external points this stack at a Postgres it does not manage.
431
+ # Every service reads \${DATABASE_URL} from .env, and \`supatype db check\` reports what that
432
+ # database still needs (roles, extensions, wal_level for realtime).
433
+ `
434
+ : ` db:
321
435
  image: \${SUPATYPE_POSTGRES_IMAGE:-supatype/postgres:latest}
322
436
  environment:
323
437
  POSTGRES_USER: \${POSTGRES_USER:-supatype_admin}
324
- POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
438
+ POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is missing from .env}
325
439
  POSTGRES_DB: \${POSTGRES_DB:-supatype}
440
+ # Read by the image's init to password the \`authenticator\` role PostgREST connects as.
441
+ AUTHENTICATOR_PASSWORD: \${AUTHENTICATOR_PASSWORD:?AUTHENTICATOR_PASSWORD is missing from .env}
326
442
  ${dbPorts} volumes:
327
443
  - db-data:/var/lib/postgresql/data
328
444
  healthcheck:
329
- test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-supatype_admin}"]
445
+ # -h 127.0.0.1 forces TCP. Without it \`pg_isready\` uses the Unix socket, which the
446
+ # entrypoint's temporary init server is already listening on while TCP is still refused,
447
+ # so the container reported healthy, \`depends_on: service_healthy\` released, and every
448
+ # service that connects over the network died with ECONNREFUSED on first boot. PostgREST
449
+ # survived only because it retries. Observed on a clean stack: server, storage and
450
+ # realtime all exited 1 while db said "healthy".
451
+ test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \${POSTGRES_USER:-supatype_admin} -d \${POSTGRES_DB:-supatype}"]
330
452
  interval: 5s
331
453
  timeout: 5s
454
+ # First boot runs initdb plus every bootstrap migration. Failures inside the start period
455
+ # do not count against retries, so a slow init waits rather than being declared unhealthy.
456
+ start_period: 90s
332
457
  retries: 20
333
458
 
334
- postgrest:
459
+ `
460
+
461
+ return `# Generated by supatype self-host compose
462
+ # Kong → supatype-server (unified gateway) → internal PostgREST / storage / etc.
463
+ services:
464
+ ${dbServiceBlock} postgrest:
335
465
  image: postgrest/postgrest:v12.2.8
336
466
  expose:
337
467
  - "3000"
338
468
  environment:
339
- PGRST_DB_URI: postgresql://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}
340
- PGRST_DB_SCHEMA: "public, supatype, graphql_public, auth"
469
+ # Connects as \`authenticator\`, not \${POSTGRES_USER}, which is a superuser, and a
470
+ # superuser session may SET ROLE to any role in the cluster, so a request whose JWT
471
+ # named one got it. \`authenticator\` is NOINHERIT with membership in only
472
+ # anon/authenticated/service_role, so the same SET ROLE is refused.
473
+ #
474
+ # Its own credential, not POSTGRES_PASSWORD: yours is for direct SQL access and
475
+ # rotating it must not take the REST API down. \`supatype init\` generates this.
476
+ PGRST_DB_URI: ${postgrestUrl}
477
+ # Derived from schema.pg_schema (or schema.api_schemas). Hardcoding this is why choosing a
478
+ # non-public pg_schema used to give a correct push and an API that answered PGRST106 for
479
+ # everything: the engine moved and PostgREST was never told.
480
+ PGRST_DB_SCHEMA: "${apiSchemaList(config, options?.fieldMaskingTier)}"
341
481
  PGRST_DB_ANON_ROLE: anon
342
- PGRST_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
482
+ PGRST_JWT_SECRET: \${JWT_SECRET:?JWT_SECRET is missing from .env}
343
483
  PGRST_DB_EXTRA_SEARCH_PATH: public,extensions
344
484
  PGRST_DB_POOL: 3
345
- depends_on:
346
- db:
347
- condition: service_healthy
348
-
485
+ ${dbDependency}
349
486
  storage:
350
487
  image: \${SUPATYPE_STORAGE_IMAGE:-supatype/storage:latest}
351
488
  expose:
352
489
  - "5000"
353
490
  environment:
354
491
  PORT: 5000
355
- DATABASE_URL: "postgresql://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}"
356
- JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
492
+ DATABASE_URL: "${ownerUrl}"
493
+ JWT_SECRET: \${JWT_SECRET:?JWT_SECRET is missing from .env}
357
494
  S3_ENDPOINT: http://minio:9000
358
495
  S3_REGION: us-east-1
359
496
  S3_ACCESS_KEY: supatype
360
497
  S3_SECRET_KEY: supatype-secret
361
498
  S3_FORCE_PATH_STYLE: "true"
362
- depends_on:
363
- db:
364
- condition: service_healthy
365
-
499
+ ${dbDependency}
366
500
  functions-worker:
367
501
  image: \${SUPATYPE_FUNCTIONS_WORKER_IMAGE:-supatype/functions-worker:latest}
368
502
  expose:
@@ -372,31 +506,26 @@ ${dbPorts} volumes:
372
506
  environment:
373
507
  SUPATYPE_FUNCTIONS_ROOT: /project/functions
374
508
  SUPATYPE_DENO_FUNCTIONS_DIR: /project/functions
509
+ # Model hooks, served by this same worker under a hooks/ route the gateway refuses from
510
+ # outside. One worker rather than two: a second container would cost a pod per project on
511
+ # cloud, for isolation the route boundary already provides.
512
+ SUPATYPE_HOOKS_ROOT: /project/hooks
375
513
  PORT: "8001"
376
- SUPATYPE_URL: \${API_EXTERNAL_URL:-${externalUrlFallback}}
514
+ # In-compose loopback to Kong (not API_EXTERNAL_URL / localhost, unreachable from this container).
515
+ SUPATYPE_URL: http://kong:8000
516
+ SUPATYPE_INTERNAL_URL: http://kong:8000
377
517
  SUPATYPE_ANON_KEY: \${ANON_KEY:-}
518
+ # Present so the worker can hand it to hooks, which are procedural and unreachable from
519
+ # outside: and to the public functions named below. Withheld from every other handler before
520
+ # any of them is imported: a function is a public endpoint, and an ambient admin credential made
521
+ # each one able to read past every access rule in the schema.
378
522
  SUPATYPE_SERVICE_ROLE_KEY: \${SERVICE_ROLE_KEY:-}
523
+ SUPATYPE_SERVICE_ROLE_ROUTES: "${serviceRoleRoutes(config).join(",")}"
379
524
  STRIPE_SECRET_KEY: \${STRIPE_SECRET_KEY:-}
380
525
  STRIPE_WEBHOOK_SECRET: \${STRIPE_WEBHOOK_SECRET:-}
381
526
  SITE_URL: \${SITE_URL:-\${API_EXTERNAL_URL:-${externalUrlFallback}}}
382
- depends_on:
383
- db:
384
- condition: service_healthy
385
-
386
- realtime:
387
- image: \${SUPATYPE_REALTIME_IMAGE:-supatype/realtime:latest}
388
- expose:
389
- - "4000"
390
- environment:
391
- PORT: "4000"
392
- DATABASE_URL: "postgresql://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}"
393
- JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
394
- SLOT_NAME: supatype_realtime
395
- PUBLICATION_NAME: supatype_realtime_pub
396
- depends_on:
397
- db:
398
- condition: service_healthy
399
-
527
+ ${dbDependency}
528
+ ${realtimeBlock}
400
529
  control-plane:
401
530
  image: \${SUPATYPE_CONTROL_PLANE_IMAGE:-supatype/control-plane:latest}
402
531
  expose:
@@ -408,16 +537,13 @@ ${dbPorts} volumes:
408
537
  PORT: "8080"
409
538
  SUPATYPE_PROJECT_REF: ${JSON.stringify(config.project.name)}
410
539
  SUPATYPE_PROJECT_ROOT: /project
411
- DATABASE_URL: "postgresql://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}"
540
+ DATABASE_URL: "${ownerUrl}"
412
541
  SUPATYPE_FUNCTIONS_ROOT: /project/functions
413
542
  SUPATYPE_STATIC_ROOT: /project/${staticDir.replace(/^\.\//, "")}
414
543
  SUPATYPE_DEPLOYMENTS_DIR: /project/.supatype/deployments
415
544
  COMPOSE_PROJECT_NAME: ${composeProject}
416
545
  SUPATYPE_ENGINE_BIN: supatype-engine
417
- depends_on:
418
- db:
419
- condition: service_healthy
420
-
546
+ ${dbDependency}
421
547
  server:
422
548
  image: \${SUPATYPE_SERVER_IMAGE:-\${SUPATYPE_AUTH_IMAGE:-supatype/server:latest}}
423
549
  ${serverPorts} volumes:
@@ -434,10 +560,10 @@ ${serverPorts} volumes:
434
560
  SUPATYPE_URL: \${API_EXTERNAL_URL:-${externalUrlFallback}}
435
561
  SUPATYPE_ANON_KEY: \${ANON_KEY:-}
436
562
  SUPATYPE_SERVICE_ROLE_KEY: \${SERVICE_ROLE_KEY:-}
437
- SUPATYPE_SQL_DATABASE_URL: "postgresql://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}"
563
+ SUPATYPE_SQL_DATABASE_URL: "${ownerUrl}"
438
564
  SUPATYPE_DENO_FUNCTIONS_DIR: /project/functions
439
565
  SUPATYPE_FUNCTIONS_WORKER_URL: http://functions-worker:8001
440
- SUPATYPE_REALTIME_URL: http://realtime:4000
566
+ ${realtimeServerEnv}
441
567
  SUPATYPE_CONTROL_PLANE_URL: http://control-plane:8080
442
568
  SUPATYPE_VALKEY_ADDR: valkey:6379
443
569
  ${appEnv}
@@ -446,9 +572,9 @@ ${appEnv}
446
572
  API_EXTERNAL_URL: \${API_EXTERNAL_URL:-${externalUrlFallback}}
447
573
  GOTRUE_API_EXTERNAL_URL: \${API_EXTERNAL_URL:-${externalUrlFallback}}
448
574
  GOTRUE_DB_DRIVER: postgres
449
- GOTRUE_DB_DATABASE_URL: "postgres://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}?search_path=auth"
575
+ GOTRUE_DB_DATABASE_URL: "${gotrueUrl}${gotrueSearchPathSeparator}search_path=auth"
450
576
  GOTRUE_SITE_URL: \${SITE_URL:-${siteUrlFallback}}
451
- GOTRUE_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
577
+ GOTRUE_JWT_SECRET: \${JWT_SECRET:?JWT_SECRET is missing from .env}
452
578
  GOTRUE_JWT_EXP: 3600
453
579
  GOTRUE_JWT_AUD: authenticated
454
580
  GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
@@ -457,9 +583,7 @@ ${appEnv}
457
583
  GOTRUE_DISABLE_SIGNUP: \${DISABLE_SIGNUP:-false}
458
584
  ${devLocal ? " STUDIO_OPEN_DEV: \"1\"\n" : ""}
459
585
  depends_on:
460
- db:
461
- condition: service_healthy
462
- valkey:
586
+ ${dbDependencyClause} valkey:
463
587
  condition: service_started
464
588
  postgrest:
465
589
  condition: service_started
@@ -467,9 +591,7 @@ ${devLocal ? " STUDIO_OPEN_DEV: \"1\"\n" : ""}
467
591
  condition: service_started
468
592
  functions-worker:
469
593
  condition: service_started
470
- realtime:
471
- condition: service_started
472
- control-plane:
594
+ ${realtimeDependency} control-plane:
473
595
  condition: service_started
474
596
 
475
597
  minio:
@@ -488,10 +610,7 @@ ${minioPorts} volumes:
488
610
  volumes:
489
611
  - ${projectMount}:/project
490
612
  working_dir: /project
491
- depends_on:
492
- db:
493
- condition: service_healthy
494
- ${studioBlock}${valkeyBlock}${tlsHintComment} kong:
613
+ ${dbDependency}${studioBlock}${valkeyBlock}${tlsHintComment} kong:
495
614
  image: kong:3.6
496
615
  environment:
497
616
  KONG_DATABASE: "off"
@@ -510,23 +629,173 @@ ${kongDependsOn}${kongValkeyDepends}
510
629
  ${volumesBlock}`
511
630
  }
512
631
 
513
- function ensureComposeManifest(cwd: string): void {
632
+ /** In-compose worker address: matches the `functions-worker` service this file always generates. */
633
+ const COMPOSE_FUNCTIONS_WORKER_URL = "http://functions-worker:8001"
634
+
635
+ function ensureComposeManifest(cwd: string, config: SupatypeProjectConfig): void {
514
636
  const manifestPath = join(cwd, ".supatype", "manifest.json")
515
- if (existsSync(manifestPath)) return
516
637
  mkdirSync(dirname(manifestPath), { recursive: true })
638
+
639
+ // The manifest is generated, not operator-authored, but `push` writes real values into it (the
640
+ // schema, for one), so an existing file is repaired rather than replaced.
641
+ if (existsSync(manifestPath)) {
642
+ repairComposeFunctionsFlag(manifestPath)
643
+ return
644
+ }
645
+
517
646
  const manifest = {
518
647
  schema: "public",
519
648
  postgrest_url: "http://postgrest:3000",
520
649
  storage_url: "http://storage:5000",
521
- realtime_enabled: true,
522
- realtime_url: "http://realtime:4000",
523
- functions_enabled: false,
650
+ // Matches the compose file: a manifest advertising a service that was not generated is how
651
+ // the server ends up proxying subscriptions to a host that does not resolve.
652
+ realtime_enabled: realtimeEnabled(config),
653
+ ...(realtimeEnabled(config) && { realtime_url: "http://realtime:4000" }),
654
+ // True because this file *always* generates a `functions-worker` service and hands the server
655
+ // `SUPATYPE_FUNCTIONS_WORKER_URL`. It said false, so the server answered 404 for every function
656
+ // in a stack that was running a worker for them, provisioned, then switched off.
657
+ functions_enabled: true,
658
+ functions_worker_url: COMPOSE_FUNCTIONS_WORKER_URL,
524
659
  }
525
660
  writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8")
526
661
  }
527
662
 
663
+ /**
664
+ * Turn functions on in a manifest written by an older CLI, leaving every other key alone.
665
+ *
666
+ * Without this, only new projects get working functions: a stack generated before the flag was
667
+ * corrected keeps `functions_enabled: false` on disk, and regenerating the compose file, the
668
+ * obvious thing to try, does not fix it.
669
+ */
670
+ function repairComposeFunctionsFlag(manifestPath: string): void {
671
+ let parsed: Record<string, unknown>
672
+ try {
673
+ parsed = JSON.parse(readFileSync(manifestPath, "utf8")) as Record<string, unknown>
674
+ } catch {
675
+ return // Malformed: leave it for the server to complain about rather than overwrite it here.
676
+ }
677
+ if (typeof parsed !== "object" || parsed === null) return
678
+ if (parsed["functions_enabled"] === true && parsed["functions_worker_url"] !== undefined) return
679
+
680
+ parsed["functions_enabled"] = true
681
+ if (parsed["functions_worker_url"] === undefined) {
682
+ parsed["functions_worker_url"] = COMPOSE_FUNCTIONS_WORKER_URL
683
+ }
684
+ writeFileSync(manifestPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8")
685
+ }
686
+
528
687
  function ensureProjectFunctionsDir(cwd: string, config: SupatypeProjectConfig): void {
529
688
  mkdirSync(preferredFunctionsPathFromProject(config, cwd), { recursive: true })
689
+ // Both roots must exist before compose mounts the project read-only: a missing directory becomes
690
+ // a bind mount of a file that is not there, and the worker fails to start rather than serving the
691
+ // half it does have.
692
+ mkdirSync(hooksPathFromProject(config, cwd), { recursive: true })
693
+ }
694
+
695
+ /**
696
+ * The config's external URL and `.env`'s `DATABASE_URL` must be the same string.
697
+ *
698
+ * The CLI resolves the URL from config; Compose substitutes `.env` at up-time. If the two disagree,
699
+ * `push` migrates one database while the services serve another, which reads as data loss and
700
+ * isn't. It also makes the generated GoTrue DSN wrong, since whether to append `search_path` with
701
+ * `?` or `&` is decided from the config URL's query string.
702
+ */
703
+ function assertExternalUrlMatchesEnv(cwd: string, config: SupatypeProjectConfig): void {
704
+ const configured = externalDatabaseUrl(config)
705
+ if (configured === undefined) return
706
+
707
+ const fromEnv = readEnvFile(cwd)["DATABASE_URL"]
708
+ if (fromEnv === undefined) {
709
+ // Nothing to disagree with. Compose's own `:?` guard reports this at up-time, and the operator
710
+ // may be exporting the variable in their shell rather than keeping a file.
711
+ return
712
+ }
713
+ if (fromEnv.trim() === configured) return
714
+
715
+ fatalError(
716
+ "DATABASE_URL in .env does not match database.external.url",
717
+ [
718
+ `.env: ${fromEnv.trim()}`,
719
+ `config: ${configured}`,
720
+ "",
721
+ "The CLI connects using the config value and the stack connects using .env, so a push would",
722
+ "land in one database while the services serve the other.",
723
+ "Point the config at the same variable to keep them in step:",
724
+ " database: { external: { url: process.env.DATABASE_URL! } }",
725
+ ],
726
+ { brand: { intro: "Self-host compose" } },
727
+ )
728
+ }
729
+
730
+ /**
731
+ * A loopback host in the external URL cannot work from inside a container.
732
+ *
733
+ * `127.0.0.1` means "this container" to every service in the stack, so an external database on the
734
+ * host machine is unreachable, while the *CLI* reaches it fine, because the CLI runs on the host.
735
+ * That asymmetry is the trap: `supatype db check` passes, `push` applies the schema, and then
736
+ * storage, realtime and the server all die with ECONNREFUSED against their own loopback. Found by
737
+ * rehearsing exactly that.
738
+ *
739
+ * Refused rather than rewritten. The compose file interpolates one `${DATABASE_URL}` for every
740
+ * service, so silently substituting a different host would mean the CLI and the stack no longer
741
+ * agree about which database they are talking to, the thing every other check here exists to
742
+ * prevent.
743
+ */
744
+ export function loopbackExternalHost(config: SupatypeProjectConfig): string | undefined {
745
+ const url = externalDatabaseUrl(config)
746
+ if (url === undefined) return undefined
747
+
748
+ let host: string
749
+ try {
750
+ host = new URL(url).hostname.toLowerCase()
751
+ } catch {
752
+ return undefined // Shape is validated at config load; nothing useful to add here.
753
+ }
754
+ // Node strips the brackets from an IPv6 hostname, and 127.0.0.0/8 is all loopback.
755
+ const loopback =
756
+ host === "localhost" ||
757
+ host === "::1" ||
758
+ host === "[::1]" ||
759
+ /^127\.\d+\.\d+\.\d+$/.test(host)
760
+ return loopback ? host : undefined
761
+ }
762
+
763
+ function assertExternalUrlReachableFromContainers(config: SupatypeProjectConfig): void {
764
+ const host = loopbackExternalHost(config)
765
+ if (host === undefined) return
766
+
767
+ fatalError(
768
+ `database.external.url points at ${host}, which no container can reach`,
769
+ [
770
+ "Inside a container, localhost is that container, not the machine running Docker. Every",
771
+ "service in the stack would fail to connect, while the CLI succeeds because it runs on the host.",
772
+ "",
773
+ "Use a host the containers can resolve:",
774
+ " host.docker.internal (Docker Desktop on macOS and Windows)",
775
+ " 172.17.0.1 (the docker0 bridge on Linux, or add an extra_hosts entry)",
776
+ " the database's LAN address or hostname",
777
+ "",
778
+ "The CLI reaches the same address, so one value keeps working for both.",
779
+ ],
780
+ { brand: { intro: "Self-host compose" } },
781
+ )
782
+ }
783
+
784
+ /**
785
+ * The tier to write into the compose file.
786
+ *
787
+ * Resolved here so every caller gets it, rather than threaded through four call sites that load the
788
+ * schema a moment later anyway. A schema that fails to load falls back to the default list: compose
789
+ * generation is the wrong place to report a syntax error, and `push`/`dev` do it properly seconds
790
+ * later with the file and line.
791
+ */
792
+ function resolveFieldMaskingTier(
793
+ cwd: string,
794
+ config: SupatypeProjectConfig,
795
+ options?: SelfHostComposeOptions,
796
+ ): FieldMaskingTier | undefined {
797
+ if (options?.fieldMaskingTier !== undefined) return options.fieldMaskingTier
798
+ return fieldMaskingTierFromProject(cwd, config)
530
799
  }
531
800
 
532
801
  export function writeSelfHostCompose(
@@ -534,11 +803,18 @@ export function writeSelfHostCompose(
534
803
  config: SupatypeProjectConfig,
535
804
  options?: SelfHostComposeOptions,
536
805
  ): SelfHostComposePaths {
806
+ assertExternalUrlMatchesEnv(cwd, config)
807
+ const tier = resolveFieldMaskingTier(cwd, config, options)
808
+ const resolved: SelfHostComposeOptions = {
809
+ ...options,
810
+ ...(tier !== undefined && { fieldMaskingTier: tier }),
811
+ }
812
+ assertExternalUrlReachableFromContainers(config)
537
813
  const paths = selfHostComposePaths(cwd)
538
814
  mkdirSync(paths.dir, { recursive: true })
539
815
  ensureProjectFunctionsDir(cwd, config)
540
- ensureComposeManifest(cwd)
541
- writeFileSync(paths.composePath, renderSelfHostCompose(config, cwd, options), "utf8")
816
+ ensureComposeManifest(cwd, config)
817
+ writeFileSync(paths.composePath, renderSelfHostCompose(config, cwd, resolved), "utf8")
542
818
  const studioHostDev = options?.devLocal === true && hasStudioOverride(config)
543
819
  const tlsEnabled = selfHostTlsEnabled(config, options?.devLocal === true)
544
820
  const domain = config.server.domain?.trim()
@@ -626,7 +902,7 @@ export function runDockerCompose(
626
902
  return result.status ?? 1
627
903
  }
628
904
 
629
- /** Compose project name for a Supatype project isolates docker state per project. */
905
+ /** Compose project name for a Supatype project, isolates docker state per project. */
630
906
  export function composeProjectName(projectName: string): string {
631
907
  const slug = projectName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "")
632
908
  return `supatype-${slug || "project"}`