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.
This commit is contained in:
Eric Hare
2026-05-19 09:22:59 -07:00
committed by GitHub
parent 9882f9f381
commit baceda7cc1
4 changed files with 195 additions and 104 deletions

View File

@ -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"

189
.github/workflows/release_bundles.yml vendored Normal file
View File

@ -0,0 +1,189 @@
name: Release Bundles
# Manually-triggered workflow for publishing stable `lfx-<name>` 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

View File

@ -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

View File

@ -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"
}