@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
@@ -0,0 +1,295 @@
1
+ /**
2
+ * docker-postgres — manage a supatype/postgres Docker container for local dev.
3
+ *
4
+ * Used by `supatype dev` when database.provider = "docker".
5
+ * The container is named supatype-{projectName} and persists data in a
6
+ * named Docker volume (supatype-{projectName}-data) across restarts.
7
+ */
8
+
9
+ import { spawnSync } from "node:child_process"
10
+
11
+ export interface DockerPgOptions {
12
+ /** Docker image to run. Defaults to supatype/postgres:latest. */
13
+ image: string
14
+ /** Project name — used to derive the container and volume names. */
15
+ projectName: string
16
+ /** Host port to bind to container's 5432. */
17
+ port: number
18
+ /** Superuser password (dev only). Defaults to "postgres". */
19
+ password?: string
20
+ }
21
+
22
+ const PG_USER = "supatype_admin"
23
+ /** Must match `dockerPgStart` default `POSTGRES_PASSWORD`. */
24
+ const DEFAULT_DEV_PASSWORD = "postgres"
25
+
26
+ function dockerPgPsql(
27
+ name: string,
28
+ db: string,
29
+ sql: string,
30
+ password = DEFAULT_DEV_PASSWORD,
31
+ ) {
32
+ return spawnSync(
33
+ "docker",
34
+ [
35
+ "exec",
36
+ "-e", `PGPASSWORD=${password}`,
37
+ name,
38
+ "psql", "--no-password", "-U", PG_USER, "-d", db, "-tAc", sql,
39
+ ],
40
+ { encoding: "utf8", stdio: "pipe" },
41
+ )
42
+ }
43
+
44
+ /** Derived container name for a project. */
45
+ export function containerName(projectName: string): string {
46
+ return `supatype-${projectName}`
47
+ }
48
+
49
+ /**
50
+ * Start the supatype/postgres Docker container.
51
+ * Removes any stopped container with the same name before starting.
52
+ * Throws if `docker run` exits non-zero.
53
+ */
54
+ export function dockerPgStart(opts: DockerPgOptions): void {
55
+ const { image, projectName, port, password = "postgres" } = opts
56
+ const name = containerName(projectName)
57
+ const volume = `${name}-data`
58
+
59
+ // Remove any stopped container from a previous session.
60
+ spawnSync("docker", ["rm", "-f", name], { encoding: "utf8" })
61
+
62
+ const result = spawnSync(
63
+ "docker",
64
+ [
65
+ "run", "-d",
66
+ "--name", name,
67
+ "-e", `POSTGRES_USER=${PG_USER}`,
68
+ "-e", `POSTGRES_PASSWORD=${password}`,
69
+ "-e", `POSTGRES_DB=${projectName}`,
70
+ "-p", `${port}:5432`,
71
+ "-v", `${volume}:/var/lib/postgresql/data`,
72
+ image,
73
+ ],
74
+ { encoding: "utf8", stdio: "pipe" },
75
+ )
76
+
77
+ if (result.status !== 0) {
78
+ const detail = (result.stderr ?? result.stdout ?? "").trim()
79
+ throw new Error(
80
+ `Failed to start Docker container "${name}".\n` +
81
+ (detail ? ` docker: ${detail}\n` : "") +
82
+ ` Is Docker running? docker info`,
83
+ )
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Stop the container (fast — does not remove it or the data volume).
89
+ * Safe to call even if the container is not running.
90
+ */
91
+ export function dockerPgStop(projectName: string): void {
92
+ spawnSync("docker", ["stop", containerName(projectName)], { encoding: "utf8" })
93
+ }
94
+
95
+ function dockerPgLogsTail(name: string, tail = 120): string {
96
+ const logs = spawnSync(
97
+ "docker",
98
+ ["logs", "--tail", String(tail), name],
99
+ { encoding: "utf8" },
100
+ )
101
+ return `${logs.stdout ?? ""}${logs.stderr ?? ""}`
102
+ }
103
+
104
+ function dockerPgHealthStatus(name: string): string | undefined {
105
+ const inspect = spawnSync(
106
+ "docker",
107
+ ["inspect", "-f", "{{if .State.Health}}{{.State.Health.Status}}{{end}}", name],
108
+ { encoding: "utf8", stdio: "pipe" },
109
+ )
110
+ if (inspect.status !== 0) return undefined
111
+ const status = inspect.stdout?.trim()
112
+ return status === "" ? undefined : status
113
+ }
114
+
115
+ /** True when the final post-init Postgres process is accepting connections. */
116
+ export function dockerPgPostInitServing(logs: string): boolean {
117
+ const ready = "database system is ready to accept connections"
118
+ const lastReady = logs.lastIndexOf(ready)
119
+ if (lastReady === -1) return false
120
+
121
+ const initDone = logs.lastIndexOf("PostgreSQL init process complete")
122
+ if (initDone === -1) {
123
+ // Reused data volume — this run did not re-run entrypoint init.
124
+ return true
125
+ }
126
+ return initDone < lastReady
127
+ }
128
+
129
+ function migrateStillRunning(logs: string): boolean {
130
+ return /99-supatype-migrate\.sh: running .+\.sql/.test(logs)
131
+ }
132
+
133
+ function psqlTruthy(stdout: string | undefined): boolean {
134
+ const v = (stdout ?? "").replace(/\r/g, "").trim().toLowerCase()
135
+ return v === "t" || v === "true"
136
+ }
137
+
138
+ const ANON_ROLE_SQL = "SELECT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon')"
139
+
140
+ function dockerPgHasAnonRole(
141
+ name: string,
142
+ projectName: string,
143
+ password = DEFAULT_DEV_PASSWORD,
144
+ ): boolean {
145
+ for (const db of [projectName, "postgres"]) {
146
+ const result = dockerPgPsql(name, db, ANON_ROLE_SQL, password)
147
+ if (result.status === 0 && psqlTruthy(result.stdout)) return true
148
+ }
149
+ return false
150
+ }
151
+
152
+ function dockerPgExecReady(name: string): boolean {
153
+ const ready = spawnSync(
154
+ "docker",
155
+ ["exec", name, "pg_isready", "-U", PG_USER, "-q"],
156
+ { encoding: "utf8", stdio: "pipe" },
157
+ )
158
+ return ready.status === 0
159
+ }
160
+
161
+ /**
162
+ * Poll until Postgres accepts connections and the image entrypoint init has
163
+ * finished (anon/authenticated/service_role come from supatype-postgres
164
+ * migrations/db/init-scripts/00000000000000-initial-schema.sql via migrate.sh).
165
+ */
166
+ export async function dockerPgWaitReady(
167
+ projectName: string,
168
+ timeoutMs = 180_000,
169
+ password = DEFAULT_DEV_PASSWORD,
170
+ ): Promise<void> {
171
+ const name = containerName(projectName)
172
+ const deadline = Date.now() + timeoutMs
173
+ let servingWithoutAnonMs = 0
174
+ let lastPsqlDetail = ""
175
+
176
+ while (Date.now() < deadline) {
177
+ const health = dockerPgHealthStatus(name)
178
+ const logs = dockerPgLogsTail(name)
179
+ const serving =
180
+ health === "healthy" ||
181
+ dockerPgPostInitServing(logs) ||
182
+ dockerPgExecReady(name)
183
+
184
+ if (serving && !migrateStillRunning(logs)) {
185
+ if (dockerPgHasAnonRole(name, projectName, password)) return
186
+
187
+ const probe = dockerPgPsql(name, projectName, ANON_ROLE_SQL, password)
188
+ lastPsqlDetail = [
189
+ probe.status !== 0 ? `psql exit ${probe.status}` : "",
190
+ probe.stderr?.trim(),
191
+ probe.stdout?.trim() ? `stdout=${probe.stdout.trim()}` : "",
192
+ ]
193
+ .filter(Boolean)
194
+ .join("; ")
195
+
196
+ const reusedVolume =
197
+ logs.includes("database system is ready to accept connections") &&
198
+ !logs.includes("PostgreSQL init process complete")
199
+
200
+ if (reusedVolume) {
201
+ servingWithoutAnonMs += 500
202
+ if (servingWithoutAnonMs >= 5_000) throw staleVolumeError(name)
203
+ } else {
204
+ servingWithoutAnonMs = 0
205
+ }
206
+ } else {
207
+ servingWithoutAnonMs = 0
208
+ }
209
+
210
+ await sleep(500)
211
+ }
212
+
213
+ const logs = dockerPgLogsTail(name, 80)
214
+ throw new Error(
215
+ `Docker Postgres "${name}" did not finish image init within ${timeoutMs}ms.\n` +
216
+ " API roles (anon, authenticated, service_role) are created by the supatype/postgres\n" +
217
+ " entrypoint (99-supatype-migrate.sh), not by the CLI.\n" +
218
+ " If you upgraded the image, remove the stale volume:\n" +
219
+ ` docker volume rm ${name}-data\n` +
220
+ (lastPsqlDetail ? ` Last anon probe: ${lastPsqlDetail}\n` : "") +
221
+ (logs ? ` logs (tail):\n${indent(logs)}\n` : ""),
222
+ )
223
+ }
224
+
225
+ function staleVolumeError(name: string): Error {
226
+ return new Error(
227
+ `Docker Postgres "${name}" is up but API roles are missing.\n` +
228
+ " The data volume was initialised without supatype/postgres migrations (stale or wrong image).\n" +
229
+ " Remove the volume so first-boot 99-supatype-migrate.sh runs again:\n" +
230
+ ` docker volume rm ${name}-data`,
231
+ )
232
+ }
233
+
234
+ /** Connection string for the Docker container (local dev credentials). */
235
+ export function dockerDbUrl(projectName: string, port: number, password = DEFAULT_DEV_PASSWORD): string {
236
+ // Host → published port. sqlx/libpq "prefer" can mis-handle SSL on some hosts (e.g. Docker Desktop on Windows).
237
+ return `postgres://${PG_USER}:${password}@127.0.0.1:${port}/${projectName}?sslmode=disable`
238
+ }
239
+
240
+ /**
241
+ * DB URL for processes sharing the Postgres container network namespace
242
+ * (supatype-server migrate in a one-shot container). Uses loopback inside the
243
+ * container where pg_hba grants trust — avoids host-published-port SCRAM/SSL issues.
244
+ */
245
+ export function dockerPgLoopbackDbUrl(projectName: string, password = DEFAULT_DEV_PASSWORD): string {
246
+ return `postgres://${PG_USER}:${password}@127.0.0.1:5432/${projectName}?sslmode=disable`
247
+ }
248
+
249
+ /**
250
+ * Published Hub tag for local dev when CDN server version is not on Docker Hub yet.
251
+ * Keep in sync with tests/integration/scripts/compose-smoke.sh.
252
+ */
253
+ export const DEFAULT_SERVER_DOCKER_IMAGE = "supatype/server:latest"
254
+
255
+ /**
256
+ * Run `supatype-server migrate` on the Postgres container network (loopback trust).
257
+ * Used on Windows + database.provider docker — host-published :5432 breaks libpq TLS there.
258
+ */
259
+ export function runGotrueMigrationsViaDocker(
260
+ pgContainerName: string,
261
+ serverImage: string,
262
+ migrateEnv: Record<string, string>,
263
+ ): void {
264
+ const envArgs = Object.entries(migrateEnv).flatMap(([k, v]) => ["-e", `${k}=${v}`])
265
+ const result = spawnSync(
266
+ "docker",
267
+ [
268
+ "run", "--rm",
269
+ "--network", `container:${pgContainerName}`,
270
+ ...envArgs,
271
+ serverImage,
272
+ "migrate",
273
+ ],
274
+ { encoding: "utf8", stdio: "pipe" },
275
+ )
276
+ if (result.status !== 0) {
277
+ const detail = (result.stderr ?? result.stdout ?? "").trim()
278
+ throw new Error(
279
+ `GoTrue migrations failed in Docker (exit ${result.status ?? "unknown"})` +
280
+ (detail ? `:\n${detail}` : ""),
281
+ )
282
+ }
283
+ }
284
+
285
+ function sleep(ms: number): Promise<void> {
286
+ return new Promise((resolve) => setTimeout(resolve, ms))
287
+ }
288
+
289
+ function indent(s: string): string {
290
+ return s
291
+ .trimEnd()
292
+ .split("\n")
293
+ .map((l) => ` ${l}`)
294
+ .join("\n")
295
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * engine-client.ts — subprocess-based engine invocation.
3
+ *
4
+ * Replaces the former HTTP-based engine client (Docker container API).
5
+ * All callers use the same interface; only the transport changed.
6
+ *
7
+ * The engine binary reads a request JSON file passed via --request-file and
8
+ * writes a response JSON to stdout.
9
+ */
10
+
11
+ import { spawnSync } from "node:child_process"
12
+ import { mkdirSync, writeFileSync, unlinkSync, existsSync, readdirSync } from "node:fs"
13
+ import { tmpdir, homedir } from "node:os"
14
+ import { join } from "node:path"
15
+ import { loadConfig } from "./config.js"
16
+ import { resolveBinary, currentPlatform, cachePath } from "./binary-cache.js"
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Types (kept for backward compatibility with existing callers)
20
+ // ---------------------------------------------------------------------------
21
+
22
+ export interface Operation {
23
+ kind: "create_table" | "alter_table" | "drop_table" | "create_index" | "drop_index" |
24
+ "create_policy" | "drop_policy" | "add_column" | "drop_column" | "alter_column" |
25
+ "recreate_column"
26
+ type?: string
27
+ description?: string
28
+ risk?: "safe" | "warn" | "danger" | "cautious" | "destructive"
29
+ warning?: string
30
+ sql?: string
31
+ }
32
+
33
+ export interface DiffResult {
34
+ operations: Operation[]
35
+ warnings?: string[]
36
+ summary?: string
37
+ }
38
+
39
+ export interface IntrospectResult {
40
+ models: Array<{
41
+ name: string
42
+ table: string
43
+ columns: Array<{
44
+ name: string
45
+ type: string
46
+ nullable: boolean
47
+ default?: string
48
+ primaryKey?: boolean
49
+ unique?: boolean
50
+ references?: { table: string; column: string }
51
+ }>
52
+ }>
53
+ }
54
+
55
+ export interface EngineResult<T = unknown> {
56
+ ok: boolean
57
+ data: T
58
+ message?: string
59
+ error?: string
60
+ }
61
+
62
+ export class EngineError extends Error {
63
+ constructor(
64
+ message: string,
65
+ public readonly endpoint: string,
66
+ public readonly exitCode: number | null,
67
+ ) {
68
+ super(message)
69
+ this.name = "EngineError"
70
+ }
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Engine binary resolution
75
+ // ---------------------------------------------------------------------------
76
+
77
+ let _engineBin: string | null = null
78
+
79
+ async function getEngineBin(): Promise<string> {
80
+ if (_engineBin) return _engineBin
81
+
82
+ const cwd = process.cwd()
83
+
84
+ try {
85
+ const config = loadConfig(cwd)
86
+ _engineBin = await resolveBinary("engine", config)
87
+ return _engineBin
88
+ } catch {
89
+ // No valid project config — fall through to default cache path.
90
+ }
91
+
92
+ // No config found — scan the cache for any available engine binary.
93
+ const platform = currentPlatform()
94
+ const engineCacheDir = join(homedir(), ".supatype", "cache", "engine")
95
+ try {
96
+ const cachedVersions = readdirSync(engineCacheDir).sort()
97
+ for (const version of cachedVersions.reverse()) {
98
+ const bin = join(cachePath("engine", version), `supatype-engine-${platform.os}-${platform.arch}`)
99
+ if (existsSync(bin)) {
100
+ _engineBin = bin
101
+ return _engineBin
102
+ }
103
+ }
104
+ } catch { /* cache dir doesn't exist */ }
105
+
106
+ throw new Error(
107
+ "Engine binary not found. Run: supatype update",
108
+ )
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Public API
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /** Verify the engine binary is accessible. Throws if not found. */
116
+ export async function ensureEngine(): Promise<void> {
117
+ await getEngineBin()
118
+ }
119
+
120
+ /** Check if the engine can be invoked. Returns true/false. */
121
+ export async function engineHealth(): Promise<boolean> {
122
+ try {
123
+ const bin = await getEngineBin()
124
+ const result = spawnSync(bin, ["--version"], { encoding: "utf8" })
125
+ return result.status === 0
126
+ } catch {
127
+ return false
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Invoke the engine and return a typed result.
133
+ *
134
+ * The endpoint maps to a subcommand:
135
+ * /diff → engine diff
136
+ * /push → engine push
137
+ * /generate → engine generate
138
+ * /migrations → engine migrations
139
+ * /introspect → engine introspect
140
+ * /validate → engine validate
141
+ * /admin → engine admin (admin-config JSON on stdout)
142
+ */
143
+ export async function engineRequest<T = unknown>(
144
+ endpoint: string,
145
+ body: Record<string, unknown>,
146
+ ): Promise<T> {
147
+ const bin = await getEngineBin()
148
+
149
+ // Write request to a temp file.
150
+ // For CLI-mode endpoints the engine reads the input file as a raw SchemaAst,
151
+ // so we extract the `ast` field when present, otherwise write the full body.
152
+ const tmpDir = join(tmpdir(), "supatype-engine")
153
+ mkdirSync(tmpDir, { recursive: true })
154
+ const reqFile = join(tmpDir, `req-${Date.now()}.json`)
155
+ const inputPayload = body["ast"] !== undefined ? body["ast"] : body
156
+ writeFileSync(reqFile, JSON.stringify(inputPayload))
157
+
158
+ const args = endpointToArgs(endpoint, body, reqFile)
159
+
160
+ const result = spawnSync(bin, args, {
161
+ encoding: "utf8",
162
+ cwd: process.cwd(),
163
+ })
164
+
165
+ // Clean up temp file.
166
+ try { unlinkSync(reqFile) } catch { /* ignore */ }
167
+
168
+ if (result.status !== 0) {
169
+ const stderr = result.stderr?.trim() || "(no output)"
170
+ throw new EngineError(
171
+ `Engine ${endpoint} failed (exit ${result.status}): ${stderr}`,
172
+ endpoint,
173
+ result.status,
174
+ )
175
+ }
176
+
177
+ if (!result.stdout?.trim()) {
178
+ // Some subcommands print nothing on success.
179
+ return {} as T
180
+ }
181
+
182
+ try {
183
+ return JSON.parse(result.stdout) as T
184
+ } catch {
185
+ // Non-JSON stdout — return as message.
186
+ return { message: result.stdout.trim() } as T
187
+ }
188
+ }
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // Endpoint → CLI args mapping
192
+ // ---------------------------------------------------------------------------
193
+
194
+ function endpointToArgs(
195
+ endpoint: string,
196
+ body: Record<string, unknown>,
197
+ reqFile: string,
198
+ ): string[] {
199
+ const dbUrl = (body["database_url"] as string | undefined) ?? ""
200
+ const schema = (body["schema"] as string | undefined) ?? "public"
201
+ const force = body["force"] ? ["--force"] : []
202
+ const nonInteractive =
203
+ body["non_interactive"] === true || body["force"] === true ? ["--non-interactive"] : []
204
+
205
+ switch (endpoint) {
206
+ case "/diff":
207
+ return ["diff", "--input", reqFile, "--database-url", dbUrl, "--schema", schema]
208
+
209
+ case "/push":
210
+ return ["push", "--input", reqFile, "--database-url", dbUrl, "--schema", schema, ...force, ...nonInteractive]
211
+
212
+ case "/parse":
213
+ return ["parse", "--input", reqFile]
214
+
215
+ case "/generate": {
216
+ const lang = (body["lang"] as string | undefined) ?? "typescript"
217
+ return ["generate", "--input", reqFile, "--lang", lang]
218
+ }
219
+
220
+ case "/introspect":
221
+ return ["introspect", "--database-url", dbUrl, "--schema", schema]
222
+
223
+ case "/validate":
224
+ return ["validate", "--input", reqFile]
225
+
226
+ case "/admin":
227
+ return ["admin", "--input", reqFile]
228
+
229
+ default:
230
+ if (endpoint.startsWith("/migrations")) {
231
+ const action = (body["action"] as string | undefined) ?? "list"
232
+ return ["migrations", action, "--database-url", dbUrl]
233
+ }
234
+ return [endpoint.replace(/^\//, ""), "--input", reqFile]
235
+ }
236
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Resolve component binaries, downloading from the CDN when not cached.
3
+ */
4
+
5
+ import {
6
+ resolveBinary,
7
+ download,
8
+ currentPlatform,
9
+ versionFor,
10
+ type Component,
11
+ } from "./binary-cache.js"
12
+ import type { SupatypeProjectConfig } from "./project-config.js"
13
+
14
+ export async function ensureBinary(
15
+ component: Component,
16
+ config: SupatypeProjectConfig,
17
+ ): Promise<string> {
18
+ try {
19
+ return await resolveBinary(component, config)
20
+ } catch (err) {
21
+ const message = err instanceof Error ? err.message : String(err)
22
+ if (!message.includes("not found in cache")) {
23
+ throw err
24
+ }
25
+ }
26
+
27
+ return download(component, versionFor(component, config), currentPlatform())
28
+ }