veryfront 0.1.26 → 0.1.28

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 (124) hide show
  1. package/README.md +3 -11
  2. package/esm/cli/app/shell.d.ts.map +1 -1
  3. package/esm/cli/app/shell.js +9 -5
  4. package/esm/cli/commands/demo/demo.js +1 -1
  5. package/esm/cli/commands/init/catalog.d.ts.map +1 -1
  6. package/esm/cli/commands/init/catalog.js +13 -5
  7. package/esm/cli/commands/init/command-help.js +4 -4
  8. package/esm/cli/commands/init/types.d.ts +1 -1
  9. package/esm/cli/commands/init/types.d.ts.map +1 -1
  10. package/esm/cli/commands/serve/command.d.ts.map +1 -1
  11. package/esm/cli/commands/serve/command.js +0 -4
  12. package/esm/cli/commands/start/command.d.ts.map +1 -1
  13. package/esm/cli/commands/start/command.js +16 -9
  14. package/esm/cli/help/tips.js +6 -6
  15. package/esm/cli/mcp/remote-file-tools.js +1 -1
  16. package/esm/cli/mcp/tools/catalog-tools.d.ts +3 -3
  17. package/esm/cli/mcp/tools/catalog-tools.d.ts.map +1 -1
  18. package/esm/cli/mcp/tools/catalog-tools.js +21 -13
  19. package/esm/cli/mcp/tools/project-tools.js +1 -1
  20. package/esm/cli/templates/index.js +11 -11
  21. package/esm/cli/templates/manifest.d.ts +22 -15
  22. package/esm/cli/templates/manifest.js +24 -17
  23. package/esm/cli/templates/types.d.ts +1 -1
  24. package/esm/cli/templates/types.d.ts.map +1 -1
  25. package/esm/cli/utils/index.d.ts.map +1 -1
  26. package/esm/cli/utils/index.js +13 -1
  27. package/esm/deno.js +1 -1
  28. package/esm/src/html/html-shell-generator.d.ts.map +1 -1
  29. package/esm/src/html/html-shell-generator.js +2 -0
  30. package/esm/src/html/styles-builder/project-css-cache.d.ts +8 -1
  31. package/esm/src/html/styles-builder/project-css-cache.d.ts.map +1 -1
  32. package/esm/src/html/styles-builder/project-css-cache.js +13 -2
  33. package/esm/src/html/styles-builder/tailwind-compiler.d.ts +2 -0
  34. package/esm/src/html/styles-builder/tailwind-compiler.d.ts.map +1 -1
  35. package/esm/src/html/styles-builder/tailwind-compiler.js +52 -19
  36. package/esm/src/modules/react-loader/css-import-collector.d.ts +29 -0
  37. package/esm/src/modules/react-loader/css-import-collector.d.ts.map +1 -0
  38. package/esm/src/modules/react-loader/css-import-collector.js +41 -0
  39. package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
  40. package/esm/src/modules/react-loader/ssr-module-loader/loader.js +6 -0
  41. package/esm/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.d.ts.map +1 -1
  42. package/esm/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.js +5 -0
  43. package/esm/src/platform/adapters/fs/factory.d.ts.map +1 -1
  44. package/esm/src/platform/adapters/fs/factory.js +5 -1
  45. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +1 -0
  46. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
  47. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +19 -5
  48. package/esm/src/platform/compat/process.d.ts.map +1 -1
  49. package/esm/src/platform/compat/process.js +20 -3
  50. package/esm/src/proxy/main.js +31 -12
  51. package/esm/src/proxy/token-manager.d.ts +2 -0
  52. package/esm/src/proxy/token-manager.d.ts.map +1 -1
  53. package/esm/src/proxy/token-manager.js +47 -8
  54. package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts +23 -0
  55. package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts.map +1 -0
  56. package/esm/src/rendering/orchestrator/css-candidate-manifest.js +132 -0
  57. package/esm/src/rendering/orchestrator/html.d.ts +11 -1
  58. package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
  59. package/esm/src/rendering/orchestrator/html.js +103 -18
  60. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  61. package/esm/src/rendering/orchestrator/pipeline.js +14 -2
  62. package/esm/src/server/bootstrap.d.ts +2 -0
  63. package/esm/src/server/bootstrap.d.ts.map +1 -1
  64. package/esm/src/server/bootstrap.js +10 -0
  65. package/esm/src/server/handlers/preview/markdown-html-generator.d.ts.map +1 -1
  66. package/esm/src/server/handlers/preview/markdown-html-generator.js +11 -5
  67. package/esm/src/server/production-server.js +10 -2
  68. package/esm/src/studio/bridge-template.d.ts +2 -0
  69. package/esm/src/studio/bridge-template.d.ts.map +1 -1
  70. package/esm/src/studio/bridge-template.js +3390 -52
  71. package/esm/src/transforms/css-modules/naming.d.ts +33 -0
  72. package/esm/src/transforms/css-modules/naming.d.ts.map +1 -0
  73. package/esm/src/transforms/css-modules/naming.js +128 -0
  74. package/esm/src/transforms/esm/import-parser.d.ts +1 -0
  75. package/esm/src/transforms/esm/import-parser.d.ts.map +1 -1
  76. package/esm/src/transforms/esm/import-parser.js +16 -5
  77. package/esm/src/transforms/pipeline/index.d.ts.map +1 -1
  78. package/esm/src/transforms/pipeline/index.js +3 -1
  79. package/esm/src/transforms/pipeline/stages/index.d.ts +1 -0
  80. package/esm/src/transforms/pipeline/stages/index.d.ts.map +1 -1
  81. package/esm/src/transforms/pipeline/stages/index.js +1 -0
  82. package/esm/src/transforms/pipeline/stages/ssr-css-strip.d.ts +18 -0
  83. package/esm/src/transforms/pipeline/stages/ssr-css-strip.d.ts.map +1 -0
  84. package/esm/src/transforms/pipeline/stages/ssr-css-strip.js +168 -0
  85. package/package.json +1 -1
  86. package/src/cli/app/shell.ts +9 -5
  87. package/src/cli/commands/demo/demo.ts +1 -1
  88. package/src/cli/commands/init/catalog.ts +13 -5
  89. package/src/cli/commands/init/command-help.ts +4 -4
  90. package/src/cli/commands/init/types.ts +5 -5
  91. package/src/cli/commands/serve/command.ts +0 -5
  92. package/src/cli/commands/start/command.ts +15 -10
  93. package/src/cli/help/tips.ts +6 -6
  94. package/src/cli/mcp/remote-file-tools.ts +1 -1
  95. package/src/cli/mcp/tools/catalog-tools.ts +21 -13
  96. package/src/cli/mcp/tools/project-tools.ts +1 -1
  97. package/src/cli/templates/index.ts +11 -11
  98. package/src/cli/templates/manifest.js +24 -17
  99. package/src/cli/templates/types.ts +5 -5
  100. package/src/cli/utils/index.ts +12 -1
  101. package/src/deno.js +1 -1
  102. package/src/src/html/html-shell-generator.ts +2 -0
  103. package/src/src/html/styles-builder/project-css-cache.ts +24 -1
  104. package/src/src/html/styles-builder/tailwind-compiler.ts +67 -26
  105. package/src/src/modules/react-loader/css-import-collector.ts +50 -0
  106. package/src/src/modules/react-loader/ssr-module-loader/loader.ts +7 -0
  107. package/src/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.ts +6 -0
  108. package/src/src/platform/adapters/fs/factory.ts +5 -1
  109. package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +21 -5
  110. package/src/src/platform/compat/process.ts +28 -4
  111. package/src/src/proxy/main.ts +32 -12
  112. package/src/src/proxy/token-manager.ts +54 -8
  113. package/src/src/rendering/orchestrator/css-candidate-manifest.ts +176 -0
  114. package/src/src/rendering/orchestrator/html.ts +128 -16
  115. package/src/src/rendering/orchestrator/pipeline.ts +183 -165
  116. package/src/src/server/bootstrap.ts +16 -0
  117. package/src/src/server/handlers/preview/markdown-html-generator.ts +12 -5
  118. package/src/src/server/production-server.ts +12 -2
  119. package/src/src/studio/bridge-template.ts +3392 -52
  120. package/src/src/transforms/css-modules/naming.ts +152 -0
  121. package/src/src/transforms/esm/import-parser.ts +15 -5
  122. package/src/src/transforms/pipeline/index.ts +3 -0
  123. package/src/src/transforms/pipeline/stages/index.ts +1 -0
  124. package/src/src/transforms/pipeline/stages/ssr-css-strip.ts +201 -0
