@beignet/cli 0.0.40 → 0.0.41
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 +9 -0
- package/README.md +76 -10
- package/dist/analysis/source-index.d.ts +38 -0
- package/dist/analysis/source-index.d.ts.map +1 -0
- package/dist/analysis/source-index.js +271 -0
- package/dist/analysis/source-index.js.map +1 -0
- package/dist/analysis/workspace.d.ts +16 -0
- package/dist/analysis/workspace.d.ts.map +1 -0
- package/dist/analysis/workspace.js +134 -0
- package/dist/analysis/workspace.js.map +1 -0
- package/dist/app-map-schema.d.ts +5 -0
- package/dist/app-map-schema.d.ts.map +1 -0
- package/dist/app-map-schema.js +26 -0
- package/dist/app-map-schema.js.map +1 -0
- package/dist/app-map.d.ts +96 -0
- package/dist/app-map.d.ts.map +1 -0
- package/dist/app-map.js +1141 -0
- package/dist/app-map.js.map +1 -0
- package/dist/check.d.ts +7 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +20 -5
- package/dist/check.js.map +1 -1
- package/dist/db.d.ts +32 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +182 -14
- package/dist/db.js.map +1 -1
- package/dist/explain.d.ts +98 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +512 -0
- package/dist/explain.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect.js +53 -3
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +4 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +2 -0
- package/dist/lib.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +63 -2
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +69 -1
- package/dist/mcp.js.map +1 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +10 -0
- package/dist/preflight.js.map +1 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +167 -13
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.d.ts +6 -0
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +63 -15
- package/dist/provider-audit.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +17 -4
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +10 -4
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +8 -1
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +3 -0
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +55 -1
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.js +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +22 -4
- package/src/analysis/source-index.ts +395 -0
- package/src/analysis/workspace.ts +180 -0
- package/src/app-map-schema.ts +28 -0
- package/src/app-map.ts +1705 -0
- package/src/check.ts +27 -4
- package/src/db.ts +232 -14
- package/src/explain.ts +786 -0
- package/src/index.ts +113 -2
- package/src/inspect.ts +70 -2
- package/src/lib.ts +36 -0
- package/src/make.ts +90 -2
- package/src/mcp.ts +107 -1
- package/src/preflight.ts +14 -0
- package/src/provider-add.ts +211 -12
- package/src/provider-audit.ts +127 -22
- package/src/templates/agents.ts +17 -4
- package/src/templates/base.ts +22 -4
- package/src/templates/index.ts +18 -1
- package/src/templates/server.ts +58 -1
- package/src/templates/shared.ts +1 -1
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type StricliProcess,
|
|
14
14
|
text_en,
|
|
15
15
|
} from "@stricli/core";
|
|
16
|
+
import { type AppMapNodeKind, appMapNodeKinds } from "./app-map-schema.js";
|
|
16
17
|
import {
|
|
17
18
|
type CompletionShell,
|
|
18
19
|
type CreateProviderName,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
} from "./choices.js";
|
|
37
38
|
// Type-only: the runtime module stays behind the create command's lazy loader.
|
|
38
39
|
import type { CreateSelections } from "./create-prompts.js";
|
|
40
|
+
import type { ExplainTargetKind } from "./explain.js";
|
|
39
41
|
import type { AddProviderPresetResult } from "./provider-add.js";
|
|
40
42
|
import { getCliVersion } from "./version.js";
|
|
41
43
|
|
|
@@ -106,6 +108,13 @@ type RoutesFlags = {
|
|
|
106
108
|
cwd?: string;
|
|
107
109
|
};
|
|
108
110
|
|
|
111
|
+
type MapFlags = RoutesFlags & {
|
|
112
|
+
feature?: string;
|
|
113
|
+
kind?: readonly AppMapNodeKind[];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
type ExplainFlags = RoutesFlags;
|
|
117
|
+
|
|
109
118
|
type LintFlags = {
|
|
110
119
|
json?: boolean;
|
|
111
120
|
cwd?: string;
|
|
@@ -366,6 +375,21 @@ const routesFlagParameters = {
|
|
|
366
375
|
cwd: cwdFlag,
|
|
367
376
|
} satisfies FlagParametersForType<RoutesFlags, CliContext>;
|
|
368
377
|
|
|
378
|
+
const mapFlagParameters = {
|
|
379
|
+
json: jsonFlag,
|
|
380
|
+
feature: parsedStringFlag(
|
|
381
|
+
"Project one feature and its direct relationships.",
|
|
382
|
+
),
|
|
383
|
+
kind: {
|
|
384
|
+
kind: "enum",
|
|
385
|
+
values: appMapNodeKinds,
|
|
386
|
+
optional: true,
|
|
387
|
+
variadic: ",",
|
|
388
|
+
brief: "Include only these node kinds. Accepts comma-separated values.",
|
|
389
|
+
},
|
|
390
|
+
cwd: cwdFlag,
|
|
391
|
+
} satisfies FlagParametersForType<MapFlags, CliContext>;
|
|
392
|
+
|
|
369
393
|
const lintFlagParameters = {
|
|
370
394
|
json: jsonFlag,
|
|
371
395
|
cwd: cwdFlag,
|
|
@@ -585,6 +609,19 @@ const providerPresetPositional = {
|
|
|
585
609
|
],
|
|
586
610
|
} as const;
|
|
587
611
|
|
|
612
|
+
function explainTargetPositional(kind: ExplainTargetKind) {
|
|
613
|
+
return {
|
|
614
|
+
kind: "tuple",
|
|
615
|
+
parameters: [
|
|
616
|
+
{
|
|
617
|
+
parse: parseString,
|
|
618
|
+
placeholder: kind,
|
|
619
|
+
brief: `${kind[0]?.toUpperCase()}${kind.slice(1)} to explain.`,
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
} as const;
|
|
623
|
+
}
|
|
624
|
+
|
|
588
625
|
function resolveOutputFormat(flags: {
|
|
589
626
|
json?: boolean;
|
|
590
627
|
format?: OutputFormat;
|
|
@@ -829,6 +866,77 @@ const routesCommand = buildCommand<RoutesFlags, [], CliContext>({
|
|
|
829
866
|
},
|
|
830
867
|
});
|
|
831
868
|
|
|
869
|
+
const mapCommand = buildCommand<MapFlags, [], CliContext>({
|
|
870
|
+
docs: {
|
|
871
|
+
brief: "Map the app's architecture and registered workflows.",
|
|
872
|
+
fullDescription:
|
|
873
|
+
"Build a deterministic graph of features, HTTP contracts, routes, use cases, policies, workflows, ports, providers, OpenAPI, tests, dependencies, and doctor findings.",
|
|
874
|
+
},
|
|
875
|
+
parameters: {
|
|
876
|
+
flags: mapFlagParameters,
|
|
877
|
+
},
|
|
878
|
+
loader: async () => {
|
|
879
|
+
const { formatAppMap, mapApp, projectAppMap } = await import(
|
|
880
|
+
"./app-map.js"
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
return async function runMap(this: CliContext, flags: MapFlags) {
|
|
884
|
+
const result = projectAppMap(
|
|
885
|
+
await mapApp({ cwd: flags.cwd, strict: true }),
|
|
886
|
+
{
|
|
887
|
+
feature: flags.feature,
|
|
888
|
+
kinds: flags.kind,
|
|
889
|
+
},
|
|
890
|
+
);
|
|
891
|
+
writeOutput(
|
|
892
|
+
this,
|
|
893
|
+
flags.json ? JSON.stringify(result, null, 2) : formatAppMap(result),
|
|
894
|
+
);
|
|
895
|
+
};
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
function explainCommand(kind: ExplainTargetKind) {
|
|
900
|
+
return buildCommand<ExplainFlags, [string], CliContext>({
|
|
901
|
+
docs: {
|
|
902
|
+
brief: `Explain one mapped Beignet ${kind}.`,
|
|
903
|
+
fullDescription:
|
|
904
|
+
"Returns source-backed relationships, conventions, findings, suggested files, and follow-up commands without generating advice or changing the app.",
|
|
905
|
+
},
|
|
906
|
+
parameters: {
|
|
907
|
+
flags: routesFlagParameters,
|
|
908
|
+
positional: explainTargetPositional(kind),
|
|
909
|
+
},
|
|
910
|
+
loader: async () => {
|
|
911
|
+
const { explainApp, formatExplain } = await import("./explain.js");
|
|
912
|
+
|
|
913
|
+
return async function runExplain(
|
|
914
|
+
this: CliContext,
|
|
915
|
+
flags: ExplainFlags,
|
|
916
|
+
target: string,
|
|
917
|
+
) {
|
|
918
|
+
const result = await explainApp({ kind, target, cwd: flags.cwd });
|
|
919
|
+
writeOutput(
|
|
920
|
+
this,
|
|
921
|
+
flags.json ? JSON.stringify(result, null, 2) : formatExplain(result),
|
|
922
|
+
);
|
|
923
|
+
};
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const explainRoutes = buildRouteMap({
|
|
929
|
+
docs: {
|
|
930
|
+
brief: "Explain mapped Beignet app concepts with source evidence.",
|
|
931
|
+
},
|
|
932
|
+
routes: {
|
|
933
|
+
diagnostic: explainCommand("diagnostic"),
|
|
934
|
+
feature: explainCommand("feature"),
|
|
935
|
+
provider: explainCommand("provider"),
|
|
936
|
+
route: explainCommand("route"),
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
|
|
832
940
|
const doctorCommand = buildCommand<DoctorFlags, [], CliContext>({
|
|
833
941
|
docs: {
|
|
834
942
|
brief: "Inspect app wiring and framework conventions.",
|
|
@@ -1035,7 +1143,7 @@ const lintCommand = buildCommand<LintFlags, [], CliContext>({
|
|
|
1035
1143
|
const mcpCommand = buildCommand<McpFlags, [], CliContext>({
|
|
1036
1144
|
docs: {
|
|
1037
1145
|
brief:
|
|
1038
|
-
"Run a Model Context Protocol server exposing
|
|
1146
|
+
"Run a Model Context Protocol server exposing the app map, validation, and generators over stdio.",
|
|
1039
1147
|
},
|
|
1040
1148
|
parameters: {
|
|
1041
1149
|
flags: {
|
|
@@ -2109,7 +2217,7 @@ const makeRoutes = buildRouteMap({
|
|
|
2109
2217
|
const rootRoutes = buildRouteMap({
|
|
2110
2218
|
docs: {
|
|
2111
2219
|
brief: "Beignet CLI",
|
|
2112
|
-
fullDescription: `Create apps, generate framework files, inspect routes, and check Beignet conventions.
|
|
2220
|
+
fullDescription: `Create apps, generate framework files, map and explain architecture, inspect routes, and check Beignet conventions.
|
|
2113
2221
|
|
|
2114
2222
|
Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
|
|
2115
2223
|
},
|
|
@@ -2119,7 +2227,9 @@ Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
|
|
|
2119
2227
|
create: createCommand,
|
|
2120
2228
|
db: dbRoutes,
|
|
2121
2229
|
doctor: doctorCommand,
|
|
2230
|
+
explain: explainRoutes,
|
|
2122
2231
|
lint: lintCommand,
|
|
2232
|
+
map: mapCommand,
|
|
2123
2233
|
make: makeRoutes,
|
|
2124
2234
|
mcp: mcpCommand,
|
|
2125
2235
|
outbox: outboxRoutes,
|
|
@@ -2431,6 +2541,7 @@ ${openStep}
|
|
|
2431
2541
|
|
|
2432
2542
|
Inspect the app:
|
|
2433
2543
|
${cli} routes
|
|
2544
|
+
${cli} map
|
|
2434
2545
|
${cli} check
|
|
2435
2546
|
|
|
2436
2547
|
Generate a feature:
|
package/src/inspect.ts
CHANGED
|
@@ -618,7 +618,7 @@ function parseContracts(source: string, file: string): InspectedContract[] {
|
|
|
618
618
|
for (const exportMatch of source.matchAll(exportRegex)) {
|
|
619
619
|
const block = exportMatch[2];
|
|
620
620
|
const methodMatch =
|
|
621
|
-
/([A-Za-z_$][\w$]*)(?:\s*\.\s*[A-Za-z_$][\w$]*\([^)]*\))*\s*\.\s*(get|post|put|patch|delete|head|options)\(\s*["']([^"']+)["']
|
|
621
|
+
/([A-Za-z_$][\w$]*)(?:\s*\.\s*[A-Za-z_$][\w$]*\([^)]*\))*\s*\.\s*(get|post|put|patch|delete|head|options)\(\s*["']([^"']+)["'](?:\s*,\s*["'][^"']+["'])?\s*\)/.exec(
|
|
622
622
|
block,
|
|
623
623
|
);
|
|
624
624
|
if (methodMatch) {
|
|
@@ -1521,6 +1521,7 @@ async function inspectDiagnostics(
|
|
|
1521
1521
|
config,
|
|
1522
1522
|
convention,
|
|
1523
1523
|
)),
|
|
1524
|
+
...(await inspectInngestProductionPath(targetDir, files, config)),
|
|
1524
1525
|
...(await inspectDatabaseLifecycleDrift(
|
|
1525
1526
|
targetDir,
|
|
1526
1527
|
files,
|
|
@@ -3095,6 +3096,73 @@ async function inspectProviderRegistrationDrift(
|
|
|
3095
3096
|
return diagnostics;
|
|
3096
3097
|
}
|
|
3097
3098
|
|
|
3099
|
+
async function inspectInngestProductionPath(
|
|
3100
|
+
targetDir: string,
|
|
3101
|
+
files: string[],
|
|
3102
|
+
config: ResolvedBeignetConfig,
|
|
3103
|
+
): Promise<InspectDiagnostic[]> {
|
|
3104
|
+
const packageJson = await readPackageJson(targetDir, files);
|
|
3105
|
+
if (
|
|
3106
|
+
!installedPackageNames(packageJson).has("@beignet/provider-jobs-inngest")
|
|
3107
|
+
) {
|
|
3108
|
+
return [];
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
const diagnostics: InspectDiagnostic[] = [];
|
|
3112
|
+
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
3113
|
+
const registryFile = `${serverDir}/inngest.ts`;
|
|
3114
|
+
const routeFile = `${directoryPath(config.paths.routes)}/inngest/route.ts`;
|
|
3115
|
+
|
|
3116
|
+
if (!files.includes(registryFile)) {
|
|
3117
|
+
diagnostics.push({
|
|
3118
|
+
severity: "warning",
|
|
3119
|
+
code: "BEIGNET_INNGEST_REGISTRY_MISSING",
|
|
3120
|
+
file: registryFile,
|
|
3121
|
+
message: `@beignet/provider-jobs-inngest is installed, but ${registryFile} is missing. Run beignet provider add jobs-inngest or create a central createInngestJobFunctions(...) registry so dispatched jobs can execute.`,
|
|
3122
|
+
});
|
|
3123
|
+
} else {
|
|
3124
|
+
const registrySource = await readFile(
|
|
3125
|
+
path.join(targetDir, registryFile),
|
|
3126
|
+
"utf8",
|
|
3127
|
+
);
|
|
3128
|
+
const registry = arrayInitializerInfo(registrySource, "inngestJobs");
|
|
3129
|
+
const registeredJobs = registry
|
|
3130
|
+
? identifiersFromArrayExpression(registry.text)
|
|
3131
|
+
: new Set<string>();
|
|
3132
|
+
const featureRoot = directoryPath(config.paths.features);
|
|
3133
|
+
const jobIndexes = files.filter((file) =>
|
|
3134
|
+
new RegExp(`^${escapeRegExp(featureRoot)}/[^/]+/jobs/index\\.ts$`).test(
|
|
3135
|
+
file,
|
|
3136
|
+
),
|
|
3137
|
+
);
|
|
3138
|
+
for (const jobIndex of jobIndexes) {
|
|
3139
|
+
const source = await readFile(path.join(targetDir, jobIndex), "utf8");
|
|
3140
|
+
const match = source.match(/export const\s+([A-Za-z_$][\w$]*Jobs)\s*=/);
|
|
3141
|
+
const registryName = match?.[1];
|
|
3142
|
+
if (!registryName || registeredJobs.has(registryName)) {
|
|
3143
|
+
continue;
|
|
3144
|
+
}
|
|
3145
|
+
diagnostics.push({
|
|
3146
|
+
severity: "warning",
|
|
3147
|
+
code: "BEIGNET_INNGEST_JOB_REGISTRATION_MISSING",
|
|
3148
|
+
file: registryFile,
|
|
3149
|
+
message: `${jobIndex} exports ${registryName}, but it is not registered in inngestJobs in ${registryFile}. Add ...${registryName} so Inngest can execute the job.`,
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
if (config.framework === "next" && !files.includes(routeFile)) {
|
|
3155
|
+
diagnostics.push({
|
|
3156
|
+
severity: "warning",
|
|
3157
|
+
code: "BEIGNET_INNGEST_ROUTE_MISSING",
|
|
3158
|
+
file: routeFile,
|
|
3159
|
+
message: `@beignet/provider-jobs-inngest is installed in a Next.js app, but ${routeFile} is missing. Expose the shared Inngest client and function registry with serve(...) so Inngest can discover and invoke jobs.`,
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
return diagnostics;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3098
3166
|
function isFeatureUploadDefinition(
|
|
3099
3167
|
file: string,
|
|
3100
3168
|
config: ResolvedBeignetConfig,
|
|
@@ -4358,7 +4426,7 @@ function callReferencesCentralListenerRegistry(args: {
|
|
|
4358
4426
|
|
|
4359
4427
|
for (const identifier of args.callIdentifiers) {
|
|
4360
4428
|
const imported = args.namedImports.get(identifier);
|
|
4361
|
-
if (
|
|
4429
|
+
if (imported?.importedName !== "listeners") continue;
|
|
4362
4430
|
|
|
4363
4431
|
const importedFile = sourceFileFromImport(
|
|
4364
4432
|
imported.sourcePath,
|
package/src/lib.ts
CHANGED
|
@@ -6,9 +6,45 @@
|
|
|
6
6
|
* loads command modules lazily.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
export type {
|
|
10
|
+
AppMapEdge,
|
|
11
|
+
AppMapEdgeKind,
|
|
12
|
+
AppMapNode,
|
|
13
|
+
AppMapNodeKind,
|
|
14
|
+
AppMapNodeStatus,
|
|
15
|
+
AppMapOptions,
|
|
16
|
+
AppMapProjectionOptions,
|
|
17
|
+
AppMapResult,
|
|
18
|
+
AppMapSource,
|
|
19
|
+
AppMapSummary,
|
|
20
|
+
AppMapUnresolvedReference,
|
|
21
|
+
} from "./app-map.js";
|
|
22
|
+
export {
|
|
23
|
+
appMapNodeKinds,
|
|
24
|
+
formatAppMap,
|
|
25
|
+
mapApp,
|
|
26
|
+
projectAppMap,
|
|
27
|
+
} from "./app-map.js";
|
|
9
28
|
export type { CreateOptions } from "./create.js";
|
|
10
29
|
export { createProject } from "./create.js";
|
|
11
30
|
export { runDatabaseCommand } from "./db.js";
|
|
31
|
+
export type {
|
|
32
|
+
ExplainAppOptions,
|
|
33
|
+
ExplainAppResult,
|
|
34
|
+
ExplainEvidence,
|
|
35
|
+
ExplainFinding,
|
|
36
|
+
ExplainRelatedNode,
|
|
37
|
+
ExplainRelationship,
|
|
38
|
+
ExplainSuggestedAction,
|
|
39
|
+
ExplainSuggestedFile,
|
|
40
|
+
ExplainTarget,
|
|
41
|
+
ExplainTargetKind,
|
|
42
|
+
} from "./explain.js";
|
|
43
|
+
export {
|
|
44
|
+
explainApp,
|
|
45
|
+
explainTargetKinds,
|
|
46
|
+
formatExplain,
|
|
47
|
+
} from "./explain.js";
|
|
12
48
|
export { main } from "./index.js";
|
|
13
49
|
export {
|
|
14
50
|
applyDoctorFixes,
|
package/src/make.ts
CHANGED
|
@@ -1090,6 +1090,7 @@ export async function makeJob(options: MakeJobOptions): Promise<MakeJobResult> {
|
|
|
1090
1090
|
await updateOutboxRegistry(targetDir, names, "jobs", config, {
|
|
1091
1091
|
dryRun: true,
|
|
1092
1092
|
});
|
|
1093
|
+
await updateInngestJobRegistry(targetDir, names, config, { dryRun: true });
|
|
1093
1094
|
|
|
1094
1095
|
const result = await makeFeatureArtifact({
|
|
1095
1096
|
targetDir,
|
|
@@ -1116,6 +1117,9 @@ export async function makeJob(options: MakeJobOptions): Promise<MakeJobResult> {
|
|
|
1116
1117
|
await applyOutboxPortWiring(result, targetDir, config, {
|
|
1117
1118
|
dryRun: Boolean(options.dryRun),
|
|
1118
1119
|
});
|
|
1120
|
+
await applyInngestJobRegistryUpdate(result, targetDir, names, config, {
|
|
1121
|
+
dryRun: Boolean(options.dryRun),
|
|
1122
|
+
});
|
|
1119
1123
|
|
|
1120
1124
|
return result;
|
|
1121
1125
|
}
|
|
@@ -3814,6 +3818,92 @@ async function applyOutboxRegistryUpdate(
|
|
|
3814
3818
|
}
|
|
3815
3819
|
}
|
|
3816
3820
|
|
|
3821
|
+
function inngestRegistryPath(config: ResolvedBeignetConfig): string {
|
|
3822
|
+
return path.posix.join(path.posix.dirname(config.paths.server), "inngest.ts");
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
async function appUsesInngestJobs(targetDir: string): Promise<boolean> {
|
|
3826
|
+
const source = await readOptionalFile(path.join(targetDir, "package.json"));
|
|
3827
|
+
if (source === undefined) return false;
|
|
3828
|
+
|
|
3829
|
+
try {
|
|
3830
|
+
const packageJson = JSON.parse(source) as PackageJsonLike;
|
|
3831
|
+
return Boolean(
|
|
3832
|
+
packageJson.dependencies?.["@beignet/provider-jobs-inngest"] ??
|
|
3833
|
+
packageJson.devDependencies?.["@beignet/provider-jobs-inngest"],
|
|
3834
|
+
);
|
|
3835
|
+
} catch {
|
|
3836
|
+
return false;
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
async function updateInngestJobRegistry(
|
|
3841
|
+
targetDir: string,
|
|
3842
|
+
names: JobNames,
|
|
3843
|
+
config: ResolvedBeignetConfig,
|
|
3844
|
+
options: { dryRun: boolean },
|
|
3845
|
+
): Promise<WriteGeneratedFileResult | undefined> {
|
|
3846
|
+
if (!(await appUsesInngestJobs(targetDir))) return undefined;
|
|
3847
|
+
|
|
3848
|
+
const registryPath = inngestRegistryPath(config);
|
|
3849
|
+
const destination = path.join(targetDir, registryPath);
|
|
3850
|
+
const existing = await readOptionalFile(destination);
|
|
3851
|
+
if (existing === undefined) {
|
|
3852
|
+
throw new Error(
|
|
3853
|
+
`@beignet/provider-jobs-inngest is installed, but ${registryPath} is missing. Run \`beignet provider add jobs-inngest\` to create the execution registry before generating jobs.`,
|
|
3854
|
+
);
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
const featureIndexPath = path.join(
|
|
3858
|
+
featureArtifactDir(names, "jobs", config),
|
|
3859
|
+
"index.ts",
|
|
3860
|
+
);
|
|
3861
|
+
const registryName = `${names.featureSingularCamel}Jobs`;
|
|
3862
|
+
const importLine = `import { ${registryName} } from "${aliasModule(featureIndexPath)}";`;
|
|
3863
|
+
let next = existing;
|
|
3864
|
+
if (!next.includes(importLine)) next = insertAfterImports(next, importLine);
|
|
3865
|
+
|
|
3866
|
+
const registry = arrayInitializerInfo(next, "inngestJobs");
|
|
3867
|
+
if (!registry) {
|
|
3868
|
+
throw new Error(
|
|
3869
|
+
`Could not find \`export const inngestJobs = [...]\` in ${registryPath}. Restore the generated registry or register ${registryName} manually.`,
|
|
3870
|
+
);
|
|
3871
|
+
}
|
|
3872
|
+
if (!identifiersFromArrayExpression(registry.text).has(registryName)) {
|
|
3873
|
+
const updatedArray = appendToArrayExpression(registry.text, [
|
|
3874
|
+
`...${registryName}`,
|
|
3875
|
+
]);
|
|
3876
|
+
next = `${next.slice(0, registry.start)}${updatedArray}${next.slice(
|
|
3877
|
+
registry.end,
|
|
3878
|
+
)}`;
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
if (next === existing) return "skipped";
|
|
3882
|
+
if (!options.dryRun) await writeFile(destination, next);
|
|
3883
|
+
return "updated";
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
async function applyInngestJobRegistryUpdate(
|
|
3887
|
+
result: MakeJobResult,
|
|
3888
|
+
targetDir: string,
|
|
3889
|
+
names: JobNames,
|
|
3890
|
+
config: ResolvedBeignetConfig,
|
|
3891
|
+
options: { dryRun: boolean },
|
|
3892
|
+
): Promise<void> {
|
|
3893
|
+
const registryResult = await updateInngestJobRegistry(
|
|
3894
|
+
targetDir,
|
|
3895
|
+
names,
|
|
3896
|
+
config,
|
|
3897
|
+
options,
|
|
3898
|
+
);
|
|
3899
|
+
if (registryResult === undefined) return;
|
|
3900
|
+
|
|
3901
|
+
const registryPath = inngestRegistryPath(config);
|
|
3902
|
+
if (registryResult === "updated") result.updatedFiles.push(registryPath);
|
|
3903
|
+
if (registryResult === "skipped") result.skippedFiles.push(registryPath);
|
|
3904
|
+
if (!result.files.includes(registryPath)) result.files.push(registryPath);
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3817
3907
|
function schemasFile(
|
|
3818
3908
|
names: ResourceNames,
|
|
3819
3909
|
mode: ResourceGenerationMode,
|
|
@@ -5145,8 +5235,6 @@ export const ${names.jobExportName} = defineJob("${names.jobName}", {
|
|
|
5145
5235
|
\tpayload: ${names.payloadSchemaName},
|
|
5146
5236
|
\tretry: retry.exponential({
|
|
5147
5237
|
\t\tattempts: 3,
|
|
5148
|
-
\t\tinitialDelay: "1s",
|
|
5149
|
-
\t\tmaxDelay: "1m",
|
|
5150
5238
|
\t}),
|
|
5151
5239
|
\tasync handle({ payload, ctx }) {
|
|
5152
5240
|
\t\tctx.ports.logger.info("Job handled", {
|
package/src/mcp.ts
CHANGED
|
@@ -2,7 +2,11 @@ import path from "node:path";
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { mapApp, projectAppMap } from "./app-map.js";
|
|
6
|
+
import { appMapNodeKinds } from "./app-map-schema.js";
|
|
7
|
+
import { checkApp } from "./check.js";
|
|
5
8
|
import { type ProviderPresetName, providerPresetChoices } from "./choices.js";
|
|
9
|
+
import { explainApp, explainTargetKinds } from "./explain.js";
|
|
6
10
|
import { applyDoctorFixes, inspectApp } from "./inspect.js";
|
|
7
11
|
import { lintApp } from "./lint.js";
|
|
8
12
|
import {
|
|
@@ -68,6 +72,8 @@ const makeArtifactChoices = [
|
|
|
68
72
|
"use-case",
|
|
69
73
|
] as const;
|
|
70
74
|
|
|
75
|
+
const defaultMcpCheckTimeoutMs = 10 * 60 * 1_000;
|
|
76
|
+
|
|
71
77
|
type MakeArtifact = (typeof makeArtifactChoices)[number];
|
|
72
78
|
|
|
73
79
|
const makeInputSchema = {
|
|
@@ -325,7 +331,7 @@ async function runProviderAddTool(
|
|
|
325
331
|
}
|
|
326
332
|
|
|
327
333
|
/**
|
|
328
|
-
* Build the Beignet MCP server with
|
|
334
|
+
* Build the Beignet MCP server with app-map, validation, and generator tools.
|
|
329
335
|
* Every tool resolves against the app directory captured at launch.
|
|
330
336
|
*/
|
|
331
337
|
export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
|
|
@@ -335,6 +341,106 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
|
|
|
335
341
|
version: options.version,
|
|
336
342
|
});
|
|
337
343
|
|
|
344
|
+
server.registerTool(
|
|
345
|
+
"app_map",
|
|
346
|
+
{
|
|
347
|
+
description:
|
|
348
|
+
"Return a deterministic graph of this Beignet app: features, contracts, routes, use cases, authorization, workflows, ports, providers, OpenAPI, tests, dependency edges, and validation findings. Project by feature or node kind to keep agent context focused.",
|
|
349
|
+
inputSchema: {
|
|
350
|
+
feature: z
|
|
351
|
+
.string()
|
|
352
|
+
.optional()
|
|
353
|
+
.describe("Project one feature and its direct relationships."),
|
|
354
|
+
kinds: z
|
|
355
|
+
.array(z.enum(appMapNodeKinds))
|
|
356
|
+
.optional()
|
|
357
|
+
.describe("Include only these app-map node kinds."),
|
|
358
|
+
includeDiagnostics: z
|
|
359
|
+
.boolean()
|
|
360
|
+
.optional()
|
|
361
|
+
.describe("Include doctor and lint findings. Defaults to true."),
|
|
362
|
+
},
|
|
363
|
+
annotations: { readOnlyHint: true },
|
|
364
|
+
},
|
|
365
|
+
async (input) =>
|
|
366
|
+
safeToolResult(async () => {
|
|
367
|
+
const result = await mapApp({ cwd, strict: true });
|
|
368
|
+
return jsonResult(
|
|
369
|
+
projectAppMap(result, {
|
|
370
|
+
feature: input.feature,
|
|
371
|
+
kinds: input.kinds,
|
|
372
|
+
includeDiagnostics: input.includeDiagnostics,
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
server.registerTool(
|
|
379
|
+
"explain",
|
|
380
|
+
{
|
|
381
|
+
description:
|
|
382
|
+
"Explain one mapped Beignet feature, route, provider, or diagnostic using deterministic source evidence. Returns relationships, relevant conventions, findings, suggested files, and follow-up commands without changing the app.",
|
|
383
|
+
inputSchema: {
|
|
384
|
+
kind: z.enum(explainTargetKinds).describe("Mapped concept to explain."),
|
|
385
|
+
target: z
|
|
386
|
+
.string()
|
|
387
|
+
.min(1)
|
|
388
|
+
.describe(
|
|
389
|
+
"Feature name, METHOD /path route, provider package or port name, or diagnostic code.",
|
|
390
|
+
),
|
|
391
|
+
},
|
|
392
|
+
annotations: { readOnlyHint: true },
|
|
393
|
+
},
|
|
394
|
+
async (input) =>
|
|
395
|
+
safeToolResult(async () =>
|
|
396
|
+
jsonResult(
|
|
397
|
+
await explainApp({ cwd, kind: input.kind, target: input.target }),
|
|
398
|
+
),
|
|
399
|
+
),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
server.registerTool(
|
|
403
|
+
"check",
|
|
404
|
+
{
|
|
405
|
+
description:
|
|
406
|
+
"Run the full Beignet app validation loop: Beignet lint, strict doctor, and the app's lint, typecheck, and test scripts. Returns the same versioned JSON as beignet check --json, including every step, bounded failure output, durations, and overall ok status. Does not apply Beignet fixes, but app-owned scripts run with their normal side effects.",
|
|
407
|
+
inputSchema: {
|
|
408
|
+
preflight: z
|
|
409
|
+
.boolean()
|
|
410
|
+
.optional()
|
|
411
|
+
.describe(
|
|
412
|
+
"Include the environment preflight check before app package scripts. Defaults to false.",
|
|
413
|
+
),
|
|
414
|
+
timeoutMs: z
|
|
415
|
+
.number()
|
|
416
|
+
.int()
|
|
417
|
+
.min(1_000)
|
|
418
|
+
.max(60 * 60 * 1_000)
|
|
419
|
+
.optional()
|
|
420
|
+
.describe(
|
|
421
|
+
"Maximum duration for each app package script in milliseconds. Defaults to 600000 (10 minutes).",
|
|
422
|
+
),
|
|
423
|
+
},
|
|
424
|
+
annotations: {
|
|
425
|
+
readOnlyHint: false,
|
|
426
|
+
destructiveHint: true,
|
|
427
|
+
idempotentHint: false,
|
|
428
|
+
openWorldHint: true,
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
async (input, extra) =>
|
|
432
|
+
safeToolResult(async () =>
|
|
433
|
+
jsonResult(
|
|
434
|
+
await checkApp({
|
|
435
|
+
cwd,
|
|
436
|
+
preflight: input.preflight ?? false,
|
|
437
|
+
scriptTimeoutMs: input.timeoutMs ?? defaultMcpCheckTimeoutMs,
|
|
438
|
+
signal: extra.signal,
|
|
439
|
+
}),
|
|
440
|
+
),
|
|
441
|
+
),
|
|
442
|
+
);
|
|
443
|
+
|
|
338
444
|
server.registerTool(
|
|
339
445
|
"routes",
|
|
340
446
|
{
|
package/src/preflight.ts
CHANGED
|
@@ -245,6 +245,20 @@ export async function runPreflight(
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
const inngestDev = env.INNGEST_DEV?.trim().toLowerCase();
|
|
249
|
+
if (
|
|
250
|
+
env.NODE_ENV === "production" &&
|
|
251
|
+
inngestDev !== undefined &&
|
|
252
|
+
!["", "0", "false"].includes(inngestDev)
|
|
253
|
+
) {
|
|
254
|
+
findings.push({
|
|
255
|
+
severity: "error",
|
|
256
|
+
code: "BEIGNET_PREFLIGHT_INNGEST_DEV_IN_PRODUCTION",
|
|
257
|
+
message:
|
|
258
|
+
"INNGEST_DEV enables the local Inngest dev server and must be disabled in production.",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
248
262
|
const exampleSource = await readFileIfExists(
|
|
249
263
|
path.join(options.cwd, ".env.example"),
|
|
250
264
|
);
|