Skip to content

Commit a815e54

Browse files
Merge branch 'main' into fix/jest-xml-path-resolution-monorepo
2 parents 8223796 + 4a7432e commit a815e54

File tree

7 files changed

+347
-41
lines changed

7 files changed

+347
-41
lines changed

codeflash/cli_cmds/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,11 +359,13 @@ def _handle_show_config() -> None:
359359
detected = detect_project(project_root)
360360

361361
# Check if config exists or is auto-detected
362-
config_exists, _ = has_existing_config(project_root)
362+
config_exists, config_file = has_existing_config(project_root)
363363
status = "Saved config" if config_exists else "Auto-detected (not saved)"
364364

365365
console.print()
366366
console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
367+
if config_exists and config_file:
368+
console.print(f"[dim]Config file: {project_root / config_file}[/dim]")
367369
console.print()
368370

369371
table = Table(show_header=True, header_style="bold cyan")

codeflash/code_utils/config_js.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from pathlib import Path
77
from typing import Any
88

9+
from codeflash.setup.detector import is_build_output_dir
10+
911
PACKAGE_JSON_CACHE: dict[Path, Path] = {}
1012
PACKAGE_JSON_DATA_CACHE: dict[Path, dict[str, Any]] = {}
1113

@@ -50,12 +52,15 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
5052
"""Detect module root from package.json fields or directory conventions.
5153
5254
Detection order:
53-
1. package.json "exports" field (extract directory from main export)
54-
2. package.json "module" field (ESM entry point)
55-
3. package.json "main" field (CJS entry point)
56-
4. "src/" directory if it exists
55+
1. src/, lib/, source/ directories (common source directories)
56+
2. package.json "exports" field (if not in build output directory)
57+
3. package.json "module" field (ESM, if not in build output directory)
58+
4. package.json "main" field (CJS, if not in build output directory)
5759
5. Fall back to "." (project root)
5860
61+
Build output directories (build/, dist/, out/) are skipped since they contain
62+
compiled code, not source files.
63+
5964
Args:
6065
project_root: Root directory of the project.
6166
package_data: Parsed package.json data.
@@ -64,6 +69,11 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
6469
Detected module root path (relative to project root).
6570
6671
"""
72+
# Check for common source directories first - these are always preferred
73+
for src_dir in ["src", "lib", "source"]:
74+
if (project_root / src_dir).is_dir():
75+
return src_dir
76+
6777
# Check exports field (modern Node.js)
6878
exports = package_data.get("exports")
6979
if exports:
@@ -80,27 +90,38 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
8090

8191
if entry_path and isinstance(entry_path, str):
8292
parent = Path(entry_path).parent
83-
if parent != Path() and (project_root / parent).is_dir():
93+
if (
94+
parent != Path()
95+
and parent.as_posix() != "."
96+
and (project_root / parent).is_dir()
97+
and not is_build_output_dir(parent)
98+
):
8499
return parent.as_posix()
85100

86101
# Check module field (ESM)
87102
module_field = package_data.get("module")
88103
if module_field and isinstance(module_field, str):
89104
parent = Path(module_field).parent
90-
if parent != Path() and (project_root / parent).is_dir():
105+
if (
106+
parent != Path()
107+
and parent.as_posix() != "."
108+
and (project_root / parent).is_dir()
109+
and not is_build_output_dir(parent)
110+
):
91111
return parent.as_posix()
92112

93113
# Check main field (CJS)
94114
main_field = package_data.get("main")
95115
if main_field and isinstance(main_field, str):
96116
parent = Path(main_field).parent
97-
if parent != Path() and (project_root / parent).is_dir():
117+
if (
118+
parent != Path()
119+
and parent.as_posix() != "."
120+
and (project_root / parent).is_dir()
121+
and not is_build_output_dir(parent)
122+
):
98123
return parent.as_posix()
99124

100-
# Check for src/ directory convention
101-
if (project_root / "src").is_dir():
102-
return "src"
103-
104125
# Default to project root
105126
return "."
106127

codeflash/setup/detector.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import tomlkit
2323

24+
_BUILD_DIRS = frozenset({"build", "dist", "out", ".next", ".nuxt"})
25+
2426

2527
@dataclass
2628
class DetectedProject:
@@ -310,14 +312,21 @@ def _detect_js_module_root(project_root: Path) -> tuple[Path, str]:
310312
"""Detect JavaScript/TypeScript module root.
311313
312314
Priority:
313-
1. package.json "exports" field
314-
2. package.json "module" field (ESM)
315-
3. package.json "main" field (CJS)
316-
4. src/ directory
317-
5. lib/ directory
318-
6. Project root
315+
1. src/, lib/, source/ directories (common source directories)
316+
2. package.json "exports" field (if not in build output directory)
317+
3. package.json "module" field (ESM, if not in build output directory)
318+
4. package.json "main" field (CJS, if not in build output directory)
319+
5. Project root
320+
321+
Build output directories (build/, dist/, out/) are skipped since they contain
322+
compiled code, not source files.
319323
320324
"""
325+
# Check for common source directories first - these are always preferred
326+
for src_dir in ["src", "lib", "source"]:
327+
if (project_root / src_dir).is_dir():
328+
return project_root / src_dir, f"{src_dir}/ directory"
329+
321330
package_json_path = project_root / "package.json"
322331
package_data: dict[str, Any] = {}
323332

@@ -334,32 +343,52 @@ def _detect_js_module_root(project_root: Path) -> tuple[Path, str]:
334343
entry_path = _extract_entry_path(exports)
335344
if entry_path:
336345
parent = Path(entry_path).parent
337-
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
346+
if (
347+
parent != Path()
348+
and parent.as_posix() != "."
349+
and (project_root / parent).is_dir()
350+
and not is_build_output_dir(parent)
351+
):
338352
return project_root / parent, f'{parent.as_posix()}/ (from package.json "exports")'
339353

