Merge branch 'main' into fix/jest-xml-path-resolution-monorepo

This commit is contained in:
Sarthak Agarwal 2026-02-03 20:00:10 +05:30 committed by GitHub
commit 6384a555df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 459 additions and 214 deletions

View file

@ -359,11 +359,13 @@ def _handle_show_config() -> None:
detected = detect_project(project_root)
# Check if config exists or is auto-detected
config_exists, _ = has_existing_config(project_root)
config_exists, config_file = has_existing_config(project_root)
status = "Saved config" if config_exists else "Auto-detected (not saved)"
console.print()
console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
if config_exists and config_file:
console.print(f"[dim]Config file: {project_root / config_file}[/dim]")
console.print()
table = Table(show_header=True, header_style="bold cyan")

View file

@ -6,6 +6,8 @@ import json
from pathlib import Path
from typing import Any
from codeflash.setup.detector import is_build_output_dir
PACKAGE_JSON_CACHE: dict[Path, Path] = {}
PACKAGE_JSON_DATA_CACHE: dict[Path, dict[str, Any]] = {}
@ -50,12 +52,15 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
"""Detect module root from package.json fields or directory conventions.
Detection order:
1. package.json "exports" field (extract directory from main export)
2. package.json "module" field (ESM entry point)
3. package.json "main" field (CJS entry point)
4. "src/" directory if it exists
1. src/, lib/, source/ directories (common source directories)
2. package.json "exports" field (if not in build output directory)
3. package.json "module" field (ESM, if not in build output directory)
4. package.json "main" field (CJS, if not in build output directory)
5. Fall back to "." (project root)
Build output directories (build/, dist/, out/) are skipped since they contain
compiled code, not source files.
Args:
project_root: Root directory of the project.
package_data: Parsed package.json data.
@ -64,6 +69,11 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
Detected module root path (relative to project root).
"""
# Check for common source directories first - these are always preferred
for src_dir in ["src", "lib", "source"]:
if (project_root / src_dir).is_dir():
return src_dir
# Check exports field (modern Node.js)
exports = package_data.get("exports")
if exports:
@ -80,27 +90,38 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
if entry_path and isinstance(entry_path, str):
parent = Path(entry_path).parent
if parent != Path() and (project_root / parent).is_dir():
if (
parent != Path()
and parent.as_posix() != "."
and (project_root / parent).is_dir()
and not is_build_output_dir(parent)
):
return parent.as_posix()
# Check module field (ESM)
module_field = package_data.get("module")
if module_field and isinstance(module_field, str):
parent = Path(module_field).parent
if parent != Path() and (project_root / parent).is_dir():
if (
parent != Path()
and parent.as_posix() != "."
and (project_root / parent).is_dir()
and not is_build_output_dir(parent)
):
return parent.as_posix()
# Check main field (CJS)
main_field = package_data.get("main")
if main_field and isinstance(main_field, str):
parent = Path(main_field).parent
if parent != Path() and (project_root / parent).is_dir():
if (
parent != Path()
and parent.as_posix() != "."
and (project_root / parent).is_dir()
and not is_build_output_dir(parent)
):
return parent.as_posix()
# Check for src/ directory convention
if (project_root / "src").is_dir():
return "src"
# Default to project root
return "."

View file

