mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-23 13:55:17 +08:00
fix: release candidate version selection (#13776)
* fix: share release candidate version selection * fix: review comments addressed
This commit is contained in:
42
.github/workflows/docker-build-v2.yml
vendored
42
.github/workflows/docker-build-v2.yml
vendored
@ -16,6 +16,11 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
description: "Ref to check out (branch, tag, or commit). This is required -- it specifies where the source code for the release is located."
|
||||
version_override:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
description: "Resolved version to use for image tags. When omitted, the workflow computes the version from the checked-out ref and registry history."
|
||||
push_to_registry:
|
||||
required: false
|
||||
type: boolean
|
||||
@ -44,6 +49,11 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
description: "Ref to check out (branch, tag, or commit). This is required -- it specifies where the source code for the release is located."
|
||||
version_override:
|
||||
description: "Resolved version to use for image tags. Leave blank to compute from the checked-out ref and registry history."
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
push_to_registry:
|
||||
description: "Whether to push images to registries. Set to false for testing builds without publishing."
|
||||
required: false
|
||||
@ -81,10 +91,16 @@ jobs:
|
||||
version=$(uv tree 2>/dev/null | grep '^langflow-base' | cut -d' ' -f2 | sed 's/^v//')
|
||||
echo "Base version from pyproject.toml: $version"
|
||||
|
||||
if [ ${{inputs.pre_release}} == "true" ]; then
|
||||
last_released_version=$(curl -s "https://registry.hub.docker.com/v2/repositories/langflowai/langflow/tags?page_size=100" | jq -r '.results[].name' | grep -E '^base-.*\.rc[0-9]+' | grep -vE '\-(amd64|arm64)$' | sed 's/^base-//' | sort -V | tail -n 1)
|
||||
version="$(uv run ./scripts/ci/langflow_pre_release_tag.py "$version" "$last_released_version")"
|
||||
echo "Latest base pre-release version: $last_released_version"
|
||||
if [ -n "${{ inputs.version_override }}" ]; then
|
||||
version="${{ inputs.version_override }}"
|
||||
echo "Using version override: $version"
|
||||
if [ ${{inputs.pre_release}} != "true" ]; then
|
||||
last_released_version=$(curl -s "https://registry.hub.docker.com/v2/repositories/langflowai/langflow/tags?page_size=100" | jq -r '.results[].name' | grep -E '^base-' | grep -vE '\-(amd64|arm64)$' | grep -v 'latest' | sed 's/^base-//' | sort -V | tail -n 1)
|
||||
echo "Latest base release version: $last_released_version"
|
||||
fi
|
||||
elif [ ${{inputs.pre_release}} == "true" ]; then
|
||||
released_versions=$(curl -s "https://registry.hub.docker.com/v2/repositories/langflowai/langflow/tags?page_size=100" | jq -r '.results[].name' | grep -E '^base-' | grep -vE '\-(amd64|arm64)$' | sed 's/^base-//' || true)
|
||||
version="$(printf '%s\n' "$released_versions" | uv run ./scripts/ci/langflow_pre_release_tag.py "$version" -)"
|
||||
echo "Base pre-release version to be released: $version"
|
||||
else
|
||||
last_released_version=$(curl -s "https://registry.hub.docker.com/v2/repositories/langflowai/langflow/tags?page_size=100" | jq -r '.results[].name' | grep -E '^base-' | grep -vE '\-(amd64|arm64)$' | grep -v 'latest' | sed 's/^base-//' | sort -V | tail -n 1)
|
||||
@ -127,10 +143,16 @@ jobs:
|
||||
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
|
||||
last_released_version=$(curl -s "https://registry.hub.docker.com/v2/repositories/langflowai/langflow/tags?page_size=100" | jq -r '.results[].name' | grep -E '\.rc[0-9]+' | grep -v '^base-' | grep -vE '\-(amd64|arm64)$' | sort -V | tail -n 1)
|
||||
version="$(uv run ./scripts/ci/langflow_pre_release_tag.py "$version" "$last_released_version")"
|
||||
echo "Latest main pre-release version: $last_released_version"
|
||||
if [ -n "${{ inputs.version_override }}" ]; then
|
||||
version="${{ inputs.version_override }}"
|
||||
echo "Using version override: $version"
|
||||
if [ ${{inputs.pre_release}} != "true" ]; then
|
||||
last_released_version=$(curl -s "https://registry.hub.docker.com/v2/repositories/langflowai/langflow/tags?page_size=100" | jq -r '.results[].name' | grep -v '^base-' | grep -vE '\-(amd64|arm64)$' | grep -v 'latest' | sort -V | tail -n 1)
|
||||
echo "Latest main release version: $last_released_version"
|
||||
fi
|
||||
elif [ ${{inputs.pre_release}} == "true" ]; then
|
||||
released_versions=$(curl -s "https://registry.hub.docker.com/v2/repositories/langflowai/langflow/tags?page_size=100" | jq -r '.results[].name' | grep -v '^base-' | grep -vE '\-(amd64|arm64)$' || true)
|
||||
version="$(printf '%s\n' "$released_versions" | uv run ./scripts/ci/langflow_pre_release_tag.py "$version" -)"
|
||||
echo "Main pre-release version to be released: $version"
|
||||
else
|
||||
last_released_version=$(curl -s "https://registry.hub.docker.com/v2/repositories/langflowai/langflow/tags?page_size=100" | jq -r '.results[].name' | grep -v '^base-' | grep -vE '\-(amd64|arm64)$' | grep -v 'latest' | sort -V | tail -n 1)
|
||||
@ -335,7 +357,7 @@ jobs:
|
||||
context: .
|
||||
push: ${{ inputs.push_to_registry }}
|
||||
build-args: |
|
||||
LANGFLOW_IMAGE=langflowai/langflow:${{ steps.version.outputs.version }}-${{ matrix.arch }}
|
||||
LANGFLOW_IMAGE=langflowai/langflow:${{ needs.determine-main-version.outputs.version }}-${{ matrix.arch }}
|
||||
file: ./docker/build_and_push_backend.Dockerfile
|
||||
tags: ${{ steps.tags.outputs.docker_tags }}
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
@ -356,7 +378,7 @@ jobs:
|
||||
context: .
|
||||
push: ${{ inputs.push_to_registry }}
|
||||
build-args: |
|
||||
LANGFLOW_IMAGE=ghcr.io/langflow-ai/langflow:${{ steps.version.outputs.version }}-${{ matrix.arch }}
|
||||
LANGFLOW_IMAGE=ghcr.io/langflow-ai/langflow:${{ needs.determine-main-version.outputs.version }}-${{ matrix.arch }}
|
||||
file: ./docker/build_and_push_backend.Dockerfile
|
||||
tags: ${{ steps.tags.outputs.ghcr_tags }}
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
|
||||
233
.github/workflows/release.yml
vendored
233
.github/workflows/release.yml
vendored
@ -177,9 +177,160 @@ jobs:
|
||||
|
||||
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:
|
||||
- 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 shared pre-release RC number
|
||||
id: rc
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${{ inputs.pre_release }}" != "true" ]; then
|
||||
echo "rc_number=0" >> "$GITHUB_OUTPUT"
|
||||
echo "Not a pre-release; shared rc number is 0."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
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"
|
||||
consider_versions "PyPI ${package_name}" "$version" "$output_file"
|
||||
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]
|
||||
needs: [validate-tag, validate-dependencies, determine-rc-number]
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
@ -212,16 +363,16 @@ jobs:
|
||||
echo "Base version from pyproject.toml: $version"
|
||||
|
||||
if [ ${{inputs.pre_release}} == "true" ]; then
|
||||
last_released_version=$(curl -s "https://pypi.org/pypi/langflow-base/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 base pre-release version: $last_released_version"
|
||||
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" ]; then
|
||||
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
|
||||
@ -232,7 +383,7 @@ jobs:
|
||||
|
||||
determine-main-version:
|
||||
name: Determine Main Version
|
||||
needs: [validate-tag, validate-dependencies]
|
||||
needs: [validate-tag, validate-dependencies, determine-rc-number]
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
@ -261,16 +412,16 @@ jobs:
|
||||
echo "Main version from pyproject.toml: $version"
|
||||
|
||||
if [ ${{inputs.pre_release}} == "true" ]; then
|
||||
last_released_version=$(curl -s "https://pypi.org/pypi/langflow/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 main pre-release version: $last_released_version"
|
||||
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" ]; then
|
||||
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
|
||||
@ -281,7 +432,7 @@ jobs:
|
||||
|
||||
determine-lfx-version:
|
||||
name: Determine LFX Version
|
||||
needs: [validate-tag, validate-dependencies]
|
||||
needs: [validate-tag, validate-dependencies, determine-rc-number]
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
@ -310,9 +461,9 @@ jobs:
|
||||
echo "LFX version from pyproject.toml: $version"
|
||||
|
||||
if [ ${{inputs.pre_release}} == "true" ]; then
|
||||
last_released_version=$(curl -s "https://pypi.org/pypi/lfx/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 LFX pre-release version: $last_released_version"
|
||||
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)
|
||||
@ -330,7 +481,7 @@ jobs:
|
||||
|
||||
determine-sdk-version:
|
||||
name: Determine SDK Version
|
||||
needs: [validate-tag, validate-dependencies]
|
||||
needs: [validate-tag, validate-dependencies, determine-rc-number]
|
||||
if: ${{ inputs.release_sdk || inputs.release_lfx }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
@ -368,11 +519,17 @@ jobs:
|
||||
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
|
||||
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"
|
||||
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)
|
||||
@ -862,7 +1019,7 @@ jobs:
|
||||
|
||||
build-bundles:
|
||||
name: Build Langflow Bundles
|
||||
needs: [build-lfx, determine-lfx-version]
|
||||
needs: [build-lfx, determine-lfx-version, determine-rc-number]
|
||||
if: |
|
||||
always() &&
|
||||
!cancelled() &&
|
||||
@ -888,6 +1045,26 @@ jobs:
|
||||
prune-cache: false
|
||||
- name: Install the project
|
||||
run: uv sync
|
||||
- name: Set bundle versions for pre-release
|
||||
if: ${{ inputs.pre_release }}
|
||||
run: |
|
||||
RC_NUMBER="${{ needs.determine-rc-number.outputs.rc_number }}"
|
||||
echo "Setting bundle pre-release versions with shared rc number: rc$RC_NUMBER"
|
||||
for pyproject in src/bundles/*/pyproject.toml; do
|
||||
CURRENT_VERSION=$(python3 - "$pyproject" <<'PY'
|
||||
import pathlib
|
||||
import sys
|
||||
import tomllib
|
||||
|
||||
data = tomllib.loads(pathlib.Path(sys.argv[1]).read_text())
|
||||
print(data["project"]["version"])
|
||||
PY
|
||||
)
|
||||
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"
|
||||
grep "^version" "$pyproject" | sed "s|^| ${pyproject}: |"
|
||||
done
|
||||
- name: Relax bundle lfx floor for pre-release
|
||||
if: ${{ inputs.pre_release }}
|
||||
run: |
|
||||
@ -1129,72 +1306,78 @@ jobs:
|
||||
call_docker_build_base:
|
||||
name: Call Docker Build Workflow for Langflow Base
|
||||
if: ${{ inputs.build_docker_base }}
|
||||
needs: [ci]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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
|
||||
|
||||
|
||||
@ -143,7 +143,7 @@
|
||||
"filename": ".github/workflows/release.yml",
|
||||
"hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0",
|
||||
"is_verified": false,
|
||||
"line_number": 403,
|
||||
"line_number": 560,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@ -9287,5 +9287,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2026-06-22T20:34:55Z"
|
||||
"generated_at": "2026-06-22T22:13:44Z"
|
||||
}
|
||||
|
||||
@ -1,39 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
ARGUMENT_NUMBER = 3
|
||||
import packaging.version
|
||||
from packaging.version import Version
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
|
||||
|
||||
def create_tag(package_version: str, latest_released_version: str | None) -> str:
|
||||
# normalize optional leading 'v' and whitespace
|
||||
pkg = package_version.strip().lstrip("v")
|
||||
latest = None
|
||||
if latest_released_version is not None:
|
||||
lr = latest_released_version.strip()
|
||||
if lr != "":
|
||||
latest = lr.lstrip("v")
|
||||
def _read_released_versions(values: list[str]) -> list[str]:
|
||||
if values == ["-"]:
|
||||
return [line.strip() for line in sys.stdin if line.strip()]
|
||||
return [value.strip() for value in values if value.strip()]
|
||||
|
||||
new_pre_release_version = f"{pkg}.rc0"
|
||||
|
||||
if latest:
|
||||
# match either exact pkg or pkg.rcN (with or without dot before rc, per PEP 440 normalization)
|
||||
m = re.match(rf"^{re.escape(pkg)}\.?rc(\d+)$", latest)
|
||||
if m:
|
||||
rc_number = int(m.group(1)) + 1
|
||||
new_pre_release_version = f"{pkg}.rc{rc_number}"
|
||||
elif latest == pkg:
|
||||
new_pre_release_version = f"{pkg}.rc1"
|
||||
def next_rc_number(package_version: str, released_versions: Iterable[str]) -> int:
|
||||
base_version = Version(package_version.strip().lstrip("v")).base_version
|
||||
rc_numbers: list[int] = []
|
||||
exact_final_exists = False
|
||||
|
||||
return new_pre_release_version
|
||||
for released_version_str in released_versions:
|
||||
normalized_version_str = released_version_str.strip().lstrip("v")
|
||||
if not normalized_version_str:
|
||||
continue
|
||||
try:
|
||||
released_version = Version(normalized_version_str)
|
||||
except packaging.version.InvalidVersion:
|
||||
continue
|
||||
|
||||
if released_version.base_version != base_version:
|
||||
continue
|
||||
|
||||
if released_version.pre and released_version.pre[0] == "rc":
|
||||
rc_numbers.append(released_version.pre[1])
|
||||
elif not released_version.is_prerelease:
|
||||
exact_final_exists = True
|
||||
|
||||
if rc_numbers:
|
||||
return max(rc_numbers) + 1
|
||||
if exact_final_exists:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def create_tag(
|
||||
package_version: str,
|
||||
released_versions: Iterable[str] | str | None,
|
||||
rc_number: int | None = None,
|
||||
) -> str:
|
||||
if released_versions is None:
|
||||
versions: list[str] = []
|
||||
elif isinstance(released_versions, str):
|
||||
versions = [released_versions]
|
||||
else:
|
||||
versions = list(released_versions)
|
||||
|
||||
base_version = Version(package_version.strip().lstrip("v")).base_version
|
||||
next_rc = next_rc_number(base_version, versions)
|
||||
if rc_number is not None:
|
||||
next_rc = max(next_rc, rc_number)
|
||||
|
||||
return str(Version(f"{base_version}rc{next_rc}"))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Return the next rc pre-release for a package base version.",
|
||||
)
|
||||
parser.add_argument("package_version")
|
||||
parser.add_argument(
|
||||
"released_versions",
|
||||
nargs="*",
|
||||
help="Released versions to inspect, or '-' to read one version per line from stdin.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rc-number",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Minimum rc number to use. This lets a workflow apply one shared rc number across packages.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--print-rc-number",
|
||||
action="store_true",
|
||||
help="Print only the next rc number instead of the full version.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
released_versions = _read_released_versions(args.released_versions)
|
||||
if args.print_rc_number:
|
||||
print(next_rc_number(args.package_version, released_versions))
|
||||
else:
|
||||
print(create_tag(args.package_version, released_versions, args.rc_number))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != ARGUMENT_NUMBER:
|
||||
msg = "Specify package_version and latest_released_version (use empty string for none)."
|
||||
raise ValueError(msg)
|
||||
|
||||
package_version = sys.argv[1]
|
||||
latest_released_version = sys.argv[2]
|
||||
print(create_tag(package_version, latest_released_version))
|
||||
main()
|
||||
|
||||
73
scripts/ci/test_langflow_pre_release_tag.py
Normal file
73
scripts/ci/test_langflow_pre_release_tag.py
Normal file
@ -0,0 +1,73 @@
|
||||
"""Unit tests for langflow_pre_release_tag.py.
|
||||
|
||||
Run directly or with pytest:
|
||||
|
||||
cd scripts/ci && uv run python test_langflow_pre_release_tag.py
|
||||
cd scripts/ci && uv run python -m pytest test_langflow_pre_release_tag.py -q
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
import langflow_pre_release_tag as pr
|
||||
|
||||
|
||||
def test_ignores_unrelated_newer_pre_release_line():
|
||||
assert pr.create_tag("1.10.1", ["1.11.0.dev15"]) == "1.10.1rc0"
|
||||
|
||||
|
||||
def test_increments_same_base_rc_with_or_without_dot():
|
||||
releases = ["1.10.1rc0", "1.10.1.rc1", "1.11.0.dev15"]
|
||||
assert pr.create_tag("1.10.1", releases) == "1.10.1rc2"
|
||||
|
||||
|
||||
def test_final_same_base_starts_at_rc1():
|
||||
assert pr.create_tag("1.10.1", ["1.10.1"]) == "1.10.1rc1"
|
||||
|
||||
|
||||
def test_shared_rc_number_floor_applies_to_other_package_base():
|
||||
assert pr.create_tag("0.10.1", [], rc_number=1) == "0.10.1rc1"
|
||||
|
||||
|
||||
def test_shared_rc_number_floor_does_not_regress_existing_higher_rc():
|
||||
assert pr.create_tag("1.10.1", ["1.10.1rc5"], rc_number=1) == "1.10.1rc6"
|
||||
|
||||
|
||||
def test_cli_reads_released_versions_from_stdin():
|
||||
output = io.StringIO()
|
||||
with (
|
||||
mock.patch.object(sys, "argv", ["langflow_pre_release_tag.py", "1.10.1", "--print-rc-number", "-"]),
|
||||
mock.patch.object(sys, "stdin", io.StringIO("1.10.1rc0\n1.11.0.dev15\n")),
|
||||
contextlib.redirect_stdout(output),
|
||||
):
|
||||
pr.main()
|
||||
assert output.getvalue().strip() == "1"
|
||||
|
||||
|
||||
def test_unparseable_versions_are_skipped():
|
||||
assert pr.create_tag("1.10.1", ["not-a-version", "1.10.1rc0"]) == "1.10.1rc1"
|
||||
|
||||
|
||||
def _main():
|
||||
failures = 0
|
||||
for name, fn in sorted(globals().items()):
|
||||
if name.startswith("test_") and callable(fn):
|
||||
try:
|
||||
fn()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
failures += 1
|
||||
print(f"FAIL {name}: {exc}")
|
||||
else:
|
||||
print(f"ok {name}")
|
||||
if failures:
|
||||
msg = f"{failures} test(s) failed."
|
||||
raise SystemExit(msg)
|
||||
print("All langflow_pre_release_tag tests passed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
||||
Reference in New Issue
Block a user