py-wlcli 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_wlcli-0.8.0.dist-info/METADATA +139 -0
- py_wlcli-0.8.0.dist-info/RECORD +320 -0
- py_wlcli-0.8.0.dist-info/WHEEL +4 -0
- py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
- py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
- wl_cli/__init__.py +27 -0
- wl_cli/__main__.py +130 -0
- wl_cli/capabilities.py +79 -0
- wl_cli/cc/__init__.py +5 -0
- wl_cli/cc/checks.py +1019 -0
- wl_cli/cc/cli.py +370 -0
- wl_cli/cc/mutate.py +1350 -0
- wl_cli/cli_click.py +156 -0
- wl_cli/commands/__init__.py +84 -0
- wl_cli/commands/base.py +80 -0
- wl_cli/commands/buildcommands/__init__.py +1 -0
- wl_cli/commands/buildcommands/build.py +71 -0
- wl_cli/commands/buildcommands/build_utils.py +47 -0
- wl_cli/commands/buildcommands/builders/__init__.py +18 -0
- wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
- wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
- wl_cli/commands/buildcommands/builders/environment.py +81 -0
- wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
- wl_cli/commands/buildcommands/builders/stubs.py +58 -0
- wl_cli/commands/buildcommands/dist.py +326 -0
- wl_cli/commands/buildcommands/test.py +89 -0
- wl_cli/commands/clean/__init__.py +1 -0
- wl_cli/commands/clean/build.py +151 -0
- wl_cli/commands/clean/clean.py +294 -0
- wl_cli/commands/clean/clean_utils.py +77 -0
- wl_cli/commands/clean/core.py +236 -0
- wl_cli/commands/clean/dead_code/__init__.py +0 -0
- wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
- wl_cli/commands/clean/dead_code/detector.py +161 -0
- wl_cli/commands/clean/dead_code/fixer.py +176 -0
- wl_cli/commands/clean/dead_code/reporter.py +295 -0
- wl_cli/commands/clean/dead_code/utils.py +0 -0
- wl_cli/commands/clean/lfs.py +215 -0
- wl_cli/commands/clean/python.py +81 -0
- wl_cli/commands/clean/rust.py +91 -0
- wl_cli/commands/clean/utils/__init__.py +34 -0
- wl_cli/commands/clean/venv.py +91 -0
- wl_cli/commands/cleanup.py +73 -0
- wl_cli/commands/click_base.py +79 -0
- wl_cli/commands/config/__init__.py +1 -0
- wl_cli/commands/config/config.py +291 -0
- wl_cli/commands/debug/__init__.py +6 -0
- wl_cli/commands/debug/debug.py +309 -0
- wl_cli/commands/debug/debug_cache.py +86 -0
- wl_cli/commands/debug/debug_subagent.py +156 -0
- wl_cli/commands/debug/output_formatter.py +165 -0
- wl_cli/commands/debug/source_hasher.py +92 -0
- wl_cli/commands/format/__init__.py +13 -0
- wl_cli/commands/format/format_cache_adapter.py +125 -0
- wl_cli/commands/format/format_command.py +197 -0
- wl_cli/commands/format/format_coordinator.py +191 -0
- wl_cli/commands/format/format_executor.py +470 -0
- wl_cli/commands/format/format_outcome.py +44 -0
- wl_cli/commands/format/format_path_handler.py +1016 -0
- wl_cli/commands/format/format_progress.py +62 -0
- wl_cli/commands/format/format_reporter.py +31 -0
- wl_cli/commands/format/python_formatter.py +506 -0
- wl_cli/commands/format/rust_formatter.py +106 -0
- wl_cli/commands/gate/__init__.py +1 -0
- wl_cli/commands/gate/gate_command.py +862 -0
- wl_cli/commands/gate/preflight.py +445 -0
- wl_cli/commands/git/__init__.py +1 -0
- wl_cli/commands/git/config.py +169 -0
- wl_cli/commands/git/git_facts.py +60 -0
- wl_cli/commands/git/git_flow.py +86 -0
- wl_cli/commands/git/hook.py +112 -0
- wl_cli/commands/git/policy.py +216 -0
- wl_cli/commands/git/push_update.py +82 -0
- wl_cli/commands/initenv/__init__.py +1 -0
- wl_cli/commands/initenv/common/__init__.py +23 -0
- wl_cli/commands/initenv/common/check_git_bash.py +78 -0
- wl_cli/commands/initenv/common/error_handler.py +68 -0
- wl_cli/commands/initenv/common/exceptions.py +52 -0
- wl_cli/commands/initenv/components/__init__.py +4 -0
- wl_cli/commands/initenv/components/cargo_sync.py +240 -0
- wl_cli/commands/initenv/components/config_manager.py +106 -0
- wl_cli/commands/initenv/components/directory_utils.py +71 -0
- wl_cli/commands/initenv/components/git/__init__.py +9 -0
- wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
- wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
- wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
- wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
- wl_cli/commands/initenv/components/git/git_utility.py +254 -0
- wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
- wl_cli/commands/initenv/components/git_initializer.py +212 -0
- wl_cli/commands/initenv/components/i18n_manager.py +104 -0
- wl_cli/commands/initenv/components/log_manager.py +241 -0
- wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
- wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
- wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
- wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
- wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
- wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
- wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
- wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
- wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
- wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
- wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
- wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
- wl_cli/commands/initenv/components/rust_initializer.py +145 -0
- wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
- wl_cli/commands/initenv/components/symlink_manager.py +52 -0
- wl_cli/commands/initenv/components/venv_manager.py +118 -0
- wl_cli/commands/initenv/initenv.py +169 -0
- wl_cli/commands/initenv/services/__init__.py +11 -0
- wl_cli/commands/initenv/services/environment_initializer.py +288 -0
- wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
- wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
- wl_cli/commands/lfs/README.md +221 -0
- wl_cli/commands/lfs/__init__.py +5 -0
- wl_cli/commands/lfs/cli.py +106 -0
- wl_cli/commands/lfs/config.py +164 -0
- wl_cli/commands/lfs/hook.py +61 -0
- wl_cli/commands/lfs/initializer.py +531 -0
- wl_cli/commands/lfs/manager.py +95 -0
- wl_cli/commands/lfs/policy.py +165 -0
- wl_cli/commands/lfs/watcher.py +294 -0
- wl_cli/commands/lint/__init__.py +1 -0
- wl_cli/commands/lint/lint_command.py +297 -0
- wl_cli/commands/lint/lint_executor.py +409 -0
- wl_cli/commands/lint/lint_reporter.py +579 -0
- wl_cli/commands/lint/lint_run.py +258 -0
- wl_cli/commands/lint/mypy_config.py +90 -0
- wl_cli/commands/registry.py +103 -0
- wl_cli/commands/self/__init__.py +7 -0
- wl_cli/commands/self/common/__init__.py +8 -0
- wl_cli/commands/self/common/capabilities.py +30 -0
- wl_cli/commands/self/common/config.py +27 -0
- wl_cli/commands/self/common/version_cache.py +85 -0
- wl_cli/commands/self/handlers/__init__.py +19 -0
- wl_cli/commands/self/handlers/base_handler.py +28 -0
- wl_cli/commands/self/handlers/install_handler.py +85 -0
- wl_cli/commands/self/handlers/status_handler.py +66 -0
- wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
- wl_cli/commands/self/handlers/update_handler.py +102 -0
- wl_cli/commands/self/handlers/version_handler.py +16 -0
- wl_cli/commands/self/self_command.py +237 -0
- wl_cli/commands/self/services/__init__.py +15 -0
- wl_cli/commands/self/services/installation_manager.py +187 -0
- wl_cli/commands/self/services/local_installer.py +165 -0
- wl_cli/commands/self/services/pypi_updater.py +278 -0
- wl_cli/commands/self/services/windows_update.py +174 -0
- wl_cli/commands/testcommands/__init__.py +8 -0
- wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
- wl_cli/commands/testcommands/batch_processor.py +278 -0
- wl_cli/commands/testcommands/cache_manager.py +1156 -0
- wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
- wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
- wl_cli/commands/testcommands/progress_manager.py +79 -0
- wl_cli/commands/testcommands/python_env_detector.py +207 -0
- wl_cli/commands/testcommands/report_generator.py +101 -0
- wl_cli/commands/testcommands/single_test_executor.py +250 -0
- wl_cli/commands/testcommands/speed_checker.py +118 -0
- wl_cli/commands/testcommands/test_command_builder.py +165 -0
- wl_cli/commands/testcommands/test_commands.py +786 -0
- wl_cli/commands/testcommands/test_reporter.py +181 -0
- wl_cli/commands/testcommands/test_result_handler.py +96 -0
- wl_cli/commands/testcommands/test_runner.py +1343 -0
- wl_cli/commands/testcommands/test_utils.py +197 -0
- wl_cli/commands/testcommands/utils/__init__.py +17 -0
- wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
- wl_cli/commands/testcommands/utils/env_info.py +36 -0
- wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
- wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
- wl_cli/commands/testcommands/utils/project_root.py +61 -0
- wl_cli/commands/update/__init__.py +9 -0
- wl_cli/commands/update/services/__init__.py +21 -0
- wl_cli/commands/update/services/backup_manager.py +207 -0
- wl_cli/commands/update/services/component_manager.py +83 -0
- wl_cli/commands/update/services/conflict_resolver.py +189 -0
- wl_cli/commands/update/services/vendors_updater.py +85 -0
- wl_cli/commands/update/services/version_tracker.py +127 -0
- wl_cli/commands/update/update_command.py +270 -0
- wl_cli/commands/update/utils/__init__.py +3 -0
- wl_cli/commands/version/__init__.py +9 -0
- wl_cli/commands/version/version_command.py +155 -0
- wl_cli/commands/workflow.py +477 -0
- wl_cli/env/__init__.py +5 -0
- wl_cli/env/cli.py +167 -0
- wl_cli/env_status.py +157 -0
- wl_cli/envelope.py +91 -0
- wl_cli/exceptions.py +85 -0
- wl_cli/lib/__init__.py +3 -0
- wl_cli/lib/rust_utils.py +35 -0
- wl_cli/mcp/__init__.py +6 -0
- wl_cli/mcp/capabilities.py +19 -0
- wl_cli/mcp/cli.py +22 -0
- wl_cli/mcp/gitlab.py +553 -0
- wl_cli/mcp/k8s.py +344 -0
- wl_cli/mcp/protocol.py +159 -0
- wl_cli/platform.py +139 -0
- wl_cli/quality/__init__.py +18 -0
- wl_cli/utils/__init__.py +7 -0
- wl_cli/utils/cache.py +491 -0
- wl_cli/utils/cache_ledger.py +355 -0
- wl_cli/utils/cache_store.py +546 -0
- wl_cli/utils/config/__init__.py +27 -0
- wl_cli/utils/config/config_loader.py +163 -0
- wl_cli/utils/config/core.py +862 -0
- wl_cli/utils/config/defaults.py +142 -0
- wl_cli/utils/config/env_exporter.py +86 -0
- wl_cli/utils/config/env_parser.py +108 -0
- wl_cli/utils/config/validation.py +280 -0
- wl_cli/utils/error_handler.py +165 -0
- wl_cli/utils/file_copier.py +56 -0
- wl_cli/utils/file_operations.py +506 -0
- wl_cli/utils/language_utils.py +122 -0
- wl_cli/utils/lazy_import.py +240 -0
- wl_cli/utils/log_levels.py +35 -0
- wl_cli/utils/log_rotators.py +74 -0
- wl_cli/utils/logging.py +458 -0
- wl_cli/utils/output_context.py +142 -0
- wl_cli/utils/performance/__init__.py +1 -0
- wl_cli/utils/performance/decorators.py +111 -0
- wl_cli/utils/performance/exporter.py +38 -0
- wl_cli/utils/performance/models.py +16 -0
- wl_cli/utils/performance/monitor.py +161 -0
- wl_cli/utils/performance_monitor.py +32 -0
- wl_cli/utils/platform_adapter.py +131 -0
- wl_cli/utils/pre_commit.py +266 -0
- wl_cli/utils/pre_commit_config.py +72 -0
- wl_cli/utils/root.py +521 -0
- wl_cli/utils/root_fallback.py +256 -0
- wl_cli/utils/run_evidence.py +236 -0
- wl_cli/utils/structured_logger.py +18 -0
- wl_cli/utils/structured_logging/__init__.py +19 -0
- wl_cli/utils/structured_logging/config.py +337 -0
- wl_cli/utils/structured_logging/core.py +177 -0
- wl_cli/utils/structured_logging/env.py +56 -0
- wl_cli/utils/structured_logging/filtering.py +75 -0
- wl_cli/utils/structured_logging/filters.py +55 -0
- wl_cli/utils/structured_logging/formatters.py +251 -0
- wl_cli/utils/structured_logging/handlers.py +308 -0
- wl_cli/utils/structured_logging/logger.py +354 -0
- wl_cli/utils/subprocess.py +71 -0
- wl_cli/utils/subprocess_utils.py +260 -0
- wl_cli/utils/test_utils/__init__.py +5 -0
- wl_cli/utils/test_utils/test_filter.py +36 -0
- wl_cli/utils/uv_run.py +79 -0
- wl_cli/utils/uv_tool.py +117 -0
- wl_cli/utils/uv_tree.py +71 -0
- wl_cli/utils/uv_workspace.py +174 -0
- wl_cli/utils/version/__init__.py +15 -0
- wl_cli/utils/version/pypi_version_checker.py +95 -0
- wl_cli/utils/version/version_comparator.py +133 -0
- wl_cli/utils/version/version_detectors.py +312 -0
- wl_cli/utils/version/version_service.py +131 -0
- wl_cli/utils/version/version_updater.py +198 -0
- wl_cli/utils/wl_dir_updater.py +1440 -0
- wl_cli/utils/workspace_detector/__init__.py +28 -0
- wl_cli/utils/workspace_detector/core.py +161 -0
- wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
- wl_cli/utils/workspace_detector/exceptions.py +5 -0
- wl_cli/utils/workspace_detector/types.py +17 -0
- wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
- wl_cli/vendors/config/.codespellrc +2 -0
- wl_cli/vendors/config/dependencies.toml +10 -0
- wl_cli/vendors/config/mypy.ini +62 -0
- wl_cli/vendors/config/pyproject_defaults.toml +28 -0
- wl_cli/vendors/config/template_config.toml +24 -0
- wl_cli/vendors/config/tool_configs.toml +76 -0
- wl_cli/vendors/git/.gitignore +122 -0
- wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
- wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/post-update.sample +8 -0
- wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/pre-commit +45 -0
- wl_cli/vendors/hooks/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/pre-push.sample +60 -0
- wl_cli/vendors/hooks/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
- wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
- wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
- wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
- wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/unix/post-update.sample +8 -0
- wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/unix/pre-commit +190 -0
- wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
- wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/unix/pre-push +417 -0
- wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
- wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/update.sample +128 -0
- wl_cli/vendors/hooks/win/work.bat +63 -0
- wl_cli/vendors/project/.gitignore +90 -0
- wl_cli/vendors/project/.python-version +1 -0
- wl_cli/vendors/project/LICENSE +21 -0
- wl_cli/vendors/project/README.md +0 -0
- wl_cli/vendors/project/pyproject.toml +27 -0
- wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
- wl_cli/vendors/project/pyproject_workspace.toml +14 -0
- wl_cli/vendors/readme/README.md +120 -0
- wl_cli/vendors/rust/Cargo.toml +14 -0
- wl_cli/vendors/rust/lib/__init__.py +3 -0
- wl_cli/vendors/rust/lib/rust_utils.py +31 -0
- wl_cli/vendors/rust/src/lib.rs +13 -0
- wl_cli/vendors/rust/src/main.rs +3 -0
- wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
- wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
- wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
- wl_cli/vendors/sop_doc/python-sop.md +342 -0
- wl_cli/vendors/sop_doc/rust-readme.md +219 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Test filter for managing which test files to skip."""
|
|
2
|
+
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestFilter:
|
|
7
|
+
"""Filter for test files to skip during test runs."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
"""Initialize the test filter."""
|
|
11
|
+
self._excludes: list[str] = self._load_excludes()
|
|
12
|
+
|
|
13
|
+
def _load_excludes(self) -> list[str]:
|
|
14
|
+
"""Load the list of test files to exclude from configuration."""
|
|
15
|
+
from ...utils.config import get_config
|
|
16
|
+
|
|
17
|
+
excludes = get_config("test_excludes", [])
|
|
18
|
+
return cast("list[str]", excludes)
|
|
19
|
+
|
|
20
|
+
def _get_default_excludes(self) -> list[str]:
|
|
21
|
+
"""Get the default list of test files to exclude."""
|
|
22
|
+
return []
|
|
23
|
+
|
|
24
|
+
def get_excludes(self) -> list[str]:
|
|
25
|
+
"""Get the list of test files to exclude."""
|
|
26
|
+
return self._excludes
|
|
27
|
+
|
|
28
|
+
def add_exclude(self, test_file: str) -> None:
|
|
29
|
+
"""Add a test file to the exclusion list."""
|
|
30
|
+
if test_file not in self._excludes:
|
|
31
|
+
self._excludes.append(test_file)
|
|
32
|
+
|
|
33
|
+
def remove_exclude(self, test_file: str) -> None:
|
|
34
|
+
"""Remove a test file from the exclusion list."""
|
|
35
|
+
if test_file in self._excludes:
|
|
36
|
+
self._excludes.remove(test_file)
|
wl_cli/utils/uv_run.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""The ``uv run`` launcher fallback, shared by its callers (#165).
|
|
2
|
+
|
|
3
|
+
``uv run <tool>`` has one failure mode that says nothing about ``<tool>``:
|
|
4
|
+
``uv`` itself could not be started, so the child never ran. Callers recover by
|
|
5
|
+
re-running the child directly. The rule lives here -- in ``utils`` -- so that
|
|
6
|
+
``utils/pre_commit.py`` can share it with the formatters without importing a
|
|
7
|
+
formatter back out of ``commands``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .subprocess_utils import SubprocessExecutor, SubprocessResult
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def uv_unavailable(result: SubprocessResult) -> bool:
|
|
16
|
+
"""Whether the ``uv`` launcher never got as far as running its child.
|
|
17
|
+
|
|
18
|
+
Deliberately **not** a substring sniff of ``result.stderr``. The old test
|
|
19
|
+
read ``"not found" in stderr and "uv" in stderr``, which fires on any *child*
|
|
20
|
+
that quotes the command line back -- ``uv run: ruff: not found`` is uv
|
|
21
|
+
working perfectly and ruff missing, yet it sent the tool re-running outside
|
|
22
|
+
its environment. What actually means "the launcher was not there" is
|
|
23
|
+
:attr:`SubprocessResult.launch_error`, the field set only when the
|
|
24
|
+
executable could not be started at all (#100). ``127`` is kept as a
|
|
25
|
+
belt-and-braces spelling of a shell's "command not found", which an executor
|
|
26
|
+
without ``shell=True`` never produces itself.
|
|
27
|
+
|
|
28
|
+
The same discriminator, under the ``Ruff`` name, guards the dead-code
|
|
29
|
+
detector and fixer (#150/#153) -- see ``DeadCodeFixer._ruff_unavailable``
|
|
30
|
+
for why those copies stay spelled out (#155 M2).
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
result: The finished subprocess result.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
True when the process never ran.
|
|
37
|
+
"""
|
|
38
|
+
if result.launch_error is not None:
|
|
39
|
+
return True
|
|
40
|
+
return result.returncode == 127
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run_uv_command(
|
|
44
|
+
executor: SubprocessExecutor,
|
|
45
|
+
command: list[str],
|
|
46
|
+
*,
|
|
47
|
+
env: dict[str, str] | None = None,
|
|
48
|
+
cwd: Path | None = None,
|
|
49
|
+
quiet: bool = False,
|
|
50
|
+
) -> SubprocessResult:
|
|
51
|
+
"""Run ``command``, retrying it without its ``uv run`` prefix if uv is absent.
|
|
52
|
+
|
|
53
|
+
The executor is passed in rather than built here: each caller keeps its own
|
|
54
|
+
patch point and its own environment preparation (the formatters prepare one
|
|
55
|
+
per command; the pre-commit run prepares one per run).
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
executor: The executor that runs both attempts.
|
|
59
|
+
command: The command, optionally led by ``uv run``.
|
|
60
|
+
env: Environment for the child.
|
|
61
|
+
cwd: Working directory for the child.
|
|
62
|
+
quiet: Whether to capture the child's output.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
The result of the direct re-run when the launcher was unavailable and
|
|
66
|
+
the re-run did not itself raise; otherwise the original result.
|
|
67
|
+
"""
|
|
68
|
+
result = executor.run(command, env=env, cwd=cwd, quiet=quiet)
|
|
69
|
+
if (
|
|
70
|
+
len(command) >= 2
|
|
71
|
+
and command[0] == "uv"
|
|
72
|
+
and command[1] == "run"
|
|
73
|
+
and uv_unavailable(result)
|
|
74
|
+
):
|
|
75
|
+
try:
|
|
76
|
+
return executor.run(command[2:], env=env, cwd=cwd, quiet=quiet)
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
return result
|
wl_cli/utils/uv_tool.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""UV Tool environment utilities for WL Commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def is_running_in_uv_tool() -> bool:
|
|
8
|
+
"""Check if we are running in a UV Tool environment.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
bool: True if running in UV Tool environment, False otherwise.
|
|
12
|
+
"""
|
|
13
|
+
return "UV_TOOL_DIR" in os.environ or "UV_PYTHON" in os.environ
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_uv_tool_python_path() -> str | None:
|
|
17
|
+
"""Get the Python executable path from UV Tool environment.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Optional[str]: Python executable path if in UV Tool environment, None otherwise.
|
|
21
|
+
"""
|
|
22
|
+
if "UV_PYTHON" in os.environ:
|
|
23
|
+
return os.environ["UV_PYTHON"]
|
|
24
|
+
|
|
25
|
+
if "UV_TOOL_DIR" in os.environ:
|
|
26
|
+
# Try to find python executable in UV tool directory
|
|
27
|
+
uv_tool_dir = Path(os.environ["UV_TOOL_DIR"])
|
|
28
|
+
|
|
29
|
+
# First, try to get Python executable from sys.executable
|
|
30
|
+
import sys
|
|
31
|
+
|
|
32
|
+
if sys.executable:
|
|
33
|
+
return sys.executable
|
|
34
|
+
|
|
35
|
+
# If sys.executable is not available, try common locations
|
|
36
|
+
# Try parent directory of UV_TOOL_DIR
|
|
37
|
+
python_executable = uv_tool_dir.parent.parent / "python"
|
|
38
|
+
if python_executable.exists():
|
|
39
|
+
return str(python_executable)
|
|
40
|
+
|
|
41
|
+
# Try Windows-specific path
|
|
42
|
+
python_executable = uv_tool_dir.parent.parent / "python.exe"
|
|
43
|
+
if python_executable.exists():
|
|
44
|
+
return str(python_executable)
|
|
45
|
+
|
|
46
|
+
# Try to find python in PATH
|
|
47
|
+
import shutil
|
|
48
|
+
|
|
49
|
+
python_path = shutil.which("python")
|
|
50
|
+
if python_path:
|
|
51
|
+
return python_path
|
|
52
|
+
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_uv_tool_bin_path() -> Path | None:
|
|
57
|
+
"""Get the bin directory path from UV Tool environment.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Optional[Path]: Bin directory path if in UV Tool environment, None otherwise.
|
|
61
|
+
"""
|
|
62
|
+
if "UV_TOOL_DIR" in os.environ:
|
|
63
|
+
uv_tool_dir = Path(os.environ["UV_TOOL_DIR"])
|
|
64
|
+
return uv_tool_dir
|
|
65
|
+
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_uv_tool_env() -> dict[str, str]:
|
|
70
|
+
"""Get environment variables for UV Tool environment.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
dict[str, str]: Environment variables for UV Tool environment.
|
|
74
|
+
"""
|
|
75
|
+
env = os.environ.copy()
|
|
76
|
+
|
|
77
|
+
# Add UV Tool bin directory to PATH if not already present
|
|
78
|
+
uv_tool_bin = get_uv_tool_bin_path()
|
|
79
|
+
if uv_tool_bin:
|
|
80
|
+
path = env.get("PATH", "")
|
|
81
|
+
if str(uv_tool_bin) not in path:
|
|
82
|
+
env["PATH"] = f"{uv_tool_bin}{os.pathsep}{path}"
|
|
83
|
+
|
|
84
|
+
return env
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def should_use_direct_execution(command: list[str]) -> bool:
|
|
88
|
+
"""Check if a command should be executed directly in UV Tool environment.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
command: Command to check.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
bool: True if command should be executed directly, False otherwise.
|
|
95
|
+
"""
|
|
96
|
+
if not is_running_in_uv_tool():
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
# Check if it's an 'uv run' command
|
|
100
|
+
if len(command) >= 2 and command[0] == "uv" and command[1] == "run":
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_direct_command(command: list[str]) -> list[str]:
|
|
107
|
+
"""Get the direct command by removing 'uv run' prefix.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
command: Original command with 'uv run' prefix.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
list[str]: Command without 'uv run' prefix.
|
|
114
|
+
"""
|
|
115
|
+
if len(command) >= 2 and command[0] == "uv" and command[1] == "run":
|
|
116
|
+
return command[2:]
|
|
117
|
+
return command
|
wl_cli/utils/uv_tree.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Shared parsing of ``uv tree`` output.
|
|
2
|
+
|
|
3
|
+
``uv tree`` prints one root row per workspace member, normally as
|
|
4
|
+
``<name> v<version>``, followed by indented dependency rows drawn with box
|
|
5
|
+
characters. Two detectors used to parse that output by hand with slightly
|
|
6
|
+
different heuristics, so a root package whose name begins with ``v`` --
|
|
7
|
+
``vite-app``, ``vue-lib``, ``virtualenv-*`` -- got two different wrong answers
|
|
8
|
+
(#178). This module owns the one parser; both entry points delegate here.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
#: Leading characters of a dependency row in ``uv tree`` output.
|
|
12
|
+
_TREE_PREFIXES: tuple[str, ...] = ("├", "└", "│", "─", "┌", "┐", "┬", "┴", "┼")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_valid_version(version: str) -> bool:
|
|
16
|
+
"""检查版本字符串是否有效。"""
|
|
17
|
+
if not version:
|
|
18
|
+
return False
|
|
19
|
+
return all(c.isalnum() or c in ".-+" for c in version)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _is_version_only_line(line: str) -> bool:
|
|
23
|
+
"""判断整行是否只是版本号(如 ``v1.2.3``)。
|
|
24
|
+
|
|
25
|
+
``uv tree`` 不会把裸版本号输出为根行,但旧解析器会把它们排除,这里保留该
|
|
26
|
+
约定。判定要求 ``v`` 前缀且后接数字,因此 ``vite-app`` 这类真实包名仍然
|
|
27
|
+
会被当作根包。
|
|
28
|
+
"""
|
|
29
|
+
if not line.startswith(("v", "V")):
|
|
30
|
+
return False
|
|
31
|
+
candidate = line[1:]
|
|
32
|
+
return candidate[:1].isdigit() and is_valid_version(candidate)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _is_root_row(line: str) -> bool:
|
|
36
|
+
"""判断一行是否可能是根包行(已去掉行尾空白)。"""
|
|
37
|
+
if not line or line[0].isspace():
|
|
38
|
+
return False
|
|
39
|
+
if line.startswith(_TREE_PREFIXES):
|
|
40
|
+
return False
|
|
41
|
+
return not (line.startswith("Resolved") or line.startswith("(*)"))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_uv_tree_roots(output: str | list[str]) -> list[str]:
|
|
45
|
+
"""从 ``uv tree`` 输出中解析根包名。
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
output: ``uv tree`` 的原始输出,或其按行拆分后的列表。
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
按输出顺序排列的根包名。``<name> v<version>`` 行贡献 ``name``;``" v"``
|
|
52
|
+
之后的部分必须形如版本(数字开头且通过 :func:`is_valid_version`),
|
|
53
|
+
因此名字里含 ``" v"`` 的包不会被静默截断。
|
|
54
|
+
"""
|
|
55
|
+
lines = output.splitlines() if isinstance(output, str) else output
|
|
56
|
+
roots: list[str] = []
|
|
57
|
+
for raw in lines:
|
|
58
|
+
stripped = raw.rstrip()
|
|
59
|
+
if not _is_root_row(stripped):
|
|
60
|
+
continue
|
|
61
|
+
line = stripped.strip()
|
|
62
|
+
parts = line.rsplit(" v", 1)
|
|
63
|
+
if len(parts) == 2:
|
|
64
|
+
name, version = parts[0].strip(), parts[1].strip()
|
|
65
|
+
if name and version[:1].isdigit() and is_valid_version(version):
|
|
66
|
+
roots.append(name)
|
|
67
|
+
continue
|
|
68
|
+
if _is_version_only_line(line):
|
|
69
|
+
continue
|
|
70
|
+
roots.append(line)
|
|
71
|
+
return roots
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""UV workspace detection utilities for WL Commands."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .logging import log_info
|
|
7
|
+
from .subprocess_utils import SubprocessExecutor
|
|
8
|
+
from .uv_tree import is_valid_version, parse_uv_tree_roots
|
|
9
|
+
from .workspace_detector.detection_strategies import UV_PROBE_TIMEOUT_SECONDS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UVWorkspaceDetector:
|
|
13
|
+
"""A utility class for detecting UV workspaces."""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self._subprocess_executor = SubprocessExecutor()
|
|
17
|
+
self._workspace_cache: dict[str, bool] = {}
|
|
18
|
+
|
|
19
|
+
def is_workspace(self, project_root: Path | None = None) -> bool:
|
|
20
|
+
"""
|
|
21
|
+
Detect if we are in a uv workspace.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
project_root: The project root directory. If None, uses current directory.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
True if in a workspace, False otherwise.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
if project_root is None:
|
|
31
|
+
project_root = Path.cwd()
|
|
32
|
+
|
|
33
|
+
project_root = project_root.resolve()
|
|
34
|
+
root_str = str(project_root)
|
|
35
|
+
|
|
36
|
+
# Check cache first
|
|
37
|
+
if root_str in self._workspace_cache:
|
|
38
|
+
return self._workspace_cache[root_str]
|
|
39
|
+
|
|
40
|
+
# Method 1: Check for uv.lock file which indicates a workspace
|
|
41
|
+
uv_lock_path = project_root / "uv.lock"
|
|
42
|
+
if uv_lock_path.exists() and uv_lock_path.is_file():
|
|
43
|
+
log_info("Debug: uv.lock file found, workspace detected")
|
|
44
|
+
self._workspace_cache[root_str] = True
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
# Method 2: Use uv init command to check if we're already in a workspace
|
|
48
|
+
try:
|
|
49
|
+
# A wedged uv must read as "not detected", not as an
|
|
50
|
+
# infinite wait -- same bound as WorkspaceDetector (#184).
|
|
51
|
+
result = self._subprocess_executor.run(
|
|
52
|
+
["uv", "init", "--dry-run"],
|
|
53
|
+
cwd=project_root,
|
|
54
|
+
quiet=True,
|
|
55
|
+
timeout=UV_PROBE_TIMEOUT_SECONDS,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
result.success
|
|
60
|
+
and "is already a member of workspace" in result.stderr
|
|
61
|
+
):
|
|
62
|
+
log_info("Debug: 'is already a member of workspace' detected")
|
|
63
|
+
self._workspace_cache[root_str] = True
|
|
64
|
+
return True
|
|
65
|
+
except (subprocess.SubprocessError, OSError, ValueError):
|
|
66
|
+
# Fall back to other methods if uv init fails
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
# Method 3: Use uv tree command to check for multiple root packages
|
|
70
|
+
is_workspace_result = self._detect_workspace_via_tree(project_root)
|
|
71
|
+
self._workspace_cache[root_str] = is_workspace_result
|
|
72
|
+
return is_workspace_result
|
|
73
|
+
|
|
74
|
+
except (OSError, ValueError) as e:
|
|
75
|
+
log_info(f"Debug: Workspace detection error: {e}")
|
|
76
|
+
# Cache the result even for errors to avoid repeated failures
|
|
77
|
+
# Use a default key if root_str is not defined
|
|
78
|
+
cache_key = root_str if "root_str" in locals() else "error_default"
|
|
79
|
+
self._workspace_cache[cache_key] = False
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def _detect_workspace_via_tree(self, project_root: Path) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Detect workspace using uv tree command.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
project_root: The project root directory.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if workspace detected, False otherwise.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
result = self._subprocess_executor.run(
|
|
94
|
+
["uv", "tree"],
|
|
95
|
+
cwd=project_root,
|
|
96
|
+
quiet=True,
|
|
97
|
+
timeout=UV_PROBE_TIMEOUT_SECONDS,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if not result.success:
|
|
101
|
+
log_info(f"Debug: uv tree command failed with code {result.returncode}")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
# Check for a workspace by counting root packages
|
|
105
|
+
# In a workspace, there would be multiple top-level packages
|
|
106
|
+
lines = result.stdout.split("\n") if result.stdout else []
|
|
107
|
+
root_packages = self._parse_root_packages(lines)
|
|
108
|
+
|
|
109
|
+
log_info(f"Debug: uv tree root packages: {root_packages}")
|
|
110
|
+
log_info(f"Debug: root packages count: {len(root_packages)}")
|
|
111
|
+
|
|
112
|
+
# There should be more than one root package in a workspace
|
|
113
|
+
if len(root_packages) > 1:
|
|
114
|
+
log_info("Debug: Multiple root packages found, workspace detected")
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
except (subprocess.SubprocessError, OSError, ValueError) as e:
|
|
118
|
+
log_info(f"Debug: Workspace detection via tree error: {e}")
|
|
119
|
+
pass # Ignore errors in workspace detection
|
|
120
|
+
|
|
121
|
+
log_info("Debug: No workspace detected via tree")
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
def _parse_root_packages(self, lines: list[str]) -> list[str]:
|
|
125
|
+
"""
|
|
126
|
+
Parse root packages from uv tree output.
|
|
127
|
+
|
|
128
|
+
Delegates to the shared parser so both detectors agree (#178).
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
lines: Lines from uv tree output.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of root package names.
|
|
135
|
+
"""
|
|
136
|
+
return parse_uv_tree_roots(lines)
|
|
137
|
+
|
|
138
|
+
def _is_valid_version(self, version: str) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Check if a version string is valid.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
version: Version string to check.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
True if valid, False otherwise.
|
|
147
|
+
"""
|
|
148
|
+
return is_valid_version(version)
|
|
149
|
+
|
|
150
|
+
def clear_cache(self) -> None:
|
|
151
|
+
"""Clear the workspace detection cache."""
|
|
152
|
+
self._workspace_cache.clear()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Global instance
|
|
156
|
+
_uv_workspace_detector = UVWorkspaceDetector()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def is_uv_workspace(project_root: Path | None = None) -> bool:
|
|
160
|
+
"""
|
|
161
|
+
Detect if we are in a uv workspace.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
project_root: The project root directory. If None, uses current directory.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
True if in a workspace, False otherwise.
|
|
168
|
+
"""
|
|
169
|
+
return _uv_workspace_detector.is_workspace(project_root)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def clear_uv_workspace_cache() -> None:
|
|
173
|
+
"""Clear the UV workspace detection cache."""
|
|
174
|
+
_uv_workspace_detector.clear_cache()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Version management service."""
|
|
2
|
+
|
|
3
|
+
from .pypi_version_checker import PyPIVersionChecker
|
|
4
|
+
from .version_comparator import VersionComparator
|
|
5
|
+
from .version_detectors import VersionDetector
|
|
6
|
+
from .version_service import VersionService
|
|
7
|
+
from .version_updater import VersionUpdater
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"VersionService",
|
|
11
|
+
"VersionComparator",
|
|
12
|
+
"VersionDetector",
|
|
13
|
+
"VersionUpdater",
|
|
14
|
+
"PyPIVersionChecker",
|
|
15
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyPI version checking utilities for publish command.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from typing import cast
|
|
8
|
+
from urllib import request
|
|
9
|
+
from urllib.error import URLError
|
|
10
|
+
|
|
11
|
+
from ...exceptions import CommandError
|
|
12
|
+
from ..logging import log_info_pair
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PyPIVersionChecker:
|
|
16
|
+
"""Check versions against PyPI servers."""
|
|
17
|
+
|
|
18
|
+
def _get_pypi_version(self) -> str | None:
|
|
19
|
+
"""Get the latest version from PyPI."""
|
|
20
|
+
package_name = "py_wlcli"
|
|
21
|
+
try:
|
|
22
|
+
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
23
|
+
with request.urlopen(url) as response:
|
|
24
|
+
data = json.loads(response.read())
|
|
25
|
+
return cast("str | None", data["info"]["version"])
|
|
26
|
+
except (OSError, json.JSONDecodeError, KeyError, URLError):
|
|
27
|
+
# If we can't get the PyPI version, return None
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
async def _get_pypi_version_async(self) -> str | None:
|
|
31
|
+
"""Async get the latest version from PyPI."""
|
|
32
|
+
return await asyncio.to_thread(self._get_pypi_version)
|
|
33
|
+
|
|
34
|
+
def check_version_with_pypi(
|
|
35
|
+
self, repository: str, current_version: str, comparator
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Check the current version against PyPI to ensure proper versioning."""
|
|
38
|
+
package_name = "py_wlcli" # This should ideally be read from project config
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# Determine the repository URL
|
|
42
|
+
if repository == "pypi":
|
|
43
|
+
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
44
|
+
else:
|
|
45
|
+
# For other repositories, we might need to get the URL from twine config
|
|
46
|
+
# For now, we'll assume it's TestPyPI
|
|
47
|
+
url = f"https://test.pypi.org/pypi/{package_name}/json"
|
|
48
|
+
|
|
49
|
+
log_info_pair(
|
|
50
|
+
f"Checking version on {repository} server...",
|
|
51
|
+
f"正在检查 {repository} 服务器上的版本...",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Make request to PyPI API
|
|
55
|
+
with request.urlopen(url) as response:
|
|
56
|
+
data = json.loads(response.read())
|
|
57
|
+
|
|
58
|
+
# Get the latest version from PyPI
|
|
59
|
+
pypi_version = data["info"]["version"]
|
|
60
|
+
log_info_pair(
|
|
61
|
+
f"Latest version on {repository}: {pypi_version}",
|
|
62
|
+
f"{repository} 上的最新版本: {pypi_version}",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Compare versions
|
|
66
|
+
if not comparator.is_version_increment_valid(pypi_version, current_version):
|
|
67
|
+
raise CommandError(
|
|
68
|
+
f"Version check failed: Local version {current_version} "
|
|
69
|
+
f"is not a valid increment from PyPI version {pypi_version}. "
|
|
70
|
+
f"Version must be incremented and not skip numbers."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
log_info_pair(
|
|
74
|
+
"✓ Version check passed",
|
|
75
|
+
"✓ 版本检查通过",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
except URLError as e:
|
|
79
|
+
log_info_pair(
|
|
80
|
+
f"Warning: Could not check version on PyPI: {e}",
|
|
81
|
+
f"警告: 无法检查 PyPI 上的版本: {e}",
|
|
82
|
+
)
|
|
83
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
84
|
+
log_info_pair(
|
|
85
|
+
f"Warning: Version check failed: {e}",
|
|
86
|
+
f"警告: 版本检查失败: {e}",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def check_version_with_pypi_async(
|
|
90
|
+
self, repository: str, current_version: str, comparator
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Async check the current version against PyPI to ensure proper versioning."""
|
|
93
|
+
return await asyncio.to_thread(
|
|
94
|
+
self.check_version_with_pypi, repository, current_version, comparator
|
|
95
|
+
)
|