@agentuity/cli 1.0.39 → 1.0.40

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 (37) hide show
  1. package/dist/cmd/build/app-router-detector.d.ts +42 -0
  2. package/dist/cmd/build/app-router-detector.d.ts.map +1 -0
  3. package/dist/cmd/build/app-router-detector.js +253 -0
  4. package/dist/cmd/build/app-router-detector.js.map +1 -0
  5. package/dist/cmd/build/ast.d.ts +11 -1
  6. package/dist/cmd/build/ast.d.ts.map +1 -1
  7. package/dist/cmd/build/ast.js +273 -29
  8. package/dist/cmd/build/ast.js.map +1 -1
  9. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  10. package/dist/cmd/build/entry-generator.js +23 -16
  11. package/dist/cmd/build/entry-generator.js.map +1 -1
  12. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  13. package/dist/cmd/build/vite/registry-generator.js +37 -13
  14. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  15. package/dist/cmd/build/vite/route-discovery.d.ts +17 -3
  16. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  17. package/dist/cmd/build/vite/route-discovery.js +91 -3
  18. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  19. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  20. package/dist/cmd/build/vite-bundler.js +3 -0
  21. package/dist/cmd/build/vite-bundler.js.map +1 -1
  22. package/dist/cmd/dev/index.d.ts.map +1 -1
  23. package/dist/cmd/dev/index.js +30 -0
  24. package/dist/cmd/dev/index.js.map +1 -1
  25. package/dist/utils/route-migration.d.ts +61 -0
  26. package/dist/utils/route-migration.d.ts.map +1 -0
  27. package/dist/utils/route-migration.js +662 -0
  28. package/dist/utils/route-migration.js.map +1 -0
  29. package/package.json +6 -6
  30. package/src/cmd/build/app-router-detector.ts +350 -0
  31. package/src/cmd/build/ast.ts +339 -36
  32. package/src/cmd/build/entry-generator.ts +23 -16
  33. package/src/cmd/build/vite/registry-generator.ts +38 -13
  34. package/src/cmd/build/vite/route-discovery.ts +151 -3
  35. package/src/cmd/build/vite-bundler.ts +4 -0
  36. package/src/cmd/dev/index.ts +34 -0
  37. package/src/utils/route-migration.ts +793 -0
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Route Discovery - READ-ONLY AST analysis
3
3
  *
4
- * Discovers routes by scanning src/api/**\/*.ts files
5
- * Extracts route definitions WITHOUT mutating source files
4
+ * Discovers routes by scanning src/api/**\/*.ts files or by following
5
+ * explicit router mounts from createApp({ router }).
6
+ * Extracts route definitions WITHOUT mutating source files.
6
7
  */
7
8
 
8
9
  import { join, relative } from 'node:path';
@@ -10,6 +11,7 @@ import { existsSync } from 'node:fs';
10
11
  import type { Logger } from '../../../types';
11
12
  import { parseRoute } from '../ast';
12
13
  import { toForwardSlash } from '../../../utils/normalize-path';
14
+ import { detectExplicitRouter, type AppRouterDetection } from '../app-router-detector';
13
15
 
