@beignet/cli 0.0.40 → 0.0.42
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 +19 -0
- package/README.md +139 -17
- package/dist/analysis/source-index.d.ts +38 -0
- package/dist/analysis/source-index.d.ts.map +1 -0
- package/dist/analysis/source-index.js +271 -0
- package/dist/analysis/source-index.js.map +1 -0
- package/dist/analysis/workspace.d.ts +16 -0
- package/dist/analysis/workspace.d.ts.map +1 -0
- package/dist/analysis/workspace.js +134 -0
- package/dist/analysis/workspace.js.map +1 -0
- package/dist/app-map-schema.d.ts +5 -0
- package/dist/app-map-schema.d.ts.map +1 -0
- package/dist/app-map-schema.js +26 -0
- package/dist/app-map-schema.js.map +1 -0
- package/dist/app-map.d.ts +96 -0
- package/dist/app-map.d.ts.map +1 -0
- package/dist/app-map.js +1141 -0
- package/dist/app-map.js.map +1 -0
- package/dist/check.d.ts +7 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +20 -5
- package/dist/check.js.map +1 -1
- package/dist/choices.d.ts +18 -0
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +35 -0
- package/dist/choices.js.map +1 -1
- package/dist/db.d.ts +49 -13
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +202 -21
- package/dist/db.js.map +1 -1
- package/dist/doctor-fixes.d.ts +64 -0
- package/dist/doctor-fixes.d.ts.map +1 -0
- package/dist/doctor-fixes.js +142 -0
- package/dist/doctor-fixes.js.map +1 -0
- package/dist/explain.d.ts +100 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +606 -0
- package/dist/explain.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +165 -21
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +33 -9
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +406 -119
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +10 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +5 -2
- package/dist/lib.js.map +1 -1
- package/dist/make/shared.js +3 -3
- package/dist/make/shared.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +63 -2
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +188 -12
- package/dist/mcp.js.map +1 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +10 -0
- package/dist/preflight.js.map +1 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +167 -13
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.d.ts +6 -0
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +63 -15
- package/dist/provider-audit.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +35 -6
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +13 -7
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +8 -1
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +3 -0
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +55 -1
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +2 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +8 -5
- package/dist/templates/shared.js.map +1 -1
- package/package.json +3 -2
- package/skills/app-structure/SKILL.md +48 -6
- package/src/analysis/source-index.ts +395 -0
- package/src/analysis/workspace.ts +180 -0
- package/src/app-map-schema.ts +28 -0
- package/src/app-map.ts +1705 -0
- package/src/check.ts +27 -4
- package/src/choices.ts +57 -0
- package/src/db.ts +276 -28
- package/src/doctor-fixes.ts +252 -0
- package/src/explain.ts +894 -0
- package/src/index.ts +237 -31
- package/src/inspect.ts +567 -147
- package/src/lib.ts +64 -1
- package/src/make/shared.ts +3 -3
- package/src/make.ts +90 -2
- package/src/mcp.ts +292 -12
- package/src/preflight.ts +14 -0
- package/src/provider-add.ts +211 -12
- package/src/provider-audit.ts +127 -22
- package/src/templates/agents.ts +35 -6
- package/src/templates/base.ts +25 -6
- package/src/templates/index.ts +18 -1
- package/src/templates/server.ts +58 -1
- package/src/templates/shared.ts +15 -7
package/src/inspect.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseProviderPackageMetadata } from "@beignet/core/providers";
|
|
4
4
|
import { createPainter } from "./ansi.js";
|
|
@@ -15,6 +15,18 @@ import {
|
|
|
15
15
|
type ResolvedBeignetConfig,
|
|
16
16
|
resolveConfig,
|
|
17
17
|
} from "./config.js";
|
|
18
|
+
import {
|
|
19
|
+
applyPlannedDoctorFixes,
|
|
20
|
+
createDoctorFixPlan,
|
|
21
|
+
type DoctorFixApplyResult,
|
|
22
|
+
type DoctorFixOperationId,
|
|
23
|
+
type DoctorFixPlan,
|
|
24
|
+
type InspectFix,
|
|
25
|
+
type PlannedDoctorFixOperation,
|
|
26
|
+
type PlannedDoctorFixPlan,
|
|
27
|
+
planDoctorFixFileChange,
|
|
28
|
+
publicDoctorFixPlan,
|
|
29
|
+
} from "./doctor-fixes.js";
|
|
18
30
|
import {
|
|
19
31
|
formatGithubAnnotation,
|
|
20
32
|
type GithubAnnotationSeverity,
|
|
@@ -55,6 +67,7 @@ import {
|
|
|
55
67
|
import {
|
|
56
68
|
currentGeneratedPackageScripts,
|
|
57
69
|
externalVersions,
|
|
70
|
+
isLegacyGeneratedPackageScript,
|
|
58
71
|
legacyGeneratedPackageScripts,
|
|
59
72
|
} from "./templates/shared.js";
|
|
60
73
|
import { testSupportTemplateFiles } from "./templates/testing.js";
|
|
@@ -119,14 +132,7 @@ export type InspectDiagnostic = {
|
|
|
119
132
|
contract?: string;
|
|
120
133
|
};
|
|
121
134
|
|
|
122
|
-
|
|
123
|
-
* Automatic doctor fix that was applied.
|
|
124
|
-
*/
|
|
125
|
-
export type InspectFix = {
|
|
126
|
-
code: string;
|
|
127
|
-
message: string;
|
|
128
|
-
file: string;
|
|
129
|
-
};
|
|
135
|
+
export type { InspectFix } from "./doctor-fixes.js";
|
|
130
136
|
|
|
131
137
|
/**
|
|
132
138
|
* Detected app convention and missing convention files.
|
|
@@ -157,6 +163,18 @@ export type InspectAppResult = {
|
|
|
157
163
|
fixes: InspectFix[];
|
|
158
164
|
};
|
|
159
165
|
|
|
166
|
+
/** Read-only doctor fix plan paired with the diagnostics it was derived from. */
|
|
167
|
+
export type DoctorFixPlanResult = DoctorFixPlan & {
|
|
168
|
+
convention: InspectConvention;
|
|
169
|
+
diagnostics: InspectDiagnostic[];
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** Post-apply inspection paired with the guarded plan metadata that ran. */
|
|
173
|
+
export type DoctorFixInspectionResult = InspectAppResult & {
|
|
174
|
+
planId: string;
|
|
175
|
+
operationIds: DoctorFixOperationId[];
|
|
176
|
+
};
|
|
177
|
+
|
|
160
178
|
type RouteExport = {
|
|
161
179
|
method: HttpMethod;
|
|
162
180
|
handlerFile: string;
|
|
@@ -235,9 +253,66 @@ export async function inspectApp(
|
|
|
235
253
|
};
|
|
236
254
|
}
|
|
237
255
|
|
|
256
|
+
/** Options for guarded all-or-selected doctor fix application. */
|
|
257
|
+
export type ApplyDoctorFixPlanOptions = InspectAppOptions & {
|
|
258
|
+
planId: string;
|
|
259
|
+
fixIds?: readonly DoctorFixOperationId[];
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/** Plan every currently available low-risk doctor repair without writing. */
|
|
263
|
+
export async function planDoctorFixes(
|
|
264
|
+
options: InspectAppOptions = {},
|
|
265
|
+
): Promise<DoctorFixPlanResult> {
|
|
266
|
+
const plan = publicDoctorFixPlan(await buildDoctorFixPlan(options));
|
|
267
|
+
const inspected = await inspectApp(options);
|
|
268
|
+
return {
|
|
269
|
+
...plan,
|
|
270
|
+
convention: inspected.convention,
|
|
271
|
+
diagnostics: inspected.diagnostics,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Apply a current doctor fix plan after verifying its plan ID. */
|
|
276
|
+
export async function applyDoctorFixPlan(
|
|
277
|
+
options: ApplyDoctorFixPlanOptions,
|
|
278
|
+
): Promise<DoctorFixApplyResult> {
|
|
279
|
+
const plan = await buildDoctorFixPlan(options);
|
|
280
|
+
if (plan.planId !== options.planId) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
"Doctor fix plan is stale because the available repairs changed. No files were changed; run beignet doctor --fix --dry-run again.",
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return applyPlannedDoctorFixes(plan, options.fixIds);
|
|
286
|
+
}
|
|
287
|
+
|
|
238
288
|
export async function applyDoctorFixes(
|
|
239
289
|
options: InspectAppOptions = {},
|
|
240
290
|
): Promise<InspectFix[]> {
|
|
291
|
+
return (await applyDoctorFixesWithResult(options)).fixes;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function applyDoctorFixesWithResult(
|
|
295
|
+
options: InspectAppOptions = {},
|
|
296
|
+
): Promise<DoctorFixApplyResult> {
|
|
297
|
+
const plan = await buildDoctorFixPlan(options);
|
|
298
|
+
return applyPlannedDoctorFixes(plan);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function createDoctorFixInspectionResult(
|
|
302
|
+
inspected: InspectAppResult,
|
|
303
|
+
applied: DoctorFixApplyResult,
|
|
304
|
+
): DoctorFixInspectionResult {
|
|
305
|
+
return {
|
|
306
|
+
...inspected,
|
|
307
|
+
planId: applied.planId,
|
|
308
|
+
operationIds: applied.operationIds,
|
|
309
|
+
fixes: applied.fixes,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function buildDoctorFixPlan(
|
|
314
|
+
options: InspectAppOptions = {},
|
|
315
|
+
): Promise<PlannedDoctorFixPlan> {
|
|
241
316
|
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
242
317
|
await assertDirectory(targetDir);
|
|
243
318
|
|
|
@@ -253,44 +328,63 @@ export async function applyDoctorFixes(
|
|
|
253
328
|
config,
|
|
254
329
|
contracts,
|
|
255
330
|
);
|
|
256
|
-
const
|
|
331
|
+
const operations: PlannedDoctorFixOperation[] = [];
|
|
257
332
|
|
|
258
|
-
const packageFix = await
|
|
259
|
-
if (packageFix)
|
|
333
|
+
const packageFix = await planPackageScripts(targetDir, files, convention);
|
|
334
|
+
if (packageFix) operations.push(packageFix);
|
|
260
335
|
|
|
261
|
-
const routeGroupFix = await
|
|
336
|
+
const routeGroupFix = await planUnregisteredRouteGroups(
|
|
262
337
|
targetDir,
|
|
263
338
|
files,
|
|
264
339
|
config,
|
|
265
340
|
);
|
|
266
|
-
if (routeGroupFix)
|
|
341
|
+
if (routeGroupFix) operations.push(routeGroupFix);
|
|
267
342
|
|
|
268
343
|
const drift = await workflowRegistrationDrift(targetDir, files, config);
|
|
269
344
|
|
|
270
|
-
|
|
345
|
+
if (config.paths.schedules === config.paths.tasks) {
|
|
346
|
+
const workflowFix = await planCoLocatedScheduleAndTaskRegistries(
|
|
347
|
+
targetDir,
|
|
348
|
+
files,
|
|
349
|
+
drift,
|
|
350
|
+
config,
|
|
351
|
+
);
|
|
352
|
+
if (workflowFix) operations.push(workflowFix);
|
|
353
|
+
} else {
|
|
354
|
+
const scheduleFix = await planUnregisteredSchedules(
|
|
355
|
+
targetDir,
|
|
356
|
+
files,
|
|
357
|
+
drift,
|
|
358
|
+
config,
|
|
359
|
+
);
|
|
360
|
+
if (scheduleFix) operations.push(scheduleFix);
|
|
361
|
+
|
|
362
|
+
const taskFix = await planUnregisteredTasks(
|
|
363
|
+
targetDir,
|
|
364
|
+
files,
|
|
365
|
+
drift,
|
|
366
|
+
config,
|
|
367
|
+
);
|
|
368
|
+
if (taskFix) operations.push(taskFix);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const outboxFix = await planUnregisteredOutboxEntries(
|
|
271
372
|
targetDir,
|
|
272
373
|
files,
|
|
273
374
|
drift,
|
|
274
375
|
config,
|
|
275
376
|
);
|
|
276
|
-
if (
|
|
377
|
+
if (outboxFix) operations.push(outboxFix);
|
|
277
378
|
|
|
278
|
-
const
|
|
279
|
-
if (taskFix) fixes.push(taskFix);
|
|
280
|
-
|
|
281
|
-
fixes.push(
|
|
282
|
-
...(await fixUnregisteredOutboxEntries(targetDir, files, drift, config)),
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
const listenerFix = await fixUnregisteredListeners(
|
|
379
|
+
const listenerFix = await planUnregisteredListeners(
|
|
286
380
|
targetDir,
|
|
287
381
|
files,
|
|
288
382
|
drift,
|
|
289
383
|
config,
|
|
290
384
|
);
|
|
291
|
-
if (listenerFix)
|
|
385
|
+
if (listenerFix) operations.push(listenerFix);
|
|
292
386
|
|
|
293
|
-
const openApiFix = await
|
|
387
|
+
const openApiFix = await planDirectOpenApiArrayDrift(
|
|
294
388
|
targetDir,
|
|
295
389
|
files,
|
|
296
390
|
config,
|
|
@@ -298,9 +392,13 @@ export async function applyDoctorFixes(
|
|
|
298
392
|
contracts,
|
|
299
393
|
matchedRoutes,
|
|
300
394
|
);
|
|
301
|
-
if (openApiFix)
|
|
395
|
+
if (openApiFix) operations.push(openApiFix);
|
|
302
396
|
|
|
303
|
-
return
|
|
397
|
+
return createDoctorFixPlan({
|
|
398
|
+
targetDir,
|
|
399
|
+
strict: Boolean(options.strict),
|
|
400
|
+
operations,
|
|
401
|
+
});
|
|
304
402
|
}
|
|
305
403
|
|
|
306
404
|
/**
|
|
@@ -329,12 +427,97 @@ export function formatRoutes(result: InspectAppResult): string {
|
|
|
329
427
|
]);
|
|
330
428
|
}
|
|
331
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Format a read-only doctor fix plan with exact patches and apply commands.
|
|
432
|
+
*/
|
|
433
|
+
export function formatDoctorFixPlan(
|
|
434
|
+
plan: DoctorFixPlanResult,
|
|
435
|
+
options: { color?: boolean } = {},
|
|
436
|
+
): string {
|
|
437
|
+
const paint = createPainter(options.color);
|
|
438
|
+
const lines: string[] = [];
|
|
439
|
+
if (plan.operations.length === 0) {
|
|
440
|
+
lines.push(`No applicable Beignet fixes found in ${plan.targetDir}.`);
|
|
441
|
+
} else {
|
|
442
|
+
const strictFlag = plan.strict ? " --strict" : "";
|
|
443
|
+
lines.push(
|
|
444
|
+
`Planned ${plan.operations.length} Beignet fix operation${plan.operations.length === 1 ? "" : "s"} in ${plan.targetDir}:`,
|
|
445
|
+
"",
|
|
446
|
+
);
|
|
447
|
+
for (const operation of plan.operations) {
|
|
448
|
+
lines.push(
|
|
449
|
+
`${paint("PLAN", "cyan")} ${operation.id}`,
|
|
450
|
+
...operation.fixes.map(
|
|
451
|
+
(fix) => ` ${fix.code} ${fix.file}\n ${fix.message}`,
|
|
452
|
+
),
|
|
453
|
+
...operation.changes.flatMap((change) => [
|
|
454
|
+
` ${change.kind === "create" ? "Create" : "Update"} ${change.file}`,
|
|
455
|
+
change.patch.trimEnd(),
|
|
456
|
+
]),
|
|
457
|
+
"",
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
lines.push(
|
|
462
|
+
`Plan ID: ${plan.planId}`,
|
|
463
|
+
"",
|
|
464
|
+
"Apply the complete plan:",
|
|
465
|
+
` beignet doctor --fix${strictFlag} --plan ${plan.planId}`,
|
|
466
|
+
"",
|
|
467
|
+
"Apply selected operations:",
|
|
468
|
+
` beignet doctor --fix${strictFlag} --plan ${plan.planId} --only ${plan.operations.map((operation) => operation.id).join(",")}`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
lines.push(
|
|
473
|
+
"",
|
|
474
|
+
formatDoctorResult(
|
|
475
|
+
{
|
|
476
|
+
targetDir: plan.targetDir,
|
|
477
|
+
strict: plan.strict,
|
|
478
|
+
diagnostics: plan.diagnostics,
|
|
479
|
+
fixes: [],
|
|
480
|
+
},
|
|
481
|
+
options,
|
|
482
|
+
),
|
|
483
|
+
);
|
|
484
|
+
return lines.join("\n");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Format planned fixes as GitHub Actions notice annotations.
|
|
489
|
+
*/
|
|
490
|
+
export function formatDoctorFixPlanGithub(plan: DoctorFixPlanResult): string {
|
|
491
|
+
return [
|
|
492
|
+
...plan.operations.map((operation) =>
|
|
493
|
+
formatGithubAnnotation({
|
|
494
|
+
severity: "notice",
|
|
495
|
+
file: operation.changes[0]?.file,
|
|
496
|
+
message: `${operation.id}: ${operation.fixes.map((fix) => fix.message).join(" ")} Plan ${plan.planId}.`,
|
|
497
|
+
}),
|
|
498
|
+
),
|
|
499
|
+
...formatDoctorDiagnosticsGithub(plan.diagnostics),
|
|
500
|
+
].join("\n");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
type DoctorFormatResult = Pick<
|
|
504
|
+
InspectAppResult,
|
|
505
|
+
"targetDir" | "strict" | "diagnostics" | "fixes"
|
|
506
|
+
>;
|
|
507
|
+
|
|
332
508
|
/**
|
|
333
509
|
* Format doctor diagnostics and applied fixes for CLI output.
|
|
334
510
|
*/
|
|
335
511
|
export function formatDoctor(
|
|
336
512
|
result: InspectAppResult,
|
|
337
513
|
options: { color?: boolean } = {},
|
|
514
|
+
): string {
|
|
515
|
+
return formatDoctorResult(result, options);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function formatDoctorResult(
|
|
519
|
+
result: DoctorFormatResult,
|
|
520
|
+
options: { color?: boolean } = {},
|
|
338
521
|
): string {
|
|
339
522
|
const paint = createPainter(options.color);
|
|
340
523
|
const fixLines =
|
|
@@ -456,7 +639,7 @@ function productionHardeningChecklist(
|
|
|
456
639
|
];
|
|
457
640
|
}
|
|
458
641
|
|
|
459
|
-
function formatDoctorFooter(result:
|
|
642
|
+
function formatDoctorFooter(result: DoctorFormatResult): string {
|
|
460
643
|
const count = (severity: InspectDiagnostic["severity"]) =>
|
|
461
644
|
result.diagnostics.filter((diagnostic) => diagnostic.severity === severity)
|
|
462
645
|
.length;
|
|
@@ -469,15 +652,19 @@ function formatDoctorFooter(result: InspectAppResult): string {
|
|
|
469
652
|
* Format doctor diagnostics as GitHub Actions annotations.
|
|
470
653
|
*/
|
|
471
654
|
export function formatDoctorGithub(result: InspectAppResult): string {
|
|
472
|
-
return result.diagnostics
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
655
|
+
return formatDoctorDiagnosticsGithub(result.diagnostics).join("\n");
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function formatDoctorDiagnosticsGithub(
|
|
659
|
+
diagnostics: InspectDiagnostic[],
|
|
660
|
+
): string[] {
|
|
661
|
+
return diagnostics.map((diagnostic) =>
|
|
662
|
+
formatGithubAnnotation({
|
|
663
|
+
severity: githubAnnotationSeverity(diagnostic.severity),
|
|
664
|
+
file: diagnostic.file,
|
|
665
|
+
message: `${diagnostic.code}: ${diagnostic.message}`,
|
|
666
|
+
}),
|
|
667
|
+
);
|
|
481
668
|
}
|
|
482
669
|
|
|
483
670
|
function githubAnnotationSeverity(
|
|
@@ -618,7 +805,7 @@ function parseContracts(source: string, file: string): InspectedContract[] {
|
|
|
618
805
|
for (const exportMatch of source.matchAll(exportRegex)) {
|
|
619
806
|
const block = exportMatch[2];
|
|
620
807
|
const methodMatch =
|
|
621
|
-
/([A-Za-z_$][\w$]*)(?:\s*\.\s*[A-Za-z_$][\w$]*\([^)]*\))*\s*\.\s*(get|post|put|patch|delete|head|options)\(\s*["']([^"']+)["']
|
|
808
|
+
/([A-Za-z_$][\w$]*)(?:\s*\.\s*[A-Za-z_$][\w$]*\([^)]*\))*\s*\.\s*(get|post|put|patch|delete|head|options)\(\s*["']([^"']+)["'](?:\s*,\s*["'][^"']+["'])?\s*\)/.exec(
|
|
622
809
|
block,
|
|
623
810
|
);
|
|
624
811
|
if (methodMatch) {
|
|
@@ -1521,6 +1708,7 @@ async function inspectDiagnostics(
|
|
|
1521
1708
|
config,
|
|
1522
1709
|
convention,
|
|
1523
1710
|
)),
|
|
1711
|
+
...(await inspectInngestProductionPath(targetDir, files, config)),
|
|
1524
1712
|
...(await inspectDatabaseLifecycleDrift(
|
|
1525
1713
|
targetDir,
|
|
1526
1714
|
files,
|
|
@@ -3095,6 +3283,73 @@ async function inspectProviderRegistrationDrift(
|
|
|
3095
3283
|
return diagnostics;
|
|
3096
3284
|
}
|
|
3097
3285
|
|
|
3286
|
+
async function inspectInngestProductionPath(
|
|
3287
|
+
targetDir: string,
|
|
3288
|
+
files: string[],
|
|
3289
|
+
config: ResolvedBeignetConfig,
|
|
3290
|
+
): Promise<InspectDiagnostic[]> {
|
|
3291
|
+
const packageJson = await readPackageJson(targetDir, files);
|
|
3292
|
+
if (
|
|
3293
|
+
!installedPackageNames(packageJson).has("@beignet/provider-jobs-inngest")
|
|
3294
|
+
) {
|
|
3295
|
+
return [];
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
const diagnostics: InspectDiagnostic[] = [];
|
|
3299
|
+
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
3300
|
+
const registryFile = `${serverDir}/inngest.ts`;
|
|
3301
|
+
const routeFile = `${directoryPath(config.paths.routes)}/inngest/route.ts`;
|
|
3302
|
+
|
|
3303
|
+
if (!files.includes(registryFile)) {
|
|
3304
|
+
diagnostics.push({
|
|
3305
|
+
severity: "warning",
|
|
3306
|
+
code: "BEIGNET_INNGEST_REGISTRY_MISSING",
|
|
3307
|
+
file: registryFile,
|
|
3308
|
+
message: `@beignet/provider-jobs-inngest is installed, but ${registryFile} is missing. Run beignet provider add jobs-inngest or create a central createInngestJobFunctions(...) registry so dispatched jobs can execute.`,
|
|
3309
|
+
});
|
|
3310
|
+
} else {
|
|
3311
|
+
const registrySource = await readFile(
|
|
3312
|
+
path.join(targetDir, registryFile),
|
|
3313
|
+
"utf8",
|
|
3314
|
+
);
|
|
3315
|
+
const registry = arrayInitializerInfo(registrySource, "inngestJobs");
|
|
3316
|
+
const registeredJobs = registry
|
|
3317
|
+
? identifiersFromArrayExpression(registry.text)
|
|
3318
|
+
: new Set<string>();
|
|
3319
|
+
const featureRoot = directoryPath(config.paths.features);
|
|
3320
|
+
const jobIndexes = files.filter((file) =>
|
|
3321
|
+
new RegExp(`^${escapeRegExp(featureRoot)}/[^/]+/jobs/index\\.ts$`).test(
|
|
3322
|
+
file,
|
|
3323
|
+
),
|
|
3324
|
+
);
|
|
3325
|
+
for (const jobIndex of jobIndexes) {
|
|
3326
|
+
const source = await readFile(path.join(targetDir, jobIndex), "utf8");
|
|
3327
|
+
const match = source.match(/export const\s+([A-Za-z_$][\w$]*Jobs)\s*=/);
|
|
3328
|
+
const registryName = match?.[1];
|
|
3329
|
+
if (!registryName || registeredJobs.has(registryName)) {
|
|
3330
|
+
continue;
|
|
3331
|
+
}
|
|
3332
|
+
diagnostics.push({
|
|
3333
|
+
severity: "warning",
|
|
3334
|
+
code: "BEIGNET_INNGEST_JOB_REGISTRATION_MISSING",
|
|
3335
|
+
file: registryFile,
|
|
3336
|
+
message: `${jobIndex} exports ${registryName}, but it is not registered in inngestJobs in ${registryFile}. Add ...${registryName} so Inngest can execute the job.`,
|
|
3337
|
+
});
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
if (config.framework === "next" && !files.includes(routeFile)) {
|
|
3342
|
+
diagnostics.push({
|
|
3343
|
+
severity: "warning",
|
|
3344
|
+
code: "BEIGNET_INNGEST_ROUTE_MISSING",
|
|
3345
|
+
file: routeFile,
|
|
3346
|
+
message: `@beignet/provider-jobs-inngest is installed in a Next.js app, but ${routeFile} is missing. Expose the shared Inngest client and function registry with serve(...) so Inngest can discover and invoke jobs.`,
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
return diagnostics;
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3098
3353
|
function isFeatureUploadDefinition(
|
|
3099
3354
|
file: string,
|
|
3100
3355
|
config: ResolvedBeignetConfig,
|
|
@@ -4358,7 +4613,7 @@ function callReferencesCentralListenerRegistry(args: {
|
|
|
4358
4613
|
|
|
4359
4614
|
for (const identifier of args.callIdentifiers) {
|
|
4360
4615
|
const imported = args.namedImports.get(identifier);
|
|
4361
|
-
if (
|
|
4616
|
+
if (imported?.importedName !== "listeners") continue;
|
|
4362
4617
|
|
|
4363
4618
|
const importedFile = sourceFileFromImport(
|
|
4364
4619
|
imported.sourcePath,
|
|
@@ -5952,8 +6207,7 @@ async function inspectPackageScripts(
|
|
|
5952
6207
|
severity: "warning",
|
|
5953
6208
|
code: "BEIGNET_PACKAGE_TEST_SCRIPT_MISSING",
|
|
5954
6209
|
file: "package.json",
|
|
5955
|
-
message:
|
|
5956
|
-
'package.json does not define a test script. Add "test": "tsx lib/beignet-test-runner.ts" or run beignet doctor --fix.',
|
|
6210
|
+
message: `package.json does not define a test script. Add "test": "${currentGeneratedPackageScripts.test}" or run beignet doctor --fix.`,
|
|
5957
6211
|
});
|
|
5958
6212
|
}
|
|
5959
6213
|
|
|
@@ -5965,7 +6219,7 @@ async function inspectPackageScripts(
|
|
|
5965
6219
|
severity: "warning",
|
|
5966
6220
|
code: "BEIGNET_PACKAGE_SCRIPT_LEGACY",
|
|
5967
6221
|
file: "package.json",
|
|
5968
|
-
message: `package.json still uses legacy generated
|
|
6222
|
+
message: `package.json still uses legacy generated scripts for ${legacyScripts.join(", ")}. Run beignet doctor --fix to migrate them to the current package-manager-neutral scripts.`,
|
|
5969
6223
|
});
|
|
5970
6224
|
}
|
|
5971
6225
|
|
|
@@ -5999,11 +6253,11 @@ async function inspectPackageScripts(
|
|
|
5999
6253
|
return diagnostics;
|
|
6000
6254
|
}
|
|
6001
6255
|
|
|
6002
|
-
async function
|
|
6256
|
+
async function planPackageScripts(
|
|
6003
6257
|
targetDir: string,
|
|
6004
6258
|
files: string[],
|
|
6005
6259
|
convention: InspectConvention,
|
|
6006
|
-
): Promise<
|
|
6260
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6007
6261
|
if (!convention.resourceGenerator || !files.includes("package.json")) {
|
|
6008
6262
|
return undefined;
|
|
6009
6263
|
}
|
|
@@ -6029,10 +6283,8 @@ async function fixPackageScripts(
|
|
|
6029
6283
|
}
|
|
6030
6284
|
|
|
6031
6285
|
if (!hasDirectBunTestImports) {
|
|
6032
|
-
for (const
|
|
6033
|
-
|
|
6034
|
-
)) {
|
|
6035
|
-
if (packageJson.scripts[name] === legacyCommand) {
|
|
6286
|
+
for (const name of Object.keys(legacyGeneratedPackageScripts)) {
|
|
6287
|
+
if (isLegacyGeneratedPackageScript(name, packageJson.scripts[name])) {
|
|
6036
6288
|
packageJson.scripts[name] = currentGeneratedPackageScripts[name];
|
|
6037
6289
|
if (!updatedScripts.includes(name)) updatedScripts.push(name);
|
|
6038
6290
|
}
|
|
@@ -6050,29 +6302,25 @@ async function fixPackageScripts(
|
|
|
6050
6302
|
}
|
|
6051
6303
|
|
|
6052
6304
|
const next = `${JSON.stringify(packageJson, null, "\t")}\n`;
|
|
6053
|
-
const createdSupportFiles =
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
);
|
|
6305
|
+
const createdSupportFiles =
|
|
6306
|
+
packageJson.scripts.test === currentGeneratedPackageScripts.test
|
|
6307
|
+
? missingTestSupportFiles(files)
|
|
6308
|
+
: [];
|
|
6058
6309
|
|
|
6059
6310
|
if (next === original && createdSupportFiles.length === 0) return undefined;
|
|
6060
6311
|
|
|
6061
|
-
|
|
6062
|
-
|
|
6312
|
+
let fix: InspectFix | undefined;
|
|
6063
6313
|
if (addedTestScript) {
|
|
6064
|
-
|
|
6314
|
+
fix = {
|
|
6065
6315
|
code: "BEIGNET_PACKAGE_TEST_SCRIPT_MISSING",
|
|
6066
6316
|
file: "package.json",
|
|
6067
6317
|
message:
|
|
6068
6318
|
createdSupportFiles.length > 0
|
|
6069
|
-
? `Added "test": "
|
|
6070
|
-
:
|
|
6319
|
+
? `Added "test": "${currentGeneratedPackageScripts.test}" and ${createdSupportFiles.join(", ")}.`
|
|
6320
|
+
: `Added "test": "${currentGeneratedPackageScripts.test}".`,
|
|
6071
6321
|
};
|
|
6072
|
-
}
|
|
6073
|
-
|
|
6074
|
-
if (updatedScripts.length > 0) {
|
|
6075
|
-
return {
|
|
6322
|
+
} else if (updatedScripts.length > 0) {
|
|
6323
|
+
fix = {
|
|
6076
6324
|
code: "BEIGNET_PACKAGE_SCRIPT_LEGACY",
|
|
6077
6325
|
file: "package.json",
|
|
6078
6326
|
message: packageScriptFixMessage(
|
|
@@ -6081,31 +6329,48 @@ async function fixPackageScripts(
|
|
|
6081
6329
|
addedTsx,
|
|
6082
6330
|
),
|
|
6083
6331
|
};
|
|
6084
|
-
}
|
|
6085
|
-
|
|
6086
|
-
if (createdSupportFiles.length > 0) {
|
|
6087
|
-
return {
|
|
6332
|
+
} else if (createdSupportFiles.length > 0) {
|
|
6333
|
+
fix = {
|
|
6088
6334
|
code: "BEIGNET_PACKAGE_TEST_SUPPORT_MISSING",
|
|
6089
6335
|
file: "package.json",
|
|
6090
6336
|
message: `Restored ${createdSupportFiles.join(", ")}.`,
|
|
6091
6337
|
};
|
|
6092
|
-
}
|
|
6093
|
-
|
|
6094
|
-
if (addedTsx) {
|
|
6095
|
-
return {
|
|
6338
|
+
} else if (addedTsx) {
|
|
6339
|
+
fix = {
|
|
6096
6340
|
code: "BEIGNET_PACKAGE_TSX_DEPENDENCY_MISSING",
|
|
6097
6341
|
file: "package.json",
|
|
6098
6342
|
message: 'Added "tsx" to devDependencies.',
|
|
6099
6343
|
};
|
|
6100
6344
|
}
|
|
6101
6345
|
|
|
6102
|
-
return undefined;
|
|
6346
|
+
if (!fix) return undefined;
|
|
6347
|
+
|
|
6348
|
+
return {
|
|
6349
|
+
id: "package.repair-generated-support",
|
|
6350
|
+
fixes: [fix],
|
|
6351
|
+
changes: [
|
|
6352
|
+
...(next === original
|
|
6353
|
+
? []
|
|
6354
|
+
: [
|
|
6355
|
+
planDoctorFixFileChange({
|
|
6356
|
+
file: "package.json",
|
|
6357
|
+
before: original,
|
|
6358
|
+
after: next,
|
|
6359
|
+
}),
|
|
6360
|
+
]),
|
|
6361
|
+
...testSupportTemplateFiles
|
|
6362
|
+
.filter((file) => createdSupportFiles.includes(file.path))
|
|
6363
|
+
.map((file) =>
|
|
6364
|
+
planDoctorFixFileChange({ file: file.path, after: file.content }),
|
|
6365
|
+
),
|
|
6366
|
+
],
|
|
6367
|
+
};
|
|
6103
6368
|
}
|
|
6104
6369
|
|
|
6105
6370
|
function legacyGeneratedScriptNames(scripts: Record<string, string>): string[] {
|
|
6106
|
-
return Object.
|
|
6107
|
-
|
|
6108
|
-
|
|
6371
|
+
return Object.keys(legacyGeneratedPackageScripts).filter((name) =>
|
|
6372
|
+
isLegacyGeneratedPackageScript(name, scripts[name]),
|
|
6373
|
+
);
|
|
6109
6374
|
}
|
|
6110
6375
|
|
|
6111
6376
|
async function appUsesDirectBunTestImports(
|
|
@@ -6126,7 +6391,8 @@ function hasCurrentGeneratedTsxScript(
|
|
|
6126
6391
|
): boolean {
|
|
6127
6392
|
return Object.values(currentGeneratedPackageScripts).some(
|
|
6128
6393
|
(command) =>
|
|
6129
|
-
command.startsWith("tsx ")
|
|
6394
|
+
(command.startsWith("tsx ") || command.includes("--import tsx")) &&
|
|
6395
|
+
Object.values(scripts).includes(command),
|
|
6130
6396
|
);
|
|
6131
6397
|
}
|
|
6132
6398
|
|
|
@@ -6136,23 +6402,6 @@ function missingTestSupportFiles(files: string[]): string[] {
|
|
|
6136
6402
|
.filter((file) => !files.includes(file));
|
|
6137
6403
|
}
|
|
6138
6404
|
|
|
6139
|
-
async function createMissingTestSupportFiles(
|
|
6140
|
-
targetDir: string,
|
|
6141
|
-
files: string[],
|
|
6142
|
-
shouldCreate: boolean,
|
|
6143
|
-
): Promise<string[]> {
|
|
6144
|
-
if (!shouldCreate) return [];
|
|
6145
|
-
const createdSupportFiles: string[] = [];
|
|
6146
|
-
for (const file of testSupportTemplateFiles) {
|
|
6147
|
-
if (files.includes(file.path)) continue;
|
|
6148
|
-
const destination = path.join(targetDir, file.path);
|
|
6149
|
-
await mkdir(path.dirname(destination), { recursive: true });
|
|
6150
|
-
await writeFile(destination, file.content);
|
|
6151
|
-
createdSupportFiles.push(file.path);
|
|
6152
|
-
}
|
|
6153
|
-
return createdSupportFiles;
|
|
6154
|
-
}
|
|
6155
|
-
|
|
6156
6405
|
function packageScriptFixMessage(
|
|
6157
6406
|
updatedScripts: string[],
|
|
6158
6407
|
createdSupportFiles: string[],
|
|
@@ -6659,14 +6908,14 @@ function findRouteGroupContract(
|
|
|
6659
6908
|
);
|
|
6660
6909
|
}
|
|
6661
6910
|
|
|
6662
|
-
async function
|
|
6911
|
+
async function planDirectOpenApiArrayDrift(
|
|
6663
6912
|
targetDir: string,
|
|
6664
6913
|
files: string[],
|
|
6665
6914
|
config: ResolvedBeignetConfig,
|
|
6666
6915
|
convention: InspectConvention,
|
|
6667
6916
|
contracts: InspectedContract[],
|
|
6668
6917
|
matchedRoutes: MatchedRoutesResult,
|
|
6669
|
-
): Promise<
|
|
6918
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6670
6919
|
const routePath = path.join(targetDir, config.paths.openapiRoute);
|
|
6671
6920
|
let source: string;
|
|
6672
6921
|
try {
|
|
@@ -6709,21 +6958,32 @@ async function fixDirectOpenApiArrayDrift(
|
|
|
6709
6958
|
)}`;
|
|
6710
6959
|
if (next === source) return undefined;
|
|
6711
6960
|
|
|
6712
|
-
await writeFile(routePath, next);
|
|
6713
6961
|
return {
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6962
|
+
id: "openapi.register-missing",
|
|
6963
|
+
fixes: [
|
|
6964
|
+
{
|
|
6965
|
+
code: "BEIGNET_OPENAPI_MISSING",
|
|
6966
|
+
file: config.paths.openapiRoute,
|
|
6967
|
+
message: `Added ${missingContracts
|
|
6968
|
+
.map((contract) => contract.exportName)
|
|
6969
|
+
.join(", ")} to the direct createOpenAPIHandler contract array.`,
|
|
6970
|
+
},
|
|
6971
|
+
],
|
|
6972
|
+
changes: [
|
|
6973
|
+
planDoctorFixFileChange({
|
|
6974
|
+
file: config.paths.openapiRoute,
|
|
6975
|
+
before: source,
|
|
6976
|
+
after: next,
|
|
6977
|
+
}),
|
|
6978
|
+
],
|
|
6719
6979
|
};
|
|
6720
6980
|
}
|
|
6721
6981
|
|
|
6722
|
-
async function
|
|
6982
|
+
async function planUnregisteredRouteGroups(
|
|
6723
6983
|
targetDir: string,
|
|
6724
6984
|
files: string[],
|
|
6725
6985
|
config: ResolvedBeignetConfig,
|
|
6726
|
-
): Promise<
|
|
6986
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6727
6987
|
if (!files.includes(config.paths.server)) return undefined;
|
|
6728
6988
|
|
|
6729
6989
|
const routeGroups = await readFeatureRouteGroups(targetDir, files, config);
|
|
@@ -6794,25 +7054,36 @@ async function fixUnregisteredRouteGroups(
|
|
|
6794
7054
|
)}`;
|
|
6795
7055
|
if (next === original) return undefined;
|
|
6796
7056
|
|
|
6797
|
-
await writeFile(targetPath, next);
|
|
6798
7057
|
return {
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
7058
|
+
id: "routes.register-missing",
|
|
7059
|
+
fixes: [
|
|
7060
|
+
{
|
|
7061
|
+
code: "BEIGNET_ROUTE_GROUP_UNREGISTERED",
|
|
7062
|
+
file: targetFile,
|
|
7063
|
+
message: `Registered ${groupsToAppend
|
|
7064
|
+
.map((routeGroup) => routeGroup.name)
|
|
7065
|
+
.join(", ")} in the central route list.`,
|
|
7066
|
+
},
|
|
7067
|
+
],
|
|
7068
|
+
changes: [
|
|
7069
|
+
planDoctorFixFileChange({
|
|
7070
|
+
file: targetFile,
|
|
7071
|
+
before: original,
|
|
7072
|
+
after: next,
|
|
7073
|
+
}),
|
|
7074
|
+
],
|
|
6804
7075
|
};
|
|
6805
7076
|
}
|
|
6806
7077
|
|
|
6807
|
-
async function
|
|
7078
|
+
async function planUnregisteredSchedules(
|
|
6808
7079
|
targetDir: string,
|
|
6809
7080
|
files: string[],
|
|
6810
7081
|
drift: WorkflowRegistrationDrift,
|
|
6811
7082
|
config: ResolvedBeignetConfig,
|
|
6812
|
-
): Promise<
|
|
7083
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6813
7084
|
if (!drift.schedules.centralExists) return undefined;
|
|
6814
7085
|
|
|
6815
|
-
return
|
|
7086
|
+
return planWorkflowRegistryOperation("schedules.register-missing", {
|
|
6816
7087
|
targetDir,
|
|
6817
7088
|
files,
|
|
6818
7089
|
centralFile: config.paths.schedules,
|
|
@@ -6825,15 +7096,15 @@ async function fixUnregisteredSchedules(
|
|
|
6825
7096
|
});
|
|
6826
7097
|
}
|
|
6827
7098
|
|
|
6828
|
-
async function
|
|
7099
|
+
async function planUnregisteredTasks(
|
|
6829
7100
|
targetDir: string,
|
|
6830
7101
|
files: string[],
|
|
6831
7102
|
drift: WorkflowRegistrationDrift,
|
|
6832
7103
|
config: ResolvedBeignetConfig,
|
|
6833
|
-
): Promise<
|
|
7104
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6834
7105
|
if (!drift.tasks.centralExists) return undefined;
|
|
6835
7106
|
|
|
6836
|
-
return
|
|
7107
|
+
return planWorkflowRegistryOperation("tasks.register-missing", {
|
|
6837
7108
|
targetDir,
|
|
6838
7109
|
files,
|
|
6839
7110
|
centralFile: config.paths.tasks,
|
|
@@ -6847,6 +7118,84 @@ async function fixUnregisteredTasks(
|
|
|
6847
7118
|
});
|
|
6848
7119
|
}
|
|
6849
7120
|
|
|
7121
|
+
async function planCoLocatedScheduleAndTaskRegistries(
|
|
7122
|
+
targetDir: string,
|
|
7123
|
+
files: string[],
|
|
7124
|
+
drift: WorkflowRegistrationDrift,
|
|
7125
|
+
config: ResolvedBeignetConfig,
|
|
7126
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
7127
|
+
if (!drift.schedules.centralExists || !drift.tasks.centralExists) {
|
|
7128
|
+
return undefined;
|
|
7129
|
+
}
|
|
7130
|
+
|
|
7131
|
+
const centralFile = config.paths.schedules;
|
|
7132
|
+
let original: string;
|
|
7133
|
+
try {
|
|
7134
|
+
original = await readFile(path.join(targetDir, centralFile), "utf8");
|
|
7135
|
+
} catch {
|
|
7136
|
+
return undefined;
|
|
7137
|
+
}
|
|
7138
|
+
|
|
7139
|
+
let next = original;
|
|
7140
|
+
const fixes: InspectFix[] = [];
|
|
7141
|
+
const repairedKinds: Array<"schedules" | "tasks"> = [];
|
|
7142
|
+
|
|
7143
|
+
const scheduleEdit = planWorkflowRegistryEdit({
|
|
7144
|
+
files,
|
|
7145
|
+
centralFile,
|
|
7146
|
+
source: next,
|
|
7147
|
+
unregistered: drift.schedules.unregistered,
|
|
7148
|
+
code: "BEIGNET_SCHEDULE_UNREGISTERED",
|
|
7149
|
+
listName: "the schedules array",
|
|
7150
|
+
importSpecifier: (indexFile) => `@/${modulePath(indexFile)}`,
|
|
7151
|
+
append: (source, entry, importLine) =>
|
|
7152
|
+
appendToNamedArray(source, "schedules", entry, importLine),
|
|
7153
|
+
});
|
|
7154
|
+
if (scheduleEdit) {
|
|
7155
|
+
next = scheduleEdit.after;
|
|
7156
|
+
fixes.push(scheduleEdit.fix);
|
|
7157
|
+
repairedKinds.push("schedules");
|
|
7158
|
+
}
|
|
7159
|
+
|
|
7160
|
+
const taskEdit = planWorkflowRegistryEdit({
|
|
7161
|
+
files,
|
|
7162
|
+
centralFile,
|
|
7163
|
+
source: next,
|
|
7164
|
+
unregistered: drift.tasks.unregistered,
|
|
7165
|
+
code: "BEIGNET_TASK_UNREGISTERED",
|
|
7166
|
+
listName: "defineTasks([...])",
|
|
7167
|
+
importSpecifier: (indexFile) => relativeModule(centralFile, indexFile),
|
|
7168
|
+
append: (source, entry, importLine) =>
|
|
7169
|
+
appendToNamedArray(source, "tasks", entry, importLine),
|
|
7170
|
+
});
|
|
7171
|
+
if (taskEdit) {
|
|
7172
|
+
next = taskEdit.after;
|
|
7173
|
+
fixes.push(taskEdit.fix);
|
|
7174
|
+
repairedKinds.push("tasks");
|
|
7175
|
+
}
|
|
7176
|
+
|
|
7177
|
+
if (fixes.length === 0 || next === original) return undefined;
|
|
7178
|
+
|
|
7179
|
+
const id: DoctorFixOperationId =
|
|
7180
|
+
repairedKinds.length === 2
|
|
7181
|
+
? "workflows.register-missing"
|
|
7182
|
+
: repairedKinds[0] === "schedules"
|
|
7183
|
+
? "schedules.register-missing"
|
|
7184
|
+
: "tasks.register-missing";
|
|
7185
|
+
|
|
7186
|
+
return {
|
|
7187
|
+
id,
|
|
7188
|
+
fixes,
|
|
7189
|
+
changes: [
|
|
7190
|
+
planDoctorFixFileChange({
|
|
7191
|
+
file: centralFile,
|
|
7192
|
+
before: original,
|
|
7193
|
+
after: next,
|
|
7194
|
+
}),
|
|
7195
|
+
],
|
|
7196
|
+
};
|
|
7197
|
+
}
|
|
7198
|
+
|
|
6850
7199
|
/**
|
|
6851
7200
|
* Wire fully unregistered feature listener registries into the central
|
|
6852
7201
|
* listener registry and ensure the providers file registers that central
|
|
@@ -6858,12 +7207,12 @@ async function fixUnregisteredTasks(
|
|
|
6858
7207
|
* register against, or when the registry name is already imported from another
|
|
6859
7208
|
* file. The diagnostic stays in every bail case.
|
|
6860
7209
|
*/
|
|
6861
|
-
async function
|
|
7210
|
+
async function planUnregisteredListeners(
|
|
6862
7211
|
targetDir: string,
|
|
6863
7212
|
files: string[],
|
|
6864
7213
|
drift: WorkflowRegistrationDrift,
|
|
6865
7214
|
config: ResolvedBeignetConfig,
|
|
6866
|
-
): Promise<
|
|
7215
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6867
7216
|
const candidates = drift.listeners.unregistered.filter(
|
|
6868
7217
|
(entry) => entry.fullyUnregistered,
|
|
6869
7218
|
);
|
|
@@ -6960,32 +7309,61 @@ async function fixUnregisteredListeners(
|
|
|
6960
7309
|
return undefined;
|
|
6961
7310
|
}
|
|
6962
7311
|
|
|
6963
|
-
await mkdir(path.dirname(listenersPath), { recursive: true });
|
|
6964
|
-
await writeFile(listenersPath, nextListeners);
|
|
6965
|
-
if (nextProviders !== originalProviders) {
|
|
6966
|
-
await writeFile(providersPath, nextProviders);
|
|
6967
|
-
}
|
|
6968
7312
|
return {
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
7313
|
+
id: "listeners.register-missing",
|
|
7314
|
+
fixes: [
|
|
7315
|
+
{
|
|
7316
|
+
code: "BEIGNET_LISTENER_UNREGISTERED",
|
|
7317
|
+
file: listenersFile,
|
|
7318
|
+
message: `Registered ${candidateNames.join(", ")} in ${listenersFile} and wired listeners with registerListeners(...) in ${providersFile}.`,
|
|
7319
|
+
},
|
|
7320
|
+
],
|
|
7321
|
+
changes: [
|
|
7322
|
+
...(nextListeners === originalListeners
|
|
7323
|
+
? []
|
|
7324
|
+
: [
|
|
7325
|
+
planDoctorFixFileChange({
|
|
7326
|
+
file: listenersFile,
|
|
7327
|
+
before: originalListeners,
|
|
7328
|
+
after: nextListeners,
|
|
7329
|
+
}),
|
|
7330
|
+
]),
|
|
7331
|
+
...(nextProviders === originalProviders
|
|
7332
|
+
? []
|
|
7333
|
+
: [
|
|
7334
|
+
planDoctorFixFileChange({
|
|
7335
|
+
file: providersFile,
|
|
7336
|
+
before: originalProviders,
|
|
7337
|
+
after: nextProviders,
|
|
7338
|
+
}),
|
|
7339
|
+
]),
|
|
7340
|
+
],
|
|
6972
7341
|
};
|
|
6973
7342
|
}
|
|
6974
7343
|
|
|
6975
|
-
async function
|
|
7344
|
+
async function planUnregisteredOutboxEntries(
|
|
6976
7345
|
targetDir: string,
|
|
6977
7346
|
files: string[],
|
|
6978
7347
|
drift: WorkflowRegistrationDrift,
|
|
6979
7348
|
config: ResolvedBeignetConfig,
|
|
6980
|
-
): Promise<
|
|
6981
|
-
if (!drift.outbox.exists) return
|
|
7349
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
7350
|
+
if (!drift.outbox.exists) return undefined;
|
|
7351
|
+
|
|
7352
|
+
const outboxPath = path.join(targetDir, config.paths.outbox);
|
|
7353
|
+
let original: string;
|
|
7354
|
+
try {
|
|
7355
|
+
original = await readFile(outboxPath, "utf8");
|
|
7356
|
+
} catch {
|
|
7357
|
+
return undefined;
|
|
7358
|
+
}
|
|
6982
7359
|
|
|
7360
|
+
let next = original;
|
|
6983
7361
|
const fixes: InspectFix[] = [];
|
|
6984
7362
|
|
|
6985
|
-
const eventFix =
|
|
6986
|
-
targetDir,
|
|
7363
|
+
const eventFix = planWorkflowRegistryEdit({
|
|
6987
7364
|
files,
|
|
6988
7365
|
centralFile: config.paths.outbox,
|
|
7366
|
+
source: next,
|
|
6989
7367
|
unregistered: drift.outbox.events,
|
|
6990
7368
|
code: "BEIGNET_OUTBOX_EVENT_UNREGISTERED",
|
|
6991
7369
|
listName: "the outbox events list",
|
|
@@ -6993,12 +7371,15 @@ async function fixUnregisteredOutboxEntries(
|
|
|
6993
7371
|
append: (source, entry, importLine) =>
|
|
6994
7372
|
appendToOutboxRegistryArray(source, "events", entry, importLine),
|
|
6995
7373
|
});
|
|
6996
|
-
if (eventFix)
|
|
7374
|
+
if (eventFix) {
|
|
7375
|
+
next = eventFix.after;
|
|
7376
|
+
fixes.push(eventFix.fix);
|
|
7377
|
+
}
|
|
6997
7378
|
|
|
6998
|
-
const jobFix =
|
|
6999
|
-
targetDir,
|
|
7379
|
+
const jobFix = planWorkflowRegistryEdit({
|
|
7000
7380
|
files,
|
|
7001
7381
|
centralFile: config.paths.outbox,
|
|
7382
|
+
source: next,
|
|
7002
7383
|
unregistered: drift.outbox.jobs,
|
|
7003
7384
|
code: "BEIGNET_OUTBOX_JOB_UNREGISTERED",
|
|
7004
7385
|
listName: "the outbox jobs list",
|
|
@@ -7006,9 +7387,23 @@ async function fixUnregisteredOutboxEntries(
|
|
|
7006
7387
|
append: (source, entry, importLine) =>
|
|
7007
7388
|
appendToOutboxRegistryArray(source, "jobs", entry, importLine),
|
|
7008
7389
|
});
|
|
7009
|
-
if (jobFix)
|
|
7390
|
+
if (jobFix) {
|
|
7391
|
+
next = jobFix.after;
|
|
7392
|
+
fixes.push(jobFix.fix);
|
|
7393
|
+
}
|
|
7010
7394
|
|
|
7011
|
-
return
|
|
7395
|
+
if (fixes.length === 0 || next === original) return undefined;
|
|
7396
|
+
return {
|
|
7397
|
+
id: "outbox.register-missing",
|
|
7398
|
+
fixes,
|
|
7399
|
+
changes: [
|
|
7400
|
+
planDoctorFixFileChange({
|
|
7401
|
+
file: config.paths.outbox,
|
|
7402
|
+
before: original,
|
|
7403
|
+
after: next,
|
|
7404
|
+
}),
|
|
7405
|
+
],
|
|
7406
|
+
};
|
|
7012
7407
|
}
|
|
7013
7408
|
|
|
7014
7409
|
/**
|
|
@@ -7020,7 +7415,7 @@ async function fixUnregisteredOutboxEntries(
|
|
|
7020
7415
|
* some members are already individually registered (appending the registry
|
|
7021
7416
|
* spread would run those members twice). The diagnostic stays in that case.
|
|
7022
7417
|
*/
|
|
7023
|
-
|
|
7418
|
+
type WorkflowRegistryPlanOptions = {
|
|
7024
7419
|
targetDir: string;
|
|
7025
7420
|
files: string[];
|
|
7026
7421
|
centralFile: string;
|
|
@@ -7029,12 +7424,12 @@ async function applyWorkflowRegistryFix(options: {
|
|
|
7029
7424
|
listName: string;
|
|
7030
7425
|
importSpecifier: (indexFile: string) => string;
|
|
7031
7426
|
append: (source: string, entry: string, importLine?: string) => AppendResult;
|
|
7032
|
-
}
|
|
7033
|
-
const candidates = options.unregistered.filter(
|
|
7034
|
-
(entry) => entry.fullyUnregistered,
|
|
7035
|
-
);
|
|
7036
|
-
if (candidates.length === 0) return undefined;
|
|
7427
|
+
};
|
|
7037
7428
|
|
|
7429
|
+
async function planWorkflowRegistryOperation(
|
|
7430
|
+
id: DoctorFixOperationId,
|
|
7431
|
+
options: WorkflowRegistryPlanOptions,
|
|
7432
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
7038
7433
|
const centralPath = path.join(options.targetDir, options.centralFile);
|
|
7039
7434
|
let original: string;
|
|
7040
7435
|
try {
|
|
@@ -7043,7 +7438,30 @@ async function applyWorkflowRegistryFix(options: {
|
|
|
7043
7438
|
return undefined;
|
|
7044
7439
|
}
|
|
7045
7440
|
|
|
7046
|
-
|
|
7441
|
+
const edit = planWorkflowRegistryEdit({ ...options, source: original });
|
|
7442
|
+
if (!edit) return undefined;
|
|
7443
|
+
return {
|
|
7444
|
+
id,
|
|
7445
|
+
fixes: [edit.fix],
|
|
7446
|
+
changes: [
|
|
7447
|
+
planDoctorFixFileChange({
|
|
7448
|
+
file: options.centralFile,
|
|
7449
|
+
before: original,
|
|
7450
|
+
after: edit.after,
|
|
7451
|
+
}),
|
|
7452
|
+
],
|
|
7453
|
+
};
|
|
7454
|
+
}
|
|
7455
|
+
|
|
7456
|
+
function planWorkflowRegistryEdit(
|
|
7457
|
+
options: Omit<WorkflowRegistryPlanOptions, "targetDir"> & { source: string },
|
|
7458
|
+
): { after: string; fix: InspectFix } | undefined {
|
|
7459
|
+
const candidates = options.unregistered.filter(
|
|
7460
|
+
(entry) => entry.fullyUnregistered,
|
|
7461
|
+
);
|
|
7462
|
+
if (candidates.length === 0) return undefined;
|
|
7463
|
+
|
|
7464
|
+
let next = options.source;
|
|
7047
7465
|
const registeredNames: string[] = [];
|
|
7048
7466
|
|
|
7049
7467
|
for (const { registry } of candidates) {
|
|
@@ -7075,13 +7493,15 @@ async function applyWorkflowRegistryFix(options: {
|
|
|
7075
7493
|
}
|
|
7076
7494
|
}
|
|
7077
7495
|
|
|
7078
|
-
if (next ===
|
|
7496
|
+
if (next === options.source || registeredNames.length === 0) return undefined;
|
|
7079
7497
|
|
|
7080
|
-
await writeFile(centralPath, next);
|
|
7081
7498
|
return {
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7499
|
+
after: next,
|
|
7500
|
+
fix: {
|
|
7501
|
+
code: options.code,
|
|
7502
|
+
file: options.centralFile,
|
|
7503
|
+
message: `Registered ${registeredNames.join(", ")} in ${options.listName}.`,
|
|
7504
|
+
},
|
|
7085
7505
|
};
|
|
7086
7506
|
}
|
|
7087
7507
|
|