@supatype/cli 0.1.0-alpha.10

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 (416) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +221 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/assets/supatype-logo-wordmark.ascii.txt +6 -0
  5. package/bin/dev-entry.ts +2 -0
  6. package/bin/supatype.js +5 -0
  7. package/dist/app/framework.d.ts +44 -0
  8. package/dist/app/framework.d.ts.map +1 -0
  9. package/dist/app/framework.js +200 -0
  10. package/dist/app/framework.js.map +1 -0
  11. package/dist/app/proxy-dev-app.d.ts +13 -0
  12. package/dist/app/proxy-dev-app.d.ts.map +1 -0
  13. package/dist/app/proxy-dev-app.js +54 -0
  14. package/dist/app/proxy-dev-app.js.map +1 -0
  15. package/dist/app-config.d.ts +7 -0
  16. package/dist/app-config.d.ts.map +1 -0
  17. package/dist/app-config.js +113 -0
  18. package/dist/app-config.js.map +1 -0
  19. package/dist/assets/supatype-logo-wordmark.ascii.txt +6 -0
  20. package/dist/augmentation-generator.d.ts +2 -0
  21. package/dist/augmentation-generator.d.ts.map +1 -0
  22. package/dist/augmentation-generator.js +111 -0
  23. package/dist/augmentation-generator.js.map +1 -0
  24. package/dist/binary-cache.d.ts +98 -0
  25. package/dist/binary-cache.d.ts.map +1 -0
  26. package/dist/binary-cache.js +687 -0
  27. package/dist/binary-cache.js.map +1 -0
  28. package/dist/cli.d.ts +2 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +61 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/commands/admin.d.ts +4 -0
  33. package/dist/commands/admin.d.ts.map +1 -0
  34. package/dist/commands/admin.js +271 -0
  35. package/dist/commands/admin.js.map +1 -0
  36. package/dist/commands/app.d.ts +3 -0
  37. package/dist/commands/app.d.ts.map +1 -0
  38. package/dist/commands/app.js +82 -0
  39. package/dist/commands/app.js.map +1 -0
  40. package/dist/commands/cache.d.ts +6 -0
  41. package/dist/commands/cache.d.ts.map +1 -0
  42. package/dist/commands/cache.js +105 -0
  43. package/dist/commands/cache.js.map +1 -0
  44. package/dist/commands/cloud.d.ts +23 -0
  45. package/dist/commands/cloud.d.ts.map +1 -0
  46. package/dist/commands/cloud.js +254 -0
  47. package/dist/commands/cloud.js.map +1 -0
  48. package/dist/commands/db.d.ts +8 -0
  49. package/dist/commands/db.d.ts.map +1 -0
  50. package/dist/commands/db.js +116 -0
  51. package/dist/commands/db.js.map +1 -0
  52. package/dist/commands/deploy-types.d.ts +14 -0
  53. package/dist/commands/deploy-types.d.ts.map +1 -0
  54. package/dist/commands/deploy-types.js +38 -0
  55. package/dist/commands/deploy-types.js.map +1 -0
  56. package/dist/commands/deploy.d.ts +15 -0
  57. package/dist/commands/deploy.d.ts.map +1 -0
  58. package/dist/commands/deploy.js +322 -0
  59. package/dist/commands/deploy.js.map +1 -0
  60. package/dist/commands/dev.d.ts +14 -0
  61. package/dist/commands/dev.d.ts.map +1 -0
  62. package/dist/commands/dev.js +806 -0
  63. package/dist/commands/dev.js.map +1 -0
  64. package/dist/commands/diff.d.ts +3 -0
  65. package/dist/commands/diff.d.ts.map +1 -0
  66. package/dist/commands/diff.js +54 -0
  67. package/dist/commands/diff.js.map +1 -0
  68. package/dist/commands/engine.d.ts +7 -0
  69. package/dist/commands/engine.d.ts.map +1 -0
  70. package/dist/commands/engine.js +27 -0
  71. package/dist/commands/engine.js.map +1 -0
  72. package/dist/commands/functions.d.ts +3 -0
  73. package/dist/commands/functions.d.ts.map +1 -0
  74. package/dist/commands/functions.js +749 -0
  75. package/dist/commands/functions.js.map +1 -0
  76. package/dist/commands/generate.d.ts +3 -0
  77. package/dist/commands/generate.d.ts.map +1 -0
  78. package/dist/commands/generate.js +38 -0
  79. package/dist/commands/generate.js.map +1 -0
  80. package/dist/commands/init.d.ts +7 -0
  81. package/dist/commands/init.d.ts.map +1 -0
  82. package/dist/commands/init.js +228 -0
  83. package/dist/commands/init.js.map +1 -0
  84. package/dist/commands/keys.d.ts +4 -0
  85. package/dist/commands/keys.d.ts.map +1 -0
  86. package/dist/commands/keys.js +57 -0
  87. package/dist/commands/keys.js.map +1 -0
  88. package/dist/commands/logs.d.ts +6 -0
  89. package/dist/commands/logs.d.ts.map +1 -0
  90. package/dist/commands/logs.js +52 -0
  91. package/dist/commands/logs.js.map +1 -0
  92. package/dist/commands/migrate-from-v1.d.ts +5 -0
  93. package/dist/commands/migrate-from-v1.d.ts.map +1 -0
  94. package/dist/commands/migrate-from-v1.js +125 -0
  95. package/dist/commands/migrate-from-v1.js.map +1 -0
  96. package/dist/commands/migrate.d.ts +3 -0
  97. package/dist/commands/migrate.d.ts.map +1 -0
  98. package/dist/commands/migrate.js +75 -0
  99. package/dist/commands/migrate.js.map +1 -0
  100. package/dist/commands/pg.d.ts +8 -0
  101. package/dist/commands/pg.d.ts.map +1 -0
  102. package/dist/commands/pg.js +102 -0
  103. package/dist/commands/pg.js.map +1 -0
  104. package/dist/commands/plugins.d.ts +3 -0
  105. package/dist/commands/plugins.d.ts.map +1 -0
  106. package/dist/commands/plugins.js +431 -0
  107. package/dist/commands/plugins.js.map +1 -0
  108. package/dist/commands/pull.d.ts +3 -0
  109. package/dist/commands/pull.d.ts.map +1 -0
  110. package/dist/commands/pull.js +12 -0
  111. package/dist/commands/pull.js.map +1 -0
  112. package/dist/commands/push.d.ts +3 -0
  113. package/dist/commands/push.d.ts.map +1 -0
  114. package/dist/commands/push.js +179 -0
  115. package/dist/commands/push.js.map +1 -0
  116. package/dist/commands/seed.d.ts +5 -0
  117. package/dist/commands/seed.d.ts.map +1 -0
  118. package/dist/commands/seed.js +55 -0
  119. package/dist/commands/seed.js.map +1 -0
  120. package/dist/commands/self-host.d.ts +9 -0
  121. package/dist/commands/self-host.d.ts.map +1 -0
  122. package/dist/commands/self-host.js +310 -0
  123. package/dist/commands/self-host.js.map +1 -0
  124. package/dist/commands/self-update.d.ts +9 -0
  125. package/dist/commands/self-update.d.ts.map +1 -0
  126. package/dist/commands/self-update.js +33 -0
  127. package/dist/commands/self-update.js.map +1 -0
  128. package/dist/commands/status.d.ts +6 -0
  129. package/dist/commands/status.d.ts.map +1 -0
  130. package/dist/commands/status.js +70 -0
  131. package/dist/commands/status.js.map +1 -0
  132. package/dist/commands/types.d.ts +3 -0
  133. package/dist/commands/types.d.ts.map +1 -0
  134. package/dist/commands/types.js +62 -0
  135. package/dist/commands/types.js.map +1 -0
  136. package/dist/commands/update.d.ts +7 -0
  137. package/dist/commands/update.d.ts.map +1 -0
  138. package/dist/commands/update.js +118 -0
  139. package/dist/commands/update.js.map +1 -0
  140. package/dist/components.d.ts +5 -0
  141. package/dist/components.d.ts.map +1 -0
  142. package/dist/components.js +3 -0
  143. package/dist/components.js.map +1 -0
  144. package/dist/config.d.ts +65 -0
  145. package/dist/config.d.ts.map +1 -0
  146. package/dist/config.js +134 -0
  147. package/dist/config.js.map +1 -0
  148. package/dist/dev-compose.d.ts +19 -0
  149. package/dist/dev-compose.d.ts.map +1 -0
  150. package/dist/dev-compose.js +468 -0
  151. package/dist/dev-compose.js.map +1 -0
  152. package/dist/dev-log-bus.d.ts +30 -0
  153. package/dist/dev-log-bus.d.ts.map +1 -0
  154. package/dist/dev-log-bus.js +87 -0
  155. package/dist/dev-log-bus.js.map +1 -0
  156. package/dist/dev-log-filter.d.ts +10 -0
  157. package/dist/dev-log-filter.d.ts.map +1 -0
  158. package/dist/dev-log-filter.js +36 -0
  159. package/dist/dev-log-filter.js.map +1 -0
  160. package/dist/dev-logo.d.ts +12 -0
  161. package/dist/dev-logo.d.ts.map +1 -0
  162. package/dist/dev-logo.js +57 -0
  163. package/dist/dev-logo.js.map +1 -0
  164. package/dist/dev-session.d.ts +26 -0
  165. package/dist/dev-session.d.ts.map +1 -0
  166. package/dist/dev-session.js +106 -0
  167. package/dist/dev-session.js.map +1 -0
  168. package/dist/dev-shutdown.d.ts +9 -0
  169. package/dist/dev-shutdown.d.ts.map +1 -0
  170. package/dist/dev-shutdown.js +50 -0
  171. package/dist/dev-shutdown.js.map +1 -0
  172. package/dist/dev-task-colors.d.ts +14 -0
  173. package/dist/dev-task-colors.d.ts.map +1 -0
  174. package/dist/dev-task-colors.js +44 -0
  175. package/dist/dev-task-colors.js.map +1 -0
  176. package/dist/dev-tui.d.ts +24 -0
  177. package/dist/dev-tui.d.ts.map +1 -0
  178. package/dist/dev-tui.js +188 -0
  179. package/dist/dev-tui.js.map +1 -0
  180. package/dist/diff-output.d.ts +4 -0
  181. package/dist/diff-output.d.ts.map +1 -0
  182. package/dist/diff-output.js +12 -0
  183. package/dist/diff-output.js.map +1 -0
  184. package/dist/docker-postgres.d.ts +57 -0
  185. package/dist/docker-postgres.d.ts.map +1 -0
  186. package/dist/docker-postgres.js +208 -0
  187. package/dist/docker-postgres.js.map +1 -0
  188. package/dist/engine-client.d.ts +69 -0
  189. package/dist/engine-client.d.ts.map +1 -0
  190. package/dist/engine-client.js +157 -0
  191. package/dist/engine-client.js.map +1 -0
  192. package/dist/engine-push-output.d.ts +16 -0
  193. package/dist/engine-push-output.d.ts.map +1 -0
  194. package/dist/engine-push-output.js +61 -0
  195. package/dist/engine-push-output.js.map +1 -0
  196. package/dist/ensure-binary.d.ts +7 -0
  197. package/dist/ensure-binary.d.ts.map +1 -0
  198. package/dist/ensure-binary.js +17 -0
  199. package/dist/ensure-binary.js.map +1 -0
  200. package/dist/functions-router-gen.d.ts +14 -0
  201. package/dist/functions-router-gen.d.ts.map +1 -0
  202. package/dist/functions-router-gen.js +199 -0
  203. package/dist/functions-router-gen.js.map +1 -0
  204. package/dist/index.d.ts +11 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +9 -0
  207. package/dist/index.js.map +1 -0
  208. package/dist/jwt.d.ts +3 -0
  209. package/dist/jwt.d.ts.map +1 -0
  210. package/dist/jwt.js +13 -0
  211. package/dist/jwt.js.map +1 -0
  212. package/dist/kong-config.d.ts +25 -0
  213. package/dist/kong-config.d.ts.map +1 -0
  214. package/dist/kong-config.js +71 -0
  215. package/dist/kong-config.js.map +1 -0
  216. package/dist/local-gateway.d.ts +7 -0
  217. package/dist/local-gateway.d.ts.map +1 -0
  218. package/dist/local-gateway.js +9 -0
  219. package/dist/local-gateway.js.map +1 -0
  220. package/dist/local-storage.d.ts +8 -0
  221. package/dist/local-storage.d.ts.map +1 -0
  222. package/dist/local-storage.js +14 -0
  223. package/dist/local-storage.js.map +1 -0
  224. package/dist/pgbouncer-userlist.d.ts +5 -0
  225. package/dist/pgbouncer-userlist.d.ts.map +1 -0
  226. package/dist/pgbouncer-userlist.js +14 -0
  227. package/dist/pgbouncer-userlist.js.map +1 -0
  228. package/dist/postgres-ctl.d.ts +44 -0
  229. package/dist/postgres-ctl.d.ts.map +1 -0
  230. package/dist/postgres-ctl.js +137 -0
  231. package/dist/postgres-ctl.js.map +1 -0
  232. package/dist/process-manager.d.ts +49 -0
  233. package/dist/process-manager.d.ts.map +1 -0
  234. package/dist/process-manager.js +177 -0
  235. package/dist/process-manager.js.map +1 -0
  236. package/dist/project-config.d.ts +238 -0
  237. package/dist/project-config.d.ts.map +1 -0
  238. package/dist/project-config.js +159 -0
  239. package/dist/project-config.js.map +1 -0
  240. package/dist/pull-utils.d.ts +31 -0
  241. package/dist/pull-utils.d.ts.map +1 -0
  242. package/dist/pull-utils.js +77 -0
  243. package/dist/pull-utils.js.map +1 -0
  244. package/dist/release-pins.d.ts +7 -0
  245. package/dist/release-pins.d.ts.map +1 -0
  246. package/dist/release-pins.js +27 -0
  247. package/dist/release-pins.js.map +1 -0
  248. package/dist/release-public-key.d.ts +8 -0
  249. package/dist/release-public-key.d.ts.map +1 -0
  250. package/dist/release-public-key.js +13 -0
  251. package/dist/release-public-key.js.map +1 -0
  252. package/dist/restore-system-relation-targets.d.ts +3 -0
  253. package/dist/restore-system-relation-targets.d.ts.map +1 -0
  254. package/dist/restore-system-relation-targets.js +45 -0
  255. package/dist/restore-system-relation-targets.js.map +1 -0
  256. package/dist/runtime-routes.d.ts +34 -0
  257. package/dist/runtime-routes.d.ts.map +1 -0
  258. package/dist/runtime-routes.js +252 -0
  259. package/dist/runtime-routes.js.map +1 -0
  260. package/dist/schema-ast-v2.d.ts +127 -0
  261. package/dist/schema-ast-v2.d.ts.map +1 -0
  262. package/dist/schema-ast-v2.js +226 -0
  263. package/dist/schema-ast-v2.js.map +1 -0
  264. package/dist/scripts/postinstall.d.ts +11 -0
  265. package/dist/scripts/postinstall.d.ts.map +1 -0
  266. package/dist/scripts/postinstall.js +47 -0
  267. package/dist/scripts/postinstall.js.map +1 -0
  268. package/dist/seed.d.ts +8 -0
  269. package/dist/seed.d.ts.map +1 -0
  270. package/dist/seed.js +32 -0
  271. package/dist/seed.js.map +1 -0
  272. package/dist/self-host-compose.d.ts +43 -0
  273. package/dist/self-host-compose.d.ts.map +1 -0
  274. package/dist/self-host-compose.js +400 -0
  275. package/dist/self-host-compose.js.map +1 -0
  276. package/dist/storage-provision.d.ts +24 -0
  277. package/dist/storage-provision.d.ts.map +1 -0
  278. package/dist/storage-provision.js +44 -0
  279. package/dist/storage-provision.js.map +1 -0
  280. package/dist/studio-admin-roles.d.ts +7 -0
  281. package/dist/studio-admin-roles.d.ts.map +1 -0
  282. package/dist/studio-admin-roles.js +14 -0
  283. package/dist/studio-admin-roles.js.map +1 -0
  284. package/dist/studio-dev-server.d.ts +22 -0
  285. package/dist/studio-dev-server.d.ts.map +1 -0
  286. package/dist/studio-dev-server.js +28 -0
  287. package/dist/studio-dev-server.js.map +1 -0
  288. package/dist/supatype-eval-1781522769253.d.mts +2 -0
  289. package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
  290. package/dist/supatype-eval-1781522769253.mjs +3 -0
  291. package/dist/supatype-eval-1781522769253.mjs.map +1 -0
  292. package/dist/systemd.d.ts +26 -0
  293. package/dist/systemd.d.ts.map +1 -0
  294. package/dist/systemd.js +102 -0
  295. package/dist/systemd.js.map +1 -0
  296. package/dist/tsx-runner.d.ts +18 -0
  297. package/dist/tsx-runner.d.ts.map +1 -0
  298. package/dist/tsx-runner.js +69 -0
  299. package/dist/tsx-runner.js.map +1 -0
  300. package/dist/type-extractor.d.ts +4 -0
  301. package/dist/type-extractor.d.ts.map +1 -0
  302. package/dist/type-extractor.js +1213 -0
  303. package/dist/type-extractor.js.map +1 -0
  304. package/dist/type-resolver.d.ts +33 -0
  305. package/dist/type-resolver.d.ts.map +1 -0
  306. package/dist/type-resolver.js +338 -0
  307. package/dist/type-resolver.js.map +1 -0
  308. package/package.json +41 -0
  309. package/releases/deno/VERSION +1 -0
  310. package/scripts/mirror-deno-release.sh +76 -0
  311. package/src/TYPE-RESOLUTION.md +294 -0
  312. package/src/app/framework.ts +249 -0
  313. package/src/app/proxy-dev-app.ts +68 -0
  314. package/src/app-config.ts +128 -0
  315. package/src/augmentation-generator.ts +126 -0
  316. package/src/binary-cache.ts +845 -0
  317. package/src/cli.ts +63 -0
  318. package/src/commands/admin.ts +372 -0
  319. package/src/commands/app.ts +97 -0
  320. package/src/commands/cache.ts +117 -0
  321. package/src/commands/cloud.ts +325 -0
  322. package/src/commands/db.ts +136 -0
  323. package/src/commands/deploy-types.ts +49 -0
  324. package/src/commands/deploy.ts +400 -0
  325. package/src/commands/dev.ts +1009 -0
  326. package/src/commands/diff.ts +63 -0
  327. package/src/commands/engine.ts +30 -0
  328. package/src/commands/functions.ts +901 -0
  329. package/src/commands/generate.ts +44 -0
  330. package/src/commands/init.ts +253 -0
  331. package/src/commands/keys.ts +66 -0
  332. package/src/commands/logs.ts +58 -0
  333. package/src/commands/migrate-from-v1.ts +131 -0
  334. package/src/commands/migrate.ts +87 -0
  335. package/src/commands/pg.ts +133 -0
  336. package/src/commands/plugins.ts +508 -0
  337. package/src/commands/pull.ts +17 -0
  338. package/src/commands/push.ts +226 -0
  339. package/src/commands/seed.ts +68 -0
  340. package/src/commands/self-host.ts +364 -0
  341. package/src/commands/self-update.ts +45 -0
  342. package/src/commands/status.ts +84 -0
  343. package/src/commands/types.ts +76 -0
  344. package/src/commands/update.ts +136 -0
  345. package/src/components.ts +6 -0
  346. package/src/config.ts +223 -0
  347. package/src/dev-compose.ts +583 -0
  348. package/src/dev-log-bus.ts +101 -0
  349. package/src/dev-log-filter.ts +32 -0
  350. package/src/dev-logo.ts +62 -0
  351. package/src/dev-session.ts +130 -0
  352. package/src/dev-shutdown.ts +54 -0
  353. package/src/dev-task-colors.ts +47 -0
  354. package/src/dev-tui.ts +232 -0
  355. package/src/diff-output.ts +12 -0
  356. package/src/docker-postgres.ts +295 -0
  357. package/src/engine-client.ts +236 -0
  358. package/src/engine-push-output.ts +71 -0
  359. package/src/ensure-binary.ts +28 -0
  360. package/src/functions-router-gen.ts +224 -0
  361. package/src/index.ts +11 -0
  362. package/src/jwt.ts +14 -0
  363. package/src/kong-config.ts +93 -0
  364. package/src/local-gateway.ts +9 -0
  365. package/src/local-storage.ts +14 -0
  366. package/src/pgbouncer-userlist.ts +15 -0
  367. package/src/postgres-ctl.ts +171 -0
  368. package/src/process-manager.ts +220 -0
  369. package/src/project-config.ts +388 -0
  370. package/src/pull-utils.ts +81 -0
  371. package/src/release-pins.ts +31 -0
  372. package/src/release-public-key.ts +12 -0
  373. package/src/restore-system-relation-targets.ts +45 -0
  374. package/src/runtime-routes.ts +291 -0
  375. package/src/schema-ast-v2.ts +324 -0
  376. package/src/scripts/postinstall.ts +51 -0
  377. package/src/seed.ts +43 -0
  378. package/src/self-host-compose.ts +452 -0
  379. package/src/storage-provision.ts +58 -0
  380. package/src/studio-admin-roles.ts +16 -0
  381. package/src/studio-dev-server.ts +53 -0
  382. package/src/supatype-eval-1781522769253.mts +1 -0
  383. package/src/systemd.ts +137 -0
  384. package/src/tsx-runner.ts +89 -0
  385. package/src/type-extractor.ts +1479 -0
  386. package/src/type-resolver.ts +457 -0
  387. package/tests/app-command.test.ts +54 -0
  388. package/tests/augmentation-generator.test.ts +59 -0
  389. package/tests/binary-cache-cloud-overrides.test.ts +123 -0
  390. package/tests/cached-artifact-format.test.ts +84 -0
  391. package/tests/cli-help.test.ts +133 -0
  392. package/tests/config.test.ts +252 -0
  393. package/tests/dev-ui.test.ts +139 -0
  394. package/tests/docker-postgres.test.ts +39 -0
  395. package/tests/engine-distribution.test.ts +418 -0
  396. package/tests/engine-push-output.test.ts +67 -0
  397. package/tests/ensure-binary.test.ts +59 -0
  398. package/tests/init.test.ts +127 -0
  399. package/tests/keys.test.ts +160 -0
  400. package/tests/migrate-from-v1.test.ts +29 -0
  401. package/tests/normalize-admin-config.test.ts +48 -0
  402. package/tests/pg-spawn-env.test.ts +18 -0
  403. package/tests/postgres-archive-tag.test.ts +9 -0
  404. package/tests/proxy-dev-app.test.ts +33 -0
  405. package/tests/pull-utils.test.ts +150 -0
  406. package/tests/release-pins.test.ts +28 -0
  407. package/tests/runtime-contract.test.ts +370 -0
  408. package/tests/seed-discover.test.ts +31 -0
  409. package/tests/studio-admin-roles.test.ts +27 -0
  410. package/tests/tsconfig.json +9 -0
  411. package/tests/tsx-runner.test.ts +66 -0
  412. package/tests/type-extractor.test.ts +985 -0
  413. package/tests/type-resolver.test.ts +59 -0
  414. package/tsconfig.json +10 -0
  415. package/tsconfig.tsbuildinfo +1 -0
  416. package/vitest.config.ts +12 -0
