@geekmidas/cli 10.0.0-alpha.46 → 10.0.0-alpha.48
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.
- package/bin/gkm.mjs +9 -1
- package/bin/owning-tsconfig-jsx-register.mjs +18 -0
- package/bin/owning-tsconfig-jsx.mjs +108 -0
- package/dist/backends-B9V2XD2M.cjs +82 -0
- package/dist/backends-B9V2XD2M.cjs.map +1 -0
- package/dist/backends-CSuLl0YP.mjs +59 -0
- package/dist/backends-CSuLl0YP.mjs.map +1 -0
- package/dist/encryption-C7OGMve_.cjs +91 -0
- package/dist/encryption-C7OGMve_.cjs.map +1 -0
- package/dist/encryption-DyQnPsZN.mjs +90 -0
- package/dist/encryption-DyQnPsZN.mjs.map +1 -0
- package/dist/index.cjs +131 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +123 -43
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-Dp5HFzuQ.cjs → migrate-BgEY9pdP.cjs} +2 -2
- package/dist/{migrate-Dp5HFzuQ.cjs.map → migrate-BgEY9pdP.cjs.map} +1 -1
- package/dist/{migrate-B3opyC3o.mjs → migrate-DhxTIA0z.mjs} +2 -2
- package/dist/{migrate-B3opyC3o.mjs.map → migrate-DhxTIA0z.mjs.map} +1 -1
- package/dist/{ready-V6Ou6-B6.mjs → ready-WfT-eWlm.mjs} +2 -1
- package/dist/{ready-V6Ou6-B6.mjs.map → ready-WfT-eWlm.mjs.map} +1 -1
- package/dist/{ready-L7I68yXf.cjs → ready-lr2Fm-ep.cjs} +2 -1
- package/dist/{ready-L7I68yXf.cjs.map → ready-lr2Fm-ep.cjs.map} +1 -1
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/{workspace-CHEC80sp.mjs → workspace-cb6U29EL.mjs} +56 -77
- package/dist/workspace-cb6U29EL.mjs.map +1 -0
- package/dist/{workspace-D9z2ymBW.cjs → workspace-vafrzZTC.cjs} +60 -99
- package/dist/workspace-vafrzZTC.cjs.map +1 -0
- package/package.json +14 -14
- package/dist/workspace-CHEC80sp.mjs.map +0 -1
- package/dist/workspace-D9z2ymBW.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ready-L7I68yXf.cjs","names":["join","pathToFileURL","kebabCase","access","join","existsSync","readFileSync","ConstructGenerator","Subscriber","join","mkdir","relative","dirname","runtimeFor","writeFile","provideKey","canonicalId","readdir","join","canonicalId","extname","pathToFileURL","serviceKey","discover","EndpointGenerator","OpenApiTsGenerator","kebabCase","rm","mkdir","writeFile","EnvironmentParser","serviceContext","relative","provideKey"],"sources":["../src/types.ts","../src/reconcile/fakes.ts","../src/generators/drivers.ts","../src/generators/SubscriberGenerator.ts","../src/test/harness.ts","../src/test/ready.ts"],"sourcesContent":["import type {\n\tSecretsConfig,\n\tStagesConfig,\n\tTestConfig,\n} from './workspace/types.js';\n\nexport type MainProvider = 'aws' | 'server';\n\nexport type Routes = string | string[];\n\n/**\n * Supported event backend types.\n *\n * - `pgboss`: Reuses PostgreSQL with a dedicated user/schema. Auto-enables db service.\n * - `sns`: Adds the AWS emulator (floci) container; each topic and queue is\n * created on it and addressed by its own string.\n * - `rabbitmq`: Adds RabbitMQ container.\n *\n * Each topic and queue resolves its own `<ID>_PUBLISHER_CONNECTION_STRING`,\n * which its producers and its consumers both read. pg-boss and RabbitMQ also\n * resolve `EVENT_PUBLISHER_CONNECTION_STRING`, the one broker crons schedule\n * through.\n */\nexport type EventsBackend = 'pgboss' | 'sns' | 'rabbitmq';\n\n/**\n * Where a declared cache actually lives.\n *\n * A deployment choice, not an application one — the same code caches into any\n * of these — so it is config here beside `events` rather than a field on the\n * construct. Each backend is consistent between local and deployed, which is\n * the property worth having: a cache that behaves differently in the two places\n * is worse than a slower one.\n *\n * - `upstash` (default) — HTTP with a token. Reachable from a Lambda with no\n * VPC and no connection pool, which is why it is the default; locally the\n * `serverless-redis-http` proxy in front of Redis speaks the same protocol.\n * - `elasticache` — the Redis wire protocol, inside a VPC. Locally, plain Redis.\n * - `db` — a table in the database the app already declared. No infrastructure\n * at all, and the same relationship pg-boss has to Postgres: it creates its\n * own table on first use and needs a declared database to live in.\n */\nexport type CacheBackend = 'upstash' | 'elasticache' | 'db';\n\n/**\n * Where a declared bucket actually lives.\n *\n * The fourth of these selectors and the last to arrive, which is why\n * `UPLOADS_URL` had no answer on a container target: there was nothing to\n * choose with. Like the others it changes no application code — the `s3://`\n * scheme picks the driver, and every one of these speaks it.\n *\n * - `s3` — a real bucket. Nothing is created on the host; the URL is composed\n * against credentials you already hold, the same way `resend` is an API key\n * rather than infrastructure.\n * - `r2` — Cloudflare's, reached through the same S3 API and differing only in\n * its endpoint.\n * - `minio` — a container. Everything the app declares lives on the box, which\n * is what makes a single-host deploy self-contained, and what makes it a\n * single disk with no replication. Locally this is what every backend is.\n */\nexport type StorageBackend = 'minio' | 's3' | 'r2';\n\n/**\n * The backend a project gets when it says nothing, by where it deploys.\n *\n * Flat defaults could not be right for both: `upstash` on a single box means a\n * SaaS round-trip for a cache the database could have held, and `minio` on AWS\n * means running a bucket beside S3. So the default is a function of the target,\n * and an app that says nothing gets the obvious answer in both places — which\n * is the whole of what \"zero config\" can mean once there is more than one\n * target.\n *\n * `server` covers Dokploy and a bare host alike: the question these answer is\n * whether the app is next to a managed cloud or next to its own containers, and\n * on that question the two are the same.\n *\n * A per-construct, per-stage override is the layer above this — see\n * `constructs-outstanding.md` — so two buckets in one app can differ. This is\n * only what happens when nobody has said anything at all.\n */\nexport const DEFAULT_CACHE: Record<MainProvider, CacheBackend> = {\n\t// Reachable from a Lambda with no VPC and no connection pool.\n\taws: 'upstash',\n\t// The database is already there and already has a connection pool open to\n\t// it. Anything else is a second service to run for a table.\n\tserver: 'db',\n};\n\n/**\n * Which broker carries a declared queue or topic, by target.\n *\n * Whether there are events at all is the manifest's answer — a declared\n * `Topic` or `Queue` — so this only ever answers the second question. AWS\n * deploys them to SNS and SQS, and locally the AWS emulator stands in, so the\n * broker a developer runs is the one production has. A server has a Postgres\n * already, and pg-boss keeps its queues there.\n */\nexport const DEFAULT_EVENTS: Record<MainProvider, EventsBackend> = {\n\taws: 'sns',\n\tserver: 'pgboss',\n};\n\n/** Where a bucket lives when nobody said — see {@link DEFAULT_CACHE}. */\nexport const DEFAULT_STORAGE: Record<MainProvider, StorageBackend> = {\n\taws: 's3',\n\tserver: 'minio',\n};\n\n/**\n * Who delivers a declared app's mail.\n *\n * Unlike a cache, this changes *nothing* about the client. Every backend speaks\n * SMTP, so the declaration's `smtp://` URL is true of all of them and only the\n * provisioner differs — which is the same reason the declaration has no\n * `provider` field and there is no `ses://` scheme.\n *\n * - `ses` (default) — the only backend that can be a *chain* of resources: an\n * identity, a user, an access key, and an SMTP password derived from it,\n * because SES issues IAM credentials rather than SMTP ones. Hand it a URL and\n * it uses that instead, which is what you want when the credentials already\n * exist — provisioning a second set of sending credentials for an identity\n * that already has them is not a thing to do by default.\n * - `resend` — an API key you hold, composed into an SMTP URL. Nothing to\n * provision: a SaaS account is not infrastructure.\n * - `smtp` — a URL supplied whole, for a relay somebody else runs.\n *\n * Locally every one of them is Mailpit, because locally none of it matters.\n */\nexport type EmailBackend = 'resend' | 'ses' | 'smtp';\n\n/**\n * Who delivers mail when nobody said.\n *\n * Flat, unlike the others, and deliberately: mail is a SaaS on every target.\n * There is no self-hosted preset to default a container deploy to — Mailpit is\n * a development inbox, not a sender — so `server` has no answer of its own to\n * give and would only be `ses` written twice.\n */\nexport const DEFAULT_EMAIL: EmailBackend = 'ses';\n\n/** Supported docker-compose service names */\nexport type ComposeServiceName =\n\t| 'postgres'\n\t| 'redis'\n\t| 'rabbitmq'\n\t| 'minio'\n\t| 'mailpit'\n\t| 'localstack';\n\nexport interface DockerConfig {\n\t/** Container registry URL (e.g., 'ghcr.io/myorg') */\n\tregistry?: string;\n\t/** Docker image name (default: derived from package.json name) */\n\timageName?: string;\n\t/** Base Docker image (default: 'node:22-alpine') */\n\tbaseImage?: string;\n\t/** Container port (default: 3000) */\n\tport?: number;\n}\n\nexport type Runtime = 'node' | 'bun';\n\nexport interface TelescopeConfig {\n\t/** Enable/disable telescope (default: true in development) */\n\tenabled?: boolean;\n\t/** Port for telescope to use (defaults to server port) */\n\tport?: number;\n\t/** Path prefix for telescope UI (default: /__telescope) */\n\tpath?: string;\n\t/** Ignore patterns for telescope (e.g., ['/health', '/metrics']) */\n\tignore?: string[];\n\t/** Record request/response bodies (default: true) */\n\trecordBody?: boolean;\n\t/** Maximum entries to keep in memory (default: 1000) */\n\tmaxEntries?: number;\n\t/** Enable WebSocket for real-time updates (default: true, requires @hono/node-ws for Node.js) */\n\twebsocket?: boolean;\n}\n\nexport interface StudioConfig {\n\t/** Enable/disable studio (default: true in development) */\n\tenabled?: boolean;\n\t/** Path prefix for studio UI (default: /__studio) */\n\tpath?: string;\n\t/** Schema to introspect (default: 'public') */\n\tschema?: string;\n}\n\nexport interface OpenApiConfig {\n\t/** Enable OpenAPI generation (default: true) */\n\tenabled?: boolean;\n\t/** API title */\n\ttitle?: string;\n\t/** API version */\n\tversion?: string;\n\t/** API description */\n\tdescription?: string;\n}\n\nexport interface HooksConfig {\n\t/**\n\t * Path to a module exporting server lifecycle hooks.\n\t * The module should export `beforeSetup` and/or `afterSetup` functions.\n\t *\n\t * @example\n\t * ```typescript\n\t * // src/config/hooks.ts\n\t * import type { Hono } from 'hono';\n\t * import type { Logger } from '@geekmidas/logger';\n\t * import type { EnvironmentParser } from '@geekmidas/envkit';\n\t *\n\t * // Called BEFORE gkm endpoints are registered\n\t * export function beforeSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.use('*', cors());\n\t * app.get('/custom/health', (c) => c.json({ status: 'ok' }));\n\t * }\n\t *\n\t * // Called AFTER gkm endpoints are registered\n\t * export function afterSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.notFound((c) => c.json({ error: 'Not found' }, 404));\n\t * app.onError((err, c) => c.json({ error: err.message }, 500));\n\t * }\n\t * ```\n\t */\n\tserver?: string;\n}\n\nexport interface GkmConfig {\n\t/**\n\t * What this app is called, and therefore how everything it declares is named.\n\t *\n\t * The same field `sst.config.ts` carries, doing the same job: every physical\n\t * name is `{stage}-{name}-{construct}`, so `Database` in the `production`\n\t * stage of `kitchen-sink` is `production-kitchen-sink-database` on AWS and on\n\t * Dokploy alike. One statement, in the config, rather than a rule each\n\t * provider restates.\n\t *\n\t * `defineWorkspace` has always had this. A single-app config did not, so its\n\t * names fell back to whatever the directory happened to be called — the\n\t * monorepo's package name for the project and the app's for the application,\n\t * two accidents of layout standing in for a decision.\n\t *\n\t * Defaults to the package.json name with any scope stripped, then the\n\t * directory name.\n\t */\n\tname?: string;\n\t/** The project's stages: which one is local, which deploy */\n\tstages: StagesConfig;\n\t/** Encrypted secrets, and where deployed stages' secrets are stored */\n\tsecrets?: SecretsConfig;\n\t/** What `gkm test` hands a feature test: where the factories are. */\n\ttest?: TestConfig;\n\t/**\n\t * Constructs glob pattern — one glob, every kind.\n\t *\n\t * What reconcile reads to derive the containers a stage needs: a database\n\t * implies Postgres, objects imply MinIO, mail implies Mailpit. A glob per\n\t * kind cannot find a resource, because a declared `ObjectStorage` has no\n\t * kind to be listed under.\n\t *\n\t * @example './src/**\\/*.ts'\n\t */\n\tconstructs: Routes;\n\t/**\n\t * Server lifecycle hooks for customizing the Hono app.\n\t * Allows adding custom routes, middleware, error handlers, etc.\n\t *\n\t * @example\n\t * hooks: {\n\t * server: './src/config/hooks'\n\t * }\n\t */\n\thooks?: HooksConfig;\n\t/**\n\t * Telescope configuration for debugging/monitoring.\n\t * Can be:\n\t * - A string path to a module that exports a Telescope instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A TelescopeConfig object for inline configuration\n\t */\n\ttelescope?: string | boolean | TelescopeConfig;\n\t/**\n\t * Studio configuration for database browsing.\n\t * Can be:\n\t * - A string path to a module that exports a Studio instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A StudioConfig object for inline configuration\n\t *\n\t * Requires a database connection configured via services.\n\t */\n\tstudio?: string | boolean | StudioConfig;\n\t/**\n\t * OpenAPI generation configuration.\n\t * Can be:\n\t * - A boolean to enable/disable with defaults (output: ./src/api/openapi.ts)\n\t * - An OpenApiConfig object for customization\n\t *\n\t * When enabled, OpenAPI spec is generated on startup and regenerated on route changes.\n\t *\n\t * @example\n\t * openapi: true\n\t *\n\t * @example\n\t * openapi: {\n\t * output: './src/api/openapi.ts',\n\t * title: 'My API',\n\t * version: '1.0.0',\n\t * }\n\t */\n\topenapi?: boolean | OpenApiConfig;\n\t/** Runtime to use for dev server (default: 'node') */\n\truntime?: Runtime;\n\t/**\n\t * Environment file(s) to load for development.\n\t * Can be:\n\t * - A string path to a single env file (e.g., '.env.local')\n\t * - An array of paths to load in order (later files override earlier)\n\t * - Defaults to '.env' if not specified\n\t *\n\t * @example\n\t * env: '.env.local'\n\t *\n\t * @example\n\t * env: ['.env', '.env.local']\n\t */\n\tenv?: string | string[];\n\t/**\n\t * Docker deployment configuration.\n\t * Used by `gkm docker` and `gkm prepack` commands.\n\t *\n\t * @example\n\t * docker: {\n\t * registry: 'ghcr.io/myorg',\n\t * imageName: 'my-api',\n\t * compose: {\n\t * services: ['postgres', 'redis']\n\t * }\n\t * }\n\t */\n\tdocker?: DockerConfig;\n\t/**\n\t * Where this app deploys, and what the target needs to know.\n\t *\n\t * Absent until now, which meant a single-app project could not configure a\n\t * Dokploy deploy at all: the wrap that turns one into a workspace hardcoded\n\t * `{ default: 'dokploy' }` and dropped everything else, so there was no\n\t * endpoint, no registry and no domain — and `resolveHost` failed with \"no\n\t * domain configured for stage\" on a config that had nowhere to put one.\n\t *\n\t * The same shape a workspace config uses, so moving from one to the other is\n\t * a rename rather than a rewrite.\n\t */\n\tdeploy?: import('./workspace/types').DeployConfig;\n}\n\nexport interface BuildOptions {\n\t/** Overrides the deploy target's: a Dockerfile builds a server regardless. */\n\tprovider?: MainProvider;\n\tenableOpenApi?: boolean;\n\t/** Build for production (no dev tools, bundled output) */\n\tproduction?: boolean;\n\t/** Skip bundling step in production build */\n\tskipBundle?: boolean;\n\t/** Stage for secrets injection (e.g., 'production', 'staging') */\n\tstage?: string;\n\t/**\n\t * When true, optional environment variables (those with `.optional()` or\n\t * `.default()` in the envParser) are suffixed with `?` within each\n\t * construct's `environment` array in the manifest (e.g. `PORT?`).\n\t * Defaults to false.\n\t */\n\tmarkOptional?: boolean;\n}\n\n/** Result from build command when secrets are injected */\nexport interface BuildResult {\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n// The deployment manifest types are a shared, dependency-free data contract\n// in `@geekmidas/manifest`, re-exported here for back-compat with existing\n// `@geekmidas/cli` imports.\nexport type {\n\tCronInfo,\n\tFunctionInfo,\n\tQueueInfo,\n\tRouteInfo,\n\tSubscriberInfo,\n\tTopicInfo,\n} from '@geekmidas/manifest';\n","/**\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","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { Subscriber } from '@geekmidas/constructs/subscribers';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { BuildContext } from '../build/types';\nimport type { SubscriberInfo } from '../types';\nimport { runtimeFor } from './EndpointGenerator.js';\nimport {\n\tConstructGenerator,\n\ttype GeneratedConstruct,\n\ttype GeneratorOptions,\n} from './Generator';\n\nexport class SubscriberGenerator extends ConstructGenerator<\n\tSubscriber<any, any, any, any, any, any, any>,\n\tSubscriberInfo[]\n> {\n\tisConstruct(\n\t\tvalue: any,\n\t): value is Subscriber<any, any, any, any, any, any, any> {\n\t\treturn Subscriber.isSubscriber(value);\n\t}\n\n\tasync build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<\n\t\t\tSubscriber<any, any, any, any, any, any, any>\n\t\t>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<SubscriberInfo[]> {\n\t\tconst target = options?.target ?? 'aws';\n\t\tconst logger = console;\n\t\tconst subscriberInfos: SubscriberInfo[] = [];\n\n\t\tif (target === 'server') {\n\t\t\t// Generate subscribers.ts for server-based polling (even if empty)\n\t\t\tawait this.generateServerSubscribersFile(outputDir, constructs);\n\n\t\t\tlogger.log(\n\t\t\t\t`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`,\n\t\t\t);\n\n\t\t\t// Return empty array as server subscribers don't have individual handlers\n\t\t\treturn subscriberInfos;\n\t\t}\n\n\t\tif (constructs.length === 0) {\n\t\t\treturn subscriberInfos;\n\t\t}\n\n\t\t// Create subscribers subdirectory\n\t\tconst subscribersDir = join(outputDir, 'subscribers');\n\t\tawait mkdir(subscribersDir, { recursive: true });\n\n\t\t// Generate subscriber handlers\n\t\tfor (const { key, construct, path } of constructs) {\n\t\t\tconst handlerFile = await this.generateSubscriberHandler(\n\t\t\t\tsubscribersDir,\n\t\t\t\tpath.relative,\n\t\t\t\tkey,\n\t\t\t\tconstruct,\n\t\t\t\tcontext,\n\t\t\t\tconstruct.owner,\n\t\t\t);\n\n\t\t\tsubscriberInfos.push({\n\t\t\t\tname: key,\n\t\t\t\thandler: relative(options?.root ?? process.cwd(), handlerFile).replace(\n\t\t\t\t\t/\\.ts$/,\n\t\t\t\t\t'.handler',\n\t\t\t\t),\n\t\t\t\tsubscribedEvents: construct.subscribedEvents || [],\n\t\t\t\t// A subscriber bound to a topic (via `s.topic(topic)`) records the\n\t\t\t\t// binding so infra wires the SNS subscription.\n\t\t\t\t...(construct.topicName\n\t\t\t\t\t? { transport: 'topic' as const, topic: construct.topicName }\n\t\t\t\t\t: {}),\n\t\t\t\ttimeout: construct.timeout,\n\t\t\t\tmemorySize: construct.memorySize,\n\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t}),\n\t\t\t\tdependencies: construct.constructs,\n\t\t\t});\n\n\t\t\tlogger.log(`Generated subscriber handler: ${key}`);\n\t\t}\n\n\t\treturn subscriberInfos;\n\t}\n\n\tprivate async generateSubscriberHandler(\n\t\toutputDir: string,\n\t\tsourceFile: string,\n\t\texportName: string,\n\t\t_subscriber: Subscriber<any, any, any, any, any, any, any>,\n\t\tcontext: BuildContext,\n\t\t/** The construct that owns this — its worker, or its surface. */\n\t\towner: string | undefined,\n\t): Promise<string> {\n\t\tconst handlerFileName = `${exportName}.ts`;\n\t\tconst handlerPath = join(outputDir, handlerFileName);\n\n\t\tconst relativePath = relative(dirname(handlerPath), sourceFile);\n\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\t// Imports the construct that owns this, and binds `envParser` and\n\t\t// `logger` off it — the objects it was declared with, rather than a\n\t\t// module path printed in from config.\n\t\tconst runtime = runtimeFor(context, dirname(handlerPath), owner);\n\n\t\tconst content = `import { AWSLambdaSubscriber } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${runtime.imports}\n${runtime.bindings}\n\nconst adapter = new AWSLambdaSubscriber(envParser, ${exportName});\n\nexport const handler = adapter.handler;\n`;\n\n\t\tawait writeFile(handlerPath, content);\n\t\treturn handlerPath;\n\t}\n\n\tprivate async generateServerSubscribersFile(\n\t\toutputDir: string,\n\t\tsubscribers: GeneratedConstruct<\n\t\t\tSubscriber<any, any, any, any, any, any, any>\n\t\t>[],\n\t): Promise<string> {\n\t\tawait mkdir(outputDir, { recursive: true });\n\t\tconst subscribersPath = join(outputDir, 'subscribers.ts');\n\n\t\t// Nothing to run, so nothing to import. Every app gets this file, and\n\t\t// `@geekmidas/events` is only installed by one that declared a Topic or\n\t\t// a Queue — importing it here crashed every dev server that had not.\n\t\tif (subscribers.length === 0) {\n\t\t\tawait writeFile(\n\t\t\t\tsubscribersPath,\n\t\t\t\t`/**\n * Generated subscribers setup — this app declares none.\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\n\nexport async function setupSubscribers(\n _app: unknown,\n _envParser: EnvironmentParser<any>,\n _logger: Logger,\n): Promise<(port: number) => Promise<void>> {\n return async () => {};\n}\n`,\n\t\t\t);\n\t\t\treturn subscribersPath;\n\t\t}\n\n\t\tconst importsByFile = new Map<string, string[]>();\n\t\tfor (const { path, key } of subscribers) {\n\t\t\tconst importPath = relative(\n\t\t\t\tdirname(subscribersPath),\n\t\t\t\tpath.relative,\n\t\t\t).replace(/\\.ts$/, '.js');\n\t\t\timportsByFile.set(importPath, [\n\t\t\t\t...(importsByFile.get(importPath) ?? []),\n\t\t\t\tkey,\n\t\t\t]);\n\t\t}\n\t\tconst imports = Array.from(importsByFile.entries())\n\t\t\t.map(\n\t\t\t\t([importPath, exports]) =>\n\t\t\t\t\t`import { ${exports.join(', ')} } from '${importPath}';`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\t// Each subscriber reaches the topic it is bound to, by that topic's own\n\t\t// key — resolved here, at build time, from the binding.\n\t\tconst entries = subscribers\n\t\t\t.map(({ key, construct }) => {\n\t\t\t\tconst connectionKey = construct.topicName\n\t\t\t\t\t? provideKey(\n\t\t\t\t\t\t\tcanonicalId(construct.topicName),\n\t\t\t\t\t\t\t'publisherConnectionString',\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined;\n\t\t\t\treturn ` { id: '${key}', subscriber: ${key}, topic: ${\n\t\t\t\t\tconstruct.topicName ? `'${construct.topicName}'` : 'undefined'\n\t\t\t\t}, connectionKey: ${\n\t\t\t\t\tconnectionKey ? `'${connectionKey}'` : 'undefined'\n\t\t\t\t} },`;\n\t\t\t})\n\t\t\t.join('\\n');\n\n\t\tconst content = `/**\n * Generated subscribers setup.\n *\n * Each subscriber is run against the topic it is bound to, through that\n * topic's own connection string:\n *\n * - **sns://** — pushed, not polled. The subscriber gets an HTTP route on this\n * server, and the route is subscribed to the topic with a filter policy of\n * the events it named, so SNS fans out to each subscriber. The route runs the\n * subscriber through the same adaptor a Lambda subscription does.\n * - **anything else** (pgboss://, rabbitmq://, sqs://) — polled. On pg-boss\n * each subscriber drains a queue of its own, \\`<topic>/<subscriber>\\`, so\n * every subscriber sees every event; replicas of one subscriber share it.\n *\n * GKM_SUBSCRIBERS=off (\\`gkm dev --no-subscribers\\`) runs none of them.\n * GKM_SUBSCRIBER_PUSH_URL is the address SNS pushes to; against the local\n * emulator it defaults to this server through host.docker.internal.\n */\nimport { connect } from 'node:net';\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { EventConnectionFactory, Subscriber } from '@geekmidas/events';\nimport type { EventConnection } from '@geekmidas/events';\nimport { ServiceDiscovery } from '@geekmidas/services';\n${imports}\n\nconst subscribers = [\n${entries}\n];\n\n/** The route a subscriber's pushes arrive on. */\nexport const SUBSCRIBER_ROUTE = (id: string) => \\`/__gkm/subscribers/\\${id}\\`;\n\n/** Resolves once something is accepting connections on the port. */\nasync function listening(port: number): Promise<void> {\n for (let attempt = 0; attempt < 100; attempt++) {\n const open = await new Promise<boolean>((resolve) => {\n const socket = connect(port, '127.0.0.1');\n socket.once('connect', () => { socket.end(); resolve(true); });\n socket.once('error', () => resolve(false));\n });\n if (open) return;\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n}\n\n/**\n * Mount each pushed subscriber's route and start each polled one. Returns what\n * to run once the server is listening: subscribing a route makes SNS send its\n * confirmation at once, so the route has to be reachable first.\n */\nexport async function setupSubscribers(\n app: { post: (path: string, handler: (c: any) => unknown) => unknown },\n envParser: EnvironmentParser<any>,\n logger: Logger,\n): Promise<(port: number) => Promise<void>> {\n if (process.env.GKM_SUBSCRIBERS === 'off') {\n logger.info('Subscribers are off (--no-subscribers)');\n return async () => {};\n }\n\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n const connections = new Map<string, EventConnection>();\n const afterListening: ((port: number) => Promise<void>)[] = [];\n\n for (const { id, subscriber, topic, connectionKey } of subscribers) {\n const events = subscriber.subscribedEvents || [];\n if (events.length === 0) {\n logger.warn({ subscriber: id }, 'Subscriber has no subscribed events, skipping');\n continue;\n }\n const connectionString = connectionKey ? process.env[connectionKey] : undefined;\n if (!connectionString) {\n logger.error(\n { subscriber: id, connectionKey },\n 'No connection string for the topic this subscriber is bound to',\n );\n continue;\n }\n\n try {\n if (connectionString.startsWith('sns:')) {\n const { SNSConnection, snsUrl, subscribeHttpEndpoint } = await import('@geekmidas/events/sns');\n const { SnsPushSubscriberAdaptor } = await import('@geekmidas/constructs/aws');\n const address = snsUrl.parse(connectionString);\n const route = SUBSCRIBER_ROUTE(id);\n const adaptor = new SnsPushSubscriberAdaptor(envParser, subscriber, {\n topicArn: address.topicArn,\n // An emulator signs nothing. Decided by the address this server was\n // configured with, never by anything a request says about itself.\n verify: !address.endpoint,\n ...(address.endpoint ? { emulatorEndpoint: address.endpoint } : {}),\n });\n\n app.post(route, async (c: any) => {\n const body = JSON.parse(await c.req.text());\n const response = await adaptor.handle(body);\n return c.json(response.body, response.status);\n });\n\n afterListening.push(async (port) => {\n const base =\n process.env.GKM_SUBSCRIBER_PUSH_URL ??\n (address.endpoint ? \\`http://host.docker.internal:\\${port}\\` : undefined);\n if (!base) {\n logger.error(\n { subscriber: id },\n 'Set GKM_SUBSCRIBER_PUSH_URL to the public URL of this worker, which SNS pushes to',\n );\n return;\n }\n const connection = await SNSConnection.fromConnectionString(connectionString);\n await subscribeHttpEndpoint(connection, {\n endpoint: new URL(route, base).toString(),\n events,\n });\n logger.info({ subscriber: id, events }, 'Subscriber subscribed for push');\n });\n continue;\n }\n\n let connection = connections.get(connectionString);\n if (!connection) {\n connection = await EventConnectionFactory.fromConnectionString(connectionString);\n connections.set(connectionString, connection);\n }\n // Named, so a broker with one address for everything (pg-boss) gives\n // this subscriber a queue of its own: fan-out, not competition.\n const eventSubscriber = await Subscriber.fromConnection(connection, {\n topic,\n subscription: id,\n });\n const services = subscriber.services.length > 0\n ? await serviceDiscovery.register(subscriber.services)\n : {};\n // The subscriber's database — the worker's, or its own — as \\`db\\`. A\n // pushed subscriber gets it from the adaptor above instead.\n const db = subscriber.databaseService\n ? (await serviceDiscovery.register([subscriber.databaseService]))[\n subscriber.databaseService.serviceName\n ]\n : undefined;\n\n await eventSubscriber.subscribe(events, async (event) => {\n try {\n await subscriber.handler({\n events: [event],\n services: services as any,\n logger: subscriber.logger,\n db,\n } as any);\n } catch (error) {\n logger.error({ error, event, subscriber: id }, 'Failed to process event');\n // Rethrown so the transport keeps the event for a retry — as a queue\n // consumer's failure does, and as a pushed subscriber's 500 does.\n throw error;\n }\n });\n logger.info({ subscriber: id, events }, 'Subscriber started polling');\n } catch (error) {\n logger.error({ error, subscriber: id }, 'Failed to set up subscriber');\n }\n }\n\n const shutdown = () => {\n for (const connection of connections.values()) void connection.close();\n };\n process.once('SIGTERM', shutdown);\n process.once('SIGINT', shutdown);\n\n return async (port) => {\n if (afterListening.length === 0) return;\n await listening(port);\n for (const subscribe of afterListening) {\n await subscribe(port).catch((error) => {\n logger.error({ error }, 'Failed to subscribe for push');\n });\n }\n };\n}\n`;\n\n\t\tawait writeFile(subscribersPath, content);\n\t\treturn subscribersPath;\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 { SubscriberGenerator } from '../generators/SubscriberGenerator.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\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});\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'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
|
+
{"version":3,"file":"ready-lr2Fm-ep.cjs","names":["join","pathToFileURL","kebabCase","access","join","existsSync","readFileSync","ConstructGenerator","Subscriber","join","mkdir","relative","dirname","runtimeFor","writeFile","provideKey","canonicalId","readdir","join","canonicalId","extname","pathToFileURL","serviceKey","discover","EndpointGenerator","OpenApiTsGenerator","kebabCase","rm","mkdir","writeFile","EnvironmentParser","serviceContext","relative","provideKey"],"sources":["../src/types.ts","../src/reconcile/fakes.ts","../src/generators/drivers.ts","../src/generators/SubscriberGenerator.ts","../src/test/harness.ts","../src/test/ready.ts"],"sourcesContent":["import type {\n\tSecretsConfig,\n\tStagesConfig,\n\tTestConfig,\n} from './workspace/types.js';\n\nexport type MainProvider = 'aws' | 'server';\n\nexport type Routes = string | string[];\n\n/**\n * Supported event backend types.\n *\n * - `pgboss`: Reuses PostgreSQL with a dedicated user/schema. Auto-enables db service.\n * - `sns`: Adds the AWS emulator (floci) container; each topic and queue is\n * created on it and addressed by its own string.\n * - `rabbitmq`: Adds RabbitMQ container.\n *\n * Each topic and queue resolves its own `<ID>_PUBLISHER_CONNECTION_STRING`,\n * which its producers and its consumers both read. pg-boss and RabbitMQ also\n * resolve `EVENT_PUBLISHER_CONNECTION_STRING`, the one broker crons schedule\n * through.\n */\nexport type EventsBackend = 'pgboss' | 'sns' | 'rabbitmq';\n\n/**\n * Where a declared cache actually lives.\n *\n * A deployment choice, not an application one — the same code caches into any\n * of these — so it is config here beside `events` rather than a field on the\n * construct. Each backend is consistent between local and deployed, which is\n * the property worth having: a cache that behaves differently in the two places\n * is worse than a slower one.\n *\n * - `upstash` (default) — HTTP with a token. Reachable from a Lambda with no\n * VPC and no connection pool, which is why it is the default; locally the\n * `serverless-redis-http` proxy in front of Redis speaks the same protocol.\n * - `elasticache` — the Redis wire protocol, inside a VPC. Locally, plain Redis.\n * - `db` — a table in the database the app already declared. No infrastructure\n * at all, and the same relationship pg-boss has to Postgres: it creates its\n * own table on first use and needs a declared database to live in.\n */\nexport type CacheBackend = 'upstash' | 'elasticache' | 'db';\n\n/**\n * Where a declared bucket actually lives.\n *\n * The fourth of these selectors and the last to arrive, which is why\n * `UPLOADS_URL` had no answer on a container target: there was nothing to\n * choose with. Like the others it changes no application code — the `s3://`\n * scheme picks the driver, and every one of these speaks it.\n *\n * - `s3` — a real bucket. Nothing is created on the host; the URL is composed\n * against credentials you already hold, the same way `resend` is an API key\n * rather than infrastructure.\n * - `r2` — Cloudflare's, reached through the same S3 API and differing only in\n * its endpoint.\n * - `minio` — a container. Everything the app declares lives on the box, which\n * is what makes a single-host deploy self-contained, and what makes it a\n * single disk with no replication. Locally this is what every backend is.\n */\nexport type StorageBackend = 'minio' | 's3' | 'r2';\n\n/**\n * The backend a project gets when it says nothing, by where it deploys.\n *\n * Flat defaults could not be right for both: `upstash` on a single box means a\n * SaaS round-trip for a cache the database could have held, and `minio` on AWS\n * means running a bucket beside S3. So the default is a function of the target,\n * and an app that says nothing gets the obvious answer in both places — which\n * is the whole of what \"zero config\" can mean once there is more than one\n * target.\n *\n * `server` covers Dokploy and a bare host alike: the question these answer is\n * whether the app is next to a managed cloud or next to its own containers, and\n * on that question the two are the same.\n *\n * A per-construct, per-stage override is the layer above this — see\n * `constructs-outstanding.md` — so two buckets in one app can differ. This is\n * only what happens when nobody has said anything at all.\n */\nexport const DEFAULT_CACHE: Record<MainProvider, CacheBackend> = {\n\t// Reachable from a Lambda with no VPC and no connection pool.\n\taws: 'upstash',\n\t// The database is already there and already has a connection pool open to\n\t// it. Anything else is a second service to run for a table.\n\tserver: 'db',\n};\n\n/**\n * Which broker carries a declared queue or topic, by target.\n *\n * Whether there are events at all is the manifest's answer — a declared\n * `Topic` or `Queue` — so this only ever answers the second question. AWS\n * deploys them to SNS and SQS, and locally the AWS emulator stands in, so the\n * broker a developer runs is the one production has. A server has a Postgres\n * already, and pg-boss keeps its queues there.\n */\nexport const DEFAULT_EVENTS: Record<MainProvider, EventsBackend> = {\n\taws: 'sns',\n\tserver: 'pgboss',\n};\n\n/** Where a bucket lives when nobody said — see {@link DEFAULT_CACHE}. */\nexport const DEFAULT_STORAGE: Record<MainProvider, StorageBackend> = {\n\taws: 's3',\n\tserver: 'minio',\n};\n\n/**\n * Who delivers a declared app's mail.\n *\n * Unlike a cache, this changes *nothing* about the client. Every backend speaks\n * SMTP, so the declaration's `smtp://` URL is true of all of them and only the\n * provisioner differs — which is the same reason the declaration has no\n * `provider` field and there is no `ses://` scheme.\n *\n * - `ses` (default) — the only backend that can be a *chain* of resources: an\n * identity, a user, an access key, and an SMTP password derived from it,\n * because SES issues IAM credentials rather than SMTP ones. Hand it a URL and\n * it uses that instead, which is what you want when the credentials already\n * exist — provisioning a second set of sending credentials for an identity\n * that already has them is not a thing to do by default.\n * - `resend` — an API key you hold, composed into an SMTP URL. Nothing to\n * provision: a SaaS account is not infrastructure.\n * - `smtp` — a URL supplied whole, for a relay somebody else runs.\n *\n * Locally every one of them is Mailpit, because locally none of it matters.\n */\nexport type EmailBackend = 'resend' | 'ses' | 'smtp';\n\n/**\n * Who delivers mail when nobody said.\n *\n * Flat, unlike the others, and deliberately: mail is a SaaS on every target.\n * There is no self-hosted preset to default a container deploy to — Mailpit is\n * a development inbox, not a sender — so `server` has no answer of its own to\n * give and would only be `ses` written twice.\n */\nexport const DEFAULT_EMAIL: EmailBackend = 'ses';\n\n/** Supported docker-compose service names */\nexport type ComposeServiceName =\n\t| 'postgres'\n\t| 'redis'\n\t| 'rabbitmq'\n\t| 'minio'\n\t| 'mailpit'\n\t| 'localstack';\n\nexport interface DockerConfig {\n\t/** Container registry URL (e.g., 'ghcr.io/myorg') */\n\tregistry?: string;\n\t/** Docker image name (default: derived from package.json name) */\n\timageName?: string;\n\t/** Base Docker image (default: 'node:22-alpine') */\n\tbaseImage?: string;\n\t/** Container port (default: 3000) */\n\tport?: number;\n}\n\nexport type Runtime = 'node' | 'bun';\n\nexport interface TelescopeConfig {\n\t/** Enable/disable telescope (default: true in development) */\n\tenabled?: boolean;\n\t/** Port for telescope to use (defaults to server port) */\n\tport?: number;\n\t/** Path prefix for telescope UI (default: /__telescope) */\n\tpath?: string;\n\t/** Ignore patterns for telescope (e.g., ['/health', '/metrics']) */\n\tignore?: string[];\n\t/** Record request/response bodies (default: true) */\n\trecordBody?: boolean;\n\t/** Maximum entries to keep in memory (default: 1000) */\n\tmaxEntries?: number;\n\t/** Enable WebSocket for real-time updates (default: true, requires @hono/node-ws for Node.js) */\n\twebsocket?: boolean;\n}\n\nexport interface StudioConfig {\n\t/** Enable/disable studio (default: true in development) */\n\tenabled?: boolean;\n\t/** Path prefix for studio UI (default: /__studio) */\n\tpath?: string;\n\t/** Schema to introspect (default: 'public') */\n\tschema?: string;\n}\n\nexport interface OpenApiConfig {\n\t/** Enable OpenAPI generation (default: true) */\n\tenabled?: boolean;\n\t/** API title */\n\ttitle?: string;\n\t/** API version */\n\tversion?: string;\n\t/** API description */\n\tdescription?: string;\n}\n\nexport interface HooksConfig {\n\t/**\n\t * Path to a module exporting server lifecycle hooks.\n\t * The module should export `beforeSetup` and/or `afterSetup` functions.\n\t *\n\t * @example\n\t * ```typescript\n\t * // src/config/hooks.ts\n\t * import type { Hono } from 'hono';\n\t * import type { Logger } from '@geekmidas/logger';\n\t * import type { EnvironmentParser } from '@geekmidas/envkit';\n\t *\n\t * // Called BEFORE gkm endpoints are registered\n\t * export function beforeSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.use('*', cors());\n\t * app.get('/custom/health', (c) => c.json({ status: 'ok' }));\n\t * }\n\t *\n\t * // Called AFTER gkm endpoints are registered\n\t * export function afterSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.notFound((c) => c.json({ error: 'Not found' }, 404));\n\t * app.onError((err, c) => c.json({ error: err.message }, 500));\n\t * }\n\t * ```\n\t */\n\tserver?: string;\n}\n\nexport interface GkmConfig {\n\t/**\n\t * What this app is called, and therefore how everything it declares is named.\n\t *\n\t * The same field `sst.config.ts` carries, doing the same job: every physical\n\t * name is `{stage}-{name}-{construct}`, so `Database` in the `production`\n\t * stage of `kitchen-sink` is `production-kitchen-sink-database` on AWS and on\n\t * Dokploy alike. One statement, in the config, rather than a rule each\n\t * provider restates.\n\t *\n\t * `defineWorkspace` has always had this. A single-app config did not, so its\n\t * names fell back to whatever the directory happened to be called — the\n\t * monorepo's package name for the project and the app's for the application,\n\t * two accidents of layout standing in for a decision.\n\t *\n\t * Defaults to the package.json name with any scope stripped, then the\n\t * directory name.\n\t */\n\tname?: string;\n\t/** The project's stages: which one is local, which deploy */\n\tstages: StagesConfig;\n\t/** Encrypted secrets, and where deployed stages' secrets are stored */\n\tsecrets?: SecretsConfig;\n\t/** What `gkm test` hands a feature test: where the factories are. */\n\ttest?: TestConfig;\n\t/**\n\t * Constructs glob pattern — one glob, every kind.\n\t *\n\t * What reconcile reads to derive the containers a stage needs: a database\n\t * implies Postgres, objects imply MinIO, mail implies Mailpit. A glob per\n\t * kind cannot find a resource, because a declared `ObjectStorage` has no\n\t * kind to be listed under.\n\t *\n\t * @example './src/**\\/*.ts'\n\t */\n\tconstructs: Routes;\n\t/**\n\t * Server lifecycle hooks for customizing the Hono app.\n\t * Allows adding custom routes, middleware, error handlers, etc.\n\t *\n\t * @example\n\t * hooks: {\n\t * server: './src/config/hooks'\n\t * }\n\t */\n\thooks?: HooksConfig;\n\t/**\n\t * Telescope configuration for debugging/monitoring.\n\t * Can be:\n\t * - A string path to a module that exports a Telescope instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A TelescopeConfig object for inline configuration\n\t */\n\ttelescope?: string | boolean | TelescopeConfig;\n\t/**\n\t * Studio configuration for database browsing.\n\t * Can be:\n\t * - A string path to a module that exports a Studio instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A StudioConfig object for inline configuration\n\t *\n\t * Requires a database connection configured via services.\n\t */\n\tstudio?: string | boolean | StudioConfig;\n\t/**\n\t * OpenAPI generation configuration.\n\t * Can be:\n\t * - A boolean to enable/disable with defaults (output: ./src/api/openapi.ts)\n\t * - An OpenApiConfig object for customization\n\t *\n\t * When enabled, OpenAPI spec is generated on startup and regenerated on route changes.\n\t *\n\t * @example\n\t * openapi: true\n\t *\n\t * @example\n\t * openapi: {\n\t * output: './src/api/openapi.ts',\n\t * title: 'My API',\n\t * version: '1.0.0',\n\t * }\n\t */\n\topenapi?: boolean | OpenApiConfig;\n\t/** Runtime to use for dev server (default: 'node') */\n\truntime?: Runtime;\n\t/**\n\t * Environment file(s) to load for development.\n\t * Can be:\n\t * - A string path to a single env file (e.g., '.env.local')\n\t * - An array of paths to load in order (later files override earlier)\n\t * - Defaults to '.env' if not specified\n\t *\n\t * @example\n\t * env: '.env.local'\n\t *\n\t * @example\n\t * env: ['.env', '.env.local']\n\t */\n\tenv?: string | string[];\n\t/**\n\t * Docker deployment configuration.\n\t * Used by `gkm docker` and `gkm prepack` commands.\n\t *\n\t * @example\n\t * docker: {\n\t * registry: 'ghcr.io/myorg',\n\t * imageName: 'my-api',\n\t * compose: {\n\t * services: ['postgres', 'redis']\n\t * }\n\t * }\n\t */\n\tdocker?: DockerConfig;\n\t/**\n\t * Where this app deploys, and what the target needs to know.\n\t *\n\t * Absent until now, which meant a single-app project could not configure a\n\t * Dokploy deploy at all: the wrap that turns one into a workspace hardcoded\n\t * `{ default: 'dokploy' }` and dropped everything else, so there was no\n\t * endpoint, no registry and no domain — and `resolveHost` failed with \"no\n\t * domain configured for stage\" on a config that had nowhere to put one.\n\t *\n\t * The same shape a workspace config uses, so moving from one to the other is\n\t * a rename rather than a rewrite.\n\t */\n\tdeploy?: import('./workspace/types').DeployConfig;\n}\n\nexport interface BuildOptions {\n\t/** Overrides the deploy target's: a Dockerfile builds a server regardless. */\n\tprovider?: MainProvider;\n\tenableOpenApi?: boolean;\n\t/** Build for production (no dev tools, bundled output) */\n\tproduction?: boolean;\n\t/** Skip bundling step in production build */\n\tskipBundle?: boolean;\n\t/** Stage for secrets injection (e.g., 'production', 'staging') */\n\tstage?: string;\n\t/**\n\t * When true, optional environment variables (those with `.optional()` or\n\t * `.default()` in the envParser) are suffixed with `?` within each\n\t * construct's `environment` array in the manifest (e.g. `PORT?`).\n\t * Defaults to false.\n\t */\n\tmarkOptional?: boolean;\n}\n\n/** Result from build command when secrets are injected */\nexport interface BuildResult {\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n// The deployment manifest types are a shared, dependency-free data contract\n// in `@geekmidas/manifest`, re-exported here for back-compat with existing\n// `@geekmidas/cli` imports.\nexport type {\n\tCronInfo,\n\tFunctionInfo,\n\tQueueInfo,\n\tRouteInfo,\n\tSubscriberInfo,\n\tTopicInfo,\n} from '@geekmidas/manifest';\n","/**\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","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { Subscriber } from '@geekmidas/constructs/subscribers';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { BuildContext } from '../build/types';\nimport type { SubscriberInfo } from '../types';\nimport { runtimeFor } from './EndpointGenerator.js';\nimport {\n\tConstructGenerator,\n\ttype GeneratedConstruct,\n\ttype GeneratorOptions,\n} from './Generator';\n\nexport class SubscriberGenerator extends ConstructGenerator<\n\tSubscriber<any, any, any, any, any, any, any>,\n\tSubscriberInfo[]\n> {\n\tisConstruct(\n\t\tvalue: any,\n\t): value is Subscriber<any, any, any, any, any, any, any> {\n\t\treturn Subscriber.isSubscriber(value);\n\t}\n\n\tasync build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<\n\t\t\tSubscriber<any, any, any, any, any, any, any>\n\t\t>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<SubscriberInfo[]> {\n\t\tconst target = options?.target ?? 'aws';\n\t\tconst logger = console;\n\t\tconst subscriberInfos: SubscriberInfo[] = [];\n\n\t\tif (target === 'server') {\n\t\t\t// Generate subscribers.ts for server-based polling (even if empty)\n\t\t\tawait this.generateServerSubscribersFile(outputDir, constructs);\n\n\t\t\tlogger.log(\n\t\t\t\t`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`,\n\t\t\t);\n\n\t\t\t// Return empty array as server subscribers don't have individual handlers\n\t\t\treturn subscriberInfos;\n\t\t}\n\n\t\tif (constructs.length === 0) {\n\t\t\treturn subscriberInfos;\n\t\t}\n\n\t\t// Create subscribers subdirectory\n\t\tconst subscribersDir = join(outputDir, 'subscribers');\n\t\tawait mkdir(subscribersDir, { recursive: true });\n\n\t\t// Generate subscriber handlers\n\t\tfor (const { key, construct, path } of constructs) {\n\t\t\tconst handlerFile = await this.generateSubscriberHandler(\n\t\t\t\tsubscribersDir,\n\t\t\t\tpath.relative,\n\t\t\t\tkey,\n\t\t\t\tconstruct,\n\t\t\t\tcontext,\n\t\t\t\tconstruct.owner,\n\t\t\t);\n\n\t\t\tsubscriberInfos.push({\n\t\t\t\tname: key,\n\t\t\t\thandler: relative(options?.root ?? process.cwd(), handlerFile).replace(\n\t\t\t\t\t/\\.ts$/,\n\t\t\t\t\t'.handler',\n\t\t\t\t),\n\t\t\t\tsubscribedEvents: construct.subscribedEvents || [],\n\t\t\t\t// A subscriber bound to a topic (via `s.topic(topic)`) records the\n\t\t\t\t// binding so infra wires the SNS subscription.\n\t\t\t\t...(construct.topicName\n\t\t\t\t\t? { transport: 'topic' as const, topic: construct.topicName }\n\t\t\t\t\t: {}),\n\t\t\t\ttimeout: construct.timeout,\n\t\t\t\tmemorySize: construct.memorySize,\n\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t}),\n\t\t\t\tdependencies: construct.constructs,\n\t\t\t});\n\n\t\t\tlogger.log(`Generated subscriber handler: ${key}`);\n\t\t}\n\n\t\treturn subscriberInfos;\n\t}\n\n\tprivate async generateSubscriberHandler(\n\t\toutputDir: string,\n\t\tsourceFile: string,\n\t\texportName: string,\n\t\t_subscriber: Subscriber<any, any, any, any, any, any, any>,\n\t\tcontext: BuildContext,\n\t\t/** The construct that owns this — its worker, or its surface. */\n\t\towner: string | undefined,\n\t): Promise<string> {\n\t\tconst handlerFileName = `${exportName}.ts`;\n\t\tconst handlerPath = join(outputDir, handlerFileName);\n\n\t\tconst relativePath = relative(dirname(handlerPath), sourceFile);\n\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\t// Imports the construct that owns this, and binds `envParser` and\n\t\t// `logger` off it — the objects it was declared with, rather than a\n\t\t// module path printed in from config.\n\t\tconst runtime = runtimeFor(context, dirname(handlerPath), owner);\n\n\t\tconst content = `import { AWSLambdaSubscriber } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${runtime.imports}\n${runtime.bindings}\n\nconst adapter = new AWSLambdaSubscriber(envParser, ${exportName});\n\nexport const handler = adapter.handler;\n`;\n\n\t\tawait writeFile(handlerPath, content);\n\t\treturn handlerPath;\n\t}\n\n\tprivate async generateServerSubscribersFile(\n\t\toutputDir: string,\n\t\tsubscribers: GeneratedConstruct<\n\t\t\tSubscriber<any, any, any, any, any, any, any>\n\t\t>[],\n\t): Promise<string> {\n\t\tawait mkdir(outputDir, { recursive: true });\n\t\tconst subscribersPath = join(outputDir, 'subscribers.ts');\n\n\t\t// Nothing to run, so nothing to import. Every app gets this file, and\n\t\t// `@geekmidas/events` is only installed by one that declared a Topic or\n\t\t// a Queue — importing it here crashed every dev server that had not.\n\t\tif (subscribers.length === 0) {\n\t\t\tawait writeFile(\n\t\t\t\tsubscribersPath,\n\t\t\t\t`/**\n * Generated subscribers setup — this app declares none.\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\n\nexport async function setupSubscribers(\n _app: unknown,\n _envParser: EnvironmentParser<any>,\n _logger: Logger,\n): Promise<(port: number) => Promise<void>> {\n return async () => {};\n}\n`,\n\t\t\t);\n\t\t\treturn subscribersPath;\n\t\t}\n\n\t\tconst importsByFile = new Map<string, string[]>();\n\t\tfor (const { path, key } of subscribers) {\n\t\t\tconst importPath = relative(\n\t\t\t\tdirname(subscribersPath),\n\t\t\t\tpath.relative,\n\t\t\t).replace(/\\.ts$/, '.js');\n\t\t\timportsByFile.set(importPath, [\n\t\t\t\t...(importsByFile.get(importPath) ?? []),\n\t\t\t\tkey,\n\t\t\t]);\n\t\t}\n\t\tconst imports = Array.from(importsByFile.entries())\n\t\t\t.map(\n\t\t\t\t([importPath, exports]) =>\n\t\t\t\t\t`import { ${exports.join(', ')} } from '${importPath}';`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\t// Each subscriber reaches the topic it is bound to, by that topic's own\n\t\t// key — resolved here, at build time, from the binding.\n\t\tconst entries = subscribers\n\t\t\t.map(({ key, construct }) => {\n\t\t\t\tconst connectionKey = construct.topicName\n\t\t\t\t\t? provideKey(\n\t\t\t\t\t\t\tcanonicalId(construct.topicName),\n\t\t\t\t\t\t\t'publisherConnectionString',\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined;\n\t\t\t\treturn ` { id: '${key}', subscriber: ${key}, topic: ${\n\t\t\t\t\tconstruct.topicName ? `'${construct.topicName}'` : 'undefined'\n\t\t\t\t}, connectionKey: ${\n\t\t\t\t\tconnectionKey ? `'${connectionKey}'` : 'undefined'\n\t\t\t\t} },`;\n\t\t\t})\n\t\t\t.join('\\n');\n\n\t\tconst content = `/**\n * Generated subscribers setup.\n *\n * Each subscriber is run against the topic it is bound to, through that\n * topic's own connection string:\n *\n * - **sns://** — pushed, not polled. The subscriber gets an HTTP route on this\n * server, and the route is subscribed to the topic with a filter policy of\n * the events it named, so SNS fans out to each subscriber. The route runs the\n * subscriber through the same adaptor a Lambda subscription does.\n * - **anything else** (pgboss://, rabbitmq://, sqs://) — polled. On pg-boss\n * each subscriber drains a queue of its own, \\`<topic>/<subscriber>\\`, so\n * every subscriber sees every event; replicas of one subscriber share it.\n *\n * GKM_SUBSCRIBERS=off (\\`gkm dev --no-subscribers\\`) runs none of them.\n * GKM_SUBSCRIBER_PUSH_URL is the address SNS pushes to; against the local\n * emulator it defaults to this server through host.docker.internal.\n */\nimport { connect } from 'node:net';\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { EventConnectionFactory, Subscriber } from '@geekmidas/events';\nimport type { EventConnection } from '@geekmidas/events';\nimport { ServiceDiscovery } from '@geekmidas/services';\n${imports}\n\nconst subscribers = [\n${entries}\n];\n\n/** The route a subscriber's pushes arrive on. */\nexport const SUBSCRIBER_ROUTE = (id: string) => \\`/__gkm/subscribers/\\${id}\\`;\n\n/** Resolves once something is accepting connections on the port. */\nasync function listening(port: number): Promise<void> {\n for (let attempt = 0; attempt < 100; attempt++) {\n const open = await new Promise<boolean>((resolve) => {\n const socket = connect(port, '127.0.0.1');\n socket.once('connect', () => { socket.end(); resolve(true); });\n socket.once('error', () => resolve(false));\n });\n if (open) return;\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n}\n\n/**\n * Mount each pushed subscriber's route and start each polled one. Returns what\n * to run once the server is listening: subscribing a route makes SNS send its\n * confirmation at once, so the route has to be reachable first.\n */\nexport async function setupSubscribers(\n app: { post: (path: string, handler: (c: any) => unknown) => unknown },\n envParser: EnvironmentParser<any>,\n logger: Logger,\n): Promise<(port: number) => Promise<void>> {\n if (process.env.GKM_SUBSCRIBERS === 'off') {\n logger.info('Subscribers are off (--no-subscribers)');\n return async () => {};\n }\n\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n const connections = new Map<string, EventConnection>();\n const afterListening: ((port: number) => Promise<void>)[] = [];\n\n for (const { id, subscriber, topic, connectionKey } of subscribers) {\n const events = subscriber.subscribedEvents || [];\n if (events.length === 0) {\n logger.warn({ subscriber: id }, 'Subscriber has no subscribed events, skipping');\n continue;\n }\n const connectionString = connectionKey ? process.env[connectionKey] : undefined;\n if (!connectionString) {\n logger.error(\n { subscriber: id, connectionKey },\n 'No connection string for the topic this subscriber is bound to',\n );\n continue;\n }\n\n try {\n if (connectionString.startsWith('sns:')) {\n const { SNSConnection, snsUrl, subscribeHttpEndpoint } = await import('@geekmidas/events/sns');\n const { SnsPushSubscriberAdaptor } = await import('@geekmidas/constructs/aws');\n const address = snsUrl.parse(connectionString);\n const route = SUBSCRIBER_ROUTE(id);\n const adaptor = new SnsPushSubscriberAdaptor(envParser, subscriber, {\n topicArn: address.topicArn,\n // An emulator signs nothing. Decided by the address this server was\n // configured with, never by anything a request says about itself.\n verify: !address.endpoint,\n ...(address.endpoint ? { emulatorEndpoint: address.endpoint } : {}),\n });\n\n app.post(route, async (c: any) => {\n const body = JSON.parse(await c.req.text());\n const response = await adaptor.handle(body);\n return c.json(response.body, response.status);\n });\n\n afterListening.push(async (port) => {\n const base =\n process.env.GKM_SUBSCRIBER_PUSH_URL ??\n (address.endpoint ? \\`http://host.docker.internal:\\${port}\\` : undefined);\n if (!base) {\n logger.error(\n { subscriber: id },\n 'Set GKM_SUBSCRIBER_PUSH_URL to the public URL of this worker, which SNS pushes to',\n );\n return;\n }\n const connection = await SNSConnection.fromConnectionString(connectionString);\n await subscribeHttpEndpoint(connection, {\n endpoint: new URL(route, base).toString(),\n events,\n });\n logger.info({ subscriber: id, events }, 'Subscriber subscribed for push');\n });\n continue;\n }\n\n let connection = connections.get(connectionString);\n if (!connection) {\n connection = await EventConnectionFactory.fromConnectionString(connectionString);\n connections.set(connectionString, connection);\n }\n // Named, so a broker with one address for everything (pg-boss) gives\n // this subscriber a queue of its own: fan-out, not competition.\n const eventSubscriber = await Subscriber.fromConnection(connection, {\n topic,\n subscription: id,\n });\n const services = subscriber.services.length > 0\n ? await serviceDiscovery.register(subscriber.services)\n : {};\n // The subscriber's database — the worker's, or its own — as \\`db\\`. A\n // pushed subscriber gets it from the adaptor above instead.\n const db = subscriber.databaseService\n ? (await serviceDiscovery.register([subscriber.databaseService]))[\n subscriber.databaseService.serviceName\n ]\n : undefined;\n\n await eventSubscriber.subscribe(events, async (event) => {\n try {\n await subscriber.handler({\n events: [event],\n services: services as any,\n logger: subscriber.logger,\n db,\n } as any);\n } catch (error) {\n logger.error({ error, event, subscriber: id }, 'Failed to process event');\n // Rethrown so the transport keeps the event for a retry — as a queue\n // consumer's failure does, and as a pushed subscriber's 500 does.\n throw error;\n }\n });\n logger.info({ subscriber: id, events }, 'Subscriber started polling');\n } catch (error) {\n logger.error({ error, subscriber: id }, 'Failed to set up subscriber');\n }\n }\n\n const shutdown = () => {\n for (const connection of connections.values()) void connection.close();\n };\n process.once('SIGTERM', shutdown);\n process.once('SIGINT', shutdown);\n\n return async (port) => {\n if (afterListening.length === 0) return;\n await listening(port);\n for (const subscribe of afterListening) {\n await subscribe(port).catch((error) => {\n logger.error({ error }, 'Failed to subscribe for push');\n });\n }\n };\n}\n`;\n\n\t\tawait writeFile(subscribersPath, content);\n\t\treturn subscribersPath;\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 { SubscriberGenerator } from '../generators/SubscriberGenerator.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\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});\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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"}
|
package/dist/vitest.cjs
CHANGED
package/dist/vitest.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as TEST_MANIFEST_ENV, n as TEST_READY_FILE } from "./ready-
|
|
1
|
+
import { i as TEST_MANIFEST_ENV, n as TEST_READY_FILE } from "./ready-WfT-eWlm.mjs";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|