From fb4dfd49fdece7078155c3a417ce354a74d2caf8 Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Thu, 28 May 2026 15:38:29 -0700 Subject: [PATCH] ci: sync workflow fixes from main into release-1.10.0 (fix broken nightly) (#13400) ci: sync workflow fixes from main into release-1.10.0 Brings the latest CI/workflow fixes from main into release-1.10.0 so the nightly build is unblocked and the two branches share the same hardening. Most important fix: release_nightly.yml's "Verify Nightly Name and Version" step now uses the anchored grep '^langflow(-nightly)?[[:space:]]' instead of 'grep langflow | grep -v langflow-base | grep -v langflow-sdk'. The old filter started matching two packages once the langflow-stepflow workspace package was added, so the name became multi-line and the check failed (this is what broke the nightly build). Also carries main's fixes for cross-platform install (--prerelease=if-necessary-or-explicit), docker build workflows, db-migration-validation, and the backend test timeout bump (30 -> 40). 3-way merged from the common ancestor, so release-only changes are preserved; no release-only feature workflows are modified. --- .github/workflows/cross-platform-test.yml | 18 ++- .github/workflows/db-migration-validation.yml | 2 +- .github/workflows/docker-build-v2.yml | 4 +- .github/workflows/docker-build.yml | 2 +- .github/workflows/docker-nightly-build.yml | 6 +- .github/workflows/nightly_build.yml | 2 +- .github/workflows/python_test.yml | 2 +- .github/workflows/release.yml | 123 +++++++++++++++++- .github/workflows/release_nightly.yml | 7 +- .secrets.baseline | 22 +--- 10 files changed, 148 insertions(+), 40 deletions(-) diff --git a/.github/workflows/cross-platform-test.yml b/.github/workflows/cross-platform-test.yml index efdc2b2e98..1a6c3456f4 100644 --- a/.github/workflows/cross-platform-test.yml +++ b/.github/workflows/cross-platform-test.yml @@ -293,7 +293,12 @@ jobs: printf 'Installing %s wheel(s):\n' "$label" printf '%s\n' "$@" refresh_find_links - uv pip install --prerelease=allow --python "$PYTHON" "${FIND_LINKS[@]}" "$@" + # Use `if-necessary-or-explicit` (not `allow`): nightly langflow* wheels + # are .dev pre-releases so we need to allow them explicitly, but `allow` + # leaks to transitive deps and resolves pre-release pydantic alphas + # (e.g. 2.14.0a1) which break langchain-core's module-level + # `RunnablePassthrough()` call at import time. + uv pip install --prerelease=if-necessary-or-explicit --python "$PYTHON" "${FIND_LINKS[@]}" "$@" } SDK_WHEELS=(./sdk-dist/*.whl) @@ -581,7 +586,8 @@ jobs: printf 'Installing %s wheel(s):\n' "$label" printf '%s\n' "$@" refresh_find_links - uv pip install --prerelease=allow --python "$PYTHON" "${FIND_LINKS[@]}" "$@" + # See stable job above for rationale on --prerelease=if-necessary-or-explicit. + uv pip install --prerelease=if-necessary-or-explicit --python "$PYTHON" "${FIND_LINKS[@]}" "$@" } SDK_WHEELS=(./sdk-dist/*.whl) @@ -631,7 +637,7 @@ jobs: if [ "${{ inputs.pre_release }}" = "true" ]; then NO_DEPS="" fi - uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/Scripts/python.exe "${INSTALL_WHEELS[@]}" + uv pip install --force-reinstall $NO_DEPS --prerelease=if-necessary-or-explicit --python ./test-env/Scripts/python.exe "${INSTALL_WHEELS[@]}" fi fi @@ -643,7 +649,7 @@ jobs: if [ "${{ inputs.pre_release }}" = "true" ]; then NO_DEPS="" fi - uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/Scripts/python.exe "$BASE_WHEEL" + uv pip install --force-reinstall $NO_DEPS --prerelease=if-necessary-or-explicit --python ./test-env/Scripts/python.exe "$BASE_WHEEL" fi shell: bash @@ -668,7 +674,7 @@ jobs: if [ "${{ inputs.pre_release }}" = "true" ]; then NO_DEPS="" fi - uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/bin/python "${INSTALL_WHEELS[@]}" + uv pip install --force-reinstall $NO_DEPS --prerelease=if-necessary-or-explicit --python ./test-env/bin/python "${INSTALL_WHEELS[@]}" fi fi @@ -680,7 +686,7 @@ jobs: if [ "${{ inputs.pre_release }}" = "true" ]; then NO_DEPS="" fi - uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/bin/python "$BASE_WHEEL" + uv pip install --force-reinstall $NO_DEPS --prerelease=if-necessary-or-explicit --python ./test-env/bin/python "$BASE_WHEEL" fi shell: bash diff --git a/.github/workflows/db-migration-validation.yml b/.github/workflows/db-migration-validation.yml index bb8bc7a76d..f638785519 100644 --- a/.github/workflows/db-migration-validation.yml +++ b/.github/workflows/db-migration-validation.yml @@ -302,7 +302,7 @@ jobs: - name: Create Docker Compose file for stable working-directory: docker-migration-test - run: | + run: | # pragma: allowlist secret cat > docker-compose.yml <<'EOF' services: langflow: diff --git a/.github/workflows/docker-build-v2.yml b/.github/workflows/docker-build-v2.yml index 86f74f4a7a..68d24a4a36 100644 --- a/.github/workflows/docker-build-v2.yml +++ b/.github/workflows/docker-build-v2.yml @@ -124,7 +124,7 @@ jobs: - name: Determine version id: version run: | - version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | grep -v '^langflow-sdk' | cut -d' ' -f2 | sed 's/^v//') + version=$(uv tree 2>/dev/null | grep -E '^langflow(-nightly)?[[:space:]]' | cut -d' ' -f2 | sed 's/^v//') echo "Main version from pyproject.toml: $version" if [ ${{inputs.pre_release}} == "true" ]; then @@ -604,7 +604,7 @@ jobs: if [[ "${{ inputs.release_type }}" == "base" ]]; then version=$(uv tree 2>/dev/null | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1) else - version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | grep -v '^langflow-sdk' | cut -d' ' -f2 | sed 's/^v//') + version=$(uv tree 2>/dev/null | grep -E '^langflow(-nightly)?[[:space:]]' | cut -d' ' -f2 | sed 's/^v//') fi echo "Using version: $version" echo version=$version >> $GITHUB_OUTPUT diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 4c90ad6ec3..4fd92dd6c0 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -150,7 +150,7 @@ jobs: if: ${{ inputs.main_version == '' && (inputs.release_type == 'main' || inputs.release_type == 'main-ep' || inputs.release_type == 'nightly-main' || inputs.release_type == 'main-all' || inputs.release_type == 'nightly-main-all') }} id: get-version-main run: | - version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | grep -v 'langflow-sdk' | awk '{print $2}' | sed 's/^v//') + version=$(uv tree | grep -E '^langflow(-nightly)?[[:space:]]' | awk '{print $2}' | sed 's/^v//') echo version=$version echo version=$version >> $GITHUB_OUTPUT setup: diff --git a/.github/workflows/docker-nightly-build.yml b/.github/workflows/docker-nightly-build.yml index 53a59b9269..7411e5a6b6 100644 --- a/.github/workflows/docker-nightly-build.yml +++ b/.github/workflows/docker-nightly-build.yml @@ -168,7 +168,7 @@ jobs: id: version run: | echo "Extracting main version from pyproject.toml" - version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | grep -v '^langflow-sdk' | cut -d' ' -f2 | sed 's/^v//') + version=$(uv tree 2>/dev/null | grep -E '^langflow(-nightly)?[[:space:]]' | cut -d' ' -f2 | sed 's/^v//') # Verify nightly tag format if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then @@ -263,7 +263,7 @@ jobs: id: version run: | echo "Extracting main version from pyproject.toml" - version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | grep -v '^langflow-sdk' | cut -d' ' -f2 | sed 's/^v//') + version=$(uv tree 2>/dev/null | grep -E '^langflow(-nightly)?[[:space:]]' | cut -d' ' -f2 | sed 's/^v//') # Verify nightly tag format if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then @@ -353,7 +353,7 @@ jobs: if [[ "${{ inputs.release_type }}" == "nightly-base" ]]; then version=$(uv tree 2>/dev/null | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1) else - version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | grep -v '^langflow-sdk' | cut -d' ' -f2 | sed 's/^v//') + version=$(uv tree 2>/dev/null | grep -E '^langflow(-nightly)?[[:space:]]' | cut -d' ' -f2 | sed 's/^v//') fi echo "Using version: $version" echo version=$version >> $GITHUB_OUTPUT diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml index 988fe4e915..24af71004c 100644 --- a/.github/workflows/nightly_build.yml +++ b/.github/workflows/nightly_build.yml @@ -283,7 +283,7 @@ jobs: # ref: ${{ needs.create-nightly-tag.outputs.tag }} release-nightly-build: - if: github.repository == 'langflow-ai/langflow' && always() && needs.frontend-tests-linux.result != 'failure' && needs.backend-unit-tests.result != 'failure' + if: github.repository == 'langflow-ai/langflow' && always() && needs.create-nightly-tag.result == 'success' && needs.frontend-tests-linux.result != 'failure' && needs.backend-unit-tests.result != 'failure' name: Run Nightly Langflow Build needs: [validate-inputs, create-nightly-tag, frontend-tests-linux, frontend-tests-windows, backend-unit-tests] diff --git a/.github/workflows/python_test.yml b/.github/workflows/python_test.yml index 4063fec810..40d3a6cf95 100644 --- a/.github/workflows/python_test.yml +++ b/.github/workflows/python_test.yml @@ -93,7 +93,7 @@ jobs: - name: Run unit tests uses: nick-fields/retry@v3 with: - timeout_minutes: 30 + timeout_minutes: 40 max_attempts: 2 command: make unit_tests args="-x -vv --splits ${{ matrix.splitCount }} --group ${{ matrix.group }} --reruns 5 --cov --cov-config=src/backend/.coveragerc --cov-report=xml --cov-report=html" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 504103ac4c..1cade02e7b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,11 @@ on: required: false type: boolean default: false + release_bundles: + description: "Release every src/bundles/* package (required when releasing main, since main pins exact bundle versions)" + required: false + type: boolean + default: false build_docker_base: description: "Build Docker Image for Langflow Base" required: true @@ -71,6 +76,7 @@ jobs: echo "release_package_main: ${{ inputs.release_package_main }}" echo "release_lfx: ${{ inputs.release_lfx }}" echo "release_sdk: ${{ inputs.release_sdk }}" + echo "release_bundles: ${{ inputs.release_bundles }}" echo "build_docker_base: ${{ inputs.build_docker_base }}" echo "build_docker_main: ${{ inputs.build_docker_main }}" echo "pre_release: ${{ inputs.pre_release }}" @@ -163,6 +169,12 @@ jobs: exit 1 fi + if [ "${{ inputs.release_package_main }}" = "true" ] && [ "${{ inputs.release_bundles }}" = "false" ]; then + echo "Error: Cannot release Langflow Main without releasing bundles." + echo "Langflow Main pins exact bundle versions (lfx-arxiv, lfx-duckduckgo, ...). Please enable 'release_bundles' or disable 'release_package_main'." + exit 1 + fi + echo "✅ Release dependencies validated successfully." determine-base-version: @@ -245,7 +257,7 @@ jobs: - name: Determine version id: version run: | - version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | grep -v 'langflow-sdk' | awk '{print $2}' | sed 's/^v//') + version=$(uv tree | grep -E '^langflow(-nightly)?[[:space:]]' | awk '{print $2}' | sed 's/^v//') echo "Main version from pyproject.toml: $version" if [ ${{inputs.pre_release}} == "true" ]; then @@ -844,9 +856,63 @@ jobs: name: dist-main path: dist + build-bundles: + name: Build Langflow Bundles + needs: [build-lfx, determine-lfx-version] + if: | + always() && + !cancelled() && + inputs.release_bundles && + needs.build-lfx.result == 'success' + 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 }} + - 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: 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-cross-platform: name: Test Cross-Platform Installation - needs: [build-base, build-main, build-lfx, build-sdk] + needs: [build-base, build-main, build-lfx, build-sdk, build-bundles] if: | always() && !cancelled() && @@ -857,6 +923,7 @@ jobs: main-artifact-name: "dist-main" lfx-artifact-name: "dist-lfx" sdk-artifact-name: ${{ needs.build-sdk.result == 'success' && 'dist-sdk' || '' }} + bundles-artifact-name: ${{ needs.build-bundles.result == 'success' && needs.build-bundles.outputs.bundles-found == '1' && 'dist-bundles' || '' }} pre_release: ${{ inputs.pre_release }} publish-base: @@ -889,10 +956,58 @@ jobs: run: | cd src/backend/base && uv publish dist/*.whl + publish-bundles: + name: Publish Bundles to PyPI + needs: [build-bundles, test-cross-platform, ci, publish-lfx] + if: | + always() && + !cancelled() && + inputs.release_bundles && + needs.build-bundles.result == 'success' && + needs.build-bundles.outputs.bundles-found == '1' && + needs.test-cross-platform.result == 'success' && + needs.ci.result == 'success' && + (needs.publish-lfx.result == 'success' || needs.publish-lfx.result == 'skipped') + 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: "3.13" + - name: Publish bundles to PyPI + if: ${{ !inputs.dry_run }} + 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 + for wheel in "${wheels[@]}"; do + echo "Publishing $wheel" + uv publish "$wheel" + done + publish-main: name: Publish Langflow Main to PyPI - if: ${{ inputs.release_package_main }} - needs: [build-main, test-cross-platform, publish-base, ci] + if: | + always() && + !cancelled() && + inputs.release_package_main && + needs.build-main.result == 'success' && + needs.test-cross-platform.result == 'success' && + needs.publish-base.result == 'success' && + needs.ci.result == 'success' && + (needs.publish-bundles.result == 'success' || needs.publish-bundles.result == 'skipped') + needs: [build-main, test-cross-platform, publish-base, publish-bundles, ci] runs-on: ubuntu-latest steps: - name: Download main artifact diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index c939f9942d..ff3cf90727 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -194,15 +194,18 @@ jobs: name_without_extras=$(echo $name | sed 's/\[.*\]//') if [ "$name_without_extras" != "langflow-base-nightly" ]; then echo "Name $name_without_extras does not match langflow-base-nightly. Exiting the workflow." + echo "skipped=true" >> $GITHUB_OUTPUT exit 1 fi if [ "$version" != "${{ inputs.nightly_tag_base }}" ]; then echo "Version $version does not match nightly tag ${{ inputs.nightly_tag_base }}. Exiting the workflow." + echo "skipped=true" >> $GITHUB_OUTPUT exit 1 fi # Strip the leading `v` from the version version=$(echo $version | sed 's/^v//') echo "version=$version" >> $GITHUB_OUTPUT + echo "skipped=false" >> $GITHUB_OUTPUT - name: Build Langflow Base for distribution run: | @@ -294,8 +297,8 @@ jobs: - name: Verify Nightly Name and Version id: verify run: | - name=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | grep -v 'langflow-sdk' | awk '{print $1}') - version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | grep -v 'langflow-sdk' | awk '{print $2}') + name=$(uv tree | grep -E '^langflow(-nightly)?[[:space:]]' | awk '{print $1}') + version=$(uv tree | grep -E '^langflow(-nightly)?[[:space:]]' | awk '{print $2}') if [ "$name" != "langflow-nightly" ]; then echo "Name $name does not match langflow-nightly. Exiting the workflow." exit 1 diff --git a/.secrets.baseline b/.secrets.baseline index ffc4ab745a..e7e27df8b4 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -127,22 +127,6 @@ } ], "results": { - ".github/workflows/db-migration-validation.yml": [ - { - "type": "Secret Keyword", - "filename": ".github/workflows/db-migration-validation.yml", - "hashed_secret": "86267c394a92ee1c619541c3e399bb0ef3c461a9", - "is_verified": false, - "line_number": 305 - }, - { - "type": "Basic Auth Credentials", - "filename": ".github/workflows/db-migration-validation.yml", - "hashed_secret": "e80c4f90316c87b6b24d03890493c8d1c7c1c99d", - "is_verified": false, - "line_number": 305 - } - ], ".github/workflows/nightly_build.yml": [ { "type": "Secret Keyword", @@ -159,7 +143,7 @@ "filename": ".github/workflows/release.yml", "hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0", "is_verified": false, - "line_number": 391, + "line_number": 403, "is_secret": false } ], @@ -169,7 +153,7 @@ "filename": ".github/workflows/release_nightly.yml", "hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0", "is_verified": false, - "line_number": 512, + "line_number": 515, "is_secret": false } ], @@ -9297,5 +9281,5 @@ } ] }, - "generated_at": "2026-05-28T15:17:56Z" + "generated_at": "2026-05-28T22:21:26Z" }