@@ -0,0 +1,176 @@
1
+ import { extractCandidates } from "../../html/styles-builder/tailwind-compiler.js";
2
+ import { getRouteModulePaths } from "../../modules/manifest/route-module-manifest.js";
3
+ import { rendererLogger } from "../../utils/index.js";
4
+
5
+ interface SourceFileLike {
6
+ path: string;
7
+ content?: string;
8
+ }
9
+
10
+ interface CandidateManifest {
11
+ fileCandidates: Map<string, Set<string>>;
12
+ allCandidates: Set<string>;
13
+ builtAt: number;
14
+ }
15
+
16
+ interface RouteCandidateOptions {
17
+ projectScope: string;
18
+ projectVersion: string;
19
+ projectDir: string;
20
+ routeKey: string;
21
+ routeFilePaths: string[];
22
+ files: SourceFileLike[];
23
+ developmentMode: boolean;
24
+ }
25
+
26
+ const logger = rendererLogger.component("css-candidate-manifest");
27
+ const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
28
+ const DEV_MANIFEST_TTL_MS = 2000;
29
+
30
+ const manifestCache = new Map<string, CandidateManifest>();
31
+ const routeCandidateCache = new Map<string, Set<string>>();
32
+
33
+ function normalizePath(path: string): string {
34
+ return path.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
35
+ }
36
+
37
+ function toRelativeProjectPath(path: string, projectDir: string): string {
38
+ const normalized = normalizePath(path);
39
+ const normalizedProjectDir = normalizePath(projectDir).replace(/\/+$/, "");
40
+ if (normalized.startsWith(normalizedProjectDir)) {
41
+ return normalized.slice(normalizedProjectDir.length).replace(/^\/+/, "");
42
+ }
43
+ return normalized.replace(/^\/+/, "");
44
+ }
45
+
46
+ function buildManifestCacheKey(projectScope: string, projectVersion: string): string {
47
+ return `${projectScope}:${projectVersion}`;
48
+ }
49
+
50
+ function shouldRebuildManifest(
51
+ existing: CandidateManifest | undefined,
52
+ developmentMode: boolean,
53
+ ): boolean {
54
+ if (!existing) return true;
55
+ if (!developmentMode) return false;
56
+ return (Date.now() - existing.builtAt) > DEV_MANIFEST_TTL_MS;
57
+ }
58
+
59
+ function buildSourceCandidatePaths(modulePath: string): string[] {
60
+ const normalized = normalizePath(modulePath).replace(/^\/+/, "").replace(/^_vf_modules\//, "");
61
+ if (!normalized.endsWith(".js")) return [normalized];
62
+ const withoutJs = normalized.slice(0, -3);
63
+ return [
64
+ `${withoutJs}.tsx`,
65
+ `${withoutJs}.ts`,
66
+ `${withoutJs}.jsx`,
67
+ `${withoutJs}.mdx`,
68
+ `${withoutJs}.js`,
69
+ ];
70
+ }
71
+
72
+ function buildCandidateManifest(files: SourceFileLike[], projectDir: string): CandidateManifest {
73
+ const fileCandidates = new Map<string, Set<string>>();
74
+ const allCandidates = new Set<string>();
75
+
76
+ for (const file of files) {
77
+ if (!file.content) continue;
78
+ if (!SOURCE_EXTENSIONS.some((ext) => file.path.endsWith(ext))) continue;
79
+
80
+ const candidates = new Set(extractCandidates(file.content));
81
+ const relativePath = toRelativeProjectPath(file.path, projectDir);
82
+ const absolutePath = normalizePath(file.path);
83
+
84
+ fileCandidates.set(relativePath, candidates);
85
+ fileCandidates.set(absolutePath, candidates);
86
+
87
+ for (const cls of candidates) allCandidates.add(cls);
88
+ }
89
+
90
+ return { fileCandidates, allCandidates, builtAt: Date.now() };
91
+ }
92
+
93
+ function addCandidatesForPath(
94
+ target: Set<string>,
95
+ manifest: CandidateManifest,
96
+ path: string,
97
+ projectDir: string,
98
+ ): void {
99
+ const absolutePath = normalizePath(path);
100
+ const relativePath = toRelativeProjectPath(path, projectDir);
101
+ const candidates = manifest.fileCandidates.get(absolutePath) ??
102
+ manifest.fileCandidates.get(relativePath);
103
+ if (!candidates) return;
104
+ for (const cls of candidates) target.add(cls);
105
+ }
106
+
107
+ /**
108
+ * Resolve route-scoped Tailwind candidates from a precomputed per-project manifest.
109
+ */
110
+ export function getRouteCandidates(options: RouteCandidateOptions): Set<string> {
111
+ const manifestKey = buildManifestCacheKey(options.projectScope, options.projectVersion);
112
+ const existingManifest = manifestCache.get(manifestKey);
113
+ const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
114
+ ? buildCandidateManifest(options.files, options.projectDir)
115
+ : existingManifest!;
116
+
117
+ if (manifest !== existingManifest) {
118
+ manifestCache.set(manifestKey, manifest);
119
+
120
+ // Clear route subsets when project-level file manifest is rebuilt.
121
+ for (const key of routeCandidateCache.keys()) {
122
+ if (key.startsWith(`${manifestKey}:`)) routeCandidateCache.delete(key);
123
+ }
124
+ }
125
+
126
+ const routeCacheKey = `${manifestKey}:${options.routeKey}`;
127
+ const cachedRoute = routeCandidateCache.get(routeCacheKey);
128
+ if (cachedRoute) return new Set(cachedRoute);
129
+
130
+ const routeCandidates = new Set<string>();
131
+
132
+ for (const path of options.routeFilePaths) {
133
+ addCandidatesForPath(routeCandidates, manifest, path, options.projectDir);
134
+ }
135
+
136
+ for (const modulePath of getRouteModulePaths(options.projectScope, options.routeKey)) {
137
+ for (const sourcePath of buildSourceCandidatePaths(modulePath)) {
138
+ addCandidatesForPath(routeCandidates, manifest, sourcePath, options.projectDir);
139
+ }
140
+ }
141
+
142
+ // Fallback to full-project candidates for correctness if route manifest is incomplete.
143
+ if (routeCandidates.size === 0) {
144
+ for (const cls of manifest.allCandidates) routeCandidates.add(cls);
145
+ }
146
+
147
+ routeCandidateCache.set(routeCacheKey, routeCandidates);
148
+
149
+ logger.debug("Resolved route candidates", {
150
+ projectScope: options.projectScope,
151
+ projectVersion: options.projectVersion,
152
+ route: options.routeKey,
153
+ count: routeCandidates.size,
154
+ });
155
+
156
+ return new Set(routeCandidates);
157
+ }
158
+
159
+ /**
160
+ * Invalidate cached candidate manifests for one project scope (or all scopes).
161
+ */
162
+ export function invalidateProjectCandidateManifests(projectScope?: string): void {
163
+ if (!projectScope) {
164
+ manifestCache.clear();
165
+ routeCandidateCache.clear();
166
+ return;
167
+ }
168
+
169
+ for (const key of manifestCache.keys()) {
170
+ if (key.startsWith(`${projectScope}:`)) manifestCache.delete(key);
171
+ }
172
+
173
+ for (const key of routeCandidateCache.keys()) {
174
+ if (key.startsWith(`${projectScope}:`)) routeCandidateCache.delete(key);
175
+ }
176
+ }
@@ -8,7 +8,6 @@ import {
8
8
  injectHTMLContent,
9
9
  isFullHTMLDocument,
10
10
  } from "../../html/index.js";
11
- import { extractCandidates } from "../../html/styles-builder/tailwind-compiler.js";
12
11
  import type { CollectedHead } from "../../react/head-collector.js";
13
12
  import type { RuntimeAdapter } from "../../platform/adapters/base.js";
14
13
  import type {
@@ -25,6 +24,11 @@ import { computeSourceHash } from "../../studio/hash-utils.js";
25
24
  import { extractRelativePath } from "../../utils/route-path-utils.js";
26
25
  import { resolveAppComponentPath } from "../layouts/utils/app-resolver.js";
27
26
  import { StreamTimeoutError, streamToString } from "../utils/stream-utils.js";
27
+ import {
28
+ normalizeCssModuleKey,
29
+ rewriteCssModuleContent,
30
+ } from "../../transforms/css-modules/naming.js";
31
+ import { getRouteCandidates } from "./css-candidate-manifest.js";
28
32
 
29
33
  const logger = rendererLogger.component("html-generator");
30
34
 
@@ -46,6 +50,8 @@ export interface HTMLGenerationContext {
46
50
  ssrHash: string;
47
51
  options?: RenderOptions;
48
52
  collectedHead?: CollectedHead;
53
+ /** Absolute paths to CSS files imported by components (collected during module loading) */
54
+ cssImports?: string[];
49
55
  }
50
56
 
51
57
  export class HTMLGenerator {
@@ -306,11 +312,15 @@ export class HTMLGenerator {
306
312
  mergedFrontmatter: MDXFrontmatter,
307
313
  ): Promise<HTMLGenerationOptions> {
308
314
  const stylesheetPath = this.config.config?.tailwind?.stylesheet || "globals.css";
309
- const [appComponentPath, globalCSS, projectClasses] = await Promise.all([
315
+ const [appComponentPath, globalCSS] = await Promise.all([
310
316
  this.resolveAppPath().then((p) => p ?? undefined),
311
317
  this.loadProjectFile(stylesheetPath),
312
- this.extractProjectClasses(),
313
318
  ]);
319
+ const projectClasses = await this.extractProjectClassesForRoute(context, appComponentPath);
320
+
321
+ // Load CSS imported by components and merge with globalCSS.
322
+ // Deduplicate against the configured stylesheet to avoid double-loading.
323
+ const combinedCSS = await this.mergeImportedCSS(globalCSS, context.cssImports, stylesheetPath);
314
324
 
315
325
  logger.debug("App component resolution", {
316
326
  appComponentPath,
@@ -351,7 +361,7 @@ export class HTMLGenerator {
351
361
  pagePath,
352
362
  pageType,
353
363
  nonce: context.options?.nonce,
354
- globalCSS,
364
+ globalCSS: combinedCSS,
355
365
  frontmatter: mergedFrontmatter,
356
366
  studioEmbed: context.options?.studioEmbed,
357
367
  projectId: context.options?.projectId,
@@ -368,8 +378,94 @@ export class HTMLGenerator {
368
378
  };
369
379
  }
370
380
 
371
- private async extractProjectClasses(): Promise<Set<string>> {
372
- const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
381
+ /**
382
+ * Load CSS files imported by components and merge with the global stylesheet.
383
+ * Deduplicates against the configured Tailwind stylesheet path to avoid
384
+ * double-loading globals.css when it's both auto-discovered and explicitly imported.
385
+ */
386
+ private async mergeImportedCSS(
387
+ globalCSS: string | undefined,
388
+ cssImports: string[] | undefined,
389
+ stylesheetPath: string,
390
+ ): Promise<string | undefined> {
391
+ if (!cssImports || cssImports.length === 0) return globalCSS;
392
+
393
+ const normalizedStylesheetPath = stylesheetPath.replace(/^\/+/, "");
394
+ const configuredStylesheetAbsolute = normalizeCssModuleKey(
395
+ join(this.config.projectDir, normalizedStylesheetPath),
396
+ );
397
+ const uniqueImports = new Map<string, string>();
398
+ for (const cssPath of cssImports) {
399
+ const normalized = normalizeCssModuleKey(cssPath);
400
+ if (!uniqueImports.has(normalized)) {
401
+ uniqueImports.set(normalized, cssPath);
402
+ }
403
+ }
404
+
405
+ const sortedImports = [...uniqueImports.entries()].sort((a, b) => a[0].localeCompare(b[0]));
406
+ const regularCssSegments: string[] = [];
407
+ const moduleCssSegments: string[] = [];
408
+
409
+ for (const [normalizedCssPath, cssPath] of sortedImports) {
410
+ // Deduplicate only exact path matches to avoid skipping unrelated files
411
+ // like /styles/globals.css when the configured stylesheet is /globals.css.
412
+ if (normalizedCssPath === configuredStylesheetAbsolute) {
413
+ continue;
414
+ }
415
+
416
+ try {
417
+ const content = await this.config.adapter.fs.readFile(cssPath);
418
+ if (!content) continue;
419
+
420
+ if (normalizedCssPath.endsWith(".module.css")) {
421
+ moduleCssSegments.push(rewriteCssModuleContent(content, normalizedCssPath));
422
+ } else {
423
+ regularCssSegments.push(content);
424
+ }
425
+ } catch {
426
+ logger.debug("Could not load imported CSS file", { cssPath });
427
+ }
428
+ }
429
+
430
+ if (regularCssSegments.length === 0 && moduleCssSegments.length === 0) return globalCSS;
431
+
432
+ const combined = [globalCSS, ...regularCssSegments, ...moduleCssSegments]
433
+ .filter(Boolean)
434
+ .join("\n");
435
+ logger.debug("Merged imported CSS with global stylesheet", {
436
+ importedCount: regularCssSegments.length + moduleCssSegments.length,
437
+ regularCount: regularCssSegments.length,
438
+ moduleCount: moduleCssSegments.length,
439
+ totalLength: combined.length,
440
+ });
441
+ return combined;
442
+ }
443
+
444
+ private getProjectContentVersion(): string | undefined {
445
+ const wrappedFs = this.config.adapter.fs as unknown as {
446
+ getUnderlyingAdapter?: () => unknown;
447
+ };
448
+
449
+ if (typeof wrappedFs.getUnderlyingAdapter !== "function") return undefined;
450
+
451
+ const fsAdapter = wrappedFs.getUnderlyingAdapter() as {
452
+ getProjectData?: () => { updated_at?: string } | undefined;
453
+ };
454
+
455
+ return fsAdapter.getProjectData?.()?.updated_at;
456
+ }
457
+
458
+ private buildRouteManifestKey(pagePath: string): string {
459
+ const relativePagePath = extractRelativePath(pagePath, this.config.projectDir);
460
+ return relativePagePath
461
+ .replace(/\.(tsx|ts|jsx|mdx|md|js)$/, "")
462
+ .replace(/^pages\//, "");
463
+ }
464
+
465
+ private async extractProjectClassesForRoute(
466
+ context: HTMLGenerationContext,
467
+ appComponentPath?: string,
468
+ ): Promise<Set<string>> {
373
469
  const classes = new Set<string>();
374
470
 
375
471
  const wrappedFs = this.config.adapter.fs as unknown as {
@@ -387,20 +483,36 @@ export class HTMLGenerator {
387
483
  if (typeof fsAdapter.getAllSourceFiles !== "function") return classes;
388
484
 
389
485
  const files = await fsAdapter.getAllSourceFiles();
486
+ const projectScope = context.options?.projectSlug || context.options?.projectId ||
487
+ this.config.projectDir;
488
+ const projectVersion = this.getProjectContentVersion() ??
489
+ (this.config.mode === "development" ? "dev" : "unknown");
490
+ const routeKey = this.buildRouteManifestKey(context.pageInfo.entity.path);
491
+ const routeLayoutPaths = context.nestedLayouts
492
+ .map((layout) => layout.componentPath || layout.path)
493
+ .filter((path): path is string => Boolean(path));
494
+ const routeFilePaths = [
495
+ context.pageInfo.entity.path,
496
+ ...routeLayoutPaths,
497
+ ...(appComponentPath ? [appComponentPath] : []),
498
+ ];
390
499
 
391
- let filesProcessed = 0;
392
- for (const file of files) {
393
- if (!file.content) continue;
394
- if (!SOURCE_EXTENSIONS.some((ext) => file.path.endsWith(ext))) continue;
500
+ const routeCandidates = getRouteCandidates({
501
+ projectScope,
502
+ projectVersion,
503
+ projectDir: this.config.projectDir,
504
+ routeKey,
505
+ routeFilePaths,
506
+ files,
507
+ developmentMode: this.config.mode === "development",
508
+ });
395
509
 
396
- filesProcessed++;
397
- for (const cls of extractCandidates(file.content)) {
398
- classes.add(cls);
399
- }
400
- }
510
+ for (const cls of routeCandidates) classes.add(cls);
401
511
 
402
512
  logger.debug("extractProjectClasses", {
403
- filesProcessed,
513
+ filesProcessed: files.length,
514
+ routeKey,
515
+ routeFileCount: routeFilePaths.length,
404
516
  totalClasses: classes.size,
405
517
  });
406
518