@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.
Files changed (92) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +50 -21
  3. package/dist/check.d.ts +8 -1
  4. package/dist/check.d.ts.map +1 -1
  5. package/dist/check.js +18 -6
  6. package/dist/check.js.map +1 -1
  7. package/dist/db.d.ts +38 -0
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +52 -0
  10. package/dist/db.js.map +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +73 -6
  13. package/dist/index.js.map +1 -1
  14. package/dist/inspect.d.ts.map +1 -1
  15. package/dist/inspect.js +39 -2
  16. package/dist/inspect.js.map +1 -1
  17. package/dist/lib.d.ts +2 -2
  18. package/dist/lib.d.ts.map +1 -1
  19. package/dist/lib.js +1 -1
  20. package/dist/lib.js.map +1 -1
  21. package/dist/make/inbox.js +1 -1
  22. package/dist/make/inbox.js.map +1 -1
  23. package/dist/make/payments.js +1 -1
  24. package/dist/make/payments.js.map +1 -1
  25. package/dist/make/tenancy.js +1 -1
  26. package/dist/make/tenancy.js.map +1 -1
  27. package/dist/mcp.d.ts.map +1 -1
  28. package/dist/mcp.js +39 -2
  29. package/dist/mcp.js.map +1 -1
  30. package/dist/preflight.d.ts +11 -3
  31. package/dist/preflight.d.ts.map +1 -1
  32. package/dist/preflight.js +139 -16
  33. package/dist/preflight.js.map +1 -1
  34. package/dist/provider-add.d.ts.map +1 -1
  35. package/dist/provider-add.js +7 -2
  36. package/dist/provider-add.js.map +1 -1
  37. package/dist/provider-audit.d.ts +13 -0
  38. package/dist/provider-audit.d.ts.map +1 -1
  39. package/dist/provider-audit.js +122 -8
  40. package/dist/provider-audit.js.map +1 -1
  41. package/dist/templates/agents.d.ts.map +1 -1
  42. package/dist/templates/agents.js +13 -7
  43. package/dist/templates/agents.js.map +1 -1
  44. package/dist/templates/base.d.ts +1 -0
  45. package/dist/templates/base.d.ts.map +1 -1
  46. package/dist/templates/base.js +11 -5
  47. package/dist/templates/base.js.map +1 -1
  48. package/dist/templates/db/index.d.ts +1 -0
  49. package/dist/templates/db/index.d.ts.map +1 -1
  50. package/dist/templates/db/index.js.map +1 -1
  51. package/dist/templates/db/migration-status.d.ts +9 -0
  52. package/dist/templates/db/migration-status.d.ts.map +1 -0
  53. package/dist/templates/db/migration-status.js +117 -0
  54. package/dist/templates/db/migration-status.js.map +1 -0
  55. package/dist/templates/db/mysql.d.ts.map +1 -1
  56. package/dist/templates/db/mysql.js +23 -0
  57. package/dist/templates/db/mysql.js.map +1 -1
  58. package/dist/templates/db/postgres.d.ts.map +1 -1
  59. package/dist/templates/db/postgres.js +21 -0
  60. package/dist/templates/db/postgres.js.map +1 -1
  61. package/dist/templates/db/sqlite.d.ts.map +1 -1
  62. package/dist/templates/db/sqlite.js +53 -0
  63. package/dist/templates/db/sqlite.js.map +1 -1
  64. package/dist/templates/index.d.ts.map +1 -1
  65. package/dist/templates/index.js +5 -0
  66. package/dist/templates/index.js.map +1 -1
  67. package/dist/templates/shared.d.ts.map +1 -1
  68. package/dist/templates/shared.js +1 -0
  69. package/dist/templates/shared.js.map +1 -1
  70. package/package.json +2 -2
  71. package/skills/app-structure/SKILL.md +11 -7
  72. package/src/check.ts +35 -7
  73. package/src/db.ts +97 -0
  74. package/src/index.ts +108 -7
  75. package/src/inspect.ts +76 -1
  76. package/src/lib.ts +9 -1
  77. package/src/make/inbox.ts +1 -1
  78. package/src/make/payments.ts +1 -1
  79. package/src/make/tenancy.ts +1 -1
  80. package/src/mcp.ts +56 -1
  81. package/src/preflight.ts +195 -16
  82. package/src/provider-add.ts +7 -2
  83. package/src/provider-audit.ts +210 -18
  84. package/src/templates/agents.ts +13 -7
  85. package/src/templates/base.ts +12 -3
  86. package/src/templates/db/index.ts +1 -0
  87. package/src/templates/db/migration-status.ts +126 -0
  88. package/src/templates/db/mysql.ts +23 -0
  89. package/src/templates/db/postgres.ts +21 -0
  90. package/src/templates/db/sqlite.ts +53 -0
  91. package/src/templates/index.ts +5 -0
  92. package/src/templates/shared.ts +1 -0
