@beignet/cli 0.0.39 → 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.
Files changed (112) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +110 -16
  3. package/dist/analysis/source-index.d.ts +38 -0
  4. package/dist/analysis/source-index.d.ts.map +1 -0
  5. package/dist/analysis/source-index.js +271 -0
  6. package/dist/analysis/source-index.js.map +1 -0
  7. package/dist/analysis/workspace.d.ts +16 -0
  8. package/dist/analysis/workspace.d.ts.map +1 -0
  9. package/dist/analysis/workspace.js +134 -0
  10. package/dist/analysis/workspace.js.map +1 -0
  11. package/dist/app-map-schema.d.ts +5 -0
  12. package/dist/app-map-schema.d.ts.map +1 -0
  13. package/dist/app-map-schema.js +26 -0
  14. package/dist/app-map-schema.js.map +1 -0
  15. package/dist/app-map.d.ts +96 -0
  16. package/dist/app-map.d.ts.map +1 -0
  17. package/dist/app-map.js +1141 -0
  18. package/dist/app-map.js.map +1 -0
  19. package/dist/check.d.ts +7 -0
  20. package/dist/check.d.ts.map +1 -1
  21. package/dist/check.js +20 -5
  22. package/dist/check.js.map +1 -1
  23. package/dist/config.d.ts +1 -1
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +4 -2
  26. package/dist/config.js.map +1 -1
  27. package/dist/db.d.ts +32 -7
  28. package/dist/db.d.ts.map +1 -1
  29. package/dist/db.js +182 -14
  30. package/dist/db.js.map +1 -1
  31. package/dist/explain.d.ts +98 -0
  32. package/dist/explain.d.ts.map +1 -0
  33. package/dist/explain.js +512 -0
  34. package/dist/explain.js.map +1 -0
  35. package/dist/framework.d.ts +3 -0
  36. package/dist/framework.d.ts.map +1 -0
  37. package/dist/framework.js +6 -0
  38. package/dist/framework.js.map +1 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +79 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/inspect.d.ts +4 -2
  43. package/dist/inspect.d.ts.map +1 -1
  44. package/dist/inspect.js +173 -36
  45. package/dist/inspect.js.map +1 -1
  46. package/dist/lib.d.ts +4 -0
  47. package/dist/lib.d.ts.map +1 -1
  48. package/dist/lib.js +2 -0
  49. package/dist/lib.js.map +1 -1
  50. package/dist/make/payments.d.ts.map +1 -1
  51. package/dist/make/payments.js +2 -0
  52. package/dist/make/payments.js.map +1 -1
  53. package/dist/make.d.ts.map +1 -1
  54. package/dist/make.js +157 -3
  55. package/dist/make.js.map +1 -1
  56. package/dist/mcp.d.ts +1 -1
  57. package/dist/mcp.d.ts.map +1 -1
  58. package/dist/mcp.js +69 -1
  59. package/dist/mcp.js.map +1 -1
  60. package/dist/preflight.d.ts.map +1 -1
  61. package/dist/preflight.js +10 -0
  62. package/dist/preflight.js.map +1 -1
  63. package/dist/provider-add.d.ts.map +1 -1
  64. package/dist/provider-add.js +167 -13
  65. package/dist/provider-add.js.map +1 -1
  66. package/dist/provider-audit.d.ts +6 -0
  67. package/dist/provider-audit.d.ts.map +1 -1
  68. package/dist/provider-audit.js +63 -15
  69. package/dist/provider-audit.js.map +1 -1
  70. package/dist/templates/agents.d.ts.map +1 -1
  71. package/dist/templates/agents.js +17 -4
  72. package/dist/templates/agents.js.map +1 -1
  73. package/dist/templates/base.d.ts.map +1 -1
  74. package/dist/templates/base.js +10 -4
  75. package/dist/templates/base.js.map +1 -1
  76. package/dist/templates/index.d.ts +1 -1
  77. package/dist/templates/index.d.ts.map +1 -1
  78. package/dist/templates/index.js +8 -1
  79. package/dist/templates/index.js.map +1 -1
  80. package/dist/templates/server.d.ts +3 -0
  81. package/dist/templates/server.d.ts.map +1 -1
  82. package/dist/templates/server.js +55 -1
  83. package/dist/templates/server.js.map +1 -1
  84. package/dist/templates/shadcn.js +1 -1
  85. package/dist/templates/shared.js +1 -1
  86. package/package.json +2 -2
  87. package/skills/app-structure/SKILL.md +38 -5
  88. package/src/analysis/source-index.ts +395 -0
  89. package/src/analysis/workspace.ts +180 -0
  90. package/src/app-map-schema.ts +28 -0
  91. package/src/app-map.ts +1705 -0
  92. package/src/check.ts +27 -4
  93. package/src/config.ts +7 -3
  94. package/src/db.ts +232 -14
  95. package/src/explain.ts +786 -0
  96. package/src/framework.ts +13 -0
  97. package/src/index.ts +113 -2
  98. package/src/inspect.ts +259 -40
  99. package/src/lib.ts +36 -0
  100. package/src/make/payments.ts +7 -0
  101. package/src/make.ts +232 -2
  102. package/src/mcp.ts +107 -1
  103. package/src/preflight.ts +14 -0
  104. package/src/provider-add.ts +211 -12
  105. package/src/provider-audit.ts +127 -22
  106. package/src/templates/agents.ts +17 -4
  107. package/src/templates/base.ts +22 -4
  108. package/src/templates/index.ts +18 -1
  109. package/src/templates/server.ts +58 -1
  110. package/src/templates/shadcn.ts +1 -1
  111. package/src/templates/shared.ts +1 -1
  112. package/src/test-helpers/generated-app.ts +1 -1
