Nightly Build #1185
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Nightly Build | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| runs_on: | |
| description: "Runner to use for tests (use self-hosted for safe/release code)" | |
| required: false | |
| type: choice | |
| options: | |
| - ubuntu-latest | |
| - self-hosted | |
| - "[self-hosted, linux, ARM64, langflow-ai-arm64-40gb]" | |
| default: ubuntu-latest | |
| skip_frontend_tests: | |
| description: "Skip frontend tests. Only do this for testing purposes." | |
| required: false | |
| type: boolean | |
| default: false | |
| skip_backend_tests: | |
| description: "Skip backend tests. Only do this for testing purposes." | |
| required: false | |
| type: boolean | |
| default: false | |
| skip_slack: | |
| description: "Skip slack message. Only do this for testing purposes." | |
| required: false | |
| type: boolean | |
| default: false | |
| push_to_registry: | |
| description: "Whether to push images to registries. Set to false for testing builds without publishing." | |
| required: false | |
| type: boolean | |
| default: true | |
| auto_merge_hash_history: | |
| description: "Create PR to merge hash history back to main. Set to false when not running from main." | |
| required: false | |
| type: boolean | |
| default: true | |
| schedule: | |
| # Run job at 00:00 UTC (4:00 PM PST / 5:00 PM PDT) | |
| - cron: "0 0 * * *" | |
| env: | |
| POETRY_VERSION: "1.8.3" | |
| PYTHON_VERSION: "3.13" | |
| jobs: | |
| validate-inputs: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate inputs | |
| if: inputs.push_to_registry && (inputs.skip_frontend_tests || inputs.skip_backend_tests) | |
| run: | | |
| echo "Cannot skip tests while push_to_registry is true." | |
| exit 1 | |
| create-nightly-tag: | |
| if: github.repository == 'langflow-ai/langflow' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| shell: bash -ex -o pipefail {0} | |
| permissions: | |
| # Required to create tag | |
| contents: write | |
| outputs: | |
| main_tag: ${{ steps.generate_main_tag.outputs.main_tag }} | |
| base_tag: ${{ steps.set_base_tag.outputs.base_tag }} | |
| lfx_tag: ${{ steps.generate_lfx_tag.outputs.lfx_tag }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: true | |
| - name: "Setup Environment" | |
| uses: astral-sh/setup-uv@v6 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: "uv.lock" | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| prune-cache: false | |
| - name: Install the project | |
| run: uv sync | |
| - name: Generate main nightly tag | |
| id: generate_main_tag | |
| run: | | |
| # NOTE: This outputs the tag with the `v` prefix. | |
| MAIN_TAG="$(uv run ./scripts/ci/pypi_nightly_tag.py main)" | |
| echo "main_tag=$MAIN_TAG" >> $GITHUB_OUTPUT | |
| echo "main_tag=$MAIN_TAG" | |
| - name: Delete existing tag if it exists | |
| id: check_main_tag | |
| run: | | |
| git fetch --tags | |
| git tag -d ${{ steps.generate_main_tag.outputs.main_tag }} || true | |
| git push --delete origin ${{ steps.generate_main_tag.outputs.main_tag }} || true | |
| echo "main_tag_exists=false" >> $GITHUB_OUTPUT | |
| - name: Generate base nightly tag | |
| id: generate_base_tag | |
| run: | | |
| # NOTE: This outputs the tag with the `v` prefix. | |
| BASE_TAG="$(uv run ./scripts/ci/pypi_nightly_tag.py base)" | |
| echo "base_tag=$BASE_TAG" >> $GITHUB_OUTPUT | |
| echo "base_tag=$BASE_TAG" | |
| - name: Generate LFX nightly tag | |
| id: generate_lfx_tag | |
| run: | | |
| # NOTE: This outputs the tag with the `v` prefix. | |
| LFX_TAG="$(uv run ./scripts/ci/lfx_nightly_tag.py)" | |
| echo "lfx_tag=$LFX_TAG" >> $GITHUB_OUTPUT | |
| echo "lfx_tag=$LFX_TAG" | |
| - name: Commit tag | |
| id: commit_tag | |
| run: | | |
| # If the main tag does not exist in GH, we create the base tag from the existing codebase. | |
| git config --global user.email "[email protected]" | |
| git config --global user.name "Langflow Bot" | |
| MAIN_TAG="${{ steps.generate_main_tag.outputs.main_tag }}" | |
| BASE_TAG="${{ steps.generate_base_tag.outputs.base_tag }}" | |
| LFX_TAG="${{ steps.generate_lfx_tag.outputs.lfx_tag }}" | |
| echo "Updating LFX project version to $LFX_TAG" | |
| uv run ./scripts/ci/update_lfx_version.py $LFX_TAG | |
| echo "Updating base project version to $BASE_TAG and updating main project version to $MAIN_TAG" | |
| uv run --no-sync ./scripts/ci/update_pyproject_combined.py main $MAIN_TAG $BASE_TAG $LFX_TAG | |
| uv lock | |
| cd src/backend/base && uv lock && cd ../../.. | |
| cd src/lfx && uv lock && cd ../.. | |
| git add pyproject.toml src/backend/base/pyproject.toml src/lfx/pyproject.toml uv.lock src/backend/base/uv.lock | |
| git commit -m "Update version and project name" | |
| echo "Tagging main with $MAIN_TAG" | |
| if ! git tag -a $MAIN_TAG -m "Langflow nightly $MAIN_TAG"; then | |
| echo "Tag creation failed. Exiting the workflow." | |
| exit 1 | |
| fi | |
| echo "Pushing main tag $MAIN_TAG" | |
| if ! git push origin $MAIN_TAG; then | |
| echo "Tag push failed. Check if the tag already exists. Exiting the workflow." | |
| exit 1 | |
| fi | |
| # TODO: notify on failure | |
| - name: Checkout main nightly tag | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ steps.generate_main_tag.outputs.main_tag }} | |
| - name: Retrieve Base Tag | |
| id: retrieve_base_tag | |
| working-directory: src/backend/base | |
| run: | | |
| # If the main tag already exists, we need to retrieve the base version from the main tag codebase. | |
| version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | head -n 1) | |
| echo "base_tag=$version" >> $GITHUB_OUTPUT | |
| echo "base_tag=$version" | |
| - name: Set Base Tag | |
| id: set_base_tag | |
| run: | | |
| if [ "${{ steps.retrieve_base_tag.conclusion }}" != "skipped" ] && [ "${{ steps.retrieve_base_tag.outputs.base_tag }}" ]; then | |
| BASE_TAG="${{ steps.retrieve_base_tag.outputs.base_tag }}" | |
| echo "base_tag=$BASE_TAG" >> $GITHUB_OUTPUT | |
| echo "base_tag=$BASE_TAG" | |
| elif [ "${{ steps.commit_tag.conclusion }}" != "skipped" ] && [ "${{ steps.generate_base_tag.outputs.base_tag }}" ]; then | |
| BASE_TAG="${{ steps.generate_base_tag.outputs.base_tag }}" | |
| echo "base_tag=$BASE_TAG" >> $GITHUB_OUTPUT | |
| echo "base_tag=$BASE_TAG" | |
| else | |
| echo "No base tag found. Exiting the workflow." | |
| exit 1 | |
| fi | |
| build-hash-history: | |
| if: github.repository == 'langflow-ai/langflow' | |
| name: Build Nightly Hash History | |
| needs: create-nightly-tag | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| shell: bash -ex -o pipefail {0} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout nightly tag | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.create-nightly-tag.outputs.main_tag }} | |
| persist-credentials: true | |
| - name: "Setup Environment" | |
| uses: astral-sh/setup-uv@v6 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: "uv.lock" | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| prune-cache: false | |
| - name: Install the project | |
| run: uv sync | |
| - name: Force reinstall LFX to pick up version change | |
| run: | | |
| echo "Force reinstalling lfx-nightly to ensure version metadata is updated..." | |
| uv pip install --reinstall --no-deps src/lfx | |
| - name: Verify LFX version | |
| run: | | |
| echo "Checking installed LFX version..." | |
| uv run python -c "from importlib.metadata import version; print(f'lfx-nightly version: {version(\"lfx-nightly\")}')" || \ | |
| uv run python -c "from importlib.metadata import version; print(f'lfx version: {version(\"lfx\")}')" | |
| - name: Build and validate nightly hash history | |
| run: | | |
| # The script includes append-only validation | |
| uv run python scripts/build_hash_history.py --nightly | |
| - name: Commit and push hash history changes | |
| run: | | |
| echo "=== Configuring git ===" | |
| git config --global user.email "[email protected]" | |
| git config --global user.name "Langflow Bot" | |
| echo "=== Checking for hash history changes ===" | |
| # Check if there are changes to commit (handles if it's a new file for first run) | |
| git add src/lfx/src/lfx/_assets/nightly_hash_history.json | |
| if git diff --cached --quiet src/lfx/src/lfx/_assets/nightly_hash_history.json; then | |
| echo "No changes to nightly hash history" | |
| else | |
| echo "Hash history changes detected, committing..." | |
| # Commit to the nightly tag itself so builds include the hash history | |
| git commit -m "Update nightly hash history for ${{ needs.create-nightly-tag.outputs.main_tag }}" | |
| echo "=== Updating nightly tag ${{ needs.create-nightly-tag.outputs.main_tag }} ===" | |
| # Update the tag to include the hash history | |
| git tag -f ${{ needs.create-nightly-tag.outputs.main_tag }} | |
| git push -f origin ${{ needs.create-nightly-tag.outputs.main_tag }} | |
| echo "=== Pushing to nightly-hash-history-updates branch ===" | |
| # Also push to the branch for PR creation | |
| git push origin HEAD:refs/heads/nightly-hash-history-updates --force | |
| echo "Updated nightly tag and branch with hash history changes" | |
| fi | |
| frontend-tests: | |
| if: github.repository == 'langflow-ai/langflow' && !inputs.skip_frontend_tests | |
| name: Run Frontend Tests | |
| needs: create-nightly-tag | |
| uses: ./.github/workflows/typescript_test.yml | |
| with: | |
| tests_folder: "tests" | |
| release: true | |
| runs-on: ${{ (inputs['runs_on'] && startsWith(format('{0}', inputs['runs_on']), '[') && fromJSON(inputs['runs_on'])) || inputs['runs_on'] || github.event.inputs['runs_on'] || 'ubuntu-latest' }} | |
| secrets: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| STORE_API_KEY: ${{ secrets.STORE_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} | |
| backend-unit-tests: | |
| if: github.repository == 'langflow-ai/langflow' && !inputs.skip_backend_tests | |
| name: Run Backend Unit Tests | |
| needs: create-nightly-tag | |
| uses: ./.github/workflows/python_test.yml | |
| with: | |
| python-versions: '["3.10", "3.11", "3.12", "3.13"]' | |
| runs-on: ${{ (inputs['runs_on'] && startsWith(format('{0}', inputs['runs_on']), '[') && fromJSON(inputs['runs_on'])) || inputs['runs_on'] || github.event.inputs['runs_on'] || 'ubuntu-latest' }} | |
| secrets: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| # Not making nightly builds dependent on integration test success | |
| # due to inherent flakiness of 3rd party integrations | |
| # Revisit when https://github.com/langflow-ai/langflow/pull/3607 is merged. | |
| # backend-integration-tests: | |
| # name: Run Backend Integration Tests | |
| # needs: create-nightly-tag | |
| # uses: ./.github/workflows/integration_tests.yml | |
| # with: | |
| # python-versions: '["3.10", "3.11", "3.12", "3.13"]' | |
| # ref: ${{ needs.create-nightly-tag.outputs.tag }} | |
| release-nightly-build: | |
| if: github.repository == 'langflow-ai/langflow' && always() && needs.frontend-tests.result != 'failure' && needs.backend-unit-tests.result != 'failure' && needs.build-hash-history.result != 'failure' | |
| name: Run Nightly Langflow Build | |
| needs: [create-nightly-tag, frontend-tests, backend-unit-tests, build-hash-history] | |
| uses: ./.github/workflows/release_nightly.yml | |
| with: | |
| build_docker_base: true | |
| build_docker_main: true | |
| build_docker_ep: true | |
| build_lfx: true | |
| nightly_tag_main: ${{ needs.create-nightly-tag.outputs.main_tag }} | |
| nightly_tag_base: ${{ needs.create-nightly-tag.outputs.base_tag }} | |
| nightly_tag_lfx: ${{ needs.create-nightly-tag.outputs.lfx_tag }} | |
| # When triggered by schedule, inputs.push_to_registry is not set, so default to true | |
| # When triggered manually, use the provided value (default is also true) | |
| push_to_registry: ${{ github.event_name == 'schedule' || inputs.push_to_registry != false }} | |
| secrets: inherit | |
| slack-notification: | |
| name: Send Slack Notification | |
| needs: [frontend-tests, backend-unit-tests, release-nightly-build] | |
| if: ${{ github.repository == 'langflow-ai/langflow' && !inputs.skip_slack && always() && (needs.release-nightly-build.result == 'failure' || needs.frontend-tests.result == 'failure' || needs.backend-unit-tests.result == 'failure' || needs.release-nightly-build.result == 'success') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Send failure notification to Slack | |
| if: ${{ needs.release-nightly-build.result == 'failure' || needs.frontend-tests.result == 'failure' || needs.backend-unit-tests.result == 'failure' }} | |
| run: | | |
| curl -X POST -H 'Content-type: application/json' \ | |
| --data '{ | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "❌ *Nightly Build Failed*" | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Job:*\nrelease-nightly-build" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Status:*\n`${{ needs.release-nightly-build.result }}`" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "context", | |
| "elements": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "<https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View full logs on GitHub>" | |
| } | |
| ] | |
| } | |
| ] | |
| }' ${{ secrets.LANGFLOW_ENG_SLACK_WEBHOOK_URL }} | |
| merge-hash-history-to-main: | |
| name: Create PR to Merge Hash History to Main | |
| needs: [create-nightly-tag, build-hash-history, release-nightly-build] | |
| # Run if auto_merge_hash_history is true (default) or not set (scheduled runs) | |
| # When triggered by schedule, inputs.auto_merge_hash_history is not set, so default to true | |
| # Only run when the base branch is 'main' to prevent PRs from feature branches | |
| if: github.repository == 'langflow-ai/langflow' && github.ref == 'refs/heads/main' && always() && needs.build-hash-history.result == 'success' && needs.release-nightly-build.result == 'success' && (github.event_name == 'schedule' || inputs.auto_merge_hash_history != false) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout main branch | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| persist-credentials: true | |
| - name: Configure Git | |
| run: | | |
| git config --global user.email "[email protected]" | |
| git config --global user.name "Langflow Bot" | |
| - name: "Setup Environment" | |
| uses: astral-sh/setup-uv@v6 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: "uv.lock" | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| prune-cache: false | |
| - name: Fetch nightly hash history branch | |
| run: | | |
| echo "=== Fetching nightly-hash-history-updates branch ===" | |
| git fetch origin nightly-hash-history-updates || echo "Branch does not exist yet, will be created" | |
| echo "Fetch completed" | |
| - name: Check if branch exists and has changes | |
| id: check_branch | |
| run: | | |
| echo "=== Checking branch status ===" | |
| if git rev-parse --verify origin/nightly-hash-history-updates >/dev/null 2>&1; then | |
| echo "branch_exists=true" >> $GITHUB_OUTPUT | |
| echo "Branch exists" | |
| # Check if there are differences between main and the nightly branch | |
| echo "=== Comparing with main branch ===" | |
| if git diff --quiet main origin/nightly-hash-history-updates -- src/lfx/src/lfx/_assets/nightly_hash_history.json; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "No changes to merge" | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| echo "Changes detected - PR will be created/updated" | |
| fi | |
| else | |
| echo "branch_exists=false" >> $GITHUB_OUTPUT | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "Branch does not exist yet - will be created by build-hash-history job" | |
| fi | |
| - name: Validate and prepare PR | |
| if: steps.check_branch.outputs.branch_exists == 'true' && steps.check_branch.outputs.has_changes == 'true' | |
| run: | | |
| echo "=== Checking out hash history file from nightly branch ===" | |
| # Checkout the nightly hash history file to verify it | |
| git checkout origin/nightly-hash-history-updates -- src/lfx/src/lfx/_assets/nightly_hash_history.json | |
| echo "=== Verifying only hash history file was modified ===" | |
| # Verify that ONLY the nightly_hash_history.json file was modified | |
| CHANGED_FILES=$(git diff --name-only main) | |
| if [ "$CHANGED_FILES" != "src/lfx/src/lfx/_assets/nightly_hash_history.json" ]; then | |
| echo "ERROR: Unexpected files were modified: $CHANGED_FILES" | |
| echo "Only nightly_hash_history.json should be changed" | |
| exit 1 | |
| fi | |
| echo "Only hash history file modified" | |
| echo "=== Validating hash history file ===" | |
| # Basic validation - ensure file exists and is valid JSON | |
| if [ ! -f "src/lfx/src/lfx/_assets/nightly_hash_history.json" ]; then | |
| echo "ERROR: nightly_hash_history.json file was deleted!" | |
| exit 1 | |
| fi | |
| if ! uv run python -c "import json; json.load(open('src/lfx/src/lfx/_assets/nightly_hash_history.json'))"; then | |
| echo "ERROR: nightly_hash_history.json is not valid JSON!" | |
| exit 1 | |
| fi | |
| echo "Hash history file validation passed" | |
| echo "Note: Append-only validation was performed during the build step" | |
| - name: Create Pull Request | |
| if: steps.check_branch.outputs.branch_exists == 'true' && steps.check_branch.outputs.has_changes == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Create or update PR from the nightly-hash-history-updates branch | |
| PR_URL=$(gh pr create \ | |
| --title "chore: update nightly hash history" \ | |
| --body "This PR updates the nightly hash history file with the latest component hashes from the nightly build. | |
| **Automated PR** - Generated by nightly build workflow | |
| - Updates: \`src/lfx/src/lfx/_assets/nightly_hash_history.json\` | |
| - Source: Nightly build tag ${{ needs.create-nightly-tag.outputs.main_tag }} | |
| This PR is automatically updated each night with the latest hash history. | |
| Auto-merge is enabled - PR will merge automatically when checks pass." \ | |
| --base main \ | |
| --head nightly-hash-history-updates \ | |
| --label "automated" \ | |
| --label "nightly" 2>&1 || echo "") | |
| # Enable auto-merge if PR was created or get existing PR | |
| if [ -n "$PR_URL" ]; then | |
| echo "PR created or found: $PR_URL" | |
| # Enable auto-merge on the PR | |
| gh pr merge nightly-hash-history-updates --auto --squash || echo "Auto-merge may already be enabled or PR doesn't exist yet" | |
| fi | |
| - name: Send success notification to Slack | |
| if: ${{ !inputs.skip_slack && needs.release-nightly-build.result == 'success' }} | |
| run: | | |
| curl -X POST -H 'Content-type: application/json' \ | |
| --data '{ | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "✅ *Nightly Build Successful*" | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Job:*\nrelease-nightly-build" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Status:*\n`${{ needs.release-nightly-build.result }}`" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "context", | |
| "elements": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "<https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View full logs on GitHub>" | |
| } | |
| ] | |
| } | |
| ] | |
| }' ${{ secrets.LANGFLOW_ENG_SLACK_WEBHOOK_URL }} |