porringer 0.2.1.dev50__tar.gz → 0.2.1.dev52__tar.gz
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.
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/PKG-INFO +2 -2
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/core/action_builder.py +5 -1
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/core/execution.py +103 -120
- porringer-0.2.1.dev52/porringer/backend/command/core/phase.py +247 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/command/sync.py +133 -160
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/scm.py +47 -2
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/git/plugin.py +39 -5
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/__init__.py +4 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/execution.py +34 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/mock/scm.py +9 -3
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/pyproject.toml +6 -14
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/conftest.py +13 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/test_example_bootstrap.py +2 -1
- porringer-0.2.1.dev52/tests/integration/test_example_presence.py +39 -0
- porringer-0.2.1.dev52/tests/unit/plugins/git/test_clone_detection.py +146 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pim/test_environment.py +24 -8
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pyenv/test_environment.py +38 -13
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_backend_resolver.py +15 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_cache.py +2 -1
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_check_updates.py +30 -2
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_manifest.py +10 -5
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_plugin_manager.py +22 -17
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_project_directory.py +5 -3
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_runtime_propagation.py +60 -32
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_update_detection.py +15 -1
- porringer-0.2.1.dev50/tests/integration/test_example_presence.py +0 -71
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/LICENSE.md +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/README.md +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/api.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/backend.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/builder.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/cache.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/core/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/core/discovery.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/core/presence.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/core/resolution.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/manifest.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/self.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/sync.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/resolver.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/schema.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/command/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/command/cache.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/command/check.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/command/download.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/command/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/command/self.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/entry.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/console/schema.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/manifest.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/plugin_manager.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/project_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/python_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/runtime.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/plugin_schema/tool_based.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/core/schema.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/apt/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/apt/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/brew/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/brew/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/bun/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/bun/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/bun_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/bun_project/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/deno/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/deno/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/deno_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/deno_project/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/git/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/npm/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/npm/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/npm_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/npm_project/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pdm/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pdm/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pim/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pim/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pip/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pip/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pipx/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pipx/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pnpm/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pnpm/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pnpm_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pnpm_project/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/poetry/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/poetry/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pyenv/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/pyenv/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/uv/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/uv/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/uv_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/uv_project/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/winget/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/winget/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/yarn_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/plugin/yarn_project/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/py.typed +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/cache.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/check.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/config.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/download.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/manifest.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/schema/progress.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/mock/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/mock/environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/mock/plugin_manager.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/mock/project_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/pytest/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/pytest/plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/pytest/shared.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/pytest/tests.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/test/pytest/variants.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/utility/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/utility/download.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/utility/exception.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/utility/py.typed +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/utility/utility.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/git/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/git/test_scm.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/pip/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/pip/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/pipx/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/pipx/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/winget/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/plugins/winget/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/integration/test_bootstrap_presence.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/apt/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/apt/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/brew/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/brew/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/bun/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/bun/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/bun_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/bun_project/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/deno/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/deno/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/deno_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/deno_project/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/git/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/git/test_scm.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/npm/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/npm/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/npm_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/npm_project/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pdm/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pdm/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pim/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pip/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pip/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pipx/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pipx/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pnpm/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pnpm/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pnpm_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pnpm_project/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/poetry/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/poetry/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/pyenv/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/uv/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/uv/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/uv_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/uv_project/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/yarn_project/__init__.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/plugins/yarn_project/test_environment.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_bootstrap_cross_platform.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_check.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_cli.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_command_plugin.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_command_self.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_package_ref.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_plugin_filtering.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_project_root.py +0 -0
- {porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/tests/unit/test_sub_action_progress.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: porringer
|
|
3
|
-
Version: 0.2.1.
|
|
3
|
+
Version: 0.2.1.dev52
|
|
4
4
|
Author-Email: Synodic Software <contact@synodic.software>
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: homepage, https://github.com/synodic/porringer
|
|
7
7
|
Project-URL: repository, https://github.com/synodic/porringer
|
|
8
8
|
Requires-Python: >=3.14
|
|
9
|
-
Requires-Dist: typer[all]>=0.24.
|
|
9
|
+
Requires-Dist: typer[all]>=0.24.1
|
|
10
10
|
Requires-Dist: pydantic>=2.12.5
|
|
11
11
|
Requires-Dist: platformdirs>=4.9.2
|
|
12
12
|
Requires-Dist: userpath>=1.9.2
|
{porringer-0.2.1.dev50 → porringer-0.2.1.dev52}/porringer/backend/command/core/action_builder.py
RENAMED
|
@@ -224,6 +224,10 @@ def build_actions(
|
|
|
224
224
|
for package in packages:
|
|
225
225
|
if not package.is_applicable():
|
|
226
226
|
continue
|
|
227
|
+
# Use the manifest description if set, otherwise surface
|
|
228
|
+
# the repo URL as the package description so UI cards
|
|
229
|
+
# show *what* is being cloned.
|
|
230
|
+
scm_description = package.description or str(package.name)
|
|
227
231
|
actions.append(
|
|
228
232
|
SetupAction(
|
|
229
233
|
description=action_description(kind, verb, installer, package=package.name),
|
|
@@ -231,7 +235,7 @@ def build_actions(
|
|
|
231
235
|
ecosystem=ecosystem,
|
|
232
236
|
installer=installer,
|
|
233
237
|
package=package.name,
|
|
234
|
-
package_description=
|
|
238
|
+
package_description=scm_description,
|
|
235
239
|
)
|
|
236
240
|
)
|
|
237
241
|
continue
|
|
@@ -30,6 +30,7 @@ from porringer.core.plugin_schema.runtime import RuntimeConsumer, RuntimeProvide
|
|
|
30
30
|
from porringer.core.plugin_schema.scm import ScmEnvironment
|
|
31
31
|
from porringer.core.schema import Package, PluginKind
|
|
32
32
|
from porringer.schema import (
|
|
33
|
+
CloneStatusKind,
|
|
33
34
|
ManifestMetadata,
|
|
34
35
|
ProgressEvent,
|
|
35
36
|
ProgressEventKind,
|
|
@@ -53,9 +54,9 @@ from .action_builder import (
|
|
|
53
54
|
from .discovery import (
|
|
54
55
|
DiscoveredPlugins,
|
|
55
56
|
discover_all_plugins,
|
|
56
|
-
discover_plugins,
|
|
57
57
|
invalidate_plugin_cache,
|
|
58
58
|
)
|
|
59
|
+
from .phase import run_phases
|
|
59
60
|
from .presence import async_dry_run_action
|
|
60
61
|
from .resolution import (
|
|
61
62
|
OperationKind,
|
|
@@ -73,8 +74,8 @@ class ExecutionState:
|
|
|
73
74
|
|
|
74
75
|
Owns the discovered plugin dicts, grouped phase actions, and
|
|
75
76
|
common parameters that every phase needs. Provides
|
|
76
|
-
|
|
77
|
-
so callers don't need to pass a half-dozen arguments.
|
|
77
|
+
:meth:`refresh_all_plugins` and :meth:`propagate_runtime` as
|
|
78
|
+
methods so callers don't need to pass a half-dozen arguments.
|
|
78
79
|
"""
|
|
79
80
|
|
|
80
81
|
actions: list[SetupAction]
|
|
@@ -103,36 +104,23 @@ class ExecutionState:
|
|
|
103
104
|
"""The sync strategy from the current parameters."""
|
|
104
105
|
return self.parameters.strategy
|
|
105
106
|
|
|
106
|
-
# --
|
|
107
|
+
# -- plugin refresh machinery --------------------------------------
|
|
107
108
|
|
|
108
|
-
def
|
|
109
|
-
"""Refresh PATH
|
|
109
|
+
def refresh_all_plugins(self) -> None:
|
|
110
|
+
"""Refresh PATH and re-discover all plugin types.
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
re-propagated to newly-created consumer instances.
|
|
112
|
+
Invalidates the plugin cache, re-discovers environments,
|
|
113
|
+
project environments, and SCM environments, then re-applies
|
|
114
|
+
the cached runtime executable to all new consumer instances.
|
|
115
115
|
"""
|
|
116
116
|
refresh_path()
|
|
117
117
|
invalidate_plugin_cache()
|
|
118
|
-
|
|
118
|
+
plugins = discover_all_plugins()
|
|
119
|
+
self.environments = dict(plugins.environments)
|
|
120
|
+
self.project_environments = dict(plugins.project_environments)
|
|
121
|
+
self.scm_environments = dict(plugins.scm_environments)
|
|
119
122
|
self._apply_resolved_runtime()
|
|
120
123
|
|
|
121
|
-
for phase_actions in self.phases.values():
|
|
122
|
-
if phase_actions:
|
|
123
|
-
_resolve_deferred_actions(phase_actions, self.environments, self.strategy)
|
|
124
|
-
|
|
125
|
-
for phase_actions in self.phases.values():
|
|
126
|
-
for action in phase_actions:
|
|
127
|
-
if action.cli_command is None or action.installer is not None:
|
|
128
|
-
action.cli_command = get_cli_command(
|
|
129
|
-
action,
|
|
130
|
-
self.environments,
|
|
131
|
-
self.strategy,
|
|
132
|
-
self.project_environments,
|
|
133
|
-
self.scm_environments,
|
|
134
|
-
)
|
|
135
|
-
|
|
136
124
|
def propagate_runtime(self) -> None:
|
|
137
125
|
"""Resolve interpreter paths from completed runtime actions and propagate downstream.
|
|
138
126
|
|
|
@@ -140,8 +128,7 @@ class ExecutionState:
|
|
|
140
128
|
actions, resolves its executable, injects the directory onto
|
|
141
129
|
``PATH``, and sets ``runtime_executable`` on every matching
|
|
142
130
|
``RuntimeConsumer``. The result is cached so that subsequent
|
|
143
|
-
calls to :meth:`
|
|
144
|
-
:meth:`refresh_project_environments` can re-apply it to
|
|
131
|
+
calls to :meth:`refresh_all_plugins` can re-apply it to
|
|
145
132
|
newly-created plugin instances.
|
|
146
133
|
"""
|
|
147
134
|
result = _propagate_runtime(
|
|
@@ -152,17 +139,6 @@ class ExecutionState:
|
|
|
152
139
|
if result is not None:
|
|
153
140
|
self._resolved_runtime = result
|
|
154
141
|
|
|
155
|
-
def refresh_project_environments(self) -> None:
|
|
156
|
-
"""Re-discover project-environment plugins and re-propagate the runtime.
|
|
157
|
-
|
|
158
|
-
Replaces ``self.project_environments`` with freshly-discovered
|
|
159
|
-
instances and re-applies the cached runtime executable so that
|
|
160
|
-
new ``RuntimeConsumer`` project environments inherit the
|
|
161
|
-
resolved interpreter path.
|
|
162
|
-
"""
|
|
163
|
-
self.project_environments = discover_plugins('project_environment', ProjectEnvironment)
|
|
164
|
-
self._apply_resolved_runtime()
|
|
165
|
-
|
|
166
142
|
def _apply_resolved_runtime(self) -> None:
|
|
167
143
|
"""Re-apply the cached runtime executable to all current consumers.
|
|
168
144
|
|
|
@@ -195,6 +171,59 @@ class ExecutionState:
|
|
|
195
171
|
"""Plugin-management context for this execution run."""
|
|
196
172
|
return PluginContext(project_environments=self.project_environments)
|
|
197
173
|
|
|
174
|
+
# -- phase executor delegates --------------------------------------
|
|
175
|
+
|
|
176
|
+
async def run_package_actions(self, actions: list[SetupAction]) -> tuple[list[SetupActionResult], bool]:
|
|
177
|
+
"""Execute package/tool/runtime actions.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Tuple of (results, should_continue).
|
|
181
|
+
"""
|
|
182
|
+
return await execute_package_actions(
|
|
183
|
+
actions,
|
|
184
|
+
self.environments,
|
|
185
|
+
self.parameters,
|
|
186
|
+
self.event_queue,
|
|
187
|
+
self.plugin_context,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def run_project_phase(self, actions: list[SetupAction]) -> list[SetupActionResult]:
|
|
191
|
+
"""Execute or skip project sync actions."""
|
|
192
|
+
return await handle_project_phase(actions, self)
|
|
193
|
+
|
|
194
|
+
async def run_scm_actions(self, actions: list[SetupAction]) -> list[SetupActionResult]:
|
|
195
|
+
"""Execute SCM clone actions."""
|
|
196
|
+
return await _execute_scm_actions(
|
|
197
|
+
actions,
|
|
198
|
+
self.scm_environments,
|
|
199
|
+
self.fallback_dir,
|
|
200
|
+
self.parameters,
|
|
201
|
+
self.event_queue,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
async def run_command_actions(self, actions: list[SetupAction]) -> list[SetupActionResult]:
|
|
205
|
+
"""Execute post-sync shell commands."""
|
|
206
|
+
return await execute_command_actions(actions, self)
|
|
207
|
+
|
|
208
|
+
def resolve_deferred(self, actions: list[SetupAction]) -> None:
|
|
209
|
+
"""Resolve deferred actions and update their CLI commands."""
|
|
210
|
+
_resolve_deferred_actions(
|
|
211
|
+
actions,
|
|
212
|
+
self.environments,
|
|
213
|
+
self.strategy,
|
|
214
|
+
scm_environments=self.scm_environments,
|
|
215
|
+
project_environments=self.project_environments,
|
|
216
|
+
)
|
|
217
|
+
for action in actions:
|
|
218
|
+
if action.cli_command is None or action.installer is not None:
|
|
219
|
+
action.cli_command = get_cli_command(
|
|
220
|
+
action,
|
|
221
|
+
self.environments,
|
|
222
|
+
self.strategy,
|
|
223
|
+
self.project_environments,
|
|
224
|
+
self.scm_environments,
|
|
225
|
+
)
|
|
226
|
+
|
|
198
227
|
def early_return(self) -> SetupResults:
|
|
199
228
|
"""Create a ``SetupResults`` from the results accumulated so far."""
|
|
200
229
|
return SetupResults(
|
|
@@ -1100,78 +1129,8 @@ async def execute_single(
|
|
|
1100
1129
|
)
|
|
1101
1130
|
)
|
|
1102
1131
|
|
|
1103
|
-
#
|
|
1104
|
-
|
|
1105
|
-
runtime_results, should_continue = await execute_package_actions(
|
|
1106
|
-
state.phases[PluginKind.RUNTIME],
|
|
1107
|
-
state.environments,
|
|
1108
|
-
state.parameters,
|
|
1109
|
-
state.event_queue,
|
|
1110
|
-
state.plugin_context,
|
|
1111
|
-
)
|
|
1112
|
-
state.results.extend(runtime_results)
|
|
1113
|
-
if not should_continue:
|
|
1114
|
-
return state.early_return()
|
|
1115
|
-
state.propagate_runtime()
|
|
1116
|
-
|
|
1117
|
-
# Phase transition: re-discover plugins now that the runtime
|
|
1118
|
-
# is installed and its directories are on PATH, so that
|
|
1119
|
-
# downstream consumers (pip, uv, etc.) become available.
|
|
1120
|
-
state.phase_transition()
|
|
1121
|
-
|
|
1122
|
-
# --- Phase 2a: package-kind actions (pip, uv, etc.) ---------------
|
|
1123
|
-
if state.phases[PluginKind.PACKAGE]:
|
|
1124
|
-
package_results, should_continue = await execute_package_actions(
|
|
1125
|
-
state.phases[PluginKind.PACKAGE],
|
|
1126
|
-
state.environments,
|
|
1127
|
-
state.parameters,
|
|
1128
|
-
state.event_queue,
|
|
1129
|
-
state.plugin_context,
|
|
1130
|
-
)
|
|
1131
|
-
state.results.extend(package_results)
|
|
1132
|
-
if not should_continue:
|
|
1133
|
-
return state.early_return()
|
|
1134
|
-
|
|
1135
|
-
# --- Phase 2b: tool-kind actions (pipx, etc.) ---------------------
|
|
1136
|
-
if state.phases[PluginKind.TOOL]:
|
|
1137
|
-
# Phase transition: re-discover plugins so that tools installed
|
|
1138
|
-
# in Phase 2a (e.g. pipx via pip) are now available as backends.
|
|
1139
|
-
state.phase_transition()
|
|
1140
|
-
|
|
1141
|
-
tool_results, should_continue = await execute_package_actions(
|
|
1142
|
-
state.phases[PluginKind.TOOL],
|
|
1143
|
-
state.environments,
|
|
1144
|
-
state.parameters,
|
|
1145
|
-
state.event_queue,
|
|
1146
|
-
state.plugin_context,
|
|
1147
|
-
)
|
|
1148
|
-
state.results.extend(tool_results)
|
|
1149
|
-
if not should_continue:
|
|
1150
|
-
return state.early_return()
|
|
1151
|
-
|
|
1152
|
-
# --- Phase 3: project sync ----------------------------------------
|
|
1153
|
-
if state.phases[PluginKind.PROJECT]:
|
|
1154
|
-
# Re-discover project environments in case tools installed in
|
|
1155
|
-
# earlier phases provide new project-environment backends.
|
|
1156
|
-
state.refresh_project_environments()
|
|
1157
|
-
|
|
1158
|
-
state.results.extend(await handle_project_phase(state.phases[PluginKind.PROJECT], state))
|
|
1159
|
-
|
|
1160
|
-
# --- Phase 4: SCM clone -------------------------------------------
|
|
1161
|
-
if state.phases[PluginKind.SCM]:
|
|
1162
|
-
state.results.extend(
|
|
1163
|
-
await _execute_scm_actions(
|
|
1164
|
-
state.phases[PluginKind.SCM],
|
|
1165
|
-
state.scm_environments,
|
|
1166
|
-
state.fallback_dir,
|
|
1167
|
-
state.parameters,
|
|
1168
|
-
state.event_queue,
|
|
1169
|
-
)
|
|
1170
|
-
)
|
|
1171
|
-
|
|
1172
|
-
# --- Phase 5: post-sync commands ----------------------------------
|
|
1173
|
-
if state.phases[None]:
|
|
1174
|
-
state.results.extend(await execute_command_actions(state.phases[None], state))
|
|
1132
|
+
# Run all phases via the generalized phase loop.
|
|
1133
|
+
await run_phases(state)
|
|
1175
1134
|
|
|
1176
1135
|
return SetupResults(
|
|
1177
1136
|
actions=actions,
|
|
@@ -1284,6 +1243,8 @@ def _resolve_deferred_actions(
|
|
|
1284
1243
|
actions: list[SetupAction],
|
|
1285
1244
|
environments: dict[str, Environment],
|
|
1286
1245
|
strategy: SyncStrategy = SyncStrategy.MINIMAL,
|
|
1246
|
+
scm_environments: dict[str, ScmEnvironment] | None = None,
|
|
1247
|
+
project_environments: dict[str, ProjectEnvironment] | None = None,
|
|
1287
1248
|
) -> None:
|
|
1288
1249
|
"""Resolve deferred actions whose `installer` is `None`.
|
|
1289
1250
|
|
|
@@ -1297,12 +1258,19 @@ def _resolve_deferred_actions(
|
|
|
1297
1258
|
actions: Mutable list of actions to resolve in-place.
|
|
1298
1259
|
environments: Freshly-discovered environment plugins.
|
|
1299
1260
|
strategy: Sync strategy (for description verb).
|
|
1261
|
+
scm_environments: Optional SCM-environment plugins.
|
|
1262
|
+
project_environments: Optional project-environment plugins.
|
|
1300
1263
|
"""
|
|
1301
1264
|
deferred = [a for a in actions if a.installer is None and a.ecosystem is not None]
|
|
1302
1265
|
if not deferred:
|
|
1303
1266
|
return
|
|
1304
1267
|
|
|
1305
|
-
|
|
1268
|
+
all_plugins: dict[str, Environment | ScmEnvironment | ProjectEnvironment] = {
|
|
1269
|
+
**environments,
|
|
1270
|
+
**(scm_environments or {}),
|
|
1271
|
+
**(project_environments or {}),
|
|
1272
|
+
}
|
|
1273
|
+
resolver = BackendResolver(all_plugins)
|
|
1306
1274
|
verb = STRATEGY_VERB[strategy]
|
|
1307
1275
|
|
|
1308
1276
|
for action in deferred:
|
|
@@ -1646,14 +1614,29 @@ async def _execute_scm_clone(
|
|
|
1646
1614
|
destination = working_dir
|
|
1647
1615
|
|
|
1648
1616
|
# Skip if already cloned
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
message
|
|
1656
|
-
|
|
1617
|
+
clone_status = scm_env.is_cloned(url, destination)
|
|
1618
|
+
|
|
1619
|
+
match clone_status.kind:
|
|
1620
|
+
case CloneStatusKind.CLONED:
|
|
1621
|
+
actual_url = clone_status.remote_url or url
|
|
1622
|
+
message = f"SCM skip: repo already cloned at '{destination}' (remote: {actual_url})"
|
|
1623
|
+
logger.info(message)
|
|
1624
|
+
return SetupActionResult(
|
|
1625
|
+
action=action,
|
|
1626
|
+
success=True,
|
|
1627
|
+
skipped=True,
|
|
1628
|
+
skip_reason=SkipReason.ALREADY_INSTALLED,
|
|
1629
|
+
message=message,
|
|
1630
|
+
)
|
|
1631
|
+
case CloneStatusKind.MISSING:
|
|
1632
|
+
logger.info("SCM clone needed: .git not found at '%s'", destination)
|
|
1633
|
+
case CloneStatusKind.URL_MISMATCH:
|
|
1634
|
+
logger.info(
|
|
1635
|
+
"SCM clone needed: remote URL mismatch at '%s' (expected '%s', found '%s')",
|
|
1636
|
+
destination,
|
|
1637
|
+
url,
|
|
1638
|
+
clone_status.remote_url,
|
|
1639
|
+
)
|
|
1657
1640
|
|
|
1658
1641
|
if parameters.dry_run:
|
|
1659
1642
|
# Dry-run — no-op
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Generalized phase abstraction for the execution pipeline.
|
|
2
|
+
|
|
3
|
+
Each phase in the setup flow (runtime → packages → tools → project-sync →
|
|
4
|
+
SCM → post-sync commands) is represented by a :class:`Phase` instance that
|
|
5
|
+
encapsulates the *refresh → resolve-deferred → execute → post-hook* cycle.
|
|
6
|
+
|
|
7
|
+
The :func:`run_phases` driver replaces the hand-coded per-phase blocks in
|
|
8
|
+
``execute_single()`` with a uniform loop.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import TYPE_CHECKING, Protocol, override
|
|
16
|
+
|
|
17
|
+
from porringer.core.schema import PluginKind
|
|
18
|
+
from porringer.schema import SetupActionResult
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from .execution import ExecutionState
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Phase result
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class PhaseResult:
|
|
33
|
+
"""Outcome of a single phase execution.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
results: Per-action results produced by this phase.
|
|
37
|
+
should_continue: ``False`` when fail-fast was triggered and the
|
|
38
|
+
pipeline should abort immediately.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
results: list[SetupActionResult]
|
|
42
|
+
should_continue: bool = True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Phase protocol
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Phase(Protocol):
|
|
51
|
+
"""Unified interface for every execution phase.
|
|
52
|
+
|
|
53
|
+
Implementors define *kind* (which action bucket they handle),
|
|
54
|
+
*refresh* (plugin re-discovery), *execute* (the actual work), and
|
|
55
|
+
*post_execute* (hooks that run after a successful phase).
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def kind(self) -> PluginKind | None:
|
|
60
|
+
"""The ``PluginKind`` this phase processes (``None`` for post-sync commands)."""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
def refresh(self, state: ExecutionState) -> None:
|
|
64
|
+
"""Re-discover plugins and resolve deferred actions for **this** phase only.
|
|
65
|
+
|
|
66
|
+
Called *before* :meth:`execute`. The default implementation is a no-op.
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
async def execute(self, state: ExecutionState) -> PhaseResult:
|
|
71
|
+
"""Run the phase's actions and return aggregated results.
|
|
72
|
+
|
|
73
|
+
Must return a :class:`PhaseResult`. Set ``should_continue=False``
|
|
74
|
+
to abort the pipeline (fail-fast).
|
|
75
|
+
"""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
def post_execute(self, state: ExecutionState) -> None:
|
|
79
|
+
"""Hook called *after* a successful execution (``should_continue=True``).
|
|
80
|
+
|
|
81
|
+
Typical use: propagate a resolved runtime to downstream consumers.
|
|
82
|
+
The default implementation is a no-op.
|
|
83
|
+
"""
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Concrete phases
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class _PhaseBase:
|
|
93
|
+
"""Shared helpers for concrete phase implementations."""
|
|
94
|
+
|
|
95
|
+
_kind: PluginKind | None
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def kind(self) -> PluginKind | None:
|
|
99
|
+
return self._kind
|
|
100
|
+
|
|
101
|
+
# Default no-ops — subclasses override as needed.
|
|
102
|
+
def refresh(self, state: ExecutionState) -> None: # noqa: D102
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
def post_execute(self, state: ExecutionState) -> None: # noqa: D102
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class RuntimePhase(_PhaseBase):
|
|
110
|
+
"""Phase 1: install / resolve language runtimes (pim, pyenv)."""
|
|
111
|
+
|
|
112
|
+
_kind = PluginKind.RUNTIME
|
|
113
|
+
|
|
114
|
+
async def execute(self, state: ExecutionState) -> PhaseResult: # noqa: D102
|
|
115
|
+
results, ok = await state.run_package_actions(state.phases[self._kind])
|
|
116
|
+
return PhaseResult(results=results, should_continue=ok)
|
|
117
|
+
|
|
118
|
+
@override
|
|
119
|
+
def post_execute(self, state: ExecutionState) -> None:
|
|
120
|
+
"""Propagate the resolved runtime and trigger a full plugin refresh."""
|
|
121
|
+
state.propagate_runtime()
|
|
122
|
+
state.refresh_all_plugins()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class PackagePhase(_PhaseBase):
|
|
126
|
+
"""Phase 2a: install packages into the current environment (pip, uv)."""
|
|
127
|
+
|
|
128
|
+
_kind = PluginKind.PACKAGE
|
|
129
|
+
|
|
130
|
+
async def execute(self, state: ExecutionState) -> PhaseResult: # noqa: D102
|
|
131
|
+
results, ok = await state.run_package_actions(state.phases[self._kind])
|
|
132
|
+
return PhaseResult(results=results, should_continue=ok)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class ToolPhase(_PhaseBase):
|
|
136
|
+
"""Phase 2b: install isolated CLI tools (pipx, etc.)."""
|
|
137
|
+
|
|
138
|
+
_kind = PluginKind.TOOL
|
|
139
|
+
|
|
140
|
+
@override
|
|
141
|
+
def refresh(self, state: ExecutionState) -> None:
|
|
142
|
+
"""Re-discover plugins so that tools installed in Phase 2a become available."""
|
|
143
|
+
state.refresh_all_plugins()
|
|
144
|
+
|
|
145
|
+
async def execute(self, state: ExecutionState) -> PhaseResult: # noqa: D102
|
|
146
|
+
results, ok = await state.run_package_actions(state.phases[self._kind])
|
|
147
|
+
return PhaseResult(results=results, should_continue=ok)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ProjectPhase(_PhaseBase):
|
|
151
|
+
"""Phase 3: run project-sync (pdm install, uv sync, etc.)."""
|
|
152
|
+
|
|
153
|
+
_kind = PluginKind.PROJECT
|
|
154
|
+
|
|
155
|
+
@override
|
|
156
|
+
def refresh(self, state: ExecutionState) -> None:
|
|
157
|
+
"""Re-discover all plugins so that project environments installed in earlier phases are available."""
|
|
158
|
+
state.refresh_all_plugins()
|
|
159
|
+
|
|
160
|
+
async def execute(self, state: ExecutionState) -> PhaseResult: # noqa: D102
|
|
161
|
+
results = await state.run_project_phase(state.phases[self._kind])
|
|
162
|
+
failed = any(not r.success and not r.skipped for r in results)
|
|
163
|
+
ok = not (failed and state.parameters.fail_fast)
|
|
164
|
+
return PhaseResult(results=results, should_continue=ok)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ScmPhase(_PhaseBase):
|
|
168
|
+
"""Phase 4: clone source-control repositories."""
|
|
169
|
+
|
|
170
|
+
_kind = PluginKind.SCM
|
|
171
|
+
|
|
172
|
+
@override
|
|
173
|
+
def refresh(self, state: ExecutionState) -> None:
|
|
174
|
+
"""Re-discover plugins so that SCM tools installed in earlier phases are available."""
|
|
175
|
+
state.refresh_all_plugins()
|
|
176
|
+
|
|
177
|
+
async def execute(self, state: ExecutionState) -> PhaseResult: # noqa: D102
|
|
178
|
+
results = await state.run_scm_actions(state.phases[self._kind])
|
|
179
|
+
failed = any(not r.success and not r.skipped for r in results)
|
|
180
|
+
ok = not (failed and state.parameters.fail_fast)
|
|
181
|
+
return PhaseResult(results=results, should_continue=ok)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class CommandPhase(_PhaseBase):
|
|
185
|
+
"""Phase 5: run arbitrary post-sync shell commands."""
|
|
186
|
+
|
|
187
|
+
_kind = None
|
|
188
|
+
|
|
189
|
+
async def execute(self, state: ExecutionState) -> PhaseResult: # noqa: D102
|
|
190
|
+
results = await state.run_command_actions(state.phases[self._kind])
|
|
191
|
+
failed = any(not r.success and not r.skipped for r in results)
|
|
192
|
+
ok = not (failed and state.parameters.fail_fast)
|
|
193
|
+
return PhaseResult(results=results, should_continue=ok)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
# Canonical phase ordering
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
PHASES: list[Phase] = [
|
|
201
|
+
RuntimePhase(),
|
|
202
|
+
PackagePhase(),
|
|
203
|
+
ToolPhase(),
|
|
204
|
+
ProjectPhase(),
|
|
205
|
+
ScmPhase(),
|
|
206
|
+
CommandPhase(),
|
|
207
|
+
]
|
|
208
|
+
"""The ordered list of phases that :func:`run_phases` iterates."""
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
# Phase loop driver
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
async def run_phases(state: ExecutionState) -> None:
|
|
217
|
+
"""Execute all non-empty phases in order.
|
|
218
|
+
|
|
219
|
+
For each phase:
|
|
220
|
+
|
|
221
|
+
1. **Skip** if the phase bucket is empty.
|
|
222
|
+
2. **Refresh** — re-discover plugins and resolve deferred actions
|
|
223
|
+
(scoped to this phase only).
|
|
224
|
+
3. **Execute** — run the phase's actions and collect results.
|
|
225
|
+
4. **Post-execute** — run post-hooks (e.g. runtime propagation)
|
|
226
|
+
only when the phase completed without aborting the pipeline.
|
|
227
|
+
"""
|
|
228
|
+
for phase in PHASES:
|
|
229
|
+
actions = state.phases.get(phase.kind)
|
|
230
|
+
if not actions:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
# 1. Refresh plugins
|
|
234
|
+
phase.refresh(state)
|
|
235
|
+
|
|
236
|
+
# 2. Resolve deferred actions for THIS phase only
|
|
237
|
+
state.resolve_deferred(actions)
|
|
238
|
+
|
|
239
|
+
# 3. Execute
|
|
240
|
+
result = await phase.execute(state)
|
|
241
|
+
state.results.extend(result.results)
|
|
242
|
+
|
|
243
|
+
if not result.should_continue:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
# 4. Post-execute hooks
|
|
247
|
+
phase.post_execute(state)
|