@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.
- package/CHANGELOG.md +18 -0
- package/README.md +68 -67
- 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 +14 -4
- package/dist/create.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -6
- package/dist/index.js.map +1 -1
- package/dist/inspect.js +78 -12
- package/dist/inspect.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/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +80 -10
- 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/db.js +16 -16
- package/dist/templates/index.d.ts +5 -5
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +21 -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 +2 -2
- package/src/choices.ts +70 -3
- package/src/create-prompts.ts +25 -3
- package/src/create.ts +25 -3
- package/src/index.ts +29 -5
- package/src/inspect.ts +137 -19
- package/src/make.ts +265 -34
- package/src/templates/base.ts +96 -10
- 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} +47 -168
- package/src/templates/index.ts +24 -19
- package/src/templates/server.ts +161 -97
- package/src/templates/shared.ts +90 -1
package/dist/templates/shared.js
CHANGED
|
@@ -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":"
|
|
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.
|
|
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.
|
|
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
|
|
3
|
-
* heavier scaffold/generator modules. Keeping them
|
|
4
|
-
* flag enums
|
|
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
|
*/
|
package/src/create-prompts.ts
CHANGED
|
@@ -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
|
|
39
|
-
* manager selection means the invocation is scripted, so the
|
|
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-
|
|
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
|
-
|
|
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
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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
|
-
"
|
|
2194
|
-
"
|
|
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
|
|
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
|
-
|
|
2366
|
-
|
|
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";
|