@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/src/inspect.ts CHANGED
@@ -22,7 +22,13 @@ import {
22
22
  stripSourceFileExtension,
23
23
  typescriptAnalysisSourceFileExtensions,
24
24
  } from "./analysis/source-files.js";
25
- import { exportedVariableDeclarations } from "./analysis/source-index.js";
25
+ import {
26
+ createSourceIndex,
27
+ exportedVariableDeclarations,
28
+ type SourceIndex,
29
+ } from "./analysis/source-index.js";
30
+ import { createAnalysisWorkspace, projectPath } from "./analysis/workspace.js";
31
+ import { workspaceRouteContracts } from "./analysis/workspace-routes.js";
26
32
  import { createPainter } from "./ansi.js";
27
33
  import type { DatabaseSchemaDialect, DatabaseSchemaTable } from "./choices.js";
28
34
  import {
@@ -213,6 +219,7 @@ type RouteExport = {
213
219
  handlerFile: string;
214
220
  contractRef?: string;
215
221
  contractFile?: string;
222
+ contractUnresolved?: boolean;
216
223
  catchAllPrefix?: string;
217
224
  source: "next-route" | "route-local";
218
225
  };
@@ -261,19 +268,76 @@ export async function inspectApp(
261
268
  const targetDir = path.resolve(options.cwd ?? process.cwd());
262
269
  await assertDirectory(targetDir);
263
270
 
264
- const files = await listFiles(targetDir);
265
- const config = options.config
266
- ? resolveConfig(options.config)
267
- : await loadBeignetConfig(targetDir, files);
268
- const convention = inspectConvention(files, config);
271
+ const workspace = await createAnalysisWorkspace(targetDir, {
272
+ config: options.config,
273
+ });
274
+ const { config } = workspace;
275
+ const files = workspace.projects[0].files;
269
276
  const inspectedContracts = await readContracts(targetDir, files, config);
277
+ for (const project of workspace.projects.slice(1)) {
278
+ const shared = await readContracts(
279
+ project.targetDir,
280
+ project.files,
281
+ project.config,
282
+ );
283
+ inspectedContracts.contracts.push(
284
+ ...shared.contracts.map((contract) => ({
285
+ ...contract,
286
+ file: projectPath(project, contract.file),
287
+ })),
288
+ );
289
+ inspectedContracts.queryTransportDiagnostics.push(
290
+ ...shared.queryTransportDiagnostics.map((diagnostic) => ({
291
+ ...diagnostic,
292
+ ...(diagnostic.file
293
+ ? { file: projectPath(project, diagnostic.file) }
294
+ : {}),
295
+ ...(diagnostic.subject
296
+ ? {
297
+ subject: {
298
+ ...diagnostic.subject,
299
+ file: projectPath(project, diagnostic.subject.file),
300
+ },
301
+ }
302
+ : {}),
303
+ })),
304
+ );
305
+ }
270
306
  const { contracts } = inspectedContracts;
307
+ contracts.sort(compareContracts);
308
+ const convention = inspectConvention(
309
+ files,
310
+ config,
311
+ contracts.length > 0 && workspace.projects.length > 1,
312
+ );
271
313
  const matchedRoutes = await inspectRouteSurface(
272
314
  targetDir,
273
315
  files,
274
316
  config,
275
317
  contracts,
276
- );
318
+ workspace.projects.length > 1 ? createSourceIndex(workspace) : undefined,
319
+ );
320
+ let workspaceRoutesUnresolved = false;
321
+ if (workspace.projects.length > 1) {
322
+ const registration = await workspaceRouteContracts(workspace);
323
+ workspaceRoutesUnresolved = registration.unresolved;
324
+ matchedRoutes.routes = matchedRoutes.routes.map((route) => {
325
+ if (route.handlerSource === "route-local") return route;
326
+ if (!registration.contracts.has(`${route.file}#${route.exportName}`))
327
+ return {
328
+ ...route,
329
+ handlerFile: undefined,
330
+ handlerSource: "missing" as const,
331
+ };
332
+ return config.framework === "web"
333
+ ? {
334
+ ...route,
335
+ handlerFile: config.paths.server,
336
+ handlerSource: "web-server" as const,
337
+ }
338
+ : route;
339
+ });
340
+ }
277
341
  const diagnostics = await inspectDiagnostics(
278
342
  targetDir,
279
343
  files,
@@ -285,6 +349,15 @@ export async function inspectApp(
285
349
  inspectedContracts.queryTransportDiagnostics,
286
350
  );
287
351
 
352
+ if (workspaceRoutesUnresolved)
353
+ diagnostics.push({
354
+ severity: "warning",
355
+ code: "BEIGNET_WORKSPACE_ROUTES_UNRESOLVED",
356
+ file: config.paths.server,
357
+ message:
358
+ "Could not fully resolve workspace route composition. Use explicit defineRoutes arrays and defineRouteGroup bindings reachable from the server routes option.",
359
+ });
360
+
288
361
  return {
289
362
  schemaVersion: 1,
290
363
  targetDir,
@@ -367,11 +440,15 @@ async function buildDoctorFixPlan(
367
440
  : await loadBeignetConfig(targetDir, files);
368
441
  const convention = inspectConvention(files, config);
369
442
  const { contracts } = await readContracts(targetDir, files, config);
443
+ const sourceIndex = config.workspace.packages.length
444
+ ? createSourceIndex(await createAnalysisWorkspace(targetDir, { config }))
445
+ : undefined;
370
446
  const matchedRoutes = await inspectRouteSurface(
371
447
  targetDir,
372
448
  files,
373
449
  config,
374
450
  contracts,
451
+ sourceIndex,
375
452
  );
376
453
  const operations: PlannedDoctorFixOperation[] = [];
377
454
 
@@ -896,14 +973,15 @@ async function readContracts(
896
973
  function inspectConvention(
897
974
  files: string[],
898
975
  config: ResolvedBeignetConfig,
976
+ sharedContracts = false,
899
977
  ): InspectConvention {
900
978
  const missingNextLayout = missingRequirements(files, [
901
- `${directoryPath(config.paths.contracts)}/`,
979
+ ...(sharedContracts ? [] : [`${directoryPath(config.paths.contracts)}/`]),
902
980
  `${directoryPath(config.paths.routes)}/`,
903
981
  config.paths.server,
904
982
  ]);
905
983
  const missingWebLayout = missingRequirements(files, [
906
- `${directoryPath(config.paths.contracts)}/`,
984
+ ...(sharedContracts ? [] : [`${directoryPath(config.paths.contracts)}/`]),
907
985
  config.paths.server,
908
986
  ]);
909
987
  const missingResourceGenerator = missingRequirements(files, [
@@ -1571,6 +1649,7 @@ async function readRouteExports(
1571
1649
  routeFiles: string[],
1572
1650
  files: string[],
1573
1651
  config: ResolvedBeignetConfig,
1652
+ sourceIndex?: SourceIndex,
1574
1653
  ): Promise<Map<string, RouteExport[]>> {
1575
1654
  const routeExports = new Map<string, RouteExport[]>();
1576
1655
 
@@ -1579,20 +1658,28 @@ async function readRouteExports(
1579
1658
  const source = await readFile(path.join(targetDir, file), "utf8");
1580
1659
  routeExports.set(
1581
1660
  file,
1582
- parseRouteExports(source, file, routePath, files, config),
1661
+ await parseRouteExports(
1662
+ source,
1663
+ file,
1664
+ routePath,
1665
+ files,
1666
+ config,
1667
+ sourceIndex,
1668
+ ),
1583
1669
  );
1584
1670
  }
1585
1671
 
1586
1672
  return routeExports;
1587
1673
  }
1588
1674
 
1589
- function parseRouteExports(
1675
+ async function parseRouteExports(
1590
1676
  source: string,
1591
1677
  handlerFile: string,
1592
1678
  routePath: string,
1593
1679
  files: string[],
1594
1680
  config: ResolvedBeignetConfig,
1595
- ): RouteExport[] {
1681
+ sourceIndex?: SourceIndex,
1682
+ ): Promise<RouteExport[]> {
1596
1683
  const exports: RouteExport[] = [];
1597
1684
  const sourceFile = ts.createSourceFile(
1598
1685
  handlerFile,
@@ -1651,11 +1738,17 @@ function parseRouteExports(
1651
1738
 
1652
1739
  if (localName) {
1653
1740
  const imported = imports.get(localName);
1741
+ const resolved = await sourceIndex?.resolveValue(
1742
+ handlerFile,
1743
+ ts.factory.createIdentifier(localName),
1744
+ );
1654
1745
  exports.push({
1655
1746
  method,
1656
1747
  handlerFile,
1657
- contractRef: imported?.importedName ?? localName,
1658
- contractFile: imported?.contractFile,
1748
+ contractRef:
1749
+ resolved?.exportName ?? imported?.importedName ?? localName,
1750
+ contractFile: resolved?.file ?? imported?.contractFile,
1751
+ ...(sourceIndex && !resolved ? { contractUnresolved: true } : {}),
1659
1752
  source: "route-local",
1660
1753
  });
1661
1754
  continue;
@@ -2065,6 +2158,7 @@ async function inspectRouteSurface(
2065
2158
  files: string[],
2066
2159
  config: ResolvedBeignetConfig,
2067
2160
  contracts: InspectedContract[],
2161
+ sourceIndex?: SourceIndex,
2068
2162
  ): Promise<MatchedRoutesResult> {
2069
2163
  if (config.framework === "next") {
2070
2164
  const routeFiles = files.filter(
@@ -2077,6 +2171,7 @@ async function inspectRouteSurface(
2077
2171
  routeFiles,
2078
2172
  files,
2079
2173
  config,
2174
+ sourceIndex,
2080
2175
  );
2081
2176
  return matchRoutes(contracts, routeExports);
2082
2177
  }
@@ -2144,6 +2239,7 @@ function findContracts(
2144
2239
  routeExport: RouteExport,
2145
2240
  ): InspectedContract[] {
2146
2241
  if (routeExport.source === "route-local") {
2242
+ if (routeExport.contractUnresolved) return [];
2147
2243
  const contract = contracts.find(
2148
2244
  (contract) =>
2149
2245
  contract.exportName === routeExport.contractRef &&
@@ -2391,6 +2487,7 @@ async function inspectCanonicalConformance(
2391
2487
  files.some((file) => file.startsWith(`${clientDir}/`)) ||
2392
2488
  [
2393
2489
  "@beignet/react-query",
2490
+ "@beignet/react-form",
2394
2491
  "@beignet/react-hook-form",
2395
2492
  "@beignet/react-uploads",
2396
2493
  "@beignet/nuqs",
@@ -2437,12 +2534,13 @@ async function inspectCanonicalConformance(
2437
2534
  }
2438
2535
 
2439
2536
  if (
2537
+ installedPackages.has("@beignet/react-form") ||
2440
2538
  installedPackages.has("@beignet/react-hook-form") ||
2441
2539
  files.includes(clientForms)
2442
2540
  ) {
2443
2541
  requiredFiles.push({
2444
2542
  file: clientForms,
2445
- reason: `${clientForms} should own React Hook Form adapter setup when form helpers are used.`,
2543
+ reason: `${clientForms} should own form adapter setup (createReactForm or createReactHookForm) when form helpers are used.`,
2446
2544
  });
2447
2545
  }
2448
2546
 
@@ -6338,6 +6436,10 @@ async function inspectPortProviderDrift(
6338
6436
  const portsSource = files.includes(config.paths.ports)
6339
6437
  ? await readFile(path.join(targetDir, config.paths.ports), "utf8")
6340
6438
  : "";
6439
+ const sharedPorts =
6440
+ config.workspace.packages.length > 0
6441
+ ? await analyzePortWiringFiles({ targetDir, files, config })
6442
+ : undefined;
6341
6443
  const packageJson = files.includes("package.json")
6342
6444
  ? await readPackageJson(targetDir, files)
6343
6445
  : undefined;
@@ -6361,7 +6463,12 @@ async function inspectPortProviderDrift(
6361
6463
 
6362
6464
  for (const expected of providerPortRequirements) {
6363
6465
  if (!installedPackages.has(expected.packageName)) continue;
6364
- if (portsSource.includes(`${expected.portName}:`)) continue;
6466
+ if (
6467
+ sharedPorts
6468
+ ? sharedPorts.declared.has(expected.portName)
6469
+ : portsSource.includes(`${expected.portName}:`)
6470
+ )
6471
+ continue;
6365
6472
  diagnostics.push({
6366
6473
  severity: "warning",
6367
6474
  code: "BEIGNET_PROVIDER_PORT_MISSING",