machineconfig 3.93__py3-none-any.whl → 3.95__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.

Files changed (79) hide show
  1. machineconfig/jobs/{python_custom_installers → installer/custom}/gh.py +22 -7
  2. machineconfig/jobs/{python_custom_installers → installer/custom}/hx.py +13 -4
  3. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/alacritty.py +11 -5
  4. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/brave.py +17 -14
  5. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/bypass_paywall.py +7 -9
  6. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/code.py +13 -13
  7. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/cursor.py +7 -7
  8. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/espanso.py +21 -17
  9. machineconfig/jobs/installer/custom_dev/goes.py +63 -0
  10. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/lvim.py +10 -14
  11. machineconfig/jobs/installer/custom_dev/nerdfont.py +87 -0
  12. machineconfig/{setup_windows/wt_and_pwsh/install_nerd_fonts.py → jobs/installer/custom_dev/nerfont_windows_helper.py} +68 -25
  13. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/redis.py +13 -8
  14. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/wezterm.py +13 -7
  15. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/winget.py +1 -3
  16. machineconfig/jobs/installer/packages_custom_dev.json +226 -0
  17. machineconfig/jobs/installer/packages_custom_essential.json +39 -0
  18. machineconfig/jobs/installer/packages_github_dev.json +1110 -0
  19. machineconfig/jobs/installer/packages_github_essential.json +804 -0
  20. machineconfig/jobs/linux/msc/cli_agents.sh +5 -0
  21. machineconfig/profile/create.py +5 -1
  22. machineconfig/scripts/linux/devops +1 -1
  23. machineconfig/scripts/python/ai/solutions/gemini/settings.json +1 -1
  24. machineconfig/scripts/python/devops.py +126 -65
  25. machineconfig/scripts/python/devops_devapps_install.py +30 -19
  26. machineconfig/setup_linux/web_shortcuts/interactive.py +267 -0
  27. machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -152
  28. machineconfig/utils/installer.py +17 -80
  29. machineconfig/utils/installer_utils/github_release_bulk.py +198 -0
  30. machineconfig/utils/installer_utils/installer_class.py +223 -210
  31. machineconfig/utils/schemas/installer/installer_types.py +29 -6
  32. {machineconfig-3.93.dist-info → machineconfig-3.95.dist-info}/METADATA +2 -1
  33. {machineconfig-3.93.dist-info → machineconfig-3.95.dist-info}/RECORD +50 -71
  34. machineconfig/jobs/python_custom_installers/archive/ngrok.py +0 -63
  35. machineconfig/jobs/python_custom_installers/dev/aider.py +0 -37
  36. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +0 -78
  37. machineconfig/jobs/python_custom_installers/dev/goes.py +0 -55
  38. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +0 -68
  39. machineconfig/jobs/python_custom_installers/dev/reverse_proxy.md +0 -31
  40. machineconfig/jobs/python_custom_installers/docker.py +0 -74
  41. machineconfig/jobs/python_custom_installers/warp-cli.py +0 -71
  42. machineconfig/jobs/python_generic_installers/config.json +0 -603
  43. machineconfig/jobs/python_generic_installers/config.json.bak +0 -414
  44. machineconfig/jobs/python_generic_installers/dev/config.archive.json +0 -18
  45. machineconfig/jobs/python_generic_installers/dev/config.json +0 -825
  46. machineconfig/jobs/python_generic_installers/dev/config.json.bak +0 -565
  47. machineconfig/jobs/python_linux_installers/__init__.py +0 -0
  48. machineconfig/jobs/python_linux_installers/archive/config.json +0 -18
  49. machineconfig/jobs/python_linux_installers/archive/config.json.bak +0 -10
  50. machineconfig/jobs/python_linux_installers/config.json +0 -145
  51. machineconfig/jobs/python_linux_installers/config.json.bak +0 -110
  52. machineconfig/jobs/python_linux_installers/dev/__init__.py +0 -0
  53. machineconfig/jobs/python_linux_installers/dev/config.json +0 -276
  54. machineconfig/jobs/python_linux_installers/dev/config.json.bak +0 -206
  55. machineconfig/jobs/python_windows_installers/__init__.py +0 -0
  56. machineconfig/jobs/python_windows_installers/archive/__init__.py +0 -0
  57. machineconfig/jobs/python_windows_installers/archive/file.json +0 -11
  58. machineconfig/jobs/python_windows_installers/config.json +0 -82
  59. machineconfig/jobs/python_windows_installers/config.json.bak +0 -56
  60. machineconfig/jobs/python_windows_installers/dev/__init__.py +0 -0
  61. machineconfig/jobs/python_windows_installers/dev/config.json +0 -4
  62. machineconfig/jobs/python_windows_installers/dev/config.json.bak +0 -3
  63. /machineconfig/jobs/{python_custom_installers → installer}/__init__.py +0 -0
  64. /machineconfig/jobs/{python_generic_installers → installer/custom_dev}/__init__.py +0 -0
  65. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/brave.sh +0 -0
  66. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/docker.sh +0 -0
  67. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/docker_start.sh +0 -0
  68. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/edge.sh +0 -0
  69. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/nerdfont.sh +0 -0
  70. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/pgsql.sh +0 -0
  71. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/redis.sh +0 -0
  72. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/timescaledb.sh +0 -0
  73. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/vscode.sh +0 -0
  74. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/warp-cli.sh +0 -0
  75. /machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/wezterm.sh +0 -0
  76. /machineconfig/{setup_windows/wt_and_pwsh → jobs/installer/powershell_scripts}/install_fonts.ps1 +0 -0
  77. {machineconfig-3.93.dist-info → machineconfig-3.95.dist-info}/WHEEL +0 -0
  78. {machineconfig-3.93.dist-info → machineconfig-3.95.dist-info}/entry_points.txt +0 -0
  79. {machineconfig-3.93.dist-info → machineconfig-3.95.dist-info}/top_level.txt +0 -0
