@beignet/cli 0.0.47 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +126 -42
  3. package/dist/analysis/contract-factories.d.ts +17 -0
  4. package/dist/analysis/contract-factories.d.ts.map +1 -0
  5. package/dist/analysis/contract-factories.js +176 -0
  6. package/dist/analysis/contract-factories.js.map +1 -0
  7. package/dist/analysis/layers.d.ts +5 -0
  8. package/dist/analysis/layers.d.ts.map +1 -0
  9. package/dist/analysis/layers.js +172 -0
  10. package/dist/analysis/layers.js.map +1 -0
  11. package/dist/analysis/port-wiring.d.ts +23 -0
  12. package/dist/analysis/port-wiring.d.ts.map +1 -0
  13. package/dist/analysis/port-wiring.js +379 -0
  14. package/dist/analysis/port-wiring.js.map +1 -0
  15. package/dist/analysis/source-index.d.ts +5 -0
  16. package/dist/analysis/source-index.d.ts.map +1 -1
  17. package/dist/analysis/source-index.js +171 -42
  18. package/dist/analysis/source-index.js.map +1 -1
  19. package/dist/analysis/workspace.d.ts +10 -4
  20. package/dist/analysis/workspace.d.ts.map +1 -1
  21. package/dist/analysis/workspace.js +66 -6
  22. package/dist/analysis/workspace.js.map +1 -1
  23. package/dist/app-map.d.ts.map +1 -1
  24. package/dist/app-map.js +311 -92
  25. package/dist/app-map.js.map +1 -1
  26. package/dist/check.d.ts +8 -1
  27. package/dist/check.d.ts.map +1 -1
  28. package/dist/check.js +55 -9
  29. package/dist/check.js.map +1 -1
  30. package/dist/db.d.ts +38 -0
  31. package/dist/db.d.ts.map +1 -1
  32. package/dist/db.js +52 -0
  33. package/dist/db.js.map +1 -1
  34. package/dist/explain.js +4 -4
  35. package/dist/explain.js.map +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +73 -6
  38. package/dist/index.js.map +1 -1
  39. package/dist/inspect.d.ts.map +1 -1
  40. package/dist/inspect.js +454 -170
  41. package/dist/inspect.js.map +1 -1
  42. package/dist/lib.d.ts +2 -2
  43. package/dist/lib.d.ts.map +1 -1
  44. package/dist/lib.js +1 -1
  45. package/dist/lib.js.map +1 -1
  46. package/dist/lint.d.ts.map +1 -1
  47. package/dist/lint.js +309 -487
  48. package/dist/lint.js.map +1 -1
  49. package/dist/make/inbox.d.ts.map +1 -1
  50. package/dist/make/inbox.js +12 -5
  51. package/dist/make/inbox.js.map +1 -1
  52. package/dist/make/payments.js +3 -3
  53. package/dist/make/payments.js.map +1 -1
  54. package/dist/make/tenancy.js +1 -1
  55. package/dist/make/tenancy.js.map +1 -1
  56. package/dist/make.d.ts.map +1 -1
  57. package/dist/make.js +144 -8
  58. package/dist/make.js.map +1 -1
  59. package/dist/mcp.d.ts +1 -1
  60. package/dist/mcp.d.ts.map +1 -1
  61. package/dist/mcp.js +56 -19
  62. package/dist/mcp.js.map +1 -1
  63. package/dist/preflight.d.ts +11 -3
  64. package/dist/preflight.d.ts.map +1 -1
  65. package/dist/preflight.js +139 -16
  66. package/dist/preflight.js.map +1 -1
  67. package/dist/provider-add.d.ts.map +1 -1
  68. package/dist/provider-add.js +149 -9
  69. package/dist/provider-add.js.map +1 -1
  70. package/dist/provider-audit.d.ts +13 -0
  71. package/dist/provider-audit.d.ts.map +1 -1
  72. package/dist/provider-audit.js +122 -8
  73. package/dist/provider-audit.js.map +1 -1
  74. package/dist/registry-edits.d.ts +7 -0
  75. package/dist/registry-edits.d.ts.map +1 -1
  76. package/dist/registry-edits.js +237 -53
  77. package/dist/registry-edits.js.map +1 -1
  78. package/dist/templates/agents.d.ts.map +1 -1
  79. package/dist/templates/agents.js +29 -7
  80. package/dist/templates/agents.js.map +1 -1
  81. package/dist/templates/base.d.ts +1 -0
  82. package/dist/templates/base.d.ts.map +1 -1
  83. package/dist/templates/base.js +20 -6
  84. package/dist/templates/base.js.map +1 -1
  85. package/dist/templates/db/index.d.ts +1 -0
  86. package/dist/templates/db/index.d.ts.map +1 -1
  87. package/dist/templates/db/index.js.map +1 -1
  88. package/dist/templates/db/migration-status.d.ts +9 -0
  89. package/dist/templates/db/migration-status.d.ts.map +1 -0
  90. package/dist/templates/db/migration-status.js +117 -0
  91. package/dist/templates/db/migration-status.js.map +1 -0
  92. package/dist/templates/db/mysql.d.ts.map +1 -1
  93. package/dist/templates/db/mysql.js +23 -0
  94. package/dist/templates/db/mysql.js.map +1 -1
  95. package/dist/templates/db/postgres.d.ts.map +1 -1
  96. package/dist/templates/db/postgres.js +21 -0
  97. package/dist/templates/db/postgres.js.map +1 -1
  98. package/dist/templates/db/sqlite.d.ts.map +1 -1
  99. package/dist/templates/db/sqlite.js +53 -0
  100. package/dist/templates/db/sqlite.js.map +1 -1
  101. package/dist/templates/index.d.ts.map +1 -1
  102. package/dist/templates/index.js +5 -0
  103. package/dist/templates/index.js.map +1 -1
  104. package/dist/templates/server.d.ts.map +1 -1
  105. package/dist/templates/server.js +2 -0
  106. package/dist/templates/server.js.map +1 -1
  107. package/dist/templates/shared.d.ts +1 -0
  108. package/dist/templates/shared.d.ts.map +1 -1
  109. package/dist/templates/shared.js +4 -2
  110. package/dist/templates/shared.js.map +1 -1
  111. package/package.json +7 -6
  112. package/skills/app-structure/SKILL.md +56 -11
  113. package/src/analysis/contract-factories.ts +256 -0
  114. package/src/analysis/layers.ts +266 -0
  115. package/src/analysis/port-wiring.ts +506 -0
  116. package/src/analysis/source-index.ts +225 -57
  117. package/src/analysis/workspace.ts +115 -6
  118. package/src/app-map.ts +418 -109
  119. package/src/check.ts +86 -10
  120. package/src/db.ts +97 -0
  121. package/src/explain.ts +4 -4
  122. package/src/index.ts +108 -7
  123. package/src/inspect.ts +659 -216
  124. package/src/lib.ts +9 -1
  125. package/src/lint.ts +393 -643
  126. package/src/make/inbox.ts +12 -5
  127. package/src/make/payments.ts +3 -3
  128. package/src/make/tenancy.ts +1 -1
  129. package/src/make.ts +225 -8
  130. package/src/mcp.ts +73 -21
  131. package/src/preflight.ts +195 -16
  132. package/src/provider-add.ts +193 -9
  133. package/src/provider-audit.ts +210 -18
  134. package/src/registry-edits.ts +313 -63
  135. package/src/templates/agents.ts +29 -7
  136. package/src/templates/base.ts +21 -4
  137. package/src/templates/db/index.ts +1 -0
  138. package/src/templates/db/migration-status.ts +126 -0
  139. package/src/templates/db/mysql.ts +23 -0
  140. package/src/templates/db/postgres.ts +21 -0
  141. package/src/templates/db/sqlite.ts +53 -0
  142. package/src/templates/index.ts +5 -0
  143. package/src/templates/server.ts +2 -0
  144. package/src/templates/shared.ts +4 -2
