@supatype/cli 0.1.0-alpha.6 → 0.1.0-alpha.8

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 (350) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +208 -1
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/dist/app/proxy-dev-app.d.ts +13 -0
  5. package/dist/app/proxy-dev-app.d.ts.map +1 -0
  6. package/dist/app/proxy-dev-app.js +53 -0
  7. package/dist/app/proxy-dev-app.js.map +1 -0
  8. package/dist/app-config.d.ts +7 -0
  9. package/dist/app-config.d.ts.map +1 -0
  10. package/dist/app-config.js +113 -0
  11. package/dist/app-config.js.map +1 -0
  12. package/dist/augmentation-generator.d.ts +2 -0
  13. package/dist/augmentation-generator.d.ts.map +1 -0
  14. package/dist/augmentation-generator.js +111 -0
  15. package/dist/augmentation-generator.js.map +1 -0
  16. package/dist/binary-cache.d.ts +94 -0
  17. package/dist/binary-cache.d.ts.map +1 -0
  18. package/dist/binary-cache.js +669 -0
  19. package/dist/binary-cache.js.map +1 -0
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +13 -7
  22. package/dist/cli.js.map +1 -1
  23. package/dist/commands/admin.d.ts.map +1 -1
  24. package/dist/commands/admin.js +4 -3
  25. package/dist/commands/admin.js.map +1 -1
  26. package/dist/commands/app.d.ts.map +1 -1
  27. package/dist/commands/app.js +56 -209
  28. package/dist/commands/app.js.map +1 -1
  29. package/dist/commands/cache.d.ts +6 -0
  30. package/dist/commands/cache.d.ts.map +1 -0
  31. package/dist/commands/cache.js +105 -0
  32. package/dist/commands/cache.js.map +1 -0
  33. package/dist/commands/cloud.d.ts +20 -0
  34. package/dist/commands/cloud.d.ts.map +1 -1
  35. package/dist/commands/cloud.js +50 -52
  36. package/dist/commands/cloud.js.map +1 -1
  37. package/dist/commands/db.d.ts.map +1 -1
  38. package/dist/commands/db.js +47 -54
  39. package/dist/commands/db.js.map +1 -1
  40. package/dist/commands/deploy.d.ts +2 -1
  41. package/dist/commands/deploy.d.ts.map +1 -1
  42. package/dist/commands/deploy.js +79 -52
  43. package/dist/commands/deploy.js.map +1 -1
  44. package/dist/commands/dev.d.ts +11 -0
  45. package/dist/commands/dev.d.ts.map +1 -1
  46. package/dist/commands/dev.js +759 -385
  47. package/dist/commands/dev.js.map +1 -1
  48. package/dist/commands/diff.d.ts.map +1 -1
  49. package/dist/commands/diff.js +30 -15
  50. package/dist/commands/diff.js.map +1 -1
  51. package/dist/commands/engine.d.ts +1 -3
  52. package/dist/commands/engine.d.ts.map +1 -1
  53. package/dist/commands/engine.js +13 -85
  54. package/dist/commands/engine.js.map +1 -1
  55. package/dist/commands/functions.d.ts.map +1 -1
  56. package/dist/commands/functions.js +92 -105
  57. package/dist/commands/functions.js.map +1 -1
  58. package/dist/commands/generate.d.ts.map +1 -1
  59. package/dist/commands/generate.js +22 -12
  60. package/dist/commands/generate.js.map +1 -1
  61. package/dist/commands/init.d.ts +1 -1
  62. package/dist/commands/init.d.ts.map +1 -1
  63. package/dist/commands/init.js +137 -410
  64. package/dist/commands/init.js.map +1 -1
  65. package/dist/commands/migrate-from-v1.d.ts +5 -0
  66. package/dist/commands/migrate-from-v1.d.ts.map +1 -0
  67. package/dist/commands/migrate-from-v1.js +125 -0
  68. package/dist/commands/migrate-from-v1.js.map +1 -0
  69. package/dist/commands/migrate.d.ts.map +1 -1
  70. package/dist/commands/migrate.js +27 -23
  71. package/dist/commands/migrate.js.map +1 -1
  72. package/dist/commands/pg.d.ts +8 -0
  73. package/dist/commands/pg.d.ts.map +1 -0
  74. package/dist/commands/pg.js +102 -0
  75. package/dist/commands/pg.js.map +1 -0
  76. package/dist/commands/pull.d.ts.map +1 -1
  77. package/dist/commands/pull.js +5 -66
  78. package/dist/commands/pull.js.map +1 -1
  79. package/dist/commands/push.d.ts.map +1 -1
  80. package/dist/commands/push.js +128 -38
  81. package/dist/commands/push.js.map +1 -1
  82. package/dist/commands/seed.d.ts +2 -0
  83. package/dist/commands/seed.d.ts.map +1 -1
  84. package/dist/commands/seed.js +44 -11
  85. package/dist/commands/seed.js.map +1 -1
  86. package/dist/commands/self-host.d.ts +7 -1
  87. package/dist/commands/self-host.d.ts.map +1 -1
  88. package/dist/commands/self-host.js +272 -758
  89. package/dist/commands/self-host.js.map +1 -1
  90. package/dist/commands/self-update.d.ts +9 -0
  91. package/dist/commands/self-update.d.ts.map +1 -0
  92. package/dist/commands/self-update.js +33 -0
  93. package/dist/commands/self-update.js.map +1 -0
  94. package/dist/commands/status.d.ts.map +1 -1
  95. package/dist/commands/status.js +4 -3
  96. package/dist/commands/status.js.map +1 -1
  97. package/dist/commands/types.d.ts +3 -0
  98. package/dist/commands/types.d.ts.map +1 -0
  99. package/dist/commands/types.js +62 -0
  100. package/dist/commands/types.js.map +1 -0
  101. package/dist/commands/update.d.ts +7 -0
  102. package/dist/commands/update.d.ts.map +1 -0
  103. package/dist/commands/update.js +93 -0
  104. package/dist/commands/update.js.map +1 -0
  105. package/dist/components.d.ts +5 -0
  106. package/dist/components.d.ts.map +1 -0
  107. package/dist/components.js +3 -0
  108. package/dist/components.js.map +1 -0
  109. package/dist/config.d.ts +10 -51
  110. package/dist/config.d.ts.map +1 -1
  111. package/dist/config.js +101 -33
  112. package/dist/config.js.map +1 -1
  113. package/dist/dev-compose.d.ts +17 -0
  114. package/dist/dev-compose.d.ts.map +1 -0
  115. package/dist/dev-compose.js +374 -0
  116. package/dist/dev-compose.js.map +1 -0
  117. package/dist/diff-output.d.ts +4 -0
  118. package/dist/diff-output.d.ts.map +1 -0
  119. package/dist/diff-output.js +12 -0
  120. package/dist/diff-output.js.map +1 -0
  121. package/dist/docker-postgres.d.ts +57 -0
  122. package/dist/docker-postgres.d.ts.map +1 -0
  123. package/dist/docker-postgres.js +208 -0
  124. package/dist/docker-postgres.js.map +1 -0
  125. package/dist/engine-client.d.ts +69 -0
  126. package/dist/engine-client.d.ts.map +1 -0
  127. package/dist/engine-client.js +157 -0
  128. package/dist/engine-client.js.map +1 -0
  129. package/dist/ensure-binary.d.ts +7 -0
  130. package/dist/ensure-binary.d.ts.map +1 -0
  131. package/dist/ensure-binary.js +17 -0
  132. package/dist/ensure-binary.js.map +1 -0
  133. package/dist/functions-router-gen.d.ts +14 -0
  134. package/dist/functions-router-gen.d.ts.map +1 -0
  135. package/dist/functions-router-gen.js +199 -0
  136. package/dist/functions-router-gen.js.map +1 -0
  137. package/dist/index.d.ts +4 -5
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +2 -3
  140. package/dist/index.js.map +1 -1
  141. package/dist/kong-config.d.ts +25 -0
  142. package/dist/kong-config.d.ts.map +1 -0
  143. package/dist/kong-config.js +71 -0
  144. package/dist/kong-config.js.map +1 -0
  145. package/dist/local-gateway.d.ts +7 -0
  146. package/dist/local-gateway.d.ts.map +1 -0
  147. package/dist/local-gateway.js +9 -0
  148. package/dist/local-gateway.js.map +1 -0
  149. package/dist/local-storage.d.ts +8 -0
  150. package/dist/local-storage.d.ts.map +1 -0
  151. package/dist/local-storage.js +14 -0
  152. package/dist/local-storage.js.map +1 -0
  153. package/dist/pgbouncer-userlist.d.ts +5 -0
  154. package/dist/pgbouncer-userlist.d.ts.map +1 -0
  155. package/dist/pgbouncer-userlist.js +14 -0
  156. package/dist/pgbouncer-userlist.js.map +1 -0
  157. package/dist/postgres-ctl.d.ts +44 -0
  158. package/dist/postgres-ctl.d.ts.map +1 -0
  159. package/dist/postgres-ctl.js +137 -0
  160. package/dist/postgres-ctl.js.map +1 -0
  161. package/dist/process-manager.d.ts +43 -0
  162. package/dist/process-manager.d.ts.map +1 -0
  163. package/dist/process-manager.js +135 -0
  164. package/dist/process-manager.js.map +1 -0
  165. package/dist/project-config.d.ts +235 -0
  166. package/dist/project-config.d.ts.map +1 -0
  167. package/dist/project-config.js +160 -0
  168. package/dist/project-config.js.map +1 -0
  169. package/dist/pull-utils.d.ts +15 -0
  170. package/dist/pull-utils.d.ts.map +1 -1
  171. package/dist/pull-utils.js +12 -0
  172. package/dist/pull-utils.js.map +1 -1
  173. package/dist/release-pins.d.ts +7 -0
  174. package/dist/release-pins.d.ts.map +1 -0
  175. package/dist/release-pins.js +27 -0
  176. package/dist/release-pins.js.map +1 -0
  177. package/dist/release-public-key.d.ts +8 -0
  178. package/dist/release-public-key.d.ts.map +1 -0
  179. package/dist/release-public-key.js +13 -0
  180. package/dist/release-public-key.js.map +1 -0
  181. package/dist/runtime-routes.d.ts +34 -0
  182. package/dist/runtime-routes.d.ts.map +1 -0
  183. package/dist/runtime-routes.js +252 -0
  184. package/dist/runtime-routes.js.map +1 -0
  185. package/dist/schema-ast-v2.d.ts +127 -0
  186. package/dist/schema-ast-v2.d.ts.map +1 -0
  187. package/dist/schema-ast-v2.js +226 -0
  188. package/dist/schema-ast-v2.js.map +1 -0
  189. package/dist/scripts/postinstall.d.ts +5 -6
  190. package/dist/scripts/postinstall.d.ts.map +1 -1
  191. package/dist/scripts/postinstall.js +36 -20
  192. package/dist/scripts/postinstall.js.map +1 -1
  193. package/dist/self-host-compose.d.ts +22 -0
  194. package/dist/self-host-compose.d.ts.map +1 -0
  195. package/dist/self-host-compose.js +347 -0
  196. package/dist/self-host-compose.js.map +1 -0
  197. package/dist/storage-provision.d.ts +24 -0
  198. package/dist/storage-provision.d.ts.map +1 -0
  199. package/dist/storage-provision.js +44 -0
  200. package/dist/storage-provision.js.map +1 -0
  201. package/dist/studio-admin-roles.d.ts +7 -0
  202. package/dist/studio-admin-roles.d.ts.map +1 -0
  203. package/dist/studio-admin-roles.js +14 -0
  204. package/dist/studio-admin-roles.js.map +1 -0
  205. package/dist/studio-dev-server.d.ts +22 -0
  206. package/dist/studio-dev-server.d.ts.map +1 -0
  207. package/dist/studio-dev-server.js +28 -0
  208. package/dist/studio-dev-server.js.map +1 -0
  209. package/dist/systemd.d.ts +26 -0
  210. package/dist/systemd.d.ts.map +1 -0
  211. package/dist/systemd.js +102 -0
  212. package/dist/systemd.js.map +1 -0
  213. package/dist/tsx-runner.d.ts.map +1 -1
  214. package/dist/tsx-runner.js +9 -2
  215. package/dist/tsx-runner.js.map +1 -1
  216. package/dist/type-extractor.d.ts +4 -0
  217. package/dist/type-extractor.d.ts.map +1 -0
  218. package/dist/type-extractor.js +1213 -0
  219. package/dist/type-extractor.js.map +1 -0
  220. package/dist/type-resolver.d.ts +33 -0
  221. package/dist/type-resolver.d.ts.map +1 -0
  222. package/dist/type-resolver.js +338 -0
  223. package/dist/type-resolver.js.map +1 -0
  224. package/package.json +4 -3
  225. package/releases/deno/VERSION +1 -0
  226. package/scripts/mirror-deno-release.sh +76 -0
  227. package/src/TYPE-RESOLUTION.md +294 -0
  228. package/src/app/proxy-dev-app.ts +67 -0
  229. package/src/app-config.ts +128 -0
  230. package/src/augmentation-generator.ts +126 -0
  231. package/src/binary-cache.ts +822 -0
  232. package/src/cli.ts +13 -8
  233. package/src/commands/admin.ts +4 -3
  234. package/src/commands/app.ts +67 -231
  235. package/src/commands/cache.ts +117 -0
  236. package/src/commands/cloud.ts +63 -64
  237. package/src/commands/db.ts +54 -63
  238. package/src/commands/deploy.ts +96 -62
  239. package/src/commands/dev.ts +933 -405
  240. package/src/commands/diff.ts +31 -29
  241. package/src/commands/engine.ts +13 -116
  242. package/src/commands/functions.ts +97 -115
  243. package/src/commands/generate.ts +23 -10
  244. package/src/commands/init.ts +149 -414
  245. package/src/commands/migrate-from-v1.ts +131 -0
  246. package/src/commands/migrate.ts +27 -23
  247. package/src/commands/pg.ts +133 -0
  248. package/src/commands/pull.ts +6 -85
  249. package/src/commands/push.ts +161 -56
  250. package/src/commands/seed.ts +54 -12
  251. package/src/commands/self-host.ts +312 -880
  252. package/src/commands/self-update.ts +45 -0
  253. package/src/commands/status.ts +4 -3
  254. package/src/commands/types.ts +76 -0
  255. package/src/commands/update.ts +109 -0
  256. package/src/components.ts +6 -0
  257. package/src/config.ts +127 -94
  258. package/src/dev-compose.ts +455 -0
  259. package/src/diff-output.ts +12 -0
  260. package/src/docker-postgres.ts +295 -0
  261. package/src/engine-client.ts +236 -0
  262. package/src/ensure-binary.ts +28 -0
  263. package/src/functions-router-gen.ts +224 -0
  264. package/src/index.ts +4 -12
  265. package/src/kong-config.ts +93 -0
  266. package/src/local-gateway.ts +9 -0
  267. package/src/local-storage.ts +14 -0
  268. package/src/pgbouncer-userlist.ts +15 -0
  269. package/src/postgres-ctl.ts +171 -0
  270. package/src/process-manager.ts +168 -0
  271. package/src/project-config.ts +386 -0
  272. package/src/pull-utils.ts +24 -0
  273. package/src/release-pins.ts +31 -0
  274. package/src/release-public-key.ts +12 -0
  275. package/src/runtime-routes.ts +291 -0
  276. package/src/schema-ast-v2.ts +324 -0
  277. package/src/scripts/postinstall.ts +36 -25
  278. package/src/self-host-compose.ts +389 -0
  279. package/src/storage-provision.ts +58 -0
  280. package/src/studio-admin-roles.ts +16 -0
  281. package/src/studio-dev-server.ts +53 -0
  282. package/src/systemd.ts +137 -0
  283. package/src/tsx-runner.ts +11 -1
  284. package/src/type-extractor.ts +1479 -0
  285. package/src/type-resolver.ts +457 -0
  286. package/tests/app-command.test.ts +54 -0
  287. package/tests/augmentation-generator.test.ts +59 -0
  288. package/tests/binary-cache-cloud-overrides.test.ts +123 -0
  289. package/tests/cached-artifact-format.test.ts +84 -0
  290. package/tests/cli-help.test.ts +40 -14
  291. package/tests/config.test.ts +171 -37
  292. package/tests/docker-postgres.test.ts +39 -0
  293. package/tests/engine-distribution.test.ts +3 -3
  294. package/tests/ensure-binary.test.ts +59 -0
  295. package/tests/init.test.ts +28 -86
  296. package/tests/migrate-from-v1.test.ts +29 -0
  297. package/tests/normalize-admin-config.test.ts +48 -0
  298. package/tests/pg-spawn-env.test.ts +18 -0
  299. package/tests/postgres-archive-tag.test.ts +9 -0
  300. package/tests/proxy-dev-app.test.ts +33 -0
  301. package/tests/pull-utils.test.ts +36 -1
  302. package/tests/release-pins.test.ts +28 -0
  303. package/tests/runtime-contract.test.ts +351 -0
  304. package/tests/seed-discover.test.ts +31 -0
  305. package/tests/studio-admin-roles.test.ts +27 -0
  306. package/tests/tsconfig.json +9 -0
  307. package/tests/type-extractor.test.ts +985 -0
  308. package/tests/type-resolver.test.ts +59 -0
  309. package/tsconfig.tsbuildinfo +1 -1
  310. package/vitest.config.ts +12 -0
  311. package/dist/engine/cache.d.ts +0 -37
  312. package/dist/engine/cache.d.ts.map +0 -1
  313. package/dist/engine/cache.js +0 -121
  314. package/dist/engine/cache.js.map +0 -1
  315. package/dist/engine/download.d.ts +0 -19
  316. package/dist/engine/download.d.ts.map +0 -1
  317. package/dist/engine/download.js +0 -108
  318. package/dist/engine/download.js.map +0 -1
  319. package/dist/engine/platform.d.ts +0 -24
  320. package/dist/engine/platform.d.ts.map +0 -1
  321. package/dist/engine/platform.js +0 -50
  322. package/dist/engine/platform.js.map +0 -1
  323. package/dist/engine/resolve.d.ts +0 -37
  324. package/dist/engine/resolve.d.ts.map +0 -1
  325. package/dist/engine/resolve.js +0 -133
  326. package/dist/engine/resolve.js.map +0 -1
  327. package/dist/engine/update-notify.d.ts +0 -11
  328. package/dist/engine/update-notify.d.ts.map +0 -1
  329. package/dist/engine/update-notify.js +0 -43
  330. package/dist/engine/update-notify.js.map +0 -1
  331. package/dist/engine/verify.d.ts +0 -50
  332. package/dist/engine/verify.d.ts.map +0 -1
  333. package/dist/engine/verify.js +0 -161
  334. package/dist/engine/verify.js.map +0 -1
  335. package/dist/engine-version.d.ts +0 -35
  336. package/dist/engine-version.d.ts.map +0 -1
  337. package/dist/engine-version.js +0 -35
  338. package/dist/engine-version.js.map +0 -1
  339. package/dist/engine.d.ts +0 -34
  340. package/dist/engine.d.ts.map +0 -1
  341. package/dist/engine.js +0 -76
  342. package/dist/engine.js.map +0 -1
  343. package/src/engine/cache.ts +0 -135
  344. package/src/engine/download.ts +0 -143
  345. package/src/engine/platform.ts +0 -66
  346. package/src/engine/resolve.ts +0 -197
  347. package/src/engine/update-notify.ts +0 -50
  348. package/src/engine/verify.ts +0 -206
  349. package/src/engine-version.ts +0 -39
  350. package/src/engine.ts +0 -99