@@ -1,68 +1,15 @@
1
1
  #!/bin/bash
2
2
 
3
- echo """#=======================================================================
3
+ echo """
4
+ =======================================================================
4
5
  📦 MACHINE CONFIGURATION | Interactive Installation Script
5
- #=======================================================================
6
- """
6
+ ======================================================================="""
7
7
 
8
- read -p "📥 Install Apps [y]/n? " choice
8
+ curl https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_linux/ve.sh | bash
9
+ $HOME/.local/bin/uv run --python 3.13 --with machineconfig ia
9
10
 
10
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
11
- echo """ #=======================================================================
12
- 📦 APPLICATIONS | Installing base system applications
13
- #=======================================================================
14
- """
15
- curl https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_linux/apps.sh | bash
16
- else
17
- echo """ ⏭️ Skipping applications installation
18
- """
19
- fi
20
11
 
21
- echo """#=======================================================================
22
- 🔄 SYSTEM UPDATE | Package management
23
- #=======================================================================
24
- """
25
- read -p "🔄 Upgrade system packages [y]/n? " choice
26
12
 
27
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
28
- echo """ 📦 Upgrading system packages...
29
- """
30
- sudo nala upgrade -y
31
- else
32
- echo """ ⏭️ Skipping system upgrade
33
- """
34
- fi
35
-
36
- echo """#=======================================================================
37
- 🐍 PYTHON ENVIRONMENT | Virtual environment setup
38
- #=======================================================================
39
- """
40
- read -p "🐍 Install UV and repos [y]/n? " choice
41
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
42
- echo """ 🔧 Setting up Python environment...
43
- """
44
- curl https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_linux/ve.sh | bash
45
- curl https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_linux/repos.sh | bash
46
- else
47
- echo """ ⏭️ Skipping virtual environment setup
48
- """
49
- fi
50
-
51
-
52
- echo """#=======================================================================
53
- 🔒 SSH SERVER | Remote access setup
54
- #=======================================================================
55
- """
56
- read -p "🔒 Install SSH Server [y]/n? " choice
57
- choice=${choice:-y}
58
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
59
- echo """ 🔧 Installing SSH server...
60
- """
61
- sudo nala install openssh-server -y
62
- else
63
- echo """ ⏭️ Skipping SSH server installation
64
- """
65
- fi
66
13
 
67
14
  echo """#=======================================================================
68
15
  📂 DOTFILES MIGRATION | Configuration transfer options
@@ -101,7 +48,7 @@ choice=${choice:-y}
101
48
  if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
102
49
  echo """ 🔧 Creating symlinks and setting permissions...
103
50
  """
104
- uv run --python 3.13 --with machineconfig python -m fire machineconfig.profile.create main --choice=all
51
+ uv run --python 3.13 --with machineconfig python -m fire machineconfig.profile.create main_symlinks --choice=all
105
52
  sudo chmod 600 $HOME/.ssh/*
106
53
  sudo chmod 700 $HOME/.ssh
107
54
  else
@@ -109,96 +56,3 @@ else
109
56
  """
