Repository navigation
Cli first strategy - #23
Conversation
|
Warning Rate limit exceeded@FaserF has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 11 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (1)
WalkthroughAdds a CLI-first dispatcher and CLI bootstrap, a modern Flet GUI and legacy Tk splash, a full CLI implementation, a Winget manifest service and separate winget package, PyInstaller spec additions, CI/release workflow updates, numerous GUI/service API signature changes, new tests and documentation. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Entrypoint as main()
participant CLI as switchcraft.cli.commands
participant GUI as switchcraft.gui.app / gui_modern
participant Addon as AddonService
participant Winget as switchcraft_winget.WingetHelper
participant Manifest as WingetManifestService
User->>Entrypoint: run (with args / without args)
Entrypoint->>Entrypoint: decide CLI vs GUI
alt CLI mode (args)
Entrypoint->>CLI: import & invoke CLI
CLI->>Addon: import_addon_module("winget")
Addon-->>CLI: module or error
CLI->>Winget: search/install/download
CLI->>Manifest: generate/validate manifest (optional)
Manifest-->>CLI: path / validation result
CLI-->>User: JSON or formatted output
else GUI mode (no args)
Entrypoint->>GUI: start legacy or modern UI
GUI->>Addon: lazy-load addons on demand
GUI->>Winget: trigger install/download or manifest generation
Winget-->>GUI: install/download result
Manifest-->>GUI: manifest generation/validation result
GUI-->>User: interactive dialogs / restart flow
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Areas needing focused review:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/switchcraft/services/notification_service.py (1)
45-61: Remove the unused_is_app_foregroundmethod (lines 45-61).This method is never called in the codebase. The only reference was commented out and the method serves no purpose. Removing it will reduce maintenance overhead.
src/switchcraft/gui/views/winget_view.py (1)
188-188: Remove unused variable.The
cmdvariable is assigned but never used after the refactoring towinget_helper.install_package(). This is flagged by the pipeline linter.🔎 Proposed fix
else: scope = "user" - cmd = f"winget install --id {info['Id']} --scope {scope} --accept-package-agreements --accept-source-agreements" - top = ctk.CTkToplevel(self)scripts/build_release.sh (1)
46-55: Misleading artifact naming for Unix builds.The spec file hardcodes
name='SwitchCraft-windows'regardless of platform, and the Unix build script correctly looks for this name. However, the "-windows" suffix is confusing for Linux/macOS builds, where the artifact will be a Unix binary without the.exeextension. The naming contradicts the documented purpose of the script ("SwitchCraft Release Builder (Unix)") and the script's own comment acknowledging that "Default is 'SwitchCraft' on Unix."Consider making the spec file platform-aware or renaming the artifact to something like 'SwitchCraft-legacy' to avoid confusion about what platform the binary targets.
src/entry.py (1)
10-12: Duplicate comment block - copy-paste error.Lines 11-12 are exact duplicates of lines 8-9. Remove the duplicate.
🔎 Suggested fix
# 1. We import 'switchcraft.gui.app' here to force PyInstaller to bundle it # (Static Analysis sees this import). # 2. We import it at runtime to populate sys.modules, bypassing any # shadowing 'switchcraft' folder that might exist in _MEIPASS. - # (Static Analysis sees this import). - # 2. We import it at runtime to populate sys.modules, bypassing any - # shadowing 'switchcraft' folder that might exist in _MEIPASS.
🟡 Minor comments (14)
docs/CI_Architecture.md-8-8 (1)
8-8: Fix the operating system name capitalization.Apple's operating system should be written as "macOS" not "MacOS".
🔎 Proposed fix
- - **Standard Build**: Windows (GUI+CLI), Linux, MacOS. + - **Standard Build**: Windows (GUI+CLI), Linux, macOS.switchcraft_modern.spec-49-49 (1)
49-49: Update the executable name from test placeholder.The executable name
SwitchCraft-new-Testappears to be a temporary/test name. Consider using a production-ready name likeSwitchCraft-modernorSwitchCraft-Modern-windowsto align with the other build variants.🔎 Proposed fix
- name='SwitchCraft-new-Test', # Modern (Flet) test build + name='SwitchCraft-Modern', # Modern (Flet) GUI buildswitchcraft_modern.spec-61-61 (1)
61-61: Convert the icon file from PNG to ICO format.PyInstaller on Windows requires .ico format for application icons. The .png file specified will not work as an executable icon on Windows. Convert
images/switchcraft_logo.pngto.icoformat before building.src/switchcraft/gui_modern/app.py-122-124 (1)
122-124: Address the unused variable flagged by the linter.The variable
appis assigned but never used, as flagged by the CI pipeline. Determine if:
- The reference must be retained to prevent garbage collection of the
ModernAppinstance, or- The variable can be removed since
ModernApp.__init__modifies the page in placeIf the reference is necessary, add a comment explaining why. Otherwise, remove the assignment or use
_ =to indicate intentional discard.🔎 Potential fixes
If the reference is needed:
# Initialize the app # ModernApp.__init__ calls page.clean() - app = ModernApp(page) + app = ModernApp(page) # Keep reference to prevent garbage collectionIf the reference is not needed:
# Initialize the app # ModernApp.__init__ calls page.clean() - app = ModernApp(page) + ModernApp(page)src/switchcraft/gui_modern/app.py-117-120 (1)
117-120: Replacetime.sleep()withasyncio.sleep()for async-compatible delays.In async Flet apps,
asyncio.sleep()should be used instead oftime.sleep(). In Flet 1.0+, blocking calls liketime.sleep()freeze the UI and should be replaced withawait asyncio.sleep().README.md-16-17 (1)
16-17: Fix duplicate heading.Line 17 contains a duplicate "## 📚 Documentation" heading. This appears to be a copy-paste error and will cause rendering issues.
🔎 Proposed fix
-## 📚 Documentation ## 📚 Documentationsrc/switchcraft/gui/splash.py-3-5 (1)
3-5: Remove unused imports.As flagged by the pipeline,
sys,os, andPathare imported but never used.🔎 Proposed fix
import tkinter as tk from tkinter import ttk -import sys -import os -from pathlib import PathCommittable suggestion skipped: line range outside the PR's diff.
src/switchcraft/gui/views/analyzer_view.py-969-979 (1)
969-979: Remove unusedmessageboximport and dead code.Pipeline failure indicates the
messageboximport on line 973 is unused. The entire conditional block (lines 971-975) appears to be a no-op placeholder.🔎 Proposed fix
def _open_manifest_dialog(self, info): - # Check Config - repo_path = SwitchCraftConfig.get_value("WingetRepoPath") - if not repo_path: - from tkinter import messagebox - # Optional: Ask user if they want to configure it, or just rely on default logic in Service - pass - from switchcraft.gui.views.manifest_dialog import ManifestDialog dlg = ManifestDialog(self.winfo_toplevel(), info) dlg.grab_set()src/switchcraft/gui/views/manifest_dialog.py-4-4 (1)
4-4: Remove unused importwebbrowser.Pipeline failure indicates this import is unused.
🔎 Proposed fix
-import webbrowserscripts/build_release.ps1-11-12 (1)
11-12: Documentation mismatch:-Guiparameter documented but code uses-Modernand-Legacy.The
.PARAMETER Guidocumentation doesn't match the actual parameter names in the param block (lines 37-44).🔎 Proposed fix for documentation
-.PARAMETER Gui - Builds the Standard GUI application (SwitchCraft.exe). Default if no other flags used. +.PARAMETER Modern + Builds the Modern GUI application using Flet (SwitchCraft-new-Test.exe). Default if no other flags used. + +.PARAMETER Legacy + Builds the Legacy GUI application using Tkinter (SwitchCraft-windows.exe).Also update the examples:
.EXAMPLE - .\build_release.ps1 -Gui -Cli + .\build_release.ps1 -Modern -Cli Builds both versions.src/switchcraft_winget/utils/winget.py-3-3 (1)
3-3: Remove unused importshutil.Pipeline failure indicates this import is unused.
🔎 Proposed fix
-import shutilsrc/switchcraft/gui/views/manifest_dialog.py-8-8 (1)
8-8: Remove unused importSwitchCraftConfig.Pipeline failure indicates this import is unused.
🔎 Proposed fix
-from switchcraft.utils.config import SwitchCraftConfigsrc/switchcraft/services/winget_manifest_service.py-1-7 (1)
1-7: Remove unused imports flagged by pipeline.Pipeline failures indicate
os,shutil, anddatetimeare imported but never used.🔎 Proposed fix
-import os import yaml import logging import subprocess -import shutil from pathlib import Path -from datetime import datetime from switchcraft.utils.config import SwitchCraftConfigsrc/switchcraft/cli/commands.py-178-185 (1)
178-185: Remove or use the unusedoutput_logvariable.The variable is assigned but never used, causing the pipeline failure (F841).
Proposed fix
try: - output_log = svc.create_intunewin( + svc.create_intunewin( source_folder=source, setup_file=setup_file, output_folder=output, quiet=quiet, progress_callback=lambda x: print(x.strip()) if not quiet else None ) print("[green]Package created successfully![/green]")Or if you want to log the output in verbose mode:
try: output_log = svc.create_intunewin( source_folder=source, setup_file=setup_file, output_folder=output, quiet=quiet, progress_callback=lambda x: print(x.strip()) if not quiet else None ) + if not quiet and output_log: + logger.debug(f"Tool output: {output_log}") print("[green]Package created successfully![/green]")
🧹 Nitpick comments (17)
src/switchcraft/services/notification_service.py (1)
79-83: Update the docstring to reflect current behavior.The docstring mentions conditional behavior based on foreground state ("If app is in background... If app is in foreground..."), but the code now always sends system notifications regardless of foreground state. Update the docstring to accurately describe the current implementation.
🔎 Proposed docstring update
@staticmethod def send_notification(title: str, message: str, timeout: int = 10): """ Sends a desktop notification. - If app is in background, uses system notification center. - If app is in foreground, notification still shows but app stays visible. + Always sends system notifications through the notification center. + On Windows, uses winotify for toast notifications with click support, + falling back to plyer or PowerShell if unavailable. """pyproject.toml (1)
17-31: Consider pinning dependency versions for stability and security.All dependencies are unpinned, which can lead to:
- Unexpected breaking changes from major version updates
- Potential security vulnerabilities
- Non-reproducible builds
Consider adding version constraints (at minimum, upper bounds) for production stability, especially for security-sensitive packages like
requests,PyJWT,openai, andgoogle-generativeai.Example approach:
dependencies = [ "requests>=2.31.0,<3.0", "click>=8.1.0,<9.0", "PyJWT>=2.8.0,<3.0", # ... etc ]src/switchcraft/gui_modern/app.py (1)
1-3: Consider using the imported i18n module for UI strings.The
i18nmodule is imported but never used. All UI strings are hardcoded in English (e.g., "Home", "Analyzer", "Settings", etc.). Consider either:
- Using
i18nto internationalize the UI strings, or- Removing the unused import if internationalization is planned for a later phase
docs/CLI_Reference.md (1)
46-46: Consider fixing list indentation for consistency.The nested list item uses 4 spaces instead of the Markdown standard 2 spaces, flagged by markdownlint.
🔎 Proposed fix
* `switchcraft intune package <setup_file> -o <out_folder> -s <source_folder>` * `switchcraft intune upload <intunewin> --name "App Name" --publisher "Pub" ...` - * Requires `IntuneTenantId`, `IntuneClientId`, `IntuneClientSecret` in config. + * Requires `IntuneTenantId`, `IntuneClientId`, `IntuneClientSecret` in config..github/workflows/test.yml (1)
30-30: Consider declaring PYTHONPATH separately.Shellcheck flags the combined
export PYTHONPATH=$PYTHONPATH:$(pwd)/srcpattern as it can mask return values from the command substitution.🔎 Proposed fix
- export PYTHONPATH=$PYTHONPATH:$(pwd)/src + REPO_SRC="$(pwd)/src" + export PYTHONPATH="$PYTHONPATH:$REPO_SRC"src/switchcraft/main.py (1)
27-28: Consider enhancing the GUI error message.The error message for missing GUI dependencies could guide users toward installing GUI extras or using the CLI.
🔎 Proposed enhancement
except ImportError as e: - print(f"GUI dependencies not found. Error: {e}") + print(f"GUI dependencies not found. Error: {e}") + print("Hint: Install GUI support with 'pip install switchcraft[gui]' or use CLI mode.") sys.exit(1)switchcraft_legacy.spec (1)
31-42: Minor style issues in module enumeration logic.
- Line 33: Prefer
file != '__init__.py'overnot file == '__init__.py'for readability.- Line 39: Inconsistent indentation (extra space before
full_path).🔎 Suggested style improvements
for root, dirs, files in os.walk(pkg_path): for file in files: - if file.endswith('.py') and not file == '__init__.py': + if file.endswith('.py') and file != '__init__.py': full_path = os.path.join(root, file) rel_path = os.path.relpath(full_path, src_root) module_name = rel_path.replace(os.sep, '.').replace('.py', '') hidden_imports.append(module_name) elif file == '__init__.py': - full_path = os.path.join(root, file) - rel_path = os.path.relpath(root, src_root) - module_name = rel_path.replace(os.sep, '.') - hidden_imports.append(module_name) + full_path = os.path.join(root, file) + rel_path = os.path.relpath(root, src_root) + module_name = rel_path.replace(os.sep, '.') + hidden_imports.append(module_name)src/switchcraft/cli_main.py (1)
1-1: Remove leading blank line.PEP 8 recommends no blank lines at the start of a file.
tests/test_cli_dynamic.py (2)
6-12: Unusedctxparameter inget_all_commands.The
ctxparameter is passed but never used in the function body. Consider removing it.🔎 Suggested fix
-def get_all_commands(cli_obj, ctx): +def get_all_commands(cli_obj): """Recursively yield all command paths and command objects.""" yield [], cli_obj if isinstance(cli_obj, click.Group): for name, cmd in cli_obj.commands.items(): - for sub_path, sub_cmd in get_all_commands(cmd, ctx): + for sub_path, sub_cmd in get_all_commands(cmd): yield [name] + sub_path, sub_cmdAnd update the call site at line 30:
- commands = list(get_all_commands(cli, ctx)) + commands = list(get_all_commands(cli))
48-65: Test is functional but comments are verbose.The test correctly validates that
--jsonflag is accepted. Consider trimming the inline comments - the test name and docstring already convey the purpose.🔎 Suggested simplification
def test_cli_json_flag(): """Verify the --json flag on the main entry point.""" runner = CliRunner() - # We pass no arguments, which should trigger help or specific behavior - # But here we want to test if --json flag is accepted even without file (might fail validation but check it exists) - - # Actually, main cli requires filepath argument? - # Let's check commands.py: @click.argument('filepath', required=False) - result = runner.invoke(cli, ['--json']) assert result.exit_code == 0 - # Expected: empty JSON output with maybe default values or handled gracefully - # Based on commands.py: if not filepath: click.echo(ctx.get_help()) -> output_json not reached. - # Wait, commands.py logic: - # if not filepath: click.echo(ctx.get_help()) - - # So --json without file just shows help. + # Without required arguments, CLI shows help even with --json assert "Usage:" in result.outputsrc/switchcraft/gui/app.py (1)
242-273: Consider the implications of accessing_tab_dictand re-initializingHistoryView.Two observations:
self.tabview._tab_dictis a private/internal attribute ofCTkTabview. This could break with future customtkinter updates.When the History tab is re-added (line 264-266),
setup_history_tab()creates a newHistoryViewinstance. If the oldself.history_viewheld state or references, this could cause orphaned objects or lost state.🔎 Consider storing History tab state before deletion
if history_exists: # Delete History, add Winget Store, re-add History + # Note: Any unsaved state in history_view will be lost self.tabview.delete("History") logger.debug("Temporarily removed History tab for repositioning")Consider whether
HistoryViewmaintains any state that should be preserved across this tab repositioning operation.tests/test_full_coverage.py (1)
95-100: Unused mock parameter and test doesn't exercise registry code.The
@patch("winreg.OpenKey")decorator providesmock_open_key, but it's never used. More importantly, checkingis_addon_installed("fake_addon")returnsFalsebecause"fake_addon"isn't inAddonService.ADDONS, so the registry code path is never reached.🔎 Consider testing an actual addon ID
-@patch("winreg.OpenKey") -def test_addon_detection(mock_open_key): +def test_addon_detection(): """Test registry detection for advanced addon.""" - # This is tricky to test on non-windows or without registry, - # but we can verify it doesn't crash on import/usage - assert not AddonService.is_addon_installed("fake_addon") + # Verify known addon ID resolution and that method doesn't crash + # "advanced" is a valid addon ID that will check filesystem + result = AddonService.is_addon_installed("advanced") + assert isinstance(result, bool) + + # Unknown addon should always return False + assert not AddonService.is_addon_installed("fake_addon")src/switchcraft/gui/views/manifest_dialog.py (1)
154-157:os.startfileis Windows-only.Consider using a cross-platform approach or adding a platform check, though this appears to be a Windows-focused application.
🔎 Proposed cross-platform alternative
def _show_next_steps(self, manifest_dir): # Open folder import os - os.startfile(manifest_dir) + import sys + if sys.platform == 'win32': + os.startfile(manifest_dir) + elif sys.platform == 'darwin': + subprocess.run(['open', manifest_dir]) + else: + subprocess.run(['xdg-open', manifest_dir])scripts/build_release.ps1 (1)
56-62: Variable$AutoLaunchshould be initialized at script scope.
$AutoLaunchis set conditionally inside the default behavior block but referenced later (line 170). If no default path is taken, it will be$null. Consider initializing it at the script scope.🔎 Proposed fix
+$AutoLaunch = $false + # Default behavior: If no specific target provided, build Modern (Standard) if (-not $Modern -and -not $Legacy -and -not $Cli -and -not $Pip -and -not $Installer) { Write-Host "No specific target variables provided. Defaulting to Modern GUI." -ForegroundColor Cyan Write-Host "TIP: Use '.\scripts\build_release.ps1 -All' to build Modern, Legacy, CLI, and Installer at once." -ForegroundColor Green $Modern = $true $AutoLaunch = $true }src/switchcraft/services/winget_manifest_service.py (1)
132-136: Remove dead code block.This conditional block does nothing (just contains
pass).🔎 Proposed fix
filename = f"{meta['PackageIdentifier']}.installer.yaml" - - # Global scope/type if consistent across installers - if len(installers) > 0: - first = installers[0] - if "InstallerType" in first: # If all have same type, can move to root, but keeping explicit is safer - pass - self._dump_yaml(folder / filename, data)src/switchcraft/cli/commands.py (2)
77-90: Consider type coercion for configuration values.The
valueargument is always passed as a string from the CLI. If users need to set numeric or boolean preferences, consider adding type inference or an explicit--typeoption. Per theSwitchCraftConfigcontext,set_user_preferenceaccepts an optionalvalue_typeparameter.
276-283: Consider logging unhandled prompt types.The
cli_promptcallback only handles'ask_browser'and returnsFalsefor all other types. Consider logging unhandled types for debugging purposes.Proposed enhancement
def cli_prompt(type, **kwargs): # CLI Handler for addon prompts if type == 'ask_browser': # Maybe skip browser for CLI or print URL url = kwargs.get('url') print(f"Please check: {url}") return False # Don't rely on browser flow in CLI + logger.debug(f"Unhandled prompt type in CLI: {type}") return False
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (41)
.github/workflows/release.yml(3 hunks).github/workflows/test.yml(1 hunks).gitignore(1 hunks)README.md(1 hunks)docs/CI_Architecture.md(1 hunks)docs/CLI_Reference.md(1 hunks)pyproject.toml(2 hunks)scripts/build_release.ps1(2 hunks)scripts/build_release.sh(1 hunks)src/entry.py(2 hunks)src/switchcraft/assets/lang/de.json(1 hunks)src/switchcraft/assets/lang/en.json(1 hunks)src/switchcraft/cli/commands.py(1 hunks)src/switchcraft/cli_main.py(1 hunks)src/switchcraft/gui/app.py(3 hunks)src/switchcraft/gui/splash.py(1 hunks)src/switchcraft/gui/views/analyzer_view.py(6 hunks)src/switchcraft/gui/views/manifest_dialog.py(1 hunks)src/switchcraft/gui/views/winget_view.py(1 hunks)src/switchcraft/gui_modern/app.py(1 hunks)src/switchcraft/main.py(1 hunks)src/switchcraft/modern_main.py(1 hunks)src/switchcraft/services/addon_service.py(5 hunks)src/switchcraft/services/backup_service.py(0 hunks)src/switchcraft/services/history_service.py(1 hunks)src/switchcraft/services/notification_service.py(1 hunks)src/switchcraft/services/sync_service.py(0 hunks)src/switchcraft/services/winget_manifest_service.py(1 hunks)src/switchcraft/utils/__init__.py(1 hunks)src/switchcraft/utils/i18n.py(0 hunks)src/switchcraft_advanced/utils/winget.py(0 hunks)src/switchcraft_debug/console.py(0 hunks)src/switchcraft_winget/utils/__init__.py(1 hunks)src/switchcraft_winget/utils/winget.py(4 hunks)switchcraft.iss(2 hunks)switchcraft.spec(1 hunks)switchcraft_cli.spec(1 hunks)switchcraft_legacy.spec(1 hunks)switchcraft_modern.spec(1 hunks)tests/test_cli_dynamic.py(1 hunks)tests/test_full_coverage.py(1 hunks)
💤 Files with no reviewable changes (5)
- src/switchcraft/utils/i18n.py
- src/switchcraft_debug/console.py
- src/switchcraft/services/sync_service.py
- src/switchcraft/services/backup_service.py
- src/switchcraft_advanced/utils/winget.py
🧰 Additional context used
🧬 Code graph analysis (12)
tests/test_cli_dynamic.py (1)
src/switchcraft/cli/commands.py (1)
cli(42-62)
src/switchcraft/main.py (5)
src/switchcraft/gui/app.py (1)
main(771-791)src/switchcraft/gui_modern/app.py (1)
main(96-124)src/switchcraft/modern_main.py (1)
main(4-6)src/switchcraft/cli_main.py (1)
main(9-19)src/switchcraft/cli/commands.py (1)
cli(42-62)
src/switchcraft/gui/views/manifest_dialog.py (2)
src/switchcraft/services/winget_manifest_service.py (3)
WingetManifestService(12-174)generate_manifests(18-71)validate_manifest(73-104)src/switchcraft/utils/config.py (1)
SwitchCraftConfig(8-199)
src/switchcraft/gui_modern/app.py (2)
src/switchcraft/main.py (1)
main(8-28)src/switchcraft/modern_main.py (1)
main(4-6)
src/switchcraft/services/winget_manifest_service.py (1)
src/switchcraft/utils/config.py (1)
get_value(22-59)
src/switchcraft/gui/app.py (2)
src/switchcraft/main.py (1)
main(8-28)src/switchcraft/gui/splash.py (1)
close(78-79)
src/switchcraft/gui/views/winget_view.py (2)
src/switchcraft_winget/utils/winget.py (1)
install_package(102-113)src/switchcraft/utils/i18n.py (1)
get(116-143)
tests/test_full_coverage.py (3)
src/switchcraft/services/winget_manifest_service.py (3)
WingetManifestService(12-174)generate_manifests(18-71)validate_manifest(73-104)src/switchcraft/services/addon_service.py (2)
AddonService(13-390)is_addon_installed(41-50)src/switchcraft/utils/config.py (2)
SwitchCraftConfig(8-199)get_value(22-59)
src/switchcraft/modern_main.py (2)
src/switchcraft/gui_modern/app.py (2)
ModernApp(5-93)main(96-124)src/switchcraft/main.py (1)
main(8-28)
src/switchcraft/cli_main.py (2)
src/switchcraft/main.py (1)
main(8-28)src/switchcraft/cli/commands.py (1)
cli(42-62)
src/switchcraft/cli/commands.py (3)
src/switchcraft/utils/config.py (6)
SwitchCraftConfig(8-199)is_debug_mode(72-93)get_value(22-59)set_user_preference(105-129)get_secret(132-139)set_secret(142-148)src/switchcraft_winget/utils/winget.py (4)
WingetHelper(10-178)search_packages(29-72)install_package(102-113)search_by_name(14-27)src/switchcraft/services/addon_service.py (4)
AddonService(13-390)is_addon_installed(41-50)install_addon(82-167)import_addon_module(62-79)
src/entry.py (3)
src/switchcraft/gui/app.py (1)
main(771-791)src/switchcraft/main.py (1)
main(8-28)src/switchcraft/cli_main.py (1)
main(9-19)
🪛 actionlint (1.7.9)
.github/workflows/test.yml
29-29: shellcheck reported issue in this script: SC2155:warning:1:8: Declare and assign separately to avoid masking return values
(shellcheck)
🪛 GitHub Actions: CI Orchestrator
src/switchcraft/gui/splash.py
[warning] 3-3: Unused import 'sys' (F401).
[warning] 4-4: Unused import 'os' (F401).
[warning] 5-5: Unused import 'pathlib.Path' (F401).
src/switchcraft/gui/views/manifest_dialog.py
[warning] 4-4: Unused import 'webbrowser' (F401).
[warning] 8-8: Unused import 'SwitchCraftConfig' (F401).
src/switchcraft/gui_modern/app.py
[warning] 124-124: Local variable 'app' assigned to but never used (F841).
src/switchcraft/services/winget_manifest_service.py
[warning] 1-1: Unused import 'os' (F401).
[warning] 5-5: Unused import 'shutil' (F401).
[warning] 7-7: Unused import 'datetime' (F401).
src/switchcraft/gui/views/winget_view.py
[warning] 188-188: Local variable 'cmd' assigned to but never used (F841).
src/switchcraft/gui/views/analyzer_view.py
[error] 973-973: F401 [*] 'tkinter.messagebox' imported but unused
src/switchcraft_winget/utils/winget.py
[warning] 3-3: Unused import 'shutil' (F401).
src/switchcraft/cli/commands.py
[error] 102-102: Undefined name 'WingetHelper' (F821).
[error] 125-125: Undefined name 'WingetHelper' (F821).
[error] 136-136: Redefinition of unused 'winget_install' (F811).
[error] 138-138: Undefined name 'WingetHelper' (F821).
[error] 179-179: Local variable 'output_log' assigned but never used (F841).
🪛 GitHub Actions: PR Assistant (The Janitor)
src/switchcraft/gui_modern/app.py
[error] 123-123: Local variable 'app' is assigned to but never used.
src/switchcraft/gui/views/winget_view.py
[error] 188-188: Local variable 'cmd' is assigned to but never used.
src/switchcraft/cli/commands.py
[error] 102-102: Undefined name 'WingetHelper'.
[error] 125-125: Undefined name 'WingetHelper'.
[error] 138-138: Undefined name 'WingetHelper'.
[error] 136-136: Redefinition of unused 'winget_install'.
[error] 179-179: Local variable 'output_log' is assigned to but never used.
🪛 LanguageTool
docs/CI_Architecture.md
[uncategorized] ~8-~8: The operating system from Apple is written “macOS”.
Context: ...dard Build**: Windows (GUI+CLI), Linux, MacOS. - CLI-Only Build: Dedicated job ...
(MAC_OS)
docs/CLI_Reference.md
[grammar] ~49-~49: Ensure spelling is correct
Context: ...Secretin config. ###addonsManage addons. * switchcraft addons list* swi...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🪛 markdownlint-cli2 (0.18.1)
README.md
17-17: Multiple headings with the same content
(MD024, no-duplicate-heading)
docs/CLI_Reference.md
46-46: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
🪛 RuboCop (1.81.7)
switchcraft_cli.spec
[fatal] 12-12: unexpected token kIN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 12-12: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 18-18: unexpected token kFOR
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 18-18: unexpected token kIF_MOD
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 18-18: unexpected token tRBRACK
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
switchcraft_modern.spec
[fatal] 15-15: unexpected token kIN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 15-15: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 19-19: unexpected token tCOMMA
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 20-20: unexpected token tCOMMA
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
switchcraft_legacy.spec
[fatal] 12-12: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 14-14: unexpected token tRPAREN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 25-25: unexpected token kIN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 25-25: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 31-31: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 32-32: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 33-33: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 38-38: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 45-45: unexpected token tRPAREN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 47-47: unexpected token tCOMMA
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
🔇 Additional comments (55)
src/switchcraft/assets/lang/de.json (1)
312-315: LGTM! Localization additions are well-structured.The three new SFX-related keys are properly added with valid JSON syntax, and the German translations are accurate and clear. The trailing comma on line 312 correctly maintains JSON validity, and the content aligns with the parallel English localization updates.
src/switchcraft/assets/lang/en.json (1)
291-294: LGTM! English localization additions are clear and consistent.The three new keys for 7-Zip SFX detection are properly formatted with valid JSON syntax. The English content is clear, concise, and maintains consistency with the German localization file. The trailing comma on line 291 correctly maintains JSON validity.
src/switchcraft/services/notification_service.py (1)
86-86: LGTM - Removal of unused code.The
is_foregroundvariable was never used after assignment, so commenting out this line removes dead code without changing behavior..gitignore (1)
15-17: LGTM!The addition of these negation patterns correctly ensures the three new spec files (CLI, Legacy, Modern variants) are tracked in version control, consistent with the existing pattern for
switchcraft.spec.pyproject.toml (4)
11-11: LGTM!The license metadata change from a file reference to the direct string "MIT" is a simplification that improves clarity.
34-41: Excellent modular dependency design!Separating GUI dependencies into optional groups (
guiandmodern) enables lightweight CLI-only installations while supporting multiple GUI variants. This architecture aligns well with the PR's "CLI first strategy" objective.
53-58: LGTM!The pytest configuration follows standard conventions with appropriate test discovery patterns and output file exclusions.
45-45: Entry point correctly routes between CLI and GUI based on arguments.The entry point change from
clitomainis properly implemented. Themain()function insrc/switchcraft/main.pycorrectly handles both invocation patterns: with arguments it loads the CLI viaswitchcraft.cli.commands, and without arguments it launches the GUI viaswitchcraft.gui.app. Error handling for missing dependencies is in place for both paths.switchcraft.iss (1)
12-12: LGTM!The executable name change to
SwitchCraft-windows.exeis consistently applied throughout the installer script, aligning with the new multi-platform build strategy.Also applies to: 80-80
src/switchcraft/gui_modern/app.py (2)
5-93: LGTM!The
ModernAppclass is well-structured with clear separation of concerns:
__init__: Page initializationsetup_page: Configurationbuild_ui: UI constructionnav_change: Event handlingThe navigation rail implementation follows Flet patterns correctly.
127-128: LGTM!The
__main__guard allows the module to be run directly, which is useful for testing the modern GUI independently.switchcraft_modern.spec (1)
1-38: LGTM!The PyInstaller Analysis configuration correctly:
- Collects Flet and SwitchCraft submodules
- Sets up Python paths
- Includes necessary data files (logo and assets)
The manual collection approach is appropriate for ensuring all dependencies are bundled.
src/switchcraft/utils/__init__.py (1)
2-2: No breaking change detected—WingetHelper remains importable from the module.The removal of
WingetHelperfrom__all__is not a breaking change. The only code importingWingetHelperuses direct module imports (from switchcraft.utils.winget import WingetHelper), which continue to work. The commented import inwinget_view.pyis already handled with an explanation ("Moved to Addon"), indicating this was a deliberate refactoring.Likely an incorrect or invalid review comment.
README.md (1)
19-20: LGTM!The new documentation links for CLI Reference and CI Architecture are well-placed and align with the CLI-first strategy introduced in this PR.
switchcraft.spec (1)
82-82: LGTM!The executable name change to 'SwitchCraft-windows' with the clarifying comment about legacy Tkinter builds aligns well with the multi-variant build strategy in this PR.
src/switchcraft/gui/views/winget_view.py (1)
196-200: LGTM!The refactoring to delegate installation to
winget_helper.install_package()improves code organization and error handling. The boolean return pattern is cleaner than parsing subprocess output.docs/CLI_Reference.md (1)
1-73: Excellent CLI documentation!The CLI reference is comprehensive, well-structured, and provides clear examples for all major commands. This will be valuable for users adopting the CLI-first approach.
.github/workflows/test.yml (2)
25-25: LGTM!Adding GUI extras to the backend test installation correctly separates core dependencies from GUI dependencies, enabling the new CLI-only test workflow.
33-48: Excellent test separation!The new
test_cli_corejob properly validates CLI functionality without GUI dependencies, aligning well with the CLI-first architecture.src/switchcraft/main.py (2)
8-28: Excellent CLI-first routing implementation!The main() function cleanly separates CLI and GUI startup paths based on command-line arguments. The error handling is appropriate, and the comment about the splash screen removal is helpful context.
20-23: Informative comment about splash screen removal.The explanation of why the splash screen was removed (Tkinter dual-root conflict) is valuable for future maintainers.
switchcraft_legacy.spec (1)
88-88: Verify PNG icon compatibility on Windows.Windows executables traditionally require
.icoformat for icons. While PyInstaller may convert PNG to ICO internally on some versions, this behavior isn't guaranteed. Consider using a proper.icofile for reliable Windows builds.switchcraft_cli.spec (3)
15-18: Good approach to filter GUI modules from CLI build.The filtering logic correctly removes
switchcraft.guimodules from hidden imports. This ensures a minimal CLI-only build.
33-44: Comprehensive excludes list for CLI-only build.Good defensive exclusion of GUI and heavy libraries. Note that
PILandPillowin excludes are somewhat redundant (Pillow provides thePILnamespace), but this is harmless and ensures thorough exclusion.
53-74: CLI EXE configuration looks correct.
console=Trueis appropriate for CLI, andupx=Truewill help reduce executable size. Thestrip=Falsechoice is safer for debugging; you can enable it later for release builds if size is critical.src/switchcraft/cli_main.py (2)
9-19: CLI bootstrap structure is correct.The main() function correctly provides a CLI-only entry point that avoids GUI imports. The duplication with
src/switchcraft/main.pyis acceptable given the different use cases (dedicated CLI vs. hybrid entry point).
14-19: Overly broad exception handling may swallowSystemExitandKeyboardInterrupt.Catching
Exceptionwill interceptKeyboardInterrupt(Ctrl+C) and turn it into "Critical Error in CLI" instead of a clean exit. It will also catchSystemExitraised by Click'sctx.exit(), potentially masking the intended exit code.Consider handling specific exceptions or re-raising system exceptions:
🔎 Suggested fix
def main(): """ CLI-Only entry point. Strictly avoids importing any GUI modules. """ try: from switchcraft.cli.commands import cli cli() + except SystemExit: + raise + except KeyboardInterrupt: + sys.exit(130) # Standard exit code for SIGINT except Exception as e: print(f"Critical Error in CLI: {e}") sys.exit(1)Likely an incorrect or invalid review comment.
src/entry.py (1)
35-50: Exception handling and graceful shutdown look well-designed.Good handling of:
SystemExitwith code 0 (clean exit, no error message)- Traceback printing for actual errors
EOFError/RuntimeErrorfor environments without stdin (e.g., GUI-only launch)tests/test_cli_dynamic.py (1)
14-46: Good dynamic test approach for CLI commands.This test provides valuable coverage by automatically discovering and validating all CLI commands. The assertions for
exit_code == 0,"Usage:", and"Options:"are appropriate for verifying help text generation.src/switchcraft/gui/splash.py (1)
7-79: LGTM!The
LegacySplashclass correctly handles both standalone and toplevel scenarios to avoid the pyimage error. The_owns_rootflag properly tracks ownership, and the UI composition is clean.src/switchcraft/gui/app.py (1)
771-791: LGTM!The updated
main()function properly handles the splash parameter with defensive exception handling, and the fallback error dialog using a temporary Tk root ensures users see critical startup errors even when the main app fails to initialize..github/workflows/release.yml (2)
157-163: Build caching looks good.The PyInstaller build cache keyed on
pyproject.tomlhash is a reasonable approach to speed up builds.
299-343: CLI build job is well-structured.The separate CLI build job correctly installs only the base package (no GUI extras) and uses a distinct cache key. The rename and upload steps are correct.
src/switchcraft/services/addon_service.py (3)
27-38: LGTM!The
.resolve()calls ensure canonical absolute paths, which improves reliability for path comparisons and logging.
49-50: Simplified addon detection is cleaner.Removing the conditional dev/source check and always requiring
__init__.pyprovides consistent behavior across environments.
99-103: Dev mode install behavior is appropriate.In non-frozen (development) mode, addons are expected to be present as sibling packages in the source tree. Returning
Falsewith a warning when missing (rather than attempting network download) is the correct behavior for development environments.tests/test_full_coverage.py (3)
17-38: LGTM!The manifest generation test validates the expected file structure and uses
tmp_pathfixture correctly for isolation.
57-84: LGTM!The Intune packaging test properly mocks
Popenand validates the command structure. The mock stdout as a list of strings is compatible with iteration.
104-112: LGTM!Good defensive pattern for testing optional dependency imports - skipping when Flet is unavailable while failing on unexpected exceptions.
src/switchcraft/gui/views/analyzer_view.py (3)
309-313: Dynamic addon loading for WingetHelper is well implemented.The pattern correctly handles the case where the addon module is unavailable by checking the return value before instantiation.
395-401: SFX archive notice section looks good.The UI enhancement properly surfaces archive details when a PE/SFX archive is detected.
452-458: Winget Manifest button integration looks correct.The button is properly wired to open the manifest dialog with the analysis info.
scripts/build_release.ps1 (2)
84-97: Process cleanup with proper error handling looks good.The cleanup loop properly handles cases where processes may not be running or are stubborn, with appropriate warnings.
237-298: Installer build section is well-structured.Good approach checking for prerequisites (Legacy EXE, Inno Setup), handling ICO conversion fallback, and proper error handling.
src/switchcraft_winget/utils/winget.py (2)
115-132: Good implementation ofdownload_packagemethod.The method correctly uses an argument list (not shell=True), handles errors appropriately, and searches for common installer extensions.
173-178: Good refactor:_get_startup_infohelper for cross-platform compatibility.This centralizes the Windows-specific STARTUPINFO handling and safely returns
Noneon non-Windows platforms.src/switchcraft/services/winget_manifest_service.py (1)
12-71: Overall manifest generation logic is well-structured.The service correctly implements the Winget manifest structure with version, installer, and locale manifests. The folder structure follows the Winget convention.
src/switchcraft/cli/commands.py (8)
21-36: LGTM!The logging setup is reasonable for a CLI entry point. Note that
logging.basicConfigonly has effect on the first call, so this works well as a CLI startup function.
38-62: LGTM!The CLI group setup is appropriate. The comments explaining the backward compatibility considerations are helpful for future maintainers.
64-69: LGTM!Clean delegation to the helper function with proper path validation.
191-239: LGTM!The upload command has proper credential validation and clear error messaging guiding users on how to configure missing credentials. Good use of progress callbacks for user feedback.
292-299: LGTM!Clean implementation for secure secret storage. This properly complements the
intune uploadcommand's credential requirements.
317-323: Good pattern: Use this dynamic import approach for the winget commands.This correctly handles the optional Winget addon via dynamic import through
AddonService. The same pattern should be applied to thewinget_searchandwinget_installcommands (lines 102, 125) to resolve the undefinedWingetHelpererrors.
333-354: LGTM!The report rendering with Rich panels and tables provides clear, well-organized output. Good conditional handling for missing data and helpful suggestions for EXE files without detected switches.
115-118: The code at lines 115-118 is correct and does not require changes. Thesearch_packagesmethod consistently returns dictionaries with PascalCase keys (Name,Id,Version,Source), as shown in both the PowerShell path (lines 60-66) and the CLI fallback path (lines 160-165). The confusion in the original review stemmed fromget_package_detailsusing lowercase keys; however, these are two separate methods with different return structures. The table row population usingr.get('Id'),r.get('Name'), andr.get('Version')is correct.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
src/switchcraft/services/winget_manifest_service.py (2)
53-57: Guard against emptypublisherstring.If
publisheris an empty string,publisher[0]will raise anIndexError. This issue was previously identified but remains unresolved.🔎 Proposed fix
# Folder structure: manifests/{first_char_lower}/{Publisher}/{PackageName}/{Version} - p_char = publisher[0].lower() + p_char = publisher[0].lower() if publisher else "_" package_name = meta.get("PackageName", pkg_id.split('.')[-1])Alternatively, strengthen the validation at line 39:
- if not (pkg_id and version and publisher): - raise ValueError("Missing required fields: PackageIdentifier, PackageVersion, Publisher") + if not (pkg_id and version and publisher and len(publisher) > 0): + raise ValueError("Missing or empty required fields: PackageIdentifier, PackageVersion, Publisher")Based on past review comments.
80-81:subprocess.STARTUPINFO()is Windows-only.This code will raise an
AttributeErroron non-Windows platforms. Thewinget.pyfile has a_get_startup_info()helper that handles this correctly—consider using a similar pattern here.🔎 Proposed fix
+ def _get_startup_info(self): + if hasattr(subprocess, 'STARTUPINFO'): + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + return si + return None + def validate_manifest(self, manifest_dir: str) -> dict: """ Validates the generated manifests using 'winget validate'. Returns dict with keys: 'valid' (bool), 'output' (str), 'errors' (list) """ try: # winget validate --manifest <path_to_directory> cmd = [self.winget_exe, "validate", "--manifest", str(manifest_dir)] # Start process without window - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo = self._get_startup_info() result = subprocess.run( cmd, capture_output=True, text=True, startupinfo=startupinfo, check=False )Based on past review comments.
src/switchcraft_winget/utils/winget.py (1)
101-112: Command injection vulnerability withshell=True.Using
shell=Truewith string interpolation ofpackage_idandscopeparameters allows command injection. An attacker-controlled package ID likefoo; rm -rf /could execute arbitrary commands.🔎 Proposed fix using argument list
def install_package(self, package_id: str, scope: str = "machine") -> bool: """Install a package via Winget CLI.""" - cmd = f"winget install --id {package_id} --scope {scope} --accept-package-agreements --accept-source-agreements" + if scope not in ("machine", "user"): + logger.error(f"Invalid scope: {scope}") + return False + cmd = [ + "winget", "install", + "--id", package_id, + "--scope", scope, + "--accept-package-agreements", + "--accept-source-agreements" + ] try: - proc = subprocess.run(cmd, shell=True, capture_output=True, text=True) + proc = subprocess.run(cmd, capture_output=True, text=True) if proc.returncode != 0: logger.error(f"Winget install failed: {proc.stderr}") return False return True except Exception as e: logger.error(f"Winget install exception: {e}") return FalseBased on past review comments.
🧹 Nitpick comments (4)
src/switchcraft/gui/splash.py (2)
37-67: Consider font fallback for cross-platform compatibility.The hard-coded
"Segoe UI"font (lines 43, 50, 59) is Windows-specific. On Linux and macOS, this font may not be available, causing tkinter to fall back to a default font, which could affect the visual consistency of the splash screen.🔎 Suggested improvement
Consider using a font fallback list or a cross-platform font:
+# At the top of __init__, define a cross-platform font +font_family = "Segoe UI" if self.root.tk.call("tk", "windowingsystem") == "win32" else "Helvetica" + # Then use it in the labels: tk.Label( main_frame, text="SwitchCraft", - font=("Segoe UI", 32, "bold"), + font=(font_family, 32, "bold"), bg="#2c3e50", fg="#ecf0f1" ).pack(pady=(40, 10))Or use tkinter's default font families like
"TkDefaultFont"or"Helvetica"which are available across platforms.Optional: Store progress bar reference for explicit cleanup.
The progress bar is started but the reference is not stored (line 65). While
destroy()will clean it up, storing it asself.progress = progresswould allow you to explicitly stop it in theclose()method withself.progress.stop()if needed.
75-76: Optional: Explicitly stop the progress bar before destroying.While
destroy()will clean up all widgets, explicitly stopping the progress bar first is a minor best practice improvement:def close(self): if hasattr(self, 'progress'): self.progress.stop() self.root.destroy()This requires storing the progress bar reference as
self.progressduring initialization (see previous comment).src/switchcraft/gui_modern/app.py (1)
77-92: Consider dictionary-based navigation for cleaner code.The if-elif chain works correctly, but a dictionary mapping index to content would be more maintainable as the number of navigation items grows.
🔎 Optional refactor using dictionary mapping
def nav_change(self, e): idx = e.control.selected_index self.content_area.controls.clear() - if idx == 0: - self.content_area.controls.append(ft.Text("Home", size=30)) - elif idx == 1: - self.content_area.controls.append(ft.Text("Analyzer (Coming Soon)", size=30)) - elif idx == 2: - self.content_area.controls.append(ft.Text("Winget (Coming Soon)", size=30)) - elif idx == 3: - self.content_area.controls.append(ft.Text("Intune (Coming Soon)", size=30)) - elif idx == 4: - self.content_area.controls.append(ft.Text("Settings (Coming Soon)", size=30)) + nav_content = { + 0: "Home", + 1: "Analyzer (Coming Soon)", + 2: "Winget (Coming Soon)", + 3: "Intune (Coming Soon)", + 4: "Settings (Coming Soon)", + } + + if idx in nav_content: + self.content_area.controls.append(ft.Text(nav_content[idx], size=30)) self.page.update()src/switchcraft/gui/views/analyzer_view.py (1)
969-978: Consider adding error handling for dialog instantiation.The lazy import and dialog creation could fail if the ManifestDialog module has issues. While unlikely, wrapping the dialog instantiation in a try-except would improve robustness.
🔎 Optional error handling enhancement
def _open_manifest_dialog(self, info): # Check Config repo_path = SwitchCraftConfig.get_value("WingetRepoPath") if not repo_path: # Optional: Ask user if they want to configure it, or just rely on default logic in Service pass - from switchcraft.gui.views.manifest_dialog import ManifestDialog - dlg = ManifestDialog(self.winfo_toplevel(), info) - dlg.grab_set() + try: + from switchcraft.gui.views.manifest_dialog import ManifestDialog + dlg = ManifestDialog(self.winfo_toplevel(), info) + dlg.grab_set() + except Exception as e: + logger.error(f"Failed to open manifest dialog: {e}") + messagebox.showerror("Error", f"Could not open manifest dialog: {e}")
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/switchcraft/cli/commands.py(1 hunks)src/switchcraft/gui/splash.py(1 hunks)src/switchcraft/gui/views/analyzer_view.py(6 hunks)src/switchcraft/gui/views/manifest_dialog.py(1 hunks)src/switchcraft/gui/views/winget_view.py(1 hunks)src/switchcraft/gui_modern/app.py(1 hunks)src/switchcraft/services/winget_manifest_service.py(1 hunks)src/switchcraft_winget/utils/winget.py(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/switchcraft/gui/views/manifest_dialog.py
- src/switchcraft/gui/views/winget_view.py
🧰 Additional context used
🧬 Code graph analysis (3)
src/switchcraft/services/winget_manifest_service.py (1)
src/switchcraft/utils/config.py (1)
get_value(22-59)
src/switchcraft/gui_modern/app.py (3)
src/switchcraft/main.py (1)
main(8-28)src/switchcraft/modern_main.py (1)
main(4-6)src/switchcraft/gui/app.py (1)
main(771-791)
src/switchcraft/gui/views/analyzer_view.py (6)
src/switchcraft/services/addon_service.py (1)
import_addon_module(62-79)src/switchcraft/cli/commands.py (1)
winget(94-96)src/switchcraft_winget/utils/winget.py (1)
WingetHelper(9-177)src/switchcraft/utils/i18n.py (1)
get(116-143)src/switchcraft/utils/config.py (1)
get_value(22-59)src/switchcraft/gui/views/manifest_dialog.py (1)
ManifestDialog(8-198)
🔇 Additional comments (17)
src/switchcraft/gui/splash.py (4)
1-9: LGTM! Clear documentation of the design rationale.The class structure and docstring clearly explain why
LegacySplashaccepts an optionalmain_rootparameter to avoid tkinter's multiple root issues.
10-17: LGTM! Proper handling of root window creation.The logic correctly creates either a standalone
Tk()root or aToplevelwindow depending on whethermain_rootis provided. The_owns_rootflag is tracked but currently unused—this is fine asdestroy()works for both window types.
19-34: LGTM! Standard splash screen configuration.The borderless, topmost window with proper centering logic is a typical and correct implementation for a splash screen.
71-73: LGTM! Simple and effective status update.The method correctly updates the status label and refreshes the UI—appropriate for a splash screen context.
src/switchcraft/gui_modern/app.py (4)
1-9: LGTM! Clean initialization flow.The import structure and initialization sequence are well-organized. The explicit splash removal via
page.clean()followed by setup and UI building provides a clear initialization path.
11-19: LGTM! Sensible page configuration.The page setup establishes appropriate defaults: versioned title, dark theme, and minimum window dimensions to ensure usability. The commented custom title bar code is clearly marked for future consideration.
20-76: LGTM! Well-structured UI layout.The navigation rail and content area layout follows Flet best practices. The use of
expand=Trueensures proper responsive behavior, and the navigation destinations are clearly labeled with appropriate icons.
126-127: LGTM! Direct run support is properly implemented.The
if __name__ == "__main__"block correctly usesft.app(target=main)to enable standalone execution of this module.src/switchcraft/gui/views/analyzer_view.py (4)
309-313: LGTM! Clean dynamic addon loading pattern.The dynamic loading of the Winget addon through
AddonServiceis well-implemented with proper null-safety checks. This allows the Winget functionality to be optional without breaking the core analyzer flow.
395-401: Good UX enhancement for SFX archive detection.The info panel clearly communicates the SFX detection to users and provides actionable instructions. The i18n integration ensures proper localization support.
453-458: Clean integration of Winget manifest creation.The button is appropriately gated by the presence of install switches and integrates well with the existing UI flow. The color choice helps distinguish it from other actions.
745-745: The field rename fromraw_outputtobrute_force_outputis consistent across the codebase. The data source (UniversalAnalyzer.extract_and_analyze_nestedinuniversal.py:484) produces thebrute_force_outputfield, and the consumer at line 745 correctly reads it.src/switchcraft/services/winget_manifest_service.py (1)
9-171: Well-designed manifest service structure.The separation of concerns into
_write_version_manifest,_write_installer_manifest, and_write_locale_manifestis clean and maintainable. The validation flow with graceful fallback for missing Winget CLI is also well-handled.src/switchcraft_winget/utils/winget.py (2)
114-131: LGTM! Safe subprocess usage in download method.The
download_packagemethod correctly uses an argument list withoutshell=True, avoiding command injection risks. The file discovery logic with glob patterns is appropriate for finding downloaded installers.
172-177: Excellent cross-platform subprocess helper.The
_get_startup_infohelper properly encapsulates Windows-specific subprocess configuration, making the code portable and reducing duplication. This pattern should be adopted inwinget_manifest_service.pyas well.src/switchcraft/cli/commands.py (2)
98-121: Clean dynamic Winget integration in CLI.The
winget searchcommand properly uses the addon service to dynamically load WingetHelper, addressing previous concerns about missing imports. The error handling and Rich table output provide good UX.
123-139: LGTM! No duplicate command definition in current code.The
winget installcommand is properly implemented with dynamic addon loading and scope validation. The duplicate command issue flagged in past reviews is not present in the current code.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
README.md (1)
98-116: Update README CLI flags section to match actual implementation.The README documents three global CLI flags that don't match the implemented behavior:
--json: ✓ Correctly implemented inanalyzesubcommand and main group--install-addons=<list>: ✗ Not implemented as documented. Users must instead runswitchcraft addons install <addon_id>for each addon--debug: ✗ Not implemented as a CLI flag. Debug mode is controlled via configuration (SwitchCraftConfig.is_debug_mode()), not command-line argumentsThe README example
switchcraft --install-addons=advanced,wingetwill fail for users. Update the documentation to reflect the actual CLI subcommand structure (switchcraft addons install advanced,switchcraft addons install winget) or implement the documented global flags in the CLI parser.
♻️ Duplicate comments (1)
src/switchcraft/gui_modern/app.py (1)
121-127: Verify entry point inmodern_main.pymatches this file's pattern.This file correctly passes the
mainfunction toft.app(target=main)(line 127). However, the relevant code snippet shows thatsrc/switchcraft/modern_main.pypassesModernAppclass directly:ft.app(target=ModernApp). This will fail at runtime since Flet expects a callable that accepts apageparameter.Verify that
modern_main.pyhas been updated to match this pattern:#!/bin/bash # Check if modern_main.py passes the correct callable to ft.app cat src/switchcraft/modern_main.py | grep -A 2 "def main"
🧹 Nitpick comments (4)
src/switchcraft/gui/splash.py (1)
10-87: Consider using_owns_rootflag inclose()method.The
_owns_rootflag is set in__init__but never checked inclose(). If the splash owns the root (standalone run), destroying just the splash window may leave the Tk root running. Consider conditionally destroying the root when_owns_rootis True.🔎 Proposed fix
def close(self): if hasattr(self, 'progress'): self.progress.stop() - self.root.destroy() + if self._owns_root: + self.root.quit() # Stop mainloop if we own it + self.root.destroy()src/switchcraft_winget/utils/winget.py (1)
124-142: Consider filtering installer files by modification time.Lines 134-137 search for any
.exeor.msifile indest_dir. If the directory contains pre-existing installers, this could return the wrong file. Consider filtering by files modified after thewinget downloadcommand started, or use a unique temporary directory for each download.🔎 Possible improvement
def download_package(self, package_id: str, dest_dir: Path) -> Optional[Path]: """Download a package installer to dest_dir. Returns path to installer if found.""" + import time + start_time = time.time() cmd = ["winget", "download", "--id", package_id, "--dir", str(dest_dir), "--accept-source-agreements", "--accept-package-agreements"] try: proc = subprocess.run(cmd, capture_output=True, text=True) if proc.returncode != 0: logger.error(f"Winget download failed: {proc.stderr}") return None # Find the installer file files = list(dest_dir.glob("*.*")) for f in files: - if f.suffix.lower() in [".exe", ".msi"]: + if f.suffix.lower() in [".exe", ".msi"] and f.stat().st_mtime >= start_time: return f return Nonesrc/switchcraft/services/winget_manifest_service.py (1)
70-103: Windows-specific subprocess handling fixed.Lines 80-83 correctly use
hasattr(subprocess, 'STARTUPINFO')to check for Windows-specific functionality before using it. This resolves the cross-platform compatibility issue from the previous review.For consistency with
winget.py, consider extracting this pattern into a_get_startup_info()helper method:def _get_startup_info(self): if hasattr(subprocess, 'STARTUPINFO'): si = subprocess.STARTUPINFO() si.dwFlags |= subprocess.STARTF_USESHOWWINDOW return si return Nonesrc/switchcraft/gui/views/analyzer_view.py (1)
969-981: Consider explicit modal dialog handling.Line 979 calls
dlg.grab_set()to make the dialog modal, but doesn't wait for it to close or handle its result. In CTk, dialogs are typically non-blocking unless explicitly managed. Consider whether the dialog should block until closed or if the current fire-and-forget pattern is intentional.If blocking behavior is desired:
dlg = ManifestDialog(self.winfo_toplevel(), info) dlg.transient(self.winfo_toplevel()) dlg.grab_set() self.wait_window(dlg) # Wait for dialog to close
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
README.md(2 hunks)src/switchcraft/gui/app.py(8 hunks)src/switchcraft/gui/splash.py(1 hunks)src/switchcraft/gui/views/analyzer_view.py(6 hunks)src/switchcraft/gui/views/missing_addon_view.py(2 hunks)src/switchcraft/gui/views/settings_view.py(3 hunks)src/switchcraft/gui_modern/app.py(1 hunks)src/switchcraft/services/winget_manifest_service.py(1 hunks)src/switchcraft_winget/utils/winget.py(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/switchcraft/gui/views/missing_addon_view.py (2)
src/switchcraft/gui/app.py (1)
_show_restart_countdown(202-245)src/switchcraft/utils/i18n.py (1)
get(116-143)
src/switchcraft/gui_modern/app.py (2)
src/switchcraft/main.py (1)
main(8-28)src/switchcraft/modern_main.py (1)
main(4-6)
src/switchcraft/gui/app.py (1)
src/switchcraft/gui/views/missing_addon_view.py (1)
MissingAddonView(8-70)
src/switchcraft/gui/views/settings_view.py (2)
src/switchcraft/gui/app.py (1)
_show_restart_countdown(202-245)src/switchcraft/utils/i18n.py (1)
get(116-143)
src/switchcraft/services/winget_manifest_service.py (1)
src/switchcraft/utils/config.py (2)
SwitchCraftConfig(8-199)get_value(22-59)
src/switchcraft/gui/views/analyzer_view.py (3)
src/switchcraft/services/addon_service.py (2)
AddonService(13-390)import_addon_module(62-79)src/switchcraft_winget/utils/winget.py (1)
WingetHelper(9-187)src/switchcraft/gui/views/manifest_dialog.py (1)
ManifestDialog(8-198)
🪛 markdownlint-cli2 (0.18.1)
README.md
17-17: Multiple headings with the same content
(MD024, no-duplicate-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build / Build Installer (windows-latest)
🔇 Additional comments (11)
README.md (1)
19-20: Both referenced documentation files exist in the repository. No action required.src/switchcraft/gui/views/settings_view.py (2)
15-17: LGTM! Constructor signature updated correctly.The addition of the
appparameter enables centralized restart UX when addons are installed. The implementation correctly stores the reference and uses it throughout the file.
939-942: Restart flow implementation is consistent and defensive.The conditional check for
_show_restart_countdown()with appropriate fallback to message boxes provides a good user experience while maintaining backward compatibility. The pattern is applied consistently across both addon installation paths.Also applies to: 958-962
src/switchcraft_winget/utils/winget.py (1)
101-122: Command injection vulnerability fixed!The
install_packagemethod now correctly uses an argument list (lines 107-113) instead of string interpolation withshell=True. The scope validation on line 103 provides additional defense. This addresses the critical security issue from the previous review.src/switchcraft/services/winget_manifest_service.py (1)
39-56: Publisher validation addressed correctly.Line 39 validates that
publisheris truthy (catchesNoneand empty string). Line 53 adds an additional safeguard withif publisher else "_". The past review concern about empty publisher strings is resolved.For extra clarity, consider explicitly checking length:
- if not (pkg_id and version and publisher): + if not (pkg_id and version and publisher and len(publisher) > 0): raise ValueError("Missing required fields: PackageIdentifier, PackageVersion, Publisher")src/switchcraft/gui/views/missing_addon_view.py (1)
9-11: Consistent implementation with centralized restart UX.The addition of the
appparameter and conditional use of_show_restart_countdown()matches the pattern established insettings_view.py. The fallback to message boxes maintains compatibility.Also applies to: 56-59
src/switchcraft/gui/app.py (3)
779-799: Robust splash handling and error recovery.The optional
splashparameter with defensivetry/exceptaroundsplash.close()(lines 782-786) provides good backward compatibility. The fatal error handling (lines 788-799) with fallback UI is appropriate for startup failures.
211-231: Critical PyInstaller restart fix properly implemented.Lines 214-216 clean
_MEIPASSenvironment variables before restart, preventing the new process from using stale temporary directory paths. The comments clearly explain the issue and fix. This is essential for frozen application restarts.
733-765: View instantiation updated consistently.Lines 733, 744, and 765 correctly pass
selfas theappparameter toMissingAddonViewandSettingsView, enabling the centralized restart countdown flow. The pattern is applied consistently across all usage sites.src/switchcraft/gui/views/analyzer_view.py (2)
309-316: Dynamic Winget integration properly implemented.Lines 309-313 correctly load the Winget addon dynamically via
AddonService.import_addon_module()and only instantiateWingetHelperwhen both the module is available andproduct_nameexists. This gracefully handles missing addon scenarios.
453-458: New Winget Manifest button integrated well.The "Create Winget Manifest" button is appropriately placed in the analysis results flow and uses a distinct color scheme to differentiate it from other deployment actions. The integration with
_open_manifest_dialog()follows existing patterns.
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
src/switchcraft/gui/app.py (1)
294-327: Tab reordering still loses History view state (as noted in previous review).The added debug logging (lines 296-297, 314, 320, 325, 327) is helpful for troubleshooting. However, the fundamental issue identified in the previous review remains: recreating the History tab (line 319) creates a new
HistoryViewinstance, which loses any runtime state (selected items, scroll position, filters, etc.).The previous review comment suggested state preservation or documenting this limitation. Consider addressing this in a future iteration if user experience is impacted.
🧹 Nitpick comments (5)
src/switchcraft_winget/utils/winget.py (1)
130-147: Consider documenting behavior when multiple installers exist.The method returns the first installer file found, but if Winget downloads multiple installers (e.g., x86 and x64 versions), the choice is non-deterministic since glob iteration order is not guaranteed. Consider documenting this behavior or filtering for a specific architecture if determinism is required.
Also consider adding a
timeoutparameter to thesubprocess.runcall to prevent indefinite hangs.switchcraft_legacy.spec (1)
17-40: Redundant module collection withcollect_submodules.Line 19 already uses
collect_submodules('switchcraft')to automatically discover all switchcraft submodules recursively. The manual directory walking (lines 21-40) duplicates this effort. While PyInstaller deduplicates hidden imports internally, the manual walk adds unnecessary complexity and maintenance burden.🔎 Proposed simplification
Remove the manual walking logic and rely solely on
collect_submodules:# MANUAL COLLECTION to ensure robustness hidden_imports = ['PIL._tkinter_finder', 'tkinterdnd2', 'plyer.platforms.win.notification', 'defusedxml', 'winotify', 'switchcraft.services.addon_service'] hidden_imports += collect_submodules('switchcraft') - -# Manually walk src/switchcraft to find all modules -src_root = os.path.abspath('src') -if src_root not in sys.path: - sys.path.insert(0, src_root) - -import switchcraft -pkg_path = os.path.dirname(switchcraft.__file__) - -for root, dirs, files in os.walk(pkg_path): - for file in files: - if file.endswith('.py') and not file == '__init__.py': - full_path = os.path.join(root, file) - rel_path = os.path.relpath(full_path, src_root) - module_name = rel_path.replace(os.sep, '.').replace('.py', '') - hidden_imports.append(module_name) - elif file == '__init__.py': - full_path = os.path.join(root, file) - rel_path = os.path.relpath(root, src_root) - module_name = rel_path.replace(os.sep, '.') - hidden_imports.append(module_name)src/switchcraft/gui/views/settings_view.py (2)
972-975: Consider making_show_restart_countdowna public API.The code defensively checks for the existence of
self.app._show_restart_countdown()and falls back gracefully. However, accessing a private method (indicated by the leading underscore) from outside the class is generally discouraged. Consider either:
- Making this a public method by removing the underscore prefix
- Providing a formal public API for triggering restart countdowns
This improves code maintainability and signals the intended usage pattern.
972-975: Consider extracting the restart UX pattern to a helper method.The same pattern (check for
app._show_restart_countdown()with fallback) is repeated at lines 972-975 and 991-995. Consider extracting this to a helper method to reduce duplication:🔎 Possible refactor
def _trigger_restart_ui(self, success_msg=None): """Trigger app restart UI with fallback.""" if hasattr(self.app, '_show_restart_countdown'): self.app._show_restart_countdown() else: msg = success_msg or i18n.get("status_installed_restart") or "Addon installed successfully! Please restart." messagebox.showinfo(i18n.get("restart_required"), msg)Then use it as:
if AddonService.install_addon(addon_id, prompt_callback=prompt_handler): self._trigger_restart_ui(f"Addon {addon_id} installed! Please restart.")Also applies to: 991-995
src/switchcraft/gui/app.py (1)
250-291: Solid implementation of weekly cloud backup check.The logic correctly:
- Checks authentication and config before proceeding
- Uses a 7-day interval (604800 seconds)
- Detects changes via MD5 hash comparison (appropriate for non-cryptographic use)
- Updates the timestamp even when no changes are detected to avoid repeated checks
- Runs in a background thread to avoid blocking the UI
Consider adding error notification for users if the auto-backup fails:
🔎 Optional enhancement
if SyncService.sync_up(): logger.info("Auto-backup successful.") SwitchCraftConfig.set_user_preference("LastCloudBackup", now) SwitchCraftConfig.set_user_preference("LastBackupHash", current_hash) +else: + logger.warning("Auto-backup failed. Will retry next week.") + # Optionally notify user via NotificationService
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/switchcraft/gui/app.py(10 hunks)src/switchcraft/gui/views/settings_view.py(4 hunks)src/switchcraft/modern_main.py(1 hunks)src/switchcraft/services/history_service.py(1 hunks)src/switchcraft_winget/utils/winget.py(4 hunks)switchcraft_legacy.spec(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/switchcraft/services/history_service.py
- src/switchcraft/modern_main.py
🧰 Additional context used
🧬 Code graph analysis (1)
src/switchcraft/gui/views/settings_view.py (3)
src/switchcraft/services/sync_service.py (3)
find_sync_gist(31-55)sync_down(159-173)sync_up(142-156)src/switchcraft/utils/i18n.py (1)
get(116-143)src/switchcraft/gui/app.py (1)
_show_restart_countdown(205-248)
🪛 GitHub Actions: CI Orchestrator
src/switchcraft/gui/app.py
[error] 872-872: Ruff: E722 Do not use bare 'except'.
[error] 872-872: Ruff: E701 Multiple statements on one line (colon).
🪛 GitHub Actions: PR Assistant (The Janitor)
src/switchcraft/gui/app.py
[error] 872-872: E722 Do not use bare except.
[error] 872-872: E701 Multiple statements on one line (colon).
🪛 RuboCop (1.81.7)
switchcraft_legacy.spec
[fatal] 10-10: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 12-12: unexpected token tRPAREN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 23-23: unexpected token kIN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 23-23: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 29-29: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 30-30: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 31-31: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 36-36: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 43-43: unexpected token tRPAREN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 45-45: unexpected token tCOMMA
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
🔇 Additional comments (14)
src/switchcraft_winget/utils/winget.py (3)
3-4: LGTM!The added imports are appropriate for the Winget CLI integration functionality.
42-42: LGTM!Good refactoring to centralize Windows startup info creation via
_get_startup_info(), and the dict-to-list normalization in line 64 properly handles PowerShell returning a single object instead of an array. The defensive check forSTARTUPINFOavailability ensures cross-platform compatibility.Also applies to: 64-64, 84-84, 154-154, 188-193
107-128: LGTM!The command injection vulnerability has been properly addressed. The method now uses an argument list instead of
shell=Truewith string interpolation, validates thescopeparameter, and includes appropriate error handling.switchcraft_legacy.spec (6)
1-12: LGTM!The imports and helper function are appropriate for a PyInstaller spec file that bundles customtkinter and tkinterdnd2.
14-15: LGTM!Package data extraction is correctly implemented.
42-47: LGTM!The data files configuration correctly bundles UI libraries, assets, and the application logo.
49-62: LGTM!The Analysis configuration is correct and appropriate for the legacy Windows build. Note that the past review concern about the
cipherparameter has been resolved—noblock_ciphervariable orcipherparameter is present in this version.
64-64: LGTM!The PYZ configuration is standard and correct.
66-87: LGTM!The EXE configuration correctly produces a Windows GUI executable with appropriate icon and version metadata. The
console=Falsesetting is correct for the legacy Tkinter interface.src/switchcraft/gui/app.py (4)
145-146: LGTM!The cloud backup check is appropriately scheduled 6 seconds after startup to avoid blocking the initial application load.
214-234: Excellent improvement to restart reliability.The enhanced restart logic properly:
- Cleans PyInstaller-specific environment variables (
_MEIPASS,_MEIPASS2) to prevent stale temp directory issues- Sets an appropriate working directory based on frozen/script state
- Propagates the clean environment and working directory to the new process
This improves restart robustness across different deployment scenarios.
780-780: LGTM! View instantiations correctly updated.All view instantiations (lines 780, 791, 812) have been correctly updated to pass the
self(app instance) parameter, consistent with the updated constructor signatures inSettingsViewandMissingAddonView.Also applies to: 791-791, 812-812
826-868: Excellent enhancement to startup flow.The updated
mainfunction provides:
- Optional splash screen support with safe cleanup (lines 860-864)
- Automatic debug console enablement for dev/nightly builds with clear user requirement documentation (lines 828-857)
- Comprehensive startup error handling with crash log generation (lines 866-872, excluding the linting issue on line 872)
The logic correctly sets
ShowDebugConsoletoTruefor dev/nightly builds at startup, honoring the requirement that it should always start enabled for these builds even if manually disabled during the session.src/switchcraft/gui/views/settings_view.py (1)
15-17: All callers of SettingsView have been properly updated.The constructor signature change is a breaking change requiring the new
appparameter. The single instantiation insrc/switchcraft/gui/app.py(line 789-795) has been correctly updated with all required parameters in the proper order:parent,app,show_update_callback,intune_service, and the optionalon_winget_togglekeyword argument.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/test_winget.py (1)
55-59: Test may not be testing the intended behavior.The test
test_search_no_clipatchesshutil.whichto returnNone, but theWingetHelperimplementation doesn't useshutil.which. Without mockingsubprocess.run, this test will attempt to execute the actual PowerShell/winget commands, making it an integration test rather than a unit test. If PowerShell or winget is not available in the test environment, the test may pass for the right reasons, but if they are available, the test behavior becomes unpredictable.Consider either:
- Mocking
subprocess.runto simulate the CLI not being available (e.g., FileNotFoundError or specific return codes)- Renaming and repurposing the test to explicitly verify behavior when PowerShell and CLI both fail
🔎 Suggested fix to properly test the "no CLI" scenario
- @patch('shutil.which', return_value=None) - def test_search_no_cli(self, mock_which): + @patch('subprocess.run') + def test_search_winget_unavailable(self, mock_run): + # Simulate both PowerShell module and CLI failing + mock_proc = MagicMock() + mock_proc.returncode = 1 + mock_proc.stderr = "Find-WinGetPackage command not found" + mock_proc.stdout = "" + mock_run.return_value = mock_proc + helper = WingetHelper() self.assertIsNone(helper.search_by_name("AnyApp")) self.assertEqual(helper.search_packages("AnyApp"), [])
♻️ Duplicate comments (1)
src/switchcraft/gui/app.py (1)
312-346: Note: History view state is recreated during Winget tab toggle.The tab reordering logic (lines 326-338) correctly repositions tabs but recreates the History view, which will lose any runtime state (scroll position, filters, selections). This is a known limitation from the previous review.
The added debug logging is helpful for troubleshooting tab management issues.
🧹 Nitpick comments (7)
tests/test_config.py (2)
98-118: Consider adding edge case tests for float conversion.The test verifies that float values are converted to int for REG_DWORD, but doesn't cover edge cases:
- Negative floats: Test how negative values are handled (e.g.,
-123.45)- Large floats: Test floats that might exceed DWORD range (e.g.,
5e9)- Precision loss: Verify truncation behavior (e.g.,
123.99becomes123, not124)- Round-trip: Test that writing and reading back produces expected results
These edge cases would help ensure the conversion logic handles all scenarios correctly.
Example edge case tests
@patch('sys.platform', 'win32') def test_set_user_preference_float_negative(self): """Test that negative float values are handled.""" with patch.object(self.winreg_mock, 'CreateKey'): mock_key = MagicMock() self.winreg_mock.OpenKey = MagicMock(return_value=mock_key) mock_key.__enter__ = MagicMock(return_value=mock_key) mock_key.__exit__ = MagicMock(return_value=False) SwitchCraftConfig.set_user_preference("TestNegFloat", -123.45) call_args = self.winreg_mock.SetValueEx.call_args[0] self.assertEqual(call_args[4], -123) @patch('sys.platform', 'win32') def test_set_user_preference_float_truncation(self): """Test that float truncation works as expected.""" with patch.object(self.winreg_mock, 'CreateKey'): mock_key = MagicMock() self.winreg_mock.OpenKey = MagicMock(return_value=mock_key) mock_key.__enter__ = MagicMock(return_value=mock_key) mock_key.__exit__ = MagicMock(return_value=False) SwitchCraftConfig.set_user_preference("TestTrunc", 123.99) call_args = self.winreg_mock.SetValueEx.call_args[0] self.assertEqual(call_args[4], 123) # Truncated, not rounded
120-131: Add test coverage for False and verify REG_DWORD type.The test verifies that
Trueis converted to1, but doesn't test:
- False value: Verify that
Falseis converted to0- Type verification: Assert that
REG_DWORDtype is used (similar to the float test at line 117)Enhanced test
@patch('sys.platform', 'win32') def test_set_user_preference_bool(self): """Test that bool values are converted to 0/1 for REG_DWORD.""" with patch.object(self.winreg_mock, 'CreateKey'): mock_key = MagicMock() self.winreg_mock.OpenKey = MagicMock(return_value=mock_key) mock_key.__enter__ = MagicMock(return_value=mock_key) mock_key.__exit__ = MagicMock(return_value=False) SwitchCraftConfig.set_user_preference("TestBool", True) call_args = self.winreg_mock.SetValueEx.call_args[0] + self.assertEqual(call_args[3], self.winreg_mock.REG_DWORD) self.assertEqual(call_args[4], 1) + + # Reset mock for False test + self.winreg_mock.SetValueEx.reset_mock() + SwitchCraftConfig.set_user_preference("TestBool", False) + call_args = self.winreg_mock.SetValueEx.call_args[0] + self.assertEqual(call_args[3], self.winreg_mock.REG_DWORD) + self.assertEqual(call_args[4], 0)tests/test_winget.py (2)
6-29: Test name misleading and unused mock present.The test name
test_search_by_name_cli_foundsuggests it's testing the CLI fallback path, but it's actually mocking a successful PowerShell JSON response, which exercises the primary PowerShell module path. Additionally, the@patch('shutil.which', ...)decorator appears unused since theWingetHelperimplementation doesn't callshutil.which.Consider:
- Renaming to
test_search_by_name_powershell_successor similar- Removing the unused
shutil.whichpatch- Adding a separate test for the CLI fallback by mocking a PowerShell failure (returncode != 0 with "Find-WinGetPackage" in stderr)
🔎 Suggested improvements
- @patch('shutil.which', return_value="C:\\winget.exe") @patch('subprocess.run') - def test_search_by_name_cli_found(self, mock_run, mock_which): + def test_search_by_name_powershell_success(self, mock_run): # Mock successful winget search outputAdditionally, consider verifying that the correct PowerShell command is called:
# After helper.search_by_name("7zip") mock_run.assert_called() call_args = mock_run.call_args cmd = call_args[0][0] self.assertIn("Find-WinGetPackage", cmd[4]) self.assertIn("-Query '7zip'", cmd[4])
31-54: Remove unusedshutil.whichpatch for consistency.Similar to the previous test, the
@patch('shutil.which', ...)decorator is not used by the implementation. Removing it will make the test cleaner and more accurate.🔎 Suggested diff
- @patch('shutil.which', return_value="C:\\winget.exe") @patch('subprocess.run') - def test_search_packages_found(self, mock_run, mock_which): + def test_search_packages_found(self, mock_run):switchcraft.spec (2)
43-50: Consider moving theimportlibimport to module level.The
can_import()helper importsimportlibon every call. While this runs only at build time (not performance-critical), moving the import to the top of the file would be more idiomatic.🔎 Proposed refactor
Move the import to the top of the file alongside other imports:
from pathlib import Path from PyInstaller.utils.hooks import collect_submodules +import importlibThen simplify the function:
def can_import(module_name): """Test if a module can actually be imported.""" try: - import importlib importlib.import_module(module_name) return True except (ImportError, ModuleNotFoundError): return False
59-61: Optional: Simplify duplicate checking logic.The inline
module_name not in hidden_importschecks are O(n) operations repeated for each discovered module. Since line 70 performs final deduplication anyway, these inline checks add complexity without much benefit.Consider either:
- Removing the inline duplicate checks and relying solely on the final
set()deduplication at line 70, or- Using a
setforhidden_importsfrom the start and converting tolistonly when passing to PyInstaller.Also applies to: 66-67
src/switchcraft/gui/app.py (1)
846-876: Consider simplifying the comment block.The logic is correct: for dev/nightly builds, the debug console is force-enabled at startup (line 875). However, the comment block (lines 846-874) is quite verbose.
🔎 Suggested simplification
- # --- Auto-Enable Debug Console for Dev/Nightly Builds --- - from switchcraft import __version__ - if "dev" in __version__.lower() or "nightly" in __version__.lower(): - # If preference is missing (None) or True, ensure it is set to True. - # If user explicitly disabled it (False), we respect it (unless first run logic overrides, but simple is better). - # User request: "Start also if disabled in user settings... IF it is dev/nightly. If user manually turns off, keep off." - # To track "manually turned off", we rely on the config value being explicitly False. - # If it is None (default), we force True. - # Wait, user said: "Das soll auch hochkommen, selbst wenn in den User/Systemeinstellungen debugging aus ist." (Even if debug is off). - # "Wenn der User es dann nach dem Start manuell ausschaltet, soll es auch aus bleiben" (If user turns off AFTER start, keep off). - # This implies a runtime override: Always ON at startup for Dev, unless... "until man die anwendung neustartet"? - # "bis man die anwendung neustartet" -> "until one restarts application". - # Logic: Force ON at startup always for Dev? - # "Wenn der User es dann nach dem Start manuell ausschaltet, soll es auch aus bleiben, bis man die anwendung neustartet" - # --> If I turn it off, it stays off UNTIL restart. So restart -> ON again. - # So: ALWAYS ON at startup for Dev. - # But "bei beta und stable ... wie bisher". - - # Simple Logic: For Dev/Nightly, Runtime Debug = True. - # switchcraft.services.addon_service or config needs to know. - # We can just set the config in memory? Or set the preference? - # If we set preference, it saves to file. - # If we just open the window... - # The Debug Console is likely opened by SettingsView checking config or App checking config. - # Let's override the in-memory config to True on startup. (But not save it if we don't want to mess up user pref?) - # SwitchCraftConfig is a singleton handling file I/O? - # Let's just set the user preference to True. Ideally we wouldn't persist it if user wants it "off by default" but we are forcing it "on by default" for dev. - # But "Always ON at startup" means we overwrite "Off". - + # Auto-enable debug console for dev/nightly builds + # User can disable during session, but it will be re-enabled on next startup + from switchcraft import __version__ + if "dev" in __version__.lower() or "nightly" in __version__.lower(): SwitchCraftConfig.set_user_preference("ShowDebugConsole", True)
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/switchcraft/assets/lang/de.json(2 hunks)src/switchcraft/assets/lang/en.json(2 hunks)src/switchcraft/gui/app.py(11 hunks)src/switchcraft/gui/views/settings_view.py(5 hunks)src/switchcraft/services/addon_service.py(7 hunks)src/switchcraft/services/sync_service.py(1 hunks)src/switchcraft/utils/config.py(1 hunks)switchcraft.spec(4 hunks)tests/test_addons.py(1 hunks)tests/test_config.py(1 hunks)tests/test_phase7.py(1 hunks)tests/test_winget.py(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/switchcraft/assets/lang/de.json
- src/switchcraft/services/sync_service.py
🧰 Additional context used
🧬 Code graph analysis (4)
tests/test_config.py (1)
src/switchcraft/utils/config.py (1)
set_user_preference(105-136)
tests/test_winget.py (2)
src/switchcraft/cli/commands.py (1)
winget(94-96)src/switchcraft_winget/utils/winget.py (1)
WingetHelper(9-193)
tests/test_phase7.py (1)
src/switchcraft_winget/utils/winget.py (1)
search_by_name(13-32)
src/switchcraft/gui/views/settings_view.py (3)
src/switchcraft/services/sync_service.py (4)
find_sync_gist(31-55)get_backup_metadata(144-159)sync_down(179-193)sync_up(162-176)src/switchcraft/gui/app.py (2)
_show_restart_countdown(214-259)_run(290-307)src/switchcraft/gui/views/analyzer_view.py (1)
_run(116-131)
🪛 GitHub Actions: CI Orchestrator
src/switchcraft/services/addon_service.py
[error] 268-268: F841 Local variable original_suffix_len is assigned to but never used. Remove assignment to unused variable.
🪛 GitHub Actions: PR Assistant (The Janitor)
src/switchcraft/services/addon_service.py
[error] 268-268: F841 Local variable original_suffix_len is assigned to but never used. Remove assignment.
🪛 RuboCop (1.81.7)
switchcraft.spec
[fatal] 30-30: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 43-43: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 45-45: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 49-49: dynamic constant assignment
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 52-52: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 53-53: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 54-54: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 60-60: unexpected token kIN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 62-62: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 66-66: unexpected token kIN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
🔇 Additional comments (33)
src/switchcraft/assets/lang/en.json (1)
281-284: LGTM! Cloud backup localization keys are clear and appropriate.The new cloud backup-related keys are well-named and the text is grammatically correct and user-friendly.
src/switchcraft/services/addon_service.py (9)
27-27: Good addition of.resolve()for path normalization.These changes ensure paths are fully resolved (symlinks followed, relative components removed), improving robustness across different environments.
Also applies to: 33-33, 38-38
49-50: Simplified installation check is more reliable.Removing the dev/source conditional and always verifying
__init__.pyexistence makes the check consistent across environments.
56-59: Correct conversion to string for sys.path operations.
sys.pathrequires string entries, not Path objects. This change prevents potential issues with path comparisons and lookups.
64-80: Verify the pre-check doesn't mask import issues.The added pre-check at line 64 returns
Noneif the addon isn't installed, which is reasonable. However, the broadened exception handling (line 78:except Exception) now catches all errors uniformly. This could hide legitimate bugs like syntax errors in addon code or issues unrelated to missing dependencies.Consider whether distinguishing between
ModuleNotFoundError(missing deps) and other exceptions (addon bugs) would provide better diagnostics.
100-104: Dev mode installation check logic looks correct.The change from "always assume present" to "conditionally verify" makes dev mode behavior more accurate and explicit.
222-236: New public API provides a clean interface for manual installs.The
install_addon_from_zipmethod appropriately delegates to the enhanced extraction logic with auto-detection enabled, making manual addon installation straightforward.
251-273: Cross-platform path normalization handles Windows-created zips correctly.The normalization of backslashes to forward slashes (line 252) ensures consistent behavior regardless of the platform that created the zip archive. The detection logic correctly identifies addon packages and computes the source prefix.
322-327: Path normalization for root-level package detection is correct.Creating a normalized view of all zip entries (line 322) for consistent matching is the right approach, especially when handling zips created on different platforms.
342-349: Consistent normalization of member filenames during extraction.Normalizing
member.filename(line 343) before comparison withsource_prefixensures the extraction logic works correctly with Windows-style paths in zip archives.tests/test_addons.py (1)
65-96: Excellent test coverage for Windows-style zip paths.This test validates the cross-platform path normalization implemented in
_extract_and_install_zip. Creating a mock zip with backslash paths and verifying successful extraction ensures the fix works correctly for Windows-created archives.src/switchcraft/utils/config.py (2)
117-119: Correct ordering: bool check before int.The bool check before int is correct since
boolis a subclass ofintin Python. If the order were reversed, boolean values would incorrectly match theisinstance(value, int)check.
126-127: LGTM: Explicit string coercion for REG_SZ.The explicit
str(value)conversion ensures that non-string types are properly converted before writing to the registry as REG_SZ.tests/test_winget.py (1)
3-3: LGTM! Import path correctly updated.The import path change aligns with the WingetHelper relocation to the
switchcraft_wingetmodule.tests/test_phase7.py (1)
11-25: LGTM! Test correctly validates the new GitHub-based URL format.The import path and URL expectations are correctly updated to reflect the new WingetHelper module location and GitHub manifest URL structure. The test properly validates that:
- Package ID "FaserF.SwitchCraft" generates the correct GitHub URL
- Case-insensitive searches return the same URL (since the mock provides consistent results)
switchcraft.spec (6)
20-27: LGTM! Explicit hidden imports improve build robustness.The explicit enumeration of hidden imports, including py7zr and its submodules, is a solid approach for ensuring reproducible builds. The comment clearly documents that addons are downloaded separately at runtime.
30-33: LGTM! Error handling prevents build failures.The try/except wrapper around
collect_submoduleswith a clear warning message is excellent defensive programming that ensures the build can continue even if submodule collection fails.
69-71: LGTM! Deduplication ensures clean imports list.The set-based deduplication is a standard Python pattern. Note that this loses the original order of imports, though this is unlikely to matter for PyInstaller's hidden imports.
89-90: Verify that bundled py7zr is accessible to downloaded addons at runtime.The comment indicates py7zr is needed by addons at runtime. While py7zr is bundled in hidden_imports (lines 23-26), ensure that separately downloaded addons can successfully import and use the bundled py7zr modules when the executable runs.
Consider manual verification or adding integration tests that:
- Run the built executable
- Download an addon that requires py7zr (e.g., switchcraft_advanced)
- Verify the addon can successfully import and use py7zr for 7z extraction
112-113: Verifyconsole=Trueis intentional for this build.The console is now force-enabled for debugging. While the comment indicates this should be set to False for release builds, confirm that:
- This setting is appropriate for the CI build process, and
- There's a mechanism (different spec file or build parameter) to produce release builds with
console=False.Having the console enabled in production releases of a GUI application would result in both a console window and the GUI window appearing, which may not be the desired user experience.
106-106: No action required—CI/CD workflows have already been updated to reference the correct executable name.The executable name change from "SwitchCraft" to "SwitchCraft-windows" at line 106 (switchcraft.spec) is properly reflected in the CI/CD workflows. The release.yml workflow correctly references
SwitchCraft-windows.exefor build artifacts, signing operations, and asset uploads. No outdated references to the old executable name exist in the workflow configuration.src/switchcraft/gui/views/settings_view.py (5)
15-17: LGTM: Constructor updated to accept app instance.The app parameter is correctly added and stored for use in restart countdown flows throughout the view.
148-177: LGTM: Cloud sync down with timestamp and restart flow.The sync down logic correctly:
- Retrieves backup metadata and formats timestamp for user display
- Schedules messagebox interactions on the main thread via
self.after(0, ...)- Triggers app restart countdown when available, with appropriate fallback
185-225: LGTM: Post-login cloud backup check with proper threading.The background thread
_check()correctly schedules all UI operations on the main thread:
ask_import_login()andask_create()are posted viaself.after(0, ...)- All messagebox interactions occur on the main thread
- Restart countdown is triggered appropriately
This properly addresses the threading concern raised in the previous review.
365-368: LGTM: Import settings with restart flow.Correctly triggers app restart countdown after successful settings import, with appropriate fallback messaging.
986-989: LGTM: Addon installation with restart flow.Both addon installation paths consistently use the app restart countdown when available, with appropriate fallback messaging.
Also applies to: 1005-1009
src/switchcraft/gui/app.py (7)
87-98: LGTM: Enhanced Winget addon load error tracking.Properly distinguishes between:
- Addon not installed (normal case, no error)
- Addon installed but import failed (captured as error for user notification)
The use of
logger.exception()at line 96 correctly logs full traceback for debugging.
148-156: LGTM: Startup checks scheduled appropriately.Error checking and cloud backup are scheduled with reasonable delays to allow UI initialization to complete first.
261-267: LGTM: Initialization error display.Safely checks for load errors and displays user-friendly error dialog with logging for debugging.
268-310: LGTM: Automatic weekly cloud backup with change detection.The implementation correctly:
- Checks authentication and config before proceeding
- Uses MD5 hash with sorted keys for stable change detection
- Runs in background thread to avoid blocking UI
- Updates timestamp even when no changes are detected (prevents repeated checks)
The weekly interval (604800 seconds) and opt-in default (True) align with user experience goals.
223-245: LGTM: Enhanced restart logic with environment cleanup.Correctly:
- Cleans PyInstaller temp directory variables (
_MEIPASS,_MEIPASS2) to prevent new process from using stale temp directories- Derives appropriate working directory based on frozen state
- Adds helpful logging for debugging restart issues
- Passes cleaned environment and cwd to subprocess
798-798: LGTM: View constructors updated with app instance.Correctly passes the app instance to view constructors, enabling centralized restart flows and other app-level interactions.
Also applies to: 809-809, 830-830
877-891: LGTM: Enhanced main function with proper error handling.Correctly:
- Accepts optional splash parameter and safely closes it after app initialization
- Writes crash logs to file for debugging
- Fixed the bare except and multiple statements issue from previous review (now uses
except Exception:on separate line)
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
src/switchcraft/utils/config.py (1)
128-129: Consider validating integer range as well.Currently, only float values are validated for the REG_DWORD range. If a caller passes a large integer directly (e.g.,
2**40), it bypasses validation and may causeSetValueExto fail with an unclear error.For consistency and robustness, consider applying the same range check:
🔎 Suggested validation for integers
elif isinstance(value, int): value_type = winreg.REG_DWORD + # Validate range for REG_DWORD (unsigned 32-bit: 0 to 4294967295) + if value < 0 or value > 0xFFFFFFFF: + raise ValueError(f"Registry value '{value_name}' out of range for REG_DWORD: {value}")tests/test_config.py (1)
98-118: Test coverage looks good.The test verifies that float values are converted to int and stored as REG_DWORD. The mock setup is correct.
Optional enhancement: Consider also verifying the actual converted value to ensure rounding works as expected:
🔎 Optional verification of converted value
# Verify SetValueEx was called with int, not float self.winreg_mock.SetValueEx.assert_called_once() call_args = self.winreg_mock.SetValueEx.call_args[0] # SetValueEx(key, name, reserved, type, value) # Index 3 is type, Index 4 is value self.assertEqual(call_args[3], self.winreg_mock.REG_DWORD) self.assertIsInstance(call_args[4], int) +# Verify the value is close to the original (within 1 due to rounding) +self.assertAlmostEqual(call_args[4], now, delta=1)src/switchcraft/services/addon_service.py (1)
263-270: Remove incomplete/debugging comments.Lines 263-270 contain comments that suggest concerns about the path normalization approach but don't lead to any implementation. These appear to be leftover debugging notes or incomplete thoughts from development. Since the logic on line 262 correctly calculates the prefix using normalized paths, these comments can be removed for clarity.
🔎 Proposed cleanup
suffix = f"{part}/__init__.py" source_prefix = f_norm[:-len(suffix)] - # Maintain original slash style for the zip member lookup or just assume normalized? - # z.open(member) handles the object, but we need the prefix valid for matching string - # Ideally we map back to original filename, but zipfile might just handle / - # Actually, member.filename is the source of truth. - # Let's find the ORIGINAL prefix - - # If separators differ, len might match but content differs. - # Safe bet: Use index from original 'f' if we can find the pattern breakswitchcraft.spec (1)
20-27: Consider clarifying the addon dependency strategy.The comment on Lines 20–21 states that addon modules are not bundled and are downloaded at runtime. However, Line 26 includes
py7zr(a dependency of theswitchcraft_advancedaddon) in the main executable'shidden_imports. This approach increases the base executable size and may cause confusion for future maintainers.Options to consider:
Keep as-is but clarify: Update the comment to explicitly state that common or critical addon dependencies (like py7zr) are pre-bundled for reliability:
# Note: Addon modules (switchcraft_winget, switchcraft_ai, etc.) are NOT bundled. # They are downloaded separately at runtime. # However, commonly-needed addon dependencies (py7zr) are pre-bundled # to reduce runtime issues and ensure 7z extraction works out-of-the-box.Move dependency to addon: Let each addon manage its own dependencies (including py7zr) so the main executable remains lean. This requires addons to bundle or install their dependencies on first run.
Lazy-load py7zr: Only import and use py7zr when the addon is actually loaded, with a fallback or user prompt if it's missing.
The current approach is valid if pre-bundling common dependencies is intentional, but clarifying the rationale will help maintainability.
src/switchcraft/gui/app.py (1)
87-98: Well-designed error tracking for addon load failures.The distinction between addon not installed vs. installed-but-failed is handled correctly, and the exception logging provides good diagnostics. The error is appropriately stored for user notification later.
💡 Optional: Consider enhancing the user-facing error message
The message on line 94 could be more actionable for users:
- self.winget_load_error = "Import failed (returned None). Check log for details." + self.winget_load_error = "Addon failed to load. This may indicate a corrupted installation or missing dependencies. Try reinstalling from the Addon Manager."
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/switchcraft/assets/lang/en.json(2 hunks)src/switchcraft/gui/app.py(11 hunks)src/switchcraft/services/addon_service.py(7 hunks)src/switchcraft/utils/config.py(1 hunks)switchcraft.spec(4 hunks)tests/test_config.py(1 hunks)tests/test_winget.py(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/test_winget.py
🧰 Additional context used
🧬 Code graph analysis (2)
tests/test_config.py (1)
src/switchcraft/utils/config.py (1)
set_user_preference(105-143)
src/switchcraft/gui/app.py (2)
src/switchcraft/services/addon_service.py (3)
AddonService(13-405)import_addon_module(62-80)is_addon_installed(41-50)src/switchcraft/gui/views/missing_addon_view.py (1)
MissingAddonView(8-70)
🪛 RuboCop (1.81.7)
switchcraft.spec
[fatal] 30-30: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 45-45: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 47-47: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 50-50: dynamic constant assignment
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 56-56: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 57-57: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 58-58: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 63-63: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 73-73: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 74-74: unexpected token kIN
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 74-74: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build / Build Installer (windows-latest)
🔇 Additional comments (18)
src/switchcraft/utils/config.py (2)
116-127: Well done addressing the previous review concerns!The float-to-REG_DWORD conversion now properly:
- Uses
round()instead of truncation for better precision handling- Validates the unsigned 32-bit range (0 to 4,294,967,295)
- Raises a clear ValueError for out-of-range values
- Handles bool before float/int (important since bool is a subclass of int)
This implementation directly addresses the concerns raised in the previous review.
140-141: Good error propagation design.Explicitly re-raising
ValueErrorensures that validation errors (like out-of-range values) are propagated to the caller rather than being logged and swallowed. This allows callers to handle validation failures appropriately.tests/test_config.py (3)
121-144: Excellent edge case coverage!The test thoroughly validates all critical float conversion scenarios:
- Negative floats correctly raise
ValueError- Rounding behavior is verified (123.99 → 124)
- Large values exceeding 32-bit range correctly raise
ValueErrorThis provides strong confidence in the implementation's robustness.
148-159: LGTM - Boolean True conversion validated.The test correctly verifies that
Trueis converted to1and stored asREG_DWORD.
162-177: LGTM - Boolean False conversion validated.The test correctly verifies that
Falseis converted to0and stored asREG_DWORD, completing the boolean conversion test coverage.src/switchcraft/services/addon_service.py (8)
27-38: LGTM! Path resolution improves consistency.The addition of
.resolve()ensures absolute, normalized paths are returned in all branches (portable, APPDATA, and dev modes), which improves path handling consistency across the application.
49-50: LGTM! Simplified validation is more consistent.Checking for
__init__.pyin all cases ensures consistent package validation regardless of environment (dev vs. frozen).
56-59: LGTM! Explicit string conversion for sys.path.Converting the Path object to string before sys.path operations is the correct approach, as sys.path expects string entries.
74-80: Verify that catching all exceptions is intentional.The change from
ImportErrorto genericExceptioncatches all failures during module import. While this provides robustness against various dependency issues (e.g., missing py7zr), it may also mask unexpected errors like syntax errors or initialization failures in the addon code itself.Consider whether more specific exception handling or additional diagnostics would help debugging addon issues.
100-104: LGTM! Clearer dev/source mode handling.The updated logic correctly validates addon presence in dev mode using
is_addon_installedand provides helpful logging for troubleshooting.
222-236: LGTM! Well-designed API for manual addon installation.The method cleanly handles both file paths and byte content, with appropriate error handling and auto-detection logic for user-provided zips.
321-327: LGTM! Consistent path normalization for root-level detection.Normalizing all zip entry names before checking ensures cross-platform compatibility when detecting addon packages at the root level.
342-363: LGTM! Robust extraction with security checks.The extraction loop properly normalizes paths, filters MACOSX artifacts, performs security validation (ensuring files belong to the expected package), and handles both files and directories correctly.
src/switchcraft/gui/app.py (5)
148-156: LGTM! Well-structured startup task scheduling.The timing is appropriately staggered to avoid blocking the UI, and the separation into dedicated methods improves maintainability.
223-246: Excellent restart hardening.The environment cleanup prevents PyInstaller temp directory issues, the cwd logic correctly handles both frozen and script execution modes, and the logging provides valuable diagnostics. This is a robust implementation.
312-346: LGTM! Tab management with helpful diagnostics.The debug logging provides excellent visibility into tab state changes, and the reordering logic correctly achieves the desired tab sequence. The approach is clean and maintainable.
844-881: Well-implemented startup and crash handling.The splash parameter integration, automatic debug console for development builds, and multi-layered crash logging/reporting provide excellent developer experience and user communication.
848-848: Clarify version check consistency across build types.Three version checks exist with different criteria:
- Lines 174 & 463:
"dev"or"beta"(addon auto-install, version banner)- Line 848:
"dev"or"nightly"(debug console)Confirm whether this distinction is intentional (different features for different build types) or if these should use consistent version identifiers across the codebase.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/switchcraft/services/addon_service.py (1)
113-113: Pin requests to a minimum secure version.The
requestslibrary is specified without a version constraint inpyproject.toml, which means vulnerable versions could be installed. Requests releases prior to 2.32.4 may leak .netrc credentials to third parties for specific maliciously-crafted URLs. Change"requests"to"requests>=2.32.4"to ensure a secure version is used for addon downloads.
♻️ Duplicate comments (1)
src/switchcraft/services/addon_service.py (1)
260-260: Fix typo in comment (previously flagged but not addressed)."Calulcate" should be "Calculate".
🔎 Proposed fix
- # Calulcate prefix + # Calculate prefix
🧹 Nitpick comments (1)
src/switchcraft/services/addon_service.py (1)
74-80: Consider whether catching all exceptions is necessary.While catching
Exceptionprovides robustness for addon loading, it may mask unexpected errors likeSyntaxErrororAttributeErrorin addon code, making debugging harder. If the primary concern is missing dependencies (per the comment), consider catching(ImportError, ModuleNotFoundError)specifically and letting other exceptions propagate with a clear traceback.🔎 Alternative: More targeted exception handling
- # Gracefully handle import failures (e.g., missing dependencies like py7zr) try: import importlib return importlib.import_module(full_name) - except Exception as e: - logger.error(f"Failed to import addon module {full_name}: {e}") + except (ImportError, ModuleNotFoundError) as e: + logger.error(f"Failed to import addon module {full_name} (likely missing dependency): {e}") return None + except Exception as e: + logger.exception(f"Unexpected error importing addon module {full_name}: {e}") + raise
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/switchcraft/services/addon_service.py(7 hunks)
🔇 Additional comments (6)
src/switchcraft/services/addon_service.py (6)
27-27: LGTM: Path resolution improvements.The addition of
.resolve()ensures absolute paths are returned and symlinks are properly resolved across all modes (portable, APPDATA, dev). This improves consistency and reliability.Also applies to: 33-33, 38-38
49-50: LGTM: Simplified and robust package validation.Unconditionally checking for
__init__.pyis the correct approach to validate Python packages, removing fragile conditional logic.
56-59: LGTM: Correct sys.path handling.Converting the Path object to a string before sys.path operations is the correct approach, as sys.path expects string entries.
100-104: LGTM: Dev mode check is appropriate.The early return for dev/source mode with clear logging is correct. Preventing download attempts when running from source is the right behavior.
251-269: LGTM: Robust cross-platform ZIP handling.The path normalization logic (converting backslashes to forward slashes) properly handles Windows-created ZIP files. The auto-detection of
switchcraft_*packages with__init__.pyvalidation is sound, and the prefix calculation is correct.
319-325: LGTM: Comprehensive and consistent path normalization.The normalization is applied consistently across root-level detection and member extraction. The approach properly handles ZIP files created on different platforms and correctly filters out
__MACOSXentries.Also applies to: 340-347
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/switchcraft/gui/app.py (1)
217-251: Fix comment discrepancy about process creation flags.Line 238 comments mention "DETACHED_PROCESS flag" but line 240 uses
CREATE_NEW_CONSOLE = 0x00000010. The actual constant for DETACHED_PROCESS is0x00000008. Either correct the comment or use the intended flag.If you want the process to survive parent termination without creating a console window, use:
DETACHED_PROCESS = 0x00000008Otherwise, update the comment to match the current implementation:
- # Use Popen with DETACHED_PROCESS flag on Windows to ensure it survives parent death + # Use Popen with CREATE_NEW_CONSOLE flag on Windows to ensure it survives parent death🔎 Additional context
The rest of the restart logic is solid: environment cleanup prevents PyInstaller issues, proper
cwdhandling, and good error logging.src/switchcraft/services/addon_service.py (1)
344-356: Critical: Zip slip vulnerability - path traversal attack possible.The security check at Line 344 only verifies
rel_path.startswith(detected_pkg_name), but a malicious zip could contain paths like"switchcraft_advanced/../../../etc/passwd". This would pass the check but the..components would resolve outsideaddon_rootwhen writing files at lines 353-355, allowing arbitrary file writes (zip slip attack).🔎 Proposed fix to prevent path traversal
Add a check after Line 348 to ensure the resolved path remains within
addon_root:# Extract to addon_root / rel_path -> addon_root / switchcraft_advanced / mod.py full_target = addon_root / rel_path + + # Security: Prevent zip slip - ensure resolved path is within addon_root + try: + full_target.resolve().relative_to(addon_root.resolve()) + except ValueError: + logger.warning(f"Skipping file outside addon root (zip slip attempt): {rel_path}") + continue if member.is_dir(): full_target.mkdir(parents=True, exist_ok=True)This ensures that even with
..components inrel_path, the resolved target must still be within the addon directory.
♻️ Duplicate comments (2)
src/switchcraft/gui/app.py (2)
317-351: Tab reordering still loses History view state.The implementation continues to delete and recreate the History tab (lines 331, 341-342) when enabling Winget, which loses any runtime state in the History view as previously noted.
268-315: Handle the False return case from sync_up().The exception handling is good (lines 309-312), but if
SyncService.sync_up()returnsFalse(line 301), the timestamps won't update. This means failed syncs will retry every 7 days, which may be undesirable for persistent failures.Consider handling the
Falsecase explicitly as suggested in the previous review:🔎 Proposed enhancement
if current_hash != last_hash: logger.info("Changes detected since last backup. Performing auto-backup...") if SyncService.sync_up(): logger.info("Auto-backup successful.") SwitchCraftConfig.set_user_preference("LastCloudBackup", now) SwitchCraftConfig.set_user_preference("LastBackupHash", current_hash) + else: + logger.warning("Auto-backup failed (returned False).") + # Still update timestamp to avoid continuous retries + SwitchCraftConfig.set_user_preference("LastCloudBackup", now)Based on past review comments suggesting similar handling.
🧹 Nitpick comments (1)
switchcraft.spec (1)
19-27: Maintainability concern: Fixed list may become stale.The switch from dynamic collection to a fixed list addresses the redundancy issue from past reviews. However, this approach requires manual updates whenever new modules are added to the codebase. If a developer adds a new module but forgets to update this list, the PyInstaller build may silently exclude it, leading to runtime import errors.
Consider keeping a hybrid approach: maintain the explicit list for critical dependencies like py7zr, but continue to use the dynamic collection (lines 29-33) as a safety net to catch newly added switchcraft modules.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/switchcraft/gui/app.py(11 hunks)src/switchcraft/services/addon_service.py(7 hunks)src/switchcraft/utils/config.py(3 hunks)switchcraft.spec(4 hunks)tests/test_config.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/test_config.py
- src/switchcraft/utils/config.py
🧰 Additional context used
🧬 Code graph analysis (1)
src/switchcraft/gui/app.py (2)
src/switchcraft/services/addon_service.py (2)
import_addon_module(62-80)is_addon_installed(41-50)src/switchcraft/gui/views/missing_addon_view.py (1)
MissingAddonView(8-70)
🪛 RuboCop (1.81.7)
switchcraft.spec
[fatal] 30-30: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build / Build Installer (windows-latest)
🔇 Additional comments (19)
switchcraft.spec (3)
29-33: Good resolution of past redundancy concern.The try/except wrapper with graceful warning and removal of the manual walk code (previously lines 56-77) successfully addresses the redundancy issue flagged in past reviews. This approach is cleaner and avoids duplicate module collection.
77-78: Excellent resolution of console flag concern.The environment variable approach perfectly addresses the past review comment about the hardcoded
console=True. This allows debug builds viaSWITCHCRAFT_DEBUG_CONSOLE=1while defaulting to a console-free release build. Well done!
84-84: Update icon format or ensure Pillow is available.Modern PyInstaller can auto-convert PNG to ICO if Pillow is installed, but this is not guaranteed. For reliable Windows builds, convert the PNG to
.icoformat or explicitly add Pillow to the project dependencies. PyInstaller supports.icoand.exeicon file formats; PNG and JPG formats need conversion to.icobefore use.src/switchcraft/gui/app.py (7)
87-98: LGTM! Winget load error tracking is well-designed.The error tracking correctly distinguishes between addon not being installed versus load failures when installed. The exception logging and error message capture provide good diagnostics for users.
148-156: LGTM! Startup task scheduling is well-staggered.The timing delays (1.5s, 4s, 6s) appropriately stagger initialization checks to avoid blocking the UI during startup.
261-267: LGTM! Initialization error reporting is clear and user-friendly.The method safely checks for load errors and provides clear messaging to users with appropriate logging.
803-803: LGTM! Constructor signature updates are correct.The
MissingAddonViewinstantiation correctly passesself(the App instance) as the second parameter, matching the updated constructor signature shown in the related snippets.
814-819: LGTM! SettingsView signature update is correct.The
SettingsViewinstantiation correctly passesself(the App instance) as the second parameter, consistent with the updated constructor signature mentioned in the AI summary.
835-835: LGTM! Second MissingAddonView instantiation is correct.The Winget addon's
MissingAddonViewcorrectly passesselfas the app parameter, consistent with the updated constructor signature.
849-888: LGTM! Main function robustness improvements are well-implemented.The changes enhance startup robustness:
- Auto-enabling debug console for dev/nightly builds (lines 851-857) improves troubleshooting
- Splash closing is safely handled (lines 860-864)
- Crash logging is properly implemented (lines 869-873) ✅ This addresses the previous pipeline failure
- Emergency error dialog provides better user experience on fatal errors (lines 875-883)
The exception handling at line 872 is now correctly implemented with
except Exception:and proper formatting, resolving the previous linting errors (E722, E701).src/switchcraft/services/addon_service.py (9)
27-27: LGTM: Path resolution improvements.Adding
.resolve()ensures absolute paths are returned in all modes (portable, APPDATA, and dev), which improves path consistency and prevents issues with relative path resolution.Also applies to: 33-33, 38-38
49-50: LGTM: Consistent validation.Always checking for
__init__.pyensures robust validation that the addon is a valid Python package, regardless of execution mode.
56-59: LGTM: Correct sys.path handling.Converting
addon_dirto string before adding tosys.pathis necessary sincesys.pathexpects string entries, not Path objects.
74-80: LGTM: Improved error handling.Broadening exception handling to catch both
ImportErrorandModuleNotFoundError(though the latter is a subclass of the former) and logging at error level is appropriate for import failures.
100-104: LGTM: Dev mode handling.The dev mode logic correctly verifies addon presence in source and returns appropriate results without attempting downloads.
221-236: LGTM: Clean public API.The new
install_addon_from_zipmethod provides a clean interface for manual addon installation from local files or bytes, with appropriate error handling.
251-263: LGTM: Robust auto-detection.The auto-detection logic properly normalizes paths for cross-platform compatibility and correctly identifies switchcraft packages by finding
__init__.pymarkers. The prefix calculation is correct.
314-319: LGTM: Root-level package detection.The fallback logic for root-level packages (common in release assets) is correct and uses consistent path normalization.
340-341: LGTM: macOS metadata filtering.The check correctly filters out
__MACOSXmetadata entries after prefix removal, handling both empty relative paths and macOS-specific files.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/switchcraft/gui/app.py (1)
321-354: Tab reordering still loses History view state.The tab reordering logic continues to delete and recreate the History tab (lines 335, 345-347), which will lose any runtime state as previously noted. The past review comment's concern remains unaddressed.
🧹 Nitpick comments (1)
src/switchcraft/gui/app.py (1)
855-861: Consider documenting the debug console auto-enable behavior.The auto-enable of debug console for dev/nightly builds overrides user preference, which is reasonable for troubleshooting but may surprise users. Consider adding a log message or comment explaining this behavior.
🔎 Suggested documentation addition
if "dev" in __version__.lower() or "nightly" in __version__.lower(): # Force Debug Console ON for Dev/Nightly builds at startup for better troubleshooting. # This overrides user preference for the session but respects if user manually closes/disables it later. + logger.info("Dev/Nightly build detected: Auto-enabling debug console for troubleshooting.") SwitchCraftConfig.set_user_preference("ShowDebugConsole", True)
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/switchcraft/gui/app.py(11 hunks)src/switchcraft/services/addon_service.py(7 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build / Build Installer (windows-latest)
🔇 Additional comments (14)
src/switchcraft/gui/app.py (8)
87-98: LGTM! Solid error tracking for addon loading.The winget addon load error tracking properly distinguishes between "addon not installed" and "addon installed but failed to load", with appropriate exception logging. This enables better error reporting to users via
_check_init_errors.
148-156: LGTM! Well-timed initialization checks.The scheduled checks for initialization errors and cloud backup are appropriately delayed to avoid blocking startup.
261-267: LGTM! Clear error reporting to users.The initialization error check provides clear feedback when addons fail to load, improving the user experience.
268-319: LGTM! Exception handling properly implemented.The weekly cloud backup logic is well-designed with hash-based change detection and proper exception handling. The timestamp is correctly updated even on failure to prevent continuous retry loops.
Note: This addresses the previous review comment requesting exception handling in the cloud backup thread.
223-252: LGTM! Robust restart logic for PyInstaller environments.The restart implementation properly handles PyInstaller-specific concerns:
- Cleans
_MEIPASSvariables to prevent stale temp directory usage- Uses
CREATE_NEW_CONSOLEflag on Windows for process independence- Correctly determines working directory for both frozen and script modes
853-868: LGTM! Splash integration and improved error handling.The main function now properly handles splash window closing and includes the ability to receive a splash parameter for clean GUI initialization flow.
873-877: LGTM! Exception handling properly fixed.The bare except has been replaced with a specific
Exceptiontype, and the statements are properly separated on different lines. This addresses the pipeline failures mentioned in previous review comments.Note: This addresses the previous review comment about E722 and E701 linting errors.
807-807: All constructor signature calls are correctly aligned with view definitions. No issues found.
- Line 807: MissingAddonView receives (parent, app, addon_id, addon_name, description) — matches signature
- Line 818: SettingsView receives (parent, app, show_update_callback, intune_service, on_winget_toggle) — matches signature
- Line 839: MissingAddonView receives (parent, app, addon_id, addon_name, description) — matches signature
src/switchcraft/services/addon_service.py (6)
27-27: LGTM: Path resolution improves consistency.The
.resolve()calls ensure addon directories are always absolute and symlink-free, preventing potential issues with relative path handling throughout the addon system.Also applies to: 33-33, 38-38
49-59: LGTM: Validation and sys.path handling are correct.
- The stricter
__init__.pyvalidation ensures consistent package detection across all modes.- Converting
addon_dirto string before sys.path operations is necessary and correct, assys.pathexpects string entries.
64-65: LGTM: Import handling is more robust.
- The pre-installation check (lines 64-65) prevents unnecessary work when an addon isn't installed.
- Enhanced exception handling (lines 78-80) now catches both
ImportErrorandModuleNotFoundError, and appropriately logs as error rather than warning since addon import failure is significant.Also applies to: 74-80
100-104: LGTM: Dev mode logic is clearer.The explicit check for addon presence in source mode with appropriate logging makes the behavior clear and provides better feedback when addons are missing from the development environment.
251-262: LGTM: Path normalization improves cross-platform compatibility.The normalization of backslashes to forward slashes (lines 251-253, 314-315) handles Windows-created ZIP files defensively, ensuring consistent path matching across platforms. The ZIP specification mandates forward slashes, but this defensive approach prevents issues with non-compliant ZIP creation tools.
Also applies to: 313-319
334-356: Excellent security improvement: Zip slip prevention implemented correctly.The addition of path traversal protection (lines 350-356) is a critical security enhancement:
- Resolves the full extraction path before validation
- Uses
relative_to()to verify extracted files remain withinaddon_root- Catches
ValueErrorto block malicious paths attempting directory traversalCombined with path normalization (line 335) and package boundary validation (lines 343-345), this provides comprehensive protection against malicious ZIP archives.
Description
Please include a summary of the change and which issue is fixed.
Fixes # (issue)
Type of change
How Has This Been Tested?
Please describe the tests that you ran to verify your changes.
Checklist:
Summary by CodeRabbit
New Features
Bug Fixes / Improvements
Documentation
Tests
Chores
Localization
✏️ Tip: You can customize this high-level summary in your review settings.