package/src/mcp.ts CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  defaultDatabaseCommandMaxOutputBytes,
24
24
  defaultDatabaseCommandTimeoutMs,
25
25
  runDatabaseCommand,
26
+ runDatabaseStatus,
26
27
  syncDatabaseSchema,
27
28
  } from "./db.js";
28
29
  import { explainApp, explainTargetKinds } from "./explain.js";
@@ -639,7 +640,7 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
639
640
  "check",
640
641
  {
641
642
  description:
642
- "Run the full Beignet app validation loop: Beignet lint, strict doctor, and the app's lint, typecheck, and test scripts. Returns the same versioned JSON as beignet check --json, including every step, bounded failure output, durations, and overall ok status. Does not apply Beignet fixes, but app-owned scripts run with their normal side effects.",
643
+ "Run the full Beignet app validation loop: Beignet lint, strict doctor, optional disconnected or connected preflight, and the app's lint, typecheck, and test scripts. Returns the same versioned JSON as beignet check --json, including every step, bounded failure output, durations, and overall ok status. Does not apply Beignet fixes, but app-owned scripts run with their normal side effects.",
643
644
  inputSchema: {
644
645
  preflight: z
645
646
  .boolean()
@@ -647,6 +648,21 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
647
648
  .describe(
648
649
  "Include the environment preflight check before app package scripts. Defaults to false.",
649
650
  ),
651
+ preflightConnect: z
652
+ .boolean()
653
+ .optional()
654
+ .describe(
655
+ "Include connected preflight with migration status and dependency health checks. Implies preflight and defaults to false.",
656
+ ),
657
+ connectTimeoutMs: z
658
+ .number()
659
+ .int()
660
+ .min(1)
661
+ .max(60 * 60 * 1_000)
662
+ .optional()
663
+ .describe(
664
+ "Maximum duration for migration status and each dependency health check in milliseconds. Defaults to 5000.",
665
+ ),
650
666
  timeoutMs: z
651
667
  .number()
652
668
  .int()
@@ -670,6 +686,8 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
670
686
  await checkApp({
671
687
  cwd,
672
688
  preflight: input.preflight ?? false,
689
+ preflightConnect: input.preflightConnect ?? false,
690
+ connectTimeoutMs: input.connectTimeoutMs,
673
691
  scriptTimeoutMs: input.timeoutMs ?? defaultMcpCheckTimeoutMs,
674
692
  signal: extra.signal,
675
693
  }),
@@ -725,6 +743,43 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
725
743
  ),
726
744
  );
727
745
 
