Skip to content

Commit 743312d

Browse files
committed
Sprint 5: __all__ exports, --doctor CLI, source_url on TweakDef, type-safety fixes, security fixes, improved pre-commit
- Add __all__ to cli, gui, gui_dialogs, gui_theme, gui_tooltip, gui_widgets, menu, deps modules - Add --doctor CLI command with 7-point health check (Python, winreg, admin, config, tweaks, corpguard, log) - Add source_url field to TweakDef (optional KB/docs URL reference per tweak) - Fix _MissingSentinel to inherit from ModuleType (removes type:ignore in deps.py) - Add None-guards and type-safe attr declarations to TweakRow (gui_widgets.py) - Add path-traversal security guard to load_plugin() (marketplace.py) - Add auto-detect system dark/light theme on first run (gui.py) - Improve pre-commit config: trailing-whitespace, end-of-file, check-yaml/toml/json, large files, LF endings - Code style: reformatted long lines to ruff 150-char limit across registry.py, tweaks/__init__.py - Tests: TestDoctor (6 tests), path-traversal tests for marketplace, source_url tests for tweaks
1 parent e628ec2 commit 743312d

15 files changed

Lines changed: 415 additions & 195 deletions

.pre-commit-config.yaml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,30 @@ repos:
33
rev: v0.11.12
44
hooks:
55
- id: ruff
6-
args: [--fix]
6+
args: [--fix, --exit-non-zero-on-fix]
7+
types_or: [python, pyi]
78
- id: ruff-format
9+
types_or: [python, pyi]
810
- repo: https://github.com/pre-commit/mirrors-mypy
911
rev: v1.16.0
1012
hooks:
1113
- id: mypy
12-
args: [--strict]
14+
args: [--strict, --ignore-missing-imports]
1315
additional_dependencies: []
1416
files: ^regilattice/
17+
- repo: https://github.com/pre-commit/pre-commit-hooks
18+
rev: v5.0.0
19+
hooks:
20+
- id: trailing-whitespace
21+
- id: end-of-file-fixer
22+
- id: check-yaml
23+
- id: check-toml
24+
- id: check-json
25+
- id: check-merge-conflict
26+
- id: check-added-large-files
27+
args: [--maxkb=500]
28+
- id: mixed-line-ending
29+
args: [--fix=lf]
1530
- repo: local
1631
hooks:
1732
- id: pytest-smoke

regilattice/cli.py

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
tweaks_for_profile,
3838
)
3939

40+
__all__ = ["main"]
41+
4042

4143
def _confirm(prompt: str) -> bool:
4244
try:
@@ -311,6 +313,96 @@ def _split_root_for_reg(path: str) -> tuple[int, str]:
311313
raise ValueError(f"Unsupported registry path: {path}")
312314

313315

