@beignet/cli 0.0.10 → 0.0.12
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/LICENSE +21 -0
- package/README.md +37 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -2
- package/dist/index.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 +108 -0
- package/dist/templates/agents.js.map +1 -0
- package/dist/templates/base.d.ts +1 -0
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +49 -1
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/mysql.js +1 -1
- package/dist/templates/db/postgres.js +1 -1
- package/dist/templates/db/sqlite.js +3 -3
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +4 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/shared.d.ts +1 -0
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +1 -0
- package/dist/templates/shared.js.map +1 -1
- package/package.json +5 -3
- package/src/index.ts +33 -2
- package/src/mcp.ts +367 -0
- package/src/templates/agents.ts +115 -0
- package/src/templates/base.ts +51 -1
- package/src/templates/db/mysql.ts +1 -1
- package/src/templates/db/postgres.ts +1 -1
- package/src/templates/db/sqlite.ts +3 -3
- package/src/templates/index.ts +4 -0
- package/src/templates/shared.ts +1 -0
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,115 @@
|
|
|
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 lint =
|
|
15
|
+
ctx.packageManager === "npm"
|
|
16
|
+
? "npm run lint"
|
|
17
|
+
: `${ctx.packageManager} run lint`;
|
|
18
|
+
const format =
|
|
19
|
+
ctx.packageManager === "npm"
|
|
20
|
+
? "npm run format"
|
|
21
|
+
: `${ctx.packageManager} run format`;
|
|
22
|
+
const typecheck =
|
|
23
|
+
ctx.packageManager === "npm"
|
|
24
|
+
? "npm run typecheck"
|
|
25
|
+
: `${ctx.packageManager} run typecheck`;
|
|
26
|
+
|
|
27
|
+
return `# ${ctx.name} — agent guide
|
|
28
|
+
|
|
29
|
+
This is a Beignet contract-first app. Full framework docs live at
|
|
30
|
+
https://beignetjs.com (agent-friendly index at https://beignetjs.com/llms.txt).
|
|
31
|
+
README.md covers setup and the app map; this file covers what is not
|
|
32
|
+
discoverable from the code.
|
|
33
|
+
|
|
34
|
+
## Registration is not automatic
|
|
35
|
+
|
|
36
|
+
Creating an artifact file does not wire it into the app. Non-registration is
|
|
37
|
+
a silent failure — the file exists but never runs:
|
|
38
|
+
|
|
39
|
+
- Feature route groups in \`features/<feature>/routes.ts\` must be composed in
|
|
40
|
+
\`server/routes.ts\` via \`defineRoutes([...])\`.
|
|
41
|
+
- Schedules must be added to the \`schedules\` array in \`server/schedules.ts\`.
|
|
42
|
+
- Tasks must be added to \`defineTasks([...])\` in \`server/tasks.ts\`.
|
|
43
|
+
- Outbox events and jobs must be registered in \`defineOutboxRegistry({...})\`
|
|
44
|
+
in \`server/outbox.ts\`.
|
|
45
|
+
- Listeners must be wired through a \`registerListeners(...)\` call in infra
|
|
46
|
+
wiring.
|
|
47
|
+
|
|
48
|
+
The starter ships no workflow registries — generators create them on first
|
|
49
|
+
use, so their absence is fine. \`${cli} doctor\` detects registration drift.
|
|
50
|
+
\`${cli} doctor --fix\` repairs route-group, schedule, task, and outbox
|
|
51
|
+
registration; listener drift is report-only and must be fixed by hand.
|
|
52
|
+
|
|
53
|
+
## Prefer generators
|
|
54
|
+
|
|
55
|
+
\`${cli} make <artifact> <name>\` creates correctly placed, pre-registered
|
|
56
|
+
files — prefer it over hand-writing them. See \`${cli} make --help\` for the
|
|
57
|
+
artifact list. After changing the Drizzle schema in \`infra/db/schema/\`, run
|
|
58
|
+
\`${cli} db generate\` then \`${cli} db migrate\`.
|
|
59
|
+
|
|
60
|
+
## Validation loop
|
|
61
|
+
|
|
62
|
+
Run after every change:
|
|
63
|
+
|
|
64
|
+
\`\`\`bash
|
|
65
|
+
${lint}
|
|
66
|
+
${cli} lint
|
|
67
|
+
${cli} doctor --strict
|
|
68
|
+
${test}
|
|
69
|
+
${typecheck}
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
\`${lint}\` runs Biome's code lint. Use \`${format}\` to apply formatting.
|
|
73
|
+
\`${cli} lint\` is Beignet's dependency-direction lint.
|
|
74
|
+
|
|
75
|
+
## Placement rules
|
|
76
|
+
|
|
77
|
+
- Feature artifacts live under \`features/<feature>/\`, with tests in
|
|
78
|
+
\`features/<feature>/tests/\` (not \`__tests__/\`).
|
|
79
|
+
- Domain and use-case code must not import infra, providers, or React.
|
|
80
|
+
- Routes must not import concrete infra.
|
|
81
|
+
|
|
82
|
+
## Naming grammar
|
|
83
|
+
|
|
84
|
+
- \`defineX\` declares things you register: contracts, routes, jobs,
|
|
85
|
+
schedules, and the rest.
|
|
86
|
+
- \`createX\` builds runtime objects you call: servers, clients, providers,
|
|
87
|
+
and the \`createX<AppContext>()\` factories that return app-bound
|
|
88
|
+
\`defineX\` builders.
|
|
89
|
+
|
|
90
|
+
## MCP server
|
|
91
|
+
|
|
92
|
+
\`.mcp.json\` registers the \`beignet mcp\` stdio server, which exposes
|
|
93
|
+
routes/doctor/lint/make as structured tools named exactly: \`routes\`,
|
|
94
|
+
\`doctor\`, \`doctor_fix\`, \`lint\`, \`make\`. Clients that do not read
|
|
95
|
+
\`.mcp.json\` can launch \`${cli} mcp\` directly.
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* .mcp.json registers the `beignet mcp` stdio server for MCP-aware clients.
|
|
101
|
+
* The runner must not print banners on stdout, which corrupts JSON-RPC, so
|
|
102
|
+
* the config launches the locally installed CLI bin via bunx/npx.
|
|
103
|
+
*/
|
|
104
|
+
export function mcpJson(ctx: TemplateContext): string {
|
|
105
|
+
const command = ctx.packageManager === "bun" ? "bunx" : "npx";
|
|
106
|
+
|
|
107
|
+
return json({
|
|
108
|
+
mcpServers: {
|
|
109
|
+
beignet: {
|
|
110
|
+
command,
|
|
111
|
+
args: ["beignet", "mcp"],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
package/src/templates/base.ts
CHANGED
|
@@ -66,6 +66,7 @@ export function packageJson(ctx: TemplateContext): string {
|
|
|
66
66
|
const devDependencies: Record<string, string> = {
|
|
67
67
|
"@beignet/cli": ctx.beignetVersion,
|
|
68
68
|
"@beignet/web": ctx.beignetVersion,
|
|
69
|
+
"@biomejs/biome": externalVersions.biome,
|
|
69
70
|
"@types/bun": externalVersions.typesBun,
|
|
70
71
|
"@types/node": externalVersions.typesNode,
|
|
71
72
|
"@types/react": externalVersions.typesReact,
|
|
@@ -94,6 +95,8 @@ export function packageJson(ctx: TemplateContext): string {
|
|
|
94
95
|
dev: "next dev",
|
|
95
96
|
build: "next build",
|
|
96
97
|
start: "next start",
|
|
98
|
+
lint: "biome lint .",
|
|
99
|
+
format: "biome format --write .",
|
|
97
100
|
test: "bun test",
|
|
98
101
|
typecheck: "tsc --noEmit",
|
|
99
102
|
"db:generate": "drizzle-kit generate",
|
|
@@ -136,6 +139,14 @@ export function readme(ctx: TemplateContext): string {
|
|
|
136
139
|
ctx.packageManager === "npm"
|
|
137
140
|
? "npm run test"
|
|
138
141
|
: `${ctx.packageManager} run test`;
|
|
142
|
+
const lint =
|
|
143
|
+
ctx.packageManager === "npm"
|
|
144
|
+
? "npm run lint"
|
|
145
|
+
: `${ctx.packageManager} run lint`;
|
|
146
|
+
const format =
|
|
147
|
+
ctx.packageManager === "npm"
|
|
148
|
+
? "npm run format"
|
|
149
|
+
: `${ctx.packageManager} run format`;
|
|
139
150
|
const typecheck =
|
|
140
151
|
ctx.packageManager === "npm"
|
|
141
152
|
? "npm run typecheck"
|
|
@@ -209,13 +220,15 @@ ${firstOpen}
|
|
|
209
220
|
\`\`\`bash
|
|
210
221
|
# in another terminal
|
|
211
222
|
${cli} routes
|
|
223
|
+
${lint}
|
|
212
224
|
${cli} lint
|
|
213
225
|
${cli} doctor
|
|
214
226
|
${test}
|
|
215
227
|
${typecheck}
|
|
216
228
|
\`\`\`
|
|
217
229
|
|
|
218
|
-
\`routes\` shows the contracts Beignet can inspect.
|
|
230
|
+
\`routes\` shows the contracts Beignet can inspect. \`${cli} lint\` checks dependency direction. \`doctor\` catches route, OpenAPI, and resource drift.
|
|
231
|
+
\`${lint}\` runs Biome over the starter; use \`${format}\` to apply formatting.
|
|
219
232
|
${testingNotes}
|
|
220
233
|
## Build for production
|
|
221
234
|
|
|
@@ -231,6 +244,7 @@ ${cli} make feature projects
|
|
|
231
244
|
${cli} db generate
|
|
232
245
|
${cli} db migrate
|
|
233
246
|
${test}
|
|
247
|
+
${lint}
|
|
234
248
|
${typecheck}
|
|
235
249
|
${cli} lint
|
|
236
250
|
${cli} doctor
|
|
@@ -429,6 +443,42 @@ const nextConfig = {};
|
|
|
429
443
|
|
|
430
444
|
export default nextConfig;
|
|
431
445
|
`,
|
|
446
|
+
biomeConfig: json({
|
|
447
|
+
$schema: "https://biomejs.dev/schemas/2.4.14/schema.json",
|
|
448
|
+
vcs: {
|
|
449
|
+
enabled: true,
|
|
450
|
+
clientKind: "git",
|
|
451
|
+
useIgnoreFile: true,
|
|
452
|
+
},
|
|
453
|
+
formatter: {
|
|
454
|
+
enabled: true,
|
|
455
|
+
indentStyle: "tab",
|
|
456
|
+
},
|
|
457
|
+
linter: {
|
|
458
|
+
enabled: true,
|
|
459
|
+
rules: {
|
|
460
|
+
recommended: true,
|
|
461
|
+
suspicious: {
|
|
462
|
+
noExplicitAny: "warn",
|
|
463
|
+
useIterableCallbackReturn: "warn",
|
|
464
|
+
},
|
|
465
|
+
correctness: {
|
|
466
|
+
noUnusedImports: "off",
|
|
467
|
+
useHookAtTopLevel: "off",
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
javascript: {
|
|
472
|
+
formatter: {
|
|
473
|
+
quoteStyle: "double",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
css: {
|
|
477
|
+
parser: {
|
|
478
|
+
tailwindDirectives: true,
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
}),
|
|
432
482
|
tsconfig: json({
|
|
433
483
|
compilerOptions: {
|
|
434
484
|
target: "ES2017",
|
|
@@ -207,7 +207,7 @@ export function createDrizzleTodoRepository(
|
|
|
207
207
|
dbRepositories: `import type { DrizzleMysqlDatabase } from "@beignet/provider-db-drizzle/mysql";
|
|
208
208
|
import { createDrizzleTodoRepository } from "@/infra/todos/drizzle-todo-repository";
|
|
209
209
|
import type { AppTransactionPorts } from "@/ports";
|
|
210
|
-
import * as schema from "./schema";
|
|
210
|
+
import type * as schema from "./schema";
|
|
211
211
|
|
|
212
212
|
export function createRepositories(
|
|
213
213
|
db: DrizzleMysqlDatabase<typeof schema>,
|
|
@@ -195,7 +195,7 @@ export function createDrizzleTodoRepository(
|
|
|
195
195
|
dbRepositories: `import type { DrizzlePostgresDatabase } from "@beignet/provider-db-drizzle/postgres";
|
|
196
196
|
import { createDrizzleTodoRepository } from "@/infra/todos/drizzle-todo-repository";
|
|
197
197
|
import type { AppTransactionPorts } from "@/ports";
|
|
198
|
-
import * as schema from "./schema";
|
|
198
|
+
import type * as schema from "./schema";
|
|
199
199
|
|
|
200
200
|
export function createRepositories(
|
|
201
201
|
db: DrizzlePostgresDatabase<typeof schema>,
|
|
@@ -191,7 +191,7 @@ export function createDrizzleTodoRepository(
|
|
|
191
191
|
dbRepositories: `import type { DrizzleSqliteDatabase } from "@beignet/provider-db-drizzle/sqlite";
|
|
192
192
|
import { createDrizzleTodoRepository } from "@/infra/todos/drizzle-todo-repository";
|
|
193
193
|
import type { AppTransactionPorts } from "@/ports";
|
|
194
|
-
import * as schema from "./schema";
|
|
194
|
+
import type * as schema from "./schema";
|
|
195
195
|
|
|
196
196
|
export function createRepositories(
|
|
197
197
|
db: DrizzleSqliteDatabase<typeof schema>,
|
|
@@ -354,8 +354,8 @@ export type TestDatabase = {
|
|
|
354
354
|
};
|
|
355
355
|
|
|
356
356
|
export async function createTestDatabase(): Promise<TestDatabase> {
|
|
357
|
-
const path = join(tmpdir(),
|
|
358
|
-
const client = createClient({ url:
|
|
357
|
+
const path = join(tmpdir(), \`beignet-test-\${crypto.randomUUID()}.db\`);
|
|
358
|
+
const client = createClient({ url: \`file:\${path}\` });
|
|
359
359
|
const db = drizzle(client, { schema });
|
|
360
360
|
await migrate(db, { migrationsFolder: "drizzle" });
|
|
361
361
|
|
package/src/templates/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type {
|
|
|
7
7
|
export { databaseChoices, integrationChoices } from "../choices.js";
|
|
8
8
|
export type { TemplateContext, TemplateFile } from "./shared.js";
|
|
9
9
|
|
|
10
|
+
import { agentsMd, mcpJson } from "./agents.js";
|
|
10
11
|
import {
|
|
11
12
|
baseFiles,
|
|
12
13
|
envExample,
|
|
@@ -42,10 +43,13 @@ export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
|
|
|
42
43
|
const templateFiles: TemplateFile[] = [
|
|
43
44
|
{ path: "package.json", content: packageJson(ctx) },
|
|
44
45
|
{ path: "README.md", content: readme(ctx) },
|
|
46
|
+
{ path: "AGENTS.md", content: agentsMd(ctx) },
|
|
47
|
+
{ path: ".mcp.json", content: mcpJson(ctx) },
|
|
45
48
|
{ path: ".gitignore", content: baseFiles.gitignore },
|
|
46
49
|
{ path: ".env.example", content: envExample(ctx) },
|
|
47
50
|
{ path: "next-env.d.ts", content: baseFiles.nextEnv },
|
|
48
51
|
{ path: "next.config.js", content: baseFiles.nextConfig },
|
|
52
|
+
{ path: "biome.json", content: baseFiles.biomeConfig },
|
|
49
53
|
{ path: "tsconfig.json", content: baseFiles.tsconfig },
|
|
50
54
|
{ path: "app-context.ts", content: serverFiles.appContext },
|
|
51
55
|
{
|