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

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 (309) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +203 -1
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/dist/app-config.d.ts +7 -0
  5. package/dist/app-config.d.ts.map +1 -0
  6. package/dist/app-config.js +113 -0
  7. package/dist/app-config.js.map +1 -0
  8. package/dist/augmentation-generator.d.ts +2 -0
  9. package/dist/augmentation-generator.d.ts.map +1 -0
  10. package/dist/augmentation-generator.js +111 -0
  11. package/dist/augmentation-generator.js.map +1 -0
  12. package/dist/binary-cache.d.ts +89 -0
  13. package/dist/binary-cache.d.ts.map +1 -0
  14. package/dist/binary-cache.js +656 -0
  15. package/dist/binary-cache.js.map +1 -0
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +13 -7
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/admin.d.ts.map +1 -1
  20. package/dist/commands/admin.js +4 -3
  21. package/dist/commands/admin.js.map +1 -1
  22. package/dist/commands/app.d.ts.map +1 -1
  23. package/dist/commands/app.js +56 -209
  24. package/dist/commands/app.js.map +1 -1
  25. package/dist/commands/cache.d.ts +6 -0
  26. package/dist/commands/cache.d.ts.map +1 -0
  27. package/dist/commands/cache.js +105 -0
  28. package/dist/commands/cache.js.map +1 -0
  29. package/dist/commands/cloud.d.ts +12 -0
  30. package/dist/commands/cloud.d.ts.map +1 -1
  31. package/dist/commands/cloud.js +36 -46
  32. package/dist/commands/cloud.js.map +1 -1
  33. package/dist/commands/db.d.ts.map +1 -1
  34. package/dist/commands/db.js +47 -54
  35. package/dist/commands/db.js.map +1 -1
  36. package/dist/commands/deploy.d.ts +2 -1
  37. package/dist/commands/deploy.d.ts.map +1 -1
  38. package/dist/commands/deploy.js +92 -51
  39. package/dist/commands/deploy.js.map +1 -1
  40. package/dist/commands/dev.d.ts +11 -0
  41. package/dist/commands/dev.d.ts.map +1 -1
  42. package/dist/commands/dev.js +751 -384
  43. package/dist/commands/dev.js.map +1 -1
  44. package/dist/commands/diff.d.ts.map +1 -1
  45. package/dist/commands/diff.js +20 -15
  46. package/dist/commands/diff.js.map +1 -1
  47. package/dist/commands/engine.d.ts +1 -3
  48. package/dist/commands/engine.d.ts.map +1 -1
  49. package/dist/commands/engine.js +13 -85
  50. package/dist/commands/engine.js.map +1 -1
  51. package/dist/commands/functions.d.ts.map +1 -1
  52. package/dist/commands/functions.js +92 -105
  53. package/dist/commands/functions.js.map +1 -1
  54. package/dist/commands/generate.d.ts.map +1 -1
  55. package/dist/commands/generate.js +22 -12
  56. package/dist/commands/generate.js.map +1 -1
  57. package/dist/commands/init.d.ts +1 -1
  58. package/dist/commands/init.d.ts.map +1 -1
  59. package/dist/commands/init.js +124 -410
  60. package/dist/commands/init.js.map +1 -1
  61. package/dist/commands/migrate-from-v1.d.ts +5 -0
  62. package/dist/commands/migrate-from-v1.d.ts.map +1 -0
  63. package/dist/commands/migrate-from-v1.js +125 -0
  64. package/dist/commands/migrate-from-v1.js.map +1 -0
  65. package/dist/commands/migrate.d.ts.map +1 -1
  66. package/dist/commands/migrate.js +27 -23
  67. package/dist/commands/migrate.js.map +1 -1
  68. package/dist/commands/pg.d.ts +8 -0
  69. package/dist/commands/pg.d.ts.map +1 -0
  70. package/dist/commands/pg.js +102 -0
  71. package/dist/commands/pg.js.map +1 -0
  72. package/dist/commands/pull.d.ts.map +1 -1
  73. package/dist/commands/pull.js +5 -66
  74. package/dist/commands/pull.js.map +1 -1
  75. package/dist/commands/push.d.ts.map +1 -1
  76. package/dist/commands/push.js +99 -39
  77. package/dist/commands/push.js.map +1 -1
  78. package/dist/commands/seed.d.ts +2 -0
  79. package/dist/commands/seed.d.ts.map +1 -1
  80. package/dist/commands/seed.js +44 -11
  81. package/dist/commands/seed.js.map +1 -1
  82. package/dist/commands/self-host.d.ts +7 -1
  83. package/dist/commands/self-host.d.ts.map +1 -1
  84. package/dist/commands/self-host.js +272 -758
  85. package/dist/commands/self-host.js.map +1 -1
  86. package/dist/commands/self-update.d.ts +9 -0
  87. package/dist/commands/self-update.d.ts.map +1 -0
  88. package/dist/commands/self-update.js +33 -0
  89. package/dist/commands/self-update.js.map +1 -0
  90. package/dist/commands/status.d.ts.map +1 -1
  91. package/dist/commands/status.js +4 -3
  92. package/dist/commands/status.js.map +1 -1
  93. package/dist/commands/types.d.ts +3 -0
  94. package/dist/commands/types.d.ts.map +1 -0
  95. package/dist/commands/types.js +62 -0
  96. package/dist/commands/types.js.map +1 -0
  97. package/dist/commands/update.d.ts +7 -0
  98. package/dist/commands/update.d.ts.map +1 -0
  99. package/dist/commands/update.js +77 -0
  100. package/dist/commands/update.js.map +1 -0
  101. package/dist/components.d.ts +5 -0
  102. package/dist/components.d.ts.map +1 -0
  103. package/dist/components.js +3 -0
  104. package/dist/components.js.map +1 -0
  105. package/dist/config.d.ts +10 -51
  106. package/dist/config.d.ts.map +1 -1
  107. package/dist/config.js +101 -33
  108. package/dist/config.js.map +1 -1
  109. package/dist/docker-postgres.d.ts +39 -0
  110. package/dist/docker-postgres.d.ts.map +1 -0
  111. package/dist/docker-postgres.js +96 -0
  112. package/dist/docker-postgres.js.map +1 -0
  113. package/dist/engine-client.d.ts +67 -0
  114. package/dist/engine-client.d.ts.map +1 -0
  115. package/dist/engine-client.js +156 -0
  116. package/dist/engine-client.js.map +1 -0
  117. package/dist/ensure-binary.d.ts +7 -0
  118. package/dist/ensure-binary.d.ts.map +1 -0
  119. package/dist/ensure-binary.js +17 -0
  120. package/dist/ensure-binary.js.map +1 -0
  121. package/dist/functions-router-gen.d.ts +14 -0
  122. package/dist/functions-router-gen.d.ts.map +1 -0
  123. package/dist/functions-router-gen.js +199 -0
  124. package/dist/functions-router-gen.js.map +1 -0
  125. package/dist/index.d.ts +4 -5
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +2 -3
  128. package/dist/index.js.map +1 -1
  129. package/dist/kong-config.d.ts +21 -0
  130. package/dist/kong-config.d.ts.map +1 -0
  131. package/dist/kong-config.js +60 -0
  132. package/dist/kong-config.js.map +1 -0
  133. package/dist/local-gateway.d.ts +7 -0
  134. package/dist/local-gateway.d.ts.map +1 -0
  135. package/dist/local-gateway.js +9 -0
  136. package/dist/local-gateway.js.map +1 -0
  137. package/dist/local-storage.d.ts +8 -0
  138. package/dist/local-storage.d.ts.map +1 -0
  139. package/dist/local-storage.js +14 -0
  140. package/dist/local-storage.js.map +1 -0
  141. package/dist/pgbouncer-userlist.d.ts +5 -0
  142. package/dist/pgbouncer-userlist.d.ts.map +1 -0
  143. package/dist/pgbouncer-userlist.js +14 -0
  144. package/dist/pgbouncer-userlist.js.map +1 -0
  145. package/dist/postgres-ctl.d.ts +44 -0
  146. package/dist/postgres-ctl.d.ts.map +1 -0
  147. package/dist/postgres-ctl.js +137 -0
  148. package/dist/postgres-ctl.js.map +1 -0
  149. package/dist/process-manager.d.ts +41 -0
  150. package/dist/process-manager.d.ts.map +1 -0
  151. package/dist/process-manager.js +120 -0
  152. package/dist/process-manager.js.map +1 -0
  153. package/dist/project-config.d.ts +215 -0
  154. package/dist/project-config.d.ts.map +1 -0
  155. package/dist/project-config.js +145 -0
  156. package/dist/project-config.js.map +1 -0
  157. package/dist/pull-utils.d.ts +15 -0
  158. package/dist/pull-utils.d.ts.map +1 -1
  159. package/dist/pull-utils.js +12 -0
  160. package/dist/pull-utils.js.map +1 -1
  161. package/dist/release-pins.d.ts +7 -0
  162. package/dist/release-pins.d.ts.map +1 -0
  163. package/dist/release-pins.js +27 -0
  164. package/dist/release-pins.js.map +1 -0
  165. package/dist/release-public-key.d.ts +8 -0
  166. package/dist/release-public-key.d.ts.map +1 -0
  167. package/dist/release-public-key.js +13 -0
  168. package/dist/release-public-key.js.map +1 -0
  169. package/dist/runtime-routes.d.ts +25 -0
  170. package/dist/runtime-routes.d.ts.map +1 -0
  171. package/dist/runtime-routes.js +189 -0
  172. package/dist/runtime-routes.js.map +1 -0
  173. package/dist/scripts/postinstall.d.ts +5 -6
  174. package/dist/scripts/postinstall.d.ts.map +1 -1
  175. package/dist/scripts/postinstall.js +36 -20
  176. package/dist/scripts/postinstall.js.map +1 -1
  177. package/dist/self-host-compose.d.ts +14 -0
  178. package/dist/self-host-compose.d.ts.map +1 -0
  179. package/dist/self-host-compose.js +236 -0
  180. package/dist/self-host-compose.js.map +1 -0
  181. package/dist/storage-provision.d.ts +24 -0
  182. package/dist/storage-provision.d.ts.map +1 -0
  183. package/dist/storage-provision.js +44 -0
  184. package/dist/storage-provision.js.map +1 -0
  185. package/dist/systemd.d.ts +26 -0
  186. package/dist/systemd.d.ts.map +1 -0
  187. package/dist/systemd.js +102 -0
  188. package/dist/systemd.js.map +1 -0
  189. package/dist/tsx-runner.d.ts.map +1 -1
  190. package/dist/tsx-runner.js +9 -2
  191. package/dist/tsx-runner.js.map +1 -1
  192. package/dist/type-extractor.d.ts +31 -0
  193. package/dist/type-extractor.d.ts.map +1 -0
  194. package/dist/type-extractor.js +876 -0
  195. package/dist/type-extractor.js.map +1 -0
  196. package/package.json +4 -3
  197. package/releases/deno/VERSION +1 -0
  198. package/scripts/mirror-deno-release.sh +76 -0
  199. package/src/app-config.ts +128 -0
  200. package/src/augmentation-generator.ts +126 -0
  201. package/src/binary-cache.ts +802 -0
  202. package/src/cli.ts +13 -8
  203. package/src/commands/admin.ts +4 -3
  204. package/src/commands/app.ts +67 -231
  205. package/src/commands/cache.ts +117 -0
  206. package/src/commands/cloud.ts +46 -57
  207. package/src/commands/db.ts +54 -63
  208. package/src/commands/deploy.ts +110 -61
  209. package/src/commands/dev.ts +930 -405
  210. package/src/commands/diff.ts +21 -29
  211. package/src/commands/engine.ts +13 -116
  212. package/src/commands/functions.ts +97 -115
  213. package/src/commands/generate.ts +23 -10
  214. package/src/commands/init.ts +136 -414
  215. package/src/commands/migrate-from-v1.ts +131 -0
  216. package/src/commands/migrate.ts +27 -23
  217. package/src/commands/pg.ts +133 -0
  218. package/src/commands/pull.ts +6 -85
  219. package/src/commands/push.ts +128 -59
  220. package/src/commands/seed.ts +54 -12
  221. package/src/commands/self-host.ts +312 -880
  222. package/src/commands/self-update.ts +45 -0
  223. package/src/commands/status.ts +4 -3
  224. package/src/commands/types.ts +76 -0
  225. package/src/commands/update.ts +92 -0
  226. package/src/components.ts +6 -0
  227. package/src/config.ts +127 -94
  228. package/src/docker-postgres.ts +138 -0
  229. package/src/engine-client.ts +231 -0
  230. package/src/ensure-binary.ts +28 -0
  231. package/src/functions-router-gen.ts +224 -0
  232. package/src/index.ts +4 -12
  233. package/src/kong-config.ts +78 -0
  234. package/src/local-gateway.ts +9 -0
  235. package/src/local-storage.ts +14 -0
  236. package/src/pgbouncer-userlist.ts +15 -0
  237. package/src/postgres-ctl.ts +171 -0
  238. package/src/process-manager.ts +151 -0
  239. package/src/project-config.ts +353 -0
  240. package/src/pull-utils.ts +24 -0
  241. package/src/release-pins.ts +31 -0
  242. package/src/release-public-key.ts +12 -0
  243. package/src/runtime-routes.ts +216 -0
  244. package/src/scripts/postinstall.ts +36 -25
  245. package/src/self-host-compose.ts +257 -0
  246. package/src/storage-provision.ts +58 -0
  247. package/src/systemd.ts +137 -0
  248. package/src/tsx-runner.ts +11 -1
  249. package/src/type-extractor.ts +1016 -0
  250. package/tests/app-command.test.ts +54 -0
  251. package/tests/augmentation-generator.test.ts +59 -0
  252. package/tests/binary-cache-cloud-overrides.test.ts +123 -0
  253. package/tests/cached-artifact-format.test.ts +84 -0
  254. package/tests/cli-help.test.ts +40 -14
  255. package/tests/config.test.ts +140 -37
  256. package/tests/engine-distribution.test.ts +3 -3
  257. package/tests/ensure-binary.test.ts +59 -0
  258. package/tests/init.test.ts +28 -86
  259. package/tests/migrate-from-v1.test.ts +29 -0
  260. package/tests/pg-spawn-env.test.ts +18 -0
  261. package/tests/postgres-archive-tag.test.ts +9 -0
  262. package/tests/pull-utils.test.ts +36 -1
  263. package/tests/release-pins.test.ts +28 -0
  264. package/tests/runtime-contract.test.ts +236 -0
  265. package/tests/seed-discover.test.ts +31 -0
  266. package/tests/tsconfig.json +9 -0
  267. package/tests/type-extractor.test.ts +401 -0
  268. package/tsconfig.tsbuildinfo +1 -1
  269. package/vitest.config.ts +12 -0
  270. package/dist/engine/cache.d.ts +0 -37
  271. package/dist/engine/cache.d.ts.map +0 -1
  272. package/dist/engine/cache.js +0 -121
  273. package/dist/engine/cache.js.map +0 -1
  274. package/dist/engine/download.d.ts +0 -19
  275. package/dist/engine/download.d.ts.map +0 -1
  276. package/dist/engine/download.js +0 -108
  277. package/dist/engine/download.js.map +0 -1
  278. package/dist/engine/platform.d.ts +0 -24
  279. package/dist/engine/platform.d.ts.map +0 -1
  280. package/dist/engine/platform.js +0 -50
  281. package/dist/engine/platform.js.map +0 -1
  282. package/dist/engine/resolve.d.ts +0 -37
  283. package/dist/engine/resolve.d.ts.map +0 -1
  284. package/dist/engine/resolve.js +0 -133
  285. package/dist/engine/resolve.js.map +0 -1
  286. package/dist/engine/update-notify.d.ts +0 -11
  287. package/dist/engine/update-notify.d.ts.map +0 -1
  288. package/dist/engine/update-notify.js +0 -43
  289. package/dist/engine/update-notify.js.map +0 -1
  290. package/dist/engine/verify.d.ts +0 -50
  291. package/dist/engine/verify.d.ts.map +0 -1
  292. package/dist/engine/verify.js +0 -161
  293. package/dist/engine/verify.js.map +0 -1
  294. package/dist/engine-version.d.ts +0 -35
  295. package/dist/engine-version.d.ts.map +0 -1
  296. package/dist/engine-version.js +0 -35
  297. package/dist/engine-version.js.map +0 -1
  298. package/dist/engine.d.ts +0 -34
  299. package/dist/engine.d.ts.map +0 -1
  300. package/dist/engine.js +0 -76
  301. package/dist/engine.js.map +0 -1
  302. package/src/engine/cache.ts +0 -135
  303. package/src/engine/download.ts +0 -143
  304. package/src/engine/platform.ts +0 -66
  305. package/src/engine/resolve.ts +0 -197
  306. package/src/engine/update-notify.ts +0 -50
  307. package/src/engine/verify.ts +0 -206
  308. package/src/engine-version.ts +0 -39
  309. package/src/engine.ts +0 -99