316+
def _run_doctor() -> int:
317+
"""Comprehensive system health check — prints a report and returns exit code."""
318+
import platform
319+
320+
checks: list[tuple[str, bool, str]] = [] # (label, passed, detail)
321+
322+
# 1. Python version
323+
vi = sys.version_info
324+
py_ok = (vi.major, vi.minor) >= (3, 9)
325+
checks.append(("Python >= 3.9", py_ok, f"{vi.major}.{vi.minor}.{vi.micro}"))
326+
327+
# 2. winreg availability
328+
win_ok = is_windows()
329+
checks.append(("Windows / winreg", win_ok, platform.system()))
330+
331+
# 3. Admin status
332+
from .elevation import is_admin
333+
334+
admin_ok = is_admin()
335+
checks.append(("Running as admin", admin_ok, "yes" if admin_ok else "no (some tweaks unavailable)"))
336+
337+
# 4. Config file validity
338+
config_detail = "OK"
339+
try:
340+
from .config import load_config
341+
342+
load_config(None)
343+
cfg_ok = True
344+
except Exception as exc:
345+
cfg_ok = False
346+
config_detail = str(exc)[:80]
347+
checks.append(("Config file", cfg_ok, config_detail))
348+
349+
# 5. Tweaks load cleanly
350+
tweak_detail = "OK"
351+
try:
352+
from .tweaks import all_tweaks
353+
354+
all_tweaks_list = all_tweaks()
355+
ids = [td.id for td in all_tweaks_list]
356+
dup_ids = {tid for tid in ids if ids.count(tid) > 1}
357+
tweaks_ok = len(dup_ids) == 0
358+
tweak_detail = f"{len(all_tweaks_list)} tweaks loaded" if tweaks_ok else f"Duplicate IDs: {', '.join(sorted(dup_ids))}"
359+
except Exception as exc:
360+
tweaks_ok = False
361+
tweak_detail = str(exc)[:80]
362+
checks.append(("Tweaks registry", tweaks_ok, tweak_detail))
363+
364+
# 6. Corporate guard
365+
corp_detail = "not detected"
366+
try:
367+
from .corpguard import corp_guard_status, is_corporate_network
368+
369+
if is_corporate_network():
370+
corp_detail = corp_guard_status() or "corporate network detected"
371+
corp_ok = True # detecting is not a failure; just informational
372+
except Exception as exc:
373+
corp_ok = False
374+
corp_detail = str(exc)[:80]
375+
checks.append(("Corp guard", corp_ok, corp_detail))
376+
377+
# 7. Session log writable
378+
try:
379+
SESSION.log_path.parent.mkdir(parents=True, exist_ok=True)
380+
log_ok = True
381+
log_detail = str(SESSION.log_path)
382+
except Exception as exc:
383+
log_ok = False
384+
log_detail = str(exc)[:80]
385+
checks.append(("Log path writable", log_ok, log_detail))
386+
387+
# ── Report ────────────────────────────────────────────────────────────────
388+
_W = 30
389+
print(f"\n {'RegiLattice Doctor':^{_W + 20}}")
390+
print(f" {platform_summary()}")
391+
print()
392+
all_ok = True
393+
for label, passed, detail in checks:
394+
icon = "\u2705" if passed else "\u274c"
395+
all_ok = all_ok and passed
396+
print(f" {icon} {label:<{_W}} {detail}")
397+
print()
398+
if all_ok:
399+
print(" All checks passed \u2014 RegiLattice is healthy.")
400+
else:
401+
print(" \u26a0\ufe0f Some checks failed. Review the items marked with \u274c above.")
402+
print()
403+
return 0 if all_ok else 1
404+
405+
314406
def _build_parser() -> argparse.ArgumentParser:
315407
parser = argparse.ArgumentParser(
316408
prog="regilattice",
@@ -499,6 +591,11 @@ def _build_parser() -> argparse.ArgumentParser:
499591
dest="needs_admin",
500592
help="Show only tweaks that require administrator rights (use with --list/--search).",
501593
)
594+
parser.add_argument(
595+
"--doctor",
596+
action="store_true",
597+
help="Run a comprehensive system health check: Python version, winreg, admin, config, tweaks.",
598+
)
502599
return parser
503600

504601

@@ -542,6 +639,9 @@ def _handle_shutdown(signum: int, frame: object) -> None:
542639
print(f" \u274c {pkg} \u2014 could not install")
543640
return 0
544641

642+
if args.doctor:
643+
return _run_doctor()
644+
545645
if args.hwinfo:
546646
from .hwinfo import detect_hardware, hardware_summary, suggest_profile
547647

@@ -716,12 +816,15 @@ def _handle_shutdown(signum: int, frame: object) -> None:
716816
if getattr(args, "output", "table") == "json":
717817
import json as _j
718818

719-
print(_j.dumps(
720-
[{"id": td.id, "label": td.label, "category": td.category,
721-
"needs_admin": td.needs_admin, "corp_safe": td.corp_safe}
722-
for td in tweaks],
723-
indent=2,
724-
))
819+
print(
820+
_j.dumps(
821+
[
822+
{"id": td.id, "label": td.label, "category": td.category, "needs_admin": td.needs_admin, "corp_safe": td.corp_safe}
823+
for td in tweaks
824+
],
825+
indent=2,
826+
)
827+
)
725828
else:
726829
print(f"{'ID':<30} {'Category':<14} {'Status':<14} Label")
727830
print("-" * 80)

regilattice/deps.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,18 @@ def install_package(pip_name: str) -> bool:
104104
# ── Lazy import ──────────────────────────────────────────────────────────────
105105

106106

107-
class _MissingSentinel:
108-
"""Placeholder for a package that couldn't be imported or installed."""
107+
class _MissingSentinel(ModuleType):
108+
"""Placeholder for a package that couldn't be imported or installed.
109+
110+
Inherits from :class:`types.ModuleType` so the return type of
111+
:func:`lazy_import` stays ``ModuleType`` without a ``type: ignore``.
112+
"""
109113

110114
def __init__(self, name: str) -> None:
115+
super().__init__(name)
111116
self._name = name
112117

113-
def __getattr__(self, item: str) -> None:
118+
def __getattr__(self, item: str) -> object:
114119
raise ImportError(f"Optional dependency '{self._name}' is not available. Install it manually: pip install {self._name}")
115120

116121

@@ -152,7 +157,7 @@ def lazy_import(
152157

153158
# Return a sentinel object so that downstream code can still reference
154159
# the name — it will raise a clear error when actually used.
155-
return _MissingSentinel(module_name) # type: ignore[return-value]
160+
return _MissingSentinel(module_name)
156161

157162

158163
def require(*packages: str) -> None:

regilattice/gui.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
)
5050
from .tweaks.maintenance import create_restore_point
5151

