@geekmidas/cli 10.0.0-alpha.104 → 10.0.0-alpha.106

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 (109) hide show
  1. package/dist/{GoDaddyProvider-dezqtUth.cjs → GoDaddyProvider-BesL1P15.cjs} +2 -2
  2. package/dist/{GoDaddyProvider-dezqtUth.cjs.map → GoDaddyProvider-BesL1P15.cjs.map} +1 -1
  3. package/dist/{GoDaddyProvider-CuPEbbjl.mjs → GoDaddyProvider-DufGBcDt.mjs} +2 -2
  4. package/dist/{GoDaddyProvider-CuPEbbjl.mjs.map → GoDaddyProvider-DufGBcDt.mjs.map} +1 -1
  5. package/dist/{LegacyStateStore-Bc8FnHEB.mjs → LegacyStateStore-1kOwqcd_.mjs} +3 -3
  6. package/dist/{LegacyStateStore-Bc8FnHEB.mjs.map → LegacyStateStore-1kOwqcd_.mjs.map} +1 -1
  7. package/dist/{LegacyStateStore-ecxYpm9-.cjs → LegacyStateStore-aFp6-7im.cjs} +3 -3
  8. package/dist/{LegacyStateStore-ecxYpm9-.cjs.map → LegacyStateStore-aFp6-7im.cjs.map} +1 -1
  9. package/dist/{LocalStateStore-wSKMDDh5.mjs → LocalStateStore-DFGsiLDy.mjs} +2 -2
  10. package/dist/{LocalStateStore-wSKMDDh5.mjs.map → LocalStateStore-DFGsiLDy.mjs.map} +1 -1
  11. package/dist/{LocalStateStore-Sf4B6kUn.cjs → LocalStateStore-Fo33UPTI.cjs} +2 -2
  12. package/dist/{LocalStateStore-Sf4B6kUn.cjs.map → LocalStateStore-Fo33UPTI.cjs.map} +1 -1
  13. package/dist/{S3StateStore-BPlVpTfg.mjs → S3StateStore-BlwEPivO.mjs} +2 -2
  14. package/dist/{S3StateStore-BPlVpTfg.mjs.map → S3StateStore-BlwEPivO.mjs.map} +1 -1
  15. package/dist/{S3StateStore-DQYjsYBl.cjs → S3StateStore-ZWXXuNQp.cjs} +2 -2
  16. package/dist/{S3StateStore-DQYjsYBl.cjs.map → S3StateStore-ZWXXuNQp.cjs.map} +1 -1
  17. package/dist/{SSMStateStore-P9OMZHkp.cjs → SSMStateStore-D5ej5j5M.cjs} +2 -2
  18. package/dist/{SSMStateStore-P9OMZHkp.cjs.map → SSMStateStore-D5ej5j5M.cjs.map} +1 -1
  19. package/dist/{SSMStateStore-De2PY12p.mjs → SSMStateStore-DG9h2iVY.mjs} +2 -2
  20. package/dist/{SSMStateStore-De2PY12p.mjs.map → SSMStateStore-DG9h2iVY.mjs.map} +1 -1
  21. package/dist/{add-Dgdsb3ox.mjs → add-1VSxmKnf.mjs} +3 -3
  22. package/dist/{add-Dgdsb3ox.mjs.map → add-1VSxmKnf.mjs.map} +1 -1
  23. package/dist/{add-C_lp7-oD.cjs → add-B9IgkvTU.cjs} +3 -3
  24. package/dist/{add-C_lp7-oD.cjs.map → add-B9IgkvTU.cjs.map} +1 -1
  25. package/dist/{appPorts-C9z9Pzn9.mjs → appPorts-BoXMwolc.mjs} +32 -6
  26. package/dist/appPorts-BoXMwolc.mjs.map +1 -0
  27. package/dist/{appPorts-Cu79hJF6.cjs → appPorts-IW-OZmRi.cjs} +37 -5
  28. package/dist/appPorts-IW-OZmRi.cjs.map +1 -0
  29. package/dist/{bundler-DBTA8gXs.cjs → bundler-BBDrSnvE.cjs} +5 -5
  30. package/dist/{bundler-DBTA8gXs.cjs.map → bundler-BBDrSnvE.cjs.map} +1 -1
  31. package/dist/{bundler-DVPoeMRa.mjs → bundler-By29I7km.mjs} +5 -5
  32. package/dist/{bundler-DVPoeMRa.mjs.map → bundler-By29I7km.mjs.map} +1 -1
  33. package/dist/{cli-D7FyFbeC.cjs → cli-Btfl8J6K.cjs} +2 -2
  34. package/dist/{cli-D7FyFbeC.cjs.map → cli-Btfl8J6K.cjs.map} +1 -1
  35. package/dist/{cli-CGXOvWRP.mjs → cli-D-ZAyAub.mjs} +2 -2
  36. package/dist/{cli-CGXOvWRP.mjs.map → cli-D-ZAyAub.mjs.map} +1 -1
  37. package/dist/{compose-B8ND6s9C.mjs → compose-C-lnkCAT.mjs} +3 -3
  38. package/dist/{compose-B8ND6s9C.mjs.map → compose-C-lnkCAT.mjs.map} +1 -1
  39. package/dist/{compose-qbPKKAMC.cjs → compose-daUOp1bm.cjs} +3 -3
  40. package/dist/{compose-qbPKKAMC.cjs.map → compose-daUOp1bm.cjs.map} +1 -1
  41. package/dist/deploy/public.cjs +6 -6
  42. package/dist/deploy/public.mjs +6 -6
  43. package/dist/{deploy-yLGEWuam.mjs → deploy-T3TwqzKf.mjs} +5 -5
  44. package/dist/{deploy-yLGEWuam.mjs.map → deploy-T3TwqzKf.mjs.map} +1 -1
  45. package/dist/{deploy-Bsxl6nyG.cjs → deploy-V43Rbboj.cjs} +5 -5
  46. package/dist/{deploy-Bsxl6nyG.cjs.map → deploy-V43Rbboj.cjs.map} +1 -1
  47. package/dist/{dns-BQrhafpk.mjs → dns-AosA2wwP.mjs} +2 -2
  48. package/dist/{dns-BQrhafpk.mjs.map → dns-AosA2wwP.mjs.map} +1 -1
  49. package/dist/{dns-BUUEztBH.cjs → dns-BfNzYlpR.cjs} +2 -2
  50. package/dist/{dns-BUUEztBH.cjs.map → dns-BfNzYlpR.cjs.map} +1 -1
  51. package/dist/{dokploy-CCnoXmGV.cjs → dokploy-COnyBUBW.cjs} +2 -2
  52. package/dist/{dokploy-CCnoXmGV.cjs.map → dokploy-COnyBUBW.cjs.map} +1 -1
  53. package/dist/{dokploy-BuIvCaE1.mjs → dokploy-CaLl5YmO.mjs} +2 -2
  54. package/dist/{dokploy-BuIvCaE1.mjs.map → dokploy-CaLl5YmO.mjs.map} +1 -1
  55. package/dist/{encryption-BFs3ItV9.mjs → encryption-BqSWtrXU.mjs} +2 -2
  56. package/dist/{encryption-BFs3ItV9.mjs.map → encryption-BqSWtrXU.mjs.map} +1 -1
  57. package/dist/{encryption-2KOlWvfq.cjs → encryption-CJdgXVFM.cjs} +2 -2
  58. package/dist/{encryption-2KOlWvfq.cjs.map → encryption-CJdgXVFM.cjs.map} +1 -1
  59. package/dist/{engine-vvr2EpTb.mjs → engine-BMM2Vy26.mjs} +3 -3
  60. package/dist/{engine-vvr2EpTb.mjs.map → engine-BMM2Vy26.mjs.map} +1 -1
  61. package/dist/{engine-Bx_ea2_k.cjs → engine-DjI9DFDW.cjs} +3 -3
  62. package/dist/{engine-Bx_ea2_k.cjs.map → engine-DjI9DFDW.cjs.map} +1 -1
  63. package/dist/{github-BM6afosB.cjs → github-MYWre3_E.cjs} +4 -4
  64. package/dist/{github-BM6afosB.cjs.map → github-MYWre3_E.cjs.map} +1 -1
  65. package/dist/{github-DuXBKTvr.mjs → github-SViQU_St.mjs} +4 -4
  66. package/dist/{github-DuXBKTvr.mjs.map → github-SViQU_St.mjs.map} +1 -1
  67. package/dist/index.cjs +50 -50
  68. package/dist/index.cjs.map +1 -1
  69. package/dist/index.mjs +50 -50
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/{migrate-CQal4P1q.mjs → migrate-BNyMi8h5.mjs} +2 -2
  72. package/dist/{migrate-CQal4P1q.mjs.map → migrate-BNyMi8h5.mjs.map} +1 -1
  73. package/dist/{migrate-DXVzuw-s.cjs → migrate-CNQp-SAH.cjs} +2 -2
  74. package/dist/{migrate-DXVzuw-s.cjs.map → migrate-CNQp-SAH.cjs.map} +1 -1
  75. package/dist/{migrate-BQfq48xK.cjs → migrate-DwsOYzmN.cjs} +2 -2
  76. package/dist/{migrate-BQfq48xK.cjs.map → migrate-DwsOYzmN.cjs.map} +1 -1
  77. package/dist/{migrate-BlLFYSIx.mjs → migrate-H2vGmQ3l.mjs} +2 -2
  78. package/dist/{migrate-BlLFYSIx.mjs.map → migrate-H2vGmQ3l.mjs.map} +1 -1
  79. package/dist/{ready-DU7wvxWk.cjs → ready-BMFjxBYA.cjs} +3 -2
  80. package/dist/ready-BMFjxBYA.cjs.map +1 -0
  81. package/dist/{ready-CVNE5Pix.mjs → ready-CIt5SOtg.mjs} +3 -2
  82. package/dist/ready-CIt5SOtg.mjs.map +1 -0
  83. package/dist/{resolve-De9vfAnN.mjs → resolve-C2vVcD64.mjs} +3 -3
  84. package/dist/{resolve-De9vfAnN.mjs.map → resolve-C2vVcD64.mjs.map} +1 -1
  85. package/dist/{resolve-By_cw893.cjs → resolve-ClAnYHo-.cjs} +3 -3
  86. package/dist/{resolve-By_cw893.cjs.map → resolve-ClAnYHo-.cjs.map} +1 -1
  87. package/dist/{rollback-YCj4_q9l.mjs → rollback-BrBL9j9s.mjs} +4 -4
  88. package/dist/{rollback-YCj4_q9l.mjs.map → rollback-BrBL9j9s.mjs.map} +1 -1
  89. package/dist/{rollback-dkinZaOv.cjs → rollback-CRVwGkEl.cjs} +4 -4
  90. package/dist/{rollback-dkinZaOv.cjs.map → rollback-CRVwGkEl.cjs.map} +1 -1
  91. package/dist/{seeds-DZGuovWF.mjs → seeds-C1v4ZOro.mjs} +42 -12
  92. package/dist/seeds-C1v4ZOro.mjs.map +1 -0
  93. package/dist/{seeds-CjBwLvWY.cjs → seeds-RTQPjA4K.cjs} +41 -11
  94. package/dist/seeds-RTQPjA4K.cjs.map +1 -0
  95. package/dist/{sniffer-bo0KeZsK.mjs → sniffer-CLJ3cB3b.mjs} +6 -6
  96. package/dist/{sniffer-bo0KeZsK.mjs.map → sniffer-CLJ3cB3b.mjs.map} +1 -1
  97. package/dist/{sniffer-t0NsMNNP.cjs → sniffer-Cy1pb4hg.cjs} +6 -6
  98. package/dist/{sniffer-t0NsMNNP.cjs.map → sniffer-Cy1pb4hg.cjs.map} +1 -1
  99. package/dist/target/public.cjs +1 -1
  100. package/dist/target/public.mjs +1 -1
  101. package/dist/vitest.cjs +1 -1
  102. package/dist/vitest.mjs +1 -1
  103. package/package.json +15 -15
  104. package/dist/appPorts-C9z9Pzn9.mjs.map +0 -1
  105. package/dist/appPorts-Cu79hJF6.cjs.map +0 -1
  106. package/dist/ready-CVNE5Pix.mjs.map +0 -1
  107. package/dist/ready-DU7wvxWk.cjs.map +0 -1
  108. package/dist/seeds-CjBwLvWY.cjs.map +0 -1
  109. package/dist/seeds-DZGuovWF.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_define = require("../define-crmMcXFS.cjs");