@@ -0,0 +1,220 @@
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, spawnSync } 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
+ import { enhanceProcessOptions } from "./dev-session.js"
11
+
12
+ export interface ProcessOptions {
13
+ /** Human-readable label (used in log prefix and PID filename). */
14
+ label: string
15
+ /** Directory to write {label}.pid to. */
16
+ pidDir: string
17
+ /** ANSI colour prefix string (e.g. "\x1b[36m"). Pass "" for no colour. */
18
+ colour?: string
19
+ /** Working directory for the spawned process. Defaults to process.cwd(). */
20
+ cwd?: string
21
+ /** Environment variables to merge with process.env. */
22
+ env?: Record<string, string>
23
+ /** Initial restart backoff in ms. Doubles each crash up to maxBackoffMs. */
24
+ initialBackoffMs?: number
25
+ /** Maximum restart backoff cap in ms. */
26
+ maxBackoffMs?: number
27
+ /** Called when the process exits cleanly (code 0). */
28
+ onExit?: () => void
29
+ /** Use shell to spawn (required for pnpm/npm/yarn .cmd shims on Windows). */
30
+ shell?: boolean
31
+ /** When set (TUI mode), receive log lines instead of writing to stdout/stderr. */
32
+ onLine?: (line: string, stream: "stdout" | "stderr") => void
33
+ /** Return false to drop a line before logging. */
34
+ shouldLogLine?: (line: string) => boolean
35
+ }
36
+
37
+ const RESET = "\x1b[0m"
38
+
39
+ export class ProcessManager {
40
+ private child: ChildProcess | null = null
41
+ private stopped = false
42
+ private backoffMs: number
43
+ private opts: ProcessOptions & {
44
+ colour: string
45
+ cwd: string
46
+ env: Record<string, string>
47
+ initialBackoffMs: number
48
+ maxBackoffMs: number
49
+ onExit: () => void
50
+ shell: boolean
51
+ }
52
+ /** Skip immediate duplicate lines (npm prints script banners to both streams). */
53
+ private lastLoggedLine = ""
54
+
55
+ constructor(
56
+ private readonly bin: string,
57
+ private readonly args: string[],
58
+ opts: ProcessOptions,
59
+ ) {
60
+ const merged = enhanceProcessOptions(opts.label, opts)
61
+ this.opts = {
62
+ colour: "\x1b[36m",
63
+ cwd: process.cwd(),
64
+ env: {},
65
+ initialBackoffMs: 1_000,
66
+ maxBackoffMs: 30_000,
67
+ onExit: () => {},
68
+ shell: false,
69
+ ...merged,
70
+ }
71
+ this.backoffMs = this.opts.initialBackoffMs
72
+ }
73
+
74
+ /** Start the process. Returns immediately — the process runs in the background. */
75
+ start(): void {
76
+ this.stopped = false
77
+ this.spawn()
78
+ }
79
+
80
+ /** Stop the process and clear the PID file. */
81
+ async stop(): Promise<void> {
82
+ this.stopped = true
83
+ if (!this.child || this.child.killed) {
84
+ await this.clearPid()
85
+ return
86
+ }
87
+
88
+ const pid = this.child.pid
89
+ if (process.platform === "win32" && this.opts.shell && pid) {
90
+ spawnSync("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" })
91
+ this.child = null
92
+ await this.clearPid()
93
+ return
94
+ }
95
+
96
+ if (!this.child.killed) {
97
+ this.child.kill("SIGTERM")
98
+ // Give it 5s to exit gracefully, then SIGKILL.
99
+ await new Promise<void>((resolve) => {
100
+ const timeout = setTimeout(() => {
101
+ this.child?.kill("SIGKILL")
102
+ resolve()
103
+ }, 5_000)
104
+ this.child!.once("exit", () => {
105
+ clearTimeout(timeout)
106
+ resolve()
107
+ })
108
+ })
109
+ }
110
+ await this.clearPid()
111
+ }
112
+
113
+ private spawn(): void {
114
+ if (this.stopped) return
115
+
116
+ const env = { ...process.env, ...this.opts.env } as NodeJS.ProcessEnv
117
+ this.child = spawn(this.bin, this.args, {
118
+ env,
119
+ cwd: this.opts.cwd,
120
+ stdio: "pipe",
121
+ ...(this.opts.shell ? { shell: true } : {}),
122
+ })
123
+
124
+ const pid = this.child.pid
125
+ if (pid) this.writePid(pid)
126
+
127
+ const prefix = this.opts.colour
128
+ ? `${this.opts.colour}[${this.opts.label}]${RESET} `
129
+ : `[${this.opts.label}] `
130
+
131
+ const logLine = (line: string, stream: NodeJS.WriteStream): void => {
132
+ if (!line) return
133
+ if (this.opts.shouldLogLine && !this.opts.shouldLogLine(line)) return
134
+ if (this.opts.onLine) {
135
+ if (line !== this.lastLoggedLine) {
136
+ this.lastLoggedLine = line
137
+ this.opts.onLine(line, stream === process.stderr ? "stderr" : "stdout")
138
+ }
139
+ return
140
+ }
141
+ if (line === this.lastLoggedLine) return
142
+ this.lastLoggedLine = line
143
+ stream.write(prefix + line + "\n")
144
+ }
145
+
146
+ this.child.stdout?.on("data", (chunk: Buffer) => {
147
+ for (const line of chunk.toString().split("\n")) {
148
+ logLine(line, process.stdout)
149
+ }
150
+ })
151
+
152
+ this.child.stderr?.on("data", (chunk: Buffer) => {
153
+ for (const line of chunk.toString().split("\n")) {
154
+ logLine(line, process.stderr)
155
+ }
156
+ })
157
+
158
+ this.child.once("error", (err) => {
159
+ if (this.stopped) return
160
+ const message = `${prefix}failed to start: ${err.message}\n`
161
+ if (this.opts.onLine) {
162
+ this.opts.onLine(`failed to start: ${err.message}`, "stderr")
163
+ } else {
164
+ process.stderr.write(message)
165
+ }
166
+ setTimeout(() => {
167
+ this.backoffMs = Math.min(this.backoffMs * 2, this.opts.maxBackoffMs)
168
+ this.spawn()
169
+ }, this.backoffMs)
170
+ })
171
+
172
+ this.child.once("exit", (code, signal) => {
173
+ if (this.stopped) return
174
+
175
+ if (code === 0) {
176
+ this.opts.onExit()
177
+ return
178
+ }
179
+
180
+ const reason = signal ? `signal ${signal}` : `code ${code}`
181
+ const restartMsg = `${prefix}process exited (${reason}), restarting in ${this.backoffMs}ms\n`
182
+ if (this.opts.onLine) {
183
+ this.opts.onLine(`process exited (${reason}), restarting in ${this.backoffMs}ms`, "stderr")
184
+ } else {
185
+ process.stderr.write(restartMsg)
186
+ }
187
+
188
+ setTimeout(() => {
189
+ this.backoffMs = Math.min(this.backoffMs * 2, this.opts.maxBackoffMs)
190
+ this.spawn()
191
+ }, this.backoffMs)
192
+ })
193
+ }
194
+
195
+ private writePid(pid: number): void {
196
+ try {
197
+ mkdirSync(this.opts.pidDir, { recursive: true })
198
+ writeFileSync(join(this.opts.pidDir, `${this.opts.label}.pid`), String(pid))
199
+ } catch {
200
+ // Non-fatal — PID tracking is best-effort.
201
+ }
202
+ }
203
+
204
+ private async clearPid(): Promise<void> {
205
+ try {
206
+ await unlink(join(this.opts.pidDir, `${this.opts.label}.pid`))
207
+ } catch {
208
+ // Ignore — file may already be gone.
209
+ }
210
+ }
211
+ }
212
+
213
+ /** Read a PID from a file (returns null if not found or stale). */
214
+ export function readPid(pidDir: string, label: string): number | null {
215
+ const pidFile = join(pidDir, `${label}.pid`)
216
+ if (!existsSync(pidFile)) return null
217
+ const raw = readFileSync(pidFile, "utf8").trim()
218
+ const pid = Number(raw)
219
+ return Number.isFinite(pid) && pid > 0 ? pid : null
220
+ }
@@ -0,0 +1,388 @@
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
+ /**
11
+ * Runtime stack for local dev and `supatype update`.
12
+ * "native" = host binaries (default). "docker" = self-host Compose stack.
13
+ * Falls back to `database.provider` when omitted (deprecated).
14
+ */
15
+ provider?: "native" | "docker"
16
+ supatype?: {
17
+ /**
18
+ * Base directory for Supatype project assets (schema, functions, etc).
19
+ * "." means the current working directory (default).
20
+ */
21
+ root?: string
22
+ }
23
+ project: {
24
+ /** Project name — used for per-project state dirs and logging. */
25
+ name: string
26
+ /** Cloud project reference (set by `supatype link`). */
27
+ ref?: string
28
+ }
29
+ database: {
30
+ /**
31
+ * Database backend.
32
+ * "native" = supatype manages a native Postgres binary (downloaded from CDN).
33
+ * "docker" = supatype runs supatype/postgres via Docker (includes all extensions).
34
+ */
35
+ provider: "native" | "docker"
36
+ /**
37
+ * Directory where Postgres stores its data files (provider=native).
38
+ * Defaults to ~/.supatype/projects/{name}/data when omitted.
39
+ */
40
+ data_dir?: string
41
+ /**
42
+ * Docker image to use (provider=docker).
43
+ * Defaults to supatype/postgres:latest.
44
+ * Override in supatype.local.config.ts for local builds.
45
+ */
46
+ image?: string
47
+ }
48
+ server: {
49
+ /**
50
+ * Server mode.
51
+ * "dev" = no TLS, permissive CORS, Vite HMR proxy
52
+ * "standalone" = ACME TLS (Let's Encrypt)
53
+ * "managed" = cloud-managed, HMAC tenant verification
54
+ */
55
+ mode: "dev" | "standalone" | "managed"
56
+ /** Port supatype-server listens on (default: 54321). */
57
+ port?: number
58
+ /** Port PostgREST listens on in local dev (default: 3001). */
59
+ postgrestPort?: number
60
+ /** Domain for ACME TLS certificate (mode=standalone). */
61
+ domain?: string
62
+ }
63
+ app: {
64
+ /**
65
+ * How the root path "/" is handled by supatype-server.
66
+ * "none" = 404
67
+ * "static" = serve files from static_dir
68
+ * "proxy" = reverse-proxy to upstream
69
+ */
70
+ mode: "none" | "static" | "proxy"
71
+ /** Directory to serve static files from (mode=static). */
72
+ static_dir?: string
73
+ /** Upstream URL to proxy to (mode=proxy). */
74
+ upstream?: string
75
+ /**
76
+ * Vite dev server base URL for HMR (`/_vite/*`) when `server.mode` is dev.
77
+ * Example: `http://127.0.0.1:5173`. Sets `SUPATYPE_VITE_DEV_URL` for supatype-server.
78
+ * When omitted, dev still falls back to `SUPATYPE_APP_UPSTREAM` for non-proxy app modes.
79
+ */
80
+ vite_dev_url?: string
81
+ /**
82
+ * package.json script name for `supatype dev` to run when mode is proxy.
83
+ * Default: `"start"`. Ignored for static/none modes.
84
+ */
85
+ start?: string
86
+ }
87
+ /**
88
+ * Optional pins for engine, server, postgres, and deno.
89
+ * Omitted = resolve latest from CDN at runtime (native) or use Docker :latest.
90
+ * When set, native binaries cache under ~/.supatype/cache/{component}/{version}/
91
+ * and Docker image tags are synced to `.env` on `supatype dev` / `supatype push`.
92
+ * Use **`"local"`** with the matching **`overrides.*`** entry for contributor builds.
93
+ */
94
+ versions?: Partial<ComponentVersions>
95
+ /**
96
+ * Override component binaries with local build paths.
97
+ * Intended for supatype contributors testing local changes.
98
+ * Cannot be combined with a linked cloud project (`project.ref`, `.supatype/cloud.json`, or `.supatype/linked.json`; hard error in `resolveBinary`).
99
+ */
100
+ overrides?: {
101
+ /** Path to local engine binary. */
102
+ engine?: string
103
+ /** Path to local supatype-server binary. */
104
+ server?: string
105
+ /** Path to a directory containing a local Postgres installation. */
106
+ postgres_dir?: string
107
+ /** Path to a local deno binary. */
108
+ deno?: string
109
+ /** Path to the @supatype/studio package directory (starts Vite dev server). */
110
+ studio?: string
111
+ /** Path to a local PostgREST binary. */
112
+ postgrest?: string
113
+ }
114
+ email?: {
115
+ /**
116
+ * Email delivery provider.
117
+ * "console" = log to stdout (default for dev)
118
+ * "smtp" = SMTP (set `smtp` below and/or GOTRUE_SMTP_* in `.env`)
119
+ * "resend" = Resend API (requires RESEND_API_KEY, RESEND_FROM)
120
+ * "ses" = AWS SES v2 (ambient credentials, requires SES_FROM)
121
+ */
122
+ provider: "console" | "smtp" | "resend" | "ses"
123
+ /**
124
+ * SMTP settings for provider=smtp (merged into process env as GOTRUE_SMTP_*).
125
+ * Omitted keys can still be set via `.env` / shell.
126
+ */
127
+ smtp?: {
128
+ host?: string
129
+ port?: number
130
+ user?: string
131
+ pass?: string
132
+ admin_email?: string
133
+ sender_name?: string
134
+ }
135
+ /** Resend API key (provider=resend, or set RESEND_API_KEY env var). */
136
+ resend_api_key?: string
137
+ /** From address for Resend (provider=resend, or set RESEND_FROM env var). */
138
+ resend_from?: string
139
+ /** From address for SES (provider=ses, or set SES_FROM env var). */
140
+ ses_from?: string
141
+ /**
142
+ * When true, `supatype dev` enables the GoTrue send-email HTTP hook pointing at this
143
+ * server's POST `/internal/v0hooks/send-email` (signed delivery, dev-only secret).
144
+ * Override `GOTRUE_HOOK_SEND_EMAIL_*` in `.env` if needed.
145
+ */
146
+ send_email_hook?: boolean
147
+ /**
148
+ * Override hook target URL when `send_email_hook` is true (e.g. HTTPS tunnel or Edge URL).
149
+ * Default: `http://127.0.0.1:<serverPort>/internal/v0hooks/send-email`.
150
+ */
151
+ send_email_hook_uri?: string
152
+ /**
153
+ * Standard Webhooks v1 secrets for the send-email hook (`v1,whsec_...`, pipe-separated for rotation).
154
+ * Default in dev: a fixed local secret; override for team-shared dev or CI.
155
+ */
156
+ send_email_hook_secrets?: string
157
+ }
158
+ storage?: {
159
+ /**
160
+ * Storage backend.
161
+ * "local" = files on disk (LocalStoragePath required)
162
+ * "s3" = AWS S3 or compatible (ambient credentials)
163
+ */
164
+ provider: "local" | "s3"
165
+ /** Local directory to store objects in (provider=local). */
166
+ local_path?: string
167
+ }
168
+ schema?: {
169
+ /** Path (or glob) to the schema entry point. Defaults to "schema/index.ts". */
170
+ path?: string
171
+ /** Postgres schema name. Defaults to "public". */
172
+ pg_schema?: string
173
+ }
174
+ functions?: {
175
+ /** Path to edge functions directory, relative to `supatype.root` when not absolute. */
176
+ path?: string
177
+ }
178
+ output?: {
179
+ /** Path for generated TypeScript types. */
180
+ types?: string
181
+ /** Path for generated client helpers. */
182
+ client?: string
183
+ }
184
+ /**
185
+ * App build configuration for `supatype deploy`.
186
+ * Separate from `app` which controls how supatype-server serves at runtime.
187
+ */
188
+ build?: {
189
+ /** Framework name. Auto-detected from package.json when omitted. */
190
+ framework?: "nextjs" | "astro" | "vite" | "remix-spa" | "sveltekit" | "nuxt" | "static"
191
+ /** Path to the app directory. Defaults to cwd. */
192
+ directory?: string
193
+ /** Build command. Inferred from framework when omitted. */
194
+ buildCommand?: string
195
+ /** Build output directory. Inferred from framework when omitted. */
196
+ outputDirectory?: string
197
+ /** Enable SPA fallback routing. */
198
+ spa?: boolean
199
+ /** Environment variables injected at build time. */
200
+ env?: Record<string, string>
201
+ /** Custom response headers for the deployed static site. */
202
+ headers?: Record<string, string>
203
+ }
204
+ /**
205
+ * Optional Postgres URL for CLI commands that talk to the DB (`push`, `migrate`, …).
206
+ * When omitted, `DATABASE_URL` from the environment is used, then a local default DSN.
207
+ */
208
+ connection?: string
209
+ /** Studio admin panel access (Gap Appendices task 47). */
210
+ admin?: {
211
+ /** JWT `app_metadata.role` values allowed to use Studio. Default: admin, supatype_admin */
212
+ roles?: string[]
213
+ }
214
+ }
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Merge + validate
218
+ // ---------------------------------------------------------------------------
219
+
220
+ /**
221
+ * Merge each top-level section from `override` on top of `base`.
222
+ * Within each section, override values win. New optional sections in override are added.
223
+ */
224
+ export function mergeProjectConfig(
225
+ base: SupatypeProjectConfig,
226
+ override: Partial<SupatypeProjectConfig>,
227
+ ): SupatypeProjectConfig {
228
+ return {
229
+ ...(base.provider !== undefined || override.provider !== undefined
230
+ ? { provider: override.provider ?? base.provider }
231
+ : {}),
232
+ ...(base.supatype !== undefined || override.supatype !== undefined
233
+ ? { supatype: { ...base.supatype, ...override.supatype } as NonNullable<SupatypeProjectConfig["supatype"]> }
234
+ : {}),
235
+ project: { ...base.project, ...override.project },
236
+ database: { ...base.database, ...override.database },
237
+ server: { ...base.server, ...override.server },
238
+ app: { ...base.app, ...override.app },
239
+ ...(base.versions !== undefined || override.versions !== undefined
240
+ ? { versions: { ...base.versions, ...override.versions } }
241
+ : {}),
242
+ ...(base.overrides !== undefined || override.overrides !== undefined
243
+ ? {
244
+ overrides: {
245
+ ...base.overrides,
246
+ ...override.overrides,
247
+ } as NonNullable<SupatypeProjectConfig["overrides"]>,
248
+ }
249
+ : {}),
250
+ ...(base.email !== undefined || override.email !== undefined
251
+ ? (() => {
252
+ const b = base.email
253
+ const o = override.email
254
+ const mergedSmtp =
255
+ b?.smtp !== undefined || o?.smtp !== undefined
256
+ ? { ...(b?.smtp ?? {}), ...(o?.smtp ?? {}) }
257
+ : undefined
258
+ return {
259
+ email: {
260
+ ...b,
261
+ ...o,
262
+ ...(mergedSmtp !== undefined ? { smtp: mergedSmtp } : {}),
263
+ } as NonNullable<SupatypeProjectConfig["email"]>,
264
+ }
265
+ })()
266
+ : {}),
267
+ ...(base.storage !== undefined || override.storage !== undefined
268
+ ? {
269
+ storage: {
270
+ ...base.storage,
271
+ ...override.storage,
272
+ } as NonNullable<SupatypeProjectConfig["storage"]>,
273
+ }
274
+ : {}),
275
+ ...(base.schema !== undefined || override.schema !== undefined
276
+ ? { schema: { ...base.schema, ...override.schema } as NonNullable<SupatypeProjectConfig["schema"]> }
277
+ : {}),
278
+ ...(base.functions !== undefined || override.functions !== undefined
279
+ ? { functions: { ...base.functions, ...override.functions } as NonNullable<SupatypeProjectConfig["functions"]> }
280
+ : {}),
281
+ ...(base.output !== undefined || override.output !== undefined
282
+ ? { output: { ...base.output, ...override.output } as NonNullable<SupatypeProjectConfig["output"]> }
283
+ : {}),
284
+ ...(base.build !== undefined || override.build !== undefined
285
+ ? { build: { ...base.build, ...override.build } as NonNullable<SupatypeProjectConfig["build"]> }
286
+ : {}),
287
+ ...(base.connection !== undefined || override.connection !== undefined
288
+ ? { connection: override.connection ?? base.connection }
289
+ : {}),
290
+ ...(base.admin !== undefined || override.admin !== undefined
291
+ ? { admin: { ...base.admin, ...override.admin } as NonNullable<SupatypeProjectConfig["admin"]> }
292
+ : {}),
293
+ }
294
+ }
295
+
296
+ export function validateProjectConfig(raw: unknown, filename: string): SupatypeProjectConfig {
297
+ if (typeof raw !== "object" || raw === null) {
298
+ throw new Error(`${filename}: expected a config object at the root`)
299
+ }
300
+
301
+ const cfg = raw as Record<string, unknown>
302
+
303
+ if (!cfg["project"] || typeof (cfg["project"] as Record<string, unknown>)["name"] !== "string") {
304
+ throw new Error(`${filename}: project.name is required`)
305
+ }
306
+ if (!cfg["database"]) {
307
+ throw new Error(`${filename}: database section is required`)
308
+ }
309
+ if (!cfg["server"]) {
310
+ throw new Error(`${filename}: server section is required`)
311
+ }
312
+ if (!cfg["app"]) {
313
+ throw new Error(`${filename}: app section is required`)
314
+ }
315
+
316
+ return raw as SupatypeProjectConfig
317
+ }
318
+
319
+ /** Schema entry path (with fallback). */
320
+ export function schemaPathFromProject(cfg: SupatypeProjectConfig, cwd: string): string {
321
+ return resolve(projectRootFromConfig(cfg, cwd), cfg.schema?.path ?? "schema/index.ts")
322
+ }
323
+
324
+ /** Resolve project root for schema/functions defaults. */
325
+ export function projectRootFromConfig(cfg: SupatypeProjectConfig, cwd: string): string {
326
+ return resolve(cwd, cfg.supatype?.root ?? ".")
327
+ }
328
+
329
+ /** Candidate functions directories in lookup order. */
330
+ export function functionsPathCandidatesFromProject(cfg: SupatypeProjectConfig, cwd: string): string[] {
331
+ const root = projectRootFromConfig(cfg, cwd)
332
+ if (cfg.functions?.path) {
333
+ return [resolve(root, cfg.functions.path)]
334
+ }
335
+ // Prefer modern default, but keep legacy fallback for compatibility.
336
+ return [resolve(root, "functions"), resolve(root, "supatype/functions")]
337
+ }
338
+
339
+ /** Preferred default functions path (used when creating new functions). */
340
+ export function preferredFunctionsPathFromProject(cfg: SupatypeProjectConfig, cwd: string): string {
341
+ const candidates = functionsPathCandidatesFromProject(cfg, cwd)
342
+ for (const dir of candidates) {
343
+ if (existsSync(dir)) return dir
344
+ }
345
+ return candidates[0] ?? resolve(projectRootFromConfig(cfg, cwd), "functions")
346
+ }
347
+
348
+ /**
349
+ * Derive the supatype-server base URL from the project config.
350
+ * Returns undefined if the mode is "managed" (cloud controls the URL).
351
+ */
352
+ export function serverBaseUrl(cfg: SupatypeProjectConfig): string | undefined {
353
+ const port = cfg.server.port ?? 54321
354
+ switch (cfg.server.mode) {
355
+ case "dev":
356
+ case "standalone":
357
+ if (cfg.server.mode === "dev" && resolveRuntimeProvider(cfg) === "docker") {
358
+ return `http://localhost:${COMPOSE_DEV_KONG_PORT}`
359
+ }
360
+ return cfg.server.domain
361
+ ? `https://${cfg.server.domain}`
362
+ : `http://localhost:${port}`
363
+ case "managed":
364
+ return undefined
365
+ }
366
+ }
367
+
368
+ /** Resolved runtime provider (`config.provider` ?? `database.provider` ?? native). */
369
+ export function resolveRuntimeProvider(cfg: SupatypeProjectConfig): "native" | "docker" {
370
+ return cfg.provider ?? cfg.database.provider ?? "native"
371
+ }
372
+
373
+ /** Kong gateway port when `provider: docker` (self-host compose dev). */
374
+ export const COMPOSE_DEV_KONG_PORT = 18473
375
+
376
+ /** The local Postgres DSN derived from project name (dev default). */
377
+ export function localDSN(cfg: SupatypeProjectConfig): string {
378
+ const port = 5432 // standard; per-project state dir isolates data dirs
379
+ return `postgres://postgres:postgres@127.0.0.1:${port}/${cfg.project.name}?sslmode=disable`
380
+ }
381
+
382
+ /**
383
+ * Resolve the database connection string.
384
+ * Prefers optional `connection` in config, then `DATABASE_URL` env, then a local default DSN.
385
+ */
386
+ export function connectionString(cfg: SupatypeProjectConfig): string {
387
+ return cfg.connection ?? process.env["DATABASE_URL"] ?? localDSN(cfg)
388
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Utilities for the `pull` command — extracted for unit testability.
3
+ */
4
+
5
+ export interface ColumnInfo {
6
+ name: string
7
+ pgType: string
8
+ nullable: boolean
9
+ isPrimary: boolean
10
+ isUnique: boolean
11
+ hasDefault: boolean
12
+ }
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
+
38
+ /** Map a Postgres column type to the corresponding field.X() call string. */
39
+ export function pgTypeToField(col: ColumnInfo): string {
40
+ const opts: Record<string, unknown> = { required: !col.nullable }
41
+ if (col.isPrimary) opts["primaryKey"] = true
42
+ if (col.isUnique && !col.isPrimary) opts["unique"] = true
43
+ const optsStr = JSON.stringify(opts)
44
+
45
+ const type = col.pgType.toLowerCase()
46
+
47
+ if (type.includes("uuid")) return `field.uuid(${optsStr})`
48
+ if (type.includes("text") || type.includes("varchar") || type.includes("char"))
49
+ return `field.text(${optsStr})`
50
+ if (type.includes("int8") || type.includes("bigint")) return `field.bigInt(${optsStr})`
51
+ if (type.includes("int2") || type.includes("smallint")) return `field.smallInt(${optsStr})`
52
+ if (type.includes("interval")) return `field.interval(${optsStr})`
53
+ if (type.includes("int") || type.includes("serial")) return `field.integer(${optsStr})`
54
+ if (type.includes("float") || type.includes("double") || type.includes("real"))
55
+ return `field.float(${optsStr})`
56
+ if (type.includes("numeric") || type.includes("decimal")) return `field.decimal(${optsStr})`
57
+ if (type.includes("bool")) return `field.boolean(${optsStr})`
58
+ if (type.includes("timestamptz") || type.includes("timestamp with time zone"))
59
+ return `field.datetime(${optsStr})`
60
+ if (type.includes("timestamp")) return `field.timestamp(${optsStr})`
61
+ if (type.includes("date")) return `field.date(${optsStr})`
62
+ if (type.includes("jsonb")) return `field.json({ ...${optsStr}, jsonb: true })`
63
+ if (type.includes("json")) return `field.json(${optsStr})`
64
+ if (type.includes("inet")) return `field.ip(${optsStr})`
65
+ if (type.includes("cidr")) return `field.cidr(${optsStr})`
66
+ if (type.includes("macaddr")) return `field.macaddr(${optsStr})`
67
+ if (type.includes("bytea")) return `field.bytea(${optsStr})`
68
+ if (type.includes("money")) return `field.money(${optsStr})`
69
+ if (type.includes("xml")) return `field.xml(${optsStr})`
70
+ if (type.includes("tsvector")) return `field.tsvector(${optsStr})`
71
+ if (type.includes("tsquery")) return `field.tsquery(${optsStr})`
72
+
73
+ return `field.text({ ...${optsStr} }) /* TODO: ${col.pgType} */`
74
+ }
75
+
76
+ /** Convert snake_case table name to PascalCase model export name. */
77
+ export function toCamelCase(s: string): string {
78
+ return s
79
+ .replace(/_([a-z])/g, (_, c: string) => c.toUpperCase())
80
+ .replace(/^([a-z])/, (c: string) => c.toUpperCase())
81
+ }