@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
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
|
-
*
|
|
50
|
-
* access and real credentials, so it
|
|
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.
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/src/provider-add.ts
CHANGED
|
@@ -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
|
|
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
|
|
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"),
|