3
- const require_resolve = require("../resolve-By_cw893.cjs");
3
+ const require_resolve = require("../resolve-ClAnYHo-.cjs");
4
4
  const require_provider = require("../provider-BqpjG9QM.cjs");
5
5
  exports.DeployTargetNotYetSupported = require_define.DeployTargetNotYetSupported;
6
6
  exports.InvalidTargetOptions = require_resolve.InvalidTargetOptions;
@@ -1,4 +1,4 @@
1
1
  import { a as UnknownDeployTarget, i as DeployTargetNotYetSupported, n as isDeployTarget, t as defineTarget } from "../define-6poNqm5O.mjs";
2
- import { i as TargetRuntimeMismatch, l as TargetPackageNotFound, n as TargetEntryInvalid, r as TargetPackageInvalid, s as TargetRuntimeUndeclared, t as InvalidTargetOptions } from "../resolve-De9vfAnN.mjs";
2
+ import { i as TargetRuntimeMismatch, l as TargetPackageNotFound, n as TargetEntryInvalid, r as TargetPackageInvalid, s as TargetRuntimeUndeclared, t as InvalidTargetOptions } from "../resolve-C2vVcD64.mjs";
3
3
  import { t as ProviderRemoved } from "../provider-CEOKk8NS.mjs";
