From baceda7cc1ea59b321d51d94a4b42d7e87f9c351 Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Tue, 19 May 2026 09:22:59 -0700 Subject: [PATCH] ci: stop publishing -nightly bundle variants; release bundles on demand (#13206) Bundles (lfx-arxiv, lfx-duckduckgo) change infrequently enough that a nightly cadence is overkill. Drop the rename-to-`-nightly` and bundle publish lanes from the nightly pipeline and add a dedicated workflow_dispatch-only release_bundles.yml for purposeful releases. - nightly_build.yml: drop update_bundle_versions.py invocation and the src/bundles/*/pyproject.toml git-add guard. - release_nightly.yml: remove build-nightly-bundles and publish-nightly-bundles jobs; untether test-cross-platform and publish-nightly-main from them. - release_bundles.yml (new): build all src/bundles/* wheels, run a cross-OS install smoke test, then publish to PyPI under their stable names. Tolerates already-published versions so re-runs without a version bump are no-ops. release.yml still owns bundle publishing as part of main releases. Manual follow-up (PyPI admin): delete the lfx-arxiv-nightly and lfx-duckduckgo-nightly projects from PyPI. --- .github/workflows/nightly_build.yml | 9 -- .github/workflows/release_bundles.yml | 189 ++++++++++++++++++++++++++ .github/workflows/release_nightly.yml | 95 +------------ .secrets.baseline | 6 +- 4 files changed, 195 insertions(+), 104 deletions(-) create mode 100644 .github/workflows/release_bundles.yml diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml index 6023a421fe..f82fcc9228 100644 --- a/.github/workflows/nightly_build.yml +++ b/.github/workflows/nightly_build.yml @@ -163,12 +163,6 @@ jobs: uv run --no-sync ./scripts/ci/update_sdk_version.py $SDK_TAG echo "Updating LFX project version to $LFX_TAG" uv run --no-sync ./scripts/ci/update_lfx_version.py $LFX_TAG $SDK_TAG - if [ -f ./scripts/ci/update_bundle_versions.py ]; then - echo "Renaming bundle packages to their -nightly counterparts (lfx dev suffix: $LFX_TAG)" - uv run --no-sync ./scripts/ci/update_bundle_versions.py $LFX_TAG - else - echo "Skipping bundle rename: scripts/ci/update_bundle_versions.py not present on this branch" - fi echo "Updating base project version to $BASE_TAG and updating main project version to $RELEASE_TAG" uv run --no-sync ./scripts/ci/update_pyproject_combined.py main $RELEASE_TAG $BASE_TAG $LFX_TAG @@ -177,9 +171,6 @@ jobs: cd src/lfx && uv lock && cd ../.. git add pyproject.toml src/backend/base/pyproject.toml src/lfx/pyproject.toml src/sdk/pyproject.toml uv.lock src/backend/base/uv.lock - if compgen -G "src/bundles/*/pyproject.toml" > /dev/null; then - git add src/bundles/*/pyproject.toml - fi git commit -m "Update version and project name" echo "Tagging main with $RELEASE_TAG" diff --git a/.github/workflows/release_bundles.yml b/.github/workflows/release_bundles.yml new file mode 100644 index 0000000000..53651f51cc --- /dev/null +++ b/.github/workflows/release_bundles.yml @@ -0,0 +1,189 @@ +name: Release Bundles + +# Manually-triggered workflow for publishing stable `lfx-` bundle wheels +# from src/bundles/* to PyPI. Bundles change infrequently, so we release them +# on demand rather than on a schedule. Re-running the workflow without a +# version bump is a no-op (PyPI rejects the duplicate upload and we tolerate +# the conflict). + +on: + workflow_dispatch: + inputs: + release_tag: + description: "Git ref to build from (branch, tag, or SHA). Defaults to the workflow's checked-out ref." + required: false + type: string + default: "" + dry_run: + description: "Build and test only; skip the PyPI publish step." + required: false + type: boolean + default: false + +env: + PYTHON_VERSION: "3.13" + +jobs: + build-bundles: + name: Build Langflow Bundles + runs-on: ubuntu-latest + defaults: + run: + shell: bash + outputs: + bundles-found: ${{ steps.build.outputs.bundles-found }} + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ inputs.release_tag || github.ref }} + - name: Setup Environment + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + python-version: ${{ env.PYTHON_VERSION }} + prune-cache: false + - name: Install the project + run: uv sync + - name: Build bundle wheels + id: build + run: | + shopt -s nullglob + bundles=(src/bundles/*/pyproject.toml) + if [ ${#bundles[@]} -eq 0 ]; then + echo "No bundles found under src/bundles/*/" + echo "bundles-found=0" >> $GITHUB_OUTPUT + exit 0 + fi + mkdir -p bundles-dist + BUNDLES_DIST="$PWD/bundles-dist" + for bundle_pyproject in "${bundles[@]}"; do + bundle_dir=$(dirname "$bundle_pyproject") + echo "Building wheel for $bundle_dir" + (cd "$bundle_dir" && uv build --wheel --out-dir "$BUNDLES_DIST") + done + echo "bundles-found=1" >> $GITHUB_OUTPUT + ls -la bundles-dist/ + - name: Upload bundles artifact + if: steps.build.outputs.bundles-found == '1' + uses: actions/upload-artifact@v6 + with: + name: dist-bundles + path: bundles-dist + + test-install: + name: Install Smoke Test - ${{ matrix.os }} ${{ matrix.python-version }} + needs: [build-bundles] + if: needs.build-bundles.outputs.bundles-found == '1' + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - os: linux + runner: ubuntu-latest + python-version: "3.10" + - os: linux + runner: ubuntu-latest + python-version: "3.12" + - os: macos + runner: macos-latest + python-version: "3.12" + - os: windows + runner: windows-latest + python-version: "3.12" + steps: + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Setup UV + uses: astral-sh/setup-uv@v6 + with: + enable-cache: false + ignore-empty-workdir: true + - name: Download bundles artifact + uses: actions/download-artifact@v7 + with: + name: dist-bundles + path: ./bundles-dist + - name: Create fresh virtual environment + run: uv venv test-env --seed + shell: bash + - name: Install bundle wheels (Unix) + if: matrix.os != 'windows' + run: | + shopt -s nullglob + BUNDLE_WHEELS=(./bundles-dist/*.whl) + if [ ${#BUNDLE_WHEELS[@]} -eq 0 ]; then + echo "No bundle wheels found in ./bundles-dist/" + exit 1 + fi + printf 'Installing: %s\n' "${BUNDLE_WHEELS[@]}" + uv pip install --prerelease=allow --python ./test-env/bin/python "${BUNDLE_WHEELS[@]}" + shell: bash + - name: Install bundle wheels (Windows) + if: matrix.os == 'windows' + run: | + shopt -s nullglob + BUNDLE_WHEELS=(./bundles-dist/*.whl) + if [ ${#BUNDLE_WHEELS[@]} -eq 0 ]; then + echo "No bundle wheels found in ./bundles-dist/" + exit 1 + fi + printf 'Installing: %s\n' "${BUNDLE_WHEELS[@]}" + uv pip install --prerelease=allow --python ./test-env/Scripts/python.exe "${BUNDLE_WHEELS[@]}" + shell: bash + + publish-bundles: + name: Publish Bundles to PyPI + needs: [build-bundles, test-install] + if: | + always() && + !cancelled() && + !inputs.dry_run && + needs.build-bundles.result == 'success' && + needs.build-bundles.outputs.bundles-found == '1' && + needs.test-install.result == 'success' + runs-on: ubuntu-latest + steps: + - name: Download bundles artifact + uses: actions/download-artifact@v7 + with: + name: dist-bundles + path: bundles-dist + - name: Setup Environment + uses: astral-sh/setup-uv@v6 + with: + enable-cache: false + python-version: ${{ env.PYTHON_VERSION }} + - name: Publish bundles to PyPI + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + run: | + shopt -s nullglob + wheels=(bundles-dist/*.whl) + if [ ${#wheels[@]} -eq 0 ]; then + echo "No bundle wheels to publish." + exit 0 + fi + failed=0 + for wheel in "${wheels[@]}"; do + echo "Publishing $wheel" + # Capture stderr so we can tolerate "already exists" without failing + # the whole run — re-running this workflow without bumping a bundle + # version should be a no-op for that bundle. + if ! err=$(uv publish "$wheel" 2>&1); then + echo "$err" + if echo "$err" | grep -qiE 'already exists|file already exists|HTTP 400|duplicate'; then + echo "Skipping $wheel: already published." + else + echo "Publish failed for $wheel" + failed=1 + fi + fi + done + if [ "$failed" = "1" ]; then + exit 1 + fi diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 61802de087..fad5158c2f 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -339,67 +339,15 @@ jobs: name: dist-nightly-main path: dist - build-nightly-bundles: - name: Build Langflow Nightly Bundles - needs: [build-nightly-lfx] - if: ${{ always() && (needs.build-nightly-lfx.result == 'success' || inputs.build_lfx == false) }} - runs-on: ubuntu-latest - defaults: - run: - shell: bash - outputs: - bundles-found: ${{ steps.build.outputs.bundles-found }} - steps: - - name: Check out the code at a specific ref - uses: actions/checkout@v6 - with: - ref: ${{ inputs.nightly_tag_release }} - persist-credentials: true - - name: "Setup Environment" - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - cache-dependency-glob: "uv.lock" - python-version: ${{ env.PYTHON_VERSION }} - prune-cache: false - - name: Install the project - run: uv sync - - name: Build bundle wheels - id: build - run: | - shopt -s nullglob - bundles=(src/bundles/*/pyproject.toml) - if [ ${#bundles[@]} -eq 0 ]; then - echo "No bundles found under src/bundles/*/" - echo "bundles-found=0" >> $GITHUB_OUTPUT - exit 0 - fi - mkdir -p bundles-dist - BUNDLES_DIST="$PWD/bundles-dist" - for bundle_pyproject in "${bundles[@]}"; do - bundle_dir=$(dirname "$bundle_pyproject") - echo "Building wheel for $bundle_dir" - (cd "$bundle_dir" && uv build --wheel --out-dir "$BUNDLES_DIST") - done - echo "bundles-found=1" >> $GITHUB_OUTPUT - ls -la bundles-dist/ - - name: Upload bundles artifact - if: steps.build.outputs.bundles-found == '1' - uses: actions/upload-artifact@v6 - with: - name: dist-nightly-bundles - path: bundles-dist - test-cross-platform: name: Test Cross-Platform Installation - needs: [build-nightly-lfx, build-nightly-base, build-nightly-main, build-nightly-bundles] + needs: [build-nightly-lfx, build-nightly-base, build-nightly-main] uses: ./.github/workflows/cross-platform-test.yml with: base-artifact-name: "dist-nightly-base" main-artifact-name: "dist-nightly-main" lfx-artifact-name: "dist-nightly-lfx" sdk-artifact-name: "dist-nightly-sdk" - bundles-artifact-name: ${{ needs.build-nightly-bundles.outputs.bundles-found == '1' && 'dist-nightly-bundles' || '' }} publish-nightly-lfx: name: Publish LFX Nightly to PyPI @@ -486,47 +434,10 @@ jobs: run: | make publish base=true - publish-nightly-bundles: - name: Publish Bundle Nightlies to PyPI - needs: [build-nightly-bundles, test-cross-platform, publish-nightly-lfx] - if: ${{ always() && needs.build-nightly-bundles.result == 'success' && needs.build-nightly-bundles.outputs.bundles-found == '1' && needs.test-cross-platform.result == 'success' && (needs.publish-nightly-lfx.result == 'success' || inputs.build_lfx == false) }} - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - ref: ${{ inputs.nightly_tag_release }} - persist-credentials: true - - name: Download bundles artifact - uses: actions/download-artifact@v7 - with: - name: dist-nightly-bundles - path: bundles-dist - - name: Setup Environment - uses: astral-sh/setup-uv@v6 - with: - enable-cache: false - python-version: "3.13" - - name: Publish bundles to PyPI - env: - POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }} - UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - run: | - shopt -s nullglob - wheels=(bundles-dist/*.whl) - if [ ${#wheels[@]} -eq 0 ]; then - echo "No bundle wheels to publish." - exit 0 - fi - for wheel in "${wheels[@]}"; do - echo "Publishing $wheel" - uv publish "$wheel" - done - publish-nightly-main: name: Publish Langflow Main Nightly to PyPI - needs: [build-nightly-main, test-cross-platform, publish-nightly-base, publish-nightly-bundles] - if: ${{ always() && needs.build-nightly-main.result == 'success' && needs.test-cross-platform.result == 'success' && needs.publish-nightly-base.result == 'success' && (needs.publish-nightly-bundles.result == 'success' || needs.publish-nightly-bundles.result == 'skipped') }} + needs: [build-nightly-main, test-cross-platform, publish-nightly-base] + if: ${{ always() && needs.build-nightly-main.result == 'success' && needs.test-cross-platform.result == 'success' && needs.publish-nightly-base.result == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/.secrets.baseline b/.secrets.baseline index 2f6eac1eff..91c197f213 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -187,7 +187,7 @@ "filename": ".github/workflows/nightly_build.yml", "hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0", "is_verified": false, - "line_number": 306, + "line_number": 297, "is_secret": false } ], @@ -207,7 +207,7 @@ "filename": ".github/workflows/release_nightly.yml", "hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0", "is_verified": false, - "line_number": 473, + "line_number": 474, "is_secret": false } ], @@ -8287,5 +8287,5 @@ } ] }, - "generated_at": "2026-05-19T01:41:00Z" + "generated_at": "2026-05-19T16:19:23Z" }