[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/compforge/devloop/main/scripts/bump_plugin_version.py [Back]  [Original]

#!/usr/bin/env python3
"""Bump version across all CLI manifests under a plugin directory.

Iterates /.{claude,codex,opencode}-plugin/plugin.json and writes the new
version. Designed for `make bump-version PLUGIN=`  see ../Makefile.

Usage:
    bump_plugin_version.py --plugin  [--level patch|minor|major]
    bump_plugin_version.py --plugin  --version 
"""
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",
)


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

    manifests = [plugin_dir / rel for rel in MANIFEST_RELS if (plugin_dir / rel).exists()]
    if not manifests:
        print(f"ERROR: no plugin.json found under {plugin_dir}", file=sys.stderr)
        return 1

    current_versions = []
    for p in manifests:
        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(manifests, 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}")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

Web Proxy Viewer  |  New URL  |  Original Page