@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
@@ -0,0 +1,148 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
2
+ import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from "node:fs"
3
+ import { join } from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import { resolveTarget, targetSchemaRollback } from "../src/resolve-target.js"
6
+ import { loadProjectLink, migrateLegacyLinkFiles } from "../src/link.js"
7
+ import { scaffold } from "../src/commands/init.js"
8
+
9
+ function writePlainConfig(dir: string): void {
10
+ writeFileSync(
11
+ join(dir, "supatype.config.ts"),
12
+ `export default ${JSON.stringify({
13
+ project: { name: "demo" },
14
+ database: { provider: "docker" },
15
+ server: { mode: "dev" },
16
+ app: { mode: "none" },
17
+ schema: { path: "schema/index.ts", pg_schema: "public" },
18
+ })}
19
+ `,
20
+ )
21
+ }
22
+
23
+ describe("link model", () => {
24
+ let tmp: string
25
+
26
+ beforeEach(() => {
27
+ tmp = mkdtempSync(join(tmpdir(), "supatype-link-"))
28
+ })
29
+
30
+ afterEach(() => {
31
+ rmSync(tmp, { recursive: true, force: true })
32
+ })
33
+
34
+ it("migrates cloud.json into link.json", () => {
35
+ scaffold(tmp, "demo")
36
+ mkdirSync(join(tmp, ".supatype"), { recursive: true })
37
+ writeFileSync(join(tmp, ".supatype", "cloud.json"), JSON.stringify({
38
+ apiUrl: "https://api.example.com",
39
+ token: "pat",
40
+ projectSlug: "myproj",
41
+ orgId: "org1",
42
+ }), { encoding: "utf8" })
43
+
44
+ migrateLegacyLinkFiles(tmp)
45
+ const link = loadProjectLink(tmp)
46
+ expect(link?.kind).toBe("cloud")
47
+ expect(link?.projectRef).toBe("myproj")
48
+ expect(link?.environments.production?.apiUrl).toBe("https://api.example.com")
49
+ })
50
+
51
+ it("resolveTarget returns cloud mode with /api/v1 prefix", () => {
52
+ writePlainConfig(tmp)
53
+ mkdirSync(join(tmp, ".supatype"), { recursive: true })
54
+ writeFileSync(join(tmp, ".supatype", "link.json"), JSON.stringify({
55
+ version: 1,
56
+ kind: "cloud",
57
+ projectRef: "myproj",
58
+ defaultEnvironment: "production",
59
+ token: "pat",
60
+ orgId: "org1",
61
+ cloudApiUrl: "https://api.example.com",
62
+ linkedAt: new Date().toISOString(),
63
+ environments: {
64
+ production: {
65
+ name: "production",
66
+ apiUrl: "https://myproj.supatype.dev",
67
+ linkedAt: new Date().toISOString(),
68
+ },
69
+ },
70
+ }))
71
+
72
+ const target = resolveTarget(tmp)
73
+ expect(target.mode).toBe("cloud")
74
+ expect(target.apiPrefix).toBe("/api/v1")
75
+ expect(target.apiBaseUrl).toBe("https://api.example.com")
76
+ })
77
+
78
+ it("resolveTarget returns self-host mode with /platform/v1 prefix", () => {
79
+ writePlainConfig(tmp)
80
+ mkdirSync(join(tmp, ".supatype"), { recursive: true })
81
+ writeFileSync(join(tmp, ".supatype", "link.json"), JSON.stringify({
82
+ version: 1,
83
+ kind: "self-host",
84
+ projectRef: "demo",
85
+ defaultEnvironment: "production",
86
+ linkedAt: new Date().toISOString(),
87
+ environments: {
88
+ production: {
89
+ name: "production",
90
+ apiUrl: "https://app.example.com",
91
+ token: "srk",
92
+ linkedAt: new Date().toISOString(),
93
+ },
94
+ },
95
+ }))
96
+
97
+ const target = resolveTarget(tmp)
98
+ expect(target.mode).toBe("self-host")
99
+ expect(target.apiPrefix).toBe("/platform/v1")
100
+ expect(target.token).toBe("srk")
101
+ })
102
+
103
+ it("targetSchemaRollback posts to /platform/v1 when linked", async () => {
104
+ writePlainConfig(tmp)
105
+ mkdirSync(join(tmp, ".supatype"), { recursive: true })
106
+ writeFileSync(join(tmp, ".supatype", "link.json"), JSON.stringify({
107
+ version: 1,
108
+ kind: "self-host",
109
+ projectRef: "demo",
110
+ defaultEnvironment: "production",
111
+ linkedAt: new Date().toISOString(),
112
+ environments: {
113
+ production: {
114
+ name: "production",
115
+ apiUrl: "https://app.example.com",
116
+ token: "srk",
117
+ linkedAt: new Date().toISOString(),
118
+ },
119
+ },
120
+ }))
121
+
122
+ const fetchMock = vi.fn().mockResolvedValue({
123
+ ok: true,
124
+ json: async () => ({
125
+ data: {
126
+ status: "rolled_back",
127
+ name: "push_test",
128
+ message: "Rolled back migration push_test.",
129
+ },
130
+ }),
131
+ })
132
+ vi.stubGlobal("fetch", fetchMock)
133
+
134
+ const target = resolveTarget(tmp)
135
+ await targetSchemaRollback(target, { schema: "public" })
136
+
137
+ expect(fetchMock).toHaveBeenCalledOnce()
138
+ const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
139
+ expect(url).toBe("https://app.example.com/platform/v1/projects/demo/schema/rollback")
140
+ expect(init.method).toBe("POST")
141
+ expect(init.headers).toMatchObject({
142
+ Authorization: "Bearer srk",
143
+ "Content-Type": "application/json",
144
+ })
145
+
146
+ vi.unstubAllGlobals()
147
+ })
148
+ })
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ createHash,
4
+ generateKeyPairSync,
5
+ randomBytes,
6
+ sign as edSign,
7
+ type KeyObject,
8
+ } from "node:crypto"
9
+ import { verifyMinisign } from "../src/binary-cache.js"
10
+
11
+ /**
12
+ * Build a minisign public-key string + signature file for the given mode so we
13
+ * can exercise {@link verifyMinisign} without shelling out to the minisign tool.
14
+ *
15
+ * Layout mirrors the minisign format the verifier parses:
16
+ * public key payload: [2 algo]["Ed"][8 keyId][32 raw ed25519 pubkey]
17
+ * signature payload : [2 algo][8 keyId][64 ed25519 signature]
18
+ */
19
+ function makeMinisign(
20
+ data: Buffer,
21
+ mode: "legacy" | "prehashed",
22
+ opts: { keyId?: Buffer; privateKey?: KeyObject; publicKey?: KeyObject } = {},
23
+ ): { pubKeyStr: string; sigFile: string } {
24
+ const { publicKey, privateKey } =
25
+ opts.privateKey && opts.publicKey
26
+ ? { publicKey: opts.publicKey, privateKey: opts.privateKey }
27
+ : generateKeyPairSync("ed25519")
28
+
29
+ const keyId = opts.keyId ?? randomBytes(8)
30
+
31
+ // Raw 32-byte ed25519 key = SPKI DER minus its 12-byte prefix.
32
+ const rawPub = publicKey.export({ format: "der", type: "spki" }).subarray(12)
33
+ const pkPayload = Buffer.concat([Buffer.from("Ed"), keyId, rawPub])
34
+ const pubKeyStr = `untrusted comment: test\n${pkPayload.toString("base64")}`
35
+
36
+ const prehashed = mode === "prehashed"
37
+ const signedData = prehashed
38
+ ? createHash("blake2b512").update(data).digest()
39
+ : data
40
+ const signature = edSign(null, signedData, privateKey)
41
+
42
+ const algo = prehashed ? Buffer.from("ED") : Buffer.from("Ed")
43
+ const sigPayload = Buffer.concat([algo, keyId, signature])
44
+ const sigFile =
45
+ `untrusted comment: signature\n${sigPayload.toString("base64")}\n` +
46
+ `trusted comment: timestamp\n${Buffer.alloc(64).toString("base64")}`
47
+
48
+ return { pubKeyStr, sigFile }
49
+ }
50
+
51
+ describe("verifyMinisign", () => {
52
+ const data = Buffer.from("the quick brown fox\n")
53
+
54
+ it("accepts a valid legacy ('Ed') signature", () => {
55
+ const keyId = randomBytes(8)
56
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519")
57
+ const { pubKeyStr, sigFile } = makeMinisign(data, "legacy", { keyId, privateKey, publicKey })
58
+ expect(() => verifyMinisign(data, sigFile, pubKeyStr)).not.toThrow()
59
+ })
60
+
61
+ it("accepts a valid prehashed ('ED', BLAKE2b-512) signature", () => {
62
+ const keyId = randomBytes(8)
63
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519")
64
+ const { pubKeyStr, sigFile } = makeMinisign(data, "prehashed", { keyId, privateKey, publicKey })
65
+ expect(() => verifyMinisign(data, sigFile, pubKeyStr)).not.toThrow()
66
+ })
67
+
68
+ it("rejects a prehashed signature when the file was tampered", () => {
69
+ const { pubKeyStr, sigFile } = makeMinisign(data, "prehashed")
70
+ const tampered = Buffer.from("the quick brown dog\n")
71
+ expect(() => verifyMinisign(tampered, sigFile, pubKeyStr)).toThrow(/verification FAILED/i)
72
+ })
73
+
74
+ it("rejects when the signature key id does not match the public key", () => {
75
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519")
76
+ const { sigFile } = makeMinisign(data, "prehashed", {
77
+ keyId: randomBytes(8),
78
+ privateKey,
79
+ publicKey,
80
+ })
81
+ // Public key advertising a different key id.
82
+ const { pubKeyStr } = makeMinisign(data, "prehashed", {
83
+ keyId: randomBytes(8),
84
+ privateKey,
85
+ publicKey,
86
+ })
87
+ expect(() => verifyMinisign(data, sigFile, pubKeyStr)).toThrow(/key ID mismatch/i)
88
+ })
89
+
90
+ it("rejects an unsupported algorithm", () => {
91
+ const { pubKeyStr, sigFile } = makeMinisign(data, "legacy")
92
+ // Corrupt the algorithm bytes in the signature payload to "XX".
93
+ const lines = sigFile.split("\n")
94
+ const sigBytes = Buffer.from(lines[1]!, "base64")
95
+ sigBytes[0] = 0x58
96
+ sigBytes[1] = 0x58
97
+ lines[1] = sigBytes.toString("base64")
98
+ expect(() => verifyMinisign(data, lines.join("\n"), pubKeyStr)).toThrow(
99
+ /Unsupported minisign algorithm/i,
100
+ )
101
+ })
102
+ })
@@ -1,5 +1,12 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { tmpdir } from "node:os"
1
4
  import { describe, expect, it } from "vitest"
