machineconfig 3.92__py3-none-any.whl → 3.94__py3-none-any.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.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/jobs/{python_custom_installers → installer/custom}/gh.py +22 -7
- machineconfig/jobs/{python_custom_installers → installer/custom}/hx.py +13 -4
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/alacritty.py +11 -5
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/brave.py +17 -14
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/bypass_paywall.py +7 -9
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/code.py +13 -13
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/cursor.py +7 -7
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/espanso.py +21 -17
- machineconfig/jobs/installer/custom_dev/goes.py +63 -0
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/lvim.py +10 -14
- machineconfig/jobs/installer/custom_dev/nerdfont.py +87 -0
- machineconfig/{setup_windows/wt_and_pwsh/install_nerd_fonts.py → jobs/installer/custom_dev/nerfont_windows_helper.py} +68 -25
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/redis.py +13 -8
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/wezterm.py +13 -7
- machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/winget.py +1 -3
- machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/docker.sh +1 -1
- machineconfig/jobs/installer/packages_custom_dev.json +226 -0
- machineconfig/jobs/installer/packages_custom_essential.json +39 -0
- machineconfig/jobs/installer/packages_github_dev.json +1110 -0
- machineconfig/jobs/installer/packages_github_essential.json +804 -0
- machineconfig/jobs/linux/msc/cli_agents.sh +5 -0
- machineconfig/scripts/python/ai/solutions/gemini/settings.json +1 -1
- machineconfig/scripts/python/devops_devapps_install.py +31 -20
- machineconfig/utils/installer.py +17 -80
- machineconfig/utils/installer_utils/github_release_bulk.py +198 -0
- machineconfig/utils/installer_utils/installer_class.py +223 -210
- machineconfig/utils/schemas/installer/installer_types.py +29 -6
- {machineconfig-3.92.dist-info → machineconfig-3.94.dist-info}/METADATA +1 -1
- {machineconfig-3.92.dist-info → machineconfig-3.94.dist-info}/RECORD +45 -67
- machineconfig/jobs/python_custom_installers/archive/ngrok.py +0 -63
- machineconfig/jobs/python_custom_installers/dev/aider.py +0 -37
- machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +0 -78
- machineconfig/jobs/python_custom_installers/dev/goes.py +0 -55
- machineconfig/jobs/python_custom_installers/dev/nerdfont.py +0 -68
- machineconfig/jobs/python_custom_installers/dev/reverse_proxy.md +0 -31
- machineconfig/jobs/python_custom_installers/docker.py +0 -74
- machineconfig/jobs/python_custom_installers/warp-cli.py +0 -71
- machineconfig/jobs/python_generic_installers/config.json +0 -603
- machineconfig/jobs/python_generic_installers/config.json.bak +0 -414
- machineconfig/jobs/python_generic_installers/dev/config.archive.json +0 -18
- machineconfig/jobs/python_generic_installers/dev/config.json +0 -825
- machineconfig/jobs/python_generic_installers/dev/config.json.bak +0 -565
- machineconfig/jobs/python_linux_installers/__init__.py +0 -0
- machineconfig/jobs/python_linux_installers/archive/config.json +0 -18
- machineconfig/jobs/python_linux_installers/archive/config.json.bak +0 -10
- machineconfig/jobs/python_linux_installers/config.json +0 -145
- machineconfig/jobs/python_linux_installers/config.json.bak +0 -110
- machineconfig/jobs/python_linux_installers/dev/__init__.py +0 -0
- machineconfig/jobs/python_linux_installers/dev/config.json +0 -276
- machineconfig/jobs/python_linux_installers/dev/config.json.bak +0 -206
- machineconfig/jobs/python_windows_installers/__init__.py +0 -0
- machineconfig/jobs/python_windows_installers/archive/__init__.py +0 -0
- machineconfig/jobs/python_windows_installers/archive/file.json +0 -11
- machineconfig/jobs/python_windows_installers/config.json +0 -82
- machineconfig/jobs/python_windows_installers/config.json.bak +0 -56
- machineconfig/jobs/python_windows_installers/dev/__init__.py +0 -0
- machineconfig/jobs/python_windows_installers/dev/config.json +0 -4
- machineconfig/jobs/python_windows_installers/dev/config.json.bak +0 -3
- /machineconfig/jobs/{python_custom_installers → installer}/__init__.py +0 -0
- /machineconfig/jobs/{python_generic_installers → installer/custom_dev}/__init__.py +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/brave.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/docker_start.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/edge.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/nerdfont.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/pgsql.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/redis.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/timescaledb.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/vscode.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/warp-cli.sh +0 -0
- /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/wezterm.sh +0 -0
- /machineconfig/{setup_windows/wt_and_pwsh → jobs/installer/powershell_scripts}/install_fonts.ps1 +0 -0
- {machineconfig-3.92.dist-info → machineconfig-3.94.dist-info}/WHEEL +0 -0
- {machineconfig-3.92.dist-info → machineconfig-3.94.dist-info}/entry_points.txt +0 -0
- {machineconfig-3.92.dist-info → machineconfig-3.94.dist-info}/top_level.txt +0 -0
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
# Terminal-based CLI agents and tools for productivity and coding.
|
|
5
|
+
npm install -g @github/copilot
|
|
5
6
|
npm install -g @google/gemini-cli
|
|
6
7
|
npm install -g @charmland/crush
|
|
7
8
|
npm install -g opencode-ai@latest # or curl -fsSL https://opencode.ai/install | bash
|
|
8
9
|
uv tool install --force --python python3.12 --with pip aider-chat@latest
|
|
10
|
+
curl -fsSL https://app.factory.ai/cli | sh
|
|
11
|
+
# WARP TERMINAL CLI
|
|
12
|
+
# droid
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
# cursor-cli
|
|
@@ -29,4 +33,5 @@ rm -rfd q
|
|
|
29
33
|
# Editors based on AI
|
|
30
34
|
# Kiro
|
|
31
35
|
# Cursor
|
|
36
|
+
# Warp
|
|
32
37
|
|
|
@@ -6,10 +6,11 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
|
6
6
|
from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
7
7
|
from machineconfig.utils.options import choose_from_options
|
|
8
8
|
from machineconfig.utils.installer import get_installers, install_all
|
|
9
|
+
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
9
10
|
from platform import system
|
|
10
11
|
from typing import Any, Optional, Literal, TypeAlias, get_args, Annotated
|
|
11
12
|
|
|
12
|
-
WHICH_CAT: TypeAlias = Literal["essentials", "
|
|
13
|
+
WHICH_CAT: TypeAlias = Literal["essentials", "essentialsDev", "systymPackages", "precheckedPackages"]
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def main_with_parser():
|
|
@@ -26,8 +27,7 @@ def main(which: Annotated[Optional[str], typer.Argument(help=f"Choose a category
|
|
|
26
27
|
if which is not None: # install by name
|
|
27
28
|
total_messages: list[str] = []
|
|
28
29
|
for a_which in which.split(",") if type(which) == str else which:
|
|
29
|
-
|
|
30
|
-
all_installers = get_installers(system=system(), dev=False) + get_installers(system=system(), dev=True)
|
|
30
|
+
all_installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["GITHUB_ESSENTIAL", "CUSTOM_ESSENTIAL", "GITHUB_DEV", "CUSTOM_DEV"])
|
|
31
31
|
|
|
32
32
|
# Find installer by exe_name or name
|
|
33
33
|
selected_installer = None
|
|
@@ -53,33 +53,43 @@ def main(which: Annotated[Optional[str], typer.Argument(help=f"Choose a category
|
|
|
53
53
|
print(a_message)
|
|
54
54
|
return None
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
installers = get_installers(system=system(), dev=True)
|
|
58
|
-
|
|
56
|
+
installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["GITHUB_ESSENTIAL", "CUSTOM_ESSENTIAL", "GITHUB_DEV", "CUSTOM_DEV"])
|
|
59
57
|
# Check installed programs with progress indicator
|
|
60
58
|
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
|
|
61
59
|
task = progress.add_task("✅ Checking installed programs...", total=len(installers))
|
|
62
|
-
|
|
60
|
+
installer_options = []
|
|
63
61
|
for x in installers:
|
|
64
|
-
|
|
62
|
+
installer_options.append(x.get_description())
|
|
65
63
|
progress.update(task, advance=1)
|
|
66
64
|
|
|
67
|
-
options
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
# Add category options at the beginning for better visibility
|
|
66
|
+
category_options = [f"📦 {cat}" for cat in get_args(WHICH_CAT)]
|
|
67
|
+
options = category_options + ["─" * 50] + installer_options
|
|
68
|
+
|
|
69
|
+
program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", default="📦 essentials", fzf=True)
|
|
70
70
|
|
|
71
71
|
total_commands = ""
|
|
72
72
|
installation_messages: list[str] = []
|
|
73
73
|
for _an_idx, a_program_name in enumerate(program_names):
|
|
74
|
+
# Skip separator lines
|
|
75
|
+
if a_program_name.startswith("─"):
|
|
76
|
+
continue
|
|
77
|
+
|
|
74
78
|
print(f"""
|
|
75
79
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
76
80
|
┃ 🔄 Processing: {a_program_name}
|
|
77
81
|
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
|
|
83
|
+
# Handle category options (remove emoji prefix)
|
|
84
|
+
if a_program_name.startswith("📦 "):
|
|
85
|
+
category_name = a_program_name[2:] # Remove "📦 " prefix
|
|
86
|
+
if category_name in get_args(WHICH_CAT):
|
|
87
|
+
shell_commands = get_programs_by_category(program_name=category_name) # type: ignore
|
|
88
|
+
total_commands += "\n" + shell_commands
|
|
81
89
|
else:
|
|
82
|
-
|
|
90
|
+
# Handle individual installer options
|
|
91
|
+
installer_idx = installer_options.index(a_program_name)
|
|
92
|
+
an_installer = installers[installer_idx]
|
|
83
93
|
status_message = an_installer.install_robust(version=None) # finish the task - this returns a status message, not a command
|
|
84
94
|
installation_messages.append(status_message)
|
|
85
95
|
|
|
@@ -103,13 +113,14 @@ def get_programs_by_category(program_name: WHICH_CAT):
|
|
|
103
113
|
┃ 📦 Installing Category: {program_name}
|
|
104
114
|
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
|
|
105
115
|
match program_name:
|
|
106
|
-
case "essentials"
|
|
107
|
-
installers_ = get_installers(
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
case "essentials":
|
|
117
|
+
installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["GITHUB_ESSENTIAL", "CUSTOM_ESSENTIAL"])
|
|
118
|
+
install_all(installers=installers_)
|
|
119
|
+
program = ""
|
|
120
|
+
case "essentialsDev":
|
|
121
|
+
installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["GITHUB_DEV", "CUSTOM_DEV", "GITHUB_ESSENTIAL", "CUSTOM_ESSENTIAL"])
|
|
110
122
|
install_all(installers=installers_)
|
|
111
123
|
program = ""
|
|
112
|
-
|
|
113
124
|
case "systymPackages":
|
|
114
125
|
if system() == "Windows":
|
|
115
126
|
options_system = parse_apps_installer_windows(LIBRARY_ROOT.joinpath("setup_windows/apps.ps1").read_text(encoding="utf-8"))
|
machineconfig/utils/installer.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from machineconfig.utils.installer_utils.installer_abc import LINUX_INSTALL_PATH
|
|
4
4
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
5
|
-
from machineconfig.utils.schemas.installer.installer_types import APP_INSTALLER_CATEGORY, InstallerData, InstallerDataFiles
|
|
5
|
+
from machineconfig.utils.schemas.installer.installer_types import APP_INSTALLER_CATEGORY, InstallerData, InstallerDataFiles, get_normalized_arch, get_os_name, OPERATING_SYSTEMS, CPU_ARCHITECTURES
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
from rich.panel import Panel # Added import
|
|
8
8
|
|
|
@@ -18,8 +18,7 @@ from joblib import Parallel, delayed
|
|
|
18
18
|
def check_latest():
|
|
19
19
|
console = Console() # Added console initialization
|
|
20
20
|
console.print(Panel("🔍 CHECKING FOR LATEST VERSIONS", title="Status", expand=False)) # Replaced print with Panel
|
|
21
|
-
installers = get_installers(
|
|
22
|
-
# installers += get_installers(system=platform.system(), dev=True)
|
|
21
|
+
installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["GITHUB_ESSENTIAL", "CUSTOM_ESSENTIAL"])
|
|
23
22
|
installers_github = []
|
|
24
23
|
for inst__ in installers:
|
|
25
24
|
app_name = inst__.installer_data.get("appName", "unknown")
|
|
@@ -92,92 +91,30 @@ def get_installed_cli_apps():
|
|
|
92
91
|
return apps
|
|
93
92
|
|
|
94
93
|
|
|
95
|
-
def get_installers(
|
|
94
|
+
def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: list[APP_INSTALLER_CATEGORY]) -> list[Installer]:
|
|
96
95
|
print(f"\n{'=' * 80}\n🔍 LOADING INSTALLER CONFIGURATIONS 🔍\n{'=' * 80}")
|
|
97
|
-
res_all = get_all_installer_data_files(
|
|
98
|
-
if not dev:
|
|
99
|
-
print("ℹ️ Excluding development installers...")
|
|
100
|
-
del res_all["CUSTOM_DEV"]
|
|
101
|
-
del res_all["OS_SPECIFIC_DEV"]
|
|
102
|
-
del res_all["OS_GENERIC_DEV"]
|
|
103
|
-
|
|
104
|
-
# Flatten the installer data from all categories
|
|
96
|
+
res_all = get_all_installer_data_files(which_cats=which_cats)
|
|
105
97
|
all_installers: list[InstallerData] = []
|
|
106
98
|
for _category, installer_data_files in res_all.items():
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
suitable_installers = []
|
|
100
|
+
for an_installer in installer_data_files["installers"]:
|
|
101
|
+
if an_installer["fileNamePattern"][arch][os] is None:
|
|
102
|
+
continue
|
|
103
|
+
suitable_installers.append(an_installer)
|
|
104
|
+
all_installers.extend(suitable_installers)
|
|
109
105
|
print(f"✅ Loaded {len(all_installers)} installer configurations\n{'=' * 80}")
|
|
110
106
|
return [Installer(installer_data=installer_data) for installer_data in all_installers]
|
|
111
107
|
|
|
112
108
|
|
|
113
|
-
def get_all_installer_data_files(
|
|
109
|
+
def get_all_installer_data_files(which_cats: list[APP_INSTALLER_CATEGORY]) -> dict[APP_INSTALLER_CATEGORY, InstallerDataFiles]:
|
|
114
110
|
print(f"\n{'=' * 80}\n📂 LOADING CONFIGURATION FILES 📂\n{'=' * 80}")
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if system == "Windows":
|
|
118
|
-
import machineconfig.jobs.python_windows_installers as os_specific_installer
|
|
119
|
-
else:
|
|
120
|
-
import machineconfig.jobs.python_linux_installers as os_specific_installer
|
|
121
|
-
|
|
122
|
-
print("🔍 Importing generic installers...")
|
|
123
|
-
import machineconfig.jobs.python_generic_installers as generic_installer
|
|
124
|
-
|
|
125
|
-
path_os_specific = PathExtended(os_specific_installer.__file__).parent
|
|
126
|
-
path_os_generic = PathExtended(generic_installer.__file__).parent
|
|
127
|
-
|
|
128
|
-
path_os_specific_dev = path_os_specific.joinpath("dev")
|
|
129
|
-
path_os_generic_dev = path_os_generic.joinpath("dev")
|
|
130
|
-
|
|
111
|
+
import machineconfig.jobs.installer as module
|
|
112
|
+
from pathlib import Path
|
|
131
113
|
print("📂 Loading configuration files...")
|
|
132
|
-
res_final: dict[APP_INSTALLER_CATEGORY, InstallerDataFiles] = {}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
res_final["OS_SPECIFIC"] = InstallerDataFiles(os_specific_data)
|
|
137
|
-
|
|
138
|
-
print(f"""📄 Loading OS-generic config from: {path_os_generic.joinpath("config.json")}""")
|
|
139
|
-
os_generic_data = read_json(path=path_os_generic.joinpath("config.json"))
|
|
140
|
-
res_final["OS_GENERIC"] = InstallerDataFiles(os_generic_data)
|
|
141
|
-
|
|
142
|
-
print(f"""📄 Loading OS-specific dev config from: {path_os_specific_dev.joinpath("config.json")}""")
|
|
143
|
-
os_specific_dev_data = read_json(path=path_os_specific_dev.joinpath("config.json"))
|
|
144
|
-
res_final["OS_SPECIFIC_DEV"] = InstallerDataFiles(os_specific_dev_data)
|
|
145
|
-
|
|
146
|
-
print(f"""📄 Loading OS-generic dev config from: {path_os_generic_dev.joinpath("config.json")}""")
|
|
147
|
-
os_generic_dev_data = read_json(path=path_os_generic_dev.joinpath("config.json"))
|
|
148
|
-
res_final["OS_GENERIC_DEV"] = InstallerDataFiles(os_generic_dev_data)
|
|
149
|
-
|
|
150
|
-
path_custom_installer = path_os_generic.with_name("python_custom_installers")
|
|
151
|
-
path_custom_installer_dev = path_custom_installer.joinpath("dev")
|
|
152
|
-
|
|
153
|
-
print(f"🔍 Loading custom installers from: {path_custom_installer}")
|
|
154
|
-
import runpy
|
|
155
|
-
|
|
156
|
-
res_custom_installers: list[InstallerData] = []
|
|
157
|
-
for item in path_custom_installer.search("*.py", r=False, not_in=["__init__"]):
|
|
158
|
-
try:
|
|
159
|
-
print(f"📄 Loading custom installer: {item.name}")
|
|
160
|
-
installer_data: InstallerData = runpy.run_path(str(item), run_name=None)["config_dict"]
|
|
161
|
-
res_custom_installers.append(installer_data)
|
|
162
|
-
except Exception as ex:
|
|
163
|
-
print(f"❌ Failed to load {item}: {ex}")
|
|
164
|
-
|
|
165
|
-
print(f"🔍 Loading custom dev installers from: {path_custom_installer_dev}")
|
|
166
|
-
res_custom_dev_installers: list[InstallerData] = []
|
|
167
|
-
for item in path_custom_installer_dev.search("*.py", r=False, not_in=["__init__"]):
|
|
168
|
-
try:
|
|
169
|
-
print(f"📄 Loading custom dev installer: {item.name}")
|
|
170
|
-
installer_data: InstallerData = runpy.run_path(str(item), run_name=None)["config_dict"]
|
|
171
|
-
res_custom_dev_installers.append(installer_data)
|
|
172
|
-
except Exception as ex:
|
|
173
|
-
print(f"❌ Failed to load {item}: {ex}")
|
|
174
|
-
|
|
175
|
-
res_final["CUSTOM"] = InstallerDataFiles({"version": "1", "installers": res_custom_installers})
|
|
176
|
-
res_final["CUSTOM_DEV"] = InstallerDataFiles({"version": "1", "installers": res_custom_dev_installers})
|
|
177
|
-
|
|
178
|
-
print(
|
|
179
|
-
f"✅ Configuration loading complete:\n - OS_SPECIFIC: {len(res_final['OS_SPECIFIC']['installers'])} items\n - OS_GENERIC: {len(res_final['OS_GENERIC']['installers'])} items\n - CUSTOM: {len(res_final['CUSTOM']['installers'])} items\n{'=' * 80}"
|
|
180
|
-
)
|
|
114
|
+
res_final: dict[APP_INSTALLER_CATEGORY, InstallerDataFiles] = {key: read_json(Path(module.__file__).parent.joinpath(f"packages_{key.lower()}.json")) for key in which_cats}
|
|
115
|
+
print(f"Loaded: {len(res_final)} installer categories")
|
|
116
|
+
for k, v in res_final.items():
|
|
117
|
+
print(f" - {k}: {len(v['installers'])} items")
|
|
181
118
|
return res_final
|
|
182
119
|
|
|
183
120
|
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Script to fetch GitHub release information from installer JSON files.
|
|
4
|
+
Extracts GitHub repository URLs and fetches latest release data with rate limiting.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Optional, Set
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_github_repo(url: str) -> bool:
|
|
16
|
+
"""Check if URL is a GitHub repository URL."""
|
|
17
|
+
try:
|
|
18
|
+
parsed = urlparse(url)
|
|
19
|
+
return parsed.netloc == "github.com" and len(parsed.path.split("/")) >= 3
|
|
20
|
+
except Exception:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def extract_github_repos_from_json(json_file_path: Path) -> Set[str]:
|
|
25
|
+
"""Extract GitHub repository URLs from installer JSON file."""
|
|
26
|
+
github_repos: Set[str] = set()
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
with open(json_file_path, 'r', encoding='utf-8') as file:
|
|
30
|
+
data = json.load(file)
|
|
31
|
+
|
|
32
|
+
for installer in data.get("installers", []):
|
|
33
|
+
repo_url = installer.get("repoURL", "")
|
|
34
|
+
if is_github_repo(repo_url):
|
|
35
|
+
github_repos.add(repo_url)
|
|
36
|
+
|
|
37
|
+
except (json.JSONDecodeError, FileNotFoundError) as e:
|
|
38
|
+
print(f"Error reading {json_file_path}: {e}")
|
|
39
|
+
|
|
40
|
+
return github_repos
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_repo_name_from_url(repo_url: str) -> str:
|
|
44
|
+
"""Extract owner/repo from GitHub URL."""
|
|
45
|
+
try:
|
|
46
|
+
parsed = urlparse(repo_url)
|
|
47
|
+
path_parts = parsed.path.strip("/").split("/")
|
|
48
|
+
return f"{path_parts[0]}/{path_parts[1]}"
|
|
49
|
+
except (IndexError, AttributeError):
|
|
50
|
+
return ""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def fetch_github_release_data(repo_name: str) -> Optional[Dict[str, Any]]:
|
|
54
|
+
"""Fetch latest release data from GitHub API using curl."""
|
|
55
|
+
try:
|
|
56
|
+
cmd = [
|
|
57
|
+
"curl", "-s",
|
|
58
|
+
f"https://api.github.com/repos/{repo_name}/releases/latest"
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
result = subprocess.run(
|
|
62
|
+
cmd,
|
|
63
|
+
capture_output=True,
|
|
64
|
+
text=True,
|
|
65
|
+
timeout=30
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if result.returncode != 0:
|
|
69
|
+
print(f"❌ Failed to fetch data for {repo_name}: {result.stderr}")
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
response_data = json.loads(result.stdout)
|
|
73
|
+
|
|
74
|
+
# Check if API returned an error
|
|
75
|
+
if "message" in response_data:
|
|
76
|
+
if "API rate limit exceeded" in response_data.get("message", ""):
|
|
77
|
+
print(f"🚫 Rate limit exceeded for {repo_name}")
|
|
78
|
+
return None
|
|
79
|
+
elif "Not Found" in response_data.get("message", ""):
|
|
80
|
+
print(f"🔍 No releases found for {repo_name}")
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
return response_data
|
|
84
|
+
|
|
85
|
+
except (subprocess.TimeoutExpired, json.JSONDecodeError, subprocess.SubprocessError) as e:
|
|
86
|
+
print(f"❌ Error fetching {repo_name}: {e}")
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def extract_release_info(release_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
91
|
+
"""Extract relevant information from GitHub release data."""
|
|
92
|
+
if not release_data:
|
|
93
|
+
return {}
|
|
94
|
+
|
|
95
|
+
asset_names = [asset["name"] for asset in release_data.get("assets", [])]
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"tag_name": release_data.get("tag_name", ""),
|
|
99
|
+
"name": release_data.get("name", ""),
|
|
100
|
+
"published_at": release_data.get("published_at", ""),
|
|
101
|
+
"assets": asset_names,
|
|
102
|
+
"assets_count": len(asset_names)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main() -> None:
|
|
107
|
+
"""Main function to process installer JSON files and fetch GitHub release data."""
|
|
108
|
+
# Define paths
|
|
109
|
+
current_dir = Path(__file__).parent
|
|
110
|
+
installer_dir = current_dir.parent.parent / "jobs" / "installer"
|
|
111
|
+
|
|
112
|
+
standard_json = installer_dir / "packages_standard.json"
|
|
113
|
+
dev_json = installer_dir / "packages_dev.json"
|
|
114
|
+
output_json = current_dir / "github_releases.json"
|
|
115
|
+
|
|
116
|
+
print("🔍 Starting GitHub release data extraction...")
|
|
117
|
+
print(f"📁 Processing files from: {installer_dir}")
|
|
118
|
+
|
|
119
|
+
# Extract GitHub repositories from both files
|
|
120
|
+
all_github_repos: Set[str] = set()
|
|
121
|
+
|
|
122
|
+
if standard_json.exists():
|
|
123
|
+
print(f"📄 Reading {standard_json.name}...")
|
|
124
|
+
repos = extract_github_repos_from_json(standard_json)
|
|
125
|
+
all_github_repos.update(repos)
|
|
126
|
+
print(f" Found {len(repos)} GitHub repos")
|
|
127
|
+
else:
|
|
128
|
+
print(f"⚠️ File not found: {standard_json}")
|
|
129
|
+
|
|
130
|
+
if dev_json.exists():
|
|
131
|
+
print(f"📄 Reading {dev_json.name}...")
|
|
132
|
+
repos = extract_github_repos_from_json(dev_json)
|
|
133
|
+
all_github_repos.update(repos)
|
|
134
|
+
print(f" Found {len(repos)} GitHub repos")
|
|
135
|
+
else:
|
|
136
|
+
print(f"⚠️ File not found: {dev_json}")
|
|
137
|
+
|
|
138
|
+
print(f"🎯 Total unique GitHub repositories found: {len(all_github_repos)}")
|
|
139
|
+
|
|
140
|
+
if not all_github_repos:
|
|
141
|
+
print("❌ No GitHub repositories found. Exiting.")
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
# Fetch release data with rate limiting
|
|
145
|
+
release_mapping: Dict[str, Any] = {}
|
|
146
|
+
total_repos = len(all_github_repos)
|
|
147
|
+
|
|
148
|
+
print(f"\n🚀 Fetching release data for {total_repos} repositories...")
|
|
149
|
+
print("⏰ Rate limiting: 5 seconds between requests")
|
|
150
|
+
print("-" * 60)
|
|
151
|
+
|
|
152
|
+
for i, repo_url in enumerate(sorted(all_github_repos), 1):
|
|
153
|
+
repo_name = get_repo_name_from_url(repo_url)
|
|
154
|
+
|
|
155
|
+
if not repo_name:
|
|
156
|
+
print(f"⚠️ [{i:3d}/{total_repos}] Invalid repo URL: {repo_url}")
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
print(f"📡 [{i:3d}/{total_repos}] Fetching: {repo_name}", end=" ... ")
|
|
160
|
+
|
|
161
|
+
release_data = fetch_github_release_data(repo_name)
|
|
162
|
+
|
|
163
|
+
if release_data:
|
|
164
|
+
release_info = extract_release_info(release_data)
|
|
165
|
+
release_mapping[repo_url] = release_info
|
|
166
|
+
assets_count = release_info.get("assets_count", 0)
|
|
167
|
+
tag = release_info.get("tag_name", "unknown")
|
|
168
|
+
print(f"✅ {tag} ({assets_count} assets)")
|
|
169
|
+
else:
|
|
170
|
+
release_mapping[repo_url] = {}
|
|
171
|
+
print("❌ No data")
|
|
172
|
+
|
|
173
|
+
# Rate limiting - wait 5 seconds between requests (except for the last one)
|
|
174
|
+
if i < total_repos:
|
|
175
|
+
time.sleep(5)
|
|
176
|
+
|
|
177
|
+
# Save results
|
|
178
|
+
output_data = {
|
|
179
|
+
"generated_at": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
|
|
180
|
+
"total_repositories": len(all_github_repos),
|
|
181
|
+
"successful_fetches": len([v for v in release_mapping.values() if v]),
|
|
182
|
+
"releases": release_mapping
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
with open(output_json, 'w', encoding='utf-8') as f:
|
|
186
|
+
json.dump(output_data, f, indent=2, ensure_ascii=False)
|
|
187
|
+
|
|
188
|
+
successful = len([v for v in release_mapping.values() if v])
|
|
189
|
+
print("\n📊 Summary:")
|
|
190
|
+
print(f" Total repositories processed: {len(all_github_repos)}")
|
|
191
|
+
print(f" Successful fetches: {successful}")
|
|
192
|
+
print(f" Failed fetches: {len(all_github_repos) - successful}")
|
|
193
|
+
print(f" Output saved to: {output_json}")
|
|
194
|
+
print("✅ Done!")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
main()
|