Skip to content

Commit 13a937c

Browse files
authored
feat(ci): nightly → stable bundles via canonical pre-releases + decision record [gated] (#13528)
* docs: record nightly→stable bundle cutover plan (gated on lfx 1.10.0) Add src/bundles/NIGHTLY.md documenting why langflow-nightly currently renames the bundles (lfx and lfx-nightly ship the same lfx/ import, so a stable bundle would co-install both and collide) and the deferred cutover (Approach A: canonical pre-releases; B: lfx as a bundle extra), gated on stable lfx 1.10.0 being published to PyPI. Also expand two docstrings in scripts/ci/update_lfx_version.py to state the deeper install-conflict reason, not just the resolve failure. No behavior change. * feat(ci): nightly Approach A — canonical pre-releases, drop nightly bundles [DRAFT/gated] (#13529) feat(ci): nightly Approach A — canonical pre-releases, drop nightly bundles DRAFT reference implementation of the nightly→stable-bundle cutover documented in src/bundles/NIGHTLY.md. Publishes the nightly under CANONICAL package names as .devN pre-releases instead of separate *-nightly distributions, so the stable lfx-* bundles resolve against a single canonical lfx (no dual-lfx install collision) and no nightly bundle packages are produced. - tag scripts (pypi/lfx/sdk_nightly_tag.py): count .devN against the canonical PyPI histories instead of the *-nightly projects - update scripts: stop renaming to *-nightly; set .devN versions; re-pin inter-package deps to exact canonical dev versions; delete the bundle rename/repin (update_lfx_dep_in_bundles, rename_bundles_for_nightly) - release_nightly.yml: publish canonical pre-releases; remove bundle build, dist-nightly-bundles artifact, publish-nightly-bundles job + its gate; verify canonical names; main wheel glob dist/langflow-*.whl - nightly_build.yml: drop the bundle git-add in the tag commit - NIGHTLY.md: Approach A marked implemented + activation gate + A1/A2 follow-ups Held as DRAFT: do not activate until stable lfx 1.10.0 is published AND the nightly base is the next minor (release-1.11.0). A 1.10.0.devN core sorts below 1.10.0 and would fail the bundles' >=1.10.0 floor. Stacked on #13528. (.secrets.baseline: incidental line-number shifts for the two workflows + prune of pre-existing stale Pokédex Agent.json entries.) * docs(ci): drop internal 'Approach A' label from nightly cutover comments Comment- and docstring-only change across scripts/ci/* and the two nightly workflows; no logic change. The two workflow edits stay single-line so .secrets.baseline line numbers are unaffected. src/bundles/NIGHTLY.md keeps its A/B decision-record framing intentionally. * feat(ci): make nightly consumers work with canonical pre-releases Follow-ups from the nightly cutover that are part of its blast radius (the nightly now publishes canonical `.devN` pre-releases, not `*-nightly` distributions): - version.py: derive the "Nightly" label from the `.dev` version marker, since the canonical `langflow`/`langflow-base` distribution matches first in the lookup. Keeps the startup banner and telemetry `package` field identifying nightlies. Adds a canonical-dev test; updates the base-dev assertion. - ci.yml check-nightly-status: query the canonical `langflow` project and pick the latest `.devN` release date instead of `langflow-nightly`'s `.info.version`. - db-migration-validation.yml: install the nightly as `langflow[postgresql]==<dev>` (pre-release) instead of `langflow-nightly[...]`; verify via version("langflow"). - src/lfx/README.md: nightly install is `uv pip install --pre lfx`. - NIGHTLY.md: rewrite the follow-ups section (these are addressed; Docker image, A2 meta-package, and website docs remain deferred by design). The `langflowai/langflow-nightly` Docker image name is intentionally unchanged. * fix(ci): correct nightly verify uv tree parsing + stale base-dep regex Addresses review of #13528: - release_nightly.yml LFX verify: `uv tree | grep lfx | head -n1` matches the bundle `lfx-ibm` first → 'Name lfx-ibm does not match lfx'. Root the tree with `uv tree --package lfx` so the first line is the lfx package itself. - release_nightly.yml base verify: under canonical naming `langflow-base` prints as a top-level `langflow-base v<ver>` line, so the old $2/$3 field parse read name="v0.10.0" and version="". Use `uv tree --package langflow-base` and $1/$2. - update_lf_base_dependency.py update_base_dep: regex only accepted ~=/==, so its CLI entry point couldn't match the current root dep `langflow-base[complete]>=0.10.0`. Add >= (parity with update_uv_dependency.py). The active nightly path uses update_uv_dependency.py and was unaffected. * docs(nightly): point NIGHTLY.md status at the activation gate, not draft state Per review of #13528: this file ships inside #13528 (#13529 was folded in), so the 'stacked on prep #13528' + 'held as a draft' framing is stale and misleading. Reword the status block to state the real guard is the activation gate (stable lfx 1.10.0 published AND next-minor base), not merge/draft state. Also reword the follow-ups heading 'decide before un-drafting' -> 'decide before activating'.
1 parent 0195a13 commit 13a937c

18 files changed

Lines changed: 390 additions & 446 deletions

.github/workflows/ci.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,11 @@ jobs:
117117
TODAY=$(date -u +"%Y-%m-%d")
118118
echo "Today's date: $TODAY"
119119
120-
# Query PyPI API for the langflow package
121-
HTTP_STATUS=$(curl -s -o response.json -w "%{http_code}" https://pypi.org/pypi/langflow-nightly/json)
120+
# Query PyPI for the canonical langflow project. The nightly now publishes as a `.devN`
121+
# pre-release of `langflow` (not a separate `langflow-nightly` distribution), so inspect
122+
# the most recent dev release rather than `.info.version` (which is the latest stable).
123+
# See src/bundles/NIGHTLY.md.
124+
HTTP_STATUS=$(curl -s -o response.json -w "%{http_code}" https://pypi.org/pypi/langflow/json)
122125
123126
# Check HTTP status code first
124127
if [ "$HTTP_STATUS" -ne 200 ]; then
@@ -136,16 +139,16 @@ jobs:
136139
exit 0
137140
fi
138141
139-
# Extract the latest version
140-
LATEST_VERSION=$(jq -r '.info.version // empty' response.json)
142+
# Extract the most recent nightly (.devN pre-release), highest by version sort
143+
LATEST_VERSION=$(jq -r '.releases | keys[]' response.json | grep -E '\.dev[0-9]+$' | sort -V | tail -n1)
141144
142145
if [ -z "$LATEST_VERSION" ]; then
143-
echo "Could not extract latest version"
146+
echo "Could not find a nightly (.devN) release"
144147
echo "success=false" >> $GITHUB_OUTPUT
145148
exit 0
146149
fi
147150
148-
# Extract the release date of the latest version
151+
# Extract the release date of that nightly version
149152
RELEASE_DATE=$(jq -r --arg ver "$LATEST_VERSION" '.releases[$ver][0].upload_time_iso_8601 // empty' response.json | cut -d'T' -f1)
150153
151154
if [ -z "$RELEASE_DATE" ]; then

.github/workflows/db-migration-validation.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,12 @@ jobs:
161161
NIGHTLY_TAG="${{ inputs.nightly_tag || 'langflowai/langflow-nightly:latest' }}"
162162
echo "Upgrading to nightly version: $NIGHTLY_TAG"
163163
164-
# Remove stable distributions before installing nightly.
165-
# langflow and langflow-nightly share langflow-base which writes to the
166-
# same site-packages/langflow/ namespace. If stable stays installed,
167-
# python -m langflow still boots the stable version and no real
168-
# nightly migration is exercised (false-positive test).
169-
echo "Removing stable langflow to avoid namespace collision with nightly..."
164+
# Remove the stable install first. The nightly now publishes as a `.devN` pre-release of
165+
# the canonical `langflow` (same distribution as stable, different version), so a clean
166+
# uninstall avoids stale files and guarantees the dev version is what boots — otherwise
167+
# `python -m langflow` could keep running the stable version and no real nightly migration
168+
# is exercised (false-positive test).
169+
echo "Removing stable langflow to force a clean install of the nightly dev version..."
170170
uv pip uninstall -y langflow langflow-base || true
171171
172172
# Extract version from Docker tag (format: langflowai/langflow-nightly:v1.10.0.dev20260522)
@@ -179,20 +179,20 @@ jobs:
179179
echo "Version for PyPI: $VERSION"
180180
181181
if [[ "$VERSION" == "latest" ]]; then
182-
# Install latest nightly from PyPI
183-
uv pip install --upgrade 'langflow-nightly[postgresql]'
182+
# Install latest nightly (canonical pre-release) from PyPI
183+
uv pip install --upgrade --prerelease=allow 'langflow[postgresql]'
184184
else
185185
# Install specific version
186-
uv pip install --upgrade "langflow-nightly[postgresql]==$VERSION"
186+
uv pip install --upgrade "langflow[postgresql]==$VERSION"
187187
fi
188188
else
189189
# Direct version string (strip 'v' prefix if present)
190190
VERSION="${NIGHTLY_TAG#v}"
191-
uv pip install --upgrade "langflow-nightly[postgresql]==$VERSION"
191+
uv pip install --upgrade "langflow[postgresql]==$VERSION"
192192
fi
193193
194194
# Verify upgrade
195-
python -c 'from importlib.metadata import version; print("Upgraded to Langflow Nightly version:", version("langflow-nightly"))'
195+
python -c 'from importlib.metadata import version; print("Upgraded to Langflow Nightly version:", version("langflow"))'
196196
197197
- name: Run migration and verify startup
198198
working-directory: migration-test

.github/workflows/nightly_build.yml

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,9 @@ jobs:
172172
cd src/lfx && uv lock && cd ../..
173173
174174
git add pyproject.toml src/backend/base/pyproject.toml src/lfx/pyproject.toml src/sdk/pyproject.toml uv.lock
175-
# update_lfx_version.py re-pins each bundle's `lfx` dep to
176-
# lfx-nightly==<dev>, so any modified bundle pyprojects need to
177-
# ride the same commit/tag as the rest of the version bumps.
178-
if compgen -G "src/bundles/*/pyproject.toml" > /dev/null; then
179-
git add src/bundles/*/pyproject.toml
180-
fi
181-
git commit -m "Update version and project name"
175+
# The nightly keeps canonical package names and the stable `lfx-*` bundles
176+
# are not modified (see src/bundles/NIGHTLY.md), so no bundle pyprojects ride this commit.
177+
git commit -m "Update version for nightly"
182178
183179
echo "Tagging main with $RELEASE_TAG"
184180
if ! git tag -a $RELEASE_TAG -m "Langflow nightly $RELEASE_TAG"; then

.github/workflows/release_nightly.yml

Lines changed: 25 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ jobs:
8181
run: |
8282
name=$(grep '^name = "' src/sdk/pyproject.toml | head -n 1 | sed 's/.*"\(.*\)".*/\1/')
8383
version=$(grep '^version = "' src/sdk/pyproject.toml | head -n 1 | sed 's/.*"\(.*\)".*/\1/')
84-
if [ "$name" != "langflow-sdk-nightly" ]; then
85-
echo "Name $name does not match langflow-sdk-nightly. Exiting the workflow."
84+
if [ "$name" != "langflow-sdk" ]; then
85+
echo "Name $name does not match langflow-sdk. Exiting the workflow."
8686
exit 1
8787
fi
8888
if [ "v$version" != "${{ inputs.nightly_tag_sdk }}" ]; then
@@ -94,10 +94,11 @@ jobs:
9494
id: verify
9595
run: |
9696
cd src/lfx
97-
name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}')
98-
version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}')
99-
if [ "$name" != "lfx-nightly" ]; then
100-
echo "Name $name does not match lfx-nightly. Exiting the workflow."
97+
# Root the tree at lfx; a bare `grep lfx` matches bundles like `lfx-ibm` first.
98+
name=$(uv tree --package lfx | head -n 1 | awk '{print $1}')
99+
version=$(uv tree --package lfx | head -n 1 | awk '{print $2}')
100+
if [ "$name" != "lfx" ]; then
101+
echo "Name $name does not match lfx. Exiting the workflow."
101102
exit 1
102103
fi
103104
if [ "$version" != "${{ inputs.nightly_tag_lfx }}" ]; then
@@ -188,12 +189,14 @@ jobs:
188189
- name: Verify Nightly Name and Version
189190
id: verify
190191
run: |
191-
name=$(uv tree | grep 'langflow-base' | awk '{print $2}' | head -n 1)
192-
version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | head -n 1)
193-
# Strip extras from package name (e.g., "langflow-base-nightly[complete]" -> "langflow-base-nightly")
192+
# Root the tree at langflow-base; it prints as a top-level `langflow-base v<ver>` line,
193+
# so a bare `grep langflow-base` would read the version into $name and leave $version empty.
194+
name=$(uv tree --package langflow-base | head -n 1 | awk '{print $1}')
195+
version=$(uv tree --package langflow-base | head -n 1 | awk '{print $2}')
196+
# Strip extras from package name (e.g., "langflow-base[complete]" -> "langflow-base")
194197
name_without_extras=$(echo $name | sed 's/\[.*\]//')
195-
if [ "$name_without_extras" != "langflow-base-nightly" ]; then
196-
echo "Name $name_without_extras does not match langflow-base-nightly. Exiting the workflow."
198+
if [ "$name_without_extras" != "langflow-base" ]; then
199+
echo "Name $name_without_extras does not match langflow-base. Exiting the workflow."
197200
echo "skipped=true" >> $GITHUB_OUTPUT
198201
exit 1
199202
fi
@@ -297,10 +300,10 @@ jobs:
297300
- name: Verify Nightly Name and Version
298301
id: verify
299302
run: |
300-
name=$(uv tree | grep -E '^langflow(-nightly)?[[:space:]]' | awk '{print $1}')
301-
version=$(uv tree | grep -E '^langflow(-nightly)?[[:space:]]' | awk '{print $2}')
302-
if [ "$name" != "langflow-nightly" ]; then
303-
echo "Name $name does not match langflow-nightly. Exiting the workflow."
303+
name=$(uv tree | grep -E '^langflow[[:space:]]' | awk '{print $1}')
304+
version=$(uv tree | grep -E '^langflow[[:space:]]' | awk '{print $2}')
305+
if [ "$name" != "langflow" ]; then
306+
echo "Name $name does not match langflow. Exiting the workflow."
304307
exit 1
305308
fi
306309
if [ "$version" != "${{ inputs.nightly_tag_release }}" ]; then
@@ -337,33 +340,19 @@ jobs:
337340
- name: Prepare Langflow Main artifact
338341
run: |
339342
shopt -s nullglob
340-
MAIN_WHEELS=(dist/langflow_nightly-*.whl)
343+
MAIN_WHEELS=(dist/langflow-*.whl)
341344
if [ ${#MAIN_WHEELS[@]} -ne 1 ]; then
342-
echo "Expected exactly one langflow-nightly wheel in dist/, found ${#MAIN_WHEELS[@]}"
345+
echo "Expected exactly one langflow wheel in dist/, found ${#MAIN_WHEELS[@]}"
343346
ls -la dist/
344347
exit 1
345348
fi
346349
rm -rf main-dist
347350
mkdir -p main-dist
348351
cp "${MAIN_WHEELS[0]}" main-dist/
349352
350-
- name: Build bundle wheels for cross-platform test
351-
run: |
352-
shopt -s nullglob
353-
bundles=(src/bundles/*/pyproject.toml)
354-
if [ ${#bundles[@]} -eq 0 ]; then
355-
echo "No bundles found under src/bundles/*/"
356-
exit 1
357-
fi
358-
rm -rf bundles-dist
359-
mkdir -p bundles-dist
360-
BUNDLES_DIST="$PWD/bundles-dist"
361-
for bundle_pyproject in "${bundles[@]}"; do
362-
bundle_dir=$(dirname "$bundle_pyproject")
363-
echo "Building wheel for $bundle_dir"
364-
(cd "$bundle_dir" && uv build --wheel --out-dir "$BUNDLES_DIST")
365-
done
366-
ls -la bundles-dist/
353+
# Bundles are NOT rebuilt/republished for nightly. The stable `lfx-*` bundles on
354+
# PyPI work as-is against the canonical `lfx` pre-release (see src/bundles/NIGHTLY.md). The
355+
# cross-platform test self-builds the bundles from src/bundles for install coverage.
367356

368357
# PyPI publishing moved to after cross-platform testing
369358

@@ -373,12 +362,6 @@ jobs:
373362
name: dist-nightly-main
374363
path: main-dist
375364

376-
- name: Upload bundles artifact
377-
uses: actions/upload-artifact@v6
378-
with:
379-
name: dist-nightly-bundles
380-
path: bundles-dist
381-
382365
test-cross-platform:
383366
name: Test Cross-Platform Installation
384367
needs: [build-nightly-lfx, build-nightly-base, build-nightly-main]
@@ -388,7 +371,6 @@ jobs:
388371
main-artifact-name: "dist-nightly-main"
389372
lfx-artifact-name: "dist-nightly-lfx"
390373
sdk-artifact-name: "dist-nightly-sdk"
391-
bundles-artifact-name: "dist-nightly-bundles"
392374

393375
publish-nightly-lfx:
394376
name: Publish LFX Nightly to PyPI
@@ -475,60 +457,10 @@ jobs:
475457
run: |
476458
make publish base=true
477459
478-
publish-nightly-bundles:
479-
name: Publish Langflow Bundles Nightly to PyPI
480-
# langflow-nightly pins each `lfx-<name>-nightly==<dev>` exactly, so the
481-
# bundles must be on PyPI before main is published (publish-nightly-main
482-
# gates on this job). They depend on `lfx-nightly`, so gate on the lfx
483-
# publish too (or skip it when lfx isn't being rebuilt).
484-
needs: [build-nightly-main, test-cross-platform, publish-nightly-lfx]
485-
if: ${{ always() && needs.build-nightly-main.result == 'success' && needs.test-cross-platform.result == 'success' && (needs.publish-nightly-lfx.result == 'success' || inputs.build_lfx == false) }}
486-
runs-on: ubuntu-latest
487-
steps:
488-
- name: Download bundles artifact
489-
uses: actions/download-artifact@v7
490-
with:
491-
name: dist-nightly-bundles
492-
path: bundles-dist
493-
- name: Setup Environment
494-
uses: astral-sh/setup-uv@v6
495-
with:
496-
enable-cache: false
497-
python-version: "3.13"
498-
- name: Publish bundles to PyPI
499-
env:
500-
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
501-
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
502-
run: |
503-
shopt -s nullglob
504-
wheels=(bundles-dist/*.whl)
505-
if [ ${#wheels[@]} -eq 0 ]; then
506-
echo "No bundle wheels to publish."
507-
exit 1
508-
fi
509-
failed=0
510-
for wheel in "${wheels[@]}"; do
511-
echo "Publishing $wheel"
512-
# Capture stderr so a re-run without a version bump is a no-op for
513-
# an already-published bundle instead of failing the whole job.
514-
if ! err=$(uv publish "$wheel" 2>&1); then
515-
echo "$err"
516-
if echo "$err" | grep -qiE 'already exists|file already exists|HTTP 400|duplicate'; then
517-
echo "Skipping $wheel: already published."
518-
else
519-
echo "Publish failed for $wheel"
520-
failed=1
521-
fi
522-
fi
523-
done
524-
if [ "$failed" = "1" ]; then
525-
exit 1
526-
fi
527-
528460
publish-nightly-main:
529461
name: Publish Langflow Main Nightly to PyPI
530-
needs: [build-nightly-main, test-cross-platform, publish-nightly-base, publish-nightly-bundles]
531-
if: ${{ always() && needs.build-nightly-main.result == 'success' && needs.test-cross-platform.result == 'success' && needs.publish-nightly-base.result == 'success' && needs.publish-nightly-bundles.result == 'success' }}
462+
needs: [build-nightly-main, test-cross-platform, publish-nightly-base]
463+
if: ${{ always() && needs.build-nightly-main.result == 'success' && needs.test-cross-platform.result == 'success' && needs.publish-nightly-base.result == 'success' }}
532464
runs-on: ubuntu-latest
533465
steps:
534466
- name: Checkout code

.secrets.baseline

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
"filename": ".github/workflows/nightly_build.yml",
134134
"hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0",
135135
"is_verified": false,
136-
"line_number": 304,
136+
"line_number": 308,
137137
"is_secret": false
138138
}
139139
],
@@ -153,7 +153,7 @@
153153
"filename": ".github/workflows/release_nightly.yml",
154154
"hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0",
155155
"is_verified": false,
156-
"line_number": 565,
156+
"line_number": 497,
157157
"is_secret": false
158158
}
159159
],
@@ -2352,29 +2352,6 @@
23522352
"line_number": 774
23532353
}
23542354
],
2355-
"src/backend/base/langflow/initial_setup/starter_projects/Pok\u00e9dex Agent.json": [
2356-
{
2357-
"type": "Hex High Entropy String",
2358-
"filename": "src/backend/base/langflow/initial_setup/starter_projects/Pok\u00e9dex Agent.json",
2359-
"hashed_secret": "54ed260e3bc31bc77ee06754dff850981d39a66c",
2360-
"is_verified": false,
2361-
"line_number": 115
2362-
},
2363-
{
2364-
"type": "Hex High Entropy String",
2365-
"filename": "src/backend/base/langflow/initial_setup/starter_projects/Pok\u00e9dex Agent.json",
2366-
"hashed_secret": "d6e6d7b4b115cd3b9d172623199f8c403055fecc",
2367-
"is_verified": false,
2368-
"line_number": 394
2369-
},
2370-
{
2371-
"type": "Hex High Entropy String",
2372-
"filename": "src/backend/base/langflow/initial_setup/starter_projects/Pok\u00e9dex Agent.json",
2373-
"hashed_secret": "ecdbe30d19e36761df3620b37270f23c8e3eaa4c",
2374-
"is_verified": false,
2375-
"line_number": 774
2376-
}
2377-
],
23782355
"src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json": [
23792356
{
23802357
"type": "Hex High Entropy String",
@@ -9310,5 +9287,5 @@
93109287
}
93119288
]
93129289
},
9313-
"generated_at": "2026-06-05T18:07:39Z"
9290+
"generated_at": "2026-06-08T16:38:06Z"
93149291
}

0 commit comments

Comments
 (0)