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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{config-BholeD-0.cjs → config-C21ocYNd.cjs} +2 -2
  2. package/dist/{config-BholeD-0.cjs.map → config-C21ocYNd.cjs.map} +1 -1
  3. package/dist/{config-CzWnfcXM.mjs → config-WRdsQYZK.mjs} +2 -2
  4. package/dist/{config-CzWnfcXM.mjs.map → config-WRdsQYZK.mjs.map} +1 -1
  5. package/dist/config.cjs +2 -2
  6. package/dist/config.d.cts +2 -2
  7. package/dist/config.mjs +2 -2
  8. package/dist/{credentials-BZcp3CGx.mjs → credentials-BBIRJVp4.mjs} +4 -4
  9. package/dist/{credentials-BZcp3CGx.mjs.map → credentials-BBIRJVp4.mjs.map} +1 -1
  10. package/dist/{credentials-DQmbRnr1.cjs → credentials-G1JwBoXH.cjs} +4 -4
  11. package/dist/{credentials-DQmbRnr1.cjs.map → credentials-G1JwBoXH.cjs.map} +1 -1
  12. package/dist/{index-CHB4tSzX.d.cts → index-DCtipn2h.d.cts} +2 -2
  13. package/dist/{index-CHB4tSzX.d.cts.map → index-DCtipn2h.d.cts.map} +1 -1
  14. package/dist/index.cjs +488 -151
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.mjs +488 -151
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/{openapi-c9bpieI5.cjs → openapi-CA0yJUTN.cjs} +7 -2
  19. package/dist/{openapi-c9bpieI5.cjs.map → openapi-CA0yJUTN.cjs.map} +1 -1
  20. package/dist/{openapi-CY9xFkTH.mjs → openapi-kkQbsRVj.mjs} +7 -2
  21. package/dist/{openapi-CY9xFkTH.mjs.map → openapi-kkQbsRVj.mjs.map} +1 -1
  22. package/dist/openapi.cjs +3 -3
  23. package/dist/openapi.d.cts +1 -1
  24. package/dist/openapi.mjs +3 -3
  25. package/dist/reconcile/public.d.cts +1 -1
  26. package/dist/setup-B5_oHHUj.cjs +11 -0
  27. package/dist/{setup-C48C9hto.cjs → setup-Cm2UnAdh.cjs} +3 -3
  28. package/dist/{setup-C48C9hto.cjs.map → setup-Cm2UnAdh.cjs.map} +1 -1
  29. package/dist/{setup-Bc73t0ve.mjs → setup-DXvRLTZN.mjs} +3 -3
  30. package/dist/{setup-Bc73t0ve.mjs.map → setup-DXvRLTZN.mjs.map} +1 -1
  31. package/dist/{setup-DCuudnCl.mjs → setup-ZijS4qeC.mjs} +4 -4
  32. package/dist/{types-abJQuk7m.d.cts → types-BhIl-COG.d.cts} +4 -4
  33. package/dist/{types-abJQuk7m.d.cts.map → types-BhIl-COG.d.cts.map} +1 -1
  34. package/dist/workspace/index.cjs +1 -1
  35. package/dist/workspace/index.d.cts +2 -2
  36. package/dist/workspace/index.mjs +1 -1
  37. package/dist/workspace-BFMrAKqI.mjs +6634 -0
  38. package/dist/workspace-BFMrAKqI.mjs.map +1 -0
  39. package/dist/workspace-BcucjwvA.cjs +6783 -0
  40. package/dist/workspace-BcucjwvA.cjs.map +1 -0
  41. package/dist/{workspace-BO_KWohI.cjs → workspace-DdJss8I6.cjs} +3 -3
  42. package/dist/workspace-k4l3rsFh.mjs +8 -0
  43. package/package.json +30 -30
  44. package/dist/setup-fG_ivuxO.cjs +0 -11
  45. package/dist/workspace-7FqprxuJ.mjs +0 -4323
  46. package/dist/workspace-7FqprxuJ.mjs.map +0 -1
  47. package/dist/workspace-DK4WWJIn.mjs +0 -8
  48. package/dist/workspace-XvqO9jIv.cjs +0 -4472
  49. package/dist/workspace-XvqO9jIv.cjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  import { __require } from "./chunk-Duj1WY3L.mjs";
3
3
  import { ConstructGenerator, DEFAULT_CACHE, discover, isPartitionedRoutes } from "./discover-CAlFgYec.mjs";
4
- import { derivedApps, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig, resolveAppSpec } from "./config-CzWnfcXM.mjs";
5
- import { allConstructGlobs, getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, getPublicEnvPrefix, isDeployTargetSupported, stripPublicPrefix } from "./workspace-7FqprxuJ.mjs";
4
+ import { derivedApps, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig, resolveAppSpec } from "./config-WRdsQYZK.mjs";
5
+ import { allConstructGlobs, getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, getPublicEnvPrefix, isDeployTargetSupported, stripPublicPrefix } from "./workspace-BFMrAKqI.mjs";
6
6
  import { getCredentialsPath, getDokployCredentials, getDokployRegistryId, getDokployToken, removeDokployCredentials, storeDokployCredentials, storeDokployRegistryId, storeHostingerToken } from "./credentials-B5zRHdAl.mjs";