746
+ server.registerTool(
747
+ "db_status",
748
+ {
749
+ description:
750
+ "Inspect app-owned database migrations and return exactly the versioned beignet db status --json result. Exit state distinguishes current, pending, and failed inspection without mutating the database. Output is bounded, the complete child process tree is cancelled with the MCP request, and execution stops after the configured timeout.",
751
+ inputSchema: {
752
+ timeoutMs: z
753
+ .number()
754
+ .int()
755
+ .min(1_000)
756
+ .max(60 * 60 * 1_000)
757
+ .optional()
758
+ .describe(
759
+ "Maximum status duration in milliseconds. Defaults to 600000 (10 minutes).",
760
+ ),
761
+ },
762
+ annotations: {
763
+ readOnlyHint: true,
764
+ destructiveHint: false,
765
+ idempotentHint: true,
766
+ openWorldHint: true,
767
+ },
768
+ },
769
+ async (input, extra) =>
770
+ safeToolResult(async () =>
771
+ jsonResult(
772
+ await runDatabaseStatus({
773
+ cwd,
774
+ captureOutput: true,
775
+ maxOutputBytes: defaultDatabaseCommandMaxOutputBytes,
776
+ signal: extra.signal,
777
+ timeoutMs: input.timeoutMs ?? defaultDatabaseCommandTimeoutMs,
778
+ }),
779
+ ),
780
+ ),
781
+ );
782
+
728
783
  server.registerTool(
729
784
  "db_schema_sync",
730
785
  {
package/src/preflight.ts CHANGED
@@ -2,12 +2,15 @@ import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { parseProviderPackageMetadata } from "@beignet/core/providers";
4
4
  import { createJiti } from "jiti";
5
+ import { runDatabaseStatus } from "./db.js";
5
6
  import {
6
7
  type InspectDiagnostic,
7
8
  inspectApp,
8
9
  productionHardeningDiagnosticCodes,
9
10
  } from "./inspect.js";
10
11
  import {
12
+ detectedProviderVariantsForRules,
13
+ formatProviderEnvAlternatives,
11
14
  installedPackageNames,
12
15
  type ProviderPackageJson,
13
16
  providerDoctorRulesForInstalledPackages,
@@ -46,8 +49,9 @@ export interface PreflightOptions {
46
49
  */
47
50
  doctor?: boolean;
48
51
  /**
49
- * Boot the app server and run every port's `checkHealth()`. Needs network
50
- * access and real credentials, so it is opt-in.
52
+ * Inspect database migration status, then boot the app server and run every
53
+ * port's `checkHealth()`. Needs network access and real credentials, so it
54
+ * is opt-in.
51
55
  */
52
56
  connect?: boolean;
53
57
  /**
@@ -56,9 +60,12 @@ export interface PreflightOptions {
56
60
  */
57
61
  serverModule?: string;
58
62
  /**
59
- * Per-dependency health check timeout in milliseconds. Defaults to 5000.
63
+ * Per migration-status or dependency health check timeout in milliseconds.
64
+ * Defaults to 5000.
60
65
  */
61
66
  connectTimeoutMs?: number;
67
+ /** Cancel connected migration inspection and health checks on abort. */
68
+ signal?: AbortSignal;
62
69
  }
63
70
 
64
71
  export interface PreflightResult {
@@ -79,6 +86,15 @@ export interface PreflightResult {
79
86
  * Ports whose `checkHealth()` ran during a `connect` pass.
80
87
  */
81
88
  checkedDependencies: string[];
89
+ /**
90
+ * Database migration state observed before dependency checks.
91
+ */
92
+ migrationStatus:
93
+ | "not-checked"
94
+ | "current"
95
+ | "pending"
96
+ | "failed"
97
+ | "unavailable";
82
98
  }
83
99
 
84
100
  /**
@@ -175,6 +191,46 @@ function applyEnvToProcess(
175
191
  };
176
192
  }
177
193
 
194
+ function appendProviderEnvFindings(options: {
195
+ owner: string;
196
+ required: readonly string[];
197
+ alternatives: readonly (readonly string[])[];
198
+ env: Readonly<Record<string, string | undefined>>;
199
+ requiredEnvKeys: Set<string>;
200
+ findings: PreflightFinding[];
201
+ }): void {
202
+ for (const key of options.required) {
203
+ options.requiredEnvKeys.add(key);
204
+ const value = options.env[key];
205
+ if (value === undefined || value.trim() === "") {
206
+ options.findings.push({
207
+ severity: "error",
208
+ code: "BEIGNET_PREFLIGHT_ENV_MISSING",
209
+ message: `${key} is required by ${options.owner} but is not set.`,
210
+ });
211
+ }
212
+ }
213
+
214
+ for (const key of options.alternatives.flat()) {
215
+ options.requiredEnvKeys.add(key);
216
+ }
217
+ if (
218
+ options.alternatives.length > 0 &&
219
+ !options.alternatives.some((alternative) =>
220
+ alternative.every((key) => {
221
+ const value = options.env[key];
222
+ return value !== undefined && value.trim() !== "";
223
+ }),
224
+ )
225
+ ) {
226
+ options.findings.push({
227
+ severity: "error",
228
+ code: "BEIGNET_PREFLIGHT_ENV_MISSING",
229
+ message: `${options.owner} requires one complete environment configuration: ${formatProviderEnvAlternatives(options.alternatives)}.`,
230
+ });
231
+ }
232
+ }
233
+
178
234
  /**
179
235
  * Validate the deploy environment against installed provider manifests, the
180
236
  * app's `.env.example` placeholders, and the app env schema module.
@@ -221,6 +277,7 @@ export async function runPreflight(
221
277
  envModuleChecked: false,
222
278
  doctorChecked: false,
223
279
  checkedDependencies: [],
280
+ migrationStatus: "not-checked",
224
281
  };
225
282
  }
226
283
 
@@ -230,19 +287,34 @@ export async function runPreflight(
230
287
  );
231
288
  const checkedProviders = rules.map((rule) => rule.packageName);
232
289
  const requiredEnvKeys = new Set<string>();
290
+ const detectedVariants = await detectedProviderVariantsForRules(
291
+ options.cwd,
292
+ rules,
293
+ );
233
294
 
234
295
  for (const rule of rules) {
235
- for (const key of rule.requiredEnv ?? []) {
236
- requiredEnvKeys.add(key);
237
- const value = env[key];
238
- if (value === undefined || value.trim() === "") {
239
- findings.push({
240
- severity: "error",
241
- code: "BEIGNET_PREFLIGHT_ENV_MISSING",
242
- message: `${key} is required by ${rule.packageName} but is not set.`,
296
+ if (rule.variants !== undefined) {
297
+ for (const variant of detectedVariants.get(rule.packageName) ?? []) {
298
+ appendProviderEnvFindings({
299
+ owner: `${rule.packageName} (${variant.displayName})`,
300
+ required: variant.requiredEnv,
301
+ alternatives: variant.requiredEnvAlternatives,
302
+ env,
303
+ requiredEnvKeys,
304
+ findings,
243
305
  });
244
306
  }
307
+ continue;
245
308
  }
309
+
310
+ appendProviderEnvFindings({
311
+ owner: rule.packageName,
312
+ required: rule.requiredEnv ?? [],
313
+ alternatives: rule.requiredEnvAlternatives ?? [],
314
+ env,
315
+ requiredEnvKeys,
316
+ findings,
317
+ });
246
318
  }
247
319
 
248
320
  const inngestDev = env.INNGEST_DEV?.trim().toLowerCase();
@@ -303,6 +375,7 @@ export async function runPreflight(
303
375
  let envModuleChecked = false;
304
376
  let doctorChecked = false;
305
377
  const checkedDependencies: string[] = [];
378
+ let migrationStatus: PreflightResult["migrationStatus"] = "not-checked";
306
379
  const restoreProcessEnv = applyEnvToProcess(env);
307
380
 
308
381
  try {
@@ -344,7 +417,67 @@ export async function runPreflight(
344
417
  }
345
418
 
346
419
  if (options.connect) {
347
- await runDependencyChecks(options, findings, checkedDependencies);
420
+ if (!packageJson.scripts?.["db:status"]) {
421
+ migrationStatus = "unavailable";
422
+ findings.push({
423
+ severity: "note",
424
+ code: "BEIGNET_PREFLIGHT_DATABASE_STATUS_UNAVAILABLE",
425
+ message:
426
+ 'No app-owned "db:status" script is configured; skipping pending migration detection.',
427
+ });
428
+ } else {
429
+ try {
430
+ const connectTimeoutMs = options.connectTimeoutMs ?? 5000;
431
+ const result = await runDatabaseStatus({
432
+ cwd: options.cwd,
433
+ captureOutput: true,
434
+ maxOutputBytes: 8 * 1024,
435
+ signal: options.signal,
436
+ timeoutMs: connectTimeoutMs,
437
+ });
438
+ migrationStatus = result.status;
439
+
440
+ if (result.status === "pending") {
441
+ findings.push({
442
+ severity: "error",
443
+ code: "BEIGNET_PREFLIGHT_DATABASE_MIGRATIONS_PENDING",
444
+ message: databaseStatusFindingMessage(
445
+ "Database migrations are pending. Run `beignet db migrate` in the serialized migration job before booting the app.",
446
+ result.stdout,
447
+ result.stderr,
448
+ result.outputTruncated,
449
+ ),
450
+ });
451
+ } else if (result.status === "failed") {
452
+ findings.push({
453
+ severity: "error",
454
+ code: "BEIGNET_PREFLIGHT_DATABASE_STATUS_FAILED",
455
+ message: databaseStatusFindingMessage(
456
+ result.timedOut
457
+ ? `Database migration status inspection timed out after ${connectTimeoutMs}ms.`
458
+ : "Database migration status inspection failed.",
459
+ result.stdout,
460
+ result.stderr,
461
+ result.outputTruncated,
462
+ ),
463
+ });
464
+ }
465
+ } catch (error) {
466
+ options.signal?.throwIfAborted();
467
+ migrationStatus = "failed";
468
+ findings.push({
469
+ severity: "error",
470
+ code: "BEIGNET_PREFLIGHT_DATABASE_STATUS_FAILED",
471
+ message: `Database migration status inspection failed: ${
472
+ error instanceof Error ? error.message : String(error)
473
+ }`,
474
+ });
475
+ }
476
+ }
477
+
478
+ if (migrationStatus !== "pending" && migrationStatus !== "failed") {
479
+ await runDependencyChecks(options, findings, checkedDependencies);
480
+ }
348
481
  }
349
482
  } finally {
350
483
  restoreProcessEnv();
@@ -356,9 +489,26 @@ export async function runPreflight(
356
489
  envModuleChecked,
357
490
  doctorChecked,
358
491
  checkedDependencies,
492
+ migrationStatus,
359
493
  };
360
494
  }
361
495
 
496
+ function databaseStatusFindingMessage(
497
+ summary: string,
498
+ stdout?: string,
499
+ stderr?: string,
500
+ outputTruncated?: boolean,
501
+ ): string {
502
+ const details = [
503
+ stdout?.trim() ? `stdout:\n${stdout.trim()}` : undefined,
504
+ stderr?.trim() ? `stderr:\n${stderr.trim()}` : undefined,
505
+ ].filter((value): value is string => value !== undefined);
506
+ if (outputTruncated) {
507
+ details.push("Output truncated; showing the retained tail.");
508
+ }
509
+ return [summary, ...details].join("\n");
510
+ }
511
+
362
512
  interface HealthCheckable {
363
513
  checkHealth(): Promise<unknown>;
364
514
  }
@@ -381,10 +531,13 @@ async function withTimeout<T>(
381
531
  work: Promise<T>,
382
532
  ms: number,
383
533
  label: string,
534
+ signal?: AbortSignal,
384
535
  ): Promise<T> {
536
+ signal?.throwIfAborted();
385
537
  let timer: ReturnType<typeof setTimeout> | undefined;
538
+ let onAbort: (() => void) | undefined;
386
539
  try {
387
- return await Promise.race([
540
+ const contenders: Promise<T>[] = [
388
541
  work,
389
542
  new Promise<never>((_, reject) => {
390
543
  timer = setTimeout(
@@ -392,9 +545,25 @@ async function withTimeout<T>(
392
545
  ms,
393
546
  );
394
547
  }),
395
- ]);
548
+ ];
549
+ if (signal) {
550
+ contenders.push(
551
+ new Promise<never>((_, reject) => {
552
+ onAbort = () =>
553
+ reject(
554
+ signal.reason instanceof Error
555
+ ? signal.reason
556
+ : new Error("Connected preflight cancelled."),
557
+ );
558
+ signal.addEventListener("abort", onAbort, { once: true });
559
+ if (signal.aborted) onAbort();
560
+ }),
561
+ );
562
+ }
563
+ return await Promise.race(contenders);
396
564
  } finally {
397
565
  if (timer !== undefined) clearTimeout(timer);
566
+ if (onAbort) signal?.removeEventListener("abort", onAbort);
398
567
  }
399
568
  }
400
569
 
@@ -407,6 +576,7 @@ async function runDependencyChecks(
407
576
  findings: PreflightFinding[],
408
577
  checkedDependencies: string[],
409
578
  ): Promise<void> {
579
+ options.signal?.throwIfAborted();
410
580
  const serverModule = options.serverModule ?? "server/index.ts";
411
581
  const serverModulePath = path.resolve(options.cwd, serverModule);
412
582
  const timeoutMs = options.connectTimeoutMs ?? 5000;
@@ -424,6 +594,7 @@ async function runDependencyChecks(
424
594
  | { ports: Record<string, unknown>; stop?: () => Promise<void> }
425
595
  | undefined;
426
596
  try {
597
+ options.signal?.throwIfAborted();
427
598
  const jiti = createJiti(serverModulePath, {
428
599
  interopDefault: true,
429
600
  moduleCache: false,
@@ -452,6 +623,7 @@ async function runDependencyChecks(
452
623
  stop?: () => Promise<void>;
453
624
  };
454
625
  } catch (error) {
626
+ options.signal?.throwIfAborted();
455
627
  findings.push({
456
628
  severity: "error",
457
629
  code: "BEIGNET_PREFLIGHT_SERVER_BOOT_FAILED",
@@ -464,6 +636,7 @@ async function runDependencyChecks(
464
636
 
465
637
  try {
466
638
  for (const [name, port] of Object.entries(server.ports)) {
639
+ options.signal?.throwIfAborted();
467
640
  if (!isHealthCheckable(port)) continue;
468
641
  checkedDependencies.push(name);
469
642
  try {
@@ -471,6 +644,7 @@ async function runDependencyChecks(
471
644
  port.checkHealth(),
472
645
  timeoutMs,
473
646
  `${name}.checkHealth()`,
647
+ options.signal,
474
648
  );
475
649
  if (!healthResultOk(result)) {
476
650
  const message = (result as { message?: unknown }).message;
@@ -483,6 +657,7 @@ async function runDependencyChecks(
483
657
  });
484
658
  }
485
659
  } catch (error) {
660
+ options.signal?.throwIfAborted();
486
661
  findings.push({
487
662
  severity: "error",
488
663
  code: "BEIGNET_PREFLIGHT_DEPENDENCY_UNHEALTHY",
@@ -534,14 +709,18 @@ export function formatPreflight(result: PreflightResult, cwd: string): string {
534
709
  const dependencySummary = result.checkedDependencies.length
535
710
  ? `, ${result.checkedDependencies.length} dependency health check(s) run`
536
711
  : "";
712
+ const migrationSummary =
713
+ result.migrationStatus === "not-checked"
714
+ ? "migration status not checked"
715
+ : `migration status ${result.migrationStatus}`;
537
716
 
538
717
  if (errors.length === 0) {
539
718
  lines.push(
540
- `Preflight passed in ${cwd}: ${providerSummary} checked, ${envSummary}, ${doctorSummary}${dependencySummary}.`,
719
+ `Preflight passed in ${cwd}: ${providerSummary} checked, ${envSummary}, ${doctorSummary}, ${migrationSummary}${dependencySummary}.`,
541
720
  );
542
721
  } else {
543
722
  lines.push(
544
- `Preflight failed in ${cwd}: ${errors.length} error(s) across ${providerSummary}, ${envSummary}, ${doctorSummary}${dependencySummary}.`,
723
+ `Preflight failed in ${cwd}: ${errors.length} error(s) across ${providerSummary}, ${envSummary}, ${doctorSummary}, ${migrationSummary}${dependencySummary}.`,
545
724
  );
546
725
  }
547
726
 
@@ -639,6 +639,8 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
639
639
  "# Vercel Blob storage provider. Vercel sets BLOB_READ_WRITE_TOKEN",
640
640
  "# automatically on deployments with a connected Blob store.",
641
641
  "# BLOB_READ_WRITE_TOKEN=vercel_blob_rw_...",
642
+ "# OIDC alternative on Vercel: VERCEL_OIDC_TOKEN plus BLOB_STORE_ID.",
643
+ "# BLOB_STORE_ID=store_...",
642
644
  "# BLOB_ACCESS=private",
643
645
  "# BLOB_KEY_PREFIX=",
644
646
  "",
@@ -649,14 +651,14 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
649
651
  "- Package: `@beignet/provider-storage-vercel-blob`",
650
652
  "- Peer dependency: `@vercel/blob`",
651
653
  "- The preset wires `createVercelBlobStorageProvider()` in `server/providers.ts` and defers `storage` in `infra/port-wiring.ts`.",
652
- "- On Vercel, connect a Blob store to the project and `BLOB_READ_WRITE_TOKEN` is set automatically; set it manually elsewhere.",
654
+ "- On Vercel, connect a Blob store to the project. Use its `BLOB_READ_WRITE_TOKEN`, or Vercel OIDC with `VERCEL_OIDC_TOKEN` plus `BLOB_STORE_ID`; set a read-write token manually outside Vercel.",
653
655
  "- The store is uniform-visibility (`BLOB_ACCESS`, default `private`): Vercel Blob does not report per-object access back, so mixed visibility needs a second store.",
654
656
  "- Use `ctx.ports.storage` in app code; `ctx.ports.vercelBlob` is available as an escape hatch.",
655
657
  "",
656
658
  ].join("\n"),
657
659
  nextSteps: [
658
660
  "Run your package manager install command.",
659
- "Connect a Vercel Blob store to the project (or set BLOB_READ_WRITE_TOKEN manually).",
661
+ "Connect a Vercel Blob store and configure either BLOB_READ_WRITE_TOKEN or Vercel OIDC with BLOB_STORE_ID plus VERCEL_OIDC_TOKEN.",
660
662
  "Run beignet provider audit and beignet doctor --strict.",
661
663
  ],
662
664
  conflictingProviderEntries: [
@@ -689,6 +691,8 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
689
691
  "UPSTASH_REDIS_REST_TOKEN=dev-placeholder-change-me",
690
692
  "UPSTASH_PREFIX=beignet:ratelimit",
691
693
  "# UPSTASH_ALGORITHM=fixed-window",
694
+ "# UPSTASH_TIMEOUT_MS=5000",
695
+ "# UPSTASH_TIMEOUT_POLICY=fail-closed",
692
696
  "",
693
697
  ].join("\n"),
694
698
  docs: [
@@ -698,6 +702,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
698
702
  "- Peer dependencies: `@upstash/ratelimit` and `@upstash/redis`",
699
703
  "- The preset wires `createUpstashRateLimitProvider()` in `server/providers.ts` and defers `rateLimit` in `infra/port-wiring.ts`.",
700
704
  "- `.env.example` ships dev placeholders so the app boots; set real `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` values before rate limiting requests.",
705
+ "- Rate-limit checks time out after 5000ms and fail closed by default. Set `UPSTASH_TIMEOUT_POLICY=fail-open` only when availability is more important than enforcement during an Upstash outage.",
701
706
  "- Use `ctx.ports.rateLimit` in app code; `ctx.ports.upstash` is available as an escape hatch.",
702
707
  "",
703
708
  ].join("\n"),