@geekmidas/cli 10.0.0-alpha.72 → 10.0.0-alpha.74

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 (197) hide show
  1. package/dist/{OpenApiTsGenerator-BlP53kf2.cjs → EndpointGenerator-CP6sBvQV.cjs} +4 -510
  2. package/dist/EndpointGenerator-CP6sBvQV.cjs.map +1 -0
  3. package/dist/{OpenApiTsGenerator-Dv0gT2DL.mjs → EndpointGenerator-DftalLYg.mjs} +3 -509
  4. package/dist/EndpointGenerator-DftalLYg.mjs.map +1 -0
  5. package/dist/{LegacyStateStore-NSHEinQi.mjs → LegacyStateStore-DTo7z924.mjs} +2 -2
  6. package/dist/{LegacyStateStore-NSHEinQi.mjs.map → LegacyStateStore-DTo7z924.mjs.map} +1 -1
  7. package/dist/{LegacyStateStore-Dd5IwiyC.cjs → LegacyStateStore-D_78eaHM.cjs} +2 -2
  8. package/dist/{LegacyStateStore-Dd5IwiyC.cjs.map → LegacyStateStore-D_78eaHM.cjs.map} +1 -1
  9. package/dist/{LocalStateStore-DTylG4T4.cjs → LocalStateStore-BhrlQIXD.cjs} +2 -2
  10. package/dist/{LocalStateStore-DTylG4T4.cjs.map → LocalStateStore-BhrlQIXD.cjs.map} +1 -1
  11. package/dist/{LocalStateStore-DUoG2wA_.mjs → LocalStateStore-CNjgkRk4.mjs} +2 -2
  12. package/dist/{LocalStateStore-DUoG2wA_.mjs.map → LocalStateStore-CNjgkRk4.mjs.map} +1 -1
  13. package/dist/OpenApiTsGenerator-CKBCckKz.cjs +514 -0
  14. package/dist/OpenApiTsGenerator-CKBCckKz.cjs.map +1 -0
  15. package/dist/OpenApiTsGenerator-nu1r1NgB.mjs +509 -0
  16. package/dist/OpenApiTsGenerator-nu1r1NgB.mjs.map +1 -0
  17. package/dist/{S3StateStore-Dxob3PNA.mjs → S3StateStore-g2P7T_Mo.mjs} +2 -2
  18. package/dist/{S3StateStore-Dxob3PNA.mjs.map → S3StateStore-g2P7T_Mo.mjs.map} +1 -1
  19. package/dist/{S3StateStore-D7hrU-sy.cjs → S3StateStore-oCxRs6HM.cjs} +2 -2
  20. package/dist/{S3StateStore-D7hrU-sy.cjs.map → S3StateStore-oCxRs6HM.cjs.map} +1 -1
  21. package/dist/{SSMStateStore-Dvl4A8Up.mjs → SSMStateStore-CYenunmK.mjs} +2 -2
  22. package/dist/{SSMStateStore-Dvl4A8Up.mjs.map → SSMStateStore-CYenunmK.mjs.map} +1 -1
  23. package/dist/{SSMStateStore-DGIKJ4NN.cjs → SSMStateStore-DXp0m7XI.cjs} +2 -2
  24. package/dist/{SSMStateStore-DGIKJ4NN.cjs.map → SSMStateStore-DXp0m7XI.cjs.map} +1 -1
  25. package/dist/SubscriberGenerator-5looQ3gB.mjs +448 -0
  26. package/dist/SubscriberGenerator-5looQ3gB.mjs.map +1 -0
  27. package/dist/SubscriberGenerator-CajwOa5W.cjs +489 -0
  28. package/dist/SubscriberGenerator-CajwOa5W.cjs.map +1 -0
  29. package/dist/{add-XTLU19_E.mjs → add-BTH7WaH3.mjs} +4 -4
  30. package/dist/{add-XTLU19_E.mjs.map → add-BTH7WaH3.mjs.map} +1 -1
  31. package/dist/{add-DeC2Kyyv.cjs → add-Djrr12Xc.cjs} +4 -4
  32. package/dist/{add-DeC2Kyyv.cjs.map → add-Djrr12Xc.cjs.map} +1 -1
  33. package/dist/{bundler-CNTVLCCA.mjs → bundler-BUojOkof.mjs} +6 -6
  34. package/dist/bundler-BUojOkof.mjs.map +1 -0
  35. package/dist/{bundler-CEaR6Dpb.cjs → bundler-utHuESvL.cjs} +6 -6
  36. package/dist/bundler-utHuESvL.cjs.map +1 -0
  37. package/dist/{cli-MMgBrHx7.cjs → cli-Bx0sUzxH.cjs} +4 -4
  38. package/dist/{cli-MMgBrHx7.cjs.map → cli-Bx0sUzxH.cjs.map} +1 -1
  39. package/dist/{cli-D1arS0p1.mjs → cli-CyYceKTS.mjs} +4 -4
  40. package/dist/{cli-D1arS0p1.mjs.map → cli-CyYceKTS.mjs.map} +1 -1
  41. package/dist/{compose-T3hnnjKZ.mjs → compose-DZp8DJ0A.mjs} +419 -70
  42. package/dist/compose-DZp8DJ0A.mjs.map +1 -0
  43. package/dist/{compose-B-MME-4C.cjs → compose-iuw8wfHk.cjs} +420 -71
  44. package/dist/compose-iuw8wfHk.cjs.map +1 -0
  45. package/dist/{config-j9zBKaEG.d.cts → config-B38YWicj.d.cts} +3 -3
  46. package/dist/{config-BN6iXvyR.d.mts.map → config-B38YWicj.d.cts.map} +1 -1
  47. package/dist/{config-BN6iXvyR.d.mts → config-DA0kRTQe.d.mts} +3 -3
  48. package/dist/{config-j9zBKaEG.d.cts.map → config-DA0kRTQe.d.mts.map} +1 -1
  49. package/dist/config.cjs +4 -1
  50. package/dist/config.cjs.map +1 -1
  51. package/dist/config.d.cts +3 -3
  52. package/dist/config.d.mts +3 -3
  53. package/dist/config.mjs +4 -1
  54. package/dist/config.mjs.map +1 -1
  55. package/dist/{credentialSchemas-BmYxnEz_.mjs → credentialSchemas-BJqRJNK4.mjs} +2 -2
  56. package/dist/{credentialSchemas-BmYxnEz_.mjs.map → credentialSchemas-BJqRJNK4.mjs.map} +1 -1
  57. package/dist/{credentialSchemas-BcNL1N5R.cjs → credentialSchemas-DCR4Wd1W.cjs} +2 -2
  58. package/dist/{credentialSchemas-BcNL1N5R.cjs.map → credentialSchemas-DCR4Wd1W.cjs.map} +1 -1
  59. package/dist/deploy/public.cjs +7 -7
  60. package/dist/deploy/public.d.cts +4 -4
  61. package/dist/deploy/public.d.cts.map +1 -1
  62. package/dist/deploy/public.d.mts +4 -4
  63. package/dist/deploy/public.d.mts.map +1 -1
  64. package/dist/deploy/public.mjs +7 -7
  65. package/dist/deploy/sniffer-routes-worker.cjs +2 -2
  66. package/dist/deploy/sniffer-routes-worker.mjs +2 -2
  67. package/dist/{deploy-ClC53rc6.mjs → deploy-C17K7Imy.mjs} +12 -6
  68. package/dist/deploy-C17K7Imy.mjs.map +1 -0
  69. package/dist/{deploy-CZE0m6jd.cjs → deploy-eWorhvQp.cjs} +12 -6
  70. package/dist/deploy-eWorhvQp.cjs.map +1 -0
  71. package/dist/{discover-Buwf1z4P.mjs → discover-BPelzeGW.mjs} +21 -2
  72. package/dist/discover-BPelzeGW.mjs.map +1 -0
  73. package/dist/{discover-B_IkPukx.d.cts → discover-CU0paUyH.d.cts} +10 -1
  74. package/dist/discover-CU0paUyH.d.cts.map +1 -0
  75. package/dist/{discover-B4VjaFJk.cjs → discover-D1It5r_h.cjs} +21 -2
  76. package/dist/discover-D1It5r_h.cjs.map +1 -0
  77. package/dist/{discover-B6yPpvRV.d.mts → discover-DFiJxwfv.d.mts} +10 -1
  78. package/dist/discover-DFiJxwfv.d.mts.map +1 -0
  79. package/dist/{dokploy-D1UwQKm3.mjs → dokploy-DKa49Nqm.mjs} +2 -2
  80. package/dist/{dokploy-D1UwQKm3.mjs.map → dokploy-DKa49Nqm.mjs.map} +1 -1
  81. package/dist/{dokploy-lwmH05zr.cjs → dokploy-DKzcI-RL.cjs} +2 -2
  82. package/dist/{dokploy-lwmH05zr.cjs.map → dokploy-DKzcI-RL.cjs.map} +1 -1
  83. package/dist/drivers-BdvtCKc7.cjs +200 -0
  84. package/dist/drivers-BdvtCKc7.cjs.map +1 -0
  85. package/dist/drivers-Ch1rJitn.mjs +177 -0
  86. package/dist/drivers-Ch1rJitn.mjs.map +1 -0
  87. package/dist/{encryption-D05sNwED.cjs → encryption-DSkJdoyk.cjs} +2 -2
  88. package/dist/{encryption-D05sNwED.cjs.map → encryption-DSkJdoyk.cjs.map} +1 -1
  89. package/dist/{encryption-DcrxWl1s.mjs → encryption-sTMz3aWi.mjs} +2 -2
  90. package/dist/{encryption-DcrxWl1s.mjs.map → encryption-sTMz3aWi.mjs.map} +1 -1
  91. package/dist/{engine-DmQS3XHX.mjs → engine-B1b4I34Z.mjs} +117 -17
  92. package/dist/engine-B1b4I34Z.mjs.map +1 -0
  93. package/dist/{engine-Cry1vwMz.cjs → engine-Bh76sAHy.cjs} +117 -17
  94. package/dist/engine-Bh76sAHy.cjs.map +1 -0
  95. package/dist/{github-BchWoiVx.cjs → github-BvN3CfxM.cjs} +2 -2
  96. package/dist/{github-BchWoiVx.cjs.map → github-BvN3CfxM.cjs.map} +1 -1
  97. package/dist/{github-CqYOP8cI.mjs → github-C9ODoyZU.mjs} +2 -2
  98. package/dist/{github-CqYOP8cI.mjs.map → github-C9ODoyZU.mjs.map} +1 -1
  99. package/dist/{index-DvoxuTqR.d.mts → index-B-1lLGzy.d.mts} +2 -2
  100. package/dist/{index-Bk3NY6DN.d.cts.map → index-B-1lLGzy.d.mts.map} +1 -1
  101. package/dist/{index-Bk3NY6DN.d.cts → index-vMidfqhU.d.cts} +2 -2
  102. package/dist/{index-DvoxuTqR.d.mts.map → index-vMidfqhU.d.cts.map} +1 -1
  103. package/dist/index.cjs +130 -473
  104. package/dist/index.cjs.map +1 -1
  105. package/dist/index.mjs +122 -465
  106. package/dist/index.mjs.map +1 -1
  107. package/dist/{migrate-qKeGYq21.cjs → migrate-B0FCZNkp.cjs} +3 -3
  108. package/dist/{migrate-qKeGYq21.cjs.map → migrate-B0FCZNkp.cjs.map} +1 -1
  109. package/dist/{migrate-guck0NJK.mjs → migrate-CTtJhGV9.mjs} +3 -3
  110. package/dist/{migrate-guck0NJK.mjs.map → migrate-CTtJhGV9.mjs.map} +1 -1
  111. package/dist/openapi.cjs +3 -2
  112. package/dist/openapi.cjs.map +1 -1
  113. package/dist/openapi.d.cts +1 -1
  114. package/dist/openapi.d.mts +1 -1
  115. package/dist/openapi.mjs +2 -1
  116. package/dist/openapi.mjs.map +1 -1
  117. package/dist/ready-BB9xS4S9.cjs +402 -0
  118. package/dist/ready-BB9xS4S9.cjs.map +1 -0
  119. package/dist/ready-ZZoNskrr.mjs +373 -0
  120. package/dist/ready-ZZoNskrr.mjs.map +1 -0
  121. package/dist/reconcile/public.cjs +1 -1
  122. package/dist/reconcile/public.d.cts +2 -2
  123. package/dist/reconcile/public.d.mts +2 -2
  124. package/dist/reconcile/public.mjs +1 -1
  125. package/dist/{resolve-BsXEXAUz.mjs → resolve-DSAXj_47.mjs} +3 -3
  126. package/dist/{resolve-BsXEXAUz.mjs.map → resolve-DSAXj_47.mjs.map} +1 -1
  127. package/dist/{resolve-wEjEvbJ5.cjs → resolve-D_BU-wKA.cjs} +3 -3
  128. package/dist/{resolve-wEjEvbJ5.cjs.map → resolve-D_BU-wKA.cjs.map} +1 -1
  129. package/dist/{rollback-Bk91csFg.mjs → rollback-CWYTUhfW.mjs} +4 -4
  130. package/dist/{rollback-Bk91csFg.mjs.map → rollback-CWYTUhfW.mjs.map} +1 -1
  131. package/dist/{rollback-DmcU-efZ.cjs → rollback-DJy3t696.cjs} +4 -4
  132. package/dist/{rollback-DmcU-efZ.cjs.map → rollback-DJy3t696.cjs.map} +1 -1
  133. package/dist/{runtime-DHJhe4QI.d.mts → runtime-BzZD5HHi.d.cts} +2 -2
  134. package/dist/{runtime-CYAO4iZG.d.cts.map → runtime-BzZD5HHi.d.cts.map} +1 -1
  135. package/dist/{runtime-CYAO4iZG.d.cts → runtime-Cm6J6L7n.d.mts} +2 -2
  136. package/dist/{runtime-DHJhe4QI.d.mts.map → runtime-Cm6J6L7n.d.mts.map} +1 -1
  137. package/dist/sandbox/credentials-worker.cjs +2 -2
  138. package/dist/sandbox/credentials-worker.mjs +2 -2
  139. package/dist/sandbox/discover-worker.cjs +6 -3
  140. package/dist/sandbox/discover-worker.cjs.map +1 -1
  141. package/dist/sandbox/discover-worker.mjs +6 -3
  142. package/dist/sandbox/discover-worker.mjs.map +1 -1
  143. package/dist/sandbox/migrate-worker.cjs +1 -1
  144. package/dist/sandbox/migrate-worker.mjs +1 -1
  145. package/dist/{sniffer-vAXsU_4G.mjs → sniffer-BFmdZpZ2.mjs} +6 -6
  146. package/dist/{sniffer-vAXsU_4G.mjs.map → sniffer-BFmdZpZ2.mjs.map} +1 -1
  147. package/dist/{sniffer-DpHpFZBD.cjs → sniffer-C-T7dSMN.cjs} +6 -6
  148. package/dist/{sniffer-DpHpFZBD.cjs.map → sniffer-C-T7dSMN.cjs.map} +1 -1
  149. package/dist/{state-CCuTJNRH.cjs → state-DCwRWyh-.cjs} +954 -29
  150. package/dist/state-DCwRWyh-.cjs.map +1 -0
  151. package/dist/{state-BJFherj1.mjs → state-DpkxSwzI.mjs} +889 -24
  152. package/dist/state-DpkxSwzI.mjs.map +1 -0
  153. package/dist/target/public.cjs +1 -1
  154. package/dist/target/public.d.cts +2 -2
  155. package/dist/target/public.d.mts +2 -2
  156. package/dist/target/public.mjs +1 -1
  157. package/dist/{terminal-CFO3xWoF.cjs → terminal-BL5E0xbR.cjs} +2 -2
  158. package/dist/{terminal-CFO3xWoF.cjs.map → terminal-BL5E0xbR.cjs.map} +1 -1
  159. package/dist/{terminal-C6TBo_SA.mjs → terminal-BdvrmkaC.mjs} +2 -2
  160. package/dist/{terminal-C6TBo_SA.mjs.map → terminal-BdvrmkaC.mjs.map} +1 -1
  161. package/dist/{types-DkG5WW8l.d.mts → types-Dp9lgx_S.d.mts} +22 -2
  162. package/dist/types-Dp9lgx_S.d.mts.map +1 -0
  163. package/dist/{types-BD1jgA85.d.cts → types-TzCChNrA.d.cts} +22 -2
  164. package/dist/types-TzCChNrA.d.cts.map +1 -0
  165. package/dist/vitest.cjs +1 -1
  166. package/dist/vitest.mjs +1 -1
  167. package/dist/workspace/index.d.cts +2 -2
  168. package/dist/workspace/index.d.mts +2 -2
  169. package/dist/workspace-C7NqCsQF.mjs.map +1 -1
  170. package/dist/workspace-vdrcRI-m.cjs.map +1 -1
  171. package/package.json +15 -15
  172. package/dist/OpenApiTsGenerator-BlP53kf2.cjs.map +0 -1
  173. package/dist/OpenApiTsGenerator-Dv0gT2DL.mjs.map +0 -1
  174. package/dist/bundler-CEaR6Dpb.cjs.map +0 -1
  175. package/dist/bundler-CNTVLCCA.mjs.map +0 -1
  176. package/dist/compose-B-MME-4C.cjs.map +0 -1
  177. package/dist/compose-T3hnnjKZ.mjs.map +0 -1
  178. package/dist/deploy-CZE0m6jd.cjs.map +0 -1
  179. package/dist/deploy-ClC53rc6.mjs.map +0 -1
  180. package/dist/discover-B4VjaFJk.cjs.map +0 -1
  181. package/dist/discover-B6yPpvRV.d.mts.map +0 -1
  182. package/dist/discover-B_IkPukx.d.cts.map +0 -1
  183. package/dist/discover-Buwf1z4P.mjs.map +0 -1
  184. package/dist/engine-Cry1vwMz.cjs.map +0 -1
  185. package/dist/engine-DmQS3XHX.mjs.map +0 -1
  186. package/dist/fakes-C9RiT6XG.mjs +0 -125
  187. package/dist/fakes-C9RiT6XG.mjs.map +0 -1
  188. package/dist/fakes-zW-jNAbQ.cjs +0 -148
  189. package/dist/fakes-zW-jNAbQ.cjs.map +0 -1
  190. package/dist/ready-B-7LayMF.cjs +0 -872
  191. package/dist/ready-B-7LayMF.cjs.map +0 -1
  192. package/dist/ready-ULc5BTOb.mjs +0 -819
  193. package/dist/ready-ULc5BTOb.mjs.map +0 -1
  194. package/dist/state-BJFherj1.mjs.map +0 -1
  195. package/dist/state-CCuTJNRH.cjs.map +0 -1
  196. package/dist/types-BD1jgA85.d.cts.map +0 -1
  197. package/dist/types-DkG5WW8l.d.mts.map +0 -1
