@beignet/cli 0.0.31 → 0.0.32
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 +58 -0
- package/README.md +49 -6
- package/dist/check.d.ts +2 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +16 -0
- package/dist/check.js.map +1 -1
- package/dist/choices.d.ts +22 -2
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +60 -0
- package/dist/choices.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/create-prompts.d.ts +6 -2
- package/dist/create-prompts.d.ts.map +1 -1
- package/dist/create-prompts.js +1 -1
- package/dist/create-prompts.js.map +1 -1
- package/dist/db.d.ts +5 -5
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +4 -4
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +107 -26
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +5 -0
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +8 -4
- package/dist/inspect.js.map +1 -1
- package/dist/make/shared.d.ts +1 -1
- package/dist/make/shared.d.ts.map +1 -1
- package/dist/make/shared.js +8 -1
- package/dist/make/shared.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +7 -10
- package/dist/make.js.map +1 -1
- package/dist/preflight.d.ts +90 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +423 -0
- package/dist/preflight.js.map +1 -0
- package/dist/provider-add.d.ts +10 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +209 -16
- package/dist/provider-add.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +2 -8
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/server.js +4 -4
- package/dist/templates/shared.d.ts +2 -0
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +2 -0
- package/dist/templates/shared.js.map +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +1 -1
- package/src/check.ts +23 -0
- package/src/choices.ts +81 -0
- package/src/config.ts +2 -0
- package/src/create-prompts.ts +8 -2
- package/src/db.ts +11 -11
- package/src/index.ts +155 -36
- package/src/inspect.ts +8 -4
- package/src/make/shared.ts +9 -2
- package/src/make.ts +7 -11
- package/src/preflight.ts +596 -0
- package/src/provider-add.ts +223 -16
- package/src/templates/base.ts +2 -10
- package/src/templates/server.ts +4 -4
- package/src/templates/shared.ts +2 -0
package/src/check.ts
CHANGED
|
@@ -38,6 +38,8 @@ export type CheckAppOptions = {
|
|
|
38
38
|
cwd?: string;
|
|
39
39
|
fix?: boolean;
|
|
40
40
|
color?: boolean;
|
|
41
|
+
/** Append a `beignet preflight` step after the doctor step. */
|
|
42
|
+
preflight?: boolean;
|
|
41
43
|
/** Called after each step completes, for incremental progress output. */
|
|
42
44
|
onStep?: (step: CheckStep) => void;
|
|
43
45
|
};
|
|
@@ -69,6 +71,10 @@ export async function checkApp(
|
|
|
69
71
|
const { step: doctorStep, fixes } = await runDoctorStep(targetDir, options);
|
|
70
72
|
record(doctorStep);
|
|
71
73
|
|
|
74
|
+
if (options.preflight) {
|
|
75
|
+
record(await preflightStep(targetDir));
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
const scripts = await readPackageScripts(targetDir);
|
|
73
79
|
for (const script of scriptStepNames) {
|
|
74
80
|
record(await scriptStep(targetDir, runner, script, scripts));
|
|
@@ -244,3 +250,20 @@ async function readPackageScripts(
|
|
|
244
250
|
};
|
|
245
251
|
return parsed.scripts ?? {};
|
|
246
252
|
}
|
|
253
|
+
|
|
254
|
+
async function preflightStep(targetDir: string): Promise<CheckStep> {
|
|
255
|
+
const startedAt = Date.now();
|
|
256
|
+
const { formatPreflight, runPreflight } = await import("./preflight.js");
|
|
257
|
+
const result = await runPreflight({ cwd: targetDir });
|
|
258
|
+
const failed = result.findings.some(
|
|
259
|
+
(finding) => finding.severity === "error",
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
name: "beignet preflight",
|
|
264
|
+
command: "beignet preflight",
|
|
265
|
+
status: failed ? "failed" : "passed",
|
|
266
|
+
...(failed ? { output: formatPreflight(result, targetDir) } : {}),
|
|
267
|
+
durationMs: Date.now() - startedAt,
|
|
268
|
+
};
|
|
269
|
+
}
|
package/src/choices.ts
CHANGED
|
@@ -29,8 +29,11 @@ export type DatabaseName = "sqlite" | "postgres" | "mysql";
|
|
|
29
29
|
*/
|
|
30
30
|
export type ProviderPresetName =
|
|
31
31
|
| "flags-openfeature"
|
|
32
|
+
| "jobs-bullmq"
|
|
33
|
+
| "jobs-inngest"
|
|
32
34
|
| "mail-resend"
|
|
33
35
|
| "mail-smtp"
|
|
36
|
+
| "payments-stripe"
|
|
34
37
|
| "search-meilisearch"
|
|
35
38
|
| "sentry"
|
|
36
39
|
| "upstash-rate-limit"
|
|
@@ -79,8 +82,11 @@ export const databaseChoices = [
|
|
|
79
82
|
*/
|
|
80
83
|
export const providerPresetChoices = [
|
|
81
84
|
"flags-openfeature",
|
|
85
|
+
"jobs-bullmq",
|
|
86
|
+
"jobs-inngest",
|
|
82
87
|
"mail-resend",
|
|
83
88
|
"mail-smtp",
|
|
89
|
+
"payments-stripe",
|
|
84
90
|
"search-meilisearch",
|
|
85
91
|
"sentry",
|
|
86
92
|
"upstash-rate-limit",
|
|
@@ -90,6 +96,81 @@ export const providerPresetChoices = [
|
|
|
90
96
|
"vercel-blob-storage",
|
|
91
97
|
] as const satisfies readonly ProviderPresetName[];
|
|
92
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Values accepted by `beignet create --integrations`: the starter template
|
|
101
|
+
* integrations plus the full `beignet providers add` preset catalog.
|
|
102
|
+
*/
|
|
103
|
+
export type CreateIntegrationName = IntegrationName | ProviderPresetName;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Supported `beignet create --integrations` choices. Template integrations
|
|
107
|
+
* come first; the rest of the provider preset catalog follows.
|
|
108
|
+
*/
|
|
109
|
+
export const createIntegrationChoices = [
|
|
110
|
+
"inngest",
|
|
111
|
+
"resend",
|
|
112
|
+
"upstash-rate-limit",
|
|
113
|
+
"flags-openfeature",
|
|
114
|
+
"jobs-bullmq",
|
|
115
|
+
"jobs-inngest",
|
|
116
|
+
"mail-resend",
|
|
117
|
+
"mail-smtp",
|
|
118
|
+
"payments-stripe",
|
|
119
|
+
"redis-cache",
|
|
120
|
+
"redis-locks",
|
|
121
|
+
"s3-storage",
|
|
122
|
+
"search-meilisearch",
|
|
123
|
+
"sentry",
|
|
124
|
+
"vercel-blob-storage",
|
|
125
|
+
] as const satisfies readonly CreateIntegrationName[];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Provider presets whose create-time wiring already ships as a starter
|
|
129
|
+
* template integration. Selecting the preset name at create time scaffolds
|
|
130
|
+
* the equivalent template integration, so preset and integration stay
|
|
131
|
+
* consistent and `beignet providers add` remains a no-op afterwards.
|
|
132
|
+
*/
|
|
133
|
+
const presetTemplateIntegrationAliases: Partial<
|
|
134
|
+
Record<ProviderPresetName, IntegrationName>
|
|
135
|
+
> = {
|
|
136
|
+
"jobs-inngest": "inngest",
|
|
137
|
+
"mail-resend": "resend",
|
|
138
|
+
"upstash-rate-limit": "upstash-rate-limit",
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Split `--integrations` selections into starter template integrations and
|
|
143
|
+
* provider presets applied with the `beignet providers add` machinery after
|
|
144
|
+
* the scaffold is written. Preset names with a template equivalent are
|
|
145
|
+
* normalized to the template integration; duplicates collapse.
|
|
146
|
+
*/
|
|
147
|
+
export function splitCreateIntegrations(
|
|
148
|
+
values: readonly CreateIntegrationName[],
|
|
149
|
+
): {
|
|
150
|
+
integrations: IntegrationName[];
|
|
151
|
+
presets: ProviderPresetName[];
|
|
152
|
+
} {
|
|
153
|
+
const integrations = new Set<IntegrationName>();
|
|
154
|
+
const presets = new Set<ProviderPresetName>();
|
|
155
|
+
|
|
156
|
+
for (const value of values) {
|
|
157
|
+
if ((integrationChoices as readonly string[]).includes(value)) {
|
|
158
|
+
integrations.add(value as IntegrationName);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const preset = value as ProviderPresetName;
|
|
163
|
+
const alias = presetTemplateIntegrationAliases[preset];
|
|
164
|
+
if (alias) {
|
|
165
|
+
integrations.add(alias);
|
|
166
|
+
} else {
|
|
167
|
+
presets.add(preset);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { integrations: [...integrations], presets: [...presets] };
|
|
172
|
+
}
|
|
173
|
+
|
|
93
174
|
/**
|
|
94
175
|
* Local database name derived from the app name.
|
|
95
176
|
*
|
package/src/config.ts
CHANGED
|
@@ -32,6 +32,7 @@ export type BeignetPaths = {
|
|
|
32
32
|
schedulesBuilder: string;
|
|
33
33
|
notificationsBuilder: string;
|
|
34
34
|
tasksBuilder: string;
|
|
35
|
+
uploadsBuilder: string;
|
|
35
36
|
tests: string;
|
|
36
37
|
};
|
|
37
38
|
|
|
@@ -100,6 +101,7 @@ export const defaultBeignetConfig: ResolvedBeignetConfig = {
|
|
|
100
101
|
schedulesBuilder: "lib/schedules.ts",
|
|
101
102
|
notificationsBuilder: "lib/notifications.ts",
|
|
102
103
|
tasksBuilder: "lib/tasks.ts",
|
|
104
|
+
uploadsBuilder: "lib/uploads.ts",
|
|
103
105
|
tests: "tests",
|
|
104
106
|
},
|
|
105
107
|
database: {
|
package/src/create-prompts.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
+
type CreateIntegrationName,
|
|
10
11
|
type DatabaseName,
|
|
11
12
|
databaseChoices,
|
|
12
13
|
type IntegrationName,
|
|
@@ -16,12 +17,16 @@ import {
|
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Selections shared by create flags and interactive prompt answers.
|
|
20
|
+
*
|
|
21
|
+
* `integrations` accepts the starter template integrations plus the full
|
|
22
|
+
* `beignet providers add` preset catalog; the interactive prompt shows the
|
|
23
|
+
* curated template list and points at the wider catalog.
|
|
19
24
|
*/
|
|
20
25
|
export type CreateSelections = {
|
|
21
26
|
directory?: string;
|
|
22
27
|
api?: boolean;
|
|
23
28
|
db?: DatabaseName;
|
|
24
|
-
integrations?: readonly
|
|
29
|
+
integrations?: readonly CreateIntegrationName[];
|
|
25
30
|
packageManager?: PackageManager;
|
|
26
31
|
};
|
|
27
32
|
|
|
@@ -114,7 +119,8 @@ export async function promptCreateSelections(
|
|
|
114
119
|
if (prompts.isCancel(db)) return undefined;
|
|
115
120
|
|
|
116
121
|
const integrations = await prompts.multiselect<IntegrationName>({
|
|
117
|
-
message:
|
|
122
|
+
message:
|
|
123
|
+
"Add provider integrations? (press enter to skip; the full preset catalog is available via --integrations or beignet providers add)",
|
|
118
124
|
options: integrationChoices.map((integration) => ({
|
|
119
125
|
value: integration,
|
|
120
126
|
label: integration,
|
package/src/db.ts
CHANGED
|
@@ -36,7 +36,7 @@ export type RunDatabaseCommandResult = {
|
|
|
36
36
|
export type DatabaseSchemaDialect = "sqlite" | "postgres" | "mysql";
|
|
37
37
|
export type DatabaseSchemaTable = "audit" | "idempotency" | "outbox";
|
|
38
38
|
|
|
39
|
-
export type
|
|
39
|
+
export type SyncDatabaseSchemaOptions = {
|
|
40
40
|
cwd?: string;
|
|
41
41
|
dialect?: DatabaseSchemaDialect;
|
|
42
42
|
tables?: readonly DatabaseSchemaTable[];
|
|
@@ -44,9 +44,9 @@ export type GenerateDatabaseSchemaOptions = {
|
|
|
44
44
|
dryRun?: boolean;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
export type
|
|
47
|
+
export type SyncDatabaseSchemaResult = {
|
|
48
48
|
schemaVersion: 1;
|
|
49
|
-
command: "schema:
|
|
49
|
+
command: "schema:sync";
|
|
50
50
|
cwd: string;
|
|
51
51
|
dialect: DatabaseSchemaDialect;
|
|
52
52
|
tables: DatabaseSchemaTable[];
|
|
@@ -132,15 +132,15 @@ export async function runDatabaseCommand(
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
|
-
*
|
|
135
|
+
* Sync the app-owned Drizzle schema entrypoint for Beignet provider tables.
|
|
136
136
|
*
|
|
137
137
|
* This only writes schema source. The app still owns SQL migration generation
|
|
138
138
|
* through `beignet db generate` and migration application through
|
|
139
139
|
* `beignet db migrate`.
|
|
140
140
|
*/
|
|
141
|
-
export async function
|
|
142
|
-
options:
|
|
143
|
-
): Promise<
|
|
141
|
+
export async function syncDatabaseSchema(
|
|
142
|
+
options: SyncDatabaseSchemaOptions = {},
|
|
143
|
+
): Promise<SyncDatabaseSchemaResult> {
|
|
144
144
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
145
145
|
const dialect = await resolveDatabaseSchemaDialect(cwd, options.dialect);
|
|
146
146
|
const tables = uniqueSchemaTables(options.tables ?? allDatabaseSchemaTables);
|
|
@@ -159,9 +159,9 @@ export async function generateDatabaseSchema(
|
|
|
159
159
|
throw new Error("Schema output must not be the schema index file.");
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
const result:
|
|
162
|
+
const result: SyncDatabaseSchemaResult = {
|
|
163
163
|
schemaVersion: 1,
|
|
164
|
-
command: "schema:
|
|
164
|
+
command: "schema:sync",
|
|
165
165
|
cwd,
|
|
166
166
|
dialect,
|
|
167
167
|
tables,
|
|
@@ -345,7 +345,7 @@ function renderDatabaseSchema(
|
|
|
345
345
|
const tableNames = selectedTables.map((table) => table.tableName);
|
|
346
346
|
const exportNames = selectedTables.map((table) => table.exportName).sort();
|
|
347
347
|
|
|
348
|
-
return `//
|
|
348
|
+
return `// Managed by beignet db schema sync.
|
|
349
349
|
// Required tables: ${tableNames.join(", ")}.
|
|
350
350
|
// Re-run this command after upgrading @beignet/provider-db-drizzle.
|
|
351
351
|
|
|
@@ -437,7 +437,7 @@ async function readTextIfExists(file: string): Promise<string | undefined> {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
function recordFileStatus(
|
|
440
|
-
result:
|
|
440
|
+
result: SyncDatabaseSchemaResult,
|
|
441
441
|
file: string,
|
|
442
442
|
status: FileWriteStatus,
|
|
443
443
|
): void {
|
package/src/index.ts
CHANGED
|
@@ -15,12 +15,13 @@ import {
|
|
|
15
15
|
} from "@stricli/core";
|
|
16
16
|
import {
|
|
17
17
|
type CompletionShell,
|
|
18
|
+
type CreateIntegrationName,
|
|
18
19
|
completionShellChoices,
|
|
20
|
+
createIntegrationChoices,
|
|
19
21
|
type DatabaseName,
|
|
20
22
|
databaseChoices,
|
|
21
23
|
databaseStartCommand,
|
|
22
24
|
type IntegrationName,
|
|
23
|
-
integrationChoices,
|
|
24
25
|
type MakeFeatureAddon,
|
|
25
26
|
type MakeFeatureRecipe,
|
|
26
27
|
makeFeatureAddonChoices,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
type ProviderPresetName,
|
|
30
31
|
packageManagerChoices,
|
|
31
32
|
providerPresetChoices,
|
|
33
|
+
splitCreateIntegrations,
|
|
32
34
|
type TemplateName,
|
|
33
35
|
templateChoices,
|
|
34
36
|
} from "./choices.js";
|
|
@@ -54,7 +56,7 @@ type CreateFlags = {
|
|
|
54
56
|
api?: boolean;
|
|
55
57
|
db?: DatabaseName;
|
|
56
58
|
packageManager?: PackageManager;
|
|
57
|
-
integrations?: readonly
|
|
59
|
+
integrations?: readonly CreateIntegrationName[];
|
|
58
60
|
yes?: boolean;
|
|
59
61
|
force?: boolean;
|
|
60
62
|
dryRun?: boolean;
|
|
@@ -108,6 +110,16 @@ type LintFlags = {
|
|
|
108
110
|
type CheckFlags = {
|
|
109
111
|
json?: boolean;
|
|
110
112
|
fix?: boolean;
|
|
113
|
+
preflight?: boolean;
|
|
114
|
+
cwd?: string;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
type PreflightFlags = {
|
|
118
|
+
json?: boolean;
|
|
119
|
+
envFile?: string;
|
|
120
|
+
envModule?: string;
|
|
121
|
+
connect?: boolean;
|
|
122
|
+
serverModule?: string;
|
|
111
123
|
cwd?: string;
|
|
112
124
|
};
|
|
113
125
|
|
|
@@ -131,7 +143,7 @@ const databaseSchemaTableChoices = [
|
|
|
131
143
|
"outbox",
|
|
132
144
|
] as const satisfies readonly DatabaseSchemaTable[];
|
|
133
145
|
|
|
134
|
-
type
|
|
146
|
+
type DbSchemaSyncFlags = DbFlags & {
|
|
135
147
|
dialect?: DatabaseSchemaDialect;
|
|
136
148
|
tables?: readonly DatabaseSchemaTable[];
|
|
137
149
|
output?: string;
|
|
@@ -278,14 +290,14 @@ const dbFlagParameters = {
|
|
|
278
290
|
dryRun: dryRunFlag,
|
|
279
291
|
} satisfies FlagParametersForType<DbFlags, CliContext>;
|
|
280
292
|
|
|
281
|
-
const
|
|
293
|
+
const dbSchemaSyncFlagParameters = {
|
|
282
294
|
...dbFlagParameters,
|
|
283
295
|
dialect: {
|
|
284
296
|
kind: "enum",
|
|
285
297
|
values: databaseSchemaDialectChoices,
|
|
286
298
|
optional: true,
|
|
287
299
|
brief:
|
|
288
|
-
"Drizzle dialect to
|
|
300
|
+
"Drizzle dialect to sync. Inferred from server/providers.ts when omitted.",
|
|
289
301
|
},
|
|
290
302
|
tables: {
|
|
291
303
|
kind: "enum",
|
|
@@ -293,12 +305,12 @@ const dbSchemaGenerateFlagParameters = {
|
|
|
293
305
|
optional: true,
|
|
294
306
|
variadic: ",",
|
|
295
307
|
brief:
|
|
296
|
-
"Provider tables to
|
|
308
|
+
"Provider tables to sync as a comma-separated list. Defaults to audit,idempotency,outbox.",
|
|
297
309
|
},
|
|
298
310
|
output: parsedStringFlag(
|
|
299
|
-
"
|
|
311
|
+
"Synced schema file. Defaults to infra/db/schema/beignet.ts.",
|
|
300
312
|
),
|
|
301
|
-
} satisfies FlagParametersForType<
|
|
313
|
+
} satisfies FlagParametersForType<DbSchemaSyncFlags, CliContext>;
|
|
302
314
|
|
|
303
315
|
const taskRunFlagParameters = {
|
|
304
316
|
json: jsonFlag,
|
|
@@ -400,7 +412,11 @@ and a shadcn UI shell. Pass --api for an API-only scaffold without the
|
|
|
400
412
|
UI shell, and --db to choose the database.
|
|
401
413
|
|
|
402
414
|
Available databases: ${databaseChoices.join(", ")}
|
|
403
|
-
Available integrations: ${
|
|
415
|
+
Available integrations: ${createIntegrationChoices.join(", ")}
|
|
416
|
+
|
|
417
|
+
--integrations accepts the starter template integrations plus the full
|
|
418
|
+
beignet providers add preset catalog; presets are applied to the scaffold
|
|
419
|
+
with the same machinery as beignet providers add.
|
|
404
420
|
|
|
405
421
|
Running create on an interactive terminal without selection flags opens a
|
|
406
422
|
prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
|
|
@@ -433,10 +449,11 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
|
|
|
433
449
|
},
|
|
434
450
|
integrations: {
|
|
435
451
|
kind: "enum",
|
|
436
|
-
values:
|
|
452
|
+
values: createIntegrationChoices,
|
|
437
453
|
optional: true,
|
|
438
454
|
variadic: ",",
|
|
439
|
-
brief:
|
|
455
|
+
brief:
|
|
456
|
+
"Add first-party integrations or provider presets as a comma-separated list.",
|
|
440
457
|
},
|
|
441
458
|
yes: {
|
|
442
459
|
kind: "boolean",
|
|
@@ -504,7 +521,18 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
|
|
|
504
521
|
|
|
505
522
|
const api = selections.api ?? false;
|
|
506
523
|
const database = selections.db ?? "sqlite";
|
|
507
|
-
const integrations =
|
|
524
|
+
const { integrations, presets } = splitCreateIntegrations(
|
|
525
|
+
uniqueValues([...(selections.integrations ?? [])]),
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
let presetsNeedEnv = false;
|
|
529
|
+
if (presets.length > 0) {
|
|
530
|
+
const { assertCompatibleCreatePresets, providerPresetNeedsEnv } =
|
|
531
|
+
await import("./provider-add.js");
|
|
532
|
+
assertCompatibleCreatePresets(presets, integrations);
|
|
533
|
+
presetsNeedEnv = presets.some(providerPresetNeedsEnv);
|
|
534
|
+
}
|
|
535
|
+
|
|
508
536
|
const result = await createProject({
|
|
509
537
|
name: selections.directory,
|
|
510
538
|
template: flags.template,
|
|
@@ -516,11 +544,24 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
|
|
|
516
544
|
dryRun: Boolean(flags.dryRun),
|
|
517
545
|
});
|
|
518
546
|
|
|
547
|
+
if (!flags.dryRun && presets.length > 0) {
|
|
548
|
+
const { addProviderPreset } = await import("./provider-add.js");
|
|
549
|
+
for (const preset of presets) {
|
|
550
|
+
await addProviderPreset({ name: preset, cwd: result.targetDir });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
519
554
|
writeOutput(
|
|
520
555
|
this,
|
|
521
556
|
flags.json
|
|
522
|
-
? JSON.stringify(result, null, 2)
|
|
523
|
-
: nextSteps(result, {
|
|
557
|
+
? JSON.stringify({ ...result, providerPresets: presets }, null, 2)
|
|
558
|
+
: nextSteps(result, {
|
|
559
|
+
api,
|
|
560
|
+
integrations,
|
|
561
|
+
database,
|
|
562
|
+
presets,
|
|
563
|
+
presetsNeedEnv,
|
|
564
|
+
}),
|
|
524
565
|
);
|
|
525
566
|
};
|
|
526
567
|
},
|
|
@@ -622,6 +663,12 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
622
663
|
withNegated: false,
|
|
623
664
|
brief: "Apply low-risk doctor fixes before checking.",
|
|
624
665
|
},
|
|
666
|
+
preflight: {
|
|
667
|
+
kind: "boolean",
|
|
668
|
+
optional: true,
|
|
669
|
+
withNegated: false,
|
|
670
|
+
brief: "Append a beignet preflight step after doctor.",
|
|
671
|
+
},
|
|
625
672
|
cwd: cwdFlag,
|
|
626
673
|
} satisfies FlagParametersForType<CheckFlags, CliContext>,
|
|
627
674
|
},
|
|
@@ -635,6 +682,7 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
635
682
|
const result = await checkApp({
|
|
636
683
|
cwd: flags.cwd,
|
|
637
684
|
fix: flags.fix,
|
|
685
|
+
preflight: flags.preflight,
|
|
638
686
|
color,
|
|
639
687
|
...(flags.json
|
|
640
688
|
? {}
|
|
@@ -656,6 +704,64 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
656
704
|
},
|
|
657
705
|
});
|
|
658
706
|
|
|
707
|
+
const preflightCommand = buildCommand<PreflightFlags, [], CliContext>({
|
|
708
|
+
docs: {
|
|
709
|
+
brief: "Validate the deploy environment before shipping.",
|
|
710
|
+
fullDescription:
|
|
711
|
+
"Runtime production gate, unlike the static doctor checks: reads the actual environment the process runs with, verifies every env var installed provider manifests mark as required, flags values still matching .env.example or common placeholders on secret-like keys, and validates the app env schema by importing lib/env.ts. Run it in the deploy pipeline where production configuration is present. Exits 1 on any error finding.",
|
|
712
|
+
},
|
|
713
|
+
parameters: {
|
|
714
|
+
flags: {
|
|
715
|
+
json: jsonFlag,
|
|
716
|
+
envFile: parsedStringFlag(
|
|
717
|
+
"Dotenv-style file merged under the environment (existing env keys win) for local rehearsal.",
|
|
718
|
+
),
|
|
719
|
+
envModule: parsedStringFlag(
|
|
720
|
+
"App env module validated by importing it. Defaults to lib/env.ts.",
|
|
721
|
+
),
|
|
722
|
+
connect: {
|
|
723
|
+
kind: "boolean",
|
|
724
|
+
optional: true,
|
|
725
|
+
withNegated: false,
|
|
726
|
+
brief:
|
|
727
|
+
"Boot the app server and run every port's checkHealth(). Needs network and real credentials.",
|
|
728
|
+
},
|
|
729
|
+
serverModule: parsedStringFlag(
|
|
730
|
+
"Module exporting getServer, used by --connect. Defaults to server/index.ts.",
|
|
731
|
+
),
|
|
732
|
+
cwd: cwdFlag,
|
|
733
|
+
} satisfies FlagParametersForType<PreflightFlags, CliContext>,
|
|
734
|
+
},
|
|
735
|
+
loader: async () => {
|
|
736
|
+
const { formatPreflight, runPreflight } = await import("./preflight.js");
|
|
737
|
+
const path = await import("node:path");
|
|
738
|
+
|
|
739
|
+
return async function runPreflightCommand(
|
|
740
|
+
this: CliContext,
|
|
741
|
+
flags: PreflightFlags,
|
|
742
|
+
) {
|
|
743
|
+
const cwd = path.resolve(flags.cwd ?? process.cwd());
|
|
744
|
+
const result = await runPreflight({
|
|
745
|
+
cwd,
|
|
746
|
+
envFile: flags.envFile,
|
|
747
|
+
envModule: flags.envModule,
|
|
748
|
+
connect: flags.connect,
|
|
749
|
+
serverModule: flags.serverModule,
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
writeOutput(
|
|
753
|
+
this,
|
|
754
|
+
flags.json
|
|
755
|
+
? JSON.stringify({ schemaVersion: 1, cwd, ...result }, null, 2)
|
|
756
|
+
: formatPreflight(result, cwd),
|
|
757
|
+
);
|
|
758
|
+
if (result.findings.some((finding) => finding.severity === "error")) {
|
|
759
|
+
this.process.exitCode = 1;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
|
|
659
765
|
const lintCommand = buildCommand<LintFlags, [], CliContext>({
|
|
660
766
|
docs: {
|
|
661
767
|
brief: "Check Beignet dependency direction conventions.",
|
|
@@ -819,27 +925,23 @@ function databaseCommand(command: DatabaseCommandName) {
|
|
|
819
925
|
});
|
|
820
926
|
}
|
|
821
927
|
|
|
822
|
-
const
|
|
823
|
-
DbSchemaGenerateFlags,
|
|
824
|
-
[],
|
|
825
|
-
CliContext
|
|
826
|
-
>({
|
|
928
|
+
const dbSchemaSyncCommand = buildCommand<DbSchemaSyncFlags, [], CliContext>({
|
|
827
929
|
docs: {
|
|
828
|
-
brief: "
|
|
930
|
+
brief: "Sync app-owned re-exports of Beignet provider tables.",
|
|
829
931
|
fullDescription:
|
|
830
|
-
"
|
|
932
|
+
"Idempotently brings the app-owned schema file that re-exports Beignet provider tables in sync with the installed providers, then updates the schema index. Run beignet db generate afterward to let the app's Drizzle Kit script create migrations.",
|
|
831
933
|
},
|
|
832
934
|
parameters: {
|
|
833
|
-
flags:
|
|
935
|
+
flags: dbSchemaSyncFlagParameters,
|
|
834
936
|
},
|
|
835
937
|
loader: async () => {
|
|
836
|
-
const {
|
|
938
|
+
const { syncDatabaseSchema } = await import("./db.js");
|
|
837
939
|
|
|
838
|
-
return async function
|
|
940
|
+
return async function runDbSchemaSync(
|
|
839
941
|
this: CliContext,
|
|
840
|
-
flags:
|
|
942
|
+
flags: DbSchemaSyncFlags,
|
|
841
943
|
) {
|
|
842
|
-
const result = await
|
|
944
|
+
const result = await syncDatabaseSchema({
|
|
843
945
|
dialect: flags.dialect,
|
|
844
946
|
tables: flags.tables,
|
|
845
947
|
output: flags.output,
|
|
@@ -850,7 +952,7 @@ const dbSchemaGenerateCommand = buildCommand<
|
|
|
850
952
|
this,
|
|
851
953
|
flags.json
|
|
852
954
|
? JSON.stringify(result, null, 2)
|
|
853
|
-
:
|
|
955
|
+
: databaseSchemaSyncNextSteps(result),
|
|
854
956
|
);
|
|
855
957
|
};
|
|
856
958
|
},
|
|
@@ -858,10 +960,10 @@ const dbSchemaGenerateCommand = buildCommand<
|
|
|
858
960
|
|
|
859
961
|
const dbSchemaRoutes = buildRouteMap({
|
|
860
962
|
docs: {
|
|
861
|
-
brief: "
|
|
963
|
+
brief: "Sync app-owned Beignet provider schema exports.",
|
|
862
964
|
},
|
|
863
965
|
routes: {
|
|
864
|
-
|
|
966
|
+
sync: dbSchemaSyncCommand,
|
|
865
967
|
},
|
|
866
968
|
});
|
|
867
969
|
|
|
@@ -1557,6 +1659,7 @@ Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
|
|
|
1557
1659
|
make: makeRoutes,
|
|
1558
1660
|
mcp: mcpCommand,
|
|
1559
1661
|
outbox: outboxRoutes,
|
|
1662
|
+
preflight: preflightCommand,
|
|
1560
1663
|
providers: providerRoutes,
|
|
1561
1664
|
routes: routesCommand,
|
|
1562
1665
|
schedule: scheduleRoutes,
|
|
@@ -1626,7 +1729,7 @@ type DatabaseCommandNextStepsResult = {
|
|
|
1626
1729
|
dryRun: boolean;
|
|
1627
1730
|
};
|
|
1628
1731
|
|
|
1629
|
-
type
|
|
1732
|
+
type DatabaseSchemaSyncNextStepsResult = {
|
|
1630
1733
|
cwd: string;
|
|
1631
1734
|
dialect: DatabaseSchemaDialect;
|
|
1632
1735
|
tables: DatabaseSchemaTable[];
|
|
@@ -1708,15 +1811,23 @@ function nextSteps(
|
|
|
1708
1811
|
api?: boolean;
|
|
1709
1812
|
integrations?: readonly IntegrationName[];
|
|
1710
1813
|
database?: DatabaseName;
|
|
1814
|
+
presets?: readonly ProviderPresetName[];
|
|
1815
|
+
presetsNeedEnv?: boolean;
|
|
1711
1816
|
} = {},
|
|
1712
1817
|
): string {
|
|
1713
1818
|
if (result.dryRun) {
|
|
1714
1819
|
const plannedFiles = result.files.map((file) => ` ${file}`).join("\n");
|
|
1820
|
+
const plannedPresets =
|
|
1821
|
+
options.presets && options.presets.length > 0
|
|
1822
|
+
? `\n\nProvider presets applied after scaffold (skipped in dry run):\n${options.presets
|
|
1823
|
+
.map((preset) => ` ${preset}`)
|
|
1824
|
+
.join("\n")}`
|
|
1825
|
+
: "";
|
|
1715
1826
|
|
|
1716
1827
|
return `Would create ${result.name} at ${result.targetDir}
|
|
1717
1828
|
|
|
1718
1829
|
Planned files:
|
|
1719
|
-
${plannedFiles}`;
|
|
1830
|
+
${plannedFiles}${plannedPresets}`;
|
|
1720
1831
|
}
|
|
1721
1832
|
|
|
1722
1833
|
const pm = result.packageManager;
|
|
@@ -1725,9 +1836,17 @@ ${plannedFiles}`;
|
|
|
1725
1836
|
const envRequiredIntegrations =
|
|
1726
1837
|
options.integrations?.filter(isEnvRequiredProviderIntegration) ?? [];
|
|
1727
1838
|
const envStep =
|
|
1728
|
-
envRequiredIntegrations.length > 0
|
|
1839
|
+
envRequiredIntegrations.length > 0 || options.presetsNeedEnv
|
|
1729
1840
|
? " Fill .env.local for selected provider integrations before starting the app.\n"
|
|
1730
1841
|
: "";
|
|
1842
|
+
const presetSection =
|
|
1843
|
+
options.presets && options.presets.length > 0
|
|
1844
|
+
? `\nProvider presets applied:\n${options.presets
|
|
1845
|
+
.map((preset) => ` ${preset}`)
|
|
1846
|
+
.join(
|
|
1847
|
+
"\n",
|
|
1848
|
+
)}\n See docs/integrations.md for setup notes and follow-up steps.\n`
|
|
1849
|
+
: "";
|
|
1731
1850
|
const openStep = options.api
|
|
1732
1851
|
? " open http://localhost:3000 for the API landing page"
|
|
1733
1852
|
: " open http://localhost:3000/sign-up";
|
|
@@ -1742,7 +1861,7 @@ ${plannedFiles}`;
|
|
|
1742
1861
|
: "";
|
|
1743
1862
|
|
|
1744
1863
|
return `Created ${result.name} at ${result.targetDir}
|
|
1745
|
-
|
|
1864
|
+
${presetSection}
|
|
1746
1865
|
Next steps:
|
|
1747
1866
|
cd ${result.targetDir}
|
|
1748
1867
|
${pm} install
|
|
@@ -1858,15 +1977,15 @@ Command:
|
|
|
1858
1977
|
${command}`;
|
|
1859
1978
|
}
|
|
1860
1979
|
|
|
1861
|
-
function
|
|
1862
|
-
result:
|
|
1980
|
+
function databaseSchemaSyncNextSteps(
|
|
1981
|
+
result: DatabaseSchemaSyncNextStepsResult,
|
|
1863
1982
|
): string {
|
|
1864
1983
|
const { changedFiles, skippedFiles } = changedFileLines({
|
|
1865
1984
|
createdFiles: result.createdFiles,
|
|
1866
1985
|
updatedFiles: result.updatedFiles,
|
|
1867
1986
|
skippedFiles: result.skippedFiles,
|
|
1868
1987
|
});
|
|
1869
|
-
const prefix = result.dryRun ? "Would
|
|
1988
|
+
const prefix = result.dryRun ? "Would sync" : "Synced";
|
|
1870
1989
|
const tables = result.tables.join(", ");
|
|
1871
1990
|
|
|
1872
1991
|
return `${prefix} Beignet ${result.dialect} database schema (${tables}) in ${result.cwd}
|