Skip to content

Commit cab52ba

Browse files
authored
Merge pull request #1883 from codeflash-ai/node_modules_symlink
node modules symlink to avoid reinstallation of npm package
2 parents f7ee7b3 + 5300f62 commit cab52ba

4 files changed

Lines changed: 63 additions & 12 deletions

File tree

codeflash/languages/javascript/mocha_runner.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,21 @@ def _ensure_runtime_files(project_root: Path) -> None:
6565
Installs codeflash package if not already present.
6666
The package provides all runtime files needed for test instrumentation.
6767
68+
In monorepos, node_modules may be hoisted to the repo root, so we walk
69+
upward from project_root to find an existing codeflash installation.
70+
6871
Args:
6972
project_root: The project root directory.
7073
7174
"""
72-
node_modules_pkg = project_root / "node_modules" / "codeflash"
73-
if node_modules_pkg.exists():
74-
logger.debug("codeflash already installed")
75-
return
75+
# Walk upward to find codeflash in any ancestor node_modules (monorepo hoisting)
76+
current = project_root
77+
while current != current.parent:
78+
node_modules_pkg = current / "node_modules" / "codeflash"
79+
if node_modules_pkg.exists():
80+
logger.debug(f"codeflash already installed at {node_modules_pkg}")
81+
return
82+
current = current.parent
7683

7784
install_cmd = get_package_install_command(project_root, "codeflash", dev=True)
7885
try:

codeflash/languages/javascript/support.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,6 +1951,13 @@ def setup_test_config(self, test_cfg: TestConfig, file_path: Path, current_workt
19511951
)
19521952
if original_node_modules.exists() and not worktree_node_modules.exists():
19531953
worktree_node_modules.symlink_to(original_node_modules)
1954+
# In monorepos, node_modules lives at the repo root, not the package level.
1955+
# Symlink the root-level node_modules into the worktree so Vitest/npx can resolve deps.
1956+
if not worktree_node_modules.exists():
1957+
worktree_root_node_modules = current_worktree / "node_modules"
1958+
original_root_node_modules = original_js_root / "node_modules"
1959+
if original_root_node_modules.exists() and not worktree_root_node_modules.exists():
1960+
worktree_root_node_modules.symlink_to(original_root_node_modules)
19541961
verify_js_requirements(test_cfg)
19551962

19561963
def adjust_test_config_for_discovery(self, test_cfg: TestConfig) -> None:

codeflash/languages/javascript/test_runner.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -699,14 +699,21 @@ def _ensure_runtime_files(project_root: Path) -> None:
699699
The package provides all runtime files needed for test instrumentation.
700700
Uses the project's detected package manager (npm, pnpm, yarn, or bun).
701701
702+
In monorepos, node_modules may be hoisted to the repo root, so we walk
703+
upward from project_root to find an existing codeflash installation.
704+
702705
Args:
703706
project_root: The project root directory.
704707
705708
"""
706-
node_modules_pkg = project_root / "node_modules" / "codeflash"
707-
if node_modules_pkg.exists():
708-
logger.debug("codeflash already installed")
709-
return
709+
# Walk upward to find codeflash in any ancestor node_modules (monorepo hoisting)
710+
current = project_root
711+
while current != current.parent:
712+
node_modules_pkg = current / "node_modules" / "codeflash"
713+
if node_modules_pkg.exists():
714+
logger.debug(f"codeflash already installed at {node_modules_pkg}")
715+
return
716+
current = current.parent
710717

711718
install_cmd = get_package_install_command(project_root, "codeflash", dev=True)
712719
try:

codeflash/languages/javascript/vitest_runner.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
import os
910
import subprocess
1011
import time
1112
from pathlib import Path
@@ -95,14 +96,21 @@ def _ensure_runtime_files(project_root: Path) -> None:
9596
The package provides all runtime files needed for test instrumentation.
9697
Uses the project's detected package manager (npm, pnpm, yarn, or bun).
9798
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+
98102
Args:
99103
project_root: The project root directory.
100104
101105
"""
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
106114

107115
install_cmd = get_package_install_command(project_root, "codeflash", dev=True)
108116
try:
@@ -295,6 +303,18 @@ def _build_vitest_behavioral_command(
295303
if codeflash_vitest_config:
296304
cmd.append(f"--config={codeflash_vitest_config}")
297305

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+
298318
if output_file:
299319
# Use dot notation for junit reporter output file when multiple reporters are used
300320
# Format: --outputFile.junit=/path/to/file.xml
@@ -343,6 +363,16 @@ def _build_vitest_benchmarking_command(
343363
if codeflash_vitest_config:
344364
cmd.append(f"--config={codeflash_vitest_config}")
345365

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+
346376
if output_file:
347377
# Use dot notation for junit reporter output file when multiple reporters are used
348378
cmd.append(f"--outputFile.junit={output_file}")

0 commit comments

Comments
 (0)