Skip to content

Commit cf876b6

Browse files
manually install the extension from openvsx
1 parent ed89fa8 commit cf876b6

File tree

6 files changed

+215
-176
lines changed

6 files changed

+215
-176
lines changed

codeflash/cli_cmds/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
from codeflash.cli_cmds import logging_config
77
from codeflash.cli_cmds.cli_common import apologize_and_exit
8-
from codeflash.cli_cmds.cmd_init import init_codeflash, install_github_actions, install_vscode_extension
8+
from codeflash.cli_cmds.cmd_init import init_codeflash, install_github_actions
99
from codeflash.cli_cmds.console import logger
10+
from codeflash.cli_cmds.extension import install_vscode_extension
1011
from codeflash.code_utils import env_utils
1112
from codeflash.code_utils.code_utils import exit_with_message
1213
from codeflash.code_utils.config_parser import parse_config_file

codeflash/cli_cmds/cmd_init.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from codeflash.api.cfapi import is_github_app_installed_on_repo
2525
from codeflash.cli_cmds.cli_common import apologize_and_exit
2626
from codeflash.cli_cmds.console import console, logger
27-
from codeflash.cli_cmds.vscode import install_vscode_extension
27+
from codeflash.cli_cmds.extension import install_vscode_extension
2828
from codeflash.code_utils.compat import LF
2929
from codeflash.code_utils.config_parser import parse_config_file
3030
from codeflash.code_utils.env_utils import check_formatter_installed, get_codeflash_api_key

