@supatype/cli 0.1.0-alpha.8 → 0.1.0

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 (388) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-test.log +274 -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.map +1 -1
  30. package/dist/commands/admin.js +39 -53
  31. package/dist/commands/admin.js.map +1 -1
  32. package/dist/commands/adopt.d.ts +3 -0
  33. package/dist/commands/adopt.d.ts.map +1 -0
  34. package/dist/commands/adopt.js +55 -0
  35. package/dist/commands/adopt.js.map +1 -0
  36. package/dist/commands/app.d.ts.map +1 -1
  37. package/dist/commands/app.js +20 -17
  38. package/dist/commands/app.js.map +1 -1
  39. package/dist/commands/cache.d.ts.map +1 -1
  40. package/dist/commands/cache.js +11 -10
  41. package/dist/commands/cache.js.map +1 -1
  42. package/dist/commands/cloud.d.ts +4 -9
  43. package/dist/commands/cloud.d.ts.map +1 -1
  44. package/dist/commands/cloud.js +75 -125
  45. package/dist/commands/cloud.js.map +1 -1
  46. package/dist/commands/db.d.ts.map +1 -1
  47. package/dist/commands/db.js +37 -58
  48. package/dist/commands/db.js.map +1 -1
  49. package/dist/commands/deploy.d.ts.map +1 -1
  50. package/dist/commands/deploy.js +140 -96
  51. package/dist/commands/deploy.js.map +1 -1
  52. package/dist/commands/dev.d.ts.map +1 -1
  53. package/dist/commands/dev.js +72 -39
  54. package/dist/commands/dev.js.map +1 -1
  55. package/dist/commands/diff.d.ts.map +1 -1
  56. package/dist/commands/diff.js +39 -39
  57. package/dist/commands/diff.js.map +1 -1
  58. package/dist/commands/doctor.d.ts +3 -0
  59. package/dist/commands/doctor.d.ts.map +1 -0
  60. package/dist/commands/doctor.js +78 -0
  61. package/dist/commands/doctor.js.map +1 -0
  62. package/dist/commands/engine.d.ts.map +1 -1
  63. package/dist/commands/engine.js +5 -4
  64. package/dist/commands/engine.js.map +1 -1
  65. package/dist/commands/functions.d.ts.map +1 -1
  66. package/dist/commands/functions.js +172 -119
  67. package/dist/commands/functions.js.map +1 -1
  68. package/dist/commands/generate.d.ts.map +1 -1
  69. package/dist/commands/generate.js +5 -4
  70. package/dist/commands/generate.js.map +1 -1
  71. package/dist/commands/init.d.ts +30 -1
  72. package/dist/commands/init.d.ts.map +1 -1
  73. package/dist/commands/init.js +814 -107
  74. package/dist/commands/init.js.map +1 -1
  75. package/dist/commands/introspect.d.ts +3 -0
  76. package/dist/commands/introspect.d.ts.map +1 -0
  77. package/dist/commands/introspect.js +35 -0
  78. package/dist/commands/introspect.js.map +1 -0
  79. package/dist/commands/keys.d.ts +15 -1
  80. package/dist/commands/keys.d.ts.map +1 -1
  81. package/dist/commands/keys.js +46 -10
  82. package/dist/commands/keys.js.map +1 -1
  83. package/dist/commands/link-helpers.d.ts +15 -0
  84. package/dist/commands/link-helpers.d.ts.map +1 -0
  85. package/dist/commands/link-helpers.js +225 -0
  86. package/dist/commands/link-helpers.js.map +1 -0
  87. package/dist/commands/logs.d.ts.map +1 -1
  88. package/dist/commands/logs.js +5 -4
  89. package/dist/commands/logs.js.map +1 -1
  90. package/dist/commands/migrate-from-v1.d.ts.map +1 -1
  91. package/dist/commands/migrate-from-v1.js +3 -2
  92. package/dist/commands/migrate-from-v1.js.map +1 -1
  93. package/dist/commands/migrate.d.ts.map +1 -1
  94. package/dist/commands/migrate.js +119 -26
  95. package/dist/commands/migrate.js.map +1 -1
  96. package/dist/commands/pg.d.ts.map +1 -1
  97. package/dist/commands/pg.js +11 -12
  98. package/dist/commands/pg.js.map +1 -1
  99. package/dist/commands/plugins.d.ts.map +1 -1
  100. package/dist/commands/plugins.js +55 -46
  101. package/dist/commands/plugins.js.map +1 -1
  102. package/dist/commands/pull.d.ts.map +1 -1
  103. package/dist/commands/pull.js +33 -5
  104. package/dist/commands/pull.js.map +1 -1
  105. package/dist/commands/push.d.ts.map +1 -1
  106. package/dist/commands/push.js +110 -137
  107. package/dist/commands/push.js.map +1 -1
  108. package/dist/commands/seed.d.ts.map +1 -1
  109. package/dist/commands/seed.js +4 -3
  110. package/dist/commands/seed.js.map +1 -1
  111. package/dist/commands/self-host.d.ts +2 -2
  112. package/dist/commands/self-host.d.ts.map +1 -1
  113. package/dist/commands/self-host.js +65 -50
  114. package/dist/commands/self-host.js.map +1 -1
  115. package/dist/commands/self-update.d.ts.map +1 -1
  116. package/dist/commands/self-update.js +3 -2
  117. package/dist/commands/self-update.js.map +1 -1
  118. package/dist/commands/status.d.ts +1 -1
  119. package/dist/commands/status.d.ts.map +1 -1
  120. package/dist/commands/status.js +95 -29
  121. package/dist/commands/status.js.map +1 -1
  122. package/dist/commands/types.d.ts.map +1 -1
  123. package/dist/commands/types.js +3 -2
  124. package/dist/commands/types.js.map +1 -1
  125. package/dist/commands/update.d.ts.map +1 -1
  126. package/dist/commands/update.js +54 -21
  127. package/dist/commands/update.js.map +1 -1
  128. package/dist/config.d.ts +2 -1
  129. package/dist/config.d.ts.map +1 -1
  130. package/dist/config.js.map +1 -1
  131. package/dist/dev-compose.d.ts +26 -0
  132. package/dist/dev-compose.d.ts.map +1 -1
  133. package/dist/dev-compose.js +328 -34
  134. package/dist/dev-compose.js.map +1 -1
  135. package/dist/dev-log-bus.d.ts +30 -0
  136. package/dist/dev-log-bus.d.ts.map +1 -0
  137. package/dist/dev-log-bus.js +87 -0
  138. package/dist/dev-log-bus.js.map +1 -0
  139. package/dist/dev-log-filter.d.ts +10 -0
  140. package/dist/dev-log-filter.d.ts.map +1 -0
  141. package/dist/dev-log-filter.js +36 -0
  142. package/dist/dev-log-filter.js.map +1 -0
  143. package/dist/dev-logo.d.ts +12 -0
  144. package/dist/dev-logo.d.ts.map +1 -0
  145. package/dist/dev-logo.js +56 -0
  146. package/dist/dev-logo.js.map +1 -0
  147. package/dist/dev-session.d.ts +26 -0
  148. package/dist/dev-session.d.ts.map +1 -0
  149. package/dist/dev-session.js +106 -0
  150. package/dist/dev-session.js.map +1 -0
  151. package/dist/dev-shutdown.d.ts +9 -0
  152. package/dist/dev-shutdown.d.ts.map +1 -0
  153. package/dist/dev-shutdown.js +50 -0
  154. package/dist/dev-shutdown.js.map +1 -0
  155. package/dist/dev-task-colors.d.ts +13 -0
  156. package/dist/dev-task-colors.d.ts.map +1 -0
  157. package/dist/dev-task-colors.js +43 -0
  158. package/dist/dev-task-colors.js.map +1 -0
  159. package/dist/dev-tui.d.ts +24 -0
  160. package/dist/dev-tui.d.ts.map +1 -0
  161. package/dist/dev-tui.js +188 -0
  162. package/dist/dev-tui.js.map +1 -0
  163. package/dist/diff-output.d.ts +5 -1
  164. package/dist/diff-output.d.ts.map +1 -1
  165. package/dist/diff-output.js +69 -0
  166. package/dist/diff-output.js.map +1 -1
  167. package/dist/docker-runtime.d.ts +30 -0
  168. package/dist/docker-runtime.d.ts.map +1 -0
  169. package/dist/docker-runtime.js +118 -0
  170. package/dist/docker-runtime.js.map +1 -0
  171. package/dist/engine-client.d.ts +10 -1
  172. package/dist/engine-client.d.ts.map +1 -1
  173. package/dist/engine-client.js +76 -17
  174. package/dist/engine-client.js.map +1 -1
  175. package/dist/engine-push-output.d.ts +17 -0
  176. package/dist/engine-push-output.d.ts.map +1 -0
  177. package/dist/engine-push-output.js +64 -0
  178. package/dist/engine-push-output.js.map +1 -0
  179. package/dist/ensure-binary.js +2 -2
  180. package/dist/ensure-binary.js.map +1 -1
  181. package/dist/gitignore.d.ts +8 -0
  182. package/dist/gitignore.d.ts.map +1 -0
  183. package/dist/gitignore.js +41 -0
  184. package/dist/gitignore.js.map +1 -0
  185. package/dist/kong-config.d.ts +9 -0
  186. package/dist/kong-config.d.ts.map +1 -1
  187. package/dist/kong-config.js +18 -1
  188. package/dist/kong-config.js.map +1 -1
  189. package/dist/link.d.ts +66 -0
  190. package/dist/link.d.ts.map +1 -0
  191. package/dist/link.js +160 -0
  192. package/dist/link.js.map +1 -0
  193. package/dist/process-manager.d.ts +8 -0
  194. package/dist/process-manager.d.ts.map +1 -1
  195. package/dist/process-manager.js +53 -9
  196. package/dist/process-manager.js.map +1 -1
  197. package/dist/project-config.d.ts +30 -3
  198. package/dist/project-config.d.ts.map +1 -1
  199. package/dist/project-config.js +37 -4
  200. package/dist/project-config.js.map +1 -1
  201. package/dist/prompts.d.ts +3 -0
  202. package/dist/prompts.d.ts.map +1 -0
  203. package/dist/prompts.js +3 -0
  204. package/dist/prompts.js.map +1 -0
  205. package/dist/pull-utils.d.ts +50 -14
  206. package/dist/pull-utils.d.ts.map +1 -1
  207. package/dist/pull-utils.js +152 -12
  208. package/dist/pull-utils.js.map +1 -1
  209. package/dist/resolve-target.d.ts +86 -0
  210. package/dist/resolve-target.d.ts.map +1 -0
  211. package/dist/resolve-target.js +291 -0
  212. package/dist/resolve-target.js.map +1 -0
  213. package/dist/restore-system-relation-targets.d.ts +3 -0
  214. package/dist/restore-system-relation-targets.d.ts.map +1 -0
  215. package/dist/restore-system-relation-targets.js +45 -0
  216. package/dist/restore-system-relation-targets.js.map +1 -0
  217. package/dist/runtime-routes.d.ts.map +1 -1
  218. package/dist/runtime-routes.js +7 -0
  219. package/dist/runtime-routes.js.map +1 -1
  220. package/dist/schema-ast-v2.d.ts +1 -1
  221. package/dist/schema-ast-v2.d.ts.map +1 -1
  222. package/dist/schema-ast-v2.js +2 -2
  223. package/dist/schema-ast-v2.js.map +1 -1
  224. package/dist/schema-sources.d.ts +40 -0
  225. package/dist/schema-sources.d.ts.map +1 -0
  226. package/dist/schema-sources.js +183 -0
  227. package/dist/schema-sources.js.map +1 -0
  228. package/dist/scripts/postinstall.js +5 -1
  229. package/dist/scripts/postinstall.js.map +1 -1
  230. package/dist/seed.d.ts +8 -0
  231. package/dist/seed.d.ts.map +1 -0
  232. package/dist/seed.js +32 -0
  233. package/dist/seed.js.map +1 -0
  234. package/dist/self-host-compose.d.ts +37 -1
  235. package/dist/self-host-compose.d.ts.map +1 -1
  236. package/dist/self-host-compose.js +233 -43
  237. package/dist/self-host-compose.js.map +1 -1
  238. package/dist/storage-provision.d.ts +4 -0
  239. package/dist/storage-provision.d.ts.map +1 -1
  240. package/dist/storage-provision.js +24 -2
  241. package/dist/storage-provision.js.map +1 -1
  242. package/dist/supatype-eval-1781522769253.d.mts +2 -0
  243. package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
  244. package/dist/supatype-eval-1781522769253.mjs +3 -0
  245. package/dist/supatype-eval-1781522769253.mjs.map +1 -0
  246. package/dist/systemd.js +2 -2
  247. package/dist/systemd.js.map +1 -1
  248. package/dist/target-client.d.ts +10 -0
  249. package/dist/target-client.d.ts.map +1 -0
  250. package/dist/target-client.js +22 -0
  251. package/dist/target-client.js.map +1 -0
  252. package/dist/type-extractor.d.ts +11 -0
  253. package/dist/type-extractor.d.ts.map +1 -1
  254. package/dist/type-extractor.js +95 -8
  255. package/dist/type-extractor.js.map +1 -1
  256. package/dist/ui/brand.d.ts +9 -0
  257. package/dist/ui/brand.d.ts.map +1 -0
  258. package/dist/ui/brand.js +11 -0
  259. package/dist/ui/brand.js.map +1 -0
  260. package/dist/ui/confirm.d.ts +12 -0
  261. package/dist/ui/confirm.d.ts.map +1 -0
  262. package/dist/ui/confirm.js +28 -0
  263. package/dist/ui/confirm.js.map +1 -0
  264. package/dist/ui/fatal.d.ts +10 -0
  265. package/dist/ui/fatal.d.ts.map +1 -0
  266. package/dist/ui/fatal.js +34 -0
  267. package/dist/ui/fatal.js.map +1 -0
  268. package/dist/ui/index.d.ts +9 -0
  269. package/dist/ui/index.d.ts.map +1 -0
  270. package/dist/ui/index.js +9 -0
  271. package/dist/ui/index.js.map +1 -0
  272. package/dist/ui/interactive.d.ts +3 -0
  273. package/dist/ui/interactive.d.ts.map +1 -0
  274. package/dist/ui/interactive.js +5 -0
  275. package/dist/ui/interactive.js.map +1 -0
  276. package/dist/ui/messages.d.ts +10 -0
  277. package/dist/ui/messages.d.ts.map +1 -0
  278. package/dist/ui/messages.js +35 -0
  279. package/dist/ui/messages.js.map +1 -0
  280. package/dist/ui/next-steps.d.ts +3 -0
  281. package/dist/ui/next-steps.d.ts.map +1 -0
  282. package/dist/ui/next-steps.js +10 -0
  283. package/dist/ui/next-steps.js.map +1 -0
  284. package/dist/ui/progress.d.ts +5 -0
  285. package/dist/ui/progress.d.ts.map +1 -0
  286. package/dist/ui/progress.js +24 -0
  287. package/dist/ui/progress.js.map +1 -0
  288. package/dist/ui/prompts.d.ts +14 -0
  289. package/dist/ui/prompts.d.ts.map +1 -0
  290. package/dist/ui/prompts.js +34 -0
  291. package/dist/ui/prompts.js.map +1 -0
  292. package/package.json +9 -4
  293. package/src/app/framework.ts +1 -3
  294. package/src/app/proxy-dev-app.ts +114 -6
  295. package/src/app-config.ts +80 -0
  296. package/src/binary-cache.ts +102 -52
  297. package/src/cli.ts +16 -2
  298. package/src/commands/add.ts +97 -0
  299. package/src/commands/admin.ts +39 -73
  300. package/src/commands/adopt.ts +82 -0
  301. package/src/commands/app.ts +20 -17
  302. package/src/commands/cache.ts +11 -10
  303. package/src/commands/cloud.ts +91 -142
  304. package/src/commands/db.ts +40 -63
  305. package/src/commands/deploy.ts +186 -126
  306. package/src/commands/dev.ts +95 -55
  307. package/src/commands/diff.ts +52 -43
  308. package/src/commands/doctor.ts +103 -0
  309. package/src/commands/engine.ts +5 -4
  310. package/src/commands/functions.ts +187 -123
  311. package/src/commands/generate.ts +5 -4
  312. package/src/commands/init.ts +996 -105
  313. package/src/commands/introspect.ts +48 -0
  314. package/src/commands/keys.ts +56 -14
  315. package/src/commands/link-helpers.ts +273 -0
  316. package/src/commands/logs.ts +5 -4
  317. package/src/commands/migrate-from-v1.ts +3 -2
  318. package/src/commands/migrate.ts +167 -27
  319. package/src/commands/pg.ts +13 -18
  320. package/src/commands/plugins.ts +55 -46
  321. package/src/commands/pull.ts +38 -9
  322. package/src/commands/push.ts +147 -174
  323. package/src/commands/seed.ts +5 -4
  324. package/src/commands/self-host.ts +85 -54
  325. package/src/commands/self-update.ts +3 -2
  326. package/src/commands/status.ts +102 -33
  327. package/src/commands/types.ts +3 -2
  328. package/src/commands/update.ts +59 -23
  329. package/src/config.ts +2 -1
  330. package/src/dev-compose.ts +426 -34
  331. package/src/dev-log-bus.ts +101 -0
  332. package/src/dev-log-filter.ts +32 -0
  333. package/src/dev-logo.ts +61 -0
  334. package/src/dev-session.ts +130 -0
  335. package/src/dev-shutdown.ts +54 -0
  336. package/src/dev-task-colors.ts +47 -0
  337. package/src/dev-tui.ts +232 -0
  338. package/src/diff-output.ts +79 -1
  339. package/src/docker-runtime.ts +151 -0
  340. package/src/engine-client.ts +81 -17
  341. package/src/engine-push-output.ts +75 -0
  342. package/src/ensure-binary.ts +2 -2
  343. package/src/gitignore.ts +48 -0
  344. package/src/kong-config.ts +24 -1
  345. package/src/link.ts +243 -0
  346. package/src/process-manager.ts +66 -10
  347. package/src/project-config.ts +62 -7
  348. package/src/prompts.ts +2 -0
  349. package/src/pull-utils.ts +217 -23
  350. package/src/resolve-target.ts +419 -0
  351. package/src/restore-system-relation-targets.ts +45 -0
  352. package/src/runtime-routes.ts +7 -0
  353. package/src/schema-ast-v2.ts +2 -1
  354. package/src/schema-sources.ts +248 -0
  355. package/src/scripts/postinstall.ts +7 -1
  356. package/src/seed.ts +43 -0
  357. package/src/self-host-compose.ts +261 -46
  358. package/src/storage-provision.ts +33 -1
  359. package/src/supatype-eval-1781522769253.mts +1 -0
  360. package/src/systemd.ts +2 -2
  361. package/src/target-client.ts +40 -0
  362. package/src/type-extractor.ts +124 -11
  363. package/src/ui/README.md +17 -0
  364. package/src/ui/brand.ts +12 -0
  365. package/src/ui/confirm.ts +38 -0
  366. package/src/ui/fatal.ts +43 -0
  367. package/src/ui/index.ts +8 -0
  368. package/src/ui/interactive.ts +4 -0
  369. package/src/ui/messages.ts +43 -0
  370. package/src/ui/next-steps.ts +10 -0
  371. package/src/ui/progress.ts +28 -0
  372. package/src/ui/prompts.ts +40 -0
  373. package/tests/cli-help.test.ts +27 -2
  374. package/tests/config.test.ts +29 -2
  375. package/tests/dev-ui.test.ts +139 -0
  376. package/tests/docker-runtime.test.ts +236 -0
  377. package/tests/engine-push-output.test.ts +67 -0
  378. package/tests/init.test.ts +197 -18
  379. package/tests/link.test.ts +148 -0
  380. package/tests/minisign.test.ts +102 -0
  381. package/tests/proxy-dev-app.test.ts +45 -1
  382. package/tests/pull-utils.test.ts +5 -4
  383. package/tests/runtime-contract.test.ts +186 -2
  384. package/tests/schema-sources.test.ts +119 -0
  385. package/tests/storage-provision.test.ts +100 -0
  386. package/tests/ui-confirm.test.ts +41 -0
  387. package/tests/ui-messages.test.ts +66 -0
  388. package/tsconfig.tsbuildinfo +1 -1