@ -21,6 +21,8 @@ from typing import Any
import tomlkit
_BUILD_DIRS = frozenset({"build", "dist", "out", ".next", ".nuxt"})
@dataclass
class DetectedProject:
@ -310,14 +312,21 @@ def _detect_js_module_root(project_root: Path) -> tuple[Path, str]:
"""Detect JavaScript/TypeScript module root.
Priority:
1. package.json "exports" field
2. package.json "module" field (ESM)
3. package.json "main" field (CJS)
4. src/ directory
5. lib/ directory
6. Project root
1. src/, lib/, source/ directories (common source directories)
2. package.json "exports" field (if not in build output directory)
3. package.json "module" field (ESM, if not in build output directory)
4. package.json "main" field (CJS, if not in build output directory)
5. Project root
Build output directories (build/, dist/, out/) are skipped since they contain
compiled code, not source files.
"""
# Check for common source directories first - these are always preferred
for src_dir in ["src", "lib", "source"]:
if (project_root / src_dir).is_dir():
return project_root / src_dir, f"{src_dir}/ directory"
package_json_path = project_root / "package.json"
package_data: dict[str, Any] = {}
@ -334,32 +343,52 @@ def _detect_js_module_root(project_root: Path) -> tuple[Path, str]:
entry_path = _extract_entry_path(exports)
if entry_path:
parent = Path(entry_path).parent
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
if (
parent != Path()
and parent.as_posix() != "."
and (project_root / parent).is_dir()
and not is_build_output_dir(parent)
):
return project_root / parent, f'{parent.as_posix()}/ (from package.json "exports")'
# Check module field (ESM)
module_field = package_data.get("module")
if module_field and isinstance(module_field, str):
parent = Path(module_field).parent
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
if (
parent != Path()
and parent.as_posix() != "."
and (project_root / parent).is_dir()
and not is_build_output_dir(parent)
):
return project_root / parent, f'{parent.as_posix()}/ (from package.json "module")'
# Check main field (CJS)
main_field = package_data.get("main")
if main_field and isinstance(main_field, str):
parent = Path(main_field).parent
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
if (
parent != Path()
and parent.as_posix() != "."
and (project_root / parent).is_dir()
and not is_build_output_dir(parent)
):
return project_root / parent, f'{parent.as_posix()}/ (from package.json "main")'
# Check for common source directories
for src_dir in ["src", "lib", "source"]:
if (project_root / src_dir).is_dir():
return project_root / src_dir, f"{src_dir}/ directory"
# Default to project root
return project_root, "project root"
def is_build_output_dir(path: Path) -> bool:
"""Check if a path is within a common build output directory.
Build output directories contain compiled code and should be skipped
in favor of source directories.
"""
return not _BUILD_DIRS.isdisjoint(path.parts)
def _extract_entry_path(exports: Any) -> str | None:
"""Extract entry path from package.json exports field."""
if isinstance(exports, str):

View file

