redeploy 0.2.4__tar.gz → 0.2.5__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.
Files changed (94) hide show
  1. {redeploy-0.2.4 → redeploy-0.2.5}/PKG-INFO +6 -6
  2. {redeploy-0.2.4 → redeploy-0.2.5}/README.md +5 -5
  3. {redeploy-0.2.4 → redeploy-0.2.5}/pyproject.toml +1 -1
  4. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/__init__.py +1 -1
  5. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/cli.py +97 -1
  6. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_version_cli.py +90 -0
  7. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy.egg-info/PKG-INFO +6 -6
  8. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/apply/__init__.py +0 -0
  9. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/apply/executor.py +0 -0
  10. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/data_sync.py +0 -0
  11. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/detect/__init__.py +0 -0
  12. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/detect/detector.py +0 -0
  13. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/detect/probes.py +0 -0
  14. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/detect/remote.py +0 -0
  15. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/detect/templates.py +0 -0
  16. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/detect/workflow.py +0 -0
  17. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/discovery.py +0 -0
  18. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/dsl/__init__.py +0 -0
  19. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/dsl/loader.py +0 -0
  20. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/dsl/parser.py +0 -0
  21. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/fleet.py +0 -0
  22. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/iac/__init__.py +0 -0
  23. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/iac/base.py +0 -0
  24. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/iac/docker_compose.py +0 -0
  25. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/iac/parsers/__init__.py +0 -0
  26. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/iac/parsers/compose.py +0 -0
  27. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/iac/registry.py +0 -0
  28. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/models.py +0 -0
  29. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/observe.py +0 -0
  30. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/parse.py +0 -0
  31. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/patterns.py +0 -0
  32. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/plan/__init__.py +0 -0
  33. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/plan/planner.py +0 -0
  34. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/plugins/__init__.py +0 -0
  35. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/plugins/builtin/__init__.py +0 -0
  36. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/plugins/builtin/browser_reload.py +0 -0
  37. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/plugins/builtin/notify.py +0 -0
  38. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/plugins/builtin/systemd_reload.py +0 -0
  39. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/ssh.py +0 -0
  40. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/steps.py +0 -0
  41. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/__init__.py +0 -0
  42. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_cli.py +0 -0
  43. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_data_sync.py +0 -0
  44. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_discovery.py +0 -0
  45. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_dsl.py +0 -0
  46. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_examples.py +0 -0
  47. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_executor.py +0 -0
  48. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_fleet.py +0 -0
  49. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_git_integration.py +0 -0
  50. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_iac.py +0 -0
  51. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_manifest.py +0 -0
  52. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_models.py +0 -0
  53. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_observe.py +0 -0
  54. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_parse.py +0 -0
  55. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_patterns.py +0 -0
  56. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_planner_edge.py +0 -0
  57. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_plugins.py +0 -0
  58. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_probes.py +0 -0
  59. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_public_api.py +0 -0
  60. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_ssh.py +0 -0
  61. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_templates.py +0 -0
  62. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_verify.py +0 -0
  63. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_version.py +0 -0
  64. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_version_changelog.py +0 -0
  65. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_version_manifest.py +0 -0
  66. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_version_sources.py +0 -0
  67. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/tests/test_workflow.py +0 -0
  68. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/verify.py +0 -0
  69. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/__init__.py +0 -0
  70. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/bump.py +0 -0
  71. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/changelog.py +0 -0
  72. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/commits.py +0 -0
  73. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/diff.py +0 -0
  74. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/git_integration.py +0 -0
  75. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/git_transaction.py +0 -0
  76. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/manifest.py +0 -0
  77. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/sources/__init__.py +0 -0
  78. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/sources/base.py +0 -0
  79. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/sources/json_.py +0 -0
  80. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/sources/plain.py +0 -0
  81. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/sources/regex.py +0 -0
  82. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/sources/toml_.py +0 -0
  83. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/sources/yaml_.py +0 -0
  84. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version/transaction.py +0 -0
  85. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy/version.py +0 -0
  86. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy.egg-info/SOURCES.txt +0 -0
  87. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy.egg-info/dependency_links.txt +0 -0
  88. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy.egg-info/entry_points.txt +0 -0
  89. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy.egg-info/requires.txt +0 -0
  90. {redeploy-0.2.4 → redeploy-0.2.5}/redeploy.egg-info/top_level.txt +0 -0
  91. {redeploy-0.2.4 → redeploy-0.2.5}/setup.cfg +0 -0
  92. {redeploy-0.2.4 → redeploy-0.2.5}/tests/test_iac.py +0 -0
  93. {redeploy-0.2.4 → redeploy-0.2.5}/tests/test_parse.py +0 -0
  94. {redeploy-0.2.4 → redeploy-0.2.5}/tests/test_redeploy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.4