codeflash/cli_cmds/extension.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import json
2+
import shutil
3+
import tempfile
4+
import time
5+
import zipfile
6+
from contextlib import contextmanager
7+
from functools import lru_cache
8+
from pathlib import Path
9+
from typing import Any
10+
11+
import requests
12+
13+
from codeflash.cli_cmds.console import logger, progress_bar
14+
15+
supported_editor_paths = [
16+
(Path(Path.home()) / ".vscode", "VSCode"),
17+
(Path(Path.home()) / ".cursor", "Cursor"),
18+
(Path(Path.home()) / ".windsurf", "Windsurf"),
19+
]
20+
21+
22+
@lru_cache(maxsize=1)
23+
def get_extension_info() -> dict[str, Any]:
24+
url = "https://open-vsx.org/api/codeflash/codeflash/latest"
25+
try:
26+
response = requests.get(url, timeout=60)
27+
response.raise_for_status()
28+
return response.json()
29+
except Exception as e:
30+
logger.error("Failed to retrieve extension metadata from open-vsx.org: %s", e)
31+
return {}
32+
33+
34+
@contextmanager
35+
def download_and_extract_extension(download_url: str) -> Path:
36+
with tempfile.TemporaryDirectory() as tmpdir:
37+
tmpdir_path = Path(tmpdir)
38+
zip_path = tmpdir_path / "extension.zip"
39+
40+
resp = requests.get(download_url, stream=True, timeout=60)
41+
resp.raise_for_status()
42+
with zip_path.open("wb") as f:
43+
for chunk in resp.iter_content(chunk_size=8192):
44+
f.write(chunk)
45+
46+
with zipfile.ZipFile(zip_path, "r") as zf:
47+
zf.extractall(tmpdir_path)
48+
49+
extension_path = tmpdir_path / "extension"
50+
if not extension_path.is_dir():
51+
raise FileNotFoundError("Extension folder not found in downloaded archive")
52+
53+
yield extension_path
54+
55+
56+
@contextmanager
57+
def download_and_extract_extension_with_progress(download_url: str) -> Path:
58+
with (
59+
progress_bar("Downloading CodeFlash extension from open-vsx.org..."),
60+
download_and_extract_extension(download_url) as extension_path,
61+
):
62+
yield extension_path
63+
64+
65+
def copy_extension_artifacts(src: Path, dest: Path, version: str) -> bool:
66+
dst_extensions_dir = dest / "extensions"
67+
if not dst_extensions_dir.exists():
68+
logger.warning("Extensions directory does not exist: %s", str(dst_extensions_dir))
69+
return False
70+
71+
dest_path = dst_extensions_dir / f"codeflash.codeflash-{version}"
72+
73+
shutil.copytree(src, dest_path, dirs_exist_ok=True)
74+
return True
75+
76+
77+
def get_metadata_file_path(editor_path: Path) -> Path:
78+
return editor_path / "extensions" / "extensions.json"
79+
80+
81+
@lru_cache(maxsize=len(supported_editor_paths))
82+
def get_cf_extension_metadata(editor_path: Path) -> list[dict[str, Any]]:
83+
metadata_file = get_metadata_file_path(editor_path)
84+
if not metadata_file.exists():
85+
logger.warning("Extensions metadata file does not exist")
86+
return []
87+
with metadata_file.open("r", encoding="utf-8") as f:
88+
return json.load(f)
89+
90+
91+
def write_cf_extension_metadata(editor_path: Path, version: str) -> bool:
92+
data = {
93+
"identifier": {"id": "codeflash.codeflash", "uuid": "7798581f-9eab-42be-a1b2-87f90973434d"},
94+
"version": version,
95+
"location": {"$mid": 1, "path": f"{editor_path}/extensions/codeflash.codeflash-{version}", "scheme": "file"},
96+
"relativeLocation": f"codeflash.codeflash-{version}",
97+
"metadata": {
98+
"installedTimestamp": int(time.time() * 1000),
99+
"pinned": False,
100+
"source": "gallery",
101+
"id": "7798581f-9eab-42be-a1b2-87f90973434d",
102+
"publisherId": "bc13551d-2729-4c35-84ce-1d3bd3baab45",
103+
"publisherDisplayName": "CodeFlash",
104+
"targetPlatform": "universal",
105+
"updated": True,
106+
"isPreReleaseVersion": False,
107+
"hasPreReleaseVersion": False,
108+
"isApplicationScoped": False,
109+
"isMachineScoped": False,
110+
"isBuiltin": False,
111+
"private": False,
112+
"preRelease": False,
113+
},
114+
}
115+
installed_extensions = get_cf_extension_metadata(editor_path)
116+
if not installed_extensions:
117+
return False
118+
installed_extensions = [
119+
ext for ext in installed_extensions if ext.get("identifier", {}).get("id") != data["identifier"]["id"]
120+
]
121+
installed_extensions.append(data)
122+
with get_metadata_file_path(editor_path).open("w", encoding="utf-8") as f:
123+
json.dump(installed_extensions, f)
124+
return True
125+
126+
127+
def is_latest_version_installed(editor_path: Path, latest_version: str) -> bool:
128+
installed_extensions = get_cf_extension_metadata(editor_path)
129+
current_version = ""
130+
for ext in installed_extensions:
131+
if ext.get("identifier", {}).get("id") == "codeflash.codeflash":
132+
current_version = ext.get("version", "")
133+
break
134+
return current_version == latest_version
135+
136+
137+
def manually_install_vscode_extension(downloadable_paths: list[tuple[Path, str]]) -> None:
138+
with progress_bar("Fetching extension metadata..."):
139+
info = get_extension_info()
140+
141+
download_url = info.get("files", {}).get("download", "")
142+
latest_version = info.get("version", "")
143+
144+
if not download_url or not latest_version:
145+
logger.error("Failed to retrieve extension metadata")
146+
return
147+
148+
successful_installs = []
149+
with download_and_extract_extension_with_progress(download_url) as extension_path:
150+
for editor_path, editor in downloadable_paths:
151+
try:
152+
did_copy = copy_extension_artifacts(extension_path, editor_path, latest_version)
153+
if not did_copy:
154+
continue
155+
did_write_metadata = write_cf_extension_metadata(editor_path, latest_version)
156+
if not did_write_metadata:
157+
continue
158+
159+
successful_installs.append(editor)
160+
except Exception as e:
161+
logger.error("Failed to install CodeFlash extension for %s: %s", editor, e)
162+
if successful_installs:
163+
logger.info("Successfully installed CodeFlash extension for: %s", ", ".join(successful_installs))
164+
165+
166+
def install_vscode_extension() -> None:
167+
editors_installed = []
168+
downloadable_paths = []
169+
170+
for editor_path, editor in supported_editor_paths:
171+
if not editor_path.exists():
172+
continue
173+
174+
editors_installed.append(editor)
175+
176+
info = get_extension_info()
177+
latest_version = info.get("version", "")
178+
179+
if not latest_version or is_latest_version_installed(editor_path, latest_version):
180+
continue
181+
182+
downloadable_paths.append((editor_path, editor))
183+
184+
if not downloadable_paths:
185+
if editors_installed:
186+
logger.info("CodeFlash extension is already installed and up-to-date for: %s", ", ".join(editors_installed))
187+
return
188+
189+
logger.info("No supported editors found for CodeFlash extension installation")
190+
return
191+
192+
downloadable_editors = ", ".join([editor for _, editor in downloadable_paths])
193+
logger.info("Installing CodeFlash extension for %s...", downloadable_editors)
194+
manually_install_vscode_extension(downloadable_paths)

codeflash/cli_cmds/logging_config.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,15 @@ def set_level(level: int, *, echo_setting: bool = True) -> None:
1717
format=BARE_LOGGING_FORMAT,
1818
)
1919
logging.getLogger().setLevel(level)
20-
if echo_setting:
21-
if level == logging.DEBUG:
22-
logging.Formatter.converter = time.gmtime
23-
logging.basicConfig(
24-
format=VERBOSE_LOGGING_FORMAT,
25-
handlers=[
26-
RichHandler(rich_tracebacks=True, markup=False, console=console, show_path=False, show_time=False)
27-
],
28-
force=True,
29-
)
30-
logging.info("Verbose DEBUG logging enabled")
31-
else:
32-
logging.info("Logging level set to INFO")
20+
if echo_setting and level == logging.DEBUG:
21+
logging.Formatter.converter = time.gmtime
22+
logging.basicConfig(
23+
format=VERBOSE_LOGGING_FORMAT,
24+
handlers=[
25+
RichHandler(rich_tracebacks=True, markup=False, console=console, show_path=False, show_time=False)
26+
],
27+
force=True,
28+
)
29+
logging.info("Verbose DEBUG logging enabled")
30+
3331
console.rule()

0 commit comments

Comments
 (0)