@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/choices.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Choice lists and string-literal types shared by the CLI entrypoint and the
3
- * heavier scaffold/generator modules. Keeping them here lets the bin define
4
- * flag enums without loading templates or generators at startup.
2
+ * Choice lists, string-literal types, and small pure helpers shared by the
3
+ * CLI entrypoint and the heavier scaffold/generator modules. Keeping them
4
+ * here lets the bin define flag enums and next steps without loading
5
+ * templates or generators at startup.
5
6
  */
6
7
 
7
8
  /**
@@ -19,6 +20,10 @@ export type TemplateName = "next";
19
20
  * integrations that add an external service remain selectable.
20
21
  */
21
22
  export type IntegrationName = "inngest" | "resend" | "upstash-rate-limit";
23
+ /**
24
+ * Databases supported by the starter's Drizzle persistence layer.
25
+ */
26
+ export type DatabaseName = "sqlite" | "postgres" | "mysql";
22
27
 
23
28
  /**
24
29
  * Supported scaffold template choices.
@@ -46,6 +51,68 @@ export const integrationChoices = [
46
51
  "upstash-rate-limit",
47
52
  ] as const satisfies readonly IntegrationName[];
48
53
 
54
+ /**
55
+ * Supported scaffold database choices.
56
+ */
57
+ export const databaseChoices = [
58
+ "sqlite",
59
+ "postgres",
60
+ "mysql",
61
+ ] as const satisfies readonly DatabaseName[];
62
+
63
+ /**
64
+ * Local database name derived from the app name.
65
+ *
66
+ * Postgres and MySQL database identifiers are stricter than project names,
67
+ * so dashes and dots become underscores. Used by `.env.example`, the
68
+ * generated drizzle config, and the create command's next steps, which must
69
+ * all point at the same local database.
70
+ */
71
+ export function databaseLocalName(appName: string): string {
72
+ return appName.replaceAll(/[^a-z0-9_]/g, "_");
73
+ }
74
+
75
+ /**
76
+ * Docker one-liner that starts a local database matching the generated
77
+ * `.env.example` connection URL. Returns undefined for SQLite, which needs
78
+ * no server.
79
+ */
80
+ export function databaseStartCommand(
81
+ database: DatabaseName,
82
+ appName: string,
83
+ ): string | undefined {
84
+ const databaseName = databaseLocalName(appName);
85
+
86
+ switch (database) {
87
+ case "postgres":
88
+ return `docker run --rm -d -e POSTGRES_USER=beignet -e POSTGRES_PASSWORD=beignet -e POSTGRES_DB=${databaseName} -p 5432:5432 postgres:17-alpine`;
89
+ case "mysql":
90
+ return `docker run --rm -d -e MYSQL_ROOT_PASSWORD=beignet -e MYSQL_DATABASE=${databaseName} -p 3306:3306 mysql:8.4`;
91
+ default:
92
+ return undefined;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Local development connection URL matching `databaseStartCommand`.
98
+ * Used as the `.env.example` value and the generated env defaults.
99
+ */
100
+ export function databaseLocalUrl(
101
+ database: DatabaseName,
102
+ appName: string,
103
+ ): string {
104
+ const databaseName = databaseLocalName(appName);
105
+
106
+ switch (database) {
107
+ case "postgres":
108
+ return `postgres://beignet:beignet@127.0.0.1:5432/${databaseName}`;
109
+ case "mysql":
110
+ return `mysql://root:beignet@127.0.0.1:3306/${databaseName}`;
111
+ default:
112
+ return "file:local.db";
113
+ }
114
+ }
115
+
49
116
  /**
50
117
  * Shells supported by `beignet completion install` and `uninstall`.
51
118
  */
@@ -7,6 +7,8 @@
7
7
  */
8
8
 
9
9
  import {
10
+ type DatabaseName,
11
+ databaseChoices,
10
12
  type IntegrationName,
11
13
  integrationChoices,
12
14
  type PackageManager,
@@ -18,6 +20,7 @@ import {
18
20
  export type CreateSelections = {
19
21
  directory?: string;
20
22
  api?: boolean;
23
+ db?: DatabaseName;
21
24
  integrations?: readonly IntegrationName[];
22
25
  packageManager?: PackageManager;
23
26
  };
@@ -35,9 +38,9 @@ type CreatePromptTty = {
35
38
  * Decide whether `beignet create` should run the interactive prompt flow.
36
39
  *
37
40
  * Prompts run only on a full TTY, only when `--yes` is absent, and only when
38
- * no selection flag was passed. Any explicit api, integration, or package
39
- * manager selection means the invocation is scripted, so the non-interactive
40
- * path stays byte-identical for agents and CI.
41
+ * no selection flag was passed. Any explicit api, database, integration, or
42
+ * package manager selection means the invocation is scripted, so the
43
+ * non-interactive path stays byte-identical for agents and CI.
41
44
  */
42
45
  export function shouldPromptInteractively(
43
46
  flags: CreatePromptFlags,
@@ -48,6 +51,7 @@ export function shouldPromptInteractively(
48
51
 
49
52
  const selectionFlagPassed =
50
53
  flags.api !== undefined ||
54
+ flags.db !== undefined ||
51
55
  (flags.integrations?.length ?? 0) > 0 ||
52
56
  flags.packageManager !== undefined;
53
57
 
@@ -60,6 +64,12 @@ const integrationHints: Record<IntegrationName, string> = {
60
64
  "upstash-rate-limit": "Upstash-backed rate limiting",
61
65
  };
62
66
 
67
+ const databaseHints: Record<DatabaseName, string> = {
68
+ sqlite: "libSQL/Turso, zero setup — default",
69
+ postgres: "node-postgres, requires a Postgres 14+ server",
70
+ mysql: "mysql2, requires MySQL 8.0+",
71
+ };
72
+
63
73
  /**
64
74
  * Run the interactive `beignet create` prompt flow.
65
75
  *
@@ -92,6 +102,17 @@ export async function promptCreateSelections(
92
102
  });
93
103
  if (prompts.isCancel(api)) return undefined;
94
104
 
105
+ const db = await prompts.select<DatabaseName>({
106
+ message: "Which database?",
107
+ options: databaseChoices.map((database) => ({
108
+ value: database,
109
+ label: database,
110
+ hint: databaseHints[database],
111
+ })),
112
+ initialValue: initial.db ?? "sqlite",
113
+ });
114
+ if (prompts.isCancel(db)) return undefined;
115
+
95
116
  const integrations = await prompts.multiselect<IntegrationName>({
96
117
  message: "Add provider integrations? (press enter to skip)",
97
118
  options: integrationChoices.map((integration) => ({
@@ -108,6 +129,7 @@ export async function promptCreateSelections(
108
129
  return {
109
130
  directory,
110
131
  api,
132
+ db,
111
133
  integrations,
112
134
  packageManager: initial.packageManager,
113
135
  };
package/src/create.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { mkdir, readdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import {
4
+ type DatabaseName,
5
+ databaseChoices,
4
6
  getTemplateFiles,
5
7
  type IntegrationName,
6
8
  integrationChoices,
@@ -22,6 +24,11 @@ export type CreateOptions = {
22
24
  api?: boolean;
23
25
  packageManager?: PackageManager;
24
26
  integrations?: IntegrationName[];
27
+ /**
28
+ * Database backing the starter's Drizzle persistence layer.
29
+ * Defaults to "sqlite".
30
+ */
31
+ db?: DatabaseName;
25
32
  force?: boolean;
26
33
  dryRun?: boolean;
27
34
  beignetVersion?: string;
@@ -37,6 +44,7 @@ export type CreateResult = {
37
44
  dryRun: boolean;
38
45
  files: string[];
39
46
  packageManager: PackageManager;
47
+ database: DatabaseName;
40
48
  };
41
49
 
42
50
  /**
@@ -52,6 +60,7 @@ export async function createProject(
52
60
  const template = options.template ?? "next";
53
61
  const api = Boolean(options.api);
54
62
  const integrations = resolveIntegrations(options.integrations ?? []);
63
+ const database = options.db ?? "sqlite";
55
64
 
56
65
  if (template !== "next") {
57
66
  throw new Error(
@@ -59,6 +68,12 @@ export async function createProject(
59
68
  );
60
69
  }
61
70
 
71
+ if (!databaseChoices.includes(database)) {
72
+ throw new Error(
73
+ `Unknown database "${database}". Available databases: ${databaseChoices.join(", ")}`,
74
+ );
75
+ }
76
+
62
77
  const dryRun = Boolean(options.dryRun);
63
78
 
64
79
  await assertCanWrite(targetDir, Boolean(options.force));
@@ -69,6 +84,7 @@ export async function createProject(
69
84
  beignetVersion: options.beignetVersion ?? getDefaultBeignetVersion(),
70
85
  api,
71
86
  integrations,
87
+ database,
72
88
  });
73
89
 
74
90
  if (!dryRun) {
@@ -88,6 +104,7 @@ export async function createProject(
88
104
  dryRun,
89
105
  files: files.map((file) => file.path),
90
106
  packageManager,
107
+ database,
91
108
  };
92
109
  }
93
110
 
@@ -101,9 +118,14 @@ function resolveIntegrations(
101
118
  explicitIntegrations: IntegrationName[],
102
119
  ): IntegrationName[] {
103
120
  for (const integration of explicitIntegrations) {
104
- if (baseProviderNames.has(integration)) {
121
+ const integrationName: string = integration;
122
+ if (baseProviderNames.has(integrationName)) {
123
+ const databaseHint =
124
+ integrationName === "drizzle-sqlite"
125
+ ? " Choose the starter database with --db sqlite|postgres|mysql instead."
126
+ : "";
105
127
  throw new Error(
106
- `${integration} is part of every Beignet starter and no longer an --integrations choice.`,
128
+ `${integration} is part of every Beignet starter and no longer an --integrations choice.${databaseHint}`,
107
129
  );
108
130
  }
109
131
  }
package/src/index.ts CHANGED
@@ -16,6 +16,9 @@ import {
16
16
  import {
17
17
  type CompletionShell,
18
18
  completionShellChoices,
19
+ type DatabaseName,
20
+ databaseChoices,
21
+ databaseStartCommand,
19
22
  type IntegrationName,
20
23
  integrationChoices,
21
24
  type MakeFeatureAddon,
@@ -44,6 +47,7 @@ const outputFormatChoices = [
44
47
  type CreateFlags = {
45
48
  template: TemplateName;
46
49
  api?: boolean;
50
+ db?: DatabaseName;
47
51
  packageManager?: PackageManager;
48
52
  integrations?: readonly IntegrationName[];
49
53
  yes?: boolean;
@@ -123,6 +127,10 @@ type ScheduleRunFlags = {
123
127
  source?: string;
124
128
  };
125
129
 
130
+ type McpFlags = {
131
+ cwd?: string;
132
+ };
133
+
126
134
  type DoctorFlags = {
127
135
  json?: boolean;
128
136
  strict?: boolean;
@@ -282,10 +290,11 @@ const createCommand = buildCommand<
282
290
  docs: {
283
291
  brief: "Create a new Beignet app.",
284
292
  fullDescription: `Scaffolds a full-stack Beignet starter: typed contracts, Better Auth,
285
- Drizzle/libSQL persistence with checked-in migrations, pino logging,
286
- devtools, and a shadcn UI shell. Pass --api for an API-only scaffold
287
- without the UI shell.
293
+ Drizzle persistence with checked-in migrations, pino logging, devtools,
294
+ and a shadcn UI shell. Pass --api for an API-only scaffold without the
295
+ UI shell, and --db to choose the database.
288
296
 
297
+ Available databases: ${databaseChoices.join(", ")}
289
298
  Available integrations: ${integrationChoices.join(", ")}
290
299
 
291
300
  Running create on an interactive terminal without selection flags opens a
@@ -305,6 +314,12 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
305
314
  withNegated: false,
306
315
  brief: "Scaffold an API-only app without the UI shell.",
307
316
  },
317
+ db: {
318
+ kind: "enum",
319
+ values: databaseChoices,
320
+ optional: true,
321
+ brief: "Database for Drizzle persistence. Default: sqlite.",
322
+ },
308
323
  packageManager: {
309
324
  kind: "enum",
310
325
  values: packageManagerChoices,
@@ -354,6 +369,7 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
354
369
  let selections: CreateSelections = {
355
370
  directory,
356
371
  api: flags.api,
372
+ db: flags.db,
357
373
  integrations: flags.integrations,
358
374
  packageManager: flags.packageManager,
359
375
  };
@@ -382,6 +398,7 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
382
398
  }
383
399
 
384
400
  const api = selections.api ?? false;
401
+ const database = selections.db ?? "sqlite";
385
402
  const integrations = uniqueValues([...(selections.integrations ?? [])]);
386
403
  const result = await createProject({
387
404
  name: selections.directory,
@@ -389,6 +406,7 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
389
406
  api,
390
407
  packageManager: selections.packageManager,
391
408
  integrations,
409
+ db: database,
392
410
  force: Boolean(flags.force),
393
411
  dryRun: Boolean(flags.dryRun),
394
412
  });
@@ -397,7 +415,7 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
397
415
  this,
398
416
  flags.json
399
417
  ? JSON.stringify(result, null, 2)
400
- : nextSteps(result, { api, integrations }),
418
+ : nextSteps(result, { api, integrations, database }),
401
419
  );
402
420
  };
403
421
  },
@@ -512,6 +530,30 @@ const lintCommand = buildCommand<LintFlags, [], CliContext>({
512
530
  },
513
531
  });
514
532
 
533
+ const mcpCommand = buildCommand<McpFlags, [], CliContext>({
534
+ docs: {
535
+ brief:
536
+ "Run a Model Context Protocol server exposing this app's routes, doctor, lint, and generators over stdio.",
537
+ },
538
+ parameters: {
539
+ flags: {
540
+ cwd: cwdFlag,
541
+ } satisfies FlagParametersForType<McpFlags, CliContext>,
542
+ },
543
+ loader: async () => {
544
+ const { runMcpServer } = await import("./mcp.js");
545
+
546
+ return async function runMcp(this: CliContext, flags: McpFlags) {
547
+ // Stdout belongs to the MCP transport, so nothing routes through
548
+ // writeOutput here.
549
+ await runMcpServer({
550
+ cwd: flags.cwd ?? process.cwd(),
551
+ version: getCliVersion(),
552
+ });
553
+ };
554
+ },
555
+ });
556
+
515
557
  type DatabaseCommandName = "generate" | "migrate" | "reset" | "seed";
516
558
 
517
559
  function databaseCommand(command: DatabaseCommandName) {
@@ -1107,6 +1149,7 @@ Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
1107
1149
  doctor: doctorCommand,
1108
1150
  lint: lintCommand,
1109
1151
  make: makeRoutes,
1152
+ mcp: mcpCommand,
1110
1153
  outbox: outboxRoutes,
1111
1154
  routes: routesCommand,
1112
1155
  schedule: scheduleRoutes,
@@ -1238,6 +1281,7 @@ function nextSteps(
1238
1281
  options: {
1239
1282
  api?: boolean;
1240
1283
  integrations?: readonly IntegrationName[];
1284
+ database?: DatabaseName;
1241
1285
  } = {},
1242
1286
  ): string {
1243
1287
  if (result.dryRun) {
@@ -1261,6 +1305,15 @@ ${plannedFiles}`;
1261
1305
  const openStep = options.api
1262
1306
  ? " open http://localhost:3000 for the API landing page"
1263
1307
  : " open http://localhost:3000/sign-up";
1308
+ const startDatabaseCommand = options.database
1309
+ ? databaseStartCommand(options.database, result.name)
1310
+ : undefined;
1311
+ const startDatabaseStep = startDatabaseCommand
1312
+ ? `Start the database:
1313
+ ${startDatabaseCommand}
1314
+
1315
+ `
1316
+ : "";
1264
1317
 
1265
1318
  return `Created ${result.name} at ${result.targetDir}
1266
1319
 
@@ -1269,7 +1322,7 @@ Next steps:
1269
1322
  ${pm} install
1270
1323
  cp .env.example .env.local
1271
1324
  ${envStep}
1272
- Prepare the database:
1325
+ ${startDatabaseStep}Prepare the database:
1273
1326
  ${cli} db migrate
1274
1327
 
1275
1328
  Start the app: