redeploy 0.2.9__tar.gz → 0.2.10__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.9 → redeploy-0.2.10}/PKG-INFO +6 -6
- {redeploy-0.2.9 → redeploy-0.2.10}/README.md +5 -5
- {redeploy-0.2.9 → redeploy-0.2.10}/pyproject.toml +1 -1
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/__init__.py +1 -1
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/cli.py +16 -6
- redeploy-0.2.10/redeploy/spec_loader.py +41 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_cli.py +34 -0
- redeploy-0.2.10/redeploy/tests/test_spec_loader.py +44 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy.egg-info/PKG-INFO +6 -6
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy.egg-info/SOURCES.txt +2 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/discovery.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/fleet.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/models.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/observe.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/parse.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/patterns.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/ssh.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/steps.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/verify.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy/version.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/setup.cfg +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/tests/test_iac.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/tests/test_parse.py +0 -0
- {redeploy-0.2.9 → redeploy-0.2.10}/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.10
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -26,17 +26,17 @@ Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
|
26
26
|
|
|
27
27
|
## AI Cost Tracking
|
|
28
28
|
|
|
29
|
-
    
|
|
30
|
+
  
|
|
31
31
|
|
|
32
|
-
- 🤖 **LLM usage:** $4.
|
|
33
|
-
- 👤 **Human dev:** ~$
|
|
32
|
+
- 🤖 **LLM usage:** $4.3500 (29 commits)
|
|
33
|
+
- 👤 **Human dev:** ~$866 (8.7h @ $100/h, 30min dedup)
|
|
34
34
|
|
|
35
35
|
Generated on 2026-04-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
    
