@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.
- package/CHANGELOG.md +15 -0
- package/README.md +70 -21
- package/dist/choices.d.ts +32 -3
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +54 -3
- package/dist/choices.js.map +1 -1
- package/dist/create-prompts.d.ts +5 -4
- package/dist/create-prompts.d.ts.map +1 -1
- package/dist/create-prompts.js +22 -4
- package/dist/create-prompts.js.map +1 -1
- package/dist/create.d.ts +7 -1
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +13 -3
- package/dist/create.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -6
- package/dist/index.js.map +1 -1
- package/dist/make.d.ts +21 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +210 -37
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +250 -0
- package/dist/mcp.js.map +1 -0
- package/dist/templates/agents.d.ts +14 -0
- package/dist/templates/agents.d.ts.map +1 -0
- package/dist/templates/agents.js +98 -0
- package/dist/templates/agents.js.map +1 -0
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +79 -9
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/index.d.ts +21 -0
- package/dist/templates/db/index.d.ts.map +1 -0
- package/dist/templates/db/index.js +14 -0
- package/dist/templates/db/index.js.map +1 -0
- package/dist/templates/db/mysql.d.ts +4 -0
- package/dist/templates/db/mysql.d.ts.map +1 -0
- package/dist/templates/db/mysql.js +972 -0
- package/dist/templates/db/mysql.js.map +1 -0
- package/dist/templates/db/postgres.d.ts +4 -0
- package/dist/templates/db/postgres.d.ts.map +1 -0
- package/dist/templates/db/postgres.js +863 -0
- package/dist/templates/db/postgres.js.map +1 -0
- package/dist/templates/db/sqlite.d.ts +3 -0
- package/dist/templates/db/sqlite.d.ts.map +1 -0
- package/dist/templates/db/sqlite.js +878 -0
- package/dist/templates/db/sqlite.js.map +1 -0
- package/dist/templates/index.d.ts +5 -5
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +24 -20
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +3 -3
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +149 -97
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +34 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +45 -0
- package/dist/templates/shared.js.map +1 -1
- package/package.json +5 -3
- package/src/choices.ts +70 -3
- package/src/create-prompts.ts +25 -3
- package/src/create.ts +24 -2
- package/src/index.ts +58 -5
- package/src/make.ts +265 -34
- package/src/mcp.ts +367 -0
- package/src/templates/agents.ts +103 -0
- package/src/templates/base.ts +95 -9
- package/src/templates/db/index.ts +34 -0
- package/src/templates/db/mysql.ts +976 -0
- package/src/templates/db/postgres.ts +867 -0
- package/src/templates/{db.ts → db/sqlite.ts} +31 -152
- package/src/templates/index.ts +27 -19
- package/src/templates/server.ts +161 -97
- 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
|
+
}
|
package/src/templates/base.ts
CHANGED
|
@@ -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,
|
|
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
|
-
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|