From 2fa3d1c75927aa6b35d724a6226b2906862e25bd Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Mon, 13 Apr 2026 18:43:25 -0700 Subject: [PATCH] feat: add langflow-sdk support to release workflow (#12679) * feat: add langflow-sdk build and publish support to release workflow Add support for building and publishing the langflow-sdk package in the release workflow, mirroring the nightly build support added in #12491. Changes: - Add `release_sdk` workflow input for controlling SDK release - Add `determine-sdk-version` job with first-release PyPI handling - Add `build-sdk` job with version verification and import testing - Add `publish-sdk` job that publishes SDK to PyPI before LFX - Update `build-lfx` to download SDK artifact and test both wheels together - Update `build-base` and `build-main` to use SDK wheel as find-links - Pass SDK artifact to cross-platform tests - Add validation: releasing LFX requires releasing SDK - Add pre-release version handling for SDK and LFX's SDK dependency * Update release.yml * Update release.yml * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 295 +++++++++++++++++++++++++++++++--- .secrets.baseline | 4 +- 2 files changed, 278 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2aa7e7cdcf..7dc6b8e0ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,11 @@ on: required: false type: boolean default: false + release_sdk: + description: "Release langflow-sdk package (required when releasing LFX)" + required: false + type: boolean + default: false build_docker_base: description: "Build Docker Image for Langflow Base" required: true @@ -60,6 +65,7 @@ jobs: echo "release_package_base: ${{ inputs.release_package_base }}" echo "release_package_main: ${{ inputs.release_package_main }}" echo "release_lfx: ${{ inputs.release_lfx }}" + echo "release_sdk: ${{ inputs.release_sdk }}" echo "build_docker_base: ${{ inputs.build_docker_base }}" echo "build_docker_main: ${{ inputs.build_docker_main }}" echo "pre_release: ${{ inputs.pre_release }}" @@ -95,7 +101,7 @@ jobs: validate-dependencies: name: Validate Release Dependencies runs-on: ubuntu-latest - if: ${{ inputs.release_package_base || inputs.release_package_main || inputs.release_lfx || inputs.build_docker_base || inputs.build_docker_main }} + if: ${{ inputs.release_package_base || inputs.release_package_main || inputs.release_lfx || inputs.release_sdk || inputs.build_docker_base || inputs.build_docker_main }} needs: [validate-tag] steps: - name: Validate that build-base is enabled if build-main is enabled @@ -106,6 +112,12 @@ jobs: exit 1 fi + if [ "${{ inputs.release_lfx }}" = "true" ] && [ "${{ inputs.release_sdk }}" = "false" ]; then + echo "Error: Cannot release LFX without releasing langflow-sdk." + echo "LFX depends on langflow-sdk. Please enable 'release_sdk' or disable 'release_lfx'." + exit 1 + fi + echo "✅ Release dependencies validated successfully." determine-base-version: @@ -259,6 +271,64 @@ jobs: echo skipped=false >> $GITHUB_OUTPUT fi + determine-sdk-version: + name: Determine SDK Version + needs: [validate-tag, validate-dependencies] + if: ${{ inputs.release_sdk || inputs.release_lfx }} + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + skipped: ${{ steps.version.outputs.skipped }} + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ inputs.release_tag }} + + - name: Setup Environment + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + python-version: "3.13" + prune-cache: false + + - name: Determine version + id: version + run: | + version=$(python3 -c " + import tomllib, pathlib + data = tomllib.loads(pathlib.Path('src/sdk/pyproject.toml').read_text()) + print(data['project']['version']) + ") + echo "SDK version from pyproject.toml: $version" + + # Check if langflow-sdk exists on PyPI (may be first release) + pypi_response=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/langflow-sdk/json") + if [ "$pypi_response" = "404" ]; then + echo "langflow-sdk has never been published to PyPI. This will be the first release." + last_released_version="" + else + if [ ${{inputs.pre_release}} == "true" ]; then + last_released_version=$(curl -s "https://pypi.org/pypi/langflow-sdk/json" | jq -r '.releases | keys | .[]' | grep -E '(a|b|rc|dev|alpha|beta)' | sort -V | tail -n 1) + version="$(uv run ./scripts/ci/langflow_pre_release_tag.py "$version" "$last_released_version")" + echo "Latest SDK pre-release version: $last_released_version" + echo "SDK pre-release version to be released: $version" + else + last_released_version=$(curl -s "https://pypi.org/pypi/langflow-sdk/json" | jq -r '.releases | keys | .[]' | grep -vE '(a|b|rc|dev|alpha|beta)' | sort -V | tail -n 1) + echo "Latest SDK release version: $last_released_version" + fi + fi + + if [ "$version" = "$last_released_version" ]; then + echo "SDK pypi version $version is already released. Skipping release." + echo skipped=true >> $GITHUB_OUTPUT + exit 1 + else + echo version=$version >> $GITHUB_OUTPUT + echo skipped=false >> $GITHUB_OUTPUT + fi + ci: name: CI needs: [validate-tag, validate-dependencies] @@ -272,10 +342,10 @@ jobs: runs-on: ubuntu-latest secrets: inherit - build-lfx: - name: Build LFX - needs: [determine-lfx-version] - if: ${{ needs.determine-lfx-version.outputs.skipped == 'false' }} + build-sdk: + name: Build langflow-sdk + needs: [determine-sdk-version] + if: ${{ needs.determine-sdk-version.outputs.skipped == 'false' }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -289,6 +359,78 @@ jobs: cache-dependency-glob: "uv.lock" python-version: "3.13" prune-cache: false + - name: Set version for pre-release + if: ${{ inputs.pre_release }} + run: | + VERSION="${{ needs.determine-sdk-version.outputs.version }}" + echo "Setting SDK pre-release version to: $VERSION" + cd src/sdk + + # Update version in SDK pyproject.toml + sed -i.bak "s/^version = .*/version = \"$VERSION\"/" pyproject.toml + + # Verify the change + echo "Updated pyproject.toml version:" + grep "^version" pyproject.toml + - name: Build project for distribution + run: make sdk_build args="--wheel" + - name: Verify built version + run: | + EXPECTED_VERSION="${{ needs.determine-sdk-version.outputs.version }}" + WHEEL_FILE=$(ls src/sdk/dist/*.whl) + echo "Built wheel: $WHEEL_FILE" + + NORMALIZED_VERSION=$(echo "$EXPECTED_VERSION" | sed 's/\.rc/rc/g; s/\.a/a/g; s/\.b/b/g; s/\.dev/dev/g') + echo "Expected version: $EXPECTED_VERSION" + echo "Normalized for wheel: $NORMALIZED_VERSION" + + if [[ ! "$WHEEL_FILE" =~ $NORMALIZED_VERSION ]]; then + echo "❌ Error: Wheel version doesn't match expected version" + echo "Expected: $EXPECTED_VERSION (normalized: $NORMALIZED_VERSION)" + echo "Wheel file: $WHEEL_FILE" + exit 1 + fi + echo "✅ Version verified: $EXPECTED_VERSION" + - name: Test SDK import + run: | + cd src/sdk + uv venv test-env --seed + uv pip install --python ./test-env/bin/python dist/*.whl + EXPECTED_VERSION="${{ needs.determine-sdk-version.outputs.version }}" + ./test-env/bin/python -c "from importlib.metadata import version; import langflow_sdk; installed_version = version('langflow-sdk'); print(f'langflow-sdk {installed_version} imported successfully'); assert installed_version == '$EXPECTED_VERSION', f'Installed version {installed_version} does not match expected version $EXPECTED_VERSION'" + - name: Upload Artifact + uses: actions/upload-artifact@v6 + with: + name: dist-sdk + path: src/sdk/dist + + build-lfx: + name: Build LFX + needs: [determine-lfx-version, determine-sdk-version, build-sdk] + if: | + always() && + !cancelled() && + needs.determine-lfx-version.result == 'success' && + needs.determine-lfx-version.outputs.skipped == 'false' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ inputs.release_tag }} + - name: Setup Environment + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + python-version: "3.13" + prune-cache: false + - name: Download SDK artifact + if: needs.build-sdk.result == 'success' + uses: actions/download-artifact@v7 + with: + name: dist-sdk + path: ./sdk-dist - name: Install LFX dependencies run: uv sync --dev --package lfx - name: Set version for pre-release @@ -304,6 +446,31 @@ jobs: # Verify the change echo "Updated pyproject.toml version:" grep "^version" pyproject.toml + - name: Update langflow-sdk dependency for pre-release + if: ${{ inputs.pre_release && needs.build-sdk.result == 'success' }} + run: | + SDK_VERSION="${{ needs.determine-sdk-version.outputs.version }}" + echo "Updating langflow-sdk dependency to allow pre-release version: $SDK_VERSION" + cd src/lfx + + # Extract current langflow-sdk constraint from pyproject.toml + CURRENT_CONSTRAINT=$(grep -E '^\s*"langflow-sdk' pyproject.toml | head -1) + echo "Current constraint: $CURRENT_CONSTRAINT" + + # Create new constraint using the next major derived from SDK_VERSION + SDK_BASE_VERSION=$(echo "$SDK_VERSION" | sed 's/[^0-9.].*$//') + SDK_MAJOR=$(echo "$SDK_BASE_VERSION" | cut -d. -f1) + NEXT_MAJOR=$((SDK_MAJOR + 1)) + NEW_CONSTRAINT="\"langflow-sdk>=$SDK_VERSION,<$NEXT_MAJOR.dev0\"" + + echo "New constraint: $NEW_CONSTRAINT" + + # Replace the constraint + sed -i.bak "s|\"langflow-sdk[^\"]*\"|$NEW_CONSTRAINT|" pyproject.toml + + # Verify the change + echo "Updated langflow-sdk dependency:" + grep "langflow-sdk" pyproject.toml - name: Build project for distribution run: | cd src/lfx @@ -329,8 +496,16 @@ jobs: - name: Test CLI run: | cd src/lfx - uv pip install dist/*.whl --force-reinstall - uv run lfx --help + # Use a clean venv and install both SDK and LFX wheels together + # so uv can resolve the local SDK dependency without falling back to PyPI. + uv venv test-env --seed + if [ -d "../../sdk-dist" ]; then + SDK_WHEEL=$(find ../../sdk-dist -name "*.whl" -type f | head -1) + uv pip install --python ./test-env/bin/python --force-reinstall "$SDK_WHEEL" dist/*.whl + else + uv pip install --python ./test-env/bin/python --force-reinstall dist/*.whl + fi + ./test-env/bin/lfx --help - name: Upload Artifact uses: actions/upload-artifact@v6 with: @@ -339,8 +514,14 @@ jobs: build-base: name: Build Langflow Base - needs: [build-lfx, determine-base-version, determine-lfx-version] - if: ${{ inputs.release_package_base && needs.determine-base-version.outputs.skipped == 'false' }} + needs: [build-lfx, build-sdk, determine-base-version, determine-lfx-version] + if: | + always() && + !cancelled() && + inputs.release_package_base && + needs.determine-base-version.result == 'success' && + needs.determine-base-version.outputs.skipped == 'false' && + needs.build-lfx.result == 'success' runs-on: ubuntu-latest steps: - name: Checkout code @@ -359,12 +540,22 @@ jobs: with: name: dist-lfx path: ./lfx-dist - - name: Install dependencies with local LFX wheel + - name: Download SDK artifact + if: needs.build-sdk.result == 'success' + uses: actions/download-artifact@v7 + with: + name: dist-sdk + path: ./sdk-dist + - name: Install dependencies with local LFX and SDK wheels run: | # Create virtual environment uv venv - # Install using pip with local wheel directory as find-links - uv pip install --find-links ./lfx-dist --prerelease=allow -e src/backend/base + # Install using pip with local wheel directories as find-links + FIND_LINKS="--find-links ./lfx-dist" + if [ -d "./sdk-dist" ]; then + FIND_LINKS="$FIND_LINKS --find-links ./sdk-dist" + fi + uv pip install $FIND_LINKS --prerelease=allow -e src/backend/base - name: Check for dependency incompatibility run: uv pip check - name: Set version for pre-release @@ -460,11 +651,19 @@ jobs: [ build-base, build-lfx, + build-sdk, determine-base-version, determine-main-version, determine-lfx-version, ] - if: ${{ inputs.release_package_main && needs.determine-main-version.outputs.skipped == 'false' }} + if: | + always() && + !cancelled() && + inputs.release_package_main && + needs.determine-main-version.result == 'success' && + needs.determine-main-version.outputs.skipped == 'false' && + needs.build-base.result == 'success' && + needs.build-lfx.result == 'success' runs-on: ubuntu-latest steps: - name: Checkout code @@ -483,6 +682,12 @@ jobs: with: name: dist-lfx path: ./lfx-dist + - name: Download SDK artifact + if: needs.build-sdk.result == 'success' + uses: actions/download-artifact@v7 + with: + name: dist-sdk + path: ./sdk-dist - name: Download base artifact uses: actions/download-artifact@v7 with: @@ -493,7 +698,11 @@ jobs: # Create virtual environment uv venv # Install using pip with local wheel directories as find-links - uv pip install --find-links ./lfx-dist --find-links ./base-dist --prerelease=allow -e . + FIND_LINKS="--find-links ./lfx-dist --find-links ./base-dist" + if [ -d "./sdk-dist" ]; then + FIND_LINKS="$FIND_LINKS --find-links ./sdk-dist" + fi + uv pip install $FIND_LINKS --prerelease=allow -e . - name: Check for dependency incompatibility run: uv pip check - name: Set version for pre-release @@ -584,7 +793,7 @@ jobs: test-cross-platform: name: Test Cross-Platform Installation - needs: [build-base, build-main, build-lfx] + needs: [build-base, build-main, build-lfx, build-sdk] if: | always() && !cancelled() && @@ -594,12 +803,20 @@ jobs: base-artifact-name: "dist-base" main-artifact-name: "dist-main" lfx-artifact-name: "dist-lfx" + sdk-artifact-name: ${{ needs.build-sdk.result == 'success' && 'dist-sdk' || '' }} pre_release: ${{ inputs.pre_release }} publish-base: name: Publish Langflow Base to PyPI - if: ${{ inputs.release_package_base }} - needs: [build-base, test-cross-platform, ci] + if: | + always() && + !cancelled() && + inputs.release_package_base && + needs.build-base.result == 'success' && + needs.test-cross-platform.result == 'success' && + needs.ci.result == 'success' && + (needs.publish-lfx.result == 'success' || needs.publish-lfx.result == 'skipped') + needs: [build-base, test-cross-platform, ci, publish-lfx] runs-on: ubuntu-latest steps: - name: Download base artifact @@ -642,10 +859,50 @@ jobs: run: | uv publish dist/*.whl + publish-sdk: + name: Publish langflow-sdk to PyPI + needs: [build-sdk, test-cross-platform, ci] + if: | + always() && + !cancelled() && + inputs.release_sdk && + needs.build-sdk.result == 'success' && + needs.ci.result == 'success' && + (needs.test-cross-platform.result == 'success' || needs.test-cross-platform.result == 'skipped') + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ inputs.release_tag }} + - name: Download SDK artifact + uses: actions/download-artifact@v7 + with: + name: dist-sdk + path: src/sdk/dist + - name: Setup Environment + uses: astral-sh/setup-uv@v6 + with: + enable-cache: false + python-version: "3.13" + - name: Publish SDK to PyPI + if: ${{ !inputs.dry_run }} + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + run: | + make sdk_publish + publish-lfx: name: Publish LFX to PyPI - if: ${{ inputs.release_lfx }} - needs: [build-lfx, test-cross-platform, ci] + needs: [build-lfx, test-cross-platform, ci, publish-sdk] + if: | + always() && + !cancelled() && + inputs.release_lfx && + needs.build-lfx.result == 'success' && + needs.test-cross-platform.result == 'success' && + needs.ci.result == 'success' && + (needs.publish-sdk.result == 'success' || needs.publish-sdk.result == 'skipped') runs-on: ubuntu-latest steps: - name: Download LFX artifact diff --git a/.secrets.baseline b/.secrets.baseline index 5febbdb478..2c615dacf5 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -197,7 +197,7 @@ "filename": ".github/workflows/release.yml", "hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0", "is_verified": false, - "line_number": 273, + "line_number": 342, "is_secret": false } ], @@ -8405,5 +8405,5 @@ } ] }, - "generated_at": "2026-04-10T17:09:16Z" + "generated_at": "2026-04-14T00:28:39Z" }