@beignet/cli 0.0.52 → 0.0.53

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 (68) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +131 -1
  3. package/dist/analysis/layers.d.ts +2 -0
  4. package/dist/analysis/layers.d.ts.map +1 -1
  5. package/dist/analysis/layers.js +5 -0
  6. package/dist/analysis/layers.js.map +1 -1
  7. package/dist/analysis/port-wiring.d.ts +4 -2
  8. package/dist/analysis/port-wiring.d.ts.map +1 -1
  9. package/dist/analysis/port-wiring.js +39 -10
  10. package/dist/analysis/port-wiring.js.map +1 -1
  11. package/dist/analysis/source-index.d.ts +2 -1
  12. package/dist/analysis/source-index.d.ts.map +1 -1
  13. package/dist/analysis/source-index.js +34 -5
  14. package/dist/analysis/source-index.js.map +1 -1
  15. package/dist/analysis/workspace-routes.d.ts +8 -0
  16. package/dist/analysis/workspace-routes.d.ts.map +1 -0
  17. package/dist/analysis/workspace-routes.js +291 -0
  18. package/dist/analysis/workspace-routes.js.map +1 -0
  19. package/dist/analysis/workspace.d.ts +24 -3
  20. package/dist/analysis/workspace.d.ts.map +1 -1
  21. package/dist/analysis/workspace.js +166 -21
  22. package/dist/analysis/workspace.js.map +1 -1
  23. package/dist/app-map-changes.d.ts.map +1 -1
  24. package/dist/app-map-changes.js +19 -7
  25. package/dist/app-map-changes.js.map +1 -1
  26. package/dist/app-map.d.ts.map +1 -1
  27. package/dist/app-map.js +58 -55
  28. package/dist/app-map.js.map +1 -1
  29. package/dist/config.d.ts +7 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +19 -0
  32. package/dist/config.js.map +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +20 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/inspect.d.ts.map +1 -1
  37. package/dist/inspect.js +89 -21
  38. package/dist/inspect.js.map +1 -1
  39. package/dist/lint.d.ts.map +1 -1
  40. package/dist/lint.js +106 -38
  41. package/dist/lint.js.map +1 -1
  42. package/dist/make/shared.js +1 -1
  43. package/dist/make.js +18 -1
  44. package/dist/make.js.map +1 -1
  45. package/dist/provider-add.js +2 -2
  46. package/dist/provider-add.js.map +1 -1
  47. package/dist/provider-audit.d.ts.map +1 -1
  48. package/dist/provider-audit.js +22 -6
  49. package/dist/provider-audit.js.map +1 -1
  50. package/dist/templates/shared.js +3 -3
  51. package/package.json +3 -2
  52. package/skills/app-structure/SKILL.md +42 -0
  53. package/src/analysis/layers.ts +9 -0
  54. package/src/analysis/port-wiring.ts +66 -10
  55. package/src/analysis/source-index.ts +56 -4
  56. package/src/analysis/workspace-routes.ts +385 -0
  57. package/src/analysis/workspace.ts +281 -34
  58. package/src/app-map-changes.ts +31 -5
  59. package/src/app-map.ts +75 -75
  60. package/src/config.ts +33 -0
  61. package/src/index.ts +25 -0
  62. package/src/inspect.ts +123 -16
  63. package/src/lint.ts +146 -43
  64. package/src/make/shared.ts +1 -1
  65. package/src/make.ts +31 -1
  66. package/src/provider-add.ts +2 -2
  67. package/src/provider-audit.ts +30 -10
  68. package/src/templates/shared.ts +3 -3
package/dist/inspect.js CHANGED
@@ -8,7 +8,9 @@ import { contractFactoryBindings, isImportedContractFactoryCall, } from "./analy
8
8
  import { canonicalFeatureLayerPath } from "./analysis/layers.js";
9
9
  import { analyzePortWiringFiles } from "./analysis/port-wiring.js";
10
10
  import { analysisSourceFileExtensions, isAnalysisSourceFile, isAnalysisSourceFileExtension, isJavaScriptSourceFile, javascriptAnalysisSourceFileExtensions, sourceFileScriptKind, stripSourceFileExtension, typescriptAnalysisSourceFileExtensions, } from "./analysis/source-files.js";
