@beignet/cli 0.0.41 → 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 +10 -0
- package/README.md +77 -21
- 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 +18 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +20 -7
- 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 +3 -1
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +136 -42
- package/dist/explain.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +92 -25
- 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 +353 -116
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +6 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +3 -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/mcp.d.ts.map +1 -1
- package/dist/mcp.js +121 -13
- package/dist/mcp.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +26 -10
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +3 -3
- package/dist/templates/base.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 +7 -4
- package/dist/templates/shared.js.map +1 -1
- package/package.json +3 -2
- package/skills/app-structure/SKILL.md +34 -10
- package/src/choices.ts +57 -0
- package/src/db.ts +45 -15
- package/src/doctor-fixes.ts +252 -0
- package/src/explain.ts +151 -43
- package/src/index.ts +130 -35
- package/src/inspect.ts +497 -145
- package/src/lib.ts +28 -1
- package/src/make/shared.ts +3 -3
- package/src/mcp.ts +187 -13
- package/src/templates/agents.ts +26 -10
- package/src/templates/base.ts +3 -2
- package/src/templates/shared.ts +14 -6
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(
|
|
@@ -6020,8 +6207,7 @@ async function inspectPackageScripts(
|
|
|
6020
6207
|
severity: "warning",
|
|
6021
6208
|
code: "BEIGNET_PACKAGE_TEST_SCRIPT_MISSING",
|
|
6022
6209
|
file: "package.json",
|
|
6023
|
-
message:
|
|
6024
|
-
'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.`,
|
|
6025
6211
|
});
|
|
6026
6212
|
}
|
|
6027
6213
|
|
|
@@ -6033,7 +6219,7 @@ async function inspectPackageScripts(
|
|
|
6033
6219
|
severity: "warning",
|
|
6034
6220
|
code: "BEIGNET_PACKAGE_SCRIPT_LEGACY",
|
|
6035
6221
|
file: "package.json",
|
|
6036
|
-
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.`,
|
|
6037
6223
|
});
|
|
6038
6224
|
}
|
|
6039
6225
|
|
|
@@ -6067,11 +6253,11 @@ async function inspectPackageScripts(
|
|
|
6067
6253
|
return diagnostics;
|
|
6068
6254
|
}
|
|
6069
6255
|
|
|
6070
|
-
async function
|
|
6256
|
+
async function planPackageScripts(
|
|
6071
6257
|
targetDir: string,
|
|
6072
6258
|
files: string[],
|
|
6073
6259
|
convention: InspectConvention,
|
|
6074
|
-
): Promise<
|
|
6260
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6075
6261
|
if (!convention.resourceGenerator || !files.includes("package.json")) {
|
|
6076
6262
|
return undefined;
|
|
6077
6263
|
}
|
|
@@ -6097,10 +6283,8 @@ async function fixPackageScripts(
|
|
|
6097
6283
|
}
|
|
6098
6284
|
|
|
6099
6285
|
if (!hasDirectBunTestImports) {
|
|
6100
|
-
for (const
|
|
6101
|
-
|
|
6102
|
-
)) {
|
|
6103
|
-
if (packageJson.scripts[name] === legacyCommand) {
|
|
6286
|
+
for (const name of Object.keys(legacyGeneratedPackageScripts)) {
|
|
6287
|
+
if (isLegacyGeneratedPackageScript(name, packageJson.scripts[name])) {
|
|
6104
6288
|
packageJson.scripts[name] = currentGeneratedPackageScripts[name];
|
|
6105
6289
|
if (!updatedScripts.includes(name)) updatedScripts.push(name);
|
|
6106
6290
|
}
|
|
@@ -6118,29 +6302,25 @@ async function fixPackageScripts(
|
|
|
6118
6302
|
}
|
|
6119
6303
|
|
|
6120
6304
|
const next = `${JSON.stringify(packageJson, null, "\t")}\n`;
|
|
6121
|
-
const createdSupportFiles =
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
);
|
|
6305
|
+
const createdSupportFiles =
|
|
6306
|
+
packageJson.scripts.test === currentGeneratedPackageScripts.test
|
|
6307
|
+
? missingTestSupportFiles(files)
|
|
6308
|
+
: [];
|
|
6126
6309
|
|
|
6127
6310
|
if (next === original && createdSupportFiles.length === 0) return undefined;
|
|
6128
6311
|
|
|
6129
|
-
|
|
6130
|
-
|
|
6312
|
+
let fix: InspectFix | undefined;
|
|
6131
6313
|
if (addedTestScript) {
|
|
6132
|
-
|
|
6314
|
+
fix = {
|
|
6133
6315
|
code: "BEIGNET_PACKAGE_TEST_SCRIPT_MISSING",
|
|
6134
6316
|
file: "package.json",
|
|
6135
6317
|
message:
|
|
6136
6318
|
createdSupportFiles.length > 0
|
|
6137
|
-
? `Added "test": "
|
|
6138
|
-
:
|
|
6319
|
+
? `Added "test": "${currentGeneratedPackageScripts.test}" and ${createdSupportFiles.join(", ")}.`
|
|
6320
|
+
: `Added "test": "${currentGeneratedPackageScripts.test}".`,
|
|
6139
6321
|
};
|
|
6140
|
-
}
|
|
6141
|
-
|
|
6142
|
-
if (updatedScripts.length > 0) {
|
|
6143
|
-
return {
|
|
6322
|
+
} else if (updatedScripts.length > 0) {
|
|
6323
|
+
fix = {
|
|
6144
6324
|
code: "BEIGNET_PACKAGE_SCRIPT_LEGACY",
|
|
6145
6325
|
file: "package.json",
|
|
6146
6326
|
message: packageScriptFixMessage(
|
|
@@ -6149,31 +6329,48 @@ async function fixPackageScripts(
|
|
|
6149
6329
|
addedTsx,
|
|
6150
6330
|
),
|
|
6151
6331
|
};
|
|
6152
|
-
}
|
|
6153
|
-
|
|
6154
|
-
if (createdSupportFiles.length > 0) {
|
|
6155
|
-
return {
|
|
6332
|
+
} else if (createdSupportFiles.length > 0) {
|
|
6333
|
+
fix = {
|
|
6156
6334
|
code: "BEIGNET_PACKAGE_TEST_SUPPORT_MISSING",
|
|
6157
6335
|
file: "package.json",
|
|
6158
6336
|
message: `Restored ${createdSupportFiles.join(", ")}.`,
|
|
6159
6337
|
};
|
|
6160
|
-
}
|
|
6161
|
-
|
|
6162
|
-
if (addedTsx) {
|
|
6163
|
-
return {
|
|
6338
|
+
} else if (addedTsx) {
|
|
6339
|
+
fix = {
|
|
6164
6340
|
code: "BEIGNET_PACKAGE_TSX_DEPENDENCY_MISSING",
|
|
6165
6341
|
file: "package.json",
|
|
6166
6342
|
message: 'Added "tsx" to devDependencies.',
|
|
6167
6343
|
};
|
|
6168
6344
|
}
|
|
6169
6345
|
|
|
6170
|
-
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
|
+
};
|
|
6171
6368
|
}
|
|
6172
6369
|
|
|
6173
6370
|
function legacyGeneratedScriptNames(scripts: Record<string, string>): string[] {
|
|
6174
|
-
return Object.
|
|
6175
|
-
|
|
6176
|
-
|
|
6371
|
+
return Object.keys(legacyGeneratedPackageScripts).filter((name) =>
|
|
6372
|
+
isLegacyGeneratedPackageScript(name, scripts[name]),
|
|
6373
|
+
);
|
|
6177
6374
|
}
|
|
6178
6375
|
|
|
6179
6376
|
async function appUsesDirectBunTestImports(
|
|
@@ -6194,7 +6391,8 @@ function hasCurrentGeneratedTsxScript(
|
|
|
6194
6391
|
): boolean {
|
|
6195
6392
|
return Object.values(currentGeneratedPackageScripts).some(
|
|
6196
6393
|
(command) =>
|
|
6197
|
-
command.startsWith("tsx ")
|
|
6394
|
+
(command.startsWith("tsx ") || command.includes("--import tsx")) &&
|
|
6395
|
+
Object.values(scripts).includes(command),
|
|
6198
6396
|
);
|
|
6199
6397
|
}
|
|
6200
6398
|
|
|
@@ -6204,23 +6402,6 @@ function missingTestSupportFiles(files: string[]): string[] {
|
|
|
6204
6402
|
.filter((file) => !files.includes(file));
|
|
6205
6403
|
}
|
|
6206
6404
|
|
|
6207
|
-
async function createMissingTestSupportFiles(
|
|
6208
|
-
targetDir: string,
|
|
6209
|
-
files: string[],
|
|
6210
|
-
shouldCreate: boolean,
|
|
6211
|
-
): Promise<string[]> {
|
|
6212
|
-
if (!shouldCreate) return [];
|
|
6213
|
-
const createdSupportFiles: string[] = [];
|
|
6214
|
-
for (const file of testSupportTemplateFiles) {
|
|
6215
|
-
if (files.includes(file.path)) continue;
|
|
6216
|
-
const destination = path.join(targetDir, file.path);
|
|
6217
|
-
await mkdir(path.dirname(destination), { recursive: true });
|
|
6218
|
-
await writeFile(destination, file.content);
|
|
6219
|
-
createdSupportFiles.push(file.path);
|
|
6220
|
-
}
|
|
6221
|
-
return createdSupportFiles;
|
|
6222
|
-
}
|
|
6223
|
-
|
|
6224
6405
|
function packageScriptFixMessage(
|
|
6225
6406
|
updatedScripts: string[],
|
|
6226
6407
|
createdSupportFiles: string[],
|
|
@@ -6727,14 +6908,14 @@ function findRouteGroupContract(
|
|
|
6727
6908
|
);
|
|
6728
6909
|
}
|
|
6729
6910
|
|
|
6730
|
-
async function
|
|
6911
|
+
async function planDirectOpenApiArrayDrift(
|
|
6731
6912
|
targetDir: string,
|
|
6732
6913
|
files: string[],
|
|
6733
6914
|
config: ResolvedBeignetConfig,
|
|
6734
6915
|
convention: InspectConvention,
|
|
6735
6916
|
contracts: InspectedContract[],
|
|
6736
6917
|
matchedRoutes: MatchedRoutesResult,
|
|
6737
|
-
): Promise<
|
|
6918
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6738
6919
|
const routePath = path.join(targetDir, config.paths.openapiRoute);
|
|
6739
6920
|
let source: string;
|
|
6740
6921
|
try {
|
|
@@ -6777,21 +6958,32 @@ async function fixDirectOpenApiArrayDrift(
|
|
|
6777
6958
|
)}`;
|
|
6778
6959
|
if (next === source) return undefined;
|
|
6779
6960
|
|
|
6780
|
-
await writeFile(routePath, next);
|
|
6781
6961
|
return {
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
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
|
+
],
|
|
6787
6979
|
};
|
|
6788
6980
|
}
|
|
6789
6981
|
|
|
6790
|
-
async function
|
|
6982
|
+
async function planUnregisteredRouteGroups(
|
|
6791
6983
|
targetDir: string,
|
|
6792
6984
|
files: string[],
|
|
6793
6985
|
config: ResolvedBeignetConfig,
|
|
6794
|
-
): Promise<
|
|
6986
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6795
6987
|
if (!files.includes(config.paths.server)) return undefined;
|
|
6796
6988
|
|
|
6797
6989
|
const routeGroups = await readFeatureRouteGroups(targetDir, files, config);
|
|
@@ -6862,25 +7054,36 @@ async function fixUnregisteredRouteGroups(
|
|
|
6862
7054
|
)}`;
|
|
6863
7055
|
if (next === original) return undefined;
|
|
6864
7056
|
|
|
6865
|
-
await writeFile(targetPath, next);
|
|
6866
7057
|
return {
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
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
|
+
],
|
|
6872
7075
|
};
|
|
6873
7076
|
}
|
|
6874
7077
|
|
|
6875
|
-
async function
|
|
7078
|
+
async function planUnregisteredSchedules(
|
|
6876
7079
|
targetDir: string,
|
|
6877
7080
|
files: string[],
|
|
6878
7081
|
drift: WorkflowRegistrationDrift,
|
|
6879
7082
|
config: ResolvedBeignetConfig,
|
|
6880
|
-
): Promise<
|
|
7083
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6881
7084
|
if (!drift.schedules.centralExists) return undefined;
|
|
6882
7085
|
|
|
6883
|
-
return
|
|
7086
|
+
return planWorkflowRegistryOperation("schedules.register-missing", {
|
|
6884
7087
|
targetDir,
|
|
6885
7088
|
files,
|
|
6886
7089
|
centralFile: config.paths.schedules,
|
|
@@ -6893,15 +7096,15 @@ async function fixUnregisteredSchedules(
|
|
|
6893
7096
|
});
|
|
6894
7097
|
}
|
|
6895
7098
|
|
|
6896
|
-
async function
|
|
7099
|
+
async function planUnregisteredTasks(
|
|
6897
7100
|
targetDir: string,
|
|
6898
7101
|
files: string[],
|
|
6899
7102
|
drift: WorkflowRegistrationDrift,
|
|
6900
7103
|
config: ResolvedBeignetConfig,
|
|
6901
|
-
): Promise<
|
|
7104
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6902
7105
|
if (!drift.tasks.centralExists) return undefined;
|
|
6903
7106
|
|
|
6904
|
-
return
|
|
7107
|
+
return planWorkflowRegistryOperation("tasks.register-missing", {
|
|
6905
7108
|
targetDir,
|
|
6906
7109
|
files,
|
|
6907
7110
|
centralFile: config.paths.tasks,
|
|
@@ -6915,6 +7118,84 @@ async function fixUnregisteredTasks(
|
|
|
6915
7118
|
});
|
|
6916
7119
|
}
|
|
6917
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
|
+
|
|
6918
7199
|
/**
|
|
6919
7200
|
* Wire fully unregistered feature listener registries into the central
|
|
6920
7201
|
* listener registry and ensure the providers file registers that central
|
|
@@ -6926,12 +7207,12 @@ async function fixUnregisteredTasks(
|
|
|
6926
7207
|
* register against, or when the registry name is already imported from another
|
|
6927
7208
|
* file. The diagnostic stays in every bail case.
|
|
6928
7209
|
*/
|
|
6929
|
-
async function
|
|
7210
|
+
async function planUnregisteredListeners(
|
|
6930
7211
|
targetDir: string,
|
|
6931
7212
|
files: string[],
|
|
6932
7213
|
drift: WorkflowRegistrationDrift,
|
|
6933
7214
|
config: ResolvedBeignetConfig,
|
|
6934
|
-
): Promise<
|
|
7215
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
6935
7216
|
const candidates = drift.listeners.unregistered.filter(
|
|
6936
7217
|
(entry) => entry.fullyUnregistered,
|
|
6937
7218
|
);
|
|
@@ -7028,32 +7309,61 @@ async function fixUnregisteredListeners(
|
|
|
7028
7309
|
return undefined;
|
|
7029
7310
|
}
|
|
7030
7311
|
|
|
7031
|
-
await mkdir(path.dirname(listenersPath), { recursive: true });
|
|
7032
|
-
await writeFile(listenersPath, nextListeners);
|
|
7033
|
-
if (nextProviders !== originalProviders) {
|
|
7034
|
-
await writeFile(providersPath, nextProviders);
|
|
7035
|
-
}
|
|
7036
7312
|
return {
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
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
|
+
],
|
|
7040
7341
|
};
|
|
7041
7342
|
}
|
|
7042
7343
|
|
|
7043
|
-
async function
|
|
7344
|
+
async function planUnregisteredOutboxEntries(
|
|
7044
7345
|
targetDir: string,
|
|
7045
7346
|
files: string[],
|
|
7046
7347
|
drift: WorkflowRegistrationDrift,
|
|
7047
7348
|
config: ResolvedBeignetConfig,
|
|
7048
|
-
): Promise<
|
|
7049
|
-
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
|
+
}
|
|
7050
7359
|
|
|
7360
|
+
let next = original;
|
|
7051
7361
|
const fixes: InspectFix[] = [];
|
|
7052
7362
|
|
|
7053
|
-
const eventFix =
|
|
7054
|
-
targetDir,
|
|
7363
|
+
const eventFix = planWorkflowRegistryEdit({
|
|
7055
7364
|
files,
|
|
7056
7365
|
centralFile: config.paths.outbox,
|
|
7366
|
+
source: next,
|
|
7057
7367
|
unregistered: drift.outbox.events,
|
|
7058
7368
|
code: "BEIGNET_OUTBOX_EVENT_UNREGISTERED",
|
|
7059
7369
|
listName: "the outbox events list",
|
|
@@ -7061,12 +7371,15 @@ async function fixUnregisteredOutboxEntries(
|
|
|
7061
7371
|
append: (source, entry, importLine) =>
|
|
7062
7372
|
appendToOutboxRegistryArray(source, "events", entry, importLine),
|
|
7063
7373
|
});
|
|
7064
|
-
if (eventFix)
|
|
7374
|
+
if (eventFix) {
|
|
7375
|
+
next = eventFix.after;
|
|
7376
|
+
fixes.push(eventFix.fix);
|
|
7377
|
+
}
|
|
7065
7378
|
|
|
7066
|
-
const jobFix =
|
|
7067
|
-
targetDir,
|
|
7379
|
+
const jobFix = planWorkflowRegistryEdit({
|
|
7068
7380
|
files,
|
|
7069
7381
|
centralFile: config.paths.outbox,
|
|
7382
|
+
source: next,
|
|
7070
7383
|
unregistered: drift.outbox.jobs,
|
|
7071
7384
|
code: "BEIGNET_OUTBOX_JOB_UNREGISTERED",
|
|
7072
7385
|
listName: "the outbox jobs list",
|
|
@@ -7074,9 +7387,23 @@ async function fixUnregisteredOutboxEntries(
|
|
|
7074
7387
|
append: (source, entry, importLine) =>
|
|
7075
7388
|
appendToOutboxRegistryArray(source, "jobs", entry, importLine),
|
|
7076
7389
|
});
|
|
7077
|
-
if (jobFix)
|
|
7390
|
+
if (jobFix) {
|
|
7391
|
+
next = jobFix.after;
|
|
7392
|
+
fixes.push(jobFix.fix);
|
|
7393
|
+
}
|
|
7078
7394
|
|
|
7079
|
-
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
|
+
};
|
|
7080
7407
|
}
|
|
7081
7408
|
|
|
7082
7409
|
/**
|
|
@@ -7088,7 +7415,7 @@ async function fixUnregisteredOutboxEntries(
|
|
|
7088
7415
|
* some members are already individually registered (appending the registry
|
|
7089
7416
|
* spread would run those members twice). The diagnostic stays in that case.
|
|
7090
7417
|
*/
|
|
7091
|
-
|
|
7418
|
+
type WorkflowRegistryPlanOptions = {
|
|
7092
7419
|
targetDir: string;
|
|
7093
7420
|
files: string[];
|
|
7094
7421
|
centralFile: string;
|
|
@@ -7097,12 +7424,12 @@ async function applyWorkflowRegistryFix(options: {
|
|
|
7097
7424
|
listName: string;
|
|
7098
7425
|
importSpecifier: (indexFile: string) => string;
|
|
7099
7426
|
append: (source: string, entry: string, importLine?: string) => AppendResult;
|
|
7100
|
-
}
|
|
7101
|
-
const candidates = options.unregistered.filter(
|
|
7102
|
-
(entry) => entry.fullyUnregistered,
|
|
7103
|
-
);
|
|
7104
|
-
if (candidates.length === 0) return undefined;
|
|
7427
|
+
};
|
|
7105
7428
|
|
|
7429
|
+
async function planWorkflowRegistryOperation(
|
|
7430
|
+
id: DoctorFixOperationId,
|
|
7431
|
+
options: WorkflowRegistryPlanOptions,
|
|
7432
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
7106
7433
|
const centralPath = path.join(options.targetDir, options.centralFile);
|
|
7107
7434
|
let original: string;
|
|
7108
7435
|
try {
|
|
@@ -7111,7 +7438,30 @@ async function applyWorkflowRegistryFix(options: {
|
|
|
7111
7438
|
return undefined;
|
|
7112
7439
|
}
|
|
7113
7440
|
|
|
7114
|
-
|
|
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;
|
|
7115
7465
|
const registeredNames: string[] = [];
|
|
7116
7466
|
|
|
7117
7467
|
for (const { registry } of candidates) {
|
|
@@ -7143,13 +7493,15 @@ async function applyWorkflowRegistryFix(options: {
|
|
|
7143
7493
|
}
|
|
7144
7494
|
}
|
|
7145
7495
|
|
|
7146
|
-
if (next ===
|
|
7496
|
+
if (next === options.source || registeredNames.length === 0) return undefined;
|
|
7147
7497
|
|
|
7148
|
-
await writeFile(centralPath, next);
|
|
7149
7498
|
return {
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7499
|
+
after: next,
|
|
7500
|
+
fix: {
|
|
7501
|
+
code: options.code,
|
|
7502
|
+
file: options.centralFile,
|
|
7503
|
+
message: `Registered ${registeredNames.join(", ")} in ${options.listName}.`,
|
|
7504
|
+
},
|
|
7153
7505
|
};
|
|
7154
7506
|
}
|
|
7155
7507
|
|