14
16
  export interface RouteMetadata {
15
17
  id: string;
@@ -46,6 +48,12 @@ export interface RouteInfo {
46
48
  outputSchemaCode?: string;
47
49
  stream?: boolean;
48
50
  pathParams?: string[];
51
+ /**
52
+ * When a route is mounted via .route(), its filename is set to the parent file
53
+ * (for dedup filtering). schemaSourceFile preserves the actual file where the
54
+ * route's schema variables are defined/exported, so registry imports resolve correctly.
55
+ */
56
+ schemaSourceFile?: string;
49
57
  }
50
58
 
51
59
  /**
@@ -66,13 +74,152 @@ export function extractPathParams(path: string): string[] {
66
74
  }
67
75
 
68
76
  /**
69
- * Discover all routes in src/api directory (READ-ONLY)
77
+ * Discover all routes tries explicit router detection first, falls back to file-based.
78
+ *
79
+ * When `createApp({ router })` is detected in app.ts, routes are discovered by
80
+ * following the router imports with code-derived mount paths. Otherwise, falls back
81
+ * to scanning src/api/**\/*.ts with filesystem-derived paths.
70
82
  */
71
83
  export async function discoverRoutes(
72
84
  srcDir: string,
73
85
  projectId: string,
74
86
  deploymentId: string,
75
87
  logger: Logger
88
+ ): Promise<{
89
+ routes: RouteMetadata[];
90
+ routeInfoList: RouteInfo[];
91
+ /** Whether explicit router was detected (vs file-based fallback) */
92
+ explicitRouter?: AppRouterDetection;
93
+ }> {
94
+ const rootDir = join(srcDir, '..');
95
+
96
+ // Try explicit router detection first
97
+ const detection = await detectExplicitRouter(rootDir, logger);
98
+ if (detection.detected && detection.mounts.length > 0) {
99
+ logger.debug(
100
+ 'Using explicit router detection (%d mount(s) from createApp)',
101
+ detection.mounts.length
102
+ );
103
+ const result = await discoverExplicitRoutes(
104
+ rootDir,
105
+ srcDir,
106
+ projectId,
107
+ deploymentId,
108
+ detection,
109
+ logger
110
+ );
111
+ return { ...result, explicitRouter: detection };
112
+ }
113
+
114
+ // Fall back to file-based discovery
115
+ return discoverFileBasedRoutes(srcDir, projectId, deploymentId, logger);
116
+ }
117
+
118
+ /**
119
+ * Discover routes from explicit router mounts detected in app.ts.
120
+ * Parses each router file with its code-derived mount prefix.
121
+ */
122
+ async function discoverExplicitRoutes(
123
+ rootDir: string,
124
+ srcDir: string,
125
+ projectId: string,
126
+ deploymentId: string,
127
+ detection: AppRouterDetection,
128
+ logger: Logger
129
+ ): Promise<{ routes: RouteMetadata[]; routeInfoList: RouteInfo[] }> {
130
+ const routes: RouteMetadata[] = [];
131
+ const routeInfoList: RouteInfo[] = [];
132
+ const visited = new Set<string>();
133
+ const mountedSubrouters = new Set<string>();
134
+
135
+ for (const mount of detection.mounts) {
136
+ try {
137
+ const parsedRoutes = await parseRoute(rootDir, mount.routerFile, projectId, deploymentId, {
138
+ visitedFiles: visited,
139
+ mountedSubrouters,
140
+ mountPrefix: mount.path,
141
+ });
142
+
143
+ if (parsedRoutes.length > 0) {
144
+ const relFile = './' + toForwardSlash(relative(srcDir, mount.routerFile));
145
+ logger.trace(
146
+ 'Discovered %d route(s) from explicit mount at %s (%s)',
147
+ parsedRoutes.length,
148
+ mount.path,
149
+ relFile
150
+ );
151
+ routes.push(...parsedRoutes);
152
+
153
+ for (const route of parsedRoutes) {
154
+ const pathParams = extractPathParams(route.path);
155
+ routeInfoList.push({
156
+ method: route.method.toUpperCase(),
157
+ path: route.path,
158
+ filename: route.filename,
159
+ hasValidator: route.config?.hasValidator === true,
160
+ routeType: route.type || 'api',
161
+ agentVariable: route.config?.agentVariable as string | undefined,
162
+ agentImportPath: route.config?.agentImportPath as string | undefined,
163
+ inputSchemaVariable: route.config?.inputSchemaVariable as string | undefined,
164
+ outputSchemaVariable: route.config?.outputSchemaVariable as string | undefined,
165
+ inputSchemaImportPath: route.config?.inputSchemaImportPath as string | undefined,
166
+ inputSchemaImportedName: route.config?.inputSchemaImportedName as
167
+ | string
168
+ | undefined,
169
+ outputSchemaImportPath: route.config?.outputSchemaImportPath as
170
+ | string
171
+ | undefined,
172
+ outputSchemaImportedName: route.config?.outputSchemaImportedName as
173
+ | string
174
+ | undefined,
175
+ stream:
176
+ route.config?.stream !== undefined && route.config.stream !== null
177
+ ? Boolean(route.config.stream)
178
+ : route.type === 'stream'
179
+ ? true
180
+ : undefined,
181
+ pathParams: pathParams.length > 0 ? pathParams : undefined,
182
+ schemaSourceFile: route.config?.schemaSourceFile as string | undefined,
183
+ });
184
+ }
185
+ }
186
+ } catch (error) {
187
+ logger.warn(
188
+ 'Failed to parse explicit router at %s: %s',
189
+ mount.routerFile,
190
+ error instanceof Error ? error.message : String(error)
191
+ );
192
+ }
193
+ }
194
+
195
+ logger.debug('Discovered %d route(s) via explicit router detection', routes.length);
196
+
197
+ // Check for route conflicts
198
+ const conflicts = detectRouteConflicts(routeInfoList);
199
+ if (conflicts.length > 0) {
200
+ logger.error('Route conflicts detected:');
201
+ for (const conflict of conflicts) {
202
+ logger.error(' %s', conflict.message);
203
+ for (const route of conflict.routes) {
204
+ logger.error(' - %s %s in %s', route.method, route.path, route.filename);
205
+ }
206
+ }
207
+ throw new Error(
208
+ `Found ${conflicts.length} route conflict(s). Fix the conflicts and try again.`
209
+ );
210
+ }
211
+
212
+ return { routes, routeInfoList };
213
+ }
214
+
215
+ /**
216
+ * Discover routes by scanning src/api directory (original file-based approach).
217
+ */
218
+ async function discoverFileBasedRoutes(
219
+ srcDir: string,
220
+ projectId: string,
221
+ deploymentId: string,
222
+ logger: Logger
76
223
  ): Promise<{ routes: RouteMetadata[]; routeInfoList: RouteInfo[] }> {
77
224
  const apiDir = join(srcDir, 'api');
78
225
  const routes: RouteMetadata[] = [];
@@ -154,6 +301,7 @@ export async function discoverRoutes(
154
301
  ? true
155
302
  : undefined,
156
303
  pathParams: pathParams.length > 0 ? pathParams : undefined,
304
+ schemaSourceFile: route.config?.schemaSourceFile as string | undefined,
157
305
  });
158
306
  }
