Skip to content

Langflow Release by @erichare #1703

Langflow Release by @erichare

Langflow Release by @erichare #1703

Workflow file for this run

name: Langflow Release
run-name: Langflow Release by @${{ github.actor }}
on:
workflow_dispatch:
inputs:
release_tag:
description: "Tag to release from. This is the tag that contains the source code for the release."
required: true
type: string
release_package_base:
description: "Release Langflow Base"
required: true
type: boolean
default: false
release_package_main:
description: "Release Langflow"
required: true
type: boolean
default: false
release_lfx:
description: "Release LFX package (manually triggered)"
required: false
type: boolean
default: false
release_sdk:
description: "Release langflow-sdk package (required when releasing LFX)"
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
type: boolean
default: false
build_docker_main:
description: "Build Docker Image for Langflow"
required: true
type: boolean
default: false
pre_release:
description: "Pre-release"
required: false
type: boolean
default: false
create_release:
description: "Whether to create a gh release"
required: false
type: boolean
default: false
dry_run:
description: "Dry run mode - disables all pushes to external services (PyPI, Docker, GitHub releases)"
required: false
type: boolean
default: true
run_pip_check:
description: "Whether to run pip check - ONLY SET TO FALSE ONCE YOU HAVE ALREADY ATTEMPTED AND FAILED A RUN THEN MADE SURE DEPS DO NOT CAUSE FAILURES"
required: false
type: boolean
default: true
jobs:
echo-inputs:
name: Echo Workflow Inputs
runs-on: ubuntu-latest
steps:
- name: Echo workflow inputs
run: |
echo "release_tag: ${{ inputs.release_tag }}"
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 "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 }}"
echo "create_release: ${{ inputs.create_release }}"
echo "dry_run: ${{ inputs.dry_run }}"
validate-tag:
name: Validate Tag Input
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0 # Fetch all history - required for tags (?)
- name: Validate that input is a tag, not a branch
run: |
# Check if the input exists as a tag
if ! git tag -l | grep -q "^${{ inputs.release_tag }}$"; then
echo "Error: '${{ inputs.release_tag }}' is not a valid tag."
echo "Available tags:"
git tag -l | head -20
exit 1
fi
# Check if the input also exists as a branch (warn if so, but don't fail)
if git branch -r | grep -q "origin/${{ inputs.release_tag }}$"; then
echo "Tag '${{ inputs.release_tag }}' also exists as a branch. Exiting out of caution."
exit 1
fi
echo "Validated: '${{ inputs.release_tag }}' is a valid tag."
validate-tag-format:
name: Validate Tag Format and Check for Duplicates
runs-on: ubuntu-latest
needs: [validate-tag]
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Validate Tag Has v Prefix
run: |
TAG="${{ inputs.release_tag }}"
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Error: Tag must start with 'v' and follow semver format (v1.2.3)"
echo " Got: $TAG"
echo ""
echo "This is required to ensure GitHub's generateReleaseNotes uses the correct base comparison."
exit 1
fi
echo "✅ Tag format is valid: $TAG"
- name: Check for Duplicate Tag Without v Prefix
run: |
TAG="${{ inputs.release_tag }}"
TAG_WITHOUT_V="${TAG#v}"
if git rev-parse "$TAG_WITHOUT_V" >/dev/null 2>&1; then
echo "❌ Error: Duplicate tag without 'v' prefix exists: $TAG_WITHOUT_V"
echo " This will cause release notes to use the wrong base comparison."
echo ""
echo " The tag '$TAG_WITHOUT_V' points to commit: $(git rev-parse --short $TAG_WITHOUT_V)"
echo " The tag '$TAG' points to commit: $(git rev-parse --short $TAG)"
echo ""
echo " To fix this, delete the duplicate tag:"
echo " git push origin :refs/tags/$TAG_WITHOUT_V"
exit 1
fi
echo "✅ No duplicate tag found"
validate-dependencies:
name: Validate Release Dependencies
runs-on: ubuntu-latest
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
run: |
if [ "${{ inputs.release_package_main }}" = "true" ] && [ "${{ inputs.release_package_base }}" = "false" ]; then
echo "Error: Cannot release Langflow Main without releasing Langflow Base."
echo "Please enable 'release_package_base' or disable 'release_package_main'."
exit 1
fi
if [ "${{ inputs.release_package_base }}" = "true" ] && [ "${{ inputs.release_lfx }}" = "false" ]; then
echo "Error: Cannot release Langflow Base without releasing LFX."
echo "Langflow Base depends on LFX. Please enable 'release_lfx' or disable 'release_package_base'."
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
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-rc-number:
name: Determine Shared RC Number
needs: [validate-tag, validate-dependencies]
runs-on: ubuntu-latest
outputs:
rc_number: ${{ steps.rc.outputs.rc_number || steps.stable.outputs.rc_number }}
steps:
- name: Use stable RC number
id: stable
if: ${{ !inputs.pre_release }}
run: |
echo "rc_number=0" >> "$GITHUB_OUTPUT"
echo "Not a pre-release; shared rc number is 0."
- name: Checkout code
if: ${{ inputs.pre_release }}
uses: actions/checkout@v6
with:
ref: ${{ inputs.release_tag }}
- name: Setup Environment
if: ${{ inputs.pre_release }}
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
python-version: "3.13"
prune-cache: false
- name: Determine shared pre-release RC number
id: rc
if: ${{ inputs.pre_release }}
shell: bash
run: |
set -euo pipefail
read_pyproject_version() {
python3 - "$1" <<'PY'
import pathlib
import sys
import tomllib
data = tomllib.loads(pathlib.Path(sys.argv[1]).read_text())
print(data["project"]["version"])
PY
}
fetch_pypi_versions() {
local package_name="$1"
local output_file="$2"
local response_file
response_file="$(mktemp)"
local status
status=$(curl --retry 3 --retry-delay 2 --retry-connrefused -sS -o "$response_file" -w "%{http_code}" "https://pypi.org/pypi/${package_name}/json")
if [ "$status" = "404" ]; then
: > "$output_file"
return
fi
if [ "$status" != "200" ]; then
echo "Failed to fetch PyPI releases for ${package_name}: HTTP ${status}" >&2
cat "$response_file" >&2 || true
exit 1
fi
jq -r '.releases | keys[]' "$response_file" > "$output_file"
}
fetch_docker_tags() {
local repository="$1"
local output_file="$2"
local response_file
response_file="$(mktemp)"
local status
status=$(curl --retry 3 --retry-delay 2 --retry-connrefused -sS -o "$response_file" -w "%{http_code}" "https://registry.hub.docker.com/v2/repositories/${repository}/tags?page_size=100")
if [ "$status" != "200" ]; then
echo "Failed to fetch Docker tags for ${repository}: HTTP ${status}" >&2
cat "$response_file" >&2 || true
exit 1
fi
jq -r '.results[].name' "$response_file" > "$output_file"
}
max_rc=0
consider_versions() {
local label="$1"
local base_version="$2"
local versions_file="$3"
local rc_number
rc_number=$(uv run ./scripts/ci/langflow_pre_release_tag.py "$base_version" --print-rc-number - < "$versions_file")
echo "${label}: next rc number for ${base_version} is ${rc_number}"
if [ "$rc_number" -gt "$max_rc" ]; then
max_rc="$rc_number"
fi
}
tmp_dir="$(mktemp -d)"
if [ "${{ inputs.release_package_main }}" = "true" ]; then
version="$(read_pyproject_version pyproject.toml)"
fetch_pypi_versions "langflow" "$tmp_dir/langflow-pypi.txt"
consider_versions "PyPI langflow" "$version" "$tmp_dir/langflow-pypi.txt"
fi
if [ "${{ inputs.release_package_base }}" = "true" ]; then
version="$(read_pyproject_version src/backend/base/pyproject.toml)"
fetch_pypi_versions "langflow-base" "$tmp_dir/langflow-base-pypi.txt"
consider_versions "PyPI langflow-base" "$version" "$tmp_dir/langflow-base-pypi.txt"
fi
if [ "${{ inputs.release_lfx }}" = "true" ]; then
version="$(read_pyproject_version src/lfx/pyproject.toml)"
fetch_pypi_versions "lfx" "$tmp_dir/lfx-pypi.txt"
consider_versions "PyPI lfx" "$version" "$tmp_dir/lfx-pypi.txt"
fi
if [ "${{ inputs.release_sdk || inputs.release_lfx }}" = "true" ]; then
version="$(read_pyproject_version src/sdk/pyproject.toml)"
fetch_pypi_versions "langflow-sdk" "$tmp_dir/langflow-sdk-pypi.txt"
consider_versions "PyPI langflow-sdk" "$version" "$tmp_dir/langflow-sdk-pypi.txt"
fi
if [ "${{ inputs.release_bundles }}" = "true" ]; then
for pyproject in src/bundles/*/pyproject.toml; do
package_name=$(python3 - "$pyproject" <<'PY'
import pathlib
import sys
import tomllib
data = tomllib.loads(pathlib.Path(sys.argv[1]).read_text())
print(data["project"]["name"])
PY
)
version="$(read_pyproject_version "$pyproject")"
output_file="$tmp_dir/${package_name}-pypi.txt"
fetch_pypi_versions "$package_name" "$output_file"
if grep -Fxq "$version" "$output_file"; then
echo "PyPI ${package_name}: final ${version} is already published; excluding its historical RCs."
else
consider_versions "PyPI ${package_name}" "$version" "$output_file"
fi
done
fi
if [ "${{ inputs.build_docker_main }}" = "true" ]; then
version="$(read_pyproject_version pyproject.toml)"
fetch_docker_tags "langflowai/langflow" "$tmp_dir/langflow-docker.txt"
grep -v '^base-' "$tmp_dir/langflow-docker.txt" | grep -vE '\-(amd64|arm64)$' > "$tmp_dir/langflow-docker-release-tags.txt" || true
consider_versions "Docker langflowai/langflow" "$version" "$tmp_dir/langflow-docker-release-tags.txt"
fi
if [ "${{ inputs.build_docker_base }}" = "true" ]; then
version="$(read_pyproject_version src/backend/base/pyproject.toml)"
fetch_docker_tags "langflowai/langflow" "$tmp_dir/langflow-base-docker.txt"
grep -E '^base-' "$tmp_dir/langflow-base-docker.txt" | grep -vE '\-(amd64|arm64)$' | sed 's/^base-//' > "$tmp_dir/langflow-base-docker-release-tags.txt" || true
consider_versions "Docker langflowai/langflow base" "$version" "$tmp_dir/langflow-base-docker-release-tags.txt"
fi
echo "Shared pre-release rc number: ${max_rc}"
echo "rc_number=${max_rc}" >> "$GITHUB_OUTPUT"
determine-base-version:
name: Determine Base Version
needs: [validate-tag, validate-dependencies, determine-rc-number]
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: Install the project
run: uv sync
- name: Determine version
id: version
run: |
version=$(python3 -c "
import tomllib, pathlib
data = tomllib.loads(pathlib.Path('src/backend/base/pyproject.toml').read_text())
print(data['project']['version'])
")
echo "Base version from pyproject.toml: $version"
if [ ${{inputs.pre_release}} == "true" ]; then
rc_number="${{ needs.determine-rc-number.outputs.rc_number }}"
version="$(uv run ./scripts/ci/langflow_pre_release_tag.py "$version" --rc-number "$rc_number")"
echo "Shared release candidate number: rc$rc_number"
echo "Base pre-release version to be released: $version"
else
last_released_version=$(curl -s "https://pypi.org/pypi/langflow-base/json" | jq -r '.releases | keys | .[]' | grep -vE '(a|b|rc|dev|alpha|beta)' | sort -V | tail -n 1)
echo "Latest base release version: $last_released_version"
fi
if [ "$version" = "$last_released_version" ] && [ "${{ inputs.release_package_base }}" = "true" ]; then
echo "Base 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
determine-main-version:
name: Determine Main Version
needs: [validate-tag, validate-dependencies, determine-rc-number]
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: Install the project
run: uv sync
- name: Determine version
id: version
run: |
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
rc_number="${{ needs.determine-rc-number.outputs.rc_number }}"
version="$(uv run ./scripts/ci/langflow_pre_release_tag.py "$version" --rc-number "$rc_number")"
echo "Shared release candidate number: rc$rc_number"
echo "Main pre-release version to be released: $version"
else
last_released_version=$(curl -s "https://pypi.org/pypi/langflow/json" | jq -r '.releases | keys | .[]' | grep -vE '(a|b|rc|dev|alpha|beta)' | sort -V | tail -n 1)
echo "Latest main release version: $last_released_version"
fi
if [ "$version" = "$last_released_version" ] && [ "${{ inputs.release_package_main }}" = "true" ]; then
echo "Main 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
determine-lfx-version:
name: Determine LFX Version
needs: [validate-tag, validate-dependencies, determine-rc-number]
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: Install LFX dependencies
run: uv sync --dev --package lfx
- name: Determine version
id: version
run: |
version=$(uv tree | grep 'lfx' | awk '{print $3}' | sed 's/^v//' | head -n 2 | xargs)
echo "LFX version from pyproject.toml: $version"
if [ ${{inputs.pre_release}} == "true" ]; then
rc_number="${{ needs.determine-rc-number.outputs.rc_number }}"
version="$(uv run ./scripts/ci/langflow_pre_release_tag.py "$version" --rc-number "$rc_number")"
echo "Shared release candidate number: rc$rc_number"
echo "LFX pre-release version to be released: $version"
else
last_released_version=$(curl -s "https://pypi.org/pypi/lfx/json" | jq -r '.releases | keys | .[]' | grep -vE '(a|b|rc|dev|alpha|beta)' | sort -V | tail -n 1)
echo "Latest LFX release version: $last_released_version"
fi
if [ "$version" = "$last_released_version" ]; then
echo "LFX 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
determine-sdk-version:
name: Determine SDK Version
needs: [validate-tag, validate-dependencies, determine-rc-number]
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: Install SDK dependencies
run: uv sync --dev --package langflow-sdk
- 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=""
if [ ${{inputs.pre_release}} == "true" ]; then
rc_number="${{ needs.determine-rc-number.outputs.rc_number }}"
version="$(uv run ./scripts/ci/langflow_pre_release_tag.py "$version" --rc-number "$rc_number")"
echo "Shared release candidate number: rc$rc_number"
echo "SDK pre-release version to be released: $version"
fi
else
if [ ${{inputs.pre_release}} == "true" ]; then
rc_number="${{ needs.determine-rc-number.outputs.rc_number }}"
version="$(uv run ./scripts/ci/langflow_pre_release_tag.py "$version" --rc-number "$rc_number")"
echo "Shared release candidate number: rc$rc_number"
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]
uses: ./.github/workflows/ci.yml
with:
ref: ${{ inputs.release_tag }}
python-versions: "['3.10', '3.11', '3.12', '3.13', '3.14']"
frontend-tests-folder: "tests"
release: true
run-all-tests: true
runs-on: ubuntu-latest
secrets: inherit
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
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: 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 }}"
# importlib.metadata returns the PEP 440 canonical form, so normalize
# the expected version the same way the wheel filename check does.
NORMALIZED_VERSION=$(echo "$EXPECTED_VERSION" | sed 's/\.rc/rc/g; s/\.a/a/g; s/\.b/b/g; s/\.dev/dev/g')
./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 == '$NORMALIZED_VERSION', f'Installed version {installed_version} does not match expected version $NORMALIZED_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
if: ${{ inputs.pre_release }}
run: |
VERSION="${{ needs.determine-lfx-version.outputs.version }}"
echo "Setting pre-release version to: $VERSION"
cd src/lfx
# Update version in lfx pyproject.toml
sed -i.bak "s/^version = .*/version = \"$VERSION\"/" pyproject.toml
# Keep the bundled component index version/hash aligned with the RC wheel.
uv run --no-sync python ../../scripts/ci/update_component_index_version.py \
src/lfx/_assets/component_index.json "$VERSION"
# 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
rm -rf dist/
uv build --wheel --out-dir dist
- name: Verify built version
run: |
EXPECTED_VERSION="${{ needs.determine-lfx-version.outputs.version }}"
WHEEL_FILE=$(ls src/lfx/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 CLI
run: |
cd src/lfx
# 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:
name: dist-lfx
path: src/lfx/dist
build-base:
name: Build Langflow Base
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
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.12"
prune-cache: false
- name: Download LFX artifact
uses: actions/download-artifact@v7
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: Install dependencies with local LFX and SDK wheels
run: |
# Create virtual environment
uv venv
# 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
if: ${{ inputs.run_pip_check }}
run: uv pip check
- name: Set version for pre-release
if: ${{ inputs.pre_release }}
run: |
VERSION="${{ needs.determine-base-version.outputs.version }}"
echo "Setting pre-release version to: $VERSION"
cd src/backend/base
# Update version in pyproject.toml
sed -i.bak "s/^version = .*/version = \"$VERSION\"/" pyproject.toml
# Verify the change
echo "Updated pyproject.toml version:"
grep "^version" pyproject.toml
- name: Update lfx dependency for pre-release
if: ${{ inputs.pre_release }}
run: |
LFX_VERSION="${{ needs.determine-lfx-version.outputs.version }}"
echo "Updating lfx dependency to allow pre-release version: $LFX_VERSION"
python3 scripts/ci/update_lfx_pre_release_dependency.py \
src/backend/base/pyproject.toml "$LFX_VERSION"
echo "Updated lfx dependency:"
grep "lfx" src/backend/base/pyproject.toml
- name: Build project for distribution
run: make build base=true args="--wheel"
- name: Verify built version
run: |
EXPECTED_VERSION="${{ needs.determine-base-version.outputs.version }}"
WHEEL_FILE=$(ls dist/*.whl 2>/dev/null || ls src/backend/base/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 CLI
run: |
# TODO: Unsure why the whl is not built in src/backend/base/dist
mkdir src/backend/base/dist
mv dist/*.whl src/backend/base/dist
uv pip install src/backend/base/dist/*.whl
uv run python -m langflow run --host localhost --port 7860 --backend-only &
SERVER_PID=$!
# Wait for the server to start
timeout 120 bash -c 'until curl -f http://localhost:7860/api/v1/auto_login; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
# Terminate the server
kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
sleep 20 # give the server some time to terminate
# Check if the server is still running
if kill -0 $SERVER_PID 2>/dev/null; then
echo "Failed to terminate the server"
exit 0
else
echo "Server terminated successfully"
fi
# PyPI publishing moved to after cross-platform testing
- name: Upload Artifact
uses: actions/upload-artifact@v6
with:
name: dist-base
path: src/backend/base/dist
build-main:
name: Build Langflow Main
needs:
[
build-base,
build-lfx,
build-sdk,
determine-base-version,
determine-main-version,
determine-lfx-version,
]
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
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.12"
prune-cache: false
- name: Download LFX artifact
uses: actions/download-artifact@v7
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:
name: dist-base
path: ./base-dist
- name: Install dependencies with local wheels
run: |
# Create virtual environment
uv venv
# Install using pip with local wheel directories as find-links
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
if: ${{ inputs.run_pip_check }}
run: uv pip check
- name: Set version for pre-release
if: ${{ inputs.pre_release }}
run: |
VERSION="${{ needs.determine-main-version.outputs.version }}"
echo "Setting main pre-release version to: $VERSION"
# Update version in main pyproject.toml
sed -i.bak "s/^version = .*/version = \"$VERSION\"/" pyproject.toml
# Verify the change
echo "Updated pyproject.toml version:"
grep "^version" pyproject.toml
- name: Update langflow-base dependency for pre-release
if: ${{ inputs.pre_release }}
run: |
BASE_VERSION="${{ needs.determine-base-version.outputs.version }}"
echo "Base version for pre-release: $BASE_VERSION"
# Extract current langflow-base constraint from pyproject.toml
CURRENT_CONSTRAINT=$(grep -E '^\s*"langflow-base' pyproject.toml | head -1)
echo "Current constraint: $CURRENT_CONSTRAINT"
# Extract the major.minor version (e.g., "0.8" from "~=0.8.0")
MAJOR_MINOR=$(echo "$CURRENT_CONSTRAINT" | sed -E 's/.*[~>=<]+([0-9]+\.[0-9]+).*/\1/')
NEXT_MAJOR=$((${MAJOR_MINOR%.*} + 1))
# Create new constraint: >=BASE_VERSION,<NEXT_MAJOR.dev0 (preserve [complete] extra)
NEW_CONSTRAINT="\"langflow-base[complete]>=$BASE_VERSION,<$NEXT_MAJOR.dev0\""
echo "New constraint: $NEW_CONSTRAINT"
# Replace the constraint
sed -i.bak "s|\"langflow-base[^\"]*\"|$NEW_CONSTRAINT|" pyproject.toml
# Verify the change
echo "Updated dependency:"
grep "langflow-base" pyproject.toml
- name: Build project for pre-release distribution
if: ${{ inputs.pre_release }}
run: make build pre=true args="--no-sources --wheel"
- name: Build project for distribution
if: ${{ !inputs.pre_release }}
run: make build main=true args="--no-sources --wheel"
- name: Verify built version
run: |
EXPECTED_VERSION="${{ needs.determine-main-version.outputs.version }}"
WHEEL_FILE=$(ls 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 CLI
run: |
uv pip install dist/*.whl
uv run python -m langflow run --host localhost --port 7860 --backend-only &
SERVER_PID=$!
# Wait for the server to start
timeout 120 bash -c 'until curl -f http://localhost:7860/health_check; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
# Terminate the server
kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
sleep 20 # give the server some time to terminate
# Check if the server is still running
if kill -0 $SERVER_PID 2>/dev/null; then
echo "Failed to terminate the server"
exit 0
else
echo "Server terminated successfully"
fi
# PyPI publishing moved to after cross-platform testing
- name: Upload Artifact
uses: actions/upload-artifact@v6
with:
name: dist-main
path: dist
build-bundles:
name: Build Langflow Bundles
needs: [build-lfx, determine-lfx-version, determine-rc-number]
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: Set unpublished bundle versions for pre-release
if: ${{ inputs.pre_release }}
run: |
set -euo pipefail
pypi_final_exists() {
local package_name="$1"
local version="$2"
local response_file
response_file="$(mktemp)"
local status
status=$(curl --retry 3 --retry-delay 2 --retry-connrefused -sS -o "$response_file" -w "%{http_code}" "https://pypi.org/pypi/${package_name}/${version}/json")
if [ "$status" = "200" ]; then
return 0
fi
if [ "$status" = "404" ]; then
return 1
fi
echo "Failed to check PyPI release ${package_name} ${version}: HTTP ${status}" >&2
cat "$response_file" >&2 || true
exit 1
}
RC_NUMBER="${{ needs.determine-rc-number.outputs.rc_number }}"
echo "Setting unpublished bundle pre-release versions with shared rc number: rc$RC_NUMBER"
for pyproject in src/bundles/*/pyproject.toml; do
read -r PACKAGE_NAME CURRENT_VERSION < <(python3 - "$pyproject" <<'PY'
import pathlib
import sys
import tomllib
data = tomllib.loads(pathlib.Path(sys.argv[1]).read_text())
print(data["project"]["name"], data["project"]["version"])
PY
)
if pypi_final_exists "$PACKAGE_NAME" "$CURRENT_VERSION"; then
echo "Keeping ${PACKAGE_NAME} at ${CURRENT_VERSION}: final version is already published."
continue
fi
VERSION=$(uv run ./scripts/ci/langflow_pre_release_tag.py "$CURRENT_VERSION" --rc-number "$RC_NUMBER")
sed -i.bak "0,/^version = /s|^version = .*|version = \"$VERSION\"|" "$pyproject"
rm -f "${pyproject}.bak"
echo "Set ${PACKAGE_NAME} to ${VERSION}."
done
- name: Relax bundle lfx floor for pre-release
if: ${{ inputs.pre_release }}
run: |
# Bundles floor lfx at the release line (e.g. lfx>=1.10.0). A pre-release
# such as 1.10.0rc0 sorts BELOW 1.10.0 under PEP 440, so it fails that
# floor and the cross-platform install test cannot resolve. Mirror what
# build-base / build-main / build-lfx already do for pre-release: rewrite
# the floor to the exact pre-release lfx version, preserving the bundle's
# wide <2.0.0 BUNDLE_API cap. Stable source stays at >=1.10.0 -- only the
# RC wheels are relaxed, at build time.
LFX_VERSION="${{ needs.determine-lfx-version.outputs.version }}"
echo "Relaxing bundle lfx floor to pre-release version: $LFX_VERSION"
for pyproject in src/bundles/*/pyproject.toml; do
sed -i.bak -E "s|\"lfx>=[^\"]*\"|\"lfx>=${LFX_VERSION},<2.0.0\"|" "$pyproject"
rm -f "${pyproject}.bak"
grep -E '"lfx>=' "$pyproject" | sed "s|^| ${pyproject}: |"
done
- 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, build-bundles]
if: |
always() &&
!cancelled() &&
(needs.build-base.result == 'success' || needs.build-main.result == 'success' || needs.build-lfx.result == 'success')
uses: ./.github/workflows/cross-platform-test.yml
with:
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' || '' }}
bundles-artifact-name: ${{ needs.build-bundles.result == 'success' && needs.build-bundles.outputs.bundles-found == '1' && 'dist-bundles' || '' }}
pre_release: ${{ inputs.pre_release }}
publish-base:
name: Publish Langflow Base to PyPI
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
uses: actions/download-artifact@v7
with:
name: dist-base
path: src/backend/base/dist
- name: Setup Environment
uses: astral-sh/setup-uv@v6
with:
enable-cache: false
python-version: "3.13"
- name: Publish base to PyPI
if: ${{ !inputs.dry_run }}
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
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
failed=0
pypi_publish_delay_seconds=60
pypi_publish_max_attempts=5
wheel_count=${#wheels[@]}
wheel_number=0
for wheel in "${wheels[@]}"; do
wheel_number=$((wheel_number + 1))
echo "Publishing $wheel"
attempt=1
published=0
while true; do
# Tolerate "already exists" so reruns -- or a bundle whose version is
# unchanged and already on PyPI -- do not fail the job. Matches the
# duplicate-tolerant publish in release_bundles.yml.
if err=$(uv publish "$wheel" 2>&1); then
echo "$err"
published=1
break
fi
echo "$err"
if echo "$err" | grep -qiE 'already exists|file already exists|HTTP 400|duplicate'; then
echo "Skipping $wheel: already published."
break
fi
if echo "$err" | grep -qiE 'HTTP 429|429 Too Many Requests|Too many new projects created'; then
if [ "$attempt" -lt "$pypi_publish_max_attempts" ]; then
echo "PyPI rate limited $wheel; sleeping ${pypi_publish_delay_seconds}s before retry $((attempt + 1))/${pypi_publish_max_attempts}."
sleep "$pypi_publish_delay_seconds"
attempt=$((attempt + 1))
continue
fi
fi
echo "Publish failed for $wheel"
failed=1
break
done
if [ "$published" = "1" ] && [ "$wheel_number" -lt "$wheel_count" ]; then
echo "Sleeping ${pypi_publish_delay_seconds}s before the next PyPI upload."
sleep "$pypi_publish_delay_seconds"
fi
done
if [ "$failed" = "1" ]; then
exit 1
fi
publish-main:
name: Publish Langflow Main to PyPI
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
uses: actions/download-artifact@v7
with:
name: dist-main
path: dist
- name: Setup Environment
uses: astral-sh/setup-uv@v6
with:
enable-cache: false
python-version: "3.13"
- name: Publish main to PyPI
if: ${{ !inputs.dry_run }}
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
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
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
uses: actions/download-artifact@v7
with:
name: dist-lfx
path: src/lfx/dist
- name: Setup Environment
uses: astral-sh/setup-uv@v6
with:
enable-cache: false
python-version: "3.13"
- name: Publish LFX to PyPI
if: ${{ !inputs.dry_run }}
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
run: |
cd src/lfx && uv publish dist/*.whl
call_docker_build_base:
name: Call Docker Build Workflow for Langflow Base
if: ${{ inputs.build_docker_base }}
needs: [ci, determine-base-version]
uses: ./.github/workflows/docker-build-v2.yml
with:
ref: ${{ inputs.release_tag }}
release_type: base
pre_release: ${{ inputs.pre_release }}
version_override: ${{ needs.determine-base-version.outputs.version }}
push_to_registry: ${{ !inputs.dry_run }}
secrets: inherit
call_docker_build_main:
name: Call Docker Build Workflow for Langflow
if: ${{ inputs.build_docker_main }}
needs: [ci, determine-main-version]
uses: ./.github/workflows/docker-build-v2.yml
with:
ref: ${{ inputs.release_tag }}
release_type: main
pre_release: ${{ inputs.pre_release }}
version_override: ${{ needs.determine-main-version.outputs.version }}
push_to_registry: ${{ !inputs.dry_run }}
secrets: inherit
call_docker_build_main_backend:
name: Call Docker Build Workflow for Langflow Backend
if: ${{ inputs.build_docker_main && !inputs.dry_run }}
needs: [call_docker_build_main, determine-main-version]
uses: ./.github/workflows/docker-build-v2.yml
with:
ref: ${{ inputs.release_tag }}
release_type: main-backend
pre_release: ${{ inputs.pre_release }}
version_override: ${{ needs.determine-main-version.outputs.version }}
push_to_registry: ${{ !inputs.dry_run }}
secrets: inherit
call_docker_build_main_frontend:
name: Call Docker Build Workflow for Langflow Frontend
if: ${{ inputs.build_docker_main && !inputs.dry_run }}
needs: [call_docker_build_main, determine-main-version]
uses: ./.github/workflows/docker-build-v2.yml
with:
ref: ${{ inputs.release_tag }}
release_type: main-frontend
pre_release: ${{ inputs.pre_release }}
version_override: ${{ needs.determine-main-version.outputs.version }}
push_to_registry: ${{ !inputs.dry_run }}
secrets: inherit
call_docker_build_main_ep:
name: Call Docker Build Workflow for Langflow with Entrypoint
if: ${{ inputs.build_docker_main }}
needs: [ci, determine-main-version]
uses: ./.github/workflows/docker-build-v2.yml
with:
ref: ${{ inputs.release_tag }}
release_type: main-ep
pre_release: ${{ inputs.pre_release }}
version_override: ${{ needs.determine-main-version.outputs.version }}
push_to_registry: ${{ !inputs.dry_run }}
secrets: inherit
call_docker_build_main_all:
name: Call Docker Build Workflow for langflow-all
if: ${{ inputs.build_docker_main }}
needs: [ci, determine-main-version]
uses: ./.github/workflows/docker-build-v2.yml
with:
ref: ${{ inputs.release_tag }}
release_type: main-all
pre_release: ${{ inputs.pre_release }}
version_override: ${{ needs.determine-main-version.outputs.version }}
push_to_registry: ${{ !inputs.dry_run }}
secrets: inherit
create_release:
name: Create Release
runs-on: ubuntu-latest
needs:
[determine-main-version, build-main, publish-main, validate-tag-format]
if: |
always() &&
!cancelled() &&
!inputs.dry_run &&
inputs.create_release &&
needs.build-main.result == 'success' &&
needs.publish-main.result == 'success' &&
needs.validate-tag-format.result == 'success'
steps:
- uses: actions/download-artifact@v4
with:
name: dist-main
path: dist
# The release must attach to the dispatched v-prefixed tag. Passing the
# bare version as `tag` made GitHub mint a new lightweight tag at the
# default-branch HEAD — the wrong commit, still carrying the previous
# version (main only adopts the new version via the post-release
# back-merge). Pre-releases keep their computed tag (e.g. 1.10.0rc1),
# but `commit` pins any newly minted tag to the release commit.
- name: Resolve release commit
id: release_commit
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
sha=$(gh api "repos/${{ github.repository }}/commits/${{ inputs.release_tag }}" --jq '.sha')
echo "Release tag '${{ inputs.release_tag }}' resolves to commit $sha"
echo "sha=$sha" >> "$GITHUB_OUTPUT"
- name: Create Release
uses: ncipollo/release-action@v1
with:
artifacts: dist/*
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
generateReleaseNotes: true
prerelease: ${{ inputs.pre_release }}
tag: ${{ inputs.pre_release && needs.determine-main-version.outputs.version || inputs.release_tag }}
name: ${{ needs.determine-main-version.outputs.version }}
commit: ${{ steps.release_commit.outputs.sha }}
allowUpdates: true
updateOnlyUnreleased: false