11
- import { exportedVariableDeclarations } from "./analysis/source-index.js";
11
+ import { createSourceIndex, exportedVariableDeclarations, } from "./analysis/source-index.js";
12
+ import { createAnalysisWorkspace, projectPath } from "./analysis/workspace.js";
13
+ import { workspaceRouteContracts } from "./analysis/workspace-routes.js";
12
14
  import { createPainter } from "./ansi.js";
13
15
  import { clientDirPath, clientFormsPath, clientIndexPath, defaultBeignetConfig, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
14
16
  import { renderDatabaseSchema, resolveDatabaseSchemaDialect } from "./db.js";
@@ -24,15 +26,67 @@ import { getCliVersion } from "./version.js";
24
26
  export async function inspectApp(options = {}) {
25
27
  const targetDir = path.resolve(options.cwd ?? process.cwd());
26
28
  await assertDirectory(targetDir);
27
- const files = await listFiles(targetDir);
28
- const config = options.config
29
- ? resolveConfig(options.config)
30
- : await loadBeignetConfig(targetDir, files);
31
- const convention = inspectConvention(files, config);
29
+ const workspace = await createAnalysisWorkspace(targetDir, {
30
+ config: options.config,
31
+ });
32
+ const { config } = workspace;
33
+ const files = workspace.projects[0].files;
32
34
  const inspectedContracts = await readContracts(targetDir, files, config);
35
+ for (const project of workspace.projects.slice(1)) {
36
+ const shared = await readContracts(project.targetDir, project.files, project.config);
37
+ inspectedContracts.contracts.push(...shared.contracts.map((contract) => ({
38
+ ...contract,
39
+ file: projectPath(project, contract.file),
40
+ })));
41
+ inspectedContracts.queryTransportDiagnostics.push(...shared.queryTransportDiagnostics.map((diagnostic) => ({
42
+ ...diagnostic,
43
+ ...(diagnostic.file
44
+ ? { file: projectPath(project, diagnostic.file) }
45
+ : {}),
46
+ ...(diagnostic.subject
47
+ ? {
48
+ subject: {
49
+ ...diagnostic.subject,
50
+ file: projectPath(project, diagnostic.subject.file),
51
+ },
52
+ }
53
+ : {}),
54
+ })));
55
+ }
33
56
  const { contracts } = inspectedContracts;
34
- const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts);
57
+ contracts.sort(compareContracts);
58
+ const convention = inspectConvention(files, config, contracts.length > 0 && workspace.projects.length > 1);
59
+ const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts, workspace.projects.length > 1 ? createSourceIndex(workspace) : undefined);
60
+ let workspaceRoutesUnresolved = false;
61
+ if (workspace.projects.length > 1) {
62
+ const registration = await workspaceRouteContracts(workspace);
63
+ workspaceRoutesUnresolved = registration.unresolved;
64
+ matchedRoutes.routes = matchedRoutes.routes.map((route) => {
65
+ if (route.handlerSource === "route-local")
66
+ return route;
67
+ if (!registration.contracts.has(`${route.file}#${route.exportName}`))
68
+ return {
69
+ ...route,
70
+ handlerFile: undefined,
71
+ handlerSource: "missing",
72
+ };
73
+ return config.framework === "web"
74
+ ? {
75
+ ...route,
76
+ handlerFile: config.paths.server,
77
+ handlerSource: "web-server",
78
+ }
79
+ : route;
80
+ });
81
+ }
35
82
  const diagnostics = await inspectDiagnostics(targetDir, files, config, convention, contracts, matchedRoutes, Boolean(options.strict), inspectedContracts.queryTransportDiagnostics);