@ -1,60 +0,0 @@
# codeflash
AI-powered code performance optimization for JavaScript and TypeScript.
## Installation
```bash
npm install -g codeflash
# or
npx codeflash
```
## Quick Start
1. Get your API key from [codeflash.ai](https://codeflash.ai)
2. Set your API key:
```bash
export CODEFLASH_API_KEY=your-api-key
```
3. Optimize a function:
```bash
codeflash --file src/utils.ts --function slowFunction
```
## Usage
```bash
# Optimize a specific function
codeflash --file <path> --function <name>
# Optimize all functions in a directory
codeflash --all src/
# Initialize GitHub Actions workflow
codeflash init-actions
# Verify setup
codeflash --verify-setup
```
## Requirements
- Node.js >= 16.0.0
- A codeflash API key
## Supported Platforms
- Linux (x64, arm64)
- macOS (x64, arm64)
- Windows (x64)
## Documentation
See [codeflash.ai/docs](https://codeflash.ai/docs) for full documentation.
## License
BSL-1.1

View file

@ -1,47 +0,0 @@
#!/usr/bin/env node
/**
* Wrapper script for codeflash CLI.
* Invokes the downloaded binary with all passed arguments.
*/
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
function getBinaryPath() {
const binDir = __dirname;
const isWindows = process.platform === 'win32';
return path.join(binDir, isWindows ? 'codeflash.exe' : 'codeflash-binary');
}
function main() {
const binaryPath = getBinaryPath();
if (!fs.existsSync(binaryPath)) {
console.error('\x1b[31mError: codeflash binary not found.\x1b[0m');
console.error('Try reinstalling: npm install codeflash');
process.exit(1);
}
// Pass all arguments to the binary
const args = process.argv.slice(2);
const child = spawn(binaryPath, args, {
stdio: 'inherit',
env: process.env,
});
child.on('error', (error) => {
console.error(`\x1b[31mError running codeflash: ${error.message}\x1b[0m`);
process.exit(1);
});
child.on('exit', (code, signal) => {
if (signal) {
process.exit(1);
}
process.exit(code || 0);
});
}
main();

View file

@ -1,47 +0,0 @@
{
"name": "codeflash",
"version": "0.0.0",
"description": "AI-powered code performance optimization - automatically find and fix slow code",
"keywords": [
"codeflash",
"performance",
"optimization",
"ai",
"code",
"profiler",
"typescript",
"javascript"
],
"author": "CodeFlash Inc. <contact@codeflash.ai>",
"license": "BSL-1.1",
"homepage": "https://codeflash.ai",
"repository": {
"type": "git",
"url": "git+https://github.com/codeflash-ai/codeflash.git"
},
"bugs": {
"url": "https://github.com/codeflash-ai/codeflash/issues"
},
"bin": {
"codeflash": "./bin/codeflash"
},
"scripts": {
"postinstall": "node lib/install.js"
},
"engines": {
"node": ">=16.0.0"
},
"os": [
"darwin",
"linux",
"win32"
],
"cpu": [
"x64",
"arm64"
],
"files": [
"bin/",
"lib/"
]
}

View file

@ -1,12 +1,12 @@
{
"name": "codeflash",
"version": "0.5.0",
"version": "0.7.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeflash",
"version": "0.5.0",
"version": "0.7.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "codeflash",
"version": "0.5.0",
"version": "0.7.0",
"description": "Codeflash - AI-powered code optimization for JavaScript and TypeScript",
"main": "runtime/index.js",
"types": "runtime/index.d.ts",

View file

@ -0,0 +1,73 @@
/**
* Test: Dynamic environment variable reading
*
* This test verifies that the performance configuration functions read
* environment variables at runtime rather than at module load time.
*
* This is critical for Vitest compatibility, where modules may be cached
* and loaded before environment variables are set.
*
* Run with: node __tests__/dynamic-env-vars.test.js
*/
const assert = require('assert');
// Clear any existing env vars before loading the module
delete process.env.CODEFLASH_PERF_LOOP_COUNT;
delete process.env.CODEFLASH_PERF_MIN_LOOPS;
delete process.env.CODEFLASH_PERF_TARGET_DURATION_MS;
delete process.env.CODEFLASH_PERF_BATCH_SIZE;
delete process.env.CODEFLASH_PERF_STABILITY_CHECK;
delete process.env.CODEFLASH_PERF_CURRENT_BATCH;
// Now load the module - at this point env vars are not set
const capture = require('../capture');
console.log('Testing dynamic environment variable reading...\n');
// Test 1: Default values when env vars are not set
console.log('Test 1: Default values');
assert.strictEqual(capture.getPerfLoopCount(), 1, 'getPerfLoopCount default should be 1');
assert.strictEqual(capture.getPerfMinLoops(), 5, 'getPerfMinLoops default should be 5');
assert.strictEqual(capture.getPerfTargetDurationMs(), 10000, 'getPerfTargetDurationMs default should be 10000');
assert.strictEqual(capture.getPerfBatchSize(), 10, 'getPerfBatchSize default should be 10');
assert.strictEqual(capture.getPerfStabilityCheck(), false, 'getPerfStabilityCheck default should be false');
assert.strictEqual(capture.getPerfCurrentBatch(), 0, 'getPerfCurrentBatch default should be 0');
console.log(' PASS: All defaults correct\n');
// Test 2: Values change when env vars are set AFTER module load
// This is the critical test - if these were constants, they would still return defaults
console.log('Test 2: Dynamic reading after module load');
process.env.CODEFLASH_PERF_LOOP_COUNT = '100';
process.env.CODEFLASH_PERF_MIN_LOOPS = '10';
process.env.CODEFLASH_PERF_TARGET_DURATION_MS = '5000';
process.env.CODEFLASH_PERF_BATCH_SIZE = '20';
process.env.CODEFLASH_PERF_STABILITY_CHECK = 'true';
process.env.CODEFLASH_PERF_CURRENT_BATCH = '5';
assert.strictEqual(capture.getPerfLoopCount(), 100, 'getPerfLoopCount should read 100 from env');
assert.strictEqual(capture.getPerfMinLoops(), 10, 'getPerfMinLoops should read 10 from env');
assert.strictEqual(capture.getPerfTargetDurationMs(), 5000, 'getPerfTargetDurationMs should read 5000 from env');
assert.strictEqual(capture.getPerfBatchSize(), 20, 'getPerfBatchSize should read 20 from env');
assert.strictEqual(capture.getPerfStabilityCheck(), true, 'getPerfStabilityCheck should read true from env');
assert.strictEqual(capture.getPerfCurrentBatch(), 5, 'getPerfCurrentBatch should read 5 from env');
console.log(' PASS: Dynamic reading works correctly\n');
// Test 3: Values change again when env vars are modified
console.log('Test 3: Values update when env vars change');
process.env.CODEFLASH_PERF_LOOP_COUNT = '500';
process.env.CODEFLASH_PERF_BATCH_SIZE = '50';
assert.strictEqual(capture.getPerfLoopCount(), 500, 'getPerfLoopCount should update to 500');
assert.strictEqual(capture.getPerfBatchSize(), 50, 'getPerfBatchSize should update to 50');
console.log(' PASS: Values update correctly\n');
// Cleanup
delete process.env.CODEFLASH_PERF_LOOP_COUNT;
delete process.env.CODEFLASH_PERF_MIN_LOOPS;
delete process.env.CODEFLASH_PERF_TARGET_DURATION_MS;
delete process.env.CODEFLASH_PERF_BATCH_SIZE;
delete process.env.CODEFLASH_PERF_STABILITY_CHECK;
delete process.env.CODEFLASH_PERF_CURRENT_BATCH;
console.log('All tests passed!');

View file

@ -47,14 +47,30 @@ const TEST_MODULE = process.env.CODEFLASH_TEST_MODULE;
// Batch 1: Test1(5 loops) → Test2(5 loops) → Test3(5 loops)
// Batch 2: Test1(5 loops) → Test2(5 loops) → Test3(5 loops)
// ...until time budget exhausted
const PERF_LOOP_COUNT = parseInt(process.env.CODEFLASH_PERF_LOOP_COUNT || '1', 10);
const PERF_MIN_LOOPS = parseInt(process.env.CODEFLASH_PERF_MIN_LOOPS || '5', 10);
const PERF_TARGET_DURATION_MS = parseInt(process.env.CODEFLASH_PERF_TARGET_DURATION_MS || '10000', 10);
const PERF_BATCH_SIZE = parseInt(process.env.CODEFLASH_PERF_BATCH_SIZE || '10', 10);
const PERF_STABILITY_CHECK = (process.env.CODEFLASH_PERF_STABILITY_CHECK || 'false').toLowerCase() === 'true';
//
// IMPORTANT: These are getter functions, NOT constants!
// Vitest caches modules and may load this file before env vars are set.
// Using getter functions ensures we read the env vars at runtime when they're actually needed.
function getPerfLoopCount() {
return parseInt(process.env.CODEFLASH_PERF_LOOP_COUNT || '1', 10);
}
function getPerfMinLoops() {
return parseInt(process.env.CODEFLASH_PERF_MIN_LOOPS || '5', 10);
}
function getPerfTargetDurationMs() {
return parseInt(process.env.CODEFLASH_PERF_TARGET_DURATION_MS || '10000', 10);
}
function getPerfBatchSize() {
return parseInt(process.env.CODEFLASH_PERF_BATCH_SIZE || '10', 10);
}
function getPerfStabilityCheck() {
return (process.env.CODEFLASH_PERF_STABILITY_CHECK || 'false').toLowerCase() === 'true';
}
// Current batch number - set by loop-runner before each batch
// This allows continuous loop indices even when Jest resets module state
const PERF_CURRENT_BATCH = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '0', 10);
function getPerfCurrentBatch() {
return parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '0', 10);
}
// Stability constants (matching Python's config_consts.py)
const STABILITY_WINDOW_SIZE = 0.35;
@ -86,7 +102,7 @@ function checkSharedTimeLimit() {
return false;
}
const elapsed = Date.now() - sharedPerfState.startTime;
if (elapsed >= PERF_TARGET_DURATION_MS && sharedPerfState.totalLoopsCompleted >= PERF_MIN_LOOPS) {
if (elapsed >= getPerfTargetDurationMs() && sharedPerfState.totalLoopsCompleted >= getPerfMinLoops()) {
sharedPerfState.shouldStop = true;
return true;
}
@ -111,7 +127,7 @@ function getInvocationLoopIndex(invocationKey) {
// Calculate global loop index using batch number from environment
// PERF_CURRENT_BATCH is 1-based (set by loop-runner before each batch)
const currentBatch = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '1', 10);
const globalIndex = (currentBatch - 1) * PERF_BATCH_SIZE + localIndex;
const globalIndex = (currentBatch - 1) * getPerfBatchSize() + localIndex;
return globalIndex;
}
@ -606,7 +622,7 @@ function capture(funcName, lineId, fn, ...args) {
*/
function capturePerf(funcName, lineId, fn, ...args) {
// Check if we should skip looping entirely (shared time budget exceeded)
const shouldLoop = PERF_LOOP_COUNT > 1 && !checkSharedTimeLimit();
const shouldLoop = getPerfLoopCount() > 1 && !checkSharedTimeLimit();
// Get test context (computed once, reused across batch)
let testModulePath;
@ -636,9 +652,9 @@ function capturePerf(funcName, lineId, fn, ...args) {
// If so, just execute the function once without timing (for test assertions)
const peekLoopIndex = (sharedPerfState.invocationLoopCounts[invocationKey] || 0);
const currentBatch = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '1', 10);
const nextGlobalIndex = (currentBatch - 1) * PERF_BATCH_SIZE + peekLoopIndex + 1;
const nextGlobalIndex = (currentBatch - 1) * getPerfBatchSize() + peekLoopIndex + 1;
if (shouldLoop && nextGlobalIndex > PERF_LOOP_COUNT) {
if (shouldLoop && nextGlobalIndex > getPerfLoopCount()) {
// All loops completed, just execute once for test assertion
return fn(...args);
}
@ -654,7 +670,7 @@ function capturePerf(funcName, lineId, fn, ...args) {
// Batched looping: run BATCH_SIZE loops per capturePerf call when using loop-runner
// For Vitest (no loop-runner), do all loops internally in a single call
const batchSize = shouldLoop
? (hasExternalLoopRunner ? PERF_BATCH_SIZE : PERF_LOOP_COUNT)
? (hasExternalLoopRunner ? getPerfBatchSize() : getPerfLoopCount())
: 1;
for (let batchIndex = 0; batchIndex < batchSize; batchIndex++) {
@ -667,7 +683,7 @@ function capturePerf(funcName, lineId, fn, ...args) {
const loopIndex = getInvocationLoopIndex(invocationKey);
// Check if we've exceeded max loops for this invocation
if (loopIndex > PERF_LOOP_COUNT) {
if (loopIndex > getPerfLoopCount()) {
break;
}
@ -872,7 +888,11 @@ module.exports = {
LOOP_INDEX,
OUTPUT_FILE,
TEST_ITERATION,
// Batch configuration
PERF_BATCH_SIZE,
PERF_LOOP_COUNT,
// Batch configuration (getter functions for dynamic env var reading)
getPerfBatchSize,
getPerfLoopCount,
getPerfMinLoops,
getPerfTargetDurationMs,
getPerfStabilityCheck,
getPerfCurrentBatch,
};

View file

@ -127,13 +127,14 @@ class TestDetectModuleRoot:
assert result == "lib"
def test_detects_from_exports_object_dot(self, tmp_path: Path) -> None:
"""Should detect module root from exports object with '.' key."""
"""Should skip build output dirs and return '.' when no src dir exists."""
(tmp_path / "dist").mkdir()
package_data = {"exports": {".": "./dist/index.js"}}
result = detect_module_root(tmp_path, package_data)
assert result == "dist"
# dist is a build output directory, so it's skipped
assert result == "."
def test_detects_from_exports_object_nested(self, tmp_path: Path) -> None:
"""Should detect module root from nested exports object."""
@ -227,13 +228,14 @@ class TestDetectModuleRoot:
assert result == "src"
def test_handles_deeply_nested_exports(self, tmp_path: Path) -> None:
"""Should handle deeply nested export paths."""
"""Should handle deeply nested export paths but skip build output dirs."""
(tmp_path / "packages" / "core" / "dist").mkdir(parents=True)
package_data = {"exports": {".": {"import": "./packages/core/dist/index.mjs"}}}
result = detect_module_root(tmp_path, package_data)
assert result == "packages/core/dist"
# dist is a build output directory, so it's skipped even when nested
assert result == "."
def test_handles_empty_exports(self, tmp_path: Path) -> None:
"""Should handle empty exports gracefully."""
@ -756,7 +758,7 @@ class TestRealWorldPackageJsonExamples:
assert config["formatter_cmds"] == ["npx eslint --fix $file"]
def test_library_with_exports(self, tmp_path: Path) -> None:
"""Should handle library with modern exports field."""
"""Should handle library with modern exports field, skipping build output dirs."""
(tmp_path / "dist").mkdir()
package_json = tmp_path / "package.json"
package_json.write_text(
@ -773,7 +775,8 @@ class TestRealWorldPackageJsonExamples:
assert result is not None
config, _ = result
assert config["module_root"] == str((tmp_path / "dist").resolve())
# dist is a build output directory, so it's skipped and falls back to project root
assert config["module_root"] == str(tmp_path.resolve())
def test_monorepo_package(self, tmp_path: Path) -> None:
"""Should handle monorepo package configuration."""

View file

@ -1822,15 +1822,15 @@ export const sendSlackMessage = async (
target_func = "sendSlackMessage"
functions = ts_support.discover_functions(file_path)
func_info = next(f for f in functions if f.name == target_func)
func_info = next(f for f in functions if f.function_name == target_func)
fto = FunctionToOptimize(
function_name=target_func,
file_path=file_path,
parents=func_info.parents,
starting_line=func_info.start_line,
ending_line=func_info.end_line,
starting_col=func_info.start_col,
ending_col=func_info.end_col,
starting_line=func_info.starting_line,
ending_line=func_info.ending_line,
starting_col=func_info.starting_col,
ending_col=func_info.ending_col,
is_async=func_info.is_async,
language="typescript",
)

View file

@ -14,6 +14,7 @@ from codeflash.setup.detector import (
_find_project_root,
detect_project,
has_existing_config,
is_build_output_dir,
)
@ -139,15 +140,15 @@ class TestDetectModuleRoot:
assert "pyproject.toml" in detail
def test_js_detects_from_exports(self, tmp_path):
"""Should detect module root from package.json exports."""
"""Should detect module root from package.json exports when no common src dir exists."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"exports": {".": "./src/index.js"}
"exports": {".": "./packages/core/index.js"}
}))
(tmp_path / "src").mkdir()
(tmp_path / "packages" / "core").mkdir(parents=True)
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path / "src"
assert module_root == tmp_path / "packages" / "core"
assert "exports" in detail
def test_js_detects_src_convention(self, tmp_path):
@ -158,6 +159,214 @@ class TestDetectModuleRoot:
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path / "src"
def test_js_prefers_src_over_build_src(self, tmp_path):
"""Should prefer src/ over build/src/ even when package.json points to build/."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"main": "build/src/index.js",
"module": "build/src/index.js"
}))
(tmp_path / "src").mkdir()
(tmp_path / "build" / "src").mkdir(parents=True)
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path / "src"
assert "src/ directory" in detail
def test_js_skips_build_dir_from_main(self, tmp_path):
"""Should skip build output directories from package.json main field."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"main": "build/index.js"
}))
(tmp_path / "build").mkdir()
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path
assert "project root" in detail
def test_js_skips_dist_dir_from_exports(self, tmp_path):
"""Should skip dist output directories from package.json exports field."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"exports": {".": "./dist/index.js"}
}))
(tmp_path / "dist").mkdir()
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path
assert "project root" in detail
def test_js_skips_out_dir_from_module(self, tmp_path):
"""Should skip out output directories from package.json module field."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"module": "out/esm/index.js"
}))
(tmp_path / "out" / "esm").mkdir(parents=True)
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path
assert "project root" in detail
def test_js_prefers_lib_over_build_dir(self, tmp_path):
"""Should prefer lib/ over build output directories."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"main": "dist/index.js"
}))
(tmp_path / "lib").mkdir()
(tmp_path / "dist").mkdir()
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path / "lib"
assert "lib/ directory" in detail
def test_js_prefers_source_over_build_dir(self, tmp_path):
"""Should prefer source/ over build output directories."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"main": "build/index.js"
}))
(tmp_path / "source").mkdir()
(tmp_path / "build").mkdir()
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path / "source"
assert "source/ directory" in detail
def test_js_falls_back_to_valid_exports_path(self, tmp_path):
"""Should use exports path when no common source dirs exist and path is not build output."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"exports": {".": "./packages/core/index.js"}
}))
(tmp_path / "packages" / "core").mkdir(parents=True)
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path / "packages" / "core"
assert "exports" in detail
def test_js_falls_back_to_valid_main_path(self, tmp_path):
"""Should use main path when no common source dirs exist and path is not build output."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"main": "packages/main/index.js"
}))
(tmp_path / "packages" / "main").mkdir(parents=True)
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path / "packages" / "main"
assert "main" in detail
def test_js_falls_back_to_valid_module_path(self, tmp_path):
"""Should use module path when no common source dirs exist and path is not build output."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"module": "esm/index.js"
}))
(tmp_path / "esm").mkdir()
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path / "esm"
assert "module" in detail
def test_js_returns_project_root_when_all_paths_are_build_output(self, tmp_path):
"""Should return project root when all package.json paths point to build outputs."""
(tmp_path / "package.json").write_text(json.dumps({
"name": "test",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {".": "./build/index.js"}
}))
(tmp_path / "dist" / "cjs").mkdir(parents=True)
(tmp_path / "dist" / "esm").mkdir(parents=True)
(tmp_path / "build").mkdir()
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path
assert "project root" in detail
def test_js_handles_malformed_package_json(self, tmp_path):
"""Should handle malformed package.json gracefully."""
(tmp_path / "package.json").write_text("{ invalid json }")
module_root, detail = _detect_js_module_root(tmp_path)
assert module_root == tmp_path
assert "project root" in detail
class TestIsBuildOutputDir:
"""Tests for is_build_output_dir function."""
def test_detects_build_dir(self):
"""Should detect build/ as build output."""
from pathlib import Path
assert is_build_output_dir(Path("build"))
assert is_build_output_dir(Path("build/src"))
assert is_build_output_dir(Path("build/src/index.js"))
def test_detects_dist_dir(self):
"""Should detect dist/ as build output."""
from pathlib import Path
assert is_build_output_dir(Path("dist"))
assert is_build_output_dir(Path("dist/esm"))
assert is_build_output_dir(Path("dist/cjs/index.js"))
def test_detects_out_dir(self):
"""Should detect out/ as build output."""
from pathlib import Path
assert is_build_output_dir(Path("out"))
assert is_build_output_dir(Path("out/src"))
def test_detects_next_dir(self):
"""Should detect .next/ as build output."""
from pathlib import Path
assert is_build_output_dir(Path(".next"))
assert is_build_output_dir(Path(".next/static"))
def test_detects_nuxt_dir(self):
"""Should detect .nuxt/ as build output."""
from pathlib import Path
assert is_build_output_dir(Path(".nuxt"))
assert is_build_output_dir(Path(".nuxt/dist"))
def test_detects_nested_build_dir(self):
"""Should detect build dir nested in path."""
from pathlib import Path
assert is_build_output_dir(Path("packages/build/index.js"))
assert is_build_output_dir(Path("foo/dist/bar"))
def test_does_not_detect_src(self):
"""Should not detect src/ as build output."""
from pathlib import Path
assert not is_build_output_dir(Path("src"))
assert not is_build_output_dir(Path("src/index.js"))
def test_does_not_detect_lib(self):
"""Should not detect lib/ as build output."""
from pathlib import Path
assert not is_build_output_dir(Path("lib"))
assert not is_build_output_dir(Path("lib/utils"))
def test_does_not_detect_source(self):
"""Should not detect source/ as build output."""
from pathlib import Path
assert not is_build_output_dir(Path("source"))
def test_does_not_detect_packages(self):
"""Should not detect packages/ as build output."""
from pathlib import Path
assert not is_build_output_dir(Path("packages"))
assert not is_build_output_dir(Path("packages/core"))
def test_does_not_detect_similar_names(self):
"""Should not detect directories with similar but different names."""
from pathlib import Path
assert not is_build_output_dir(Path("builder"))
assert not is_build_output_dir(Path("distribution"))
assert not is_build_output_dir(Path("output"))
class TestDetectTestsRoot:
"""Tests for tests root detection."""