@@ -7,10 +7,13 @@
7
7
  *
8
8
  * Security model:
9
9
  * 1. Download checksums.sha256 + checksums.sha256.minisig from CDN.
10
- * 2. Verify Ed25519 minisign signature on the checksum file using the
11
- * embedded public key (SUPATYPE_RELEASE_PUBLIC_KEY).
10
+ * 2. Verify the Ed25519 minisign signature on the checksum file using the
11
+ * release public key (embedded at publish, overridable via
12
+ * SUPATYPE_RELEASE_PUBLIC_KEY).
12
13
  * 3. Verify SHA256 of the downloaded binary against the signed checksum.
13
- * Both checks are mandatory when SUPATYPE_RELEASE_PUBLIC_KEY is set.
14
+ * Verification is mandatory and fails closed: if no public key is configured,
15
+ * the download errors out rather than silently degrading to SHA256-only.
16
+ * The only escape hatch is the explicit SUPATYPE_ALLOW_UNVERIFIED_DOWNLOADS=1.
14
17
  */
15
18
 
16
19
  import { createHash, createPublicKey, verify as cryptoVerify } from "node:crypto"
@@ -23,6 +26,7 @@ import {
23
26
  openSync,
24
27
  readFileSync,
25
28
  readSync,
29
+ rmdirSync,
26
30
  statSync,
27
31
  unlinkSync,
28
32
  writeFileSync,
@@ -31,6 +35,7 @@ import { chmod } from "node:fs/promises"
31
35
  import { homedir } from "node:os"
32
36
  import { basename, join, resolve, isAbsolute } from "node:path"
33
37
  import type { SupatypeProjectConfig } from "./project-config.js"
38
+ import { loadProjectLink, migrateLegacyLinkFiles } from "./link.js"
34
39
  import { releasePublicKey } from "./release-public-key.js"
35
40
 
36
41
  /**
@@ -81,12 +86,16 @@ export function describeActiveOverrides(config: SupatypeProjectConfig): string[]
81
86
 
82
87
  /**
83
88
  * True when this working tree is associated with a remote Supatype Cloud project:
84
- * `project.ref`, `.supatype/cloud.json` (schema deploy link), or `.supatype/linked.json` (functions link).
89
+ * `project.ref` or `.supatype/link.json` (cloud kind).
85
90
  */
86
91
  export function isLinkedToCloudProject(cwd: string, config: SupatypeProjectConfig): boolean {
87
92
  const ref = config.project.ref
88
93
  if (typeof ref === "string" && ref.trim() !== "") return true
89
94
 
95
+ migrateLegacyLinkFiles(cwd)
96
+ const link = loadProjectLink(cwd)
97
+ if (link?.kind === "cloud" && link.projectRef.trim() !== "") return true
98
+
90
99
  const linkedPath = join(cwd, ".supatype", "linked.json")
91
100
  if (existsSync(linkedPath)) {
92
101
  try {
@@ -112,7 +121,7 @@ export function isLinkedToCloudProject(cwd: string, config: SupatypeProjectConfi
112
121
 
113
122
  export type { Component, ComponentVersions } from "./components.js"
114
123
  export { BINARY_COMPONENTS } from "./components.js"
115
- import { BINARY_COMPONENTS, type Component } from "./components.js"
124
+ import { BINARY_COMPONENTS, type Component, type ComponentVersions } from "./components.js"
116
125
 
117
126
  export interface PlatformId {
118
127
  os: "linux" | "darwin" | "windows"
@@ -130,17 +139,6 @@ export function postgresArchiveTag(version: string): string {
130
139
  return version.split(".")[0]!
131
140
  }
132
141
 
133
- /**
134
- * Supatype release signing public key (minisign format).
135
- * Generated with: minisign -G
136
- * Rotate by: generating a new pair, updating this constant, and updating
137
- * the MINISIGN_PRIVATE_KEY GitHub Actions secret.
138
- *
139
- * ⚠ PLACEHOLDER — replace with actual public key before first release.
140
- * When empty, minisign verification is skipped with a warning (SHA256 only).
141
- */
142
- const SUPATYPE_RELEASE_PUBLIC_KEY = ""
143
-
144
142
  // CDN path templates per component.
145
143
  const CDN_PATHS: Record<Component, (version: string, platform: PlatformId) => string> = {
146
144
  engine: (v, p) => `/engine/v${v}/supatype-engine-${p.os}-${p.arch}${p.os === "windows" ? ".exe" : ""}`,
@@ -226,7 +224,7 @@ export async function resolveBinary(
226
224
  }
227
225
 
228
226
  const overridePath = config.overrides?.[component === "postgres" ? "postgres_dir" : component]
229
- const version = versionFor(component, config)
227
+ const version = await resolveVersionFor(component, config)
230
228
 
231
229
  if (version === VERSION_PIN_LOCAL && !overridePath) {
232
230
  const key = component === "postgres" ? "postgres_dir" : component
@@ -335,14 +333,14 @@ export async function download(
335
333
 
336
334
  console.log(`[supatype] Downloading ${component} v${version} (${platform.os}/${platform.arch})...`)
337
335
 
338
- // ── Fetch checksums + optional minisig ────────────────────────────────────
339
- const expectedChecksum = await withRetry(() =>
340
- fetchChecksums(checksumsUrl, minisigUrl, name),
341
- )
342
-
343
- // ── Stream-download binary with progress ─────────────────────────────────
344
336
  const tmpPath = destPath + ".tmp"
345
337
  try {
338
+ // ── Fetch checksums + optional minisig (retried on transient failures) ───
339
+ const expectedChecksum = await withRetry(() =>
340
+ fetchChecksums(checksumsUrl, minisigUrl, name),
341
+ )
342
+
343
+ // ── Stream-download binary with progress (retried on transient failures) ─
346
344
  await withRetry(() => streamToFileWithProgress(binaryUrl, tmpPath))
347
345
 
348
346
  // ── Verify SHA256 ────────────────────────────────────────────────────────
@@ -354,9 +352,17 @@ export async function download(
354
352
  if (process.platform !== "win32" && EXECUTABLE_COMPONENTS.has(component)) {
355
353
  await chmod(destPath, 0o755)
356
354
  }
355
+ } catch (err) {
356
+ // Never leave a partial binary or an empty version directory behind: a stale
357
+ // empty dir makes the next resolve look fine while silently lacking a binary.
358
+ try { if (existsSync(destPath)) unlinkSync(destPath) } catch { /* ignore */ }
359
+ try { rmdirSync(dir) } catch { /* dir not empty or already removed */ }
360
+ throw new Error(
361
+ `Failed to download ${component} v${version} from ${CDN_BASE}: ${(err as Error).message}`,
362
+ )
357
363
  } finally {
358
364
  if (existsSync(tmpPath)) {
359
- try { require("node:fs").unlinkSync(tmpPath) } catch { /* ignore */ }
365
+ try { unlinkSync(tmpPath) } catch { /* ignore */ }
360
366
  }
361
367
  }
362
368
 
@@ -379,24 +385,36 @@ async function fetchChecksums(
379
385
  const checksumsText = await csResp.text()
380
386
 
381
387
  const pubKey = releasePublicKey()
382
- if (pubKey) {
383
- // Minisign signature is required when a public key is embedded.
384
- const sigResp = await fetch(minisigUrl)
385
- if (!sigResp.ok) {
386
- throw new Error(
387
- `Failed to fetch checksum signature from ${minisigUrl}: HTTP ${sigResp.status}\n` +
388
- "Cannot verify release integrity. Aborting download.",
388
+ if (!pubKey) {
389
+ // Fail closed: a missing public key means we cannot verify authenticity, only
390
+ // integrity (SHA256). Published builds always embed the key, so this only
391
+ // happens in source/contributor builds — never silently downgrade.
392
+ if (process.env["SUPATYPE_ALLOW_UNVERIFIED_DOWNLOADS"] === "1") {
393
+ console.warn(
394
+ "[supatype] \u26a0 SUPATYPE_ALLOW_UNVERIFIED_DOWNLOADS=1 no minisign public " +
395
+ "key configured; verifying SHA256 only (authenticity NOT checked).",
389
396
  )
397
+ return extractChecksum(checksumsText, binaryFilename)
390
398
  }
391
- const sigText = await sigResp.text()
392
- verifyMinisign(Buffer.from(checksumsText, "utf8"), sigText, pubKey)
393
- } else {
394
- console.warn(
395
- "[supatype] \u26a0 Minisign public key not configured " +
396
- "skipping signature verification (SHA256 only).",
399
+ throw new Error(
400
+ "No minisign public key configured — cannot verify release authenticity.\n" +
401
+ "Published @supatype/cli builds embed the key automatically; if you are building " +
402
+ "from source, set SUPATYPE_RELEASE_PUBLIC_KEY to the release public key, or set " +
403
+ "SUPATYPE_ALLOW_UNVERIFIED_DOWNLOADS=1 to download with SHA256-only verification (unsafe).",
397
404
  )
398
405
  }
399
406
 
407
+ // Minisign signature is mandatory when a public key is configured.
408
+ const sigResp = await fetch(minisigUrl)
409
+ if (!sigResp.ok) {
410
+ throw new Error(
411
+ `Failed to fetch checksum signature from ${minisigUrl}: HTTP ${sigResp.status}\n` +
412
+ "Cannot verify release integrity. Aborting download.",
413
+ )
414
+ }
415
+ const sigText = await sigResp.text()
416
+ verifyMinisign(Buffer.from(checksumsText, "utf8"), sigText, pubKey)
417
+
400
418
  return extractChecksum(checksumsText, binaryFilename)
401
419
  }
402
420
 
@@ -420,10 +438,10 @@ async function fetchChecksums(
420
438
  const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex")
421
439
 
422
440
  /**
423
- * Verify a minisign signature (Ed25519 legacy mode, algorithm bytes "Ed").
424
- * Throws if verification fails.
441
+ * Verify a minisign signature. Supports both Ed25519 legacy mode ("Ed", over the
442
+ * raw file) and prehashed mode ("ED", over BLAKE2b-512(file)). Throws if invalid.
425
443
  */
426
- function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKeyStr: string): void {
444
+ export function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKeyStr: string): void {
427
445
  // Parse public key: [2 algo][8 keyId][32 ed25519 key]
428
446
  const pkLines = pubKeyStr.trim().split("\n")
429
447
  const pkBytes = Buffer.from(pkLines[pkLines.length - 1]!.trim(), "base64")
@@ -445,14 +463,17 @@ function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKeyStr: st
445
463
  const sigKeyId = sigBytes.subarray(2, 10)
446
464
  const signature = sigBytes.subarray(10, 74)
447
465
 
448
- // Only Ed25519 legacy mode ("Ed" = 0x45, 0x64) is supported.
449
- // Hashed mode ("ED") requires BLAKE2b prehashing not implemented.
450
- if (algo[0] !== 0x45 || algo[1] !== 0x64) {
466
+ // Both Ed25519 modes are supported:
467
+ // "Ed" (0x45, 0x64) legacy: signature is over the raw file bytes.
468
+ // "ED" (0x45, 0x44) prehashed: signature is over BLAKE2b-512(file).
469
+ // Modern minisign (and our release pipeline) default to prehashed mode.
470
+ if (algo[0] !== 0x45 || (algo[1] !== 0x64 && algo[1] !== 0x44)) {
451
471
  throw new Error(
452
- "Unsupported minisign algorithm — only Ed25519 legacy mode supported.\n" +
472
+ "Unsupported minisign algorithm — expected Ed25519 ('Ed' legacy or 'ED' prehashed).\n" +
453
473
  `Got: 0x${algo[0]?.toString(16)}${algo[1]?.toString(16)}`,
454
474
  )
455
475
  }
476
+ const prehashed = algo[1] === 0x44
456
477
 
457
478
  if (!sigKeyId.equals(pkKeyId)) {
458
479
  throw new Error(
@@ -464,7 +485,13 @@ function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKeyStr: st
464
485
  const spkiDer = Buffer.concat([ED25519_SPKI_PREFIX, pkEd25519])
465
486
  const keyObject = createPublicKey({ key: spkiDer, format: "der", type: "spki" })
466
487
 
467
- const valid = cryptoVerify(null, fileBytes, keyObject, signature)
488
+ // Pure Ed25519 (PureEdDSA) verifies over the message directly; for prehashed
489
+ // minisign the "message" is the BLAKE2b-512 digest of the file.
490
+ const signedData = prehashed
491
+ ? createHash("blake2b512").update(fileBytes).digest()
492
+ : fileBytes
493
+
494
+ const valid = cryptoVerify(null, signedData, keyObject, signature)
468
495
  if (!valid) {
469
496
  throw new Error(
470
497
  "Minisign signature verification FAILED — the checksum file may have been tampered with.\n" +
@@ -730,10 +757,30 @@ export function normalisePlatformPath(p: string): string {
730
757
  return result
731
758
  }
732
759
 
760
+ export function pinnedVersion(component: Component, config: SupatypeProjectConfig): string | undefined {
761
+ const version = config.versions?.[component]
762
+ if (typeof version !== "string") return undefined
763
+ const trimmed = version.trim()
764
+ return trimmed === "" ? undefined : trimmed
765
+ }
766
+
767
+ /** Pinned version from config, or latest from CDN when unset. */
768
+ export async function resolveVersionFor(
769
+ component: Component,
770
+ config: SupatypeProjectConfig,
771
+ ): Promise<string> {
772
+ const pinned = pinnedVersion(component, config)
773
+ if (pinned) return pinned
774
+ return fetchLatestVersion(component)
775
+ }
776
+
777
+ /** @deprecated Prefer {@link pinnedVersion} or {@link resolveVersionFor}. */
733
778
  export function versionFor(component: Component, config: SupatypeProjectConfig): string {
734
- const version = config.versions[component]
735
- if (typeof version !== "string" || version.trim() === "") {
736
- throw new Error(`[supatype] versions.${component} must be set in supatype.config.ts`)
779
+ const version = pinnedVersion(component, config)
780
+ if (!version) {
781
+ throw new Error(
782
+ `[supatype] versions.${component} is not pinned in supatype.config.ts (omit versions to use latest)`,
783
+ )
737
784
  }
738
785
  return version
739
786
  }
@@ -780,7 +827,10 @@ export async function fetchAllLatestVersions(): Promise<Record<Component, string
780
827
  * Verify all cached binaries for the current platform (used by integration CI).
781
828
  * Throws if any cached component is missing or fails format checks.
782
829
  */
783
- export function verifyCachedBinaries(versions: SupatypeProjectConfig["versions"]): void {
830
+ export function verifyCachedBinaries(versions: Partial<ComponentVersions> | undefined): void {
831
+ if (!versions) {
832
+ throw new Error("[supatype] verifyCachedBinaries requires pinned versions")
833
+ }
784
834
  const platform = currentPlatform()
785
835
  for (const component of BINARY_COMPONENTS) {
786
836
  const version = versions[component]
@@ -798,15 +848,15 @@ export function verifyCachedBinaries(versions: SupatypeProjectConfig["versions"]
798
848
  }
799
849
 
800
850
  export async function downloadAll(
801
- versions: SupatypeProjectConfig["versions"],
851
+ versions: Partial<ComponentVersions> | undefined,
802
852
  graceful = false,
803
853
  ): Promise<void> {
804
854
  const platform = currentPlatform()
805
855
  const components: Component[] = [...BINARY_COMPONENTS]
806
- const fakeConfig = { versions } as SupatypeProjectConfig
856
+ const latest = await fetchAllLatestVersions()
807
857
 
808
858
  for (const component of components) {
809
- const version = versionFor(component, fakeConfig)
859
+ const version = versions?.[component] ?? latest[component]
810
860
  if (version === VERSION_PIN_LOCAL) continue
811
861
  try {
812
862
  await download(component, version, platform)
package/src/cli.ts CHANGED
@@ -7,11 +7,15 @@ import { registerPg } from "./commands/pg.js"
7
7
  import { registerPush } from "./commands/push.js"
8
8
  import { registerDiff } from "./commands/diff.js"
9
9
  import { registerPull } from "./commands/pull.js"
10
+ import { registerDoctor } from "./commands/doctor.js"
11
+ import { registerIntrospect } from "./commands/introspect.js"
12
+ import { registerAdopt } from "./commands/adopt.js"
10
13
  import { registerGenerate } from "./commands/generate.js"
11
14
  import { registerMigrate } from "./commands/migrate.js"
12
15
  import { registerSeed } from "./commands/seed.js"
13
16
  import { registerKeys } from "./commands/keys.js"
14
17
  import { registerApp } from "./commands/app.js"
18
+ import { registerAdd } from "./commands/add.js"
15
19
  import { registerSelfHost } from "./commands/self-host.js"
16
20
  import { registerCloud } from "./commands/cloud.js"
17
21
  import { registerEngine } from "./commands/engine.js"
@@ -25,8 +29,9 @@ import { registerPlugins } from "./commands/plugins.js"
25
29
  import { registerTypes } from "./commands/types.js"
26
30
  import { registerMigrateFromV1 } from "./commands/migrate-from-v1.js"
27
31
  import { registerSelfUpdate } from "./commands/self-update.js"
32
+ import { reportCliFatal } from "./ui/fatal.js"
28
33
 
29
- export function run(): void {
34
+ export async function run(): Promise<void> {
30
35
  const program = new Command()
31
36
  .name("supatype")
32
37
  .description("Supatype — schema-first Postgres API")
@@ -41,11 +46,15 @@ export function run(): void {
41
46
  registerPush(program)
42
47
  registerDiff(program)
43
48
  registerPull(program)
49
+ registerDoctor(program)
50
+ registerIntrospect(program)
51
+ registerAdopt(program)
44
52
  registerGenerate(program)
45
53
  registerMigrate(program)
46
54
  registerSeed(program)
47
55
  registerKeys(program)
48
56
  registerApp(program)
57
+ registerAdd(program)
49
58
  registerSelfHost(program)
50
59
  registerCloud(program)
51
60
  registerEngine(program)
@@ -59,5 +68,10 @@ export function run(): void {
59
68
  registerTypes(program)
60
69
  registerMigrateFromV1(program)
61
70
 
62
- program.parse()
71
+ try {
72
+ await program.parseAsync(process.argv)
73
+ } catch (err) {
74
+ reportCliFatal(err)
75
+ process.exit(1)
76
+ }
63
77
  }
@@ -0,0 +1,97 @@
1
+ import type { Command } from "commander"
2
+ import * as p from "@clack/prompts"
3
+ import { loadConfig } from "../config.js"
4
+ import { selfHostTlsEnabled } from "../project-config.js"
5
+ import { updateServerConfigInProject } from "../app-config.js"
6
+ import { ensureNotCancelled, printLogo } from "../ui/prompts.js"
7
+ import { file, error, info, plain } from "../ui/messages.js"
8
+ import { nextSteps } from "../ui/next-steps.js"
9
+
10
+ export function registerAdd(program: Command): void {
11
+ const addCmd = program
12
+ .command("add")
13
+ .description("Add capabilities to an existing project")
14
+
15
+ addCmd
16
+ .command("domain [domain]")
17
+ .description("Add a custom domain with automatic HTTPS (self-host)")
18
+ .option("--email <email>", "Email for Let's Encrypt TLS certificates")
19
+ .action(async (domainArg: string | undefined, opts: { email?: string }) => {
20
+ await addDomain(domainArg, opts.email)
21
+ })
22
+ }
23
+
24
+ async function promptDomain(initial?: string): Promise<string> {
25
+ const trimmed = initial?.trim()
26
+ if (trimmed) return trimmed
27
+ return ensureNotCancelled(
28
+ await p.text({
29
+ message: "Please provide domain:",
30
+ placeholder: "demo.supatype.com",
31
+ validate: (v) => ((v ?? "").trim().length === 0 ? "Domain is required" : undefined),
32
+ }),
33
+ ).trim()
34
+ }
35
+
36
+ async function promptEmail(initial?: string): Promise<string> {
37
+ const trimmed = initial?.trim()
38
+ if (trimmed) return trimmed
39
+ return ensureNotCancelled(
40
+ await p.text({
41
+ message: "Please provide email for TLS",
42
+ placeholder: "hello@supatype.com",
43
+ validate: (v) => ((v ?? "").trim().length === 0 ? "Email is required" : undefined),
44
+ }),
45
+ ).trim()
46
+ }
47
+
48
+ async function addDomain(domainArg?: string, emailArg?: string): Promise<void> {
49
+ const cwd = process.cwd()
50
+ const interactive = !domainArg?.trim() || !emailArg?.trim()
51
+
52
+ if (interactive) {
53
+ printLogo()
54
+ p.intro("Add a custom domain")
55
+ }
56
+
57
+ const domain = await promptDomain(domainArg)
58
+ const email = await promptEmail(emailArg)
59
+
60
+ try {
61
+ const configPath = updateServerConfigInProject(cwd, { domain, tlsEmail: email })
62
+ if (interactive) {
63
+ p.outro(`Updated ${configPath}`)
64
+ } else {
65
+ file("updated", configPath)
66
+ }
67
+ printDomainNextSteps(cwd, domain)
68
+ } catch (err) {
69
+ error((err as Error).message)
70
+ process.exit(1)
71
+ }
72
+ }
73
+
74
+ function printDomainNextSteps(cwd: string, domain: string): void {
75
+ let tlsActive = true
76
+ try {
77
+ tlsActive = selfHostTlsEnabled(loadConfig(cwd))
78
+ } catch {
79
+ // config re-load is best-effort for the warning below
80
+ }
81
+
82
+ info(`Domain set to ${domain} with automatic HTTPS.`)
83
+ if (!tlsActive) {
84
+ plain(
85
+ "\nNote: a supatype.local.config.ts override (server.mode=dev) is suppressing HTTPS locally.\n" +
86
+ "That file is gitignored, so HTTPS still activates on your production server.",
87
+ )
88
+ }
89
+ nextSteps("Go live:", [
90
+ `Point DNS: an A record for ${domain} -> your server's public IP`,
91
+ "Open ports 80 and 443 on the server firewall",
92
+ "supatype self-host compose up -d # Kong provisions HTTPS automatically",
93
+ `Platform URL: https://${domain}`,
94
+ ])
95
+ plain(" App, REST, Auth, Storage, Realtime, Functions, and Studio — one HTTPS domain.")
96
+ plain(" Certificates persist in the valkey-data volume.\n")
97
+ }
@@ -4,12 +4,14 @@
4
4
  // {ref}_auth.users table. Used for initial setup and ongoing admin management.
5
5
 
6
6
  import type { Command } from "commander"
7
- import { createInterface } from "node:readline"
8
7
  import { randomBytes, scrypt } from "node:crypto"
9
8
  import { promisify } from "node:util"
10
9
  import { loadConfig } from "../config.js"
11
10
  import { connectionString } from "../project-config.js"
12
11
  import { signJwt } from "../jwt.js"
12
+ import { confirm as uiConfirm } from "../ui/confirm.js"
13
+ import { error, info, plain } from "../ui/messages.js"
14
+ import { promptText } from "../ui/prompts.js"
13
15
 
14
16
  const scryptAsync = promisify(scrypt)
15
17
 
@@ -36,22 +38,22 @@ export function registerAdmin(program: Command): void {
36
38
  const config = loadConfig(cwd)
37
39
  const connection = opts.connection ?? connectionString(config)
38
40
 
39
- const email = opts.email ?? (await prompt("Admin email: "))
41
+ const email = opts.email ?? (await promptText("Admin email"))
40
42
  if (!email || !email.includes("@")) {
41
- console.error("A valid email address is required.")
43
+ error("A valid email address is required.")
42
44
  process.exit(1)
43
45
  }
44
46
 
45
47
  const password =
46
- opts.password ?? (await prompt("Admin password (min 8 chars): "))
48
+ opts.password ?? (await promptText("Admin password (min 8 chars)"))
47
49
  if (!password || password.length < 8) {
48
- console.error("Password must be at least 8 characters.")
50
+ error("Password must be at least 8 characters.")
49
51
  process.exit(1)
50
52
  }
51
53
 
52
54
  const role = opts.role
53
55
 
54
- console.log(`\nCreating admin user: ${email} (role: ${role})...`)
56
+ info(`Creating admin user: ${email} (role: ${role})...`)
55
57
 
56
58
  // We use pg directly to insert into the auth.users table
57
59
  const pg = await importPg()
@@ -88,12 +90,8 @@ export function registerAdmin(program: Command): void {
88
90
  )
89
91
 
90
92
  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
- )
93
+ error(`User with email "${email}" already exists.`)
94
+ info(`To update their role, use: supatype admin set-role --email ${email} --role ${role}`)
97
95
  process.exit(1)
98
96
  }
99
97
 
@@ -119,17 +117,15 @@ export function registerAdmin(program: Command): void {
119
117
 
120
118
  const user = result.rows[0] as { id: string; email: string }
121
119
 
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
- )
120
+ info("Admin user created successfully.")
121
+ plain(` ID: ${user.id}`)
122
+ plain(` Email: ${user.email}`)
123
+ plain(` Role: ${role}`)
124
+ info("This user can now log in to the admin panel at /admin")
129
125
  } catch (err) {
130
126
  const message =
131
127
  err instanceof Error ? err.message : "Unknown error"
132
- console.error(`\nFailed to create admin user: ${message}`)
128
+ error(`Failed to create admin user: ${message}`)
133
129
  process.exit(1)
134
130
  } finally {
135
131
  await pool.end()
@@ -163,7 +159,7 @@ export function registerAdmin(program: Command): void {
163
159
  )
164
160
 
165
161
  if (result.rows.length === 0) {
166
- console.error(`\nNo user found with email "${opts.email}".`)
162
+ error(`No user found with email "${opts.email}".`)
167
163
  process.exit(1)
168
164
  }
169
165
 
@@ -173,14 +169,14 @@ export function registerAdmin(program: Command): void {
173
169
  raw_app_meta_data: Record<string, unknown>
174
170
  }
175
171
 
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`)
172
+ info("Role updated successfully.")
173
+ plain(` ID: ${user.id}`)
174
+ plain(` Email: ${user.email}`)
175
+ plain(` Role: ${opts.role}`)
180
176
  } catch (err) {
181
177
  const message =
182
178
  err instanceof Error ? err.message : "Unknown error"
183
- console.error(`\nFailed to update role: ${message}`)
179
+ error(`Failed to update role: ${message}`)
184
180
  process.exit(1)
185
181
  } finally {
186
182
  await pool.end()
@@ -210,17 +206,15 @@ export function registerAdmin(program: Command): void {
210
206
  )
211
207
 
212
208
  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
- )
209
+ info("No admin users found.")
210
+ info("Create one with: supatype admin create-user --email admin@example.com --role admin")
217
211
  return
218
212
  }
219
213
 
220
- console.log(
214
+ plain(
221
215
  "\n ID Email Role Created",
222
216
  )
223
- console.log(" " + "-".repeat(100))
217
+ plain(" " + "-".repeat(100))
224
218
  for (const row of result.rows) {
225
219
  const r = row as {
226
220
  id: string
@@ -229,15 +223,15 @@ export function registerAdmin(program: Command): void {
229
223
  created_at: string
230
224
  }
231
225
  const date = new Date(r.created_at).toISOString().slice(0, 10)
232
- console.log(
226
+ plain(
233
227
  ` ${r.id} ${r.email.padEnd(30)} ${r.role.padEnd(12)} ${date}`,
234
228
  )
235
229
  }
236
- console.log()
230
+ plain()
237
231
  } catch (err) {
238
232
  const message =
239
233
  err instanceof Error ? err.message : "Unknown error"
240
- console.error(`\nFailed to list admin users: ${message}`)
234
+ error(`Failed to list admin users: ${message}`)
241
235
  process.exit(1)
242
236
  } finally {
243
237
  await pool.end()
@@ -278,30 +272,22 @@ export async function promptFirstAdminUser(
278
272
  if (count > 0) return
279
273
 
280
274
  // 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
- )
275
+ info("No admin users found for the admin panel.")
276
+ const createAdmin = await uiConfirm("Create an admin user now?")
285
277
  if (!createAdmin) {
286
- console.log(
287
- " Skipped. You can create one later with: supatype admin create-user\n",
288
- )
278
+ info("Skipped. You can create one later with: supatype admin create-user")
289
279
  return
290
280
  }
291
281
 
292
- const email = await prompt(" Admin email: ")
282
+ const email = await promptText("Admin email")
293
283
  if (!email || !email.includes("@")) {
294
- console.log(" Invalid email. Skipping admin user creation.\n")
284
+ info("Invalid email. Skipping admin user creation.")
295
285
  return
296
286
  }
297
287
 
298
- const password = await prompt(
299
- " Admin password (min 8 chars): ",
300
- )
288
+ const password = await promptText("Admin password (min 8 chars)")
301
289
  if (!password || password.length < 8) {
302
- console.log(
303
- " Password too short. Skipping admin user creation.\n",
304
- )
290
+ info("Password too short. Skipping admin user creation.")
305
291
  return
306
292
  }
307
293
 
@@ -325,8 +311,8 @@ export async function promptFirstAdminUser(
325
311
  [email.toLowerCase(), passwordHash, appMetadata],
326
312
  )
327
313
 
328
- console.log(`\n Admin user "${email}" created (role: admin).`)
329
- console.log(` Log in at /admin after starting the dev server.\n`)
314
+ info(`Admin user "${email}" created (role: admin).`)
315
+ info("Log in at /admin after starting the dev server.")
330
316
  } catch {
331
317
  // Non-fatal — if auth schema doesn't exist yet, skip silently
332
318
  } finally {
@@ -340,9 +326,7 @@ async function importPg(): Promise<typeof import("pg")> {
340
326
  try {
341
327
  return await import("pg")
342
328
  } catch {
343
- console.error(
344
- "pg package is required for admin commands. Install it with: pnpm add pg",
345
- )
329
+ error("pg package is required for admin commands. Install it with: pnpm add pg")
346
330
  process.exit(1)
347
331
  }
348
332
  }
@@ -352,21 +336,3 @@ async function hashPassword(password: string): Promise<string> {
352
336
  const derived = (await scryptAsync(password, salt, 64)) as Buffer
353
337
  return `${salt}:${derived.toString("hex")}`
354
338
  }
355
-
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())
365
- })
366
- })
367
- }
368
-
369
- async function confirm(question: string): Promise<boolean> {
370
- const answer = await prompt(question)
371
- return answer.toLowerCase() === "y"
372
- }