Files
langflow/scripts/release-lfx.sh
Jordan Frazier 1ab6251164 feat: sync langflow and lfx versions (#13176)
* feat(lfx): synchronize LFX onto Langflow major.minor version line (Phase 1)

- Bump src/lfx/pyproject.toml from 0.5.0 to 1.10.0
- Tighten langflow-base lfx pin: ~=0.5.0 → ~=1.10.0
- Fix release.yml pre-release boundary from next-major to next-minor (<X.(Y+1).dev0)
- Add minor-parity soft check to scripts/release-lfx.sh
- Extend make patch to sync lfx version alongside langflow/base/frontend
- Document LFX compatibility contract in RELEASE.md

Contract: LFX X.Y.N is compatible with any Flow from Langflow X.Y.M.

* feat(lfx/upgrade): add compatibility checker (Phase 2)

* fix(lfx/upgrade): fix registry_code guard and types order-sensitivity in checker

* feat(lfx/upgrade): implement safe-upgrade applier (Phase 2)

* feat(lfx): add lfx upgrade command (Phase 2)

* feat(lfx/run): add --upgrade-flow option (Phase 2)

* feat(lfx/serve): add --upgrade-flow option (Phase 2)

* update lfx pyproject version

* [autofix.ci] apply automated fixes

* test(lfx/upgrade): add v1.9.0 starter flow fixtures for upgrade integration tests

* test(lfx/upgrade): add v1.9.0 starter flow fixtures and real-flow integration tests

* [autofix.ci] apply automated fixes

* fix(lfx/upgrade): use alias-aware registry lookup so renamed components are not falsely blocked

* fix(lfx/upgrade): handle outer flow envelope and file-path inputs in upgrade checks

* fix(lfx/upgrade): fail-fast on upgrade_flow errors; add regression tests for Bugs 1-3

* chore(tests): remove bug-number labels from test comments

* [autofix.ci] apply automated fixes

* fix(lfx/upgrade): address PR review comments

- Reject .py files early in serve --upgrade-flow with a clear error message
- Preserve outer flow envelope metadata (name, description, etc.) when lfx upgrade --write rewrites a file
- Move Mapping import under TYPE_CHECKING to satisfy Ruff TC003
- Assert fixture flows and registry are non-empty so parametrized tests cannot pass vacuously
- Remove unused capsys arg and replace print with sys.stderr.write (Ruff T201/ARG001)
- Add upgrade_flow param to run command docstring

* fix(lfx/upgrade): preserve envelope on run, apply nested upgrades (#13200)

* fix(lfx/upgrade): preserve envelope on run, apply nested upgrades

Three follow-ups on top of the upgrade tooling:

- run --upgrade-flow: re-attach the inner graph to the outer envelope before handing the flow to aload_flow_from_json. Previously the upgrade path unwrapped {"data": ...} and passed the inner dict to the loader, which raised KeyError: 'data'. Adds happy-path tests for envelope and flat file inputs.
- applier: recurse into one level of nested flow nodes (node.data.node.flow.data.nodes), matching the checker. Without this, outdated_safe nodes inside grouped components were reported but never written. Adds a regression test.
- upgrade --write test: assert the envelope is preserved (name, description, endpoint_name, etc.) instead of the previous unwrapped-output expectation, so the test matches the actual fix in this PR.
- Drive-by ruff cleanup: D417 docstring, SIM103, SIM108, E501, RUF059.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix(lfx/upgrade): fix loader KeyError, outer-envelope checker gap, and nested-node applier

Three bugs from ogabrielluiz's PR review:

1. run/base.py loader KeyError: file-path branch unwrapped the outer envelope
   for the checker but passed the inner dict to aload_flow_from_json, which does
   flow_graph["data"] unconditionally. Re-wrap as {"data": flow_dict} before the
   loader call. Applied consistently to all three input paths (--flow-json,
   --stdin, file-path).

2. run/base.py zero-node checker for --flow-json/--stdin: those paths stored the
   raw parsed JSON without unwrapping, so a caller passing an exported flow
   {"name":..., "data":{...}} caused the checker to see zero nodes and silently
   pass. Now unwrap with raw.get("data", raw) matching the file-path branch.

3. applier.py nested flows never upgraded: the early `continue` on top-level
   nodes not in safe_ids prevented the nested-node loop from running. Restructured
   so the nested check fires unconditionally for every top-level node, mirroring
   checker.py:160-165.

* fix(lfx/run): unify outer-envelope unwrap across all --upgrade-flow input paths

File-path reads now call raw.get("data", raw) matching --flow-json and
--stdin, so the upgrade checker always sees the inner graph. This also
removes the has_envelope/re-wrap machinery that was double-wrapping
outer-envelope files when passing to aload_flow_from_json.

Fix two test assertions that described the old double-wrapped shape.

* fix(lfx/upgrade): inject registry into upgrade_command; fix Makefile lfx pin regex

- upgrade_command now accepts an optional registry parameter so tests
  can pass the dict directly instead of mocking load_registry_from_index
- Makefile sed regex broadened from \"lfx~=.*\" to match both ~= and >=
  forms so make patch works after release.yml rewrites the pin
- Echo label changed to LFX (synced) to clarify the variable is the
  shared Langflow version, not a separate LFX-specific value

* fix(make/patch): fix langflow-base sed regex and validation grep

The dependency in pyproject.toml is "langflow-base[complete]>=X.Y.Z",
not the "langflow-base==X" form the original regex expected, so make
patch silently left the pin unchanged and the validation step always
failed. The sed pattern now matches any extras/operator combination and
rewrites to the canonical [complete]>= form; the grep uses -F so the
[complete] brackets are treated literally.

* test(lfx): add patch regex tests and symmetric safe-mode envelope tests

test_patch_regexes.py — 15 tests covering the Python regexes embedded in
the Makefile patch target. Exercises all three substitutions (langflow-base
pin, lfx pin, version field) against every realistic pin format including
the >=X.Y.Z,<dev0 form that release.yml writes. Would have caught the
langflow-base==.* vs [complete]>= mismatch before manual testing.

test_base.py — two new TestUpgradeFlowOption tests:
  test_upgrade_flow_safe_envelope_inline_json_loads_successfully
  test_upgrade_flow_safe_envelope_stdin_loads_successfully
Symmetric to the existing file-path envelope test; verifies that --flow-json
and --stdin with an outer-envelope flow also pass {"data": inner} to the
loader after safe upgrades, not a double-wrapped {"data": outer_envelope}.

* fix(lfx/upgrade): address review: compat checker, shared gate, fail-fast registry

Checker correctness:
- _outputs_are_compatible: drop cosmetic display_name from the breaking check;
  treat widened output types as safe (flow types must be a subset of registry
  types), only narrowing breaks downstream edges.
- _input_types_contained: stop flagging widened input_types as breaking; keep
  narrowing as the only breaking case; fix misleading comments.
- check_flow_compatibility now recurses fully into nested grouped components
  (symmetric with the applier) and accepts a pre-built registry lookup.

CLI/run/serve:
- New lfx.upgrade.cli_gate (UpgradeFlowMode enum, UpgradeFlowError,
  apply_upgrade_gate) shared by run_flow and serve_command so the two
  --upgrade-flow paths can't diverge.
- run_flow: extract _materialize_flow_dict and route gating through the shared
  helper.
- run/serve --upgrade-flow options typed as UpgradeFlowMode (check|safe choices).
- lfx upgrade: load_registry_from_index fails fast when the bundled registry is
  empty/missing instead of silently marking every node blocked; ASCII-only report
  output; new --strict flag; build the registry lookup once and reuse it.

Docs/tests:
- RELEASE.md: migration note for the lfx 0.5.0 -> 1.10.0 version jump.
- Regression tests for the checker fixes, the shared gate, fail-fast registry,
  --strict, and serve --upgrade-flow parity.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix(lfx): consolidate flow-envelope handling and fix version-ceiling regex

Extract the outer-envelope unwrap/rewrap logic (previously hand-rolled as
raw.get("data", raw) with subtly different rules across serve, run, and
upgrade) into a single lfx.utils.flow_envelope module with split/merge
helpers. This fixes a serve bug where an enveloped flow had its inner graph
written bare to the temp file, making the loader's flow_graph["data"] raise
KeyError.

Also fix release.yml's major.minor extraction: the greedy sed grabbed the
upper bound from a range pin (>=1.10.0,<1.11.dev0), drifting the version
ceiling up one minor each release cycle. Anchor to the first version instead.

* fix(lfx): upgrade-flow gate reads bundled component index, not empty cache

The --upgrade-flow=check|safe gate on lfx run and lfx serve rejected every
flow as 'blocked'. Both call sites passed component_cache.all_types_dict to
apply_upgrade_gate, but that cache is populated lazily after services start,
so at gate time it is empty -- an empty registry classifies every node as
blocked. The standalone lfx upgrade command was unaffected because it reads
the bundled _assets/component_index.json instead.

Make the gate own registry loading: apply_upgrade_gate now defaults
all_types_dict to None and loads the bundled index (the same source lfx
upgrade uses) via a new _load_bundled_registry helper, raising
UpgradeFlowError on a missing/empty index so a broken install fails loudly
instead of silently blocking every component. Both call sites pass mode=
and let the gate load the registry.

Existing gate tests mocked component_cache with a populated registry, which
is exactly what hid the bug; repoint them at the new _load_bundled_registry
seam and add regression tests that do not mock the registry, including an
end-to-end run_flow check against a real clean v1.9.0 starter flow.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@logspace.ai>
2026-06-05 01:30:59 +00:00

231 lines
6.7 KiB
Bash
Executable File

#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
DRY_RUN=false
# Function to print colored output
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_dry_run() {
echo -e "${BLUE}[DRY RUN]${NC} $1"
}
# Function to show usage
show_usage() {
echo "Usage: $0 [OPTIONS] [VERSION]"
echo ""
echo "Options:"
echo " --dry-run Run the script without making actual changes"
echo " --help Show this help message"
echo ""
echo "Arguments:"
echo " VERSION The new version to release (e.g., 0.1.0)"
echo ""
echo "Examples:"
echo " $0 0.1.0 # Release version 0.1.0"
echo " $0 --dry-run 0.1.0 # Dry run for version 0.1.0"
echo " $0 --dry-run # Dry run with interactive version prompt"
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN=true
shift
;;
--help|-h)
show_usage
exit 0
;;
*)
if [ -z "$NEW_VERSION" ]; then
NEW_VERSION=$1
fi
shift
;;
esac
done
# Check if we're in the right directory
if [ ! -f "src/lfx/pyproject.toml" ]; then
print_error "This script must be run from the root of the langflow repository"
exit 1
fi
# Get current version
CURRENT_VERSION=$(grep '^version = ' src/lfx/pyproject.toml | cut -d'"' -f2)
print_info "Current LFX version: $CURRENT_VERSION"
if [ "$DRY_RUN" = true ]; then
print_dry_run "Running in dry run mode - no changes will be made"
fi
# Check for uncommitted changes (skip in dry run)
if [ "$DRY_RUN" = false ]; then
if ! git diff-index --quiet HEAD --; then
print_warning "You have uncommitted changes. Please commit or stash them before releasing."
exit 1
fi
else
if ! git diff-index --quiet HEAD --; then
print_warning "Uncommitted changes detected (ignored in dry run mode)"
fi
fi
# Get new version from argument or prompt
if [ -z "$NEW_VERSION" ]; then
echo -n "Enter new version (current: $CURRENT_VERSION): "
read NEW_VERSION
fi
# Validate version format
if ! [[ $NEW_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then
print_error "Invalid version format. Use semantic versioning (e.g., 0.1.0 or 0.1.0-alpha)"
exit 1
fi
print_info "Preparing to release LFX version $NEW_VERSION"
# Soft check: warn if LFX minor doesn't match Langflow minor
LANGFLOW_VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2)
LFX_MINOR=$(echo "$NEW_VERSION" | cut -d. -f1-2)
LANGFLOW_MINOR=$(echo "$LANGFLOW_VERSION" | cut -d. -f1-2)
if [ "$LFX_MINOR" != "$LANGFLOW_MINOR" ]; then
print_warning "LFX minor version ($LFX_MINOR) does not match Langflow minor version ($LANGFLOW_MINOR)."
print_warning "Per the compatibility policy, LFX X.Y.N must align with Langflow X.Y.M."
print_warning "Proceed only if this is intentional (e.g., a patch-only LFX release)."
fi
# Update version in pyproject.toml
if [ "$DRY_RUN" = true ]; then
print_dry_run "Would update version in pyproject.toml to $NEW_VERSION"
else
print_info "Updating version in pyproject.toml..."
sed -i.bak "s/^version = \".*\"/version = \"$NEW_VERSION\"/" src/lfx/pyproject.toml
rm src/lfx/pyproject.toml.bak
fi
# Update version in Dockerfiles if they have ARG LFX_VERSION
if grep -q "ARG LFX_VERSION" src/lfx/docker/Dockerfile 2>/dev/null; then
if [ "$DRY_RUN" = true ]; then
print_dry_run "Would update version in Dockerfiles to $NEW_VERSION"
else
print_info "Updating version in Dockerfiles..."
sed -i.bak "s/ARG LFX_VERSION=.*/ARG LFX_VERSION=$NEW_VERSION/" src/lfx/docker/Dockerfile*
rm src/lfx/docker/Dockerfile*.bak
fi
fi
# Run tests
print_info "Running tests..."
cd src/lfx
if ! make test; then
print_error "Tests failed!"
if [ "$DRY_RUN" = false ]; then
print_info "Rolling back changes..."
git checkout -- .
fi
exit 1
fi
cd ../..
# Build package to verify
print_info "Building package..."
cd src/lfx
if ! uv build; then
print_error "Build failed!"
if [ "$DRY_RUN" = false ]; then
print_info "Rolling back changes..."
cd ../..
git checkout -- .
fi
exit 1
fi
cd ../..
if [ "$DRY_RUN" = true ]; then
print_dry_run "Skipping cleanup of build artifacts in dry run mode"
else
# Clean up build artifacts
rm -rf src/lfx/dist/
fi
# Create git commit
if [ "$DRY_RUN" = true ]; then
print_dry_run "Would create git commit: 'chore(lfx): bump version to $NEW_VERSION'"
else
print_info "Creating git commit..."
git add src/lfx/pyproject.toml src/lfx/docker/Dockerfile* 2>/dev/null || true
git commit -m "chore(lfx): bump version to $NEW_VERSION
- Update version in pyproject.toml
- Prepare for PyPI and Docker release"
fi
# Create git tag
TAG_NAME="lfx-v$NEW_VERSION"
if [ "$DRY_RUN" = true ]; then
print_dry_run "Would create git tag: $TAG_NAME"
else
print_info "Creating git tag: $TAG_NAME"
git tag -a "$TAG_NAME" -m "LFX Release $NEW_VERSION"
fi
if [ "$DRY_RUN" = true ]; then
print_info "✅ Dry run complete!"
echo ""
echo "Dry run performed:"
echo "✅ Validated version format"
echo "✅ Ran tests successfully"
echo "✅ Built package successfully"
echo ""
echo "What would happen in a real run:"
echo "1. Update version in pyproject.toml to $NEW_VERSION"
echo "2. Update version in Dockerfiles (if applicable)"
echo "3. Create git commit with message: 'chore(lfx): bump version to $NEW_VERSION'"
echo "4. Create git tag: $TAG_NAME"
echo ""
echo "To perform the actual release, run without --dry-run:"
echo " $0 $NEW_VERSION"
else
print_info "✅ Release preparation complete!"
echo ""
echo "Next steps:"
echo "1. Push the commit and tag:"
echo " git push origin HEAD"
echo " git push origin $TAG_NAME"
echo ""
echo "2. Go to GitHub Actions and run the 'LFX Release' workflow:"
echo " https://github.com/langflow-ai/langflow/actions/workflows/release-lfx.yml"
echo ""
echo "3. Enter version: $NEW_VERSION"
echo ""
echo "4. Select options:"
echo " - Publish to PyPI: Yes"
echo " - Build Docker images: Yes"
echo " - Create GitHub release: Yes"
echo ""
echo "The workflow will:"
echo "- Run tests on all Python versions"
echo "- Build and publish to PyPI"
echo "- Build and push Docker images (standard and alpine)"
echo "- Create a GitHub release with artifacts"
fi