@beignet/cli 0.0.8 → 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 (70) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +68 -67
  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 +14 -4
  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/inspect.js +78 -12
  19. package/dist/inspect.js.map +1 -1
  20. package/dist/make.d.ts +21 -1
  21. package/dist/make.d.ts.map +1 -1
  22. package/dist/make.js +210 -37
  23. package/dist/make.js.map +1 -1
  24. package/dist/templates/base.d.ts.map +1 -1
  25. package/dist/templates/base.js +80 -10
  26. package/dist/templates/base.js.map +1 -1
  27. package/dist/templates/db/index.d.ts +21 -0
  28. package/dist/templates/db/index.d.ts.map +1 -0
  29. package/dist/templates/db/index.js +14 -0
  30. package/dist/templates/db/index.js.map +1 -0
  31. package/dist/templates/db/mysql.d.ts +4 -0
  32. package/dist/templates/db/mysql.d.ts.map +1 -0
  33. package/dist/templates/db/mysql.js +972 -0
  34. package/dist/templates/db/mysql.js.map +1 -0
  35. package/dist/templates/db/postgres.d.ts +4 -0
  36. package/dist/templates/db/postgres.d.ts.map +1 -0
  37. package/dist/templates/db/postgres.js +863 -0
  38. package/dist/templates/db/postgres.js.map +1 -0
  39. package/dist/templates/db/sqlite.d.ts +3 -0
  40. package/dist/templates/db/sqlite.d.ts.map +1 -0
  41. package/dist/templates/db/sqlite.js +878 -0
  42. package/dist/templates/db/sqlite.js.map +1 -0
  43. package/dist/templates/db.js +16 -16
  44. package/dist/templates/index.d.ts +5 -5
  45. package/dist/templates/index.d.ts.map +1 -1
  46. package/dist/templates/index.js +21 -20
  47. package/dist/templates/index.js.map +1 -1
  48. package/dist/templates/server.d.ts +3 -3
  49. package/dist/templates/server.d.ts.map +1 -1
  50. package/dist/templates/server.js +149 -97
  51. package/dist/templates/server.js.map +1 -1
  52. package/dist/templates/shared.d.ts +34 -1
  53. package/dist/templates/shared.d.ts.map +1 -1
  54. package/dist/templates/shared.js +45 -0
  55. package/dist/templates/shared.js.map +1 -1
  56. package/package.json +2 -2
  57. package/src/choices.ts +70 -3
  58. package/src/create-prompts.ts +25 -3
  59. package/src/create.ts +25 -3
  60. package/src/index.ts +29 -5
  61. package/src/inspect.ts +137 -19
  62. package/src/make.ts +265 -34
  63. package/src/templates/base.ts +96 -10
  64. package/src/templates/db/index.ts +34 -0
  65. package/src/templates/db/mysql.ts +976 -0
  66. package/src/templates/db/postgres.ts +867 -0
  67. package/src/templates/{db.ts → db/sqlite.ts} +47 -168
  68. package/src/templates/index.ts +24 -19
  69. package/src/templates/server.ts +161 -97
  70. package/src/templates/shared.ts +90 -1
@@ -16,11 +16,56 @@ export const externalVersions = {
16
16
  drizzleOrm: "^0.45.2",
17
17
  inngest: "^3.0.0",
18
18
  libsqlClient: "^0.14.0",
19
+ mysql2: "^3.12.0",
20
+ pg: "^8.13.0",
21
+ pglite: "^0.5.2",
19
22
  pino: "^9.7.0",
20
23
  resend: "^4.0.1",
24
+ typesPg: "^8.11.0",
21
25
  upstashRateLimit: "^2.0.0",
22
26
  upstashRedis: "^1.0.0",
23
27
  };