340354
# Check module field (ESM)
341355
module_field = package_data.get("module")
342356
if module_field and isinstance(module_field, str):
343357
parent = Path(module_field).parent
344-
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
358+
if (
359+
parent != Path()
360+
and parent.as_posix() != "."
361+
and (project_root / parent).is_dir()
362+
and not is_build_output_dir(parent)
363+
):
345364
return project_root / parent, f'{parent.as_posix()}/ (from package.json "module")'
346365

347366
# Check main field (CJS)
348367
main_field = package_data.get("main")
349368
if main_field and isinstance(main_field, str):
350369
parent = Path(main_field).parent
351-
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
370+
if (
371+
parent != Path()
372+
and parent.as_posix() != "."
373+
and (project_root / parent).is_dir()
374+
and not is_build_output_dir(parent)
375+
):
352376
return project_root / parent, f'{parent.as_posix()}/ (from package.json "main")'
353377

354-
# Check for common source directories
355-
for src_dir in ["src", "lib", "source"]:
356-
if (project_root / src_dir).is_dir():
357-
return project_root / src_dir, f"{src_dir}/ directory"
358-
359378
# Default to project root
360379
return project_root, "project root"
361380

362381

382+
def is_build_output_dir(path: Path) -> bool:
383+
"""Check if a path is within a common build output directory.
384+
385+
Build output directories contain compiled code and should be skipped
386+
in favor of source directories.
387+
388+
"""
389+
return not _BUILD_DIRS.isdisjoint(path.parts)
390+
391+
363392
def _extract_entry_path(exports: Any) -> str | None:
364393
"""Extract entry path from package.json exports field."""
365394
if isinstance(exports, str):

tests/code_utils/test_config_js.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,14 @@ def test_detects_from_exports_string(self, tmp_path: Path) -> None:
127127
assert result == "lib"
128128

129129
def test_detects_from_exports_object_dot(self, tmp_path: Path) -> None:
130-
"""Should detect module root from exports object with '.' key."""
130+
"""Should skip build output dirs and return '.' when no src dir exists."""
131131
(tmp_path / "dist").mkdir()
132132
package_data = {"exports": {".": "./dist/index.js"}}
133133

134134
result = detect_module_root(tmp_path, package_data)
135135

136-
assert result == "dist"
136+
# dist is a build output directory, so it's skipped
137+
assert result == "."
137138

138139
def test_detects_from_exports_object_nested(self, tmp_path: Path) -> None:
139140
"""Should detect module root from nested exports object."""
@@ -227,13 +228,14 @@ def test_ignores_root_level_main(self, tmp_path: Path) -> None:
227228
assert result == "src"
228229

229230
def test_handles_deeply_nested_exports(self, tmp_path: Path) -> None:
230-
"""Should handle deeply nested export paths."""
231+
"""Should handle deeply nested export paths but skip build output dirs."""
231232
(tmp_path / "packages" / "core" / "dist").mkdir(parents=True)
232233
package_data = {"exports": {".": {"import": "./packages/core/dist/index.mjs"}}}
233234

234235
result = detect_module_root(tmp_path, package_data)
235236

236-
assert result == "packages/core/dist"
237+
# dist is a build output directory, so it's skipped even when nested
238+
assert result == "."
237239

238240
def test_handles_empty_exports(self, tmp_path: Path) -> None:
239241
"""Should handle empty exports gracefully."""
@@ -756,7 +758,7 @@ def test_vite_react_project(self, tmp_path: Path) -> None:
756758
assert config["formatter_cmds"] == ["npx eslint --fix $file"]
757759

758760
def test_library_with_exports(self, tmp_path: Path) -> None:
759-
"""Should handle library with modern exports field."""
761+
"""Should handle library with modern exports field, skipping build output dirs."""
760762
(tmp_path / "dist").mkdir()
761763
package_json = tmp_path / "package.json"
762764
package_json.write_text(
@@ -773,7 +775,8 @@ def test_library_with_exports(self, tmp_path: Path) -> None:
773775

774776
assert result is not None
775777
config, _ = result
776-
assert config["module_root"] == str((tmp_path / "dist").resolve())
778+
# dist is a build output directory, so it's skipped and falls back to project root
779+
assert config["module_root"] == str(tmp_path.resolve())
777780

778781
def test_monorepo_package(self, tmp_path: Path) -> None:
779782
"""Should handle monorepo package configuration."""

tests/test_languages/test_code_context_extraction.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,15 +1822,15 @@ def test_with_tricky_helpers(self, ts_support, temp_project):
18221822
target_func = "sendSlackMessage"
18231823

18241824
functions = ts_support.discover_functions(file_path)
1825-
func_info = next(f for f in functions if f.name == target_func)
1825+
func_info = next(f for f in functions if f.function_name == target_func)
18261826
fto = FunctionToOptimize(
18271827
function_name=target_func,
18281828
file_path=file_path,
18291829
parents=func_info.parents,
1830-
starting_line=func_info.start_line,
1831-
ending_line=func_info.end_line,
1832-
starting_col=func_info.start_col,
1833-
ending_col=func_info.end_col,
1830+
starting_line=func_info.starting_line,
1831+
ending_line=func_info.ending_line,
1832+
starting_col=func_info.starting_col,
1833+
ending_col=func_info.ending_col,
18341834
is_async=func_info.is_async,
18351835
language="typescript",
18361836
)

0 commit comments

Comments
 (0)