7
- import { applyPostgres, cacheBackendOf, constructGlobs, createCredentialsPreload, createEntryWrapper, derivedContainers, emailBackendOf, findAvailablePort, isPortAvailable, loadEnvFiles, loadSecretsForApp, prepareEntryCredentials, providerOf, reconcileWorkspace, resolveServicePorts, resourceName, rewriteUrlsWithPorts, startWorkspaceServices, storageBackendOf, usesConstructs } from "./credentials-BZcp3CGx.mjs";
7
+ import { applyPostgres, cacheBackendOf, constructGlobs, createCredentialsPreload, createEntryWrapper, derivedContainers, emailBackendOf, findAvailablePort, isPortAvailable, loadEnvFiles, loadSecretsForApp, prepareEntryCredentials, providerOf, reconcileWorkspace, resolveServicePorts, resourceName, rewriteUrlsWithPorts, startWorkspaceServices, storageBackendOf, usesConstructs } from "./credentials-BBIRJVp4.mjs";
8
8
  import { createStageSecrets, rotateServicePassword } from "./generator-VE2JrFlY.mjs";
9
9
  import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-Bi24WfoW.mjs";
10
- import { EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, normalizeRoutes, openapiCommand, resolveOpenApiConfig } from "./openapi-CY9xFkTH.mjs";
10
+ import { EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, normalizeRoutes, openapiCommand, resolveOpenApiConfig } from "./openapi-kkQbsRVj.mjs";
11
11
  import { withCompute, withRoutes } from "./emit-CiZS_5Ub.mjs";
12
12
  import { DokployApi } from "./dokploy-api-aTFPz7_4.mjs";
13
13
  import { encryptSecrets } from "./encryption-D-abyhZx.mjs";
@@ -15,7 +15,7 @@ import { CachedStateProvider } from "./CachedStateProvider-DTniehMP.mjs";
15
15
  import { generateDbPassword, generateDbUrl, generateFullstackCustomSecrets } from "./fullstack-secrets-D1o9-G5l.mjs";
16
16
  import { generateReactQueryCommand } from "./openapi-react-query-Byltvm_7.mjs";
17
17
  import "./sync-CmEuNZsB.mjs";
18
- import { setupCommand, trustCommand } from "./setup-Bc73t0ve.mjs";
18
+ import { setupCommand, trustCommand } from "./setup-DXvRLTZN.mjs";
19
19
  import { createRequire } from "node:module";
