@beignet/cli 0.0.9 → 0.0.11

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 (76) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +70 -21
  3. package/dist/choices.d.ts +32 -3
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +54 -3
  6. package/dist/choices.js.map +1 -1
  7. package/dist/create-prompts.d.ts +5 -4
  8. package/dist/create-prompts.d.ts.map +1 -1
  9. package/dist/create-prompts.js +22 -4
  10. package/dist/create-prompts.js.map +1 -1
  11. package/dist/create.d.ts +7 -1
  12. package/dist/create.d.ts.map +1 -1
  13. package/dist/create.js +13 -3
  14. package/dist/create.js.map +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +47 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/make.d.ts +21 -1
  19. package/dist/make.d.ts.map +1 -1
  20. package/dist/make.js +210 -37
  21. package/dist/make.js.map +1 -1
  22. package/dist/mcp.d.ts +19 -0
  23. package/dist/mcp.d.ts.map +1 -0
  24. package/dist/mcp.js +250 -0
  25. package/dist/mcp.js.map +1 -0
  26. package/dist/templates/agents.d.ts +14 -0
  27. package/dist/templates/agents.d.ts.map +1 -0
  28. package/dist/templates/agents.js +98 -0
  29. package/dist/templates/agents.js.map +1 -0
  30. package/dist/templates/base.d.ts.map +1 -1
  31. package/dist/templates/base.js +79 -9
  32. package/dist/templates/base.js.map +1 -1
  33. package/dist/templates/db/index.d.ts +21 -0
  34. package/dist/templates/db/index.d.ts.map +1 -0
  35. package/dist/templates/db/index.js +14 -0
  36. package/dist/templates/db/index.js.map +1 -0
  37. package/dist/templates/db/mysql.d.ts +4 -0
  38. package/dist/templates/db/mysql.d.ts.map +1 -0
  39. package/dist/templates/db/mysql.js +972 -0
  40. package/dist/templates/db/mysql.js.map +1 -0
  41. package/dist/templates/db/postgres.d.ts +4 -0
  42. package/dist/templates/db/postgres.d.ts.map +1 -0
  43. package/dist/templates/db/postgres.js +863 -0
  44. package/dist/templates/db/postgres.js.map +1 -0
  45. package/dist/templates/db/sqlite.d.ts +3 -0
  46. package/dist/templates/db/sqlite.d.ts.map +1 -0
  47. package/dist/templates/db/sqlite.js +878 -0
  48. package/dist/templates/db/sqlite.js.map +1 -0
  49. package/dist/templates/index.d.ts +5 -5
  50. package/dist/templates/index.d.ts.map +1 -1
  51. package/dist/templates/index.js +24 -20
  52. package/dist/templates/index.js.map +1 -1
  53. package/dist/templates/server.d.ts +3 -3
  54. package/dist/templates/server.d.ts.map +1 -1
  55. package/dist/templates/server.js +149 -97
  56. package/dist/templates/server.js.map +1 -1
  57. package/dist/templates/shared.d.ts +34 -1
  58. package/dist/templates/shared.d.ts.map +1 -1
  59. package/dist/templates/shared.js +45 -0
  60. package/dist/templates/shared.js.map +1 -1
  61. package/package.json +5 -3
  62. package/src/choices.ts +70 -3
  63. package/src/create-prompts.ts +25 -3
  64. package/src/create.ts +24 -2
  65. package/src/index.ts +58 -5
  66. package/src/make.ts +265 -34
  67. package/src/mcp.ts +367 -0
  68. package/src/templates/agents.ts +103 -0
  69. package/src/templates/base.ts +95 -9
  70. package/src/templates/db/index.ts +34 -0
  71. package/src/templates/db/mysql.ts +976 -0
  72. package/src/templates/db/postgres.ts +867 -0
  73. package/src/templates/{db.ts → db/sqlite.ts} +31 -152
  74. package/src/templates/index.ts +27 -19
  75. package/src/templates/server.ts +161 -97
  76. package/src/templates/shared.ts +90 -1
