@geekmidas/cli 10.0.0-alpha.4 → 10.0.0-alpha.40

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 (257) hide show
  1. package/README.md +52 -74
  2. package/bin/adjacent-tsconfig.mjs +97 -0
  3. package/bin/gkm.mjs +7 -1
  4. package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-BZFGYYVA.cjs} +21 -22
  5. package/dist/HostingerProvider-BZFGYYVA.cjs.map +1 -0
  6. package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-Bh6K7QYz.mjs} +20 -21
  7. package/dist/HostingerProvider-Bh6K7QYz.mjs.map +1 -0
  8. package/dist/{LocalStateProvider-Db9SEjyH.cjs → LocalStateProvider-7HR6SS2t.cjs} +11 -6
  9. package/dist/LocalStateProvider-7HR6SS2t.cjs.map +1 -0
  10. package/dist/{LocalStateProvider-Bn6tS-oL.mjs → LocalStateProvider-D7QyJoLM.mjs} +10 -4
  11. package/dist/LocalStateProvider-D7QyJoLM.mjs.map +1 -0
  12. package/dist/{openapi-CA0yJUTN.cjs → OpenApiTsGenerator-B4Kj_qNU.cjs} +142 -303
  13. package/dist/OpenApiTsGenerator-B4Kj_qNU.cjs.map +1 -0
  14. package/dist/{openapi-kkQbsRVj.mjs → OpenApiTsGenerator-CEh3EKN3.mjs} +113 -255
  15. package/dist/OpenApiTsGenerator-CEh3EKN3.mjs.map +1 -0
  16. package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
  17. package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
  18. package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
  19. package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
  20. package/dist/{SSMStateProvider-BRt2py-A.mjs → SSMStateProvider-B0Hu1MKZ.mjs} +13 -4
  21. package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +1 -0
  22. package/dist/{SSMStateProvider-DyX8s9HI.cjs → SSMStateProvider-VrQ-yeqd.cjs} +17 -9
  23. package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +1 -0
  24. package/dist/aws-DkHdTV-p.cjs +61 -0
  25. package/dist/aws-DkHdTV-p.cjs.map +1 -0
  26. package/dist/aws-DojzQnLd.mjs +61 -0
  27. package/dist/aws-DojzQnLd.mjs.map +1 -0
  28. package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
  29. package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
  30. package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
  31. package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
  32. package/dist/{bundler-Be8Zg4Wd.mjs → bundler--fyybf1N.mjs} +19 -30
  33. package/dist/bundler--fyybf1N.mjs.map +1 -0
  34. package/dist/{bundler-BY1OmV1e.cjs → bundler-By6_sXiL.cjs} +20 -32
  35. package/dist/bundler-By6_sXiL.cjs.map +1 -0
  36. package/dist/{config-WRdsQYZK.mjs → config-5watB1D0.mjs} +142 -154
  37. package/dist/config-5watB1D0.mjs.map +1 -0
  38. package/dist/{config-C21ocYNd.cjs → config-CJ-PAb7M.cjs} +209 -210
  39. package/dist/config-CJ-PAb7M.cjs.map +1 -0
  40. package/dist/config.cjs +9 -6
  41. package/dist/config.d.cts +62 -33
  42. package/dist/config.d.cts.map +1 -1
  43. package/dist/config.d.mts +62 -33
  44. package/dist/config.d.mts.map +1 -1
  45. package/dist/config.mjs +3 -5
  46. package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
  47. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  48. package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
  49. package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
  50. package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
  51. package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
  52. package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
  53. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  54. package/dist/deploy/sniffer-hooks.cjs +13 -7
  55. package/dist/deploy/sniffer-hooks.cjs.map +1 -1
  56. package/dist/deploy/sniffer-hooks.d.cts +1 -2
  57. package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
  58. package/dist/deploy/sniffer-hooks.d.mts +1 -2
  59. package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
  60. package/dist/deploy/sniffer-hooks.mjs +9 -2
  61. package/dist/deploy/sniffer-hooks.mjs.map +1 -1
  62. package/dist/deploy/sniffer-loader.cjs +16 -7
  63. package/dist/deploy/sniffer-loader.cjs.map +1 -1
  64. package/dist/deploy/sniffer-loader.d.cts +1 -1
  65. package/dist/deploy/sniffer-loader.d.mts +1 -1
  66. package/dist/deploy/sniffer-loader.mjs +13 -2
  67. package/dist/deploy/sniffer-loader.mjs.map +1 -1
  68. package/dist/deploy/sniffer-routes-worker.cjs +11 -11
  69. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
  70. package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
  71. package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
  72. package/dist/deploy/sniffer-routes-worker.mjs +3 -3
  73. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
  74. package/dist/deploy/sniffer-worker.cjs +15 -6
  75. package/dist/deploy/sniffer-worker.cjs.map +1 -1
  76. package/dist/deploy/sniffer-worker.d.cts +1 -3
  77. package/dist/deploy/sniffer-worker.d.cts.map +1 -1
  78. package/dist/deploy/sniffer-worker.d.mts +1 -3
  79. package/dist/deploy/sniffer-worker.d.mts.map +1 -1
  80. package/dist/deploy/sniffer-worker.mjs +15 -4
  81. package/dist/deploy/sniffer-worker.mjs.map +1 -1
  82. package/dist/{discover-DTmFeloL.cjs → discover-B0f_kuNN.cjs} +118 -66
  83. package/dist/discover-B0f_kuNN.cjs.map +1 -0
  84. package/dist/{discover-CAlFgYec.mjs → discover-BLhf7V_J.mjs} +67 -22
  85. package/dist/discover-BLhf7V_J.mjs.map +1 -0
  86. package/dist/{emit-CbcRolsa.cjs → emit-BdoPFjZf.cjs} +53 -36
  87. package/dist/emit-BdoPFjZf.cjs.map +1 -0
  88. package/dist/{emit-CiZS_5Ub.mjs → emit-Bzq7JsMP.mjs} +24 -6
  89. package/dist/emit-Bzq7JsMP.mjs.map +1 -0
  90. package/dist/github-BAr8WGkR.cjs +213 -0
  91. package/dist/github-BAr8WGkR.cjs.map +1 -0
  92. package/dist/github-D5MthUlR.mjs +213 -0
  93. package/dist/github-D5MthUlR.mjs.map +1 -0
  94. package/dist/{index-DCtipn2h.d.cts → index-B3PeGOQp.d.cts} +4 -23
  95. package/dist/index-B3PeGOQp.d.cts.map +1 -0
  96. package/dist/{index-BThUzEIp.d.mts → index-BOyaGcCm.d.mts} +4 -23
  97. package/dist/index-BOyaGcCm.d.mts.map +1 -0
  98. package/dist/index.cjs +7071 -4519
  99. package/dist/index.cjs.map +1 -1
  100. package/dist/index.d.cts +1 -1
  101. package/dist/index.d.mts +1 -1
  102. package/dist/index.mjs +7117 -4586
  103. package/dist/index.mjs.map +1 -1
  104. package/dist/migrate-BVI3yqcE.mjs +736 -0
  105. package/dist/migrate-BVI3yqcE.mjs.map +1 -0
  106. package/dist/migrate-CFOUApUt.cjs +743 -0
  107. package/dist/migrate-CFOUApUt.cjs.map +1 -0
  108. package/dist/openapi.cjs +197 -8
  109. package/dist/openapi.cjs.map +1 -0
  110. package/dist/openapi.d.cts +47 -14
  111. package/dist/openapi.d.cts.map +1 -1
  112. package/dist/openapi.d.mts +47 -14
  113. package/dist/openapi.d.mts.map +1 -1
  114. package/dist/openapi.mjs +191 -5
  115. package/dist/openapi.mjs.map +1 -0
  116. package/dist/ready-D8UXsP6q.mjs +516 -0
  117. package/dist/ready-D8UXsP6q.mjs.map +1 -0
  118. package/dist/ready-mcxamf73.cjs +563 -0
  119. package/dist/ready-mcxamf73.cjs.map +1 -0
  120. package/dist/reconcile/public.cjs +4 -4
  121. package/dist/reconcile/public.d.cts +27 -26
  122. package/dist/reconcile/public.d.cts.map +1 -1
  123. package/dist/reconcile/public.d.mts +27 -26
  124. package/dist/reconcile/public.d.mts.map +1 -1
  125. package/dist/reconcile/public.mjs +3 -4
  126. package/dist/{reconcile-CFs8C_1a.mjs → reconcile--I7V4Klj.mjs} +4 -7
  127. package/dist/reconcile--I7V4Klj.mjs.map +1 -0
  128. package/dist/{reconcile-D0pimNiy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
  129. package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
  130. package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
  131. package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
  132. package/dist/storage-BzaUxmqe.mjs +124 -0
  133. package/dist/storage-BzaUxmqe.mjs.map +1 -0
  134. package/dist/storage-CSGoC3bK.cjs +152 -0
  135. package/dist/storage-CSGoC3bK.cjs.map +1 -0
  136. package/dist/store-DQgxT_WR.cjs +211 -0
  137. package/dist/store-DQgxT_WR.cjs.map +1 -0
  138. package/dist/store-DeGATCS-.mjs +182 -0
  139. package/dist/store-DeGATCS-.mjs.map +1 -0
  140. package/dist/types-0R124hrY.d.mts +6324 -0
  141. package/dist/types-0R124hrY.d.mts.map +1 -0
  142. package/dist/types-DfdYe31u.d.cts +6325 -0
  143. package/dist/types-DfdYe31u.d.cts.map +1 -0
  144. package/dist/vitest.cjs +65 -0
  145. package/dist/vitest.cjs.map +1 -0
  146. package/dist/vitest.d.cts +29 -0
  147. package/dist/vitest.d.cts.map +1 -0
  148. package/dist/vitest.d.mts +29 -0
  149. package/dist/vitest.d.mts.map +1 -0
  150. package/dist/vitest.mjs +65 -0
  151. package/dist/vitest.mjs.map +1 -0
  152. package/dist/workspace/index.cjs +3 -4
  153. package/dist/workspace/index.d.cts +3 -3
  154. package/dist/workspace/index.d.mts +3 -3
  155. package/dist/workspace/index.mjs +2 -3
  156. package/dist/{credentials-G1JwBoXH.cjs → workspace-BIqeeqcR.cjs} +2248 -1809
  157. package/dist/workspace-BIqeeqcR.cjs.map +1 -0
  158. package/dist/{workspace-BFMrAKqI.mjs → workspace-DOmIhiau.mjs} +863 -952
  159. package/dist/workspace-DOmIhiau.mjs.map +1 -0
  160. package/dist/{credentials-BBIRJVp4.mjs → workspace-DwS1JK5u.mjs} +2114 -1706
  161. package/dist/workspace-DwS1JK5u.mjs.map +1 -0
  162. package/dist/{workspace-BcucjwvA.cjs → workspace-RYSYtHP7.cjs} +1032 -1098
  163. package/dist/workspace-RYSYtHP7.cjs.map +1 -0
  164. package/package.json +29 -20
  165. package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
  166. package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
  167. package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
  168. package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
  169. package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
  170. package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
  171. package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
  172. package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
  173. package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
  174. package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
  175. package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
  176. package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
  177. package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
  178. package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
  179. package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
  180. package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
  181. package/dist/bundler-BY1OmV1e.cjs.map +0 -1
  182. package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
  183. package/dist/chunk-Duj1WY3L.mjs +0 -7
  184. package/dist/config-C21ocYNd.cjs.map +0 -1
  185. package/dist/config-WRdsQYZK.mjs.map +0 -1
  186. package/dist/credentials-B5zRHdAl.mjs +0 -141
  187. package/dist/credentials-B5zRHdAl.mjs.map +0 -1
  188. package/dist/credentials-BBIRJVp4.mjs.map +0 -1
  189. package/dist/credentials-BapzjyV5.cjs +0 -195
  190. package/dist/credentials-BapzjyV5.cjs.map +0 -1
  191. package/dist/credentials-G1JwBoXH.cjs.map +0 -1
  192. package/dist/discover-CAlFgYec.mjs.map +0 -1
  193. package/dist/discover-DTmFeloL.cjs.map +0 -1
  194. package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
  195. package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
  196. package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
  197. package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
  198. package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
  199. package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
  200. package/dist/emit-CbcRolsa.cjs.map +0 -1
  201. package/dist/emit-CiZS_5Ub.mjs.map +0 -1
  202. package/dist/encryption-C6jaU0hE.cjs +0 -54
  203. package/dist/encryption-C6jaU0hE.cjs.map +0 -1
  204. package/dist/encryption-CHzzeR8a.cjs +0 -4
  205. package/dist/encryption-CQUncu7v.mjs +0 -3
  206. package/dist/encryption-D-abyhZx.mjs +0 -42
  207. package/dist/encryption-D-abyhZx.mjs.map +0 -1
  208. package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
  209. package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
  210. package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
  211. package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
  212. package/dist/generator-CNzBw7g5.cjs +0 -289
  213. package/dist/generator-CNzBw7g5.cjs.map +0 -1
  214. package/dist/generator-VE2JrFlY.mjs +0 -253
  215. package/dist/generator-VE2JrFlY.mjs.map +0 -1
  216. package/dist/index-BThUzEIp.d.mts.map +0 -1
  217. package/dist/index-DCtipn2h.d.cts.map +0 -1
  218. package/dist/openapi-CA0yJUTN.cjs.map +0 -1
  219. package/dist/openapi-kkQbsRVj.mjs.map +0 -1
  220. package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
  221. package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
  222. package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
  223. package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
  224. package/dist/openapi-react-query.cjs +0 -4
  225. package/dist/openapi-react-query.d.cts +0 -11
  226. package/dist/openapi-react-query.d.cts.map +0 -1
  227. package/dist/openapi-react-query.d.mts +0 -11
  228. package/dist/openapi-react-query.d.mts.map +0 -1
  229. package/dist/openapi-react-query.mjs +0 -4
  230. package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
  231. package/dist/reconcile-D0pimNiy.cjs.map +0 -1
  232. package/dist/setup-B5_oHHUj.cjs +0 -11
  233. package/dist/setup-Cm2UnAdh.cjs +0 -473
  234. package/dist/setup-Cm2UnAdh.cjs.map +0 -1
  235. package/dist/setup-DXvRLTZN.mjs +0 -443
  236. package/dist/setup-DXvRLTZN.mjs.map +0 -1
  237. package/dist/setup-ZijS4qeC.mjs +0 -11
  238. package/dist/storage-BJmzYxt8.cjs +0 -363
  239. package/dist/storage-BJmzYxt8.cjs.map +0 -1
  240. package/dist/storage-BM9hGpgv.cjs +0 -7
  241. package/dist/storage-Bi24WfoW.mjs +0 -297
  242. package/dist/storage-Bi24WfoW.mjs.map +0 -1
  243. package/dist/storage-CYlGwWTj.mjs +0 -3
  244. package/dist/sync-CmEuNZsB.mjs +0 -76
  245. package/dist/sync-CmEuNZsB.mjs.map +0 -1
  246. package/dist/sync-D306yOWu.cjs +0 -4
  247. package/dist/sync-SizaChmC.cjs +0 -93
  248. package/dist/sync-SizaChmC.cjs.map +0 -1
  249. package/dist/sync-hOqAWHR6.mjs +0 -3
  250. package/dist/types-BhIl-COG.d.cts +0 -2184
  251. package/dist/types-BhIl-COG.d.cts.map +0 -1
  252. package/dist/types-C-arnQ1r.d.mts +0 -2184
  253. package/dist/types-C-arnQ1r.d.mts.map +0 -1
  254. package/dist/workspace-BFMrAKqI.mjs.map +0 -1
  255. package/dist/workspace-BcucjwvA.cjs.map +0 -1
  256. package/dist/workspace-DdJss8I6.cjs +0 -9
  257. package/dist/workspace-k4l3rsFh.mjs +0 -8
@@ -0,0 +1,563 @@
1
+ const require_discover = require("./discover-B0f_kuNN.cjs");
2
+ const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-B4Kj_qNU.cjs");
3
+ let node_fs = require("node:fs");
4
+ let node_fs_promises = require("node:fs/promises");
5
+ let node_path = require("node:path");
6
+ let _geekmidas_manifest = require("@geekmidas/manifest");
7
+ let node_url = require("node:url");
8
+ let _geekmidas_envkit = require("@geekmidas/envkit");
9
+ let _geekmidas_services = require("@geekmidas/services");
10
+ //#region src/reconcile/fakes.ts
11
+ /**
12
+ * External APIs' fakes, read from where the convention puts them.
13
+ *
14
+ * `test/fakes/<id>.ts` default-exports `fake.app(…)` or `fake.image(…)`. The
15
+ * construct never names it — that is what keeps a fake out of a deployed
16
+ * bundle — so this is the one place it is found, by the same rule that finds a
17
+ * database's factory in `test/factories/<id>.ts`.
18
+ */
19
+ /** Where a project keeps its external APIs' fakes, relative to its root. */
20
+ const DEFAULT_FAKES_DIR = "test/fakes";
21
+ /** The marker `fake.app` and `fake.image` set — the same registered symbol. */
22
+ const FAKE = Symbol.for("@geekmidas/constructs/fake");
23
+ /**
24
+ * The fake for every external API the manifest declares, keyed by id.
25
+ *
26
+ * @throws {NoFake} when an external API has no file, since a local stage would
27
+ * otherwise have nothing to call.
28
+ * @throws {NotAFake} when the file's default export is not one.
29
+ */
30
+ async function readFakes(root, manifest) {
31
+ const fakes = {};
32
+ for (const [id, declaration] of Object.entries(manifest)) {
33
+ if (declaration?.kind !== "external-api") continue;
34
+ const file = await fakeFile((0, node_path.join)(root, DEFAULT_FAKES_DIR), id);
35
+ const value = (await import((0, node_url.pathToFileURL)(file).href)).default;
36
+ if (!isFakeExport(value)) throw new NotAFake(file);
37
+ fakes[id] = {
38
+ file,
39
+ ...value.kind === "image" ? {
40
+ image: value.image,
41
+ port: value.port
42
+ } : {},
43
+ credentials: JSON.stringify(value.credentials)
44
+ };
45
+ }
46
+ return fakes;
47
+ }
48
+ /** The first of `<id>.ts`, `.mts`, `.js`, `.mjs` that exists. */
49
+ async function fakeFile(folder, id) {
50
+ const base = (0, node_path.join)(folder, (0, _geekmidas_manifest.kebabCase)(id));
51
+ for (const extension of [
52
+ ".ts",
53
+ ".mts",
54
+ ".js",
55
+ ".mjs"
56
+ ]) {
57
+ const file = `${base}${extension}`;
58
+ if (await (0, node_fs_promises.access)(file).then(() => true, () => false)) return file;
59
+ }
60
+ throw new NoFake(id, `${base}.ts`);
61
+ }
62
+ function isFakeExport(value) {
63
+ return typeof value === "object" && value !== null && value[FAKE] === true;
64
+ }
65
+ /** An external API with no fake for a local stage to call. */
66
+ var NoFake = class extends Error {
67
+ id;
68
+ file;
69
+ constructor(id, file) {
70
+ super(`'${id}' is an external API, and a local stage calls its fake rather than the provider — but there is none. Create ${file}, default-exporting fake.app(…) or fake.image(…) from @geekmidas/constructs/external-api.`);
71
+ this.id = id;
72
+ this.file = file;
73
+ this.name = "NoFake";
74
+ }
75
+ };
76
+ /** A file in the fakes folder whose default export is not a fake. */
77
+ var NotAFake = class extends Error {
78
+ file;
79
+ constructor(file) {
80
+ super(`${file} is where a fake lives, and its default export is not one. Export default fake.app(handler, { credentials }) or fake.image(image, { port, credentials }).`);
81
+ this.file = file;
82
+ this.name = "NotAFake";
83
+ }
84
+ };
85
+ //#endregion
86
+ //#region src/generators/drivers.ts
87
+ /**
88
+ * Driver registration for generated entry points.
89
+ *
90
+ * A construct declares that it needs object storage and is handed one URL; the
91
+ * scheme in that URL picks the driver that builds the client. Which drivers
92
+ * exist is therefore the *entry point's* decision, not the construct's and not
93
+ * the application's — an app that never talks to S3 should never resolve the AWS
94
+ * SDK, and application code that registers a driver has named a provider in the
95
+ * one layer this design keeps provider-free.
96
+ *
97
+ * So the generated entries register: `gkm dev`'s server for local development,
98
+ * and each Lambda handler for its own target. Registration is an explicit call
99
+ * rather than an import side effect, because a side-effecting module is exactly
100
+ * what a bundler is entitled to drop.
101
+ */
102
+ /** The `s3://` driver, which serves MinIO locally and S3 deployed. */
103
+ const S3 = {
104
+ imports: `import { registerStorageDriver } from '@geekmidas/storage';\nimport { s3Driver } from '@geekmidas/storage/aws';`,
105
+ setup: "registerStorageDriver(s3Driver);"
106
+ };
107
+ const NONE = {
108
+ imports: "",
109
+ setup: ""
110
+ };
111
+ /**
112
+ * The cache driver for a backend — exactly one, never all three.
113
+ *
114
+ * Keyed off the *backend* rather than off a dependency, because unlike storage
115
+ * the backend already says which driver is right: a project caching in Postgres
116
+ * resolves `pg` and never `ioredis`, and one caching in Upstash resolves
117
+ * neither. That is the whole reason the drivers live behind separate subpaths.
118
+ *
119
+ * Both HTTP schemes are registered for Upstash, because the local proxy speaks
120
+ * `http://` and Upstash speaks `https://`, and an entry that registered one
121
+ * would work in exactly one of the two places.
122
+ */
123
+ const CACHE_DRIVERS = {
124
+ upstash: {
125
+ imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { upstashCacheDriver, upstashInsecureCacheDriver } from '@geekmidas/cache/upstash';`,
126
+ setup: "registerCacheDriver(upstashCacheDriver);\nregisterCacheDriver(upstashInsecureCacheDriver);"
127
+ },
128
+ elasticache: {
129
+ imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { redisCacheDriver, redissCacheDriver } from '@geekmidas/cache/redis';`,
130
+ setup: "registerCacheDriver(redisCacheDriver);\nregisterCacheDriver(redissCacheDriver);"
131
+ },
132
+ db: {
133
+ imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { postgresCacheDriver } from '@geekmidas/cache/postgres';`,
134
+ setup: "registerCacheDriver(postgresCacheDriver);"
135
+ }
136
+ };
137
+ /**
138
+ * Everything a generated entry should register: object storage, and the cache.
139
+ *
140
+ * Merged here rather than threaded separately, because a generated file has one
141
+ * import block and one setup block whatever fills them.
142
+ */
143
+ function driversFor(options) {
144
+ const backends = options.cache === false || options.cache === void 0 ? [] : typeof options.cache === "string" ? [options.cache] : options.cache;
145
+ const parts = [storageDriversFor(options.appRoot), ...[...new Set(backends)].map((backend) => CACHE_DRIVERS[backend] ?? CACHE_DRIVERS[require_discover.DEFAULT_CACHE.aws])].filter((part) => part.imports || part.setup);
146
+ if (parts.length === 0) return NONE;
147
+ return {
148
+ imports: parts.map((part) => part.imports).join("\n"),
149
+ setup: parts.map((part) => part.setup).join("\n")
150
+ };
151
+ }
152
+ /**
153
+ * Which cache backends an app's entry actually has to speak.
154
+ *
155
+ * The config answers this for a cache that named nowhere, and the *declaration*
156
+ * answers it for one that named a database — `orders.cache('Sessions')` is a
157
+ * statement about the application, so a deployment cannot move that cache and
158
+ * the entry cannot register a driver for somewhere else.
159
+ *
160
+ * Reading only the config is how an entry ends up registering the Upstash
161
+ * driver for a URL the target composed as `postgres://`, which fails at the
162
+ * first request with `UnregisteredCacheScheme` and a stack that points at the
163
+ * cache rather than at the config that disagreed.
164
+ */
165
+ function cacheBackendsIn(manifest, configured) {
166
+ const caches = Object.values(manifest).filter((d) => d.kind === "cache");
167
+ return [...new Set(caches.map((c) => c.of ? "db" : configured))];
168
+ }
169
+ /**
170
+ * The drivers an app's entry points should register.
171
+ *
172
+ * Keyed off the dependency rather than off a declared bucket, because the entry
173
+ * is generated before discovery has run on the watcher's rebuild path — and an
174
+ * app that installed `@geekmidas/storage` has already paid for the SDK it would
175
+ * otherwise resolve lazily.
176
+ */
177
+ function storageDriversFor(appRoot) {
178
+ return dependsOnStorage(appRoot) ? S3 : NONE;
179
+ }
180
+ function dependsOnStorage(appRoot) {
181
+ const manifest = (0, node_path.join)(appRoot, "package.json");
182
+ if (!(0, node_fs.existsSync)(manifest)) return false;
183
+ try {
184
+ const pkg = JSON.parse((0, node_fs.readFileSync)(manifest, "utf8"));
185
+ return Boolean(pkg.dependencies?.["@geekmidas/storage"] ?? pkg.devDependencies?.["@geekmidas/storage"]);
186
+ } catch {
187
+ return false;
188
+ }
189
+ }
190
+ //#endregion
191
+ //#region src/test/harness.ts
192
+ /**
193
+ * What `gkm test` writes for a feature test to be built from.
194
+ *
195
+ * `gkm test` already discovers an app's constructs and resolves the test
196
+ * stage's environment; this keeps both, under `.gkm/test/`:
197
+ *
198
+ * - `manifest.json` — which construct and endpoint is exported where, and the
199
+ * environment the test stage resolved, keyed as the constructs derive keys.
200
+ * - `clients/<surface>.ts` — each surface's typed client, from the same
201
+ * generator `gkm build` uses for the app's own.
202
+ * - `index.ts` — the configured `it`, and a `Browser` holding one client per
203
+ * surface and one per auth server, each on the browser's `fetch`.
204
+ *
205
+ * A test imports `it` from there and declares nothing — not a construct, not an
206
+ * environment key, not a client.
207
+ */
208
+ /**
209
+ * The variable the manifest's path travels in — what
210
+ * `@geekmidas/constructs/testing` reads. Its own constant rather than an import:
211
+ * that entry carries the whole harness (MSW, Hono, Vitest), and importing it
212
+ * here put all of it on the start-up path of every `gkm` command.
213
+ */
214
+ const TEST_MANIFEST_ENV = "GKM_TEST_MANIFEST";
215
+ /** Where the harness lives, relative to the workspace root. */
216
+ const TEST_HARNESS_DIR = ".gkm/test";
217
+ /**
218
+ * Better-auth's client plugin for each server plugin id.
219
+ *
220
+ * Written down rather than derived: the ids and the client names do not follow
221
+ * one rule (`admin` pairs with `adminClient`, `email-otp` with
222
+ * `emailOTPClient`), and a guess that missed would hand a test a client
223
+ * silently lacking every method of that plugin. A server plugin not listed has
224
+ * no client counterpart — `bearer`, `open-api` — or needs one from another
225
+ * package, and is left out.
226
+ */
227
+ const CLIENT_PLUGINS = {
228
+ admin: "adminClient",
229
+ anonymous: "anonymousClient",
230
+ "device-authorization": "deviceAuthorizationClient",
231
+ "email-otp": "emailOTPClient",
232
+ jwt: "jwtClient",
233
+ "last-login-method": "lastLoginMethodClient",
234
+ "magic-link": "magicLinkClient",
235
+ "multi-session": "multiSessionClient",
236
+ "oauth-popup": "oauthPopupClient",
237
+ "one-tap": "oneTapClient",
238
+ "one-time-token": "oneTimeTokenClient",
239
+ organization: "organizationClient",
240
+ "phone-number": "phoneNumberClient",
241
+ siwe: "siweClient",
242
+ "two-factor": "twoFactorClient",
243
+ username: "usernameClient"
244
+ };
245
+ /** Where a project keeps its test factories, relative to its root. */
246
+ const DEFAULT_FACTORIES_DIR = "test/factories";
247
+ /** A file in the factories folder that names no database construct. */
248
+ var UnknownFactoryFile = class extends Error {
249
+ file;
250
+ known;
251
+ constructor(file, known) {
252
+ 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.`);
253
+ this.file = file;
254
+ this.known = known;
255
+ this.name = "UnknownFactoryFile";
256
+ }
257
+ };
258
+ /** A factory file that exports no `createFactory`. */
259
+ var FactoryHasNoCreate = class extends Error {
260
+ file;
261
+ constructor(file) {
262
+ super(`${file} is a test factory but exports no \`createFactory\`. Export \`function createFactory(db: Kysely<Database>)\` returning the factory.`);
263
+ this.file = file;
264
+ this.name = "FactoryHasNoCreate";
265
+ }
266
+ };
267
+ /**
268
+ * The factories in a folder, each matched to the database it is named after.
269
+ *
270
+ * A file that names no database, or exports no `createFactory`, is refused
271
+ * rather than skipped: either is a factory no test would ever be handed.
272
+ */
273
+ async function factorySources(folder, databases) {
274
+ if (!folder) return [];
275
+ const entries = await (0, node_fs_promises.readdir)(folder, { withFileTypes: true }).catch((error) => {
276
+ if (error.code === "ENOENT") return [];
277
+ throw error;
278
+ });
279
+ const found = [];
280
+ for (const entry of entries) {
281
+ if (!entry.isFile() || !isFactoryFile(entry.name)) continue;
282
+ const file = (0, node_path.join)(folder, entry.name);
283
+ const id = (0, _geekmidas_manifest.canonicalId)(entry.name.slice(0, -(0, node_path.extname)(entry.name).length));
284
+ if (!databases.includes(id)) throw new UnknownFactoryFile(file, databases);
285
+ if (typeof (await import((0, node_url.pathToFileURL)(file).href)).createFactory !== "function") throw new FactoryHasNoCreate(file);
286
+ found.push({
287
+ file,
288
+ service: (0, _geekmidas_manifest.serviceKey)(id)
289
+ });
290
+ }
291
+ return found.sort((a, b) => a.service < b.service ? -1 : 1);
292
+ }
293
+ function isFactoryFile(file) {
294
+ if (file.endsWith(".d.ts")) return false;
295
+ if (/\.(spec|test)\.[cm]?[jt]s$/.test(file)) return false;
296
+ return [
297
+ ".ts",
298
+ ".mts",
299
+ ".js",
300
+ ".mjs"
301
+ ].includes((0, node_path.extname)(file));
302
+ }
303
+ /** Write the manifest, the clients and the harness; return the manifest path. */
304
+ async function writeTestHarness(options) {
305
+ const sources = {};
306
+ const declared = await require_discover.discover({
307
+ patterns: options.patterns,
308
+ cwd: options.root,
309
+ sources
310
+ });
311
+ const endpoints = (await new require_OpenApiTsGenerator.EndpointGenerator().load(options.patterns, options.root)).map(({ key, construct, path }) => ({
312
+ surface: construct.owner ?? construct.surface?.id,
313
+ construct,
314
+ source: {
315
+ file: path.absolute,
316
+ export: key
317
+ }
318
+ })).filter((endpoint) => endpoint.surface !== void 0);
319
+ const manifest = {
320
+ stage: options.stage,
321
+ constructs: Object.fromEntries(Object.entries(declared).flatMap(([id, declaration]) => {
322
+ const source = sources[id];
323
+ return source ? [[id, {
324
+ kind: declaration.kind,
325
+ source: {
326
+ file: source.file,
327
+ export: source.exportName
328
+ }
329
+ }]] : [];
330
+ })),
331
+ endpoints: endpoints.map(({ surface, source }) => ({
332
+ surface,
333
+ source
334
+ })),
335
+ env: options.env
336
+ };
337
+ const surfaces = [...new Set(endpoints.map(({ surface }) => surface))];
338
+ const generator = new require_OpenApiTsGenerator.OpenApiTsGenerator();
339
+ const clients = await Promise.all(surfaces.map(async (surface) => ({
340
+ file: `${(0, _geekmidas_manifest.kebabCase)(surface)}.ts`,
341
+ content: await generator.generate(endpoints.filter((endpoint) => endpoint.surface === surface).map(({ construct }) => construct), {
342
+ title: surface,
343
+ version: "1.0.0"
344
+ })
345
+ })));
346
+ const auths = await authClients(declared, sources, options.env);
347
+ const drivers = driversFor({
348
+ appRoot: options.root,
349
+ cache: cacheBackendsIn(declared, options.cacheBackend)
350
+ });
351
+ const owners = new Set(auths.map((auth) => auth.databaseId));
352
+ const databases = databaseSources(declared, sources).filter(({ id, kind }) => !owners.has(id) && kind !== "database-reader");
353
+ const factories = await factorySources(options.factories, databases.map(({ id }) => id));
354
+ const fakes = Object.entries(await readFakes(options.root, declared)).map(([id, { file }]) => ({
355
+ id,
356
+ file
357
+ }));
358
+ const files = [.../* @__PURE__ */ new Set([...Object.values(manifest.constructs).map(({ source }) => source.file), ...manifest.endpoints.map(({ source }) => source.file)])];
359
+ const harnessFor = (dir) => harnessModule({
360
+ dir,
361
+ files,
362
+ databases,
363
+ surfaces: surfaces.map((id, index) => ({
364
+ id,
365
+ secured: clients[index].content.includes("authStrategies:")
366
+ })),
367
+ auths,
368
+ drivers,
369
+ factories,
370
+ fakes,
371
+ mail: Object.values(declared).some(({ kind }) => kind === "email")
372
+ });
373
+ const json = `${JSON.stringify(manifest, null, 2)}\n`;
374
+ return (await Promise.all([...new Set(options.targets)].map(async (target) => {
375
+ const dir = (0, node_path.join)(target, TEST_HARNESS_DIR);
376
+ await (0, node_fs_promises.rm)(dir, {
377
+ recursive: true,
378
+ force: true
379
+ });
380
+ await (0, node_fs_promises.mkdir)((0, node_path.join)(dir, "clients"), { recursive: true });
381
+ await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "manifest.json"), json);
382
+ await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "index.ts"), harnessFor(dir));
383
+ for (const client of clients) await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "clients", client.file), client.content);
384
+ return (0, node_path.join)(dir, "manifest.json");
385
+ })))[0];
386
+ }
387
+ /** Each auth server, with the client plugins its server plugins pair with. */
388
+ async function authClients(declared, sources, env) {
389
+ const clients = [];
390
+ const envParser = new _geekmidas_envkit.EnvironmentParser({ ...env });
391
+ for (const [id, declaration] of Object.entries(declared)) {
392
+ const source = sources[id];
393
+ if (declaration.kind !== "rest-api" || !source) continue;
394
+ const exported = (await import(source.file))[source.exportName];
395
+ if (!isBetterAuth(exported) || exported.id !== id) continue;
396
+ const ids = await exported.pluginIds({
397
+ envParser,
398
+ context: _geekmidas_services.serviceContext
399
+ });
400
+ clients.push({
401
+ id,
402
+ basePath: exported.basePath,
403
+ plugins: ids.flatMap((plugin) => CLIENT_PLUGINS[plugin] ?? []),
404
+ databaseId: exported.databaseId
405
+ });
406
+ }
407
+ return clients;
408
+ }
409
+ function isBetterAuth(value) {
410
+ return typeof value === "object" && value !== null && "pluginIds" in value && "basePath" in value;
411
+ }
412
+ /** `Api` → `api`, `AdminApi` → `adminApi`: a construct id as a property name. */
413
+ function propertyOf(id) {
414
+ return id.charAt(0).toLowerCase() + id.slice(1);
415
+ }
416
+ /**
417
+ * Every database construct the app declares — a database, a schema tenant, a
418
+ * reader — keyed as a test is handed its transaction.
419
+ */
420
+ function databaseSources(declared, sources) {
421
+ return Object.entries(declared).filter(([id, declaration]) => Boolean(sources[id] && declaration.kind.startsWith("database"))).map(([id, declaration]) => ({
422
+ id,
423
+ kind: declaration.kind,
424
+ service: (0, _geekmidas_manifest.serviceKey)(id),
425
+ source: sources[id]
426
+ }));
427
+ }
428
+ /** An import specifier for `file`, from the harness written into `dir`. */
429
+ function specifierFrom(dir, file) {
430
+ const path = (0, node_path.relative)(dir, file).replace(/\.tsx?$/, ".js");
431
+ return path.startsWith(".") ? path : `./${path}`;
432
+ }
433
+ function harnessModule(options) {
434
+ const { surfaces, auths, drivers, databases, dir, files, factories, fakes, mail } = options;
435
+ const magicLinks = mail ? auths.filter(({ plugins }) => plugins.includes("magicLinkClient")) : [];
436
+ const signIn = magicLinks.length === 1 ? magicLinks[0] : void 0;
437
+ const plugins = [...new Set(auths.flatMap(({ plugins }) => plugins))].sort();
438
+ const imports = [
439
+ `import {${databases.length ? " type DatabaseOf," : ""} featureTest, loadTestManifest${signIn ? ", signInWithMagicLink" : ""} } from '@geekmidas/constructs/testing';`,
440
+ ...databases.map(({ id, source }) => `import type { ${source.exportName} as __${id} } from '${specifierFrom(dir, source.file)}';`),
441
+ `import { Browser as TestBrowser } from '@geekmidas/testkit/browser';`,
442
+ ...surfaces.map(({ id }) => `import { createApi as create${id} } from './clients/${(0, _geekmidas_manifest.kebabCase)(id)}.js';`),
443
+ ...auths.length ? [`import { createAuthClient } from 'better-auth/client';`] : [],
444
+ ...plugins.length ? [`import { ${plugins.join(", ")} } from 'better-auth/client/plugins';`] : [],
445
+ ...drivers.imports ? [drivers.imports] : [],
446
+ ...factories.map(({ file, service }) => `import { createFactory as __${service}Factory } from '${specifierFrom(dir, file)}';`),
447
+ ...fakes.map(({ id, file }) => `import __${id}Fake from '${specifierFrom(dir, file)}';`),
448
+ ...files.map((file, index) => `import * as __module${index} from '${specifierFrom(dir, file)}';`)
449
+ ];
450
+ const url = (id) => `manifest.env[${JSON.stringify((0, _geekmidas_manifest.provideKey)(id, "url"))}]!`;
451
+ const members = [
452
+ ...surfaces.map(({ id, secured }) => ` /** \`${id}\`, at ${(0, _geekmidas_manifest.provideKey)(id, "url")}. */
453
+ readonly ${propertyOf(id)} = create${id}({
454
+ baseURL: ${url(id)},
455
+ fetch: this.fetch,${secured ? `
456
+ // Served in-process: no authorizer runs, so nothing is signed. A scheme
457
+ // with no strategy sends its request without credentials.
458
+ authStrategies: {} as never,` : ""}
459
+ });`),
460
+ ...auths.map((auth) => ` /** \`${auth.id}\`, at ${(0, _geekmidas_manifest.provideKey)(auth.id, "url")}. */
461
+ readonly ${propertyOf(auth.id)} = createAuthClient({
462
+ baseURL: \`\${${url(auth.id)}}${auth.basePath}\`,
463
+ plugins: [${auth.plugins.map((plugin) => `${plugin}()`).join(", ")}],
464
+ fetchOptions: { customFetchImpl: this.fetch },
465
+ });`),
466
+ ...signIn ? [` /**
467
+ * Sign in as \`email\` through \`${signIn.id}\`'s magic link, the way a person
468
+ * does — the email opened, the link followed. The session it then reports.
469
+ * Without an address, as somebody new: \`user.email\` says who.
470
+ */
471
+ signIn(email?: string) {
472
+ return signInWithMagicLink(this, this.${propertyOf(signIn.id)}, email);
473
+ }`] : []
474
+ ];
475
+ const factoriesType = factories.length ? `{ ${factories.map(({ service }) => `${service}: typeof __${service}Factory`).join("; ")} }` : "";
476
+ const factoriesOption = factories.length ? `, factories: { ${factories.map(({ service }) => `${service}: __${service}Factory`).join(", ")} }` : "";
477
+ const fakesOption = fakes.length ? `, fakes: { ${fakes.map(({ id }) => `${id}: __${id}Fake`).join(", ")} }` : "";
478
+ const databasesType = `{ ${databases.map(({ id, service }) => `${service}: DatabaseOf<typeof __${id}>`).join("; ")} }`;
479
+ const generics = databases.length ? `<Browser, ${databasesType}${factoriesType ? `, ${factoriesType}` : ""}>` : "";
480
+ return `// Generated by \`gkm test\` from the app's constructs — do not edit.
481
+ ${imports.join("\n")}
482
+
483
+ ${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));
484
+
485
+ /** A browser with a client for every surface the app declares. */
486
+ export class Browser extends TestBrowser {
487
+ ${members.join("\n\n")}
488
+ }
489
+
490
+ /** The app's modules, by the path the manifest records for each. */
491
+ const modules = {
492
+ ${files.map((file, index) => `\t${JSON.stringify(file)}: __module${index},`).join("\n")}
493
+ };
494
+
495
+ /** \`it\`, for a test that drives the app the way it runs deployed. */
496
+ export const it = featureTest${generics}({ manifest, modules, browser: Browser${factoriesOption}${fakesOption} });
497
+ `;
498
+ }
499
+ //#endregion
500
+ //#region src/test/ready.ts
501
+ /**
502
+ * The hand-off between `gkm test` and the Vitest global setup.
503
+ *
504
+ * `gkm test` reconciles and migrates the test stage before Vitest starts; the
505
+ * setup, run inside Vitest, only has to load what that left. Started by plain
506
+ * `vitest` instead, the setup runs `gkm test --setup` itself first — so there is
507
+ * one way the stage gets ready, whichever started the suite.
508
+ */
509
+ /** Set on the Vitest process by `gkm test`: the stage is ready, here. */
510
+ const TEST_READY_ENV = "GKM_TEST_READY";
511
+ /** Where `gkm test` records what it prepared, from where it ran. */
512
+ const TEST_READY_FILE = ".gkm/test-ready.json";
513
+ //#endregion
514
+ Object.defineProperty(exports, "DEFAULT_FACTORIES_DIR", {
515
+ enumerable: true,
516
+ get: function() {
517
+ return DEFAULT_FACTORIES_DIR;
518
+ }
519
+ });
520
+ Object.defineProperty(exports, "TEST_MANIFEST_ENV", {
521
+ enumerable: true,
522
+ get: function() {
523
+ return TEST_MANIFEST_ENV;
524
+ }
525
+ });
526
+ Object.defineProperty(exports, "TEST_READY_ENV", {
527
+ enumerable: true,
528
+ get: function() {
529
+ return TEST_READY_ENV;
530
+ }
531
+ });
532
+ Object.defineProperty(exports, "TEST_READY_FILE", {
533
+ enumerable: true,
534
+ get: function() {
535
+ return TEST_READY_FILE;
536
+ }
537
+ });
538
+ Object.defineProperty(exports, "cacheBackendsIn", {
539
+ enumerable: true,
540
+ get: function() {
541
+ return cacheBackendsIn;
542
+ }
543
+ });
544
+ Object.defineProperty(exports, "driversFor", {
545
+ enumerable: true,
546
+ get: function() {
547
+ return driversFor;
548
+ }
549
+ });
550
+ Object.defineProperty(exports, "readFakes", {
551
+ enumerable: true,
552
+ get: function() {
553
+ return readFakes;
554
+ }
555
+ });
556
+ Object.defineProperty(exports, "writeTestHarness", {
557
+ enumerable: true,
558
+ get: function() {
559
+ return writeTestHarness;
560
+ }
561
+ });
562
+
563
+ //# sourceMappingURL=ready-mcxamf73.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready-mcxamf73.cjs","names":["join","pathToFileURL","kebabCase","access","DEFAULT_CACHE","join","existsSync","readFileSync","readdir","join","canonicalId","extname","pathToFileURL","serviceKey","discover","EndpointGenerator","OpenApiTsGenerator","kebabCase","rm","mkdir","writeFile","EnvironmentParser","serviceContext","relative","provideKey"],"sources":["../src/reconcile/fakes.ts","../src/generators/drivers.ts","../src/test/harness.ts","../src/test/ready.ts"],"sourcesContent":["/**\n * External APIs' fakes, read from where the convention puts them.\n *\n * `test/fakes/<id>.ts` default-exports `fake.app(…)` or `fake.image(…)`. The\n * construct never names it — that is what keeps a fake out of a deployed\n * bundle — so this is the one place it is found, by the same rule that finds a\n * database's factory in `test/factories/<id>.ts`.\n */\n\nimport { access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { ConstructManifest } from '@geekmidas/manifest';\nimport { kebabCase } from '@geekmidas/manifest';\n\n/** Where a project keeps its external APIs' fakes, relative to its root. */\nexport const DEFAULT_FAKES_DIR = 'test/fakes';\n\n/** One external API's fake, as the local target needs it. */\nexport interface LocalFake {\n\t/** The file it was read from — what the harness and `gkm dev` import. */\n\tfile: string;\n\t/** The image to run, for a fake the provider publishes. */\n\timage?: string;\n\t/** The port that image listens on inside its container. */\n\tport?: number;\n\t/** What the fake accepts, as the JSON `<ID>_CREDENTIALS` holds. */\n\tcredentials: string;\n}\n\n/** The marker `fake.app` and `fake.image` set — the same registered symbol. */\nconst FAKE = Symbol.for('@geekmidas/constructs/fake');\n\ninterface FakeExport {\n\tkind: 'app' | 'image';\n\timage?: string;\n\tport?: number;\n\tcredentials: unknown;\n}\n\n/**\n * The fake for every external API the manifest declares, keyed by id.\n *\n * @throws {NoFake} when an external API has no file, since a local stage would\n * otherwise have nothing to call.\n * @throws {NotAFake} when the file's default export is not one.\n */\nexport async function readFakes(\n\troot: string,\n\tmanifest: ConstructManifest,\n): Promise<Record<string, LocalFake>> {\n\tconst fakes: Record<string, LocalFake> = {};\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration?.kind !== 'external-api') continue;\n\n\t\tconst file = await fakeFile(join(root, DEFAULT_FAKES_DIR), id);\n\t\tconst module = (await import(pathToFileURL(file).href)) as {\n\t\t\tdefault?: unknown;\n\t\t};\n\t\tconst value = module.default;\n\t\tif (!isFakeExport(value)) throw new NotAFake(file);\n\n\t\tfakes[id] = {\n\t\t\tfile,\n\t\t\t...(value.kind === 'image'\n\t\t\t\t? { image: value.image, port: value.port }\n\t\t\t\t: {}),\n\t\t\tcredentials: JSON.stringify(value.credentials),\n\t\t};\n\t}\n\n\treturn fakes;\n}\n\n/** The first of `<id>.ts`, `.mts`, `.js`, `.mjs` that exists. */\nasync function fakeFile(folder: string, id: string): Promise<string> {\n\tconst base = join(folder, kebabCase(id));\n\tfor (const extension of ['.ts', '.mts', '.js', '.mjs']) {\n\t\tconst file = `${base}${extension}`;\n\t\tconst exists = await access(file).then(\n\t\t\t() => true,\n\t\t\t() => false,\n\t\t);\n\t\tif (exists) return file;\n\t}\n\n\tthrow new NoFake(id, `${base}.ts`);\n}\n\nfunction isFakeExport(value: unknown): value is FakeExport {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as { [FAKE]?: unknown })[FAKE] === true\n\t);\n}\n\n/** An external API with no fake for a local stage to call. */\nexport class NoFake extends Error {\n\tconstructor(\n\t\treadonly id: string,\n\t\treadonly file: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${id}' is an external API, and a local stage calls its fake rather ` +\n\t\t\t\t`than the provider — but there is none. Create ${file}, ` +\n\t\t\t\t`default-exporting fake.app(…) or fake.image(…) from ` +\n\t\t\t\t`@geekmidas/constructs/external-api.`,\n\t\t);\n\t\tthis.name = 'NoFake';\n\t}\n}\n\n/** A file in the fakes folder whose default export is not a fake. */\nexport class NotAFake extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is where a fake lives, and its default export is not one. ` +\n\t\t\t\t`Export default fake.app(handler, { credentials }) or ` +\n\t\t\t\t`fake.image(image, { port, credentials }).`,\n\t\t);\n\t\tthis.name = 'NotAFake';\n\t}\n}\n","/**\n * Driver registration for generated entry points.\n *\n * A construct declares that it needs object storage and is handed one URL; the\n * scheme in that URL picks the driver that builds the client. Which drivers\n * exist is therefore the *entry point's* decision, not the construct's and not\n * the application's — an app that never talks to S3 should never resolve the AWS\n * SDK, and application code that registers a driver has named a provider in the\n * one layer this design keeps provider-free.\n *\n * So the generated entries register: `gkm dev`'s server for local development,\n * and each Lambda handler for its own target. Registration is an explicit call\n * rather than an import side effect, because a side-effecting module is exactly\n * what a bundler is entitled to drop.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { type CacheBackend, DEFAULT_CACHE } from '../types.js';\n\n/** The `s3://` driver, which serves MinIO locally and S3 deployed. */\nconst S3 = {\n\timports: `import { registerStorageDriver } from '@geekmidas/storage';\\nimport { s3Driver } from '@geekmidas/storage/aws';`,\n\tsetup: 'registerStorageDriver(s3Driver);',\n};\n\n/** What a generated entry needs in order to register the drivers it uses. */\nexport interface RuntimeDrivers {\n\t/** Import lines, or `''` when there is nothing to register. */\n\timports: string;\n\t/** The registration calls, or `''`. */\n\tsetup: string;\n}\n\n/** @deprecated The same shape, from when only storage had drivers. */\nexport type StorageDrivers = RuntimeDrivers;\n\nconst NONE: RuntimeDrivers = { imports: '', setup: '' };\n\n/**\n * The cache driver for a backend — exactly one, never all three.\n *\n * Keyed off the *backend* rather than off a dependency, because unlike storage\n * the backend already says which driver is right: a project caching in Postgres\n * resolves `pg` and never `ioredis`, and one caching in Upstash resolves\n * neither. That is the whole reason the drivers live behind separate subpaths.\n *\n * Both HTTP schemes are registered for Upstash, because the local proxy speaks\n * `http://` and Upstash speaks `https://`, and an entry that registered one\n * would work in exactly one of the two places.\n */\nconst CACHE_DRIVERS: Record<CacheBackend, RuntimeDrivers> = {\n\tupstash: {\n\t\timports: `import { registerCacheDriver } from '@geekmidas/cache';\\nimport { upstashCacheDriver, upstashInsecureCacheDriver } from '@geekmidas/cache/upstash';`,\n\t\tsetup:\n\t\t\t'registerCacheDriver(upstashCacheDriver);\\nregisterCacheDriver(upstashInsecureCacheDriver);',\n\t},\n\telasticache: {\n\t\timports: `import { registerCacheDriver } from '@geekmidas/cache';\\nimport { redisCacheDriver, redissCacheDriver } from '@geekmidas/cache/redis';`,\n\t\tsetup:\n\t\t\t'registerCacheDriver(redisCacheDriver);\\nregisterCacheDriver(redissCacheDriver);',\n\t},\n\tdb: {\n\t\timports: `import { registerCacheDriver } from '@geekmidas/cache';\\nimport { postgresCacheDriver } from '@geekmidas/cache/postgres';`,\n\t\tsetup: 'registerCacheDriver(postgresCacheDriver);',\n\t},\n};\n\n/**\n * Everything a generated entry should register: object storage, and the cache.\n *\n * Merged here rather than threaded separately, because a generated file has one\n * import block and one setup block whatever fills them.\n */\nexport function driversFor(options: {\n\tappRoot: string;\n\t/**\n\t * The cache backends actually in play — normally one, and more than one only\n\t * where an app declares two caches that live in different places.\n\t *\n\t * Absent or empty means nothing declared a cache, so no cache driver is\n\t * registered at all.\n\t */\n\tcache?: CacheBackend | readonly CacheBackend[] | false;\n}): RuntimeDrivers {\n\tconst backends =\n\t\toptions.cache === false || options.cache === undefined\n\t\t\t? []\n\t\t\t: typeof options.cache === 'string'\n\t\t\t\t? [options.cache]\n\t\t\t\t: options.cache;\n\n\tconst parts = [\n\t\tstorageDriversFor(options.appRoot),\n\t\t...[...new Set(backends)].map(\n\t\t\t(backend) => CACHE_DRIVERS[backend] ?? CACHE_DRIVERS[DEFAULT_CACHE.aws],\n\t\t),\n\t].filter((part) => part.imports || part.setup);\n\n\tif (parts.length === 0) return NONE;\n\n\treturn {\n\t\timports: parts.map((part) => part.imports).join('\\n'),\n\t\tsetup: parts.map((part) => part.setup).join('\\n'),\n\t};\n}\n\n/**\n * Which cache backends an app's entry actually has to speak.\n *\n * The config answers this for a cache that named nowhere, and the *declaration*\n * answers it for one that named a database — `orders.cache('Sessions')` is a\n * statement about the application, so a deployment cannot move that cache and\n * the entry cannot register a driver for somewhere else.\n *\n * Reading only the config is how an entry ends up registering the Upstash\n * driver for a URL the target composed as `postgres://`, which fails at the\n * first request with `UnregisteredCacheScheme` and a stack that points at the\n * cache rather than at the config that disagreed.\n */\nexport function cacheBackendsIn(\n\tmanifest: Record<string, { kind: string; of?: string }>,\n\tconfigured: CacheBackend,\n): CacheBackend[] {\n\tconst caches = Object.values(manifest).filter((d) => d.kind === 'cache');\n\n\treturn [...new Set(caches.map((c) => (c.of ? 'db' : configured)))];\n}\n\n/**\n * The drivers an app's entry points should register.\n *\n * Keyed off the dependency rather than off a declared bucket, because the entry\n * is generated before discovery has run on the watcher's rebuild path — and an\n * app that installed `@geekmidas/storage` has already paid for the SDK it would\n * otherwise resolve lazily.\n */\nexport function storageDriversFor(appRoot: string): RuntimeDrivers {\n\treturn dependsOnStorage(appRoot) ? S3 : NONE;\n}\n\nfunction dependsOnStorage(appRoot: string): boolean {\n\tconst manifest = join(appRoot, 'package.json');\n\tif (!existsSync(manifest)) return false;\n\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(manifest, 'utf8')) as {\n\t\t\tdependencies?: Record<string, string>;\n\t\t\tdevDependencies?: Record<string, string>;\n\t\t};\n\n\t\treturn Boolean(\n\t\t\tpkg.dependencies?.['@geekmidas/storage'] ??\n\t\t\t\tpkg.devDependencies?.['@geekmidas/storage'],\n\t\t);\n\t} catch {\n\t\t// An unreadable package.json is the build's problem, not this function's.\n\t\treturn false;\n\t}\n}\n","/**\n * What `gkm test` writes for a feature test to be built from.\n *\n * `gkm test` already discovers an app's constructs and resolves the test\n * stage's environment; this keeps both, under `.gkm/test/`:\n *\n * - `manifest.json` — which construct and endpoint is exported where, and the\n * environment the test stage resolved, keyed as the constructs derive keys.\n * - `clients/<surface>.ts` — each surface's typed client, from the same\n * generator `gkm build` uses for the app's own.\n * - `index.ts` — the configured `it`, and a `Browser` holding one client per\n * surface and one per auth server, each on the browser's `fetch`.\n *\n * A test imports `it` from there and declares nothing — not a construct, not an\n * environment key, not a client.\n */\n\nimport { mkdir, readdir, rm, writeFile } from 'node:fs/promises';\nimport { extname, join, relative } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { BetterAuth } from '@geekmidas/constructs/auth';\nimport type { Endpoint } from '@geekmidas/constructs/endpoints';\nimport type { TestManifest } from '@geekmidas/constructs/testing';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\tcanonicalId,\n\tkebabCase,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport { serviceContext } from '@geekmidas/services';\nimport {\n\tcacheBackendsIn,\n\tdriversFor,\n\ttype RuntimeDrivers,\n} from '../generators/drivers.js';\nimport { EndpointGenerator } from '../generators/EndpointGenerator.js';\nimport { OpenApiTsGenerator } from '../generators/OpenApiTsGenerator.js';\nimport { type ConstructSource, discover } from '../reconcile/discover.js';\nimport { readFakes } from '../reconcile/fakes.js';\nimport type { CacheBackend } from '../types.js';\n\n/**\n * The variable the manifest's path travels in — what\n * `@geekmidas/constructs/testing` reads. Its own constant rather than an import:\n * that entry carries the whole harness (MSW, Hono, Vitest), and importing it\n * here put all of it on the start-up path of every `gkm` command.\n */\nexport const TEST_MANIFEST_ENV = 'GKM_TEST_MANIFEST';\n\n/** Where the harness lives, relative to the workspace root. */\nexport const TEST_HARNESS_DIR = '.gkm/test';\n\n/**\n * Better-auth's client plugin for each server plugin id.\n *\n * Written down rather than derived: the ids and the client names do not follow\n * one rule (`admin` pairs with `adminClient`, `email-otp` with\n * `emailOTPClient`), and a guess that missed would hand a test a client\n * silently lacking every method of that plugin. A server plugin not listed has\n * no client counterpart — `bearer`, `open-api` — or needs one from another\n * package, and is left out.\n */\nconst CLIENT_PLUGINS: Readonly<Record<string, string>> = {\n\tadmin: 'adminClient',\n\tanonymous: 'anonymousClient',\n\t'device-authorization': 'deviceAuthorizationClient',\n\t'email-otp': 'emailOTPClient',\n\tjwt: 'jwtClient',\n\t'last-login-method': 'lastLoginMethodClient',\n\t'magic-link': 'magicLinkClient',\n\t'multi-session': 'multiSessionClient',\n\t'oauth-popup': 'oauthPopupClient',\n\t'one-tap': 'oneTapClient',\n\t'one-time-token': 'oneTimeTokenClient',\n\torganization: 'organizationClient',\n\t'phone-number': 'phoneNumberClient',\n\tsiwe: 'siweClient',\n\t'two-factor': 'twoFactorClient',\n\tusername: 'usernameClient',\n};\n\nexport interface WriteTestHarnessOptions {\n\t/** The workspace root: where discovery runs from. */\n\troot: string;\n\t/**\n\t * The directories to write `.gkm/test/` into — each app's own. A package\n\t * maps its `#test` import only to a path inside itself, so each app gets its\n\t * own copy of the harness rather than one at the root it cannot reach.\n\t */\n\ttargets: string[];\n\t/** The constructs glob — the same patterns reconcile and the build use. */\n\tpatterns: string[];\n\t/** The stage the environment was resolved for. */\n\tstage: string;\n\t/** The environment the suite runs with. */\n\tenv: Record<string, string>;\n\t/**\n\t * Where a cache lives when it names nowhere — the target's default, which\n\t * the build's server entry registers a driver for.\n\t */\n\tcacheBackend: CacheBackend;\n\t/**\n\t * The folder of test factories, absolute: `test/factories` at the root, or\n\t * where `test.factories` in the config points. Absent or empty, a test is\n\t * handed no factories.\n\t */\n\tfactories?: string;\n}\n\n/** Where a project keeps its test factories, relative to its root. */\nexport const DEFAULT_FACTORIES_DIR = 'test/factories';\n\n/** A file in the factories folder that names no database construct. */\nexport class UnknownFactoryFile extends Error {\n\tconstructor(\n\t\treadonly file: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${file} names no database construct, so no test could be handed it. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `The databases: ${known.join(', ')}. `\n\t\t\t\t\t: 'This project declares no database. ') +\n\t\t\t\t`Name a factory after its database: test/factories/<construct>.ts.`,\n\t\t);\n\t\tthis.name = 'UnknownFactoryFile';\n\t}\n}\n\n/** A factory file that exports no `createFactory`. */\nexport class FactoryHasNoCreate extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is a test factory but exports no \\`createFactory\\`. ` +\n\t\t\t\t`Export \\`function createFactory(db: Kysely<Database>)\\` returning ` +\n\t\t\t\t`the factory.`,\n\t\t);\n\t\tthis.name = 'FactoryHasNoCreate';\n\t}\n}\n\n/** One database's factory: the file, and the service name a test keys it by. */\nexport interface FactorySource {\n\tfile: string;\n\tservice: string;\n}\n\n/**\n * The factories in a folder, each matched to the database it is named after.\n *\n * A file that names no database, or exports no `createFactory`, is refused\n * rather than skipped: either is a factory no test would ever be handed.\n */\nexport async function factorySources(\n\tfolder: string | undefined,\n\tdatabases: readonly string[],\n): Promise<FactorySource[]> {\n\tif (!folder) return [];\n\n\tconst entries = await readdir(folder, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\tconst found: FactorySource[] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !isFactoryFile(entry.name)) continue;\n\n\t\tconst file = join(folder, entry.name);\n\t\tconst id = canonicalId(entry.name.slice(0, -extname(entry.name).length));\n\t\tif (!databases.includes(id)) {\n\t\t\tthrow new UnknownFactoryFile(file, databases);\n\t\t}\n\n\t\tconst module = (await import(pathToFileURL(file).href)) as {\n\t\t\tcreateFactory?: unknown;\n\t\t};\n\t\tif (typeof module.createFactory !== 'function') {\n\t\t\tthrow new FactoryHasNoCreate(file);\n\t\t}\n\n\t\tfound.push({ file, service: serviceKey(id) });\n\t}\n\n\treturn found.sort((a, b) => (a.service < b.service ? -1 : 1));\n}\n\nfunction isFactoryFile(file: string): boolean {\n\tif (file.endsWith('.d.ts')) return false;\n\tif (/\\.(spec|test)\\.[cm]?[jt]s$/.test(file)) return false;\n\treturn ['.ts', '.mts', '.js', '.mjs'].includes(extname(file));\n}\n\n/** Write the manifest, the clients and the harness; return the manifest path. */\nexport async function writeTestHarness(\n\toptions: WriteTestHarnessOptions,\n): Promise<string> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst declared = await discover({\n\t\tpatterns: options.patterns,\n\t\tcwd: options.root,\n\t\tsources,\n\t});\n\n\tconst loaded = await new EndpointGenerator().load(\n\t\toptions.patterns,\n\t\toptions.root,\n\t);\n\tconst endpoints = loaded\n\t\t.map(({ key, construct, path }) => ({\n\t\t\tsurface: construct.owner ?? construct.surface?.id,\n\t\t\tconstruct,\n\t\t\tsource: { file: path.absolute, export: key },\n\t\t}))\n\t\t.filter(\n\t\t\t(endpoint): endpoint is typeof endpoint & { surface: string } =>\n\t\t\t\tendpoint.surface !== undefined,\n\t\t);\n\n\tconst manifest: TestManifest = {\n\t\tstage: options.stage,\n\t\tconstructs: Object.fromEntries(\n\t\t\tObject.entries(declared).flatMap(([id, declaration]) => {\n\t\t\t\tconst source = sources[id];\n\t\t\t\treturn source\n\t\t\t\t\t? [\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tkind: declaration.kind,\n\t\t\t\t\t\t\t\t\tsource: { file: source.file, export: source.exportName },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t]\n\t\t\t\t\t: [];\n\t\t\t}),\n\t\t),\n\t\tendpoints: endpoints.map(({ surface, source }) => ({ surface, source })),\n\t\tenv: options.env,\n\t};\n\t// One typed client per surface, from the generator the build uses.\n\tconst surfaces = [...new Set(endpoints.map(({ surface }) => surface))];\n\tconst generator = new OpenApiTsGenerator();\n\tconst clients = await Promise.all(\n\t\tsurfaces.map(async (surface) => ({\n\t\t\tfile: `${kebabCase(surface)}.ts`,\n\t\t\tcontent: await generator.generate(\n\t\t\t\tendpoints\n\t\t\t\t\t.filter((endpoint) => endpoint.surface === surface)\n\t\t\t\t\t.map(({ construct }) => construct as Endpoint<any, any, any, any>),\n\t\t\t\t{ title: surface, version: '1.0.0' },\n\t\t\t),\n\t\t})),\n\t);\n\n\tconst auths = await authClients(declared, sources, options.env);\n\t// The drivers the server entry registers — which cache and storage clients\n\t// exist is the entry's decision, and the harness stands where the entry does.\n\tconst drivers = driversFor({\n\t\tappRoot: options.root,\n\t\tcache: cacheBackendsIn(declared, options.cacheBackend),\n\t});\n\n\t// The app's own databases: not a tenant an auth server owns — reached\n\t// through that server — and not a reader of one. What a test is handed.\n\tconst owners = new Set(auths.map((auth) => auth.databaseId));\n\tconst databases = databaseSources(declared, sources).filter(\n\t\t({ id, kind }) => !owners.has(id) && kind !== 'database-reader',\n\t);\n\tconst factories = await factorySources(\n\t\toptions.factories,\n\t\tdatabases.map(({ id }) => id),\n\t);\n\t// Each external API's fake, from the convention rather than the construct —\n\t// which is what keeps a fake out of every deployed bundle.\n\tconst fakes = Object.entries(await readFakes(options.root, declared)).map(\n\t\t([id, { file }]) => ({ id, file }),\n\t);\n\tconst files = [\n\t\t...new Set([\n\t\t\t...Object.values(manifest.constructs).map(({ source }) => source.file),\n\t\t\t...manifest.endpoints.map(({ source }) => source.file),\n\t\t]),\n\t];\n\tconst harnessFor = (dir: string) =>\n\t\tharnessModule({\n\t\t\tdir,\n\t\t\tfiles,\n\t\t\tdatabases,\n\t\t\tsurfaces: surfaces.map((id, index) => ({\n\t\t\t\tid,\n\t\t\t\t// The client the generator writes for a surface with authorizers\n\t\t\t\t// takes a strategy per scheme; one without takes none.\n\t\t\t\tsecured: clients[index]!.content.includes('authStrategies:'),\n\t\t\t})),\n\t\t\tauths,\n\t\t\tdrivers,\n\t\t\tfactories,\n\t\t\tfakes,\n\t\t\tmail: Object.values(declared).some(({ kind }) => kind === 'email'),\n\t\t});\n\tconst json = `${JSON.stringify(manifest, null, 2)}\\n`;\n\n\tconst written = await Promise.all(\n\t\t[...new Set(options.targets)].map(async (target) => {\n\t\t\tconst dir = join(target, TEST_HARNESS_DIR);\n\t\t\t// Rewritten whole on every run: a client for a surface that no longer\n\t\t\t// exists is not something a test should be able to import.\n\t\t\tawait rm(dir, { recursive: true, force: true });\n\t\t\tawait mkdir(join(dir, 'clients'), { recursive: true });\n\t\t\tawait writeFile(join(dir, 'manifest.json'), json);\n\t\t\tawait writeFile(join(dir, 'index.ts'), harnessFor(dir));\n\t\t\tfor (const client of clients) {\n\t\t\t\tawait writeFile(join(dir, 'clients', client.file), client.content);\n\t\t\t}\n\t\t\treturn join(dir, 'manifest.json');\n\t\t}),\n\t);\n\n\treturn written[0]!;\n}\n\ninterface AuthClient {\n\tid: string;\n\tbasePath: string;\n\tplugins: string[];\n\t/** The schema tenant it owns — reached through it, never by a test. */\n\tdatabaseId: string;\n}\n\n/** Each auth server, with the client plugins its server plugins pair with. */\nasync function authClients(\n\tdeclared: Awaited<ReturnType<typeof discover>>,\n\tsources: Record<string, ConstructSource>,\n\tenv: Record<string, string>,\n): Promise<AuthClient[]> {\n\tconst clients: AuthClient[] = [];\n\tconst envParser = new EnvironmentParser({ ...env });\n\n\tfor (const [id, declaration] of Object.entries(declared)) {\n\t\tconst source = sources[id];\n\t\tif (declaration.kind !== 'rest-api' || !source) continue;\n\n\t\tconst exported = (await import(source.file))[source.exportName] as unknown;\n\t\tif (!isBetterAuth(exported) || exported.id !== id) continue;\n\n\t\tconst ids = await exported.pluginIds({\n\t\t\tenvParser,\n\t\t\tcontext: serviceContext,\n\t\t});\n\t\tclients.push({\n\t\t\tid,\n\t\t\tbasePath: exported.basePath,\n\t\t\tplugins: ids.flatMap((plugin) => CLIENT_PLUGINS[plugin] ?? []),\n\t\t\tdatabaseId: exported.databaseId,\n\t\t});\n\t}\n\n\treturn clients;\n}\n\nfunction isBetterAuth(value: unknown): value is BetterAuth {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'pluginIds' in value &&\n\t\t'basePath' in value\n\t);\n}\n\n/** `Api` → `api`, `AdminApi` → `adminApi`: a construct id as a property name. */\nfunction propertyOf(id: string): string {\n\treturn id.charAt(0).toLowerCase() + id.slice(1);\n}\n\n/**\n * The database a test's `db` is — the one the endpoints were given with\n * `.database()`, found by the service they hold, which is the construct's own.\n * The same rule `featureTest` applies at runtime, applied here so `db` can be\n * typed by the construct's schema.\n */\n/** A database construct: its id, its service name, and where it is exported. */\ninterface DatabaseSource {\n\tid: string;\n\tkind: string;\n\tservice: string;\n\tsource: ConstructSource;\n}\n\n/**\n * Every database construct the app declares — a database, a schema tenant, a\n * reader — keyed as a test is handed its transaction.\n */\nfunction databaseSources(\n\tdeclared: Awaited<ReturnType<typeof discover>>,\n\tsources: Record<string, ConstructSource>,\n): DatabaseSource[] {\n\treturn Object.entries(declared)\n\t\t.filter(([id, declaration]) =>\n\t\t\tBoolean(sources[id] && declaration.kind.startsWith('database')),\n\t\t)\n\t\t.map(([id, declaration]) => ({\n\t\t\tid,\n\t\t\tkind: declaration.kind,\n\t\t\tservice: serviceKey(id),\n\t\t\tsource: sources[id]!,\n\t\t}));\n}\n\n/** An import specifier for `file`, from the harness written into `dir`. */\nfunction specifierFrom(dir: string, file: string): string {\n\tconst path = relative(dir, file).replace(/\\.tsx?$/, '.js');\n\treturn path.startsWith('.') ? path : `./${path}`;\n}\n\nfunction harnessModule(options: {\n\tdir: string;\n\t/** Every module the manifest points at, by absolute path. */\n\tfiles: string[];\n\tdatabases: DatabaseSource[];\n\tsurfaces: { id: string; secured: boolean }[];\n\tauths: AuthClient[];\n\tdrivers: RuntimeDrivers;\n\tfactories: FactorySource[];\n\t/** Each external API's fake, by construct id and the file it lives in. */\n\tfakes: { id: string; file: string }[];\n\t/** Whether the app sends mail — what a magic-link sign-in is read from. */\n\tmail: boolean;\n}): string {\n\tconst {\n\t\tsurfaces,\n\t\tauths,\n\t\tdrivers,\n\t\tdatabases,\n\t\tdir,\n\t\tfiles,\n\t\tfactories,\n\t\tfakes,\n\t\tmail,\n\t} = options;\n\t// One auth server a test can sign in to without a person: a magic link,\n\t// read from the app's inbox. With two, which one \\`signIn\\` means is a\n\t// guess, so neither gets it.\n\tconst magicLinks = mail\n\t\t? auths.filter(({ plugins }) => plugins.includes('magicLinkClient'))\n\t\t: [];\n\tconst signIn = magicLinks.length === 1 ? magicLinks[0] : undefined;\n\tconst plugins = [...new Set(auths.flatMap(({ plugins }) => plugins))].sort();\n\n\tconst imports = [\n\t\t`import {${databases.length ? ' type DatabaseOf,' : ''} featureTest, loadTestManifest${signIn ? ', signInWithMagicLink' : ''} } from '@geekmidas/constructs/testing';`,\n\t\t...databases.map(\n\t\t\t({ id, source }) =>\n\t\t\t\t`import type { ${source.exportName} as __${id} } from '${specifierFrom(dir, source.file)}';`,\n\t\t),\n\t\t`import { Browser as TestBrowser } from '@geekmidas/testkit/browser';`,\n\t\t...surfaces.map(\n\t\t\t({ id }) =>\n\t\t\t\t`import { createApi as create${id} } from './clients/${kebabCase(id)}.js';`,\n\t\t),\n\t\t...(auths.length\n\t\t\t? [`import { createAuthClient } from 'better-auth/client';`]\n\t\t\t: []),\n\t\t...(plugins.length\n\t\t\t? [`import { ${plugins.join(', ')} } from 'better-auth/client/plugins';`]\n\t\t\t: []),\n\t\t...(drivers.imports ? [drivers.imports] : []),\n\t\t...factories.map(\n\t\t\t({ file, service }) =>\n\t\t\t\t`import { createFactory as __${service}Factory } from '${specifierFrom(dir, file)}';`,\n\t\t),\n\t\t...fakes.map(\n\t\t\t({ id, file }) =>\n\t\t\t\t`import __${id}Fake from '${specifierFrom(dir, file)}';`,\n\t\t),\n\t\t// Every construct and endpoint module, imported here — inside the app,\n\t\t// where its tsconfig paths resolve — rather than by path from the kit.\n\t\t...files.map(\n\t\t\t(file, index) =>\n\t\t\t\t`import * as __module${index} from '${specifierFrom(dir, file)}';`,\n\t\t),\n\t];\n\n\tconst url = (id: string) =>\n\t\t`manifest.env[${JSON.stringify(provideKey(id, 'url'))}]!`;\n\n\tconst members = [\n\t\t...surfaces.map(\n\t\t\t({ id, secured }) => `\t/** \\`${id}\\`, at ${provideKey(id, 'url')}. */\n\treadonly ${propertyOf(id)} = create${id}({\n\t\tbaseURL: ${url(id)},\n\t\tfetch: this.fetch,${\n\t\t\tsecured\n\t\t\t\t? `\n\t\t// Served in-process: no authorizer runs, so nothing is signed. A scheme\n\t\t// with no strategy sends its request without credentials.\n\t\tauthStrategies: {} as never,`\n\t\t\t\t: ''\n\t\t}\n\t});`,\n\t\t),\n\t\t...auths.map(\n\t\t\t(auth) => `\t/** \\`${auth.id}\\`, at ${provideKey(auth.id, 'url')}. */\n\treadonly ${propertyOf(auth.id)} = createAuthClient({\n\t\tbaseURL: \\`\\${${url(auth.id)}}${auth.basePath}\\`,\n\t\tplugins: [${auth.plugins.map((plugin) => `${plugin}()`).join(', ')}],\n\t\tfetchOptions: { customFetchImpl: this.fetch },\n\t});`,\n\t\t),\n\t\t...(signIn\n\t\t\t? [\n\t\t\t\t\t`\t/**\n\t * Sign in as \\`email\\` through \\`${signIn.id}\\`'s magic link, the way a person\n\t * does — the email opened, the link followed. The session it then reports.\n\t * Without an address, as somebody new: \\`user.email\\` says who.\n\t */\n\tsignIn(email?: string) {\n\t\treturn signInWithMagicLink(this, this.${propertyOf(signIn.id)}, email);\n\t}`,\n\t\t\t\t]\n\t\t\t: []),\n\t];\n\n\t// Each database's factory, keyed as the test is handed it.\n\tconst factoriesType = factories.length\n\t\t? `{ ${factories.map(({ service }) => `${service}: typeof __${service}Factory`).join('; ')} }`\n\t\t: '';\n\tconst factoriesOption = factories.length\n\t\t? `, factories: { ${factories.map(({ service }) => `${service}: __${service}Factory`).join(', ')} }`\n\t\t: '';\n\tconst fakesOption = fakes.length\n\t\t? `, fakes: { ${fakes.map(({ id }) => `${id}: __${id}Fake`).join(', ')} }`\n\t\t: '';\n\t// Each database's schema, keyed as the test is handed its transaction.\n\tconst databasesType = `{ ${databases.map(({ id, service }) => `${service}: DatabaseOf<typeof __${id}>`).join('; ')} }`;\n\tconst generics = databases.length\n\t\t? `<Browser, ${databasesType}${factoriesType ? `, ${factoriesType}` : ''}>`\n\t\t: '';\n\n\treturn `// Generated by \\`gkm test\\` from the app's constructs — do not edit.\n${imports.join('\\n')}\n\n${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));\n\n/** A browser with a client for every surface the app declares. */\nexport class Browser extends TestBrowser {\n${members.join('\\n\\n')}\n}\n\n/** The app's modules, by the path the manifest records for each. */\nconst modules = {\n${files.map((file, index) => `\\t${JSON.stringify(file)}: __module${index},`).join('\\n')}\n};\n\n/** \\`it\\`, for a test that drives the app the way it runs deployed. */\nexport const it = featureTest${generics}({ manifest, modules, browser: Browser${factoriesOption}${fakesOption} });\n`;\n}\n","/**\n * The hand-off between `gkm test` and the Vitest global setup.\n *\n * `gkm test` reconciles and migrates the test stage before Vitest starts; the\n * setup, run inside Vitest, only has to load what that left. Started by plain\n * `vitest` instead, the setup runs `gkm test --setup` itself first — so there is\n * one way the stage gets ready, whichever started the suite.\n */\n\n/** Set on the Vitest process by `gkm test`: the stage is ready, here. */\nexport const TEST_READY_ENV = 'GKM_TEST_READY';\n\n/** Where `gkm test` records what it prepared, from where it ran. */\nexport const TEST_READY_FILE = '.gkm/test-ready.json';\n\nexport interface TestReady {\n\t/** The resolved environment, as a JSON object of strings. */\n\tenv: string;\n\t/** The test manifest the `#test` harness reads. */\n\tmanifest?: string;\n\t/**\n\t * The test stage's databases this run created, and the port of the\n\t * Postgres that holds them: what `gkm test --teardown` drops when the\n\t * suite ends — or, if a run was killed before its teardown, what the next\n\t * one drops before it starts.\n\t */\n\tdatabases?: { port: number; names: string[] };\n}\n"],"mappings":"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"}