package/src/check.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { createPainter } from "./ansi.js";
4
4
  import { detectPackageManager, spawnCommand } from "./db.js";
@@ -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. */
@@ -68,6 +75,7 @@ export async function checkApp(
68
75
  options: CheckAppOptions = {},
69
76
  ): Promise<CheckAppResult> {
70
77
  const targetDir = path.resolve(options.cwd ?? process.cwd());
78
+ const scripts = await readAppPackageScripts(targetDir);
71
79
  const runner = await detectPackageManager(targetDir);
72
80
  const steps: CheckStep[] = [];
73
81
  const record = (step: CheckStep): void => {
@@ -81,12 +89,17 @@ export async function checkApp(
81
89
  const { step: doctorStep, fixes } = await runDoctorStep(targetDir, options);
82
90
  record(doctorStep);
83
91
 
84
- if (options.preflight) {
92
+ if (options.preflight || options.preflightConnect) {
85
93
  options.signal?.throwIfAborted();
86
- record(await preflightStep(targetDir));
94
+ record(
95
+ await preflightStep(targetDir, {
96
+ connect: Boolean(options.preflightConnect),
97
+ connectTimeoutMs: options.connectTimeoutMs,
98
+ signal: options.signal,
99
+ }),
100
+ );
87
101
  }
88
102
 
89
- const scripts = await readPackageScripts(targetDir);
90
103
  for (const script of scriptStepNames) {
91
104
  options.signal?.throwIfAborted();
92
105
  record(await scriptStep(targetDir, runner, script, scripts, options));
@@ -256,7 +269,7 @@ async function scriptStep(
256
269
  };
257
270
  }
258
271
 
259
- async function readPackageScripts(
272
+ async function readAppPackageScripts(
260
273
  targetDir: string,
261
274
  ): Promise<Record<string, string>> {
262
275
  let source: string;
@@ -269,22 +282,85 @@ async function readPackageScripts(
269
282
  }
270
283
 
271
284
  const parsed = JSON.parse(source) as {
285
+ dependencies?: Record<string, string>;
286
+ optionalDependencies?: Record<string, string>;
272
287
  scripts?: Record<string, string>;
273
288
  };
289
+ const dependencies = {
290
+ ...parsed.dependencies,
291
+ ...parsed.optionalDependencies,
292
+ };
293
+ if (
294
+ !["@beignet/core", "@beignet/next", "@beignet/web"].some(
295
+ (packageName) => dependencies[packageName] !== undefined,
296
+ )
297
+ ) {
298
+ throw new Error(
299
+ "package.json does not declare a Beignet runtime dependency. Run beignet check from a Beignet app root that depends on @beignet/core, @beignet/next, or @beignet/web.",
300
+ );
301
+ }
302
+ if (!(await hasBeignetAppStructure(targetDir))) {
303
+ throw new Error(
304
+ "package.json declares a Beignet runtime dependency, but no Beignet app structure was found. Run beignet check from an app root containing app-context.ts, beignet.config.*, or features/ with server/index.ts.",
305
+ );
306
+ }
274
307
  return parsed.scripts ?? {};
275
308
  }
276
309
 
277
- async function preflightStep(targetDir: string): Promise<CheckStep> {
310
+ async function hasBeignetAppStructure(targetDir: string): Promise<boolean> {
311
+ const entries = await readdir(targetDir, { withFileTypes: true });
312
+ const entryByName = new Map(entries.map((entry) => [entry.name, entry]));
313
+ if (entryByName.get("app-context.ts")?.isFile()) return true;
314
+ if (
315
+ [
316
+ "beignet.config.ts",
317
+ "beignet.config.json",
318
+ "beignet.config.mjs",
319
+ "beignet.config.js",
320
+ ].some((file) => entryByName.get(file)?.isFile())
321
+ ) {
322
+ return true;
323
+ }
324
+ if (
325
+ !entryByName.get("features")?.isDirectory() ||
326
+ !entryByName.get("server")?.isDirectory()
327
+ ) {
328
+ return false;
329
+ }
330
+
331
+ try {
332
+ return (await stat(path.join(targetDir, "server/index.ts"))).isFile();
333
+ } catch {
334
+ return false;
335
+ }
336
+ }
337
+
338
+ async function preflightStep(
339
+ targetDir: string,
340
+ options: {
341
+ connect: boolean;
342
+ connectTimeoutMs?: number;
343
+ signal?: AbortSignal;
344
+ },
345
+ ): Promise<CheckStep> {
278
346
  const startedAt = Date.now();
279
347
  const { formatPreflight, runPreflight } = await import("./preflight.js");
280
- const result = await runPreflight({ cwd: targetDir });
348
+ const result = await runPreflight({
349
+ cwd: targetDir,
350
+ connect: options.connect,
351
+ connectTimeoutMs: options.connectTimeoutMs,
352
+ signal: options.signal,
353
+ });
281
354
  const failed = result.findings.some(
282
355
  (finding) => finding.severity === "error",
283
356
  );
357
+ const command = options.connect
358
+ ? "beignet preflight --connect"
359
+ : "beignet preflight";
284
360
 
285
361
  return {
286
- name: "beignet preflight",
287
- command: "beignet preflight",
362
+ name: command,
363
+ command,
288
364
  status: failed ? "failed" : "passed",
289
365
  ...(failed ? { output: formatPreflight(result, targetDir) } : {}),
290
366
  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/explain.ts CHANGED
@@ -795,7 +795,7 @@ const conventionsByTargetKind = {
795
795
  ],
796
796
  job: [
797
797
  "Jobs represent background work and remain explicit at provider-backed execution boundaries.",
798
- "Feature jobs register through central runtime or outbox registries before workers can dispatch them.",
798
+ "Feature jobs register through central runtime or outbox registries before workers can dispatch them; registration does not install the required jobs port.",
799
799
  ],
800
800
  schedule: [
801
801
  "Schedules represent time-based triggers and delegate work to application workflows.",
@@ -819,11 +819,11 @@ const conventionsByTargetKind = {
819
819
  ],
820
820
  registry: [
821
821
  "Registries explicitly compose feature declarations into the runtime surface inspected by doctor and app-map.",
822
- "Central registries remain the source of truth for runtime wiring and integrity checks.",
822
+ "Central registries remain the source of truth for runtime wiring and integrity checks; outbox registries containing jobs depend on a bound or deferred jobs dispatcher.",
823
823
  ],
824
824
  port: [
825
825
  "Ports define app-facing dependency interfaces; infrastructure adapters provide their concrete implementations.",
826
- "App ports wire through infra/port-wiring.ts and remain available to use cases through application context.",
826
+ "App ports wire through the configured port-wiring file and remain available to use cases through application context; deferred ports are verified during server startup.",
827
827
  ],
828
828
  provider: [
829
829
  "Ports define app-facing interfaces, providers adapt external systems, and app infrastructure wires them together.",
@@ -839,7 +839,7 @@ const conventionsByTargetKind = {
839
839
  ],
840
840
  entrypoint: [
841
841
  "Runtime entrypoints compose the server or expose thin framework adapters over registered routes.",
842
- "Entrypoints reuse central context, routes, providers, and workflow registries instead of duplicating application behavior.",
842
+ "Entrypoints reuse central context, routes, providers, and workflow registries instead of duplicating application behavior; outbox drains require delivery ports before they claim messages.",
843
843
  ],
844
844
  test: [
845
845
  "Feature behavior tests stay under features/<feature>/tests and exercise use cases, routes, policies, workflows, and UI.",
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: "Append a beignet preflight step after doctor.",
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
- "Boot the app server and run every port's checkHealth(). Needs network and real credentials.",
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
- } satisfies Record<DatabaseCommand | "schema", unknown>,
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 migrate so the new schema is ready.",
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 migrate so the new schema is ready.",
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
  }