codegraphcontext-rust 0.5.5__cp310-cp310-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegraphcontext/__init__.py +2 -0
- codegraphcontext/__main__.py +5 -0
- codegraphcontext/_cgc_rust.cp310-win_amd64.pyd +0 -0
- codegraphcontext/cli/__init__.py +1 -0
- codegraphcontext/cli/cli_helpers.py +774 -0
- codegraphcontext/cli/config_manager.py +1051 -0
- codegraphcontext/cli/main.py +2386 -0
- codegraphcontext/cli/registry_commands.py +404 -0
- codegraphcontext/cli/setup_macos.py +93 -0
- codegraphcontext/cli/setup_wizard.py +991 -0
- codegraphcontext/cli/visualizer.py +50 -0
- codegraphcontext/core/__init__.py +165 -0
- codegraphcontext/core/bundle_registry.py +181 -0
- codegraphcontext/core/cgc_bundle.py +858 -0
- codegraphcontext/core/cgcignore.py +118 -0
- codegraphcontext/core/database.py +273 -0
- codegraphcontext/core/database_falkordb.py +480 -0
- codegraphcontext/core/database_falkordb_remote.py +199 -0
- codegraphcontext/core/database_kuzu.py +636 -0
- codegraphcontext/core/falkor_worker.py +132 -0
- codegraphcontext/core/jobs.py +132 -0
- codegraphcontext/core/watcher.py +260 -0
- codegraphcontext/prompts.py +125 -0
- codegraphcontext/server.py +471 -0
- codegraphcontext/tool_definitions.py +220 -0
- codegraphcontext/tools/__init__.py +0 -0
- codegraphcontext/tools/advanced_language_query_tool.py +103 -0
- codegraphcontext/tools/code_finder.py +1160 -0
- codegraphcontext/tools/graph_builder.py +337 -0
- codegraphcontext/tools/handlers/analysis_handlers.py +114 -0
- codegraphcontext/tools/handlers/indexing_handlers.py +116 -0
- codegraphcontext/tools/handlers/management_handlers.py +339 -0
- codegraphcontext/tools/handlers/query_handlers.py +83 -0
- codegraphcontext/tools/handlers/watcher_handlers.py +83 -0
- codegraphcontext/tools/indexing/__init__.py +1 -0
- codegraphcontext/tools/indexing/constants.py +25 -0
- codegraphcontext/tools/indexing/discovery.py +64 -0
- codegraphcontext/tools/indexing/engine.py +109 -0
- codegraphcontext/tools/indexing/persistence/__init__.py +3 -0
- codegraphcontext/tools/indexing/persistence/parquet_loader.py +643 -0
- codegraphcontext/tools/indexing/persistence/writer.py +1114 -0
- codegraphcontext/tools/indexing/pipeline.py +171 -0
- codegraphcontext/tools/indexing/pre_scan.py +105 -0
- codegraphcontext/tools/indexing/resolution/__init__.py +8 -0
- codegraphcontext/tools/indexing/resolution/calls.py +253 -0
- codegraphcontext/tools/indexing/resolution/inheritance.py +91 -0
- codegraphcontext/tools/indexing/sanitize.py +41 -0
- codegraphcontext/tools/indexing/schema.py +79 -0
- codegraphcontext/tools/indexing/schema_contract.py +44 -0
- codegraphcontext/tools/indexing/scip_pipeline.py +140 -0
- codegraphcontext/tools/languages/c.py +563 -0
- codegraphcontext/tools/languages/cpp.py +616 -0
- codegraphcontext/tools/languages/csharp.py +551 -0
- codegraphcontext/tools/languages/dart.py +378 -0
- codegraphcontext/tools/languages/elixir.py +460 -0
- codegraphcontext/tools/languages/go.py +499 -0
- codegraphcontext/tools/languages/haskell.py +426 -0
- codegraphcontext/tools/languages/java.py +500 -0
- codegraphcontext/tools/languages/javascript.py +589 -0
- codegraphcontext/tools/languages/kotlin.py +640 -0
- codegraphcontext/tools/languages/perl.py +260 -0
- codegraphcontext/tools/languages/php.py +519 -0
- codegraphcontext/tools/languages/python.py +576 -0
- codegraphcontext/tools/languages/ruby.py +537 -0
- codegraphcontext/tools/languages/rust.py +295 -0
- codegraphcontext/tools/languages/scala.py +519 -0
- codegraphcontext/tools/languages/swift.py +491 -0
- codegraphcontext/tools/languages/typescript.py +575 -0
- codegraphcontext/tools/languages/typescriptjsx.py +151 -0
- codegraphcontext/tools/package_resolver.py +472 -0
- codegraphcontext/tools/query_tool_languages/c_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +87 -0
- codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/dart_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/go_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/java_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/perl_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/python_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/rust_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/swift_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +5 -0
- codegraphcontext/tools/scip_indexer.py +467 -0
- codegraphcontext/tools/scip_pb2.py +2455 -0
- codegraphcontext/tools/system.py +133 -0
- codegraphcontext/tools/tree_sitter_parser.py +103 -0
- codegraphcontext/utils/debug_log.py +90 -0
- codegraphcontext/utils/path_ignore.py +55 -0
- codegraphcontext/utils/repo_path.py +27 -0
- codegraphcontext/utils/tree_sitter_manager.py +285 -0
- codegraphcontext/utils/visualize_graph.py +0 -0
- codegraphcontext/viz/server.py +282 -0
- codegraphcontext_rust-0.5.5.dist-info/METADATA +537 -0
- codegraphcontext_rust-0.5.5.dist-info/RECORD +101 -0
- codegraphcontext_rust-0.5.5.dist-info/WHEEL +4 -0
- codegraphcontext_rust-0.5.5.dist-info/entry_points.txt +3 -0
- codegraphcontext_rust-0.5.5.dist-info/licenses/LICENSE +21 -0
- codegraphcontext_rust-0.5.5.dist-info/sboms/cgc-python.cyclonedx.json +2333 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import urllib.parse
|
|
2
|
+
from typing import Optional, List, Dict, Any, Set
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from .cli_helpers import visualize_helper
|
|
5
|
+
|
|
6
|
+
def check_visual_flag(ctx, visual: bool, cypher_query: str = None):
|
|
7
|
+
"""
|
|
8
|
+
Helper to check the --visual flag and launch the visualizer.
|
|
9
|
+
This is called from within analyze/find commands.
|
|
10
|
+
"""
|
|
11
|
+
if visual and cypher_query:
|
|
12
|
+
# We start the visualizer on port 8000
|
|
13
|
+
# Passing empty repo handles showing just the query results
|
|
14
|
+
port = 8000
|
|
15
|
+
encoded_query = urllib.parse.quote(cypher_query)
|
|
16
|
+
visualization_url = f"http://localhost:{port}/explore?cypher_query={encoded_query}"
|
|
17
|
+
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
console = Console(stderr=True)
|
|
20
|
+
console.print(f"[green]Starting visualizer...[/green]")
|
|
21
|
+
console.print(f"[cyan]Visualizing results at:[/cyan] {visualization_url}")
|
|
22
|
+
|
|
23
|
+
# Start the backend server and open the browser
|
|
24
|
+
visualize_helper(repo_path=None, port=port)
|
|
25
|
+
return True
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
def visualize_call_graph(cypher_query: str):
|
|
29
|
+
"""Visualize a call graph result."""
|
|
30
|
+
visualize_helper(repo_path=None, port=8000)
|
|
31
|
+
|
|
32
|
+
def visualize_call_chain(cypher_query: str):
|
|
33
|
+
"""Visualize a call chain result."""
|
|
34
|
+
visualize_helper(repo_path=None, port=8000)
|
|
35
|
+
|
|
36
|
+
def visualize_dependencies(cypher_query: str):
|
|
37
|
+
"""Visualize code dependencies."""
|
|
38
|
+
visualize_helper(repo_path=None, port=8000)
|
|
39
|
+
|
|
40
|
+
def visualize_inheritance_tree(cypher_query: str):
|
|
41
|
+
"""Visualize class inheritance tree."""
|
|
42
|
+
visualize_helper(repo_path=None, port=8000)
|
|
43
|
+
|
|
44
|
+
def visualize_overrides(cypher_query: str):
|
|
45
|
+
"""Visualize method overrides."""
|
|
46
|
+
visualize_helper(repo_path=None, port=8000)
|
|
47
|
+
|
|
48
|
+
def visualize_search_results(cypher_query: str):
|
|
49
|
+
"""Visualize search results."""
|
|
50
|
+
visualize_helper(repo_path=None, port=8000)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# src/codegraphcontext/core/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Core database management module.
|
|
4
|
+
|
|
5
|
+
Supports Neo4j, FalkorDB Lite, remote FalkorDB, and KùzuDB backends.
|
|
6
|
+
|
|
7
|
+
Explicit backend selection (see ``get_database_manager``):
|
|
8
|
+
- ``CGC_RUNTIME_DB_TYPE`` — per-invocation override (CLI ``--database`` / MCP resolved context).
|
|
9
|
+
- ``DEFAULT_DATABASE`` — configured default from ``cgc config db …`` / CodeGraphContext ``.env``.
|
|
10
|
+
|
|
11
|
+
When neither is set, implicit selection:
|
|
12
|
+
- Remote FalkorDB if ``FALKORDB_HOST`` is set (explicit remote signal).
|
|
13
|
+
- Else **Unix**: FalkorDB Lite when Python 3.12+ and ``falkordblite`` work; else KùzuDB if
|
|
14
|
+
installed; else Neo4j if credentials exist.
|
|
15
|
+
- Else **Windows**: KùzuDB if installed; else Neo4j if credentials exist.
|
|
16
|
+
"""
|
|
17
|
+
import os
|
|
18
|
+
import platform
|
|
19
|
+
from typing import Union, Optional
|
|
20
|
+
import importlib.util
|
|
21
|
+
|
|
22
|
+
def _is_kuzudb_available() -> bool:
|
|
23
|
+
"""Check if KùzuDB is installed."""
|
|
24
|
+
try:
|
|
25
|
+
return importlib.util.find_spec("kuzu") is not None
|
|
26
|
+
except ImportError:
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
def _is_falkordb_available() -> bool:
|
|
30
|
+
"""Check if FalkorDB Lite is installed (Unix only)."""
|
|
31
|
+
if platform.system() == "Windows":
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
import sys
|
|
35
|
+
if sys.version_info < (3, 12):
|
|
36
|
+
return False
|
|
37
|
+
try:
|
|
38
|
+
import redislite
|
|
39
|
+
return hasattr(redislite, 'falkordb_client')
|
|
40
|
+
except ImportError:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
def _is_falkordb_remote_configured() -> bool:
|
|
44
|
+
"""Check if a remote FalkorDB host is configured."""
|
|
45
|
+
return bool(os.getenv('FALKORDB_HOST'))
|
|
46
|
+
|
|
47
|
+
def _is_neo4j_configured() -> bool:
|
|
48
|
+
"""Check if Neo4j is configured with credentials."""
|
|
49
|
+
return all([
|
|
50
|
+
os.getenv('NEO4J_URI'),
|
|
51
|
+
os.getenv('NEO4J_USERNAME'),
|
|
52
|
+
os.getenv('NEO4J_PASSWORD')
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
def get_database_manager(db_path: Optional[str] = None) -> Union['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager']:
|
|
56
|
+
"""
|
|
57
|
+
Factory function to get the appropriate database manager based on configuration.
|
|
58
|
+
|
|
59
|
+
Selection logic:
|
|
60
|
+
1. Runtime override: ``CGC_RUNTIME_DB_TYPE`` (CLI ``--database``, MCP context).
|
|
61
|
+
2. Configured default: ``DEFAULT_DATABASE`` (``cgc config db …``, CodeGraphContext ``.env``).
|
|
62
|
+
3. Implicit: ``FALKORDB_HOST`` → remote FalkorDB; else Unix → FalkorDB Lite when available,
|
|
63
|
+
then KùzuDB; Windows → KùzuDB first; Neo4j if configured.
|
|
64
|
+
"""
|
|
65
|
+
from codegraphcontext.utils.debug_log import info_logger
|
|
66
|
+
|
|
67
|
+
db_type = os.getenv("CGC_RUNTIME_DB_TYPE") or os.getenv("DEFAULT_DATABASE")
|
|
68
|
+
|
|
69
|
+
if db_type:
|
|
70
|
+
db_type = db_type.lower()
|
|
71
|
+
if db_type == 'kuzudb':
|
|
72
|
+
if not _is_kuzudb_available():
|
|
73
|
+
raise ValueError("Database set to 'kuzudb' but Kùzu is not installed.\nRun 'pip install kuzu'")
|
|
74
|
+
from .database_kuzu import KuzuDBManager
|
|
75
|
+
info_logger(f"Using KùzuDB (explicit) at {db_path or 'default path'}")
|
|
76
|
+
return KuzuDBManager(db_path=db_path)
|
|
77
|
+
|
|
78
|
+
elif db_type == 'falkordb':
|
|
79
|
+
if not _is_falkordb_available():
|
|
80
|
+
info_logger("FalkorDB Lite is not supported or not installed. Falling back to KùzuDB.")
|
|
81
|
+
if _is_kuzudb_available():
|
|
82
|
+
from .database_kuzu import KuzuDBManager
|
|
83
|
+
return KuzuDBManager()
|
|
84
|
+
raise ValueError("Database set to 'falkordb' but FalkorDB Lite is not installed or not supported on this OS.\nRun 'pip install falkordblite'")
|
|
85
|
+
|
|
86
|
+
from .database_falkordb import FalkorDBManager, FalkorDBUnavailableError
|
|
87
|
+
try:
|
|
88
|
+
mgr = FalkorDBManager(db_path=db_path)
|
|
89
|
+
info_logger(f"Using FalkorDB Lite (explicit) at {db_path or 'default path'}")
|
|
90
|
+
return mgr
|
|
91
|
+
except FalkorDBUnavailableError as falkor_err:
|
|
92
|
+
info_logger(f"FalkorDB Lite not functional ({falkor_err}). Falling back to KùzuDB.")
|
|
93
|
+
if _is_kuzudb_available():
|
|
94
|
+
from .database_kuzu import KuzuDBManager
|
|
95
|
+
return KuzuDBManager(db_path=db_path)
|
|
96
|
+
raise
|
|
97
|
+
|
|
98
|
+
elif db_type == 'falkordb-remote':
|
|
99
|
+
if not _is_falkordb_remote_configured():
|
|
100
|
+
raise ValueError(
|
|
101
|
+
"Database set to 'falkordb-remote' but FALKORDB_HOST is not set.\n"
|
|
102
|
+
"Set the FALKORDB_HOST environment variable to your remote FalkorDB host."
|
|
103
|
+
)
|
|
104
|
+
from .database_falkordb_remote import FalkorDBRemoteManager
|
|
105
|
+
info_logger("Using remote FalkorDB (explicit)")
|
|
106
|
+
return FalkorDBRemoteManager()
|
|
107
|
+
|
|
108
|
+
elif db_type == 'neo4j':
|
|
109
|
+
if not _is_neo4j_configured():
|
|
110
|
+
raise ValueError("Database set to 'neo4j' but it is not configured.\nRun 'cgc neo4j setup' to configure Neo4j.")
|
|
111
|
+
from .database import DatabaseManager
|
|
112
|
+
info_logger("Using Neo4j Server (explicit)")
|
|
113
|
+
return DatabaseManager()
|
|
114
|
+
else:
|
|
115
|
+
raise ValueError(f"Unknown database type: '{db_type}'. Use 'kuzudb', 'falkordb', 'falkordb-remote', or 'neo4j'.")
|
|
116
|
+
|
|
117
|
+
# Implicit: remote FalkorDB when FALKORDB_HOST is set (explicit infra signal)
|
|
118
|
+
if _is_falkordb_remote_configured():
|
|
119
|
+
from .database_falkordb_remote import FalkorDBRemoteManager
|
|
120
|
+
info_logger("Using remote FalkorDB (auto-detected via FALKORDB_HOST)")
|
|
121
|
+
return FalkorDBRemoteManager()
|
|
122
|
+
|
|
123
|
+
# Implicit: FalkorDB Lite on Unix when available (typical embedded default there)
|
|
124
|
+
if _is_falkordb_available():
|
|
125
|
+
from .database_falkordb import FalkorDBManager, FalkorDBUnavailableError
|
|
126
|
+
try:
|
|
127
|
+
mgr = FalkorDBManager(db_path=db_path)
|
|
128
|
+
info_logger(f"Using FalkorDB Lite (default) at {db_path or 'default path'}")
|
|
129
|
+
return mgr
|
|
130
|
+
except FalkorDBUnavailableError as falkor_err:
|
|
131
|
+
info_logger(
|
|
132
|
+
f"FalkorDB Lite not functional in this environment ({falkor_err}). "
|
|
133
|
+
"Falling back to KùzuDB."
|
|
134
|
+
)
|
|
135
|
+
# fall through to KùzuDB below
|
|
136
|
+
|
|
137
|
+
# Implicit: KùzuDB (typical on Windows; Unix fallback when Falkor Lite unavailable)
|
|
138
|
+
if _is_kuzudb_available():
|
|
139
|
+
from .database_kuzu import KuzuDBManager
|
|
140
|
+
info_logger(f"Using KùzuDB (default) at {db_path or 'default path'}")
|
|
141
|
+
return KuzuDBManager(db_path=db_path)
|
|
142
|
+
|
|
143
|
+
# Implicit: Neo4j when configured
|
|
144
|
+
if _is_neo4j_configured():
|
|
145
|
+
from .database import DatabaseManager
|
|
146
|
+
info_logger("Using Neo4j Server (auto-detected)")
|
|
147
|
+
return DatabaseManager()
|
|
148
|
+
|
|
149
|
+
error_msg = "No database backend available.\n"
|
|
150
|
+
error_msg += "Recommended: Install KùzuDB for zero-config ('pip install kuzu')\n"
|
|
151
|
+
|
|
152
|
+
if platform.system() != "Windows":
|
|
153
|
+
error_msg += "Alternative: Install FalkorDB Lite ('pip install falkordblite')\n"
|
|
154
|
+
|
|
155
|
+
error_msg += "Alternative: Run 'cgc neo4j setup' to configure Neo4j."
|
|
156
|
+
|
|
157
|
+
raise ValueError(error_msg)
|
|
158
|
+
|
|
159
|
+
# For backward compatibility, export managers
|
|
160
|
+
from .database import DatabaseManager
|
|
161
|
+
from .database_falkordb import FalkorDBManager
|
|
162
|
+
from .database_falkordb_remote import FalkorDBRemoteManager
|
|
163
|
+
from .database_kuzu import KuzuDBManager
|
|
164
|
+
|
|
165
|
+
__all__ = ['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'get_database_manager']
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional, List, Dict, Any, Tuple
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _github_headers() -> dict:
|
|
10
|
+
"""Return GitHub API headers, including auth token if available."""
|
|
11
|
+
import os
|
|
12
|
+
headers = {"Accept": "application/vnd.github.v3+json"}
|
|
13
|
+
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
14
|
+
if token:
|
|
15
|
+
headers["Authorization"] = f"token {token}"
|
|
16
|
+
return headers
|
|
17
|
+
|
|
18
|
+
GITHUB_ORG = "CodeGraphContext"
|
|
19
|
+
GITHUB_REPO = "CodeGraphContext"
|
|
20
|
+
REGISTRY_API_URL = f"https://api.github.com/repos/{GITHUB_ORG}/{GITHUB_REPO}/releases"
|
|
21
|
+
MANIFEST_URL = f"https://github.com/{GITHUB_ORG}/{GITHUB_REPO}/releases/download/on-demand-bundles/manifest.json"
|
|
22
|
+
|
|
23
|
+
class BundleRegistry:
|
|
24
|
+
"""
|
|
25
|
+
Core logic for interacting with the CodeGraphContext bundle registry.
|
|
26
|
+
Handles fetching metadata, searching, and downloading bundles without CLI dependencies.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def fetch_available_bundles() -> List[Dict[str, Any]]:
|
|
31
|
+
"""
|
|
32
|
+
Fetch all available bundles from GitHub Releases and the on-demand manifest.
|
|
33
|
+
Returns a list of bundle dictionaries with metadata.
|
|
34
|
+
Preserves all versions - no deduplication.
|
|
35
|
+
"""
|
|
36
|
+
all_bundles = []
|
|
37
|
+
|
|
38
|
+
# 1. Fetch on-demand bundles from manifest
|
|
39
|
+
try:
|
|
40
|
+
response = requests.get(MANIFEST_URL, headers=_github_headers(), timeout=10)
|
|
41
|
+
if response.status_code == 200:
|
|
42
|
+
manifest = response.json()
|
|
43
|
+
if manifest.get('bundles'):
|
|
44
|
+
for bundle in manifest['bundles']:
|
|
45
|
+
bundle['source'] = 'on-demand'
|
|
46
|
+
# Ensure bundle has a full_name field (with version info)
|
|
47
|
+
if 'bundle_name' in bundle:
|
|
48
|
+
# Extract full name without .cgc extension
|
|
49
|
+
bundle['full_name'] = bundle['bundle_name'].replace('.cgc', '')
|
|
50
|
+
all_bundles.append(bundle)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.warning(f"Could not fetch on-demand bundles from manifest: {e}")
|
|
53
|
+
|
|
54
|
+
# 2. Fetch weekly pre-indexed bundles
|
|
55
|
+
try:
|
|
56
|
+
response = requests.get(REGISTRY_API_URL, headers=_github_headers(), timeout=10)
|
|
57
|
+
if response.status_code == 200:
|
|
58
|
+
releases = response.json()
|
|
59
|
+
|
|
60
|
+
# Find weekly releases (bundles-YYYYMMDD pattern)
|
|
61
|
+
weekly_releases = [r for r in releases if r['tag_name'].startswith('bundles-') and r['tag_name'] != 'bundles-latest']
|
|
62
|
+
|
|
63
|
+
if weekly_releases:
|
|
64
|
+
# Get the most recent weekly release
|
|
65
|
+
latest_weekly = weekly_releases[0]
|
|
66
|
+
|
|
67
|
+
for asset in latest_weekly.get('assets', []):
|
|
68
|
+
if asset['name'].endswith('.cgc'):
|
|
69
|
+
# Full bundle name without extension
|
|
70
|
+
full_name = asset['name'].replace('.cgc', '')
|
|
71
|
+
|
|
72
|
+
# Parse bundle name
|
|
73
|
+
name_parts = full_name.split('-')
|
|
74
|
+
bundle = {
|
|
75
|
+
'name': name_parts[0], # Base package name
|
|
76
|
+
'full_name': full_name, # Complete name with version
|
|
77
|
+
'repo': f"{name_parts[0]}/{name_parts[0]}", # Simplified
|
|
78
|
+
'bundle_name': asset['name'],
|
|
79
|
+
'version': name_parts[1] if len(name_parts) > 1 else 'latest',
|
|
80
|
+
'commit': name_parts[2] if len(name_parts) > 2 else 'unknown',
|
|
81
|
+
'size_bytes': asset.get('size', 0),
|
|
82
|
+
'size': f"{asset['size'] / 1024 / 1024:.1f}MB",
|
|
83
|
+
'download_url': asset['browser_download_url'],
|
|
84
|
+
'generated_at': asset['updated_at'],
|
|
85
|
+
'source': 'weekly'
|
|
86
|
+
}
|
|
87
|
+
all_bundles.append(bundle)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.warning(f"Could not fetch weekly bundles from GitHub API: {e}")
|
|
90
|
+
|
|
91
|
+
# Normalize all bundles to have required fields
|
|
92
|
+
for bundle in all_bundles:
|
|
93
|
+
# Ensure 'name' field exists (base package name)
|
|
94
|
+
if 'name' not in bundle:
|
|
95
|
+
repo = bundle.get('repo', '')
|
|
96
|
+
if '/' in repo:
|
|
97
|
+
bundle['name'] = repo.split('/')[-1]
|
|
98
|
+
else:
|
|
99
|
+
# Extract from full_name or bundle_name
|
|
100
|
+
full_name = bundle.get('full_name', bundle.get('bundle_name', 'unknown'))
|
|
101
|
+
bundle['name'] = full_name.split('-')[0]
|
|
102
|
+
|
|
103
|
+
# Ensure 'full_name' exists
|
|
104
|
+
if 'full_name' not in bundle:
|
|
105
|
+
bundle['full_name'] = bundle.get('bundle_name', bundle.get('name', 'unknown')).replace('.cgc', '')
|
|
106
|
+
|
|
107
|
+
return all_bundles
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def find_bundle_download_info(name: str) -> Tuple[Optional[str], Optional[Dict[str, Any]], str]:
|
|
111
|
+
"""
|
|
112
|
+
Find a download URL and metadata for a bundle by name.
|
|
113
|
+
|
|
114
|
+
Strategies:
|
|
115
|
+
1. Exact match on full_name (e.g., 'flask-main-abc123')
|
|
116
|
+
2. Match on base name (e.g., 'flask') - returns most recent version
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
(download_url, bundle_metadata, error_message)
|
|
120
|
+
"""
|
|
121
|
+
bundles = BundleRegistry.fetch_available_bundles()
|
|
122
|
+
|
|
123
|
+
if not bundles:
|
|
124
|
+
return None, None, "Could not fetch bundle registry."
|
|
125
|
+
|
|
126
|
+
name_lower = name.lower()
|
|
127
|
+
|
|
128
|
+
# Strategy 1: Exact match on full_name
|
|
129
|
+
for b in bundles:
|
|
130
|
+
if b.get('full_name', '').lower() == name_lower:
|
|
131
|
+
url = b.get('download_url')
|
|
132
|
+
if url:
|
|
133
|
+
return url, b, ""
|
|
134
|
+
return None, b, f"No download URL found for bundle '{name}'"
|
|
135
|
+
|
|
136
|
+
# Strategy 2: Match base package name (most recent)
|
|
137
|
+
matching_bundles = []
|
|
138
|
+
for b in bundles:
|
|
139
|
+
if b.get('name', '').lower() == name_lower:
|
|
140
|
+
matching_bundles.append(b)
|
|
141
|
+
|
|
142
|
+
if matching_bundles:
|
|
143
|
+
# Sort by timestamp (newest first)
|
|
144
|
+
matching_bundles.sort(key=lambda x: x.get('generated_at', ''), reverse=True)
|
|
145
|
+
bundle = matching_bundles[0]
|
|
146
|
+
url = bundle.get('download_url')
|
|
147
|
+
if url:
|
|
148
|
+
return url, bundle, ""
|
|
149
|
+
return None, bundle, f"No download URL found for bundle '{name}'"
|
|
150
|
+
|
|
151
|
+
return None, None, f"Bundle '{name}' not found in registry."
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def download_file(url: str, output_path: Path, progress_callback=None) -> bool:
|
|
155
|
+
"""
|
|
156
|
+
Download a file from a URL to a local path.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
url: The URL to download from
|
|
160
|
+
output_path: Local path to save the file
|
|
161
|
+
progress_callback: Optional callable(chunk_size) to report progress
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if successful, raises exception otherwise
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
response = requests.get(url, stream=True, timeout=30)
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
|
|
170
|
+
with open(output_path, 'wb') as f:
|
|
171
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
172
|
+
if chunk:
|
|
173
|
+
f.write(chunk)
|
|
174
|
+
if progress_callback:
|
|
175
|
+
progress_callback(len(chunk))
|
|
176
|
+
return True
|
|
177
|
+
except Exception as e:
|
|
178
|
+
# Clean up partial file
|
|
179
|
+
if output_path.exists():
|
|
180
|
+
output_path.unlink()
|
|
181
|
+
raise e
|