@supatype/cli 0.1.0-alpha.9 → 0.1.1

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 (407) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-test.log +285 -69
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/assets/supatype-logo-wordmark.ascii.txt +6 -0
  5. package/bin/dev-entry.ts +2 -1
  6. package/dist/app/framework.js +1 -3
  7. package/dist/app/framework.js.map +1 -1
  8. package/dist/app/proxy-dev-app.d.ts +14 -0
  9. package/dist/app/proxy-dev-app.d.ts.map +1 -1
  10. package/dist/app/proxy-dev-app.js +110 -6
  11. package/dist/app/proxy-dev-app.js.map +1 -1
  12. package/dist/app-config.d.ts +10 -0
  13. package/dist/app-config.d.ts.map +1 -1
  14. package/dist/app-config.js +72 -0
  15. package/dist/app-config.js.map +1 -1
  16. package/dist/assets/supatype-logo-wordmark.ascii.txt +6 -0
  17. package/dist/binary-cache.d.ts +19 -7
  18. package/dist/binary-cache.d.ts.map +1 -1
  19. package/dist/binary-cache.js +92 -46
  20. package/dist/binary-cache.js.map +1 -1
  21. package/dist/cli.d.ts +1 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +17 -2
  24. package/dist/cli.js.map +1 -1
  25. package/dist/commands/add.d.ts +3 -0
  26. package/dist/commands/add.d.ts.map +1 -0
  27. package/dist/commands/add.js +86 -0
  28. package/dist/commands/add.js.map +1 -0
  29. package/dist/commands/admin.d.ts +28 -1
  30. package/dist/commands/admin.d.ts.map +1 -1
  31. package/dist/commands/admin.js +297 -149
  32. package/dist/commands/admin.js.map +1 -1
  33. package/dist/commands/adopt.d.ts +3 -0
  34. package/dist/commands/adopt.d.ts.map +1 -0
  35. package/dist/commands/adopt.js +55 -0
  36. package/dist/commands/adopt.js.map +1 -0
  37. package/dist/commands/app.d.ts.map +1 -1
  38. package/dist/commands/app.js +20 -17
  39. package/dist/commands/app.js.map +1 -1
  40. package/dist/commands/cache.d.ts.map +1 -1
  41. package/dist/commands/cache.js +11 -10
  42. package/dist/commands/cache.js.map +1 -1
  43. package/dist/commands/cloud.d.ts +4 -9
  44. package/dist/commands/cloud.d.ts.map +1 -1
  45. package/dist/commands/cloud.js +75 -125
  46. package/dist/commands/cloud.js.map +1 -1
  47. package/dist/commands/db.d.ts.map +1 -1
  48. package/dist/commands/db.js +37 -58
  49. package/dist/commands/db.js.map +1 -1
  50. package/dist/commands/deploy.d.ts.map +1 -1
  51. package/dist/commands/deploy.js +140 -96
  52. package/dist/commands/deploy.js.map +1 -1
  53. package/dist/commands/dev.d.ts.map +1 -1
  54. package/dist/commands/dev.js +74 -39
  55. package/dist/commands/dev.js.map +1 -1
  56. package/dist/commands/diff.d.ts.map +1 -1
  57. package/dist/commands/diff.js +39 -39
  58. package/dist/commands/diff.js.map +1 -1
  59. package/dist/commands/doctor.d.ts +3 -0
  60. package/dist/commands/doctor.d.ts.map +1 -0
  61. package/dist/commands/doctor.js +78 -0
  62. package/dist/commands/doctor.js.map +1 -0
  63. package/dist/commands/engine.d.ts.map +1 -1
  64. package/dist/commands/engine.js +5 -4
  65. package/dist/commands/engine.js.map +1 -1
  66. package/dist/commands/functions.d.ts.map +1 -1
  67. package/dist/commands/functions.js +172 -119
  68. package/dist/commands/functions.js.map +1 -1
  69. package/dist/commands/generate.d.ts.map +1 -1
  70. package/dist/commands/generate.js +5 -4
  71. package/dist/commands/generate.js.map +1 -1
  72. package/dist/commands/init.d.ts +35 -1
  73. package/dist/commands/init.d.ts.map +1 -1
  74. package/dist/commands/init.js +883 -107
  75. package/dist/commands/init.js.map +1 -1
  76. package/dist/commands/introspect.d.ts +3 -0
  77. package/dist/commands/introspect.d.ts.map +1 -0
  78. package/dist/commands/introspect.js +35 -0
  79. package/dist/commands/introspect.js.map +1 -0
  80. package/dist/commands/keys.d.ts +15 -1
  81. package/dist/commands/keys.d.ts.map +1 -1
  82. package/dist/commands/keys.js +46 -10
  83. package/dist/commands/keys.js.map +1 -1
  84. package/dist/commands/link-helpers.d.ts +15 -0
  85. package/dist/commands/link-helpers.d.ts.map +1 -0
  86. package/dist/commands/link-helpers.js +225 -0
  87. package/dist/commands/link-helpers.js.map +1 -0
  88. package/dist/commands/logs.d.ts.map +1 -1
  89. package/dist/commands/logs.js +5 -4
  90. package/dist/commands/logs.js.map +1 -1
  91. package/dist/commands/migrate-from-v1.d.ts.map +1 -1
  92. package/dist/commands/migrate-from-v1.js +3 -2
  93. package/dist/commands/migrate-from-v1.js.map +1 -1
  94. package/dist/commands/migrate.d.ts.map +1 -1
  95. package/dist/commands/migrate.js +119 -26
  96. package/dist/commands/migrate.js.map +1 -1
  97. package/dist/commands/pg.d.ts.map +1 -1
  98. package/dist/commands/pg.js +11 -12
  99. package/dist/commands/pg.js.map +1 -1
  100. package/dist/commands/plugins.d.ts.map +1 -1
  101. package/dist/commands/plugins.js +55 -46
  102. package/dist/commands/plugins.js.map +1 -1
  103. package/dist/commands/pull.d.ts.map +1 -1
  104. package/dist/commands/pull.js +33 -5
  105. package/dist/commands/pull.js.map +1 -1
  106. package/dist/commands/push.d.ts.map +1 -1
  107. package/dist/commands/push.js +111 -138
  108. package/dist/commands/push.js.map +1 -1
  109. package/dist/commands/seed.d.ts.map +1 -1
  110. package/dist/commands/seed.js +4 -3
  111. package/dist/commands/seed.js.map +1 -1
  112. package/dist/commands/self-host.d.ts +2 -2
  113. package/dist/commands/self-host.d.ts.map +1 -1
  114. package/dist/commands/self-host.js +65 -50
  115. package/dist/commands/self-host.js.map +1 -1
  116. package/dist/commands/self-update.d.ts.map +1 -1
  117. package/dist/commands/self-update.js +3 -2
  118. package/dist/commands/self-update.js.map +1 -1
  119. package/dist/commands/status.d.ts +1 -1
  120. package/dist/commands/status.d.ts.map +1 -1
  121. package/dist/commands/status.js +95 -29
  122. package/dist/commands/status.js.map +1 -1
  123. package/dist/commands/types.d.ts.map +1 -1
  124. package/dist/commands/types.js +3 -2
  125. package/dist/commands/types.js.map +1 -1
  126. package/dist/commands/update.d.ts.map +1 -1
  127. package/dist/commands/update.js +54 -21
  128. package/dist/commands/update.js.map +1 -1
  129. package/dist/compose-rename.d.ts +10 -0
  130. package/dist/compose-rename.d.ts.map +1 -0
  131. package/dist/compose-rename.js +67 -0
  132. package/dist/compose-rename.js.map +1 -0
  133. package/dist/config.d.ts +2 -1
  134. package/dist/config.d.ts.map +1 -1
  135. package/dist/config.js.map +1 -1
  136. package/dist/dev-compose.d.ts +26 -0
  137. package/dist/dev-compose.d.ts.map +1 -1
  138. package/dist/dev-compose.js +357 -79
  139. package/dist/dev-compose.js.map +1 -1
  140. package/dist/dev-log-bus.d.ts +30 -0
  141. package/dist/dev-log-bus.d.ts.map +1 -0
  142. package/dist/dev-log-bus.js +87 -0
  143. package/dist/dev-log-bus.js.map +1 -0
  144. package/dist/dev-log-filter.d.ts +10 -0
  145. package/dist/dev-log-filter.d.ts.map +1 -0
  146. package/dist/dev-log-filter.js +36 -0
  147. package/dist/dev-log-filter.js.map +1 -0
  148. package/dist/dev-logo.d.ts +12 -0
  149. package/dist/dev-logo.d.ts.map +1 -0
  150. package/dist/dev-logo.js +56 -0
  151. package/dist/dev-logo.js.map +1 -0
  152. package/dist/dev-ports.d.ts +27 -0
  153. package/dist/dev-ports.d.ts.map +1 -0
  154. package/dist/dev-ports.js +171 -0
  155. package/dist/dev-ports.js.map +1 -0
  156. package/dist/dev-session-lock.d.ts +25 -0
  157. package/dist/dev-session-lock.d.ts.map +1 -0
  158. package/dist/dev-session-lock.js +81 -0
  159. package/dist/dev-session-lock.js.map +1 -0
  160. package/dist/dev-session.d.ts +26 -0
  161. package/dist/dev-session.d.ts.map +1 -0
  162. package/dist/dev-session.js +106 -0
  163. package/dist/dev-session.js.map +1 -0
  164. package/dist/dev-shutdown.d.ts +25 -0
  165. package/dist/dev-shutdown.d.ts.map +1 -0
  166. package/dist/dev-shutdown.js +114 -0
  167. package/dist/dev-shutdown.js.map +1 -0
  168. package/dist/dev-task-colors.d.ts +13 -0
  169. package/dist/dev-task-colors.d.ts.map +1 -0
  170. package/dist/dev-task-colors.js +43 -0
  171. package/dist/dev-task-colors.js.map +1 -0
  172. package/dist/dev-tui.d.ts +24 -0
  173. package/dist/dev-tui.d.ts.map +1 -0
  174. package/dist/dev-tui.js +188 -0
  175. package/dist/dev-tui.js.map +1 -0
  176. package/dist/diff-output.d.ts +5 -1
  177. package/dist/diff-output.d.ts.map +1 -1
  178. package/dist/diff-output.js +69 -0
  179. package/dist/diff-output.js.map +1 -1
  180. package/dist/docker-runtime.d.ts +30 -0
  181. package/dist/docker-runtime.d.ts.map +1 -0
  182. package/dist/docker-runtime.js +118 -0
  183. package/dist/docker-runtime.js.map +1 -0
  184. package/dist/engine-client.d.ts +10 -1
  185. package/dist/engine-client.d.ts.map +1 -1
  186. package/dist/engine-client.js +76 -17
  187. package/dist/engine-client.js.map +1 -1
  188. package/dist/engine-push-output.d.ts +17 -0
  189. package/dist/engine-push-output.d.ts.map +1 -0
  190. package/dist/engine-push-output.js +64 -0
  191. package/dist/engine-push-output.js.map +1 -0
  192. package/dist/ensure-binary.js +2 -2
  193. package/dist/ensure-binary.js.map +1 -1
  194. package/dist/env-file.d.ts +5 -0
  195. package/dist/env-file.d.ts.map +1 -0
  196. package/dist/env-file.js +33 -0
  197. package/dist/env-file.js.map +1 -0
  198. package/dist/gitignore.d.ts +8 -0
  199. package/dist/gitignore.d.ts.map +1 -0
  200. package/dist/gitignore.js +41 -0
  201. package/dist/gitignore.js.map +1 -0
  202. package/dist/kong-config.d.ts +9 -0
  203. package/dist/kong-config.d.ts.map +1 -1
  204. package/dist/kong-config.js +18 -1
  205. package/dist/kong-config.js.map +1 -1
  206. package/dist/link.d.ts +66 -0
  207. package/dist/link.d.ts.map +1 -0
  208. package/dist/link.js +160 -0
  209. package/dist/link.js.map +1 -0
  210. package/dist/process-manager.d.ts +8 -0
  211. package/dist/process-manager.d.ts.map +1 -1
  212. package/dist/process-manager.js +53 -9
  213. package/dist/process-manager.js.map +1 -1
  214. package/dist/project-config.d.ts +30 -3
  215. package/dist/project-config.d.ts.map +1 -1
  216. package/dist/project-config.js +37 -4
  217. package/dist/project-config.js.map +1 -1
  218. package/dist/prompts.d.ts +3 -0
  219. package/dist/prompts.d.ts.map +1 -0
  220. package/dist/prompts.js +3 -0
  221. package/dist/prompts.js.map +1 -0
  222. package/dist/pull-utils.d.ts +50 -14
  223. package/dist/pull-utils.d.ts.map +1 -1
  224. package/dist/pull-utils.js +152 -12
  225. package/dist/pull-utils.js.map +1 -1
  226. package/dist/resolve-target.d.ts +86 -0
  227. package/dist/resolve-target.d.ts.map +1 -0
  228. package/dist/resolve-target.js +291 -0
  229. package/dist/resolve-target.js.map +1 -0
  230. package/dist/restore-system-relation-targets.d.ts +3 -0
  231. package/dist/restore-system-relation-targets.d.ts.map +1 -0
  232. package/dist/restore-system-relation-targets.js +45 -0
  233. package/dist/restore-system-relation-targets.js.map +1 -0
  234. package/dist/runtime-routes.d.ts.map +1 -1
  235. package/dist/runtime-routes.js +7 -0
  236. package/dist/runtime-routes.js.map +1 -1
  237. package/dist/schema-ast-v2.d.ts +1 -1
  238. package/dist/schema-ast-v2.d.ts.map +1 -1
  239. package/dist/schema-ast-v2.js +2 -2
  240. package/dist/schema-ast-v2.js.map +1 -1
  241. package/dist/schema-sources.d.ts +40 -0
  242. package/dist/schema-sources.d.ts.map +1 -0
  243. package/dist/schema-sources.js +183 -0
  244. package/dist/schema-sources.js.map +1 -0
  245. package/dist/scripts/postinstall.js +5 -1
  246. package/dist/scripts/postinstall.js.map +1 -1
  247. package/dist/self-host-compose.d.ts +37 -1
  248. package/dist/self-host-compose.d.ts.map +1 -1
  249. package/dist/self-host-compose.js +234 -43
  250. package/dist/self-host-compose.js.map +1 -1
  251. package/dist/storage-provision.d.ts +4 -0
  252. package/dist/storage-provision.d.ts.map +1 -1
  253. package/dist/storage-provision.js +24 -2
  254. package/dist/storage-provision.js.map +1 -1
  255. package/dist/supatype-eval-1781522769253.d.mts +2 -0
  256. package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
  257. package/dist/supatype-eval-1781522769253.mjs +3 -0
  258. package/dist/supatype-eval-1781522769253.mjs.map +1 -0
  259. package/dist/systemd.js +2 -2
  260. package/dist/systemd.js.map +1 -1
  261. package/dist/target-client.d.ts +10 -0
  262. package/dist/target-client.d.ts.map +1 -0
  263. package/dist/target-client.js +22 -0
  264. package/dist/target-client.js.map +1 -0
  265. package/dist/type-extractor.d.ts +11 -0
  266. package/dist/type-extractor.d.ts.map +1 -1
  267. package/dist/type-extractor.js +95 -8
  268. package/dist/type-extractor.js.map +1 -1
  269. package/dist/ui/brand.d.ts +9 -0
  270. package/dist/ui/brand.d.ts.map +1 -0
  271. package/dist/ui/brand.js +11 -0
  272. package/dist/ui/brand.js.map +1 -0
  273. package/dist/ui/confirm.d.ts +12 -0
  274. package/dist/ui/confirm.d.ts.map +1 -0
  275. package/dist/ui/confirm.js +28 -0
  276. package/dist/ui/confirm.js.map +1 -0
  277. package/dist/ui/fatal.d.ts +10 -0
  278. package/dist/ui/fatal.d.ts.map +1 -0
  279. package/dist/ui/fatal.js +34 -0
  280. package/dist/ui/fatal.js.map +1 -0
  281. package/dist/ui/index.d.ts +9 -0
  282. package/dist/ui/index.d.ts.map +1 -0
  283. package/dist/ui/index.js +9 -0
  284. package/dist/ui/index.js.map +1 -0
  285. package/dist/ui/interactive.d.ts +3 -0
  286. package/dist/ui/interactive.d.ts.map +1 -0
  287. package/dist/ui/interactive.js +5 -0
  288. package/dist/ui/interactive.js.map +1 -0
  289. package/dist/ui/messages.d.ts +10 -0
  290. package/dist/ui/messages.d.ts.map +1 -0
  291. package/dist/ui/messages.js +35 -0
  292. package/dist/ui/messages.js.map +1 -0
  293. package/dist/ui/next-steps.d.ts +3 -0
  294. package/dist/ui/next-steps.d.ts.map +1 -0
  295. package/dist/ui/next-steps.js +10 -0
  296. package/dist/ui/next-steps.js.map +1 -0
  297. package/dist/ui/progress.d.ts +5 -0
  298. package/dist/ui/progress.d.ts.map +1 -0
  299. package/dist/ui/progress.js +24 -0
  300. package/dist/ui/progress.js.map +1 -0
  301. package/dist/ui/prompts.d.ts +14 -0
  302. package/dist/ui/prompts.d.ts.map +1 -0
  303. package/dist/ui/prompts.js +34 -0
  304. package/dist/ui/prompts.js.map +1 -0
  305. package/package.json +5 -2
  306. package/src/app/framework.ts +1 -3
  307. package/src/app/proxy-dev-app.ts +114 -6
  308. package/src/app-config.ts +80 -0
  309. package/src/binary-cache.ts +102 -52
  310. package/src/cli.ts +16 -2
  311. package/src/commands/add.ts +97 -0
  312. package/src/commands/admin.ts +381 -190
  313. package/src/commands/adopt.ts +82 -0
  314. package/src/commands/app.ts +20 -17
  315. package/src/commands/cache.ts +11 -10
  316. package/src/commands/cloud.ts +91 -142
  317. package/src/commands/db.ts +40 -63
  318. package/src/commands/deploy.ts +186 -126
  319. package/src/commands/dev.ts +98 -55
  320. package/src/commands/diff.ts +52 -43
  321. package/src/commands/doctor.ts +103 -0
  322. package/src/commands/engine.ts +5 -4
  323. package/src/commands/functions.ts +187 -123
  324. package/src/commands/generate.ts +5 -4
  325. package/src/commands/init.ts +1087 -104
  326. package/src/commands/introspect.ts +48 -0
  327. package/src/commands/keys.ts +56 -14
  328. package/src/commands/link-helpers.ts +273 -0
  329. package/src/commands/logs.ts +5 -4
  330. package/src/commands/migrate-from-v1.ts +3 -2
  331. package/src/commands/migrate.ts +167 -27
  332. package/src/commands/pg.ts +13 -18
  333. package/src/commands/plugins.ts +55 -46
  334. package/src/commands/pull.ts +38 -9
  335. package/src/commands/push.ts +148 -175
  336. package/src/commands/seed.ts +5 -4
  337. package/src/commands/self-host.ts +85 -54
  338. package/src/commands/self-update.ts +3 -2
  339. package/src/commands/status.ts +102 -33
  340. package/src/commands/types.ts +3 -2
  341. package/src/commands/update.ts +59 -23
  342. package/src/compose-rename.ts +76 -0
  343. package/src/config.ts +2 -1
  344. package/src/dev-compose.ts +462 -76
  345. package/src/dev-log-bus.ts +101 -0
  346. package/src/dev-log-filter.ts +32 -0
  347. package/src/dev-logo.ts +61 -0
  348. package/src/dev-ports.ts +212 -0
  349. package/src/dev-session-lock.ts +101 -0
  350. package/src/dev-session.ts +130 -0
  351. package/src/dev-shutdown.ts +147 -0
  352. package/src/dev-task-colors.ts +47 -0
  353. package/src/dev-tui.ts +232 -0
  354. package/src/diff-output.ts +79 -1
  355. package/src/docker-runtime.ts +151 -0
  356. package/src/engine-client.ts +81 -17
  357. package/src/engine-push-output.ts +75 -0
  358. package/src/ensure-binary.ts +2 -2
  359. package/src/env-file.ts +37 -0
  360. package/src/gitignore.ts +48 -0
  361. package/src/kong-config.ts +24 -1
  362. package/src/link.ts +243 -0
  363. package/src/process-manager.ts +66 -10
  364. package/src/project-config.ts +62 -7
  365. package/src/prompts.ts +2 -0
  366. package/src/pull-utils.ts +217 -23
  367. package/src/resolve-target.ts +419 -0
  368. package/src/restore-system-relation-targets.ts +45 -0
  369. package/src/runtime-routes.ts +7 -0
  370. package/src/schema-ast-v2.ts +2 -1
  371. package/src/schema-sources.ts +248 -0
  372. package/src/scripts/postinstall.ts +7 -1
  373. package/src/self-host-compose.ts +262 -46
  374. package/src/storage-provision.ts +33 -1
  375. package/src/supatype-eval-1781522769253.mts +1 -0
  376. package/src/systemd.ts +2 -2
  377. package/src/target-client.ts +40 -0
  378. package/src/type-extractor.ts +124 -11
  379. package/src/ui/README.md +17 -0
  380. package/src/ui/brand.ts +12 -0
  381. package/src/ui/confirm.ts +38 -0
  382. package/src/ui/fatal.ts +43 -0
  383. package/src/ui/index.ts +8 -0
  384. package/src/ui/interactive.ts +4 -0
  385. package/src/ui/messages.ts +43 -0
  386. package/src/ui/next-steps.ts +10 -0
  387. package/src/ui/progress.ts +28 -0
  388. package/src/ui/prompts.ts +40 -0
  389. package/tests/admin-ensure.test.ts +59 -0
  390. package/tests/cli-help.test.ts +27 -2
  391. package/tests/config.test.ts +29 -2
  392. package/tests/dev-ports.test.ts +41 -0
  393. package/tests/dev-session-lock.test.ts +54 -0
  394. package/tests/dev-ui.test.ts +162 -0
  395. package/tests/docker-runtime.test.ts +236 -0
  396. package/tests/engine-push-output.test.ts +67 -0
  397. package/tests/init.test.ts +197 -18
  398. package/tests/link.test.ts +148 -0
  399. package/tests/minisign.test.ts +102 -0
  400. package/tests/proxy-dev-app.test.ts +45 -1
  401. package/tests/pull-utils.test.ts +5 -4
  402. package/tests/runtime-contract.test.ts +186 -2
  403. package/tests/schema-sources.test.ts +119 -0
  404. package/tests/storage-provision.test.ts +100 -0
  405. package/tests/ui-confirm.test.ts +41 -0
  406. package/tests/ui-messages.test.ts +66 -0
  407. package/tsconfig.tsbuildinfo +1 -1