52+
__all__ = [
53+
"RegiLatticeGUI",
54+
"launch",
55+
]
56+
5257
# ── Theme aliases ────────────────────────────────────────────────────────────
5358

5459
_ACCENT = theme.ACCENT
@@ -818,6 +823,10 @@ def _finish_loading(self) -> None:
818823
self._restore_collapse_state()
819824
# Restore saved preferences (theme, profile, filters)
820825
self._restore_preferences()
826+
# If no saved preferences exist yet, apply auto-detected system dark/light theme
827+
if not _PREFS_FILE.exists():
828+
self._theme_var.set("Auto")
829+
self._switch_theme("Auto")
821830
# Kick off status detection
822831
self._initial_refresh()
823832

regilattice/gui_dialogs.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
from .registry import SESSION, platform_summary
1818
from .tweaks import TweakDef, all_tweaks, tweaks_by_category
1919

20+
__all__ = [
21+
"export_json_selection",
22+
"export_powershell",
23+
"import_json_selection",
24+
"open_psmodule_manager",
25+
"open_scoop_manager",
26+
"show_about",
27+
]
28+
2029
# ── Theme aliases ────────────────────────────────────────────────────────────
2130

2231
_ACCENT = theme.ACCENT
@@ -388,10 +397,7 @@ def _refresh_list() -> None:
388397
try:
389398
scope = scope_var.get()
390399
scope_flag = f"-Scope {scope}" if scope == "CurrentUser" else ""
391-
raw = _run_ps(
392-
f"Get-InstalledModule {scope_flag} | Sort-Object Name | "
393-
"ForEach-Object {{ '{0} v{1}' -f $_.Name, $_.Version }}"
394-
)
400+
raw = _run_ps(f"Get-InstalledModule {scope_flag} | Sort-Object Name | ForEach-Object {{{{ '{{0}} v{{1}}' -f $_.Name, $_.Version }}}}")
395401
lines = [ln for ln in raw.splitlines() if ln.strip()]
396402
for ln in lines:
397403
listbox.insert("end", ln)
@@ -505,9 +511,16 @@ def _update_action() -> None:
505511
pop_frame = tk.LabelFrame(dlg, text="Quick Install Popular Modules", bg=_BG, fg=_FG_DIM, font=_FONT_XS)
506512
pop_frame.pack(fill="x", padx=16, pady=(0, 8))
507513
popular_modules = [
508-
"PSReadLine", "posh-git", "PowerShellGet", "Az",
509-
"Microsoft.Graph", "Terminal-Icons", "oh-my-posh",
510-
"PSScriptAnalyzer", "Pester", "SqlServer",
514+
"PSReadLine",
515+
"posh-git",
516+
"PowerShellGet",
517+
"Az",
518+
"Microsoft.Graph",
519+
"Terminal-Icons",
520+
"oh-my-posh",
521+
"PSScriptAnalyzer",
522+
"Pester",
523+
"SqlServer",
511524
]
512525
for i, mod in enumerate(popular_modules):
513526
tk.Button(

regilattice/gui_theme.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,42 @@
1616
else:
1717
winreg = None # type: ignore[assignment]
1818

19+
__all__ = [
20+
"ACCENT",
21+
"BG",
22+
"BG_SURFACE",
23+
"CARD_BG",
24+
"CARD_BG_ALT",
25+
"CARD_HOVER",
26+
"DIM_BG",
27+
"ERR_RED",
28+
"FG",
29+
"FG_DIM",
30+
"FONT",
31+
"FONT_BOLD",
32+
"FONT_CAT",
33+
"FONT_SM",
34+
"FONT_TITLE",
35+
"FONT_XS",
36+
"FONT_XS_BOLD",
37+
"GPO_ORANGE",
38+
"HEADER_BG",
39+
"OK_GREEN",
40+
"PURPLE",
41+
"STATUS_APPLIED",
42+
"STATUS_CORP_BLOCKED",
43+
"STATUS_DEFAULT",
44+
"STATUS_NOT_APPLIED",
45+
"STATUS_UNKNOWN",
46+
"TEAL",
47+
"WARN_YELLOW",
48+
"ThemeDict",
49+
"available_themes",
50+
"current_theme",
51+
"detect_system_theme",
52+
"set_theme",
53+
]
54+
1955
# ── Theme data structure ─────────────────────────────────────────────────────
2056

2157

regilattice/gui_tooltip.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
from .corpguard import is_gpo_managed
1010
from .tweaks import TweakDef, TweakResult
1111

12+
__all__ = [
13+
"Tooltip",
14+
"build_tooltip_text",
15+
"has_recommendation",
16+
"parse_description_metadata",
17+
]
18+
1219
# ── Theme aliases ────────────────────────────────────────────────────────────
1320

1421
_CARD_HOVER = theme.CARD_HOVER

0 commit comments

Comments
 (0)