@@ -1,428 +1,802 @@
1
- import { spawnSync, spawn } from "node:child_process";
1
+ /**
2
+ * supatype dev — start local Postgres, apply schema, run supatype-server.
3
+ *
4
+ * Runtime provider (top-level `provider` or legacy `database.provider`):
5
+ * native — host Postgres + host server + host engine (default)
6
+ * docker — full self-host Compose stack (Kong :18473); see dev-compose.ts
7
+ *
8
+ * Edge functions (when a functions/ dir exists): Deno is resolved from the CDN cache
9
+ * (auto-download on miss). Self-host/cloud Docker stacks use supatype-server in-container;
10
+ * Deno is not provisioned by the CLI on those paths.
11
+ */
12
+ import { spawnSync } from "node:child_process";
2
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { resolve } from "node:path";
14
+ import { homedir } from "node:os";
15
+ import { join, relative, resolve } from "node:path";
4
16
  import { loadConfig } from "../config.js";
5
- import { ensureEngine, invokeEngine } from "../engine.js";
6
- const POSTGREST_URL = "http://localhost:3000";
7
- const HEALTH_TIMEOUT_MS = 60_000;
8
- const HEALTH_POLL_MS = 2_000;
17
+ import { functionsPathCandidatesFromProject, resolveRuntimeProvider, schemaPathFromProject, } from "../project-config.js";
18
+ import { discoverTsFunctionsInDir, writeDevFunctionsRouter } from "../functions-router-gen.js";
19
+ import { signJwt } from "../jwt.js";
20
+ import { normalisePlatformPath, cachePath, currentPlatform, hasMeaningfulOverrides, describeActiveOverrides, postgresArchiveTag, } from "../binary-cache.js";
21
+ import { ensureBinary } from "../ensure-binary.js";
22
+ import { startProxyDevApp } from "../app/proxy-dev-app.js";
23
+ import { ProcessManager } from "../process-manager.js";
24
+ import { startStudioViteDevServer } from "../studio-dev-server.js";
25
+ import { localStorageEnv } from "../local-storage.js";
26
+ import { initdb, start as pgStart, stop as pgStop, waitReady as pgWaitReady, isPortInUse, pgSpawnEnv, } from "../postgres-ctl.js";
27
+ /** Map `email.smtp` from supatype.config.ts into GOTRUE_SMTP_* for the embedded GoTrue process. */
28
+ function gotrueSMTPFromEmailConfig(email) {
29
+ const s = email?.smtp;
30
+ if (!s)
31
+ return {};
32
+ const out = {};
33
+ const host = s.host?.trim();
34
+ if (host)
35
+ out.GOTRUE_SMTP_HOST = host;
36
+ if (s.port !== undefined)
37
+ out.GOTRUE_SMTP_PORT = String(s.port);
38
+ const user = s.user?.trim();
39
+ if (user)
40
+ out.GOTRUE_SMTP_USER = user;
41
+ if (s.pass !== undefined && s.pass !== "")
42
+ out.GOTRUE_SMTP_PASS = s.pass;
43
+ const admin = s.admin_email?.trim();
44
+ if (admin)
45
+ out.GOTRUE_SMTP_ADMIN_EMAIL = admin;
46
+ const sender = s.sender_name?.trim();
47
+ if (sender)
48
+ out.GOTRUE_SMTP_SENDER_NAME = sender;
49
+ return out;
50
+ }
9
51
  export function registerDev(program) {
10
52
  program
11
53
  .command("dev")
12
- .description("Start local Postgres, PostgREST, and Kong via Docker Compose, then watch for schema changes")
54
+ .description("Start local Postgres, apply schema, and run supatype-server")
13
55
  .option("--no-watch", "Start services but do not watch for schema changes")
14
- .option("--local", "Run storage, realtime, and studio from source (monorepo dev)")
56
+ .option("--port <port>", "Port for supatype-server (overrides config)", String)
15
57
  .action(async (opts) => {
16
58
  const cwd = process.cwd();
17
- // Generate .env with local defaults if missing
18
- ensureDevEnv(cwd);
19
- if (opts.local) {
20
- // Monorepo dev generate an infra-only compose if needed, then start
21
- const composePath = resolve(cwd, "docker-compose.yml");
22
- if (!existsSync(composePath)) {
23
- ensureInfraCompose(cwd);
59
+ // ── 1. Load project config ─────────────────────────────────────────────
60
+ const config = loadConfig(cwd);
61
+ if (hasMeaningfulOverrides(config)) {
62
+ console.warn("[supatype] Local binary overrides active:");
63
+ for (const line of describeActiveOverrides(config)) {
64
+ console.warn(line);
65
+ }
66
+ console.warn("");
67
+ }
68
+ const projectName = config.project.name;
69
+ const serverPort = opts.port ?? String(config.server.port ?? 54321);
70
+ const postgrestPort = String(config.server.postgrestPort ?? 3001);
71
+ const provider = resolveRuntimeProvider(config);
72
+ if (provider === "docker") {
73
+ const { runDevCompose } = await import("../dev-compose.js");
74
+ await runDevCompose(cwd, config, { watch: opts.watch !== false });
75
+ return;
76
+ }
77
+ // ── 2. Resolve engine + server binaries ──────────────────────────────
78
+ console.log(`[supatype] Resolving component binaries for "${projectName}"...`);
79
+ const [engineBin, serverBin] = await Promise.all([
80
+ ensureBinary("engine", config),
81
+ ensureBinary("server", config),
82
+ ]);
83
+ // ── 3. Per-project state directories ─────────────────────────────────
84
+ const stateRoot = join(homedir(), ".supatype", "projects", projectName);
85
+ const pidDir = join(stateRoot, "pid");
86
+ const logsDir = join(stateRoot, "logs");
87
+ const tmpDir = join(stateRoot, "tmp");
88
+ for (const d of [pidDir, logsDir, tmpDir]) {
89
+ mkdirSync(d, { recursive: true });
90
+ }
91
+ // ── 4. Port collision check ───────────────────────────────────────────
92
+ const pgPort = 5432;
93
+ if (await isPortInUse(pgPort)) {
94
+ console.error(`[supatype] Port ${pgPort} is already in use. Another Postgres instance may be running.\n` +
95
+ ` Check: lsof -i :${pgPort}`);
96
+ process.exit(1);
97
+ }
98
+ if (await isPortInUse(Number(serverPort))) {
99
+ console.error(`[supatype] Port ${serverPort} is already in use. Another supatype-server may be running.\n` +
100
+ ` Check: lsof -i :${serverPort}`);
101
+ process.exit(1);
102
+ }
103
+ if (await isPortInUse(Number(postgrestPort))) {
104
+ console.error(`[supatype] Port ${postgrestPort} is already in use. Another service may be running.\n` +
105
+ ` Check: lsof -i :${postgrestPort}`);
106
+ process.exit(1);
107
+ }
108
+ // ── 5–7. Start Postgres ───────────────────────────────────────────────
109
+ let dbURL;
110
+ let stopPostgres;
111
+ const pgPassword = "postgres";
112
+ // pgBinDir is set on the native path and used to add DLL search path for
113
+ // PostgREST on Windows (PostgREST links against libpq + SSL from MinGW).
114
+ let pgBinDir = null;
115
+ {
116
+ // native — resolve pg bin dir and manage with pg_ctl
117
+ pgBinDir = await resolvePgBinDir(config);
118
+ const dataDir = config.database.data_dir ?? join(stateRoot, "data");
119
+ mkdirSync(dataDir, { recursive: true });
120
+ const pgOpts = { pgBinDir, dataDir, port: pgPort, logPath: join(logsDir, "postgres.log") };
121
+ console.log("[supatype] Initialising Postgres data directory...");
122
+ initdb(pgOpts);
123
+ console.log("[supatype] Starting Postgres...");
124
+ pgStart(pgOpts);
125
+ await pgWaitReady(pgOpts, 15_000);
126
+ console.log("[supatype] Postgres is ready.");
127
+ dbURL = `postgres://postgres:postgres@127.0.0.1:${pgPort}/${projectName}?sslmode=disable`;
128
+ stopPostgres = () => pgStop(pgOpts);
129
+ // Create project database if it doesn't exist (native only).
130
+ const psqlBin = join(pgBinDir, process.platform === "win32" ? "psql.exe" : "psql");
131
+ const createdbBin = join(pgBinDir, process.platform === "win32" ? "createdb.exe" : "createdb");
132
+ const pgConnArgs = ["-h", "127.0.0.1", "-p", String(pgPort), "-U", "postgres"];
133
+ const pgEnv = pgSpawnEnv(pgBinDir);
134
+ const createDbResult = spawnSync(createdbBin, [...pgConnArgs, projectName], { stdio: "pipe", encoding: "utf8", env: pgEnv });
135
+ if (createDbResult.status !== 0) {
136
+ const stderr = createDbResult.stderr ?? "";
137
+ if (!stderr.includes("already exists")) {
138
+ throw new Error(`Failed to create database "${projectName}": ${stderr}`);
139
+ }
24
140
  }
25
- console.log("Starting infra services...");
26
- const up = spawnSync("docker", ["compose", "up", "-d", "--wait", "db", "pgbouncer", "gotrue", "postgrest", "minio", "kong"], { cwd, stdio: "inherit" });
27
- if (up.status !== 0) {
28
- console.error("docker compose up failed.");
29
- process.exit(1);
141
+ else {
142
+ console.log(`[supatype] Created database "${projectName}".`);
30
143
  }
144
+ // Create roles required by PostgREST and grant them to postgres so
145
+ // PostgREST can SET ROLE when processing requests.
146
+ // anon – unauthenticated requests (RLS enforced)
147
+ // authenticated – signed-in user requests (RLS enforced)
148
+ // service_role – developer/admin bypass (BYPASSRLS)
149
+ const rolesSql = `
150
+ CREATE SCHEMA IF NOT EXISTS auth;
151
+ DO $$ BEGIN
152
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon')
153
+ THEN CREATE ROLE anon NOLOGIN; END IF;
154
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticated')
155
+ THEN CREATE ROLE authenticated NOLOGIN; END IF;
156
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'service_role')
157
+ THEN CREATE ROLE service_role NOLOGIN BYPASSRLS; END IF;
158
+ END $$;
159
+ GRANT anon, authenticated, service_role TO postgres;
160
+ GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role;
161
+ -- Table-level privileges (RLS restricts rows; roles still need table access)
162
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO anon;
163
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO authenticated;
164
+ GRANT ALL ON ALL TABLES IN SCHEMA public TO service_role;
165
+ GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO authenticated, service_role;
166
+ -- Default privileges so tables created by the engine push inherit these grants
167
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO anon;
168
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO authenticated;
169
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO service_role;
170
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated, service_role;
171
+ `;
172
+ spawnSync(psqlBin, [...pgConnArgs, "-d", projectName, "-c", rolesSql], { stdio: "pipe", encoding: "utf8", env: pgEnv });
31
173
  }
32
- else {
33
- if (!existsSync(resolve(cwd, "docker-compose.yml"))) {
34
- console.error("docker-compose.yml not found. Run: supatype init");
35
- process.exit(1);
174
+ const LOCAL_JWT_SECRET = "super-secret-jwt-token-with-at-least-32-characters-long";
175
+ const authDbURL = dbURL.includes("?")
176
+ ? `${dbURL}&search_path=auth`
177
+ : `${dbURL}?search_path=auth`;
178
+ // ── 8. GoTrue migrations (auth.users before engine studio SQL) ─────────
179
+ console.log("[supatype] Running GoTrue migrations...");
180
+ const migrateEnv = gotrueMigrateEnv(serverPort, dbURL, LOCAL_JWT_SECRET);
181
+ runGotrueMigrations(serverBin, migrateEnv);
182
+ // ── 9. Engine: apply schema ───────────────────────────────────────────
183
+ const schemaPath = schemaPathFromProject(config, cwd);
184
+ const supatypeDir = join(cwd, ".supatype");
185
+ const manifestPath = join(supatypeDir, "manifest.json");
186
+ const adminConfigPath = join(supatypeDir, "admin-config.json");
187
+ mkdirSync(supatypeDir, { recursive: true });
188
+ const localStoragePath = config.storage?.provider !== "s3" ? join(stateRoot, "storage") : undefined;
189
+ // Native Postgres builds don't include PostGIS — skip geo fields rather than failing.
190
+ const skipFieldKinds = new Set(["geo", "vector"]);
191
+ await runSchemaPush(cwd, engineBin, schemaPath, dbURL, manifestPath, adminConfigPath, localStoragePath, skipFieldKinds, config).catch((e) => console.error("[supatype] Initial schema push failed:", e.message));
192
+ // ── 10. Spawn supatype-server ─────────────────────────────────────────
193
+ // Resolve edge functions config: only enable Deno if a functions dir exists.
194
+ const functionsDir = functionsPathCandidatesFromProject(config, cwd).find(dir => existsSync(dir));
195
+ const hasFunctionsDir = functionsDir !== undefined;
196
+ /** Always set when a functions dir exists so Studio admin API can list functions; Deno runtime is separate. */
197
+ const denoFunctionsDir = hasFunctionsDir ? functionsDir : "";
198
+ const functionRoutes = hasFunctionsDir && functionsDir !== undefined
199
+ ? discoverTsFunctionsInDir(functionsDir)
200
+ : [];
201
+ const denoServeScriptAbs = hasFunctionsDir && functionsDir !== undefined
202
+ ? (writeDevFunctionsRouter(cwd, functionsDir, functionRoutes) ?? "")
203
+ : "";
204
+ let denoBinPath;
205
+ if (hasFunctionsDir) {
206
+ console.log(`[supatype] Edge functions enabled (${functionsDir})`);
207
+ try {
208
+ denoBinPath = await ensureBinary("deno", config);
209
+ console.log(`[supatype] Deno runtime: ${denoBinPath} (v${config.versions.deno})`);
210
+ if (functionRoutes.length > 0) {
211
+ console.log(`[supatype] Edge functions router: ${relative(cwd, denoServeScriptAbs) || ".supatype/functions-router.ts"} ` +
212
+ `(${functionRoutes.length} function(s): ${functionRoutes.map(fn => fn.name).join(", ")})`);
213
+ }
214
+ else {
215
+ console.log("[supatype] Edge functions router not generated (no handler files discovered yet)");
216
+ }
36
217
  }
37
- console.log("Starting services...");
38
- const up = spawnSync("docker", ["compose", "up", "-d", "--wait"], { cwd, stdio: "inherit" });
39
- if (up.status !== 0) {
40
- console.error("docker compose up failed.");
41
- process.exit(1);
218
+ catch (err) {
219
+ console.warn(`[supatype] ⚠ Found ${functionsDir} but could not provision Deno v${config.versions.deno} edge functions will not run.\n` +
220
+ ` ${err.message}\n` +
221
+ " (Functions still appear in Studio; invocations need Deno.)");
42
222
  }
43
223
  }
44
- console.log("Waiting for PostgREST to be ready...");
45
- await waitForPostgREST();
46
- const children = [];
47
- if (opts.local) {
48
- console.log("\nStarting local services from source...");
49
- children.push(...startLocalServices(cwd));
224
+ // Matches GOTRUE_HOOK_SEND_EMAIL_SECRETS symmetric format (dev only). Override via .env.
225
+ const LOCAL_SEND_EMAIL_HOOK_SECRETS = "v1,whsec_abcdefghijklmnopqrstuvwxyz01234567";
226
+ const now = Math.floor(Date.now() / 1000);
227
+ const jwtBase = { iss: "supatype", iat: now, exp: now + 315_360_000 };
228
+ const anonKey = signJwt({ ...jwtBase, role: "anon" }, LOCAL_JWT_SECRET);
229
+ const serviceRoleKey = signJwt({ ...jwtBase, role: "service_role" }, LOCAL_JWT_SECRET);
230
+ const emailProvider = config.email?.provider ?? "console";
231
+ const gotrueMailerProvider = emailProvider === "console"
232
+ ? "console"
233
+ : emailProvider === "resend"
234
+ ? "resend"
235
+ : emailProvider === "ses"
236
+ ? "ses"
237
+ : "smtp";
238
+ const serverEnv = {
239
+ // supatype-server outer layer
240
+ SUPATYPE_MODE: config.server.mode ?? "dev",
241
+ SUPATYPE_MANIFEST_PATH: manifestPath,
242
+ SUPATYPE_ADMIN_CONFIG_PATH: adminConfigPath,
243
+ SUPATYPE_POSTGREST_URL: `http://127.0.0.1:${postgrestPort}`,
244
+ SUPATYPE_DENO_FUNCTIONS_DIR: denoFunctionsDir,
245
+ ...(denoFunctionsDir !== "" ? { SUPATYPE_SHARED_ENV_FILE: resolve(denoFunctionsDir, ".env.local") } : {}),
246
+ ...(denoBinPath !== undefined ? { SUPATYPE_DENO_PATH: denoBinPath } : {}),
247
+ ...(denoServeScriptAbs !== ""
248
+ ? { SUPATYPE_DENO_SERVE_SCRIPT: denoServeScriptAbs }
249
+ : {}),
250
+ SUPATYPE_URL: `http://localhost:${serverPort}`,
251
+ SUPATYPE_ANON_KEY: anonKey,
252
+ SUPATYPE_SERVICE_ROLE_KEY: serviceRoleKey,
253
+ PORT: serverPort,
254
+ SUPATYPE_APP_MODE: config.app.mode ?? "none",
255
+ ...(config.app.mode === "static" && config.app.static_dir?.trim()
256
+ ? { SUPATYPE_APP_STATIC_DIR: resolve(cwd, config.app.static_dir.trim()) }
257
+ : {}),
258
+ ...(config.app.mode === "proxy" && config.app.upstream?.trim()
259
+ ? { SUPATYPE_APP_UPSTREAM: config.app.upstream.trim() }
260
+ : {}),
261
+ ...(config.app.vite_dev_url !== undefined && config.app.vite_dev_url.trim() !== ""
262
+ ? { SUPATYPE_VITE_DEV_URL: config.app.vite_dev_url.trim() }
263
+ : {}),
264
+ // GoTrue required fields (sensible local-dev defaults)
265
+ GOTRUE_DB_DATABASE_URL: authDbURL,
266
+ DATABASE_URL: authDbURL,
267
+ SUPATYPE_SQL_DATABASE_URL: dbURL,
268
+ PGSSLMODE: "disable",
269
+ GOTRUE_DB_NAMESPACE: "auth",
270
+ GOTRUE_DB_DRIVER: "postgres",
271
+ GOTRUE_JWT_SECRET: LOCAL_JWT_SECRET,
272
+ GOTRUE_JWT_EXP: "3600",
273
+ GOTRUE_JWT_AUD: "authenticated",
274
+ GOTRUE_JWT_ADMIN_ROLES: "supatype_admin,service_role",
275
+ API_EXTERNAL_URL: `http://localhost:${serverPort}/auth/v1`,
276
+ GOTRUE_API_HOST: "localhost",
277
+ GOTRUE_SITE_URL: `http://localhost:${serverPort}`,
278
+ GOTRUE_MAILER_MAILER_PROVIDER: gotrueMailerProvider,
279
+ GOTRUE_MAILER_AUTOCONFIRM: "true",
280
+ GOTRUE_LOG_LEVEL: "info",
281
+ GOTRUE_DISABLE_SIGNUP: "false",
282
+ ...(config.email?.resend_api_key !== undefined && config.email.resend_api_key !== ""
283
+ ? { RESEND_API_KEY: config.email.resend_api_key }
284
+ : {}),
285
+ ...(gotrueMailerProvider === "resend" &&
286
+ config.email?.resend_from !== undefined &&
287
+ config.email.resend_from.trim() !== ""
288
+ ? { RESEND_FROM: config.email.resend_from.trim() }
289
+ : {}),
290
+ ...(gotrueMailerProvider === "ses" &&
291
+ config.email?.ses_from !== undefined &&
292
+ config.email.ses_from.trim() !== ""
293
+ ? { SES_FROM: config.email.ses_from.trim() }
294
+ : {}),
295
+ ...(gotrueMailerProvider === "smtp" ? gotrueSMTPFromEmailConfig(config.email) : {}),
296
+ ...(config.email?.send_email_hook === true
297
+ ? {
298
+ GOTRUE_HOOK_SEND_EMAIL_ENABLED: "true",
299
+ GOTRUE_HOOK_SEND_EMAIL_URI: config.email?.send_email_hook_uri !== undefined &&
300
+ config.email.send_email_hook_uri.trim() !== ""
301
+ ? config.email.send_email_hook_uri.trim()
302
+ : `http://127.0.0.1:${serverPort}/internal/v0hooks/send-email`,
303
+ GOTRUE_HOOK_SEND_EMAIL_SECRETS: config.email?.send_email_hook_secrets !== undefined &&
304
+ config.email.send_email_hook_secrets.trim() !== ""
305
+ ? config.email.send_email_hook_secrets.trim()
306
+ : LOCAL_SEND_EMAIL_HOOK_SECRETS,
307
+ }
308
+ : {}),
309
+ ...(config.storage?.provider !== "s3" ? localStorageEnv(stateRoot) : {}),
310
+ ...loadDotEnv(cwd),
311
+ };
312
+ const serverProc = new ProcessManager(serverBin, [], {
313
+ label: "server",
314
+ pidDir,
315
+ colour: "\x1b[32m",
316
+ env: serverEnv,
317
+ });
318
+ serverProc.start();
319
+ // ── 9b. PostgREST ────────────────────────────────────────────────────
320
+ let postgrestProc = null;
321
+ const postgrestBin = await resolvePostgrestBin(config.overrides?.postgrest);
322
+ if (postgrestBin) {
323
+ // Windows PostgREST builds are dynamically linked and require libpq/OpenSSL
324
+ // DLLs from a Postgres bin directory, even when the database runs in Docker.
325
+ let postgrestRuntimeBinDir = pgBinDir;
326
+ if (process.platform === "win32" && postgrestRuntimeBinDir === null) {
327
+ try {
328
+ postgrestRuntimeBinDir = await resolvePgBinDir(config);
329
+ }
330
+ catch (error) {
331
+ console.warn(`[supatype] ⚠ Could not resolve Postgres runtime DLL directory for PostgREST: ${error.message}\n` +
332
+ " PostgREST may fail to start on Windows until Postgres binaries are available locally.");
333
+ }
334
+ }
335
+ const postgrestEnv = {
336
+ PGRST_DB_URI: dbURL,
337
+ PGRST_DB_SCHEMA: "public, supatype, graphql_public",
338
+ PGRST_DB_ANON_ROLE: "anon",
339
+ PGRST_SERVER_PORT: postgrestPort,
340
+ PGRST_SERVER_HOST: "127.0.0.1",
341
+ PGRST_JWT_SECRET: serverEnv["GOTRUE_JWT_SECRET"] ?? "",
342
+ PGRST_LOG_LEVEL: "warn",
343
+ // On Windows, PostgREST (MinGW/GHC binary) needs libpq.dll and
344
+ // OpenSSL DLLs. Prepend a Postgres bin dir which bundles these
345
+ // runtime dependencies.
346
+ ...(process.platform === "win32" && postgrestRuntimeBinDir !== null
347
+ ? { PATH: `${postgrestRuntimeBinDir};${process.env["PATH"] ?? ""}` }
348
+ : {}),
349
+ };
350
+ const preflight = spawnSync(postgrestBin, ["--help"], { env: { ...process.env, ...postgrestEnv }, stdio: "pipe", encoding: "utf8" });
351
+ if (preflight.status !== 0) {
352
+ const detail = (preflight.stderr || preflight.stdout || "").trim();
353
+ console.warn(`[supatype] ⚠ PostgREST failed preflight (exit ${preflight.status}). ` +
354
+ "Skipping /rest/v1 startup to avoid crash loop.");
355
+ if (detail) {
356
+ console.warn(`[supatype] PostgREST preflight output:\n${detail}`);
357
+ }
358
+ }
359
+ else {
360
+ postgrestProc = new ProcessManager(postgrestBin, [], {
361
+ label: "postgrest",
362
+ pidDir,
363
+ colour: "\x1b[36m",
364
+ env: postgrestEnv,
365
+ });
366
+ postgrestProc.start();
367
+ }
50
368
  }
51
- console.log("\nServices running:");
52
- console.log(" Postgres postgresql://localhost:5432");
53
- console.log(" PostgREST http://localhost:3000");
54
- console.log(" Kong http://localhost:8000");
55
- console.log(" REST API http://localhost:8000/rest/v1/");
56
- console.log(" GraphQL http://localhost:8000/graphql/v1");
57
- if (opts.local) {
58
- console.log(" Storage http://localhost:5000 (from source)");
59
- console.log(" Realtime http://localhost:4000 (from source)");
60
- console.log(" Studio http://localhost:3002 (from source)");
369
+ // ── 9d. Studio (optional) ─────────────────────────────────────────────
370
+ const studioPort = 3002;
371
+ let studioProc = null;
372
+ const studioOverride = config.overrides?.studio;
373
+ if (studioOverride) {
374
+ studioProc = startStudioViteDevServer({
375
+ cwd,
376
+ studioOverride,
377
+ pidDir,
378
+ serviceRoleKey,
379
+ proxyTarget: `http://localhost:${serverPort}`,
380
+ viteSupatypeUrl: `http://localhost:${studioPort}`,
381
+ });
382
+ studioProc?.start();
61
383
  }
62
- console.log();
63
- // Clean shutdown on Ctrl+C
64
- const cleanup = () => {
65
- for (const child of children) {
66
- child.kill();
67
- }
384
+ const appProc = startProxyDevApp(cwd, config, pidDir);
385
+ // ── Print status ──────────────────────────────────────────────────────
386
+ console.log(`
387
+ [supatype] Services running:
388
+ Postgres ${dbURL}
389
+ supatype-server http://localhost:${serverPort}
390
+ REST API http://localhost:${serverPort}/rest/v1/
391
+ Auth http://localhost:${serverPort}/auth/v1/
392
+ Storage http://localhost:${serverPort}/storage/v1/
393
+ Realtime ws://localhost:${serverPort}/realtime/v1/${studioProc ? `\n Studio http://localhost:${studioPort}` : ""}
394
+
395
+ API keys (local dev only):
396
+ anon key ${anonKey}
397
+ service_role ${serviceRoleKey}
398
+
399
+ JWT secret: ${LOCAL_JWT_SECRET}
400
+
401
+ Press Ctrl+C to stop.
402
+ `);
403
+ // ── Shutdown handler ──────────────────────────────────────────────────
404
+ const cleanup = async () => {
405
+ console.log("\n[supatype] Shutting down...");
406
+ await Promise.all([
407
+ serverProc.stop(),
408
+ postgrestProc?.stop(),
409
+ studioProc?.stop(),
410
+ appProc?.stop(),
411
+ ]);
412
+ await stopPostgres();
68
413
  process.exit(0);
69
414
  };
70
- process.on("SIGINT", cleanup);
71
- process.on("SIGTERM", cleanup);
415
+ process.once("SIGINT", cleanup);
416
+ process.once("SIGTERM", cleanup);
417
+ // ── 10. Schema watch ──────────────────────────────────────────────────
72
418
  if (opts.watch) {
73
- await watchAndPush(cwd);
419
+ const schemaDir = resolve(cwd, schemaPath, "..");
420
+ console.log(`[supatype] Watching ${schemaDir} for changes...`);
421
+ const { watch } = await import("node:fs");
422
+ // Debounce: Windows fs.watch fires multiple events per save.
423
+ // Wait 300 ms after the last event before pushing.
424
+ let debounceTimer = null;
425
+ watch(schemaDir, { recursive: true }, (_eventType, filename) => {
426
+ if (!filename?.endsWith(".ts"))
427
+ return;
428
+ if (debounceTimer)
429
+ clearTimeout(debounceTimer);
430
+ debounceTimer = setTimeout(() => {
431
+ debounceTimer = null;
432
+ console.log(`\n[supatype] Change detected in ${filename}, checking schema...`);
433
+ runSchemaPush(cwd, engineBin, schemaPath, dbURL, manifestPath, adminConfigPath, localStoragePath, skipFieldKinds, config).catch((e) => console.error("[supatype] Schema push failed:", e.message));
434
+ }, 300);
435
+ });
74
436
  }
437
+ // Block until killed.
438
+ await new Promise(() => undefined);
75
439
  });
76
440
  }
77
- function ensureInfraCompose(cwd) {
78
- const projectName = resolve(cwd).split(/[\\/]/).pop() ?? "supatype";
79
- const content = `# Generated by supatype dev --local — infra services only
80
- services:
81
- db:
82
- image: supatype/postgres:17-latest
83
- environment:
84
- POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
85
- POSTGRES_DB: \${POSTGRES_DB:-${projectName}}
86
- ports:
87
- - "5432:5432"
88
- volumes:
89
- - db-data:/var/lib/postgresql/data
90
- healthcheck:
91
- test: ["CMD-SHELL", "pg_isready -U postgres"]
92
- interval: 5s
93
- timeout: 5s
94
- retries: 20
95
-
96
- pgbouncer:
97
- image: pgbouncer/pgbouncer:latest
98
- volumes:
99
- - ./.supatype/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro
100
- - ./.supatype/userlist.txt:/etc/pgbouncer/userlist.txt:ro
101
- depends_on:
102
- db:
103
- condition: service_healthy
104
- healthcheck:
105
- test: ["CMD", "pg_isready", "-h", "localhost", "-p", "6432", "-U", "postgres"]
106
- interval: 5s
107
- timeout: 5s
108
- retries: 10
109
-
110
- gotrue:
111
- image: supatype/auth:v1.0.0
112
- environment:
113
- GOTRUE_API_HOST: 0.0.0.0
114
- GOTRUE_API_PORT: 9999
115
- GOTRUE_DB_DRIVER: postgres
116
- GOTRUE_DB_DATABASE_URL: "postgres://postgres:\${POSTGRES_PASSWORD:-postgres}@pgbouncer:6432/\${POSTGRES_DB:-${projectName}}?search_path=auth"
117
- GOTRUE_SITE_URL: \${SITE_URL:-http://localhost:3000}
118
- GOTRUE_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
119
- GOTRUE_JWT_EXP: 3600
120
- GOTRUE_JWT_AUD: authenticated
121
- GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
122
- GOTRUE_JWT_ADMIN_ROLES: service_role
123
- GOTRUE_MAILER_AUTOCONFIRM: \${GOTRUE_MAILER_AUTOCONFIRM:-true}
124
- GOTRUE_DISABLE_SIGNUP: \${DISABLE_SIGNUP:-false}
125
- ports:
126
- - "9999:9999"
127
- depends_on:
128
- pgbouncer:
129
- condition: service_healthy
130
-
131
- postgrest:
132
- image: postgrest/postgrest:v12.2.8
133
- environment:
134
- PGRST_DB_URI: postgresql://authenticator:\${POSTGRES_PASSWORD:-postgres}@pgbouncer:6432/\${POSTGRES_DB:-${projectName}}
135
- PGRST_DB_SCHEMA: public
136
- PGRST_DB_ANON_ROLE: anon
137
- PGRST_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
138
- PGRST_DB_EXTRA_SEARCH_PATH: public,extensions
139
- PGRST_DB_POOL: 3
140
- ports:
141
- - "3000:3000"
142
- depends_on:
143
- pgbouncer:
144
- condition: service_healthy
145
-
146
- minio:
147
- image: minio/minio:RELEASE.2024-11-07T00-52-20Z
148
- command: server /data --console-address ":9001"
149
- environment:
150
- MINIO_ROOT_USER: supatype
151
- MINIO_ROOT_PASSWORD: supatype-secret
152
- ports:
153
- - "9000:9000"
154
- - "9001:9001"
155
- volumes:
156
- - minio-data:/data
157
- healthcheck:
158
- test: ["CMD", "mc", "ready", "local"]
159
- interval: 5s
160
- timeout: 5s
161
- retries: 10
162
-
163
- kong:
164
- image: kong:3.6
165
- environment:
166
- KONG_DATABASE: "off"
167
- KONG_DECLARATIVE_CONFIG: /etc/kong/kong.yml
168
- KONG_PROXY_ACCESS_LOG: /dev/stdout
169
- KONG_ADMIN_ACCESS_LOG: /dev/stdout
170
- KONG_PROXY_ERROR_LOG: /dev/stderr
171
- KONG_ADMIN_ERROR_LOG: /dev/stderr
172
- volumes:
173
- - ./.supatype/kong.yml:/etc/kong/kong.yml:ro
174
- ports:
175
- - "8000:8000"
176
- depends_on:
177
- - postgrest
178
- - gotrue
179
-
180
- volumes:
181
- db-data:
182
- minio-data:
183
- `;
184
- writeFileSync(resolve(cwd, "docker-compose.yml"), content, "utf8");
185
- console.log(" created docker-compose.yml (infra only)\n");
186
- // Also ensure pgbouncer config exists
187
- const supatypeDir = resolve(cwd, ".supatype");
188
- mkdirSync(supatypeDir, { recursive: true });
189
- if (!existsSync(resolve(supatypeDir, "pgbouncer.ini"))) {
190
- writeFileSync(resolve(supatypeDir, "pgbouncer.ini"), `[databases]
191
- * = host=db port=5432
192
-
193
- [pgbouncer]
194
- listen_addr = 0.0.0.0
195
- listen_port = 6432
196
- auth_type = trust
197
- auth_file = /etc/pgbouncer/userlist.txt
198
- pool_mode = transaction
199
- default_pool_size = 20
200
- max_db_connections = 60
201
- max_client_conn = 100
202
- server_reset_query = DEALLOCATE ALL
203
- ignore_startup_parameters = extra_float_digits
204
- `, "utf8");
441
+ // ---------------------------------------------------------------------------
442
+ // Schema push (engine subprocess)
443
+ // ---------------------------------------------------------------------------
444
+ // Last successfully-pushed AST JSON — used to skip no-op re-fires.
445
+ let _lastPushedAst = null;
446
+ // AST that failed on its last attempt — always retried even if content is unchanged.
447
+ let _lastFailedAst = null;
448
+ async function runSchemaPush(cwd, engineBin, schemaPath, dbURL, manifestPath, adminConfigPath, storagePath, skipFieldKinds, config) {
449
+ // Build AST JSON from schema file.
450
+ const { loadSchemaAst } = await import("../config.js");
451
+ let ast = loadSchemaAst(schemaPath, cwd);
452
+ // Strip fields whose kind requires an unavailable Postgres extension.
453
+ if (skipFieldKinds && skipFieldKinds.size > 0) {
454
+ const { filtered, adapted } = adaptUnsupportedKinds(ast, skipFieldKinds);
455
+ ast = filtered;
456
+ if (adapted.length > 0) {
457
+ console.warn(`[supatype] ⚠ ${adapted.length} field(s) replaced with JSONB — required extensions not available:\n` +
458
+ adapted.map((s) => ` ${s}`).join("\n"));
459
+ }
205
460
  }
206
- if (!existsSync(resolve(supatypeDir, "userlist.txt"))) {
207
- writeFileSync(resolve(supatypeDir, "userlist.txt"), "", "utf8");
461
+ const astJson = JSON.stringify(ast);
462
+ // Skip only when the last push of this exact AST succeeded.
463
+ // If it previously failed we always retry so the user can trigger a re-run
464
+ // by simply saving the file again without needing to make a content change.
465
+ if (astJson === _lastPushedAst && astJson !== _lastFailedAst) {
466
+ return;
208
467
  }
209
- if (!existsSync(resolve(supatypeDir, "kong.yml"))) {
210
- writeFileSync(resolve(supatypeDir, "kong.yml"), `_format_version: "3.0"
211
-
212
- services:
213
- - name: rest-v1
214
- url: http://postgrest:3000
215
- routes:
216
- - name: rest-v1-all
217
- strip_path: true
218
- paths:
219
- - /rest/v1/
220
- - name: auth-v1
221
- url: http://gotrue:9999
222
- routes:
223
- - name: auth-v1-all
224
- strip_path: true
225
- paths:
226
- - /auth/v1/
227
- - name: storage-v1
228
- url: http://host.docker.internal:5000
229
- routes:
230
- - name: storage-v1-all
231
- strip_path: true
232
- paths:
233
- - /storage/v1/
234
- - name: realtime-v1
235
- url: http://host.docker.internal:4000
236
- routes:
237
- - name: realtime-v1-all
238
- strip_path: true
239
- paths:
240
- - /realtime/v1/
241
- protocols:
242
- - http
243
- - https
244
- - ws
245
- - wss
246
- - name: functions-v1
247
- url: http://host.docker.internal:54321
248
- routes:
249
- - name: functions-v1-all
250
- strip_path: false
251
- paths:
252
- - /functions/v1/
253
- - name: studio
254
- url: http://host.docker.internal:3002
255
- routes:
256
- - name: studio-all
257
- strip_path: true
258
- paths:
259
- - /studio/
260
- `, "utf8");
468
+ const astPath = join(cwd, ".supatype", "schema.ast.json");
469
+ writeFileSync(astPath, astJson);
470
+ // Push schema.
471
+ console.log("[supatype] Applying schema...");
472
+ const pushResult = spawnSync(engineBin, ["push", "-i", astPath, "--database-url", dbURL, "--force", "--non-interactive"], { cwd, stdio: "inherit", encoding: "utf8" });
473
+ if (pushResult.status !== 0) {
474
+ _lastFailedAst = astJson;
475
+ throw new Error(`Engine schema push failed (exit ${pushResult.status})`);
261
476
  }
477
+ _lastPushedAst = astJson;
478
+ _lastFailedAst = null;
479
+ // Provision storage buckets declared in the schema.
480
+ if (storagePath) {
481
+ const parseResult = spawnSync(engineBin, ["parse", "-i", astPath], { cwd, stdio: "pipe", encoding: "utf8" });
482
+ if (parseResult.status === 0 && parseResult.stdout) {
483
+ try {
484
+ const resolvedAst = JSON.parse(parseResult.stdout);
485
+ if (resolvedAst.storageBuckets && resolvedAst.storageBuckets.length > 0) {
486
+ provisionStorageBuckets(resolvedAst.storageBuckets, storagePath);
487
+ }
488
+ }
489
+ catch { /* ignore parse errors */ }
490
+ }
491
+ }
492
+ // Generate manifest.
493
+ const genResult = spawnSync(engineBin, ["generate", "-i", astPath, "-o", manifestPath], { cwd, stdio: "pipe", encoding: "utf8" });
494
+ if (genResult.status !== 0) {
495
+ console.warn("[supatype] Manifest generation failed — server routing may be stale.");
496
+ }
497
+ // Generate admin config (for Studio). Engine writes to stdout.
498
+ if (adminConfigPath) {
499
+ const adminResult = spawnSync(engineBin, ["admin", "-i", astPath], { cwd, stdio: "pipe", encoding: "utf8" });
500
+ if (adminResult.status === 0 && adminResult.stdout) {
501
+ const { withAdminRoles } = await import("../studio-admin-roles.js");
502
+ let admin;
503
+ try {
504
+ admin = JSON.parse(adminResult.stdout);
505
+ }
506
+ catch {
507
+ admin = adminResult.stdout;
508
+ }
509
+ const merged = config ? withAdminRoles(admin, config) : admin;
510
+ writeFileSync(adminConfigPath, `${JSON.stringify(merged, null, 2)}\n`);
511
+ }
512
+ }
513
+ console.log("[supatype] Schema applied.");
262
514
  }
263
- function ensureDevEnv(cwd) {
264
- const envPath = resolve(cwd, ".env");
265
- if (existsSync(envPath))
266
- return;
267
- const projectName = resolve(cwd).split(/[\\/]/).pop() ?? "supatype";
268
- const content = `# Generated by supatype dev — all defaults for local development
269
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${projectName}
270
- POSTGRES_PASSWORD=postgres
271
- POSTGRES_DB=${projectName}
272
-
273
- JWT_SECRET=super-secret-jwt-token-change-in-production
274
- ANON_KEY=
275
- SERVICE_ROLE_KEY=
276
-
277
- SITE_URL=http://localhost:3000
278
-
279
- # Storage (MinIO)
280
- S3_ENDPOINT=http://localhost:9000
281
- S3_REGION=us-east-1
282
- S3_ACCESS_KEY=supatype
283
- S3_SECRET_KEY=supatype-secret
284
- S3_FORCE_PATH_STYLE=true
285
-
286
- # SMTP — leave empty for email autoconfirm in dev
287
- SMTP_HOST=
288
- SMTP_PORT=
289
- SMTP_USER=
290
- SMTP_PASS=
291
- `;
292
- writeFileSync(envPath, content, "utf8");
293
- console.log(" created .env (local dev defaults)\n");
294
- }
295
- function loadDotEnv(cwd) {
296
- const envPath = resolve(cwd, ".env");
297
- if (!existsSync(envPath))
298
- return {};
299
- const vars = {};
300
- for (const line of readFileSync(envPath, "utf8").split("\n")) {
301
- const trimmed = line.trim();
302
- if (!trimmed || trimmed.startsWith("#"))
303
- continue;
304
- const eq = trimmed.indexOf("=");
305
- if (eq === -1)
515
+ // ---------------------------------------------------------------------------
516
+ // Storage bucket provisioning (local dev only)
517
+ // ---------------------------------------------------------------------------
518
+ function provisionStorageBuckets(declared, storagePath) {
519
+ const bucketsDir = join(storagePath, ".supatype");
520
+ const bucketsFile = join(bucketsDir, "buckets.json");
521
+ mkdirSync(bucketsDir, { recursive: true });
522
+ let existing = [];
523
+ try {
524
+ existing = JSON.parse(readFileSync(bucketsFile, "utf8"));
525
+ }
526
+ catch { /* file doesn't exist yet */ }
527
+ const existingIds = new Set(existing.map((b) => b["id"]));
528
+ let added = 0;
529
+ for (const bucket of declared) {
530
+ if (existingIds.has(bucket.id))
306
531
  continue;
307
- const key = trimmed.slice(0, eq);
308
- const value = trimmed.slice(eq + 1);
309
- vars[key] = value;
532
+ const now = new Date().toISOString();
533
+ existing.push({
534
+ id: bucket.id,
535
+ name: bucket.id,
536
+ public: bucket.public,
537
+ file_size_limit: bucket.fileSizeLimit ?? null,
538
+ allowed_mime_types: bucket.allowedMimeTypes ?? null,
539
+ access_mode: bucket.accessMode ?? (bucket.public ? "public" : "private"),
540
+ s3_bucket_policy: bucket.s3BucketPolicy ?? null,
541
+ created_at: now,
542
+ updated_at: now,
543
+ });
544
+ mkdirSync(join(storagePath, bucket.id), { recursive: true });
545
+ added++;
546
+ }
547
+ if (added > 0) {
548
+ writeFileSync(bucketsFile, JSON.stringify(existing, null, 2));
549
+ console.log(`[supatype] Storage: provisioned ${added} bucket(s).`);
310
550
  }
311
- return vars;
312
551
  }
313
- function localDevDefaults(cwd) {
314
- const projectName = resolve(cwd).split(/[\\/]/).pop() ?? "supatype";
315
- // Known defaults that match docker-compose local dev setup
316
- const defaults = {
317
- DATABASE_URL: `postgresql://postgres:postgres@localhost:5432/${projectName}`,
318
- POSTGRES_PASSWORD: "postgres",
319
- POSTGRES_DB: projectName,
320
- JWT_SECRET: "super-secret-jwt-token-change-in-production",
321
- SITE_URL: "http://localhost:3000",
322
- S3_ENDPOINT: "http://localhost:9000",
323
- S3_REGION: "us-east-1",
324
- S3_ACCESS_KEY: "supatype",
325
- S3_SECRET_KEY: "supatype-secret",
326
- S3_FORCE_PATH_STYLE: "true",
327
- SLOT_NAME: "realtime_slot",
328
- REPLICATION_POLL_INTERVAL: "100",
329
- SECURE_CHANNELS: "true",
330
- };
331
- // .env file values override defaults
332
- const dotEnv = loadDotEnv(cwd);
333
- return { ...defaults, ...dotEnv };
552
+ // ---------------------------------------------------------------------------
553
+ // Resolve Postgres bin dir
554
+ // ---------------------------------------------------------------------------
555
+ async function resolvePgBinDir(config) {
556
+ const override = config.overrides?.postgres_dir;
557
+ if (override) {
558
+ // Normalize Git Bash (/c/Users/...) paths to Win32 form (C:\Users\...) on Windows.
559
+ const normalised = normalisePlatformPath(override);
560
+ const resolved = resolve(process.cwd(), normalised);
561
+ const binDir = join(resolved, "bin");
562
+ if (!existsSync(binDir)) {
563
+ throw new Error(`[overrides] postgres_dir does not contain a bin/ directory: ${resolved}`);
564
+ }
565
+ console.warn(`\u26a0 Using local Postgres build: ${resolved}`);
566
+ return binDir;
567
+ }
568
+ // Locate cached Postgres archive.
569
+ const { cachePath } = await import("../binary-cache.js");
570
+ const version = config.versions.postgres;
571
+ const { currentPlatform } = await import("../binary-cache.js");
572
+ const platform = currentPlatform();
573
+ const pgCacheDir = cachePath("postgres", version);
574
+ const extractedDir = join(pgCacheDir, `pg-${version}`);
575
+ const pgCtlName = platform.os === "windows" ? "pg_ctl.exe" : "pg_ctl";
576
+ if (!existsSync(join(extractedDir, "bin", pgCtlName))) {
577
+ // Try to extract the cached archive.
578
+ await extractPostgresArchive(pgCacheDir, version, platform, extractedDir);
579
+ }
580
+ return join(extractedDir, "bin");
334
581
  }
335
- function startLocalServices(cwd) {
336
- const children = [];
337
- const devEnv = localDevDefaults(cwd);
338
- const services = [
339
- { name: "storage", filter: "@supatype/storage", color: "\x1b[34m" },
340
- { name: "realtime", filter: "@supatype/realtime", color: "\x1b[35m" },
341
- { name: "studio", filter: "@supatype/studio", color: "\x1b[36m" },
342
- ];
343
- for (const svc of services) {
344
- const pkgDir = resolve(cwd, "..", "packages", svc.name);
345
- if (!existsSync(resolve(pkgDir, "package.json"))) {
346
- console.warn(` Skipping ${svc.name} not found at ${pkgDir}`);
347
- continue;
582
+ async function extractPostgresArchive(pgCacheDir, version, platform, extractDir) {
583
+ const ext = platform.os === "windows" ? ".zip" : ".tar.gz";
584
+ const archiveName = `supatype-pg-${postgresArchiveTag(version)}-${platform.os}-${platform.arch}${ext}`;
585
+ const archivePath = join(pgCacheDir, archiveName);
586
+ if (!existsSync(archivePath)) {
587
+ throw new Error(`Postgres ${version} archive not found. Run: supatype update`);
588
+ }
589
+ mkdirSync(extractDir, { recursive: true });
590
+ // On Windows, Git Bash tar is typically first in PATH and chokes on drive-letter
591
+ // paths (C:\...). Use PowerShell's Expand-Archive instead, which handles Windows
592
+ // paths natively. On Linux/macOS, use tar as normal.
593
+ const result = platform.os === "windows"
594
+ ? spawnSync("powershell.exe", ["-NoProfile", "-Command", `Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`], { stdio: "inherit" })
595
+ : spawnSync("tar", ["-xzf", archivePath, "-C", extractDir], { stdio: "inherit" });
596
+ if (result.status !== 0) {
597
+ throw new Error(`Failed to extract Postgres archive: ${archivePath}`);
598
+ }
599
+ }
600
+ // ---------------------------------------------------------------------------
601
+ // PostgREST resolver — downloads from GitHub releases if not cached
602
+ // ---------------------------------------------------------------------------
603
+ const POSTGREST_DEFAULT_VERSION = "12.2.3";
604
+ const POSTGREST_GITHUB = "https://github.com/PostgREST/postgrest/releases/download";
605
+ async function resolvePostgrestBin(overridePath) {
606
+ // Honour local override (same pattern as engine/server).
607
+ if (overridePath) {
608
+ let p = resolve(process.cwd(), normalisePlatformPath(overridePath));
609
+ if (process.platform === "win32" && !p.endsWith(".exe") && !existsSync(p)) {
610
+ const withExe = p + ".exe";
611
+ if (existsSync(withExe))
612
+ p = withExe;
348
613
  }
349
- const reset = "\x1b[0m";
350
- const prefix = `${svc.color}[${svc.name}]${reset}`;
351
- const child = spawn("pnpm", ["dev"], {
352
- cwd: pkgDir,
353
- stdio: ["ignore", "pipe", "pipe"],
354
- shell: true,
355
- env: {
356
- ...process.env,
357
- ...devEnv,
358
- PORT: svc.name === "storage" ? "5000" : svc.name === "realtime" ? "4000" : "3002",
359
- },
360
- });
361
- child.stdout?.on("data", (data) => {
362
- for (const line of data.toString().trimEnd().split("\n")) {
363
- console.log(`${prefix} ${line}`);
364
- }
365
- });
366
- child.stderr?.on("data", (data) => {
367
- for (const line of data.toString().trimEnd().split("\n")) {
368
- console.error(`${prefix} ${line}`);
369
- }
370
- });
371
- child.on("exit", (code) => {
372
- if (code !== 0 && code !== null) {
373
- console.error(`${prefix} exited with code ${code}`);
614
+ if (existsSync(p))
615
+ return p;
616
+ console.warn(`[supatype] ⚠ PostgREST override not found at ${p}`);
617
+ return null;
618
+ }
619
+ const version = POSTGREST_DEFAULT_VERSION;
620
+ const platform = currentPlatform();
621
+ const arch = platform.arch === "arm64" ? "aarch64" : "x86_64";
622
+ const binName = platform.os === "windows" ? "postgrest.exe" : "postgrest";
623
+ const cacheDir = cachePath("postgres", version).replace(/postgres/, "postgrest");
624
+ const binPath = join(cacheDir, binName);
625
+ const archiveName = platform.os === "windows"
626
+ ? `postgrest-v${version}-windows-x64.zip`
627
+ : platform.os === "darwin"
628
+ ? `postgrest-v${version}-macos-${arch}.tar.xz`
629
+ : `postgrest-v${version}-linux-static-${arch}.tar.xz`;
630
+ const archivePath = join(cacheDir, archiveName);
631
+ if (existsSync(binPath)) {
632
+ // Backfill DLLs for older cached Windows installs where only postgrest.exe
633
+ // was copied from the release archive.
634
+ if (platform.os === "windows" && !hasLikelyWindowsRuntimeDlls(cacheDir) && existsSync(archivePath)) {
635
+ const repaired = repairWindowsPostgrestRuntime(cacheDir, archivePath, binPath);
636
+ if (!repaired) {
637
+ console.warn("[supatype] ⚠ PostgREST runtime DLL repair failed; REST API may be unavailable.");
374
638
  }
375
- });
376
- children.push(child);
377
- console.log(` ${prefix} started (pnpm dev)`);
639
+ }
640
+ return binPath;
378
641
  }
379
- return children;
380
- }
381
- async function waitForPostgREST() {
382
- const deadline = Date.now() + HEALTH_TIMEOUT_MS;
383
- while (Date.now() < deadline) {
384
- try {
385
- const res = await fetch(POSTGREST_URL, { signal: AbortSignal.timeout(2000) });
386
- if (res.ok || res.status === 401)
387
- return; // 401 = JWT required = server up
642
+ // Download from GitHub releases.
643
+ const url = `${POSTGREST_GITHUB}/v${version}/${archiveName}`;
644
+ console.log(`[supatype] Downloading PostgREST v${version}...`);
645
+ mkdirSync(cacheDir, { recursive: true });
646
+ let resp;
647
+ try {
648
+ resp = await fetch(url);
649
+ }
650
+ catch (e) {
651
+ console.warn(`[supatype] ⚠ Could not download PostgREST (${e.message}).\n` +
652
+ ` REST API (/rest/v1/) will be unavailable until the download succeeds.\n` +
653
+ ` Re-run 'supatype dev' once network access to github.com:443 is restored.`);
654
+ return null;
655
+ }
656
+ if (!resp.ok) {
657
+ console.warn(`[supatype] ⚠ Could not download PostgREST: HTTP ${resp.status}. REST API will be unavailable.`);
658
+ return null;
659
+ }
660
+ const buf = Buffer.from(await resp.arrayBuffer());
661
+ writeFileSync(archivePath, buf);
662
+ // Extract. The Windows zip may nest postgrest.exe inside a subdirectory, so
663
+ // after Expand-Archive we copy postgrest.exe and sibling DLLs to cacheDir.
664
+ if (platform.os === "windows") {
665
+ const r = spawnSync("powershell.exe", [
666
+ "-NoProfile", "-Command",
667
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${cacheDir}' -Force; ` +
668
+ `$exe = Get-ChildItem -Path '${cacheDir}' -Recurse -Filter 'postgrest.exe' | Select-Object -First 1; ` +
669
+ `if ($exe) { ` +
670
+ ` Copy-Item -Path $exe.FullName -Destination '${binPath}' -Force; ` +
671
+ ` Get-ChildItem -Path $exe.Directory.FullName -Filter '*.dll' | ` +
672
+ ` ForEach-Object { Copy-Item -Path $_.FullName -Destination '${cacheDir}' -Force }; ` +
673
+ `}`,
674
+ ], { stdio: "pipe", encoding: "utf8" });
675
+ if (r.status !== 0) {
676
+ console.warn(`[supatype] ⚠ PostgREST extraction failed: ${r.stderr?.trim() ?? "unknown error"}. REST API will be unavailable.`);
677
+ return null;
388
678
  }
389
- catch {
390
- // not ready yet
679
+ }
680
+ else {
681
+ const r = spawnSync("tar", ["-xJf", archivePath, "-C", cacheDir], { stdio: "pipe" });
682
+ if (r.status !== 0) {
683
+ console.warn("[supatype] ⚠ PostgREST extraction failed. REST API will be unavailable.");
684
+ return null;
391
685
  }
392
- await sleep(HEALTH_POLL_MS);
393
686
  }
394
- throw new Error(`PostgREST did not become healthy within ${HEALTH_TIMEOUT_MS / 1000}s.\n` +
395
- "Check: docker compose logs postgrest");
687
+ if (!existsSync(binPath)) {
688
+ console.warn("[supatype] ⚠ PostgREST binary not found after extraction. REST API will be unavailable.");
689
+ return null;
690
+ }
691
+ if (platform.os !== "windows") {
692
+ const { chmod } = await import("node:fs/promises");
693
+ await chmod(binPath, 0o755);
694
+ }
695
+ console.log(`[supatype] PostgREST v${version} ready.`);
696
+ return binPath;
396
697
  }
397
- async function watchAndPush(cwd) {
398
- const config = loadConfig(cwd);
399
- const schemaDir = resolve(cwd, config.schema, "..");
400
- console.log(`Watching ${schemaDir} for changes... (Ctrl+C to stop)\n`);
401
- // Initial push on start
402
- await runPush(cwd);
403
- const { watch } = await import("node:fs");
404
- watch(schemaDir, { recursive: true }, (eventType, filename) => {
405
- if (!filename?.endsWith(".ts"))
406
- return;
407
- console.log(`\nChange detected in ${filename}, pushing...`);
408
- runPush(cwd).catch((e) => console.error("Push failed:", e.message));
698
+ function hasLikelyWindowsRuntimeDlls(dir) {
699
+ if (process.platform !== "win32")
700
+ return true;
701
+ return (existsSync(join(dir, "libpq.dll")) ||
702
+ existsSync(join(dir, "libpq-5.dll")) ||
703
+ existsSync(join(dir, "libssl-3-x64.dll")));
704
+ }
705
+ function repairWindowsPostgrestRuntime(cacheDir, archivePath, binPath) {
706
+ const r = spawnSync("powershell.exe", [
707
+ "-NoProfile",
708
+ "-Command",
709
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${cacheDir}' -Force; ` +
710
+ `$exe = Get-ChildItem -Path '${cacheDir}' -Recurse -Filter 'postgrest.exe' | Select-Object -First 1; ` +
711
+ `if ($exe) { ` +
712
+ ` Copy-Item -Path $exe.FullName -Destination '${binPath}' -Force; ` +
713
+ ` Get-ChildItem -Path $exe.Directory.FullName -Filter '*.dll' | ` +
714
+ ` ForEach-Object { Copy-Item -Path $_.FullName -Destination '${cacheDir}' -Force }; ` +
715
+ `}`,
716
+ ], { stdio: "pipe", encoding: "utf8" });
717
+ return r.status === 0;
718
+ }
719
+ // Field kinds that require Postgres extensions not available in all builds.
720
+ // Maps kind → { extension name, JSONB fallback AST }
721
+ const EXTENSION_FIELDS = {
722
+ geo: { ext: "PostGIS", fallback: { kind: "json", pgType: "JSONB" } },
723
+ vector: { ext: "pgvector", fallback: { kind: "json", pgType: "JSONB" } },
724
+ };
725
+ function adaptUnsupportedKinds(ast, skipKinds) {
726
+ const adapted = [];
727
+ if (!ast || typeof ast !== "object")
728
+ return { filtered: ast, adapted };
729
+ const schema = ast;
730
+ if (!Array.isArray(schema.models))
731
+ return { filtered: ast, adapted };
732
+ const models = schema.models.map((model) => {
733
+ const fields = {};
734
+ for (const [name, field] of Object.entries(model.fields ?? {})) {
735
+ const info = skipKinds.has(field.kind) ? EXTENSION_FIELDS[field.kind] : undefined;
736
+ if (info) {
737
+ fields[name] = { ...info.fallback, required: field.required ?? false };
738
+ adapted.push(`${model.name}.${name} (${info.ext} → JSONB)`);
739
+ }
740
+ else {
741
+ fields[name] = field;
742
+ }
743
+ }
744
+ return { ...model, fields };
409
745
  });
410
- // Block forever
411
- await new Promise(() => undefined);
746
+ return { filtered: { ...schema, models }, adapted };
412
747
  }
413
- async function runPush(cwd) {
414
- const { loadConfig, loadSchemaAst } = await import("../config.js");
415
- const config = loadConfig(cwd);
416
- const ast = loadSchemaAst(config.schema, cwd);
417
- await ensureEngine();
418
- const result = invokeEngine(["migrate", "--connection", config.connection], JSON.stringify(ast));
419
- if (result.exitCode !== 0) {
420
- console.error(result.stderr || result.stdout);
421
- return;
748
+ // ---------------------------------------------------------------------------
749
+ // .env loader
750
+ // ---------------------------------------------------------------------------
751
+ /** Minimal GoTrue env for `migrate` (matches required fields in serverEnv below). */
752
+ function gotrueMigrateEnv(serverPort, sqlDbURL, jwtSecret) {
753
+ const base = `http://localhost:${serverPort}`;
754
+ return {
755
+ // envconfig: gotrue + DB.DATABASE_URL GOTRUE_DB_DATABASE_URL
756
+ GOTRUE_DB_DATABASE_URL: sqlDbURL,
757
+ DATABASE_URL: sqlDbURL,
758
+ GOTRUE_DB_DRIVER: "postgres",
759
+ GOTRUE_DB_NAMESPACE: "auth",
760
+ PGSSLMODE: "disable",
761
+ GOTRUE_JWT_SECRET: jwtSecret,
762
+ API_EXTERNAL_URL: `${base}/auth/v1`,
763
+ GOTRUE_API_HOST: "localhost",
764
+ GOTRUE_SITE_URL: base,
765
+ GOTRUE_MAILER_AUTOCONFIRM: "true",
766
+ };
767
+ }
768
+ /** Apply GoTrue DDL (auth.users, etc.) before engine push references auth schema. */
769
+ function runGotrueMigrations(serverBin, migrateEnv) {
770
+ const result = spawnSync(serverBin, ["migrate"], {
771
+ stdio: "pipe",
772
+ encoding: "utf8",
773
+ env: {
774
+ ...process.env,
775
+ ...migrateEnv,
776
+ },
777
+ });
778
+ if (result.status !== 0) {
779
+ const detail = (result.stderr ?? result.stdout ?? "").trim();
780
+ throw new Error(`GoTrue migrations failed (exit ${result.status ?? "unknown"})` +
781
+ (detail ? `:\n${detail}` : ""));
422
782
  }
423
- console.log(result.stdout || "Schema up to date.");
424
783
  }
425
- function sleep(ms) {
426
- return new Promise((r) => setTimeout(r, ms));
784
+ function loadDotEnv(cwd) {
785
+ const candidates = [resolve(cwd, ".env"), resolve(cwd, ".env.local")];
786
+ const vars = {};
787
+ for (const envPath of candidates) {
788
+ if (!existsSync(envPath))
789
+ continue;
790
+ for (const line of readFileSync(envPath, "utf8").split("\n")) {
791
+ const trimmed = line.trim();
792
+ if (!trimmed || trimmed.startsWith("#"))
793
+ continue;
794
+ const eq = trimmed.indexOf("=");
795
+ if (eq === -1)
796
+ continue;
797
+ vars[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
798
+ }
799
+ }
800
+ return vars;
427
801
  }
428
802
  //# sourceMappingURL=dev.js.map