28
+ const databaseDescriptors = {
29
+ sqlite: {
30
+ name: "sqlite",
31
+ subpath: "sqlite",
32
+ apiToken: "Sqlite",
33
+ databaseType: "DrizzleSqliteDatabase",
34
+ providerFactory: "createDrizzleSqliteProvider",
35
+ providerInstance: "drizzleSqliteProvider",
36
+ providerName: "drizzle-sqlite",
37
+ urlEnvVar: "SQLITE_DB_URL",
38
+ drizzleKitDialect: "sqlite",
39
+ betterAuthProvider: "sqlite",
40
+ },
41
+ postgres: {
42
+ name: "postgres",
43
+ subpath: "postgres",
44
+ apiToken: "Postgres",
45
+ databaseType: "DrizzlePostgresDatabase",
46
+ providerFactory: "createDrizzlePostgresProvider",
47
+ providerInstance: "drizzlePostgresProvider",
48
+ providerName: "drizzle-postgres",
49
+ urlEnvVar: "POSTGRES_DB_URL",
50
+ drizzleKitDialect: "postgresql",
51
+ betterAuthProvider: "pg",
52
+ },
53
+ mysql: {
54
+ name: "mysql",
55
+ subpath: "mysql",
56
+ apiToken: "Mysql",
57
+ databaseType: "DrizzleMysqlDatabase",
58
+ providerFactory: "createDrizzleMysqlProvider",
59
+ providerInstance: "drizzleMysqlProvider",
60
+ providerName: "drizzle-mysql",
61
+ urlEnvVar: "MYSQL_DB_URL",
62
+ drizzleKitDialect: "mysql",
63
+ betterAuthProvider: "mysql",
64
+ },
65
+ };
66
+ export function databaseDescriptor(ctx) {
67
+ return databaseDescriptors[ctx.database];
68
+ }
24
69
  export function packageRunner(ctx) {
25
70
  switch (ctx.packageManager) {
26
71
  case "npm":
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/templates/shared.ts"],"names":[],"mappings":"AAwBA,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,QAAQ,EAAE,SAAS;IACnB,UAAU,EAAE,QAAQ;IACpB,QAAQ,EAAE,SAAS;IACnB,SAAS,EAAE,UAAU;IACrB,UAAU,EAAE,SAAS;IACrB,aAAa,EAAE,SAAS;IACxB,GAAG,EAAE,QAAQ;IACb,kBAAkB,EAAE,UAAU;IAC9B,iBAAiB,EAAE,QAAQ;IAC3B,aAAa,EAAE,SAAS;IACxB,UAAU,EAAE,QAAQ;IACpB,UAAU,EAAE,UAAU;IACtB,UAAU,EAAE,SAAS;IACrB,OAAO,EAAE,QAAQ;IACjB,YAAY,EAAE,SAAS;IACvB,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,QAAQ;IAChB,gBAAgB,EAAE,QAAQ;IAC1B,YAAY,EAAE,QAAQ;CACvB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,GAAoB;IAChD,QAAQ,GAAG,CAAC,cAAc,EAAE,CAAC;QAC3B,KAAK,KAAK;YACR,OAAO,oBAAoB,CAAC;QAC9B,KAAK,MAAM;YACT,OAAO,cAAc,CAAC;QACxB,KAAK,MAAM;YACT,OAAO,cAAc,CAAC;QACxB;YACE,OAAO,aAAa,CAAC;IACzB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAGxB;IACF,OAAO,EAAE;QACP,2BAA2B,EAAE,EAAE;QAC/B,OAAO,EAAE,gBAAgB,CAAC,OAAO;KAClC;IACD,MAAM,EAAE;QACN,+BAA+B,EAAE,EAAE;QACnC,MAAM,EAAE,gBAAgB,CAAC,MAAM;KAChC;IACD,oBAAoB,EAAE;QACpB,sCAAsC,EAAE,EAAE;QAC1C,oBAAoB,EAAE,gBAAgB,CAAC,gBAAgB;QACvD,gBAAgB,EAAE,gBAAgB,CAAC,YAAY;KAChD;CACF,CAAC;AAEF,MAAM,UAAU,cAAc,CAC5B,GAAoB,EACpB,WAA4B;IAE5B,OAAO,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,KAAc;IACjC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;AAClD,CAAC"}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/templates/shared.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beignet/cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "description": "CLI for creating and maintaining Beignet apps.",
6
6
  "main": "./dist/lib.js",
@@ -66,7 +66,7 @@
66
66
  "typescript": "^5.3.0"
67
67
  },
68
68
  "dependencies": {
69
- "@beignet/core": "^0.0.8",
69
+ "@beignet/core": "^0.0.10",
70
70
  "@clack/prompts": "^1.5.1",
71
71
  "@stricli/core": "^1.2.7",
72
72
  "jiti": "^2.7.0"
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
 
@@ -95,15 +112,20 @@ export async function createProject(
95
112
  * Providers that ship in every starter. They used to be `--integrations`
96
113
  * choices, so passing them gets a pointed error instead of a generic one.
97
114
  */
98
- const baseProviderNames = new Set(["better-auth", "drizzle-turso", "pino"]);
115
+ const baseProviderNames = new Set(["better-auth", "drizzle-sqlite", "pino"]);
99
116
 
100
117
  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:
package/src/inspect.ts CHANGED
@@ -1865,6 +1865,14 @@ async function inspectRouteErrorCatalogDrift(
1865
1865
  return diagnostics;
1866
1866
  }
1867
1867
 
1868
+ type ProviderDoctorVariantRule = {
1869
+ name: string;
1870
+ displayName: string;
1871
+ tokens: readonly string[];
1872
+ requiredEnv: readonly string[];
1873
+ registrationSeverity?: "warning" | "hint";
1874
+ };
1875
+
1868
1876
  type ProviderDoctorRule = {
1869
1877
  packageName: string;
1870
1878
  displayName: string;
@@ -1872,6 +1880,7 @@ type ProviderDoctorRule = {
1872
1880
  appPorts?: readonly { name: string; type: string }[];
1873
1881
  requiredEnv?: readonly string[];
1874
1882
  registrationSeverity?: "warning" | "hint";
1883
+ variants?: readonly ProviderDoctorVariantRule[];
1875
1884
  };
1876
1885
 
1877
1886
  type ProviderPackageMetadataSource = {
@@ -1960,8 +1969,40 @@ async function inspectProductionReadiness(
1960
1969
  }
1961
1970
  }
1962
1971
 
1972
+ const providerEntries = await readProviderListEntries(
1973
+ targetDir,
1974
+ files,
1975
+ config,
1976
+ sourceCache,
1977
+ );
1978
+
1963
1979
  for (const provider of providerDoctorRules) {
1964
1980
  if (!installedPackages.has(provider.packageName)) continue;
1981
+
1982
+ if (provider.variants !== undefined) {
1983
+ // Multi-variant packages only require the env vars of the variants the
1984
+ // app actually registers; an unused variant must not warn.
1985
+ for (const variant of detectedProviderVariants(
1986
+ provider.variants,
1987
+ providerEntries,
1988
+ )) {
1989
+ for (const envVar of variant.requiredEnv) {
1990
+ if (
1991
+ await anyFileContains(targetDir, configFiles, envVar, sourceCache)
1992
+ ) {
1993
+ continue;
1994
+ }
1995
+ diagnostics.push({
1996
+ severity: "warning",
1997
+ code: "BEIGNET_PROVIDER_ENV_MISSING",
1998
+ file: "package.json",
1999
+ message: `${provider.packageName} is installed and ${variant.displayName} is registered, but ${envVar} is not mentioned in app env/config files. ${variant.displayName} requires ${envVar}; add the environment variable or remove the provider registration.`,
2000
+ });
2001
+ }
2002
+ }
2003
+ continue;
2004
+ }
2005
+
1965
2006
  for (const envVar of provider.requiredEnv ?? []) {
1966
2007
  if (await anyFileContains(targetDir, configFiles, envVar, sourceCache)) {
1967
2008
  continue;
@@ -2153,23 +2194,38 @@ async function inspectProviderRegistrationDrift(
2153
2194
  );
2154
2195
  const providerFiles = serverProviderFiles(files, config);
2155
2196
  const sourceCache = new Map<string, string>();
2156
- const providerSource = (
2157
- await Promise.all(
2158
- providerFiles.map((file) =>
2159
- readCachedSource(targetDir, file, sourceCache),
2160
- ),
2161
- )
2162
- ).join("\n");
2163
- const providerListSource = extractProviderListSource(providerSource);
2164
- const providerEntries =
2165
- providerListSource === undefined
2166
- ? []
2167
- : providerListEntries(providerListSource);
2197
+ const providerEntries = await readProviderListEntries(
2198
+ targetDir,
2199
+ files,
2200
+ config,
2201
+ sourceCache,
2202
+ );
2168
2203
 
2169
2204
  for (const provider of providerDoctorRules) {
2205
+ if (!installedPackages.has(provider.packageName)) continue;
2206
+
2207
+ if (provider.variants !== undefined) {
2208
+ if (
2209
+ detectedProviderVariants(provider.variants, providerEntries).length > 0
2210
+ ) {
2211
+ continue;
2212
+ }
2213
+ const severity = providerVariantRegistrationSeverity(provider.variants);
2214
+ if (severity === undefined) continue;
2215
+ const hints = provider.variants
2216
+ .filter((variant) => variant.tokens.length > 0)
2217
+ .map((variant) => registrationTokenHint(variant.tokens));
2218
+ diagnostics.push({
2219
+ severity,
2220
+ code: "BEIGNET_PROVIDER_REGISTRATION_MISSING",
2221
+ file: providerFiles[0] ?? config.paths.server,
2222
+ message: `${provider.packageName} is installed, but no ${provider.displayName} variant is registered in server/providers.ts. Register one of the available providers (${hints.join(", ")}) in the exported providers array or remove the unused package.`,
2223
+ });
2224
+ continue;
2225
+ }
2226
+
2170
2227
  const severity = provider.registrationSeverity;
2171
2228
  if (severity === undefined) continue;
2172
- if (!installedPackages.has(provider.packageName)) continue;
2173
2229
  if (
2174
2230
  provider.tokens.some((token) =>
2175
2231
  providerEntries.some((entry) => entry.includes(token)),
@@ -2190,8 +2246,12 @@ async function inspectProviderRegistrationDrift(
2190
2246
  }
2191
2247
 
2192
2248
  const drizzleIndex = findProviderEntryIndex(providerEntries, [
2193
- "drizzleTursoProvider",
2194
- "createDrizzleTursoProvider",
2249
+ "drizzleSqliteProvider",
2250
+ "createDrizzleSqliteProvider",
2251
+ "drizzlePostgresProvider",
2252
+ "createDrizzlePostgresProvider",
2253
+ "drizzleMysqlProvider",
2254
+ "createDrizzleMysqlProvider",
2195
2255
  ]);
2196
2256
  const appDatabaseProviderIndex = findProviderEntryIndex(providerEntries, [
2197
2257
  "starterDatabaseProvider",
@@ -2209,7 +2269,7 @@ async function inspectProviderRegistrationDrift(
2209
2269
  code: "BEIGNET_PROVIDER_ORDER",
2210
2270
  file: providerFiles[0] ?? config.paths.server,
2211
2271
  message:
2212
- "An app database provider is registered before createDrizzleTursoProvider(...). Register the Drizzle/Turso provider first so app-owned database wiring can read ctx.ports.db during setup.",
2272
+ "An app database provider is registered before the Drizzle database provider. Register the Drizzle database provider first so app-owned database wiring can read ctx.ports.db during setup.",
2213
2273
  });
2214
2274
  }
2215
2275
 
@@ -2361,12 +2421,70 @@ function providerDoctorRuleFromMetadata(
2361
2421
  tokens: registration?.tokens ?? [],
2362
2422
  appPorts: metadata.appPorts ?? [],
2363
2423
  requiredEnv: metadata.requiredEnv ?? [],
2364
- registrationSeverity:
2365
- registration?.severity ??
2366
- (registration?.required === true ? "warning" : undefined),
2424
+ registrationSeverity: registrationSeverityFromMetadata(registration),
2425
+ variants: metadata.variants?.map((variant) => ({
2426
+ name: variant.name,
2427
+ displayName: variant.displayName ?? variant.name,
2428
+ tokens: variant.registration?.tokens ?? [],
2429
+ requiredEnv: variant.requiredEnv ?? [],
2430
+ registrationSeverity: registrationSeverityFromMetadata(
2431
+ variant.registration,
2432
+ ),
2433
+ })),
2367
2434
  };
2368
2435
  }
2369
2436
 
2437
+ function registrationSeverityFromMetadata(
2438
+ registration:
2439
+ | { required?: boolean; severity?: "warning" | "hint" }
2440
+ | undefined,
2441
+ ): "warning" | "hint" | undefined {
2442
+ return (
2443
+ registration?.severity ??
2444
+ (registration?.required === true ? "warning" : undefined)
2445
+ );
2446
+ }
2447
+
2448
+ function detectedProviderVariants(
2449
+ variants: readonly ProviderDoctorVariantRule[],
2450
+ providerEntries: readonly string[],
2451
+ ): ProviderDoctorVariantRule[] {
2452
+ return variants.filter((variant) =>
2453
+ variant.tokens.some((token) =>
2454
+ providerEntries.some((entry) => entry.includes(token)),
2455
+ ),
2456
+ );
2457
+ }
2458
+
2459
+ function providerVariantRegistrationSeverity(
2460
+ variants: readonly ProviderDoctorVariantRule[],
2461
+ ): "warning" | "hint" | undefined {
2462
+ const severities = variants.map((variant) => variant.registrationSeverity);
2463
+ if (severities.includes("warning")) return "warning";
2464
+ if (severities.includes("hint")) return "hint";
2465
+ return undefined;
2466
+ }
2467
+
2468
+ async function readProviderListEntries(
2469
+ targetDir: string,
2470
+ files: string[],
2471
+ config: ResolvedBeignetConfig,
2472
+ sourceCache: Map<string, string>,
2473
+ ): Promise<string[]> {
2474
+ const providerFiles = serverProviderFiles(files, config);
2475
+ const providerSource = (
2476
+ await Promise.all(
2477
+ providerFiles.map((file) =>
2478
+ readCachedSource(targetDir, file, sourceCache),
2479
+ ),
2480
+ )
2481
+ ).join("\n");
2482
+ const providerListSource = extractProviderListSource(providerSource);
2483
+ return providerListSource === undefined
2484
+ ? []
2485
+ : providerListEntries(providerListSource);
2486
+ }
2487
+
2370
2488
  function registrationTokenHint(tokens: readonly string[]): string {
2371
2489
  const token = tokens[0];
2372
2490
  if (!token) return "the provider";