@@ -1,872 +0,0 @@
1
- const require_discover = require("./discover-B4VjaFJk.cjs");
2
- const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-BlP53kf2.cjs");
3
- const require_fakes = require("./fakes-zW-jNAbQ.cjs");
4
- let node_path = require("node:path");
5
- let node_fs = require("node:fs");
6
- let node_fs_promises = require("node:fs/promises");
7
- let _geekmidas_manifest = require("@geekmidas/manifest");
8
- let node_url = require("node:url");
9
- let _geekmidas_constructs_subscribers = require("@geekmidas/constructs/subscribers");
10
- let _geekmidas_envkit = require("@geekmidas/envkit");
11
- let _geekmidas_services = require("@geekmidas/services");
12
- //#region src/generators/drivers.ts
13
- /**
14
- * Driver registration for generated entry points.
15
- *
16
- * A construct declares that it needs object storage and is handed one URL; the
17
- * scheme in that URL picks the driver that builds the client. Which drivers
18
- * exist is therefore the *entry point's* decision, not the construct's and not
19
- * the application's — an app that never talks to S3 should never resolve the AWS
20
- * SDK, and application code that registers a driver has named a provider in the
21
- * one layer this design keeps provider-free.
22
- *
23
- * So the generated entries register: `gkm dev`'s server for local development,
24
- * and each Lambda handler for its own target. Registration is an explicit call
25
- * rather than an import side effect, because a side-effecting module is exactly
26
- * what a bundler is entitled to drop.
27
- */
28
- /** The `s3://` driver, which serves MinIO locally and S3 deployed. */
29
- const S3 = {
30
- imports: `import { registerStorageDriver } from '@geekmidas/storage';\nimport { s3Driver } from '@geekmidas/storage/aws';`,
31
- setup: "registerStorageDriver(s3Driver);"
32
- };
33
- const NONE = {
34
- imports: "",
35
- setup: ""
36
- };
37
- /**
38
- * The cache driver for a backend — exactly one, never all three.
39
- *
40
- * Keyed off the *backend* rather than off a dependency, because unlike storage
41
- * the backend already says which driver is right: a project caching in Postgres
42
- * resolves `pg` and never `ioredis`, and one caching in Upstash resolves
43
- * neither. That is the whole reason the drivers live behind separate subpaths.
44
- *
45
- * Both HTTP schemes are registered for Upstash, because the local proxy speaks
46
- * `http://` and Upstash speaks `https://`, and an entry that registered one
47
- * would work in exactly one of the two places.
48
- */
49
- const CACHE_DRIVERS = {
50
- upstash: {
51
- imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { upstashCacheDriver, upstashInsecureCacheDriver } from '@geekmidas/cache/upstash';`,
52
- setup: "registerCacheDriver(upstashCacheDriver);\nregisterCacheDriver(upstashInsecureCacheDriver);"
53
- },
54
- elasticache: {
55
- imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { redisCacheDriver, redissCacheDriver } from '@geekmidas/cache/redis';`,
56
- setup: "registerCacheDriver(redisCacheDriver);\nregisterCacheDriver(redissCacheDriver);"
57
- },
58
- db: {
59
- imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { postgresCacheDriver } from '@geekmidas/cache/postgres';`,
60
- setup: "registerCacheDriver(postgresCacheDriver);"
61
- }
62
- };
63
- /**
64
- * The events drivers for a broker — only its own, so a bundle never resolves
65
- * another broker's client library.
66
- *
67
- * Keyed off the backend, as the cache is: the target already says which broker
68
- * carries the app's topics and queues. SNS registers SQS beside it, because on
69
- * AWS a topic is `sns://` and a queue is `sqs://`, and an entry registering one
70
- * would publish to topics and fail on the first queue.
71
- */
72
- const EVENTS_DRIVERS = {
73
- pgboss: {
74
- imports: `import { registerEventsDriver } from '@geekmidas/events';\nimport { pgbossEventsDriver } from '@geekmidas/events/pgboss';`,
75
- setup: "registerEventsDriver(pgbossEventsDriver);"
76
- },
77
- sns: {
78
- imports: `import { registerEventsDriver } from '@geekmidas/events';\nimport { snsEventsDriver } from '@geekmidas/events/sns';\nimport { sqsEventsDriver } from '@geekmidas/events/sqs';`,
79
- setup: "registerEventsDriver(snsEventsDriver);\nregisterEventsDriver(sqsEventsDriver);"
80
- },
81
- rabbitmq: {
82
- imports: `import { registerEventsDriver } from '@geekmidas/events';\nimport { rabbitmqEventsDriver } from '@geekmidas/events/rabbitmq';`,
83
- setup: "registerEventsDriver(rabbitmqEventsDriver);"
84
- }
85
- };
86
- /**
87
- * Everything a generated entry should register: object storage, the cache,
88
- * and the events broker.
89
- *
90
- * Merged here rather than threaded separately, because a generated file has one
91
- * import block and one setup block whatever fills them.
92
- */
93
- function driversFor(options) {
94
- const backends = listOf(options.cache);
95
- const brokers = listOf(options.events);
96
- const parts = [
97
- storageDriversFor(options.appRoot),
98
- ...[...new Set(backends)].map((backend) => CACHE_DRIVERS[backend] ?? CACHE_DRIVERS[require_fakes.DEFAULT_CACHE.aws]),
99
- ...[...new Set(brokers)].map((broker) => EVENTS_DRIVERS[broker])
100
- ].filter((part) => part.imports || part.setup);
101
- if (parts.length === 0) return NONE;
102
- return {
103
- imports: parts.map((part) => part.imports).join("\n"),
104
- setup: parts.map((part) => part.setup).join("\n")
105
- };
106
- }
107
- function listOf(value) {
108
- if (value === false || value === void 0) return [];
109
- return typeof value === "string" ? [value] : value;
110
- }
111
- /**
112
- * Which events brokers an app's entry has to speak: the target's, when the app
113
- * declares a `Topic` or a `Queue` — and none at all when it declares neither,
114
- * so a project without events never resolves `@geekmidas/events`.
115
- *
116
- * A worker on pg-boss counts too: a server schedules its crons through the
117
- * pg-boss broker, which the plan provisions for a declared worker beside a
118
- * declared database (`workerBroker` in `reconcile/plan.ts`) whether or not
119
- * anything publishes.
120
- */
121
- function eventsBackendsIn(manifest, configured) {
122
- const kinds = new Set(Object.values(manifest).map((d) => d.kind));
123
- return kinds.has("topic") || kinds.has("queue") || configured === "pgboss" && kinds.has("worker") && kinds.has("database") ? [configured] : [];
124
- }
125
- /**
126
- * Which cache backends an app's entry actually has to speak.
127
- *
128
- * The config answers this for a cache that named nowhere, and the *declaration*
129
- * answers it for one that named a database — `orders.cache('Sessions')` is a
130
- * statement about the application, so a deployment cannot move that cache and
131
- * the entry cannot register a driver for somewhere else.
132
- *
133
- * Reading only the config is how an entry ends up registering the Upstash
134
- * driver for a URL the target composed as `postgres://`, which fails at the
135
- * first request with `UnregisteredCacheScheme` and a stack that points at the
136
- * cache rather than at the config that disagreed.
137
- */
138
- function cacheBackendsIn(manifest, configured) {
139
- const caches = Object.values(manifest).filter((d) => d.kind === "cache");
140
- return [...new Set(caches.map((c) => c.of ? "db" : configured))];
141
- }
142
- /**
143
- * The drivers an app's entry points should register.
144
- *
145
- * Keyed off the dependency rather than off a declared bucket, because the entry
146
- * is generated before discovery has run on the watcher's rebuild path — and an
147
- * app that installed `@geekmidas/storage` has already paid for the SDK it would
148
- * otherwise resolve lazily.
149
- *
150
- * "Installed" the way Node resolves it: the app's own `package.json`, or any
151
- * directory above it. A workspace that lists `@geekmidas/storage` once, at its
152
- * root, is the common case — and reading only the app's file registered no S3
153
- * driver there, so the first service to inject a bucket threw
154
- * `UnregisteredStorageScheme`. A queue whose consumer depended on one logged
155
- * that once and was never polled.
156
- */
157
- function storageDriversFor(appRoot) {
158
- return dependsOnStorage(appRoot) ? S3 : NONE;
159
- }
160
- function dependsOnStorage(appRoot) {
161
- for (let dir = (0, node_path.resolve)(appRoot);; dir = (0, node_path.dirname)(dir)) {
162
- if (listsStorage((0, node_path.join)(dir, "package.json"))) return true;
163
- if ((0, node_path.dirname)(dir) === dir) return false;
164
- }
165
- }
166
- function listsStorage(manifest) {
167
- if (!(0, node_fs.existsSync)(manifest)) return false;
168
- try {
169
- const pkg = JSON.parse((0, node_fs.readFileSync)(manifest, "utf8"));
170
- return Boolean(pkg.dependencies?.["@geekmidas/storage"] ?? pkg.devDependencies?.["@geekmidas/storage"]);
171
- } catch {
172
- return false;
173
- }
174
- }
175
- //#endregion
176
- //#region src/generators/SubscriberGenerator.ts
177
- var SubscriberGenerator = class extends require_discover.ConstructGenerator {
178
- isConstruct(value) {
179
- return _geekmidas_constructs_subscribers.Subscriber.isSubscriber(value);
180
- }
181
- async build(context, constructs, outputDir, options) {
182
- const target = options?.target ?? "aws";
183
- const logger = console;
184
- const subscriberInfos = [];
185
- if (target === "server") {
186
- await this.generateServerSubscribersFile(outputDir, constructs, context.eventsBackends?.includes("sns") ?? false);
187
- logger.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
188
- return subscriberInfos;
189
- }
190
- if (constructs.length === 0) return subscriberInfos;
191
- const subscribersDir = (0, node_path.join)(outputDir, "subscribers");
192
- await (0, node_fs_promises.mkdir)(subscribersDir, { recursive: true });
193
- for (const { key, construct, path } of constructs) {
194
- const handlerFile = await this.generateSubscriberHandler(subscribersDir, path.relative, key, construct, context, construct.owner);
195
- subscriberInfos.push({
196
- name: key,
197
- handler: (0, node_path.relative)(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
198
- subscribedEvents: construct.subscribedEvents || [],
199
- ...construct.topicName ? {
200
- transport: "topic",
201
- topic: construct.topicName
202
- } : {},
203
- timeout: construct.timeout,
204
- memorySize: construct.memorySize,
205
- environment: await construct.getEnvironment({ markOptional: context.markOptional }),
206
- dependencies: construct.constructs
207
- });
208
- logger.log(`Generated subscriber handler: ${key}`);
209
- }
210
- return subscriberInfos;
211
- }
212
- async generateSubscriberHandler(outputDir, sourceFile, exportName, _subscriber, context, owner) {
213
- const handlerFileName = `${exportName}.ts`;
214
- const handlerPath = (0, node_path.join)(outputDir, handlerFileName);
215
- const importPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile).replace(/\.ts$/, ".js");
216
- const runtime = require_OpenApiTsGenerator.runtimeFor(context, (0, node_path.dirname)(handlerPath), owner);
217
- const content = `import { AWSLambdaSubscriber } from '@geekmidas/constructs/aws';
218
- import { ${exportName} } from '${importPath}';
219
- ${runtime.imports}
220
- ${context.storageDrivers?.imports ?? ""}
221
- ${runtime.bindings}
222
- ${context.storageDrivers?.setup ? `\n// The handler registers the drivers its target needs.\n${context.storageDrivers.setup}\n` : ""}
223
- const adapter = new AWSLambdaSubscriber(envParser, ${exportName});
224
-
225
- export const handler = adapter.handler;
226
- `;
227
- await (0, node_fs_promises.writeFile)(handlerPath, content);
228
- return handlerPath;
229
- }
230
- async generateServerSubscribersFile(outputDir, subscribers, push) {
231
- await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
232
- const subscribersPath = (0, node_path.join)(outputDir, "subscribers.ts");
233
- if (subscribers.length === 0) {
234
- await (0, node_fs_promises.writeFile)(subscribersPath, `/**
235
- * Generated subscribers setup — this app declares none.
236
- */
237
- import type { EnvironmentParser } from '@geekmidas/envkit';
238
- import type { Logger } from '@geekmidas/logger';
239
-
240
- export async function setupSubscribers(
241
- _app: unknown,
242
- _envParser: EnvironmentParser<any>,
243
- _logger: Logger,
244
- ): Promise<(port: number) => Promise<void>> {
245
- return async () => {};
246
- }
247
- `);
248
- return subscribersPath;
249
- }
250
- const importsByFile = /* @__PURE__ */ new Map();
251
- for (const { path, key } of subscribers) {
252
- const importPath = (0, node_path.relative)((0, node_path.dirname)(subscribersPath), path.relative).replace(/\.ts$/, ".js");
253
- importsByFile.set(importPath, [...importsByFile.get(importPath) ?? [], key]);
254
- }
255
- const content = `/**
256
- * Generated subscribers setup.
257
- *
258
- * Each subscriber is run against the topic it is bound to, through that
259
- * topic's own connection string:
260
- *
261
- * - **sns://** — pushed, not polled. The subscriber gets an HTTP route on this
262
- * server, and the route is subscribed to the topic with a filter policy of
263
- * the events it named, so SNS fans out to each subscriber. The route runs the
264
- * subscriber through the same adaptor a Lambda subscription does.
265
- * - **anything else** (pgboss://, rabbitmq://, sqs://) — polled. On pg-boss
266
- * each subscriber drains a queue of its own, \`<topic>/<subscriber>\`, so
267
- * every subscriber sees every event; replicas of one subscriber share it.
268
- *
269
- * GKM_SUBSCRIBERS=off (\`gkm dev --no-subscribers\`) runs none of them.
270
- * GKM_SUBSCRIBER_PUSH_URL is the address SNS pushes to; against the local
271
- * emulator it defaults to this server through host.docker.internal.
272
- */
273
- import { connect } from 'node:net';
274
- import type { EnvironmentParser } from '@geekmidas/envkit';
275
- import type { Logger } from '@geekmidas/logger';
276
- import { EventConnectionFactory, Subscriber } from '@geekmidas/events';
277
- import type { EventConnection } from '@geekmidas/events';
278
- import { ServiceDiscovery } from '@geekmidas/services';
279
- ${Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n")}
280
-
281
- const subscribers = [
282
- ${subscribers.map(({ key, construct }) => {
283
- const connectionKey = construct.topicName ? (0, _geekmidas_manifest.provideKey)((0, _geekmidas_manifest.canonicalId)(construct.topicName), "publisherConnectionString") : void 0;
284
- return ` { id: '${key}', subscriber: ${key}, topic: ${construct.topicName ? `'${construct.topicName}'` : "undefined"}, connectionKey: ${connectionKey ? `'${connectionKey}'` : "undefined"} },`;
285
- }).join("\n")}
286
- ];
287
-
288
- /** The route a subscriber's pushes arrive on. */
289
- export const SUBSCRIBER_ROUTE = (id: string) => \`/__gkm/subscribers/\${id}\`;
290
-
291
- /** Resolves once something is accepting connections on the port. */
292
- async function listening(port: number): Promise<void> {
293
- for (let attempt = 0; attempt < 100; attempt++) {
294
- const open = await new Promise<boolean>((resolve) => {
295
- const socket = connect(port, '127.0.0.1');
296
- socket.once('connect', () => { socket.end(); resolve(true); });
297
- socket.once('error', () => resolve(false));
298
- });
299
- if (open) return;
300
- await new Promise((resolve) => setTimeout(resolve, 50));
301
- }
302
- }
303
-
304
- /**
305
- * Mount each pushed subscriber's route and start each polled one. Returns what
306
- * to run once the server is listening: subscribing a route makes SNS send its
307
- * confirmation at once, so the route has to be reachable first.
308
- */
309
- export async function setupSubscribers(
310
- app: { post: (path: string, handler: (c: any) => unknown) => unknown },
311
- envParser: EnvironmentParser<any>,
312
- logger: Logger,
313
- ): Promise<(port: number) => Promise<void>> {
314
- if (process.env.GKM_SUBSCRIBERS === 'off') {
315
- logger.info('Subscribers are off (--no-subscribers)');
316
- return async () => {};
317
- }
318
-
319
- const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
320
- const connections = new Map<string, EventConnection>();
321
- const afterListening: ((port: number) => Promise<void>)[] = [];
322
-
323
- for (const { id, subscriber, topic, connectionKey } of subscribers) {
324
- const events = subscriber.subscribedEvents || [];
325
- if (events.length === 0) {
326
- logger.warn({ subscriber: id }, 'Subscriber has no subscribed events, skipping');
327
- continue;
328
- }
329
- const connectionString = connectionKey ? process.env[connectionKey] : undefined;
330
- if (!connectionString) {
331
- logger.error(
332
- { subscriber: id, connectionKey },
333
- 'No connection string for the topic this subscriber is bound to',
334
- );
335
- continue;
336
- }
337
-
338
- try {
339
- ${push ? ` if (connectionString.startsWith('sns:')) {
340
- const { SNSConnection, snsUrl, subscribeHttpEndpoint } = await import('@geekmidas/events/sns');
341
- const { SnsPushSubscriberAdaptor } = await import('@geekmidas/constructs/subscribers');
342
- const address = snsUrl.parse(connectionString);
343
- const route = SUBSCRIBER_ROUTE(id);
344
- const adaptor = new SnsPushSubscriberAdaptor(envParser, subscriber, {
345
- topicArn: address.topicArn,
346
- // An emulator signs nothing. Decided by the address this server was
347
- // configured with, never by anything a request says about itself.
348
- verify: !address.endpoint,
349
- ...(address.endpoint ? { emulatorEndpoint: address.endpoint } : {}),
350
- });
351
-
352
- app.post(route, async (c: any) => {
353
- const body = JSON.parse(await c.req.text());
354
- const response = await adaptor.handle(body);
355
- return c.json(response.body, response.status);
356
- });
357
-
358
- afterListening.push(async (port) => {
359
- const base =
360
- process.env.GKM_SUBSCRIBER_PUSH_URL ??
361
- (address.endpoint ? \`http://host.docker.internal:\${port}\` : undefined);
362
- if (!base) {
363
- logger.error(
364
- { subscriber: id },
365
- 'Set GKM_SUBSCRIBER_PUSH_URL to the public URL of this worker, which SNS pushes to',
366
- );
367
- return;
368
- }
369
- const connection = await SNSConnection.fromConnectionString(connectionString);
370
- await subscribeHttpEndpoint(connection, {
371
- endpoint: new URL(route, base).toString(),
372
- events,
373
- });
374
- logger.info({ subscriber: id, events }, 'Subscriber subscribed for push');
375
- });
376
- continue;
377
- }
378
- ` : ` if (connectionString.startsWith('sns:')) {
379
- // Built for a broker other than SNS, so this entry registered no SNS
380
- // driver and has no push route to offer.
381
- logger.error(
382
- { subscriber: id, connectionKey },
383
- 'This build has no SNS driver: it was built for a target whose broker is not SNS',
384
- );
385
- continue;
386
- }
387
- `}
388
-
389
- let connection = connections.get(connectionString);
390
- if (!connection) {
391
- connection = await EventConnectionFactory.fromConnectionString(connectionString);
392
- connections.set(connectionString, connection);
393
- }
394
- // Named, so a broker with one address for everything (pg-boss) gives
395
- // this subscriber a queue of its own: fan-out, not competition.
396
- const eventSubscriber = await Subscriber.fromConnection(connection, {
397
- topic,
398
- subscription: id,
399
- });
400
- const services = subscriber.services.length > 0
401
- ? await serviceDiscovery.register(subscriber.services)
402
- : {};
403
- // The subscriber's database — the worker's, or its own — as \`db\`. A
404
- // pushed subscriber gets it from the adaptor above instead.
405
- const db = subscriber.databaseService
406
- ? (await serviceDiscovery.register([subscriber.databaseService]))[
407
- subscriber.databaseService.serviceName
408
- ]
409
- : undefined;
410
-
411
- await eventSubscriber.subscribe(events, async (event) => {
412
- try {
413
- await subscriber.handler({
414
- events: [event],
415
- services: services as any,
416
- logger: subscriber.logger,
417
- db,
418
- } as any);
419
- } catch (error) {
420
- logger.error({ err: error, event, subscriber: id }, 'Failed to process event');
421
- // Rethrown so the transport keeps the event for a retry — as a queue
422
- // consumer's failure does, and as a pushed subscriber's 500 does.
423
- throw error;
424
- }
425
- });
426
- logger.info({ subscriber: id, events }, 'Subscriber started polling');
427
- } catch (error) {
428
- // The message too: a missing module's code alone does not say which.
429
- logger.error(
430
- { err: error, subscriber: id },
431
- 'Failed to set up subscriber',
432
- );
433
- }
434
- }
435
-
436
- const shutdown = () => {
437
- for (const connection of connections.values()) void connection.close();
438
- };
439
- process.once('SIGTERM', shutdown);
440
- process.once('SIGINT', shutdown);
441
-
442
- return async (port) => {
443
- if (afterListening.length === 0) return;
444
- await listening(port);
445
- for (const subscribe of afterListening) {
446
- await subscribe(port).catch((error) => {
447
- logger.error({ err: error }, 'Failed to subscribe for push');
448
- });
449
- }
450
- };
451
- }
452
- `;
453
- await (0, node_fs_promises.writeFile)(subscribersPath, content);
454
- return subscribersPath;
455
- }
456
- };
457
- //#endregion
458
- //#region src/test/harness.ts
459
- /**
460
- * What `gkm test` writes for a feature test to be built from.
461
- *
462
- * `gkm test` already discovers an app's constructs and resolves the test
463
- * stage's environment; this keeps both, under `.gkm/test/`:
464
- *
465
- * - `manifest.json` — which construct and endpoint is exported where, and the
466
- * environment the test stage resolved, keyed as the constructs derive keys.
467
- * - `clients/<surface>.ts` — each surface's typed client, from the same
468
- * generator `gkm build` uses for the app's own.
469
- * - `index.ts` — the configured `it`, and a `Browser` holding one client per
470
- * surface and one per auth server, each on the browser's `fetch`.
471
- *
472
- * A test imports `it` from there and declares nothing — not a construct, not an
473
- * environment key, not a client.
474
- */
475
- /**
476
- * The variable the manifest's path travels in — what
477
- * `@geekmidas/constructs/testing` reads. Its own constant rather than an import:
478
- * that entry carries the whole harness (MSW, Hono, Vitest), and importing it
479
- * here put all of it on the start-up path of every `gkm` command.
480
- */
481
- const TEST_MANIFEST_ENV = "GKM_TEST_MANIFEST";
482
- /** Where the harness lives, relative to the workspace root. */
483
- const TEST_HARNESS_DIR = ".gkm/test";
484
- /**
485
- * Better-auth's client plugin for each server plugin id.
486
- *
487
- * Written down rather than derived: the ids and the client names do not follow
488
- * one rule (`admin` pairs with `adminClient`, `email-otp` with
489
- * `emailOTPClient`), and a guess that missed would hand a test a client
490
- * silently lacking every method of that plugin. A server plugin not listed has
491
- * no client counterpart — `bearer`, `open-api` — or needs one from another
492
- * package, and is left out.
493
- */
494
- const CLIENT_PLUGINS = {
495
- admin: "adminClient",
496
- anonymous: "anonymousClient",
497
- "device-authorization": "deviceAuthorizationClient",
498
- "email-otp": "emailOTPClient",
499
- jwt: "jwtClient",
500
- "last-login-method": "lastLoginMethodClient",
501
- "magic-link": "magicLinkClient",
502
- "multi-session": "multiSessionClient",
503
- "oauth-popup": "oauthPopupClient",
504
- "one-tap": "oneTapClient",
505
- "one-time-token": "oneTimeTokenClient",
506
- organization: "organizationClient",
507
- "phone-number": "phoneNumberClient",
508
- siwe: "siweClient",
509
- "two-factor": "twoFactorClient",
510
- username: "usernameClient"
511
- };
512
- /** Where a project keeps its test factories, relative to its root. */
513
- const DEFAULT_FACTORIES_DIR = "test/factories";
514
- /** A file in the factories folder that names no database construct. */
515
- var UnknownFactoryFile = class extends Error {
516
- file;
517
- known;
518
- constructor(file, known) {
519
- super(`${file} names no database construct, so no test could be handed it. ` + (known.length > 0 ? `The databases: ${known.join(", ")}. ` : "This project declares no database. ") + `Name a factory after its database: test/factories/<construct>.ts.`);
520
- this.file = file;
521
- this.known = known;
522
- this.name = "UnknownFactoryFile";
523
- }
524
- };
525
- /** A factory file that exports no `createFactory`. */
526
- var FactoryHasNoCreate = class extends Error {
527
- file;
528
- constructor(file) {
529
- super(`${file} is a test factory but exports no \`createFactory\`. Export \`function createFactory(db: Kysely<Database>)\` returning the factory.`);
530
- this.file = file;
531
- this.name = "FactoryHasNoCreate";
532
- }
533
- };
534
- /**
535
- * The factories in a folder, each matched to the database it is named after.
536
- *
537
- * A file that names no database, or exports no `createFactory`, is refused
538
- * rather than skipped: either is a factory no test would ever be handed.
539
- */
540
- async function factorySources(folder, databases) {
541
- if (!folder) return [];
542
- const entries = await (0, node_fs_promises.readdir)(folder, { withFileTypes: true }).catch((error) => {
543
- if (error.code === "ENOENT") return [];
544
- throw error;
545
- });
546
- const found = [];
547
- for (const entry of entries) {
548
- if (!entry.isFile() || !isFactoryFile(entry.name)) continue;
549
- const file = (0, node_path.join)(folder, entry.name);
550
- const id = (0, _geekmidas_manifest.canonicalId)(entry.name.slice(0, -(0, node_path.extname)(entry.name).length));
551
- if (!databases.includes(id)) throw new UnknownFactoryFile(file, databases);
552
- if (typeof (await import((0, node_url.pathToFileURL)(file).href)).createFactory !== "function") throw new FactoryHasNoCreate(file);
553
- found.push({
554
- file,
555
- service: (0, _geekmidas_manifest.serviceKey)(id)
556
- });
557
- }
558
- return found.sort((a, b) => a.service < b.service ? -1 : 1);
559
- }
560
- function isFactoryFile(file) {
561
- if (file.endsWith(".d.ts")) return false;
562
- if (/\.(spec|test)\.[cm]?[jt]s$/.test(file)) return false;
563
- return [
564
- ".ts",
565
- ".mts",
566
- ".js",
567
- ".mjs"
568
- ].includes((0, node_path.extname)(file));
569
- }
570
- /** Write the manifest, the clients and the harness; return the manifest path. */
571
- async function writeTestHarness(options) {
572
- const sources = {};
573
- const declared = await require_discover.discover({
574
- patterns: options.patterns,
575
- cwd: options.root,
576
- sources
577
- });
578
- const endpoints = (await new require_OpenApiTsGenerator.EndpointGenerator().load(options.patterns, options.root)).map(({ key, construct, path }) => ({
579
- surface: construct.owner ?? construct.surface?.id,
580
- construct,
581
- source: {
582
- file: path.absolute,
583
- export: key
584
- }
585
- })).filter((endpoint) => endpoint.surface !== void 0);
586
- const subscribers = (await new SubscriberGenerator().load(options.patterns, options.root)).map(({ key, path }) => ({ source: {
587
- file: path.absolute,
588
- export: key
589
- } }));
590
- const manifest = {
591
- stage: options.stage,
592
- constructs: Object.fromEntries(Object.entries(declared).flatMap(([id, declaration]) => {
593
- const source = sources[id];
594
- return source ? [[id, {
595
- kind: declaration.kind,
596
- source: {
597
- file: source.file,
598
- export: source.exportName
599
- }
600
- }]] : [];
601
- })),
602
- endpoints: endpoints.map(({ surface, source }) => ({
603
- surface,
604
- source
605
- })),
606
- subscribers,
607
- env: options.env
608
- };
609
- const surfaces = [...new Set(endpoints.map(({ surface }) => surface))];
610
- const generator = new require_OpenApiTsGenerator.OpenApiTsGenerator();
611
- const clients = await Promise.all(surfaces.map(async (surface) => ({
612
- file: `${(0, _geekmidas_manifest.kebabCase)(surface)}.ts`,
613
- content: await generator.generate(endpoints.filter((endpoint) => endpoint.surface === surface).map(({ construct }) => construct), {
614
- title: surface,
615
- version: "1.0.0"
616
- })
617
- })));
618
- const auths = await authClients(declared, sources, options.env);
619
- const drivers = driversFor({
620
- appRoot: options.root,
621
- cache: cacheBackendsIn(declared, options.cacheBackend),
622
- events: eventsBackendsIn(declared, options.eventsBackend)
623
- });
624
- const owners = new Set(auths.map((auth) => auth.databaseId));
625
- const databases = databaseSources(declared, sources).filter(({ id, kind }) => !owners.has(id) && kind !== "database-reader");
626
- const factories = await factorySources(options.factories, databases.map(({ id }) => id));
627
- const fakes = Object.entries(await require_fakes.readFakes(options.root, declared)).map(([id, { file }]) => ({
628
- id,
629
- file
630
- }));
631
- const services = serviceSources(declared, sources).filter(({ id }) => !owners.has(id));
632
- const files = [.../* @__PURE__ */ new Set([
633
- ...Object.values(manifest.constructs).map(({ source }) => source.file),
634
- ...manifest.endpoints.map(({ source }) => source.file),
635
- ...(manifest.subscribers ?? []).map(({ source }) => source.file)
636
- ])];
637
- const harnessFor = (dir) => harnessModule({
638
- dir,
639
- files,
640
- databases,
641
- surfaces: surfaces.map((id, index) => ({
642
- id,
643
- secured: clients[index].content.includes("authStrategies:")
644
- })),
645
- auths,
646
- drivers,
647
- factories,
648
- fakes,
649
- services,
650
- mail: Object.values(declared).some(({ kind }) => kind === "email")
651
- });
652
- const json = `${JSON.stringify(manifest, null, 2)}\n`;
653
- return (await Promise.all([...new Set(options.targets)].map(async (target) => {
654
- const dir = (0, node_path.join)(target, TEST_HARNESS_DIR);
655
- await (0, node_fs_promises.rm)(dir, {
656
- recursive: true,
657
- force: true
658
- });
659
- await (0, node_fs_promises.mkdir)((0, node_path.join)(dir, "clients"), { recursive: true });
660
- await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "manifest.json"), json);
661
- await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "index.ts"), harnessFor(dir));
662
- for (const client of clients) await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "clients", client.file), client.content);
663
- return (0, node_path.join)(dir, "manifest.json");
664
- })))[0];
665
- }
666
- /** Each auth server, with the client plugins its server plugins pair with. */
667
- async function authClients(declared, sources, env) {
668
- const clients = [];
669
- const envParser = new _geekmidas_envkit.EnvironmentParser({ ...env });
670
- for (const [id, declaration] of Object.entries(declared)) {
671
- const source = sources[id];
672
- if (declaration.kind !== "rest-api" || !source) continue;
673
- const exported = (await import(source.file))[source.exportName];
674
- if (!isBetterAuth(exported) || exported.id !== id) continue;
675
- const ids = await exported.pluginIds({
676
- envParser,
677
- context: _geekmidas_services.serviceContext
678
- });
679
- clients.push({
680
- id,
681
- basePath: exported.basePath,
682
- plugins: ids.flatMap((plugin) => CLIENT_PLUGINS[plugin] ?? []),
683
- databaseId: exported.databaseId
684
- });
685
- }
686
- return clients;
687
- }
688
- function isBetterAuth(value) {
689
- return typeof value === "object" && value !== null && "pluginIds" in value && "basePath" in value;
690
- }
691
- /** `Api` → `api`, `AdminApi` → `adminApi`: a construct id as a property name. */
692
- function propertyOf(id) {
693
- return id.charAt(0).toLowerCase() + id.slice(1);
694
- }
695
- /**
696
- * Every database construct the app declares — a database, a schema tenant, a
697
- * reader — keyed as a test is handed its transaction.
698
- */
699
- function databaseSources(declared, sources) {
700
- return Object.entries(declared).filter(([id, declaration]) => Boolean(sources[id] && declaration.kind.startsWith("database"))).map(([id, declaration]) => ({
701
- id,
702
- kind: declaration.kind,
703
- service: (0, _geekmidas_manifest.serviceKey)(id),
704
- source: sources[id]
705
- }));
706
- }
707
- /** The kinds whose construct hands a handler a client — what `services` reaches. */
708
- const SERVICE_KINDS = /* @__PURE__ */ new Set([
709
- "cache",
710
- "credential",
711
- "database",
712
- "database-schema",
713
- "database-reader",
714
- "email",
715
- "encryption",
716
- "external-api",
717
- "file-server",
718
- "objects",
719
- "queue",
720
- "topic"
721
- ]);
722
- function serviceSources(declared, sources) {
723
- return Object.entries(declared).filter(([id, declaration]) => Boolean(sources[id] && SERVICE_KINDS.has(declaration.kind))).map(([id, declaration]) => ({
724
- id,
725
- kind: declaration.kind,
726
- service: (0, _geekmidas_manifest.serviceKey)(id),
727
- source: sources[id]
728
- }));
729
- }
730
- /** An import specifier for `file`, from the harness written into `dir`. */
731
- function specifierFrom(dir, file) {
732
- const path = (0, node_path.relative)(dir, file).replace(/\.tsx?$/, ".js");
733
- return path.startsWith(".") ? path : `./${path}`;
734
- }
735
- function harnessModule(options) {
736
- const { surfaces, auths, drivers, databases, services, dir, files, factories, fakes, mail } = options;
737
- const magicLinks = mail ? auths.filter(({ plugins }) => plugins.includes("magicLinkClient")) : [];
738
- const signIn = magicLinks.length === 1 ? magicLinks[0] : void 0;
739
- const plugins = [...new Set(auths.flatMap(({ plugins }) => plugins))].sort();
740
- const imports = [
741
- `import {${services.length ? " type ClientOf," : ""}${databases.length ? " type DatabaseOf," : ""} featureTest, loadTestManifest${signIn ? ", signInWithMagicLink" : ""} } from '@geekmidas/constructs/testing';`,
742
- ...[...new Map([...databases, ...services].map((construct) => [construct.id, construct])).values()].map(({ id, source }) => `import type { ${source.exportName} as __${id} } from '${specifierFrom(dir, source.file)}';`),
743
- `import { Browser as TestBrowser } from '@geekmidas/testkit/browser';`,
744
- ...surfaces.map(({ id }) => `import { createApi as create${id} } from './clients/${(0, _geekmidas_manifest.kebabCase)(id)}.js';`),
745
- ...auths.length ? [`import { createAuthClient } from 'better-auth/client';`] : [],
746
- ...plugins.length ? [`import { ${plugins.join(", ")} } from 'better-auth/client/plugins';`] : [],
747
- ...drivers.imports ? [drivers.imports] : [],
748
- ...factories.map(({ file, service }) => `import { createFactory as __${service}Factory } from '${specifierFrom(dir, file)}';`),
749
- ...fakes.map(({ id, file }) => `import __${id}Fake from '${specifierFrom(dir, file)}';`),
750
- ...files.map((file, index) => `import * as __module${index} from '${specifierFrom(dir, file)}';`)
751
- ];
752
- const url = (id) => `manifest.env[${JSON.stringify((0, _geekmidas_manifest.provideKey)(id, "url"))}]!`;
753
- const members = [
754
- ...surfaces.map(({ id, secured }) => ` /** \`${id}\`, at ${(0, _geekmidas_manifest.provideKey)(id, "url")}. */
755
- readonly ${propertyOf(id)} = create${id}({
756
- baseURL: ${url(id)},
757
- fetch: this.fetch,${secured ? `
758
- // Served in-process: no authorizer runs, so nothing is signed. A scheme
759
- // with no strategy sends its request without credentials.
760
- authStrategies: {} as never,` : ""}
761
- });`),
762
- ...auths.map((auth) => ` /** \`${auth.id}\`, at ${(0, _geekmidas_manifest.provideKey)(auth.id, "url")}. */
763
- readonly ${propertyOf(auth.id)} = createAuthClient({
764
- baseURL: \`\${${url(auth.id)}}${auth.basePath}\`,
765
- plugins: [${auth.plugins.map((plugin) => `${plugin}()`).join(", ")}],
766
- fetchOptions: { customFetchImpl: this.fetch },
767
- });`),
768
- ...signIn ? [` /**
769
- * Sign in as \`email\` through \`${signIn.id}\`'s magic link, the way a person
770
- * does — the email opened, the link followed. The session it then reports.
771
- * Without an address, as somebody new: \`user.email\` says who.
772
- */
773
- signIn(email?: string) {
774
- return signInWithMagicLink(this, this.${propertyOf(signIn.id)}, email);
775
- }`] : []
776
- ];
777
- const factoriesType = factories.length ? `{ ${factories.map(({ service }) => `${service}: typeof __${service}Factory`).join("; ")} }` : "";
778
- const factoriesOption = factories.length ? `, factories: { ${factories.map(({ service }) => `${service}: __${service}Factory`).join(", ")} }` : "";
779
- const fakesOption = fakes.length ? `, fakes: { ${fakes.map(({ id }) => `${id}: __${id}Fake`).join(", ")} }` : "";
780
- const databasesType = `{ ${databases.map(({ id, service }) => `${service}: DatabaseOf<typeof __${id}>`).join("; ")} }`;
781
- const servicesType = services.length ? `{ ${services.map(({ id, service }) => `${service}: ClientOf<typeof __${id}>`).join("; ")} }` : "";
782
- const generics = databases.length || factoriesType || servicesType ? `<Browser, ${databases.length ? databasesType : "{}"}${factoriesType || servicesType ? `, ${factoriesType || "{}"}` : ""}${servicesType ? `, ${servicesType}` : ""}>` : "";
783
- return `// Generated by \`gkm test\` from the app's constructs — do not edit.
784
- ${imports.join("\n")}
785
-
786
- ${drivers.setup ? `// The drivers the app's server entry registers, registered the same way.\n${drivers.setup}\n\n` : ""}const manifest = loadTestManifest(new URL('./manifest.json', import.meta.url));
787
-
788
- /** A browser with a client for every surface the app declares. */
789
- export class Browser extends TestBrowser {
790
- ${members.join("\n\n")}
791
- }
792
-
793
- /** The app's modules, by the path the manifest records for each. */
794
- const modules = {
795
- ${files.map((file, index) => `\t${JSON.stringify(file)}: __module${index},`).join("\n")}
796
- };
797
-
798
- /** \`it\`, for a test that drives the app the way it runs deployed. */
799
- export const it = featureTest${generics}({ manifest, modules, browser: Browser${factoriesOption}${fakesOption} });
800
- `;
801
- }
802
- //#endregion
803
- //#region src/test/ready.ts
804
- /**
805
- * The hand-off between `gkm test` and the Vitest global setup.
806
- *
807
- * `gkm test` reconciles and migrates the test stage before Vitest starts; the
808
- * setup, run inside Vitest, only has to load what that left. Started by plain
809
- * `vitest` instead, the setup runs `gkm test --setup` itself first — so there is
810
- * one way the stage gets ready, whichever started the suite.
811
- */
812
- /** Set on the Vitest process by `gkm test`: the stage is ready, here. */
813
- const TEST_READY_ENV = "GKM_TEST_READY";
814
- /** Where `gkm test` records what it prepared, from where it ran. */
815
- const TEST_READY_FILE = ".gkm/test-ready.json";
816
- //#endregion
817
- Object.defineProperty(exports, "DEFAULT_FACTORIES_DIR", {
818
- enumerable: true,
819
- get: function() {
820
- return DEFAULT_FACTORIES_DIR;
821
- }
822
- });
823
- Object.defineProperty(exports, "SubscriberGenerator", {
824
- enumerable: true,
825
- get: function() {
826
- return SubscriberGenerator;
827
- }
828
- });
829
- Object.defineProperty(exports, "TEST_MANIFEST_ENV", {
830
- enumerable: true,
831
- get: function() {
832
- return TEST_MANIFEST_ENV;
833
- }
834
- });
835
- Object.defineProperty(exports, "TEST_READY_ENV", {
836
- enumerable: true,
837
- get: function() {
838
- return TEST_READY_ENV;
839
- }
840
- });
841
- Object.defineProperty(exports, "TEST_READY_FILE", {
842
- enumerable: true,
843
- get: function() {
844
- return TEST_READY_FILE;
845
- }
846
- });
847
- Object.defineProperty(exports, "cacheBackendsIn", {
848
- enumerable: true,
849
- get: function() {
850
- return cacheBackendsIn;
851
- }
852
- });
853
- Object.defineProperty(exports, "driversFor", {
854
- enumerable: true,
855
- get: function() {
856
- return driversFor;
857
- }
858
- });
859
- Object.defineProperty(exports, "eventsBackendsIn", {
860
- enumerable: true,
861
- get: function() {
862
- return eventsBackendsIn;
863
- }
864
- });
865
- Object.defineProperty(exports, "writeTestHarness", {
866
- enumerable: true,
867
- get: function() {
868
- return writeTestHarness;
869
- }
870
- });
871
-
872
- //# sourceMappingURL=ready-B-7LayMF.cjs.map