@@ -1,17 +1,42 @@
1
1
  // ─── Admin panel CLI commands (Gap Appendices task 48) ──────────────────────
2
2
  //
3
3
  // `npx supatype admin create-user` — create an admin user in the project's
4
- // {ref}_auth.users table. Used for initial setup and ongoing admin management.
4
+ // auth.users table. First admin is ensured on `supatype dev` or `supatype push`.
5
5
 
6
6
  import type { Command } from "commander"
7
- import { createInterface } from "node:readline"
8
- import { randomBytes, scrypt } from "node:crypto"
9
- import { promisify } from "node:util"
7
+ import { spawnSync } from "node:child_process"
8
+ import { existsSync } from "node:fs"
9
+ import { dirname, join, resolve } from "node:path"
10
+ import bcrypt from "bcryptjs"
11
+ import type { Pool, QueryResult } from "pg"
10
12
  import { loadConfig } from "../config.js"
11
- import { connectionString } from "../project-config.js"
12
- import { signJwt } from "../jwt.js"
13
+ import {
14
+ connectionString,
15
+ resolveRuntimeProvider,
16
+ type SupatypeProjectConfig,
17
+ } from "../project-config.js"
18
+ import { readEnvValue, upsertEnvFile } from "../env-file.js"
19
+ import { hasEngineOverride } from "../binary-cache.js"
20
+ import { confirm as uiConfirm } from "../ui/confirm.js"
21
+ import { error, info, plain } from "../ui/messages.js"
22
+ import { promptText } from "../ui/prompts.js"
23
+ import { isInteractive } from "../ui/interactive.js"
24
+
25
+ export const ADMIN_EMAIL_ENV = "SUPATYPE_ADMIN_EMAIL"
26
+ export const ADMIN_PASSWORD_ENV = "SUPATYPE_ADMIN_PASSWORD"
27
+
28
+ const BCRYPT_ROUNDS = 10
29
+
30
+ export interface EnsureFirstAdminOptions {
31
+ email?: string
32
+ password?: string
33
+ cwd?: string
34
+ role?: string
35
+ connection?: string
36
+ compose?: { project: string; composePath: string }
37
+ }
13
38
 