4
4
  export { DeployTargetNotYetSupported, InvalidTargetOptions, ProviderRemoved, TargetEntryInvalid, TargetPackageInvalid, TargetPackageNotFound, TargetRuntimeMismatch, TargetRuntimeUndeclared, UnknownDeployTarget, defineTarget, isDeployTarget };
package/dist/vitest.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_ready = require("./ready-DU7wvxWk.cjs");
1
+ const require_ready = require("./ready-BMFjxBYA.cjs");
2
2
  let node_path = require("node:path");
3
3
  let node_fs = require("node:fs");
4
4
  let node_child_process = require("node:child_process");
package/dist/vitest.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as TEST_MANIFEST_ENV, n as TEST_READY_FILE } from "./ready-CVNE5Pix.mjs";
1
+ import { i as TEST_MANIFEST_ENV, n as TEST_READY_FILE } from "./ready-CIt5SOtg.mjs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { execFileSync } from "node:child_process";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cli",
3
- "version": "10.0.0-alpha.104",
3
+ "version": "10.0.0-alpha.106",
4
4
  "description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
5
5
  "private": false,
6
6
  "type": "module",
@@ -120,16 +120,16 @@
120
120
  "prompts": "~2.4.2",
121
121
  "tsx": "~4.23.15",
122
122
  "yaml": "~2.9.1",
123
- "@geekmidas/cache": "10.0.0-alpha.104",
124
- "@geekmidas/constructs": "~10.0.0-alpha.104",
125
- "@geekmidas/db": "10.0.0-alpha.104",
126
- "@geekmidas/envkit": "~10.0.0-alpha.104",
127
- "@geekmidas/errors": "~10.0.0-alpha.104",
128
- "@geekmidas/logger": "~10.0.0-alpha.104",
129
- "@geekmidas/manifest": "10.0.0-alpha.104",
130
- "@geekmidas/schema": "~10.0.0-alpha.104",
131
- "@geekmidas/services": "~10.0.0-alpha.104",
132
- "@geekmidas/storage": "~10.0.0-alpha.104"
123
+ "@geekmidas/cache": "10.0.0-alpha.106",
124
+ "@geekmidas/constructs": "~10.0.0-alpha.106",
125
+ "@geekmidas/db": "10.0.0-alpha.106",
126
+ "@geekmidas/envkit": "~10.0.0-alpha.106",
127
+ "@geekmidas/errors": "~10.0.0-alpha.106",
128
+ "@geekmidas/logger": "~10.0.0-alpha.106",
129
+ "@geekmidas/manifest": "10.0.0-alpha.106",
130
+ "@geekmidas/schema": "~10.0.0-alpha.106",
131
+ "@geekmidas/services": "~10.0.0-alpha.106",
132
+ "@geekmidas/storage": "~10.0.0-alpha.106"
133
133
  },
134
134
  "devDependencies": {
135
135
  "@hono/node-server": "~1.19.5",
@@ -144,12 +144,12 @@
144
144
  "typescript": "~7.0.2",
145
145
  "vitest": "~5.0.2",
146
146
  "zod": "~4.6.5",
147
- "@geekmidas/emailkit": "~10.0.0-alpha.104",
148
- "@geekmidas/events": "~10.0.0-alpha.104",
149
- "@geekmidas/testkit": "10.0.0-alpha.104"
147
+ "@geekmidas/emailkit": "~10.0.0-alpha.106",
148
+ "@geekmidas/events": "~10.0.0-alpha.106",
149
+ "@geekmidas/testkit": "10.0.0-alpha.106"
150
150
  },
151
151
  "peerDependencies": {
152
- "@geekmidas/telescope": "~10.0.0-alpha.104"
152
+ "@geekmidas/telescope": "~10.0.0-alpha.106"
153
153
  },
