diff --git a/.bumpversion.toml b/.bumpversion.toml new file mode 100644 index 0000000..5c55e7b --- /dev/null +++ b/.bumpversion.toml @@ -0,0 +1,34 @@ +[tool.bumpversion] +current_version = "0.2.0-beta.1" +parse = """(?x) + (?P0|[1-9]\\d*)\\. + (?P0|[1-9]\\d*)\\. + (?P0|[1-9]\\d*) + (?:-(?P[a-zA-Z-]+)\\.(?P0|[1-9]\\d*))? +""" +serialize = [ + "{major}.{minor}.{patch}-{pre_l}.{pre_n}", + "{major}.{minor}.{patch}", +] +search = "{current_version}" +replace = "{new_version}" +regex = false +ignore_missing_version = false +ignore_missing_files = false +tag = true +sign_tags = false +tag_name = "v{new_version}" +tag_message = "Bump version: {current_version} → {new_version}" +allow_dirty = true +commit = true +message = "Bump version: {current_version} → {new_version}" +commit_args = "" + +[tool.bumpversion.parts.pre_l] +optional_value = "final" +values = ["beta", "final"] + +[[tool.bumpversion.files]] +filename = "package.json" +replace = "\"version\": \"{new_version}\"," +search = "\"version\": \"{current_version}\"," diff --git a/.github/workflows/make-release-commit.yml b/.github/workflows/make-release-commit.yml new file mode 100644 index 0000000..0a2961a --- /dev/null +++ b/.github/workflows/make-release-commit.yml @@ -0,0 +1,66 @@ +name: Create release commit + +# This workflow increments versions, tags the version, and pushes it. +# When a tag is pushed, another workflow is triggered that creates a GH release +# and uploads the binaries. This workflow is only for creating the tag. + +# This script will enforce that a minor version is incremented if there are any +# breaking changes since the last minor increment +on: + workflow_dispatch: + inputs: + dry_run: + description: "Dry run (create the local commit/tags but do not push it)" + required: true + default: false + type: boolean + bump-minor: + description: "Bump minor version" + required: true + default: false + type: boolean + +jobs: + make-release: + # Creates tag and GH release. The GH release will trigger the build and release jobs. + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Output Inputs + run: echo "${{ toJSON(github.event.inputs) }}" + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + lfs: true + # It's important we use our token here, as the default token will NOT + # trigger any workflows watching for new tags. See: + # https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow + token: ${{ secrets.FSQL_DEPLOY_TOKEN }} + - name: Set git configs for bumpversion + shell: bash + run: | + git config user.name 'Lance Release' + git config user.email 'lance-dev@lancedb.com' + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Bump Node/Rust version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + pip install bump-my-version PyGithub packaging + bash ci/bump_version.sh ${{ inputs.bump-minor }} $COMMIT_BEFORE_BUMP + - name: Push new version tag + if: ${{ !inputs.dry_run }} + uses: ad-m/github-push-action@master + with: + # Need to use PAT here too to trigger next workflow. See comment above. + github_token: ${{ secrets.FSQL_DEPLOY_TOKEN }} + branch: ${{ github.ref }} + tags: true + - uses: ./.github/workflows/update_package_lock + if: ${{ !inputs.dry_run }} + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update_package_lock/action.yml b/.github/workflows/update_package_lock/action.yml new file mode 100644 index 0000000..eb8f10f --- /dev/null +++ b/.github/workflows/update_package_lock/action.yml @@ -0,0 +1,37 @@ +name: update_package_lock +description: "Update node's package.lock" + +inputs: + github_token: + required: true + description: "github token for the repo" + dry_run: + required: true + description: "Dry run (create the local commit but do not push it)" + default: false + type: boolean + +runs: + using: "composite" + steps: + - uses: actions/setup-node@v3 + with: + node-version: 20 + - name: Set git configs + shell: bash + run: | + git config user.name 'Lance Release' + git config user.email 'lance-dev@lancedb.com' + - name: Update package-lock.json file + run: | + npm install + git add package-lock.json + git commit -m "Updating package-lock.json" + shell: bash + - name: Push changes + if: ${{ inputs.dry_run }} == "false" + uses: ad-m/github-push-action@master + with: + github_token: ${{ inputs.github_token }} + branch: main + tags: true diff --git a/ci/bump_version.sh b/ci/bump_version.sh new file mode 100644 index 0000000..5a77288 --- /dev/null +++ b/ci/bump_version.sh @@ -0,0 +1,35 @@ +set -e + +BUMP_MINOR=${1:-false} +HEAD_SHA=${2:-$(git rev-parse HEAD)} + +TAG_PREFIX="v" + +readonly SELF_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +PREV_TAG=$(git tag --sort='version:refname' | grep ^$TAG_PREFIX | python $SELF_DIR/semver_sort.py $TAG_PREFIX | tail -n 1) +echo "Found previous tag $PREV_TAG" + +if [[ "$BUMP_MINOR" != "false" ]]; then + # X.Y.Z -> X.(Y+1).0-beta.0 + bump-my-version bump -vv --no-commit --no-tag minor +else + # X.Y.Z -> X.Y.(Z+1)-beta.0 + bump-my-version bump -vv --no-commit --no-tag patch +fi + +# The above bump will always bump to a pre-release version. +# Now bump the pre-release level ("pre_l") to make it stable. + +# X.Y.Z-beta.N -> X.Y.Z +bump-my-version bump -vv pre_l + +# Validate that we have incremented version appropriately for breaking changes +NEW_TAG=$(git describe --tags --exact-match HEAD) +NEW_VERSION=$(echo $NEW_TAG | sed "s/^$TAG_PREFIX//") +LAST_STABLE_RELEASE=$(git tag --sort='version:refname' | grep ^$TAG_PREFIX | grep -v beta | grep -vF "$NEW_TAG" | python $SELF_DIR/semver_sort.py $TAG_PREFIX | tail -n 1) +LAST_STABLE_VERSION=$(echo $LAST_STABLE_RELEASE | sed "s/^$TAG_PREFIX//") + +echo "Checking for breaking changes between $LAST_STABLE_RELEASE / $LAST_STABLE_VERSION and $HEAD_SHA / $NEW_VERSION" + +python $SELF_DIR/check_breaking_changes.py $LAST_STABLE_RELEASE $HEAD_SHA $LAST_STABLE_VERSION $NEW_VERSION diff --git a/ci/check_breaking_changes.py b/ci/check_breaking_changes.py new file mode 100644 index 0000000..6c75436 --- /dev/null +++ b/ci/check_breaking_changes.py @@ -0,0 +1,35 @@ +""" +Check whether there are any breaking changes in the PRs between the base and head commits. +If there are, assert that we have incremented the minor version. +""" +import argparse +import os +from packaging.version import parse + +from github import Github + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("base") + parser.add_argument("head") + parser.add_argument("last_stable_version") + parser.add_argument("current_version") + args = parser.parse_args() + + repo = Github(os.environ["GITHUB_TOKEN"]).get_repo(os.environ["GITHUB_REPOSITORY"]) + commits = repo.compare(args.base, args.head).commits + prs = (pr for commit in commits for pr in commit.get_pulls()) + + for pr in prs: + if any(label.name == "breaking-change" for label in pr.labels): + print(f"Breaking change in PR: {pr.html_url}") + break + else: + print("No breaking changes found.") + exit(0) + + last_stable_version = parse(args.last_stable_version) + current_version = parse(args.current_version) + if current_version.minor <= last_stable_version.minor: + print("Minor version is not greater than the last stable version.") + exit(1) diff --git a/ci/semver_sort.py b/ci/semver_sort.py new file mode 100644 index 0000000..5f99c6c --- /dev/null +++ b/ci/semver_sort.py @@ -0,0 +1,36 @@ +""" +Takes a list of semver strings and sorts them in ascending order. +""" + +import sys +from packaging.version import parse, InvalidVersion + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("prefix", default="v") + args = parser.parse_args() + + # Read the input from stdin + lines = sys.stdin.readlines() + + # Parse the versions + versions = [] + for line in lines: + line = line.strip() + try: + version_str = line.removeprefix(args.prefix) + version = parse(version_str) + except InvalidVersion: + # There are old tags that don't follow the semver format + print(f"Invalid version: {line}", file=sys.stderr) + continue + versions.append((line, version)) + + # Sort the versions + versions.sort(key=lambda x: x[1]) + + # Print the sorted versions as original strings + for line, _ in versions: + print(line) diff --git a/package-lock.json b/package-lock.json index 689a388..c38286c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lancedb/arrow-flight-sql-client", - "version": "0.1.0", + "version": "0.2.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lancedb/arrow-flight-sql-client", - "version": "0.1.0", + "version": "0.2.0-beta.1", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.12.4", diff --git a/package.json b/package.json index 7295549..12e071e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lancedb/arrow-flight-sql-client", - "version": "0.1.0", + "version": "0.2.0-beta.1", "description": "A native typescript project for querying Flight SQL endpoints", "main": "dist/index.js", "types": "dist/index.d.ts",