machineconfig 3.2__py3-none-any.whl → 3.3__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 (51) hide show
  1. machineconfig/cluster/sessions_managers/zellij_local.py +2 -2
  2. machineconfig/cluster/sessions_managers/zellij_utils/monitoring_types.py +17 -7
  3. machineconfig/jobs/python_custom_installers/archive/ngrok.py +7 -6
  4. machineconfig/jobs/python_custom_installers/dev/aider.py +9 -1
  5. machineconfig/jobs/python_custom_installers/dev/alacritty.py +2 -1
  6. machineconfig/jobs/python_custom_installers/dev/brave.py +10 -1
  7. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +9 -1
  8. machineconfig/jobs/python_custom_installers/dev/code.py +10 -3
  9. machineconfig/jobs/python_custom_installers/dev/cursor.py +2 -1
  10. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +7 -6
  11. machineconfig/jobs/python_custom_installers/dev/espanso.py +13 -5
  12. machineconfig/jobs/python_custom_installers/dev/goes.py +10 -1
  13. machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -1
  14. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +9 -1
  15. machineconfig/jobs/python_custom_installers/dev/redis.py +2 -2
  16. machineconfig/jobs/python_custom_installers/dev/wezterm.py +3 -1
  17. machineconfig/jobs/python_custom_installers/dev/winget.py +2 -1
  18. machineconfig/jobs/python_custom_installers/docker.py +9 -1
  19. machineconfig/jobs/python_custom_installers/gh.py +11 -2
  20. machineconfig/jobs/python_custom_installers/hx.py +9 -8
  21. machineconfig/jobs/python_custom_installers/warp-cli.py +9 -1
  22. machineconfig/jobs/python_generic_installers/config.json +612 -412
  23. machineconfig/jobs/python_generic_installers/config.json.bak +414 -0
  24. machineconfig/jobs/python_generic_installers/dev/config.json +822 -562
  25. machineconfig/jobs/python_generic_installers/dev/config.json.bak +565 -0
  26. machineconfig/jobs/python_linux_installers/archive/config.json +16 -8
  27. machineconfig/jobs/python_linux_installers/archive/config.json.bak +10 -0
  28. machineconfig/jobs/python_linux_installers/config.json +134 -99
  29. machineconfig/jobs/python_linux_installers/config.json.bak +110 -0
  30. machineconfig/jobs/python_linux_installers/dev/config.json +273 -203
  31. machineconfig/jobs/python_linux_installers/dev/config.json.bak +206 -0
  32. machineconfig/jobs/python_windows_installers/config.json +74 -48
  33. machineconfig/jobs/python_windows_installers/config.json.bak +56 -0
  34. machineconfig/jobs/python_windows_installers/dev/config.json +3 -2
  35. machineconfig/jobs/python_windows_installers/dev/config.json.bak +3 -0
  36. machineconfig/scripts/python/devops.py +1 -0
  37. machineconfig/scripts/python/devops_devapps_install.py +39 -17
  38. machineconfig/scripts/python/fire_agents.py +4 -0
  39. machineconfig/setup_windows/wt_and_pwsh/install_nerd_fonts.py +8 -7
  40. machineconfig/utils/installer.py +45 -31
  41. machineconfig/utils/installer_utils/installer_abc.py +1 -4
  42. machineconfig/utils/installer_utils/installer_class.py +108 -102
  43. machineconfig/utils/path_reduced.py +1 -1
  44. machineconfig/utils/procs.py +1 -0
  45. machineconfig/utils/schemas/installer/installer_types.py +20 -0
  46. machineconfig/utils/utils2.py +2 -0
  47. {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/METADATA +1 -1
  48. {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/RECORD +51 -43
  49. {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/WHEEL +0 -0
  50. {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/entry_points.txt +0 -0
  51. {machineconfig-3.2.dist-info → machineconfig-3.3.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,8 @@
1
1
  """package manager"""
2
2
 
3
- from machineconfig.utils.installer_utils.installer_abc import LINUX_INSTALL_PATH, CATEGORY
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
6
  from rich.console import Console
6
7
  from rich.panel import Panel # Added import
7
8
 
@@ -9,7 +10,6 @@ from machineconfig.utils.path_reduced import PathExtended as PathExtended
9
10
  from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT
10
11
  from machineconfig.utils.utils2 import read_json
11
12
 
12
- # from dataclasses import dataclass
13
13
  from typing import Any
14
14
  import platform
15
15
  from joblib import Parallel, delayed
@@ -22,21 +22,25 @@ def check_latest():
22
22
  # installers += get_installers(system=platform.system(), dev=True)
23
23
  installers_github = []
24
24
  for inst__ in installers:
25
- if "ntop" in inst__.name:
26
- print(f"⏭️ Skipping {inst__.name} (ntop)")
25
+ app_name = inst__.installer_data.get("appName", "unknown")
26
+ repo_url = inst__.installer_data.get("repoURL", "")
27
+ if "ntop" in app_name:
28
+ print(f"⏭️ Skipping {app_name} (ntop)")
27
29
  continue
28
- if "github" not in inst__.repo_url:
29
- print(f"⏭️ Skipping {inst__.name} (not a GitHub release)")
30
+ if "github" not in repo_url:
31
+ print(f"⏭️ Skipping {app_name} (not a GitHub release)")
30
32
  continue
31
33
  installers_github.append(inst__)
32
34
 
33
35
  print(f"\n🔍 Checking {len(installers_github)} GitHub-based installers...\n")
34
36
 
35
37
  def func(inst: Installer):
36
- print(f"🔎 Checking {inst.exe_name}...")
37
- _release_url, version_to_be_installed = inst.get_github_release(repo_url=inst.repo_url, version=None)
38
- verdict, current_ver, new_ver = inst.check_if_installed_already(exe_name=inst.exe_name, version=version_to_be_installed, use_cache=False)
39
- return inst.exe_name, verdict, current_ver, new_ver
38
+ exe_name = inst.installer_data.get("exeName", "unknown")
39
+ repo_url = inst.installer_data.get("repoURL", "")
40
+ print(f"🔎 Checking {exe_name}...")
41
+ _release_url, version_to_be_installed = inst.get_github_release(repo_url=repo_url, version=None)
42
+ verdict, current_ver, new_ver = inst.check_if_installed_already(exe_name=exe_name, version=version_to_be_installed, use_cache=False)
43
+ return exe_name, verdict, current_ver, new_ver
40
44
 
41
45
  print("\n⏳ Processing installers...\n")
42
46
  res = [func(inst) for inst in installers_github]
@@ -90,20 +94,23 @@ def get_installed_cli_apps():
90
94
 
91
95
  def get_installers(system: str, dev: bool) -> list[Installer]:
92
96
  print(f"\n{'=' * 80}\n🔍 LOADING INSTALLER CONFIGURATIONS 🔍\n{'=' * 80}")
93
- res_all = get_all_dicts(system=system)
97
+ res_all = get_all_installer_data_files(system=system)
94
98
  if not dev:
95
99
  print("ℹ️ Excluding development installers...")
96
100
  del res_all["CUSTOM_DEV"]
97
101
  del res_all["OS_SPECIFIC_DEV"]
98
102
  del res_all["OS_GENERIC_DEV"]
99
- res_final = {}
100
- for _k, v in res_all.items():
101
- res_final.update(v)
102
- print(f"✅ Loaded {len(res_final)} installer configurations\n{'=' * 80}")
103
- return [Installer.from_dict(d=vd, name=k) for k, vd in res_final.items()]
104
103
 
104
+ # Flatten the installer data from all categories
105
+ all_installers: list[InstallerData] = []
106
+ for _category, installer_data_files in res_all.items():
107
+ all_installers.extend(installer_data_files["installers"])
105
108
 
106
- def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
109
+ print(f"✅ Loaded {len(all_installers)} installer configurations\n{'=' * 80}")
110
+ return [Installer(installer_data=installer_data) for installer_data in all_installers]
111
+
112
+
113
+ def get_all_installer_data_files(system: str) -> dict[APP_INSTALLER_CATEGORY, InstallerDataFiles]:
107
114
  print(f"\n{'=' * 80}\n📂 LOADING CONFIGURATION FILES 📂\n{'=' * 80}")
108
115
 
109
116
  print(f"🔍 Importing OS-specific installers for {system}...")
@@ -122,18 +129,23 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
122
129
  path_os_generic_dev = path_os_generic.joinpath("dev")
123
130
 
124
131
  print("📂 Loading configuration files...")
125
- res_final: dict[CATEGORY, dict[str, dict[str, Any]]] = {}
132
+ res_final: dict[APP_INSTALLER_CATEGORY, InstallerDataFiles] = {}
133
+
126
134
  print(f"""📄 Loading OS-specific config from: {path_os_specific.joinpath("config.json")}""")
127
- res_final["OS_SPECIFIC"] = read_json(path=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)
128
137
 
129
138
  print(f"""📄 Loading OS-generic config from: {path_os_generic.joinpath("config.json")}""")
130
- res_final["OS_GENERIC"] = read_json(path=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)
131
141
 
132
142
  print(f"""📄 Loading OS-specific dev config from: {path_os_specific_dev.joinpath("config.json")}""")
133
- res_final["OS_SPECIFIC_DEV"] = read_json(path=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)
134
145
 
135
146
  print(f"""📄 Loading OS-generic dev config from: {path_os_generic_dev.joinpath("config.json")}""")
136
- res_final["OS_GENERIC_DEV"] = read_json(path=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)
137
149
 
138
150
  path_custom_installer = path_os_generic.with_name("python_custom_installers")
139
151
  path_custom_installer_dev = path_custom_installer.joinpath("dev")
@@ -141,29 +153,31 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
141
153
  print(f"🔍 Loading custom installers from: {path_custom_installer}")
142
154
  import runpy
143
155
 
144
- res_custom: dict[str, dict[str, Any]] = {}
156
+ res_custom_installers: list[InstallerData] = []
145
157
  for item in path_custom_installer.search("*.py", r=False, not_in=["__init__"]):
146
158
  try:
147
159
  print(f"📄 Loading custom installer: {item.name}")
148
- config_dict = runpy.run_path(str(item), run_name=None)["config_dict"]
149
- res_custom[item.stem] = config_dict
160
+ installer_data: InstallerData = runpy.run_path(str(item), run_name=None)["config_dict"]
161
+ res_custom_installers.append(installer_data)
150
162
  except Exception as ex:
151
163
  print(f"❌ Failed to load {item}: {ex}")
152
164
 
153
165
  print(f"🔍 Loading custom dev installers from: {path_custom_installer_dev}")
154
- res_custom_dev: dict[str, dict[str, Any]] = {}
166
+ res_custom_dev_installers: list[InstallerData] = []
155
167
  for item in path_custom_installer_dev.search("*.py", r=False, not_in=["__init__"]):
156
168
  try:
157
169
  print(f"📄 Loading custom dev installer: {item.name}")
158
- config_dict = runpy.run_path(str(item), run_name=None)["config_dict"]
159
- res_custom_dev[item.stem] = config_dict
170
+ installer_data: InstallerData = runpy.run_path(str(item), run_name=None)["config_dict"]
171
+ res_custom_dev_installers.append(installer_data)
160
172
  except Exception as ex:
161
173
  print(f"❌ Failed to load {item}: {ex}")
162
174
 
163
- res_final["CUSTOM"] = res_custom
164
- res_final["CUSTOM_DEV"] = res_custom_dev
175
+ res_final["CUSTOM"] = InstallerDataFiles({"version": "1", "installers": res_custom_installers})
176
+ res_final["CUSTOM_DEV"] = InstallerDataFiles({"version": "1", "installers": res_custom_dev_installers})
165
177
 
166
- print(f"✅ Configuration loading complete:\n - OS_SPECIFIC: {len(res_final['OS_SPECIFIC'])} items\n - OS_GENERIC: {len(res_final['OS_GENERIC'])} items\n - CUSTOM: {len(res_final['CUSTOM'])} items\n{'=' * 80}")
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
+ )
167
181
  return res_final
168
182
 
169
183
 
@@ -1,12 +1,9 @@
1
1
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
2
2
  from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
3
- from typing import Optional, TypeAlias, Literal
3
+ from typing import Optional
4
4
  import subprocess
5
5
 
6
6
 
7
- CATEGORY: TypeAlias = Literal["OS_SPECIFIC", "OS_GENERIC", "CUSTOM", "OS_SPECIFIC_DEV", "OS_GENERIC_DEV", "CUSTOM_DEV"]
8
-
9
-
10
7
  def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optional[str] = None, delete: bool = True, rename_to: Optional[str] = None):
11
8
  print(f"\n{'=' * 80}\n🔍 PROCESSING WINDOWS EXECUTABLE 🔍\n{'=' * 80}")
12
9
  if exe_name is not None and ".exe" in exe_name:
@@ -2,62 +2,37 @@ from machineconfig.utils.path_reduced import PathExtended as PathExtended
2
2
  from machineconfig.utils.installer_utils.installer_abc import find_move_delete_linux, find_move_delete_windows
3
3
  from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT, LIBRARY_ROOT
4
4
  from machineconfig.utils.options import check_tool_exists
5
- from machineconfig.utils.utils2 import pprint, read_json
5
+ from machineconfig.utils.utils2 import read_json
6
+ from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles
6
7
 
7
8
  import platform
8
9
  import subprocess
9
- from typing import Any, Optional
10
+ from typing import Optional
10
11
  from pathlib import Path
11
12
 
12
13
 
13
14
  class Installer:
14
- def __init__(
15
- self,
16
- repo_url: str,
17
- name: str,
18
- doc: str,
19
- strip_v: bool,
20
- exe_name: str,
21
- filename_template_windows_amd_64: str,
22
- filename_template_linux_amd_64: str,
23
- filename_template_windows_arm_64: Optional[str] = None,
24
- filename_template_linux_arm_64: Optional[str] = None,
25
- filename_template_macos_amd_64: Optional[str] = None,
26
- filename_template_macos_arm_64: Optional[str] = None,
27
- ):
28
- self.repo_url: str = repo_url
29
- self.name: str = name
30
- self.doc: str = doc
31
- self.filename_template_windows_amd_64: str = filename_template_windows_amd_64
32
- self.filename_template_windows_arm_64: Optional[str] = filename_template_windows_arm_64
33
- self.filename_template_linux_arm_64: Optional[str] = filename_template_linux_arm_64
34
- self.filename_template_linux_amd_64: str = filename_template_linux_amd_64
35
- self.filename_template_macos_amd_64: Optional[str] = filename_template_macos_amd_64
36
- self.filename_template_macos_arm_64: Optional[str] = filename_template_macos_arm_64
37
- self.strip_v: bool = strip_v
38
- self.exe_name: str = exe_name
15
+ def __init__(self, installer_data: InstallerData):
16
+ self.installer_data: InstallerData = installer_data
39
17
 
40
18
  def __repr__(self) -> str:
41
- return f"Installer of {self.exe_name} {self.name} @ {self.repo_url}"
19
+ exe_name = self.installer_data.get("exeName", "unknown")
20
+ app_name = self.installer_data.get("appName", "unknown")
21
+ repo_url = self.installer_data.get("repoURL", "unknown")
22
+ return f"Installer of {exe_name} {app_name} @ {repo_url}"
42
23
 
43
24
  def get_description(self):
44
25
  # old_version_cli = Terminal().run(f"{self.exe_name} --version").op.replace("\n", "")
45
26
  # old_version_cli = os.system(f"{self.exe_name} --version").replace("\n", "")
46
- old_version_cli: bool = check_tool_exists(tool_name=self.exe_name)
27
+ exe_name = self.installer_data.get("exeName", "")
28
+ if not exe_name:
29
+ return "Invalid installer: missing exeName"
30
+
31
+ old_version_cli: bool = check_tool_exists(tool_name=exe_name)
47
32
  old_version_cli_str = "✅" if old_version_cli else "❌"
48
33
  # name_version = f"{self.exe_name} {old_version_cli_str}"
49
- return f"{self.exe_name:<12} {old_version_cli_str} {self.doc}"
50
-
51
- def to_dict(self):
52
- return self.__dict__
53
-
54
- @staticmethod
55
- def from_dict(d: dict[str, Any], name: str):
56
- try:
57
- return Installer(name=name, **d)
58
- except Exception as ex:
59
- pprint(d, "Installer Creation Error")
60
- raise ex
34
+ doc = self.installer_data.get("doc", "No description")
35
+ return f"{exe_name:<12} {old_version_cli_str} {doc}"
61
36
 
62
37
  @staticmethod
63
38
  def choose_app_and_install():
@@ -69,49 +44,69 @@ class Installer:
69
44
  config_paths = [Path(p) for p in jobs_dir.rglob("config.json")]
70
45
  path = choose_one_option(options=config_paths)
71
46
  print(f"📄 Loading configuration from: {path}")
72
- config: dict[str, Any] = read_json(path) # /python_generic_installers/config.json"))
47
+ config_data = read_json(path)
48
+ installer_data_files = InstallerDataFiles(config_data)
49
+
50
+ # Extract app names from the installers
51
+ app_names = [installer["appName"] for installer in installer_data_files["installers"]]
73
52
  print("🔍 Select an application to install:")
74
- app_name = choose_one_option(options=list(config.keys()), fzf=True)
75
- # for keys, dict_ in config.items():
76
- installer = Installer.from_dict(d=config[app_name], name=app_name)
77
- print(f"📦 Selected application: {installer.exe_name}")
78
- version = input(f"📝 Enter version to install for {installer.exe_name} [latest]: ") or None
79
- print(f"\n{'=' * 80}\n🚀 INSTALLING {installer.exe_name.upper()} 🚀\n{'=' * 80}")
53
+ app_name = choose_one_option(options=app_names, fzf=True)
54
+
55
+ # Find the selected installer data
56
+ selected_installer_data = None
57
+ for installer_data in installer_data_files["installers"]:
58
+ if installer_data["appName"] == app_name:
59
+ selected_installer_data = installer_data
60
+ break
61
+
62
+ if selected_installer_data is None:
63
+ raise ValueError(f"Could not find installer data for {app_name}")
64
+
65
+ installer = Installer(installer_data=selected_installer_data)
66
+ print(f"📦 Selected application: {selected_installer_data.get('exeName', 'unknown')}")
67
+ version = input(f"📝 Enter version to install for {selected_installer_data.get('exeName', 'unknown')} [latest]: ") or None
68
+ print(f"\n{'=' * 80}\n🚀 INSTALLING {selected_installer_data.get('exeName', 'UNKNOWN').upper()} 🚀\n{'=' * 80}")
80
69
  installer.install(version=version)
81
70
 
82
71
  def install_robust(self, version: Optional[str]):
83
72
  try:
84
- print(f"\n{'=' * 80}\n🚀 INSTALLING {self.exe_name.upper()} 🚀\n{'=' * 80}")
85
- result_old = subprocess.run(f"{self.exe_name} --version", shell=True, capture_output=True, text=True)
73
+ exe_name = self.installer_data.get("exeName", "unknown")
74
+ print(f"\n{'=' * 80}\n🚀 INSTALLING {exe_name.upper()} 🚀\n{'=' * 80}")
75
+ result_old = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
86
76
  old_version_cli = result_old.stdout.strip()
87
77
  print(f"📊 Current version: {old_version_cli or 'Not installed'}")
88
78
 
89
79
  self.install(version=version)
90
80
 
91
- result_new = subprocess.run(f"{self.exe_name} --version", shell=True, capture_output=True, text=True)
81
+ result_new = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
92
82
  new_version_cli = result_new.stdout.strip()
93
83
  print(f"📊 New version: {new_version_cli}")
94
84
 
95
85
  if old_version_cli == new_version_cli:
96
86
  print(f"ℹ️ Same version detected: {old_version_cli}")
97
- return f"""📦️ 😑 {self.exe_name}, same version: {old_version_cli}"""
87
+ return f"""📦️ 😑 {exe_name}, same version: {old_version_cli}"""
98
88
  else:
99
89
  print(f"🚀 Update successful: {old_version_cli} ➡️ {new_version_cli}")
100
- return f"""📦️ 🤩 {self.exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
90
+ return f"""📦️ 🤩 {exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
101
91
 
102
92
  except Exception as ex:
103
- print(f"❌ ERROR: Installation failed for {self.exe_name}: {ex}")
104
- return f"""📦️ ❌ Failed to install `{self.name}` with error: {ex}"""
93
+ exe_name = self.installer_data.get("exeName", "unknown")
94
+ app_name = self.installer_data.get("appName", "unknown")
95
+ print(f"❌ ERROR: Installation failed for {exe_name}: {ex}")
96
+ return f"""📦️ ❌ Failed to install `{app_name}` with error: {ex}"""
105
97
 
106
98
  def install(self, version: Optional[str]):
107
- print(f"\n{'=' * 80}\n🔧 INSTALLATION PROCESS: {self.exe_name} 🔧\n{'=' * 80}")
108
- if self.repo_url == "CUSTOM":
109
- print(f"🧩 Using custom installer for {self.exe_name}")
99
+ exe_name = self.installer_data.get("exeName", "unknown")
100
+ repo_url = self.installer_data.get("repoURL", "")
101
+
102
+ print(f"\n{'=' * 80}\n🔧 INSTALLATION PROCESS: {exe_name} 🔧\n{'=' * 80}")
103
+ if repo_url == "CUSTOM":
104
+ print(f"🧩 Using custom installer for {exe_name}")
110
105
  import machineconfig.jobs.python_custom_installers as python_custom_installers
111
106
 
112
- installer_path = Path(python_custom_installers.__file__).parent.joinpath(self.exe_name + ".py")
107
+ installer_path = Path(python_custom_installers.__file__).parent.joinpath(exe_name + ".py")
113
108
  if not installer_path.exists():
114
- installer_path = Path(python_custom_installers.__file__).parent.joinpath("dev", self.exe_name + ".py")
109
+ installer_path = Path(python_custom_installers.__file__).parent.joinpath("dev", exe_name + ".py")
115
110
  print(f"🔍 Looking for installer in dev folder: {installer_path}")
116
111
  else:
117
112
  print(f"🔍 Found installer at: {installer_path}")
@@ -138,13 +133,13 @@ class Installer:
138
133
  version_to_be_installed = str(version)
139
134
  print(f"✅ Custom installation completed\n{'=' * 80}")
140
135
 
141
- elif "npm " in self.repo_url or "pip " in self.repo_url or "winget " in self.repo_url:
142
- package_manager = self.repo_url.split(" ", maxsplit=1)[0]
136
+ elif "npm " in repo_url or "pip " in repo_url or "winget " in repo_url:
137
+ package_manager = repo_url.split(" ", maxsplit=1)[0]
143
138
  print(f"📦 Using package manager: {package_manager}")
144
139
  desc = package_manager + " installation"
145
140
  version_to_be_installed = package_manager + "Latest"
146
- print(f"🚀 Running: {self.repo_url}")
147
- result = subprocess.run(self.repo_url, shell=True, capture_output=True, text=True)
141
+ print(f"🚀 Running: {repo_url}")
142
+ result = subprocess.run(repo_url, shell=True, capture_output=True, text=True)
148
143
  success = result.returncode == 0 and result.stderr == ""
149
144
  if not success:
150
145
  print(f"❌ {desc} failed")
@@ -177,38 +172,43 @@ class Installer:
177
172
  else:
178
173
  if platform.system() == "Windows":
179
174
  print("🪟 Installing on Windows...")
180
- exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=self.exe_name, delete=True, rename_to=self.exe_name.replace(".exe", "") + ".exe")
175
+ exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=exe_name, delete=True, rename_to=exe_name.replace(".exe", "") + ".exe")
181
176
  elif platform.system() in ["Linux", "Darwin"]:
182
177
  system_name = "Linux" if platform.system() == "Linux" else "macOS"
183
178
  print(f"🐧 Installing on {system_name}...")
184
- exe = find_move_delete_linux(downloaded=downloaded, tool_name=self.exe_name, delete=True, rename_to=self.exe_name)
179
+ exe = find_move_delete_linux(downloaded=downloaded, tool_name=exe_name, delete=True, rename_to=exe_name)
185
180
  else:
186
181
  error_msg = f"❌ ERROR: System {platform.system()} not supported"
187
182
  print(error_msg)
188
183
  raise NotImplementedError(error_msg)
189
184
 
190
185
  _ = exe
191
- if exe.name.replace(".exe", "") != self.exe_name.replace(".exe", ""):
186
+ if exe.name.replace(".exe", "") != exe_name.replace(".exe", ""):
192
187
  from rich import print as pprint
193
188
  from rich.panel import Panel
194
189
 
195
190
  print("⚠️ Warning: Executable name mismatch")
196
- pprint(Panel(f"Expected exe name: [red]{self.exe_name}[/red] \nAttained name: [red]{exe.name.replace('.exe', '')}[/red]", title="exe name mismatch", subtitle=self.repo_url))
197
- new_exe_name = self.exe_name + ".exe" if platform.system() == "Windows" else self.exe_name
191
+ pprint(Panel(f"Expected exe name: [red]{exe_name}[/red] \nAttained name: [red]{exe.name.replace('.exe', '')}[/red]", title="exe name mismatch", subtitle=repo_url))
192
+ new_exe_name = exe_name + ".exe" if platform.system() == "Windows" else exe_name
198
193
  print(f"🔄 Renaming to correct name: {new_exe_name}")
199
194
  exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
200
195
 
201
- print(f"💾 Saving version information to: {INSTALL_VERSION_ROOT.joinpath(self.exe_name)}")
202
- INSTALL_VERSION_ROOT.joinpath(self.exe_name).parent.mkdir(parents=True, exist_ok=True)
203
- INSTALL_VERSION_ROOT.joinpath(self.exe_name).write_text(version_to_be_installed, encoding="utf-8")
196
+ print(f"💾 Saving version information to: {INSTALL_VERSION_ROOT.joinpath(exe_name)}")
197
+ INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
198
+ INSTALL_VERSION_ROOT.joinpath(exe_name).write_text(version_to_be_installed, encoding="utf-8")
204
199
  print(f"✅ Installation completed successfully!\n{'=' * 80}")
205
200
 
206
201
  def download(self, version: Optional[str]):
207
- print(f"\n{'=' * 80}\n📥 DOWNLOADING: {self.exe_name} 📥\n{'=' * 80}")
202
+ exe_name = self.installer_data.get("exeName", "unknown")
203
+ repo_url = self.installer_data.get("repoURL", "")
204
+ app_name = self.installer_data.get("appName", "unknown")
205
+ strip_v = self.installer_data.get("stripVersion", False)
206
+
207
+ print(f"\n{'=' * 80}\n📥 DOWNLOADING: {exe_name} 📥\n{'=' * 80}")
208
208
  download_link: Optional[Path] = None
209
209
  version_to_be_installed: Optional[str] = None
210
- if "github" not in self.repo_url or ".zip" in self.repo_url or ".tar.gz" in self.repo_url:
211
- download_link = Path(self.repo_url)
210
+ if "github" not in repo_url or ".zip" in repo_url or ".tar.gz" in repo_url:
211
+ download_link = Path(repo_url)
212
212
  version_to_be_installed = "predefined_url"
213
213
  print(f"🔗 Using direct download URL: {download_link}")
214
214
  print(f"📦 Version to be installed: {version_to_be_installed}")
@@ -228,11 +228,11 @@ class Installer:
228
228
 
229
229
  else:
230
230
  print("🌐 Retrieving release information from GitHub...")
231
- release_url, version_to_be_installed = Installer.get_github_release(repo_url=self.repo_url, version=version)
231
+ release_url, version_to_be_installed = Installer.get_github_release(repo_url=repo_url, version=version)
232
232
  print(f"📦 Version to be installed: {version_to_be_installed}")
233
233
  print(f"📦 Release URL: {release_url}")
234
234
 
235
- version_to_be_installed_stripped = version_to_be_installed.replace("v", "") if self.strip_v else version_to_be_installed
235
+ version_to_be_installed_stripped = version_to_be_installed.replace("v", "") if strip_v else version_to_be_installed
236
236
  version_to_be_installed_stripped = version_to_be_installed_stripped.replace("ipinfo-", "")
237
237
 
238
238
  template, arch = self._select_template()
@@ -247,7 +247,7 @@ class Installer:
247
247
 
248
248
  assert download_link is not None, "download_link must be set"
249
249
  assert version_to_be_installed is not None, "version_to_be_installed must be set"
250
- print(f"📥 Downloading {self.name} from: {download_link}")
250
+ print(f"📥 Downloading {app_name} from: {download_link}")
251
251
  downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
252
252
  print(f"✅ Download and extraction completed to: {downloaded}\n{'=' * 80}")
253
253
  return downloaded, version_to_be_installed
@@ -268,47 +268,53 @@ class Installer:
268
268
  return sys_
269
269
 
270
270
  def _any_direct_http_template(self) -> bool:
271
- templates: list[Optional[str]] = [
272
- self.filename_template_windows_amd_64,
273
- self.filename_template_windows_arm_64,
274
- self.filename_template_linux_amd_64,
275
- self.filename_template_linux_arm_64,
276
- self.filename_template_macos_amd_64,
277
- self.filename_template_macos_arm_64,
278
- ]
279
- return any(t for t in templates if t is not None and t.startswith("http"))
271
+ filename_templates = self.installer_data.get("filenameTemplate", {})
272
+ templates: list[str] = []
273
+
274
+ for arch_templates in filename_templates.values():
275
+ templates.extend([t for t in arch_templates.values() if t])
276
+
277
+ return any(t for t in templates if t.startswith("http"))
280
278
 
281
279
  def _select_template(self) -> tuple[str, str]:
282
280
  sys_name = platform.system()
283
281
  arch = self._normalized_arch()
282
+
283
+ filename_templates = self.installer_data.get("filenameTemplate", {})
284
+
285
+ # Get templates for each architecture
286
+ amd64_templates = filename_templates.get("amd64", {})
287
+ arm64_templates = filename_templates.get("arm64", {})
288
+
284
289
  # mapping logic
285
290
  candidates: list[str] = []
286
291
  template: Optional[str] = None
292
+
287
293
  if sys_name == "Windows":
288
- if arch == "arm64" and self.filename_template_windows_arm_64:
289
- template = self.filename_template_windows_arm_64
294
+ if arch == "arm64" and arm64_templates.get("windows"):
295
+ template = arm64_templates["windows"]
290
296
  else:
291
- template = self.filename_template_windows_amd_64
292
- candidates = ["filename_template_windows_arm_64", "filename_template_windows_amd_64"]
297
+ template = amd64_templates.get("windows", "")
298
+ candidates = ["arm64.windows", "amd64.windows"]
293
299
  elif sys_name == "Linux":
294
- if arch == "arm64" and self.filename_template_linux_arm_64:
295
- template = self.filename_template_linux_arm_64
300
+ if arch == "arm64" and arm64_templates.get("linux"):
301
+ template = arm64_templates["linux"]
296
302
  else:
297
- template = self.filename_template_linux_amd_64
298
- candidates = ["filename_template_linux_arm_64", "filename_template_linux_amd_64"]
303
+ template = amd64_templates.get("linux", "")
304
+ candidates = ["arm64.linux", "amd64.linux"]
299
305
  elif sys_name == "Darwin":
300
- if arch == "arm64" and self.filename_template_macos_arm_64:
301
- template = self.filename_template_macos_arm_64
302
- elif arch == "amd64" and self.filename_template_macos_amd_64:
303
- template = self.filename_template_macos_amd_64
306
+ if arch == "arm64" and arm64_templates.get("macos"):
307
+ template = arm64_templates["macos"]
308
+ elif arch == "amd64" and amd64_templates.get("macos"):
309
+ template = amd64_templates["macos"]
304
310
  else:
305
311
  # fallback between available mac templates
306
- template = self.filename_template_macos_arm_64 or self.filename_template_macos_amd_64
307
- candidates = ["filename_template_macos_arm_64", "filename_template_macos_amd_64"]
312
+ template = arm64_templates.get("macos") or amd64_templates.get("macos") or ""
313
+ candidates = ["arm64.macos", "amd64.macos"]
308
314
  else:
309
315
  raise NotImplementedError(f"System {sys_name} not supported")
310
316
 
311
- if template is None:
317
+ if not template:
312
318
  raise ValueError(f"No filename template available for system={sys_name} arch={arch}. Checked {candidates}")
313
319
 
314
320
  return template, arch
@@ -395,7 +395,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
395
395
  from machineconfig.utils.terminal import Terminal
396
396
 
397
397
  if system() == "Windows" and not Terminal.is_user_admin(): # you cannot create symlink without priviliages.
398
- import win32com.shell.shell
398
+ import win32com.shell.shell # type: ignore # pylint: disable=E0401
399
399
 
400
400
  _proce_info = win32com.shell.shell.ShellExecuteEx(lpVerb="runas", lpFile=sys.executable, lpParameters=f" -c \"from pathlib import Path; Path(r'{self.expanduser()}').symlink_to(r'{str(target_obj)}')\"")
401
401
  # TODO update PATH for this to take effect immediately.
@@ -241,6 +241,7 @@ def get_age(create_time: Any) -> str:
241
241
 
242
242
  def main():
243
243
  from machineconfig.utils.procs import ProcessManager
244
+
244
245
  ProcessManager().choose_and_kill()
245
246
 
246
247
 
@@ -0,0 +1,20 @@
1
+ from typing import TypedDict, Literal, TypeAlias
2
+
3
+
4
+ APP_INSTALLER_CATEGORY: TypeAlias = Literal["OS_SPECIFIC", "OS_GENERIC", "CUSTOM", "OS_SPECIFIC_DEV", "OS_GENERIC_DEV", "CUSTOM_DEV"]
5
+ CPU_ARCHITECTURES: TypeAlias = Literal["amd64", "arm64"]
6
+ OPERATING_SYSTEMS: TypeAlias = Literal["windows", "linux", "macos"]
7
+
8
+
9
+ class InstallerData(TypedDict):
10
+ appName: str
11
+ repoURL: str
12
+ doc: str
13
+ filenameTemplate: dict[CPU_ARCHITECTURES, dict[OPERATING_SYSTEMS, str]]
14
+ stripVersion: bool
15
+ exeName: str
16
+
17
+
18
+ class InstallerDataFiles(TypedDict):
19
+ version: str
20
+ installers: list[InstallerData]
@@ -44,10 +44,12 @@ def read_ini(path: "Path", encoding: Optional[str] = None):
44
44
 
45
45
  def read_json(path: "Path", r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
46
46
  import json
47
+
47
48
  try:
48
49
  mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
49
50
  except Exception:
50
51
  import pyjson5
52
+
51
53
  mydict = pyjson5.loads(Path(path).read_text(encoding="utf-8"), **kwargs) # file has C-style comments.
52
54
  _ = r
53
55
  return mydict
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 3.2
3
+ Version: 3.3
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0