package/dist/inspect.js CHANGED
@@ -19,10 +19,7 @@ export async function inspectApp(options = {}) {
19
19
  : await loadBeignetConfig(targetDir, files);
20
20
  const convention = inspectConvention(files, config);
21
21
  const contracts = await readContracts(targetDir, files, config);
22
- const routeFiles = files.filter((file) => file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
23
- file.endsWith("/route.ts"));
24
- const routeExports = await readRouteExports(targetDir, routeFiles, config);
25
- const matchedRoutes = matchRoutes(contracts, routeExports);
22
+ const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts);
26
23
  const diagnostics = await inspectDiagnostics(targetDir, files, config, convention, contracts, matchedRoutes, Boolean(options.strict));
27
24
  return {
28
25
  schemaVersion: 1,
@@ -45,10 +42,7 @@ export async function applyDoctorFixes(options = {}) {
45
42
  : await loadBeignetConfig(targetDir, files);
46
43
  const convention = inspectConvention(files, config);
47
44
  const contracts = await readContracts(targetDir, files, config);
48
- const routeFiles = files.filter((file) => file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
49
- file.endsWith("/route.ts"));
50
- const routeExports = await readRouteExports(targetDir, routeFiles, config);
51
- const matchedRoutes = matchRoutes(contracts, routeExports);
45
+ const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts);
52
46
  const fixes = [];
53
47
  const packageFix = await fixPackageScripts(targetDir, files, convention);
54
48
  if (packageFix)
@@ -85,7 +79,9 @@ export function formatRoutes(result) {
85
79
  contract: route.exportName,
86
80
  handler: route.handlerSource === "missing"
87
81
  ? "missing"
88
- : `${route.handlerFile ?? "unknown"}:${route.handlerExport ?? route.method}`,
82
+ : route.handlerSource === "web-server"
83
+ ? `${route.handlerFile ?? "unknown"}:registry`
84
+ : `${route.handlerFile ?? "unknown"}:${route.handlerExport ?? route.method}`,
89
85
  }));
