diff --git a/.bumpversion.toml b/.bumpversion.toml new file mode 100644 index 000000000..429dc4dfe --- /dev/null +++ b/.bumpversion.toml @@ -0,0 +1,90 @@ +[tool.bumpversion] +current_version = "0.0.10" +parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" +serialize = ["{major}.{minor}.{patch}"] +search = "{current_version}" +replace = "{new_version}" +regex = false +ignore_missing_files = false +ignore_missing_version = false +tag = false +sign_tags = false +tag_name = "v{new_version}" +tag_message = "Release version {new_version}" +allow_dirty = false +commit = false +message = "chore: bump version {current_version} โ†’ {new_version}" + +# Makefiles - VERSION variable +[[tool.bumpversion.files]] +filename = "java/Makefile" +search = "VERSION = {current_version}" +replace = "VERSION = {new_version}" + +[[tool.bumpversion.files]] +filename = "python/Makefile" +search = "VERSION = {current_version}" +replace = "VERSION = {new_version}" + +[[tool.bumpversion.files]] +filename = "rust/Makefile" +search = "VERSION = {current_version}" +replace = "VERSION = {new_version}" + +# Java pom.xml files (non-auto-generated) +[[tool.bumpversion.files]] +filename = "java/pom.xml" +search = "{current_version}" +replace = "{new_version}" + +[[tool.bumpversion.files]] +filename = "java/lance-namespace-core/pom.xml" +search = "{current_version}" +replace = "{new_version}" + +[[tool.bumpversion.files]] +filename = "java/lance-namespace-adapter/pom.xml" +search = "{current_version}" +replace = "{new_version}" + +[[tool.bumpversion.files]] +filename = "java/lance-namespace-hive2/pom.xml" +search = "{current_version}" +replace = "{new_version}" + +[[tool.bumpversion.files]] +filename = "java/lance-namespace-hive3/pom.xml" +search = "{current_version}" +replace = "{new_version}" + +[[tool.bumpversion.files]] +filename = "java/lance-namespace-glue/pom.xml" +search = "{current_version}" +replace = "{new_version}" + +[[tool.bumpversion.files]] +filename = "java/lance-namespace-unity/pom.xml" +search = "{current_version}" +replace = "{new_version}" + +[[tool.bumpversion.files]] +filename = "java/lance-namespace-lancedb/pom.xml" +search = "{current_version}" +replace = "{new_version}" + +# Python pyproject.toml files (non-auto-generated) +[[tool.bumpversion.files]] +filename = "python/lance_namespace/pyproject.toml" +search = 'version = "{current_version}"' +replace = 'version = "{new_version}"' + +[[tool.bumpversion.files]] +filename = "python/pyproject.urllib3_client.toml" +search = 'version = "{current_version}"' +replace = 'version = "{new_version}"' + +# Rust Cargo.toml files (non-auto-generated) +[[tool.bumpversion.files]] +filename = "rust/lance-namespace/Cargo.toml" +search = 'version = "{current_version}"' +replace = 'version = "{new_version}"' \ No newline at end of file diff --git a/.github/workflows/java-publish.yml b/.github/workflows/java-publish.yml index 43082cdfb..18508d69b 100644 --- a/.github/workflows/java-publish.yml +++ b/.github/workflows/java-publish.yml @@ -72,3 +72,87 @@ jobs: env: SONATYPE_USER: ${{ secrets.SONATYPE_USER }} SONATYPE_TOKEN: ${{ secrets.SONATYPE_TOKEN }} + + - name: Get published version + if: | + (github.event_name == 'release' && github.event.action == 'released') || + (github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'release') + id: get_version + run: | + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Published version: $VERSION" + + - name: Wait for Maven Central availability + if: | + (github.event_name == 'release' && github.event.action == 'released') || + (github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'release') + run: | + VERSION="${{ steps.get_version.outputs.version }}" + GROUP_ID="com.lancedb" + + # List of some artifacts to check + ARTIFACTS=( + "lance-namespace-core" + "lance-namespace-apache-client" + ) + + echo "Waiting for version $VERSION to be available in Maven Central..." + echo "This typically takes 10-30 minutes after publishing to OSSRH." + + # Maximum wait time: 60 minutes + MAX_WAIT=3600 + INTERVAL=60 + ELAPSED=0 + + while [ $ELAPSED -lt $MAX_WAIT ]; do + ALL_AVAILABLE=true + + for ARTIFACT_ID in "${ARTIFACTS[@]}"; do + URL="https://repo1.maven.org/maven2/com/lancedb/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}.pom" + + if curl --head --silent --fail "$URL" > /dev/null 2>&1; then + echo "โœ“ ${ARTIFACT_ID} is available" + else + echo "โœ— ${ARTIFACT_ID} is not yet available" + ALL_AVAILABLE=false + fi + done + + if [ "$ALL_AVAILABLE" = true ]; then + echo "" + echo "๐ŸŽ‰ All artifacts are now available in Maven Central!" + echo "" + echo "Users can now add the following dependencies to their projects:" + echo "" + echo "Maven:" + echo "" + echo " com.lancedb" + echo " lance-namespace-core" + echo " ${VERSION}" + echo "" + echo "" + echo "Gradle:" + echo "implementation 'com.lancedb:lance-namespace-core:${VERSION}'" + echo "" + echo "SBT:" + echo "libraryDependencies += \"com.lancedb\" % \"lance-namespace-core\" % \"${VERSION}\"" + exit 0 + fi + + ELAPSED=$((ELAPSED + INTERVAL)) + + if [ $ELAPSED -lt $MAX_WAIT ]; then + echo "" + echo "Artifacts not yet available. Waiting ${INTERVAL} seconds... (${ELAPSED}s elapsed)" + sleep $INTERVAL + fi + done + + echo "" + echo "โš ๏ธ WARNING: Artifacts are not yet available in Maven Central after ${MAX_WAIT} seconds." + echo "This is normal - Maven Central sync can take up to 2 hours." + echo "The artifacts will appear at: https://central.sonatype.com/artifact/com.lancedb/lance-namespace-core/${VERSION}" + echo "" + echo "You can check the sync status at: https://s01.oss.sonatype.org/" + exit 0 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 6960922de..3c30ce112 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -58,4 +58,4 @@ jobs: (github.event_name == 'release' && github.event.action == 'released') || (github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'release') run: | - uv publish --trusted-publishing always \ No newline at end of file + uv publish --trusted-publishing always diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..7f6c8f452 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,191 @@ +name: Create Release + +on: + workflow_dispatch: + inputs: + release_type: + description: 'Release type' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major + release_channel: + description: 'Release channel' + required: true + default: 'preview' + type: choice + options: + - preview + - stable + dry_run: + description: 'Dry run (simulate the release without pushing)' + required: true + default: true + type: boolean + +jobs: + create-release: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.tag_name.outputs.tag }} + version: ${{ steps.new_version.outputs.version }} + steps: + - name: Output Inputs + run: echo "${{ toJSON(github.event.inputs) }}" + + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install packaging bump-my-version toml + + - name: Get current version + id: current_version + run: | + CURRENT_VERSION=$(python -c "import toml; print(toml.load('.bumpversion.toml')['tool']['bumpversion']['current_version'])") + echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "Current version: $CURRENT_VERSION" + + - name: Calculate new version + id: new_version + run: | + python ci/calculate_version.py \ + --current "${{ steps.current_version.outputs.version }}" \ + --type "${{ inputs.release_type }}" \ + --channel "${{ inputs.release_channel }}" + + - name: Determine tag name + id: tag_name + run: | + if [ "${{ inputs.release_channel }}" == "stable" ]; then + VERSION="${{ steps.new_version.outputs.version }}" + TAG="v${VERSION}" + else + # For preview releases, use current version with beta suffix + VERSION="${{ steps.current_version.outputs.version }}" + # Find the next beta number for current version + BETA_TAGS=$(git tag -l "v${VERSION}-beta.*" | sort -V) + if [ -z "$BETA_TAGS" ]; then + BETA_NUM=1 + else + LAST_BETA=$(echo "$BETA_TAGS" | tail -n 1) + LAST_NUM=$(echo "$LAST_BETA" | sed "s/v${VERSION}-beta.//") + BETA_NUM=$((LAST_NUM + 1)) + fi + TAG="v${VERSION}-beta.${BETA_NUM}" + fi + + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Tag will be: $TAG" + + - name: Install uv for code generation (stable releases only) + if: inputs.release_channel == 'stable' + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install openapi-generator-cli (stable releases only) + if: inputs.release_channel == 'stable' + run: | + uv tool install openapi-generator-cli + + - name: Update version (stable releases only) + if: inputs.release_channel == 'stable' + run: | + python ci/bump_version.py --version "${{ steps.new_version.outputs.version }}" + + - name: Regenerate auto-generated code (stable releases only) + if: inputs.release_channel == 'stable' + run: | + make build + + - name: Configure git identity + run: | + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + + - name: Create release commit (stable releases only) + if: inputs.release_channel == 'stable' + run: | + git add -A + git commit -m "chore: release version ${{ steps.new_version.outputs.version }}" || echo "No changes to commit" + + - name: Create tag + run: | + git tag -a "${{ steps.tag_name.outputs.tag }}" -m "Release ${{ steps.tag_name.outputs.tag }}" + + - name: Push changes (if not dry run) + if: ${{ !inputs.dry_run }} + run: | + if [ "${{ inputs.release_channel }}" == "stable" ]; then + # Push the commit for stable releases + git push origin main + fi + # Always push the tag + git push origin "${{ steps.tag_name.outputs.tag }}" + + - name: Generate release notes + id: release_notes + if: ${{ !inputs.dry_run }} + run: | + python ci/generate_release_notes.py \ + --tag "${{ steps.tag_name.outputs.tag }}" \ + --repo "${{ github.repository }}" \ + --token "${{ secrets.GITHUB_TOKEN }}" + + - name: Create GitHub Release Draft (if not dry run) + if: ${{ !inputs.dry_run }} + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.tag_name.outputs.tag }} + name: ${{ steps.tag_name.outputs.tag }} + body_path: release_notes.md + draft: true + prerelease: ${{ inputs.release_channel == 'preview' }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Summary + run: | + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Release Type:** ${{ inputs.release_type }}" >> $GITHUB_STEP_SUMMARY + echo "- **Release Channel:** ${{ inputs.release_channel }}" >> $GITHUB_STEP_SUMMARY + echo "- **Current Version:** ${{ steps.current_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + if [ "${{ inputs.release_channel }}" == "stable" ]; then + echo "- **New Version:** ${{ steps.new_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + fi + echo "- **Tag:** ${{ steps.tag_name.outputs.tag }}" >> $GITHUB_STEP_SUMMARY + echo "- **Dry Run:** ${{ inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY + + if [ "${{ inputs.dry_run }}" == "true" ]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "โš ๏ธ This was a dry run. No changes were pushed." >> $GITHUB_STEP_SUMMARY + else + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿ“ Draft release created successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Next Steps:" >> $GITHUB_STEP_SUMMARY + echo "1. Review the draft release on the [releases page](https://github.com/${{ github.repository }}/releases)" >> $GITHUB_STEP_SUMMARY + echo "2. Edit the release notes if needed" >> $GITHUB_STEP_SUMMARY + echo "3. Publish the release to:" >> $GITHUB_STEP_SUMMARY + if [ "${{ inputs.release_channel }}" == "stable" ]; then + echo " - Create the official release" >> $GITHUB_STEP_SUMMARY + echo " - Trigger automatic publishing to Maven Central and PyPI" >> $GITHUB_STEP_SUMMARY + else + echo " - Create a preview/beta release" >> $GITHUB_STEP_SUMMARY + echo " - Note: Preview releases are not published to package repositories" >> $GITHUB_STEP_SUMMARY + fi + fi \ No newline at end of file diff --git a/.github/workflows/rust-publish.yml b/.github/workflows/rust-publish.yml new file mode 100644 index 000000000..b7b53943c --- /dev/null +++ b/.github/workflows/rust-publish.yml @@ -0,0 +1,71 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Rust Publish + +on: + release: + types: + - released + pull_request: + paths: + - .github/workflows/rust-publish.yml + workflow_dispatch: + inputs: + mode: + description: "dry_run: build & test only, release: build & publish to crates.io" + required: true + default: "dry_run" + type: choice + options: + - dry_run + - release + +env: + # This env var is used by Swatinem/rust-cache@v2 for the cache + # key, so we set it to make sure it is always consistent. + CARGO_TERM_COLOR: always + # Disable full debug symbol generation to speed up CI build and keep memory down + # "1" means line tables only, which is useful for panic tracebacks. + RUSTFLAGS: "-C debuginfo=1" + RUST_BACKTRACE: "1" + # according to: https://matklad.github.io/2021/09/04/fast-rust-builds.html + # CI builds are faster with incremental disabled. + CARGO_INCREMENTAL: "0" + CARGO_BUILD_JOBS: "1" + +jobs: + publish: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo apt update + sudo apt install -y protobuf-compiler libssl-dev + # pin the toolchain version to avoid surprises + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + - uses: rui314/setup-mold@v1 + - uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + - uses: katyo/publish-crates@v2 + with: + # registry-token: ${{ steps.auth.outputs.token }} + registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} + args: "--all-features" + path: rust + dry-run: ${{ github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'dry_run') }} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b64f193e3..5828670a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,4 +83,30 @@ After code merge, the contents are added to the during the Lance doc CI build time, and is presented in the Lance website under [Lance Namespace Spec](https://lancedb.github.io/lance/format/namespace). -The CONTRIBUTING.md document is auto-built to the [Lance Contributing Guide](https://lancedb.github.io/lance/community/contributing/) \ No newline at end of file +The CONTRIBUTING.md document is auto-built to the [Lance Contributing Guide](https://lancedb.github.io/lance/community/contributing/) + +## Release Process + +This section describes the CI/CD workflows for automated version management, releases, and publishing. + +### Version Scheme + +- **Stable releases:** `X.Y.Z` (e.g., 1.2.3) +- **Preview releases:** `X.Y.Z-beta.N` (e.g., 1.2.3-beta.1) + +### Creating a Release + +1. **Create Release Draft** + - Go to Actions โ†’ "Create Release" + - Select parameters: + - Release type (major/minor/patch) + - Release channel (stable/preview) + - Dry run (test without pushing) + - Run workflow (creates a draft release) + +2. **Review and Publish** + - Go to the [Releases page](../../releases) to review the draft + - Edit release notes if needed + - Click "Publish release" to: + - For stable releases: Trigger automatic publishing for Java, Python, Rust + - For preview releases: Create a beta release (not published) diff --git a/ci/bump_version.py b/ci/bump_version.py new file mode 100755 index 000000000..313707ddf --- /dev/null +++ b/ci/bump_version.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +Version management script for Lance Namespace project. +Uses bump-my-version to handle version bumping across all project components. +""" + +import argparse +import subprocess +import sys +from pathlib import Path + + +def run_command(cmd: list[str], capture_output: bool = True) -> subprocess.CompletedProcess: + """Run a command and return the result.""" + print(f"Running: {' '.join(cmd)}") + result = subprocess.run(cmd, capture_output=capture_output, text=True) + if result.returncode != 0: + print(f"Error running command: {' '.join(cmd)}") + if capture_output: + print(f"stderr: {result.stderr}") + sys.exit(result.returncode) + return result + + +def get_current_version() -> str: + """Get the current version from .bumpversion.toml.""" + config_path = Path(".bumpversion.toml") + if not config_path.exists(): + raise FileNotFoundError(".bumpversion.toml not found in current directory") + + with open(config_path, "r") as f: + for line in f: + if line.strip().startswith('current_version = "'): + return line.split('"')[1] + raise ValueError("Could not find current_version in .bumpversion.toml") + + +def main(): + parser = argparse.ArgumentParser(description='Bump version using bump-my-version') + parser.add_argument('--version', required=True, help='New version to set') + parser.add_argument('--dry-run', action='store_true', help='Show what would be changed without making changes') + + args = parser.parse_args() + + # Get current version + current_version = get_current_version() + new_version = args.version + + print(f"Current version: {current_version}") + print(f"New version: {new_version}") + + if args.dry_run: + print("\nDry run mode - no changes will be made") + # Run bump-my-version in dry-run mode + cmd = ["bump-my-version", "bump", "--current-version", current_version, + "--new-version", new_version, "--dry-run", "--verbose", "--allow-dirty"] + run_command(cmd, capture_output=False) + else: + # Use bump-my-version to update all files + print("\nUpdating version in all files...") + cmd = ["bump-my-version", "bump", "--current-version", current_version, + "--new-version", new_version, "--no-commit", "--no-tag", "--allow-dirty"] + run_command(cmd) + + print(f"\nSuccessfully updated version from {current_version} to {new_version}") + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ci/calculate_version.py b/ci/calculate_version.py new file mode 100755 index 000000000..8810a9f0e --- /dev/null +++ b/ci/calculate_version.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +""" +Script to calculate the next version based on release type +""" + +import argparse +import sys +from packaging import version + +def calculate_next_version(current_version, release_type, channel): + """Calculate the next version based on release type and channel""" + + # Parse current version + v = version.parse(current_version) + + # Extract major, minor, patch + if hasattr(v, 'release'): + major, minor, patch = v.release[:3] if len(v.release) >= 3 else (*v.release, 0, 0)[:3] + else: + # Fallback for simple versions + parts = current_version.split('.') + major = int(parts[0]) if len(parts) > 0 else 0 + minor = int(parts[1]) if len(parts) > 1 else 0 + patch = int(parts[2]) if len(parts) > 2 else 0 + + # Calculate new version for stable releases + if channel == 'stable': + if release_type == 'major': + new_version = f"{major + 1}.0.0" + elif release_type == 'minor': + new_version = f"{major}.{minor + 1}.0" + elif release_type == 'patch': + new_version = f"{major}.{minor}.{patch + 1}" + else: + raise ValueError(f"Unknown release type: {release_type}") + else: + # For preview releases, keep the current version + new_version = current_version + + return new_version + +def main(): + parser = argparse.ArgumentParser(description='Calculate next version') + parser.add_argument('--current', required=True, help='Current version') + parser.add_argument('--type', required=True, choices=['major', 'minor', 'patch'], help='Release type') + parser.add_argument('--channel', required=True, choices=['stable', 'preview'], help='Release channel') + + args = parser.parse_args() + + try: + new_version = calculate_next_version(args.current, args.type, args.channel) + + # Output for GitHub Actions + print(f"version={new_version}") + + # Also write to GITHUB_OUTPUT if available + github_output = os.environ.get('GITHUB_OUTPUT') + if github_output: + with open(github_output, 'a') as f: + f.write(f"version={new_version}\n") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == '__main__': + import os + main() \ No newline at end of file diff --git a/ci/generate_release_notes.py b/ci/generate_release_notes.py new file mode 100755 index 000000000..fe1101345 --- /dev/null +++ b/ci/generate_release_notes.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +""" +Generate release notes for a given tag +""" + +import argparse +import subprocess +import sys +from pathlib import Path +import requests +import json + +def get_commits_since_last_tag(tag): + """Get commits since the last tag""" + # Get the previous tag + try: + result = subprocess.run( + ["git", "describe", "--tags", "--abbrev=0", f"{tag}^"], + capture_output=True, + text=True, + check=True + ) + prev_tag = result.stdout.strip() + except subprocess.CalledProcessError: + # No previous tag, use all commits + prev_tag = None + + # Get commits between tags + if prev_tag: + cmd = ["git", "log", f"{prev_tag}..{tag}", "--pretty=format:%H|%s|%an"] + else: + cmd = ["git", "log", tag, "--pretty=format:%H|%s|%an"] + + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + commits = [] + for line in result.stdout.strip().split('\n'): + if line: + parts = line.split('|', 2) + if len(parts) == 3: + commits.append({ + 'sha': parts[0], + 'message': parts[1], + 'author': parts[2] + }) + + return commits, prev_tag + +def categorize_commits(commits): + """Categorize commits by type""" + categories = { + 'features': [], + 'fixes': [], + 'docs': [], + 'refactor': [], + 'test': [], + 'chore': [], + 'other': [] + } + + for commit in commits: + msg = commit['message'].lower() + if msg.startswith('feat:') or msg.startswith('feature:'): + categories['features'].append(commit) + elif msg.startswith('fix:'): + categories['fixes'].append(commit) + elif msg.startswith('docs:'): + categories['docs'].append(commit) + elif msg.startswith('refactor:'): + categories['refactor'].append(commit) + elif msg.startswith('test:'): + categories['test'].append(commit) + elif msg.startswith('chore:'): + categories['chore'].append(commit) + else: + categories['other'].append(commit) + + return categories + +def generate_markdown(tag, categories, prev_tag, repo): + """Generate markdown release notes""" + lines = [] + + # Header + lines.append(f"# Release {tag}") + lines.append("") + + # Compare link + if prev_tag and repo: + lines.append(f"[Compare with {prev_tag}](https://github.com/{repo}/compare/{prev_tag}...{tag})") + lines.append("") + + # Sections + sections = [ + ('features', 'โœจ Features'), + ('fixes', '๐Ÿ› Bug Fixes'), + ('refactor', 'โ™ป๏ธ Refactoring'), + ('docs', '๐Ÿ“š Documentation'), + ('test', '๐Ÿงช Tests'), + ('chore', '๐Ÿ”ง Chores'), + ('other', '๐Ÿ“ Other Changes') + ] + + for key, title in sections: + if categories[key]: + lines.append(f"## {title}") + lines.append("") + for commit in categories[key]: + msg = commit['message'] + # Remove conventional commit prefix + for prefix in ['feat:', 'feature:', 'fix:', 'docs:', 'refactor:', 'test:', 'chore:']: + if msg.lower().startswith(prefix): + msg = msg[len(prefix):].strip() + break + lines.append(f"- {msg} ({commit['sha'][:7]})") + lines.append("") + + # Contributors section + authors = set(commit['author'] for commit in sum(categories.values(), [])) + if authors: + lines.append("## Contributors") + lines.append("") + for author in sorted(authors): + lines.append(f"- {author}") + lines.append("") + + return '\n'.join(lines) + +def main(): + parser = argparse.ArgumentParser(description='Generate release notes') + parser.add_argument('--tag', required=True, help='Git tag for the release') + parser.add_argument('--repo', help='GitHub repository (owner/name)') + parser.add_argument('--token', help='GitHub token for API access') + parser.add_argument('--output', default='release_notes.md', help='Output file') + + args = parser.parse_args() + + try: + # Get commits + commits, prev_tag = get_commits_since_last_tag(args.tag) + + # Categorize commits + categories = categorize_commits(commits) + + # Generate markdown + markdown = generate_markdown(args.tag, categories, prev_tag, args.repo) + + # Write to file + with open(args.output, 'w') as f: + f.write(markdown) + + print(f"Release notes written to {args.output}") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/rust/lance-namespace/Cargo.toml b/rust/lance-namespace/Cargo.toml index 7c3eb289d..0a9ba7bb5 100644 --- a/rust/lance-namespace/Cargo.toml +++ b/rust/lance-namespace/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lance-namespace" -version = "0.1.0" +version = "0.0.10" edition = "2021" authors = ["Lance Namespace Contributors"] description = "Lance Namespace Rust Client - A unified interface for managing namespaces and tables"