@beignet/cli 0.0.47 → 0.0.48
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 +12 -0
- package/README.md +50 -21
- package/dist/check.d.ts +8 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +18 -6
- package/dist/check.js.map +1 -1
- package/dist/db.d.ts +38 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +52 -0
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -6
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +39 -2
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +2 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/make/inbox.js +1 -1
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.js +1 -1
- package/dist/make/payments.js.map +1 -1
- package/dist/make/tenancy.js +1 -1
- package/dist/make/tenancy.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +39 -2
- package/dist/mcp.js.map +1 -1
- package/dist/preflight.d.ts +11 -3
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +139 -16
- package/dist/preflight.js.map +1 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +7 -2
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.d.ts +13 -0
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +122 -8
- package/dist/provider-audit.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +13 -7
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts +1 -0
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +11 -5
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/index.d.ts +1 -0
- package/dist/templates/db/index.d.ts.map +1 -1
- package/dist/templates/db/index.js.map +1 -1
- package/dist/templates/db/migration-status.d.ts +9 -0
- package/dist/templates/db/migration-status.d.ts.map +1 -0
- package/dist/templates/db/migration-status.js +117 -0
- package/dist/templates/db/migration-status.js.map +1 -0
- package/dist/templates/db/mysql.d.ts.map +1 -1
- package/dist/templates/db/mysql.js +23 -0
- package/dist/templates/db/mysql.js.map +1 -1
- package/dist/templates/db/postgres.d.ts.map +1 -1
- package/dist/templates/db/postgres.js +21 -0
- package/dist/templates/db/postgres.js.map +1 -1
- package/dist/templates/db/sqlite.d.ts.map +1 -1
- package/dist/templates/db/sqlite.js +53 -0
- package/dist/templates/db/sqlite.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +5 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +1 -0
- package/dist/templates/shared.js.map +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +11 -7
- package/src/check.ts +35 -7
- package/src/db.ts +97 -0
- package/src/index.ts +108 -7
- package/src/inspect.ts +76 -1
- package/src/lib.ts +9 -1
- package/src/make/inbox.ts +1 -1
- package/src/make/payments.ts +1 -1
- package/src/make/tenancy.ts +1 -1
- package/src/mcp.ts +56 -1
- package/src/preflight.ts +195 -16
- package/src/provider-add.ts +7 -2
- package/src/provider-audit.ts +210 -18
- package/src/templates/agents.ts +13 -7
- package/src/templates/base.ts +12 -3
- package/src/templates/db/index.ts +1 -0
- package/src/templates/db/migration-status.ts +126 -0
- package/src/templates/db/mysql.ts +23 -0
- package/src/templates/db/postgres.ts +21 -0
- package/src/templates/db/sqlite.ts +53 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/shared.ts +1 -0
|
@@ -64,6 +64,7 @@ changing `infra/db/schema/`, run the app's db lifecycle commands:
|
|
|
64
64
|
```bash
|
|
65
65
|
beignet db generate
|
|
66
66
|
beignet db migrate
|
|
67
|
+
beignet db status
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
## Explicit Registration
|
|
@@ -195,7 +196,7 @@ Generated apps register:
|
|
|
195
196
|
}
|
|
196
197
|
```
|
|
197
198
|
|
|
198
|
-
The MCP server exposes `app_map`, `explain`, `check`, `db`,
|
|
199
|
+
The MCP server exposes `app_map`, `explain`, `check`, `db`, `db_status`,
|
|
199
200
|
`db_schema_sync`, `task_run`, `schedule_run`, `outbox_inspect`, `outbox_run`,
|
|
200
201
|
`routes`, `doctor`, `doctor_fix_plan`, `doctor_fix`, `lint`, `make`, and
|
|
201
202
|
`provider_add`. It also publishes `beignet://app/guidance` and the
|
|
@@ -210,10 +211,11 @@ concept or diagnostic and returns the same deterministic result as
|
|
|
210
211
|
`beignet explain <kind> <target> --json`. Explain accepts every app-map node
|
|
211
212
|
kind plus `diagnostic`; targets may use stable IDs, runtime or declaration
|
|
212
213
|
names, source selectors, and applicable aliases. `check` accepts optional
|
|
213
|
-
`{ preflight?: boolean,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
`{ preflight?: boolean, preflightConnect?: boolean, connectTimeoutMs?: number, timeoutMs?: number }`.
|
|
215
|
+
Connected preflight adds migration status and dependency health checks. Check
|
|
216
|
+
bounds failure output, cancels the active script with the MCP request, and
|
|
217
|
+
returns the same versioned result as `beignet check --json`. It does not apply
|
|
218
|
+
Beignet fixes, but app package scripts retain their normal side effects. `db` accepts
|
|
217
219
|
`{ command: "generate" | "migrate" | "seed" | "reset", dryRun?, timeoutMs? }`
|
|
218
220
|
and returns the matching versioned `beignet db <command> --json` report with
|
|
219
221
|
bounded output, cancellation, and process-tree timeouts. `db_schema_sync`
|
|
@@ -221,7 +223,9 @@ accepts `{ dialect?, tables?, output?, dryRun? }` and returns the matching
|
|
|
221
223
|
`beignet db schema sync --json` report. For lifecycle commands, `dryRun`
|
|
222
224
|
validates and reports the app-owned script without executing it; it does not
|
|
223
225
|
simulate SQL or data changes. `reset` remains environment-sensitive even
|
|
224
|
-
though the generated entrypoint has a production guard. `
|
|
226
|
+
though the generated entrypoint has a production guard. `db_status` runs the
|
|
227
|
+
app-owned read-only migration inspection: current exits 0, pending exits 2,
|
|
228
|
+
and inspection failures use another non-zero exit. `task_run` and
|
|
225
229
|
`schedule_run` execute registered app workflows. Use `outbox_inspect` for
|
|
226
230
|
read-only `list` and `show`, and `outbox_run` for state-changing `drain`,
|
|
227
231
|
`requeue`, `purge`, and `prune`; purge and prune support `dryRun`. Operational
|
|
@@ -255,7 +259,7 @@ When MCP is available, call `check` after edits to run the full validation loop
|
|
|
255
259
|
through the app-local CLI without reconstructing these commands individually.
|
|
256
260
|
For a generated persistent resource, the complete structured workflow is
|
|
257
261
|
`app_map` → `explain` → `make` → `db_schema_sync` when provider tables changed
|
|
258
|
-
→ `db` generate → `db` migrate → the relevant `task_run`, `schedule_run`, or
|
|
262
|
+
→ `db` generate → `db` migrate → `db_status` → the relevant `task_run`, `schedule_run`, or
|
|
259
263
|
outbox tool when operational work is required → `check`.
|
|
260
264
|
|
|
261
265
|
For generated app template changes inside the Beignet monorepo, also run:
|
package/src/check.ts
CHANGED
|
@@ -39,8 +39,15 @@ export type CheckAppOptions = {
|
|
|
39
39
|
cwd?: string;
|
|
40
40
|
fix?: boolean;
|
|
41
41
|
color?: boolean;
|
|
42
|
-
/** Append a `beignet preflight` step after the doctor step. */
|
|
42
|
+
/** Append a disconnected `beignet preflight` step after the doctor step. */
|
|
43
43
|
preflight?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Append a connected `beignet preflight --connect` step after doctor.
|
|
46
|
+
* Implies `preflight`.
|
|
47
|
+
*/
|
|
48
|
+
preflightConnect?: boolean;
|
|
49
|
+
/** Per migration-status or dependency health check timeout in milliseconds. */
|
|
50
|
+
connectTimeoutMs?: number;
|
|
44
51
|
/** Keep only the last N bytes from each package-script output stream. */
|
|
45
52
|
maxOutputBytes?: number;
|
|
46
53
|
/** Cancel the active package script with the owning request. */
|
|
@@ -81,9 +88,15 @@ export async function checkApp(
|
|
|
81
88
|
const { step: doctorStep, fixes } = await runDoctorStep(targetDir, options);
|
|
82
89
|
record(doctorStep);
|
|
83
90
|
|
|
84
|
-
if (options.preflight) {
|
|
91
|
+
if (options.preflight || options.preflightConnect) {
|
|
85
92
|
options.signal?.throwIfAborted();
|
|
86
|
-
record(
|
|
93
|
+
record(
|
|
94
|
+
await preflightStep(targetDir, {
|
|
95
|
+
connect: Boolean(options.preflightConnect),
|
|
96
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
97
|
+
signal: options.signal,
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
const scripts = await readPackageScripts(targetDir);
|
|
@@ -274,17 +287,32 @@ async function readPackageScripts(
|
|
|
274
287
|
return parsed.scripts ?? {};
|
|
275
288
|
}
|
|
276
289
|
|
|
277
|
-
async function preflightStep(
|
|
290
|
+
async function preflightStep(
|
|
291
|
+
targetDir: string,
|
|
292
|
+
options: {
|
|
293
|
+
connect: boolean;
|
|
294
|
+
connectTimeoutMs?: number;
|
|
295
|
+
signal?: AbortSignal;
|
|
296
|
+
},
|
|
297
|
+
): Promise<CheckStep> {
|
|
278
298
|
const startedAt = Date.now();
|
|
279
299
|
const { formatPreflight, runPreflight } = await import("./preflight.js");
|
|
280
|
-
const result = await runPreflight({
|
|
300
|
+
const result = await runPreflight({
|
|
301
|
+
cwd: targetDir,
|
|
302
|
+
connect: options.connect,
|
|
303
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
304
|
+
signal: options.signal,
|
|
305
|
+
});
|
|
281
306
|
const failed = result.findings.some(
|
|
282
307
|
(finding) => finding.severity === "error",
|
|
283
308
|
);
|
|
309
|
+
const command = options.connect
|
|
310
|
+
? "beignet preflight --connect"
|
|
311
|
+
: "beignet preflight";
|
|
284
312
|
|
|
285
313
|
return {
|
|
286
|
-
name:
|
|
287
|
-
command
|
|
314
|
+
name: command,
|
|
315
|
+
command,
|
|
288
316
|
status: failed ? "failed" : "passed",
|
|
289
317
|
...(failed ? { output: formatPreflight(result, targetDir) } : {}),
|
|
290
318
|
durationMs: Date.now() - startedAt,
|
package/src/db.ts
CHANGED
|
@@ -18,6 +18,8 @@ export type {
|
|
|
18
18
|
export const defaultDatabaseCommandMaxOutputBytes = 64 * 1024;
|
|
19
19
|
/** Default timeout used by request-controlled database command callers. */
|
|
20
20
|
export const defaultDatabaseCommandTimeoutMs = 10 * 60 * 1_000;
|
|
21
|
+
/** Exit code reserved for an app-owned status script that found pending migrations. */
|
|
22
|
+
export const databaseMigrationsPendingExitCode = 2;
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Options for running a database lifecycle command.
|
|
@@ -53,6 +55,37 @@ export type RunDatabaseCommandResult = {
|
|
|
53
55
|
exitCode: number;
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
/** Migration state reported by an app-owned database status script. */
|
|
59
|
+
export type DatabaseMigrationStatus = "current" | "pending" | "failed";
|
|
60
|
+
|
|
61
|
+
/** Options for inspecting app-owned database migration status. */
|
|
62
|
+
export type RunDatabaseStatusOptions = {
|
|
63
|
+
cwd?: string;
|
|
64
|
+
captureOutput?: boolean;
|
|
65
|
+
/** Keep only the last N bytes from each output stream. */
|
|
66
|
+
maxOutputBytes?: number;
|
|
67
|
+
/** Cancel the app-owned status script when the owning request aborts. */
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
/** Stop the app-owned status script after this many milliseconds. */
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Versioned result returned by `beignet db status`. */
|
|
74
|
+
export type RunDatabaseStatusResult = {
|
|
75
|
+
schemaVersion: 1;
|
|
76
|
+
command: "status";
|
|
77
|
+
status: DatabaseMigrationStatus;
|
|
78
|
+
cwd: string;
|
|
79
|
+
script: "db:status";
|
|
80
|
+
runner: string;
|
|
81
|
+
args: string[];
|
|
82
|
+
stdout?: string;
|
|
83
|
+
stderr?: string;
|
|
84
|
+
outputTruncated?: boolean;
|
|
85
|
+
timedOut?: boolean;
|
|
86
|
+
exitCode: number;
|
|
87
|
+
};
|
|
88
|
+
|
|
56
89
|
export type SpawnCommandOptions = {
|
|
57
90
|
captureOutput: boolean;
|
|
58
91
|
/** Keep only the last N bytes from each output stream. Unbounded when omitted. */
|
|
@@ -176,6 +209,70 @@ export async function runDatabaseCommand(
|
|
|
176
209
|
};
|
|
177
210
|
}
|
|
178
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Inspect migrations through the app-owned `db:status` script.
|
|
214
|
+
*
|
|
215
|
+
* Exit 0 means current, exit 2 means pending, and every other non-zero exit
|
|
216
|
+
* means the inspection failed. The app owns database-specific comparison
|
|
217
|
+
* logic while Beignet owns the stable report and automation contract.
|
|
218
|
+
*/
|
|
219
|
+
export async function runDatabaseStatus(
|
|
220
|
+
options: RunDatabaseStatusOptions = {},
|
|
221
|
+
): Promise<RunDatabaseStatusResult> {
|
|
222
|
+
options.signal?.throwIfAborted();
|
|
223
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
224
|
+
const script = "db:status" as const;
|
|
225
|
+
const packageJson = await readPackageJson(cwd);
|
|
226
|
+
const scriptCommand = packageJson.scripts?.[script];
|
|
227
|
+
|
|
228
|
+
if (!scriptCommand) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
'Missing package.json script "db:status". Add an app-owned read-only migration status script so beignet db status can inspect the target database.',
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
scriptCommand.includes("infra/db/migration-status.ts") &&
|
|
236
|
+
!(await exists(path.join(cwd, "infra/db/migration-status.ts")))
|
|
237
|
+
) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
'Missing database status entrypoint infra/db/migration-status.ts. beignet db status runs the "db:status" script, so restore the entrypoint or update package.json.',
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
options.signal?.throwIfAborted();
|
|
244
|
+
const runner = await detectPackageManager(cwd);
|
|
245
|
+
const args = ["run", script];
|
|
246
|
+
const commandResult = await spawnCommand(runner, args, cwd, {
|
|
247
|
+
captureOutput: Boolean(options.captureOutput),
|
|
248
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
249
|
+
signal: options.signal,
|
|
250
|
+
timeoutMs: options.timeoutMs,
|
|
251
|
+
});
|
|
252
|
+
const status: DatabaseMigrationStatus =
|
|
253
|
+
commandResult.exitCode === 0
|
|
254
|
+
? "current"
|
|
255
|
+
: commandResult.exitCode === databaseMigrationsPendingExitCode
|
|
256
|
+
? "pending"
|
|
257
|
+
: "failed";
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
schemaVersion: 1,
|
|
261
|
+
command: "status",
|
|
262
|
+
status,
|
|
263
|
+
cwd,
|
|
264
|
+
script,
|
|
265
|
+
runner,
|
|
266
|
+
args,
|
|
267
|
+
exitCode: commandResult.exitCode,
|
|
268
|
+
...(options.captureOutput
|
|
269
|
+
? { stdout: commandResult.stdout, stderr: commandResult.stderr }
|
|
270
|
+
: {}),
|
|
271
|
+
...(commandResult.outputTruncated ? { outputTruncated: true } : {}),
|
|
272
|
+
...(commandResult.timedOut ? { timedOut: true } : {}),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
179
276
|
/**
|
|
180
277
|
* Sync the app-owned Drizzle schema entrypoint for Beignet provider tables.
|
|
181
278
|
*
|
package/src/index.ts
CHANGED
|
@@ -134,6 +134,8 @@ type CheckFlags = {
|
|
|
134
134
|
json?: boolean;
|
|
135
135
|
fix?: boolean;
|
|
136
136
|
preflight?: boolean;
|
|
137
|
+
preflightConnect?: boolean;
|
|
138
|
+
connectTimeoutMs?: number;
|
|
137
139
|
cwd?: string;
|
|
138
140
|
};
|
|
139
141
|
|
|
@@ -142,6 +144,7 @@ type PreflightFlags = {
|
|
|
142
144
|
envFile?: string;
|
|
143
145
|
envModule?: string;
|
|
144
146
|
connect?: boolean;
|
|
147
|
+
connectTimeoutMs?: number;
|
|
145
148
|
serverModule?: string;
|
|
146
149
|
cwd?: string;
|
|
147
150
|
};
|
|
@@ -152,6 +155,11 @@ type DbFlags = {
|
|
|
152
155
|
cwd?: string;
|
|
153
156
|
};
|
|
154
157
|
|
|
158
|
+
type DbStatusFlags = {
|
|
159
|
+
json?: boolean;
|
|
160
|
+
cwd?: string;
|
|
161
|
+
};
|
|
162
|
+
|
|
155
163
|
type DbSchemaSyncFlags = DbFlags & {
|
|
156
164
|
dialect?: DatabaseSchemaDialect;
|
|
157
165
|
tables?: readonly DatabaseSchemaTable[];
|
|
@@ -349,6 +357,14 @@ const parsedDateFlag = (brief: string) =>
|
|
|
349
357
|
brief,
|
|
350
358
|
}) as const;
|
|
351
359
|
|
|
360
|
+
const connectTimeoutMsFlag = {
|
|
361
|
+
kind: "parsed",
|
|
362
|
+
parse: parsePositiveInteger,
|
|
363
|
+
optional: true,
|
|
364
|
+
brief:
|
|
365
|
+
"Per migration-status or dependency health check timeout in milliseconds. Defaults to 5000.",
|
|
366
|
+
} as const;
|
|
367
|
+
|
|
352
368
|
const cwdFlag = parsedStringFlag(
|
|
353
369
|
"App directory. Defaults to the current working directory.",
|
|
354
370
|
);
|
|
@@ -411,6 +427,11 @@ const dbFlagParameters = {
|
|
|
411
427
|
cwd: cwdFlag,
|
|
412
428
|
} satisfies FlagParametersForType<DbFlags, CliContext>;
|
|
413
429
|
|
|
430
|
+
const dbStatusFlagParameters = {
|
|
431
|
+
json: jsonFlag,
|
|
432
|
+
cwd: cwdFlag,
|
|
433
|
+
} satisfies FlagParametersForType<DbStatusFlags, CliContext>;
|
|
434
|
+
|
|
414
435
|
const dbSchemaSyncFlagParameters = {
|
|
415
436
|
...dbFlagParameters,
|
|
416
437
|
dialect: {
|
|
@@ -1097,7 +1118,7 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
1097
1118
|
docs: {
|
|
1098
1119
|
brief: "Run the full app validation loop as one command.",
|
|
1099
1120
|
fullDescription:
|
|
1100
|
-
"Runs beignet lint, beignet doctor --strict, and the app's lint, typecheck, and test package scripts through the detected package manager. Every step runs even when an earlier one fails, so one run reports everything. Missing package scripts are skipped, not failed.",
|
|
1121
|
+
"Runs beignet lint, beignet doctor --strict, optional disconnected or connected preflight, and the app's lint, typecheck, and test package scripts through the detected package manager. Every step runs even when an earlier one fails, so one run reports everything. Missing package scripts are skipped, not failed.",
|
|
1101
1122
|
},
|
|
1102
1123
|
parameters: {
|
|
1103
1124
|
flags: {
|
|
@@ -1112,8 +1133,17 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
1112
1133
|
kind: "boolean",
|
|
1113
1134
|
optional: true,
|
|
1114
1135
|
withNegated: false,
|
|
1115
|
-
brief:
|
|
1136
|
+
brief:
|
|
1137
|
+
"Append a disconnected beignet preflight environment step after doctor.",
|
|
1138
|
+
},
|
|
1139
|
+
preflightConnect: {
|
|
1140
|
+
kind: "boolean",
|
|
1141
|
+
optional: true,
|
|
1142
|
+
withNegated: false,
|
|
1143
|
+
brief:
|
|
1144
|
+
"Append beignet preflight --connect after doctor. Implies --preflight.",
|
|
1116
1145
|
},
|
|
1146
|
+
connectTimeoutMs: connectTimeoutMsFlag,
|
|
1117
1147
|
cwd: cwdFlag,
|
|
1118
1148
|
} satisfies FlagParametersForType<CheckFlags, CliContext>,
|
|
1119
1149
|
},
|
|
@@ -1128,6 +1158,8 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
1128
1158
|
cwd: flags.cwd,
|
|
1129
1159
|
fix: flags.fix,
|
|
1130
1160
|
preflight: flags.preflight,
|
|
1161
|
+
preflightConnect: flags.preflightConnect,
|
|
1162
|
+
connectTimeoutMs: flags.connectTimeoutMs,
|
|
1131
1163
|
color,
|
|
1132
1164
|
...(flags.json
|
|
1133
1165
|
? {}
|
|
@@ -1153,7 +1185,7 @@ const preflightCommand = buildCommand<PreflightFlags, [], CliContext>({
|
|
|
1153
1185
|
docs: {
|
|
1154
1186
|
brief: "Validate the deploy environment before shipping.",
|
|
1155
1187
|
fullDescription:
|
|
1156
|
-
"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.",
|
|
1188
|
+
"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. With --connect, it checks migration status before booting the server and running port health checks. Run it in the deploy pipeline where production configuration is present. Exits 1 on any error finding.",
|
|
1157
1189
|
},
|
|
1158
1190
|
parameters: {
|
|
1159
1191
|
flags: {
|
|
@@ -1169,8 +1201,9 @@ const preflightCommand = buildCommand<PreflightFlags, [], CliContext>({
|
|
|
1169
1201
|
optional: true,
|
|
1170
1202
|
withNegated: false,
|
|
1171
1203
|
brief:
|
|
1172
|
-
"
|
|
1204
|
+
"Check migration status, then boot the app server and run every port's checkHealth(). Needs network and real credentials.",
|
|
1173
1205
|
},
|
|
1206
|
+
connectTimeoutMs: connectTimeoutMsFlag,
|
|
1174
1207
|
serverModule: parsedStringFlag(
|
|
1175
1208
|
"Module exporting getServer, used by --connect. Defaults to server/index.ts.",
|
|
1176
1209
|
),
|
|
@@ -1191,6 +1224,7 @@ const preflightCommand = buildCommand<PreflightFlags, [], CliContext>({
|
|
|
1191
1224
|
envFile: flags.envFile,
|
|
1192
1225
|
envModule: flags.envModule,
|
|
1193
1226
|
connect: flags.connect,
|
|
1227
|
+
connectTimeoutMs: flags.connectTimeoutMs,
|
|
1194
1228
|
serverModule: flags.serverModule,
|
|
1195
1229
|
});
|
|
1196
1230
|
|
|
@@ -1374,6 +1408,42 @@ function databaseCommand(command: DatabaseCommand) {
|
|
|
1374
1408
|
});
|
|
1375
1409
|
}
|
|
1376
1410
|
|
|
1411
|
+
const databaseStatusCommand = buildCommand<DbStatusFlags, [], CliContext>({
|
|
1412
|
+
docs: {
|
|
1413
|
+
brief: "Inspect whether app-owned database migrations are current.",
|
|
1414
|
+
fullDescription:
|
|
1415
|
+
"Runs the app's read-only db:status script. Exit 0 means current, exit 2 means pending migrations, and any other non-zero exit means inspection failed.",
|
|
1416
|
+
},
|
|
1417
|
+
parameters: {
|
|
1418
|
+
flags: dbStatusFlagParameters,
|
|
1419
|
+
},
|
|
1420
|
+
loader: async () => {
|
|
1421
|
+
const { defaultDatabaseCommandMaxOutputBytes, runDatabaseStatus } =
|
|
1422
|
+
await import("./db.js");
|
|
1423
|
+
|
|
1424
|
+
return async function runDbStatus(this: CliContext, flags: DbStatusFlags) {
|
|
1425
|
+
const captureOutput = Boolean(flags.json);
|
|
1426
|
+
const result = await runDatabaseStatus({
|
|
1427
|
+
cwd: flags.cwd,
|
|
1428
|
+
captureOutput,
|
|
1429
|
+
maxOutputBytes: captureOutput
|
|
1430
|
+
? defaultDatabaseCommandMaxOutputBytes
|
|
1431
|
+
: undefined,
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
writeOutput(
|
|
1435
|
+
this,
|
|
1436
|
+
flags.json
|
|
1437
|
+
? JSON.stringify(result, null, 2)
|
|
1438
|
+
: databaseStatusNextSteps(result),
|
|
1439
|
+
);
|
|
1440
|
+
if (result.exitCode !== 0) {
|
|
1441
|
+
this.process.exitCode = result.exitCode;
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
},
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1377
1447
|
const dbSchemaSyncCommand = buildCommand<DbSchemaSyncFlags, [], CliContext>({
|
|
1378
1448
|
docs: {
|
|
1379
1449
|
brief: "Sync app-owned re-exports of Beignet provider tables.",
|
|
@@ -1427,7 +1497,8 @@ const dbRoutes = buildRouteMap({
|
|
|
1427
1497
|
reset: databaseCommand("reset"),
|
|
1428
1498
|
schema: dbSchemaRoutes,
|
|
1429
1499
|
seed: databaseCommand("seed"),
|
|
1430
|
-
|
|
1500
|
+
status: databaseStatusCommand,
|
|
1501
|
+
} satisfies Record<DatabaseCommand | "schema" | "status", unknown>,
|
|
1431
1502
|
});
|
|
1432
1503
|
|
|
1433
1504
|
const taskRunCommand = buildCommand<TaskRunFlags, [string], CliContext>({
|
|
@@ -2398,6 +2469,14 @@ type DatabaseCommandNextStepsResult = {
|
|
|
2398
2469
|
dryRun: boolean;
|
|
2399
2470
|
};
|
|
2400
2471
|
|
|
2472
|
+
type DatabaseStatusNextStepsResult = {
|
|
2473
|
+
status: "current" | "pending" | "failed";
|
|
2474
|
+
cwd: string;
|
|
2475
|
+
script: string;
|
|
2476
|
+
runner: string;
|
|
2477
|
+
args: string[];
|
|
2478
|
+
};
|
|
2479
|
+
|
|
2401
2480
|
type DatabaseSchemaSyncNextStepsResult = {
|
|
2402
2481
|
cwd: string;
|
|
2403
2482
|
dialect: DatabaseSchemaDialect;
|
|
@@ -2632,6 +2711,7 @@ Next steps:
|
|
|
2632
2711
|
${envStep}
|
|
2633
2712
|
${startDatabaseStep}Prepare the database:
|
|
2634
2713
|
${cli} db migrate
|
|
2714
|
+
${cli} db status
|
|
2635
2715
|
|
|
2636
2716
|
Start the app:
|
|
2637
2717
|
${run} dev
|
|
@@ -2646,6 +2726,7 @@ Generate a feature:
|
|
|
2646
2726
|
${cli} make feature projects
|
|
2647
2727
|
${cli} db generate
|
|
2648
2728
|
${cli} db migrate
|
|
2729
|
+
${cli} db status
|
|
2649
2730
|
${cli} check`;
|
|
2650
2731
|
}
|
|
2651
2732
|
|
|
@@ -2722,7 +2803,7 @@ function makeResourceNextSteps(result: MakeNextStepsResult): string {
|
|
|
2722
2803
|
return makeNextSteps(result, "resource", [
|
|
2723
2804
|
"Use make resource when the feature maps cleanly to a REST resource with repository-backed persistence.",
|
|
2724
2805
|
"Review the generated schemas and repository fields.",
|
|
2725
|
-
"If this app uses Drizzle, run beignet db generate and beignet db
|
|
2806
|
+
"If this app uses Drizzle, run beignet db generate, beignet db migrate, and beignet db status so the new schema is ready and verified.",
|
|
2726
2807
|
"Run your app's lint, test, and typecheck commands, then beignet lint and beignet doctor.",
|
|
2727
2808
|
]);
|
|
2728
2809
|
}
|
|
@@ -2739,6 +2820,25 @@ Command:
|
|
|
2739
2820
|
${command}`;
|
|
2740
2821
|
}
|
|
2741
2822
|
|
|
2823
|
+
function databaseStatusNextSteps(
|
|
2824
|
+
result: DatabaseStatusNextStepsResult,
|
|
2825
|
+
): string {
|
|
2826
|
+
const command = [result.runner, ...result.args].join(" ");
|
|
2827
|
+
const summary =
|
|
2828
|
+
result.status === "current"
|
|
2829
|
+
? "Database migrations are current."
|
|
2830
|
+
: result.status === "pending"
|
|
2831
|
+
? "Database migrations are pending."
|
|
2832
|
+
: "Database migration status could not be determined.";
|
|
2833
|
+
|
|
2834
|
+
return `${summary}
|
|
2835
|
+
|
|
2836
|
+
Ran ${result.script} in ${result.cwd}
|
|
2837
|
+
|
|
2838
|
+
Command:
|
|
2839
|
+
${command}`;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2742
2842
|
function databaseSchemaSyncNextSteps(
|
|
2743
2843
|
result: DatabaseSchemaSyncNextStepsResult,
|
|
2744
2844
|
): string {
|
|
@@ -2759,6 +2859,7 @@ ${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
|
2759
2859
|
Next steps:
|
|
2760
2860
|
Run beignet db generate to produce app-owned Drizzle migrations.
|
|
2761
2861
|
Run beignet db migrate to apply the migration.
|
|
2862
|
+
Run beignet db status to verify the target database.
|
|
2762
2863
|
Run beignet doctor --strict to check provider setup.`;
|
|
2763
2864
|
}
|
|
2764
2865
|
|
|
@@ -2766,7 +2867,7 @@ function makeFeatureNextSteps(result: MakeNextStepsResult): string {
|
|
|
2766
2867
|
return makeNextSteps(result, "feature slice", [
|
|
2767
2868
|
"Use make feature for product capabilities and workflows. Use make resource when the concept is mostly CRUD-shaped.",
|
|
2768
2869
|
"Treat the generated name field as a placeholder and shape the contracts, use cases, and repository around the feature's real workflow.",
|
|
2769
|
-
"If this app uses Drizzle, run beignet db generate and beignet db
|
|
2870
|
+
"If this app uses Drizzle, run beignet db generate, beignet db migrate, and beignet db status so the new schema is ready and verified.",
|
|
2770
2871
|
"Run your app's lint, test, and typecheck commands, then beignet lint and beignet doctor.",
|
|
2771
2872
|
]);
|
|
2772
2873
|
}
|
package/src/inspect.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
import {
|
|
44
44
|
detectedProviderVariants,
|
|
45
45
|
diagnosticPackageJsonFile,
|
|
46
|
+
formatProviderEnvAlternatives,
|
|
46
47
|
formatProviderMetadataIssues,
|
|
47
48
|
installedPackageNames,
|
|
48
49
|
providerDoctorRulesForInstalledPackages,
|
|
@@ -2619,6 +2620,27 @@ async function inspectProductionReadiness(
|
|
|
2619
2620
|
provider.variants,
|
|
2620
2621
|
providerEntries,
|
|
2621
2622
|
)) {
|
|
2623
|
+
const envAlternatives = configuredProviderRequiredEnvAlternatives(
|
|
2624
|
+
variant.requiredEnvAlternatives,
|
|
2625
|
+
config,
|
|
2626
|
+
);
|
|
2627
|
+
if (
|
|
2628
|
+
envAlternatives.length > 0 &&
|
|
2629
|
+
!(await providerRequiredEnvAlternativeExists(
|
|
2630
|
+
targetDir,
|
|
2631
|
+
configFiles,
|
|
2632
|
+
envAlternatives,
|
|
2633
|
+
sourceCache,
|
|
2634
|
+
))
|
|
2635
|
+
) {
|
|
2636
|
+
diagnostics.push({
|
|
2637
|
+
severity: "warning",
|
|
2638
|
+
code: "BEIGNET_PROVIDER_ENV_MISSING",
|
|
2639
|
+
file: "package.json",
|
|
2640
|
+
message: `${provider.packageName} is installed and ${variant.displayName} is registered, but none of its required environment configurations are fully mentioned in app env/config files. Configure ${formatProviderEnvAlternatives(envAlternatives)}, or remove the provider registration.`,
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2622
2644
|
for (const envVar of configuredProviderRequiredEnv(
|
|
2623
2645
|
variant.requiredEnv,
|
|
2624
2646
|
config,
|
|
@@ -2669,6 +2691,27 @@ async function inspectProductionReadiness(
|
|
|
2669
2691
|
continue;
|
|
2670
2692
|
}
|
|
2671
2693
|
|
|
2694
|
+
const envAlternatives = configuredProviderRequiredEnvAlternatives(
|
|
2695
|
+
provider.requiredEnvAlternatives ?? [],
|
|
2696
|
+
config,
|
|
2697
|
+
);
|
|
2698
|
+
if (
|
|
2699
|
+
envAlternatives.length > 0 &&
|
|
2700
|
+
!(await providerRequiredEnvAlternativeExists(
|
|
2701
|
+
targetDir,
|
|
2702
|
+
configFiles,
|
|
2703
|
+
envAlternatives,
|
|
2704
|
+
sourceCache,
|
|
2705
|
+
))
|
|
2706
|
+
) {
|
|
2707
|
+
diagnostics.push({
|
|
2708
|
+
severity: "warning",
|
|
2709
|
+
code: "BEIGNET_PROVIDER_ENV_MISSING",
|
|
2710
|
+
file: "package.json",
|
|
2711
|
+
message: `${provider.packageName} is installed, but none of its required environment configurations are fully mentioned in app env/config files. Configure ${formatProviderEnvAlternatives(envAlternatives)}, or remove the unused provider package.`,
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2672
2715
|
for (const envVar of configuredProviderRequiredEnv(
|
|
2673
2716
|
provider.requiredEnv ?? [],
|
|
2674
2717
|
config,
|
|
@@ -5175,7 +5218,7 @@ async function inspectDatabaseLifecycleDrift(
|
|
|
5175
5218
|
}
|
|
5176
5219
|
|
|
5177
5220
|
if (hasDrizzleArtifacts) {
|
|
5178
|
-
for (const script of ["db:generate", "db:migrate"] as const) {
|
|
5221
|
+
for (const script of ["db:generate", "db:migrate", "db:status"] as const) {
|
|
5179
5222
|
if (packageScripts[script]) continue;
|
|
5180
5223
|
diagnostics.push({
|
|
5181
5224
|
severity: "warning",
|
|
@@ -5257,6 +5300,12 @@ async function inspectDatabaseLifecycleDrift(
|
|
|
5257
5300
|
file: `${infrastructurePath}/db/reset.ts`,
|
|
5258
5301
|
code: "BEIGNET_DB_RESET_ENTRYPOINT_MISSING",
|
|
5259
5302
|
},
|
|
5303
|
+
{
|
|
5304
|
+
script: "db:status",
|
|
5305
|
+
command: "status",
|
|
5306
|
+
file: `${infrastructurePath}/db/migration-status.ts`,
|
|
5307
|
+
code: "BEIGNET_DB_STATUS_ENTRYPOINT_MISSING",
|
|
5308
|
+
},
|
|
5260
5309
|
] as const;
|
|
5261
5310
|
|
|
5262
5311
|
for (const entrypoint of standardEntrypoints) {
|
|
@@ -6284,6 +6333,32 @@ function configuredProviderRequiredEnv(
|
|
|
6284
6333
|
return envVars.filter((envVar) => !ignored.has(envVar));
|
|
6285
6334
|
}
|
|
6286
6335
|
|
|
6336
|
+
function configuredProviderRequiredEnvAlternatives(
|
|
6337
|
+
alternatives: readonly (readonly string[])[],
|
|
6338
|
+
config: ResolvedBeignetConfig,
|
|
6339
|
+
): string[][] {
|
|
6340
|
+
return alternatives.map((alternative) =>
|
|
6341
|
+
configuredProviderRequiredEnv(alternative, config),
|
|
6342
|
+
);
|
|
6343
|
+
}
|
|
6344
|
+
|
|
6345
|
+
async function providerRequiredEnvAlternativeExists(
|
|
6346
|
+
targetDir: string,
|
|
6347
|
+
configFiles: readonly string[],
|
|
6348
|
+
alternatives: readonly (readonly string[])[],
|
|
6349
|
+
sourceCache: Map<string, string>,
|
|
6350
|
+
): Promise<boolean> {
|
|
6351
|
+
for (const alternative of alternatives) {
|
|
6352
|
+
const exists = await Promise.all(
|
|
6353
|
+
alternative.map((envVar) =>
|
|
6354
|
+
providerRequiredEnvExists(targetDir, configFiles, envVar, sourceCache),
|
|
6355
|
+
),
|
|
6356
|
+
);
|
|
6357
|
+
if (exists.every(Boolean)) return true;
|
|
6358
|
+
}
|
|
6359
|
+
return false;
|
|
6360
|
+
}
|
|
6361
|
+
|
|
6287
6362
|
function configuredProviderRequiredTable(
|
|
6288
6363
|
tableName: string,
|
|
6289
6364
|
config: ResolvedBeignetConfig,
|
package/src/lib.ts
CHANGED
|
@@ -29,14 +29,22 @@ export type { CreateOptions } from "./create.js";
|
|
|
29
29
|
export { createProject } from "./create.js";
|
|
30
30
|
export type {
|
|
31
31
|
DatabaseCommand,
|
|
32
|
+
DatabaseMigrationStatus,
|
|
32
33
|
DatabaseSchemaDialect,
|
|
33
34
|
DatabaseSchemaTable,
|
|
34
35
|
RunDatabaseCommandOptions,
|
|
35
36
|
RunDatabaseCommandResult,
|
|
37
|
+
RunDatabaseStatusOptions,
|
|
38
|
+
RunDatabaseStatusResult,
|
|
36
39
|
SyncDatabaseSchemaOptions,
|
|
37
40
|
SyncDatabaseSchemaResult,
|
|
38
41
|
} from "./db.js";
|
|
39
|
-
export {
|
|
42
|
+
export {
|
|
43
|
+
databaseMigrationsPendingExitCode,
|
|
44
|
+
runDatabaseCommand,
|
|
45
|
+
runDatabaseStatus,
|
|
46
|
+
syncDatabaseSchema,
|
|
47
|
+
} from "./db.js";
|
|
40
48
|
export type {
|
|
41
49
|
DoctorFixApplyResult,
|
|
42
50
|
DoctorFixFileChange,
|
package/src/make/inbox.ts
CHANGED
|
@@ -226,7 +226,7 @@ export async function makeInbox(
|
|
|
226
226
|
*/
|
|
227
227
|
export function makeInboxNextStepLines(): string[] {
|
|
228
228
|
return [
|
|
229
|
-
"Run beignet db generate and beignet db
|
|
229
|
+
"Run beignet db generate, beignet db migrate, and beignet db status so inbox_notifications exists and is verified.",
|
|
230
230
|
'Add an in-app channel to any feature notification: import { defineInboxNotificationChannel } from "@/features/inbox/channel" and add inApp: defineInboxNotificationChannel(({ payload }) => ({ userId: payload.userId, type: "your-type", title: "Something happened" })) to its channels.',
|
|
231
231
|
"Render the unread badge from features/inbox/components/unread-badge.tsx next to your Inbox nav link for a live unread count.",
|
|
232
232
|
"Run your app's test, lint, and typecheck commands, then beignet lint and beignet doctor.",
|
package/src/make/payments.ts
CHANGED
|
@@ -209,7 +209,7 @@ export function makePaymentsNextStepLines(
|
|
|
209
209
|
return [
|
|
210
210
|
"The free plan works out of the box; billing accounts without an active subscription resolve to it automatically.",
|
|
211
211
|
"Set BILLING_PRO_PRICE_ID in .env.local to the provider price ID for your pro plan.",
|
|
212
|
-
"Run beignet db generate and beignet db
|
|
212
|
+
"Run beignet db generate, beignet db migrate, and beignet db status so billing_accounts exists and is verified before handling webhooks.",
|
|
213
213
|
'Gate paid actions once usage passes FREE_PLAN_LIMITS: await requireEntitlement(ctx, { entitlement: "todos.create", subject: { type: "tenant", id: tenantId } });',
|
|
214
214
|
...(hasPlanUi
|
|
215
215
|
? [
|
package/src/make/tenancy.ts
CHANGED
|
@@ -249,7 +249,7 @@ export async function makeTenancy(
|
|
|
249
249
|
*/
|
|
250
250
|
export function makeTenancyNextStepLines(): string[] {
|
|
251
251
|
return [
|
|
252
|
-
"Run beignet db generate and beignet db
|
|
252
|
+
"Run beignet db generate, beignet db migrate, and beignet db status so the workspaces, workspace_members, and workspace_invites tables exist and are verified.",
|
|
253
253
|
"Requests resolve the active workspace from memberships and the beignet-workspace cookie; users without a workspace have no tenant, so point them at Settings > Workspace to create one.",
|
|
254
254
|
"Run the db:seed script for a demo workspace with a sign-in-able demo admin (demo-admin@example.com).",
|
|
255
255
|
"Invite emails send through the in-memory mailer in development; swap server/providers.ts to a real mail provider such as @beignet/provider-mail-resend for delivery.",
|