90
86
  return table([
91
87
  ["METHOD", "PATH", "CONTRACT", "HANDLER"],
@@ -265,6 +261,10 @@ function inspectConvention(files, config) {
265
261
  `${directoryPath(config.paths.routes)}/`,
266
262
  config.paths.server,
267
263
  ]);
264
+ const missingWebLayout = missingRequirements(files, [
265
+ `${directoryPath(config.paths.contracts)}/`,
266
+ config.paths.server,
267
+ ]);
268
268
  const missingResourceGenerator = missingRequirements(files, [
269
269
  config.paths.appContext,
270
270
  config.paths.portWiring,
@@ -273,13 +273,22 @@ function inspectConvention(files, config) {
273
273
  config.paths.useCaseBuilder,
274
274
  ]);
275
275
  const nextLayout = missingNextLayout.length === 0;
276
+ const webLayout = missingWebLayout.length === 0;
276
277
  const resourceGenerator = missingResourceGenerator.length === 0;
277
278
  return {
278
- kind: resourceGenerator ? "standard" : nextLayout ? "next" : "custom",
279
+ kind: resourceGenerator
280
+ ? "standard"
281
+ : config.framework === "web" && webLayout
282
+ ? "web"
283
+ : nextLayout
284
+ ? "next"
285
+ : "custom",
279
286
  nextLayout,
287
+ webLayout,
280
288
  resourceGenerator,
281
289
  configFile: config.configFile,
282
290
  missingNextLayout,
291
+ missingWebLayout,
283
292
  missingResourceGenerator,
284
293
  };
285
294
  }
@@ -299,7 +308,7 @@ function parseContracts(source, file) {
299
308
  const exportRegex = /(?:^|\n)export const\s+([A-Za-z_$][\w$]*)\s*=([\s\S]*?)(?=\nexport const\s+[A-Za-z_$][\w$]*\s*=|$)/g;
300
309
  for (const exportMatch of source.matchAll(exportRegex)) {
301
310
  const block = exportMatch[2];
302
- const methodMatch = /([A-Za-z_$][\w$]*)(?:\s*\.\s*[A-Za-z_$][\w$]*\([^)]*\))*\s*\.\s*(get|post|put|patch|delete|head|options)\(\s*["']([^"']+)["']\s*\)/.exec(block);
311
+ const methodMatch = /([A-Za-z_$][\w$]*)(?:\s*\.\s*[A-Za-z_$][\w$]*\([^)]*\))*\s*\.\s*(get|post|put|patch|delete|head|options)\(\s*["']([^"']+)["'](?:\s*,\s*["'][^"']+["'])?\s*\)/.exec(block);
303
312
  if (methodMatch) {
304
313
  const receiver = methodMatch[1];
305
314
  const pathPrefix = groupPrefixes.get(receiver) ?? "";
@@ -801,6 +810,42 @@ function matchRoutes(contracts, routeExports) {
801
810
  unmatchedRouteHandlers,
802
811
  };
803
812
  }
813
+ async function inspectRouteSurface(targetDir, files, config, contracts) {
814
+ if (config.framework === "next") {
815
+ const routeFiles = files.filter((file) => file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
816
+ file.endsWith("/route.ts"));
817
+ const routeExports = await readRouteExports(targetDir, routeFiles, config);
818
+ return matchRoutes(contracts, routeExports);
819
+ }
820
+ if (!files.includes(config.paths.server)) {
821
+ return {
822
+ routes: contracts.map((contract) => ({
823
+ ...contract,
824
+ handlerSource: "missing",
825
+ })),
826
+ unmatchedRouteHandlers: [],
827
+ };
828
+ }
829
+ const serverSource = await readFile(path.join(targetDir, config.paths.server), "utf8");
830
+ const routeGroups = await readFeatureRouteGroups(targetDir, files, config);
831
+ const registeredGroups = await registeredRouteGroupsForServer(targetDir, files, config, serverSource);
832
+ const registeredContracts = contractsForRegisteredRouteGroups(contracts, routeGroups, registeredGroups);
833
+ const registeredKeys = new Set(registeredContracts.map(contractKey));
834
+ const handlerFile = routeRegistryFileFromServerSource(serverSource, config.paths.server, files) ?? config.paths.server;
835
+ return {
836
+ routes: contracts.map((contract) => registeredKeys.has(contractKey(contract))
837
+ ? {
838
+ ...contract,
839
+ handlerFile,
840
+ handlerSource: "web-server",
841
+ }
842
+ : {
843
+ ...contract,
844
+ handlerSource: "missing",
845
+ }),
846
+ unmatchedRouteHandlers: [],
847
+ };
848
+ }
804
849
  function findContract(contracts, routeExport) {
805
850
  return findContracts(contracts, routeExport)[0];
806
851
  }
@@ -828,30 +873,66 @@ function contractPathMatchesCatchAll(contractPath, catchAllPrefix) {
828
873
  }
829
874
  async function inspectDiagnostics(targetDir, files, config, convention, contracts, matchedRoutes, strict) {
830
875
  const diagnostics = [];
831
- if (!convention.nextLayout) {
876
+ const frameworkLayout = config.framework === "next" ? convention.nextLayout : convention.webLayout;
877
+ const missingFrameworkLayout = config.framework === "next"
878
+ ? convention.missingNextLayout
879
+ : convention.missingWebLayout;
880
+ if (!frameworkLayout) {
881
+ const expectedLayout = config.framework === "next"
882
+ ? `${directoryPath(config.paths.contracts)}/, ${directoryPath(config.paths.routes)}/, and ${config.paths.server}`
883
+ : `${directoryPath(config.paths.contracts)}/ and ${config.paths.server}`;
884
+ const layoutName = config.framework === "next"
885
+ ? "standard Beignet app layout"
886
+ : "standard Beignet web app layout";
832
887
  diagnostics.push({
833
888
  severity: "warning",
834
889
  code: "BEIGNET_APP_LAYOUT_NOT_FOUND",
835
- message: `This directory does not match the standard Beignet app layout. CLI inspection expects ${directoryPath(config.paths.contracts)}/, ${directoryPath(config.paths.routes)}/, and ${config.paths.server}. Missing: ${convention.missingNextLayout.join(", ")}. Add the missing app files or configure their paths in beignet.config.ts.`,
890
+ message: `This directory does not match the ${layoutName}. CLI inspection expects ${expectedLayout}. Missing: ${missingFrameworkLayout.join(", ")}. Add the missing app files or configure their paths in beignet.config.ts.`,
836
891
  });
837
892
  }
838
- for (const route of matchedRoutes.routes) {
839
- if (route.handlerSource !== "missing")
840
- continue;
841
- diagnostics.push({
842
- severity: "error",
843
- code: "BEIGNET_ROUTE_MISSING",
844
- file: route.file,
845
- contract: route.exportName,
846
- message: `${route.exportName} (${route.method} ${route.path}) has no matching Next route handler. Add ${methodArticle(route.method)} ${route.method} export to ${directoryPath(config.paths.routes)}/[[...path]]/route.ts or a matching route file, or remove the unused contract.`,
847
- });
893
+ diagnostics.push(...(await inspectFrameworkAdapter(targetDir, files, config)));
894
+ if (config.framework === "next" || !convention.resourceGenerator) {
895
+ for (const route of matchedRoutes.routes) {
896
+ if (route.handlerSource !== "missing")
897
+ continue;
898
+ diagnostics.push({
899
+ severity: "error",
900
+ code: "BEIGNET_ROUTE_MISSING",
901
+ file: route.file,
902
+ contract: route.exportName,
903
+ message: config.framework === "next"
904
+ ? `${route.exportName} (${route.method} ${route.path}) has no matching Next route handler. Add ${methodArticle(route.method)} ${route.method} export to ${directoryPath(config.paths.routes)}/[[...path]]/route.ts or a matching route file, or remove the unused contract.`
905
+ : `${route.exportName} (${route.method} ${route.path}) is not registered in the route surface passed to createFetchServer(...). Register the contract through defineRoutes(...) or remove the unused contract.`,
906
+ });
907
+ }
848
908
  }
849
- for (const routeHandler of matchedRoutes.unmatchedRouteHandlers) {
850
- diagnostics.push(unmatchedRouteDiagnostic(routeHandler));
909
+ if (config.framework === "next") {
910
+ for (const routeHandler of matchedRoutes.unmatchedRouteHandlers) {
911
+ diagnostics.push(unmatchedRouteDiagnostic(routeHandler));
912
+ }
851
913
  }
852
- diagnostics.push(...(await inspectPackageScripts(targetDir, files, convention)), ...(await inspectOpenApiDrift(targetDir, files, config, convention, contracts, matchedRoutes)), ...(await inspectFeatureRouteRegistration(targetDir, files, config, convention, contracts)), ...(await inspectRouteErrorCatalogDrift(targetDir, files, config, convention, contracts, strict)), ...(await inspectCanonicalConformance(targetDir, files, config, convention, strict)), ...(await inspectVersionSkew(targetDir, files)), ...(await inspectProviderMetadataDrift(targetDir, files, convention)), ...(await inspectPortProviderDrift(targetDir, files, config, convention)), ...(await inspectProviderRegistrationDrift(targetDir, files, config, convention)), ...(await inspectDatabaseLifecycleDrift(targetDir, files, config, convention, strict)), ...(await inspectAuthzAuditDrift(targetDir, files, config, convention, contracts)), ...(await inspectServerlessFootguns(targetDir, files, config, convention)), ...(await inspectProductionReadiness(targetDir, files, config, convention)), ...(await inspectFeatureArtifactDrift(targetDir, files, config, convention)), ...(await inspectWorkflowRegistrationDrift(targetDir, files, config, convention)), ...(await inspectRuntimeManifestDrift(targetDir, files, config, convention)), ...(await inspectResourceSlices(targetDir, files, config, convention, strict)));
914
+ diagnostics.push(...(await inspectPackageScripts(targetDir, files, convention)), ...(await inspectOpenApiDrift(targetDir, files, config, convention, contracts, matchedRoutes)), ...(await inspectFeatureRouteRegistration(targetDir, files, config, convention, contracts)), ...(await inspectRouteErrorCatalogDrift(targetDir, files, config, convention, contracts, strict)), ...(await inspectCanonicalConformance(targetDir, files, config, convention, strict)), ...(await inspectVersionSkew(targetDir, files)), ...(await inspectProviderMetadataDrift(targetDir, files, convention)), ...(await inspectPortProviderDrift(targetDir, files, config, convention)), ...(await inspectProviderRegistrationDrift(targetDir, files, config, convention)), ...(await inspectInngestProductionPath(targetDir, files, config)), ...(await inspectDatabaseLifecycleDrift(targetDir, files, config, convention, strict)), ...(await inspectAuthzAuditDrift(targetDir, files, config, convention, contracts)), ...(await inspectServerlessFootguns(targetDir, files, config, convention)), ...(await inspectProductionReadiness(targetDir, files, config, convention)), ...(await inspectFeatureArtifactDrift(targetDir, files, config, convention)), ...(await inspectWorkflowRegistrationDrift(targetDir, files, config, convention)), ...(await inspectRuntimeManifestDrift(targetDir, files, config, convention)), ...(await inspectResourceSlices(targetDir, files, config, convention, strict)));
853
915
  return dedupeDiagnostics(diagnostics);
854
916
  }
917
+ async function inspectFrameworkAdapter(targetDir, files, config) {
918
+ if (!files.includes(config.paths.server))
919
+ return [];
920
+ const source = await readFile(path.join(targetDir, config.paths.server), "utf8");
921
+ const expectedFactory = config.framework === "next" ? "createNextServer" : "createFetchServer";
922
+ const otherFactory = config.framework === "next" ? "createFetchServer" : "createNextServer";
923
+ if (containsCallExpression(source, expectedFactory) ||
924
+ !containsCallExpression(source, otherFactory)) {
925
+ return [];
926
+ }
927
+ return [
928
+ {
929
+ severity: "error",
930
+ code: "BEIGNET_FRAMEWORK_MISMATCH",
931
+ file: config.paths.server,
932
+ message: `${config.configFile ?? "beignet.config.*"} selects framework: "${config.framework}", but ${config.paths.server} calls ${otherFactory}(...). Use ${expectedFactory}(...) or update the configured framework profile.`,
933
+ },
934
+ ];
935
+ }
855
936
  async function inspectCanonicalConformance(targetDir, files, config, convention, strict) {
856
937
  if (!strict || !convention.resourceGenerator)
857
938
  return [];
@@ -967,11 +1048,12 @@ async function inspectCanonicalConformance(targetDir, files, config, convention,
967
1048
  });
968
1049
  }
969
1050
  if (!importsAppContext(serverSource, config.paths.server, config, files)) {
1051
+ const serverFactory = config.framework === "next" ? "createNextServer" : "createFetchServer";
970
1052
  diagnostics.push({
971
1053
  severity: "warning",
972
1054
  code: "BEIGNET_SERVER_APP_CONTEXT",
973
1055
  file: config.paths.server,
974
- message: `${config.paths.server} should import type { AppContext } from ${config.paths.appContext} and thread that context through createNextServer and hooks.`,
1056
+ message: `${config.paths.server} should import type { AppContext } from ${config.paths.appContext} and thread that context through ${serverFactory} and hooks.`,
975
1057
  });
976
1058
  }
977
1059
  // Apps may keep the context blueprint in a sibling server/context.ts
@@ -1427,7 +1509,7 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
1427
1509
  }
1428
1510
  }
1429
1511
  if (installedPackages.has("@beignet/provider-auth-better-auth")) {
1430
- if (!hasBetterAuthRoute(files, config)) {
1512
+ if (config.framework === "next" && !hasBetterAuthRoute(files, config)) {
1431
1513
  diagnostics.push({
1432
1514
  severity: "warning",
1433
1515
  code: "BEIGNET_AUTH_ROUTE_MISSING",
@@ -1573,7 +1655,8 @@ async function inspectPaymentsProductionReadiness(targetDir, files, config, sour
1573
1655
  message: "@beignet/provider-payments-stripe is installed and Stripe env/config is present, but server/providers.ts still registers memory payments. Register createStripePaymentsProvider() before deploying Stripe-backed billing, or remove the Stripe provider/config until the app is ready to switch.",
1574
1656
  });
1575
1657
  }
1576
- if (!(await hasPaymentWebhookRoute(targetDir, files, config, sourceCache))) {
1658
+ if (config.framework === "next" &&
1659
+ !(await hasPaymentWebhookRoute(targetDir, files, config, sourceCache))) {
1577
1660
  diagnostics.push({
1578
1661
  severity: "warning",
1579
1662
  code: "BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
@@ -1817,6 +1900,56 @@ async function inspectProviderRegistrationDrift(targetDir, files, config, conven
1817
1900
  }
1818
1901
  return diagnostics;
1819
1902
  }
1903
+ async function inspectInngestProductionPath(targetDir, files, config) {
1904
+ const packageJson = await readPackageJson(targetDir, files);
1905
+ if (!installedPackageNames(packageJson).has("@beignet/provider-jobs-inngest")) {
1906
+ return [];
1907
+ }
1908
+ const diagnostics = [];
1909
+ const serverDir = directoryPath(path.dirname(config.paths.server));
1910
+ const registryFile = `${serverDir}/inngest.ts`;
1911
+ const routeFile = `${directoryPath(config.paths.routes)}/inngest/route.ts`;
1912
+ if (!files.includes(registryFile)) {
1913
+ diagnostics.push({
1914
+ severity: "warning",
1915
+ code: "BEIGNET_INNGEST_REGISTRY_MISSING",
1916
+ file: registryFile,
1917
+ 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.`,
1918
+ });
1919
+ }
1920
+ else {
1921
+ const registrySource = await readFile(path.join(targetDir, registryFile), "utf8");
1922
+ const registry = arrayInitializerInfo(registrySource, "inngestJobs");
1923
+ const registeredJobs = registry
1924
+ ? identifiersFromArrayExpression(registry.text)
1925
+ : new Set();
1926
+ const featureRoot = directoryPath(config.paths.features);
1927
+ const jobIndexes = files.filter((file) => new RegExp(`^${escapeRegExp(featureRoot)}/[^/]+/jobs/index\\.ts$`).test(file));
1928
+ for (const jobIndex of jobIndexes) {
1929
+ const source = await readFile(path.join(targetDir, jobIndex), "utf8");
1930
+ const match = source.match(/export const\s+([A-Za-z_$][\w$]*Jobs)\s*=/);
1931
+ const registryName = match?.[1];
1932
+ if (!registryName || registeredJobs.has(registryName)) {
1933
+ continue;
1934
+ }
1935
+ diagnostics.push({
1936
+ severity: "warning",
1937
+ code: "BEIGNET_INNGEST_JOB_REGISTRATION_MISSING",
1938
+ file: registryFile,
1939
+ message: `${jobIndex} exports ${registryName}, but it is not registered in inngestJobs in ${registryFile}. Add ...${registryName} so Inngest can execute the job.`,
1940
+ });
1941
+ }
1942
+ }
1943
+ if (config.framework === "next" && !files.includes(routeFile)) {
1944
+ diagnostics.push({
1945
+ severity: "warning",
1946
+ code: "BEIGNET_INNGEST_ROUTE_MISSING",
1947
+ file: routeFile,
1948
+ 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.`,
1949
+ });
1950
+ }
1951
+ return diagnostics;
1952
+ }
1820
1953
  function isFeatureUploadDefinition(file, config) {
1821
1954
  const featuresPath = directoryPath(config.paths.features);
1822
1955
  return new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/uploads/[^/]+\\.ts$`).test(file);
@@ -2589,7 +2722,7 @@ function callReferencesCentralListenerRegistry(args) {
2589
2722
  }
2590
2723
  for (const identifier of args.callIdentifiers) {
2591
2724
  const imported = args.namedImports.get(identifier);
2592
- if (!imported || imported.importedName !== "listeners")
2725
+ if (imported?.importedName !== "listeners")
2593
2726
  continue;
2594
2727
  const importedFile = sourceFileFromImport(imported.sourcePath, args.importerFile, args.files);
2595
2728
  if (importedFile === args.listenerRegistryFile)
@@ -2615,7 +2748,8 @@ async function inspectServerlessFootguns(targetDir, files, config, convention) {
2615
2748
  const isProviderFile = /(^|\/)provider(s)?\.ts$/.test(file) ||
2616
2749
  /(^|\/)[^/]*provider[^/]*\.ts$/.test(file);
2617
2750
  const source = await readFile(path.join(targetDir, file), "utf8");
2618
- if (convention.resourceGenerator &&
2751
+ if (config.framework === "next" &&
2752
+ convention.resourceGenerator &&
2619
2753
  isNextRouteFile(file, config) &&
2620
2754
  importsEagerServer(source, file, files, config)) {
2621
2755
  diagnostics.push({
@@ -4039,13 +4173,15 @@ function registeredRouteGroups(source) {
4039
4173
  }
4040
4174
  async function registeredRouteGroupsForServer(targetDir, files, config, serverSource) {
4041
4175
  const registeredGroups = registeredRouteGroups(serverSource);
4042
- const imports = parseNamedImports(serverSource, config, config.paths.server);
4176
+ const imports = parseNamedImportSources(serverSource);
4043
4177
  for (const identifier of routeOptionIdentifiers(serverSource)) {
4044
4178
  const imported = imports.get(identifier);
4045
- if (!imported?.contractFile || !files.includes(imported.contractFile)) {
4179
+ if (!imported)
4046
4180
  continue;
4047
- }
4048
- const source = await readFile(path.join(targetDir, imported.contractFile), {
4181
+ const importedFile = sourceFileFromImport(imported.sourcePath, config.paths.server, files);
4182
+ if (!importedFile || !files.includes(importedFile))
4183
+ continue;
4184
+ const source = await readFile(path.join(targetDir, importedFile), {
4049
4185
  encoding: "utf8",
4050
4186
  });
4051
4187
  for (const routeGroup of registeredRouteGroups(source)) {
@@ -4661,7 +4797,8 @@ async function inspectResourceSlices(targetDir, files, config, convention, stric
4661
4797
  if (!hasRequirement(files, infrastructureDir)) {
4662
4798
  diagnostics.push(resourceDiagnostic("BEIGNET_RESOURCE_INFRASTRUCTURE_MISSING", resource, infrastructureDir));
4663
4799
  }
4664
- if (!hasRequirement(files, routeFile) &&
4800
+ if (config.framework === "next" &&
4801
+ !hasRequirement(files, routeFile) &&
4665
4802
  !hasRequirement(files, catchAllRouteFile)) {
4666
4803
  diagnostics.push(resourceDiagnostic("BEIGNET_RESOURCE_ROUTE_MISSING", resource, routeFile));
4667
4804
  }