@supatype/cli 0.1.0-alpha.9 → 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 (383) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-test.log +270 -65
  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/self-host-compose.d.ts +37 -1
  231. package/dist/self-host-compose.d.ts.map +1 -1
  232. package/dist/self-host-compose.js +233 -43
  233. package/dist/self-host-compose.js.map +1 -1
  234. package/dist/storage-provision.d.ts +4 -0
  235. package/dist/storage-provision.d.ts.map +1 -1
  236. package/dist/storage-provision.js +24 -2
  237. package/dist/storage-provision.js.map +1 -1
  238. package/dist/supatype-eval-1781522769253.d.mts +2 -0
  239. package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
  240. package/dist/supatype-eval-1781522769253.mjs +3 -0
  241. package/dist/supatype-eval-1781522769253.mjs.map +1 -0
  242. package/dist/systemd.js +2 -2
  243. package/dist/systemd.js.map +1 -1
  244. package/dist/target-client.d.ts +10 -0
  245. package/dist/target-client.d.ts.map +1 -0
  246. package/dist/target-client.js +22 -0
  247. package/dist/target-client.js.map +1 -0
  248. package/dist/type-extractor.d.ts +11 -0
  249. package/dist/type-extractor.d.ts.map +1 -1
  250. package/dist/type-extractor.js +95 -8
  251. package/dist/type-extractor.js.map +1 -1
  252. package/dist/ui/brand.d.ts +9 -0
  253. package/dist/ui/brand.d.ts.map +1 -0
  254. package/dist/ui/brand.js +11 -0
  255. package/dist/ui/brand.js.map +1 -0
  256. package/dist/ui/confirm.d.ts +12 -0
  257. package/dist/ui/confirm.d.ts.map +1 -0
  258. package/dist/ui/confirm.js +28 -0
  259. package/dist/ui/confirm.js.map +1 -0
  260. package/dist/ui/fatal.d.ts +10 -0
  261. package/dist/ui/fatal.d.ts.map +1 -0
  262. package/dist/ui/fatal.js +34 -0
  263. package/dist/ui/fatal.js.map +1 -0
  264. package/dist/ui/index.d.ts +9 -0
  265. package/dist/ui/index.d.ts.map +1 -0
  266. package/dist/ui/index.js +9 -0
  267. package/dist/ui/index.js.map +1 -0
  268. package/dist/ui/interactive.d.ts +3 -0
  269. package/dist/ui/interactive.d.ts.map +1 -0
  270. package/dist/ui/interactive.js +5 -0
  271. package/dist/ui/interactive.js.map +1 -0
  272. package/dist/ui/messages.d.ts +10 -0
  273. package/dist/ui/messages.d.ts.map +1 -0
  274. package/dist/ui/messages.js +35 -0
  275. package/dist/ui/messages.js.map +1 -0
  276. package/dist/ui/next-steps.d.ts +3 -0
  277. package/dist/ui/next-steps.d.ts.map +1 -0
  278. package/dist/ui/next-steps.js +10 -0
  279. package/dist/ui/next-steps.js.map +1 -0
  280. package/dist/ui/progress.d.ts +5 -0
  281. package/dist/ui/progress.d.ts.map +1 -0
  282. package/dist/ui/progress.js +24 -0
  283. package/dist/ui/progress.js.map +1 -0
  284. package/dist/ui/prompts.d.ts +14 -0
  285. package/dist/ui/prompts.d.ts.map +1 -0
  286. package/dist/ui/prompts.js +34 -0
  287. package/dist/ui/prompts.js.map +1 -0
  288. package/package.json +3 -2
  289. package/src/app/framework.ts +1 -3
  290. package/src/app/proxy-dev-app.ts +114 -6
  291. package/src/app-config.ts +80 -0
  292. package/src/binary-cache.ts +102 -52
  293. package/src/cli.ts +16 -2
  294. package/src/commands/add.ts +97 -0
  295. package/src/commands/admin.ts +39 -73
  296. package/src/commands/adopt.ts +82 -0
  297. package/src/commands/app.ts +20 -17
  298. package/src/commands/cache.ts +11 -10
  299. package/src/commands/cloud.ts +91 -142
  300. package/src/commands/db.ts +40 -63
  301. package/src/commands/deploy.ts +186 -126
  302. package/src/commands/dev.ts +95 -55
  303. package/src/commands/diff.ts +52 -43
  304. package/src/commands/doctor.ts +103 -0
  305. package/src/commands/engine.ts +5 -4
  306. package/src/commands/functions.ts +187 -123
  307. package/src/commands/generate.ts +5 -4
  308. package/src/commands/init.ts +996 -105
  309. package/src/commands/introspect.ts +48 -0
  310. package/src/commands/keys.ts +56 -14
  311. package/src/commands/link-helpers.ts +273 -0
  312. package/src/commands/logs.ts +5 -4
  313. package/src/commands/migrate-from-v1.ts +3 -2
  314. package/src/commands/migrate.ts +167 -27
  315. package/src/commands/pg.ts +13 -18
  316. package/src/commands/plugins.ts +55 -46
  317. package/src/commands/pull.ts +38 -9
  318. package/src/commands/push.ts +147 -174
  319. package/src/commands/seed.ts +5 -4
  320. package/src/commands/self-host.ts +85 -54
  321. package/src/commands/self-update.ts +3 -2
  322. package/src/commands/status.ts +102 -33
  323. package/src/commands/types.ts +3 -2
  324. package/src/commands/update.ts +59 -23
  325. package/src/config.ts +2 -1
  326. package/src/dev-compose.ts +426 -34
  327. package/src/dev-log-bus.ts +101 -0
  328. package/src/dev-log-filter.ts +32 -0
  329. package/src/dev-logo.ts +61 -0
  330. package/src/dev-session.ts +130 -0
  331. package/src/dev-shutdown.ts +54 -0
  332. package/src/dev-task-colors.ts +47 -0
  333. package/src/dev-tui.ts +232 -0
  334. package/src/diff-output.ts +79 -1
  335. package/src/docker-runtime.ts +151 -0
  336. package/src/engine-client.ts +81 -17
  337. package/src/engine-push-output.ts +75 -0
  338. package/src/ensure-binary.ts +2 -2
  339. package/src/gitignore.ts +48 -0
  340. package/src/kong-config.ts +24 -1
  341. package/src/link.ts +243 -0
  342. package/src/process-manager.ts +66 -10
  343. package/src/project-config.ts +62 -7
  344. package/src/prompts.ts +2 -0
  345. package/src/pull-utils.ts +217 -23
  346. package/src/resolve-target.ts +419 -0
  347. package/src/restore-system-relation-targets.ts +45 -0
  348. package/src/runtime-routes.ts +7 -0
  349. package/src/schema-ast-v2.ts +2 -1
  350. package/src/schema-sources.ts +248 -0
  351. package/src/scripts/postinstall.ts +7 -1
  352. package/src/self-host-compose.ts +261 -46
  353. package/src/storage-provision.ts +33 -1
  354. package/src/supatype-eval-1781522769253.mts +1 -0
  355. package/src/systemd.ts +2 -2
  356. package/src/target-client.ts +40 -0
  357. package/src/type-extractor.ts +124 -11
  358. package/src/ui/README.md +17 -0
  359. package/src/ui/brand.ts +12 -0
  360. package/src/ui/confirm.ts +38 -0
  361. package/src/ui/fatal.ts +43 -0
  362. package/src/ui/index.ts +8 -0
  363. package/src/ui/interactive.ts +4 -0
  364. package/src/ui/messages.ts +43 -0
  365. package/src/ui/next-steps.ts +10 -0
  366. package/src/ui/progress.ts +28 -0
  367. package/src/ui/prompts.ts +40 -0
  368. package/tests/cli-help.test.ts +27 -2
  369. package/tests/config.test.ts +29 -2
  370. package/tests/dev-ui.test.ts +139 -0
  371. package/tests/docker-runtime.test.ts +236 -0
  372. package/tests/engine-push-output.test.ts +67 -0
  373. package/tests/init.test.ts +197 -18
  374. package/tests/link.test.ts +148 -0
  375. package/tests/minisign.test.ts +102 -0
  376. package/tests/proxy-dev-app.test.ts +45 -1
  377. package/tests/pull-utils.test.ts +5 -4
  378. package/tests/runtime-contract.test.ts +186 -2
  379. package/tests/schema-sources.test.ts +119 -0
  380. package/tests/storage-provision.test.ts +100 -0
  381. package/tests/ui-confirm.test.ts +41 -0
  382. package/tests/ui-messages.test.ts +66 -0
  383. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,236 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach, afterAll } from "vitest"
