-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbump_plugin_version.py
More file actions
112 lines (92 loc) · 4.06 KB
/
Copy pathbump_plugin_version.py
File metadata and controls
112 lines (92 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python3
"""Bump version across CLI manifests and an optional npm package.
Keeps <plugin>/.{claude,codex,opencode}-plugin/plugin.json, package.json, and
package-lock.json on one version. Designed for `make bump-version PLUGIN=<name>`.
Usage:
bump_plugin_version.py --plugin <name> [--level patch|minor|major]
bump_plugin_version.py --plugin <name> --version <x.y.z>
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
MANIFEST_RELS = (
".claude-plugin/plugin.json",
".codex-plugin/plugin.json",
".opencode/plugin.json",
)
PACKAGE_REL = "package.json"
def parse_version(v: str) -> tuple[int, int, int]:
parts = v.split(".")
if len(parts) != 3 or not all(p.isdigit() for p in parts):
raise ValueError(f"Invalid semver (need x.y.z all-digits): {v}")
return tuple(int(p) for p in parts) # type: ignore[return-value]
def bump(v: tuple[int, int, int], level: str) -> tuple[int, int, int]:
major, minor, patch = v
if level == "major":
return (major + 1, 0, 0)
if level == "minor":
return (major, minor + 1, 0)
if level == "patch":
return (major, minor, patch + 1)
raise ValueError(f"Unknown level: {level}")
def fmt(v: tuple[int, int, int]) -> str:
return ".".join(str(x) for x in v)
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--plugin", required=True, help="plugin directory name (e.g. devloop)")
parser.add_argument(
"--level", default="patch", choices=["patch", "minor", "major"],
help="bump level when --version not given (default: patch)",
)
parser.add_argument("--version", default=None, help="explicit semver, overrides --level")
args = parser.parse_args(argv)
plugin_dir = REPO_ROOT / args.plugin
if not plugin_dir.is_dir():
print(f"ERROR: plugin directory not found: {plugin_dir}", file=sys.stderr)
return 1
version_files = [plugin_dir / rel for rel in MANIFEST_RELS if (plugin_dir / rel).exists()]
package_file = plugin_dir / PACKAGE_REL
if package_file.exists():
version_files.append(package_file)
if not version_files:
print(f"ERROR: no plugin.json or package.json found under {plugin_dir}", file=sys.stderr)
return 1
current_versions = []
for p in version_files:
v = json.loads(p.read_text(encoding="utf-8")).get("version") or "0.0.0"
current_versions.append(parse_version(v))
if args.version:
parse_version(args.version) # validate
new_version = args.version
else:
if len(set(current_versions)) > 1:
divergent = [fmt(v) for v in current_versions]
print(
f"WARNING: divergent versions across manifests {divergent}; "
f"using max as bump basis",
file=sys.stderr,
)
basis = max(current_versions)
new_version = fmt(bump(basis, args.level))
print(f"Bumping plugin '{args.plugin}' → {new_version}")
for p, old in zip(version_files, current_versions):
data = json.loads(p.read_text(encoding="utf-8"))
data["version"] = new_version
p.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
print(f" {p.relative_to(REPO_ROOT)}: {fmt(old)} → {new_version}")
lock_file = plugin_dir / "package-lock.json"
if package_file.exists() and lock_file.exists():
data = json.loads(lock_file.read_text(encoding="utf-8"))
old = str(data.get("version") or "0.0.0")
data["version"] = new_version
root_package = data.get("packages", {}).get("")
if isinstance(root_package, dict):
root_package["version"] = new_version
lock_file.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
print(f" {lock_file.relative_to(REPO_ROOT)}: {old} → {new_version}")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))