@beignet/cli 0.0.41 → 0.0.43
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 +23 -0
- package/README.md +96 -25
- package/dist/analysis/source-index.d.ts +1 -1
- package/dist/analysis/source-index.d.ts.map +1 -1
- package/dist/analysis/source-index.js +4 -2
- package/dist/analysis/source-index.js.map +1 -1
- package/dist/analysis/workspace.d.ts +3 -2
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +51 -39
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +65 -4
- package/dist/app-map.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 +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 +38 -9
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +385 -251
- 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/inbox.js +4 -4
- package/dist/make/payments.js +6 -6
- package/dist/make/shared.js +3 -3
- package/dist/make/shared.js.map +1 -1
- package/dist/make/tenancy.d.ts.map +1 -1
- package/dist/make/tenancy.js +12 -14
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +33 -23
- package/dist/make.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/registry-edits.d.ts +7 -1
- package/dist/registry-edits.d.ts.map +1 -1
- package/dist/registry-edits.js +61 -43
- package/dist/registry-edits.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +34 -11
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +5 -5
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/mysql.js +3 -3
- package/dist/templates/db/postgres.js +3 -3
- package/dist/templates/db/sqlite.js +3 -3
- package/dist/templates/server.js +4 -4
- 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 +7 -4
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/todos.js +2 -2
- package/package.json +3 -2
- package/skills/app-structure/SKILL.md +43 -10
- package/src/analysis/source-index.ts +12 -3
- package/src/analysis/workspace.ts +94 -52
- package/src/app-map.ts +82 -4
- 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 +537 -295
- package/src/lib.ts +28 -1
- package/src/make/inbox.ts +4 -4
- package/src/make/payments.ts +6 -6
- package/src/make/shared.ts +3 -3
- package/src/make/tenancy.ts +12 -14
- package/src/make.ts +36 -24
- package/src/mcp.ts +187 -13
- package/src/registry-edits.ts +81 -39
- package/src/templates/agents.ts +34 -11
- package/src/templates/base.ts +5 -4
- package/src/templates/db/mysql.ts +3 -3
- package/src/templates/db/postgres.ts +3 -3
- package/src/templates/db/sqlite.ts +3 -3
- package/src/templates/server.ts +4 -4
- package/src/templates/shared.ts +14 -6
- package/src/templates/todos.ts +2 -2
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
|
-
import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, outboxRegistryValueInfo, } from "./registry-edits.js";
|
|
10
|
-
import { currentGeneratedPackageScripts, externalVersions, legacyGeneratedPackageScripts, } from "./templates/shared.js";
|
|
10
|
+
import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, codeCharacterIndexes, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, outboxRegistryValueInfo, } from "./registry-edits.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")
|
|
@@ -498,64 +590,17 @@ function objectArgumentBodies(source, callPattern) {
|
|
|
498
590
|
function balancedObjectSource(source, startIndex) {
|
|
499
591
|
if (source[startIndex] !== "{")
|
|
500
592
|
return undefined;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
let escaped = false;
|
|
504
|
-
for (let index = startIndex; index < source.length; index += 1) {
|
|
505
|
-
const char = source[index];
|
|
506
|
-
if (quote) {
|
|
507
|
-
if (escaped) {
|
|
508
|
-
escaped = false;
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
if (char === "\\") {
|
|
512
|
-
escaped = true;
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
if (char === quote)
|
|
516
|
-
quote = undefined;
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
520
|
-
quote = char;
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
if (char === "{") {
|
|
524
|
-
depth += 1;
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
if (char === "}") {
|
|
528
|
-
depth -= 1;
|
|
529
|
-
if (depth === 0)
|
|
530
|
-
return source.slice(startIndex, index + 1);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return undefined;
|
|
593
|
+
const endIndex = matchingDelimiterIndex(source, startIndex, "{", "}");
|
|
594
|
+
return endIndex === -1 ? undefined : source.slice(startIndex, endIndex + 1);
|
|
534
595
|
}
|
|
535
596
|
function topLevelObjectKeys(body) {
|
|
536
597
|
const keys = [];
|
|
537
598
|
let depth = 0;
|
|
538
|
-
let
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const char = body[index];
|
|
542
|
-
if (quote) {
|
|
543
|
-
if (escaped) {
|
|
544
|
-
escaped = false;
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
if (char === "\\") {
|
|
548
|
-
escaped = true;
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
if (char === quote)
|
|
552
|
-
quote = undefined;
|
|
599
|
+
let skipUntil = 0;
|
|
600
|
+
for (const index of codeCharacterIndexes(body)) {
|
|
601
|
+
if (index < skipUntil)
|
|
553
602
|
continue;
|
|
554
|
-
|
|
555
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
556
|
-
quote = char;
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
603
|
+
const char = body[index];
|
|
559
604
|
if (char === "{" || char === "[" || char === "(") {
|
|
560
605
|
depth += 1;
|
|
561
606
|
continue;
|
|
@@ -573,7 +618,7 @@ function topLevelObjectKeys(body) {
|
|
|
573
618
|
const next = body.slice(index + name.length).match(/^\s*:/);
|
|
574
619
|
if (next)
|
|
575
620
|
keys.push(name);
|
|
576
|
-
index
|
|
621
|
+
skipUntil = index + name.length;
|
|
577
622
|
}
|
|
578
623
|
return keys;
|
|
579
624
|
}
|
|
@@ -2307,6 +2352,7 @@ async function inspectWorkflowRegistrationDrift(targetDir, files, config, conven
|
|
|
2307
2352
|
severity: "error",
|
|
2308
2353
|
code,
|
|
2309
2354
|
file: centralExists ? centralFile : registry.indexFile,
|
|
2355
|
+
subject: workflowArtifactSubject(registry, member),
|
|
2310
2356
|
message: centralExists
|
|
2311
2357
|
? `${workflowArtifactOrigin(registry, member)}, but it is not registered in ${registrationTarget} in ${centralFile}, so the ${noun} never runs. Import ${registry.registryName} and spread it into the ${kind} list.`
|
|
2312
2358
|
: `${workflowArtifactOrigin(registry, member)}, but ${centralFile} does not exist, so the ${noun} never runs. Register ${registry.registryName} in ${centralFile} (or run \`${makeCommand}\` to create it).`,
|
|
@@ -2321,6 +2367,7 @@ async function inspectWorkflowRegistrationDrift(targetDir, files, config, conven
|
|
|
2321
2367
|
severity: "warning",
|
|
2322
2368
|
code: "BEIGNET_OUTBOX_EVENT_UNREGISTERED",
|
|
2323
2369
|
file: config.paths.outbox,
|
|
2370
|
+
subject: workflowArtifactSubject(registry, member),
|
|
2324
2371
|
message: `${workflowArtifactOrigin(registry, member)} and listeners react to it, but it is not registered in the events list of defineOutboxRegistry({...}) in ${config.paths.outbox}, so outbox drain cannot deliver it. Import ${registry.registryName} and spread it into the events list.`,
|
|
2325
2372
|
});
|
|
2326
2373
|
}
|
|
@@ -2331,6 +2378,7 @@ async function inspectWorkflowRegistrationDrift(targetDir, files, config, conven
|
|
|
2331
2378
|
severity: "warning",
|
|
2332
2379
|
code: "BEIGNET_OUTBOX_JOB_UNREGISTERED",
|
|
2333
2380
|
file: config.paths.outbox,
|
|
2381
|
+
subject: workflowArtifactSubject(registry, member),
|
|
2334
2382
|
message: `${workflowArtifactOrigin(registry, member)}, but it is not registered in the jobs list of defineOutboxRegistry({...}) in ${config.paths.outbox}, so outbox drain cannot deliver it. Import ${registry.registryName} and spread it into the jobs list.`,
|
|
2335
2383
|
});
|
|
2336
2384
|
}
|
|
@@ -2350,6 +2398,7 @@ async function inspectWorkflowRegistrationDrift(targetDir, files, config, conven
|
|
|
2350
2398
|
file: drift.listeners.wiringFile ??
|
|
2351
2399
|
drift.listeners.eventBusFile ??
|
|
2352
2400
|
registry.indexFile,
|
|
2401
|
+
subject: workflowArtifactSubject(registry, member),
|
|
2353
2402
|
message: `${workflowArtifactOrigin(registry, member)}, but no registerListeners(...) call references it, so the listener never receives events. Register ${registry.registryName} with registerListeners(...) in ${listenerTarget}.`,
|
|
2354
2403
|
});
|
|
2355
2404
|
}
|
|
@@ -2418,6 +2467,12 @@ function workflowArtifactOrigin(registry, member) {
|
|
|
2418
2467
|
}
|
|
2419
2468
|
return `${memberFile} declares ${member} (exported through ${registry.registryName} in ${registry.indexFile})`;
|
|
2420
2469
|
}
|
|
2470
|
+
function workflowArtifactSubject(registry, member) {
|
|
2471
|
+
return {
|
|
2472
|
+
file: registry.memberFiles.get(member) ?? registry.indexFile,
|
|
2473
|
+
exportName: member,
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2421
2476
|
async function workflowRegistrationDrift(targetDir, files, config) {
|
|
2422
2477
|
const registries = await readFeatureWorkflowRegistries(targetDir, files, config);
|
|
2423
2478
|
const byKind = (kind) => registries.filter((registry) => registry.kind === kind);
|
|
@@ -3416,26 +3471,8 @@ function splitTopLevelArguments(source) {
|
|
|
3416
3471
|
const args = [];
|
|
3417
3472
|
let start = 0;
|
|
3418
3473
|
let depth = 0;
|
|
3419
|
-
|
|
3420
|
-
let escaped = false;
|
|
3421
|
-
for (let index = 0; index < source.length; index++) {
|
|
3474
|
+
for (const index of codeCharacterIndexes(source)) {
|
|
3422
3475
|
const char = source[index];
|
|
3423
|
-
if (inString) {
|
|
3424
|
-
if (escaped) {
|
|
3425
|
-
escaped = false;
|
|
3426
|
-
}
|
|
3427
|
-
else if (char === "\\") {
|
|
3428
|
-
escaped = true;
|
|
3429
|
-
}
|
|
3430
|
-
else if (char === inString) {
|
|
3431
|
-
inString = undefined;
|
|
3432
|
-
}
|
|
3433
|
-
continue;
|
|
3434
|
-
}
|
|
3435
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
3436
|
-
inString = char;
|
|
3437
|
-
continue;
|
|
3438
|
-
}
|
|
3439
3476
|
if (char === "(" || char === "{" || char === "[") {
|
|
3440
3477
|
depth += 1;
|
|
3441
3478
|
continue;
|
|
@@ -3724,7 +3761,7 @@ async function inspectPackageScripts(targetDir, files, convention) {
|
|
|
3724
3761
|
severity: "warning",
|
|
3725
3762
|
code: "BEIGNET_PACKAGE_TEST_SCRIPT_MISSING",
|
|
3726
3763
|
file: "package.json",
|
|
3727
|
-
message:
|
|
3764
|
+
message: `package.json does not define a test script. Add "test": "${currentGeneratedPackageScripts.test}" or run beignet doctor --fix.`,
|
|
3728
3765
|
});
|
|
3729
3766
|
}
|
|
3730
3767
|
const legacyScripts = hasDirectBunTestImports
|
|
@@ -3735,7 +3772,7 @@ async function inspectPackageScripts(targetDir, files, convention) {
|
|
|
3735
3772
|
severity: "warning",
|
|
3736
3773
|
code: "BEIGNET_PACKAGE_SCRIPT_LEGACY",
|
|
3737
3774
|
file: "package.json",
|
|
3738
|
-
message: `package.json still uses legacy generated
|
|
3775
|
+
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
3776
|
});
|
|
3740
3777
|
}
|
|
3741
3778
|
const needsTsx = hasCurrentGeneratedTsxScript(scripts) &&
|
|
@@ -3761,7 +3798,7 @@ async function inspectPackageScripts(targetDir, files, convention) {
|
|
|
3761
3798
|
}
|
|
3762
3799
|
return diagnostics;
|
|
3763
3800
|
}
|
|
3764
|
-
async function
|
|
3801
|
+
async function planPackageScripts(targetDir, files, convention) {
|
|
3765
3802
|
if (!convention.resourceGenerator || !files.includes("package.json")) {
|
|
3766
3803
|
return undefined;
|
|
3767
3804
|
}
|
|
@@ -3777,8 +3814,8 @@ async function fixPackageScripts(targetDir, files, convention) {
|
|
|
3777
3814
|
updatedScripts.push("test");
|
|
3778
3815
|
}
|
|
3779
3816
|
if (!hasDirectBunTestImports) {
|
|
3780
|
-
for (const
|
|
3781
|
-
if (packageJson.scripts[name]
|
|
3817
|
+
for (const name of Object.keys(legacyGeneratedPackageScripts)) {
|
|
3818
|
+
if (isLegacyGeneratedPackageScript(name, packageJson.scripts[name])) {
|
|
3782
3819
|
packageJson.scripts[name] = currentGeneratedPackageScripts[name];
|
|
3783
3820
|
if (!updatedScripts.includes(name))
|
|
3784
3821
|
updatedScripts.push(name);
|
|
@@ -3794,47 +3831,65 @@ async function fixPackageScripts(targetDir, files, convention) {
|
|
|
3794
3831
|
packageJson.devDependencies.tsx = externalVersions.tsx;
|
|
3795
3832
|
}
|
|
3796
3833
|
const next = `${JSON.stringify(packageJson, null, "\t")}\n`;
|
|
3797
|
-
const createdSupportFiles =
|
|
3834
|
+
const createdSupportFiles = packageJson.scripts.test === currentGeneratedPackageScripts.test
|
|
3835
|
+
? missingTestSupportFiles(files)
|
|
3836
|
+
: [];
|
|
3798
3837
|
if (next === original && createdSupportFiles.length === 0)
|
|
3799
3838
|
return undefined;
|
|
3800
|
-
|
|
3801
|
-
await writeFile(filePath, next);
|
|
3839
|
+
let fix;
|
|
3802
3840
|
if (addedTestScript) {
|
|
3803
|
-
|
|
3841
|
+
fix = {
|
|
3804
3842
|
code: "BEIGNET_PACKAGE_TEST_SCRIPT_MISSING",
|
|
3805
3843
|
file: "package.json",
|
|
3806
3844
|
message: createdSupportFiles.length > 0
|
|
3807
|
-
? `Added "test": "
|
|
3808
|
-
:
|
|
3845
|
+
? `Added "test": "${currentGeneratedPackageScripts.test}" and ${createdSupportFiles.join(", ")}.`
|
|
3846
|
+
: `Added "test": "${currentGeneratedPackageScripts.test}".`,
|
|
3809
3847
|
};
|
|
3810
3848
|
}
|
|
3811
|
-
if (updatedScripts.length > 0) {
|
|
3812
|
-
|
|
3849
|
+
else if (updatedScripts.length > 0) {
|
|
3850
|
+
fix = {
|
|
3813
3851
|
code: "BEIGNET_PACKAGE_SCRIPT_LEGACY",
|
|
3814
3852
|
file: "package.json",
|
|
3815
3853
|
message: packageScriptFixMessage(updatedScripts, createdSupportFiles, addedTsx),
|
|
3816
3854
|
};
|
|
3817
3855
|
}
|
|
3818
|
-
if (createdSupportFiles.length > 0) {
|
|
3819
|
-
|
|
3856
|
+
else if (createdSupportFiles.length > 0) {
|
|
3857
|
+
fix = {
|
|
3820
3858
|
code: "BEIGNET_PACKAGE_TEST_SUPPORT_MISSING",
|
|
3821
3859
|
file: "package.json",
|
|
3822
3860
|
message: `Restored ${createdSupportFiles.join(", ")}.`,
|
|
3823
3861
|
};
|
|
3824
3862
|
}
|
|
3825
|
-
if (addedTsx) {
|
|
3826
|
-
|
|
3863
|
+
else if (addedTsx) {
|
|
3864
|
+
fix = {
|
|
3827
3865
|
code: "BEIGNET_PACKAGE_TSX_DEPENDENCY_MISSING",
|
|
3828
3866
|
file: "package.json",
|
|
3829
3867
|
message: 'Added "tsx" to devDependencies.',
|
|
3830
3868
|
};
|
|
3831
3869
|
}
|
|
3832
|
-
|
|
3870
|
+
if (!fix)
|
|
3871
|
+
return undefined;
|
|
3872
|
+
return {
|
|
3873
|
+
id: "package.repair-generated-support",
|
|
3874
|
+
fixes: [fix],
|
|
3875
|
+
changes: [
|
|
3876
|
+
...(next === original
|
|
3877
|
+
? []
|
|
3878
|
+
: [
|
|
3879
|
+
planDoctorFixFileChange({
|
|
3880
|
+
file: "package.json",
|
|
3881
|
+
before: original,
|
|
3882
|
+
after: next,
|
|
3883
|
+
}),
|
|
3884
|
+
]),
|
|
3885
|
+
...testSupportTemplateFiles
|
|
3886
|
+
.filter((file) => createdSupportFiles.includes(file.path))
|
|
3887
|
+
.map((file) => planDoctorFixFileChange({ file: file.path, after: file.content })),
|
|
3888
|
+
],
|
|
3889
|
+
};
|
|
3833
3890
|
}
|
|
3834
3891
|
function legacyGeneratedScriptNames(scripts) {
|
|
3835
|
-
return Object.
|
|
3836
|
-
.filter(([name, command]) => scripts[name] === command)
|
|
3837
|
-
.map(([name]) => name);
|
|
3892
|
+
return Object.keys(legacyGeneratedPackageScripts).filter((name) => isLegacyGeneratedPackageScript(name, scripts[name]));
|
|
3838
3893
|
}
|
|
3839
3894
|
async function appUsesDirectBunTestImports(targetDir, files) {
|
|
3840
3895
|
for (const file of files) {
|
|
@@ -3849,27 +3904,14 @@ async function appUsesDirectBunTestImports(targetDir, files) {
|
|
|
3849
3904
|
return false;
|
|
3850
3905
|
}
|
|
3851
3906
|
function hasCurrentGeneratedTsxScript(scripts) {
|
|
3852
|
-
return Object.values(currentGeneratedPackageScripts).some((command) => command.startsWith("tsx ")
|
|
3907
|
+
return Object.values(currentGeneratedPackageScripts).some((command) => (command.startsWith("tsx ") || command.includes("--import tsx")) &&
|
|
3908
|
+
Object.values(scripts).includes(command));
|
|
3853
3909
|
}
|
|
3854
3910
|
function missingTestSupportFiles(files) {
|
|
3855
3911
|
return testSupportTemplateFiles
|
|
3856
3912
|
.map((file) => file.path)
|
|
3857
3913
|
.filter((file) => !files.includes(file));
|
|
3858
3914
|
}
|
|
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
3915
|
function packageScriptFixMessage(updatedScripts, createdSupportFiles, addedTsx) {
|
|
3874
3916
|
const parts = [
|
|
3875
3917
|
`Updated generated package scripts: ${updatedScripts.join(", ")}.`,
|
|
@@ -3910,6 +3952,8 @@ function dedupeDiagnostics(diagnostics) {
|
|
|
3910
3952
|
diagnostic.code,
|
|
3911
3953
|
diagnostic.file ?? "",
|
|
3912
3954
|
diagnostic.contract ?? "",
|
|
3955
|
+
diagnostic.subject?.file ?? "",
|
|
3956
|
+
diagnostic.subject?.exportName ?? "",
|
|
3913
3957
|
diagnostic.message,
|
|
3914
3958
|
].join(":");
|
|
3915
3959
|
if (seen.has(key))
|
|
@@ -4024,6 +4068,10 @@ async function inspectFeatureRouteRegistration(targetDir, files, config, convent
|
|
|
4024
4068
|
code: "BEIGNET_ROUTE_GROUP_UNREGISTERED",
|
|
4025
4069
|
file: config.paths.server,
|
|
4026
4070
|
contract: routeGroupContract.exportName,
|
|
4071
|
+
subject: {
|
|
4072
|
+
file: routeGroup.file,
|
|
4073
|
+
exportName: routeGroup.name,
|
|
4074
|
+
},
|
|
4027
4075
|
message: contract
|
|
4028
4076
|
? `${routeGroup.file} declares ${routeGroup.name} for ${contract.exportName} (${contract.method} ${contract.path}), but ${routeGroup.name} is not registered in defineRoutes([...]) in ${config.paths.server}. Import ${routeGroup.name} and add it to the central route list.`
|
|
4029
4077
|
: `${routeGroup.file} declares ${routeGroup.name} for ${routeGroupContract.exportName}, but ${routeGroup.name} is not registered in defineRoutes([...]) in ${config.paths.server}. Import ${routeGroup.name} and add it to the central route list.`,
|
|
@@ -4108,39 +4156,8 @@ function routeGroupRoutesArrayBody(source, callStart) {
|
|
|
4108
4156
|
function balancedArraySource(source, startIndex) {
|
|
4109
4157
|
if (source[startIndex] !== "[")
|
|
4110
4158
|
return undefined;
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
let escaped = false;
|
|
4114
|
-
for (let index = startIndex; index < source.length; index += 1) {
|
|
4115
|
-
const char = source[index];
|
|
4116
|
-
if (quote) {
|
|
4117
|
-
if (escaped) {
|
|
4118
|
-
escaped = false;
|
|
4119
|
-
continue;
|
|
4120
|
-
}
|
|
4121
|
-
if (char === "\\") {
|
|
4122
|
-
escaped = true;
|
|
4123
|
-
continue;
|
|
4124
|
-
}
|
|
4125
|
-
if (char === quote)
|
|
4126
|
-
quote = undefined;
|
|
4127
|
-
continue;
|
|
4128
|
-
}
|
|
4129
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
4130
|
-
quote = char;
|
|
4131
|
-
continue;
|
|
4132
|
-
}
|
|
4133
|
-
if (char === "[") {
|
|
4134
|
-
depth += 1;
|
|
4135
|
-
continue;
|
|
4136
|
-
}
|
|
4137
|
-
if (char === "]") {
|
|
4138
|
-
depth -= 1;
|
|
4139
|
-
if (depth === 0)
|
|
4140
|
-
return source.slice(startIndex, index + 1);
|
|
4141
|
-
}
|
|
4142
|
-
}
|
|
4143
|
-
return undefined;
|
|
4159
|
+
const endIndex = matchingDelimiterIndex(source, startIndex, "[", "]");
|
|
4160
|
+
return endIndex === -1 ? undefined : source.slice(startIndex, endIndex + 1);
|
|
4144
4161
|
}
|
|
4145
4162
|
function routeGroupContractRef(expression, imports) {
|
|
4146
4163
|
const namedMatch = /^([A-Za-z_$][\w$]*)$/.exec(expression);
|
|
@@ -4161,15 +4178,10 @@ function routeGroupContractRef(expression, imports) {
|
|
|
4161
4178
|
return undefined;
|
|
4162
4179
|
}
|
|
4163
4180
|
function registeredRouteGroups(source) {
|
|
4164
|
-
const
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
const defineRoutesEnd = defineRoutesSource.indexOf("]),");
|
|
4169
|
-
const searchSource = defineRoutesEnd === -1
|
|
4170
|
-
? defineRoutesSource
|
|
4171
|
-
: defineRoutesSource.slice(0, defineRoutesEnd);
|
|
4172
|
-
return new Set([...searchSource.matchAll(/\b([A-Za-z_$][\w$]*)\b/g)].map((match) => match[1]));
|
|
4181
|
+
const routes = firstDefineRoutesArgInfo(source);
|
|
4182
|
+
return routes?.text.startsWith("[")
|
|
4183
|
+
? identifiersFromArrayExpression(routes.text)
|
|
4184
|
+
: new Set();
|
|
4173
4185
|
}
|
|
4174
4186
|
async function registeredRouteGroupsForServer(targetDir, files, config, serverSource) {
|
|
4175
4187
|
const registeredGroups = registeredRouteGroups(serverSource);
|
|
@@ -4207,7 +4219,7 @@ function findRouteGroupContract(contracts, routeGroupContract) {
|
|
|
4207
4219
|
contract.file ===
|
|
4208
4220
|
routeGroupContract.file.replace(/\.ts$/, "/index.ts")));
|
|
4209
4221
|
}
|
|
4210
|
-
async function
|
|
4222
|
+
async function planDirectOpenApiArrayDrift(targetDir, files, config, convention, contracts, matchedRoutes) {
|
|
4211
4223
|
const routePath = path.join(targetDir, config.paths.openapiRoute);
|
|
4212
4224
|
let source;
|
|
4213
4225
|
try {
|
|
@@ -4231,16 +4243,27 @@ async function fixDirectOpenApiArrayDrift(targetDir, files, config, convention,
|
|
|
4231
4243
|
const next = `${source.slice(0, firstArg.start)}${nextArray}${source.slice(firstArg.end)}`;
|
|
4232
4244
|
if (next === source)
|
|
4233
4245
|
return undefined;
|
|
4234
|
-
await writeFile(routePath, next);
|
|
4235
4246
|
return {
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4247
|
+
id: "openapi.register-missing",
|
|
4248
|
+
fixes: [
|
|
4249
|
+
{
|
|
4250
|
+
code: "BEIGNET_OPENAPI_MISSING",
|
|
4251
|
+
file: config.paths.openapiRoute,
|
|
4252
|
+
message: `Added ${missingContracts
|
|
4253
|
+
.map((contract) => contract.exportName)
|
|
4254
|
+
.join(", ")} to the direct createOpenAPIHandler contract array.`,
|
|
4255
|
+
},
|
|
4256
|
+
],
|
|
4257
|
+
changes: [
|
|
4258
|
+
planDoctorFixFileChange({
|
|
4259
|
+
file: config.paths.openapiRoute,
|
|
4260
|
+
before: source,
|
|
4261
|
+
after: next,
|
|
4262
|
+
}),
|
|
4263
|
+
],
|
|
4241
4264
|
};
|
|
4242
4265
|
}
|
|
4243
|
-
async function
|
|
4266
|
+
async function planUnregisteredRouteGroups(targetDir, files, config) {
|
|
4244
4267
|
if (!files.includes(config.paths.server))
|
|
4245
4268
|
return undefined;
|
|
4246
4269
|
const routeGroups = await readFeatureRouteGroups(targetDir, files, config);
|
|
@@ -4283,19 +4306,30 @@ async function fixUnregisteredRouteGroups(targetDir, files, config) {
|
|
|
4283
4306
|
next = `${next.slice(0, nextFirstArg.start)}${nextArray}${next.slice(nextFirstArg.end)}`;
|
|
4284
4307
|
if (next === original)
|
|
4285
4308
|
return undefined;
|
|
4286
|
-
await writeFile(targetPath, next);
|
|
4287
4309
|
return {
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4310
|
+
id: "routes.register-missing",
|
|
4311
|
+
fixes: [
|
|
4312
|
+
{
|
|
4313
|
+
code: "BEIGNET_ROUTE_GROUP_UNREGISTERED",
|
|
4314
|
+
file: targetFile,
|
|
4315
|
+
message: `Registered ${groupsToAppend
|
|
4316
|
+
.map((routeGroup) => routeGroup.name)
|
|
4317
|
+
.join(", ")} in the central route list.`,
|
|
4318
|
+
},
|
|
4319
|
+
],
|
|
4320
|
+
changes: [
|
|
4321
|
+
planDoctorFixFileChange({
|
|
4322
|
+
file: targetFile,
|
|
4323
|
+
before: original,
|
|
4324
|
+
after: next,
|
|
4325
|
+
}),
|
|
4326
|
+
],
|
|
4293
4327
|
};
|
|
4294
4328
|
}
|
|
4295
|
-
async function
|
|
4329
|
+
async function planUnregisteredSchedules(targetDir, files, drift, config) {
|
|
4296
4330
|
if (!drift.schedules.centralExists)
|
|
4297
4331
|
return undefined;
|
|
4298
|
-
return
|
|
4332
|
+
return planWorkflowRegistryOperation("schedules.register-missing", {
|
|
4299
4333
|
targetDir,
|
|
4300
4334
|
files,
|
|
4301
4335
|
centralFile: config.paths.schedules,
|
|
@@ -4306,10 +4340,10 @@ async function fixUnregisteredSchedules(targetDir, files, drift, config) {
|
|
|
4306
4340
|
append: (source, entry, importLine) => appendToNamedArray(source, "schedules", entry, importLine),
|
|
4307
4341
|
});
|
|
4308
4342
|
}
|
|
4309
|
-
async function
|
|
4343
|
+
async function planUnregisteredTasks(targetDir, files, drift, config) {
|
|
4310
4344
|
if (!drift.tasks.centralExists)
|
|
4311
4345
|
return undefined;
|
|
4312
|
-
return
|
|
4346
|
+
return planWorkflowRegistryOperation("tasks.register-missing", {
|
|
4313
4347
|
targetDir,
|
|
4314
4348
|
files,
|
|
4315
4349
|
centralFile: config.paths.tasks,
|
|
@@ -4320,6 +4354,70 @@ async function fixUnregisteredTasks(targetDir, files, drift, config) {
|
|
|
4320
4354
|
append: (source, entry, importLine) => appendToNamedArray(source, "tasks", entry, importLine),
|
|
4321
4355
|
});
|
|
4322
4356
|
}
|
|
4357
|
+
async function planCoLocatedScheduleAndTaskRegistries(targetDir, files, drift, config) {
|
|
4358
|
+
if (!drift.schedules.centralExists || !drift.tasks.centralExists) {
|
|
4359
|
+
return undefined;
|
|
4360
|
+
}
|
|
4361
|
+
const centralFile = config.paths.schedules;
|
|
4362
|
+
let original;
|
|
4363
|
+
try {
|
|
4364
|
+
original = await readFile(path.join(targetDir, centralFile), "utf8");
|
|
4365
|
+
}
|
|
4366
|
+
catch {
|
|
4367
|
+
return undefined;
|
|
4368
|
+
}
|
|
4369
|
+
let next = original;
|
|
4370
|
+
const fixes = [];
|
|
4371
|
+
const repairedKinds = [];
|
|
4372
|
+
const scheduleEdit = planWorkflowRegistryEdit({
|
|
4373
|
+
files,
|
|
4374
|
+
centralFile,
|
|
4375
|
+
source: next,
|
|
4376
|
+
unregistered: drift.schedules.unregistered,
|
|
4377
|
+
code: "BEIGNET_SCHEDULE_UNREGISTERED",
|
|
4378
|
+
listName: "the schedules array",
|
|
4379
|
+
importSpecifier: (indexFile) => `@/${modulePath(indexFile)}`,
|
|
4380
|
+
append: (source, entry, importLine) => appendToNamedArray(source, "schedules", entry, importLine),
|
|
4381
|
+
});
|
|
4382
|
+
if (scheduleEdit) {
|
|
4383
|
+
next = scheduleEdit.after;
|
|
4384
|
+
fixes.push(scheduleEdit.fix);
|
|
4385
|
+
repairedKinds.push("schedules");
|
|
4386
|
+
}
|
|
4387
|
+
const taskEdit = planWorkflowRegistryEdit({
|
|
4388
|
+
files,
|
|
4389
|
+
centralFile,
|
|
4390
|
+
source: next,
|
|
4391
|
+
unregistered: drift.tasks.unregistered,
|
|
4392
|
+
code: "BEIGNET_TASK_UNREGISTERED",
|
|
4393
|
+
listName: "defineTasks([...])",
|
|
4394
|
+
importSpecifier: (indexFile) => relativeModule(centralFile, indexFile),
|
|
4395
|
+
append: (source, entry, importLine) => appendToNamedArray(source, "tasks", entry, importLine),
|
|
4396
|
+
});
|
|
4397
|
+
if (taskEdit) {
|
|
4398
|
+
next = taskEdit.after;
|
|
4399
|
+
fixes.push(taskEdit.fix);
|
|
4400
|
+
repairedKinds.push("tasks");
|
|
4401
|
+
}
|
|
4402
|
+
if (fixes.length === 0 || next === original)
|
|
4403
|
+
return undefined;
|
|
4404
|
+
const id = repairedKinds.length === 2
|
|
4405
|
+
? "workflows.register-missing"
|
|
4406
|
+
: repairedKinds[0] === "schedules"
|
|
4407
|
+
? "schedules.register-missing"
|
|
4408
|
+
: "tasks.register-missing";
|
|
4409
|
+
return {
|
|
4410
|
+
id,
|
|
4411
|
+
fixes,
|
|
4412
|
+
changes: [
|
|
4413
|
+
planDoctorFixFileChange({
|
|
4414
|
+
file: centralFile,
|
|
4415
|
+
before: original,
|
|
4416
|
+
after: next,
|
|
4417
|
+
}),
|
|
4418
|
+
],
|
|
4419
|
+
};
|
|
4420
|
+
}
|
|
4323
4421
|
/**
|
|
4324
4422
|
* Wire fully unregistered feature listener registries into the central
|
|
4325
4423
|
* listener registry and ensure the providers file registers that central
|
|
@@ -4331,7 +4429,7 @@ async function fixUnregisteredTasks(targetDir, files, drift, config) {
|
|
|
4331
4429
|
* register against, or when the registry name is already imported from another
|
|
4332
4430
|
* file. The diagnostic stays in every bail case.
|
|
4333
4431
|
*/
|
|
4334
|
-
async function
|
|
4432
|
+
async function planUnregisteredListeners(targetDir, files, drift, config) {
|
|
4335
4433
|
const candidates = drift.listeners.unregistered.filter((entry) => entry.fullyUnregistered);
|
|
4336
4434
|
if (candidates.length === 0)
|
|
4337
4435
|
return undefined;
|
|
@@ -4407,60 +4505,93 @@ async function fixUnregisteredListeners(targetDir, files, drift, config) {
|
|
|
4407
4505
|
nextProviders === originalProviders) {
|
|
4408
4506
|
return undefined;
|
|
4409
4507
|
}
|
|
4410
|
-
await mkdir(path.dirname(listenersPath), { recursive: true });
|
|
4411
|
-
await writeFile(listenersPath, nextListeners);
|
|
4412
|
-
if (nextProviders !== originalProviders) {
|
|
4413
|
-
await writeFile(providersPath, nextProviders);
|
|
4414
|
-
}
|
|
4415
4508
|
return {
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4509
|
+
id: "listeners.register-missing",
|
|
4510
|
+
fixes: [
|
|
4511
|
+
{
|
|
4512
|
+
code: "BEIGNET_LISTENER_UNREGISTERED",
|
|
4513
|
+
file: listenersFile,
|
|
4514
|
+
message: `Registered ${candidateNames.join(", ")} in ${listenersFile} and wired listeners with registerListeners(...) in ${providersFile}.`,
|
|
4515
|
+
},
|
|
4516
|
+
],
|
|
4517
|
+
changes: [
|
|
4518
|
+
...(nextListeners === originalListeners
|
|
4519
|
+
? []
|
|
4520
|
+
: [
|
|
4521
|
+
planDoctorFixFileChange({
|
|
4522
|
+
file: listenersFile,
|
|
4523
|
+
before: originalListeners,
|
|
4524
|
+
after: nextListeners,
|
|
4525
|
+
}),
|
|
4526
|
+
]),
|
|
4527
|
+
...(nextProviders === originalProviders
|
|
4528
|
+
? []
|
|
4529
|
+
: [
|
|
4530
|
+
planDoctorFixFileChange({
|
|
4531
|
+
file: providersFile,
|
|
4532
|
+
before: originalProviders,
|
|
4533
|
+
after: nextProviders,
|
|
4534
|
+
}),
|
|
4535
|
+
]),
|
|
4536
|
+
],
|
|
4419
4537
|
};
|
|
4420
4538
|
}
|
|
4421
|
-
async function
|
|
4539
|
+
async function planUnregisteredOutboxEntries(targetDir, files, drift, config) {
|
|
4422
4540
|
if (!drift.outbox.exists)
|
|
4423
|
-
return
|
|
4541
|
+
return undefined;
|
|
4542
|
+
const outboxPath = path.join(targetDir, config.paths.outbox);
|
|
4543
|
+
let original;
|
|
4544
|
+
try {
|
|
4545
|
+
original = await readFile(outboxPath, "utf8");
|
|
4546
|
+
}
|
|
4547
|
+
catch {
|
|
4548
|
+
return undefined;
|
|
4549
|
+
}
|
|
4550
|
+
let next = original;
|
|
4424
4551
|
const fixes = [];
|
|
4425
|
-
const eventFix =
|
|
4426
|
-
targetDir,
|
|
4552
|
+
const eventFix = planWorkflowRegistryEdit({
|
|
4427
4553
|
files,
|
|
4428
4554
|
centralFile: config.paths.outbox,
|
|
4555
|
+
source: next,
|
|
4429
4556
|
unregistered: drift.outbox.events,
|
|
4430
4557
|
code: "BEIGNET_OUTBOX_EVENT_UNREGISTERED",
|
|
4431
4558
|
listName: "the outbox events list",
|
|
4432
4559
|
importSpecifier: (indexFile) => `@/${modulePath(indexFile)}`,
|
|
4433
4560
|
append: (source, entry, importLine) => appendToOutboxRegistryArray(source, "events", entry, importLine),
|
|
4434
4561
|
});
|
|
4435
|
-
if (eventFix)
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4562
|
+
if (eventFix) {
|
|
4563
|
+
next = eventFix.after;
|
|
4564
|
+
fixes.push(eventFix.fix);
|
|
4565
|
+
}
|
|
4566
|
+
const jobFix = planWorkflowRegistryEdit({
|
|
4439
4567
|
files,
|
|
4440
4568
|
centralFile: config.paths.outbox,
|
|
4569
|
+
source: next,
|
|
4441
4570
|
unregistered: drift.outbox.jobs,
|
|
4442
4571
|
code: "BEIGNET_OUTBOX_JOB_UNREGISTERED",
|
|
4443
4572
|
listName: "the outbox jobs list",
|
|
4444
4573
|
importSpecifier: (indexFile) => `@/${modulePath(indexFile)}`,
|
|
4445
4574
|
append: (source, entry, importLine) => appendToOutboxRegistryArray(source, "jobs", entry, importLine),
|
|
4446
4575
|
});
|
|
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)
|
|
4576
|
+
if (jobFix) {
|
|
4577
|
+
next = jobFix.after;
|
|
4578
|
+
fixes.push(jobFix.fix);
|
|
4579
|
+
}
|
|
4580
|
+
if (fixes.length === 0 || next === original)
|
|
4463
4581
|
return undefined;
|
|
4582
|
+
return {
|
|
4583
|
+
id: "outbox.register-missing",
|
|
4584
|
+
fixes,
|
|
4585
|
+
changes: [
|
|
4586
|
+
planDoctorFixFileChange({
|
|
4587
|
+
file: config.paths.outbox,
|
|
4588
|
+
before: original,
|
|
4589
|
+
after: next,
|
|
4590
|
+
}),
|
|
4591
|
+
],
|
|
4592
|
+
};
|
|
4593
|
+
}
|
|
4594
|
+
async function planWorkflowRegistryOperation(id, options) {
|
|
4464
4595
|
const centralPath = path.join(options.targetDir, options.centralFile);
|
|
4465
4596
|
let original;
|
|
4466
4597
|
try {
|
|
@@ -4469,7 +4600,26 @@ async function applyWorkflowRegistryFix(options) {
|
|
|
4469
4600
|
catch {
|
|
4470
4601
|
return undefined;
|
|
4471
4602
|
}
|
|
4472
|
-
|
|
4603
|
+
const edit = planWorkflowRegistryEdit({ ...options, source: original });
|
|
4604
|
+
if (!edit)
|
|
4605
|
+
return undefined;
|
|
4606
|
+
return {
|
|
4607
|
+
id,
|
|
4608
|
+
fixes: [edit.fix],
|
|
4609
|
+
changes: [
|
|
4610
|
+
planDoctorFixFileChange({
|
|
4611
|
+
file: options.centralFile,
|
|
4612
|
+
before: original,
|
|
4613
|
+
after: edit.after,
|
|
4614
|
+
}),
|
|
4615
|
+
],
|
|
4616
|
+
};
|
|
4617
|
+
}
|
|
4618
|
+
function planWorkflowRegistryEdit(options) {
|
|
4619
|
+
const candidates = options.unregistered.filter((entry) => entry.fullyUnregistered);
|
|
4620
|
+
if (candidates.length === 0)
|
|
4621
|
+
return undefined;
|
|
4622
|
+
let next = options.source;
|
|
4473
4623
|
const registeredNames = [];
|
|
4474
4624
|
for (const { registry } of candidates) {
|
|
4475
4625
|
const imported = parseNamedImportSources(next).get(registry.registryName);
|
|
@@ -4490,13 +4640,15 @@ async function applyWorkflowRegistryFix(options) {
|
|
|
4490
4640
|
registeredNames.push(registry.registryName);
|
|
4491
4641
|
}
|
|
4492
4642
|
}
|
|
4493
|
-
if (next ===
|
|
4643
|
+
if (next === options.source || registeredNames.length === 0)
|
|
4494
4644
|
return undefined;
|
|
4495
|
-
await writeFile(centralPath, next);
|
|
4496
4645
|
return {
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4646
|
+
after: next,
|
|
4647
|
+
fix: {
|
|
4648
|
+
code: options.code,
|
|
4649
|
+
file: options.centralFile,
|
|
4650
|
+
message: `Registered ${registeredNames.join(", ")} in ${options.listName}.`,
|
|
4651
|
+
},
|
|
4500
4652
|
};
|
|
4501
4653
|
}
|
|
4502
4654
|
function routeRegistryFileFromServerSource(source, serverFile, files) {
|
|
@@ -4588,26 +4740,8 @@ function firstCreateOpenAPIHandlerArgInfo(source) {
|
|
|
4588
4740
|
}
|
|
4589
4741
|
function firstCallArgInfo(source, argStart) {
|
|
4590
4742
|
let depth = 0;
|
|
4591
|
-
|
|
4592
|
-
let escaped = false;
|
|
4593
|
-
for (let index = argStart; index < source.length; index++) {
|
|
4743
|
+
for (const index of codeCharacterIndexes(source, argStart)) {
|
|
4594
4744
|
const char = source[index];
|
|
4595
|
-
if (inString) {
|
|
4596
|
-
if (escaped) {
|
|
4597
|
-
escaped = false;
|
|
4598
|
-
}
|
|
4599
|
-
else if (char === "\\") {
|
|
4600
|
-
escaped = true;
|
|
4601
|
-
}
|
|
4602
|
-
else if (char === inString) {
|
|
4603
|
-
inString = undefined;
|
|
4604
|
-
}
|
|
4605
|
-
continue;
|
|
4606
|
-
}
|
|
4607
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
4608
|
-
inString = char;
|
|
4609
|
-
continue;
|
|
4610
|
-
}
|
|
4611
4745
|
if (char === "[" || char === "(" || char === "{") {
|
|
4612
4746
|
depth++;
|
|
4613
4747
|
continue;
|