@@ -0,0 +1,171 @@
1
+ /**
2
+ * postgres-ctl — wrappers around pg_ctl, initdb, and pg_isready for managing
3
+ * a native Postgres installation.
4
+ */
5
+
6
+ import { spawnSync } from "node:child_process"
7
+ import { existsSync, mkdirSync } from "node:fs"
8
+ import { dirname, join } from "node:path"
9
+
10
+ export interface PgOptions {
11
+ /** Absolute path to the directory containing pg_ctl, initdb, psql, etc. */
12
+ pgBinDir: string
13
+ /** Absolute path to the Postgres data directory (PGDATA). */
14
+ dataDir: string
15
+ /** Port Postgres should listen on. */
16
+ port: number
17
+ /** Path to write the postgres log file. */
18
+ logPath?: string
19
+ }
20
+
21
+ /**
22
+ * Native Postgres bundles are built with prefix /usr/local/supatype-pg; dyld/ld
23
+ * must load libpq and friends from the extracted lib/ next to bin/.
24
+ */
25
+ export function pgSpawnEnv(
26
+ pgBinDir: string,
27
+ platform: NodeJS.Platform = process.platform,
28
+ ): NodeJS.ProcessEnv {
29
+ const libDir = join(dirname(pgBinDir), "lib")
30
+ const env = { ...process.env } as NodeJS.ProcessEnv
31
+ if (platform === "darwin") {
32
+ const prev = env.DYLD_LIBRARY_PATH ?? ""
33
+ env.DYLD_LIBRARY_PATH = prev ? `${libDir}:${prev}` : libDir
34
+ } else if (platform === "linux") {
35
+ const prev = env.LD_LIBRARY_PATH ?? ""
36
+ env.LD_LIBRARY_PATH = prev ? `${libDir}:${prev}` : libDir
37
+ }
38
+ return env
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // initdb
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /**
46
+ * Initialise a Postgres data directory.
47
+ * Does nothing if the data directory already contains a PG_VERSION file.
48
+ */
49
+ export function initdb(opts: PgOptions): void {
50
+ const pgVersionFile = join(opts.dataDir, "PG_VERSION")
51
+ if (existsSync(pgVersionFile)) return // Already initialised.
52
+
53
+ mkdirSync(opts.dataDir, { recursive: true })
54
+
55
+ const bin = pgBin(opts.pgBinDir, "initdb")
56
+ const result = spawnSync(bin, ["-D", opts.dataDir, "--username", "postgres", "--auth", "trust"], {
57
+ stdio: "inherit",
58
+ encoding: "utf8",
59
+ env: pgSpawnEnv(opts.pgBinDir),
60
+ })
61
+
62
+ if (result.status !== 0) {
63
+ throw new Error(`initdb failed (exit ${result.status})`)
64
+ }
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // start / stop
69
+ // ---------------------------------------------------------------------------
70
+
71
+ /**
72
+ * Start Postgres using pg_ctl.
73
+ * Returns immediately once pg_ctl has handed off to the server process.
74
+ */
75
+ export function start(opts: PgOptions): void {
76
+ const bin = pgBin(opts.pgBinDir, "pg_ctl")
77
+ const logPath = opts.logPath ?? join(opts.dataDir, "postgres.log")
78
+
79
+ const args = [
80
+ "start",
81
+ "-D", opts.dataDir,
82
+ "-l", logPath,
83
+ "-o", `-p ${opts.port}`,
84
+ "--wait",
85
+ ]
86
+
87
+ const result = spawnSync(bin, args, {
88
+ stdio: "inherit",
89
+ encoding: "utf8",
90
+ env: pgSpawnEnv(opts.pgBinDir),
91
+ })
92
+ if (result.status !== 0) {
93
+ throw new Error(`pg_ctl start failed (exit ${result.status})`)
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Stop Postgres using pg_ctl (fast mode).
99
+ */
100
+ export function stop(opts: PgOptions): void {
101
+ const bin = pgBin(opts.pgBinDir, "pg_ctl")
102
+ const result = spawnSync(bin, ["stop", "-D", opts.dataDir, "-m", "fast", "--wait"], {
103
+ stdio: "inherit",
104
+ encoding: "utf8",
105
+ env: pgSpawnEnv(opts.pgBinDir),
106
+ })
107
+ // Ignore exit code — Postgres may already be stopped.
108
+ void result
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // waitReady
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /**
116
+ * Wait until Postgres is accepting connections.
117
+ * Polls pg_isready every 200ms up to timeoutMs.
118
+ * Throws if the timeout is exceeded.
119
+ */
120
+ export async function waitReady(opts: PgOptions, timeoutMs = 10_000): Promise<void> {
121
+ const bin = pgBin(opts.pgBinDir, "pg_isready")
122
+ const deadline = Date.now() + timeoutMs
123
+
124
+ while (Date.now() < deadline) {
125
+ const result = spawnSync(bin, ["-p", String(opts.port), "-q"], {
126
+ encoding: "utf8",
127
+ env: pgSpawnEnv(opts.pgBinDir),
128
+ })
129
+ if (result.status === 0) return
130
+
131
+ await sleep(200)
132
+ }
133
+
134
+ throw new Error(
135
+ `Postgres did not become ready within ${timeoutMs}ms on port ${opts.port}`,
136
+ )
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Port check
141
+ // ---------------------------------------------------------------------------
142
+
143
+ /**
144
+ * Returns true if a TCP listener is already bound to port on 127.0.0.1.
145
+ */
146
+ export async function isPortInUse(port: number): Promise<boolean> {
147
+ const { createServer } = await import("node:net")
148
+ return new Promise((resolve) => {
149
+ const server = createServer()
150
+ server.once("error", (err: NodeJS.ErrnoException) => {
151
+ resolve(err.code === "EADDRINUSE")
152
+ })
153
+ server.once("listening", () => {
154
+ server.close(() => resolve(false))
155
+ })
156
+ server.listen(port, "127.0.0.1")
157
+ })
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Helpers
162
+ // ---------------------------------------------------------------------------
163
+
164
+ function sleep(ms: number): Promise<void> {
165
+ return new Promise((resolve) => setTimeout(resolve, ms))
166
+ }
167
+
168
+ /** Returns the full path to a Postgres binary, appending .exe on Windows. */
169
+ function pgBin(binDir: string, name: string): string {
170
+ return join(binDir, process.platform === "win32" ? `${name}.exe` : name)
171
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * ProcessManager — spawn a child process, write its PID, stream logs with a
3
+ * colored prefix, and restart on crash with exponential backoff.
4
+ */
5
+
6
+ import { type ChildProcess, spawn } from "node:child_process"
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
8
+ import { unlink } from "node:fs/promises"
9
+ import { join } from "node:path"
10
+
11
+ export interface ProcessOptions {
12
+ /** Human-readable label (used in log prefix and PID filename). */
13
+ label: string
14
+ /** Directory to write {label}.pid to. */
15
+ pidDir: string
16
+ /** ANSI colour prefix string (e.g. "\x1b[36m"). Pass "" for no colour. */
17
+ colour?: string
18
+ /** Working directory for the spawned process. Defaults to process.cwd(). */
19
+ cwd?: string
20
+ /** Environment variables to merge with process.env. */
21
+ env?: Record<string, string>
22
+ /** Initial restart backoff in ms. Doubles each crash up to maxBackoffMs. */
23
+ initialBackoffMs?: number
24
+ /** Maximum restart backoff cap in ms. */
25
+ maxBackoffMs?: number
26
+ /** Called when the process exits cleanly (code 0). */
27
+ onExit?: () => void
28
+ }
29
+
30
+ const RESET = "\x1b[0m"
31
+
32
+ export class ProcessManager {
33
+ private child: ChildProcess | null = null
34
+ private stopped = false
35
+ private backoffMs: number
36
+ private opts: Required<ProcessOptions>
37
+
38
+ constructor(
39
+ private readonly bin: string,
40
+ private readonly args: string[],
41
+ opts: ProcessOptions,
42
+ ) {
43
+ this.opts = {
44
+ colour: "\x1b[36m",
45
+ cwd: process.cwd(),
46
+ env: {},
47
+ initialBackoffMs: 1_000,
48
+ maxBackoffMs: 30_000,
49
+ onExit: () => {},
50
+ ...opts,
51
+ }
52
+ this.backoffMs = this.opts.initialBackoffMs
53
+ }
54
+
55
+ /** Start the process. Returns immediately — the process runs in the background. */
56
+ start(): void {
57
+ this.stopped = false
58
+ this.spawn()
59
+ }
60
+
61
+ /** Stop the process and clear the PID file. */
62
+ async stop(): Promise<void> {
63
+ this.stopped = true
64
+ if (this.child && !this.child.killed) {
65
+ this.child.kill("SIGTERM")
66
+ // Give it 5s to exit gracefully, then SIGKILL.
67
+ await new Promise<void>((resolve) => {
68
+ const timeout = setTimeout(() => {
69
+ this.child?.kill("SIGKILL")
70
+ resolve()
71
+ }, 5_000)
72
+ this.child!.once("exit", () => {
73
+ clearTimeout(timeout)
74
+ resolve()
75
+ })
76
+ })
77
+ }
78
+ await this.clearPid()
79
+ }
80
+
81
+ private spawn(): void {
82
+ if (this.stopped) return
83
+
84
+ const env = { ...process.env, ...this.opts.env } as NodeJS.ProcessEnv
85
+ this.child = spawn(this.bin, this.args, { env, cwd: this.opts.cwd, stdio: "pipe" })
86
+
87
+ const pid = this.child.pid
88
+ if (pid) this.writePid(pid)
89
+
90
+ const prefix = this.opts.colour
91
+ ? `${this.opts.colour}[${this.opts.label}]${RESET} `
92
+ : `[${this.opts.label}] `
93
+
94
+ this.child.stdout?.on("data", (chunk: Buffer) => {
95
+ for (const line of chunk.toString().split("\n")) {
96
+ if (line) process.stdout.write(prefix + line + "\n")
97
+ }
98
+ })
99
+
100
+ this.child.stderr?.on("data", (chunk: Buffer) => {
101
+ for (const line of chunk.toString().split("\n")) {
102
+ if (line) process.stderr.write(prefix + line + "\n")
103
+ }
104
+ })
105
+
106
+ this.child.once("exit", (code, signal) => {
107
+ if (this.stopped) return
108
+
109
+ if (code === 0) {
110
+ this.opts.onExit()
111
+ return
112
+ }
113
+
114
+ const reason = signal ? `signal ${signal}` : `code ${code}`
115
+ process.stderr.write(
116
+ `${prefix}process exited (${reason}), restarting in ${this.backoffMs}ms\n`,
117
+ )
118
+
119
+ setTimeout(() => {
120
+ this.backoffMs = Math.min(this.backoffMs * 2, this.opts.maxBackoffMs)
121
+ this.spawn()
122
+ }, this.backoffMs)
123
+ })
124
+ }
125
+
126
+ private writePid(pid: number): void {
127
+ try {
128
+ mkdirSync(this.opts.pidDir, { recursive: true })
129
+ writeFileSync(join(this.opts.pidDir, `${this.opts.label}.pid`), String(pid))
130
+ } catch {
131
+ // Non-fatal — PID tracking is best-effort.
132
+ }
133
+ }
134
+
135
+ private async clearPid(): Promise<void> {
136
+ try {
137
+ await unlink(join(this.opts.pidDir, `${this.opts.label}.pid`))
138
+ } catch {
139
+ // Ignore — file may already be gone.
140
+ }
141
+ }
142
+ }
143
+
144
+ /** Read a PID from a file (returns null if not found or stale). */
145
+ export function readPid(pidDir: string, label: string): number | null {
146
+ const pidFile = join(pidDir, `${label}.pid`)
147
+ if (!existsSync(pidFile)) return null
148
+ const raw = readFileSync(pidFile, "utf8").trim()
149
+ const pid = Number(raw)
150
+ return Number.isFinite(pid) && pid > 0 ? pid : null
151
+ }
@@ -0,0 +1,353 @@
1
+ import { existsSync } from "node:fs"
2
+ import { resolve } from "node:path"
3
+ import type { ComponentVersions } from "./components.js"
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Config schema (single canonical shape; loaded from supatype.config.ts)
7
+ // ---------------------------------------------------------------------------
8
+
9
+ export interface SupatypeProjectConfig {
10
+ supatype?: {
11
+ /**
12
+ * Base directory for Supatype project assets (schema, functions, etc).
13
+ * "." means the current working directory (default).
14
+ */
15
+ root?: string
16
+ }
17
+ project: {
18
+ /** Project name — used for per-project state dirs and logging. */
19
+ name: string
20
+ /** Cloud project reference (set by `supatype link`). */
21
+ ref?: string
22
+ }
23
+ database: {
24
+ /**
25
+ * Database backend.
26
+ * "native" = supatype manages a native Postgres binary (downloaded from CDN).
27
+ * "docker" = supatype runs supatype/postgres via Docker (includes all extensions).
28
+ */
29
+ provider: "native" | "docker"
30
+ /**
31
+ * Directory where Postgres stores its data files (provider=native).
32
+ * Defaults to ~/.supatype/projects/{name}/data when omitted.
33
+ */
34
+ data_dir?: string
35
+ /**
36
+ * Docker image to use (provider=docker).
37
+ * Defaults to supatype/postgres:17-latest.
38
+ * Override in supatype.local.config.ts for local builds.
39
+ */
40
+ image?: string
41
+ }
42
+ server: {
43
+ /**
44
+ * Server mode.
45
+ * "dev" = no TLS, permissive CORS, Vite HMR proxy
46
+ * "standalone" = ACME TLS (Let's Encrypt)
47
+ * "managed" = cloud-managed, HMAC tenant verification
48
+ */
49
+ mode: "dev" | "standalone" | "managed"
50
+ /** Port supatype-server listens on (default: 54321). */
51
+ port?: number
52
+ /** Port PostgREST listens on in local dev (default: 3001). */
53
+ postgrestPort?: number
54
+ /** Domain for ACME TLS certificate (mode=standalone). */
55
+ domain?: string
56
+ }
57
+ app: {
58
+ /**
59
+ * How the root path "/" is handled by supatype-server.
60
+ * "none" = 404
61
+ * "static" = serve files from static_dir
62
+ * "proxy" = reverse-proxy to upstream
63
+ */
64
+ mode: "none" | "static" | "proxy"
65
+ /** Directory to serve static files from (mode=static). */
66
+ static_dir?: string
67
+ /** Upstream URL to proxy to (mode=proxy). */
68
+ upstream?: string
69
+ /**
70
+ * Vite dev server base URL for HMR (`/_vite/*`) when `server.mode` is dev.
71
+ * Example: `http://127.0.0.1:5173`. Sets `SUPATYPE_VITE_DEV_URL` for supatype-server.
72
+ * When omitted, dev still falls back to `SUPATYPE_APP_UPSTREAM` for non-proxy app modes.
73
+ */
74
+ vite_dev_url?: string
75
+ }
76
+ /**
77
+ * Pinned binary versions per component. Use **`"local"`** with the matching **`overrides.*`**
78
+ * entry when testing a local build (Phase 10.7).
79
+ */
80
+ versions: ComponentVersions
81
+ /**
82
+ * Override component binaries with local build paths.
83
+ * Intended for supatype contributors testing local changes.
84
+ * Cannot be combined with a linked cloud project (`project.ref`, `.supatype/cloud.json`, or `.supatype/linked.json`; hard error in `resolveBinary`).
85
+ */
86
+ overrides?: {
87
+ /** Path to local engine binary. */
88
+ engine?: string
89
+ /** Path to local supatype-server binary. */
90
+ server?: string
91
+ /** Path to a directory containing a local Postgres installation. */
92
+ postgres_dir?: string
93
+ /** Path to a local deno binary. */
94
+ deno?: string
95
+ /** Path to the @supatype/studio package directory (starts Vite dev server). */
96
+ studio?: string
97
+ /** Path to a local PostgREST binary. */
98
+ postgrest?: string
99
+ }
100
+ email?: {
101
+ /**
102
+ * Email delivery provider.
103
+ * "console" = log to stdout (default for dev)
104
+ * "smtp" = SMTP (set `smtp` below and/or GOTRUE_SMTP_* in `.env`)
105
+ * "resend" = Resend API (requires RESEND_API_KEY, RESEND_FROM)
106
+ * "ses" = AWS SES v2 (ambient credentials, requires SES_FROM)
107
+ */
108
+ provider: "console" | "smtp" | "resend" | "ses"
109
+ /**
110
+ * SMTP settings for provider=smtp (merged into process env as GOTRUE_SMTP_*).
111
+ * Omitted keys can still be set via `.env` / shell.
112
+ */
113
+ smtp?: {
114
+ host?: string
115
+ port?: number
116
+ user?: string
117
+ pass?: string
118
+ admin_email?: string
119
+ sender_name?: string
120
+ }
121
+ /** Resend API key (provider=resend, or set RESEND_API_KEY env var). */
122
+ resend_api_key?: string
123
+ /** From address for Resend (provider=resend, or set RESEND_FROM env var). */
124
+ resend_from?: string
125
+ /** From address for SES (provider=ses, or set SES_FROM env var). */
126
+ ses_from?: string
127
+ /**
128
+ * When true, `supatype dev` enables the GoTrue send-email HTTP hook pointing at this
129
+ * server's POST `/internal/v0hooks/send-email` (signed delivery, dev-only secret).
130
+ * Override `GOTRUE_HOOK_SEND_EMAIL_*` in `.env` if needed.
131
+ */
132
+ send_email_hook?: boolean
133
+ /**
134
+ * Override hook target URL when `send_email_hook` is true (e.g. HTTPS tunnel or Edge URL).
135
+ * Default: `http://127.0.0.1:<serverPort>/internal/v0hooks/send-email`.
136
+ */
137
+ send_email_hook_uri?: string
138
+ /**
139
+ * Standard Webhooks v1 secrets for the send-email hook (`v1,whsec_...`, pipe-separated for rotation).
140
+ * Default in dev: a fixed local secret; override for team-shared dev or CI.
141
+ */
142
+ send_email_hook_secrets?: string
143
+ }
144
+ storage?: {
145
+ /**
146
+ * Storage backend.
147
+ * "local" = files on disk (LocalStoragePath required)
148
+ * "s3" = AWS S3 or compatible (ambient credentials)
149
+ */
150
+ provider: "local" | "s3"
151
+ /** Local directory to store objects in (provider=local). */
152
+ local_path?: string
153
+ }
154
+ schema?: {
155
+ /** Path (or glob) to the schema entry point. Defaults to "schema/index.ts". */
156
+ path?: string
157
+ /** Postgres schema name. Defaults to "public". */
158
+ pg_schema?: string
159
+ }
160
+ functions?: {
161
+ /** Path to edge functions directory, relative to `supatype.root` when not absolute. */
162
+ path?: string
163
+ }
164
+ output?: {
165
+ /** Path for generated TypeScript types. */
166
+ types?: string
167
+ /** Path for generated client helpers. */
168
+ client?: string
169
+ }
170
+ /**
171
+ * App build configuration for `supatype deploy`.
172
+ * Separate from `app` which controls how supatype-server serves at runtime.
173
+ */
174
+ build?: {
175
+ /** Framework name. Auto-detected from package.json when omitted. */
176
+ framework?: "nextjs" | "astro" | "vite" | "remix-spa" | "sveltekit" | "nuxt" | "static"
177
+ /** Path to the app directory. Defaults to cwd. */
178
+ directory?: string
179
+ /** Build command. Inferred from framework when omitted. */
180
+ buildCommand?: string
181
+ /** Build output directory. Inferred from framework when omitted. */
182
+ outputDirectory?: string
183
+ /** Enable SPA fallback routing. */
184
+ spa?: boolean
185
+ /** Environment variables injected at build time. */
186
+ env?: Record<string, string>
187
+ /** Custom response headers for the deployed static site. */
188
+ headers?: Record<string, string>
189
+ }
190
+ /**
191
+ * Optional Postgres URL for CLI commands that talk to the DB (`push`, `migrate`, …).
192
+ * When omitted, `DATABASE_URL` from the environment is used, then a local default DSN.
193
+ */
194
+ connection?: string
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Merge + validate
199
+ // ---------------------------------------------------------------------------
200
+
201
+ /**
202
+ * Merge each top-level section from `override` on top of `base`.
203
+ * Within each section, override values win. New optional sections in override are added.
204
+ */
205
+ export function mergeProjectConfig(
206
+ base: SupatypeProjectConfig,
207
+ override: Partial<SupatypeProjectConfig>,
208
+ ): SupatypeProjectConfig {
209
+ return {
210
+ ...(base.supatype !== undefined || override.supatype !== undefined
211
+ ? { supatype: { ...base.supatype, ...override.supatype } as NonNullable<SupatypeProjectConfig["supatype"]> }
212
+ : {}),
213
+ project: { ...base.project, ...override.project },
214
+ database: { ...base.database, ...override.database },
215
+ server: { ...base.server, ...override.server },
216
+ app: { ...base.app, ...override.app },
217
+ versions: { ...base.versions, ...override.versions },
218
+ ...(base.overrides !== undefined || override.overrides !== undefined
219
+ ? {
220
+ overrides: {
221
+ ...base.overrides,
222
+ ...override.overrides,
223
+ } as NonNullable<SupatypeProjectConfig["overrides"]>,
224
+ }
225
+ : {}),
226
+ ...(base.email !== undefined || override.email !== undefined
227
+ ? (() => {
228
+ const b = base.email
229
+ const o = override.email
230
+ const mergedSmtp =
231
+ b?.smtp !== undefined || o?.smtp !== undefined
232
+ ? { ...(b?.smtp ?? {}), ...(o?.smtp ?? {}) }
233
+ : undefined
234
+ return {
235
+ email: {
236
+ ...b,
237
+ ...o,
238
+ ...(mergedSmtp !== undefined ? { smtp: mergedSmtp } : {}),
239
+ } as NonNullable<SupatypeProjectConfig["email"]>,
240
+ }
241
+ })()
242
+ : {}),
243
+ ...(base.storage !== undefined || override.storage !== undefined
244
+ ? {
245
+ storage: {
246
+ ...base.storage,
247
+ ...override.storage,
248
+ } as NonNullable<SupatypeProjectConfig["storage"]>,
249
+ }
250
+ : {}),
251
+ ...(base.schema !== undefined || override.schema !== undefined
252
+ ? { schema: { ...base.schema, ...override.schema } as NonNullable<SupatypeProjectConfig["schema"]> }
253
+ : {}),
254
+ ...(base.functions !== undefined || override.functions !== undefined
255
+ ? { functions: { ...base.functions, ...override.functions } as NonNullable<SupatypeProjectConfig["functions"]> }
256
+ : {}),
257
+ ...(base.output !== undefined || override.output !== undefined
258
+ ? { output: { ...base.output, ...override.output } as NonNullable<SupatypeProjectConfig["output"]> }
259
+ : {}),
260
+ ...(base.build !== undefined || override.build !== undefined
261
+ ? { build: { ...base.build, ...override.build } as NonNullable<SupatypeProjectConfig["build"]> }
262
+ : {}),
263
+ ...(base.connection !== undefined || override.connection !== undefined
264
+ ? { connection: override.connection ?? base.connection }
265
+ : {}),
266
+ }
267
+ }
268
+
269
+ export function validateProjectConfig(raw: unknown, filename: string): SupatypeProjectConfig {
270
+ if (typeof raw !== "object" || raw === null) {
271
+ throw new Error(`${filename}: expected a config object at the root`)
272
+ }
273
+
274
+ const cfg = raw as Record<string, unknown>
275
+
276
+ if (!cfg["project"] || typeof (cfg["project"] as Record<string, unknown>)["name"] !== "string") {
277
+ throw new Error(`${filename}: project.name is required`)
278
+ }
279
+ if (!cfg["database"]) {
280
+ throw new Error(`${filename}: database section is required`)
281
+ }
282
+ if (!cfg["server"]) {
283
+ throw new Error(`${filename}: server section is required`)
284
+ }
285
+ if (!cfg["app"]) {
286
+ throw new Error(`${filename}: app section is required`)
287
+ }
288
+ if (!cfg["versions"]) {
289
+ throw new Error(`${filename}: versions section is required`)
290
+ }
291
+
292
+ return raw as SupatypeProjectConfig
293
+ }
294
+
295
+ /** Schema entry path (with fallback). */
296
+ export function schemaPathFromProject(cfg: SupatypeProjectConfig, cwd: string): string {
297
+ return resolve(projectRootFromConfig(cfg, cwd), cfg.schema?.path ?? "schema/index.ts")
298
+ }
299
+
300
+ /** Resolve project root for schema/functions defaults. */
301
+ export function projectRootFromConfig(cfg: SupatypeProjectConfig, cwd: string): string {
302
+ return resolve(cwd, cfg.supatype?.root ?? ".")
303
+ }
304
+
305
+ /** Candidate functions directories in lookup order. */
306
+ export function functionsPathCandidatesFromProject(cfg: SupatypeProjectConfig, cwd: string): string[] {
307
+ const root = projectRootFromConfig(cfg, cwd)
308
+ if (cfg.functions?.path) {
309
+ return [resolve(root, cfg.functions.path)]
310
+ }
311
+ // Prefer modern default, but keep legacy fallback for compatibility.
312
+ return [resolve(root, "functions"), resolve(root, "supatype/functions")]
313
+ }
314
+
315
+ /** Preferred default functions path (used when creating new functions). */
316
+ export function preferredFunctionsPathFromProject(cfg: SupatypeProjectConfig, cwd: string): string {
317
+ const candidates = functionsPathCandidatesFromProject(cfg, cwd)
318
+ for (const dir of candidates) {
319
+ if (existsSync(dir)) return dir
320
+ }
321
+ return candidates[0] ?? resolve(projectRootFromConfig(cfg, cwd), "functions")
322
+ }
323
+
324
+ /**
325
+ * Derive the supatype-server base URL from the project config.
326
+ * Returns undefined if the mode is "managed" (cloud controls the URL).
327
+ */
328
+ export function serverBaseUrl(cfg: SupatypeProjectConfig): string | undefined {
329
+ const port = cfg.server.port ?? 54321
330
+ switch (cfg.server.mode) {
331
+ case "dev":
332
+ case "standalone":
333
+ return cfg.server.domain
334
+ ? `https://${cfg.server.domain}`
335
+ : `http://localhost:${port}`
336
+ case "managed":
337
+ return undefined
338
+ }
339
+ }
340
+
341
+ /** The local Postgres DSN derived from project name (dev default). */
342
+ export function localDSN(cfg: SupatypeProjectConfig): string {
343
+ const port = 5432 // standard; per-project state dir isolates data dirs
344
+ return `postgres://postgres:postgres@127.0.0.1:${port}/${cfg.project.name}?sslmode=disable`
345
+ }
346
+
347
+ /**
348
+ * Resolve the database connection string.
349
+ * Prefers optional `connection` in config, then `DATABASE_URL` env, then a local default DSN.
350
+ */
351
+ export function connectionString(cfg: SupatypeProjectConfig): string {
352
+ return cfg.connection ?? process.env["DATABASE_URL"] ?? localDSN(cfg)
353
+ }
package/src/pull-utils.ts CHANGED
@@ -11,6 +11,30 @@ export interface ColumnInfo {
11
11
  hasDefault: boolean
12
12
  }
13
13
 
14
+ /** Engine `/introspect` column shape (see {@link IntrospectResult} in engine-client). */
15
+ export interface IntrospectColumn {
16
+ name: string
17
+ type: string
18
+ nullable: boolean
19
+ default?: string
20
+ primaryKey?: boolean
21
+ unique?: boolean
22
+ references?: { table: string; column: string }
23
+ }
24
+
25
+ /** Map engine introspection JSON to {@link ColumnInfo} for {@link pgTypeToField}. */
26
+ export function introspectColumnToColumnInfo(col: IntrospectColumn): ColumnInfo {
27
+ const def = col.default
28
+ return {
29
+ name: col.name,
30
+ pgType: col.type,
31
+ nullable: col.nullable,
32
+ isPrimary: col.primaryKey ?? false,
33
+ isUnique: col.unique ?? false,
34
+ hasDefault: def !== undefined && def !== "",
35
+ }
36
+ }
37
+
14
38
  /** Map a Postgres column type to the corresponding field.X() call string. */
15
39
  export function pgTypeToField(col: ColumnInfo): string {
16
40
  const opts: Record<string, unknown> = { required: !col.nullable }