Skip to content

Commit 1ad9c6a

Browse files
authored
Merge pull request #1438 from codeflash-ai/install_with_clone
Install cli post cloning in npm
2 parents 6428a22 + 0b611c7 commit 1ad9c6a

2 files changed

Lines changed: 192 additions & 21 deletions

File tree

packages/codeflash/scripts/postinstall.js

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -106,40 +106,63 @@ function installUv() {
106106
}
107107
}
108108

109+
/**
110+
* Check if git is available
111+
*/
112+
function hasGit() {
113+
try {
114+
const result = spawnSync('git', ['--version'], {
115+
stdio: 'ignore',
116+
shell: true,
117+
});
118+
return result.status === 0;
119+
} catch {
120+
return false;
121+
}
122+
}
123+
109124
/**
110125
* Install codeflash Python CLI using uv tool
126+
*
127+
* Installation priority:
128+
* 1. GitHub main branch (if git available) - gets latest features
129+
* 2. PyPI (fallback) - stable release
130+
*
131+
* We prefer GitHub because it has the latest JS/TS support that may not
132+
* be published to PyPI yet. uv handles cloning internally in its cache.
111133
*/
112134
function installCodeflash(uvBin) {
113135
logStep('2/3', 'Installing codeflash Python CLI...');
114136

137+
const GITHUB_REPO = 'git+https://github.com/codeflash-ai/codeflash.git';
138+
139+
// Priority 1: Install from GitHub (latest features, requires git)
140+
if (hasGit()) {
141+
try {
142+
execSync(`"${uvBin}" tool install --force --python python3.12 "${GITHUB_REPO}"`, {
143+
stdio: 'inherit',
144+
shell: true,
145+
});
146+
logSuccess('codeflash CLI installed from GitHub (latest)');
147+
return true;
148+
} catch (error) {
149+
logWarning(`GitHub installation failed: ${error.message}`);
150+
logWarning('Falling back to PyPI...');
151+
}
152+
} else {
153+
logWarning('Git not found, installing from PyPI...');
154+
}
155+
156+
// Priority 2: Install from PyPI (stable release fallback)
115157
try {
116-
// Use uv tool install to install codeflash in an isolated environment
117-
// This avoids conflicts with any existing Python environments
118158
execSync(`"${uvBin}" tool install --force --python python3.12 codeflash`, {
119159
stdio: 'inherit',
120160
shell: true,
121161
});
122-
logSuccess('codeflash CLI installed successfully');
162+
logSuccess('codeflash CLI installed from PyPI');
123163
return true;
124164
} catch (error) {
125-
// If codeflash is not on PyPI yet, try installing from the local package
126-
logWarning('codeflash not found on PyPI, trying local installation...');
127-
try {
128-
// Try installing from the current codeflash repo if we're in development
129-
const cliRoot = path.resolve(__dirname, '..', '..', '..');
130-
const pyprojectPath = path.join(cliRoot, 'pyproject.toml');
131-
132-
if (fs.existsSync(pyprojectPath)) {
133-
execSync(`"${uvBin}" tool install --force "${cliRoot}"`, {
134-
stdio: 'inherit',
135-
shell: true,
136-
});
137-
logSuccess('codeflash CLI installed from local source');
138-
return true;
139-
}
140-
} catch (localError) {
141-
logError(`Failed to install codeflash: ${localError.message}`);
142-
}
165+
logError(`Failed to install codeflash: ${error.message}`);
143166
return false;
144167
}
145168
}

tests/test_languages/test_treesitter_utils.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,151 @@ def test_find_generic_function(self, ts_analyzer):
545545