83
+ if (workspaceRoutesUnresolved)
84
+ diagnostics.push({
85
+ severity: "warning",
86
+ code: "BEIGNET_WORKSPACE_ROUTES_UNRESOLVED",
87
+ file: config.paths.server,
88
+ message: "Could not fully resolve workspace route composition. Use explicit defineRoutes arrays and defineRouteGroup bindings reachable from the server routes option.",
89
+ });
36
90
  return {
37
91
  schemaVersion: 1,
38
92
  targetDir,
@@ -87,7 +141,10 @@ async function buildDoctorFixPlan(options = {}) {
87
141
  : await loadBeignetConfig(targetDir, files);
88
142
  const convention = inspectConvention(files, config);
89
143
  const { contracts } = await readContracts(targetDir, files, config);
90
- const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts);
144
+ const sourceIndex = config.workspace.packages.length
145
+ ? createSourceIndex(await createAnalysisWorkspace(targetDir, { config }))
146
+ : undefined;
147
+ const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts, sourceIndex);
91
148
  const operations = [];
92
149
  const packageFix = await planPackageScripts(targetDir, files, convention);
93
150
  if (packageFix)
@@ -441,14 +498,14 @@ async function readContracts(targetDir, files, config) {
441
498
  queryTransportDiagnostics,
442
499
  };
443
500
  }
