chore: normalize line endings in init diffs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
JerrettDavis 2026-04-21 20:17:14 -05:00
parent a278a7b0ba
commit b852460af9
7 changed files with 1152 additions and 1152 deletions

View file

@ -1,291 +1,291 @@
"""Tests for version-sync.py."""
import json
import subprocess
import sys
from pathlib import Path
import pytest
@pytest.fixture
def temp_project(tmp_path: Path) -> dict[str, Path]:
"""Create a temporary project with all versioned files."""
# Create directory structure
root = tmp_path / "project"
headroom = root / "headroom"
headroom.mkdir(parents=True)
repo_claude_plugin = root / ".claude-plugin"
repo_claude_plugin.mkdir(parents=True)
repo_github_plugin = root / ".github" / "plugin"
repo_github_plugin.mkdir(parents=True)
plugins = root / "plugins"
openclaw = plugins / "openclaw"
openclaw.mkdir(parents=True)
agent_hooks_claude = plugins / "headroom-agent-hooks" / ".claude-plugin"
agent_hooks_claude.mkdir(parents=True)
agent_hooks_github = plugins / "headroom-agent-hooks" / ".github" / "plugin"
agent_hooks_github.mkdir(parents=True)
sdk = root / "sdk"
typescript = sdk / "typescript"
typescript.mkdir(parents=True)
# pyproject.toml
pyproject = root / "pyproject.toml"
pyproject.write_text('[project]\nversion = "0.5.25"\n')
# headroom/_version.py
version_py = headroom / "_version.py"
version_py.write_text('"""Package version metadata."""\n\n__version__ = "0.5.25"\n')
# plugins/openclaw/package.json
openclaw_pkg = openclaw / "package.json"
openclaw_pkg.write_text(json.dumps({"name": "test", "version": "0.5.25"}))
repo_claude_marketplace = repo_claude_plugin / "marketplace.json"
repo_claude_marketplace.write_text(
json.dumps(
{
"metadata": {"name": "claude-marketplace", "version": "0.1.0"},
"plugins": [{"name": "headroom-agent-hooks", "version": "0.1.0"}],
}
)
)
repo_github_marketplace = repo_github_plugin / "marketplace.json"
repo_github_marketplace.write_text(
json.dumps(
{
"metadata": {"name": "copilot-marketplace", "version": "0.1.0"},
"plugins": [{"name": "headroom-agent-hooks", "version": "0.1.0"}],
}
)
)
claude_plugin = agent_hooks_claude / "plugin.json"
claude_plugin.write_text(json.dumps({"name": "headroom-agent-hooks", "version": "0.1.0"}))
github_plugin = agent_hooks_github / "plugin.json"
github_plugin.write_text(json.dumps({"name": "headroom-agent-hooks", "version": "0.1.0"}))
# sdk/typescript/package.json
typescript_pkg = typescript / "package.json"
typescript_pkg.write_text(json.dumps({"name": "test", "version": "0.5.25"}))
return {
"root": root,
"pyproject": pyproject,
"version_py": version_py,
"openclaw_pkg": openclaw_pkg,
"repo_claude_marketplace": repo_claude_marketplace,
"repo_github_marketplace": repo_github_marketplace,
"claude_plugin": claude_plugin,
"github_plugin": github_plugin,
"typescript_pkg": typescript_pkg,
}
def test_version_sync_explicit_version(temp_project: dict[str, Path]) -> None:
"""Test --version flag updates all files."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--version", "0.7.0"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
# Verify pyproject.toml
pyproject_content = temp_project["pyproject"].read_text()
assert 'version = "0.7.0"' in pyproject_content
# Verify headroom/_version.py
version_py_content = temp_project["version_py"].read_text()
assert '__version__ = "0.7.0"' in version_py_content
# Verify plugins/openclaw/package.json
openclaw_pkg = json.loads(temp_project["openclaw_pkg"].read_text())
assert openclaw_pkg["version"] == "0.7.0"
# Verify sdk/typescript/package.json
typescript_pkg = json.loads(temp_project["typescript_pkg"].read_text())
assert typescript_pkg["version"] == "0.7.0"
repo_claude_marketplace = json.loads(temp_project["repo_claude_marketplace"].read_text())
assert repo_claude_marketplace["metadata"]["version"] == "0.7.0"
assert repo_claude_marketplace["plugins"][0]["version"] == "0.7.0"
repo_github_marketplace = json.loads(temp_project["repo_github_marketplace"].read_text())
assert repo_github_marketplace["metadata"]["version"] == "0.7.0"
assert repo_github_marketplace["plugins"][0]["version"] == "0.7.0"
claude_plugin = json.loads(temp_project["claude_plugin"].read_text())
assert claude_plugin["version"] == "0.7.0"
github_plugin = json.loads(temp_project["github_plugin"].read_text())
assert github_plugin["version"] == "0.7.0"
# Verify .releaseetadata was created
release_metadata = root / ".releaseetadata"
assert release_metadata.exists()
metadata = json.loads(release_metadata.read_text())
assert metadata["version"] == "0.7.0"
assert metadata["packages"]["pypi"] == "0.7.0"
assert metadata["packages"]["npm-sdk"] == "0.7.0"
assert metadata["packages"]["npm-openclaw"] == "0.7.0"
assert metadata["packages"]["agent-hooks-plugin"] == "0.7.0"
def test_bump_patch(temp_project: dict[str, Path]) -> None:
"""Test --bump patch bumps 0.5.25 to 0.5.26."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--bump", "patch"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
# Verify all files updated to 0.5.26
pyproject_content = temp_project["pyproject"].read_text()
assert 'version = "0.5.26"' in pyproject_content
version_py_content = temp_project["version_py"].read_text()
assert '__version__ = "0.5.26"' in version_py_content
openclaw_pkg = json.loads(temp_project["openclaw_pkg"].read_text())
assert openclaw_pkg["version"] == "0.5.26"
typescript_pkg = json.loads(temp_project["typescript_pkg"].read_text())
assert typescript_pkg["version"] == "0.5.26"
claude_plugin = json.loads(temp_project["claude_plugin"].read_text())
assert claude_plugin["version"] == "0.5.26"
def test_bump_minor(temp_project: dict[str, Path]) -> None:
"""Test --bump minor bumps 0.5.25 to 0.6.0."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--bump", "minor"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
# Verify all files updated to 0.6.0
pyproject_content = temp_project["pyproject"].read_text()
assert 'version = "0.6.0"' in pyproject_content
version_py_content = temp_project["version_py"].read_text()
assert '__version__ = "0.6.0"' in version_py_content
openclaw_pkg = json.loads(temp_project["openclaw_pkg"].read_text())
assert openclaw_pkg["version"] == "0.6.0"
typescript_pkg = json.loads(temp_project["typescript_pkg"].read_text())
assert typescript_pkg["version"] == "0.6.0"
github_plugin = json.loads(temp_project["github_plugin"].read_text())
assert github_plugin["version"] == "0.6.0"
def test_bump_major(temp_project: dict[str, Path]) -> None:
"""Test --bump major bumps 0.5.25 to 1.0.0."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--bump", "major"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
# Verify all files updated to 1.0.0
pyproject_content = temp_project["pyproject"].read_text()
assert 'version = "1.0.0"' in pyproject_content
version_py_content = temp_project["version_py"].read_text()
assert '__version__ = "1.0.0"' in version_py_content
openclaw_pkg = json.loads(temp_project["openclaw_pkg"].read_text())
assert openclaw_pkg["version"] == "1.0.0"
typescript_pkg = json.loads(temp_project["typescript_pkg"].read_text())
assert typescript_pkg["version"] == "1.0.0"
repo_claude_marketplace = json.loads(temp_project["repo_claude_marketplace"].read_text())
assert repo_claude_marketplace["metadata"]["version"] == "1.0.0"
def test_release_metadata_written(temp_project: dict[str, Path]) -> None:
"""Test .releaseetadata is written correctly."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--version", "0.6.0"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
release_metadata = root / ".releaseetadata"
assert release_metadata.exists()
metadata = json.loads(release_metadata.read_text())
assert metadata == {
"version": "0.6.0",
"packages": {
"pypi": "0.6.0",
"npm-sdk": "0.6.0",
"npm-openclaw": "0.6.0",
"agent-hooks-plugin": "0.6.0",
},
}
def test_plugin_manifests_only_leaves_package_versions_unchanged(
temp_project: dict[str, Path],
) -> None:
"""Test plugin-only sync leaves canonical package versions alone."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[
sys.executable,
str(script),
"--root",
str(root),
"--version",
"0.8.0",
"--plugin-manifests-only",
],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
assert 'version = "0.5.25"' in temp_project["pyproject"].read_text()
assert '__version__ = "0.5.25"' in temp_project["version_py"].read_text()
assert json.loads(temp_project["openclaw_pkg"].read_text())["version"] == "0.5.25"
assert json.loads(temp_project["typescript_pkg"].read_text())["version"] == "0.5.25"
assert json.loads(temp_project["claude_plugin"].read_text())["version"] == "0.8.0"
assert (
json.loads(temp_project["repo_github_marketplace"].read_text())["metadata"]["version"]
== "0.8.0"
)
assert not (root / ".releaseetadata").exists()
"""Tests for version-sync.py."""
import json
import subprocess
import sys
from pathlib import Path
import pytest
@pytest.fixture
def temp_project(tmp_path: Path) -> dict[str, Path]:
"""Create a temporary project with all versioned files."""
# Create directory structure
root = tmp_path / "project"
headroom = root / "headroom"
headroom.mkdir(parents=True)
repo_claude_plugin = root / ".claude-plugin"
repo_claude_plugin.mkdir(parents=True)
repo_github_plugin = root / ".github" / "plugin"
repo_github_plugin.mkdir(parents=True)
plugins = root / "plugins"
openclaw = plugins / "openclaw"
openclaw.mkdir(parents=True)
agent_hooks_claude = plugins / "headroom-agent-hooks" / ".claude-plugin"
agent_hooks_claude.mkdir(parents=True)
agent_hooks_github = plugins / "headroom-agent-hooks" / ".github" / "plugin"
agent_hooks_github.mkdir(parents=True)
sdk = root / "sdk"
typescript = sdk / "typescript"
typescript.mkdir(parents=True)
# pyproject.toml
pyproject = root / "pyproject.toml"
pyproject.write_text('[project]\nversion = "0.5.25"\n')
# headroom/_version.py
version_py = headroom / "_version.py"
version_py.write_text('"""Package version metadata."""\n\n__version__ = "0.5.25"\n')
# plugins/openclaw/package.json
openclaw_pkg = openclaw / "package.json"
openclaw_pkg.write_text(json.dumps({"name": "test", "version": "0.5.25"}))
repo_claude_marketplace = repo_claude_plugin / "marketplace.json"
repo_claude_marketplace.write_text(
json.dumps(
{
"metadata": {"name": "claude-marketplace", "version": "0.1.0"},
"plugins": [{"name": "headroom-agent-hooks", "version": "0.1.0"}],
}
)
)
repo_github_marketplace = repo_github_plugin / "marketplace.json"
repo_github_marketplace.write_text(
json.dumps(
{
"metadata": {"name": "copilot-marketplace", "version": "0.1.0"},
"plugins": [{"name": "headroom-agent-hooks", "version": "0.1.0"}],
}
)
)
claude_plugin = agent_hooks_claude / "plugin.json"
claude_plugin.write_text(json.dumps({"name": "headroom-agent-hooks", "version": "0.1.0"}))
github_plugin = agent_hooks_github / "plugin.json"
github_plugin.write_text(json.dumps({"name": "headroom-agent-hooks", "version": "0.1.0"}))
# sdk/typescript/package.json
typescript_pkg = typescript / "package.json"
typescript_pkg.write_text(json.dumps({"name": "test", "version": "0.5.25"}))
return {
"root": root,
"pyproject": pyproject,
"version_py": version_py,
"openclaw_pkg": openclaw_pkg,
"repo_claude_marketplace": repo_claude_marketplace,
"repo_github_marketplace": repo_github_marketplace,
"claude_plugin": claude_plugin,
"github_plugin": github_plugin,
"typescript_pkg": typescript_pkg,
}
def test_version_sync_explicit_version(temp_project: dict[str, Path]) -> None:
"""Test --version flag updates all files."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--version", "0.7.0"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
# Verify pyproject.toml
pyproject_content = temp_project["pyproject"].read_text()
assert 'version = "0.7.0"' in pyproject_content
# Verify headroom/_version.py
version_py_content = temp_project["version_py"].read_text()
assert '__version__ = "0.7.0"' in version_py_content
# Verify plugins/openclaw/package.json
openclaw_pkg = json.loads(temp_project["openclaw_pkg"].read_text())
assert openclaw_pkg["version"] == "0.7.0"
# Verify sdk/typescript/package.json
typescript_pkg = json.loads(temp_project["typescript_pkg"].read_text())
assert typescript_pkg["version"] == "0.7.0"
repo_claude_marketplace = json.loads(temp_project["repo_claude_marketplace"].read_text())
assert repo_claude_marketplace["metadata"]["version"] == "0.7.0"
assert repo_claude_marketplace["plugins"][0]["version"] == "0.7.0"
repo_github_marketplace = json.loads(temp_project["repo_github_marketplace"].read_text())
assert repo_github_marketplace["metadata"]["version"] == "0.7.0"
assert repo_github_marketplace["plugins"][0]["version"] == "0.7.0"
claude_plugin = json.loads(temp_project["claude_plugin"].read_text())
assert claude_plugin["version"] == "0.7.0"
github_plugin = json.loads(temp_project["github_plugin"].read_text())
assert github_plugin["version"] == "0.7.0"
# Verify .releaseetadata was created
release_metadata = root / ".releaseetadata"
assert release_metadata.exists()
metadata = json.loads(release_metadata.read_text())
assert metadata["version"] == "0.7.0"
assert metadata["packages"]["pypi"] == "0.7.0"
assert metadata["packages"]["npm-sdk"] == "0.7.0"
assert metadata["packages"]["npm-openclaw"] == "0.7.0"
assert metadata["packages"]["agent-hooks-plugin"] == "0.7.0"
def test_bump_patch(temp_project: dict[str, Path]) -> None:
"""Test --bump patch bumps 0.5.25 to 0.5.26."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--bump", "patch"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
# Verify all files updated to 0.5.26
pyproject_content = temp_project["pyproject"].read_text()
assert 'version = "0.5.26"' in pyproject_content
version_py_content = temp_project["version_py"].read_text()
assert '__version__ = "0.5.26"' in version_py_content
openclaw_pkg = json.loads(temp_project["openclaw_pkg"].read_text())
assert openclaw_pkg["version"] == "0.5.26"
typescript_pkg = json.loads(temp_project["typescript_pkg"].read_text())
assert typescript_pkg["version"] == "0.5.26"
claude_plugin = json.loads(temp_project["claude_plugin"].read_text())
assert claude_plugin["version"] == "0.5.26"
def test_bump_minor(temp_project: dict[str, Path]) -> None:
"""Test --bump minor bumps 0.5.25 to 0.6.0."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--bump", "minor"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
# Verify all files updated to 0.6.0
pyproject_content = temp_project["pyproject"].read_text()
assert 'version = "0.6.0"' in pyproject_content
version_py_content = temp_project["version_py"].read_text()
assert '__version__ = "0.6.0"' in version_py_content
openclaw_pkg = json.loads(temp_project["openclaw_pkg"].read_text())
assert openclaw_pkg["version"] == "0.6.0"
typescript_pkg = json.loads(temp_project["typescript_pkg"].read_text())
assert typescript_pkg["version"] == "0.6.0"
github_plugin = json.loads(temp_project["github_plugin"].read_text())
assert github_plugin["version"] == "0.6.0"
def test_bump_major(temp_project: dict[str, Path]) -> None:
"""Test --bump major bumps 0.5.25 to 1.0.0."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--bump", "major"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
# Verify all files updated to 1.0.0
pyproject_content = temp_project["pyproject"].read_text()
assert 'version = "1.0.0"' in pyproject_content
version_py_content = temp_project["version_py"].read_text()
assert '__version__ = "1.0.0"' in version_py_content
openclaw_pkg = json.loads(temp_project["openclaw_pkg"].read_text())
assert openclaw_pkg["version"] == "1.0.0"
typescript_pkg = json.loads(temp_project["typescript_pkg"].read_text())
assert typescript_pkg["version"] == "1.0.0"
repo_claude_marketplace = json.loads(temp_project["repo_claude_marketplace"].read_text())
assert repo_claude_marketplace["metadata"]["version"] == "1.0.0"
def test_release_metadata_written(temp_project: dict[str, Path]) -> None:
"""Test .releaseetadata is written correctly."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[sys.executable, str(script), "--root", str(root), "--version", "0.6.0"],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
release_metadata = root / ".releaseetadata"
assert release_metadata.exists()
metadata = json.loads(release_metadata.read_text())
assert metadata == {
"version": "0.6.0",
"packages": {
"pypi": "0.6.0",
"npm-sdk": "0.6.0",
"npm-openclaw": "0.6.0",
"agent-hooks-plugin": "0.6.0",
},
}
def test_plugin_manifests_only_leaves_package_versions_unchanged(
temp_project: dict[str, Path],
) -> None:
"""Test plugin-only sync leaves canonical package versions alone."""
root = temp_project["root"]
script = Path(__file__).parent.parent / "version-sync.py"
result = subprocess.run(
[
sys.executable,
str(script),
"--root",
str(root),
"--version",
"0.8.0",
"--plugin-manifests-only",
],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Script failed: {result.stderr}"
assert 'version = "0.5.25"' in temp_project["pyproject"].read_text()
assert '__version__ = "0.5.25"' in temp_project["version_py"].read_text()
assert json.loads(temp_project["openclaw_pkg"].read_text())["version"] == "0.5.25"
assert json.loads(temp_project["typescript_pkg"].read_text())["version"] == "0.5.25"
assert json.loads(temp_project["claude_plugin"].read_text())["version"] == "0.8.0"
assert (
json.loads(temp_project["repo_github_marketplace"].read_text())["metadata"]["version"]
== "0.8.0"
)
assert not (root / ".releaseetadata").exists()

View file

@ -1,190 +1,190 @@
#!/usr/bin/env python3
"""Synchronize version across all headroom packages."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
import tomllib
def get_version_from_pyproject(root: Path) -> str:
"""Read version from pyproject.toml."""
pyproject_path = root / "pyproject.toml"
with open(pyproject_path, "rb") as f:
data = tomllib.load(f)
return data["project"]["version"]
def bump_version(version: str, bump_type: str) -> str:
"""Bump version according to bump_type (major, minor, patch)."""
major, minor, patch = map(int, version.split("."))
if bump_type == "major":
major += 1
minor = 0
patch = 0
elif bump_type == "minor":
minor += 1
patch = 0
elif bump_type == "patch":
patch += 1
return f"{major}.{minor}.{patch}"
def update_version_py(root: Path, version: str) -> None:
"""Update headroom/_version.py with new version."""
version_py_path = root / "headroom" / "_version.py"
content = version_py_path.read_text(encoding="utf-8")
updated = re.sub(
r'__version__ = "[^"]+"',
f'__version__ = "{version}"',
content,
)
version_py_path.write_text(updated, encoding="utf-8")
def update_package_json(file_path: Path, version: str) -> None:
"""Update a package.json version field."""
with open(file_path, encoding="utf-8") as f:
data = json.load(f)
data["version"] = version
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def update_plugin_manifest(file_path: Path, version: str) -> None:
"""Update a plugin.json version field."""
with open(file_path, encoding="utf-8") as f:
data = json.load(f)
data["version"] = version
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def update_marketplace_manifest(file_path: Path, version: str) -> None:
"""Update marketplace metadata and plugin entry versions."""
with open(file_path, encoding="utf-8") as f:
data = json.load(f)
metadata = data.get("metadata")
if isinstance(metadata, dict):
metadata["version"] = version
plugins = data.get("plugins")
if isinstance(plugins, list):
for plugin in plugins:
if isinstance(plugin, dict):
plugin["version"] = version
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def update_plugin_versions(root: Path, version: str) -> None:
"""Update marketplace and plugin manifest versions."""
update_marketplace_manifest(root / ".claude-plugin" / "marketplace.json", version)
update_marketplace_manifest(root / ".github" / "plugin" / "marketplace.json", version)
update_plugin_manifest(
root / "plugins" / "headroom-agent-hooks" / ".claude-plugin" / "plugin.json", version
)
update_plugin_manifest(
root / "plugins" / "headroom-agent-hooks" / ".github" / "plugin" / "plugin.json",
version,
)
def update_openclaw_package_json(file_path: Path, version: str, sdk_version: str) -> None:
"""Update openclaw package.json version and headroom-ai dependency range."""
with open(file_path, encoding="utf-8") as f:
data = json.load(f)
data["version"] = version
if "dependencies" in data and "headroom-ai" in data["dependencies"]:
data["dependencies"]["headroom-ai"] = f"^{sdk_version}"
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def update_pyproject_version(root: Path, version: str) -> None:
"""Update pyproject.toml version."""
pyproject_path = root / "pyproject.toml"
content = pyproject_path.read_text(encoding="utf-8")
updated = re.sub(
r'^version = "[^"]+"',
f'version = "{version}"',
content,
flags=re.MULTILINE,
)
pyproject_path.write_text(updated, encoding="utf-8")
def write_release_metadata(root: Path, version: str) -> None:
"""Write .releaseetadata JSON file."""
metadata = {
"version": version,
"packages": {
"pypi": version,
"npm-sdk": version,
"npm-openclaw": version,
"agent-hooks-plugin": version,
},
}
metadata_path = root / ".releaseetadata"
with open(metadata_path, "w", encoding="utf-8") as f:
json.dump(metadata, f, indent=2)
f.write("\n")
def main() -> None:
parser = argparse.ArgumentParser(description="Synchronize version across headroom packages")
parser.add_argument(
"--root",
type=Path,
default=Path(__file__).parent.parent,
help="Root directory of the project",
)
group = parser.add_mutually_exclusive_group()
group.add_argument("--version", help="Explicit version to set (e.g., 0.6.0)")
group.add_argument(
"--bump",
choices=["major", "minor", "patch"],
help="Bump version from pyproject.toml",
)
parser.add_argument(
"--plugin-manifests-only",
action="store_true",
help="Only update marketplace/plugin manifest versions",
)
args = parser.parse_args()
if args.version:
version = args.version
elif args.bump:
base_version = get_version_from_pyproject(args.root)
version = bump_version(base_version, args.bump)
else:
version = get_version_from_pyproject(args.root)
if args.plugin_manifests_only:
update_plugin_versions(args.root, version)
print(f"Plugin versions synchronized to {version}")
return
# Update all versioned files
update_pyproject_version(args.root, version)
update_version_py(args.root, version)
update_openclaw_package_json(
args.root / "plugins" / "openclaw" / "package.json", version, version
)
update_package_json(args.root / "sdk" / "typescript" / "package.json", version)
update_plugin_versions(args.root, version)
write_release_metadata(args.root, version)
print(f"Version synchronized to {version}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Synchronize version across all headroom packages."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
import tomllib
def get_version_from_pyproject(root: Path) -> str:
"""Read version from pyproject.toml."""
pyproject_path = root / "pyproject.toml"
with open(pyproject_path, "rb") as f:
data = tomllib.load(f)
return data["project"]["version"]
def bump_version(version: str, bump_type: str) -> str:
"""Bump version according to bump_type (major, minor, patch)."""
major, minor, patch = map(int, version.split("."))
if bump_type == "major":
major += 1
minor = 0
patch = 0
elif bump_type == "minor":
minor += 1
patch = 0
elif bump_type == "patch":
patch += 1
return f"{major}.{minor}.{patch}"
def update_version_py(root: Path, version: str) -> None:
"""Update headroom/_version.py with new version."""
version_py_path = root / "headroom" / "_version.py"
content = version_py_path.read_text(encoding="utf-8")
updated = re.sub(
r'__version__ = "[^"]+"',
f'__version__ = "{version}"',
content,
)
version_py_path.write_text(updated, encoding="utf-8")
def update_package_json(file_path: Path, version: str) -> None:
"""Update a package.json version field."""
with open(file_path, encoding="utf-8") as f:
data = json.load(f)
data["version"] = version
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def update_plugin_manifest(file_path: Path, version: str) -> None:
"""Update a plugin.json version field."""
with open(file_path, encoding="utf-8") as f:
data = json.load(f)
data["version"] = version
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def update_marketplace_manifest(file_path: Path, version: str) -> None:
"""Update marketplace metadata and plugin entry versions."""
with open(file_path, encoding="utf-8") as f:
data = json.load(f)
metadata = data.get("metadata")
if isinstance(metadata, dict):
metadata["version"] = version
plugins = data.get("plugins")
if isinstance(plugins, list):
for plugin in plugins:
if isinstance(plugin, dict):
plugin["version"] = version
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def update_plugin_versions(root: Path, version: str) -> None:
"""Update marketplace and plugin manifest versions."""
update_marketplace_manifest(root / ".claude-plugin" / "marketplace.json", version)
update_marketplace_manifest(root / ".github" / "plugin" / "marketplace.json", version)
update_plugin_manifest(
root / "plugins" / "headroom-agent-hooks" / ".claude-plugin" / "plugin.json", version
)
update_plugin_manifest(
root / "plugins" / "headroom-agent-hooks" / ".github" / "plugin" / "plugin.json",
version,
)
def update_openclaw_package_json(file_path: Path, version: str, sdk_version: str) -> None:
"""Update openclaw package.json version and headroom-ai dependency range."""
with open(file_path, encoding="utf-8") as f:
data = json.load(f)
data["version"] = version
if "dependencies" in data and "headroom-ai" in data["dependencies"]:
data["dependencies"]["headroom-ai"] = f"^{sdk_version}"
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def update_pyproject_version(root: Path, version: str) -> None:
"""Update pyproject.toml version."""
pyproject_path = root / "pyproject.toml"
content = pyproject_path.read_text(encoding="utf-8")
updated = re.sub(
r'^version = "[^"]+"',
f'version = "{version}"',
content,
flags=re.MULTILINE,
)
pyproject_path.write_text(updated, encoding="utf-8")
def write_release_metadata(root: Path, version: str) -> None:
"""Write .releaseetadata JSON file."""
metadata = {
"version": version,
"packages": {
"pypi": version,
"npm-sdk": version,
"npm-openclaw": version,
"agent-hooks-plugin": version,
},
}
metadata_path = root / ".releaseetadata"
with open(metadata_path, "w", encoding="utf-8") as f:
json.dump(metadata, f, indent=2)
f.write("\n")
def main() -> None:
parser = argparse.ArgumentParser(description="Synchronize version across headroom packages")
parser.add_argument(
"--root",
type=Path,
default=Path(__file__).parent.parent,
help="Root directory of the project",
)
group = parser.add_mutually_exclusive_group()
group.add_argument("--version", help="Explicit version to set (e.g., 0.6.0)")
group.add_argument(
"--bump",
choices=["major", "minor", "patch"],
help="Bump version from pyproject.toml",
)
parser.add_argument(
"--plugin-manifests-only",
action="store_true",
help="Only update marketplace/plugin manifest versions",
)
args = parser.parse_args()
if args.version:
version = args.version
elif args.bump:
base_version = get_version_from_pyproject(args.root)
version = bump_version(base_version, args.bump)
else:
version = get_version_from_pyproject(args.root)
if args.plugin_manifests_only:
update_plugin_versions(args.root, version)
print(f"Plugin versions synchronized to {version}")
return
# Update all versioned files
update_pyproject_version(args.root, version)
update_version_py(args.root, version)
update_openclaw_package_json(
args.root / "plugins" / "openclaw" / "package.json", version, version
)
update_package_json(args.root / "sdk" / "typescript" / "package.json", version)
update_plugin_versions(args.root, version)
write_release_metadata(args.root, version)
print(f"Version synchronized to {version}")
if __name__ == "__main__":
main()