View file

@ -857,6 +857,48 @@ class TestE2ECLIFlags:
# Should complete without error
_handle_show_config()
def test_show_config_displays_config_path_when_saved(self, project_with_existing_config, monkeypatch):
"""Should display config file path when saved config exists."""
monkeypatch.chdir(project_with_existing_config)
# Track what gets printed
printed_messages = []
def mock_print(msg="", *args, **kwargs):
printed_messages.append(str(msg))
from codeflash.cli_cmds import console
monkeypatch.setattr(console.console, "print", mock_print)
from codeflash.cli_cmds.cli import _handle_show_config
_handle_show_config()
# Verify config path is displayed
all_output = "\n".join(printed_messages)
assert "pyproject.toml" in all_output
assert "Config file:" in all_output
def test_show_config_no_path_when_auto_detected(self, python_src_layout, monkeypatch):
"""Should not display config file path when config is auto-detected."""
monkeypatch.chdir(python_src_layout)
# Track what gets printed
printed_messages = []
def mock_print(msg="", *args, **kwargs):
printed_messages.append(str(msg))
from codeflash.cli_cmds import console
monkeypatch.setattr(console.console, "print", mock_print)
from codeflash.cli_cmds.cli import _handle_show_config
_handle_show_config()
# Verify no config path line is displayed
all_output = "\n".join(printed_messages)
assert "Config file:" not in all_output
assert "Auto-detected" in all_output
def test_reset_config_removes_from_pyproject(self, project_with_existing_config, monkeypatch):
"""Should remove codeflash config from pyproject.toml."""
monkeypatch.chdir(project_with_existing_config)