110
57
  fi
111
58
 
112
- echo """#=======================================================================
113
- ⚡ CLI APPLICATIONS | Command-line tools installation
114
- #=======================================================================
115
- """
116
- read -p "⚡ Install CLI Apps [y]/n? " choice
117
- choice=${choice:-y}
118
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
119
- echo """ 🔧 Installing CLI applications...
120
- """
121
- uv run --python 3.13 --with machineconfig python -m fire machineconfig.scripts.python.devops_devapps_install main --which=essentials
122
- . $HOME/.bashrc
123
- else
124
- echo """ ⏭️ Skipping CLI apps installation
125
- """
126
- fi
127
-
128
- echo """#=======================================================================
129
- 🛠️ DEVELOPMENT TOOLS | Software development packages
130
- #=======================================================================
131
- """
132
- read -p "🛠️ Install Development Tools (rust, libssql-dev, ffmpeg, wezterm, brave, code) [y]/n? " choice
133
- choice=${choice:-y}
134
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
135
- echo """ 🔧 Installing development tools... """
136
- (curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh) || true
137
- sudo nala install libssl-dev -y
138
- sudo nala install ffmpeg -y
139
- uv run --python 3.13 --with machineconfig python -m fire machineconfig.scripts.python.devops_devapps_install main --which=wezterm,brave,code
140
- else
141
- echo """ ⏭️ Skipping development tools installation
142
- """
143
- fi
144
-
145
- echo """#=======================================================================
146
- 📚 REPOSITORIES | Project code retrieval
147
- #=======================================================================
148
- """
149
- read -p "📚 Retrieve Repositories to ~/code [y]/n? " choice
150
- choice=${choice:-y}
151
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
152
- echo """ 🔄 Cloning repositories...
153
- """
154
- repos ~/code --clone --cloud odg1
155
- else
156
- echo """ ⏭️ Skipping repository retrieval
157
- """
158
- fi
159
-
160
- echo """#=======================================================================
161
- 💾 DATA RETRIEVAL | Backup restoration
162
- #=======================================================================
163
- """
164
- read -p "💾 Retrieve Data [y]/n? " choice
165
- choice=${choice:-y}
166
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
167
- echo """ 🔄 Retrieving data...
168
- """
169
- uv run --python 3.13 --with machineconfig python -m fire machineconfig.scripts.python.devops_backup_retrieve main --direction=RETRIEVE
170
- else
171
- echo """ ⏭️ Skipping data retrieval
172
- """
173
- fi
174
-
175
- echo """#=======================================================================
176
- 🎨 ASCII ART | Terminal visualization tools
177
- #=======================================================================
178
- """
179
- read -p "🎨 Install ASCII Art Libraries [y]/n? " choice
180
- choice=${choice:-y}
181
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
182
- echo """ 🎨 Installing ASCII art libraries...
183
- """
184
- curl bit.ly/cfgasciiartlinux -L | sudo bash
185
- else
186
- echo """ ⏭️ Skipping ASCII art installation
187
- """
188
- fi
189
-
190
- # echo """# 📧 Thunderbird Setup Note:
191
- # Run after installing Thunderbird and starting it once:
192
- # cd ~/AppData/Roaming/ThunderBird/Profiles
193
- # \$res = ls
194
- # \$name = \$res[0].Name
195
- # mv \$backup_folder \$name
196
- # """
197
-
198
- echo """#=======================================================================
199
- ✨ INSTALLATION COMPLETE | System setup finished successfully
200
- #=======================================================================
201
-
202
- 🎉 Your system has been configured successfully!
203
- 🔄 You may need to reboot to apply all changes.
204
- """
@@ -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(system=platform.system(), dev=False)
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(system: str, dev: bool) -> list[Installer]:
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(system=system)
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
- all_installers.extend(installer_data_files["installers"])
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(system: str) -> dict[APP_INSTALLER_CATEGORY, InstallerDataFiles]:
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
- print(f"🔍 Importing OS-specific installers for {system}...")
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
- print(f"""📄 Loading OS-specific config from: {path_os_specific.joinpath("config.json")}""")
135
- os_specific_data = read_json(path=path_os_specific.joinpath("config.json"))
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()