package/src/mcp.ts ADDED
@@ -0,0 +1,367 @@
1
+ import path from "node:path";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { applyDoctorFixes, inspectApp } from "./inspect.js";
6
+ import { lintApp } from "./lint.js";
7
+ import {
8
+ type MakeResourceResult,
9
+ makeAdapter,
10
+ makeContract,
11
+ makeEvent,
12
+ makeFactory,
13
+ makeFeature,
14
+ makeFeatureAddonChoices,
15
+ makeJob,
16
+ makeListener,
17
+ makeNotification,
18
+ makePolicy,
19
+ makePort,
20
+ makeResource,
21
+ makeSchedule,
22
+ makeSeed,
23
+ makeTask,
24
+ makeTest,
25
+ makeUpload,
26
+ makeUseCase,
27
+ } from "./make.js";
28
+
29
+ /**
30
+ * Options for building or running the Beignet MCP server.
31
+ */
32
+ export type McpServerOptions = {
33
+ cwd: string;
34
+ version: string;
35
+ };
36
+
37
+ const makeArtifactChoices = [
38
+ "adapter",
39
+ "contract",
40
+ "event",
41
+ "factory",
42
+ "feature",
43
+ "job",
44
+ "listener",
45
+ "notification",
46
+ "policy",
47
+ "port",
48
+ "resource",
49
+ "schedule",
50
+ "seed",
51
+ "task",
52
+ "test",
53
+ "upload",
54
+ "use-case",
55
+ ] as const;
56
+
57
+ type MakeArtifact = (typeof makeArtifactChoices)[number];
58
+
59
+ const makeInputSchema = {
60
+ artifact: z
61
+ .enum(makeArtifactChoices)
62
+ .describe("Kind of artifact to generate."),
63
+ name: z
64
+ .string()
65
+ .describe(
66
+ "Artifact name. Feature-owned artifacts use feature-scoped paths such as posts/backfill.",
67
+ ),
68
+ with: z
69
+ .array(z.enum(makeFeatureAddonChoices))
70
+ .optional()
71
+ .describe(
72
+ "feature only: add feature-owned artifacts such as policy, events, jobs, notifications, tasks, ui, and uploads.",
73
+ ),
74
+ event: z
75
+ .string()
76
+ .optional()
77
+ .describe(
78
+ "listener only (required): event to listen to, for example posts/published.",
79
+ ),
80
+ cron: z
81
+ .string()
82
+ .optional()
83
+ .describe("schedule only: cron expression. Defaults to 0 9 * * *."),
84
+ timezone: z
85
+ .string()
86
+ .optional()
87
+ .describe("schedule only: IANA timezone, for example America/Chicago."),
88
+ route: z
89
+ .boolean()
90
+ .optional()
91
+ .describe("schedule only: generate a Next.js cron route for the schedule."),
92
+ auth: z
93
+ .boolean()
94
+ .optional()
95
+ .describe(
96
+ "resource only: generate policy metadata and use-case authorization checks for mutating routes.",
97
+ ),
98
+ tenant: z
99
+ .boolean()
100
+ .optional()
101
+ .describe(
102
+ "resource only: generate tenant-scoped schemas, repository methods, and use-case checks.",
103
+ ),
104
+ events: z
105
+ .boolean()
106
+ .optional()
107
+ .describe(
108
+ "resource only: generate domain event definitions and publish resource events.",
109
+ ),
110
+ softDelete: z
111
+ .boolean()
112
+ .optional()
113
+ .describe(
114
+ "resource only: generate soft-delete persistence that archives rows with deletedAt.",
115
+ ),
116
+ force: z
117
+ .boolean()
118
+ .optional()
119
+ .describe("Overwrite conflicting files instead of failing."),
120
+ dryRun: z
121
+ .boolean()
122
+ .optional()
123
+ .describe("Preview generated changes without writing files."),
124
+ };
125
+
126
+ type MakeToolInput = {
127
+ artifact: MakeArtifact;
128
+ name: string;
129
+ with?: Array<(typeof makeFeatureAddonChoices)[number]>;
130
+ event?: string;
131
+ cron?: string;
132
+ timezone?: string;
133
+ route?: boolean;
134
+ auth?: boolean;
135
+ tenant?: boolean;
136
+ events?: boolean;
137
+ softDelete?: boolean;
138
+ force?: boolean;
139
+ dryRun?: boolean;
140
+ };
141
+
142
+ type McpToolResult = {
143
+ content: Array<{ type: "text"; text: string }>;
144
+ isError?: boolean;
145
+ };
146
+
147
+ function jsonResult(value: unknown): McpToolResult {
148
+ return {
149
+ content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
150
+ };
151
+ }
152
+
153
+ function errorResult(error: unknown): McpToolResult {
154
+ return {
155
+ isError: true,
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: error instanceof Error ? error.message : String(error),
160
+ },
161
+ ],
162
+ };
163
+ }
164
+
165
+ async function safeToolResult(
166
+ run: () => Promise<McpToolResult>,
167
+ ): Promise<McpToolResult> {
168
+ try {
169
+ return await run();
170
+ } catch (error) {
171
+ return errorResult(error);
172
+ }
173
+ }
174
+
175
+ async function runMakeTool(
176
+ cwd: string,
177
+ input: MakeToolInput,
178
+ ): Promise<MakeResourceResult> {
179
+ const base = {
180
+ name: input.name,
181
+ cwd,
182
+ force: input.force,
183
+ dryRun: input.dryRun,
184
+ };
185
+
186
+ switch (input.artifact) {
187
+ case "adapter":
188
+ return makeAdapter(base);
189
+ case "contract":
190
+ return makeContract(base);
191
+ case "event":
192
+ return makeEvent(base);
193
+ case "factory":
194
+ return makeFactory(base);
195
+ case "feature":
196
+ return makeFeature({ ...base, with: input.with });
197
+ case "job":
198
+ return makeJob(base);
199
+ case "listener":
200
+ if (!input.event) {
201
+ throw new Error(
202
+ "make listener requires an event, for example event: posts/published.",
203
+ );
204
+ }
205
+ return makeListener({ ...base, event: input.event });
206
+ case "notification":
207
+ return makeNotification(base);
208
+ case "policy":
209
+ return makePolicy(base);
210
+ case "port":
211
+ return makePort(base);
212
+ case "resource":
213
+ return makeResource({
214
+ ...base,
215
+ auth: input.auth,
216
+ tenant: input.tenant,
217
+ events: input.events,
218
+ softDelete: input.softDelete,
219
+ });
220
+ case "schedule":
221
+ return makeSchedule({
222
+ ...base,
223
+ cron: input.cron,
224
+ timezone: input.timezone,
225
+ route: input.route,
226
+ });
227
+ case "seed":
228
+ return makeSeed(base);
229
+ case "task":
230
+ return makeTask(base);
231
+ case "test":
232
+ return makeTest(base);
233
+ case "upload":
234
+ return makeUpload(base);
235
+ case "use-case":
236
+ return makeUseCase(base);
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Build the Beignet MCP server with routes, doctor, lint, and make tools.
242
+ * Every tool resolves against the app directory captured at launch.
243
+ */
244
+ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
245
+ const cwd = path.resolve(options.cwd);
246
+ const server = new McpServer({
247
+ name: "beignet",
248
+ version: options.version,
249
+ });
250
+
251
+ server.registerTool(
252
+ "routes",
253
+ {
254
+ description:
255
+ "List the app's registered Beignet HTTP routes as JSON: method, path, contract export, contract file, and route handler mapping.",
256
+ annotations: { readOnlyHint: true },
257
+ },
258
+ async () =>
259
+ safeToolResult(async () => {
260
+ const result = await inspectApp({ cwd });
261
+ return jsonResult(
262
+ result.routes.map((route) => ({
263
+ method: route.method,
264
+ path: route.path,
265
+ contract: route.exportName,
266
+ file: route.file,
267
+ handlerFile: route.handlerFile,
268
+ handlerSource: route.handlerSource,
269
+ metadata: route.metadata,
270
+ })),
271
+ );
272
+ }),
273
+ );
274
+
275
+ server.registerTool(
276
+ "doctor",
277
+ {
278
+ description:
279
+ "Check app wiring and Beignet conventions, returning diagnostics, the detected convention, and applied fixes as JSON. Strict mode (the default) is the CI bar and includes CI-oriented warnings. Read-only; use doctor_fix to repair registration drift.",
280
+ inputSchema: {
281
+ strict: z
282
+ .boolean()
283
+ .optional()
284
+ .describe("Include CI-oriented warnings. Defaults to true."),
285
+ },
286
+ annotations: { readOnlyHint: true },
287
+ },
288
+ async (input) =>
289
+ safeToolResult(async () => {
290
+ const result = await inspectApp({ cwd, strict: input.strict ?? true });
291
+ return jsonResult({
292
+ diagnostics: result.diagnostics,
293
+ convention: result.convention,
294
+ fixes: result.fixes,
295
+ });
296
+ }),
297
+ );
298
+
299
+ server.registerTool(
300
+ "doctor_fix",
301
+ {
302
+ description:
303
+ "Apply low-risk doctor fixes, then re-check the app. Repairs route-group, schedule, task, and outbox registration drift; listener registration drift is report-only. Returns applied fixes and remaining diagnostics as JSON.",
304
+ inputSchema: {
305
+ strict: z
306
+ .boolean()
307
+ .optional()
308
+ .describe("Include CI-oriented warnings. Defaults to true."),
309
+ },
310
+ },
311
+ async (input) =>
312
+ safeToolResult(async () => {
313
+ const strict = input.strict ?? true;
314
+ const fixes = await applyDoctorFixes({ cwd, strict });
315
+ const result = await inspectApp({ cwd, strict });
316
+ return jsonResult({
317
+ diagnostics: result.diagnostics,
318
+ convention: result.convention,
319
+ fixes,
320
+ });
321
+ }),
322
+ );
323
+
324
+ server.registerTool(
325
+ "lint",
326
+ {
327
+ description:
328
+ "Check Beignet dependency-direction conventions across domain, use cases, routes, components, and infra. Returns lint diagnostics as JSON.",
329
+ annotations: { readOnlyHint: true },
330
+ },
331
+ async () =>
332
+ safeToolResult(async () => {
333
+ return jsonResult(await lintApp({ cwd }));
334
+ }),
335
+ );
336
+
337
+ server.registerTool(
338
+ "make",
339
+ {
340
+ description:
341
+ "Generate Beignet app files for one artifact: adapter, contract, event, factory, feature, job, listener, notification, policy, port, resource, schedule, seed, task, test, upload, or use-case. Generators auto-register artifacts, so doctor stays clean. Options by artifact: feature accepts with (addons); resource accepts auth, tenant, events, softDelete; listener requires event; schedule accepts cron, timezone, route. All artifacts accept force and dryRun. Returns created, updated, and skipped files as JSON.",
342
+ inputSchema: makeInputSchema,
343
+ },
344
+ async (input) =>
345
+ safeToolResult(async () => {
346
+ return jsonResult(await runMakeTool(cwd, input));
347
+ }),
348
+ );
349
+
350
+ return server;
351
+ }
352
+
353
+ /**
354
+ * Run the Beignet MCP server over stdio until the client disconnects.
355
+ * Stdout belongs to the transport; diagnostics must go to stderr.
356
+ */
357
+ export async function runMcpServer(options: McpServerOptions): Promise<void> {
358
+ const server = buildBeignetMcpServer(options);
359
+ const transport = new StdioServerTransport();
360
+ const closed = new Promise<void>((resolve) => {
361
+ server.server.onclose = () => {
362
+ resolve();
363
+ };
364
+ });
365
+ await server.connect(transport);
366
+ await closed;
367
+ }
@@ -0,0 +1,103 @@
1
+ import { json, packageRunner, type TemplateContext } from "./shared.js";
2
+
3
+ /**
4
+ * AGENTS.md teaches coding agents the conventions that are not discoverable
5
+ * from the code: registration wiring, generators, and the validation loop.
6
+ * README.md owns setup and the app map; this file must not duplicate it.
7
+ */
8
+ export function agentsMd(ctx: TemplateContext): string {
9
+ const cli = packageRunner(ctx);
10
+ const test =
11
+ ctx.packageManager === "npm"
12
+ ? "npm run test"
13
+ : `${ctx.packageManager} run test`;
14
+ const typecheck =
15
+ ctx.packageManager === "npm"
16
+ ? "npm run typecheck"
17
+ : `${ctx.packageManager} run typecheck`;
18
+
19
+ return `# ${ctx.name} — agent guide
20
+
21
+ This is a Beignet contract-first app. Full framework docs live at
22
+ https://beignetjs.com (agent-friendly index at https://beignetjs.com/llms.txt).
23
+ README.md covers setup and the app map; this file covers what is not
24
+ discoverable from the code.
25
+
26
+ ## Registration is not automatic
27
+
28
+ Creating an artifact file does not wire it into the app. Non-registration is
29
+ a silent failure — the file exists but never runs:
30
+
31
+ - Feature route groups in \`features/<feature>/routes.ts\` must be composed in
32
+ \`server/routes.ts\` via \`defineRoutes([...])\`.
33
+ - Schedules must be added to the \`schedules\` array in \`server/schedules.ts\`.
34
+ - Tasks must be added to \`defineTasks([...])\` in \`server/tasks.ts\`.
35
+ - Outbox events and jobs must be registered in \`defineOutboxRegistry({...})\`
36
+ in \`server/outbox.ts\`.
37
+ - Listeners must be wired through a \`registerListeners(...)\` call in infra
38
+ wiring.
39
+
40
+ The starter ships no workflow registries — generators create them on first
41
+ use, so their absence is fine. \`${cli} doctor\` detects registration drift.
42
+ \`${cli} doctor --fix\` repairs route-group, schedule, task, and outbox
43
+ registration; listener drift is report-only and must be fixed by hand.
44
+
45
+ ## Prefer generators
46
+
47
+ \`${cli} make <artifact> <name>\` creates correctly placed, pre-registered
48
+ files — prefer it over hand-writing them. See \`${cli} make --help\` for the
49
+ artifact list. After changing the Drizzle schema in \`infra/db/schema/\`, run
50
+ \`${cli} db generate\` then \`${cli} db migrate\`.
51
+
52
+ ## Validation loop
53
+
54
+ Run after every change:
55
+
56
+ \`\`\`bash
57
+ ${cli} lint
58
+ ${cli} doctor --strict
59
+ ${test}
60
+ ${typecheck}
61
+ \`\`\`
62
+
63
+ ## Placement rules
64
+
65
+ - Feature artifacts live under \`features/<feature>/\`, with tests in
66
+ \`features/<feature>/tests/\` (not \`__tests__/\`).
67
+ - Domain and use-case code must not import infra, providers, or React.
68
+ - Routes must not import concrete infra.
69
+
70
+ ## Naming grammar
71
+
72
+ - \`defineX\` declares things you register: contracts, routes, jobs,
73
+ schedules, and the rest.
74
+ - \`createX\` builds runtime objects you call: servers, clients, providers,
75
+ and the \`createX<AppContext>()\` factories that return app-bound
76
+ \`defineX\` builders.
77
+
78
+ ## MCP server
79
+
80
+ \`.mcp.json\` registers the \`beignet mcp\` stdio server, which exposes
81
+ routes/doctor/lint/make as structured tools named exactly: \`routes\`,
82
+ \`doctor\`, \`doctor_fix\`, \`lint\`, \`make\`. Clients that do not read
83
+ \`.mcp.json\` can launch \`${cli} mcp\` directly.
84
+ `;
85
+ }
86
+
87
+ /**
88
+ * .mcp.json registers the `beignet mcp` stdio server for MCP-aware clients.
89
+ * The runner must not print banners on stdout, which corrupts JSON-RPC, so
90
+ * the config launches the locally installed CLI bin via bunx/npx.
91
+ */
92
+ export function mcpJson(ctx: TemplateContext): string {
93
+ const command = ctx.packageManager === "bun" ? "bunx" : "npx";
94
+
95
+ return json({
96
+ mcpServers: {
97
+ beignet: {
98
+ command,
99
+ args: ["beignet", "mcp"],
100
+ },
101
+ },
102
+ });
103
+ }
@@ -1,3 +1,4 @@
1
+ import { databaseLocalUrl, databaseStartCommand } from "../choices.js";
1
2
  import { shadcnDependencies, shadcnDevDependencies } from "./shadcn.js";
2
3
  import {
3
4
  externalVersions,
@@ -17,7 +18,6 @@ export function packageJson(ctx: TemplateContext): string {
17
18
  "@beignet/provider-auth-better-auth": ctx.beignetVersion,
18
19
  "@beignet/provider-db-drizzle": ctx.beignetVersion,
19
20
  "@beignet/provider-logger-pino": ctx.beignetVersion,
20
- "@libsql/client": externalVersions.libsqlClient,
21
21
  "better-auth": externalVersions.betterAuth,
22
22
  "drizzle-orm": externalVersions.drizzleOrm,
23
23
  next: externalVersions.next,
@@ -27,6 +27,19 @@ export function packageJson(ctx: TemplateContext): string {
27
27
  zod: externalVersions.zod,
28
28
  };
29
29
 
30
+ // Ship only the selected database driver.
31
+ switch (ctx.database) {
32
+ case "postgres":
33
+ dependencies.pg = externalVersions.pg;
34
+ break;
35
+ case "mysql":
36
+ dependencies.mysql2 = externalVersions.mysql2;
37
+ break;
38
+ default:
39
+ dependencies["@libsql/client"] = externalVersions.libsqlClient;
40
+ break;
41
+ }
42
+
30
43
  if (!ctx.api) {
31
44
  dependencies["@beignet/react-query"] = ctx.beignetVersion;
32
45
  dependencies["@beignet/react-hook-form"] = ctx.beignetVersion;
@@ -61,6 +74,12 @@ export function packageJson(ctx: TemplateContext): string {
61
74
  typescript: externalVersions.typescript,
62
75
  };
63
76
 
77
+ if (ctx.database === "postgres") {
78
+ // PGlite backs infra/db/test-database.ts with in-process Postgres.
79
+ devDependencies["@electric-sql/pglite"] = externalVersions.pglite;
80
+ devDependencies["@types/pg"] = externalVersions.typesPg;
81
+ }
82
+
64
83
  if (!ctx.api) {
65
84
  for (const [name, version] of Object.entries(shadcnDevDependencies)) {
66
85
  devDependencies[name] = version;
@@ -134,6 +153,30 @@ export function readme(ctx: TemplateContext): string {
134
153
  - \`client/\` owns the typed API client, React Query helpers, and the Better Auth client.
135
154
  `;
136
155
 
156
+ const startDatabase =
157
+ ctx.database === "sqlite"
158
+ ? ""
159
+ : `## Start the database
160
+
161
+ \`\`\`bash
162
+ ${databaseStartCommand(ctx.database, ctx.name)}
163
+ \`\`\`
164
+
165
+ The starter's \`.env.example\` points at this local ${ctx.database === "postgres" ? "Postgres" : "MySQL"} container. Point \`${ctx.database === "postgres" ? "POSTGRES_DB_URL" : "MYSQL_DB_URL"}\` at your own server to skip Docker.
166
+
167
+ `;
168
+
169
+ const testingNotes =
170
+ ctx.database === "postgres"
171
+ ? `
172
+ \`infra/db/test-database.ts\` runs repository tests against in-process Postgres via PGlite, so \`${test}\` needs no database server.
173
+ `
174
+ : ctx.database === "mysql"
175
+ ? `
176
+ The starter's shipped tests use in-memory ports and need no database server. Database-backed tests can use \`createTestDatabase()\` from \`infra/db/test-database.ts\`, which requires \`MYSQL_TEST_URL\` (see the docker one-liner in \`.env.example\`), creates a uniquely named database per call, and drops it on close. Gate such tests with \`test.skipIf(!isMysqlTestDatabaseAvailable())\`.
177
+ `
178
+ : "";
179
+
137
180
  return `# ${ctx.name}
138
181
 
139
182
  Beignet app scaffolded with \`@beignet/cli\`.
@@ -145,7 +188,7 @@ ${install}
145
188
  cp .env.example .env.local
146
189
  \`\`\`
147
190
 
148
- ## Prepare the database
191
+ ${startDatabase}## Prepare the database
149
192
 
150
193
  \`\`\`bash
151
194
  ${cli} db migrate
@@ -173,7 +216,7 @@ ${typecheck}
173
216
  \`\`\`
174
217
 
175
218
  \`routes\` shows the contracts Beignet can inspect. \`lint\` checks dependency direction. \`doctor\` catches route, OpenAPI, and resource drift.
176
-
219
+ ${testingNotes}
177
220
  ## Build for production
178
221
 
179
222
  \`\`\`bash
@@ -207,16 +250,42 @@ ${cli} doctor
207
250
  - \`infra/db/schema/\` contains the Drizzle schema, \`drizzle/\` contains the checked-in migrations, and \`infra/todos/\` contains the durable todo repository adapter.
208
251
  - \`server/routes.ts\` keeps the central route registry and OpenAPI contract list.
209
252
  - \`server/context.ts\` declares the context blueprint shared by the server and route tests.
210
- - \`server/providers.ts\` wires devtools, Better Auth, pino, Drizzle/libSQL, and the starter database provider.
253
+ - \`server/providers.ts\` wires devtools, Better Auth, pino, ${
254
+ ctx.database === "sqlite"
255
+ ? "Drizzle/libSQL"
256
+ : ctx.database === "postgres"
257
+ ? "Drizzle/node-postgres"
258
+ : "Drizzle/mysql2"
259
+ }, and the starter database provider.
211
260
  - \`lib/env.ts\` validates deployment configuration at startup.
212
261
  - \`lib/auth.ts\` exposes \`requireUser(ctx)\` for protected use cases.
213
262
  - \`lib/better-auth.ts\` owns Better Auth setup and keeps provider-specific auth details outside use cases.
214
263
  ${shellMap}
215
264
  ## Before deploying
216
265
 
217
- - Keep \`SQLITE_DB_URL=file:local.db\` for local libSQL development or point it at a hosted libSQL database such as Turso.
266
+ - ${
267
+ ctx.database === "sqlite"
268
+ ? "Keep `SQLITE_DB_URL=file:local.db` for local libSQL development or point it at a hosted libSQL database such as Turso."
269
+ : ctx.database === "postgres"
270
+ ? "Point `POSTGRES_DB_URL` at your production Postgres 14+ server and run `" +
271
+ cli +
272
+ " db migrate` against it before starting the app."
273
+ : "Point `MYSQL_DB_URL` at your production MySQL 8.0+ server and run `" +
274
+ cli +
275
+ " db migrate` against it before starting the app."
276
+ }
218
277
  - Run \`${cli} db generate\` and \`${cli} db migrate\` after changing the Drizzle schema.
219
- - Run \`${cli} db reset\` to rebuild a local SQLite database from the checked-in migrations.
278
+ - Run \`${cli} db reset\` to rebuild a local ${
279
+ ctx.database === "sqlite"
280
+ ? "SQLite"
281
+ : ctx.database === "postgres"
282
+ ? "Postgres"
283
+ : "MySQL"
284
+ } database from the checked-in migrations${
285
+ ctx.database === "sqlite"
286
+ ? ""
287
+ : "; it refuses non-local hosts unless `BEIGNET_ALLOW_DATABASE_RESET=true`"
288
+ }.
220
289
  - Remove \`DEVTOOLS_ENABLED=true\` in production unless you add authentication and stricter redaction.
221
290
  - Set \`APP_URL\`, \`BETTER_AUTH_SECRET\`, \`LOG_LEVEL\`, and service-specific integration variables in your hosting environment.
222
291
  - Set \`BETTER_AUTH_TRUSTED_ORIGINS\` before serving auth across multiple origins.
@@ -276,6 +345,25 @@ export function integrationsDoc(ctx: TemplateContext): string {
276
345
  }
277
346
 
278
347
  export function envExample(ctx: TemplateContext): string {
348
+ const databaseLines =
349
+ ctx.database === "sqlite"
350
+ ? [
351
+ "# Use file:local.db for local libSQL or libsql://... for Turso cloud.",
352
+ "SQLITE_DB_URL=file:local.db",
353
+ "# SQLITE_DB_AUTH_TOKEN=",
354
+ ]
355
+ : ctx.database === "postgres"
356
+ ? [
357
+ "# Local Postgres matching the docker one-liner below; point at your own server in production.",
358
+ `# ${databaseStartCommand("postgres", ctx.name)}`,
359
+ `POSTGRES_DB_URL=${databaseLocalUrl("postgres", ctx.name)}`,
360
+ ]
361
+ : [
362
+ "# Local MySQL matching the docker one-liner below; point at your own server in production.",
363
+ `# ${databaseStartCommand("mysql", ctx.name)}`,
364
+ `MYSQL_DB_URL=${databaseLocalUrl("mysql", ctx.name)}`,
365
+ ];
366
+
279
367
  const lines = [
280
368
  "# Copy to .env.local and fill in values for your app.",
281
369
  "APP_URL=http://localhost:3000",
@@ -287,9 +375,7 @@ export function envExample(ctx: TemplateContext): string {
287
375
  "LOG_FORMAT=json",
288
376
  `LOG_SERVICE=${ctx.name}`,
289
377
  "",
290
- "# Use file:local.db for local libSQL or libsql://... for Turso cloud.",
291
- "SQLITE_DB_URL=file:local.db",
292
- "# SQLITE_DB_AUTH_TOKEN=",
378
+ ...databaseLines,
293
379
  "",
294
380
  "BETTER_AUTH_SECRET=local-dev-better-auth-secret-change-me",
295
381
  "BETTER_AUTH_URL=http://localhost:3000",
@@ -0,0 +1,34 @@
1
+ import type { TemplateContext } from "../shared.js";
2
+ import { mysqlDbFiles } from "./mysql.js";
3
+ import { postgresDbFiles } from "./postgres.js";
4
+ import { sqliteDbFiles } from "./sqlite.js";
5
+
6
+ /**
7
+ * Generated database files shared by every dialect. Each dialect module
8
+ * provides the full set so the scaffold surface stays stable across `--db`
9
+ * choices.
10
+ */
11
+ export type DbTemplateFiles = {
12
+ drizzleConfig: string;
13
+ dbSchema: string;
14
+ drizzleTodoRepository: string;
15
+ dbRepositories: string;
16
+ databaseReady: string;
17
+ dbProvider: string;
18
+ dbReset: string;
19
+ dbTestDatabase: string;
20
+ starterMigrationSql: string;
21
+ starterMigrationJournal: string;
22
+ starterMigrationSnapshot: string;
23
+ };
24
+
25
+ export function dbFiles(ctx: TemplateContext): DbTemplateFiles {
26
+ switch (ctx.database) {
27
+ case "postgres":
28
+ return postgresDbFiles(ctx);
29
+ case "mysql":
30
+ return mysqlDbFiles(ctx);
31
+ default:
32
+ return sqliteDbFiles();
33
+ }
34
+ }