2
- import { resolveProxyDevScript } from "../src/app/proxy-dev-app.js"
5
+ import {
6
+ portFromUpstream,
7
+ resolveProxyDevScript,
8
+ resolveViteDirectSpawn,
9
+ } from "../src/app/proxy-dev-app.js"
3
10
  import type { SupatypeProjectConfig } from "../src/project-config.js"
4
11
 
5
12
  const base = {
@@ -31,3 +38,40 @@ describe("resolveProxyDevScript()", () => {
31
38
  expect(resolveProxyDevScript(config)).toBe("dev:site")
32
39
  })
33
40
  })
41
+
42
+ describe("portFromUpstream()", () => {
43
+ it("parses port from upstream URL", () => {
44
+ const config = {
45
+ ...base,
46
+ app: { mode: "proxy" as const, upstream: "http://localhost:5285" },
47
+ } satisfies SupatypeProjectConfig
48
+ expect(portFromUpstream(config)).toBe(5285)
49
+ })
50
+
51
+ it("returns null when upstream has no port", () => {
52
+ const config = {
53
+ ...base,
54
+ app: { mode: "proxy" as const, upstream: "http://localhost" },
55
+ } satisfies SupatypeProjectConfig
56
+ expect(portFromUpstream(config)).toBeNull()
57
+ })
58
+ })
59
+
60
+ describe("resolveViteDirectSpawn()", () => {
61
+ it("returns node + vite.js for a plain vite script", () => {
62
+ const appDir = join(tmpdir(), `supatype-vite-spawn-${Date.now()}`)
63
+ const viteJs = join(appDir, "node_modules", "vite", "bin", "vite.js")
64
+ mkdirSync(join(appDir, "node_modules", "vite", "bin"), { recursive: true })
65
+ writeFileSync(viteJs, "")
66
+ const result = resolveViteDirectSpawn(appDir, "dev:vite", { "dev:vite": "vite" })
67
+ expect(result).not.toBeNull()
68
+ expect(result?.bin).toBe(process.execPath)
69
+ expect(result?.args[0]).toBe(viteJs)
70
+ expect(result?.shell).toBe(false)
71
+ })
72
+
73
+ it("returns null for non-vite scripts", () => {
74
+ const result = resolveViteDirectSpawn(process.cwd(), "dev", { dev: "next dev" })
75
+ expect(result).toBeNull()
76
+ })
77
+ })
@@ -106,12 +106,12 @@ describe("pgTypeToField()", () => {
106
106
  })