3
+ Version: 0.2.5
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
30
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.30-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-5.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
29
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.5-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
30
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-5.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
31
31
 
32
- - 🤖 **LLM usage:** $3.3000 (22 commits)
33
- - 👤 **Human dev:** ~$533 (5.3h @ $100/h, 30min dedup)
32
+ - 🤖 **LLM usage:** $3.4500 (23 commits)
33
+ - 👤 **Human dev:** ~$563 (5.6h @ $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
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.4-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
39
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.5-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.30-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-5.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.5-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-5.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $3.3000 (22 commits)
10
- - 👤 **Human dev:** ~$533 (5.3h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $3.4500 (23 commits)
10
+ - 👤 **Human dev:** ~$563 (5.6h @ $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
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.4-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
16
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.5-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
17
17
 
18
18
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
19
19
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redeploy"
7
- version = "0.2.4"
7
+ version = "0.2.5"
8
8
  description = "Infrastructure migration toolkit: detect → plan → apply"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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.4"
10
+ __version__ = "0.2.5"
11
11
 
12
12
  # ── Core models ───────────────────────────────────────────────────────────────
13
13
  from .models import ( # noqa: F401
@@ -2347,6 +2347,8 @@ def _calculate_bump(current: str, bump_type: str) -> str:
2347
2347
  @click.argument("version")
2348
2348
  @click.option("--manifest", "manifest_path_str", default=".redeploy/version.yaml",
2349
2349
  help="Path to version manifest")
2350
+ @click.option("--package", "package_name", "-p", help="Set version for a specific package (for monorepo)")
2351
+ @click.option("--all-packages", is_flag=True, help="Set version for all packages (for monorepo)")
2350
2352
  @click.option("--dry-run", is_flag=True, help="Preview changes without applying")
2351
2353
  @click.option("--commit", is_flag=True, help="Create git commit with changes")
2352
2354
  @click.option("--tag", is_flag=True, help="Create git tag for new version")
@@ -2354,10 +2356,13 @@ def _calculate_bump(current: str, bump_type: str) -> str:
2354
2356
  @click.option("--sign", is_flag=True, help="Sign tag with GPG")
2355
2357
  @click.option("--allow-dirty", is_flag=True, help="Allow version change with dirty working directory")
2356
2358
  @click.option("--changelog", is_flag=True, help="Update CHANGELOG.md")
2357
- def version_set(version, manifest_path_str, dry_run, commit, tag, push, sign, allow_dirty, changelog):
2359
+ def version_set(version, manifest_path_str, package_name, all_packages, dry_run, commit, tag, push, sign, allow_dirty, changelog):
2358
2360
  """Set an explicit version across all manifest sources."""
2359
2361
  from rich.console import Console
2360
2362
  from .version import VersionManifest
2363
+ from .version.bump import bump_package
2364
+ from .version.changelog import ChangelogManager, get_commits_since_tag
2365
+ from .version.git_integration import GitIntegration, GitIntegrationError
2361
2366
 
2362
2367
  console = Console()
2363
2368
  manifest_path = Path(manifest_path_str)
@@ -2368,6 +2373,97 @@ def version_set(version, manifest_path_str, dry_run, commit, tag, push, sign, al
2368
2373
  sys.exit(1)
2369
2374
 
2370
2375
  manifest_model = VersionManifest.load(manifest_path)
2376
+
2377
+ if package_name and all_packages:
2378
+ console.print("[red]✗ Use either --package NAME or --all-packages, not both.[/red]")
2379
+ sys.exit(1)
2380
+
2381
+ if package_name or all_packages:
2382
+ if not manifest_model.is_monorepo():
2383
+ console.print("[red]✗ Manifest has no packages defined. Add 'packages:' section for monorepo support.[/red]")
2384
+ sys.exit(1)
2385
+
2386
+ if package_name:
2387
+ targets = [package_name]
2388
+ pkg = manifest_model.get_package(package_name)
2389
+ if pkg is None:
2390
+ console.print(f"[red]✗ Package '{package_name}' not found in manifest[/red]")
2391
+ console.print(f" Available: {manifest_model.list_packages()}")
2392
+ sys.exit(1)
2393
+ else:
2394
+ targets = manifest_model.list_packages()
2395
+
2396
+ repo_path = manifest_path.parent.parent
2397
+
2398
+ if dry_run:
2399
+ for pkg_name in targets:
2400
+ pkg = manifest_model.get_package(pkg_name)
2401
+ console.print(f"[DRY RUN] Would set {pkg_name}: {pkg.version} → {version}")
2402
+ console.print(f" Sources: {len(pkg.sources)}")
2403
+ for source in pkg.sources:
2404
+ console.print(f" - {source.path} ({source.format})")
2405
+ return
2406
+
2407
+ try:
2408
+ git = None
2409
+ if commit or tag or push:
2410
+ git_config = manifest_model.get_package(package_name).git if package_name and manifest_model.get_package(package_name) and manifest_model.get_package(package_name).git else manifest_model.git
2411
+ git = GitIntegration(git_config, repo_path)
2412
+ if git_config.require_clean and not allow_dirty:
2413
+ git.require_clean()
2414
+
2415
+ touched_files = []
2416
+ if changelog:
2417
+ changelog_path = manifest_model.changelog.path if manifest_model.changelog else Path("CHANGELOG.md")
2418
+ changelog_mgr = ChangelogManager(repo_path / changelog_path)
2419
+ commits = get_commits_since_tag(repo_path, manifest_model.git.tag_format.format(version=manifest_model.version))
2420
+ new_content = changelog_mgr.prepare_release(version, commit_messages=commits)
2421
+ changelog_mgr.write(new_content)
2422
+ console.print(f"[green]✓ Updated {changelog_path}[/green]")
2423
+ touched_files.append(changelog_path)
2424
+
2425
+ for pkg_name in targets:
2426
+ pkg = manifest_model.get_package(pkg_name)
2427
+ result = bump_package(manifest_model, pkg_name, "patch", new_version=version)
2428
+ touched_files.extend(source.path for source in pkg.sources)
2429
+ console.print(f"[green]✓[/green] {pkg_name}: {result['old']} → {result['new_version']} ({result['success']}/{result['total']} sources)")
2430
+
2431
+ manifest_model.save(manifest_path)
2432
+ touched_files.append(manifest_path)
2433
+
2434
+ if git:
2435
+ unique_files = []
2436
+ for file_path in touched_files:
2437
+ if file_path not in unique_files:
2438
+ unique_files.append(file_path)
2439
+
2440
+ commit_hash = None
2441
+ tag_name = None
2442
+ if commit or push:
2443
+ commit_hash = git.commit(version, unique_files)
2444
+ if tag or push:
2445
+ tag_name = git.tag(version, sign=sign)
2446
+ if push:
2447
+ git.push(follow_tags=True)
2448
+
2449
+ console.print(f"[green]✓ Set version to {version} for {len(targets)} package(s)[/green]")
2450
+ if commit_hash:
2451
+ console.print(f" Commit: {commit_hash[:8]}")
2452
+ if tag_name:
2453
+ console.print(f" Tag: {tag_name}")
2454
+ if push:
2455
+ console.print(" Pushed to origin")
2456
+
2457
+ return
2458
+ except GitIntegrationError as e:
2459
+ console.print(f"[red]✗ Git error: {e}[/red]")
2460
+ sys.exit(1)
2461
+ except Exception as e:
2462
+ console.print(f"[red]✗ Version set failed: {e}[/red]")
2463
+ import traceback
2464
+ console.print(f"[dim]{traceback.format_exc()}[/dim]")
2465
+ sys.exit(1)
2466
+
2371
2467
  _bump_single(
2372
2468
  manifest_model,
2373
2469
  "patch",
@@ -32,6 +32,41 @@ def _write_version_workspace(base: Path, version: str = "1.0.0") -> None:
32
32
  )
33
33
 
34
34
 
35
+ def _write_monorepo_version_workspace(base: Path) -> None:
36
+ (base / ".redeploy").mkdir(parents=True, exist_ok=True)
37
+ (base / "backend").mkdir(parents=True, exist_ok=True)
38
+ (base / "frontend").mkdir(parents=True, exist_ok=True)
39
+ (base / "backend" / "VERSION").write_text("1.0.0\n", encoding="utf-8")
40
+ (base / "frontend" / "VERSION").write_text("2.0.0\n", encoding="utf-8")
41
+
42
+ manifest = {
43
+ "version": {
44
+ "version": "0.0.0",
45
+ "scheme": "semver",
46
+ "policy": "independent",
47
+ "sources": [],
48
+ "packages": {
49
+ "backend": {
50
+ "version": "1.0.0",
51
+ "sources": [
52
+ {"path": "backend/VERSION", "format": "plain"},
53
+ ],
54
+ },
55
+ "frontend": {
56
+ "version": "2.0.0",
57
+ "sources": [
58
+ {"path": "frontend/VERSION", "format": "plain"},
59
+ ],
60
+ },
61
+ },
62
+ }
63
+ }
64
+ (base / ".redeploy" / "version.yaml").write_text(
65
+ yaml.dump(manifest, sort_keys=False),
66
+ encoding="utf-8",
67
+ )
68
+
69
+
35
70
  class TestVersionSet:
36
71
  def test_set_updates_manifest_and_sources(self, tmp_path):
37
72
  runner = _runner()
@@ -61,6 +96,61 @@ class TestVersionSet:
61
96
  assert "DRY RUN" in result.output
62
97
  assert Path("VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
63
98
 
99
+ def test_set_package_updates_only_selected_package(self, tmp_path):
100
+ runner = _runner()
101
+ with runner.isolated_filesystem(temp_dir=tmp_path):
102
+ _write_monorepo_version_workspace(Path.cwd())
103
+
104
+ result = runner.invoke(
105
+ cli,
106
+ ["version", "set", "3.3.3", "--package", "backend"],
107
+ catch_exceptions=False,
108
+ )
109
+
110
+ assert result.exit_code == 0, result.output
111
+ assert Path("backend/VERSION").read_text(encoding="utf-8").strip() == "3.3.3"
112
+ assert Path("frontend/VERSION").read_text(encoding="utf-8").strip() == "2.0.0"
113
+
114
+ manifest = yaml.safe_load(Path(".redeploy/version.yaml").read_text(encoding="utf-8"))
115
+ assert manifest["version"]["packages"]["backend"]["version"] == "3.3.3"
116
+ assert manifest["version"]["packages"]["frontend"]["version"] == "2.0.0"
117
+ assert manifest["version"]["version"] == "0.0.0"
118
+
119
+ def test_set_all_packages_updates_all_packages(self, tmp_path):
120
+ runner = _runner()
121
+ with runner.isolated_filesystem(temp_dir=tmp_path):
122
+ _write_monorepo_version_workspace(Path.cwd())
123
+
124
+ result = runner.invoke(
125
+ cli,
126
+ ["version", "set", "4.4.4", "--all-packages"],
127
+ catch_exceptions=False,
128
+ )
129
+
130
+ assert result.exit_code == 0, result.output
131
+ assert Path("backend/VERSION").read_text(encoding="utf-8").strip() == "4.4.4"
132
+ assert Path("frontend/VERSION").read_text(encoding="utf-8").strip() == "4.4.4"
133
+
134
+ manifest = yaml.safe_load(Path(".redeploy/version.yaml").read_text(encoding="utf-8"))
135
+ assert manifest["version"]["packages"]["backend"]["version"] == "4.4.4"
136
+ assert manifest["version"]["packages"]["frontend"]["version"] == "4.4.4"
137
+
138
+ def test_set_package_dry_run_does_not_modify_monorepo(self, tmp_path):
139
+ runner = _runner()
140
+ with runner.isolated_filesystem(temp_dir=tmp_path):
141
+ _write_monorepo_version_workspace(Path.cwd())
142
+
143
+ result = runner.invoke(
144
+ cli,
145
+ ["version", "set", "5.5.5", "--package", "backend", "--dry-run"],
146
+ catch_exceptions=False,
147
+ )
148
+
149
+ assert result.exit_code == 0, result.output
150
+ assert "Would set backend: 1.0.0 → 5.5.5" in result.output
151
+ assert Path("backend/VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
152
+ assert Path("frontend/VERSION").read_text(encoding="utf-8").strip() == "2.0.0"
153
+
64
154
 
65
155
  class TestVersionDiff:
66
156
  def test_diff_spec_match_returns_zero(self, tmp_path):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.4
3
+ Version: 0.2.5
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
30
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.30-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-5.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
29
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.5-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
30
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-5.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
31
31
 
32
- - 🤖 **LLM usage:** $3.3000 (22 commits)
33
- - 👤 **Human dev:** ~$533 (5.3h @ $100/h, 30min dedup)
32
+ - 🤖 **LLM usage:** $3.4500 (23 commits)
33
+ - 👤 **Human dev:** ~$563 (5.6h @ $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
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.4-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
39
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.5-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
40
40
 
41
41
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
42
42
 
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