redeploy 0.2.47__tar.gz → 0.2.48__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.
- {redeploy-0.2.47 → redeploy-0.2.48}/PKG-INFO +6 -6
- {redeploy-0.2.47 → redeploy-0.2.48}/README.md +5 -5
- {redeploy-0.2.47 → redeploy-0.2.48}/pyproject.toml +1 -1
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/__init__.py +1 -1
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/__init__.py +3 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/blueprint.py +2 -20
- redeploy-0.2.48/redeploy/cli/commands/bump_fix.py +245 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/device_map.py +2 -20
- redeploy-0.2.48/redeploy/steps/__init__.py +76 -0
- redeploy-0.2.47/redeploy/steps.py → redeploy-0.2.48/redeploy/steps/builtins.py +65 -118
- redeploy-0.2.48/redeploy/steps/kiosk.py +49 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/PKG-INFO +6 -6
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/SOURCES.txt +4 -1
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/audit.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/compose.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/hardware.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/infra.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/migration.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/device_map_renderers.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/hardware.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/query.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/config_apply/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/config_apply/applier.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/config_apply/loader.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/discovery.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/fleet.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/heal.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/integrations/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/integrations/op3_bridge.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/models.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/observe.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/parse.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/patterns.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/ssh.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/verify.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/setup.cfg +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_iac.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_parse.py +0 -0
- {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_redeploy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redeploy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.48
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -30,17 +30,17 @@ Requires-Dist: op3>=0.1.8; extra == "op3"
|
|
|
30
30
|
|
|
31
31
|
## AI Cost Tracking
|
|
32
32
|
|
|
33
|
-
     
|
|
34
|
+
  
|
|
35
35
|
|
|
36
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
37
|
-
- 👤 **Human dev:** ~$
|
|
36
|
+
- 🤖 **LLM usage:** $7.5000 (69 commits)
|
|
37
|
+
- 👤 **Human dev:** ~$2440 (24.4h @ $100/h, 30min dedup)
|
|
38
38
|
|
|
39
39
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
    
|
|
44
44
|
|
|
45
45
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
46
46
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
     
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $7.5000 (69 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$2440 (24.4h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
    
|
|
17
17
|
|
|
18
18
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
19
19
|
|
|
@@ -7,7 +7,7 @@ Public API (stable from 0.2.0, semver guaranteed)::
|
|
|
7
7
|
|
|
8
8
|
Everything not listed in ``__all__`` is internal and may change without notice.
|
|
9
9
|
"""
|
|
10
|
-
__version__ = "0.2.
|
|
10
|
+
__version__ = "0.2.48"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -54,6 +54,7 @@ from .commands.device_map import device_map_cmd
|
|
|
54
54
|
from .commands.blueprint import blueprint_cmd
|
|
55
55
|
from .commands.init import init
|
|
56
56
|
from .commands.status import status
|
|
57
|
+
from .commands.bump_fix import bump_cmd, fix_cmd
|
|
57
58
|
|
|
58
59
|
# Register commands
|
|
59
60
|
cli.add_command(audit)
|
|
@@ -86,6 +87,8 @@ cli.add_command(status)
|
|
|
86
87
|
cli.add_command(target)
|
|
87
88
|
cli.add_command(version_cmd)
|
|
88
89
|
cli.add_command(workflow_cmd)
|
|
90
|
+
cli.add_command(bump_cmd, name="bump")
|
|
91
|
+
cli.add_command(fix_cmd, name="fix")
|
|
89
92
|
|
|
90
93
|
|
|
91
94
|
# Backward compatibility: _resolve_device for tests
|
|
@@ -97,26 +97,8 @@ def capture(host, name, compose_files, migration_file, device_map_file,
|
|
|
97
97
|
|
|
98
98
|
def _execute_query_blueprint(bp, query_expr, output_fmt):
|
|
99
99
|
"""Execute JMESPath query on DeviceBlueprint model and output result."""
|
|
100
|
-
import
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
data = bp.model_dump(mode="json")
|
|
104
|
-
|
|
105
|
-
try:
|
|
106
|
-
result = jmespath.search(query_expr, data)
|
|
107
|
-
except jmespath.exceptions.JMESPathError as e:
|
|
108
|
-
print(f"[red]✗ JMESPath error:[/red] {e}")
|
|
109
|
-
sys.exit(1)
|
|
110
|
-
|
|
111
|
-
if result is None:
|
|
112
|
-
print("[dim]No match found for query[/dim]")
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
if output_fmt == "json":
|
|
116
|
-
click.echo(_json.dumps(result, indent=2, default=str))
|
|
117
|
-
else:
|
|
118
|
-
import yaml
|
|
119
|
-
click.echo(yaml.safe_dump(result, sort_keys=False, default_flow_style=False))
|
|
100
|
+
from ...cli.query import execute_query
|
|
101
|
+
execute_query(bp, query_expr, output_fmt, echo=click.echo)
|
|
120
102
|
|
|
121
103
|
|
|
122
104
|
# ── twin ──────────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""bump and fix commands — version bumping and self-healing deploy."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ── helpers ──────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
def _find_spec(spec_arg: str) -> Path:
|
|
15
|
+
"""Resolve spec path from '.' (use migration.yaml/migration.md) or explicit path."""
|
|
16
|
+
p = Path(spec_arg)
|
|
17
|
+
if p.is_dir():
|
|
18
|
+
for candidate in ["migration.md", "migration.yaml", "migration.yml"]:
|
|
19
|
+
c = p / candidate
|
|
20
|
+
if c.exists():
|
|
21
|
+
return c
|
|
22
|
+
# also check one level up for c2004 project pattern
|
|
23
|
+
for sub in sorted(p.rglob("migration.md"), key=lambda x: len(x.parts)):
|
|
24
|
+
return sub
|
|
25
|
+
for sub in sorted(p.rglob("migration.yaml"), key=lambda x: len(x.parts)):
|
|
26
|
+
return sub
|
|
27
|
+
raise click.ClickException(f"No migration spec found under {spec_arg!r}")
|
|
28
|
+
if not p.exists():
|
|
29
|
+
raise click.ClickException(f"Spec not found: {spec_arg!r}")
|
|
30
|
+
return p
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _find_version_file(spec_path: Path) -> Path | None:
|
|
34
|
+
"""Walk up from spec to find VERSION file."""
|
|
35
|
+
for parent in [spec_path.parent, spec_path.parent.parent,
|
|
36
|
+
spec_path.parent.parent.parent, Path.cwd()]:
|
|
37
|
+
v = parent / "VERSION"
|
|
38
|
+
if v.exists():
|
|
39
|
+
return v
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _bump_patch(version: str) -> str:
|
|
44
|
+
"""Bump the patch component: 1.0.30 → 1.0.31."""
|
|
45
|
+
m = re.match(r"^(\d+\.\d+\.)(\d+)(.*)", version.strip())
|
|
46
|
+
if not m:
|
|
47
|
+
raise click.ClickException(f"Cannot parse version: {version!r}")
|
|
48
|
+
return f"{m.group(1)}{int(m.group(2)) + 1}{m.group(3)}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _bump_version_file(version_file: Path, console: Console) -> str:
|
|
52
|
+
"""Read, increment patch, write back. Returns new version."""
|
|
53
|
+
old = version_file.read_text().strip()
|
|
54
|
+
new = _bump_patch(old)
|
|
55
|
+
version_file.write_text(new + "\n")
|
|
56
|
+
console.print(f"[green]bump[/green] {old} → [bold]{new}[/bold] ({version_file})")
|
|
57
|
+
return new
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _update_migration_header(spec_path: Path, new_version: str) -> None:
|
|
61
|
+
"""Update 'version:' line in spec file header if present."""
|
|
62
|
+
text = spec_path.read_text()
|
|
63
|
+
updated = re.sub(
|
|
64
|
+
r"^(version:\s*)[\w\.\-]+",
|
|
65
|
+
lambda m: f"{m.group(1)}{new_version}",
|
|
66
|
+
text, flags=re.MULTILINE
|
|
67
|
+
)
|
|
68
|
+
if updated != text:
|
|
69
|
+
spec_path.write_text(updated)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _load_dotenv() -> None:
|
|
73
|
+
try:
|
|
74
|
+
from dotenv import load_dotenv
|
|
75
|
+
for candidate in [Path(".env"), Path.home() / ".redeploy" / ".env"]:
|
|
76
|
+
if candidate.exists():
|
|
77
|
+
load_dotenv(candidate, override=False)
|
|
78
|
+
break
|
|
79
|
+
except ImportError:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ── bump command ──────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
@click.command("bump")
|
|
86
|
+
@click.argument("spec_or_dir", default=".", metavar="PATH")
|
|
87
|
+
@click.option(
|
|
88
|
+
"--minor", is_flag=True, help="Bump minor instead of patch (1.0.x → 1.1.0)"
|
|
89
|
+
)
|
|
90
|
+
@click.option(
|
|
91
|
+
"--major", is_flag=True, help="Bump major instead of patch (1.x.y → 2.0.0)"
|
|
92
|
+
)
|
|
93
|
+
def bump_cmd(spec_or_dir, minor, major):
|
|
94
|
+
"""Bump the project version (patch by default).
|
|
95
|
+
|
|
96
|
+
\b
|
|
97
|
+
PATH is a spec file or directory containing migration.yaml / migration.md.
|
|
98
|
+
'.' uses the current directory.
|
|
99
|
+
|
|
100
|
+
\b
|
|
101
|
+
Examples:
|
|
102
|
+
redeploy bump .
|
|
103
|
+
redeploy bump redeploy/pi109/migration.md
|
|
104
|
+
redeploy bump . --minor
|
|
105
|
+
"""
|
|
106
|
+
console = Console()
|
|
107
|
+
spec_path = _find_spec(spec_or_dir)
|
|
108
|
+
version_file = _find_version_file(spec_path)
|
|
109
|
+
|
|
110
|
+
if version_file is None:
|
|
111
|
+
console.print(
|
|
112
|
+
"[yellow]No VERSION file found — creating one alongside spec[/yellow]"
|
|
113
|
+
)
|
|
114
|
+
version_file = spec_path.parent / "VERSION"
|
|
115
|
+
version_file.write_text("1.0.0\n")
|
|
116
|
+
|
|
117
|
+
old = version_file.read_text().strip()
|
|
118
|
+
|
|
119
|
+
if major:
|
|
120
|
+
m = re.match(r"^(\d+)(.*)", old)
|
|
121
|
+
new = f"{int(m.group(1)) + 1}.0.0" if m else old
|
|
122
|
+
elif minor:
|
|
123
|
+
m = re.match(r"^(\d+)\.(\d+)(.*)", old)
|
|
124
|
+
new = f"{m.group(1)}.{int(m.group(2)) + 1}.0" if m else old
|
|
125
|
+
else:
|
|
126
|
+
new = _bump_patch(old)
|
|
127
|
+
|
|
128
|
+
version_file.write_text(new + "\n")
|
|
129
|
+
console.print(f"[green]bump[/green] {old} → [bold]{new}[/bold] ({version_file})")
|
|
130
|
+
_update_migration_header(spec_path, new)
|
|
131
|
+
console.print(f"[dim]updated spec header: {spec_path}[/dim]")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ── fix command ───────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
@click.command("fix")
|
|
137
|
+
@click.argument("spec_or_dir", default=".", metavar="PATH")
|
|
138
|
+
@click.option(
|
|
139
|
+
"--hint", "-m", default=None, metavar="TEXT",
|
|
140
|
+
help='Describe the problem, e.g. --hint "brak ikon SVG w menu"'
|
|
141
|
+
)
|
|
142
|
+
@click.option(
|
|
143
|
+
"--bump/--no-bump", default=True,
|
|
144
|
+
help="Bump version before deploying (default: on)"
|
|
145
|
+
)
|
|
146
|
+
@click.option(
|
|
147
|
+
"--retries", default=3, show_default=True,
|
|
148
|
+
help="Max LLM self-healing attempts"
|
|
149
|
+
)
|
|
150
|
+
@click.option("--dry-run", is_flag=True, help="Plan only, no apply")
|
|
151
|
+
@click.option(
|
|
152
|
+
"--env", "env_name", default="",
|
|
153
|
+
help="Named environment from redeploy.yaml"
|
|
154
|
+
)
|
|
155
|
+
def fix_cmd(spec_or_dir, hint, bump, retries, dry_run, env_name):
|
|
156
|
+
"""Self-healing deploy: bump version, then run with LLM auto-fix on failure.
|
|
157
|
+
|
|
158
|
+
\b
|
|
159
|
+
PATH is a spec file or directory containing migration.yaml / migration.md.
|
|
160
|
+
'.' uses the current directory.
|
|
161
|
+
|
|
162
|
+
\b
|
|
163
|
+
Examples:
|
|
164
|
+
redeploy fix .
|
|
165
|
+
redeploy fix . --hint "brak ikon SVG w menu"
|
|
166
|
+
redeploy fix redeploy/pi109/migration.md --no-bump
|
|
167
|
+
redeploy fix . --hint "chromium nie startuje" --retries 5
|
|
168
|
+
"""
|
|
169
|
+
console = Console()
|
|
170
|
+
_load_dotenv()
|
|
171
|
+
|
|
172
|
+
spec_path = _find_spec(spec_or_dir)
|
|
173
|
+
|
|
174
|
+
# Bump version first
|
|
175
|
+
version = ""
|
|
176
|
+
if bump and not dry_run:
|
|
177
|
+
version_file = _find_version_file(spec_path)
|
|
178
|
+
if version_file is None:
|
|
179
|
+
version_file = spec_path.parent / "VERSION"
|
|
180
|
+
version_file.write_text("1.0.0\n")
|
|
181
|
+
version = _bump_version_file(version_file, console)
|
|
182
|
+
_update_migration_header(spec_path, version)
|
|
183
|
+
|
|
184
|
+
if hint:
|
|
185
|
+
console.print(f"[cyan]hint:[/cyan] {hint}")
|
|
186
|
+
|
|
187
|
+
# Load spec + plan (reuse plan_apply helpers)
|
|
188
|
+
from ..core import load_spec_or_exit
|
|
189
|
+
from ...plan import Planner
|
|
190
|
+
from ...plugins import load_user_plugins
|
|
191
|
+
from ...models import ProjectManifest
|
|
192
|
+
|
|
193
|
+
manifest = ProjectManifest.find_and_load(Path.cwd())
|
|
194
|
+
spec = load_spec_or_exit(console, str(spec_path))
|
|
195
|
+
|
|
196
|
+
# Apply manifest overlays if any
|
|
197
|
+
if manifest and env_name:
|
|
198
|
+
env = manifest.environments.get(env_name)
|
|
199
|
+
if env:
|
|
200
|
+
if env.host:
|
|
201
|
+
spec.target.host = env.host
|
|
202
|
+
|
|
203
|
+
console.print(
|
|
204
|
+
f"\n[bold]plan[/bold] [dim]({spec_path})[/dim]"
|
|
205
|
+
)
|
|
206
|
+
planner = Planner.from_spec(spec)
|
|
207
|
+
migration = planner.run()
|
|
208
|
+
|
|
209
|
+
from ..display import print_plan_table
|
|
210
|
+
print_plan_table(console, migration)
|
|
211
|
+
|
|
212
|
+
if dry_run:
|
|
213
|
+
console.print("\n[dim]--dry-run: stopping before apply[/dim]")
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
load_user_plugins()
|
|
217
|
+
|
|
218
|
+
_print_banner(console, hint)
|
|
219
|
+
version = version or _detect_version(spec_path)
|
|
220
|
+
|
|
221
|
+
from ...heal import HealRunner
|
|
222
|
+
runner = HealRunner(
|
|
223
|
+
migration=migration,
|
|
224
|
+
spec_path=spec_path,
|
|
225
|
+
host=migration.host,
|
|
226
|
+
fix_hint=hint or "",
|
|
227
|
+
max_retries=retries,
|
|
228
|
+
dry_run=dry_run,
|
|
229
|
+
console=console,
|
|
230
|
+
version=version,
|
|
231
|
+
)
|
|
232
|
+
ok = runner.run()
|
|
233
|
+
if not ok:
|
|
234
|
+
sys.exit(1)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _print_banner(console: Console, hint: str | None) -> None:
|
|
238
|
+
console.print("\n[bold green]fix[/bold green] [dim]heal mode: on[/dim]")
|
|
239
|
+
if hint:
|
|
240
|
+
console.print(f"[cyan]user hint:[/cyan] {hint}")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _detect_version(spec_path: Path) -> str:
|
|
244
|
+
vf = _find_version_file(spec_path)
|
|
245
|
+
return vf.read_text().strip() if vf else ""
|
|
@@ -197,26 +197,8 @@ def device_map_cmd(
|
|
|
197
197
|
|
|
198
198
|
def _execute_query_device_map(console, dm, query_expr, output_fmt):
|
|
199
199
|
"""Execute JMESPath query on DeviceMap model and output result."""
|
|
200
|
-
import
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
data = dm.model_dump(mode="json")
|
|
204
|
-
|
|
205
|
-
try:
|
|
206
|
-
result = jmespath.search(query_expr, data)
|
|
207
|
-
except jmespath.exceptions.JMESPathError as e:
|
|
208
|
-
console.print(f"[red]✗ JMESPath error:[/red] {e}")
|
|
209
|
-
sys.exit(1)
|
|
210
|
-
|
|
211
|
-
if result is None:
|
|
212
|
-
console.print("[dim]No match found for query[/dim]")
|
|
213
|
-
return
|
|
214
|
-
|
|
215
|
-
if output_fmt == "json":
|
|
216
|
-
click.echo(_json.dumps(result, indent=2, default=str))
|
|
217
|
-
else:
|
|
218
|
-
import yaml
|
|
219
|
-
click.echo(yaml.safe_dump(result, sort_keys=False, default_flow_style=False))
|
|
200
|
+
from ...cli.query import execute_query
|
|
201
|
+
execute_query(dm, query_expr, output_fmt, echo=console.print)
|
|
220
202
|
|
|
221
203
|
|
|
222
204
|
# ── rendering helpers ─────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Step library — pre-defined, reusable named MigrationSteps.
|
|
2
|
+
|
|
3
|
+
Usage in a MigrationSpec::
|
|
4
|
+
|
|
5
|
+
extra_steps:
|
|
6
|
+
- id: flush_k3s_iptables # references a library step by id
|
|
7
|
+
- id: stop_nginx
|
|
8
|
+
- id: http_health_check
|
|
9
|
+
url: https://myapp.example.com/health
|
|
10
|
+
|
|
11
|
+
Usage from Python::
|
|
12
|
+
|
|
13
|
+
from redeploy.steps import StepLibrary
|
|
14
|
+
steps = StepLibrary.get("flush_k3s_iptables")
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any, Optional
|
|
19
|
+
|
|
20
|
+
from ..models import MigrationStep
|
|
21
|
+
|
|
22
|
+
from . import builtins, kiosk
|
|
23
|
+
|
|
24
|
+
# Merge domain modules into a single flat registry.
|
|
25
|
+
_LIBRARY: dict[str, MigrationStep] = {
|
|
26
|
+
s.id: s for s in (builtins.ALL + kiosk.ALL)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class StepLibrary:
|
|
31
|
+
"""Registry of pre-defined named MigrationSteps.
|
|
32
|
+
|
|
33
|
+
Steps are returned as copies so callers can override individual fields::
|
|
34
|
+
|
|
35
|
+
step = StepLibrary.get("http_health_check")
|
|
36
|
+
step.url = "https://myapp.example.com/health"
|
|
37
|
+
step.expect = "1.0.20"
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def get(step_id: str, **overrides: Any) -> Optional[MigrationStep]:
|
|
42
|
+
"""Return a copy of a named step, optionally with field overrides.
|
|
43
|
+
|
|
44
|
+
Returns ``None`` if the step_id is not in the library.
|
|
45
|
+
"""
|
|
46
|
+
template = _LIBRARY.get(step_id)
|
|
47
|
+
if template is None:
|
|
48
|
+
return None
|
|
49
|
+
data = template.model_dump()
|
|
50
|
+
data.update(overrides)
|
|
51
|
+
return MigrationStep(**data)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def list() -> list[str]:
|
|
55
|
+
"""Return sorted list of available step IDs."""
|
|
56
|
+
return sorted(_LIBRARY.keys())
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def all() -> dict[str, MigrationStep]:
|
|
60
|
+
"""Return full registry (copies)."""
|
|
61
|
+
return {k: v.model_copy() for k, v in _LIBRARY.items()}
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def resolve_from_spec(raw: dict[str, Any]) -> MigrationStep:
|
|
65
|
+
"""Resolve a raw dict (from migration YAML extra_steps) to a MigrationStep.
|
|
66
|
+
|
|
67
|
+
If ``id`` matches a library entry and no ``action`` is given, use the
|
|
68
|
+
library template as base and merge the raw dict on top.
|
|
69
|
+
"""
|
|
70
|
+
step_id = raw.get("id", "")
|
|
71
|
+
template = _LIBRARY.get(step_id)
|
|
72
|
+
if template:
|
|
73
|
+
data = template.model_dump()
|
|
74
|
+
data.update({k: v for k, v in raw.items() if v is not None})
|
|
75
|
+
return MigrationStep(**data)
|
|
76
|
+
return MigrationStep(**raw)
|