|
6 | 6 |
|
7 | 7 | from __future__ import annotations |
8 | 8 |
|
| 9 | +import os |
9 | 10 | import subprocess |
10 | 11 | import time |
11 | 12 | from pathlib import Path |
@@ -95,14 +96,21 @@ def _ensure_runtime_files(project_root: Path) -> None: |
95 | 96 | The package provides all runtime files needed for test instrumentation. |
96 | 97 | Uses the project's detected package manager (npm, pnpm, yarn, or bun). |
97 | 98 |
|
| 99 | + In monorepos, node_modules may be hoisted to the repo root, so we walk |
| 100 | + upward from project_root to find an existing codeflash installation. |
| 101 | +
|
98 | 102 | Args: |
99 | 103 | project_root: The project root directory. |
100 | 104 |
|
101 | 105 | """ |
102 | | - node_modules_pkg = project_root / "node_modules" / "codeflash" |
103 | | - if node_modules_pkg.exists(): |
104 | | - logger.debug("codeflash already installed") |
105 | | - return |
| 106 | + # Walk upward to find codeflash in any ancestor node_modules (monorepo hoisting) |
| 107 | + current = project_root |
| 108 | + while current != current.parent: |
| 109 | + node_modules_pkg = current / "node_modules" / "codeflash" |
| 110 | + if node_modules_pkg.exists(): |
| 111 | + logger.debug(f"codeflash already installed at {node_modules_pkg}") |
| 112 | + return |
| 113 | + current = current.parent |
106 | 114 |
|
107 | 115 | install_cmd = get_package_install_command(project_root, "codeflash", dev=True) |
108 | 116 | try: |
@@ -295,6 +303,18 @@ def _build_vitest_behavioral_command( |
295 | 303 | if codeflash_vitest_config: |
296 | 304 | cmd.append(f"--config={codeflash_vitest_config}") |
297 | 305 |
|
| 306 | + # In monorepos, test files may be outside the project root (e.g., tests/ at repo root |
| 307 | + # while project_root is packages/features/). Vitest only discovers files under its root |
| 308 | + # directory, so we use --dir to widen the scan to the common ancestor. |
| 309 | + if project_root and test_files: |
| 310 | + resolved_root = project_root.resolve() |
| 311 | + test_dirs = {f.resolve().parent for f in test_files} |
| 312 | + if any(not d.is_relative_to(resolved_root) for d in test_dirs): |
| 313 | + all_paths = [str(resolved_root)] + [str(d) for d in test_dirs] |
| 314 | + common_ancestor = Path(os.path.commonpath(all_paths)) |
| 315 | + cmd.append(f"--dir={common_ancestor}") |
| 316 | + logger.debug(f"Test files outside project root, using --dir={common_ancestor}") |
| 317 | + |
298 | 318 | if output_file: |
299 | 319 | # Use dot notation for junit reporter output file when multiple reporters are used |
300 | 320 | # Format: --outputFile.junit=/path/to/file.xml |
@@ -343,6 +363,16 @@ def _build_vitest_benchmarking_command( |
343 | 363 | if codeflash_vitest_config: |
344 | 364 | cmd.append(f"--config={codeflash_vitest_config}") |
345 | 365 |
|
| 366 | + # In monorepos, test files may be outside the project root. Widen the scan directory. |
| 367 | + if project_root and test_files: |
| 368 | + resolved_root = project_root.resolve() |
| 369 | + test_dirs = {f.resolve().parent for f in test_files} |
| 370 | + if any(not d.is_relative_to(resolved_root) for d in test_dirs): |
| 371 | + all_paths = [str(resolved_root)] + [str(d) for d in test_dirs] |
| 372 | + common_ancestor = Path(os.path.commonpath(all_paths)) |
| 373 | + cmd.append(f"--dir={common_ancestor}") |
| 374 | + logger.debug(f"Test files outside project root, using --dir={common_ancestor}") |
| 375 | + |
346 | 376 | if output_file: |
347 | 377 | # Use dot notation for junit reporter output file when multiple reporters are used |
348 | 378 | cmd.append(f"--outputFile.junit={output_file}") |
|
0 commit comments