107
107
 
108
108
  describe("introspectColumnToColumnInfo()", () => {
109
- it("maps engine type → pgType and flags", () => {
109
+ it("maps engine column → pgType and flags", () => {
110
110
  const out = introspectColumnToColumnInfo({
111
111
  name: "id",
112
- type: "uuid",
112
+ dataType: "uuid",
113
+ udtName: "uuid",
113
114
  nullable: false,
114
- primaryKey: true,
115
115
  default: "gen_random_uuid()",
116
116
  })
117
117
  expect(out).toEqual({
@@ -127,7 +127,8 @@ describe("introspectColumnToColumnInfo()", () => {
127
127
  it("treats empty default as no default", () => {
128
128
  const out = introspectColumnToColumnInfo({
129
129
  name: "x",
130
- type: "text",
130
+ dataType: "text",
131
+ udtName: "text",
131
132
  nullable: true,
132
133
  default: "",
133
134
  })
@@ -4,8 +4,8 @@ import { join } from "node:path"
4
4
  import { tmpdir } from "node:os"
5
5
  import { runtimeRouteSpec } from "../src/runtime-routes.js"
6
6
  import { buildKongDeclarative } from "../src/kong-config.js"
7
- import { renderSelfHostCompose, writeSelfHostCompose } from "../src/self-host-compose.js"
8
- import { updateAppConfigInProject } from "../src/app-config.js"
7
+ import { composeDockerImageEnv, composePullNeedsIgnoreFailures, hasLocalVersionPins, isRegistryPullableImageRef, renderSelfHostCompose, writeSelfHostCompose } from "../src/self-host-compose.js"
8
+ import { updateAppConfigInProject, updateServerConfigInProject } from "../src/app-config.js"
9
9
  import type { SupatypeProjectConfig } from "../src/project-config.js"
10
10
  import { DENO_RELEASE_PIN } from "../src/release-pins.js"
11
11
 
@@ -109,6 +109,68 @@ describe("runtime contract", () => {
109
109
  expect(compose).toContain("supatype/schema-engine:latest")
110
110
  })
111
111
 
112
+ it("composeDockerImageEnv maps pinned versions to SUPATYPE_*_IMAGE", () => {
113
+ expect(composeDockerImageEnv(baseConfig)).toEqual({
114
+ SUPATYPE_ENGINE_IMAGE: "supatype/schema-engine:v0.4.2",
115
+ SUPATYPE_SERVER_IMAGE: "supatype/server:v0.1.0",
116
+ SUPATYPE_POSTGRES_IMAGE: "supatype/postgres:17-latest",
117
+ })
118
+ expect(composeDockerImageEnv({ ...baseConfig, versions: undefined })).toEqual({})
119
+ expect(
120
+ composeDockerImageEnv({
121
+ ...baseConfig,
122
+ database: { provider: "docker", image: "supatype/postgres:custom" },
123
+ }),
124
+ ).toEqual({
125
+ SUPATYPE_ENGINE_IMAGE: "supatype/schema-engine:v0.4.2",
126
+ SUPATYPE_SERVER_IMAGE: "supatype/server:v0.1.0",
127
+ SUPATYPE_POSTGRES_IMAGE: "supatype/postgres:custom",
128
+ })
129
+ })
130
+
131
+ it("isRegistryPullableImageRef accepts semver and latest tags", () => {
132
+ expect(isRegistryPullableImageRef("supatype/server:latest")).toBe(true)
133
+ expect(isRegistryPullableImageRef("supatype/schema-engine:v0.4.2")).toBe(true)
134
+ expect(isRegistryPullableImageRef("supatype/postgres:17-latest")).toBe(true)
135
+ expect(isRegistryPullableImageRef("supatype/control-plane:local")).toBe(false)
136
+ expect(isRegistryPullableImageRef("supatype/server:keepsake-local")).toBe(false)
137
+ })
138
+
139
+ it("composePullNeedsIgnoreFailures is false for semver pins only", () => {
140
+ const cwd = mkdtempSync(join(tmpdir(), "supatype-pull-"))
141
+ try {
142
+ writeFileSync(
143
+ join(cwd, ".env"),
144
+ "SUPATYPE_ENGINE_IMAGE=supatype/schema-engine:v0.4.2\n",
145
+ "utf8",
146
+ )
147
+ expect(hasLocalVersionPins(baseConfig)).toBe(false)
148
+ expect(composePullNeedsIgnoreFailures(baseConfig, cwd)).toBe(false)
149
+ } finally {
150
+ rmSync(cwd, { recursive: true, force: true })
151
+ }
152
+ })
153
+
154
+ it("composePullNeedsIgnoreFailures is true for versions.local or custom tags", () => {
155
+ const cwd = mkdtempSync(join(tmpdir(), "supatype-pull-local-"))
156
+ try {
157
+ writeFileSync(
158
+ join(cwd, ".env"),
159
+ "SUPATYPE_CONTROL_PLANE_IMAGE=supatype/control-plane:local\n",
160
+ "utf8",
161
+ )
162
+ expect(
163
+ composePullNeedsIgnoreFailures(
164
+ { ...baseConfig, versions: { engine: "local" } },
165
+ cwd,
166
+ ),
167
+ ).toBe(true)
168
+ expect(composePullNeedsIgnoreFailures(baseConfig, cwd)).toBe(true)
169
+ } finally {
170
+ rmSync(cwd, { recursive: true, force: true })
171
+ }
172
+ })
173
+
112
174
  it("self-host compose mounts project root at /project (project-directory relative)", () => {
113
175
  const compose = renderSelfHostCompose(baseConfig)
114
176
  expect(compose).toContain("- .:/project")
@@ -148,6 +210,18 @@ describe("runtime contract", () => {
148
210
  expect(compose).toContain("SUPATYPE_APP_UPSTREAM: http://host.docker.internal:4321")
149
211
  })
150
212
 
213
+ it("devLocal compose injects SUPATYPE_VITE_DEV_URL when app.vite_dev_url is set", () => {
214
+ const compose = renderSelfHostCompose(
215
+ {
216
+ ...baseConfig,
217
+ app: { mode: "static", static_dir: "./dist", vite_dev_url: "http://127.0.0.1:5173" },
218
+ },
219
+ process.cwd(),
220
+ { devLocal: true },
221
+ )
222
+ expect(compose).toContain("SUPATYPE_VITE_DEV_URL: http://host.docker.internal:5173")
223
+ })
224
+
151
225
  it("devLocal compose omits studio container when overrides.studio is set", () => {
152
226
  const compose = renderSelfHostCompose(
153
227
  { ...baseConfig, overrides: { studio: "../supatype/packages/studio" } },
@@ -314,6 +388,60 @@ export default defineConfig({
314
388
  }
315
389
  })
316
390
 
391
+ it("server config updater sets standalone mode, domain, and tls (preserving other keys)", () => {
392
+ const dir = mkdtempSync(join(tmpdir(), "supatype-server-config-"))
393
+ try {
394
+ const configPath = join(dir, "supatype.config.ts")
395
+ writeFileSync(
396
+ configPath,
397
+ `import { defineConfig } from "@supatype/cli"
398
+
399
+ export default defineConfig({
400
+ project: { name: "x" },
401
+ database: { provider: "docker" },
402
+ server: { mode: "dev", port: 54321 },
403
+ app: { mode: "none" },
404
+ })
405
+ `,
406
+ "utf8",
407
+ )
408
+ updateServerConfigInProject(dir, { domain: "demo.supatype.com", tlsEmail: "hello@supatype.com" })
409
+ const next = readFileSync(configPath, "utf8")
410
+ expect(next).toContain(`mode: "standalone"`)
411
+ expect(next).toContain(`domain: "demo.supatype.com"`)
412
+ expect(next).toContain(`tls: { email: "hello@supatype.com", provider: "kong" }`)
413
+ expect(next).toContain(`port: 54321`)
414
+ } finally {
415
+ rmSync(dir, { recursive: true, force: true })
416
+ }
417
+ })
418
+
419
+ it("server config updater is idempotent and overwrites a prior domain/email", () => {
420
+ const dir = mkdtempSync(join(tmpdir(), "supatype-server-config-"))
421
+ try {
422
+ const configPath = join(dir, "supatype.config.ts")
423
+ writeFileSync(
424
+ configPath,
425
+ `export default {
426
+ project: { name: "x" },
427
+ database: { provider: "docker" },
428
+ server: { mode: "standalone", domain: "old.example.com", tls: { email: "old@example.com", provider: "kong" } },
429
+ app: { mode: "none" },
430
+ }
431
+ `,
432
+ "utf8",
433
+ )
434
+ updateServerConfigInProject(dir, { domain: "new.supatype.com", tlsEmail: "new@supatype.com" })
435
+ const next = readFileSync(configPath, "utf8")
436
+ expect(next).toContain(`domain: "new.supatype.com"`)
437
+ expect(next).toContain(`email: "new@supatype.com"`)
438
+ expect(next).not.toContain("old.example.com")
439
+ expect(next).not.toContain("old@example.com")
440
+ } finally {
441
+ rmSync(dir, { recursive: true, force: true })
442
+ }
443
+ })
444
+
317
445
  it("writes self-host compose artifacts under .supatype/self-host", () => {
318
446
  const dir = mkdtempSync(join(tmpdir(), "supatype-compose-"))
319
447
  try {
@@ -337,6 +465,62 @@ export default defineConfig({
337
465
  }
338
466
  })
339
467
 
468
+ const tlsConfig: SupatypeProjectConfig = {
469
+ ...baseConfig,
470
+ server: { mode: "standalone", domain: "api.example.com", tls: { email: "ops@example.com" } },
471
+ }
472
+
473
+ it("self-host compose renders Kong TLS + Valkey when standalone domain + email are set", () => {
474
+ const compose = renderSelfHostCompose(tlsConfig)
475
+ expect(compose).toContain("\n valkey:\n")
476
+ expect(compose).toContain("valkey/valkey:8-alpine")
477
+ expect(compose).toContain('- "80:8000"')
478
+ expect(compose).toContain('- "443:8443"')
479
+ expect(compose).toContain("KONG_PROXY_LISTEN")
480
+ expect(compose).toContain("- valkey")
481
+ expect(compose).toContain("https://api.example.com")
482
+ expect(compose).toMatch(/^\s{2}valkey-data:/m)
483
+ expect(compose).not.toContain("HTTPS is off")
484
+ })
485
+
486
+ it("self-host compose stays plain HTTP with a discoverable hint when TLS is off", () => {
487
+ const compose = renderSelfHostCompose(baseConfig)
488
+ expect(compose).not.toContain("\n valkey:\n")
489
+ expect(compose).not.toContain('- "443:8443"')
490
+ expect(compose).toContain("${SUPATYPE_KONG_PORT:-18473}:8000")
491
+ expect(compose).toContain("HTTPS is off")
492
+ })
493
+
494
+ it("self-host compose does not enable TLS for a domain without an ACME email", () => {
495
+ const compose = renderSelfHostCompose({
496
+ ...baseConfig,
497
+ server: { mode: "standalone", domain: "api.example.com" },
498
+ })
499
+ expect(compose).not.toContain("\n valkey:\n")
500
+ expect(compose).not.toContain('- "443:8443"')
501
+ })
502
+
503
+ it("writeSelfHostCompose emits a Kong acme plugin backed by Valkey when TLS is on", () => {
504
+ const dir = mkdtempSync(join(tmpdir(), "supatype-tls-"))
505
+ try {
506
+ const out = writeSelfHostCompose(dir, tlsConfig)
507
+ const kong = readFileSync(out.kongPath, "utf8")
508
+ expect(kong).toContain("name: acme")
509
+ expect(kong).toContain('account_email: "ops@example.com"')
510
+ expect(kong).toContain("tos_accepted: true")
511
+ expect(kong).toContain('- "api.example.com"')
512
+ expect(kong).toContain("storage: redis")
513
+ expect(kong).toContain('host: "valkey"')
514
+ } finally {
515
+ rmSync(dir, { recursive: true, force: true })
516
+ }
517
+ })
518
+
519
+ it("kong declarative omits the acme plugin when no acme options are provided", () => {
520
+ const kong = buildKongDeclarative({ unifiedGateway: true })
521
+ expect(kong).not.toContain("name: acme")
522
+ })
523
+
340
524
  it("writes default manifest when missing for compose", () => {
341
525
  const dir = mkdtempSync(join(tmpdir(), "supatype-manifest-"))
342
526
  try {
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest"
2
+ import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from "node:fs"
3
+ import { join } from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import { collectSchemaSourcePaths } from "../src/type-extractor.js"
6
+ import {
7
+ packSchemaSources,
8
+ unpackSchemaSources,
9
+ buildSchemaSourcesPayload,
10
+ restoreSchemaSourcesFromGz,
11
+ findOrphanSchemaFiles,
12
+ } from "../src/schema-sources.js"
13
+
14
+ function writeMultiFileSchema(root: string): void {
15
+ mkdirSync(join(root, "schema", "models"), { recursive: true })
16
+ mkdirSync(join(root, "schema", "shared"), { recursive: true })
17
+ writeFileSync(
18
+ join(root, "schema", "index.ts"),
19
+ `export type { Album } from "./models/album"\nexport type { localeConfig } from "./shared/locale"\n`,
20
+ )
21
+ writeFileSync(
22
+ join(root, "schema", "models", "album.ts"),
23
+ `import type { Nullable } from "../shared/field-types"\nexport type Album = { id: string; title: Nullable<string> }\n`,
24
+ )
25
+ writeFileSync(
26
+ join(root, "schema", "shared", "field-types.ts"),
27
+ `export type Nullable<T> = T | null\n`,
28
+ )
29
+ writeFileSync(
30
+ join(root, "schema", "shared", "locale.ts"),
31
+ `export type localeConfig = { default: string }\n`,
32
+ )
33
+ }
34
+
35
+ describe("schema-sources", () => {
36
+ let tmp: string
37
+
38
+ beforeEach(() => {
39
+ tmp = mkdtempSync(join(tmpdir(), "schema-sources-"))
40
+ })
41
+
42
+ afterEach(() => {
43
+ rmSync(tmp, { recursive: true, force: true })
44
+ })
45
+
46
+ it("discovers multi-file graph from entry point", () => {
47
+ writeMultiFileSchema(tmp)
48
+ const entry = join(tmp, "schema", "index.ts")
49
+ const graph = collectSchemaSourcePaths(entry, tmp)
50
+ const paths = graph.files.map((f) => f.relativePath).sort()
51
+ expect(paths).toEqual([
52
+ "schema/index.ts",
53
+ "schema/models/album.ts",
54
+ "schema/shared/field-types.ts",
55
+ "schema/shared/locale.ts",
56
+ ])
57
+ expect(graph.entryPoint).toBe("schema/index.ts")
58
+ })
59
+
60
+ it("roundtrips tar+gzip bytes", () => {
61
+ writeMultiFileSchema(tmp)
62
+ const graph = collectSchemaSourcePaths(join(tmp, "schema", "index.ts"), tmp)
63
+ const tar = packSchemaSources(graph)
64
+ const files = unpackSchemaSources(tar, tmp)
65
+ for (const file of graph.files) {
66
+ const original = readFileSync(file.absolutePath)
67
+ const restored = files.get(file.relativePath.replace(/\\/g, "/"))
68
+ expect(restored?.equals(original)).toBe(true)
69
+ }
70
+ })
71
+
72
+ it("buildSchemaSourcesPayload includes manifest metadata", () => {
73
+ writeMultiFileSchema(tmp)
74
+ writeFileSync(
75
+ join(tmp, "supatype.config.ts"),
76
+ `export default ${JSON.stringify({
77
+ project: { name: "demo" },
78
+ database: { provider: "docker" },
79
+ server: { mode: "dev" },
80
+ app: { mode: "none" },
81
+ schema: { path: "schema/index.ts", pg_schema: "public" },
82
+ })}\n`,
83
+ )
84
+ const payload = buildSchemaSourcesPayload(tmp, "test@example.com")
85
+ expect(payload).not.toBeNull()
86
+ expect(payload!.manifest.fileCount).toBe(4)
87
+ expect(payload!.manifest.pushedBy).toBe("test@example.com")
88
+ expect(payload!.manifest.compressedBytes).toBeGreaterThan(0)
89
+ })
90
+
91
+ it("restore overwrites modified local files", () => {
92
+ writeMultiFileSchema(tmp)
93
+ writeFileSync(
94
+ join(tmp, "supatype.config.ts"),
95
+ `export default ${JSON.stringify({
96
+ project: { name: "demo" },
97
+ database: { provider: "docker" },
98
+ server: { mode: "dev" },
99
+ app: { mode: "none" },
100
+ schema: { path: "schema/index.ts", pg_schema: "public" },
101
+ })}\n`,
102
+ )
103
+ const payload = buildSchemaSourcesPayload(tmp)!
104
+ const albumPath = join(tmp, "schema", "models", "album.ts")
105
+ writeFileSync(albumPath, "// modified\n")
106
+
107
+ restoreSchemaSourcesFromGz(payload.gz, payload.manifest, tmp)
108
+ expect(readFileSync(albumPath, "utf8")).toContain("Nullable")
109
+ })
110
+
111
+ it("warns about orphan files not in snapshot", () => {
112
+ writeMultiFileSchema(tmp)
113
+ writeFileSync(join(tmp, "schema", "models", "experimental.ts"), "export type X = {}\n")
114
+ const graph = collectSchemaSourcePaths(join(tmp, "schema", "index.ts"), tmp)
115
+ const manifestPaths = new Set(graph.files.map((f) => f.relativePath))
116
+ const orphans = findOrphanSchemaFiles(tmp, graph.entryPoint, manifestPaths)
117
+ expect(orphans).toContain("schema/models/experimental.ts")
118
+ })
119
+ })