20
20
  import { copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
21
21
  import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
@@ -43,7 +43,7 @@ import prompts from "prompts";
43
43
 
44
44
  //#region package.json
45
45
  var name = "@geekmidas/cli";
46
- var version = "10.0.0-alpha.1";
46
+ var version = "10.0.0-alpha.3";
47
47
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
48
48
  var private$1 = false;
49
49
  var type = "module";
@@ -124,12 +124,12 @@ var repository = {
124
124
  "url": "https://github.com/geekmidas/toolbox"
125
125
  };
126
126
  var dependencies = {
127
- "@apidevtools/swagger-parser": "^10.1.0",
128
- "@aws-sdk/client-iam": "~3.992.0",
129
- "@aws-sdk/client-route-53": "~3.992.0",
130
- "@aws-sdk/client-s3": "~3.992.0",
131
- "@aws-sdk/client-ssm": "~3.992.0",
132
- "@aws-sdk/credential-providers": "~3.992.0",
127
+ "@apidevtools/swagger-parser": "~13.1.0",
128
+ "@aws-sdk/client-iam": "~3.1136.0",
129
+ "@aws-sdk/client-route-53": "~3.1136.0",
130
+ "@aws-sdk/client-s3": "~3.1136.0",
131
+ "@aws-sdk/client-ssm": "~3.1136.0",
132
+ "@aws-sdk/credential-providers": "~3.1136.0",
133
133
  "@geekmidas/cache": "workspace:*",
134
134
  "@geekmidas/constructs": "workspace:~",
135
135
  "@geekmidas/db": "workspace:*",
@@ -138,28 +138,28 @@ var dependencies = {
138
138
  "@geekmidas/logger": "workspace:~",
139
139
  "@geekmidas/manifest": "workspace:*",
140
140
  "@geekmidas/schema": "workspace:~",
141
- "chokidar": "~4.0.3",
142
- "commander": "^12.1.0",
143
- "dotenv": "~17.2.3",
144
- "esbuild": "^0.28.2",
145
- "fast-glob": "^3.3.2",
146
- "hono": "~4.8.0",
147
- "lodash.kebabcase": "^4.1.1",
148
- "openapi-typescript": "^7.4.2",
149
- "pg": "~8.17.1",
141
+ "chokidar": "~5.0.0",
142
+ "commander": "~15.0.0",
143
+ "dotenv": "~18.0.1",
144
+ "esbuild": "~0.28.2",
145
+ "fast-glob": "~3.3.3",
146
+ "hono": "~4.13.8",
147
+ "lodash.kebabcase": "~4.1.1",
148
+ "openapi-typescript": "~7.13.0",
149
+ "pg": "~8.23.0",
150
150
  "prompts": "~2.4.2",
151
151
  "tsx": "~4.20.3",
152
- "yaml": "~2.8.2"
152
+ "yaml": "~2.9.1"
153
153
  };
154
154
  var devDependencies = {
155
155
  "@geekmidas/testkit": "workspace:*",
156
- "@types/lodash.kebabcase": "^4.1.9",
156
+ "@types/lodash.kebabcase": "~4.1.9",
157
157
  "@types/node": "~24.9.1",
158
- "@types/pg": "~8.16.0",
158
+ "@types/pg": "~8.23.1",
159
159
  "@types/prompts": "~2.4.9",
160
160
  "typescript": "^5.8.2",
161
161
  "vitest": "^3.2.4",
162
- "zod": "~4.1.13"
162
+ "zod": "~4.6.5"
163
163
  };
164
164
  var peerDependencies = { "@geekmidas/telescope": "workspace:~" };
165
165
  var peerDependenciesMeta = { "@geekmidas/telescope": { "optional": true } };
@@ -7342,132 +7342,16 @@ function printStateDetails(state) {
7342
7342
  }
7343
7343
  }
7344
7344
 
7345
- //#endregion
7346
- //#region src/init/versions.ts
7347
- const require$1 = createRequire(import.meta.url);
7348
- function loadPackageJson() {
7349
- try {
7350
- return require$1("../package.json");
7351
- } catch {
7352
- return require$1("../../package.json");
7353
- }
7354
- }
7355
- const pkg = loadPackageJson();
7356
- /**
7357
- * CLI version resolved from package.json at runtime
7358
- */
7359
- const CLI_VERSION = `~${pkg.version}`;
7360
- /**
7361
- * Package versions for @geekmidas packages
7362
- *
7363
- * AUTO-GENERATED (except CLI) - Do not edit manually
7364
- * Run: pnpm --filter @geekmidas/cli sync-versions
7365
- */
7366
- const GEEKMIDAS_VERSIONS = {
7367
- "@geekmidas/audit": "~10.0.0-alpha.1",
7368
- "@geekmidas/auth": "~10.0.0-alpha.1",
7369
- "@geekmidas/cache": "~10.0.0-alpha.1",
7370
- "@geekmidas/client": "~10.0.0-alpha.1",
7371
- "@geekmidas/cloud": "~10.0.0-alpha.1",
7372
- "@geekmidas/constructs": "~10.0.0-alpha.1",
7373
- "@geekmidas/db": "~10.0.0-alpha.1",
7374
- "@geekmidas/emailkit": "~10.0.0-alpha.1",
7375
- "@geekmidas/envkit": "~10.0.0-alpha.1",
7376
- "@geekmidas/errors": "~10.0.0-alpha.1",
7377
- "@geekmidas/events": "~10.0.0-alpha.1",
7378
- "@geekmidas/logger": "~10.0.0-alpha.1",
7379
- "@geekmidas/manifest": "~10.0.0-alpha.1",
7380
- "@geekmidas/rate-limit": "~10.0.0-alpha.1",
7381
- "@geekmidas/schema": "~10.0.0-alpha.1",
7382
- "@geekmidas/services": "~10.0.0-alpha.1",
7383
- "@geekmidas/storage": "~10.0.0-alpha.1",
7384
- "@geekmidas/studio": "~10.0.0-alpha.1",
7385
- "@geekmidas/telescope": "~10.0.0-alpha.1",
7386
- "@geekmidas/testkit": "~10.0.0-alpha.1",
7387
- "@geekmidas/cli": CLI_VERSION
7388
- };
7389
-
7390
- //#endregion
7391
- //#region src/init/generators/auth.ts
7392
- /**
7393
- * Generate auth app files for fullstack template
7394
- * Uses better-auth with magic link authentication
7395
- */
7396
- function generateAuthAppFiles(options) {
7397
- if (!options.monorepo || options.template !== "fullstack") return [];
7398
- const packageName = `@${options.name}/auth`;
7399
- const modelsPackage = `@${options.name}/models`;
7400
- const packageJson = {
7401
- name: packageName,
7402
- version: "0.0.1",
7403
- private: true,
7404
- type: "module",
7405
- scripts: {
7406
- dev: "gkm dev --entry ./src/index.ts",
7407
- build: "tsc",
7408
- start: "node dist/index.js",
7409
- typecheck: "tsc --noEmit",
7410
- "db:migrate": "gkm exec -- npx @better-auth/cli migrate",
7411
- "db:generate": "gkm exec -- npx @better-auth/cli generate"
7412
- },
7413
- dependencies: {
7414
- [modelsPackage]: "workspace:*",
7415
- "@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
7416
- "@geekmidas/logger": GEEKMIDAS_VERSIONS["@geekmidas/logger"],
7417
- "@hono/node-server": "~1.13.0",
7418
- "better-auth": "~1.2.0",
7419
- hono: "~4.8.0",
7420
- kysely: "~0.27.0",
7421
- pg: "~8.13.0"
7422
- },
7423
- devDependencies: {
7424
- "@geekmidas/cli": GEEKMIDAS_VERSIONS["@geekmidas/cli"],
7425
- "@types/node": "~22.0.0",
7426
- "@types/pg": "~8.11.0",
7427
- tsx: "~4.20.0",
7428
- typescript: "~5.8.2"
7429
- }
7430
- };
7431
- const tsConfig = {
7432
- extends: "../../tsconfig.json",
7433
- compilerOptions: {
7434
- noEmit: true,
7435
- allowImportingTsExtensions: true,
7436
- baseUrl: ".",
7437
- paths: {
7438
- "~/*": ["./src/*"],
7439
- [`@${options.name}/*`]: ["../../packages/*/src"]
7440
- }
7441
- },
7442
- include: ["src/**/*.ts"],
7443
- exclude: ["node_modules", "dist"]
7444
- };
7445
- const gitignore = `node_modules/
7446
- dist/
7447
- .env.local
7448
- *.log
7449
- `;
7450
- return [
7451
- {
7452
- path: "apps/auth/package.json",
7453
- content: `${JSON.stringify(packageJson, null, 2)}\n`
7454
- },
7455
- {
7456
- path: "apps/auth/tsconfig.json",
7457
- content: `${JSON.stringify(tsConfig, null, 2)}\n`
7458
- },
7459
- {
7460
- path: "apps/auth/.gitignore",
7461
- content: gitignore
7462
- }
7463
- ];
7464
- }
7465
-
7466
7345
  //#endregion
7467
7346
  //#region src/init/constructs.ts
7468
7347
  /** The glob every generated config points at. One glob, every kind. */
7469
7348
  const CONSTRUCTS_GLOB = "./src/constructs/**/*.ts";
7470
7349
  /**
7350
+ * Where a workspace keeps its constructs: at the root, beside the apps that
7351
+ * share them, rather than inside any one of them.
7352
+ */
7353
+ const WORKSPACE_CONSTRUCTS_GLOB = "./constructs/**/*.ts";
7354
+ /**
7471
7355
  * The database a project named `name` declares.
7472
7356
  *
7473
7357
  * Named after the project rather than `Db`, so two apps in one workspace can
@@ -7577,6 +7461,458 @@ export async function down(db: Kysely<unknown>): Promise<void> {
7577
7461
  }];
7578
7462
  }
7579
7463
 
7464
+ //#endregion
7465
+ //#region src/init/generators/agents.ts
7466
+ /**
7467
+ * `AGENTS.md` and `CLAUDE.md` for a scaffolded project.
7468
+ *
7469
+ * A generated project is the first thing an agent sees, and without this it
7470
+ * sees only the output: a `gkm.config.ts` naming three keys, a `constructs/`
7471
+ * directory, and a build step that reads both. None of that says what the
7472
+ * shape *is*, so an agent reconstructs it from the framework it recognises —
7473
+ * and writes an Express app beside a `RestApi`, or restates in config what a
7474
+ * construct already declared, which is the exact mistake v10 exists to make
7475
+ * impossible.
7476
+ *
7477
+ * Instructions live in `AGENTS.md` because that is the file every tool reads.
7478
+ * `CLAUDE.md` points at it rather than copying it: two files saying the same
7479
+ * thing is two files to keep in step, and the copy that drifts is always the
7480
+ * one nobody opened.
7481
+ *
7482
+ * Content is derived from the same `TemplateOptions` the rest of the scaffold
7483
+ * is built from, so a project with no database gets no database section, and a
7484
+ * worker gets no endpoint guide. A guide that documents what was not generated
7485
+ * teaches the reader to distrust it.
7486
+ */
7487
+ function generateAgentFiles(options, template) {
7488
+ return [{
7489
+ path: "AGENTS.md",
7490
+ content: agentsContent(options, template)
7491
+ }, {
7492
+ path: "CLAUDE.md",
7493
+ content: claudeContent()
7494
+ }];
7495
+ }
7496
+ /**
7497
+ * The pointer.
7498
+ *
7499
+ * Deliberately short and deliberately not a summary — a summary is a second
7500
+ * copy of the conventions that ages independently of the first.
7501
+ */
7502
+ function claudeContent() {
7503
+ return `# CLAUDE.md
7504
+
7505
+ The conventions for this project live in [AGENTS.md](./AGENTS.md).
7506
+
7507
+ Read that file before writing code here. It is the single copy, kept that way
7508
+ on purpose: anything repeated here would be a second thing to keep in step, and
7509
+ the stale one is always the copy nobody opened.
7510
+ `;
7511
+ }
7512
+ function agentsContent(options, template) {
7513
+ const { name: name$1, monorepo, database, telescope, studio, packageManager, services } = options;
7514
+ const constructsGlob = monorepo ? WORKSPACE_CONSTRUCTS_GLOB : CONSTRUCTS_GLOB;
7515
+ const isWorker = template.name === "worker";
7516
+ const isServerless = template.name === "serverless";
7517
+ const run = packageManager === "npm" ? "npx" : `${packageManager} exec`;
7518
+ const sections = [];
7519
+ sections.push(`# Agent conventions — ${name$1}
7520
+
7521
+ Rules for anyone, human or agent, writing code in this project. Built with
7522
+ [\`@geekmidas/toolbox\`](https://github.com/geekmidas/toolbox).
7523
+
7524
+ Read this before adding a file. Most of what follows exists because the obvious
7525
+ approach from another framework produces something that compiles, runs, and is
7526
+ wrong in a way nothing catches.`);
7527
+ sections.push(`## The one idea
7528
+
7529
+ **Constructs declare; config does not restate.**
7530
+
7531
+ A construct — a \`RestApi\`, a database, a bucket, a topic — is the single
7532
+ declaration of a thing. The build reads those declarations and derives
7533
+ everything downstream from them: which containers exist, which apps get built,
7534
+ what the OpenAPI spec says, which env vars a process needs.
7535
+
7536
+ \`gkm.config.ts\` holds only what no construct can answer:
7537
+
7538
+ \`\`\`typescript
7539
+ export default defineWorkspace({
7540
+ name: '${name$1}',
7541
+ constructs: '${constructsGlob}',
7542
+ secrets: { enabled: true },
7543
+ });
7544
+ \`\`\`
7545
+
7546
+ If you find yourself adding a key to config that names something already
7547
+ declared in a construct — a path, a port, a route, a container — stop. That is
7548
+ the drift this design removes. The second copy is the one that goes wrong.`);
7549
+ sections.push(`## Layout
7550
+
7551
+ \`\`\`
7552
+ ${layoutBlock(options, template)}
7553
+ \`\`\`
7554
+
7555
+ Handlers are found by **one glob**, and what kind a file exports is decided by
7556
+ the value it exports, not by the directory it sits in.`);
7557
+ sections.push(`## Commands
7558
+
7559
+ \`\`\`bash
7560
+ ${run} gkm dev # dev server, hot reload, services up, secrets injected
7561
+ ${run} gkm build # production build
7562
+ ${run} gkm test # tests, with secrets injected
7563
+ ${run} gkm exec -- <cmd> # run any command with secrets injected
7564
+ ${run} gkm openapi # regenerate the OpenAPI spec
7565
+ \`\`\`
7566
+
7567
+ Secrets are encrypted at rest and injected at run time:
7568
+
7569
+ \`\`\`bash
7570
+ ${run} gkm secrets:set DATABASE_URL postgres://...
7571
+ ${run} gkm secrets:show
7572
+ \`\`\`
7573
+
7574
+ **Never run a tool that needs project env vars directly.** It will see an empty
7575
+ environment and fail in a way that looks like a config bug. Go through
7576
+ \`gkm exec --\`, which is what injects them.`);
7577
+ if (!isWorker) sections.push(endpointSection(options));
7578
+ if (isWorker || isServerless || services.events) sections.push(backgroundSection(template));
7579
+ if (database) sections.push(databaseSection());
7580
+ sections.push(`## Environment
7581
+
7582
+ Environment is parsed once, through \`envkit\`, and never read from
7583
+ \`process.env\` directly:
7584
+
7585
+ \`\`\`typescript
7586
+ import { EnvironmentParser } from '@geekmidas/envkit';
7587
+ import { Credentials } from '@geekmidas/envkit/credentials';
7588
+
7589
+ export const envParser = new EnvironmentParser({
7590
+ ...process.env,
7591
+ ...Credentials,
7592
+ });
7593
+
7594
+ export const config = envParser
7595
+ .create((get) => ({
7596
+ port: get('PORT').string().transform(Number).default(3000),
7597
+ }))
7598
+ .parse();
7599
+ \`\`\`
7600
+
7601
+ \`Credentials\` is what \`gkm dev\` and \`gkm exec\` inject. A bare
7602
+ \`process.env.FOO\` bypasses both the decryption and the validation, so it reads
7603
+ as \`undefined\` in exactly the environments that matter.`);
7604
+ sections.push(conventionsSection());
7605
+ sections.push(testingSection(database));
7606
+ if (telescope || studio) {
7607
+ const tools = [telescope && "**Telescope** — request and exception monitoring", studio && "**Studio** — dev dashboard and database browser"].filter(Boolean);
7608
+ sections.push(`## Dev tools
7609
+
7610
+ ${tools.map((t) => `- ${t}`).join("\n")}
7611
+
7612
+ Both are derived from declared constructs. Neither needs wiring up by hand.`);
7613
+ }
7614
+ sections.push(`## Before you say it works
7615
+
7616
+ \`\`\`bash
7617
+ ${run} gkm build
7618
+ \`\`\`
7619
+
7620
+ A build is the only thing that proves the manifest still resolves. Code that
7621
+ typechecks can still declare two constructs with one id, or a handler no glob
7622
+ reaches — and neither shows up until something reads the graph.`);
7623
+ return `${sections.join("\n\n")}\n`;
7624
+ }
7625
+ function layoutBlock(options, template) {
7626
+ const { monorepo, apiPath, database } = options;
7627
+ const isWorker = template.name === "worker";
7628
+ if (monorepo) return `constructs/ # every construct: the surface, the database, topics
7629
+ ${apiPath}/ # the API app
7630
+ gkm.config.ts # name, constructs glob, secrets`;
7631
+ const lines = ["src/constructs/ # the surface, and anything it declares", "src/config/ # env parser, logger"];
7632
+ if (!isWorker) lines.push("src/endpoints/ # HTTP handlers");
7633
+ if (isWorker) {
7634
+ lines.push("src/crons/ # scheduled work");
7635
+ lines.push("src/subscribers/ # topic consumers");
7636
+ }
7637
+ if (database) lines.push("src/db/ # migrations, schema");
7638
+ lines.push("gkm.config.ts # name, constructs glob, secrets");
7639
+ return lines.join("\n");
7640
+ }
7641
+ function endpointSection(options) {
7642
+ const { database } = options;
7643
+ return `## Adding an endpoint
7644
+
7645
+ An endpoint is built **from the surface that serves it**. That is where its
7646
+ logger, environment parser and authorizers come from, so none of them is passed
7647
+ per endpoint:
7648
+
7649
+ \`\`\`typescript
7650
+ import { z } from 'zod';
7651
+ import { router } from '../constructs/router.ts';
7652
+
7653
+ export const createUser = router
7654
+ .post('/users')
7655
+ .body(z.object({ name: z.string(), email: z.email() }))
7656
+ .output(z.object({ id: z.uuid() }))
7657
+ .handle(async ({ body, logger${database ? ", services" : ""} }) => {
7658
+ logger.info({ name: body.name }, 'creating user');
7659
+ ${database ? ` const user = await services.database
7660
+ .insertInto('users')
7661
+ .values({ name: body.name, email: body.email })
7662
+ .returningAll()
7663
+ .executeTakeFirstOrThrow();
7664
+
7665
+ return { id: user.id };` : ` return { id: crypto.randomUUID() };`}
7666
+ });
7667
+ \`\`\`
7668
+
7669
+ **Export it.** An endpoint that is never exported is never found — the glob
7670
+ loads the module and filters by value, so a handler assigned to a local
7671
+ \`const\` simply does not exist as far as the build is concerned.
7672
+
7673
+ **Do not write an HTTP server.** No \`express()\`, no \`new Hono()\`, no
7674
+ \`app.listen\`. The entry is generated from the surface; a hand-written server
7675
+ beside it is a second app that shadows the real one.
7676
+
7677
+ ### Branching the factory
7678
+
7679
+ Share what a *group* of endpoints needs by branching once, in its own file:
7680
+
7681
+ \`\`\`typescript
7682
+ export const router = api.endpoints${database ? ".database(database)" : ""};
7683
+ export const sessionRouter = router.session(/* … */);
7684
+ \`\`\`
7685
+
7686
+ Branch for a group, never for a single endpoint — a one-endpoint branch is
7687
+ indirection with nothing on the other side of it.`;
7688
+ }
7689
+ function backgroundSection(template) {
7690
+ const isWorker = template.name === "worker";
7691
+ return `## Background work
7692
+
7693
+ Crons, subscribers and functions are declared the same way endpoints are: as
7694
+ exported values the build discovers.
7695
+
7696
+ \`\`\`typescript
7697
+ // a scheduled job
7698
+ import { c } from '@geekmidas/constructs/crons';
7699
+
7700
+ export const cleanup = c
7701
+ .schedule('rate(1 day)')
7702
+ .handle(async ({ logger }) => {
7703
+ logger.info('cleaning up');
7704
+ });
7705
+ \`\`\`
7706
+
7707
+ \`\`\`typescript
7708
+ // a topic subscriber
7709
+ import { s } from '@geekmidas/constructs/subscribers';
7710
+ import { userEvents } from '../constructs/topics.ts';
7711
+
7712
+ export const onUserCreated = s
7713
+ .topic(userEvents)
7714
+ .subscribe(['user.created'])
7715
+ .handle(async ({ events, logger }) => {
7716
+ logger.info({ count: events.length }, 'handling user events');
7717
+ });
7718
+ \`\`\`
7719
+
7720
+ Handlers receive a **batch**, not one event. Both transports deliver in
7721
+ batches, so a per-event round trip is a round trip per event for no reason.
7722
+
7723
+ ${isWorker ? `This project is background work and nothing else — there is no HTTP surface
7724
+ to add routes to.` : `Declaring a topic is what makes the broker exist. Nothing lists it in config.`}`;
7725
+ }
7726
+ function databaseSection() {
7727
+ return `## Database
7728
+
7729
+ The database is a **construct**, not a hand-rolled service. Declaring it is what
7730
+ makes a Postgres exist locally and a database exist on deploy — nothing names
7731
+ \`postgres\` in config.
7732
+
7733
+ An endpoint reaches it by declaring the dependency, and gets it typed:
7734
+
7735
+ \`\`\`typescript
7736
+ export const listUsers = router
7737
+ .get('/users')
7738
+ .output(z.array(z.object({ id: z.uuid() })))
7739
+ .handle(async ({ services }) => {
7740
+ return services.database.selectFrom('users').selectAll().execute();
7741
+ });
7742
+ \`\`\`
7743
+
7744
+ Queries go through Kysely. Never open your own connection pool — the one the
7745
+ construct provides is the one that gets credentials, pooling and shutdown.`;
7746
+ }
7747
+ function conventionsSection() {
7748
+ return `## Conventions
7749
+
7750
+ **Zod formats are top-level schemas.**
7751
+
7752
+ \`\`\`typescript
7753
+ // yes
7754
+ z.email()
7755
+ z.url()
7756
+ z.uuid()
7757
+
7758
+ // no
7759
+ z.string().email()
7760
+ z.string().url()
7761
+ \`\`\`
7762
+
7763
+ Zod 4 made each format a schema in its own right. The chained form still runs,
7764
+ but it types as a \`ZodString\` carrying a check, so it cannot be composed or
7765
+ narrowed — and it emits different JSON Schema, which means a published API
7766
+ contract would depend on which spelling someone reached for.
7767
+
7768
+ \`envkit\` is not affected: \`get('ADMIN_EMAIL').string().email()\` is its own
7769
+ builder, not Zod's. Leave it alone.
7770
+
7771
+ **Style.** Two-space indent, single quotes, semicolons, trailing commas, 80
7772
+ columns. \`import type\` for type-only imports. Run the formatter rather than
7773
+ matching it by hand.
7774
+
7775
+ **Construct ids are unique.** Two constructs sharing an id fails discovery for
7776
+ the whole project, not just the second one.`;
7777
+ }
7778
+ function testingSection(database) {
7779
+ return `## Testing
7780
+
7781
+ **Integration over unit.** Prefer the real dependency to a mock — a real
7782
+ database, a real cache. Mocks are for things you genuinely cannot run:
7783
+ the filesystem, the clock, external HTTP (use MSW for that).
7784
+
7785
+ **Test behaviour, not implementation.** Assert what the code did, not which
7786
+ functions it called on the way.
7787
+
7788
+ \`\`\`bash
7789
+ ${database ? "gkm test # starts services, injects secrets, then runs vitest" : "gkm test"}
7790
+ \`\`\`
7791
+
7792
+ Run tests through \`gkm test\`, not \`vitest\` directly${database ? " — it is what starts the database and injects the secrets the suite needs" : ""}.`;
7793
+ }
7794
+
7795
+ //#endregion
7796
+ //#region src/init/versions.ts
7797
+ const require$1 = createRequire(import.meta.url);
7798
+ function loadPackageJson() {
7799
+ try {
7800
+ return require$1("../package.json");
7801
+ } catch {
7802
+ return require$1("../../package.json");
7803
+ }
7804
+ }
7805
+ const pkg = loadPackageJson();
7806
+ /**
7807
+ * CLI version resolved from package.json at runtime
7808
+ */
7809
+ const CLI_VERSION = `~${pkg.version}`;
7810
+ /**
7811
+ * Package versions for @geekmidas packages
7812
+ *
7813
+ * AUTO-GENERATED (except CLI) - Do not edit manually
7814
+ * Run: pnpm --filter @geekmidas/cli sync-versions
7815
+ */
7816
+ const GEEKMIDAS_VERSIONS = {
7817
+ "@geekmidas/audit": "~10.0.0-alpha.3",
7818
+ "@geekmidas/auth": "~10.0.0-alpha.3",
7819
+ "@geekmidas/cache": "~10.0.0-alpha.3",
7820
+ "@geekmidas/client": "~10.0.0-alpha.3",
7821
+ "@geekmidas/cloud": "~10.0.0-alpha.3",
7822
+ "@geekmidas/constructs": "~10.0.0-alpha.3",
7823
+ "@geekmidas/db": "~10.0.0-alpha.3",
7824
+ "@geekmidas/emailkit": "~10.0.0-alpha.3",
7825
+ "@geekmidas/envkit": "~10.0.0-alpha.3",
7826
+ "@geekmidas/errors": "~10.0.0-alpha.3",
7827
+ "@geekmidas/events": "~10.0.0-alpha.3",
7828
+ "@geekmidas/logger": "~10.0.0-alpha.3",
7829
+ "@geekmidas/manifest": "~10.0.0-alpha.3",
7830
+ "@geekmidas/rate-limit": "~10.0.0-alpha.3",
7831
+ "@geekmidas/schema": "~10.0.0-alpha.3",
7832
+ "@geekmidas/services": "~10.0.0-alpha.3",
7833
+ "@geekmidas/storage": "~10.0.0-alpha.3",
7834
+ "@geekmidas/studio": "~10.0.0-alpha.3",
7835
+ "@geekmidas/telescope": "~10.0.0-alpha.3",
7836
+ "@geekmidas/testkit": "~10.0.0-alpha.3",
7837
+ "@geekmidas/cli": CLI_VERSION
7838
+ };
7839
+
7840
+ //#endregion
7841
+ //#region src/init/generators/auth.ts
7842
+ /**
7843
+ * Generate auth app files for fullstack template
7844
+ * Uses better-auth with magic link authentication
7845
+ */
7846
+ function generateAuthAppFiles(options) {
7847
+ if (!options.monorepo || options.template !== "fullstack") return [];
7848
+ const packageName = `@${options.name}/auth`;
7849
+ const modelsPackage = `@${options.name}/models`;
7850
+ const packageJson = {
7851
+ name: packageName,
7852
+ version: "0.0.1",
7853
+ private: true,
7854
+ type: "module",
7855
+ scripts: {
7856
+ dev: "gkm dev --entry ./src/index.ts",
7857
+ build: "tsc",
7858
+ start: "node dist/index.js",
7859
+ typecheck: "tsc --noEmit",
7860
+ "db:migrate": "gkm exec -- npx @better-auth/cli migrate",
7861
+ "db:generate": "gkm exec -- npx @better-auth/cli generate"
7862
+ },
7863
+ dependencies: {
7864
+ [modelsPackage]: "workspace:*",
7865
+ "@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
7866
+ "@geekmidas/logger": GEEKMIDAS_VERSIONS["@geekmidas/logger"],
7867
+ "@hono/node-server": "~1.13.0",
7868
+ "better-auth": "~1.2.0",
7869
+ hono: "~4.8.0",
7870
+ kysely: "~0.27.0",
7871
+ pg: "~8.13.0"
7872
+ },
7873
+ devDependencies: {
7874
+ "@geekmidas/cli": GEEKMIDAS_VERSIONS["@geekmidas/cli"],
7875
+ "@types/node": "~22.0.0",
7876
+ "@types/pg": "~8.11.0",
7877
+ tsx: "~4.20.0",
7878
+ typescript: "~5.8.2"
7879
+ }
7880
+ };
7881
+ const tsConfig = {
7882
+ extends: "../../tsconfig.json",
7883
+ compilerOptions: {
7884
+ noEmit: true,
7885
+ allowImportingTsExtensions: true,
7886
+ baseUrl: ".",
7887
+ paths: {
7888
+ "~/*": ["./src/*"],
7889
+ [`@${options.name}/*`]: ["../../packages/*/src"]
7890
+ }
7891
+ },
7892
+ include: ["src/**/*.ts"],
7893
+ exclude: ["node_modules", "dist"]
7894
+ };
7895
+ const gitignore = `node_modules/
7896
+ dist/
7897
+ .env.local
7898
+ *.log
7899
+ `;
7900
+ return [
7901
+ {
7902
+ path: "apps/auth/package.json",
7903
+ content: `${JSON.stringify(packageJson, null, 2)}\n`
7904
+ },
7905
+ {
7906
+ path: "apps/auth/tsconfig.json",
7907
+ content: `${JSON.stringify(tsConfig, null, 2)}\n`
7908
+ },
7909
+ {
7910
+ path: "apps/auth/.gitignore",
7911
+ content: gitignore
7912
+ }
7913
+ ];
7914
+ }
7915
+
7580
7916
  //#endregion
7581
7917
  //#region src/init/generators/config.ts
7582
7918
  /**
@@ -9199,7 +9535,7 @@ export default defineWorkspace({
9199
9535
  // declared bucket is why MinIO does, a declared topic is why a broker does —
9200
9536
  // none of it listed here. It is also where the apps come from: a
9201
9537
  // \`StaticSite\` is an app, and so is every \`RestApi\`.
9202
- constructs: './constructs/**/*.ts',
9538
+ constructs: '${WORKSPACE_CONSTRUCTS_GLOB}',
9203
9539
 
9204
9540
  secrets: {
9205
9541
  enabled: true,
@@ -13183,6 +13519,7 @@ async function initCommand(projectName, options = {}) {
13183
13519
  ] : [];
13184
13520
  const dockerFiles = isMonorepo$1 && baseTemplate ? generateDockerFiles(templateOptions, baseTemplate, dbApps) : [];
13185
13521
  const rootFiles = baseTemplate ? [
13522
+ ...generateAgentFiles(templateOptions, baseTemplate),
13186
13523
  ...generateMonorepoFiles(templateOptions, baseTemplate),
13187
13524
  ...generateRootConstructs(templateOptions),
13188
13525
  ...generateModelsPackage(templateOptions)
@@ -13571,7 +13908,7 @@ async function testCommand(options = {}) {
13571
13908
  console.log(`\n🧪 Running tests with ${stage} environment...\n`);
13572
13909
  const autoSetup = options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP);
13573
13910
  if (autoSetup) {
13574
- const { ensureStageSecrets } = await import("./setup-DCuudnCl.mjs");
13911
+ const { ensureStageSecrets } = await import("./setup-ZijS4qeC.mjs");
13575
13912
  const generated = await ensureStageSecrets(stage, cwd);
13576
13913
  if (generated) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
13577
13914
  }
@@ -14046,7 +14383,7 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
14046
14383
  const { workspace } = await loadWorkspaceConfig$1();
14047
14384
  const secrets = await readStageSecrets$1(options.stage, workspace.root);
14048
14385
  if (secrets) {
14049
- const { derivedContainers: derivedContainers$1 } = await import("./workspace-DK4WWJIn.mjs");
14386
+ const { derivedContainers: derivedContainers$1 } = await import("./workspace-k4l3rsFh.mjs");
14050
14387
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers$1(workspace, options.stage));
14051
14388
  if (result) {
14052
14389
  await writeStageSecrets$1(result.secrets, workspace.root);
@@ -14075,7 +14412,7 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
14075
14412
  console.error(`No remote secrets found for stage "${options.stage}".`);
14076
14413
  process.exit(1);
14077
14414
  }
14078
- const { derivedContainers: derivedContainers$1 } = await import("./workspace-DK4WWJIn.mjs");
14415
+ const { derivedContainers: derivedContainers$1 } = await import("./workspace-k4l3rsFh.mjs");
14079
14416
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers$1(workspace, options.stage));
14080
14417
  if (result) {
14081
14418
  secrets = result.secrets;
@@ -14102,7 +14439,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
14102
14439
  console.error(`No secrets found for stage "${options.stage}". Run "gkm secrets:init --stage ${options.stage}" first.`);
14103
14440
  process.exit(1);
14104
14441
  }
14105
- const { derivedContainers: derivedContainers$1 } = await import("./workspace-DK4WWJIn.mjs");
14442
+ const { derivedContainers: derivedContainers$1 } = await import("./workspace-k4l3rsFh.mjs");
14106
14443
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers$1(workspace, options.stage));
14107
14444
  if (!result) {
14108
14445
  console.log(`\n✓ Secrets for stage "${options.stage}" are up-to-date`);