@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,5 +1,6 @@
1
- import { existsSync, readFileSync } from "node:fs"
2
- import { dirname, isAbsolute, resolve } from "node:path"
1
+ import { existsSync, readFileSync, realpathSync } from "node:fs"
2
+ import { createHash } from "node:crypto"
3
+ import { dirname, isAbsolute, relative, resolve } from "node:path"
3
4
  import ts from "typescript"
4
5
  import {
5
6
  applyImportRename,
@@ -103,7 +104,7 @@ export function extractSchemaAstFromTypes(
103
104
  )
104
105
  }
105
106
 
106
- const { tableName, access, options } = parseModelMeta(
107
+ const { tableName, access, options, indexes } = parseModelMeta(
107
108
  metaArg,
108
109
  sourceFile,
109
110
  stmt.name.text,
@@ -112,7 +113,7 @@ export function extractSchemaAstFromTypes(
112
113
  )
113
114
 
114
115
  models.push(
115
- emitModel(stmt.name.text, fields, options, tableName, access),
116
+ emitModel(stmt.name.text, fields, options, tableName, access, indexes),
116
117
  )
117
118
  }
118
119
  }
@@ -143,9 +144,51 @@ export function extractSchemaAstFromTypes(
143
144
  })
144
145
  }
145
146
 