2
+ import type { SpawnSyncReturns } from "node:child_process"
3
+
4
+ const spawnSyncMock = vi.hoisted(() => vi.fn())
5
+ const isInteractiveMock = vi.hoisted(() => vi.fn(() => false))
6
+ const clackLogErrorMock = vi.hoisted(() => vi.fn())
7
+ const clackNoteMock = vi.hoisted(() => vi.fn())
8
+ const clackIntroMock = vi.hoisted(() => vi.fn())
9
+ const printLogoMock = vi.hoisted(() => vi.fn())
10
+
11
+ vi.mock("node:child_process", () => ({
12
+ spawnSync: spawnSyncMock,
13
+ }))
14
+
15
+ vi.mock("../src/ui/interactive.js", () => ({
16
+ isInteractive: isInteractiveMock,
17
+ }))
18
+
19
+ vi.mock("@clack/prompts", async (importOriginal) => {
20
+ const original = await importOriginal<typeof import("@clack/prompts")>()
21
+ return {
22
+ ...original,
23
+ intro: clackIntroMock,
24
+ note: clackNoteMock,
25
+ log: {
26
+ ...original.log,
27
+ error: clackLogErrorMock,
28
+ },
29
+ }
30
+ })
31
+
32
+ vi.mock("../src/ui/prompts.js", async (importOriginal) => {
33
+ const original = await importOriginal<typeof import("../src/ui/prompts.js")>()
34
+ return {
35
+ ...original,
36
+ printLogo: printLogoMock,
37
+ }
38
+ })
39
+
40
+ import {
41
+ probeDockerDaemon,
42
+ reportDockerUnavailable,
43
+ requireDockerDaemon,
44
+ } from "../src/docker-runtime.js"
45
+
46
+ function spawnResult(
47
+ overrides: Partial<SpawnSyncReturns<string>> & Pick<SpawnSyncReturns<string>, "status">,
48
+ ): SpawnSyncReturns<string> {
49
+ return {
50
+ stdout: "",
51
+ stderr: "",
52
+ pid: 0,
53
+ output: ["", ""],
54
+ signal: null,
55
+ ...overrides,
56
+ }
57
+ }
58
+
59
+ describe("probeDockerDaemon", () => {
60
+ beforeEach(() => {
61
+ spawnSyncMock.mockReset()
62
+ })
63
+
64
+ it("returns ok when docker version and info succeed", () => {
65
+ spawnSyncMock
66
+ .mockReturnValueOnce(spawnResult({ status: 0, stdout: "24.0.0" }))
67
+ .mockReturnValueOnce(spawnResult({ status: 0, stdout: "Server Version: 24.0.0" }))
68
+
69
+ expect(probeDockerDaemon()).toEqual({ ok: true })
70
+ expect(spawnSyncMock).toHaveBeenCalledTimes(2)
71
+ expect(spawnSyncMock.mock.calls[0]?.[1]).toEqual(["version", "--format", "{{.Client.Version}}"])
72
+ expect(spawnSyncMock.mock.calls[1]?.[1]).toEqual(["info"])
73
+ })
74
+
75
+ it("returns cli_missing when docker is not on PATH", () => {
76
+ spawnSyncMock.mockReturnValueOnce(
77
+ spawnResult({ status: 1, error: Object.assign(new Error("spawn docker ENOENT"), { code: "ENOENT" }) }),
78
+ )
79
+
80
+ expect(probeDockerDaemon()).toEqual({ ok: false, reason: "cli_missing" })
81
+ expect(spawnSyncMock).toHaveBeenCalledTimes(1)
82
+ })
83
+
84
+ it("returns daemon_unavailable when info fails", () => {
85
+ spawnSyncMock
86
+ .mockReturnValueOnce(spawnResult({ status: 0, stdout: "24.0.0" }))
87
+ .mockReturnValueOnce(
88
+ spawnResult({
89
+ status: 1,
90
+ stderr: "Cannot connect to the Docker daemon at unix:///var/run/docker.sock",
91
+ }),
92
+ )
93
+
94
+ expect(probeDockerDaemon()).toEqual({
95
+ ok: false,
96
+ reason: "daemon_unavailable",
97
+ detail: "Cannot connect to the Docker daemon at unix:///var/run/docker.sock",
98
+ })
99
+ })
100
+
101
+ it("returns daemon_unavailable when version exits non-zero but client version is present (e.g. Docker paused)", () => {
102
+ spawnSyncMock.mockReturnValueOnce(
103
+ spawnResult({
104
+ status: 1,
105
+ stdout: "29.4.0\n",
106
+ stderr: "Error response from daemon: Docker Desktop is manually paused.",
107
+ }),
108
+ )
109
+
110
+ expect(probeDockerDaemon()).toEqual({
111
+ ok: false,
112
+ reason: "daemon_unavailable",
113
+ detail: "Error response from daemon: Docker Desktop is manually paused.",
114
+ })
115
+ expect(spawnSyncMock).toHaveBeenCalledTimes(1)
116
+ })
117
+ })
118
+
119
+ describe("reportDockerUnavailable", () => {
120
+ const errorMock = vi.spyOn(console, "error").mockImplementation(() => {})
121
+ const logMock = vi.spyOn(console, "log").mockImplementation(() => {})
122
+
123
+ beforeEach(() => {
124
+ isInteractiveMock.mockReturnValue(false)
125
+ errorMock.mockClear()
126
+ logMock.mockClear()
127
+ clackLogErrorMock.mockClear()
128
+ clackNoteMock.mockClear()
129
+ clackIntroMock.mockClear()
130
+ printLogoMock.mockClear()
131
+ })
132
+
133
+ afterAll(() => {
134
+ errorMock.mockRestore()
135
+ logMock.mockRestore()
136
+ })
137
+
138
+ it("uses [supatype] prefix on the headline via error()", () => {
139
+ reportDockerUnavailable({
140
+ ok: false,
141
+ reason: "daemon_unavailable",
142
+ detail: "Cannot connect to the Docker daemon",
143
+ })
144
+
145
+ expect(errorMock).toHaveBeenCalledWith(
146
+ "[supatype] Docker is installed but the daemon is not running.",
147
+ )
148
+ expect(logMock.mock.calls.some((call) => String(call[0]).includes('provider: "native"'))).toBe(true)
149
+ expect(logMock.mock.calls.some((call) => String(call[0]).includes("docker: Cannot connect"))).toBe(
150
+ false,
151
+ )
152
+ })
153
+
154
+ it("mentions unpause when daemon detail says paused", () => {
155
+ reportDockerUnavailable({
156
+ ok: false,
157
+ reason: "daemon_unavailable",
158
+ detail: "Error response from daemon: Docker Desktop is manually paused.",
159
+ })
160
+
161
+ expect(logMock.mock.calls.some((call) => String(call[0]).includes("Unpause Docker Desktop"))).toBe(
162
+ true,
163
+ )
164
+ expect(logMock.mock.calls.some((call) => String(call[0]).includes("docker:"))).toBe(false)
165
+ })
166
+
167
+ it("mentions install for cli_missing", () => {
168
+ reportDockerUnavailable({ ok: false, reason: "cli_missing" })
169
+
170
+ expect(errorMock).toHaveBeenCalledWith("[supatype] Docker is not installed or not on your PATH.")
171
+ expect(logMock.mock.calls.some((call) => String(call[0]).includes("docker-desktop"))).toBe(true)
172
+ })
173
+
174
+ it("uses Clack log + note in interactive mode", () => {
175
+ isInteractiveMock.mockReturnValue(true)
176
+
177
+ reportDockerUnavailable(
178
+ {
179
+ ok: false,
180
+ reason: "daemon_unavailable",
181
+ detail: "Error response from daemon: Docker Desktop is manually paused.",
182
+ },
183
+ { brand: { intro: "Local development" } },
184
+ )
185
+
186
+ expect(printLogoMock).toHaveBeenCalled()
187
+ expect(clackIntroMock).toHaveBeenCalledWith("Local development")
188
+ expect(clackLogErrorMock).toHaveBeenCalledWith(
189
+ "Docker is installed but the daemon is not running.",
190
+ )
191
+ expect(clackNoteMock).toHaveBeenCalledWith(
192
+ expect.stringContaining("Unpause Docker Desktop"),
193
+ )
194
+ expect(errorMock).not.toHaveBeenCalled()
195
+ expect(logMock).not.toHaveBeenCalled()
196
+ })
197
+ })
198
+
199
+ describe("requireDockerDaemon", () => {
200
+ let exitMock: ReturnType<typeof vi.spyOn<typeof process, "exit">>
201
+ let errorMock: ReturnType<typeof vi.spyOn<typeof console, "error">>
202
+
203
+ beforeEach(() => {
204
+ spawnSyncMock.mockReset()
205
+ isInteractiveMock.mockReturnValue(false)
206
+ exitMock = vi.spyOn(process, "exit").mockImplementation((() => {}) as typeof process.exit)
207
+ errorMock = vi.spyOn(console, "error").mockImplementation(() => {})
208
+ })
209
+
210
+ afterEach(() => {
211
+ exitMock.mockRestore()
212
+ errorMock.mockRestore()
213
+ })
214
+
215
+ it("exits when the daemon is unavailable", () => {
216
+ spawnSyncMock
217
+ .mockReturnValueOnce(spawnResult({ status: 0, stdout: "24.0.0" }))
218
+ .mockReturnValueOnce(spawnResult({ status: 1, stderr: "daemon down" }))
219
+
220
+ requireDockerDaemon()
221
+
222
+ expect(errorMock).toHaveBeenCalled()
223
+ expect(exitMock).toHaveBeenCalledWith(1)
224
+ })
225
+
226
+ it("does not exit when docker is available", () => {
227
+ spawnSyncMock
228
+ .mockReturnValueOnce(spawnResult({ status: 0, stdout: "24.0.0" }))
229
+ .mockReturnValueOnce(spawnResult({ status: 0, stdout: "ok" }))
230
+
231
+ requireDockerDaemon()
232
+
233
+ expect(exitMock).not.toHaveBeenCalled()
234
+ expect(errorMock).not.toHaveBeenCalled()
235
+ })
236
+ })
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import {
3
+ filterComposeNoise,
4
+ formatEnginePushMessage,
5
+ parseEnginePushOutput,
6
+ } from "../src/engine-push-output.js"
7
+
8
+ describe("parseEnginePushOutput()", () => {
9
+ it("parses JSON on its own line amid docker progress", () => {
10
+ const output = [
11
+ " Container supatype-to-do-db-1 Running",
12
+ " Container supatype-to-do-db-1 Waiting",
13
+ " Container supatype-to-do-db-1 Healthy",
14
+ '{"status":"up_to_date","operations":0,"admin_refreshed":true}',
15
+ " Container supatype-to-do-schema-engine-run-abc Created",
16
+ ].join("\n")
17
+
18
+ expect(parseEnginePushOutput(output)).toEqual({
19
+ status: "up_to_date",
20
+ operations: 0,
21
+ admin_refreshed: true,
22
+ })
23
+ })
24
+
25
+ it("parses bare JSON output", () => {
26
+ expect(parseEnginePushOutput('{"status":"applied","operations":2}')).toEqual({
27
+ status: "applied",
28
+ operations: 2,
29
+ })
30
+ })
31
+
32
+ it("returns null when no JSON present", () => {
33
+ expect(parseEnginePushOutput("engine failed: connection refused")).toBeNull()
34
+ })
35
+ })
36
+
37
+ describe("formatEnginePushMessage()", () => {
38
+ it("formats up_to_date with admin refresh", () => {
39
+ expect(
40
+ formatEnginePushMessage({ status: "up_to_date", operations: 0, admin_refreshed: true }),
41
+ ).toBe("Schema up to date — Studio metadata synced.")
42
+ })
43
+
44
+ it("formats up_to_date without admin refresh", () => {
45
+ expect(formatEnginePushMessage({ status: "up_to_date" })).toBe("Schema up to date.")
46
+ })
47
+
48
+ it("formats applied operations", () => {
49
+ expect(formatEnginePushMessage({ status: "applied", operations: 3 })).toBe(
50
+ "Applied 3 operation(s).",
51
+ )
52
+ })
53
+ })
54
+
55
+ describe("filterComposeNoise()", () => {
56
+ it("removes container progress lines but keeps errors", () => {
57
+ const output = [
58
+ " Container supatype-to-do-db-1 Running",
59
+ "engine error: permission denied",
60
+ '{"status":"up_to_date","operations":0}',
61
+ ].join("\n")
62
+
63
+ expect(filterComposeNoise(output)).toBe(
64
+ 'engine error: permission denied\n{"status":"up_to_date","operations":0}',
65
+ )
66
+ })
67
+ })
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"
2
2
  import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from "node:fs"
