@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/dist/inspect.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
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";
|
|
5
5
|
import { clientDirPath, clientFormsPath, clientIndexPath, defaultBeignetConfig, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
|
|
6
|
+
import { applyPlannedDoctorFixes, createDoctorFixPlan, planDoctorFixFileChange, publicDoctorFixPlan, } from "./doctor-fixes.js";
|
|
6
7
|
import { formatGithubAnnotation, } from "./github-annotations.js";
|
|
7
8
|
import { createListenersRegistrySource, listenersFilePath, providersFilePath, updateListenersRegistrySource, wireListenersProviderSource, } from "./make/shared.js";
|
|
8
9
|
import { detectedProviderVariants, diagnosticPackageJsonFile, formatProviderMetadataIssues, installedPackageNames, providerDoctorRulesForInstalledPackages, providerOperationalConfigFiles, providerRequiredEnvExists, providerRequiredTableExists, providerVariantRegistrationSeverity, readProviderListEntries, readProviderPackageMetadataSource, registrationTokenHint, serverProviderFiles, } from "./provider-audit.js";
|
|
9
10
|
import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, outboxRegistryValueInfo, } from "./registry-edits.js";
|
|
10
|
-
import { currentGeneratedPackageScripts, externalVersions, legacyGeneratedPackageScripts, } from "./templates/shared.js";
|
|
11
|
+
import { currentGeneratedPackageScripts, externalVersions, isLegacyGeneratedPackageScript, legacyGeneratedPackageScripts, } from "./templates/shared.js";
|
|
11
12
|
import { testSupportTemplateFiles } from "./templates/testing.js";
|
|
12
13
|
import { getCliVersion } from "./version.js";
|
|
13
14
|
export async function inspectApp(options = {}) {
|
|
@@ -33,7 +34,40 @@ export async function inspectApp(options = {}) {
|
|
|
33
34
|
fixes: [],
|
|
34
35
|
};
|
|
35
36
|
}
|
|
37
|
+
/** Plan every currently available low-risk doctor repair without writing. */
|
|
38
|
+
export async function planDoctorFixes(options = {}) {
|
|
39
|
+
const plan = publicDoctorFixPlan(await buildDoctorFixPlan(options));
|
|
40
|
+
const inspected = await inspectApp(options);
|
|
41
|
+
return {
|
|
42
|
+
...plan,
|
|
43
|
+
convention: inspected.convention,
|
|
44
|
+
diagnostics: inspected.diagnostics,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Apply a current doctor fix plan after verifying its plan ID. */
|
|
48
|
+
export async function applyDoctorFixPlan(options) {
|
|
49
|
+
const plan = await buildDoctorFixPlan(options);
|
|
50
|
+
if (plan.planId !== options.planId) {
|
|
51
|
+
throw new Error("Doctor fix plan is stale because the available repairs changed. No files were changed; run beignet doctor --fix --dry-run again.");
|
|
52
|
+
}
|
|
53
|
+
return applyPlannedDoctorFixes(plan, options.fixIds);
|
|
54
|
+
}
|
|
36
55
|
export async function applyDoctorFixes(options = {}) {
|
|
56
|
+
return (await applyDoctorFixesWithResult(options)).fixes;
|
|
57
|
+
}
|
|
58
|
+
export async function applyDoctorFixesWithResult(options = {}) {
|
|
59
|
+
const plan = await buildDoctorFixPlan(options);
|
|
60
|
+
return applyPlannedDoctorFixes(plan);
|
|
61
|
+
}
|
|
62
|
+
export function createDoctorFixInspectionResult(inspected, applied) {
|
|
63
|
+
return {
|
|
64
|
+
...inspected,
|
|
65
|
+
planId: applied.planId,
|
|
66
|
+
operationIds: applied.operationIds,
|
|
67
|
+
fixes: applied.fixes,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function buildDoctorFixPlan(options = {}) {
|
|
37
71
|
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
38
72
|
await assertDirectory(targetDir);
|
|
39
73
|
const files = await listFiles(targetDir);
|
|
@@ -43,28 +77,41 @@ export async function applyDoctorFixes(options = {}) {
|
|
|
43
77
|
const convention = inspectConvention(files, config);
|
|
44
78
|
const contracts = await readContracts(targetDir, files, config);
|
|
45
79
|
const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts);
|
|
46
|
-
const
|
|
47
|
-
const packageFix = await
|
|
80
|
+
const operations = [];
|
|
81
|
+
const packageFix = await planPackageScripts(targetDir, files, convention);
|
|
48
82
|
if (packageFix)
|
|
49
|
-
|
|
50
|
-
const routeGroupFix = await
|
|
83
|
+
operations.push(packageFix);
|
|
84
|
+
const routeGroupFix = await planUnregisteredRouteGroups(targetDir, files, config);
|
|
51
85
|
if (routeGroupFix)
|
|
52
|
-
|
|
86
|
+
operations.push(routeGroupFix);
|
|
53
87
|
const drift = await workflowRegistrationDrift(targetDir, files, config);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
if (config.paths.schedules === config.paths.tasks) {
|
|
89
|
+
const workflowFix = await planCoLocatedScheduleAndTaskRegistries(targetDir, files, drift, config);
|
|
90
|
+
if (workflowFix)
|
|
91
|
+
operations.push(workflowFix);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const scheduleFix = await planUnregisteredSchedules(targetDir, files, drift, config);
|
|
95
|
+
if (scheduleFix)
|
|
96
|
+
operations.push(scheduleFix);
|
|
97
|
+
const taskFix = await planUnregisteredTasks(targetDir, files, drift, config);
|
|
98
|
+
if (taskFix)
|
|
99
|
+
operations.push(taskFix);
|
|
100
|
+
}
|
|
101
|
+
const outboxFix = await planUnregisteredOutboxEntries(targetDir, files, drift, config);
|
|
102
|
+
if (outboxFix)
|
|
103
|
+
operations.push(outboxFix);
|
|
104
|
+
const listenerFix = await planUnregisteredListeners(targetDir, files, drift, config);
|
|
62
105
|
if (listenerFix)
|
|
63
|
-
|
|
64
|
-
const openApiFix = await
|
|
106
|
+
operations.push(listenerFix);
|
|
107
|
+
const openApiFix = await planDirectOpenApiArrayDrift(targetDir, files, config, convention, contracts, matchedRoutes);
|
|
65
108
|
if (openApiFix)
|
|
66
|
-
|
|
67
|
-
return
|
|
109
|
+
operations.push(openApiFix);
|
|
110
|
+
return createDoctorFixPlan({
|
|
111
|
+
targetDir,
|
|
112
|
+
strict: Boolean(options.strict),
|
|
113
|
+
operations,
|
|
114
|
+
});
|
|
68
115
|
}
|
|
69
116
|
/**
|
|
70
117
|
* Format inspected routes as a CLI table.
|
|
@@ -88,10 +135,54 @@ export function formatRoutes(result) {
|
|
|
88
135
|
...rows.map((row) => [row.method, row.path, row.contract, row.handler]),
|
|
89
136
|
]);
|
|
90
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Format a read-only doctor fix plan with exact patches and apply commands.
|
|
140
|
+
*/
|
|
141
|
+
export function formatDoctorFixPlan(plan, options = {}) {
|
|
142
|
+
const paint = createPainter(options.color);
|
|
143
|
+
const lines = [];
|
|
144
|
+
if (plan.operations.length === 0) {
|
|
145
|
+
lines.push(`No applicable Beignet fixes found in ${plan.targetDir}.`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const strictFlag = plan.strict ? " --strict" : "";
|
|
149
|
+
lines.push(`Planned ${plan.operations.length} Beignet fix operation${plan.operations.length === 1 ? "" : "s"} in ${plan.targetDir}:`, "");
|
|
150
|
+
for (const operation of plan.operations) {
|
|
151
|
+
lines.push(`${paint("PLAN", "cyan")} ${operation.id}`, ...operation.fixes.map((fix) => ` ${fix.code} ${fix.file}\n ${fix.message}`), ...operation.changes.flatMap((change) => [
|
|
152
|
+
` ${change.kind === "create" ? "Create" : "Update"} ${change.file}`,
|
|
153
|
+
change.patch.trimEnd(),
|
|
154
|
+
]), "");
|
|
155
|
+
}
|
|
156
|
+
lines.push(`Plan ID: ${plan.planId}`, "", "Apply the complete plan:", ` beignet doctor --fix${strictFlag} --plan ${plan.planId}`, "", "Apply selected operations:", ` beignet doctor --fix${strictFlag} --plan ${plan.planId} --only ${plan.operations.map((operation) => operation.id).join(",")}`);
|
|
157
|
+
}
|
|
158
|
+
lines.push("", formatDoctorResult({
|
|
159
|
+
targetDir: plan.targetDir,
|
|
160
|
+
strict: plan.strict,
|
|
161
|
+
diagnostics: plan.diagnostics,
|
|
162
|
+
fixes: [],
|
|
163
|
+
}, options));
|
|
164
|
+
return lines.join("\n");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Format planned fixes as GitHub Actions notice annotations.
|
|
168
|
+
*/
|
|
169
|
+
export function formatDoctorFixPlanGithub(plan) {
|
|
170
|
+
return [
|
|
171
|
+
...plan.operations.map((operation) => formatGithubAnnotation({
|
|
172
|
+
severity: "notice",
|
|
173
|
+
file: operation.changes[0]?.file,
|
|
174
|
+
message: `${operation.id}: ${operation.fixes.map((fix) => fix.message).join(" ")} Plan ${plan.planId}.`,
|
|
175
|
+
})),
|
|
176
|
+
...formatDoctorDiagnosticsGithub(plan.diagnostics),
|
|
177
|
+
].join("\n");
|
|
178
|
+
}
|
|
91
179
|
/**
|
|
92
180
|
* Format doctor diagnostics and applied fixes for CLI output.
|
|
93
181
|
*/
|
|
94
182
|
export function formatDoctor(result, options = {}) {
|
|
183
|
+
return formatDoctorResult(result, options);
|
|
184
|
+
}
|
|
185
|
+
function formatDoctorResult(result, options = {}) {
|
|
95
186
|
const paint = createPainter(options.color);
|
|
96
187
|
const fixLines = result.fixes.length === 0
|
|
97
188
|
? []
|
|
@@ -193,13 +284,14 @@ function formatDoctorFooter(result) {
|
|
|
193
284
|
* Format doctor diagnostics as GitHub Actions annotations.
|
|
194
285
|
*/
|
|
195
286
|
export function formatDoctorGithub(result) {
|
|
196
|
-
return result.diagnostics
|
|
197
|
-
|
|
287
|
+
return formatDoctorDiagnosticsGithub(result.diagnostics).join("\n");
|
|
288
|
+
}
|
|
289
|
+
function formatDoctorDiagnosticsGithub(diagnostics) {
|
|
290
|
+
return diagnostics.map((diagnostic) => formatGithubAnnotation({
|
|
198
291
|
severity: githubAnnotationSeverity(diagnostic.severity),
|
|
199
292
|
file: diagnostic.file,
|
|
200
293
|
message: `${diagnostic.code}: ${diagnostic.message}`,
|
|
201
|
-
}))
|
|
202
|
-
.join("\n");
|
|
294
|
+
}));
|
|
203
295
|
}
|
|
204
296
|
function githubAnnotationSeverity(severity) {
|
|
205
297
|
if (severity === "hint")
|
|
@@ -3724,7 +3816,7 @@ async function inspectPackageScripts(targetDir, files, convention) {
|
|
|
3724
3816
|
severity: "warning",
|
|
3725
3817
|
code: "BEIGNET_PACKAGE_TEST_SCRIPT_MISSING",
|
|
3726
3818
|
file: "package.json",
|
|
3727
|
-
message:
|
|
3819
|
+
message: `package.json does not define a test script. Add "test": "${currentGeneratedPackageScripts.test}" or run beignet doctor --fix.`,
|
|
3728
3820
|
});
|
|
3729
3821
|
}
|
|
3730
3822
|
const legacyScripts = hasDirectBunTestImports
|
|
@@ -3735,7 +3827,7 @@ async function inspectPackageScripts(targetDir, files, convention) {
|
|
|
3735
3827
|
severity: "warning",
|
|
3736
3828
|
code: "BEIGNET_PACKAGE_SCRIPT_LEGACY",
|
|
3737
3829
|
file: "package.json",
|
|
3738
|
-
message: `package.json still uses legacy generated
|
|
3830
|
+
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.`,
|
|
3739
3831
|
});
|
|
3740
3832
|
}
|
|
3741
3833
|
const needsTsx = hasCurrentGeneratedTsxScript(scripts) &&
|
|
@@ -3761,7 +3853,7 @@ async function inspectPackageScripts(targetDir, files, convention) {
|
|
|
3761
3853
|
}
|
|
3762
3854
|
return diagnostics;
|
|
3763
3855
|
}
|
|
3764
|
-
async function
|
|
3856
|
+
async function planPackageScripts(targetDir, files, convention) {
|
|
3765
3857
|
if (!convention.resourceGenerator || !files.includes("package.json")) {
|
|
3766
3858
|
return undefined;
|
|
3767
3859
|
}
|
|
@@ -3777,8 +3869,8 @@ async function fixPackageScripts(targetDir, files, convention) {
|
|
|
3777
3869
|
updatedScripts.push("test");
|
|
3778
3870
|
}
|
|
3779
3871
|
if (!hasDirectBunTestImports) {
|
|
3780
|
-
for (const
|
|
3781
|
-
if (packageJson.scripts[name]
|
|
3872
|
+
for (const name of Object.keys(legacyGeneratedPackageScripts)) {
|
|
3873
|
+
if (isLegacyGeneratedPackageScript(name, packageJson.scripts[name])) {
|
|
3782
3874
|
packageJson.scripts[name] = currentGeneratedPackageScripts[name];
|
|
3783
3875
|
if (!updatedScripts.includes(name))
|
|
3784
3876
|
updatedScripts.push(name);
|
|
@@ -3794,47 +3886,65 @@ async function fixPackageScripts(targetDir, files, convention) {
|
|
|
3794
3886
|
packageJson.devDependencies.tsx = externalVersions.tsx;
|
|
3795
3887
|
}
|
|
3796
3888
|
const next = `${JSON.stringify(packageJson, null, "\t")}\n`;
|
|
3797
|
-
const createdSupportFiles =
|
|
3889
|
+
const createdSupportFiles = packageJson.scripts.test === currentGeneratedPackageScripts.test
|
|
3890
|
+
? missingTestSupportFiles(files)
|
|
3891
|
+
: [];
|
|
3798
3892
|
if (next === original && createdSupportFiles.length === 0)
|
|
3799
3893
|
return undefined;
|
|
3800
|
-
|
|
3801
|
-
await writeFile(filePath, next);
|
|
3894
|
+
let fix;
|
|
3802
3895
|
if (addedTestScript) {
|
|
3803
|
-
|
|
3896
|
+
fix = {
|
|
3804
3897
|
code: "BEIGNET_PACKAGE_TEST_SCRIPT_MISSING",
|
|
3805
3898
|
file: "package.json",
|
|
3806
3899
|
message: createdSupportFiles.length > 0
|
|
3807
|
-
? `Added "test": "
|
|
3808
|
-
:
|
|
3900
|
+
? `Added "test": "${currentGeneratedPackageScripts.test}" and ${createdSupportFiles.join(", ")}.`
|
|
3901
|
+
: `Added "test": "${currentGeneratedPackageScripts.test}".`,
|
|
3809
3902
|
};
|
|
3810
3903
|
}
|
|
3811
|
-
if (updatedScripts.length > 0) {
|
|
3812
|
-
|
|
3904
|
+
else if (updatedScripts.length > 0) {
|
|
3905
|
+
fix = {
|
|
3813
3906
|
code: "BEIGNET_PACKAGE_SCRIPT_LEGACY",
|
|
3814
3907
|
file: "package.json",
|
|
3815
3908
|
message: packageScriptFixMessage(updatedScripts, createdSupportFiles, addedTsx),
|
|
3816
3909
|
};
|
|
3817
3910
|
}
|
|
3818
|
-
if (createdSupportFiles.length > 0) {
|
|
3819
|
-
|
|
3911
|
+
else if (createdSupportFiles.length > 0) {
|
|
3912
|
+
fix = {
|
|
3820
3913
|
code: "BEIGNET_PACKAGE_TEST_SUPPORT_MISSING",
|
|
3821
3914
|
file: "package.json",
|
|
3822
3915
|
message: `Restored ${createdSupportFiles.join(", ")}.`,
|
|
3823
3916
|
};
|
|
3824
3917
|
}
|
|
3825
|
-
if (addedTsx) {
|
|
3826
|
-
|
|
3918
|
+
else if (addedTsx) {
|
|
3919
|
+
fix = {
|
|
3827
3920
|
code: "BEIGNET_PACKAGE_TSX_DEPENDENCY_MISSING",
|
|
3828
3921
|
file: "package.json",
|
|
3829
3922
|
message: 'Added "tsx" to devDependencies.',
|
|
3830
3923
|
};
|
|
3831
3924
|
}
|
|
3832
|
-
|
|
3925
|
+
if (!fix)
|
|
3926
|
+
return undefined;
|
|
3927
|
+
return {
|
|
3928
|
+
id: "package.repair-generated-support",
|
|
3929
|
+
fixes: [fix],
|
|
3930
|
+
changes: [
|
|
3931
|
+
...(next === original
|
|
3932
|
+
? []
|
|
3933
|
+
: [
|
|
3934
|
+
planDoctorFixFileChange({
|
|
3935
|
+
file: "package.json",
|
|
3936
|
+
before: original,
|
|
3937
|
+
after: next,
|
|
3938
|
+
}),
|
|
3939
|
+
]),
|
|
3940
|
+
...testSupportTemplateFiles
|
|
3941
|
+
.filter((file) => createdSupportFiles.includes(file.path))
|
|
3942
|
+
.map((file) => planDoctorFixFileChange({ file: file.path, after: file.content })),
|
|
3943
|
+
],
|
|
3944
|
+
};
|
|
3833
3945
|
}
|
|
3834
3946
|
function legacyGeneratedScriptNames(scripts) {
|
|
3835
|
-
return Object.
|
|
3836
|
-
.filter(([name, command]) => scripts[name] === command)
|
|
3837
|
-
.map(([name]) => name);
|
|
3947
|
+
return Object.keys(legacyGeneratedPackageScripts).filter((name) => isLegacyGeneratedPackageScript(name, scripts[name]));
|
|
3838
3948
|
}
|
|
3839
3949
|
async function appUsesDirectBunTestImports(targetDir, files) {
|
|
3840
3950
|
for (const file of files) {
|
|
@@ -3849,27 +3959,14 @@ async function appUsesDirectBunTestImports(targetDir, files) {
|
|
|
3849
3959
|
return false;
|
|
3850
3960
|
}
|
|
3851
3961
|
function hasCurrentGeneratedTsxScript(scripts) {
|
|
3852
|
-
return Object.values(currentGeneratedPackageScripts).some((command) => command.startsWith("tsx ")
|
|
3962
|
+
return Object.values(currentGeneratedPackageScripts).some((command) => (command.startsWith("tsx ") || command.includes("--import tsx")) &&
|
|
3963
|
+
Object.values(scripts).includes(command));
|
|
3853
3964
|
}
|
|
3854
3965
|
function missingTestSupportFiles(files) {
|
|
3855
3966
|
return testSupportTemplateFiles
|
|
3856
3967
|
.map((file) => file.path)
|
|
3857
3968
|
.filter((file) => !files.includes(file));
|
|
3858
3969
|
}
|
|
3859
|
-
async function createMissingTestSupportFiles(targetDir, files, shouldCreate) {
|
|
3860
|
-
if (!shouldCreate)
|
|
3861
|
-
return [];
|
|
3862
|
-
const createdSupportFiles = [];
|
|
3863
|
-
for (const file of testSupportTemplateFiles) {
|
|
3864
|
-
if (files.includes(file.path))
|
|
3865
|
-
continue;
|
|
3866
|
-
const destination = path.join(targetDir, file.path);
|
|
3867
|
-
await mkdir(path.dirname(destination), { recursive: true });
|
|
3868
|
-
await writeFile(destination, file.content);
|
|
3869
|
-
createdSupportFiles.push(file.path);
|
|
3870
|
-
}
|
|
3871
|
-
return createdSupportFiles;
|
|
3872
|
-
}
|
|
3873
3970
|
function packageScriptFixMessage(updatedScripts, createdSupportFiles, addedTsx) {
|
|
3874
3971
|
const parts = [
|
|
3875
3972
|
`Updated generated package scripts: ${updatedScripts.join(", ")}.`,
|
|
@@ -4207,7 +4304,7 @@ function findRouteGroupContract(contracts, routeGroupContract) {
|
|
|
4207
4304
|
contract.file ===
|
|
4208
4305
|
routeGroupContract.file.replace(/\.ts$/, "/index.ts")));
|
|
4209
4306
|
}
|
|
4210
|
-
async function
|
|
4307
|
+
async function planDirectOpenApiArrayDrift(targetDir, files, config, convention, contracts, matchedRoutes) {
|
|
4211
4308
|
const routePath = path.join(targetDir, config.paths.openapiRoute);
|
|
4212
4309
|
let source;
|
|
4213
4310
|
try {
|
|
@@ -4231,16 +4328,27 @@ async function fixDirectOpenApiArrayDrift(targetDir, files, config, convention,
|
|
|
4231
4328
|
const next = `${source.slice(0, firstArg.start)}${nextArray}${source.slice(firstArg.end)}`;
|
|
4232
4329
|
if (next === source)
|
|
4233
4330
|
return undefined;
|
|
4234
|
-
await writeFile(routePath, next);
|
|
4235
4331
|
return {
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4332
|
+
id: "openapi.register-missing",
|
|
4333
|
+
fixes: [
|
|
4334
|
+
{
|
|
4335
|
+
code: "BEIGNET_OPENAPI_MISSING",
|
|
4336
|
+
file: config.paths.openapiRoute,
|
|
4337
|
+
message: `Added ${missingContracts
|
|
4338
|
+
.map((contract) => contract.exportName)
|
|
4339
|
+
.join(", ")} to the direct createOpenAPIHandler contract array.`,
|
|
4340
|
+
},
|
|
4341
|
+
],
|
|
4342
|
+
changes: [
|
|
4343
|
+
planDoctorFixFileChange({
|
|
4344
|
+
file: config.paths.openapiRoute,
|
|
4345
|
+
before: source,
|
|
4346
|
+
after: next,
|
|
4347
|
+
}),
|
|
4348
|
+
],
|
|
4241
4349
|
};
|
|
4242
4350
|
}
|
|
4243
|
-
async function
|
|
4351
|
+
async function planUnregisteredRouteGroups(targetDir, files, config) {
|
|
4244
4352
|
if (!files.includes(config.paths.server))
|
|
4245
4353
|
return undefined;
|
|
4246
4354
|
const routeGroups = await readFeatureRouteGroups(targetDir, files, config);
|
|
@@ -4283,19 +4391,30 @@ async function fixUnregisteredRouteGroups(targetDir, files, config) {
|
|
|
4283
4391
|
next = `${next.slice(0, nextFirstArg.start)}${nextArray}${next.slice(nextFirstArg.end)}`;
|
|
4284
4392
|
if (next === original)
|
|
4285
4393
|
return undefined;
|
|
4286
|
-
await writeFile(targetPath, next);
|
|
4287
4394
|
return {
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4395
|
+
id: "routes.register-missing",
|
|
4396
|
+
fixes: [
|
|
4397
|
+
{
|
|
4398
|
+
code: "BEIGNET_ROUTE_GROUP_UNREGISTERED",
|
|
4399
|
+
file: targetFile,
|
|
4400
|
+
message: `Registered ${groupsToAppend
|
|
4401
|
+
.map((routeGroup) => routeGroup.name)
|
|
4402
|
+
.join(", ")} in the central route list.`,
|
|
4403
|
+
},
|
|
4404
|
+
],
|
|
4405
|
+
changes: [
|
|
4406
|
+
planDoctorFixFileChange({
|
|
4407
|
+
file: targetFile,
|
|
4408
|
+
before: original,
|
|
4409
|
+
after: next,
|
|
4410
|
+
}),
|
|
4411
|
+
],
|
|
4293
4412
|
};
|
|
4294
4413
|
}
|
|
4295
|
-
async function
|
|
4414
|
+
async function planUnregisteredSchedules(targetDir, files, drift, config) {
|
|
4296
4415
|
if (!drift.schedules.centralExists)
|
|
4297
4416
|
return undefined;
|
|
4298
|
-
return
|
|
4417
|
+
return planWorkflowRegistryOperation("schedules.register-missing", {
|
|
4299
4418
|
targetDir,
|
|
4300
4419
|
files,
|
|
4301
4420
|
centralFile: config.paths.schedules,
|
|
@@ -4306,10 +4425,10 @@ async function fixUnregisteredSchedules(targetDir, files, drift, config) {
|
|
|
4306
4425
|
append: (source, entry, importLine) => appendToNamedArray(source, "schedules", entry, importLine),
|
|
4307
4426
|
});
|
|
4308
4427
|
}
|
|
4309
|
-
async function
|
|
4428
|
+
async function planUnregisteredTasks(targetDir, files, drift, config) {
|
|
4310
4429
|
if (!drift.tasks.centralExists)
|
|
4311
4430
|
return undefined;
|
|
4312
|
-
return
|
|
4431
|
+
return planWorkflowRegistryOperation("tasks.register-missing", {
|
|
4313
4432
|
targetDir,
|
|
4314
4433
|
files,
|
|
4315
4434
|
centralFile: config.paths.tasks,
|
|
@@ -4320,6 +4439,70 @@ async function fixUnregisteredTasks(targetDir, files, drift, config) {
|
|
|
4320
4439
|
append: (source, entry, importLine) => appendToNamedArray(source, "tasks", entry, importLine),
|
|
4321
4440
|
});
|
|
4322
4441
|
}
|
|
4442
|
+
async function planCoLocatedScheduleAndTaskRegistries(targetDir, files, drift, config) {
|
|
4443
|
+
if (!drift.schedules.centralExists || !drift.tasks.centralExists) {
|
|
4444
|
+
return undefined;
|
|
4445
|
+
}
|
|
4446
|
+
const centralFile = config.paths.schedules;
|
|
4447
|
+
let original;
|
|
4448
|
+
try {
|
|
4449
|
+
original = await readFile(path.join(targetDir, centralFile), "utf8");
|
|
4450
|
+
}
|
|
4451
|
+
catch {
|
|
4452
|
+
return undefined;
|
|
4453
|
+
}
|
|
4454
|
+
let next = original;
|
|
4455
|
+
const fixes = [];
|
|
4456
|
+
const repairedKinds = [];
|
|
4457
|
+
const scheduleEdit = planWorkflowRegistryEdit({
|
|
4458
|
+
files,
|
|
4459
|
+
centralFile,
|
|
4460
|
+
source: next,
|
|
4461
|
+
unregistered: drift.schedules.unregistered,
|
|
4462
|
+
code: "BEIGNET_SCHEDULE_UNREGISTERED",
|
|
4463
|
+
listName: "the schedules array",
|
|
4464
|
+
importSpecifier: (indexFile) => `@/${modulePath(indexFile)}`,
|
|
4465
|
+
append: (source, entry, importLine) => appendToNamedArray(source, "schedules", entry, importLine),
|
|
4466
|
+
});
|
|
4467
|
+
if (scheduleEdit) {
|
|
4468
|
+
next = scheduleEdit.after;
|
|
4469
|
+
fixes.push(scheduleEdit.fix);
|
|
4470
|
+
repairedKinds.push("schedules");
|
|
4471
|
+
}
|
|
4472
|
+
const taskEdit = planWorkflowRegistryEdit({
|
|
4473
|
+
files,
|
|
4474
|
+
centralFile,
|
|
4475
|
+
source: next,
|
|
4476
|
+
unregistered: drift.tasks.unregistered,
|
|
4477
|
+
code: "BEIGNET_TASK_UNREGISTERED",
|
|
4478
|
+
listName: "defineTasks([...])",
|
|
4479
|
+
importSpecifier: (indexFile) => relativeModule(centralFile, indexFile),
|
|
4480
|
+
append: (source, entry, importLine) => appendToNamedArray(source, "tasks", entry, importLine),
|
|
4481
|
+
});
|
|
4482
|
+
if (taskEdit) {
|
|
4483
|
+
next = taskEdit.after;
|
|
4484
|
+
fixes.push(taskEdit.fix);
|
|
4485
|
+
repairedKinds.push("tasks");
|
|
4486
|
+
}
|
|
4487
|
+
if (fixes.length === 0 || next === original)
|
|
4488
|
+
return undefined;
|
|
4489
|
+
const id = repairedKinds.length === 2
|
|
4490
|
+
? "workflows.register-missing"
|
|
4491
|
+
: repairedKinds[0] === "schedules"
|
|
4492
|
+
? "schedules.register-missing"
|
|
4493
|
+
: "tasks.register-missing";
|
|
4494
|
+
return {
|
|
4495
|
+
id,
|
|
4496
|
+
fixes,
|
|
4497
|
+
changes: [
|
|
4498
|
+
planDoctorFixFileChange({
|
|
4499
|
+
file: centralFile,
|
|
4500
|
+
before: original,
|
|
4501
|
+
after: next,
|
|
4502
|
+
}),
|
|
4503
|
+
],
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4323
4506
|
/**
|
|
4324
4507
|
* Wire fully unregistered feature listener registries into the central
|
|
4325
4508
|
* listener registry and ensure the providers file registers that central
|
|
@@ -4331,7 +4514,7 @@ async function fixUnregisteredTasks(targetDir, files, drift, config) {
|
|
|
4331
4514
|
* register against, or when the registry name is already imported from another
|
|
4332
4515
|
* file. The diagnostic stays in every bail case.
|
|
4333
4516
|
*/
|
|
4334
|
-
async function
|
|
4517
|
+
async function planUnregisteredListeners(targetDir, files, drift, config) {
|
|
4335
4518
|
const candidates = drift.listeners.unregistered.filter((entry) => entry.fullyUnregistered);
|
|
4336
4519
|
if (candidates.length === 0)
|
|
4337
4520
|
return undefined;
|
|
@@ -4407,60 +4590,93 @@ async function fixUnregisteredListeners(targetDir, files, drift, config) {
|
|
|
4407
4590
|
nextProviders === originalProviders) {
|
|
4408
4591
|
return undefined;
|
|
4409
4592
|
}
|
|
4410
|
-
await mkdir(path.dirname(listenersPath), { recursive: true });
|
|
4411
|
-
await writeFile(listenersPath, nextListeners);
|
|
4412
|
-
if (nextProviders !== originalProviders) {
|
|
4413
|
-
await writeFile(providersPath, nextProviders);
|
|
4414
|
-
}
|
|
4415
4593
|
return {
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4594
|
+
id: "listeners.register-missing",
|
|
4595
|
+
fixes: [
|
|
4596
|
+
{
|
|
4597
|
+
code: "BEIGNET_LISTENER_UNREGISTERED",
|
|
4598
|
+
file: listenersFile,
|
|
4599
|
+
message: `Registered ${candidateNames.join(", ")} in ${listenersFile} and wired listeners with registerListeners(...) in ${providersFile}.`,
|
|
4600
|
+
},
|
|
4601
|
+
],
|
|
4602
|
+
changes: [
|
|
4603
|
+
...(nextListeners === originalListeners
|
|
4604
|
+
? []
|
|
4605
|
+
: [
|
|
4606
|
+
planDoctorFixFileChange({
|
|
4607
|
+
file: listenersFile,
|
|
4608
|
+
before: originalListeners,
|
|
4609
|
+
after: nextListeners,
|
|
4610
|
+
}),
|
|
4611
|
+
]),
|
|
4612
|
+
...(nextProviders === originalProviders
|
|
4613
|
+
? []
|
|
4614
|
+
: [
|
|
4615
|
+
planDoctorFixFileChange({
|
|
4616
|
+
file: providersFile,
|
|
4617
|
+
before: originalProviders,
|
|
4618
|
+
after: nextProviders,
|
|
4619
|
+
}),
|
|
4620
|
+
]),
|
|
4621
|
+
],
|
|
4419
4622
|
};
|
|
4420
4623
|
}
|
|
4421
|
-
async function
|
|
4624
|
+
async function planUnregisteredOutboxEntries(targetDir, files, drift, config) {
|
|
4422
4625
|
if (!drift.outbox.exists)
|
|
4423
|
-
return
|
|
4626
|
+
return undefined;
|
|
4627
|
+
const outboxPath = path.join(targetDir, config.paths.outbox);
|
|
4628
|
+
let original;
|
|
4629
|
+
try {
|
|
4630
|
+
original = await readFile(outboxPath, "utf8");
|
|
4631
|
+
}
|
|
4632
|
+
catch {
|
|
4633
|
+
return undefined;
|
|
4634
|
+
}
|
|
4635
|
+
let next = original;
|
|
4424
4636
|
const fixes = [];
|
|
4425
|
-
const eventFix =
|
|
4426
|
-
targetDir,
|
|
4637
|
+
const eventFix = planWorkflowRegistryEdit({
|
|
4427
4638
|
files,
|
|
4428
4639
|
centralFile: config.paths.outbox,
|
|
4640
|
+
source: next,
|
|
4429
4641
|
unregistered: drift.outbox.events,
|
|
4430
4642
|
code: "BEIGNET_OUTBOX_EVENT_UNREGISTERED",
|
|
4431
4643
|
listName: "the outbox events list",
|
|
4432
4644
|
importSpecifier: (indexFile) => `@/${modulePath(indexFile)}`,
|
|
4433
4645
|
append: (source, entry, importLine) => appendToOutboxRegistryArray(source, "events", entry, importLine),
|
|
4434
4646
|
});
|
|
4435
|
-
if (eventFix)
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4647
|
+
if (eventFix) {
|
|
4648
|
+
next = eventFix.after;
|
|
4649
|
+
fixes.push(eventFix.fix);
|
|
4650
|
+
}
|
|
4651
|
+
const jobFix = planWorkflowRegistryEdit({
|
|
4439
4652
|
files,
|
|
4440
4653
|
centralFile: config.paths.outbox,
|
|
4654
|
+
source: next,
|
|
4441
4655
|
unregistered: drift.outbox.jobs,
|
|
4442
4656
|
code: "BEIGNET_OUTBOX_JOB_UNREGISTERED",
|
|
4443
4657
|
listName: "the outbox jobs list",
|
|
4444
4658
|
importSpecifier: (indexFile) => `@/${modulePath(indexFile)}`,
|
|
4445
4659
|
append: (source, entry, importLine) => appendToOutboxRegistryArray(source, "jobs", entry, importLine),
|
|
4446
4660
|
});
|
|
4447
|
-
if (jobFix)
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
}
|
|
4451
|
-
|
|
4452
|
-
* Append `...<feature><Kind>` registry spreads to an existing central
|
|
4453
|
-
* registry file.
|
|
4454
|
-
*
|
|
4455
|
-
* Bails without writing when an import for the registry name resolves to a
|
|
4456
|
-
* different file, when the registry anchor is missing or unparseable, or when
|
|
4457
|
-
* some members are already individually registered (appending the registry
|
|
4458
|
-
* spread would run those members twice). The diagnostic stays in that case.
|
|
4459
|
-
*/
|
|
4460
|
-
async function applyWorkflowRegistryFix(options) {
|
|
4461
|
-
const candidates = options.unregistered.filter((entry) => entry.fullyUnregistered);
|
|
4462
|
-
if (candidates.length === 0)
|
|
4661
|
+
if (jobFix) {
|
|
4662
|
+
next = jobFix.after;
|
|
4663
|
+
fixes.push(jobFix.fix);
|
|
4664
|
+
}
|
|
4665
|
+
if (fixes.length === 0 || next === original)
|
|
4463
4666
|
return undefined;
|
|
4667
|
+
return {
|
|
4668
|
+
id: "outbox.register-missing",
|
|
4669
|
+
fixes,
|
|
4670
|
+
changes: [
|
|
4671
|
+
planDoctorFixFileChange({
|
|
4672
|
+
file: config.paths.outbox,
|
|
4673
|
+
before: original,
|
|
4674
|
+
after: next,
|
|
4675
|
+
}),
|
|
4676
|
+
],
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
async function planWorkflowRegistryOperation(id, options) {
|
|
4464
4680
|
const centralPath = path.join(options.targetDir, options.centralFile);
|
|
4465
4681
|
let original;
|
|
4466
4682
|
try {
|
|
@@ -4469,7 +4685,26 @@ async function applyWorkflowRegistryFix(options) {
|
|
|
4469
4685
|
catch {
|
|
4470
4686
|
return undefined;
|
|
4471
4687
|
}
|
|
4472
|
-
|
|
4688
|
+
const edit = planWorkflowRegistryEdit({ ...options, source: original });
|
|
4689
|
+
if (!edit)
|
|
4690
|
+
return undefined;
|
|
4691
|
+
return {
|
|
4692
|
+
id,
|
|
4693
|
+
fixes: [edit.fix],
|
|
4694
|
+
changes: [
|
|
4695
|
+
planDoctorFixFileChange({
|
|
4696
|
+
file: options.centralFile,
|
|
4697
|
+
before: original,
|
|
4698
|
+
after: edit.after,
|
|
4699
|
+
}),
|
|
4700
|
+
],
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4703
|
+
function planWorkflowRegistryEdit(options) {
|
|
4704
|
+
const candidates = options.unregistered.filter((entry) => entry.fullyUnregistered);
|
|
4705
|
+
if (candidates.length === 0)
|
|
4706
|
+
return undefined;
|
|
4707
|
+
let next = options.source;
|
|
4473
4708
|
const registeredNames = [];
|
|
4474
4709
|
for (const { registry } of candidates) {
|
|
4475
4710
|
const imported = parseNamedImportSources(next).get(registry.registryName);
|
|
@@ -4490,13 +4725,15 @@ async function applyWorkflowRegistryFix(options) {
|
|
|
4490
4725
|
registeredNames.push(registry.registryName);
|
|
4491
4726
|
}
|
|
4492
4727
|
}
|
|
4493
|
-
if (next ===
|
|
4728
|
+
if (next === options.source || registeredNames.length === 0)
|
|
4494
4729
|
return undefined;
|
|
4495
|
-
await writeFile(centralPath, next);
|
|
4496
4730
|
return {
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4731
|
+
after: next,
|
|
4732
|
+
fix: {
|
|
4733
|
+
code: options.code,
|
|
4734
|
+
file: options.centralFile,
|
|
4735
|
+
message: `Registered ${registeredNames.join(", ")} in ${options.listName}.`,
|
|
4736
|
+
},
|
|
4500
4737
|
};
|
|
4501
4738
|
}
|
|
4502
4739
|
function routeRegistryFileFromServerSource(source, serverFile, files) {
|