146
- function loadSchemaSourceFiles(entryPath: string): ts.SourceFile[] {
147
+ export interface SchemaSourceFile {
148
+ relativePath: string
149
+ absolutePath: string
150
+ sha256: string
151
+ bytes: number
152
+ }
153
+
154
+ export interface SchemaSourceGraph {
155
+ entryPoint: string
156
+ files: SchemaSourceFile[]
157
+ }
158
+
159
+ export function collectSchemaSourcePaths(entryAbsPath: string, projectRoot: string): SchemaSourceGraph {
160
+ const root = resolve(projectRoot)
161
+ const entryReal = realpathSync(entryAbsPath)
162
+ const entryPoint = relative(root, entryReal).replace(/\\/g, "/")
163
+ if (entryPoint.startsWith("..")) {
164
+ throw new Error(`Schema entry must be under project root: ${entryAbsPath}`)
165
+ }
166
+
167
+ const absolutePaths = walkSchemaSourceAbsPaths(entryReal)
168
+ const files: SchemaSourceFile[] = []
169
+
170
+ for (const abs of absolutePaths) {
171
+ const real = realpathSync(abs)
172
+ const rel = relative(root, real).replace(/\\/g, "/")
173
+ if (rel.startsWith("..")) {
174
+ throw new Error(`Schema source escapes project root: ${abs}`)
175
+ }
176
+ const content = readFileSync(real)
177
+ files.push({
178
+ relativePath: rel,
179
+ absolutePath: real,
180
+ sha256: createHash("sha256").update(content).digest("hex"),
181
+ bytes: content.length,
182
+ })
183
+ }
184
+
185
+ files.sort((a, b) => a.relativePath.localeCompare(b.relativePath))
186
+ return { entryPoint, files }
187
+ }
188
+
189
+ function walkSchemaSourceAbsPaths(entryPath: string): string[] {
147
190
  const visited = new Set<string>()
148
- const sourceFiles: ts.SourceFile[] = []
191
+ const paths: string[] = []
149
192
  const queue: string[] = [entryPath]
150
193
 
151
194
  while (queue.length > 0) {
@@ -153,12 +196,11 @@ function loadSchemaSourceFiles(entryPath: string): ts.SourceFile[] {
153
196
  if (!currentPath) continue
154
197
  if (visited.has(currentPath)) continue
155
198
  visited.add(currentPath)
156
-
157
199
  if (!existsSync(currentPath)) continue
200
+ paths.push(currentPath)
201
+
158
202
  const sourceText = readFileSync(currentPath, "utf8")
159
203
  const sourceFile = ts.createSourceFile(currentPath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
160
- sourceFiles.push(sourceFile)
161
-
162
204
  const baseDir = dirname(currentPath)
163
205
  for (const stmt of sourceFile.statements) {
164
206
  let specifier: string | undefined
@@ -178,7 +220,14 @@ function loadSchemaSourceFiles(entryPath: string): ts.SourceFile[] {
178
220
  }
179
221
  }
180
222
 
181
- return sourceFiles
223
+ return paths
224
+ }
225
+
226
+ function loadSchemaSourceFiles(entryPath: string): ts.SourceFile[] {
227
+ return walkSchemaSourceAbsPaths(entryPath).map((abs) => {
228
+ const sourceText = readFileSync(abs, "utf8")
229
+ return ts.createSourceFile(abs, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
230
+ })
182
231
  }
183
232
 
184
233
  function resolveTypeModulePath(fromDir: string, specifier: string): string | null {
@@ -1372,7 +1421,12 @@ function parseModelMeta(
1372
1421
  modelName: string,
1373
1422
  fieldsArg: ts.TypeNode,
1374
1423
  fields: Record<string, FieldAstV2>,
1375
- ): { tableName: string; access: Record<string, unknown>; options: Record<string, unknown> } {
1424
+ ): {
1425
+ tableName: string
1426
+ access: Record<string, unknown>
1427
+ options: Record<string, unknown>
1428
+ indexes: unknown[]
1429
+ } {
1376
1430
  const literal = parseMetaLiteral(metaArg, sourceFile)
1377
1431
  const singleton = literal.singleton === true
1378
1432
  const tableName =
@@ -1397,7 +1451,66 @@ function parseModelMeta(
1397
1451
  tableName,
1398
1452
  access: parseModelAccess(metaArg, sourceFile),
1399
1453
  options,
1454
+ indexes: parseModelIndexes(metaArg, sourceFile, fields),
1455
+ }
1456
+ }
1457
+
1458
+ function parseModelIndexes(
1459
+ metaArg: ts.TypeNode | undefined,
1460
+ sourceFile: ts.SourceFile,
1461
+ fields: Record<string, FieldAstV2>,
1462
+ ): unknown[] {
1463
+ if (!metaArg || !ts.isTypeLiteralNode(metaArg)) return []
1464
+
1465
+ const indexesProp = metaArg.members.find(
1466
+ (member) => ts.isPropertySignature(member) && getPropertyName(member.name) === "indexes",
1467
+ )
1468
+ if (
1469
+ !indexesProp ||
1470
+ !ts.isPropertySignature(indexesProp) ||
1471
+ !indexesProp.type ||
1472
+ !ts.isTupleTypeNode(indexesProp.type)
1473
+ ) {
1474
+ return []
1475
+ }
1476
+
1477
+ const indexes: unknown[] = []
1478
+ for (const element of indexesProp.type.elements) {
1479
+ if (!ts.isTypeLiteralNode(element)) continue
1480
+ const indexDef: Record<string, unknown> = { using: "btree" }
1481
+ for (const member of element.members) {
1482
+ if (!ts.isPropertySignature(member) || !member.type) continue
1483
+ const key = getPropertyName(member.name)
1484
+ if (!key) continue
1485
+ if (key === "name" && ts.isLiteralTypeNode(member.type) && ts.isStringLiteral(member.type.literal)) {
1486
+ indexDef.name = member.type.literal.text
1487
+ } else if (key === "unique" && isBooleanLiteralType(member.type, true)) {
1488
+ indexDef.unique = true
1489
+ } else if (key === "fields" && ts.isTupleTypeNode(member.type)) {
1490
+ indexDef.fields = member.type.elements
1491
+ .map((fieldNode) => {
1492
+ if (!ts.isLiteralTypeNode(fieldNode) || !ts.isStringLiteral(fieldNode.literal)) return null
1493
+ return resolveIndexFieldName(fieldNode.literal.text, fields)
1494
+ })
1495
+ .filter((field): field is string => field !== null)
1496
+ }
1497
+ }
1498
+ if (Array.isArray(indexDef.fields) && indexDef.fields.length > 0) {
1499
+ indexes.push(indexDef)
1500
+ }
1501
+ }
1502
+ return indexes
1503
+ }
1504
+
1505
+ function resolveIndexFieldName(fieldName: string, fields: Record<string, FieldAstV2>): string | null {
1506
+ if (fields[fieldName] !== undefined) {
1507
+ const field = fields[fieldName]
1508
+ if (field.kind === "relation" && field.annotations?.db?.foreignKey) {
1509
+ return field.annotations.db.foreignKey as string
1510
+ }
1511
+ return fieldName
1400
1512
  }
1513
+ return fieldName
1401
1514
  }
1402
1515
 
1403
1516
  function parseModelAccess(metaArg: ts.TypeNode | undefined, sourceFile: ts.SourceFile): Record<string, unknown> {
@@ -0,0 +1,17 @@
1
+ # CLI terminal UI (`packages/cli/src/ui/`)
2
+
3
+ Shared output layer for `supatype` commands (not `supatype dev` TUI).
4
+
5
+ | Module | Use for |
6
+ |--------|---------|
7
+ | `messages.ts` | `info` / `warn` / `error` (Clack log in TTY; `[supatype]` in CI), `file`, `step`, `plain` |
8
+ | `fatal.ts` | `fatalError`, `reportCliFatal` — branded fatals + global catch helper |
9
+ | `confirm.ts` | Yes/no prompts (Clack in TTY; respects `--yes` / non-TTY) |
10
+ | `progress.ts` | `withSpinner` for long async work |
11
+ | `prompts.ts` | Logo, Clack wizard helpers, `promptText` |
12
+ | `next-steps.ts` | Bulleted follow-up commands |
13
+ | `brand.ts` | ANSI colours + logo styling (shared with dev TUI) |
14
+
15
+ **`supatype dev`** keeps its own alt-screen TUI (`dev-tui.ts`). Do not route dev logs through Clack.
16
+
17
+ **Convention:** interactive multi-step flows use Clack (`init`, `add`, `link`); one-shot commands use `info` + optional `withSpinner`.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared ANSI styling for Supatype CLI output and the dev TUI.
3
+ */
4
+
5
+ export const RESET = "\x1b[0m"
6
+ export const DIM = "\x1b[2m"
7
+ export const BOLD = "\x1b[1m"
8
+ export const BRAND_COLOR = "\x1b[35m"
9
+
10
+ export function brandStyle(text: string): string {
11
+ return `${BRAND_COLOR}${BOLD}${text}${RESET}`
12
+ }
@@ -0,0 +1,38 @@
1
+ import * as p from "@clack/prompts"
2
+ import { isInteractive } from "./interactive.js"
3
+ import { plain } from "./messages.js"
4
+
5
+ export interface ConfirmOptions {
6
+ default?: boolean
7
+ /** When non-interactive, return this instead of `default` (e.g. require --yes). */
8
+ nonInteractive?: boolean
9
+ }
10
+
11
+ /**
12
+ * Yes/no confirmation — Clack in TTY, plain prompt or default otherwise.
13
+ */
14
+ export async function confirm(message: string, opts: ConfirmOptions = {}): Promise<boolean> {
15
+ const fallback = opts.nonInteractive ?? opts.default ?? false
16
+
17
+ if (!isInteractive()) {
18
+ if (opts.nonInteractive !== undefined) return opts.nonInteractive
19
+ return fallback
20
+ }
21
+
22
+ const value = await p.confirm({
23
+ message,
24
+ initialValue: opts.default ?? false,
25
+ })
26
+
27
+ if (p.isCancel(value)) {
28
+ p.cancel("Cancelled.")
29
+ process.exit(0)
30
+ }
31
+
32
+ return value
33
+ }
34
+
35
+ /** Legacy-style y/N prompt text for non-TTY logs when skipping confirm. */
36
+ export function logSkippedConfirm(reason: string): void {
37
+ plain(`${reason} (use --yes to skip confirmation)`)
38
+ }
@@ -0,0 +1,43 @@
1
+ import * as p from "@clack/prompts"
2
+ import type { DockerBrandOptions } from "../docker-runtime.js"
3
+ import { isInteractive } from "./interactive.js"
4
+ import { error, plain } from "./messages.js"
5
+ import { clack, printLogo } from "./prompts.js"
6
+
7
+ export type FatalOptions = {
8
+ brand?: DockerBrandOptions
9
+ exitCode?: number
10
+ }
11
+
12
+ /** Print a fatal headline + optional hint block, then exit. */
13
+ export function fatalError(message: string, hints: string[] = [], opts?: FatalOptions): never {
14
+ if (opts?.brand && isInteractive()) {
15
+ printLogo()
16
+ clack.intro(opts.brand.intro)
17
+ }
18
+
19
+ error(message)
20
+
21
+ if (hints.length > 0) {
22
+ if (isInteractive()) {
23
+ p.note(hints.join("\n\n"))
24
+ } else {
25
+ for (const hint of hints) {
26
+ plain()
27
+ plain(` ${hint}`)
28
+ }
29
+ }
30
+ }
31
+
32
+ process.exit(opts?.exitCode ?? 1)
33
+ }
34
+
35
+ /** Top-level CLI catch — user-facing message without branding. */
36
+ export function reportCliFatal(err: unknown): void {
37
+ const message = err instanceof Error ? err.message : String(err)
38
+ error(message)
39
+ if (process.env["SUPATYPE_DEBUG"] === "1" && err instanceof Error && err.stack) {
40
+ plain()
41
+ plain(err.stack)
42
+ }
43
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./brand.js"
2
+ export * from "./interactive.js"
3
+ export * from "./messages.js"
4
+ export * from "./fatal.js"
5
+ export * from "./confirm.js"
6
+ export * from "./progress.js"
7
+ export * from "./next-steps.js"
8
+ export * from "./prompts.js"
@@ -0,0 +1,4 @@
1
+ /** True when stdin/stdout are TTYs — interactive prompts and spinners are allowed. */
2
+ export function isInteractive(): boolean {
3
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY)
4
+ }
@@ -0,0 +1,43 @@
1
+ import * as p from "@clack/prompts"
2
+ import { isInteractive } from "./interactive.js"
3
+
4
+ export const SUPATYPE_PREFIX = "[supatype]"
5
+
6
+ export function info(message: string): void {
7
+ if (isInteractive()) {
8
+ p.log.info(message)
9
+ return
10
+ }
11
+ console.log(`${SUPATYPE_PREFIX} ${message}`)
12
+ }
13
+
14
+ export function warn(message: string): void {
15
+ if (isInteractive()) {
16
+ p.log.warn(message)
17
+ return
18
+ }
19
+ console.warn(`${SUPATYPE_PREFIX} ${message}`)
20
+ }
21
+
22
+ export function error(message: string): void {
23
+ if (isInteractive()) {
24
+ p.log.error(message)
25
+ return
26
+ }
27
+ console.error(`${SUPATYPE_PREFIX} ${message}`)
28
+ }
29
+
30
+ /** Unprefixed line (tables, diff output, blank lines). */
31
+ export function plain(message = ""): void {
32
+ console.log(message)
33
+ }
34
+
35
+ export function step(title: string): void {
36
+ console.log(`\n${title}`)
37
+ }
38
+
39
+ export type FileAction = "created" | "updated" | "skipped" | "removed" | "wrote"
40
+
41
+ export function file(action: FileAction, rel: string): void {
42
+ console.log(` ${action} ${rel}`)
43
+ }
@@ -0,0 +1,10 @@
1
+ import { plain } from "./messages.js"
2
+
3
+ /** Print a numbered or bulleted “next steps” block. */
4
+ export function nextSteps(title: string, steps: string[]): void {
5
+ plain(`\n${title}`)
6
+ for (const step of steps) {
7
+ plain(` ${step}`)
8
+ }
9
+ plain()
10
+ }
@@ -0,0 +1,28 @@
1
+ import * as p from "@clack/prompts"
2
+ import { isInteractive } from "./interactive.js"
3
+ import { info } from "./messages.js"
4
+
5
+ /**
6
+ * Run an async task with a Clack spinner (TTY) or a plain status line (CI/pipes).
7
+ */
8
+ export async function withSpinner<T>(
9
+ message: string,
10
+ task: () => Promise<T>,
11
+ doneMessage?: string,
12
+ ): Promise<T> {
13
+ if (!isInteractive()) {
14
+ info(`${message}...`)
15
+ return task()
16
+ }
17
+
18
+ const spinner = p.spinner()
19
+ spinner.start(message)
20
+ try {
21
+ const result = await task()
22
+ spinner.stop(doneMessage ?? message)
23
+ return result
24
+ } catch (err) {
25
+ spinner.stop("Failed")
26
+ throw err
27
+ }
28
+ }
@@ -0,0 +1,40 @@
1
+ import * as p from "@clack/prompts"
2
+ import { SUPATYPE_ASCII_LOGO_WORDMARK, colorLogoLines } from "../dev-logo.js"
3
+ import { plain } from "./messages.js"
4
+ import { isInteractive } from "./interactive.js"
5
+
6
+ /** Print the coloured Supatype ASCII wordmark at the top of an interactive command. */
7
+ export function printLogo(): void {
8
+ plain()
9
+ plain(colorLogoLines([...SUPATYPE_ASCII_LOGO_WORDMARK]).join("\n"))
10
+ plain()
11
+ }
12
+
13
+ /**
14
+ * Unwrap a clack prompt result, exiting cleanly when the user cancels (Ctrl-C).
15
+ */
16
+ export function ensureNotCancelled<T>(value: T | symbol, cancelMessage = "Cancelled."): T {
17
+ if (p.isCancel(value)) {
18
+ p.cancel(cancelMessage)
19
+ process.exit(0)
20
+ }
21
+ return value as T
22
+ }
23
+
24
+ /** Single-line text input via Clack (TTY only). */
25
+ export async function promptText(
26
+ message: string,
27
+ opts?: { defaultValue?: string; placeholder?: string },
28
+ ): Promise<string> {
29
+ if (!isInteractive()) {
30
+ throw new Error(`Cannot prompt for "${message}" in non-interactive mode.`)
31
+ }
32
+ const value = await p.text({
33
+ message,
34
+ ...(opts?.defaultValue !== undefined ? { defaultValue: opts.defaultValue } : {}),
35
+ ...(opts?.placeholder !== undefined ? { placeholder: opts.placeholder } : {}),
36
+ })
37
+ return ensureNotCancelled(value).trim()
38
+ }
39
+
40
+ export { p as clack }
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import {
3
+ ADMIN_EMAIL_ENV,
4
+ ADMIN_PASSWORD_ENV,
5
+ clearAdminSeedPassword,
6
+ hashPasswordForAuth,
7
+ } from "../src/commands/admin.js"
8
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
9
+ import { join } from "node:path"
10
+ import { tmpdir } from "node:os"
11
+ import { scaffold, defaultScaffoldOptions } from "../src/commands/init.js"
12
+
13
+ describe("hashPasswordForAuth", () => {
14
+ it("produces a bcrypt hash", async () => {
15
+ const hash = await hashPasswordForAuth("test-password-123")
16
+ expect(hash.startsWith("$2")).toBe(true)
17
+ })
18
+ })
19
+
20
+ describe("clearAdminSeedPassword", () => {
21
+ it("removes SUPATYPE_ADMIN_PASSWORD from .env", () => {
22
+ const dir = join(tmpdir(), `supatype-admin-seed-${Date.now()}`)
23
+ mkdirSync(dir, { recursive: true })
24
+ writeFileSync(
25
+ join(dir, ".env"),
26
+ `${ADMIN_EMAIL_ENV}=admin@example.com\n${ADMIN_PASSWORD_ENV}=secret123\nJWT_SECRET=x\n`,
27
+ "utf8",
28
+ )
29
+ try {
30
+ clearAdminSeedPassword(dir)
31
+ const content = readFileSync(join(dir, ".env"), "utf8")
32
+ expect(content).toContain(`${ADMIN_EMAIL_ENV}=admin@example.com`)
33
+ expect(content).not.toContain(ADMIN_PASSWORD_ENV)
34
+ expect(content).toContain("JWT_SECRET=x")
35
+ } finally {
36
+ rmSync(dir, { recursive: true, force: true })
37
+ }
38
+ })
39
+ })
40
+
41
+ describe("init admin seed in .env", () => {
42
+ it("writes SUPATYPE_ADMIN_* when scaffold options include credentials", () => {
43
+ const dir = join(tmpdir(), `supatype-init-admin-${Date.now()}`)
44
+ mkdirSync(dir, { recursive: true })
45
+ try {
46
+ scaffold(dir, {
47
+ ...defaultScaffoldOptions("seeded-app"),
48
+ adminEmail: "admin@example.com",
49
+ adminPassword: "password123",
50
+ })
51
+ const env = readFileSync(join(dir, ".env"), "utf8")
52
+ expect(env).toContain("SUPATYPE_ADMIN_EMAIL=admin@example.com")
53
+ expect(env).toContain("SUPATYPE_ADMIN_PASSWORD=password123")
54
+ expect(existsSync(join(dir, ".env"))).toBe(true)
55
+ } finally {
56
+ rmSync(dir, { recursive: true, force: true })
57
+ }
58
+ })
59
+ })
@@ -33,6 +33,9 @@ describe("CLI binary (requires built dist/)", () => {
33
33
  "push",
34
34
  "diff",
35
35
  "pull",
36
+ "doctor",
37
+ "introspect",
38
+ "adopt",
36
39
  "generate",
37
40
  "migrate",
38
41
  "rollback",
@@ -79,10 +82,32 @@ describe("CLI binary (requires built dist/)", () => {
79
82
  expect(stdout).toContain("dry run")
80
83
  })
81
84
 
82
- it("pull --help describes deprecated pull command", () => {
85
+ it("pull --help describes scaffold pull command", () => {
83
86
  const { stdout, exitCode } = runCli(["pull", "--help"])
84
87
  expect(exitCode).toBe(0)
85
- expect(stdout).toContain("deprecated")
88
+ expect(stdout).toContain("Scaffold")
89
+ expect(stdout).toContain("--dry-run")
90
+ })
91
+
92
+ it("doctor --help describes drift report command", () => {
93
+ const { stdout, exitCode } = runCli(["doctor", "--help"])
94
+ expect(exitCode).toBe(0)
95
+ expect(stdout).toContain("drift")
96
+ expect(stdout).toContain("--strict")
97
+ })
98
+
99
+ it("introspect --help describes database introspection command", () => {
100
+ const { stdout, exitCode } = runCli(["introspect", "--help"])
101
+ expect(exitCode).toBe(0)
102
+ expect(stdout).toContain("Introspect")
103
+ expect(stdout).toContain("--json")
104
+ })
105
+
106
+ it("adopt --help describes adoption ceremony command", () => {
107
+ const { stdout, exitCode } = runCli(["adopt", "--help"])
108
+ expect(exitCode).toBe(0)
109
+ expect(stdout).toContain("adopt")
110
+ expect(stdout).toContain("--yes")
86
111
  })
87
112
 
88
113
  it("reset --help shows --yes flag", () => {
@@ -101,7 +101,7 @@ describe("loadConfig()", () => {
101
101
  expect(cfg.connection).toBe("postgresql://localhost/jsdb")
102
102
  })
103
103
 
104
- it("throws if config is missing the versions section", () => {
104
+ it("loads config without a versions section (latest by default)", () => {
105
105
  writeFileSync(
106
106
  join(tmpDir, "supatype.config.ts"),
107
107
  `export default {
@@ -112,7 +112,8 @@ describe("loadConfig()", () => {
112
112
  schema: { path: "./schema/index.ts" },
113
113
  }`,
114
114
  )
115
- expect(() => loadConfig(tmpDir)).toThrow(/versions/)
115
+ const cfg = loadConfig(tmpDir)
116
+ expect(cfg.versions).toBeUndefined()
116
117
  })
117
118
 
118
119
  it("prefers supatype.config.ts over supatype.config.js when both exist", () => {
@@ -248,4 +249,30 @@ describe("mergeProjectConfig()", () => {
248
249
  expect(merged.app.start).toBe("dev:site")
249
250
  expect(merged.app.static_dir).toBe("./dist")
250
251
  })
252
+
253
+ it("preserves base environments when a local override sets only server.mode", () => {
254
+ const base = defineConfig({
255
+ ...minimalProject("p"),
256
+ server: { mode: "standalone", domain: "api.example.com" },
257
+ environments: { default: "production" },
258
+ })
259
+ const merged = mergeProjectConfig(base, { server: { mode: "dev" } })
260
+ expect(merged.server.mode).toBe("dev")
261
+ expect(merged.environments?.default).toBe("production")
262
+ })
263
+
264
+ it("deep-merges environments.branchDefaults across base and override", () => {
265
+ const base = defineConfig({
266
+ ...minimalProject("p"),
267
+ environments: { default: "production", branchDefaults: { main: "production" } },
268
+ })
269
+ const merged = mergeProjectConfig(base, {
270
+ environments: { branchDefaults: { staging: "preview" } },
271
+ })
272
+ expect(merged.environments?.default).toBe("production")
273
+ expect(merged.environments?.branchDefaults).toEqual({
274
+ main: "production",
275
+ staging: "preview",
276
+ })
277
+ })
251
278
  })
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
2
+ import { mkdtempSync, writeFileSync } from "node:fs"
3
+ import { join } from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import {
6
+ findNextFreePort,
7
+ isValidHostPort,
8
+ parseHostPortInput,
9
+ readPersistedKongPort,
10
+ } from "../src/dev-ports.js"
11
+ import { isPortInUse } from "../src/postgres-ctl.js"
12
+
13
+ vi.mock("../src/postgres-ctl.js", () => ({
14
+ isPortInUse: vi.fn(),
15
+ }))
16
+
17
+ const isPortInUseMock = vi.mocked(isPortInUse)
18
+
19
+ describe("dev-ports", () => {
20
+ beforeEach(() => {
21
+ isPortInUseMock.mockReset()
22
+ })
23
+
24
+ it("validates host ports", () => {
25
+ expect(isValidHostPort(18473)).toBe(true)
26
+ expect(isValidHostPort(80)).toBe(false)
27
+ expect(parseHostPortInput("18473")).toBe(18473)
28
+ expect(parseHostPortInput("nope")).toBeNull()
29
+ })
30
+
31
+ it("reads persisted Kong port from .env", () => {
32
+ const dir = mkdtempSync(join(tmpdir(), "supatype-ports-"))
33
+ writeFileSync(join(dir, ".env"), "SUPATYPE_KONG_PORT=18474\n", "utf8")
34
+ expect(readPersistedKongPort(dir)).toBe(18474)
35
+ })
36
+
37
+ it("findNextFreePort skips taken ports", async () => {
38
+ isPortInUseMock.mockImplementation(async (port) => port === 18473 || port === 18474)
39
+ await expect(findNextFreePort(18473)).resolves.toBe(18475)
40
+ })
41
+ })