3
3
  import { join } from "node:path"
4
4
  import { tmpdir } from "node:os"
5
- import { scaffold } from "../src/commands/init.js"
5
+ import { scaffold, defaultScaffoldOptions } from "../src/commands/init.js"
6
6
 
7
7
  let tmpRoot: string
8
8
 
@@ -17,7 +17,7 @@ afterEach(() => {
17
17
 
18
18
  describe("scaffold()", () => {
19
19
  it("creates all expected files", () => {
20
- scaffold(tmpRoot, "my-app")
20
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app"))
21
21
 
22
22
  const expected = [
23
23
  "package.json",
@@ -35,17 +35,18 @@ describe("scaffold()", () => {
35
35
  })
36
36
 
37
37
  it("supatype.config.ts embeds the project name and exports defineConfig", () => {
38
- scaffold(tmpRoot, "blog-app")
38
+ scaffold(tmpRoot, defaultScaffoldOptions("blog-app"))
39
39
  const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
40
40
  expect(content).toContain("blog-app")
41
41
  expect(content).toContain("defineConfig")
42
- expect(content).toContain('provider: "native"')
42
+ expect(content).toContain('provider: "docker"')
43
43
  expect(content).toContain("schema:")
44
- expect(content).toContain("versions:")
44
+ expect(content).toContain("Optional: pin component versions")
45
+ expect(content).not.toMatch(/^\s*versions:\s*\{/m)
45
46
  })
46
47
 
47
48
  it("package.json includes @supatype/cli and @supatype/types", () => {
48
- scaffold(tmpRoot, "pkg-app")
49
+ scaffold(tmpRoot, defaultScaffoldOptions("pkg-app"))
49
50
  const content = readFileSync(join(tmpRoot, "package.json"), "utf8")
50
51
  expect(content).toContain("@supatype/cli")
51
52
  expect(content).toContain("@supatype/types")
@@ -55,18 +56,18 @@ describe("scaffold()", () => {
55
56
  it("skips package.json when it already exists", () => {
56
57
  const pkgPath = join(tmpRoot, "package.json")
57
58
  writeFileSync(pkgPath, '{"name":"existing"}', "utf8")
58
- scaffold(tmpRoot, "my-app")
59
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app"))
59
60
  expect(readFileSync(pkgPath, "utf8")).toBe('{"name":"existing"}')
60
61
  })
61
62
 
62
63
  it("supatype.config.ts documents self-host workflow", () => {
63
- scaffold(tmpRoot, "my-app")
64
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app"))
64
65
  const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
65
66
  expect(content).toContain("self-host")
66
67
  })
67
68
 
68
69
  it(".env contains DATABASE_URL, JWT_SECRET, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB", () => {
69
- scaffold(tmpRoot, "my-app")
70
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app"))
70
71
  const content = readFileSync(join(tmpRoot, ".env"), "utf8")
71
72
  expect(content).toContain("DATABASE_URL=")
72
73
  expect(content).toContain("JWT_SECRET=")
@@ -76,44 +77,45 @@ describe("scaffold()", () => {
76
77
  })
77
78
 
78
79
  it(".env contains ANON_KEY, SERVICE_ROLE_KEY, and SITE_URL placeholders", () => {
79
- scaffold(tmpRoot, "my-app")
80
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app"))
80
81
  const content = readFileSync(join(tmpRoot, ".env"), "utf8")
81
82
  expect(content).toContain("ANON_KEY=")
82
83
  expect(content).toContain("SERVICE_ROLE_KEY=")
83
84
  expect(content).toContain("SITE_URL=")
84
85
  })
85
86
 
86
- it("schema/index.ts exports a User model using RFC v2 Model<>", () => {
87
- scaffold(tmpRoot, "my-app")
87
+ it("schema/index.ts exports a Profile model using RFC v2 Model<>", () => {
88
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app"))
88
89
  const content = readFileSync(join(tmpRoot, "schema/index.ts"), "utf8")
89
- expect(content).toContain("export type User")
90
+ expect(content).toContain("export type Profile")
91
+ expect(content).toContain("display_name")
90
92
  expect(content).toContain("Model<")
91
93
  expect(content).toContain("access:")
92
94
  })
93
95
 
94
96
  it(".gitignore excludes .env, node_modules, and engine binary", () => {
95
- scaffold(tmpRoot, "my-app")
97
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app"))
96
98
  const content = readFileSync(join(tmpRoot, ".gitignore"), "utf8")
97
99
  expect(content).toContain(".env")
98
100
  expect(content).toContain("node_modules/")
99
- expect(content).toContain(".supatype/engine/")
101
+ expect(content).toContain(".supatype/")
100
102
  expect(content).toContain("supatype.local.config.ts")
101
103
  })
102
104
 
103
105
  it("seed.ts references the project name", () => {
104
- scaffold(tmpRoot, "acme")
106
+ scaffold(tmpRoot, defaultScaffoldOptions("acme"))
105
107
  const content = readFileSync(join(tmpRoot, "seed.ts"), "utf8")
106
108
  expect(content).toContain("acme")
107
109
  })
108
110
 
109
111
  it("different project names produce different config bodies", () => {
110
- scaffold(tmpRoot, "alpha")
112
+ scaffold(tmpRoot, defaultScaffoldOptions("alpha"))
111
113
  const alpha = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
112
114
 
113
115
  const tmp2 = join(tmpdir(), `dt-init-test2-${Date.now()}`)
114
116
  mkdirSync(tmp2, { recursive: true })
115
117
  try {
116
- scaffold(tmp2, "beta")
118
+ scaffold(tmp2, defaultScaffoldOptions("beta"))
117
119
  const beta = readFileSync(join(tmp2, "supatype.config.ts"), "utf8")
118
120
  expect(alpha).toContain("alpha")
119
121
  expect(beta).toContain("beta")
@@ -123,4 +125,181 @@ describe("scaffold()", () => {
123
125
  rmSync(tmp2, { recursive: true, force: true })
124
126
  }
125
127
  })
128
+
129
+ it("self-host target emits standalone mode + domain and a local override", () => {
130
+ scaffold(tmpRoot, { ...defaultScaffoldOptions("my-app", "self-host"), domain: "api.example.com" })
131
+ const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
132
+ expect(content).toContain('mode: "standalone"')
133
+ expect(content).toContain('domain: "api.example.com"')
134
+ expect(content).toContain('environments: { default: "production" }')
135
+ expect(existsSync(join(tmpRoot, "supatype.local.config.ts"))).toBe(true)
136
+ const local = readFileSync(join(tmpRoot, "supatype.local.config.ts"), "utf8")
137
+ expect(local).toContain('mode: "dev"')
138
+ expect(local).toContain("Partial<SupatypeConfig>")
139
+ })
140
+
141
+ it("self-host with a TLS email emits an active tls block", () => {
142
+ scaffold(tmpRoot, {
143
+ ...defaultScaffoldOptions("my-app", "self-host"),
144
+ domain: "api.example.com",
145
+ tlsEmail: "ops@example.com",
146
+ })
147
+ const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
148
+ expect(content).toContain('tls: { email: "ops@example.com" }')
149
+ })
150
+
151
+ it("self-host without a TLS email emits a commented tls hint", () => {
152
+ scaffold(tmpRoot, { ...defaultScaffoldOptions("my-app", "self-host"), domain: "api.example.com" })
153
+ const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
154
+ expect(content).toContain('// tls: { email: "you@example.com" }')
155
+ })
156
+
157
+ it("cloud target emits managed mode + environments and a local override", () => {
158
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app", "cloud"))
159
+ const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
160
+ expect(content).toContain('mode: "managed"')
161
+ expect(content).toContain('environments: { default: "production" }')
162
+ expect(existsSync(join(tmpRoot, "supatype.local.config.ts"))).toBe(true)
163
+ })
164
+
165
+ it("later target stays in dev mode with no local override", () => {
166
+ scaffold(tmpRoot, defaultScaffoldOptions("my-app", "later"))
167
+ const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
168
+ expect(content).toContain('mode: "dev"')
169
+ expect(content).not.toContain("environments:")
170
+ expect(existsSync(join(tmpRoot, "supatype.local.config.ts"))).toBe(false)
171
+ })
172
+
173
+ it("static app mode writes the static dir and config block", () => {
174
+ scaffold(tmpRoot, {
175
+ ...defaultScaffoldOptions("my-app"),
176
+ app: { mode: "static", staticDir: "./dist" },
177
+ })
178
+ const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
179
+ expect(content).toContain('mode: "static"')
180
+ expect(content).toContain('static_dir: "./dist"')
181
+ expect(existsSync(join(tmpRoot, "dist/index.html"))).toBe(true)
182
+ const html = readFileSync(join(tmpRoot, "dist/index.html"), "utf8")
183
+ expect(html).toContain("Supatype")
184
+ expect(html).toContain("supatype.github.io/supatype/")
185
+ expect(html).toContain("github.com/supatype")
186
+ })
187
+
188
+ it("static app mode with Vite scaffolds root index.html and vite.config.ts", () => {
189
+ scaffold(tmpRoot, {
190
+ ...defaultScaffoldOptions("my-app"),
191
+ app: {
192
+ mode: "static",
193
+ staticDir: "./public",
194
+ viteDevUrl: "http://127.0.0.1:5173",
195
+ },
196
+ })
197
+ expect(existsSync(join(tmpRoot, "public/index.html"))).toBe(true)
198
+ expect(existsSync(join(tmpRoot, "index.html"))).toBe(true)
199
+ expect(existsSync(join(tmpRoot, "vite.config.ts"))).toBe(true)
200
+ const pkg = readFileSync(join(tmpRoot, "package.json"), "utf8")
201
+ expect(pkg).toContain('"vite": "vite"')
202
+ expect(pkg).toContain('"vite": "^6"')
203
+ const viteConfig = readFileSync(join(tmpRoot, "vite.config.ts"), "utf8")
204
+ expect(viteConfig).toContain("port: 5173")
205
+ })
206
+
207
+ it("proxy app mode writes upstream and start in the config when target is later", () => {
208
+ scaffold(tmpRoot, {
209
+ ...defaultScaffoldOptions("my-app"),
210
+ app: { mode: "proxy", upstream: "http://localhost:4000", start: "dev" },
211
+ })
212
+ const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
213
+ expect(content).toContain('mode: "proxy"')
214
+ expect(content).toContain('upstream: "http://localhost:4000"')
215
+ expect(content).toContain('start: "dev"')
216
+ expect(existsSync(join(tmpRoot, "supatype.local.config.ts"))).toBe(false)
217
+ })
218
+
219
+ it("proxy app mode with self-host splits static production config and local proxy override", () => {
220
+ scaffold(tmpRoot, {
221
+ ...defaultScaffoldOptions("my-app", "self-host"),
222
+ domain: "api.example.com",
223
+ app: {
224
+ mode: "proxy",
225
+ upstream: "http://127.0.0.1:5173",
226
+ start: "vite",
227
+ viteDevUrl: "http://127.0.0.1:5173",
228
+ },
229
+ })
230
+ const committed = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
231
+ expect(committed).toContain('mode: "static"')
232
+ expect(committed).toContain('static_dir: "./dist"')
233
+ expect(committed).not.toContain('mode: "proxy"')
234
+
235
+ const local = readFileSync(join(tmpRoot, "supatype.local.config.ts"), "utf8")
236
+ expect(local).toContain('mode: "dev"')
237
+ expect(local).toContain('mode: "proxy"')
238
+ expect(local).toContain('upstream: "http://127.0.0.1:5173"')
239
+ expect(local).toContain('start: "vite"')
240
+ expect(local).toContain('vite_dev_url: "http://127.0.0.1:5173"')
241
+ expect(existsSync(join(tmpRoot, "dist/index.html"))).toBe(true)
242
+ expect(existsSync(join(tmpRoot, "index.html"))).toBe(true)
243
+ expect(existsSync(join(tmpRoot, "vite.config.ts"))).toBe(true)
244
+ })
245
+
246
+ it("proxy app mode with cloud target splits static production config and local proxy override", () => {
247
+ scaffold(tmpRoot, {
248
+ ...defaultScaffoldOptions("my-app", "cloud"),
249
+ app: { mode: "proxy", upstream: "http://localhost:3000", start: "dev" },
250
+ })
251
+ const committed = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
252
+ expect(committed).toContain('mode: "static"')
253
+ expect(committed).not.toContain('mode: "proxy"')
254
+
255
+ const local = readFileSync(join(tmpRoot, "supatype.local.config.ts"), "utf8")
256
+ expect(local).toContain('mode: "proxy"')
257
+ expect(local).toContain('upstream: "http://localhost:3000"')
258
+ })
259
+
260
+ it("hello-world function scaffolds function files and a functions script", () => {
261
+ scaffold(tmpRoot, { ...defaultScaffoldOptions("my-app"), helloFunction: true })
262
+ expect(existsSync(join(tmpRoot, "functions/hello/index.ts"))).toBe(true)
263
+ expect(existsSync(join(tmpRoot, "functions/_shared/README.md"))).toBe(true)
264
+ expect(existsSync(join(tmpRoot, "functions/.env.local"))).toBe(true)
265
+ const pkg = readFileSync(join(tmpRoot, "package.json"), "utf8")
266
+ expect(pkg).toContain("supatype functions serve")
267
+ })
268
+
269
+ it("custom schema path is honored", () => {
270
+ scaffold(tmpRoot, { ...defaultScaffoldOptions("my-app"), schemaPath: "db/schema.ts" })
271
+ expect(existsSync(join(tmpRoot, "db/schema.ts"))).toBe(true)
272
+ const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
273
+ expect(content).toContain('path: "db/schema.ts"')
274
+ })
275
+
276
+ it("s3 storage and resend email reflect in config and .env", () => {
277
+ scaffold(tmpRoot, {
278
+ ...defaultScaffoldOptions("my-app"),
279
+ email: "resend",
280
+ storageLocal: "s3",
281
+ storageProduction: "s3",
282
+ })
283
+ const config = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
284
+ expect(config).toContain('email: { provider: "resend" }')
285
+ expect(config).toContain('storage: { provider: "s3" }')
286
+ const env = readFileSync(join(tmpRoot, ".env"), "utf8")
287
+ expect(env).toContain("RESEND_API_KEY=")
288
+ expect(env).toContain("S3_BUCKET=")
289
+ expect(env).toContain("local development and production")
290
+ })
291
+
292
+ it("mixed local dev and s3 production writes both storage sections", () => {
293
+ scaffold(tmpRoot, {
294
+ ...defaultScaffoldOptions("my-app"),
295
+ storageLocal: "local",
296
+ storageProduction: "s3",
297
+ })
298
+ const config = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
299
+ expect(config).toContain('provider: "local"')
300
+ expect(config).toContain("Production storage: external S3")
301
+ const env = readFileSync(join(tmpRoot, ".env"), "utf8")
302
+ expect(env).toContain("local development — MinIO")
303
+ expect(env).toContain("production — external bucket")
304
+ })
126
305
  })