444
- function inspectConvention(files, config) {
501
+ function inspectConvention(files, config, sharedContracts = false) {
445
502
  const missingNextLayout = missingRequirements(files, [
446
- `${directoryPath(config.paths.contracts)}/`,
503
+ ...(sharedContracts ? [] : [`${directoryPath(config.paths.contracts)}/`]),
447
504
  `${directoryPath(config.paths.routes)}/`,
448
505
  config.paths.server,
449
506
  ]);
450
507
  const missingWebLayout = missingRequirements(files, [
451
- `${directoryPath(config.paths.contracts)}/`,
508
+ ...(sharedContracts ? [] : [`${directoryPath(config.paths.contracts)}/`]),
452
509
  config.paths.server,
453
510
  ]);
454
511
  const missingResourceGenerator = missingRequirements(files, [
@@ -930,16 +987,16 @@ function normalizeContractPath(routePath) {
930
987
  const segments = routePath.split("/").filter(Boolean);
931
988
  return segments.length === 0 ? "/" : `/${segments.join("/")}`;
932
989
  }
933
- async function readRouteExports(targetDir, routeFiles, files, config) {
990
+ async function readRouteExports(targetDir, routeFiles, files, config, sourceIndex) {
934
991
  const routeExports = new Map();
935
992
  for (const file of routeFiles) {
936
993
  const routePath = nextRoutePath(file, config.paths.routes);
937
994
  const source = await readFile(path.join(targetDir, file), "utf8");
938
- routeExports.set(file, parseRouteExports(source, file, routePath, files, config));
995
+ routeExports.set(file, await parseRouteExports(source, file, routePath, files, config, sourceIndex));
939
996
  }
940
997
  return routeExports;
941
998
  }
942
- function parseRouteExports(source, handlerFile, routePath, files, config) {
999
+ async function parseRouteExports(source, handlerFile, routePath, files, config, sourceIndex) {
943
1000
  const exports = [];
944
1001
  const sourceFile = ts.createSourceFile(handlerFile, source, ts.ScriptTarget.Latest, true, sourceFileScriptKind(handlerFile));
945
1002
  const imports = parseRouteNamedImports(sourceFile, config, handlerFile, files);
@@ -977,11 +1034,13 @@ function parseRouteExports(source, handlerFile, routePath, files, config) {
977
1034
  const localName = routeLocalContractIdentifier(declaration.initializer);
978
1035
  if (localName) {
979
1036
  const imported = imports.get(localName);
1037
+ const resolved = await sourceIndex?.resolveValue(handlerFile, ts.factory.createIdentifier(localName));
980
1038
  exports.push({
981
1039
  method,
982
1040
  handlerFile,
983
- contractRef: imported?.importedName ?? localName,
984
- contractFile: imported?.contractFile,
1041
+ contractRef: resolved?.exportName ?? imported?.importedName ?? localName,
1042
+ contractFile: resolved?.file ?? imported?.contractFile,
1043
+ ...(sourceIndex && !resolved ? { contractUnresolved: true } : {}),
985
1044
  source: "route-local",
986
1045
  });
987
1046
  continue;
@@ -1262,11 +1321,11 @@ function matchRoutes(contracts, routeExports) {
1262
1321
  unmatchedRouteHandlers,
1263
1322
  };
1264
1323
  }
1265
- async function inspectRouteSurface(targetDir, files, config, contracts) {
1324
+ async function inspectRouteSurface(targetDir, files, config, contracts, sourceIndex) {
1266
1325
  if (config.framework === "next") {
1267
1326
  const routeFiles = files.filter((file) => file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
1268
1327
  isNextRouteFile(file, config));
1269
- const routeExports = await readRouteExports(targetDir, routeFiles, files, config);
1328
+ const routeExports = await readRouteExports(targetDir, routeFiles, files, config, sourceIndex);
1270
1329
  return matchRoutes(contracts, routeExports);
1271
1330
  }
1272
1331
  if (!files.includes(config.paths.server)) {
@@ -1303,6 +1362,8 @@ function findContract(contracts, routeExport) {
1303
1362
  }
1304
1363
  function findContracts(contracts, routeExport) {
1305
1364
  if (routeExport.source === "route-local") {
1365
+ if (routeExport.contractUnresolved)
1366
+ return [];
1306
1367
  const contract = contracts.find((contract) => contract.exportName === routeExport.contractRef &&
1307
1368
  (!routeExport.contractFile ||
1308
1369
  contract.file === routeExport.contractFile ||
@@ -1402,6 +1463,7 @@ async function inspectCanonicalConformance(targetDir, files, config, convention,
1402
1463
  files.some((file) => file.startsWith(`${clientDir}/`)) ||
1403
1464
  [
1404
1465
  "@beignet/react-query",
1466
+ "@beignet/react-form",
1405
1467
  "@beignet/react-hook-form",
1406
1468
  "@beignet/react-uploads",
1407
1469
  "@beignet/nuqs",
@@ -1438,11 +1500,12 @@ async function inspectCanonicalConformance(targetDir, files, config, convention,
1438
1500
  reason: `${clientIndex} should own the typed client and frontend adapter setup.`,
1439
1501
  });
1440
1502
  }
1441
- if (installedPackages.has("@beignet/react-hook-form") ||
1503
+ if (installedPackages.has("@beignet/react-form") ||
1504
+ installedPackages.has("@beignet/react-hook-form") ||
1442
1505
  files.includes(clientForms)) {
1443
1506
  requiredFiles.push({
1444
1507
  file: clientForms,
1445
- reason: `${clientForms} should own React Hook Form adapter setup when form helpers are used.`,
1508
+ reason: `${clientForms} should own form adapter setup (createReactForm or createReactHookForm) when form helpers are used.`,
1446
1509
  });
1447
1510
  }
1448
1511
  for (const required of requiredFiles) {
@@ -3956,6 +4019,9 @@ async function inspectPortProviderDrift(targetDir, files, config, convention) {
3956
4019
  const portsSource = files.includes(config.paths.ports)
3957
4020
  ? await readFile(path.join(targetDir, config.paths.ports), "utf8")
3958
4021
  : "";
4022
+ const sharedPorts = config.workspace.packages.length > 0
4023
+ ? await analyzePortWiringFiles({ targetDir, files, config })
4024
+ : undefined;
3959
4025
  const packageJson = files.includes("package.json")
3960
4026
  ? await readPackageJson(targetDir, files)
3961
4027
  : undefined;
@@ -3973,7 +4039,9 @@ async function inspectPortProviderDrift(targetDir, files, config, convention) {
3973
4039
  for (const expected of providerPortRequirements) {
3974
4040
  if (!installedPackages.has(expected.packageName))
3975
4041
  continue;
3976
- if (portsSource.includes(`${expected.portName}:`))
4042
+ if (sharedPorts
4043
+ ? sharedPorts.declared.has(expected.portName)
4044
+ : portsSource.includes(`${expected.portName}:`))
3977
4045
  continue;
3978
4046
  diagnostics.push({
3979
4047
  severity: "warning",