14
- const scryptAsync = promisify(scrypt)
39
+ type DbQuery = (sql: string, params?: unknown[]) => Promise<QueryResult>
15
40
 
16
41
  export function registerAdmin(program: Command): void {
17
42
  const adminCmd = program
@@ -36,100 +61,34 @@ export function registerAdmin(program: Command): void {
36
61
  const config = loadConfig(cwd)
37
62
  const connection = opts.connection ?? connectionString(config)
38
63
 
39
- const email = opts.email ?? (await prompt("Admin email: "))
64
+ const email = opts.email ?? (await promptText("Admin email"))
40
65
  if (!email || !email.includes("@")) {
41
- console.error("A valid email address is required.")
66
+ error("A valid email address is required.")
42
67
  process.exit(1)
43
68
  }
44
69
 
45
70
  const password =
46
- opts.password ?? (await prompt("Admin password (min 8 chars): "))
71
+ opts.password ?? (await promptText("Admin password (min 8 chars)"))
47
72
  if (!password || password.length < 8) {
48
- console.error("Password must be at least 8 characters.")
73
+ error("Password must be at least 8 characters.")
49
74
  process.exit(1)
50
75
  }
51
76
 
52
77
  const role = opts.role
53
78
 
54
- console.log(`\nCreating admin user: ${email} (role: ${role})...`)
79
+ info(`Creating admin user: ${email} (role: ${role})...`)
55
80
 
56
- // We use pg directly to insert into the auth.users table
57
81
  const pg = await importPg()
58
82
  const pool = new pg.Pool({ connectionString: connection, max: 2 })
59
83
 
60
84
  try {
61
- // Ensure the auth schema and users table exist
62
- await pool.query(`
63
- CREATE SCHEMA IF NOT EXISTS auth;
64
-
65
- CREATE TABLE IF NOT EXISTS auth.users (
66
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
67
- instance_id UUID,
68
- aud TEXT DEFAULT 'authenticated',
69
- role TEXT DEFAULT 'authenticated',
70
- email TEXT UNIQUE,
71
- encrypted_password TEXT,
72
- email_confirmed_at TIMESTAMPTZ DEFAULT now(),
73
- raw_app_meta_data JSONB DEFAULT '{}',
74
- raw_user_meta_data JSONB DEFAULT '{}',
75
- created_at TIMESTAMPTZ DEFAULT now(),
76
- updated_at TIMESTAMPTZ DEFAULT now(),
77
- confirmation_token TEXT DEFAULT '',
78
- recovery_token TEXT DEFAULT '',
79
- email_change_token_new TEXT DEFAULT '',
80
- email_change TEXT DEFAULT ''
81
- );
82
- `)
83
-
84
- // Check if user already exists
85
- const existing = await pool.query(
86
- `SELECT id FROM auth.users WHERE email = $1`,
87
- [email.toLowerCase()],
88
- )
89
-
90
- if (existing.rows.length > 0) {
91
- console.error(
92
- `\nUser with email "${email}" already exists.`,
93
- )
94
- console.log(
95
- `To update their role, use: supatype admin set-role --email ${email} --role ${role}`,
96
- )
97
- process.exit(1)
98
- }
99
-
100
- // Hash the password (bcrypt-style for GoTrue compatibility)
101
- const passwordHash = await hashPassword(password)
102
-
103
- // Insert the admin user with the admin role in app_metadata
104
- const appMetadata = JSON.stringify({ role, provider: "email", providers: ["email"] })
105
- const userMetadata = JSON.stringify({})
106
-
107
- const result = await pool.query(
108
- `INSERT INTO auth.users (
109
- email, encrypted_password, role, aud,
110
- raw_app_meta_data, raw_user_meta_data,
111
- email_confirmed_at, created_at, updated_at
112
- ) VALUES (
113
- $1, $2, 'authenticated', 'authenticated',
114
- $3::jsonb, $4::jsonb,
115
- now(), now(), now()
116
- ) RETURNING id, email`,
117
- [email.toLowerCase(), passwordHash, appMetadata, userMetadata],
118
- )
119
-
120
- const user = result.rows[0] as { id: string; email: string }
121
-
122
- console.log(`\nAdmin user created successfully.`)
123
- console.log(` ID: ${user.id}`)
124
- console.log(` Email: ${user.email}`)
125
- console.log(` Role: ${role}`)
126
- console.log(
127
- `\nThis user can now log in to the admin panel at /admin\n`,
128
- )
85
+ await ensureAuthUsersTable(pool)
86
+ await createAdminUser(pool, email, password, role)
87
+ info("This user can now log in to the admin panel at /admin")
129
88
  } catch (err) {
130
89
  const message =
131
90
  err instanceof Error ? err.message : "Unknown error"
132
- console.error(`\nFailed to create admin user: ${message}`)
91
+ error(`Failed to create admin user: ${message}`)
133
92
  process.exit(1)
134
93
  } finally {
135
94
  await pool.end()
@@ -163,7 +122,7 @@ export function registerAdmin(program: Command): void {
163
122
  )
164
123
 
165
124
  if (result.rows.length === 0) {
166
- console.error(`\nNo user found with email "${opts.email}".`)
125
+ error(`No user found with email "${opts.email}".`)
167
126
  process.exit(1)
168
127
  }
169
128
 
@@ -173,14 +132,14 @@ export function registerAdmin(program: Command): void {
173
132
  raw_app_meta_data: Record<string, unknown>
174
133
  }
175
134
 
176
- console.log(`\nRole updated successfully.`)
177
- console.log(` ID: ${user.id}`)
178
- console.log(` Email: ${user.email}`)
179
- console.log(` Role: ${opts.role}\n`)
135
+ info("Role updated successfully.")
136
+ plain(` ID: ${user.id}`)
137
+ plain(` Email: ${user.email}`)
138
+ plain(` Role: ${opts.role}`)
180
139
  } catch (err) {
181
140
  const message =
182
141
  err instanceof Error ? err.message : "Unknown error"
183
- console.error(`\nFailed to update role: ${message}`)
142
+ error(`Failed to update role: ${message}`)
184
143
  process.exit(1)
185
144
  } finally {
186
145
  await pool.end()
@@ -210,17 +169,15 @@ export function registerAdmin(program: Command): void {
210
169
  )
211
170
 
212
171
  if (result.rows.length === 0) {
213
- console.log("\nNo admin users found.")
214
- console.log(
215
- "Create one with: supatype admin create-user --email admin@example.com --role admin\n",
216
- )
172
+ info("No admin users found.")
173
+ info("Create one with: supatype admin create-user --email admin@example.com --role admin")
217
174
  return
218
175
  }
219
176
 
220
- console.log(
177
+ plain(
221
178
  "\n ID Email Role Created",
222
179
  )
223
- console.log(" " + "-".repeat(100))
180
+ plain(" " + "-".repeat(100))
224
181
  for (const row of result.rows) {
225
182
  const r = row as {
226
183
  id: string
@@ -229,15 +186,15 @@ export function registerAdmin(program: Command): void {
229
186
  created_at: string
230
187
  }
231
188
  const date = new Date(r.created_at).toISOString().slice(0, 10)
232
- console.log(
189
+ plain(
233
190
  ` ${r.id} ${r.email.padEnd(30)} ${r.role.padEnd(12)} ${date}`,
234
191
  )
235
192
  }
236
- console.log()
193
+ plain()
237
194
  } catch (err) {
238
195
  const message =
239
196
  err instanceof Error ? err.message : "Unknown error"
240
- console.error(`\nFailed to list admin users: ${message}`)
197
+ error(`Failed to list admin users: ${message}`)
241
198
  process.exit(1)
242
199
  } finally {
243
200
  await pool.end()
@@ -245,128 +202,362 @@ export function registerAdmin(program: Command): void {
245
202
  })
246
203
  }
247
204
 
248
- // ─── First admin user prompt (task 48) ──────────────────────────────────────
249
- // Called by `supatype push` on initial setup if no admin users exist.
205
+ /** @deprecated Use ensureFirstAdminUser */
206
+ export const promptFirstAdminUser = ensureFirstAdminUser
250
207
 
251
- export async function promptFirstAdminUser(
208
+ /**
209
+ * Ensure a first admin user exists (idempotent). Called from `dev` and `push`
210
+ * when auth.users is ready and no admin users exist yet.
211
+ */
212
+ export async function ensureFirstAdminUser(
252
213
  connection: string,
214
+ options: EnsureFirstAdminOptions = {},
253
215
  ): Promise<void> {
254
216
  const pg = await importPg()
255
217
  const pool = new pg.Pool({ connectionString: connection, max: 2 })
256
-
257
218
  try {
258
- // Check if auth.users table exists
259
- const tableExists = await pool.query(
260
- `SELECT EXISTS (
261
- SELECT FROM information_schema.tables
262
- WHERE table_schema = 'auth' AND table_name = 'users'
263
- ) as exists`,
219
+ await ensureFirstAdminWithQuery(
220
+ (sql, params) => pool.query(sql, params),
221
+ options,
264
222
  )
265
- if (!tableExists.rows[0]?.exists) return
223
+ } catch {
224
+ // Non-fatal — skip when DB is unreachable or auth schema is not ready
225
+ } finally {
226
+ await pool.end()
227
+ }
228
+ }
266
229
 
267
- // Check if any admin users exist
268
- const adminCount = await pool.query(
269
- `SELECT COUNT(*) as count FROM auth.users
270
- WHERE raw_app_meta_data->>'role' IS NOT NULL
271
- AND raw_app_meta_data->>'role' != 'authenticated'`,
272
- )
230
+ /**
231
+ * Resolve DB access for the current project (host URL or compose exec when DB
232
+ * is not published to the host).
233
+ */
234
+ export async function ensureFirstAdminUserForProject(
235
+ cwd: string,
236
+ config: SupatypeProjectConfig,
237
+ options: EnsureFirstAdminOptions = {},
238
+ ): Promise<void> {
239
+ const root = resolve(cwd)
240
+ const merged: EnsureFirstAdminOptions = { cwd: root, ...options }
241
+
242
+ if (
243
+ resolveRuntimeProvider(config) === "docker" &&
244
+ merged.compose &&
245
+ !hasEngineOverride(config)
246
+ ) {
247
+ try {
248
+ await ensureFirstAdminWithQuery(
249
+ (sql, params) => composeExecQuery(root, merged.compose!, sql, params),
250
+ merged,
251
+ )
252
+ } catch {
253
+ // Non-fatal
254
+ }
255
+ return
256
+ }
273
257
 
274
- const count = parseInt(
275
- (adminCount.rows[0] as { count: string }).count,
276
- 10,
277
- )
278
- if (count > 0) return
258
+ const connection =
259
+ merged.compose && hasEngineOverride(config)
260
+ ? hostComposeDbUrlFromEnv(root)
261
+ : options.connection ?? readEnvValue(root, "DATABASE_URL", connectionString(config))
279
262
 
280
- // No admin users — prompt to create one
281
- console.log("\n No admin users found for the admin panel.")
282
- const createAdmin = await confirm(
283
- " Create an admin user now? [y/N] ",
284
- )
285
- if (!createAdmin) {
286
- console.log(
287
- " Skipped. You can create one later with: supatype admin create-user\n",
263
+ await ensureFirstAdminUser(connection, merged)
264
+ }
265
+
266
+ async function ensureFirstAdminWithQuery(
267
+ query: DbQuery,
268
+ options: EnsureFirstAdminOptions,
269
+ ): Promise<void> {
270
+ const cwd = options.cwd ? resolve(options.cwd) : process.cwd()
271
+
272
+ if (!(await authUsersTableExists(query))) return
273
+ if (await hasAdminUsers(query)) return
274
+
275
+ const credentials = await resolveAdminCredentials(options, cwd)
276
+ if (!credentials) {
277
+ if (!isInteractive()) {
278
+ info(
279
+ "No admin users found. Set SUPATYPE_ADMIN_EMAIL / SUPATYPE_ADMIN_PASSWORD in .env, " +
280
+ "or run: supatype admin create-user",
288
281
  )
289
- return
290
282
  }
283
+ return
284
+ }
291
285
 
292
- const email = await prompt(" Admin email: ")
293
- if (!email || !email.includes("@")) {
294
- console.log(" Invalid email. Skipping admin user creation.\n")
295
- return
296
- }
286
+ const role = options.role ?? "admin"
287
+ await createAdminUser(query, credentials.email, credentials.password, role, { quiet: true })
288
+ clearAdminSeedPassword(cwd)
289
+ info(`Admin user "${credentials.email}" created (role: ${role}).`)
290
+ info("Log in at /admin after starting the dev server.")
291
+ }
297
292
 
298
- const password = await prompt(
299
- " Admin password (min 8 chars): ",
300
- )
301
- if (!password || password.length < 8) {
302
- console.log(
303
- " Password too short. Skipping admin user creation.\n",
304
- )
305
- return
293
+ async function resolveAdminCredentials(
294
+ options: EnsureFirstAdminOptions,
295
+ cwd: string,
296
+ ): Promise<{ email: string; password: string } | null> {
297
+ const envEmail = options.email ?? readEnvValue(cwd, ADMIN_EMAIL_ENV, "").trim()
298
+ const envPassword =
299
+ options.password ?? readEnvValue(cwd, ADMIN_PASSWORD_ENV, "").trim()
300
+
301
+ if (envEmail && envPassword) {
302
+ if (!envEmail.includes("@")) {
303
+ info("Invalid admin email in .env. Skipping admin user creation.")
304
+ return null
305
+ }
306
+ if (envPassword.length < 8) {
307
+ info("Admin password in .env is too short (min 8 chars). Skipping.")
308
+ return null
306
309
  }
310
+ return { email: envEmail, password: envPassword }
311
+ }
307
312
 
308
- const passwordHash = await hashPassword(password)
309
- const appMetadata = JSON.stringify({
310
- role: "admin",
311
- provider: "email",
312
- providers: ["email"],
313
- })
313
+ if (!isInteractive()) return null
314
314
 
315
- await pool.query(
316
- `INSERT INTO auth.users (
317
- email, encrypted_password, role, aud,
318
- raw_app_meta_data, raw_user_meta_data,
319
- email_confirmed_at, created_at, updated_at
320
- ) VALUES (
321
- $1, $2, 'authenticated', 'authenticated',
322
- $3::jsonb, '{}'::jsonb,
323
- now(), now(), now()
324
- )`,
325
- [email.toLowerCase(), passwordHash, appMetadata],
326
- )
315
+ info("No admin users found for the admin panel.")
316
+ const createAdmin = await uiConfirm("Create an admin user now?")
317
+ if (!createAdmin) {
318
+ info("Skipped. You can create one later with: supatype admin create-user")
319
+ return null
320
+ }
327
321
 
328
- console.log(`\n Admin user "${email}" created (role: admin).`)
329
- console.log(` Log in at /admin after starting the dev server.\n`)
330
- } catch {
331
- // Non-fatal — if auth schema doesn't exist yet, skip silently
332
- } finally {
333
- await pool.end()
322
+ const email = await promptText("Admin email")
323
+ if (!email || !email.includes("@")) {
324
+ info("Invalid email. Skipping admin user creation.")
325
+ return null
326
+ }
327
+
328
+ const password = await promptText("Admin password (min 8 chars)")
329
+ if (!password || password.length < 8) {
330
+ info("Password too short. Skipping admin user creation.")
331
+ return null
334
332
  }
333
+
334
+ return { email, password }
335
335
  }
336
336
 
337
- // ─── Helpers ────────────────────────────────────────────────────────────────────
337
+ export function clearAdminSeedPassword(cwd: string): void {
338
+ upsertEnvFile(cwd, {}, [ADMIN_PASSWORD_ENV])
339
+ }
338
340
 
339
- async function importPg(): Promise<typeof import("pg")> {
340
- try {
341
- return await import("pg")
342
- } catch {
343
- console.error(
344
- "pg package is required for admin commands. Install it with: pnpm add pg",
345
- )
346
- process.exit(1)
341
+ export async function hashPasswordForAuth(password: string): Promise<string> {
342
+ return bcrypt.hash(password, BCRYPT_ROUNDS)
343
+ }
344
+
345
+ async function createAdminUser(
346
+ db: Pool | DbQuery,
347
+ email: string,
348
+ password: string,
349
+ role: string,
350
+ opts: { quiet?: boolean } = {},
351
+ ): Promise<{ id: string; email: string }> {
352
+ const query: DbQuery =
353
+ typeof (db as Pool).query === "function"
354
+ ? (sql, params) => (db as Pool).query(sql, params)
355
+ : (db as DbQuery)
356
+
357
+ const normalized = email.toLowerCase()
358
+ const existing = await query(`SELECT id FROM auth.users WHERE email = $1`, [
359
+ normalized,
360
+ ])
361
+ if (existing.rows.length > 0) {
362
+ throw new Error(`User with email "${email}" already exists.`)
363
+ }
364
+
365
+ const passwordHash = await hashPasswordForAuth(password)
366
+ const appMetadata = JSON.stringify({
367
+ role,
368
+ provider: "email",
369
+ providers: ["email"],
370
+ })
371
+ const userMetadata = JSON.stringify({})
372
+
373
+ const result = await query(
374
+ `INSERT INTO auth.users (
375
+ email, encrypted_password, role, aud,
376
+ raw_app_meta_data, raw_user_meta_data,
377
+ email_confirmed_at, created_at, updated_at
378
+ ) VALUES (
379
+ $1, $2, 'authenticated', 'authenticated',
380
+ $3::jsonb, $4::jsonb,
381
+ now(), now(), now()
382
+ ) RETURNING id, email`,
383
+ [normalized, passwordHash, appMetadata, userMetadata],
384
+ )
385
+
386
+ const user = result.rows[0] as { id: string; email: string }
387
+ if (!opts.quiet) {
388
+ info("Admin user created successfully.")
389
+ plain(` ID: ${user.id}`)
390
+ plain(` Email: ${user.email}`)
391
+ plain(` Role: ${role}`)
347
392
  }
393
+ return user
348
394
  }
349
395
 
350
- async function hashPassword(password: string): Promise<string> {
351
- const salt = randomBytes(16).toString("hex")
352
- const derived = (await scryptAsync(password, salt, 64)) as Buffer
353
- return `${salt}:${derived.toString("hex")}`
396
+ async function ensureAuthUsersTable(pool: Pool): Promise<void> {
397
+ await pool.query(`
398
+ CREATE SCHEMA IF NOT EXISTS auth;
399
+
400
+ CREATE TABLE IF NOT EXISTS auth.users (
401
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
402
+ instance_id UUID,
403
+ aud TEXT DEFAULT 'authenticated',
404
+ role TEXT DEFAULT 'authenticated',
405
+ email TEXT UNIQUE,
406
+ encrypted_password TEXT,
407
+ email_confirmed_at TIMESTAMPTZ DEFAULT now(),
408
+ raw_app_meta_data JSONB DEFAULT '{}',
409
+ raw_user_meta_data JSONB DEFAULT '{}',
410
+ created_at TIMESTAMPTZ DEFAULT now(),
411
+ updated_at TIMESTAMPTZ DEFAULT now(),
412
+ confirmation_token TEXT DEFAULT '',
413
+ recovery_token TEXT DEFAULT '',
414
+ email_change_token_new TEXT DEFAULT '',
415
+ email_change TEXT DEFAULT ''
416
+ );
417
+ `)
354
418
  }
355
419
 
356
- function prompt(question: string): Promise<string> {
357
- const rl = createInterface({
358
- input: process.stdin,
359
- output: process.stdout,
360
- })
361
- return new Promise((resolve) => {
362
- rl.question(question, (answer) => {
363
- rl.close()
364
- resolve(answer.trim())
420
+ async function authUsersTableExists(query: DbQuery): Promise<boolean> {
421
+ const result = await query(
422
+ `SELECT EXISTS (
423
+ SELECT FROM information_schema.tables
424
+ WHERE table_schema = 'auth' AND table_name = 'users'
425
+ ) as exists`,
426
+ )
427
+ return Boolean(result.rows[0]?.exists)
428
+ }
429
+
430
+ async function hasAdminUsers(query: DbQuery): Promise<boolean> {
431
+ const adminCount = await query(
432
+ `SELECT COUNT(*)::int as count FROM auth.users
433
+ WHERE raw_app_meta_data->>'role' IS NOT NULL
434
+ AND raw_app_meta_data->>'role' != 'authenticated'`,
435
+ )
436
+ const count = (adminCount.rows[0] as { count: number } | undefined)?.count ?? 0
437
+ return count > 0
438
+ }
439
+
440
+ function composeExecQuery(
441
+ cwd: string,
442
+ compose: { project: string; composePath: string },
443
+ sql: string,
444
+ params: unknown[] = [],
445
+ ): Promise<QueryResult> {
446
+ const db = readEnvValue(cwd, "POSTGRES_DB", "supatype")
447
+ const user = readEnvValue(cwd, "POSTGRES_USER", "supatype_admin")
448
+ const envFile = join(cwd, ".env")
449
+ const composeDir = dirname(compose.composePath)
450
+ const args = [
451
+ "compose",
452
+ "-p",
453
+ compose.project,
454
+ "--project-directory",
455
+ cwd,
456
+ "-f",
457
+ compose.composePath,
458
+ ]
459
+ if (existsSync(envFile)) args.push("--env-file", envFile)
460
+
461
+ if (params.length === 0) {
462
+ args.push("exec", "-T", "db", "psql", "-U", user, "-d", db, "-tAc", sql)
463
+ const result = spawnSync("docker", args, { cwd: composeDir, encoding: "utf8" })
464
+ if (result.status !== 0) {
465
+ throw new Error((result.stderr ?? result.stdout ?? "compose psql failed").trim())
466
+ }
467
+ const text = (result.stdout ?? "").trim()
468
+ if (sql.trim().toUpperCase().startsWith("SELECT")) {
469
+ return Promise.resolve({
470
+ rows: parsePsqlScalarRows(sql, text),
471
+ rowCount: 1,
472
+ command: "SELECT",
473
+ oid: 0,
474
+ fields: [],
475
+ })
476
+ }
477
+ return Promise.resolve({
478
+ rows: [],
479
+ rowCount: 0,
480
+ command: "INSERT",
481
+ oid: 0,
482
+ fields: [],
483
+ })
484
+ }
485
+
486
+ args.push(
487
+ "exec",
488
+ "-T",
489
+ "db",
490
+ "psql",
491
+ "-U",
492
+ user,
493
+ "-d",
494
+ db,
495
+ "-v",
496
+ "ON_ERROR_STOP=1",
497
+ "-c",
498
+ interpolateSql(sql, params),
499
+ )
500
+ const result = spawnSync("docker", args, { cwd: composeDir, encoding: "utf8" })
501
+ if (result.status !== 0) {
502
+ throw new Error((result.stderr ?? result.stdout ?? "compose psql failed").trim())
503
+ }
504
+ const stdout = (result.stdout ?? "").trim()
505
+ const idMatch = stdout.match(
506
+ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\s+\|\s+(.+)/i,
507
+ )
508
+ if (idMatch) {
509
+ return Promise.resolve({
510
+ rows: [{ id: idMatch[1], email: idMatch[2]?.trim() }],
511
+ rowCount: 1,
512
+ command: "INSERT",
513
+ oid: 0,
514
+ fields: [],
365
515
  })
516
+ }
517
+ return Promise.resolve({
518
+ rows: [],
519
+ rowCount: 0,
520
+ command: "INSERT",
521
+ oid: 0,
522
+ fields: [],
366
523
  })
367
524
  }
368
525
 
369
- async function confirm(question: string): Promise<boolean> {
370
- const answer = await prompt(question)
371
- return answer.toLowerCase() === "y"
526
+ function parsePsqlScalarRows(sql: string, text: string): Record<string, unknown>[] {
527
+ const upper = sql.toUpperCase()
528
+ if (upper.includes(" AS EXISTS")) {
529
+ return [{ exists: text === "t" }]
530
+ }
531
+ if (upper.includes(" AS COUNT")) {
532
+ return [{ count: Number.parseInt(text, 10) || 0 }]
533
+ }
534
+ if (text === "") return []
535
+ return [{ value: text }]
536
+ }
537
+
538
+ function interpolateSql(sql: string, params: unknown[]): string {
539
+ return sql.replace(/\$(\d+)/g, (_match, index: string) => {
540
+ const value = params[Number(index) - 1]
541
+ if (value === null || value === undefined) return "NULL"
542
+ if (typeof value === "number" || typeof value === "bigint") return String(value)
543
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE"
544
+ return `'${String(value).replace(/'/g, "''")}'`
545
+ })
546
+ }
547
+
548
+ function hostComposeDbUrlFromEnv(cwd: string): string {
549
+ const port = readEnvValue(cwd, "SUPATYPE_DEV_DB_PORT", "54329")
550
+ const user = readEnvValue(cwd, "POSTGRES_USER", "supatype_admin")
551
+ const pass = readEnvValue(cwd, "POSTGRES_PASSWORD", "postgres")
552
+ const db = readEnvValue(cwd, "POSTGRES_DB", "supatype")
553
+ return `postgresql://${user}:${pass}@127.0.0.1:${port}/${db}?sslmode=disable`
554
+ }
555
+
556
+ async function importPg(): Promise<typeof import("pg")> {
557
+ try {
558
+ return await import("pg")
559
+ } catch {
560
+ error("pg package is required for admin commands. Install it with: pnpm add pg")
561
+ process.exit(1)
562
+ }
372
563
  }