546546
assert len(functions) == 1
547547
assert functions[0].name == "identity"
548+
549+
550+
class TestExportConstArrowFunctions:
551+
"""Tests for export const arrow function pattern - Issue #10.
552+
553+
Modern TypeScript codebases commonly use:
554+
- export const slugify = (str: string) => { return s; }
555+
- export const uniqueBy = <T>(array: T[]) => { ... }
556+
557+
These must be correctly recognized as optimizable functions.
558+
"""
559+
560+
@pytest.fixture
561+
def ts_analyzer(self):
562+
"""Create a TypeScript analyzer."""
563+
return TreeSitterAnalyzer(TreeSitterLanguage.TYPESCRIPT)
564+
565+
def test_export_const_arrow_function_basic(self, ts_analyzer):
566+
"""Test finding export const arrow function (basic pattern)."""
567+
code = """export const slugify = (str: string) => {
568+
return str.toLowerCase();
569+
};"""
570+
functions = ts_analyzer.find_functions(code)
571+
572+
assert len(functions) == 1
573+
assert functions[0].name == "slugify"
574+
assert functions[0].is_arrow is True
575+
assert ts_analyzer.has_return_statement(functions[0], code) is True
576+
577+
def test_export_const_arrow_function_optional_param(self, ts_analyzer):
578+
"""Test finding export const arrow function with optional parameter."""
579+
code = """export const slugify = (str: string, forDisplayingInput?: boolean) => {
580+
if (!str) {
581+
return "";
582+
}
583+
const s = str.toLowerCase();
584+
return forDisplayingInput ? s : s.replace(/-+$/, "");
585+
};"""
586+
functions = ts_analyzer.find_functions(code)
587+
588+
assert len(functions) == 1
589+
assert functions[0].name == "slugify"
590+
assert functions[0].is_arrow is True
591+
assert ts_analyzer.has_return_statement(functions[0], code) is True
592+
593+
def test_export_const_generic_arrow_function(self, ts_analyzer):
594+
"""Test finding export const arrow function with generics."""
595+
code = """export const uniqueBy = <T extends { [key: string]: unknown }>(array: T[], keys: (keyof T)[]) => {
596+
return array.filter(
597+
(item, index, self) => index === self.findIndex((t) => keys.every((key) => t[key] === item[key]))
598+
);
599+
};"""
600+
functions = ts_analyzer.find_functions(code)
601+
602+
# Should find uniqueBy, and possibly the inner arrow functions
603+
uniqueBy = next((f for f in functions if f.name == "uniqueBy"), None)
604+
assert uniqueBy is not None
605+
assert uniqueBy.is_arrow is True
606+
assert ts_analyzer.has_return_statement(uniqueBy, code) is True
607+
608+
def test_export_const_arrow_function_is_exported(self, ts_analyzer):
609+
"""Test that export const arrow functions are recognized as exported."""
610+
code = """export const slugify = (str: string) => {
611+
return str.toLowerCase();
612+
};"""
613+
614+
# Check is_function_exported
615+
is_exported, export_name = ts_analyzer.is_function_exported(code, "slugify")
616+
assert is_exported is True
617+
assert export_name == "slugify"
618+
619+
def test_export_const_with_default_export(self, ts_analyzer):
620+
"""Test export const with separate default export."""
621+
code = """export const slugify = (str: string) => {
622+
return str.toLowerCase();
623+
};
624+
625+
export default slugify;"""
626+
627+
functions = ts_analyzer.find_functions(code)
628+
assert len(functions) == 1
629+
assert functions[0].name == "slugify"
630+
631+
# Should be exported both ways
632+
is_named, named_name = ts_analyzer.is_function_exported(code, "slugify")
633+
assert is_named is True
634+
635+
def test_multiple_export_const_functions(self, ts_analyzer):
636+
"""Test multiple export const arrow functions in same file."""
637+
code = """export const notUndefined = <T>(val: T | undefined): val is T => Boolean(val);
638+
639+
export const uniqueBy = <T extends { [key: string]: unknown }>(array: T[], keys: (keyof T)[]) => {
640+
return array.filter(
641+
(item, index, self) => index === self.findIndex((t) => keys.every((key) => t[key] === item[key]))
642+
);
643+
};"""
644+
645+
functions = ts_analyzer.find_functions(code)
646+
647+
# Find the top-level exported functions
648+
names = {f.name for f in functions if f.parent_function is None}
649+
assert "notUndefined" in names
650+
assert "uniqueBy" in names
651+
652+
def test_export_const_arrow_with_implicit_return(self, ts_analyzer):
653+
"""Test export const arrow function with implicit return."""
654+
code = """export const double = (n: number) => n * 2;"""
655+
656+
functions = ts_analyzer.find_functions(code)
657+
658+
assert len(functions) == 1
659+
assert functions[0].name == "double"
660+
assert functions[0].is_arrow is True
661+
assert ts_analyzer.has_return_statement(functions[0], code) is True
662+
663+
def test_export_const_async_arrow_function(self, ts_analyzer):
664+
"""Test export const async arrow function."""
665+
code = """export const fetchData = async (url: string) => {
666+
const response = await fetch(url);
667+
return response.json();
668+
};"""
669+
670+
functions = ts_analyzer.find_functions(code)
671+
672+
assert len(functions) == 1
673+
assert functions[0].name == "fetchData"
674+
assert functions[0].is_arrow is True
675+
assert functions[0].is_async is True
676+
assert ts_analyzer.has_return_statement(functions[0], code) is True
677+
678+
def test_non_exported_const_not_exported(self, ts_analyzer):
679+
"""Test that non-exported const functions are not marked as exported."""
680+
code = """const privateFunc = (x: number) => {
681+
return x * 2;
682+
};
683+
684+
export const publicFunc = (x: number) => {
685+
return privateFunc(x);
686+
};"""
687+
688+
# privateFunc should not be exported
689+
is_private_exported, _ = ts_analyzer.is_function_exported(code, "privateFunc")
690+
assert is_private_exported is False
691+
692+
# publicFunc should be exported
693+
is_public_exported, name = ts_analyzer.is_function_exported(code, "publicFunc")
694+
assert is_public_exported is True
695+
assert name == "publicFunc"

0 commit comments

Comments
 (0)