154
154
  "peerDependenciesMeta": {
155
155
  "@geekmidas/telescope": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"appPorts-C9z9Pzn9.mjs","names":[],"sources":["../src/dev/appPorts.ts"],"sourcesContent":["/**\n * Which local port each app answers on — and what to do when it is taken.\n *\n * An app's port used to be fixed: 3000, 3001, … and `gkm dev` refused to start\n * if anything held one. That is right when the holder is this same app, left\n * running by a previous `gkm dev` — moving would start a second copy beside it.\n * It is wrong when the holder is another project's dev server, which also\n * defaults to 3000: two projects could not run at once, and nothing about the\n * app needs that port. A browser reaches it through the edge, whose URL carries\n * no app port, and every address a phone or a route uses is derived on each\n * start.\n *\n * So the holder is asked who it is. Every app process gkm starts is tagged with\n * {@link APP_TAG_ENV} — the workspace and the app — and children inherit it, so\n * whatever actually binds the port (`next-server`, a Metro worker, the tsx\n * server) carries it — or, when its own can't be read, one of its parents does.\n * A held port whose holder carries this app's tag is a\n * leftover, and is refused; anything else — untagged, another workspace's,\n * unreadable — is not ours, and the app moves to the next free port.\n *\n * The choice is kept in `.gkm/app-ports.json`, not recomputed: an app that\n * moved stays where it moved, so a bookmark or a phone's saved address does not\n * break the next time the other project happens to be stopped. Local only —\n * `gkm deploy` and `gkm docker` never read it.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/** Carried by every app process gkm starts: `<workspace root>#<app>`. */\nexport const APP_TAG_ENV = 'GKM_DEV_APP';\n\nconst APP_PORTS_PATH = '.gkm/app-ports.json';\n\n/** The tag an app's processes carry. The root is encoded, so it has no spaces. */\nexport function appTag(root: string, app: string): string {\n\treturn `${encodeURIComponent(resolve(root))}#${app}`;\n}\n\n/** What listens on a port, and the tag it carries if it can be read. */\nexport interface PortHolder {\n\tpid: number;\n\tcommand?: string;\n\ttag?: string;\n}\n\n/** Says, as `command (pid N)`, what holds a port. */\nexport function describeHolder(holder: PortHolder | undefined): string {\n\tif (!holder) return 'another process';\n\treturn `${holder.command ?? 'a process'} (pid ${holder.pid})`;\n}\n\n/**\n * Who is listening on a port, read the way a person would: `lsof` for the\n * pid, then that process's environment for its tag — `/proc/<pid>/environ` on\n * Linux, `ps eww` elsewhere. Both work for the user's own processes, which is\n * every process gkm starts; one that cannot be read has no tag, so is not ours.\n */\nexport function holderOf(port: number): PortHolder | undefined {\n\tlet out: string;\n\ttry {\n\t\tout = execFileSync(\n\t\t\t'lsof',\n\t\t\t['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-Fpc'],\n\t\t\t{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },\n\t\t);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst pid = Number(out.match(/^p(\\d+)/m)?.[1]);\n\tif (!pid) return undefined;\n\tconst command = out.match(/^c(.+)$/m)?.[1];\n\tconst tag = lineageTagOf(pid);\n\n\treturn {\n\t\tpid,\n\t\t...(command ? { command } : {}),\n\t\t...(tag ? { tag } : {}),\n\t};\n}\n\n/** How far up a holder's parents to look before calling it untagged. */\nconst MAX_ANCESTORS = 8;\n\n/**\n * The tag of `pid`, or of its nearest parent that has one.\n *\n * A process can hide the tag it inherited: `next-server` renames itself, which\n * on macOS overwrites the memory `ps eww` reads its environment from, so `ps`\n * shows only its title. Its parent, the `next dev` gkm started, still carries\n * the tag. A child is only ever started by its parent, so a tagged parent makes\n * the holder this app's.\n */\nfunction lineageTagOf(pid: number): string | undefined {\n\tlet current: number | undefined = pid;\n\tfor (let depth = 0; current && current > 1; depth += 1) {\n\t\tconst tag = tagOf(current);\n\t\tif (tag || depth === MAX_ANCESTORS) return tag;\n\t\tcurrent = parentOf(current);\n\t}\n\treturn undefined;\n}\n\nfunction tagOf(pid: number): string | undefined {\n\tconst prefix = `${APP_TAG_ENV}=`;\n\ttry {\n\t\tconst entries =\n\t\t\tprocess.platform === 'linux'\n\t\t\t\t? readFileSync(`/proc/${pid}/environ`, 'utf8').split('\\0')\n\t\t\t\t: execFileSync('ps', ['eww', '-o', 'command=', '-p', String(pid)], {\n\t\t\t\t\t\tencoding: 'utf8',\n\t\t\t\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t\t\t\t}).split(/\\s+/);\n\t\treturn entries\n\t\t\t.find((entry) => entry.startsWith(prefix))\n\t\t\t?.slice(prefix.length);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction parentOf(pid: number): number | undefined {\n\ttry {\n\t\tconst ppid = Number(\n\t\t\texecFileSync('ps', ['-o', 'ppid=', '-p', String(pid)], {\n\t\t\t\tencoding: 'utf8',\n\t\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t\t}).trim(),\n\t\t);\n\t\treturn ppid > 0 ? ppid : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** The ports apps were given before, by app name. */\nexport async function savedAppPorts(\n\troot: string,\n): Promise<Record<string, number>> {\n\ttry {\n\t\treturn JSON.parse(await readFile(join(root, APP_PORTS_PATH), 'utf8'));\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nasync function saveAppPorts(\n\troot: string,\n\tports: Record<string, number>,\n): Promise<void> {\n\tawait mkdir(join(root, '.gkm'), { recursive: true });\n\tawait writeFile(\n\t\tjoin(root, APP_PORTS_PATH),\n\t\t`${JSON.stringify(ports, null, 2)}\\n`,\n\t);\n}\n\n/** The workspace, with each app on the port it was given locally. */\nexport function withAppPorts(\n\tworkspace: NormalizedWorkspace,\n\tports: Readonly<Record<string, number>>,\n): NormalizedWorkspace {\n\tconst apps = Object.fromEntries(\n\t\tObject.entries(workspace.apps).map(([name, app]) => [\n\t\t\tname,\n\t\t\tports[name] ? { ...app, port: ports[name] } : app,\n\t\t]),\n\t);\n\treturn { ...workspace, apps };\n}\n\n/** An app this workspace already has running — left by a previous start. */\nexport interface AppRunning {\n\tapp: string;\n\tport: number;\n\tholder: PortHolder;\n}\n\n/** An app that moved, because another project holds its port. */\nexport interface AppMoved {\n\tapp: string;\n\tfrom: number;\n\tto: number;\n\tholder: PortHolder | undefined;\n}\n\nexport interface AppPortsDeps {\n\t/** Whether a port can be bound right now. */\n\tfree: (port: number) => Promise<boolean>;\n\t/** Who holds a port. */\n\tholder: (port: number) => PortHolder | undefined;\n}\n\n/**\n * Give each named app a local port: the one it had, or its usual one, unless\n * another project holds it — then the next free one, which is kept.\n *\n * @returns every app's port, the apps that moved, and the apps this workspace\n * already has running (the caller refuses to start those).\n */\nexport async function assignAppPorts(\n\tworkspace: NormalizedWorkspace,\n\tnames: readonly string[],\n\tdeps: AppPortsDeps,\n): Promise<{\n\tports: Record<string, number>;\n\tmoved: AppMoved[];\n\trunning: AppRunning[];\n}> {\n\tconst saved = await savedAppPorts(workspace.root);\n\tconst wanted: Record<string, number> = {};\n\tfor (const name of names) {\n\t\tconst port = saved[name] ?? workspace.apps[name]?.port;\n\t\tif (port) wanted[name] = port;\n\t}\n\n\t// Every app's wanted port is reserved before any moves, so one that moves\n\t// never lands on a sibling's.\n\tconst taken = new Set([...Object.values(saved), ...Object.values(wanted)]);\n\tconst ports: Record<string, number> = { ...saved };\n\tconst moved: AppMoved[] = [];\n\tconst running: AppRunning[] = [];\n\n\t// Sorted, so which app moves first — and where — does not depend on order.\n\tfor (const name of Object.keys(wanted).sort()) {\n\t\tconst want = wanted[name]!;\n\t\tports[name] = want;\n\t\tif (await deps.free(want)) continue;\n\n\t\tconst holder = deps.holder(want);\n\t\tif (holder?.tag === appTag(workspace.root, name)) {\n\t\t\trunning.push({ app: name, port: want, holder });\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet next = want + 1;\n\t\twhile (taken.has(next) || !(await deps.free(next))) next += 1;\n\t\ttaken.add(next);\n\t\tports[name] = next;\n\t\tmoved.push({ app: name, from: want, to: next, holder });\n\t}\n\n\tawait saveAppPorts(workspace.root, ports);\n\treturn { ports, moved, running };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAa,cAAc;AAE3B,MAAM,iBAAiB;;AAGvB,SAAgB,OAAO,MAAc,KAAqB;CACzD,OAAO,GAAG,mBAAmB,QAAQ,IAAI,CAAC,EAAE,GAAG;AAChD;;AAUA,SAAgB,eAAe,QAAwC;CACtE,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO,GAAG,OAAO,WAAW,YAAY,QAAQ,OAAO,IAAI;AAC5D;;;;;;;AAQA,SAAgB,SAAS,MAAsC;CAC9D,IAAI;CACJ,IAAI;EACH,MAAM,aACL,QACA;GAAC;GAAO,SAAS;GAAQ;GAAgB;EAAM,GAC/C;GAAE,UAAU;GAAQ,OAAO;IAAC;IAAU;IAAQ;GAAQ;EAAE,CACzD;CACD,QAAQ;EACP;CACD;CAEA,MAAM,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC,GAAG,EAAE;CAC7C,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,MAAM,UAAU,IAAI,MAAM,UAAU,CAAC,GAAG;CACxC,MAAM,MAAM,aAAa,GAAG;CAE5B,OAAO;EACN;EACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAC7B,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;CACtB;AACD;;AAGA,MAAM,gBAAgB;;;;;;;;;;AAWtB,SAAS,aAAa,KAAiC;CACtD,IAAI,UAA8B;CAClC,KAAK,IAAI,QAAQ,GAAG,WAAW,UAAU,GAAG,SAAS,GAAG;EACvD,MAAM,MAAM,MAAM,OAAO;EACzB,IAAI,OAAO,UAAU,eAAe,OAAO;EAC3C,UAAU,SAAS,OAAO;CAC3B;AAED;AAEA,SAAS,MAAM,KAAiC;CAC/C,MAAM,SAAS,GAAG,YAAY;CAC9B,IAAI;EAQH,QANC,QAAQ,aAAa,UAClB,aAAa,SAAS,IAAI,WAAW,MAAM,CAAC,CAAC,MAAM,IAAI,IACvD,aAAa,MAAM;GAAC;GAAO;GAAM;GAAY;GAAM,OAAO,GAAG;EAAC,GAAG;GACjE,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACnC,CAAC,CAAC,CAAC,MAAM,KAAK,EAAA,CAEf,MAAM,UAAU,MAAM,WAAW,MAAM,CAAC,CAAC,EACxC,MAAM,OAAO,MAAM;CACvB,QAAQ;EACP;CACD;AACD;AAEA,SAAS,SAAS,KAAiC;CAClD,IAAI;EACH,MAAM,OAAO,OACZ,aAAa,MAAM;GAAC;GAAM;GAAS;GAAM,OAAO,GAAG;EAAC,GAAG;GACtD,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACnC,CAAC,CAAC,CAAC,KAAK,CACT;EACA,OAAO,OAAO,IAAI,OAAO,KAAA;CAC1B,QAAQ;EACP;CACD;AACD;;AAGA,eAAsB,cACrB,MACkC;CAClC,IAAI;EACH,OAAO,KAAK,MAAM,MAAM,SAAS,KAAK,MAAM,cAAc,GAAG,MAAM,CAAC;CACrE,QAAQ;EACP,OAAO,CAAC;CACT;AACD;AAEA,eAAe,aACd,MACA,OACgB;CAChB,MAAM,MAAM,KAAK,MAAM,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;CACnD,MAAM,UACL,KAAK,MAAM,cAAc,GACzB,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,EAAE,GACnC;AACD;;AAGA,SAAgB,aACf,WACA,OACsB;CACtB,MAAM,OAAO,OAAO,YACnB,OAAO,QAAQ,UAAU,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,SAAS,CACnD,MACA,MAAM,QAAQ;EAAE,GAAG;EAAK,MAAM,MAAM;CAAM,IAAI,GAC/C,CAAC,CACF;CACA,OAAO;EAAE,GAAG;EAAW;CAAK;AAC7B;;;;;;;;AA+BA,eAAsB,eACrB,WACA,OACA,MAKE;CACF,MAAM,QAAQ,MAAM,cAAc,UAAU,IAAI;CAChD,MAAM,SAAiC,CAAC;CACxC,KAAK,MAAM,QAAQ,OAAO;EACzB,MAAM,OAAO,MAAM,SAAS,UAAU,KAAK,KAAK,EAAE;EAClD,IAAI,MAAM,OAAO,QAAQ;CAC1B;CAIA,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,OAAO,KAAK,GAAG,GAAG,OAAO,OAAO,MAAM,CAAC,CAAC;CACzE,MAAM,QAAgC,EAAE,GAAG,MAAM;CACjD,MAAM,QAAoB,CAAC;CAC3B,MAAM,UAAwB,CAAC;CAG/B,KAAK,MAAM,QAAQ,OAAO,KAAK,MAAM,CAAC,CAAC,KAAK,GAAG;EAC9C,MAAM,OAAO,OAAO;EACpB,MAAM,QAAQ;EACd,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG;EAE3B,MAAM,SAAS,KAAK,OAAO,IAAI;EAC/B,IAAI,QAAQ,QAAQ,OAAO,UAAU,MAAM,IAAI,GAAG;GACjD,QAAQ,KAAK;IAAE,KAAK;IAAM,MAAM;IAAM;GAAO,CAAC;GAC9C;EACD;EAEA,IAAI,OAAO,OAAO;EAClB,OAAO,MAAM,IAAI,IAAI,KAAK,CAAE,MAAM,KAAK,KAAK,IAAI,GAAI,QAAQ;EAC5D,MAAM,IAAI,IAAI;EACd,MAAM,QAAQ;EACd,MAAM,KAAK;GAAE,KAAK;GAAM,MAAM;GAAM,IAAI;GAAM;EAAO,CAAC;CACvD;CAEA,MAAM,aAAa,UAAU,MAAM,KAAK;CACxC,OAAO;EAAE;EAAO;EAAO;CAAQ;AAChC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"appPorts-Cu79hJF6.cjs","names":["resolve","execFileSync","readFileSync","readFile","join","mkdir","writeFile"],"sources":["../src/dev/appPorts.ts"],"sourcesContent":["/**\n * Which local port each app answers on — and what to do when it is taken.\n *\n * An app's port used to be fixed: 3000, 3001, … and `gkm dev` refused to start\n * if anything held one. That is right when the holder is this same app, left\n * running by a previous `gkm dev` — moving would start a second copy beside it.\n * It is wrong when the holder is another project's dev server, which also\n * defaults to 3000: two projects could not run at once, and nothing about the\n * app needs that port. A browser reaches it through the edge, whose URL carries\n * no app port, and every address a phone or a route uses is derived on each\n * start.\n *\n * So the holder is asked who it is. Every app process gkm starts is tagged with\n * {@link APP_TAG_ENV} — the workspace and the app — and children inherit it, so\n * whatever actually binds the port (`next-server`, a Metro worker, the tsx\n * server) carries it — or, when its own can't be read, one of its parents does.\n * A held port whose holder carries this app's tag is a\n * leftover, and is refused; anything else — untagged, another workspace's,\n * unreadable — is not ours, and the app moves to the next free port.\n *\n * The choice is kept in `.gkm/app-ports.json`, not recomputed: an app that\n * moved stays where it moved, so a bookmark or a phone's saved address does not\n * break the next time the other project happens to be stopped. Local only —\n * `gkm deploy` and `gkm docker` never read it.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/** Carried by every app process gkm starts: `<workspace root>#<app>`. */\nexport const APP_TAG_ENV = 'GKM_DEV_APP';\n\nconst APP_PORTS_PATH = '.gkm/app-ports.json';\n\n/** The tag an app's processes carry. The root is encoded, so it has no spaces. */\nexport function appTag(root: string, app: string): string {\n\treturn `${encodeURIComponent(resolve(root))}#${app}`;\n}\n\n/** What listens on a port, and the tag it carries if it can be read. */\nexport interface PortHolder {\n\tpid: number;\n\tcommand?: string;\n\ttag?: string;\n}\n\n/** Says, as `command (pid N)`, what holds a port. */\nexport function describeHolder(holder: PortHolder | undefined): string {\n\tif (!holder) return 'another process';\n\treturn `${holder.command ?? 'a process'} (pid ${holder.pid})`;\n}\n\n/**\n * Who is listening on a port, read the way a person would: `lsof` for the\n * pid, then that process's environment for its tag — `/proc/<pid>/environ` on\n * Linux, `ps eww` elsewhere. Both work for the user's own processes, which is\n * every process gkm starts; one that cannot be read has no tag, so is not ours.\n */\nexport function holderOf(port: number): PortHolder | undefined {\n\tlet out: string;\n\ttry {\n\t\tout = execFileSync(\n\t\t\t'lsof',\n\t\t\t['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-Fpc'],\n\t\t\t{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },\n\t\t);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst pid = Number(out.match(/^p(\\d+)/m)?.[1]);\n\tif (!pid) return undefined;\n\tconst command = out.match(/^c(.+)$/m)?.[1];\n\tconst tag = lineageTagOf(pid);\n\n\treturn {\n\t\tpid,\n\t\t...(command ? { command } : {}),\n\t\t...(tag ? { tag } : {}),\n\t};\n}\n\n/** How far up a holder's parents to look before calling it untagged. */\nconst MAX_ANCESTORS = 8;\n\n/**\n * The tag of `pid`, or of its nearest parent that has one.\n *\n * A process can hide the tag it inherited: `next-server` renames itself, which\n * on macOS overwrites the memory `ps eww` reads its environment from, so `ps`\n * shows only its title. Its parent, the `next dev` gkm started, still carries\n * the tag. A child is only ever started by its parent, so a tagged parent makes\n * the holder this app's.\n */\nfunction lineageTagOf(pid: number): string | undefined {\n\tlet current: number | undefined = pid;\n\tfor (let depth = 0; current && current > 1; depth += 1) {\n\t\tconst tag = tagOf(current);\n\t\tif (tag || depth === MAX_ANCESTORS) return tag;\n\t\tcurrent = parentOf(current);\n\t}\n\treturn undefined;\n}\n\nfunction tagOf(pid: number): string | undefined {\n\tconst prefix = `${APP_TAG_ENV}=`;\n\ttry {\n\t\tconst entries =\n\t\t\tprocess.platform === 'linux'\n\t\t\t\t? readFileSync(`/proc/${pid}/environ`, 'utf8').split('\\0')\n\t\t\t\t: execFileSync('ps', ['eww', '-o', 'command=', '-p', String(pid)], {\n\t\t\t\t\t\tencoding: 'utf8',\n\t\t\t\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t\t\t\t}).split(/\\s+/);\n\t\treturn entries\n\t\t\t.find((entry) => entry.startsWith(prefix))\n\t\t\t?.slice(prefix.length);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction parentOf(pid: number): number | undefined {\n\ttry {\n\t\tconst ppid = Number(\n\t\t\texecFileSync('ps', ['-o', 'ppid=', '-p', String(pid)], {\n\t\t\t\tencoding: 'utf8',\n\t\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t\t}).trim(),\n\t\t);\n\t\treturn ppid > 0 ? ppid : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** The ports apps were given before, by app name. */\nexport async function savedAppPorts(\n\troot: string,\n): Promise<Record<string, number>> {\n\ttry {\n\t\treturn JSON.parse(await readFile(join(root, APP_PORTS_PATH), 'utf8'));\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nasync function saveAppPorts(\n\troot: string,\n\tports: Record<string, number>,\n): Promise<void> {\n\tawait mkdir(join(root, '.gkm'), { recursive: true });\n\tawait writeFile(\n\t\tjoin(root, APP_PORTS_PATH),\n\t\t`${JSON.stringify(ports, null, 2)}\\n`,\n\t);\n}\n\n/** The workspace, with each app on the port it was given locally. */\nexport function withAppPorts(\n\tworkspace: NormalizedWorkspace,\n\tports: Readonly<Record<string, number>>,\n): NormalizedWorkspace {\n\tconst apps = Object.fromEntries(\n\t\tObject.entries(workspace.apps).map(([name, app]) => [\n\t\t\tname,\n\t\t\tports[name] ? { ...app, port: ports[name] } : app,\n\t\t]),\n\t);\n\treturn { ...workspace, apps };\n}\n\n/** An app this workspace already has running — left by a previous start. */\nexport interface AppRunning {\n\tapp: string;\n\tport: number;\n\tholder: PortHolder;\n}\n\n/** An app that moved, because another project holds its port. */\nexport interface AppMoved {\n\tapp: string;\n\tfrom: number;\n\tto: number;\n\tholder: PortHolder | undefined;\n}\n\nexport interface AppPortsDeps {\n\t/** Whether a port can be bound right now. */\n\tfree: (port: number) => Promise<boolean>;\n\t/** Who holds a port. */\n\tholder: (port: number) => PortHolder | undefined;\n}\n\n/**\n * Give each named app a local port: the one it had, or its usual one, unless\n * another project holds it — then the next free one, which is kept.\n *\n * @returns every app's port, the apps that moved, and the apps this workspace\n * already has running (the caller refuses to start those).\n */\nexport async function assignAppPorts(\n\tworkspace: NormalizedWorkspace,\n\tnames: readonly string[],\n\tdeps: AppPortsDeps,\n): Promise<{\n\tports: Record<string, number>;\n\tmoved: AppMoved[];\n\trunning: AppRunning[];\n}> {\n\tconst saved = await savedAppPorts(workspace.root);\n\tconst wanted: Record<string, number> = {};\n\tfor (const name of names) {\n\t\tconst port = saved[name] ?? workspace.apps[name]?.port;\n\t\tif (port) wanted[name] = port;\n\t}\n\n\t// Every app's wanted port is reserved before any moves, so one that moves\n\t// never lands on a sibling's.\n\tconst taken = new Set([...Object.values(saved), ...Object.values(wanted)]);\n\tconst ports: Record<string, number> = { ...saved };\n\tconst moved: AppMoved[] = [];\n\tconst running: AppRunning[] = [];\n\n\t// Sorted, so which app moves first — and where — does not depend on order.\n\tfor (const name of Object.keys(wanted).sort()) {\n\t\tconst want = wanted[name]!;\n\t\tports[name] = want;\n\t\tif (await deps.free(want)) continue;\n\n\t\tconst holder = deps.holder(want);\n\t\tif (holder?.tag === appTag(workspace.root, name)) {\n\t\t\trunning.push({ app: name, port: want, holder });\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet next = want + 1;\n\t\twhile (taken.has(next) || !(await deps.free(next))) next += 1;\n\t\ttaken.add(next);\n\t\tports[name] = next;\n\t\tmoved.push({ app: name, from: want, to: next, holder });\n\t}\n\n\tawait saveAppPorts(workspace.root, ports);\n\treturn { ports, moved, running };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ready-CVNE5Pix.mjs","names":[],"sources":["../src/test/harness.ts","../src/test/ready.ts"],"sourcesContent":["/**\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 { TestStage } from '@geekmidas/constructs';\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 { GkmError } from '../errors';\nimport {\n\tcacheBackendsIn,\n\tdriversFor,\n\teventsBackendsIn,\n\ttype RuntimeDrivers,\n} from '../generators/drivers.js';\nimport { EndpointGenerator } from '../generators/EndpointGenerator.js';\nimport { OpenApiTsGenerator } from '../generators/OpenApiTsGenerator.js';\nimport { SubscriberGenerator } from '../generators/SubscriberGenerator.js';\nimport { type ConstructSource, discover } from '../reconcile/discover.js';\nimport { readFakes } from '../reconcile/fakes.js';\nimport type { CacheBackend, EventsBackend } 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: always `test`. */\n\tstage: TestStage;\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 broker the target puts topics and queues on, which the server entry\n\t * registers a driver for when the app declares one.\n\t */\n\teventsBackend: EventsBackend;\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 GkmError {\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 GkmError {\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\t// Topic subscribers, which a test delivers to. Not constructs, so the build\n\t// finds them the way it finds endpoints.\n\tconst subscribers = (\n\t\tawait new SubscriberGenerator().load(options.patterns, options.root)\n\t).map(({ key, path }) => ({ source: { file: path.absolute, export: key } }));\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\tsubscribers,\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\tevents: eventsBackendsIn(declared, options.eventsBackend),\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\t// Every construct a handler can depend on — what a test reaches through\n\t// `services.get(…)`, typed by the client its service registers. A tenant an\n\t// auth server owns is reached through that server, as databases are.\n\tconst services = serviceSources(declared, sources).filter(\n\t\t({ id }) => !owners.has(id),\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\t// Delivery loads each subscriber too. Left to a dynamic import, it is\n\t\t\t// resolved by Node — which knows nothing of the app's tsconfig paths, so\n\t\t\t// a subscriber importing `~/…` failed every test file that delivered.\n\t\t\t...(manifest.subscribers ?? []).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\tservices,\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/** The kinds whose construct hands a handler a client — what `services` reaches. */\nconst SERVICE_KINDS = new Set([\n\t'cache',\n\t'credential',\n\t'database',\n\t'database-schema',\n\t'database-reader',\n\t'email',\n\t'encryption',\n\t'external-api',\n\t'file-server',\n\t'objects',\n\t'queue',\n\t'topic',\n]);\n\nfunction serviceSources(\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] && SERVICE_KINDS.has(declaration.kind)),\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\t/** Every construct a test reaches through `services`, by service name. */\n\tservices: 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\tservices,\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 {${services.length ? ' type ClientOf,' : ''}${databases.length ? ' type DatabaseOf,' : ''} featureTest, loadTestManifest${signIn ? ', signInWithMagicLink' : ''} } from '@geekmidas/constructs/testing';`,\n\t\t// A type import per construct the types name: each database, and each\n\t\t// construct a test reaches through `services` — once, though a database\n\t\t// is both.\n\t\t...[\n\t\t\t...new Map(\n\t\t\t\t[...databases, ...services].map((construct) => [\n\t\t\t\t\tconstruct.id,\n\t\t\t\t\tconstruct,\n\t\t\t\t]),\n\t\t\t).values(),\n\t\t].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\t// Each construct's client, keyed as `services.get(…)` is called.\n\tconst servicesType = services.length\n\t\t? `{ ${services.map(({ id, service }) => `${service}: ClientOf<typeof __${id}>`).join('; ')} }`\n\t\t: '';\n\t// Positional, so a later one needs those before it, even when empty.\n\tconst generics =\n\t\tdatabases.length || factoriesType || servicesType\n\t\t\t? `<Browser, ${databases.length ? databasesType : '{}'}${\n\t\t\t\t\tfactoriesType || servicesType ? `, ${factoriesType || '{}'}` : ''\n\t\t\t\t}${servicesType ? `, ${servicesType}` : ''}>`\n\t\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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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ready-DU7wvxWk.cjs","names":["GkmError","readdir","join","canonicalId","extname","pathToFileURL","serviceKey","discover","EndpointGenerator","SubscriberGenerator","OpenApiTsGenerator","kebabCase","driversFor","cacheBackendsIn","eventsBackendsIn","readFakes","rm","mkdir","writeFile","EnvironmentParser","serviceContext","relative","provideKey"],"sources":["../src/test/harness.ts","../src/test/ready.ts"],"sourcesContent":["/**\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 { TestStage } from '@geekmidas/constructs';\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 { GkmError } from '../errors';\nimport {\n\tcacheBackendsIn,\n\tdriversFor,\n\teventsBackendsIn,\n\ttype RuntimeDrivers,\n} from '../generators/drivers.js';\nimport { EndpointGenerator } from '../generators/EndpointGenerator.js';\nimport { OpenApiTsGenerator } from '../generators/OpenApiTsGenerator.js';\nimport { SubscriberGenerator } from '../generators/SubscriberGenerator.js';\nimport { type ConstructSource, discover } from '../reconcile/discover.js';\nimport { readFakes } from '../reconcile/fakes.js';\nimport type { CacheBackend, EventsBackend } 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: always `test`. */\n\tstage: TestStage;\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 broker the target puts topics and queues on, which the server entry\n\t * registers a driver for when the app declares one.\n\t */\n\teventsBackend: EventsBackend;\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 GkmError {\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 GkmError {\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\t// Topic subscribers, which a test delivers to. Not constructs, so the build\n\t// finds them the way it finds endpoints.\n\tconst subscribers = (\n\t\tawait new SubscriberGenerator().load(options.patterns, options.root)\n\t).map(({ key, path }) => ({ source: { file: path.absolute, export: key } }));\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\tsubscribers,\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\tevents: eventsBackendsIn(declared, options.eventsBackend),\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\t// Every construct a handler can depend on — what a test reaches through\n\t// `services.get(…)`, typed by the client its service registers. A tenant an\n\t// auth server owns is reached through that server, as databases are.\n\tconst services = serviceSources(declared, sources).filter(\n\t\t({ id }) => !owners.has(id),\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\t// Delivery loads each subscriber too. Left to a dynamic import, it is\n\t\t\t// resolved by Node — which knows nothing of the app's tsconfig paths, so\n\t\t\t// a subscriber importing `~/…` failed every test file that delivered.\n\t\t\t...(manifest.subscribers ?? []).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\tservices,\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/** The kinds whose construct hands a handler a client — what `services` reaches. */\nconst SERVICE_KINDS = new Set([\n\t'cache',\n\t'credential',\n\t'database',\n\t'database-schema',\n\t'database-reader',\n\t'email',\n\t'encryption',\n\t'external-api',\n\t'file-server',\n\t'objects',\n\t'queue',\n\t'topic',\n]);\n\nfunction serviceSources(\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] && SERVICE_KINDS.has(declaration.kind)),\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\t/** Every construct a test reaches through `services`, by service name. */\n\tservices: 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\tservices,\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 {${services.length ? ' type ClientOf,' : ''}${databases.length ? ' type DatabaseOf,' : ''} featureTest, loadTestManifest${signIn ? ', signInWithMagicLink' : ''} } from '@geekmidas/constructs/testing';`,\n\t\t// A type import per construct the types name: each database, and each\n\t\t// construct a test reaches through `services` — once, though a database\n\t\t// is both.\n\t\t...[\n\t\t\t...new Map(\n\t\t\t\t[...databases, ...services].map((construct) => [\n\t\t\t\t\tconstruct.id,\n\t\t\t\t\tconstruct,\n\t\t\t\t]),\n\t\t\t).values(),\n\t\t].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\t// Each construct's client, keyed as `services.get(…)` is called.\n\tconst servicesType = services.length\n\t\t? `{ ${services.map(({ id, service }) => `${service}: ClientOf<typeof __${id}>`).join('; ')} }`\n\t\t: '';\n\t// Positional, so a later one needs those before it, even when empty.\n\tconst generics =\n\t\tdatabases.length || factoriesType || servicesType\n\t\t\t? `<Browser, ${databases.length ? databasesType : '{}'}${\n\t\t\t\t\tfactoriesType || servicesType ? `, ${factoriesType || '{}'}` : ''\n\t\t\t\t}${servicesType ? `, ${servicesType}` : ''}>`\n\t\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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"}