| 
 | 1 | +# Copyright (c) Jupyter Development Team.  | 
 | 2 | +# Distributed under the terms of the Modified BSD License.  | 
 | 3 | + | 
 | 4 | +"""  | 
 | 5 | +The "bump version" script used by Jupyter Releaser to increment the version of  | 
 | 6 | +Jupyter AI on manual release workflow runs. This script:  | 
 | 7 | +
  | 
 | 8 | +- Accepts a *specified version* `spec_version` as the first positional argument.  | 
 | 9 | +`spec_version` must be either a PEP-440 version string or "minor" literally.  | 
 | 10 | +
  | 
 | 11 | +    - Normal release version examples: "0.1.0", "1.2.3", "3.0.0"  | 
 | 12 | +
  | 
 | 13 | +    - Pre-release version examples: "3.0.0a0", "3.0.0b1", "3.0.0rc2"  | 
 | 14 | +
  | 
 | 15 | +    - **NOTE**: This script was not designed to support dev & post-releases for  | 
 | 16 | +    simplicity. By convention, our repo prefers patch releases over  | 
 | 17 | +    post-releases and pre-releases over dev releases.  | 
 | 18 | +
  | 
 | 19 | +- Bumps `jupyter-ai` and `jupyter-ai-magics` to `spec_version`. If  | 
 | 20 | +`spec_version` is "minor", then this script bumps the minor version of each  | 
 | 21 | +package.  | 
 | 22 | +
  | 
 | 23 | +- Updates `jupyter-ai`'s required version of `jupyter-ai-magics` to exactly  | 
 | 24 | +match the specified version. In other words, this script ensures  | 
 | 25 | +`jupyter-ai==x.y.z` always depends on `jupyter-ai-magics==x.y.z` exactly.  | 
 | 26 | +
  | 
 | 27 | +- If `--skip-if-dirty` is passed, successive calls do nothing. This is a  | 
 | 28 | +temporary workaround for  | 
 | 29 | +https://github.com/jupyter-server/jupyter_releaser/issues/567.  | 
 | 30 | +"""  | 
 | 31 | + | 
 | 32 | +from pathlib import Path  | 
 | 33 | + | 
 | 34 | +import click  | 
 | 35 | +import tomlkit  | 
 | 36 | +from jupyter_releaser.util import get_version, run  | 
 | 37 | +from packaging.version import Version  | 
 | 38 | +from pkg_resources import parse_version  | 
 | 39 | + | 
 | 40 | +MONOREPO_ROOT = Path(__file__).parent.parent.resolve()  | 
 | 41 | +LERNA_CMD = "npx -p lerna@6.4.1 -y lerna version --no-push --force-publish --no-git-tag-version -y"  | 
 | 42 | + | 
 | 43 | + | 
 | 44 | +@click.command()  | 
 | 45 | +@click.option("--ignore-dirty", default=False, is_flag=True)  | 
 | 46 | +@click.option("--skip-if-dirty", default=False, is_flag=True)  | 
 | 47 | +@click.argument("spec", nargs=1)  | 
 | 48 | +def bump_version(ignore_dirty: bool, skip_if_dirty: bool, spec: str):  | 
 | 49 | +    is_dirty = len(run("git status --porcelain").strip()) > 0  | 
 | 50 | +    if is_dirty and not ignore_dirty:  | 
 | 51 | +        if skip_if_dirty:  | 
 | 52 | +            print(  | 
 | 53 | +                "Skipping this call as the repo is in a dirty state with untracked files."  | 
 | 54 | +            )  | 
 | 55 | +            return  | 
 | 56 | +        raise Exception("Must be in a clean git state with no untracked files")  | 
 | 57 | + | 
 | 58 | +    next_version: Version = compute_next_version(spec)  | 
 | 59 | + | 
 | 60 | +    # convert the PyPI version string to a NPM version string  | 
 | 61 | +    next_version_npm = f"{next_version.major}.{next_version.minor}.{next_version.micro}"  | 
 | 62 | +    if next_version.pre:  | 
 | 63 | +        pre_type, pre_number = next_version.pre  | 
 | 64 | +        if pre_type == "a":  | 
 | 65 | +            pre_type = "alpha"  | 
 | 66 | +        elif pre_type == "b":  | 
 | 67 | +            pre_type = "beta"  | 
 | 68 | +        elif pre_type == "rc":  | 
 | 69 | +            pre_type = "rc"  | 
 | 70 | +        else:  | 
 | 71 | +            raise Exception(f"Unrecognized pre-release type: '{pre_type}'.")  | 
 | 72 | +        next_version_npm += f"-{pre_type}.{pre_number}"  | 
 | 73 | + | 
 | 74 | +    # bump the versions in NPM packages  | 
 | 75 | +    #  | 
 | 76 | +    # Note: `_version.py` files do not need to be updated manually.  | 
 | 77 | +    # `hatch-nodejs-version` updates those files automatically on build, setting  | 
 | 78 | +    # them to the version specified in the corresponding package.json file.  | 
 | 79 | +    lerna_cmd = f"{LERNA_CMD} {next_version_npm}"  | 
 | 80 | +    run(lerna_cmd)  | 
 | 81 | + | 
 | 82 | +    # bump the version of `jupyter-ai-magics` required by `jupyter-ai`  | 
 | 83 | +    jai_pyproject_path = MONOREPO_ROOT / "packages" / "jupyter-ai" / "pyproject.toml"  | 
 | 84 | +    jai_pyproject = tomlkit.parse(jai_pyproject_path.read_text())  | 
 | 85 | +    jai_deps = jai_pyproject.get("project").get("dependencies")  | 
 | 86 | +    for i, dep in enumerate(jai_deps):  | 
 | 87 | +        if str(dep).startswith("jupyter_ai_magics"):  | 
 | 88 | +            next_major_version = f"{next_version.major + 1}.0.0"  | 
 | 89 | +            jai_deps[i] = (  | 
 | 90 | +                f"jupyter_ai_magics>={str(next_version)},<{next_major_version}"  | 
 | 91 | +            )  | 
 | 92 | +            break  | 
 | 93 | + | 
 | 94 | +    # write updated pyproject.toml file  | 
 | 95 | +    jai_pyproject_path.write_text(tomlkit.dumps(jai_pyproject))  | 
 | 96 | + | 
 | 97 | + | 
 | 98 | +def compute_next_version(spec: str) -> Version:  | 
 | 99 | +    if spec == "minor":  | 
 | 100 | +        curr_version = parse_version(get_version())  | 
 | 101 | +        next_version = parse_version(  | 
 | 102 | +            f"{curr_version.major}.{curr_version.minor + 1}.{curr_version.micro}"  | 
 | 103 | +        )  | 
 | 104 | +    else:  | 
 | 105 | +        next_version = parse_version(spec)  | 
 | 106 | + | 
 | 107 | +    return next_version  | 
 | 108 | + | 
 | 109 | + | 
 | 110 | +if __name__ == "__main__":  | 
 | 111 | +    bump_version()  | 
0 commit comments