159
307
  }
@@ -10,6 +10,7 @@ import { StructuredError } from '@agentuity/core';
10
10
  import type { Logger, DeployOptions } from '../../types';
11
11
  import { runAllBuilds } from './vite/vite-builder';
12
12
  import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
13
+ import { promptRouteMigration } from '../../utils/route-migration';
13
14
  import { checkBunVersion } from '../../utils/bun-version-checker';
14
15
  import * as tui from '../../tui';
15
16
  import type { BuildReportCollector } from '../../build-report';
@@ -89,6 +90,9 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
89
90
  });
90
91
  }
91
92
 
93
+ // Check if project can migrate from file-based to explicit routing
94
+ await promptRouteMigration(rootDir, logger);
95
+
92
96
  try {
93
97
  // Run all builds (client -> workbench -> server)
94
98
  logger.debug('Starting builds...');
@@ -20,6 +20,11 @@ import { isTTY, hasLoggedInBefore } from '../../auth';
20
20
  import { createFileWatcher } from './file-watcher';
21
21
  import { prepareDevLock, releaseLockSync } from './dev-lock';
22
22
  import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
23
+ import {
24
+ promptRouteMigration,
25
+ performMigration,
26
+ checkMigrationEligibility,
27
+ } from '../../utils/route-migration';
23
28
  import { ErrorCode } from '../../errors';
24
29
 
25
30
  const DEFAULT_PORT = 3500;
@@ -232,6 +237,12 @@ export const command = createCommand({
232
237
  .boolean()
233
238
  .optional()
234
239
  .describe('Skip TypeScript type checking on startup and restarts'),
240
+ migrateRoutes: z
241
+ .boolean()
242
+ .optional()
243
+ .describe(
244
+ 'Migrate file-based routes to explicit routing (src/api/index.ts root router)'
245
+ ),
235
246
  resume: z.string().optional().describe('Resume a paused Hub session by ID'),
236
247
  }),
237
248
  },
@@ -414,6 +425,29 @@ export const command = createCommand({
414
425
  );
415
426
  }
416
427
 
428
+ // Check if project can migrate to explicit routing
429
+ if (opts.migrateRoutes) {
430
+ const eligibility = checkMigrationEligibility(rootDir);
431
+ if (eligibility.available) {
432
+ const result = performMigration(rootDir, eligibility.routeFiles);
433
+ if (result.success) {
434
+ tui.success(result.message);
435
+ if (result.filesCreated.length > 0) {
436
+ tui.info(`Created: ${result.filesCreated.map((f) => tui.muted(f)).join(', ')}`);
437
+ }
438
+ tui.newline();
439
+ } else {
440
+ tui.warning(result.message);
441
+ tui.newline();
442
+ }
443
+ } else {
444
+ tui.info('No migration needed — already using explicit routing.');
445
+ tui.newline();
446
+ }
447
+ } else {
448
+ await promptRouteMigration(rootDir, logger, { interactive });
449
+ }
450
+
417
451
  try {
418
452
  // Setup devmode and gravity (if using public URL)
419
453
  const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';