|
|
40
40
|
|
|
41
41
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
42
42
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $4.
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $4.3500 (29 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$866 (8.7h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-04-20 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.10"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -853,7 +853,7 @@ def run(ctx, spec_file, dry_run, plan_only, do_detect, plan_out, output, env_nam
|
|
|
853
853
|
redeploy run migration.yaml --detect --plan-out plan.yaml
|
|
854
854
|
"""
|
|
855
855
|
from rich.console import Console
|
|
856
|
-
from .models import
|
|
856
|
+
from .models import ProjectManifest
|
|
857
857
|
from .plan import Planner
|
|
858
858
|
|
|
859
859
|
console = Console()
|
|
@@ -868,7 +868,7 @@ def run(ctx, spec_file, dry_run, plan_only, do_detect, plan_out, output, env_nam
|
|
|
868
868
|
console.print("[dim] Create one with: redeploy init[/dim]")
|
|
869
869
|
sys.exit(1)
|
|
870
870
|
|
|
871
|
-
spec =
|
|
871
|
+
spec = _load_spec_or_exit(console, resolved_spec)
|
|
872
872
|
|
|
873
873
|
# overlay manifest values — env-specific or global
|
|
874
874
|
if manifest:
|
|
@@ -937,6 +937,16 @@ def _find_manifest_path() -> str:
|
|
|
937
937
|
return "redeploy.yaml"
|
|
938
938
|
|
|
939
939
|
|
|
940
|
+
def _load_spec_or_exit(console, path: str | Path):
|
|
941
|
+
from .spec_loader import SpecLoaderError, load_migration_spec
|
|
942
|
+
|
|
943
|
+
try:
|
|
944
|
+
return load_migration_spec(path)
|
|
945
|
+
except SpecLoaderError as exc:
|
|
946
|
+
console.print(f"[red]✗ {exc}[/red]")
|
|
947
|
+
sys.exit(1)
|
|
948
|
+
|
|
949
|
+
|
|
940
950
|
def _resolve_device(console, device_id: str) -> tuple:
|
|
941
951
|
"""Resolve device from registry or auto-probe. Returns (device, registry) or (None, None)."""
|
|
942
952
|
from .discovery import auto_probe
|
|
@@ -964,7 +974,7 @@ def _resolve_device(console, device_id: str) -> tuple:
|
|
|
964
974
|
|
|
965
975
|
def _load_spec_with_manifest(console, spec_file: str | None, dev) -> "MigrationSpec":
|
|
966
976
|
"""Load spec and apply manifest/device overlays."""
|
|
967
|
-
from .models import
|
|
977
|
+
from .models import ProjectManifest
|
|
968
978
|
|
|
969
979
|
manifest = ProjectManifest.find_and_load(Path.cwd())
|
|
970
980
|
resolved_spec = spec_file or (manifest.spec if manifest else "migration.yaml")
|
|
@@ -972,7 +982,7 @@ def _load_spec_with_manifest(console, spec_file: str | None, dev) -> "MigrationS
|
|
|
972
982
|
console.print(f"[red]✗ spec not found: {resolved_spec}[/red]")
|
|
973
983
|
sys.exit(1)
|
|
974
984
|
|
|
975
|
-
spec =
|
|
985
|
+
spec = _load_spec_or_exit(console, resolved_spec)
|
|
976
986
|
if manifest:
|
|
977
987
|
manifest.apply_to_spec(spec)
|
|
978
988
|
|
|
@@ -1145,7 +1155,7 @@ def status(spec_file):
|
|
|
1145
1155
|
"""
|
|
1146
1156
|
from rich.console import Console
|
|
1147
1157
|
from rich.table import Table
|
|
1148
|
-
from .models import
|
|
1158
|
+
from .models import ProjectManifest
|
|
1149
1159
|
|
|
1150
1160
|
console = Console()
|
|
1151
1161
|
|
|
@@ -1163,7 +1173,7 @@ def status(spec_file):
|
|
|
1163
1173
|
resolved = spec_file or (manifest.spec if manifest else "migration.yaml")
|
|
1164
1174
|
spec_path = Path(resolved)
|
|
1165
1175
|
if spec_path.exists():
|
|
1166
|
-
spec =
|
|
1176
|
+
spec = _load_spec_or_exit(console, spec_path)
|
|
1167
1177
|
if manifest:
|
|
1168
1178
|
manifest.apply_to_spec(spec)
|
|
1169
1179
|
console.print(f"\n[bold]{spec_path}[/bold] [dim]{spec.name}[/dim]")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Load deployment specs from supported file formats."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .models import MigrationSpec
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SpecLoaderError(ValueError):
|
|
10
|
+
"""Base error raised when a deployment spec cannot be loaded."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UnsupportedSpecFormatError(SpecLoaderError):
|
|
14
|
+
"""Raised when the spec file uses an unsupported format."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_migration_spec(path: str | Path) -> MigrationSpec:
|
|
18
|
+
"""Load a deployment spec from disk.
|
|
19
|
+
|
|
20
|
+
Supported today:
|
|
21
|
+
- `.yaml`
|
|
22
|
+
- `.yml`
|
|
23
|
+
|
|
24
|
+
Reserved for future support:
|
|
25
|
+
- `.md` markpact/markdown specs
|
|
26
|
+
"""
|
|
27
|
+
spec_path = Path(path)
|
|
28
|
+
suffix = spec_path.suffix.lower()
|
|
29
|
+
|
|
30
|
+
if suffix in {"", ".yaml", ".yml"}:
|
|
31
|
+
return MigrationSpec.from_file(spec_path)
|
|
32
|
+
|
|
33
|
+
if suffix == ".md":
|
|
34
|
+
raise UnsupportedSpecFormatError(
|
|
35
|
+
"Unsupported spec format '.md': markdown/markpact specs are not implemented yet. "
|
|
36
|
+
"Use YAML (.yaml or .yml)."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
raise UnsupportedSpecFormatError(
|
|
40
|
+
f"Unsupported spec format '{suffix}': use YAML (.yaml or .yml)."
|
|
41
|
+
)
|
|
@@ -101,6 +101,16 @@ class TestRunPlanOnly:
|
|
|
101
101
|
result = _runner().invoke(cli, ["run", str(spec), "--plan-only", "--env", "prod"])
|
|
102
102
|
assert result.exit_code == 0
|
|
103
103
|
|
|
104
|
+
def test_run_plan_only_rejects_markdown_spec_with_clear_error(self, tmp_path):
|
|
105
|
+
spec = tmp_path / "migration.md"
|
|
106
|
+
spec.write_text("# markpact prototype\n", encoding="utf-8")
|
|
107
|
+
|
|
108
|
+
result = _runner().invoke(cli, ["run", str(spec), "--plan-only"])
|
|
109
|
+
|
|
110
|
+
assert result.exit_code == 1
|
|
111
|
+
assert "Unsupported spec format '.md'" in result.output
|
|
112
|
+
assert "markdown/markpact specs are not implemented" in result.output
|
|
113
|
+
|
|
104
114
|
|
|
105
115
|
# ── redeploy status ───────────────────────────────────────────────────────────
|
|
106
116
|
|
|
@@ -123,6 +133,17 @@ class TestStatus:
|
|
|
123
133
|
assert result.exit_code == 0
|
|
124
134
|
assert "myapp" in result.output
|
|
125
135
|
|
|
136
|
+
def test_status_rejects_markdown_spec_with_clear_error(self, tmp_path):
|
|
137
|
+
spec = tmp_path / "migration.md"
|
|
138
|
+
spec.write_text("# markpact prototype\n", encoding="utf-8")
|
|
139
|
+
|
|
140
|
+
with patch("redeploy.models.ProjectManifest.find_and_load", return_value=None):
|
|
141
|
+
result = _runner().invoke(cli, ["status", str(spec)])
|
|
142
|
+
|
|
143
|
+
assert result.exit_code == 1
|
|
144
|
+
assert "Unsupported spec format '.md'" in result.output
|
|
145
|
+
assert "markdown/markpact specs are not implemented" in result.output
|
|
146
|
+
|
|
126
147
|
|
|
127
148
|
# ── redeploy init ─────────────────────────────────────────────────────────────
|
|
128
149
|
|
|
@@ -192,6 +213,19 @@ class TestDevices:
|
|
|
192
213
|
assert data[0]["id"] == "x@1"
|
|
193
214
|
|
|
194
215
|
|
|
216
|
+
class TestTarget:
|
|
217
|
+
def test_target_rejects_markdown_spec_with_clear_error(self, tmp_path):
|
|
218
|
+
spec = tmp_path / "migration.md"
|
|
219
|
+
spec.write_text("# markpact prototype\n", encoding="utf-8")
|
|
220
|
+
|
|
221
|
+
with patch("redeploy.cli._resolve_device", return_value=(None, None)):
|
|
222
|
+
result = _runner().invoke(cli, ["target", "pi@192.168.1.42", str(spec), "--plan-only"])
|
|
223
|
+
|
|
224
|
+
assert result.exit_code == 1
|
|
225
|
+
assert "Unsupported spec format '.md'" in result.output
|
|
226
|
+
assert "markdown/markpact specs are not implemented" in result.output
|
|
227
|
+
|
|
228
|
+
|
|
195
229
|
# ── redeploy apply ────────────────────────────────────────────────────────────
|
|
196
230
|
|
|
197
231
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import textwrap
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from redeploy.spec_loader import UnsupportedSpecFormatError, load_migration_spec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _migration_yaml() -> str:
|
|
11
|
+
return textwrap.dedent("""\
|
|
12
|
+
name: loader test
|
|
13
|
+
source:
|
|
14
|
+
strategy: docker_full
|
|
15
|
+
host: local
|
|
16
|
+
app: myapp
|
|
17
|
+
version: "1.0.0"
|
|
18
|
+
target:
|
|
19
|
+
strategy: docker_full
|
|
20
|
+
host: local
|
|
21
|
+
app: myapp
|
|
22
|
+
version: "1.0.1"
|
|
23
|
+
remote_dir: ~/myapp
|
|
24
|
+
""")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_load_migration_spec_reads_yaml(tmp_path):
|
|
28
|
+
spec_path = tmp_path / "migration.yaml"
|
|
29
|
+
spec_path.write_text(_migration_yaml(), encoding="utf-8")
|
|
30
|
+
|
|
31
|
+
spec = load_migration_spec(spec_path)
|
|
32
|
+
|
|
33
|
+
assert spec.name == "loader test"
|
|
34
|
+
assert spec.target.version == "1.0.1"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_load_migration_spec_rejects_markdown_for_now(tmp_path):
|
|
38
|
+
spec_path = tmp_path / "migration.md"
|
|
39
|
+
spec_path.write_text("# prototype\n", encoding="utf-8")
|
|
40
|
+
|
|
41
|
+
with pytest.raises(UnsupportedSpecFormatError) as exc_info:
|
|
42
|
+
load_migration_spec(spec_path)
|
|
43
|
+
|
|
44
|
+
assert "markdown/markpact specs are not implemented yet" in str(exc_info.value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redeploy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -26,17 +26,17 @@ Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
|
26
26
|
|
|
27
27
|
## AI Cost Tracking
|
|
28
28
|
|
|
29
|
-
    
|
|
30
|
+
  
|
|
31
31
|
|
|
32
|
-
- 🤖 **LLM usage:** $4.
|
|
33
|
-
- 👤 **Human dev:** ~$
|
|
32
|
+
- 🤖 **LLM usage:** $4.3500 (29 commits)
|
|
33
|
+
- 👤 **Human dev:** ~$866 (8.7h @ $100/h, 30min dedup)
|
|
34
34
|
|
|
35
35
|
Generated on 2026-04-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
    
|
|
40
40
|
|
|
41
41
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
42
42
|
|
|
@@ -9,6 +9,7 @@ redeploy/models.py
|
|
|
9
9
|
redeploy/observe.py
|
|
10
10
|
redeploy/parse.py
|
|
11
11
|
redeploy/patterns.py
|
|
12
|
+
redeploy/spec_loader.py
|
|
12
13
|
redeploy/ssh.py
|
|
13
14
|
redeploy/steps.py
|
|
14
15
|
redeploy/verify.py
|
|
@@ -69,6 +70,7 @@ redeploy/tests/test_planner_edge.py
|
|
|
69
70
|
redeploy/tests/test_plugins.py
|
|
70
71
|
redeploy/tests/test_probes.py
|
|
71
72
|
redeploy/tests/test_public_api.py
|
|
73
|
+
redeploy/tests/test_spec_loader.py
|
|
72
74
|
redeploy/tests/test_ssh.py
|
|
73
75
|
redeploy/tests/test_templates.py
|
|
74
76
|
redeploy/tests/test_verify.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|