@beignet/cli 0.0.9 → 0.0.10

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 (66) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +35 -19
  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 +25 -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/templates/base.d.ts.map +1 -1
  23. package/dist/templates/base.js +79 -9
  24. package/dist/templates/base.js.map +1 -1
  25. package/dist/templates/db/index.d.ts +21 -0
  26. package/dist/templates/db/index.d.ts.map +1 -0
  27. package/dist/templates/db/index.js +14 -0
  28. package/dist/templates/db/index.js.map +1 -0
  29. package/dist/templates/db/mysql.d.ts +4 -0
  30. package/dist/templates/db/mysql.d.ts.map +1 -0
  31. package/dist/templates/db/mysql.js +972 -0
  32. package/dist/templates/db/mysql.js.map +1 -0
  33. package/dist/templates/db/postgres.d.ts +4 -0
  34. package/dist/templates/db/postgres.d.ts.map +1 -0
  35. package/dist/templates/db/postgres.js +863 -0
  36. package/dist/templates/db/postgres.js.map +1 -0
  37. package/dist/templates/db/sqlite.d.ts +3 -0
  38. package/dist/templates/db/sqlite.d.ts.map +1 -0
  39. package/dist/templates/db/sqlite.js +878 -0
  40. package/dist/templates/db/sqlite.js.map +1 -0
  41. package/dist/templates/index.d.ts +5 -5
  42. package/dist/templates/index.d.ts.map +1 -1
  43. package/dist/templates/index.js +21 -20
  44. package/dist/templates/index.js.map +1 -1
  45. package/dist/templates/server.d.ts +3 -3
  46. package/dist/templates/server.d.ts.map +1 -1
  47. package/dist/templates/server.js +149 -97
  48. package/dist/templates/server.js.map +1 -1
  49. package/dist/templates/shared.d.ts +34 -1
  50. package/dist/templates/shared.d.ts.map +1 -1
  51. package/dist/templates/shared.js +45 -0
  52. package/dist/templates/shared.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/choices.ts +70 -3
  55. package/src/create-prompts.ts +25 -3
  56. package/src/create.ts +24 -2
  57. package/src/index.ts +29 -5
  58. package/src/make.ts +265 -34
  59. package/src/templates/base.ts +95 -9
  60. package/src/templates/db/index.ts +34 -0
  61. package/src/templates/db/mysql.ts +976 -0
  62. package/src/templates/db/postgres.ts +867 -0
  63. package/src/templates/{db.ts → db/sqlite.ts} +31 -152
  64. package/src/templates/index.ts +24 -19
  65. package/src/templates/server.ts +161 -97
  66. 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;
@@ -282,10 +286,11 @@ const createCommand = buildCommand<
282
286
  docs: {
283
287
  brief: "Create a new Beignet app.",
284
288
  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.
289
+ Drizzle persistence with checked-in migrations, pino logging, devtools,
290
+ and a shadcn UI shell. Pass --api for an API-only scaffold without the
291
+ UI shell, and --db to choose the database.
288
292
 
293
+ Available databases: ${databaseChoices.join(", ")}
289
294
  Available integrations: ${integrationChoices.join(", ")}
290
295
 
291
296
  Running create on an interactive terminal without selection flags opens a
@@ -305,6 +310,12 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
305
310
  withNegated: false,
306
311
  brief: "Scaffold an API-only app without the UI shell.",
307
312
  },
313
+ db: {
314
+ kind: "enum",
315
+ values: databaseChoices,
316
+ optional: true,
317
+ brief: "Database for Drizzle persistence. Default: sqlite.",
318
+ },
308
319
  packageManager: {
309
320
  kind: "enum",
310
321
  values: packageManagerChoices,
@@ -354,6 +365,7 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
354
365
  let selections: CreateSelections = {
355
366
  directory,
356
367
  api: flags.api,
368
+ db: flags.db,
357
369
  integrations: flags.integrations,
358
370
  packageManager: flags.packageManager,
359
371
  };
@@ -382,6 +394,7 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
382
394
  }
383
395
 
384
396
  const api = selections.api ?? false;
397
+ const database = selections.db ?? "sqlite";
385
398
  const integrations = uniqueValues([...(selections.integrations ?? [])]);
386
399
  const result = await createProject({
387
400
  name: selections.directory,
@@ -389,6 +402,7 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
389
402
  api,
390
403
  packageManager: selections.packageManager,
391
404
  integrations,
405
+ db: database,
392
406
  force: Boolean(flags.force),
393
407
  dryRun: Boolean(flags.dryRun),
394
408
  });
@@ -397,7 +411,7 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
397
411
  this,
398
412
  flags.json
399
413
  ? JSON.stringify(result, null, 2)
400
- : nextSteps(result, { api, integrations }),
414
+ : nextSteps(result, { api, integrations, database }),
401
415
  );
402
416
  };
403
417
  },
@@ -1238,6 +1252,7 @@ function nextSteps(
1238
1252
  options: {
1239
1253
  api?: boolean;
1240
1254
  integrations?: readonly IntegrationName[];
1255
+ database?: DatabaseName;
1241
1256
  } = {},
1242
1257
  ): string {
1243
1258
  if (result.dryRun) {
@@ -1261,6 +1276,15 @@ ${plannedFiles}`;
1261
1276
  const openStep = options.api
1262
1277
  ? " open http://localhost:3000 for the API landing page"
1263
1278
  : " open http://localhost:3000/sign-up";
1279
+ const startDatabaseCommand = options.database
1280
+ ? databaseStartCommand(options.database, result.name)
1281
+ : undefined;
1282
+ const startDatabaseStep = startDatabaseCommand
1283
+ ? `Start the database:
1284
+ ${startDatabaseCommand}
1285
+
1286
+ `
1287
+ : "";
1264
1288
 
1265
1289
  return `Created ${result.name} at ${result.targetDir}
1266
1290
 
@@ -1269,7 +1293,7 @@ Next steps:
1269
1293
  ${pm} install
1270
1294
  cp .env.example .env.local
1271
1295
  ${envStep}
1272
- Prepare the database:
1296
+ ${startDatabaseStep}Prepare the database:
1273
1297
  ${cli} db migrate
1274
1298
 
1275
1299
  Start the app: