langflow-ai/langflow

35 workflows · maturity 83% · 14 patterns · GitHub ↗

Security 15.36/100

Security dimensions

permissions
2.9
security scan
12.5
supply chain
0
secret handling
0
harden runner
0

Tools: github/codeql-action/analyze, github/codeql-action/autobuild, github/codeql-action/init

Workflows (35)

add-labels .github/workflows/add-labels.yml
Triggers
pull_request_review
Runs on
ubuntu-latest
Jobs
label-on-review
View raw YAML
name: Manage Review Labels

on:
  pull_request_review:
    types: [submitted]

jobs:
  label-on-review:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      issues: write
    steps:
      - name: Manage LGTM Review Label
        uses: actions/github-script@v8
        with:
          script: |
            const LGTM_LABEL = 'lgtm';

            // Extract review details
            const { state: reviewState } = context.payload.review;
            const pullRequestNumber = context.payload.pull_request.number;
            const repoDetails = {
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: pullRequestNumber
            };

            // Log review information
            console.log(`Processing review for PR #${pullRequestNumber}`);
            console.log(`Review state: ${reviewState}`);

            // Helper function to check for LGTM label
            async function hasLgtmLabel() {
              const { data: labels } = await github.rest.issues.listLabelsOnIssue(repoDetails);
              return labels.some(label => label.name === LGTM_LABEL);
            }

            if (reviewState === 'approved') {
              const lgtmExists = await hasLgtmLabel();

              if (!lgtmExists) {
                console.log(`Adding ${LGTM_LABEL} label to PR #${pullRequestNumber}`);
                await github.rest.issues.addLabels({
                  ...repoDetails,
                  labels: [LGTM_LABEL]
                });
                console.log('Label added successfully');
              } else {
                console.log(`${LGTM_LABEL} label already exists`);
              }
            } else if (reviewState === 'changes_requested') {
              const lgtmExists = await hasLgtmLabel();

              if (lgtmExists) {
                console.log(`Removing ${LGTM_LABEL} label from PR #${pullRequestNumber}`);
                await github.rest.issues.removeLabel({
                  ...repoDetails,
                  name: LGTM_LABEL
                });
                console.log('Label removed successfully');
              } else {
                console.log(`No ${LGTM_LABEL} label to remove`);
              }
            }
auto-update .github/workflows/auto-update.yml
Triggers
push
Runs on
ubuntu-latest
Jobs
Auto
Actions
tibdex/auto-update
View raw YAML
name: Auto-update

on:
  push:
    branches:
      - main

jobs:
  Auto:
    name: Auto-update
    runs-on: ubuntu-latest
    steps:
      - uses: tibdex/auto-update@v2
ci .github/workflows/ci.yml
Triggers
workflow_call, workflow_dispatch, pull_request, merge_group
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
echo-inputs, check-nightly-status, set-ci-condition, path-filter, test-backend, test-frontend-unit, test-frontend, lint-backend, test-docs-build, test-templates, test-docker, ci_success
Actions
dorny/paths-filter, astral-sh/setup-uv
Commands
  • echo "Inputs:" echo " ref: ${{ inputs.ref }}" echo " python-versions: ${{ inputs.python-versions }}" echo " frontend-tests-folder: ${{ inputs.frontend-tests-folder }}" echo " release: ${{ inputs.release }}" echo " run-all-tests: ${{ inputs.run-all-tests }}" echo " runs-on: ${{ inputs.runs-on }}"
  • # Get today's date in ISO format for comparison TODAY=$(date -u +"%Y-%m-%d") echo "Today's date: $TODAY" # Query PyPI API for the langflow package HTTP_STATUS=$(curl -s -o response.json -w "%{http_code}" https://pypi.org/pypi/langflow-nightly/json) # Check HTTP status code first if [ "$HTTP_STATUS" -ne 200 ]; then echo "Error: PyPI API returned HTTP status $HTTP_STATUS" echo "success=false" >> $GITHUB_OUTPUT exit 0 fi # Check if response is valid JSON before proceeding if ! jq -e . response.json >/dev/null 2>&1; then echo "Error: Invalid JSON response from PyPI API" echo "Response preview:" head -n 10 response.json echo "success=false" >> $GITHUB_OUTPUT exit 0 fi # Extract the latest version LATEST_VERSION=$(jq -r '.info.version // empty' response.json) if [ -z "$LATEST_VERSION" ]; then echo "Could not extract latest version" echo "success=false" >> $GITHUB_OUTPUT exit 0 fi # Extract the release date of the latest version RELEASE_DATE=$(jq -r --arg ver "$LATEST_VERSION" '.releases[$ver][0].upload_time_iso_8601 // empty' response.json | cut -d'T' -f1) if [ -z "$RELEASE_DATE" ]; then echo "Could not extract release date" echo "success=false" >> $GITHUB_OUTPUT exit 0 fi echo "Latest version: $LATEST_VERSION" echo "Release date: $RELEASE_DATE" # Check if the release date is today if [[ "$RELEASE_DATE" == "$TODAY" ]]; then echo "Package was updated today" echo "success=true" >> $GITHUB_OUTPUT else echo "Package was not updated today" echo "success=false" >> $GITHUB_OUTPUT fi # Clean up rm -f response.json
  • echo "Debug CI Condition"
  • echo "Labels -> ${{ join(github.event.pull_request.labels.*.name, ',') }}"
  • echo "IsDraft -> ${{ github.event.pull_request.draft }}"
  • echo "Event name -> ${{ github.event_name }}"
  • echo "Should run ci -> ${{ (github.event.pull_request.draft == false) || (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'merge_group') }}"
  • echo "Should run tests -> ${{ !contains(github.event.pull_request.labels.*.name, 'fast-track') || github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' }}"
View raw YAML
name: CI

on:
  workflow_call:
    inputs:
      ref:
        description: "(Optional) Ref to checkout"
        required: false
        type: string
      python-versions:
        description: "Python Versions"
        required: false
        type: string
        default: "['3.10']"
      frontend-tests-folder:
        description: "Frontend Tests Folder"
        required: false
        type: string
        default: "tests/core"
      release:
        description: "Release"
        required: false
        type: boolean
        default: false
      run-all-tests:
        description: "Run all tests regardless of file changes (skips path filtering)"
        required: false
        type: boolean
        default: false
      runs-on:
        description: "Runner to use for the tests"
        required: false
        type: string
        default: "ubuntu-latest"
  workflow_dispatch:
    inputs:
      ref:
        description: "(Optional) Ref to checkout"
        required: false
        type: string
      openai_api_key:
        description: "OpenAI API Key"
        required: false
        type: string
      store_api_key:
        description: "Store API Key"
        required: false
        type: string
      python-versions:
        description: "Python Versions"
        required: false
        type: string
        default: "['3.10']"
      runs-on:
        description: "Runner to use for the tests"
        required: false
        type: choice
        options:
          - ubuntu-latest
          - self-hosted
          - '["self-hosted", "linux", "ARM64", "langflow-ai-arm64-40gb-ephemeral"]'
        default: ubuntu-latest
  pull_request:
    types: [opened, synchronize, labeled]
  merge_group:
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  STORE_API_KEY: ${{ secrets.STORE_API_KEY }}
  TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}

jobs:
  echo-inputs:
    name: Echo Inputs
    runs-on: ubuntu-latest
    steps:
      - name: Echo inputs
        run: |
          echo "Inputs:"
          echo "  ref: ${{ inputs.ref }}"
          echo "  python-versions: ${{ inputs.python-versions }}"
          echo "  frontend-tests-folder: ${{ inputs.frontend-tests-folder }}"
          echo "  release: ${{ inputs.release }}"
          echo "  run-all-tests: ${{ inputs.run-all-tests }}"
          echo "  runs-on: ${{ inputs.runs-on }}"

  check-nightly-status:
    name: Check PyPI Version Update
    runs-on: ubuntu-latest
    outputs:
      should-proceed: ${{ steps.check-pypi.outputs.success }}
    steps:
      - name: Check PyPI package update
        id: check-pypi
        run: |
          # Get today's date in ISO format for comparison
          TODAY=$(date -u +"%Y-%m-%d")
          echo "Today's date: $TODAY"

          # Query PyPI API for the langflow package
          HTTP_STATUS=$(curl -s -o response.json -w "%{http_code}" https://pypi.org/pypi/langflow-nightly/json)

          # Check HTTP status code first
          if [ "$HTTP_STATUS" -ne 200 ]; then
            echo "Error: PyPI API returned HTTP status $HTTP_STATUS"
            echo "success=false" >> $GITHUB_OUTPUT
            exit 0
          fi

          # Check if response is valid JSON before proceeding
          if ! jq -e . response.json >/dev/null 2>&1; then
            echo "Error: Invalid JSON response from PyPI API"
            echo "Response preview:"
            head -n 10 response.json
            echo "success=false" >> $GITHUB_OUTPUT
            exit 0
          fi

          # Extract the latest version
          LATEST_VERSION=$(jq -r '.info.version // empty' response.json)

          if [ -z "$LATEST_VERSION" ]; then
            echo "Could not extract latest version"
            echo "success=false" >> $GITHUB_OUTPUT
            exit 0
          fi

          # Extract the release date of the latest version
          RELEASE_DATE=$(jq -r --arg ver "$LATEST_VERSION" '.releases[$ver][0].upload_time_iso_8601 // empty' response.json | cut -d'T' -f1)

          if [ -z "$RELEASE_DATE" ]; then
            echo "Could not extract release date"
            echo "success=false" >> $GITHUB_OUTPUT
            exit 0
          fi

          echo "Latest version: $LATEST_VERSION"
          echo "Release date: $RELEASE_DATE"

          # Check if the release date is today
          if [[ "$RELEASE_DATE" == "$TODAY" ]]; then
            echo "Package was updated today"
            echo "success=true" >> $GITHUB_OUTPUT
          else
            echo "Package was not updated today"
            echo "success=false" >> $GITHUB_OUTPUT
          fi

          # Clean up
          rm -f response.json

  set-ci-condition:
    name: Should Run CI
    runs-on: ubuntu-latest
    outputs:
      should-run-ci: ${{ github.event.pull_request.draft == false || (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'merge_group') }}
      should-run-tests: ${{ !contains(github.event.pull_request.labels.*.name, 'fast-track') || github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' }}
    steps:
      #  Do anything just to make the job run
      - run: echo "Debug CI Condition"
      - run: echo "Labels -> ${{ join(github.event.pull_request.labels.*.name, ',') }}"
      - run: echo "IsDraft -> ${{ github.event.pull_request.draft }}"
      - run: echo "Event name -> ${{ github.event_name }}"
      - run: echo "Should run ci -> ${{ (github.event.pull_request.draft == false) || (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'merge_group') }}"
      - run: echo "Should run tests -> ${{ !contains(github.event.pull_request.labels.*.name, 'fast-track') || github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' }}"

  path-filter:
    needs: set-ci-condition
    if: ${{ needs.set-ci-condition.outputs.should-run-ci == 'true' && !inputs.run-all-tests }}
    name: Filter Paths
    runs-on: ubuntu-latest
    outputs:
      python: ${{ steps.filter.outputs.python }}
      frontend: ${{ steps.filter.outputs.frontend }}
      docs: ${{ steps.filter.outputs.docs }}
      frontend-tests: ${{ steps.filter.outputs.frontend-tests }}
      components-changes: ${{ steps.filter.outputs.components-changes }}
      starter-projects-changes: ${{ steps.filter.outputs.starter-projects-changes }}
      starter-projects: ${{ steps.filter.outputs.starter-projects }}
      components: ${{ steps.filter.outputs.components }}
      workspace: ${{ steps.filter.outputs.workspace }}
      api: ${{ steps.filter.outputs.api }}
      database: ${{ steps.filter.outputs.database }}
      docker: ${{ steps.filter.outputs.docker }}
      docs-only: ${{
        steps.filter.outputs.docs == 'true' &&
        steps.filter.outputs.python != 'true' &&
        steps.filter.outputs.frontend != 'true' &&
        steps.filter.outputs['frontend-tests'] != 'true' &&
        steps.filter.outputs['components-changes'] != 'true' &&
        steps.filter.outputs['starter-projects-changes'] != 'true' &&
        steps.filter.outputs['starter-projects'] != 'true' &&
        steps.filter.outputs.components != 'true' &&
        steps.filter.outputs.workspace != 'true' &&
        steps.filter.outputs.api != 'true' &&
        steps.filter.outputs.database != 'true' &&
        steps.filter.outputs.docker != 'true'
        }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}
      - name: Filter Paths
        id: filter
        uses: dorny/paths-filter@v3
        with:
          filters: ./.github/changes-filter.yaml
      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"
      - name: Install deps for coverage check
        run: |
          python -m pip install --upgrade pip
          pip install --disable-pip-version-check --no-cache-dir pyyaml
      - name: Validate Filter Coverage
        continue-on-error: true
        shell: bash
        run: |
          BASE_SHA="${{ github.event.pull_request.base.sha }}"
          BASE_REF="${{ github.event.pull_request.base.ref || 'main' }}"
          # Ensure base commit is present locally
          git fetch --no-tags --depth=1 origin "$BASE_REF" || true
          git fetch --no-tags --depth=1 origin "$BASE_SHA" || true
          git diff --name-only "${BASE_SHA:-origin/$BASE_REF}"...HEAD | python scripts/check_changes_filter.py

  test-backend:
    needs: [path-filter, set-ci-condition]
    name: Run Backend Tests
    if: |
      always() &&
      !cancelled() &&
      needs.set-ci-condition.outputs.should-run-tests == 'true' &&
      (
        inputs.run-all-tests ||
        (needs.path-filter.result != 'skipped' &&
        needs.path-filter.outputs.docs-only != 'true')
      )
    uses: ./.github/workflows/python_test.yml
    with:
      python-versions: ${{ inputs.python-versions || '["3.10"]' }}
      runs-on: ${{ inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
      ref: ${{ inputs.ref || github.ref }}
    secrets:
      OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}"
      ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}"
      CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"

  test-frontend-unit:
    needs: [path-filter, set-ci-condition]
    name: Run Frontend Unit Tests
    if: |
      always() &&
      !cancelled() &&
      needs.set-ci-condition.outputs.should-run-tests == 'true' &&
      (
        inputs.run-all-tests ||
        (needs.path-filter.result != 'skipped' &&
        needs.path-filter.outputs.docs-only != 'true')
      )
    uses: ./.github/workflows/jest_test.yml
    with:
      ref: ${{ inputs.ref || github.ref }}
    secrets:
      CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"

  test-frontend:
    needs: [path-filter, set-ci-condition]
    name: Run Frontend Tests
    if: |
      always() &&
      !cancelled() &&
      needs.set-ci-condition.outputs.should-run-tests == 'true' &&
      (
        inputs.run-all-tests ||
        (needs.path-filter.result != 'skipped' &&
        needs.path-filter.outputs.docs-only != 'true')
      )
    uses: ./.github/workflows/typescript_test.yml
    with:
      tests_folder: ${{ inputs.frontend-tests-folder }}
      release: ${{ inputs.release || false }}
      runs-on: ${{ inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
      ref: ${{ inputs.ref || github.ref }}
    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 }}"

  lint-backend:
    needs: path-filter
    if: |
      always() &&
      !cancelled() &&
      (inputs.run-all-tests || needs.path-filter.outputs.python == 'true')
    name: Lint Backend
    uses: ./.github/workflows/lint-py.yml

  test-docs-build:
    needs: path-filter
    if: |
      always() &&
      !cancelled() &&
      (inputs.run-all-tests || needs.path-filter.outputs.docs == 'true')
    name: Test Docs Build
    uses: ./.github/workflows/docs_test.yml

  test-templates:
    needs: [path-filter, set-ci-condition]
    name: Test Starter Templates
    if: |
      always() &&
      !cancelled() &&
      needs.set-ci-condition.outputs.should-run-tests == 'true' &&
      (inputs.run-all-tests || needs.path-filter.outputs.python == 'true' || needs.path-filter.outputs.frontend == 'true')
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}

      - name: Set up Python 3.12
        uses: actions/setup-python@v6
        with:
          python-version: 3.12

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "latest"

      - name: Install dependencies
        run: |
          uv sync --dev

      - name: Test all starter project templates
        run: |
          uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v -n auto

  test-docker:
    needs: [path-filter, set-ci-condition]
    name: Test Docker Images
    if: ${{ needs.path-filter.outputs.docker == 'true' && needs.set-ci-condition.outputs.should-run-tests == 'true' }}
    uses: ./.github/workflows/docker_test.yml
    secrets:
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

  # https://github.com/langchain-ai/langchain/blob/master/.github/workflows/check_diffs.yml
  ci_success:
    name: "CI Success"
    needs:
      [
        test-backend,
        test-frontend-unit,
        test-frontend,
        lint-backend,
        test-docs-build,
        test-templates,
        test-docker,
        set-ci-condition,
        path-filter,
        check-nightly-status,
      ]

    if: ${{ always() }}
    runs-on: ubuntu-latest
    env:
      JOBS_JSON: ${{ toJSON(needs) }}
      RESULTS_JSON: ${{ toJSON(needs.*.result) }}
      # Skip nightly build check if only docs files changed
      DOCS_ONLY: ${{ needs.path-filter.outputs.docs-only }}
      EXIT_CODE: ${{ (contains(needs.*.result, 'failure') ||
        contains(needs.*.result, 'cancelled') ||
        (needs.check-nightly-status.outputs.should-proceed != 'true' && github.event_name != 'workflow_dispatch' && needs.path-filter.outputs.docs-only != 'true'))
        && '1' || '0' }}
    steps:
      - name: "CI Success"
        run: |
          echo "=== CI Status Summary ==="
          echo "Should run tests: ${{ needs.set-ci-condition.outputs.should-run-tests }}"
          echo "Should run CI: ${{ needs.set-ci-condition.outputs.should-run-ci }}"
          echo "Nightly build status: ${{ needs.check-nightly-status.outputs.should-proceed }}"
          echo "Event type: ${{ github.event_name }}"
          echo "Docs only changes: $DOCS_ONLY"
          echo "Python changes: ${{ needs.path-filter.outputs.python }}"
          echo "Frontend changes: ${{ needs.path-filter.outputs.frontend }}"
          echo "Docs changes: ${{ needs.path-filter.outputs.docs }}"
          echo ""

          # Check for job failures
          if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
            echo "❌ CI FAILED: One or more jobs failed"
            echo ""
            echo "Failed jobs:"

            # Dynamically list failed jobs with helpful descriptions
            echo "$JOBS_JSON" | jq -r '
              to_entries[]
              | select(.value.result=="failure")
              | .key as $job
              | if $job == "test-backend" then
                  "  - Backend Tests: Check Python code, tests, and dependencies"
                elif $job == "test-frontend-unit" then
                  "  - Frontend Unit Tests: Check React components and unit test logic"
                elif $job == "test-frontend" then
                  "  - Frontend E2E Tests: Check integration tests and UI functionality"
                elif $job == "lint-backend" then
                  "  - Backend Linting: Run '\''make format_backend'\'' then '\''make lint'\'' to fix code style issues"
                elif $job == "test-docs-build" then
                  "  - Documentation Build: Check documentation syntax and build process"
                elif $job == "test-templates" then
                  "  - Template Tests: Check starter project templates"
                elif $job == "test-docker" then
                  "  - Docker Tests: Check Docker image builds and version verification"
                elif $job == "path-filter" then
                  "  - Path Filter: File path filtering failed"
                elif $job == "set-ci-condition" then
                  "  - CI Condition Check: CI condition evaluation failed"
                elif $job == "check-nightly-status" then
                  "  - Nightly Status Check: PyPI package status check failed"
                else
                  "  - \($job): See job log for details"
                end
            '

            echo ""
            echo "🔧 Next steps:"
            echo "  1. Review the failed job logs above"
            echo "  2. Fix the identified issues in your code"
            echo "  3. Push your changes to re-run the tests"

          elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
            echo "⚠️  CI CANCELLED: One or more jobs were cancelled"
            echo ""
            echo "🔧 Next steps:"
            echo "  1. Check if the cancellation was intentional"
            echo "  2. Re-run the workflow if needed"

          elif [[ "${{ needs.check-nightly-status.outputs.should-proceed }}" != "true" && "${{ github.event_name }}" != "workflow_dispatch" && "$DOCS_ONLY" != "true" ]]; then
            echo "🚫 CI BLOCKED: Nightly build is broken"
            echo ""
            echo "The nightly PyPI package was not updated today, indicating the nightly build failed."
            echo ""
            echo "🔧 Next steps:"
            echo "  1. Work with the team to investigate and fix the nightly build"
            echo "  2. Check the nightly build logs for errors"
            echo "  3. Once the nightly build is fixed and publishes successfully, re-run this workflow"
            echo "  4. Alternatively, use 'workflow_dispatch' to manually override this check if needed"
            echo "  5. Note: PRs with only documentation changes can bypass this check"

          else
            echo "✅ CI SUCCESS: All checks passed!"
            echo ""
            echo "🎉 Your changes are ready:"
            echo "  - All tests passed"
            echo "  - Code quality checks passed"
            echo "  - Nightly build is healthy"
          fi

          echo ""
          echo "Exit code: $EXIT_CODE"
          exit $EXIT_CODE
codeql matrix security .github/workflows/codeql.yml
Triggers
push, pull_request, schedule
Runs on
${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
Jobs
analyze
Matrix
language→ javascript, python
Actions
github/codeql-action/init, github/codeql-action/autobuild, github/codeql-action/analyze
View raw YAML
name: "CodeQL"

on:
  push:
    branches: [ 'dev', 'main' ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ 'dev' ]
  schedule:
    - cron: '17 2 * * 1'

jobs:
  analyze:
    name: Analyze
    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
    timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'python', 'javascript' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Use only 'java' to analyze code written in Java, Kotlin or both
        # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

    steps:
    - name: Checkout repository
      uses: actions/checkout@v6

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v3
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.

        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
        # queries: security-extended,security-and-quality


    # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v3

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

    #   If the Autobuild fails above, remove it and uncomment the following three lines.
    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

    # - run: |
    #     echo "Run, Build Application using script"
    #     ./location_of_script_within_repo/buildscript.sh

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v3
      with:
        category: "/language:${{matrix.language}}"
community-label .github/workflows/community-label.yml
Triggers
pull_request_target
Runs on
ubuntu-latest
Jobs
add-label
View raw YAML
name: Add Community Label

on:
  pull_request_target:
    types: [opened]

jobs:
  add-label:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Add community label
        if: github.event.pull_request.author_association != 'MEMBER' && github.event.pull_request.author_association != 'OWNER' && github.event.pull_request.author_association != 'COLLABORATOR'
        uses: actions/github-script@v8
        with:
          script: |
            const pullRequestNumber = context.payload.pull_request.number;
            const repoDetails = {
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: pullRequestNumber
            };
            await github.rest.issues.addLabels({
              ...repoDetails,
              labels: ['community']
            });
conventional-labels .github/workflows/conventional-labels.yml
Triggers
pull_request_target, merge_group
Runs on
ubuntu-latest, ubuntu-latest
Jobs
validate-pr, label
Actions
Namchee/conventional-pr, bcoe/conventional-release-labels
View raw YAML
# Warning, do not check out untrusted code with
# the pull_request_target event.
name: Label PRs with Conventional Commits
on:
  pull_request_target:
    types: [opened, edited, synchronize]
  merge_group:

jobs:
  validate-pr:
    name: Validate PR
    runs-on: ubuntu-latest
    steps:
      - name: Validate the pull request
        id: validate
        uses: Namchee/conventional-pr@v0.15.6
        with:
          access_token: ${{ secrets.GITHUB_TOKEN }}
          issue: false

  label:
    needs: validate-pr
    name: Label PR
    runs-on: ubuntu-latest
    if: ${{ github.event.pull_request.user.type != 'Bot'}}
    steps:
      - uses: bcoe/conventional-release-labels@v1
        with:
          type_labels: '{"feat": "enhancement","fix": "bug","docs": "documentation","style": "style","refactor": "refactor","perf": "performance","test": "test","chore": "chore","build": "build"}'
create-release .github/workflows/create-release.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
create_release
Actions
ncipollo/release-action
View raw YAML
name: Create Release
on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version to release"
        required: true
        type: string
      ref:
        description: "Commit to tag the release"
        required: true
        type: string
      pre_release:
        description: "Pre-release tag"
        required: true
        type: boolean

jobs:
  create_release:
    name: Create Release Job
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v7
        with:
          name: dist-main
          path: dist
      - name: Create Release Notes
        uses: ncipollo/release-action@v1
        with:
          artifacts: "dist/*"
          token: ${{ secrets.GITHUB_TOKEN }}
          draft: false
          generateReleaseNotes: true
          prerelease: ${{ inputs.pre_release }}
          tag: v${{ inputs.version }}
          commit: ${{ inputs.ref }}
cross-platform-test matrix .github/workflows/cross-platform-test.yml
Triggers
workflow_dispatch, workflow_call
Runs on
ubuntu-latest, ${{ matrix.runner }}, ${{ matrix.runner }}, ubuntu-latest
Jobs
build-if-needed, test-installation-stable, test-installation-experimental, test-summary
Matrix
include, include.arch, include.os, include.python-version, include.runner→ 3.10, 3.12, 3.13, amd64, arm64, linux, macos, macos-latest, macos-latest-large, ubuntu-latest, windows, windows-latest
Actions
astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv
Commands
  • uv sync
  • make install_frontendci
  • make build_frontend
  • make build_langflow_base args="--wheel"
  • # Base package builds to dist/ but should be in src/backend/base/dist/ mkdir -p src/backend/base/dist mv dist/langflow_base*.whl src/backend/base/dist/
  • make build_langflow args="--wheel"
  • echo "base-artifact-name=adhoc-dist-base" >> $GITHUB_OUTPUT echo "main-artifact-name=adhoc-dist-main" >> $GITHUB_OUTPUT
  • if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then if [ -n "${{ inputs.langflow-version }}" ]; then echo "method=pypi" >> $GITHUB_OUTPUT else echo "method=wheel" >> $GITHUB_OUTPUT fi else # workflow_call always uses wheel method (backward compatibility) echo "method=wheel" >> $GITHUB_OUTPUT fi
View raw YAML
name: Cross-Platform Installation Test

on:
  workflow_dispatch:
    inputs:
      langflow-version:
        description: "Langflow version to test from PyPI (leave empty to test from source)"
        required: false
        type: string
        default: ""
  workflow_call:
    inputs:
      base-artifact-name:
        description: "Name of the base package artifact"
        required: true
        type: string
      main-artifact-name:
        description: "Name of the main package artifact"
        required: true
        type: string
      lfx-artifact-name:
        description: "Name of the LFX package artifact"
        required: false
        type: string
      pre_release:
        description: "Whether this is a pre-release build"
        required: false
        type: boolean
        default: false

jobs:
  build-if-needed:
    name: Build Packages (if no artifacts provided)
    runs-on: ubuntu-latest
    if: inputs.langflow-version == '' && contains(github.workflow_ref, 'cross-platform-test.yml')
    outputs:
      base-artifact-name: ${{ steps.set-names.outputs.base-artifact-name }}
      main-artifact-name: ${{ steps.set-names.outputs.main-artifact-name }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
      - name: Setup Environment
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: "3.13"
      - name: Install the project
        run: uv sync
      - name: Install frontend dependencies
        run: make install_frontendci
      - name: Build frontend
        run: make build_frontend
      - name: Build base package
        run: make build_langflow_base args="--wheel"
      - name: Move base package to correct directory
        run: |
          # Base package builds to dist/ but should be in src/backend/base/dist/
          mkdir -p src/backend/base/dist
          mv dist/langflow_base*.whl src/backend/base/dist/
      - name: Build main package
        run: make build_langflow args="--wheel"
      - name: Upload base artifact
        uses: actions/upload-artifact@v6
        with:
          name: adhoc-dist-base
          path: src/backend/base/dist
      - name: Upload main artifact
        uses: actions/upload-artifact@v6
        with:
          name: adhoc-dist-main
          path: dist
      - name: Set artifact names
        id: set-names
        run: |
          echo "base-artifact-name=adhoc-dist-base" >> $GITHUB_OUTPUT
          echo "main-artifact-name=adhoc-dist-main" >> $GITHUB_OUTPUT

  test-installation-stable:
    name: Install & Run - ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.python-version }}
    needs: [build-if-needed]
    if: always() && (needs.build-if-needed.result == 'success' || needs.build-if-needed.result == 'skipped')
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # Linux AMD64
          - os: linux
            arch: amd64
            runner: ubuntu-latest
            python-version: "3.10"
          - os: linux
            arch: amd64
            runner: ubuntu-latest
            python-version: "3.12"
          # macOS AMD64
          - os: macos
            arch: amd64
            runner: macos-latest-large
            python-version: "3.10"
          - os: macos
            arch: amd64
            runner: macos-latest-large
            python-version: "3.12"
          # macOS ARM64 (Apple Silicon)
          - os: macos
            arch: arm64
            runner: macos-latest
            python-version: "3.10"
          - os: macos
            arch: arm64
            runner: macos-latest
            python-version: "3.12"
          # Windows AMD64
          - os: windows
            arch: amd64
            runner: windows-latest
            python-version: "3.10"
          - os: windows
            arch: amd64
            runner: windows-latest
            python-version: "3.12"

    steps:
      - name: Determine install method
        id: install-method
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            if [ -n "${{ inputs.langflow-version }}" ]; then
              echo "method=pypi" >> $GITHUB_OUTPUT
            else
              echo "method=wheel" >> $GITHUB_OUTPUT
            fi
          else
            # workflow_call always uses wheel method (backward compatibility)
            echo "method=wheel" >> $GITHUB_OUTPUT
          fi
        shell: bash

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}
          architecture: ${{ matrix.arch == 'amd64' && 'x64' || matrix.arch }}

      - name: Setup UV
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: false
          ignore-empty-workdir: true

      - name: Install Protocol Buffers (macOS AMD64)
        if: matrix.os == 'macos' && matrix.arch == 'amd64'
        run: |
          if ! command -v protoc &> /dev/null; then
            brew install protobuf
          else
            echo "protoc already installed, skipping"
          fi
        shell: bash

      # Download artifacts for wheel installation
      - name: Download LFX package artifact
        if: steps.install-method.outputs.method == 'wheel' && inputs.lfx-artifact-name != ''
        uses: actions/download-artifact@v7
        with:
          name: ${{ inputs.lfx-artifact-name }}
          path: ./lfx-dist

      - name: Download base package artifact
        if: steps.install-method.outputs.method == 'wheel'
        uses: actions/download-artifact@v7
        with:
          name: ${{ inputs.base-artifact-name || needs.build-if-needed.outputs.base-artifact-name || 'adhoc-dist-base' }}
          path: ./base-dist

      - name: Download main package artifact
        if: steps.install-method.outputs.method == 'wheel'
        uses: actions/download-artifact@v7
        with:
          name: ${{ inputs.main-artifact-name || needs.build-if-needed.outputs.main-artifact-name || 'adhoc-dist-main' }}
          path: ./main-dist

      - name: Create fresh virtual environment
        run: |
          uv venv test-env --seed
        shell: bash

      # Wheel installation steps
      - name: Install LFX package from wheel (Windows)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' && inputs.lfx-artifact-name != ''
        run: |
          ls -la ./lfx-dist/
          find ./lfx-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./lfx-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/Scripts/python.exe "$WHEEL_FILE"
          else
            echo "No wheel file found in ./lfx-dist/"
            exit 1
          fi
        shell: bash

      - name: Install base package from wheel (Windows)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows'
        run: |
          ls -la ./base-dist/
          find ./base-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./base-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/Scripts/python.exe "$WHEEL_FILE"
          else
            echo "No wheel file found in ./base-dist/"
            exit 1
          fi
        shell: bash

      - name: Install main package from wheel (Windows)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows'
        run: |
          ls -la ./main-dist/
          find ./main-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./main-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/Scripts/python.exe "$WHEEL_FILE"
          else
            echo "No wheel file found in ./main-dist/"
            exit 1
          fi
        shell: bash

      - name: Install LFX package from wheel (Unix)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' && inputs.lfx-artifact-name != ''
        run: |
          ls -la ./lfx-dist/
          find ./lfx-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./lfx-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/bin/python "$WHEEL_FILE"
          else
            echo "No wheel file found in ./lfx-dist/"
            exit 1
          fi
        shell: bash

      - name: Install base package from wheel (Unix)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows'
        run: |
          ls -la ./base-dist/
          find ./base-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./base-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/bin/python "$WHEEL_FILE"
          else
            echo "No wheel file found in ./base-dist/"
            exit 1
          fi
        shell: bash

      - name: Install main package from wheel (Unix)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows'
        run: |
          ls -la ./main-dist/
          find ./main-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./main-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/bin/python "$WHEEL_FILE"
          else
            echo "No wheel file found in ./main-dist/"
            exit 1
          fi
        shell: bash

      # PyPI installation steps
      - name: Install langflow from PyPI (Windows)
        if: steps.install-method.outputs.method == 'pypi' && matrix.os == 'windows'
        run: |
          if [ -n "${{ inputs.langflow-version }}" ]; then
            uv pip install --python ./test-env/Scripts/python.exe langflow==${{ inputs.langflow-version }}
          else
            uv pip install --python ./test-env/Scripts/python.exe langflow
          fi
        shell: bash

      - name: Install langflow from PyPI (Unix)
        if: steps.install-method.outputs.method == 'pypi' && matrix.os != 'windows'
        run: |
          if [ -n "${{ inputs.langflow-version }}" ]; then
            uv pip install --python ./test-env/bin/python langflow==${{ inputs.langflow-version }}
          else
            uv pip install --python ./test-env/bin/python langflow
          fi
        shell: bash

      # Install additional dependencies
      - name: Install additional dependencies (Windows)
        if: matrix.os == 'windows'
        run: |
          uv pip install --python ./test-env/Scripts/python.exe openai
        shell: bash

      - name: Install additional dependencies (Unix)
        if: matrix.os != 'windows'
        run: |
          uv pip install --python ./test-env/bin/python openai
        shell: bash

      # Test steps
      - name: Test CLI help command
        if: matrix.os == 'windows'
        run: |
          uv run --python ./test-env/Scripts/python.exe python -m langflow --help
        shell: cmd

      - name: Test CLI help command (Unix)
        if: matrix.os != 'windows'
        run: |
          uv run --python ./test-env/bin/python python -m langflow --help
        shell: bash

      - name: Test server startup (Windows)
        if: matrix.os == 'windows'
        timeout-minutes: 5
        run: |
          # Start server in background
          $serverProcess = Start-Process -FilePath ".\test-env\Scripts\python.exe" -ArgumentList "-m", "langflow", "run", "--host", "localhost", "--port", "7860", "--backend-only" -PassThru -WindowStyle Hidden

          # Wait for server to be ready (GitHub Actions will timeout after 5 minutes)
          do {
            try {
              $response = Invoke-WebRequest -Uri "http://localhost:7860/health_check" -UseBasicParsing -TimeoutSec 5
              if ($response.StatusCode -eq 200) {
                Write-Host "✅ Server is ready on ${{ matrix.os }}-${{ matrix.arch }}"
                break
              }
            } catch {
              Start-Sleep -Seconds 5
            }
          } while ($true)

          # Stop the server process
          Stop-Process -Id $serverProcess.Id -Force -ErrorAction SilentlyContinue
        shell: powershell

      - name: Test server startup (Unix)
        if: matrix.os != 'windows'
        timeout-minutes: 5
        env:
          OBJC_DISABLE_INITIALIZE_FORK_SAFETY: YES
        run: |
          # Start server in background
          ./test-env/bin/python -m langflow run --host localhost --port 7860 --backend-only &
          SERVER_PID=$!

          # Wait for server to be ready (GitHub Actions will timeout after 5 minutes)
          while true; do
            if curl -f http://localhost:7860/health_check >/dev/null 2>&1; then
              echo "✅ Server is ready on ${{ matrix.os }}-${{ matrix.arch }}"
              break
            fi
            sleep 5
          done

          # Clean shutdown
          kill $SERVER_PID 2>/dev/null || true
          sleep 5
        shell: bash

      - name: Test import in Python (Windows)
        if: matrix.os == 'windows'
        run: |
          test-env\Scripts\python.exe -c "
          try:
              import langflow
              print('✅ langflow import successful on ${{ matrix.os }}-${{ matrix.arch }}')
          except Exception as e:
              print(f'❌ langflow import failed on ${{ matrix.os }}-${{ matrix.arch }}: {e}')
              exit(1)
          "
        shell: cmd

      - name: Test import in Python (Unix)
        if: matrix.os != 'windows'
        run: |
          ./test-env/bin/python -c "
          try:
              import langflow
              print('✅ langflow import successful on ${{ matrix.os }}-${{ matrix.arch }}')
          except Exception as e:
              print(f'❌ langflow import failed on ${{ matrix.os }}-${{ matrix.arch }}: {e}')
              exit(1)
          "
        shell: bash

  test-installation-experimental:
    name: Install & Run - ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.python-version }} (Experimental)
    needs: [build-if-needed]
    if: always() && (needs.build-if-needed.result == 'success' || needs.build-if-needed.result == 'skipped')
    runs-on: ${{ matrix.runner }}
    continue-on-error: true
    strategy:
      fail-fast: false
      matrix:
        include:
          # Python 3.13 - Experimental
          - os: linux
            arch: amd64
            runner: ubuntu-latest
            python-version: "3.13"
          - os: windows
            arch: amd64
            runner: windows-latest
            python-version: "3.13"
          - os: macos
            arch: amd64
            runner: macos-latest-large
            python-version: "3.13"
          - os: macos
            arch: arm64
            runner: macos-latest
            python-version: "3.13"

    steps:
      - name: Determine install method
        id: install-method
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            if [ -n "${{ inputs.langflow-version }}" ]; then
              echo "method=pypi" >> $GITHUB_OUTPUT
            else
              echo "method=wheel" >> $GITHUB_OUTPUT
            fi
          else
            # workflow_call always uses wheel method (backward compatibility)
            echo "method=wheel" >> $GITHUB_OUTPUT
          fi
        shell: bash

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}
          architecture: ${{ matrix.arch == 'amd64' && 'x64' || matrix.arch }}

      - name: Setup UV
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: false
          ignore-empty-workdir: true

      - name: Install Protocol Buffers (macOS AMD64)
        if: matrix.os == 'macos' && matrix.arch == 'amd64'
        run: |
          if ! command -v protoc &> /dev/null; then
            brew install protobuf
          else
            echo "protoc already installed, skipping"
          fi
        shell: bash

      # Download artifacts for wheel installation
      - name: Download LFX package artifact
        if: steps.install-method.outputs.method == 'wheel' && inputs.lfx-artifact-name != ''
        uses: actions/download-artifact@v7
        with:
          name: ${{ inputs.lfx-artifact-name }}
          path: ./lfx-dist

      - name: Download base package artifact
        if: steps.install-method.outputs.method == 'wheel'
        uses: actions/download-artifact@v7
        with:
          name: ${{ inputs.base-artifact-name || needs.build-if-needed.outputs.base-artifact-name || 'adhoc-dist-base' }}
          path: ./base-dist

      - name: Download main package artifact
        if: steps.install-method.outputs.method == 'wheel'
        uses: actions/download-artifact@v7
        with:
          name: ${{ inputs.main-artifact-name || needs.build-if-needed.outputs.main-artifact-name || 'adhoc-dist-main' }}
          path: ./main-dist

      - name: Create fresh virtual environment
        run: |
          uv venv test-env --seed
        shell: bash

      # Wheel installation steps
      - name: Install LFX package from wheel (Windows)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' && inputs.lfx-artifact-name != ''
        run: |
          ls -la ./lfx-dist/
          find ./lfx-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./lfx-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/Scripts/python.exe "$WHEEL_FILE"
          else
            echo "No wheel file found in ./lfx-dist/"
            exit 1
          fi
        shell: bash

      - name: Install base package from wheel (Windows)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows'
        run: |
          ls -la ./base-dist/
          find ./base-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./base-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/Scripts/python.exe "$WHEEL_FILE"
          else
            echo "No wheel file found in ./base-dist/"
            exit 1
          fi
        shell: bash

      - name: Install main package from wheel (Windows)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows'
        run: |
          ls -la ./main-dist/
          find ./main-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./main-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/Scripts/python.exe "$WHEEL_FILE"
          else
            echo "No wheel file found in ./main-dist/"
            exit 1
          fi
        shell: bash

      - name: Install LFX package from wheel (Unix)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' && inputs.lfx-artifact-name != ''
        run: |
          ls -la ./lfx-dist/
          find ./lfx-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./lfx-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/bin/python "$WHEEL_FILE"
          else
            echo "No wheel file found in ./lfx-dist/"
            exit 1
          fi
        shell: bash

      - name: Install base package from wheel (Unix)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows'
        run: |
          ls -la ./base-dist/
          find ./base-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./base-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/bin/python "$WHEEL_FILE"
          else
            echo "No wheel file found in ./base-dist/"
            exit 1
          fi
        shell: bash

      - name: Install main package from wheel (Unix)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows'
        run: |
          ls -la ./main-dist/
          find ./main-dist -name "*.whl" -type f
          WHEEL_FILE=$(find ./main-dist -name "*.whl" -type f | head -1)
          if [ -n "$WHEEL_FILE" ]; then
            uv pip install --prerelease=allow --python ./test-env/bin/python "$WHEEL_FILE"
          else
            echo "No wheel file found in ./main-dist/"
            exit 1
          fi
        shell: bash

      - name: Force reinstall local wheels to prevent downgrades (Windows)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows'
        run: |
          # Reinstall LFX if it was provided
          if [ -n "${{ inputs.lfx-artifact-name }}" ]; then
            LFX_WHEEL=$(find ./lfx-dist -name "*.whl" -type f | head -1)
            if [ -n "$LFX_WHEEL" ]; then
              echo "Force reinstalling LFX: $LFX_WHEEL"
              NO_DEPS="--no-deps"
              if [ "${{ inputs.pre_release }}" = "true" ]; then
                NO_DEPS=""
              fi
              uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/Scripts/python.exe "$LFX_WHEEL"
            fi
          fi

          # Reinstall base
          BASE_WHEEL=$(find ./base-dist -name "*.whl" -type f | head -1)
          if [ -n "$BASE_WHEEL" ]; then
            echo "Force reinstalling langflow-base: $BASE_WHEEL"
            NO_DEPS="--no-deps"
            if [ "${{ inputs.pre_release }}" = "true" ]; then
              NO_DEPS=""
            fi
            uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/Scripts/python.exe "$BASE_WHEEL"
          fi
        shell: bash

      - name: Force reinstall local wheels to prevent downgrades (Unix)
        if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows'
        run: |
          # Reinstall LFX if it was provided
          if [ -n "${{ inputs.lfx-artifact-name }}" ]; then
            LFX_WHEEL=$(find ./lfx-dist -name "*.whl" -type f | head -1)
            if [ -n "$LFX_WHEEL" ]; then
              echo "Force reinstalling LFX: $LFX_WHEEL"
              NO_DEPS="--no-deps"
              if [ "${{ inputs.pre_release }}" = "true" ]; then
                NO_DEPS=""
              fi
              uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/bin/python "$LFX_WHEEL"
            fi
          fi

          # Reinstall base
          BASE_WHEEL=$(find ./base-dist -name "*.whl" -type f | head -1)
          if [ -n "$BASE_WHEEL" ]; then
            echo "Force reinstalling langflow-base: $BASE_WHEEL"
            NO_DEPS="--no-deps"
            if [ "${{ inputs.pre_release }}" = "true" ]; then
              NO_DEPS=""
            fi
            uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/bin/python "$BASE_WHEEL"
          fi
        shell: bash

      # PyPI installation steps
      - name: Install langflow from PyPI (Windows)
        if: steps.install-method.outputs.method == 'pypi' && matrix.os == 'windows'
        run: |
          if [ -n "${{ inputs.langflow-version }}" ]; then
            uv pip install --python ./test-env/Scripts/python.exe langflow==${{ inputs.langflow-version }}
          else
            uv pip install --python ./test-env/Scripts/python.exe langflow
          fi
        shell: bash

      - name: Install langflow from PyPI (Unix)
        if: steps.install-method.outputs.method == 'pypi' && matrix.os != 'windows'
        run: |
          if [ -n "${{ inputs.langflow-version }}" ]; then
            uv pip install --python ./test-env/bin/python langflow==${{ inputs.langflow-version }}
          else
            uv pip install --python ./test-env/bin/python langflow
          fi
        shell: bash

      # Install additional dependencies
      - name: Install additional dependencies (Windows)
        if: matrix.os == 'windows'
        run: |
          uv pip install --python ./test-env/Scripts/python.exe openai
        shell: bash

      - name: Install additional dependencies (Unix)
        if: matrix.os != 'windows'
        run: |
          uv pip install --python ./test-env/bin/python openai
        shell: bash

      # Test steps
      - name: Test CLI help command (Windows)
        if: matrix.os == 'windows'
        run: |
          uv run --python ./test-env/Scripts/python.exe python -m langflow --help
        shell: cmd

      - name: Test CLI help command (Unix)
        if: matrix.os != 'windows'
        run: |
          uv run --python ./test-env/bin/python python -m langflow --help
        shell: bash

      - name: Test server startup (Windows)
        if: matrix.os == 'windows'
        timeout-minutes: 5
        run: |
          # Start server in background
          $serverProcess = Start-Process -FilePath ".\test-env\Scripts\python.exe" -ArgumentList "-m", "langflow", "run", "--host", "localhost", "--port", "7860", "--backend-only" -PassThru -WindowStyle Hidden

          # Wait for server to be ready (GitHub Actions will timeout after 5 minutes)
          do {
            try {
              $response = Invoke-WebRequest -Uri "http://localhost:7860/health_check" -UseBasicParsing -TimeoutSec 5
              if ($response.StatusCode -eq 200) {
                Write-Host "✅ Server is ready on ${{ matrix.os }}-${{ matrix.arch }}"
                break
              }
            } catch {
              Start-Sleep -Seconds 5
            }
          } while ($true)

          # Stop the server process
          Stop-Process -Id $serverProcess.Id -Force -ErrorAction SilentlyContinue
        shell: powershell

      - name: Test server startup (Unix)
        if: matrix.os != 'windows'
        timeout-minutes: 5
        env:
          OBJC_DISABLE_INITIALIZE_FORK_SAFETY: YES
        run: |
          # Start server in background
          ./test-env/bin/python -m langflow run --host localhost --port 7860 --backend-only &
          SERVER_PID=$!

          # Wait for server to be ready (GitHub Actions will timeout after 5 minutes)
          while true; do
            if curl -f http://localhost:7860/health_check >/dev/null 2>&1; then
              echo "✅ Server is ready on ${{ matrix.os }}-${{ matrix.arch }}"
              break
            fi
            sleep 5
          done

          # Clean shutdown
          kill $SERVER_PID 2>/dev/null || true
          sleep 5
        shell: bash

      - name: Test import in Python (Windows)
        if: matrix.os == 'windows'
        run: |
          test-env\Scripts\python.exe -c "
          try:
              import langflow
              print('✅ langflow import successful on ${{ matrix.os }}-${{ matrix.arch }}')
          except Exception as e:
              print(f'❌ langflow import failed on ${{ matrix.os }}-${{ matrix.arch }}: {e}')
              exit(1)
          "
        shell: cmd

      - name: Test import in Python (Unix)
        if: matrix.os != 'windows'
        run: |
          ./test-env/bin/python -c "
          try:
              import langflow
              print('✅ langflow import successful on ${{ matrix.os }}-${{ matrix.arch }}')
          except Exception as e:
              print(f'❌ langflow import failed on ${{ matrix.os }}-${{ matrix.arch }}: {e}')
              exit(1)
          "
        shell: bash

  test-summary:
    name: Cross-Platform Test Summary
    needs: [test-installation-stable, test-installation-experimental]
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: Check test results
        run: |
          stable_result="${{ needs.test-installation-stable.result }}"
          experimental_result="${{ needs.test-installation-experimental.result }}"

          echo "Stable platforms result: $stable_result"
          echo "Experimental platforms result: $experimental_result"

          # Stable platforms must succeed, experimental can fail
          if [ "$stable_result" = "success" ]; then
            if [ "$experimental_result" = "success" ]; then
              if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
                if [ -n "${{ inputs.langflow-version }}" ]; then
                  echo "✅ All PyPI installation tests passed (including Python 3.13)"
                else
                  echo "✅ All source build and installation tests passed (including Python 3.13)"
                fi
              else
                echo "✅ All cross-platform tests passed - PyPI upload can proceed"
              fi
            else
              if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
                if [ -n "${{ inputs.langflow-version }}" ]; then
                  echo "✅ PyPI installation tests passed (Python 3.13 experimental failures are acceptable)"
                else
                  echo "✅ Source build and installation tests passed (Python 3.13 experimental failures are acceptable)"
                fi
              else
                echo "✅ Cross-platform tests passed - Python 3.13 experimental failures are acceptable - PyPI upload can proceed"
              fi
            fi
          elif [ "$stable_result" = "failure" ]; then
            echo "❌ Critical platform tests failed - blocking release"
            echo "Stable platforms (Python 3.10, 3.12) must pass for release"
            exit 1
          elif [ "$stable_result" = "cancelled" ]; then
            echo "❌ Critical platform tests were cancelled"
            exit 1
          else
            echo "❌ Critical platform tests were skipped unexpectedly"
            exit 1
          fi
deploy-docs-draft .github/workflows/deploy-docs-draft.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
build-and-deploy
Actions
peter-evans/create-or-update-comment, peter-evans/create-or-update-comment, peter-evans/find-comment, peter-evans/create-or-update-comment
Commands
  • # Check if branch names contain invalid characters. Only alphanumeric, _, -, ., and / are allowed. validate_branch_name() { local branch_name="$1" if [[ ! "$branch_name" =~ ^[a-zA-Z0-9/_\.-]+$ ]]; then echo "Error: Branch name contains invalid characters. Only alphanumeric, _, -, ., and / are allowed." exit 1 fi } validate_branch_name "${{ github.event.pull_request.head.ref }}"
  • # Extract and transform branch names extract_branch() { local input_branch="$1" # Check if input_branch starts with "refs/heads/" if [[ "$input_branch" == refs/heads/* ]]; then # Remove "refs/heads/" prefix safely using parameter expansion branch_name="${input_branch#refs/heads/}" echo "$branch_name" else echo "$input_branch" fi } # Transform branch names in form of `refs/heads/main` to `main` draft_branch=$(extract_branch "${{ github.event.pull_request.head.ref }}") # Replace / with - in the draft branch name to use as a directory name draft_directory=$(echo "$draft_branch" | tr / -) # Safe echo to $GITHUB_OUTPUT { echo "draft_branch=$draft_branch" echo "draft_directory=$draft_directory" } >> "$GITHUB_OUTPUT"
  • echo "url=${{ vars.DOCS_DRAFT_BASE_URL }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/index.html" >> $GITHUB_OUTPUT
  • cd docs && npm install
  • set -o pipefail cd docs npm run build |& tee $GITHUB_WORKSPACE/build.log
  • MULTILINE_LOG=$(cat $GITHUB_WORKSPACE/build.log) echo "BUILD_FAILURE<<EOF" >> $GITHUB_ENV echo $MULTILINE_LOG >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV
  • set -e # Get all comments on the PR that match our build comments comments=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \ --jq '.[] | select(.body | test("Build failure! :x:|Build successful! :white_check_mark:")) | .node_id') # Minimize each matching comment using GraphQL API if [[ -n "$comments" ]]; then echo "Found previous build comments to hide" while IFS= read -r comment_id; do if [[ -n "$comment_id" ]]; then echo "Minimizing comment: $comment_id" gh api graphql \ --field id="$comment_id" \ --field classifier="OUTDATED" \ --raw-field query=' mutation($id: ID!, $classifier: ReportedContentClassifiers!) { minimizeComment(input: { subjectId: $id, classifier: $classifier }) { minimizedComment { isMinimized } } }' || echo "Failed to minimize comment $comment_id, continuing..." echo fi done <<< "$comments" else echo "No previous build comments found to hide" fi
  • build_success_comment="Build successful! :white_check_mark:" build_success_comment+="\nDeploying docs draft." echo "BUILD_SUCCESS_COMMENT<<EOF" >> $GITHUB_ENV echo -e "$build_success_comment" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV
View raw YAML
name: Pull Request Docs Draft

on:
  pull_request:
    branches:
      - '**'
    paths:
      - 'docs/**'
      - '.github/workflows/deploy-docs-draft.yml'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    if: "! github.event.pull_request.head.repo.fork"

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: ./docs/package-lock.json

      - name: Validate Branch Names
        run: |
          # Check if branch names contain invalid characters. Only alphanumeric, _, -, ., and / are allowed.
          validate_branch_name() {
              local branch_name="$1"
              if [[ ! "$branch_name" =~ ^[a-zA-Z0-9/_\.-]+$ ]]; then
                  echo "Error: Branch name contains invalid characters. Only alphanumeric, _, -, ., and / are allowed."
                  exit 1
              fi
          }
          validate_branch_name "${{ github.event.pull_request.head.ref }}"

      - name: Extract Branch Names
        id: extract_branch
        run: |
          # Extract and transform branch names
          extract_branch() {
              local input_branch="$1"
              # Check if input_branch starts with "refs/heads/"
              if [[ "$input_branch" == refs/heads/* ]]; then
                  # Remove "refs/heads/" prefix safely using parameter expansion
                  branch_name="${input_branch#refs/heads/}"
                  echo "$branch_name"
              else
                  echo "$input_branch"
              fi
          }

          # Transform branch names in form of `refs/heads/main` to `main`
          draft_branch=$(extract_branch "${{ github.event.pull_request.head.ref }}")

          # Replace / with - in the draft branch name to use as a directory name
          draft_directory=$(echo "$draft_branch" | tr / -)

          # Safe echo to $GITHUB_OUTPUT
          {
              echo "draft_branch=$draft_branch"
              echo "draft_directory=$draft_directory"
          } >> "$GITHUB_OUTPUT"

      - name: Set Draft URL
        id: draft_url
        if: success()
        run: |
          echo "url=${{ vars.DOCS_DRAFT_BASE_URL }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/index.html" >> $GITHUB_OUTPUT

      - name: Install dependencies
        run: cd docs && npm install

      - name: Build website
        if: success()
        run: |
          set -o pipefail
          cd docs
          npm run build |& tee $GITHUB_WORKSPACE/build.log
        env:
          BASE_URL: /langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}
          FORCE_COLOR: 0 # Disable color output
          SEGMENT_PUBLIC_WRITE_KEY: ${{ vars.DOCS_DRAFT_IBM_SEGMENT_PUBLIC_WRITE_KEY }}

      - name: Check Build Result
        id: buildLogFail
        if: failure()
        run: |
          MULTILINE_LOG=$(cat $GITHUB_WORKSPACE/build.log)
          echo "BUILD_FAILURE<<EOF" >> $GITHUB_ENV
          echo $MULTILINE_LOG >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Hide Previous Build Comments
        if: ${{ github.event.pull_request.number && (success() || failure()) }}
        run: |
          set -e

          # Get all comments on the PR that match our build comments
          comments=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \
            --jq '.[] | select(.body | test("Build failure! :x:|Build successful! :white_check_mark:")) | .node_id')

          # Minimize each matching comment using GraphQL API
          if [[ -n "$comments" ]]; then
            echo "Found previous build comments to hide"
            while IFS= read -r comment_id; do
              if [[ -n "$comment_id" ]]; then
                echo "Minimizing comment: $comment_id"
                gh api graphql \
                  --field id="$comment_id" \
                  --field classifier="OUTDATED" \
                  --raw-field query='
                    mutation($id: ID!, $classifier: ReportedContentClassifiers!) {
                      minimizeComment(input: { subjectId: $id, classifier: $classifier }) {
                        minimizedComment {
                          isMinimized
                        }
                      }
                    }' || echo "Failed to minimize comment $comment_id, continuing..."
                echo
              fi
            done <<< "$comments"
          else
            echo "No previous build comments found to hide"
          fi
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Assemble Build Success Comment
        if: success()
        run: |
          build_success_comment="Build successful! :white_check_mark:"
          build_success_comment+="\nDeploying docs draft."

          echo "BUILD_SUCCESS_COMMENT<<EOF" >> $GITHUB_ENV
          echo -e "$build_success_comment" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Create Build Success Comment
        if: success()
        uses: peter-evans/create-or-update-comment@v4
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body: "${{ env.BUILD_SUCCESS_COMMENT }}"
          reactions: rocket

      - name: Create Build Failure Comment
        if: failure()
        uses: peter-evans/create-or-update-comment@v4
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            Build failure! :x:
            > ${{ env.BUILD_FAILURE }}
          reactions: confused

      - name: Find Comment
        id: fc
        if: success()
        uses: peter-evans/find-comment@v3
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body-includes: Build successful!
          direction: last

      - name: Configure AWS CLI
        if: success()
        run: |
          aws configure set aws_access_key_id ${{ secrets.DOCS_AWS_ACCESS_KEY_ID }}
          aws configure set aws_secret_access_key ${{ secrets.DOCS_AWS_SECRET_ACCESS_KEY }}
          aws configure set region us-west-2

      - name: Check for New Assets
        run: |
          set -o pipefail
          echo "Checking for new assets." |& tee -a $GITHUB_WORKSPACE/deploy.log
          echo "aws s3 sync docs/build/assets/ s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/assets/ --size-only --dryrun --no-progress" | tee -a $GITHUB_WORKSPACE/deploy.log
          aws s3 sync docs/build/assets/ "s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/assets/" --size-only --dryrun --no-progress | tee $GITHUB_WORKSPACE/assets.log

      - name: Determine Standard or Full Publish
        id: check_full_publish
        run: |
          # Determine if a full publish is required because of new assets.
          if grep -qE '(upload:|delete:)' "$GITHUB_WORKSPACE/assets.log"; then
            echo "New assets. Perform full publish: true" | tee -a "$GITHUB_WORKSPACE/deploy.log"
            echo "perform_full_publish=true" >> "$GITHUB_OUTPUT"
          else
            echo "No new assets. Perform full publish: false" | tee -a "$GITHUB_WORKSPACE/deploy.log"
            echo "perform_full_publish=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Deploy to S3
        if: success()
        run: |
          set -o pipefail
          cd docs
          mkdir langflow-drafts
          mv build langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}
          cd langflow-drafts

          # Records the repository that originally triggered the build so we can post back
          # comments upon clean up of a stale draft if it still has an open pull request.
          echo "${{ github.event.repository.full_name }}" > ${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository

          s3_params=(
            # Hide upload progress for a cleaner sync log
            --no-progress
            --delete
            --exclude "*"
            --include "${{ steps.extract_branch.outputs.draft_directory }}/*"
          )

          if [[ "${{ steps.check_full_publish.outputs.perform_full_publish }}" == "false" ]]; then
            s3_params+=(--size-only)
          fi

          echo "Deploying draft to S3." |& tee -a $GITHUB_WORKSPACE/deploy.log
          echo "aws s3 sync . s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts ${s3_params[@]}" |& tee -a $GITHUB_WORKSPACE/deploy.log
          aws s3 sync . "s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts" "${s3_params[@]}" |& tee -a $GITHUB_WORKSPACE/deploy.log

          # Update .github_source_repository file metadata to mark last modified time of the draft.
          # This will allow us to later determine if a draft is stale and needs to be cleaned up.
          echo "Marking last modified time of the draft." |& tee -a $GITHUB_WORKSPACE/deploy.log
          echo "aws s3 cp --metadata '{\"touched\": \"now\"}' \
            s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \
            s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository" \
            |& tee -a $GITHUB_WORKSPACE/deploy.log

          aws s3 cp --metadata '{ "touched": "now" }' \
            s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \
            s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \
            |& tee -a $GITHUB_WORKSPACE/deploy.log

      - name: Invalidate CloudFront Cache
        if: success()
        run: |
          invalidation_batch="{ \"Paths\": { \"Quantity\": 1, \"Items\": [\"/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/*\"] }, \"CallerReference\": \"langflow-docs-draft-files-$(date +%s)\" }"

          echo $invalidation_batch | jq . |& tee -a "$GITHUB_WORKSPACE/deploy.log"
          echo "Creating invalidation." |& tee -a "$GITHUB_WORKSPACE/deploy.log"
          invalidation_id=$(aws cloudfront create-invalidation --distribution-id "${{ vars.DOCS_DRAFT_CLOUD_FRONT_DISTRIBUTION_ID }}" --invalidation-batch "$invalidation_batch" --query 'Invalidation.Id' --output text |& tee -a "$GITHUB_WORKSPACE/deploy.log")

          echo "Awaiting invalidation." |& tee -a "$GITHUB_WORKSPACE/deploy.log"
          aws cloudfront wait invalidation-completed --distribution-id "${{ vars.DOCS_DRAFT_CLOUD_FRONT_DISTRIBUTION_ID }}" --id "$invalidation_id" |& tee -a "$GITHUB_WORKSPACE/deploy.log"
          echo "Invalidation complete." |& tee -a "$GITHUB_WORKSPACE/deploy.log"

      - name: Update Comment
        if: ${{ steps.fc.outputs.comment-id != '' }}
        uses: peter-evans/create-or-update-comment@v4
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          body: |
            Deploy successful! [View draft](${{ steps.draft_url.outputs.url }})
          reactions: hooray

      - name: Upload Deploy Log
        uses: actions/upload-artifact@v6
        if: always()
        with:
          name: deploy.log
          path: ${{ github.workspace }}/deploy.log
deploy-storybook .github/workflows/deploy-storybook.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest
Jobs
deploy
Actions
actions/configure-pages, actions/upload-pages-artifact, actions/deploy-pages
Commands
  • cd src/frontend && npm ci
  • cd src/frontend && npm run build-storybook
View raw YAML
name: Deploy Storybook to GitHub Pages

on:
  push:
    branches:
      - main
    paths:
      - 'src/frontend/**/*.stories.*'
      - 'src/frontend/.storybook/**'
      - 'src/frontend/package.json'
      - '.github/workflows/deploy-storybook.yml'
  workflow_dispatch: # Allow manual trigger

jobs:
  deploy:
    name: Deploy Storybook
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pages: write
      id-token: write

    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: src/frontend/package-lock.json

      - name: Install dependencies
        run: cd src/frontend && npm ci

      - name: Build Storybook
        run: cd src/frontend && npm run build-storybook

      - name: Setup Pages
        uses: actions/configure-pages@v4

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: src/frontend/storybook-static

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

deploy_gh-pages .github/workflows/deploy_gh-pages.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
deploy
Actions
peaceiris/actions-gh-pages
Commands
  • cd docs && npm install
  • cd docs && npm run build
View raw YAML
name: Deploy to GitHub Pages

on:
  workflow_dispatch:
    inputs:
      branch:
        description: "Branch to deploy docs from"
        required: false
        default: "main"
        type: string

jobs:
  deploy:
    name: Deploy to GitHub Pages
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ inputs.branch }}
      - uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: ./docs/package-lock.json

      - name: Install dependencies
        run: cd docs && npm install
      - name: Build website
        run: cd docs && npm run build
        env:
          SEGMENT_PUBLIC_WRITE_KEY: ${{ vars.DOCS_PROD_IBM_SEGMENT_PUBLIC_WRITE_KEY }}

      # Popular action to deploy to GitHub Pages:
      # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          # Build output to publish to the `gh-pages` branch:
          publish_dir: ./docs/build
          # The following lines assign commit authorship to the official
          # GH-Actions bot for deploys to `gh-pages` branch:
          # https://github.com/actions/checkout/issues/13#issuecomment-724415212
          # The GH actions bot is used by default if you didn't specify the two fields.
          # You can swap them out with your own user credentials.
docker-build matrix .github/workflows/docker-build.yml
Triggers
workflow_call, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral, self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral, ubuntu-latest
Jobs
get-version, setup, build, build_components, restart-space
Matrix
component, include, include.component, include.dockerfile, include.langflow_image, include.tags, python-version→ ./docker/build_and_push_backend.Dockerfile, ./docker/frontend/build_and_push_frontend.Dockerfile, 3.13, docker-backend, docker-frontend, ghcr-backend, ghcr-frontend, ghcr.io/langflow-ai/langflow-backend:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-backend:latest, ghcr.io/langflow-ai/langflow-frontend:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-frontend:latest, ghcr.io/langflow-ai/langflow:${{ needs.get-version.outputs.version }}, langflowai/langflow-backend:${{ needs.get-version.outputs.version }},langflowai/langflow-backend:latest, langflowai/langflow-frontend:${{ needs.get-version.outputs.version }},langflowai/langflow-frontend:latest, langflowai/langflow:${{ needs.get-version.outputs.version }}
Actions
astral-sh/setup-uv, docker/setup-buildx-action, docker/login-action, Wandalen/wretry.action, docker/login-action, Wandalen/wretry.action, docker/setup-buildx-action, docker/login-action, docker/login-action, Wandalen/wretry.action, astral-sh/setup-uv
Commands
  • if [[ "${{ inputs.warning_check }}" == "false" ]]; then echo "Warning - use docker-build-v2 unless you have a very valid reason for using this deprecated workflow. By setting to True, you acknowledge all risks of using a possibly breaking workflow." exit 1 else echo "User has acknowledged the risks of using this deprecated workflow. Proceeding with build." fi
  • # due to our how we split packages, we need to have a main version to check out. echo "Must specify a main version to check out." exit 1
  • # Produces the versions we will use to tag the docker images with. if [[ "${{ inputs.release_type }}" == "base" && "${{ inputs.base_version }}" == '' ]]; then echo "Must specify a base version for base release type." exit 1 fi if [[ "${{ inputs.release_type }}" == "nightly-base" && "${{ inputs.base_version }}" == '' ]]; then echo "Must specify a base version for nightly-base release type." exit 1 fi if [[ ("${{ inputs.release_type }}" == "main" || "${{ inputs.release_type }}" == "main-all") && "${{ inputs.main_version }}" == '' ]]; then echo "Must specify a main version for main release type." exit 1 fi if [[ "${{ inputs.release_type }}" == "main-ep" && "${{ inputs.main_version }}" == '' ]]; then echo "Must specify a main version for main-ep release type." exit 1 fi if [[ ("${{ inputs.release_type }}" == "nightly-main" || "${{ inputs.release_type }}" == "nightly-main-all") && "${{ inputs.main_version }}" == '' ]]; then echo "Must specify a main version for nightly-main release type." exit 1 fi if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then version=${{ inputs.base_version }} echo "base version=${{ inputs.base_version }}" echo version=$version echo version=$version >> $GITHUB_OUTPUT elif [[ "${{ inputs.release_type }}" == "main" || "${{ inputs.release_type }}" == "main-ep" || "${{ inputs.release_type }}" == "nightly-main" || "${{ inputs.release_type }}" == "nightly-main-all" ]]; then version=${{ inputs.main_version }} echo version=$version echo version=$version >> $GITHUB_OUTPUT else echo "No version or ref specified. Exiting the workflow." exit 1 fi
  • version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1) if [ -z "$version" ]; then echo "Failed to extract version from uv tree output" exit 1 fi echo version=$version echo version=$version >> $GITHUB_OUTPUT
  • version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $2}' | sed 's/^v//') echo version=$version echo version=$version >> $GITHUB_OUTPUT
  • nightly_suffix='' if [[ "${{ inputs.release_type }}" == "nightly-base" || "${{ inputs.release_type }}" == "nightly-main" || "${{ inputs.release_type }}" == "nightly-main-all" ]]; then nightly_suffix="-nightly" fi if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then # LANGFLOW-BASE RELEASE echo "docker_tags=langflowai/langflow${nightly_suffix}:base-${{ needs.get-version.outputs.version }},langflowai/langflow${nightly_suffix}:base-latest" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:base-${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow${nightly_suffix}:base-latest" >> $GITHUB_OUTPUT echo "file=./docker/build_and_push_base.Dockerfile" >> $GITHUB_OUTPUT else if [[ "${{ inputs.pre_release }}" == "true" ]]; then # LANGFLOW-MAIN PRE-RELEASE echo "docker_tags=langflowai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }}" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }}" >> $GITHUB_OUTPUT echo "file=./docker/build_and_push.Dockerfile" >> $GITHUB_OUTPUT elif [[ "${{ inputs.release_type }}" == "main-ep" ]]; then # LANGFLOW-MAIN (ENTRYPOINT) RELEASE echo "docker_tags=langflowai/langflow-ep${nightly_suffix}:${{ needs.get-version.outputs.version }},langflowai/langflow-ep${nightly_suffix}:latest" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow-ep${nightly_suffix}:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-ep${nightly_suffix}:latest" >> $GITHUB_OUTPUT echo "file=./docker/build_and_push_ep.Dockerfile" >> $GITHUB_OUTPUT elif [[ "${{ inputs.release_type }}" == "main" || "${{ inputs.release_type }}" == "nightly-main" ]]; then # LANGFLOW-MAIN RELEASE echo "docker_tags=langflowai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }},langflowai/langflow${nightly_suffix}:latest" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow${nightly_suffix}:latest" >> $GITHUB_OUTPUT echo "file=./docker/build_and_push.Dockerfile" >> $GITHUB_OUTPUT elif [[ "${{ inputs.release_type }}" == "main-all" || "${{ inputs.release_type }}" == "nightly-main-all" ]]; then # LANGFLOW-MAIN (ALL OPTIONAL DEPS) RELEASE echo "docker_tags=langflowai/langflow-all${nightly_suffix}:${{ needs.get-version.outputs.version }},langflowai/langflow-all${nightly_suffix}:latest" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow-all${nightly_suffix}:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-all${nightly_suffix}:latest" >> $GITHUB_OUTPUT echo "file=./docker/build_and_push_with_extras.Dockerfile" >> $GITHUB_OUTPUT else echo "Invalid release type. Exiting the workflow." exit 1 fi fi
  • if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then uv sync --directory src/backend/base --no-dev --no-sources else uv sync --no-dev --no-sources fi
  • echo "=== Docker System Usage Before Cleanup ===" docker system df || true docker buildx du || true echo "=== Cleaning up Docker System ===" docker system prune -af --volumes || true docker buildx prune -af || true echo "=== Docker System Usage After Cleanup ===" docker system df || true docker buildx du || true
View raw YAML
name: Docker Build and Push
run-name: Docker Build and Push @${{ inputs.release_type }} by @${{ github.actor }}
on:
  workflow_call:
    inputs:
      main_version:
        required: true
        type: string
        description: "Main version to tag images with. Required for both main and base releases."
      base_version:
        required: false
        type: string
        description: "Base version to tag images with. Required for base release type."
      release_type:
        required: true
        type: string
        description: "Release type. One of 'main', 'main-ep', 'base', 'nightly-main', 'nightly-base', 'main-all', 'nightly-main-all'."
      pre_release:
        required: false
        type: boolean
        default: false
      ref:
        required: false
        type: string
        description: "Ref to check out. If not specified, will default to the main version or current branch."
      warning_check:
        required: true
        type: boolean
        description: "Warning - use docker-build-v2 unless you have a very valid reason for using this deprecated workflow. By setting to True, you acknowledge all risks of using a possibly breaking workflow."
        default: false

  workflow_dispatch:
    inputs:
      main_version:
        description: "Main version to tag images with. Required for both main and base releases."
        required: false
        type: string
      base_version:
        description: "Base version to tag images with. Required for base release type."
        required: false
        type: string
      release_type:
        description: "Type of release. One of 'main', 'main-ep', 'base', 'nightly-main', 'nightly-base', 'main-all', 'nightly-main-all'."
        required: true
        type: string
      pre_release:
        description: "Whether this is a pre-release."
        required: false
        type: boolean
        default: false
      ref:
        required: false
        type: string
        description: "Ref to check out. If not specified, will default to the main version or current branch."
      warning_check:
        description: "Warning - use docker-build-v2 unless you have a very valid reason for using this deprecated workflow. By setting to True, you acknowledge all risks of using a possibly breaking workflow."
        required: true
        type: boolean
        default: false

env:
  PYTHON_VERSION: "3.13"
  TEST_TAG: "langflowai/langflow:test"

jobs:
  get-version:
    name: Get Version
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get-version-input.outputs.version || steps.get-version-base.outputs.version || steps.get-version-main.outputs.version }}
    steps:
      - name: Verify warning check
        run: |
          if [[ "${{ inputs.warning_check }}" == "false" ]]; then
            echo "Warning - use docker-build-v2 unless you have a very valid reason for using this deprecated workflow. By setting to True, you acknowledge all risks of using a possibly breaking workflow."
            exit 1
          else
            echo "User has acknowledged the risks of using this deprecated workflow. Proceeding with build."
          fi

      - name: Verify a main version exists
        if: ${{ inputs.main_version == '' }}
        run: |
          # due to our how we split packages, we need to have a main version to check out.
          echo "Must specify a main version to check out."
          exit 1

      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || inputs.main_version || github.ref }}
          persist-credentials: true

      - name: Get Version to Tag
        if: ${{ inputs.main_version != '' }}
        id: get-version-input
        run: |
          # Produces the versions we will use to tag the docker images with.

          if [[ "${{ inputs.release_type }}" == "base" && "${{ inputs.base_version }}" == '' ]]; then
            echo "Must specify a base version for base release type."
            exit 1
          fi

          if [[ "${{ inputs.release_type }}" == "nightly-base" && "${{ inputs.base_version }}" == '' ]]; then
            echo "Must specify a base version for nightly-base release type."
            exit 1
          fi

          if [[ ("${{ inputs.release_type }}" == "main" || "${{ inputs.release_type }}" == "main-all") && "${{ inputs.main_version }}" == '' ]]; then
            echo "Must specify a main version for main release type."
            exit 1
          fi

          if [[ "${{ inputs.release_type }}" == "main-ep" && "${{ inputs.main_version }}" == '' ]]; then
            echo "Must specify a main version for main-ep release type."
            exit 1
          fi

          if [[ ("${{ inputs.release_type }}" == "nightly-main" || "${{ inputs.release_type }}" == "nightly-main-all") && "${{ inputs.main_version }}" == '' ]]; then
            echo "Must specify a main version for nightly-main release type."
            exit 1
          fi

          if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then
            version=${{ inputs.base_version }}
            echo "base version=${{ inputs.base_version }}"
            echo version=$version
            echo version=$version >> $GITHUB_OUTPUT
          elif [[ "${{ inputs.release_type }}" == "main" || "${{ inputs.release_type }}" == "main-ep"  || "${{ inputs.release_type }}" == "nightly-main" || "${{ inputs.release_type }}" == "nightly-main-all" ]]; then
            version=${{ inputs.main_version }}
            echo version=$version
            echo version=$version >> $GITHUB_OUTPUT
          else
            echo "No version or ref specified. Exiting the workflow."
            exit 1
          fi
      - name: Get Version Base
        if: ${{ inputs.base_version == '' && (inputs.release_type == 'base' || inputs.release_type == 'nightly-base') }}
        id: get-version-base
        run: |
          version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1)
          if [ -z "$version" ]; then
            echo "Failed to extract version from uv tree output"
            exit 1
          fi
          echo version=$version
          echo version=$version >> $GITHUB_OUTPUT
      - name: Get Version Main
        if: ${{ inputs.main_version == '' && (inputs.release_type == 'main' || inputs.release_type == 'main-ep' || inputs.release_type == 'nightly-main' || inputs.release_type == 'main-all' || inputs.release_type == 'nightly-main-all') }}
        id: get-version-main
        run: |
          version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $2}' | sed 's/^v//')
          echo version=$version
          echo version=$version >> $GITHUB_OUTPUT
  setup:
    runs-on: ubuntu-latest
    needs: get-version
    outputs:
      docker_tags: ${{ steps.set-vars.outputs.docker_tags }}
      ghcr_tags: ${{ steps.set-vars.outputs.ghcr_tags }}
      file: ${{ steps.set-vars.outputs.file }}
    steps:
      - name: Set Dockerfile and Tags
        id: set-vars
        run: |
          nightly_suffix=''
          if [[ "${{ inputs.release_type }}" == "nightly-base" || "${{ inputs.release_type }}" == "nightly-main" || "${{ inputs.release_type }}" == "nightly-main-all" ]]; then
            nightly_suffix="-nightly"
          fi

          if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then
            # LANGFLOW-BASE RELEASE
            echo "docker_tags=langflowai/langflow${nightly_suffix}:base-${{ needs.get-version.outputs.version }},langflowai/langflow${nightly_suffix}:base-latest" >> $GITHUB_OUTPUT
            echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:base-${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow${nightly_suffix}:base-latest" >> $GITHUB_OUTPUT
            echo "file=./docker/build_and_push_base.Dockerfile" >> $GITHUB_OUTPUT
          else
            if [[ "${{ inputs.pre_release }}" == "true" ]]; then
              # LANGFLOW-MAIN PRE-RELEASE
              echo "docker_tags=langflowai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }}" >> $GITHUB_OUTPUT
              echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }}" >> $GITHUB_OUTPUT
              echo "file=./docker/build_and_push.Dockerfile" >> $GITHUB_OUTPUT
            elif [[ "${{ inputs.release_type }}" == "main-ep" ]]; then
              # LANGFLOW-MAIN (ENTRYPOINT) RELEASE
              echo "docker_tags=langflowai/langflow-ep${nightly_suffix}:${{ needs.get-version.outputs.version }},langflowai/langflow-ep${nightly_suffix}:latest" >> $GITHUB_OUTPUT
              echo "ghcr_tags=ghcr.io/langflow-ai/langflow-ep${nightly_suffix}:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-ep${nightly_suffix}:latest" >> $GITHUB_OUTPUT
              echo "file=./docker/build_and_push_ep.Dockerfile" >> $GITHUB_OUTPUT
            elif [[ "${{ inputs.release_type }}" == "main" || "${{ inputs.release_type }}" == "nightly-main" ]]; then
              # LANGFLOW-MAIN RELEASE
              echo "docker_tags=langflowai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }},langflowai/langflow${nightly_suffix}:latest" >> $GITHUB_OUTPUT
              echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow${nightly_suffix}:latest" >> $GITHUB_OUTPUT
              echo "file=./docker/build_and_push.Dockerfile" >> $GITHUB_OUTPUT
            elif [[ "${{ inputs.release_type }}" == "main-all" || "${{ inputs.release_type }}" == "nightly-main-all" ]]; then
              # LANGFLOW-MAIN (ALL OPTIONAL DEPS) RELEASE
              echo "docker_tags=langflowai/langflow-all${nightly_suffix}:${{ needs.get-version.outputs.version }},langflowai/langflow-all${nightly_suffix}:latest" >> $GITHUB_OUTPUT
              echo "ghcr_tags=ghcr.io/langflow-ai/langflow-all${nightly_suffix}:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-all${nightly_suffix}:latest" >> $GITHUB_OUTPUT
              echo "file=./docker/build_and_push_with_extras.Dockerfile" >> $GITHUB_OUTPUT
            else
              echo "Invalid release type. Exiting the workflow."
              exit 1
            fi
          fi
  build:
    runs-on: [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    needs: [get-version, setup]
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || inputs.main_version || github.ref }}
          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: |
          if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then
            uv sync --directory src/backend/base --no-dev --no-sources
          else
            uv sync --no-dev --no-sources
          fi

      - name: Docker System Info and Cleanup
        run: |
          echo "=== Docker System Usage Before Cleanup ==="
          docker system df || true
          docker buildx du || true

          echo "=== Cleaning up Docker System ==="
          docker system prune -af --volumes || true
          docker buildx prune -af || true

          echo "=== Docker System Usage After Cleanup ==="
          docker system df || true
          docker buildx du || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          version: latest
          driver: docker-container
          driver-opts: |
            image=moby/buildkit:v0.22.0
            network=host

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and Push to Docker Hub
        uses: Wandalen/wretry.action@master
        with:
          action: docker/build-push-action@v6
          with: |
            context: .
            push: true
            file: ${{ needs.setup.outputs.file }}
            tags: ${{ needs.setup.outputs.docker_tags }}
            platforms: linux/amd64,linux/arm64
            cache-from: type=gha
            cache-to: type=gha,mode=max

      - name: Login to Github Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to Github Container Registry
        uses: Wandalen/wretry.action@master
        with:
          action: docker/build-push-action@v6
          with: |
            context: .
            push: true
            file: ${{ needs.setup.outputs.file }}
            tags: ${{ needs.setup.outputs.ghcr_tags }}
            platforms: linux/amd64,linux/arm64
            cache-from: type=gha
            cache-to: type=gha,mode=max

  build_components:
    if: ${{ inputs.release_type == 'main' }}
    runs-on: [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    permissions:
      packages: write
    needs: [build, get-version]
    strategy:
      matrix:
        component:
          [docker-backend, docker-frontend, ghcr-backend, ghcr-frontend]
        include:
          - component: docker-backend
            dockerfile: ./docker/build_and_push_backend.Dockerfile
            tags: langflowai/langflow-backend:${{ needs.get-version.outputs.version }},langflowai/langflow-backend:latest
            langflow_image: langflowai/langflow:${{ needs.get-version.outputs.version }}
          - component: docker-frontend
            dockerfile: ./docker/frontend/build_and_push_frontend.Dockerfile
            tags: langflowai/langflow-frontend:${{ needs.get-version.outputs.version }},langflowai/langflow-frontend:latest
            langflow_image: langflowai/langflow:${{ needs.get-version.outputs.version }}
          - component: ghcr-backend
            dockerfile: ./docker/build_and_push_backend.Dockerfile
            tags: ghcr.io/langflow-ai/langflow-backend:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-backend:latest
            langflow_image: ghcr.io/langflow-ai/langflow:${{ needs.get-version.outputs.version }}
          - component: ghcr-frontend
            dockerfile: ./docker/frontend/build_and_push_frontend.Dockerfile
            tags: ghcr.io/langflow-ai/langflow-frontend:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-frontend:latest
            langflow_image: ghcr.io/langflow-ai/langflow:${{ needs.get-version.outputs.version }}
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || inputs.main_version || github.ref }}

      - name: Docker System Info and Cleanup
        run: |
          echo "=== Docker System Usage Before Cleanup ==="
          docker system df || true
          docker buildx du || true

          echo "=== Cleaning up Docker System ==="
          docker system prune -af --volumes || true
          docker buildx prune -af || true

          echo "=== Docker System Usage After Cleanup ==="
          docker system df || true
          docker buildx du || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          version: latest
          driver: docker-container
          driver-opts: |
            image=moby/buildkit:v0.22.0
            network=host

      - name: Login to Docker Hub
        if: ${{ matrix.component == 'docker-backend' }} || ${{ matrix.component == 'docker-frontend' }}
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to Github Container Registry
        if: ${{ matrix.component == 'ghcr-backend' }} || ${{ matrix.component == 'ghcr-frontend' }}
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Wait for propagation (for backend)
        run: sleep 120

      - name: Build and push ${{ matrix.component }}
        uses: Wandalen/wretry.action@master
        with:
          action: docker/build-push-action@v6
          with: |
            context: .
            push: true
            build-args: |
              LANGFLOW_IMAGE=${{ matrix.langflow_image }}
            file: ${{ matrix.dockerfile }}
            tags: ${{ matrix.tags }}
            # provenance: false will result in a single manifest for all platforms which makes the image pullable from arm64 machines via the emulation (e.g. Apple Silicon machines)
            provenance: false

  restart-space:
    name: Restart HuggingFace Spaces
    if: ${{ inputs.release_type == 'main' }}
    runs-on: ubuntu-latest
    needs: [build, get-version]
    strategy:
      matrix:
        python-version:
          - "3.13"
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || inputs.main_version || github.ref }}
      - name: "Setup Environment"
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false

      - name: Restart HuggingFace Spaces Build
        run: |
          uv run ./scripts/factory_restart_space.py --space "Langflow/Langflow" --token ${{ secrets.HUGGINGFACE_API_TOKEN }}
docker-build-v2 matrix .github/workflows/docker-build-v2.yml
Triggers
workflow_call, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ${{ matrix.runner }}, ${{ matrix.runner }}, ${{ matrix.runner }}, ${{ matrix.runner }}, ${{ matrix.runner }}, ${{ matrix.runner }}, ubuntu-latest
Jobs
determine-base-version, determine-main-version, build-base, build-main, build-main-backend, build-main-frontend, build-main-ep, build-main-all, create-manifest
Matrix
include, include.arch, include.runner→ ARM64, Langflow-runner, amd64, arm64, langflow-ai-arm64-40gb-ephemeral, linux, self-hosted
Actions
astral-sh/setup-uv, astral-sh/setup-uv, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action, docker/build-push-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action, docker/build-push-action, docker/setup-buildx-action, docker/login-action, docker/build-push-action, docker/login-action, docker/build-push-action, docker/setup-buildx-action, docker/login-action, docker/build-push-action, docker/login-action, docker/build-push-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action, docker/build-push-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action, docker/build-push-action, docker/login-action, docker/login-action
Commands
  • 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" 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) echo "Latest base release version: $last_released_version" fi if [ "$version" = "$last_released_version" ]; then echo "Base docker version $version is already released. Skipping release." echo skipped=true >> $GITHUB_OUTPUT exit 0 else echo version=$version >> $GITHUB_OUTPUT echo skipped=false >> $GITHUB_OUTPUT fi
  • version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | 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" 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) echo "Latest main release version: $last_released_version" fi if [ "$version" = "$last_released_version" ]; then echo "Main docker version $version is already released. Skipping release." echo skipped=true >> $GITHUB_OUTPUT exit 0 else echo version=$version >> $GITHUB_OUTPUT echo skipped=false >> $GITHUB_OUTPUT fi
  • version="${{ needs.determine-base-version.outputs.version }}" echo "docker_tags=langflowai/langflow:base-${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow:base-${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
  • docker system prune -af --volumes || true docker buildx prune -af || true
  • version="${{ needs.determine-main-version.outputs.version }}" echo "docker_tags=langflowai/langflow:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
  • docker system prune -af --volumes || true docker buildx prune -af || true
  • version="${{ needs.determine-main-version.outputs.version }}" echo "docker_tags=langflowai/langflow-backend:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow-backend:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
  • docker system prune -af --volumes || true docker buildx prune -af || true
View raw YAML
name: Docker Build and Push v2
run-name: Docker Build and Push @${{ inputs.release_type }} by @${{ github.actor }}

on:
  workflow_call:
    inputs:
      release_type:
        required: true
        type: string
        description: "Release type. One of 'main', 'main-backend', 'main-frontend', 'main-ep', 'base', 'main-all'."
      pre_release:
        required: false
        type: boolean
        default: false
      ref:
        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."
      push_to_registry:
        required: false
        type: boolean
        default: true
        description: "Whether to push images to registries. Set to false for testing builds without publishing."

  workflow_dispatch:
    inputs:
      release_type:
        description: "Type of release. One of 'main', 'main-backend', 'main-frontend', 'main-ep', 'base', 'main-all'."
        required: true
        type: choice
        options:
          - main
          - main-backend
          - main-frontend
          - main-ep
          - base
          - main-all
      pre_release:
        description: "Whether this is a pre-release."
        required: false
        type: boolean
        default: false
      ref:
        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."
      push_to_registry:
        description: "Whether to push images to registries. Set to false for testing builds without publishing."
        required: false
        type: boolean
        default: false

env:
  PYTHON_VERSION: "3.13"

jobs:
  determine-base-version:
    name: Determine Base Version
    if: ${{ inputs.release_type == 'base' }}
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      skipped: ${{ steps.version.outputs.skipped }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - 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 version
        id: version
        run: |
          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"
            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)
            echo "Latest base release version: $last_released_version"
          fi

          if [ "$version" = "$last_released_version" ]; then
            echo "Base docker version $version is already released. Skipping release."
            echo skipped=true >> $GITHUB_OUTPUT
            exit 0
          else
            echo version=$version >> $GITHUB_OUTPUT
            echo skipped=false >> $GITHUB_OUTPUT
          fi

  determine-main-version:
    name: Determine Main Version
    if: ${{ inputs.release_type != 'base' }}
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      skipped: ${{ steps.version.outputs.skipped }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - 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 version
        id: version
        run: |
          version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | 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"
            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)
            echo "Latest main release version: $last_released_version"
          fi

          if [ "$version" = "$last_released_version" ]; then
            echo "Main docker version $version is already released. Skipping release."
            echo skipped=true >> $GITHUB_OUTPUT
            exit 0
          else
            echo version=$version >> $GITHUB_OUTPUT
            echo skipped=false >> $GITHUB_OUTPUT
          fi

  build-base:
    name: Build Base Package
    needs: [determine-base-version]
    if: ${{ inputs.release_type == 'base' && needs.determine-base-version.outputs.skipped == 'false' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - name: Set tags
        id: tags
        run: |
          version="${{ needs.determine-base-version.outputs.version }}"
          echo "docker_tags=langflowai/langflow:base-${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow:base-${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_base.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_base.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  build-main:
    name: Build Main Package
    needs: [determine-main-version]
    if: ${{ inputs.release_type == 'main' && needs.determine-main-version.outputs.skipped == 'false' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - name: Set tags
        id: tags
        run: |
          version="${{ needs.determine-main-version.outputs.version }}"
          echo "docker_tags=langflowai/langflow:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  build-main-backend:
    name: Build Main Backend Package
    needs: [determine-main-version]
    if: ${{ inputs.release_type == 'main-backend' && needs.determine-main-version.outputs.skipped == 'false' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - name: Set tags
        id: tags
        run: |
          version="${{ needs.determine-main-version.outputs.version }}"
          echo "docker_tags=langflowai/langflow-backend:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow-backend:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          build-args: |
            LANGFLOW_IMAGE=langflowai/langflow:${{ steps.version.outputs.version }}-${{ matrix.arch }}
          file: ./docker/build_and_push_backend.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          build-args: |
            LANGFLOW_IMAGE=ghcr.io/langflow-ai/langflow:${{ steps.version.outputs.version }}-${{ matrix.arch }}
          file: ./docker/build_and_push_backend.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  build-main-frontend:
    name: Build Main Frontend Package
    needs: [determine-main-version]
    if: ${{ inputs.release_type == 'main-frontend' && needs.determine-main-version.outputs.skipped == 'false' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - name: Set tags
        id: tags
        run: |
          version="${{ needs.determine-main-version.outputs.version }}"
          echo "docker_tags=langflowai/langflow-frontend:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow-frontend:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/frontend/build_and_push_frontend.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/frontend/build_and_push_frontend.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  build-main-ep:
    name: Build Main EP Package
    needs: [determine-main-version]
    if: ${{ inputs.release_type == 'main-ep' && needs.determine-main-version.outputs.skipped == 'false' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - name: Set tags
        id: tags
        run: |
          version="${{ needs.determine-main-version.outputs.version }}"
          echo "docker_tags=langflowai/langflow-ep:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow-ep:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_ep.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_ep.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  build-main-all:
    name: Build Main All Package
    needs: [determine-main-version]
    if: ${{ inputs.release_type == 'main-all' && needs.determine-main-version.outputs.skipped == 'false' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - name: Set tags
        id: tags
        run: |
          version="${{ needs.determine-main-version.outputs.version }}"
          echo "docker_tags=langflowai/langflow-all:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow-all:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_with_extras.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_with_extras.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  create-manifest:
    name: Create Multi-Arch Manifest
    needs:
      [
        build-base,
        build-main,
        build-main-backend,
        build-main-frontend,
        build-main-ep,
        build-main-all,
        determine-base-version,
        determine-main-version,
      ]
    runs-on: ubuntu-latest
    if: ${{ always() && inputs.push_to_registry && (needs.build-base.result == 'success' || needs.build-main.result == 'success' || needs.build-main-backend.result == 'success' || needs.build-main-frontend.result == 'success' || needs.build-main-ep.result == 'success' || needs.build-main-all.result == 'success') }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - name: Get version
        id: version
        run: |
          if [[ "${{ inputs.release_type }}" == "base" ]]; then
            version=$(uv tree 2>/dev/null | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1)
          else
            version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | cut -d' ' -f2 | sed 's/^v//')
          fi
          echo "Using version: $version"
          echo version=$version >> $GITHUB_OUTPUT

      - name: Set tags
        id: tags
        run: |
          version="${{ needs.determine-main-version.outputs.version }}"
          case "${{ inputs.release_type }}" in
            "base")
              version="${{ needs.determine-base-version.outputs.version }}"
              if [[ "${{ inputs.pre_release }}" == "true" ]]; then
                echo "final_tags=langflowai/langflow:base-${version},ghcr.io/langflow-ai/langflow:base-${version}" >> $GITHUB_OUTPUT
              else
                echo "final_tags=langflowai/langflow:base-${version},langflowai/langflow:base-latest,ghcr.io/langflow-ai/langflow:base-${version},ghcr.io/langflow-ai/langflow:base-latest" >> $GITHUB_OUTPUT
              fi
              echo "arch_base=langflowai/langflow:base-${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow:base-${version}" >> $GITHUB_OUTPUT
              ;;
            "main")
              if [[ "${{ inputs.pre_release }}" == "true" ]]; then
                echo "final_tags=langflowai/langflow:${version},ghcr.io/langflow-ai/langflow:${version}" >> $GITHUB_OUTPUT
              else
                echo "final_tags=langflowai/langflow:${version},langflowai/langflow:latest,ghcr.io/langflow-ai/langflow:${version},ghcr.io/langflow-ai/langflow:latest" >> $GITHUB_OUTPUT
              fi
              echo "arch_base=langflowai/langflow:${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow:${version}" >> $GITHUB_OUTPUT
              ;;
            "main-backend")
              if [[ "${{ inputs.pre_release }}" == "true" ]]; then
                echo "final_tags=langflowai/langflow-backend:${version},ghcr.io/langflow-ai/langflow-backend:${version}" >> $GITHUB_OUTPUT
              else
                echo "final_tags=langflowai/langflow-backend:${version},langflowai/langflow-backend:latest,ghcr.io/langflow-ai/langflow-backend:${version},ghcr.io/langflow-ai/langflow-backend:latest" >> $GITHUB_OUTPUT
              fi
              echo "arch_base=langflowai/langflow-backend:${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow-backend:${version}" >> $GITHUB_OUTPUT
              ;;
            "main-frontend")
              if [[ "${{ inputs.pre_release }}" == "true" ]]; then
                echo "final_tags=langflowai/langflow-frontend:${version},ghcr.io/langflow-ai/langflow-frontend:${version}" >> $GITHUB_OUTPUT
              else
                echo "final_tags=langflowai/langflow-frontend:${version},langflowai/langflow-frontend:latest,ghcr.io/langflow-ai/langflow-frontend:${version},ghcr.io/langflow-ai/langflow-frontend:latest" >> $GITHUB_OUTPUT
              fi
              echo "arch_base=langflowai/langflow-frontend:${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow-frontend:${version}" >> $GITHUB_OUTPUT
              ;;
            "main-ep")
              if [[ "${{ inputs.pre_release }}" == "true" ]]; then
                echo "final_tags=langflowai/langflow-ep:${version},ghcr.io/langflow-ai/langflow-ep:${version}" >> $GITHUB_OUTPUT
              else
                echo "final_tags=langflowai/langflow-ep:${version},langflowai/langflow-ep:latest,ghcr.io/langflow-ai/langflow-ep:${version},ghcr.io/langflow-ai/langflow-ep:latest" >> $GITHUB_OUTPUT
              fi
              echo "arch_base=langflowai/langflow-ep:${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow-ep:${version}" >> $GITHUB_OUTPUT
              ;;
            "main-all")
              if [[ "${{ inputs.pre_release }}" == "true" ]]; then
                echo "final_tags=langflowai/langflow-all:${version},ghcr.io/langflow-ai/langflow-all:${version}" >> $GITHUB_OUTPUT
              else
                echo "final_tags=langflowai/langflow-all:${version},langflowai/langflow-all:latest,ghcr.io/langflow-ai/langflow-all:${version},ghcr.io/langflow-ai/langflow-all:latest" >> $GITHUB_OUTPUT
              fi
              echo "arch_base=langflowai/langflow-all:${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow-all:${version}" >> $GITHUB_OUTPUT
              ;;
            *)
              echo "Error: Invalid release_type: ${{ inputs.release_type }}" >&2
              exit 1
              ;;
          esac

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Create and push multi-arch manifests
        run: |
          # Split tags and create manifests for each
          IFS=',' read -ra TAGS <<< "${{ steps.tags.outputs.final_tags }}"
          for tag in "${TAGS[@]}"; do
            echo "Creating manifest for $tag"

            # Determine architecture-specific tags
            if [[ "$tag" == *"langflowai"* ]]; then
              amd64_tag="${{ steps.tags.outputs.arch_base }}-amd64"
              arm64_tag="${{ steps.tags.outputs.arch_base }}-arm64"
            else
              amd64_tag="${{ steps.tags.outputs.ghcr_arch_base }}-amd64"
              arm64_tag="${{ steps.tags.outputs.ghcr_arch_base }}-arm64"
            fi

            docker buildx imagetools create \
              --tag "$tag" \
              "$amd64_tag" \
              "$arm64_tag"
          done
docker-nightly-build matrix .github/workflows/docker-nightly-build.yml
Triggers
workflow_call, workflow_dispatch
Runs on
${{ matrix.runner }}, ${{ matrix.runner }}, ${{ matrix.runner }}, ubuntu-latest
Jobs
build-nightly-base, build-nightly-main, build-nightly-main-all, create-nightly-manifest
Matrix
include, include.arch, include.runner→ ARM64, Langflow-runner, amd64, arm64, langflow-ai-arm64-40gb-ephemeral, linux, self-hosted
Actions
astral-sh/setup-uv, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action, docker/build-push-action, astral-sh/setup-uv, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action, docker/build-push-action, astral-sh/setup-uv, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action, docker/build-push-action, astral-sh/setup-uv, docker/login-action, docker/login-action
Commands
  • echo "Extracting base version from pyproject.toml" version=$(uv tree 2>/dev/null | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1) # Verify nightly tag format if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then echo "Base version format is incorrect. Must be in the format (e.g.) v1.5.1.dev36" exit 1 fi echo "Using version: $version" echo version=$version >> $GITHUB_OUTPUT
  • version="${{ steps.version.outputs.version }}" echo "docker_tags=langflowai/langflow-nightly:base-${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow-nightly:base-${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
  • docker system prune -af --volumes || true docker buildx prune -af || true
  • echo "Extracting main version from pyproject.toml" version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | cut -d' ' -f2 | sed 's/^v//') # Verify nightly tag format if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then echo "Main version format is incorrect. Must be in the format (e.g.) v1.5.1.dev36" exit 1 fi echo "Using version: $version" echo version=$version >> $GITHUB_OUTPUT
  • version="${{ steps.version.outputs.version }}" echo "docker_tags=langflowai/langflow-nightly:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow-nightly:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
  • docker system prune -af --volumes || true docker buildx prune -af || true
  • echo "Extracting main version from pyproject.toml" version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | cut -d' ' -f2 | sed 's/^v//') # Verify nightly tag format if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then echo "Main version format is incorrect. Must be in the format (e.g.) v1.5.1.dev36" exit 1 fi echo "Using version: $version" echo version=$version >> $GITHUB_OUTPUT
  • version="${{ steps.version.outputs.version }}" echo "docker_tags=langflowai/langflow-nightly-all:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT echo "ghcr_tags=ghcr.io/langflow-ai/langflow-nightly-all:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
View raw YAML
name: Docker Nightly Build and Push
run-name: Docker Nightly Build @${{ inputs.release_type }} by @${{ github.actor }}

on:
  workflow_call:
    inputs:
      release_type:
        required: true
        type: string
        description: "Nightly release type. One of 'nightly-main', 'nightly-base', 'nightly-main-all'."
      ref:
        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."
      push_to_registry:
        required: false
        type: boolean
        default: true
        description: "Whether to push images to registries. Set to false for testing builds without publishing."

  workflow_dispatch:
    inputs:
      release_type:
        description: "Nightly release type. One of 'nightly-main', 'nightly-base', 'nightly-main-all'."
        required: true
        type: choice
        options:
          - nightly-main
          - nightly-base
          - nightly-main-all
      ref:
        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. Note that if running via Github Actions, this tag (formatted as, e.g., v1.5.1.dev36) must be manually created and pushed to a branch prior to running the workflow."
      push_to_registry:
        description: "Whether to push images to registries. Set to false for testing builds without publishing."
        required: false
        type: boolean
        default: false

env:
  PYTHON_VERSION: "3.13"

jobs:
  build-nightly-base:
    name: Build Nightly Base Package
    if: ${{ inputs.release_type == 'nightly-base' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - 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: Get version for tagging
        id: version
        run: |
          echo "Extracting base version from pyproject.toml"
          version=$(uv tree 2>/dev/null | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1)


          # Verify nightly tag format
          if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then
            echo "Base version format is incorrect. Must be in the format (e.g.) v1.5.1.dev36"
            exit 1
          fi

          echo "Using version: $version"
          echo version=$version >> $GITHUB_OUTPUT

      - name: Set nightly tags
        id: tags
        run: |
          version="${{ steps.version.outputs.version }}"
          echo "docker_tags=langflowai/langflow-nightly:base-${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow-nightly:base-${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_base.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_base.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  build-nightly-main:
    name: Build Nightly Main Package
    if: ${{ inputs.release_type == 'nightly-main' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - 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: Get version for tagging
        id: version
        run: |
          echo "Extracting main version from pyproject.toml"
          version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | cut -d' ' -f2 | sed 's/^v//')

          # Verify nightly tag format
          if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then
            echo "Main version format is incorrect. Must be in the format (e.g.) v1.5.1.dev36"
            exit 1
          fi

          echo "Using version: $version"
          echo version=$version >> $GITHUB_OUTPUT

      - name: Set nightly tags
        id: tags
        run: |
          version="${{ steps.version.outputs.version }}"
          echo "docker_tags=langflowai/langflow-nightly:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow-nightly:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  build-nightly-main-all:
    name: Build Nightly Main All Package
    if: ${{ inputs.release_type == 'nightly-main-all' }}
    strategy:
      matrix:
        include:
          - arch: amd64
            runner: [Langflow-runner]
          - arch: arm64
            runner:
              [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - 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: Get version for tagging
        id: version
        run: |
          echo "Extracting main version from pyproject.toml"
          version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | cut -d' ' -f2 | sed 's/^v//')

          # Verify nightly tag format
          if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then
            echo "Main version format is incorrect. Must be in the format (e.g.) v1.5.1.dev36"
            exit 1
          fi

          echo "Using version: $version"
          echo version=$version >> $GITHUB_OUTPUT

      - name: Set nightly tags
        id: tags
        run: |
          version="${{ steps.version.outputs.version }}"
          echo "docker_tags=langflowai/langflow-nightly-all:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
          echo "ghcr_tags=ghcr.io/langflow-ai/langflow-nightly-all:${version}-${{ matrix.arch }}" >> $GITHUB_OUTPUT

      - name: Docker cleanup
        run: |
          docker system prune -af --volumes || true
          docker buildx prune -af || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Build and push to Docker Hub
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_with_extras.Dockerfile
          tags: ${{ steps.tags.outputs.docker_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push to GitHub Container Registry
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ inputs.push_to_registry }}
          file: ./docker/build_and_push_with_extras.Dockerfile
          tags: ${{ steps.tags.outputs.ghcr_tags }}
          platforms: linux/${{ matrix.arch }}
          provenance: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

  create-nightly-manifest:
    name: Create Multi-Arch Nightly Manifest
    needs: [build-nightly-base, build-nightly-main, build-nightly-main-all]
    runs-on: ubuntu-latest
    if: ${{ always() && inputs.push_to_registry && (needs.build-nightly-base.result == 'success' || needs.build-nightly-main.result == 'success' || needs.build-nightly-main-all.result == 'success') }}
    steps:
      - 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: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref }}

      - name: Get version
        id: version
        run: |
          if [[ "${{ inputs.release_type }}" == "nightly-base" ]]; then
            version=$(uv tree 2>/dev/null | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1)
          else
            version=$(uv tree 2>/dev/null | grep '^langflow' | grep -v '^langflow-base' | cut -d' ' -f2 | sed 's/^v//')
          fi
          echo "Using version: $version"
          echo version=$version >> $GITHUB_OUTPUT

      - name: Set nightly tags
        id: tags
        run: |
          version="${{ steps.version.outputs.version }}"
          case "${{ inputs.release_type }}" in
            "nightly-base")
              echo "final_tags=langflowai/langflow-nightly:base-${version},langflowai/langflow-nightly:base-latest,ghcr.io/langflow-ai/langflow-nightly:base-${version},ghcr.io/langflow-ai/langflow-nightly:base-latest" >> $GITHUB_OUTPUT
              echo "arch_base=langflowai/langflow-nightly:base-${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow-nightly:base-${version}" >> $GITHUB_OUTPUT
              ;;
            "nightly-main")
              echo "final_tags=langflowai/langflow-nightly:${version},langflowai/langflow-nightly:latest,ghcr.io/langflow-ai/langflow-nightly:${version},ghcr.io/langflow-ai/langflow-nightly:latest" >> $GITHUB_OUTPUT
              echo "arch_base=langflowai/langflow-nightly:${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow-nightly:${version}" >> $GITHUB_OUTPUT
              ;;
            "nightly-main-all")
              echo "final_tags=langflowai/langflow-nightly-all:${version},langflowai/langflow-nightly-all:latest,ghcr.io/langflow-ai/langflow-nightly-all:${version},ghcr.io/langflow-ai/langflow-nightly-all:latest" >> $GITHUB_OUTPUT
              echo "arch_base=langflowai/langflow-nightly-all:${version}" >> $GITHUB_OUTPUT
              echo "ghcr_arch_base=ghcr.io/langflow-ai/langflow-nightly-all:${version}" >> $GITHUB_OUTPUT
              ;;
            *)
              echo "Error: Invalid release_type: ${{ inputs.release_type }}" >&2
              exit 1
              ;;
          esac

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.TEMP_GHCR_TOKEN}}

      - name: Create and push multi-arch nightly manifests
        run: |
          # Split tags and create manifests for each
          IFS=',' read -ra TAGS <<< "${{ steps.tags.outputs.final_tags }}"
          for tag in "${TAGS[@]}"; do
            echo "Creating manifest for $tag"

            # Determine architecture-specific tags
            if [[ "$tag" == *"langflowai"* ]]; then
              amd64_tag="${{ steps.tags.outputs.arch_base }}-amd64"
              arm64_tag="${{ steps.tags.outputs.arch_base }}-arm64"
            else
              amd64_tag="${{ steps.tags.outputs.ghcr_arch_base }}-amd64"
              arm64_tag="${{ steps.tags.outputs.ghcr_arch_base }}-arm64"
            fi

            docker buildx imagetools create \
              --tag "$tag" \
              "$amd64_tag" \
              "$arm64_tag"
          done
docker_test .github/workflows/docker_test.yml
Triggers
workflow_call, workflow_dispatch
Runs on
self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral
Jobs
test-docker
Actions
docker/login-action
Commands
  • if command -v docker &> /dev/null; then echo "CONTAINER_RUNTIME=docker" >> $GITHUB_ENV elif command -v podman &> /dev/null; then echo "CONTAINER_RUNTIME=podman" >> $GITHUB_ENV else echo "Error: Neither docker nor podman found" exit 1 fi echo "Using container runtime: ${{ env.CONTAINER_RUNTIME }}"
  • echo "=== Docker System Usage Before Cleanup ===" ${{ env.CONTAINER_RUNTIME }} system df || true if command -v docker &> /dev/null; then docker buildx du || true fi echo "=== Cleaning up Docker System ===" ${{ env.CONTAINER_RUNTIME }} system prune -af --volumes || true if command -v docker &> /dev/null; then docker buildx prune -af || true fi echo "=== Docker System Usage After Cleanup ===" ${{ env.CONTAINER_RUNTIME }} system df || true if command -v docker &> /dev/null; then docker buildx du || true fi
  • ${{ env.CONTAINER_RUNTIME }} build -t langflowai/langflow:latest-dev \ -f docker/build_and_push.Dockerfile \ .
  • expected_version=$(cat pyproject.toml | grep version | head -n 1 | cut -d '"' -f 2) version=$(${{ env.CONTAINER_RUNTIME }} run --rm --entrypoint bash langflowai/langflow:latest-dev -c "python -c 'from langflow.utils.version import get_version_info; print(get_version_info()[\"version\"])'") if [ "$expected_version" != "$version" ]; then echo "Expected version: $expected_version" echo "Actual version: $version" exit 1 fi
  • echo "=== Disk usage before cleanup ===" df -h / ${{ env.CONTAINER_RUNTIME }} rmi langflowai/langflow:latest-dev || true ${{ env.CONTAINER_RUNTIME }} system prune -af || true if command -v docker &> /dev/null; then docker buildx prune -af || true fi echo "=== Disk usage after cleanup ===" df -h /
  • ${{ env.CONTAINER_RUNTIME }} build -t langflowai/langflow-backend:latest-dev \ --build-arg LANGFLOW_IMAGE=langflowai/langflow:latest-dev \ -f docker/build_and_push_backend.Dockerfile \ .
  • expected_version=$(cat src/backend/base/pyproject.toml | grep version | head -n 1 | cut -d '"' -f 2) version=$(${{ env.CONTAINER_RUNTIME }} run --rm --entrypoint bash langflowai/langflow-backend:latest-dev -c "python -c 'from langflow.utils.version import get_version_info; print(get_version_info()[\"version\"])'") if [ "$expected_version" != "$version" ]; then echo "Expected version: $expected_version" echo "Actual version: $version" exit 1 fi
  • echo "=== Disk usage before cleanup ===" df -h / ${{ env.CONTAINER_RUNTIME }} rmi langflowai/langflow-backend:latest-dev || true ${{ env.CONTAINER_RUNTIME }} system prune -af || true if command -v docker &> /dev/null; then docker buildx prune -af || true fi echo "=== Disk usage after cleanup ===" df -h /
View raw YAML
name: Test Docker images

on:
  workflow_call:
    secrets:
      DOCKERHUB_USERNAME:
        required: true
      DOCKERHUB_TOKEN:
        required: true
  workflow_dispatch:

env:
  POETRY_VERSION: "1.8.2"

jobs:
  test-docker:
    runs-on: [self-hosted, linux, ARM64, langflow-ai-arm64-40gb-ephemeral]
    name: Test docker images
    steps:
      - uses: actions/checkout@v6
      - name: Set container runtime
        run: |
          if command -v docker &> /dev/null; then
            echo "CONTAINER_RUNTIME=docker" >> $GITHUB_ENV
          elif command -v podman &> /dev/null; then
            echo "CONTAINER_RUNTIME=podman" >> $GITHUB_ENV
          else
            echo "Error: Neither docker nor podman found"
            exit 1
          fi
          echo "Using container runtime: ${{ env.CONTAINER_RUNTIME }}"
      - name: Docker System Info and Cleanup
        run: |
          echo "=== Docker System Usage Before Cleanup ==="
          ${{ env.CONTAINER_RUNTIME }} system df || true
          if command -v docker &> /dev/null; then
            docker buildx du || true
          fi

          echo "=== Cleaning up Docker System ==="
          ${{ env.CONTAINER_RUNTIME }} system prune -af --volumes || true
          if command -v docker &> /dev/null; then
            docker buildx prune -af || true
          fi

          echo "=== Docker System Usage After Cleanup ==="
          ${{ env.CONTAINER_RUNTIME }} system df || true
          if command -v docker &> /dev/null; then
            docker buildx du || true
          fi
      - name: Login to Docker Hub
        if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build image
        run: |
          ${{ env.CONTAINER_RUNTIME }} build -t langflowai/langflow:latest-dev \
            -f docker/build_and_push.Dockerfile \
            .
      - name: Test image
        run: |
          expected_version=$(cat pyproject.toml | grep version | head -n 1 | cut -d '"' -f 2)
          version=$(${{ env.CONTAINER_RUNTIME }} run --rm --entrypoint bash langflowai/langflow:latest-dev -c "python -c 'from langflow.utils.version import get_version_info; print(get_version_info()[\"version\"])'")
          if [ "$expected_version" != "$version" ]; then
              echo "Expected version: $expected_version"
              echo "Actual version: $version"
              exit 1
          fi

      - name: Cleanup after main image
        run: |
          echo "=== Disk usage before cleanup ==="
          df -h /
          ${{ env.CONTAINER_RUNTIME }} rmi langflowai/langflow:latest-dev || true
          ${{ env.CONTAINER_RUNTIME }} system prune -af || true
          if command -v docker &> /dev/null; then
            docker buildx prune -af || true
          fi
          echo "=== Disk usage after cleanup ==="
          df -h /

      - name: Build backend image
        run: |
          ${{ env.CONTAINER_RUNTIME }} build -t langflowai/langflow-backend:latest-dev \
            --build-arg LANGFLOW_IMAGE=langflowai/langflow:latest-dev \
            -f docker/build_and_push_backend.Dockerfile \
            .
      - name: Test backend image
        run: |
          expected_version=$(cat src/backend/base/pyproject.toml | grep version | head -n 1 | cut -d '"' -f 2)
          version=$(${{ env.CONTAINER_RUNTIME }} run --rm --entrypoint bash langflowai/langflow-backend:latest-dev -c "python -c 'from langflow.utils.version import get_version_info; print(get_version_info()[\"version\"])'")
          if [ "$expected_version" != "$version" ]; then
              echo "Expected version: $expected_version"
              echo "Actual version: $version"
              exit 1
          fi

      - name: Cleanup after backend image
        run: |
          echo "=== Disk usage before cleanup ==="
          df -h /
          ${{ env.CONTAINER_RUNTIME }} rmi langflowai/langflow-backend:latest-dev || true
          ${{ env.CONTAINER_RUNTIME }} system prune -af || true
          if command -v docker &> /dev/null; then
            docker buildx prune -af || true
          fi
          echo "=== Disk usage after cleanup ==="
          df -h /

      - name: Build frontend image
        run: |
          ${{ env.CONTAINER_RUNTIME }} build -t langflowai/langflow-frontend:latest-dev \
            -f docker/frontend/build_and_push_frontend.Dockerfile \
            .
docs-update-openapi perms .github/workflows/docs-update-openapi.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
check-openapi-updates
Actions
peter-evans/create-pull-request
Commands
  • sudo apt-get install -y jq
  • if [[ -n $(gh pr list --state open --repo ${{ github.repository }} | grep "docs: OpenAPI spec") ]]; then echo "There is already an open PR with updates to the OpenAPI spec. Merge or close that PR first. Skipping." echo "pr_exists=true" >> $GITHUB_OUTPUT else echo "pr_exists=false" >> $GITHUB_OUTPUT fi
  • # Start the container in the background docker run -d --name langflow -p 7860:7860 langflowai/langflow:latest # Wait for the service to be ready (adjust timeout as needed) echo "Waiting for Langflow to start..." timeout=60 elapsed=0 while ! curl -s http://localhost:7860/health > /dev/null; do sleep 2 elapsed=$((elapsed+2)) if [ "$elapsed" -ge "$timeout" ]; then echo "Timed out waiting for Langflow to start" exit 1 fi echo "Still waiting... ($elapsed seconds)" done # Get the OpenAPI spec and save to file echo "Fetching OpenAPI spec..." curl -s http://localhost:7860/openapi.json > docs/openapi/new_openapi.json # Verify file was created ls -la docs/openapi/new_openapi.json # stop the container when done docker stop langflow
  • # Extract versions NEW_VERSION=$(jq -r '.info.version' docs/openapi/new_openapi.json) CURRENT_VERSION=$(jq -r '.info.version' docs/openapi/openapi.json) echo "Current version: $CURRENT_VERSION" echo "New version: $NEW_VERSION" # Compare file content (normalize by sorting keys) jq --sort-keys . docs/openapi/new_openapi.json > docs/openapi/sorted_new.json jq --sort-keys . docs/openapi/openapi.json > docs/openapi/sorted_current.json if ! cmp -s docs/openapi/sorted_new.json docs/openapi/sorted_current.json; then echo "OpenAPI spec content has changed." # Clean the new spec for ReDoc formatting echo "Cleaning OpenAPI spec formatting..." python3 docs/scripts/clean_openapi_formatting.py docs/openapi/new_openapi.json # Compare versions (assuming semantic versioning) if [ "$(printf '%s\n' "$CURRENT_VERSION" "$NEW_VERSION" | sort -V | tail -n1)" == "$NEW_VERSION" ] && [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then echo "New version detected. Creating PR." echo "NEEDS_UPDATE=true" >> $GITHUB_ENV echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV echo "UPDATE_REASON=version upgraded from $CURRENT_VERSION to $NEW_VERSION" >> $GITHUB_ENV else echo "File content changed but version remains the same. Creating PR." echo "NEEDS_UPDATE=true" >> $GITHUB_ENV echo "NEW_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV echo "UPDATE_REASON=content updated without version change" >> $GITHUB_ENV fi cat docs/openapi/new_openapi.json | jq > docs/openapi/openapi.json else echo "No changes detected in OpenAPI spec content." echo "NEEDS_UPDATE=false" >> $GITHUB_ENV fi # Clean up rm docs/openapi/new_openapi.json docs/openapi/sorted_new.json docs/openapi/sorted_current.json
View raw YAML
name: Update OpenAPI Spec

on:
  schedule:
    - cron: "0 20 * * 1" # Monday 4pm EST
  workflow_dispatch:     # Allow manual trigger

permissions:
  contents: write
  pull-requests: write

jobs:
  check-openapi-updates:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Install jq
        run: sudo apt-get install -y jq

      - name: Check if there is already an open pull request
        id: check_pull_request
        run: |
          if [[ -n $(gh pr list --state open --repo ${{ github.repository }} | grep "docs: OpenAPI spec") ]]; then
            echo "There is already an open PR with updates to the OpenAPI spec. Merge or close that PR first. Skipping."
            echo "pr_exists=true" >> $GITHUB_OUTPUT
          else
            echo "pr_exists=false" >> $GITHUB_OUTPUT
          fi
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Run Langflow container and get OpenAPI spec
        if: steps.check_pull_request.outputs.pr_exists != 'true'
        run: |
          # Start the container in the background
          docker run -d --name langflow -p 7860:7860 langflowai/langflow:latest

          # Wait for the service to be ready (adjust timeout as needed)
          echo "Waiting for Langflow to start..."
          timeout=60
          elapsed=0
          while ! curl -s http://localhost:7860/health > /dev/null; do
            sleep 2
            elapsed=$((elapsed+2))
            if [ "$elapsed" -ge "$timeout" ]; then
              echo "Timed out waiting for Langflow to start"
              exit 1
            fi
            echo "Still waiting... ($elapsed seconds)"
          done

          # Get the OpenAPI spec and save to file
          echo "Fetching OpenAPI spec..."
          curl -s http://localhost:7860/openapi.json > docs/openapi/new_openapi.json

          # Verify file was created
          ls -la docs/openapi/new_openapi.json

          # stop the container when done
          docker stop langflow

      - name: Compare OpenAPI files
        if: steps.check_pull_request.outputs.pr_exists != 'true'
        id: compare
        run: |
          # Extract versions
          NEW_VERSION=$(jq -r '.info.version' docs/openapi/new_openapi.json)
          CURRENT_VERSION=$(jq -r '.info.version' docs/openapi/openapi.json)

          echo "Current version: $CURRENT_VERSION"
          echo "New version: $NEW_VERSION"

          # Compare file content (normalize by sorting keys)
          jq --sort-keys . docs/openapi/new_openapi.json > docs/openapi/sorted_new.json
          jq --sort-keys . docs/openapi/openapi.json > docs/openapi/sorted_current.json

          if ! cmp -s docs/openapi/sorted_new.json docs/openapi/sorted_current.json; then
            echo "OpenAPI spec content has changed."

            # Clean the new spec for ReDoc formatting
            echo "Cleaning OpenAPI spec formatting..."
            python3 docs/scripts/clean_openapi_formatting.py docs/openapi/new_openapi.json

            # Compare versions (assuming semantic versioning)
            if [ "$(printf '%s\n' "$CURRENT_VERSION" "$NEW_VERSION" | sort -V | tail -n1)" == "$NEW_VERSION" ] && [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
              echo "New version detected. Creating PR."
              echo "NEEDS_UPDATE=true" >> $GITHUB_ENV
              echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
              echo "UPDATE_REASON=version upgraded from $CURRENT_VERSION to $NEW_VERSION" >> $GITHUB_ENV
            else
              echo "File content changed but version remains the same. Creating PR."
              echo "NEEDS_UPDATE=true" >> $GITHUB_ENV
              echo "NEW_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV
              echo "UPDATE_REASON=content updated without version change" >> $GITHUB_ENV
            fi

            cat docs/openapi/new_openapi.json | jq > docs/openapi/openapi.json
          else
            echo "No changes detected in OpenAPI spec content."
            echo "NEEDS_UPDATE=false" >> $GITHUB_ENV
          fi

          # Clean up
          rm docs/openapi/new_openapi.json docs/openapi/sorted_new.json docs/openapi/sorted_current.json

      - name: Create Pull Request
        if: env.NEEDS_UPDATE == 'true' && steps.check_pull_request.outputs.pr_exists != 'true'
        uses: peter-evans/create-pull-request@v8
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          commit-message: "docs: OpenAPI spec ${{ env.UPDATE_REASON }}"
          title: "docs: OpenAPI spec ${{ env.UPDATE_REASON }}"
          body: |
            This PR updates the OpenAPI spec.

            Update reason: ${{ env.UPDATE_REASON }}
            Version: ${{ env.NEW_VERSION }}
          branch: update-openapi-spec
          branch-suffix: timestamp
          delete-branch: true
          reviewers: mendonk
docs_test .github/workflows/docs_test.yml
Triggers
workflow_call, workflow_dispatch
Runs on
ubuntu-latest
Jobs
test-docs-build
Commands
  • cd docs && npm install
  • cd docs && npm run build
View raw YAML
name: Test Docs Build

on:
  workflow_call:
  workflow_dispatch:
    inputs:
      branch:
        description: "(Optional) Branch to checkout"
        required: false
        type: string

env:
  NODE_VERSION: "22"

jobs:
  test-docs-build:
    name: Test Docs Build
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.branch || github.ref }}

      - name: Setup Node.js
        uses: actions/setup-node@v6
        id: setup-node
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: npm
          cache-dependency-path: ./docs/package-lock.json

      - name: Install dependencies
        run: cd docs && npm install

      - name: Build docs
        run: cd docs && npm run build
integration_tests matrix .github/workflows/integration_tests.yml
Triggers
workflow_dispatch, workflow_call
Runs on
ubuntu-latest
Jobs
integration-tests
Matrix
python-version→ 3.10, 3.11, 3.12, 3.13
Actions
astral-sh/setup-uv
Commands
  • make integration_tests_api_keys
View raw YAML
name: Integration Tests

on:
  workflow_dispatch:
    inputs:
      ref:
        description: "(Optional) ref to checkout"
        required: false
        type: string
  workflow_call:
    inputs:
      python-versions:
        description: "(Optional) Python versions to test"
        required: true
        type: string
        default: "['3.10', '3.11', '3.12', '3.13']"
      ref:
        description: "(Optional) ref to checkout"
        required: false
        type: string

env:
  POETRY_VERSION: "1.8.2"

jobs:
  integration-tests:
    name: Run Integration Tests
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 1 # Currently, we can only run at a time for collection-per-db-constraints
      matrix:
        python-version:
          - "3.13"
          - "3.12"
          - "3.11"
          - "3.10"
    env:
      OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      ASTRA_DB_API_ENDPOINT: ${{ secrets.ASTRA_DB_API_ENDPOINT }}
      ASTRA_DB_APPLICATION_TOKEN: ${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}
      - name: "Setup Environment"
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false
      - name: Run integration tests with api keys
        timeout-minutes: 20
        run: |
          make integration_tests_api_keys
jest_test .github/workflows/jest_test.yml
Triggers
workflow_call, workflow_dispatch
Runs on
ubuntu-latest
Jobs
jest-unit-tests
Actions
mikepenz/action-junit-report, MishaKav/jest-coverage-comment, codecov/codecov-action
Commands
  • make test_frontend_ci
View raw YAML
name: Run Frontend Jest Unit Tests

on:
  workflow_call:
    secrets:
      CODECOV_TOKEN:
        required: false
    inputs:
      ref:
        description: "(Optional) ref to checkout"
        required: false
        type: string
  workflow_dispatch:
    inputs:
      ref:
        description: "(Optional) ref to checkout"
        required: false
        type: string

env:
  NODE_VERSION: "22"

jobs:
  jest-unit-tests:
    name: Frontend Jest Unit Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      checks: write
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}

      - name: Setup Node.js Environment
        uses: actions/setup-node@v6
        id: setup-node
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"
          cache-dependency-path: ./src/frontend/package-lock.json

      - name: Run Frontend Unit Tests
        run: make test_frontend_ci

      - name: Publish Test Results
        uses: mikepenz/action-junit-report@v5
        if: always()
        with:
          report_paths: 'src/frontend/test-results/junit.xml'
          check_name: 'Frontend Unit Test Results'
          fail_on_failure: true
          require_tests: true

      - name: Add Coverage PR Comment
        uses: MishaKav/jest-coverage-comment@main
        if: github.event_name == 'pull_request'
        with:
          coverage-summary-path: src/frontend/coverage/coverage-summary.json
          title: Frontend Unit Test Coverage Report
          summary-title: Coverage Summary
          badge-title: Coverage
          hide-comment: false
          create-new-comment: false
          hide-summary: false
          junitxml-title: Unit Test Results
          junitxml-path: src/frontend/test-results/junit.xml

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v5
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: src/frontend/coverage/lcov.info
          flags: frontend
          name: frontend-coverage
          fail_ci_if_error: false
          directory: src/frontend/coverage/

      - name: Upload Coverage Reports
        uses: actions/upload-artifact@v6
        if: always()
        with:
          name: frontend-coverage-report
          path: src/frontend/coverage/
          retention-days: 30
js_autofix perms .github/workflows/js_autofix.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
autofix
Actions
autofix-ci/action
Commands
  • cd src/frontend npm ci
  • cd src/frontend npm run format
View raw YAML
name: autofix.ci

on:
  pull_request:
    paths:
      - "src/frontend/**"

permissions:
  contents: read

env:
  NODE_VERSION: "22"
jobs:
  autofix:
    if: ${{ github.actor != 'github-actions[bot]' }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        id: setup-node
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Cache Node.js dependencies
        uses: actions/cache@v5
        id: npm-cache
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('src/frontend/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Node.js dependencies
        run: |
          cd src/frontend
          npm ci
        if: ${{ steps.setup-node.outputs.cache-hit != 'true' }}
      - name: Run Biome
        run: |
          cd src/frontend
          npm run format

      - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
lint-js .github/workflows/lint-js.yml
Triggers
workflow_call, workflow_dispatch
Runs on
ubuntu-latest
Jobs
run-linters
Commands
  • cd src/frontend npm install
  • cd src/frontend npx @biomejs/biome check --changed
View raw YAML
name: Lint Frontend

on:
  workflow_call:
  workflow_dispatch:
    inputs:
      branch:
        description: "(Optional) Branch to checkout"
        required: false
        type: string


env:
  NODE_VERSION: "22"

jobs:
  run-linters:
    name: Run Biome
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.branch || github.ref }}

      - name: Setup Node.js
        uses: actions/setup-node@v6
        id: setup-node
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Cache Node.js dependencies
        uses: actions/cache@v5
        id: npm-cache
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('src/frontend/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Node.js dependencies
        run: |
          cd src/frontend
          npm install
        if: ${{ steps.setup-node.outputs.cache-hit != 'true' }}

      - name: Run Biome
        run: |
          cd src/frontend
          npx @biomejs/biome check --changed
lint-py matrix .github/workflows/lint-py.yml
Triggers
workflow_call, workflow_dispatch
Runs on
ubuntu-latest
Jobs
lint
Matrix
python-version→ 3.10, 3.11, 3.12, 3.13
Actions
astral-sh/setup-uv
Commands
  • uv sync
  • uv run mypy --namespace-packages -p "langflow"
View raw YAML
name: Lint Python

on:
  workflow_call:
  workflow_dispatch:
    inputs:
      branch:
        description: "(Optional) Branch to checkout"
        required: false
        type: string
env:
  POETRY_VERSION: "1.8.2"


jobs:
  lint:
    name: Run Mypy
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version:
          - "3.13"
          - "3.12"
          - "3.11"
          - "3.10"
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.branch || github.ref }}
          persist-credentials: true
      - name: "Setup Environment"
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false
      - name: Install the project
        run: uv sync
      - name: Run Mypy
        run: |
          uv run mypy --namespace-packages -p "langflow"
        env:
          GITHUB_TOKEN: ${{ secrets.github_token }}

migration-validation .github/workflows/migration-validation.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
validate-migration
Commands
  • pip install sqlalchemy alembic
  • # Get all changed Python files in alembic/versions directories # CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E '(alembic|migrations)/versions/.*\.py$' || echo "") # Exclude test migrations, as they are not part of the main codebase CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E '(alembic|migrations)/versions/.*\.py$' | grep -v 'test_migrations/' || echo "") if [ -z "$CHANGED_FILES" ]; then echo "No migration files changed" echo "files=" >> $GITHUB_OUTPUT else echo "Changed migration files:" echo "$CHANGED_FILES" # Convert newlines to spaces for passing as arguments echo "files=$(echo $CHANGED_FILES | tr '\n' ' ')" >> $GITHUB_OUTPUT fi
  • python src/backend/base/langflow/alembic/migration_validator.py ${{ steps.changed-files.outputs.files }}
  • python src/backend/base/langflow/alembic/migration_validator.py \ --json ${{ steps.changed-files.outputs.files }} > validation-report.json || true
View raw YAML
name: Database Migration Validation

on:
  pull_request:
    paths:
      - 'src/backend/base/langflow/alembic/versions/*.py'
      - 'alembic/versions/*.py'

jobs:
  validate-migration:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          pip install sqlalchemy alembic

      - name: Get changed migration files
        id: changed-files
        run: |
          # Get all changed Python files in alembic/versions directories

          # CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E '(alembic|migrations)/versions/.*\.py$' || echo "")

          # Exclude test migrations, as they are not part of the main codebase
          CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E '(alembic|migrations)/versions/.*\.py$' | grep -v 'test_migrations/' || echo "")

          if [ -z "$CHANGED_FILES" ]; then
            echo "No migration files changed"
            echo "files=" >> $GITHUB_OUTPUT
          else
            echo "Changed migration files:"
            echo "$CHANGED_FILES"
            # Convert newlines to spaces for passing as arguments
            echo "files=$(echo $CHANGED_FILES | tr '\n' ' ')" >> $GITHUB_OUTPUT
          fi

      - name: Validate migrations
        if: steps.changed-files.outputs.files != ''
        run: |
          python src/backend/base/langflow/alembic/migration_validator.py ${{ steps.changed-files.outputs.files }}

#       - name: Check migration phase sequence
#         if: steps.changed-files.outputs.files != ''
#         run: |
#           python scripts/check_phase_sequence.py ${{ steps.changed-files.outputs.files }}

      - name: Generate validation report
        if: always() && steps.changed-files.outputs.files != ''
        run: |
          python src/backend/base/langflow/alembic/migration_validator.py \
            --json ${{ steps.changed-files.outputs.files }} > validation-report.json || true

      - name: Post PR comment with results
        if: always() && steps.changed-files.outputs.files != ''
        uses: actions/github-script@v8
        with:
          script: |
            const fs = require('fs');

            let message = '';
            let validationPassed = true;

            try {
              const report = JSON.parse(fs.readFileSync('validation-report.json', 'utf8'));

              for (const result of report) {
                if (!result.valid) {
                  validationPassed = false;
                }
              }

              if (validationPassed) {
                message = `✅ **Migration Validation Passed**\n\n`;
                message += `All migrations follow the Expand-Contract pattern correctly.\n\n`;
              } else {
                message = `❌ **Migration Validation Failed**\n\n`;
                message += `Your migrations don't follow the Expand-Contract pattern.\n\n`;

                for (const result of report) {
                  if (!result.valid || result.warnings.length > 0) {
                    message += `### File: \`${result.file.split('/').pop()}\`\n`;
                    message += `**Phase:** ${result.phase}\n\n`;

                    if (result.violations && result.violations.length > 0) {
                      message += `**Violations:**\n`;
                      for (const v of result.violations) {
                        message += `- Line ${v.line}: ${v.message}\n`;
                      }
                      message += `\n`;
                    }

                    if (result.warnings && result.warnings.length > 0) {
                      message += `**Warnings:**\n`;
                      for (const w of result.warnings) {
                        message += `- Line ${w.line}: ${w.message}\n`;
                      }
                      message += `\n`;
                    }
                  }
                }

                message += `### 📚 Resources\n`;
                message += `- Review the [DB Migration Guide](./src/backend/base/langflow/alembic/DB-MIGRATION-GUIDE.MD)\n`;
                message += `- Use \`python scripts/generate_migration.py --help\` to generate compliant migrations\n\n`;

                message += `### Common Issues & Solutions\n`;
                message += `- **New columns must be nullable:** Add \`nullable=True\` or \`server_default\`\n`;
                message += `- **Missing phase marker:** Add \`Phase: EXPAND/MIGRATE/CONTRACT\` to docstring\n`;
                message += `- **Column drops:** Only allowed in CONTRACT phase\n`;
                message += `- **Direct renames:** Use expand-contract pattern instead\n`;
              }
            } catch (error) {
              message = `⚠️ **Migration validation check failed to run properly**\n`;
              message += `Error: ${error.message}\n`;
            }

            // Post or update comment
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });

            const botComment = comments.find(comment =>
              comment.user.type === 'Bot' &&
              comment.body.includes('Migration Validation')
            );

            if (botComment) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: message
              });
            } else {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: message
              });
            }

            // Fail the workflow if validation didn't pass
            if (!validationPassed) {
              core.setFailed('Migration validation failed');
            }
nightly_build .github/workflows/nightly_build.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
validate-inputs, resolve-release-branch, create-nightly-tag, frontend-tests-linux, frontend-tests-windows, backend-unit-tests, release-nightly-build, slack-notification
Actions
astral-sh/setup-uv
Commands
  • echo "Cannot skip tests while push_to_registry is true." exit 1
  • git ls-remote --heads https://github.com/${{ github.repository }} 'refs/heads/release-*' \ | awk '{print $2}' \ | sed 's|refs/heads/||' \ | grep -E '^release-[0-9]+\.[0-9]+\.[0-9]+$' \ | sort -V \ | tail -n 1 > branch.txt BRANCH=$(cat branch.txt) if [ -z "$BRANCH" ]; then echo "No release-* branch found in ${{ github.repository }}" exit 1 fi echo "branch=$BRANCH" >> $GITHUB_OUTPUT echo "Using release branch: $BRANCH"
  • git branch --show-current
  • uv sync
  • echo "PWD: $(pwd)" find . -name "pypi_nightly_tag.py"
  • # NOTE: This outputs the tag with the `v` prefix. RELEASE_TAG="$(uv run ./scripts/ci/pypi_nightly_tag.py main)" echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT echo "release_tag=$RELEASE_TAG"
  • git fetch --tags git tag -d ${{ steps.generate_release_tag.outputs.release_tag }} || true git push --delete origin ${{ steps.generate_release_tag.outputs.release_tag }} || true echo "release_tag_exists=false" >> $GITHUB_OUTPUT
  • # 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"
View raw YAML
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-ephemeral"]'
        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
  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
  resolve-release-branch:
    runs-on: ubuntu-latest
    outputs:
      branch: ${{ steps.get_branch.outputs.branch }}
    steps:
      - name: Find latest release branch
        id: get_branch
        run: |
          git ls-remote --heads https://github.com/${{ github.repository }} 'refs/heads/release-*' \
          | awk '{print $2}' \
          | sed 's|refs/heads/||' \
          | grep -E '^release-[0-9]+\.[0-9]+\.[0-9]+$' \
          | sort -V \
          | tail -n 1 > branch.txt

          BRANCH=$(cat branch.txt)
          if [ -z "$BRANCH" ]; then
            echo "No release-* branch found in ${{ github.repository }}"
            exit 1
          fi
           echo "branch=$BRANCH" >> $GITHUB_OUTPUT
           echo "Using release branch: $BRANCH"
  create-nightly-tag:
    if: github.repository == 'langflow-ai/langflow'
    needs: [validate-inputs, resolve-release-branch]
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash -ex -o pipefail {0}
    permissions:
      # Required to create tag
      contents: write
    outputs:
      release_tag: ${{ steps.generate_release_tag.outputs.release_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:
          ref: ${{ needs.resolve-release-branch.outputs.branch }}
          persist-credentials: true
      - name: Confirm branch
        run: git branch --show-current
      - 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: Check script location
        run: |
          echo "PWD: $(pwd)"
          find . -name "pypi_nightly_tag.py"
      - name: Generate main nightly tag
        id: generate_release_tag
        run: |
          # NOTE: This outputs the tag with the `v` prefix.
          RELEASE_TAG="$(uv run ./scripts/ci/pypi_nightly_tag.py main)"
          echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
          echo "release_tag=$RELEASE_TAG"

      - name: Delete existing tag if it exists
        id: check_release_tag
        run: |
          git fetch --tags
          git tag -d ${{ steps.generate_release_tag.outputs.release_tag }} || true
          git push --delete origin ${{ steps.generate_release_tag.outputs.release_tag }} || true
          echo "release_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 "bot-nightly-builds@langflow.org"
          git config --global user.name "Langflow Bot"

          RELEASE_TAG="${{ steps.generate_release_tag.outputs.release_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 $RELEASE_TAG"
          uv run --no-sync ./scripts/ci/update_pyproject_combined.py main $RELEASE_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 $RELEASE_TAG"
          if ! git tag -a $RELEASE_TAG -m "Langflow nightly $RELEASE_TAG"; then
            echo "Tag creation failed. Exiting the workflow."
            exit 1
          fi

          echo "Pushing main tag $RELEASE_TAG"
          if ! git push origin $RELEASE_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_release_tag.outputs.release_tag }}
          persist-credentials: true

      - 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

  frontend-tests-linux:
    if: github.repository == 'langflow-ai/langflow' && !inputs.skip_frontend_tests
    name: Run Frontend Tests - Linux
    needs: [resolve-release-branch, create-nightly-tag]
    uses: ./.github/workflows/typescript_test.yml
    with:
      tests_folder: "tests"
      release: true
      runs-on: ubuntu-latest
      ref: ${{ needs.resolve-release-branch.outputs.branch }}
    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 }}

  frontend-tests-windows:
    if: github.repository == 'langflow-ai/langflow' && !inputs.skip_frontend_tests
    name: Run Frontend Tests - Windows
    needs: [resolve-release-branch, create-nightly-tag]
    # Windows tests are non-blocking - the release-nightly-build job only checks
    # frontend-tests-linux.result, allowing Windows tests to fail without blocking the build.
    # This gives us visibility into Windows-specific issues while we stabilize the tests.
    uses: ./.github/workflows/typescript_test.yml
    with:
      tests_folder: "tests"
      release: true
      runs-on: windows-latest
      ref: ${{ needs.resolve-release-branch.outputs.branch }}
    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: [resolve-release-branch, 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'] || github.event.inputs['runs_on'] || 'ubuntu-latest' }}
      ref: ${{ needs.resolve-release-branch.outputs.branch }}
    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-linux.result != 'failure' && needs.backend-unit-tests.result != 'failure'
    name: Run Nightly Langflow Build
    needs:
      [validate-inputs, create-nightly-tag, frontend-tests-linux, frontend-tests-windows, backend-unit-tests]
    uses: ./.github/workflows/release_nightly.yml
    with:
      build_docker_base: true
      build_docker_main: true
      build_docker_ep: true
      build_lfx: true
      nightly_tag_release: ${{ needs.create-nightly-tag.outputs.release_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-linux, frontend-tests-windows, 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-linux.result == 'failure' || needs.frontend-tests-windows.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-linux.result == 'failure' || needs.frontend-tests-windows.result == 'failure' || needs.backend-unit-tests.result == 'failure' }}
        run: |
          # Determine which job failed
          FAILED_JOB="unknown"
          if [ "${{ needs.release-nightly-build.result }}" == "failure" ]; then
            FAILED_JOB="release-nightly-build"
          elif [ "${{ needs.frontend-tests-linux.result }}" == "failure" ]; then
            FAILED_JOB="frontend-tests-linux"
          elif [ "${{ needs.frontend-tests-windows.result }}" == "failure" ]; then
            FAILED_JOB="frontend-tests-windows (non-blocking)"
          elif [ "${{ needs.backend-unit-tests.result }}" == "failure" ]; then
            FAILED_JOB="backend-unit-tests"
          fi

          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\": \"*Failed Job:*\\n$FAILED_JOB\"
                  },
                  {
                    \"type\": \"mrkdwn\",
                    \"text\": \"*Status:*\\nfailure\"
                  }
                ]
              },
              {
                \"type\": \"section\",
                \"text\": {
                  \"type\": \"mrkdwn\",
                  \"text\": \"*Note:* Frontend tests now run on both Linux and Windows\"
                }
              },
              {
                \"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 }}

      - 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": "section",
                "text": {
                  "type": "mrkdwn",
                  "text": "*Platforms:* Linux & Windows Playwright tests passed"
                }
              },
              {
                "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 }}
py_autofix .github/workflows/py_autofix.yml
Triggers
pull_request
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
lint, update-starter-projects, update-component-index
Actions
astral-sh/setup-uv, autofix-ci/action, astral-sh/setup-uv, autofix-ci/action, astral-sh/setup-uv, autofix-ci/action
Commands
  • uv run ruff check --fix-only .
  • uv run ruff format . --config pyproject.toml
  • uv sync uv pip install -e .
  • uv run python scripts/ci/update_starter_projects.py
  • uv sync --dev --project .
  • make build_component_index
View raw YAML
name: autofix.ci
on:
  pull_request:
    paths:
      - "**/*.py"
      - "src/lfx/src/lfx/components/**"
      - "scripts/build_component_index.py"
env:
  PYTHON_VERSION: "3.13"


jobs:
  lint:
    name: Run Ruff Check and Format
    if: ${{ github.actor != 'github-actions[bot]' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - 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
      - run: uv run ruff check --fix-only .
      - run: uv run ruff format . --config pyproject.toml
      - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27


  update-starter-projects:
    name: Update Starter Projects
    if: ${{ github.actor != 'github-actions[bot]' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - 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 dependencies"
        run: |
          uv sync
          uv pip install -e .

      - name: Run starter projects update
        run: uv run python scripts/ci/update_starter_projects.py

      - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27


  update-component-index:
    name: Update Component Index
    if: ${{ github.actor != 'github-actions[bot]' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - 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 dependencies"
        run: uv sync --dev --project .

      - name: Build component index
        env:
          LFX_DEV: "1"
        run: make build_component_index

      - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27



python_test matrix .github/workflows/python_test.yml
Triggers
workflow_call, workflow_dispatch
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' }}, ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}, ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}, ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
Jobs
build, integration-tests, lfx-tests, test-cli
Matrix
group, python-version, splitCount→ ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}, ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]') }}, 1, 2, 3, 4, 5
Actions
astral-sh/setup-uv, nick-fields/retry, codecov/codecov-action, astral-sh/setup-uv, astral-sh/setup-uv, codecov/codecov-action, astral-sh/setup-uv
Commands
  • uv sync
  • echo "Generating dynamic coverage configuration..." python3 scripts/generate_coverage_config.py echo "Generated .coveragerc with the following exclusions:" echo "Bundled components and legacy files excluded from coverage"
  • uv sync
  • make integration_tests_no_api_keys
  • uv run make lfx_tests
  • version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1) url="https://pypi.org/pypi/langflow-base/json" if [ ${{ inputs.nightly }} == true ]; then url="https://pypi.org/pypi/langflow-base-nightly/json" fi last_released_version=$(curl -s $url | jq -r '.releases | keys | .[]' | sort -V | tail -n 1) if [ "$version" != "$last_released_version" ]; then echo "Version $version has not been released yet. Skipping the rest of the job." echo skipped=true >> $GITHUB_OUTPUT exit 0 else echo version=$version >> $GITHUB_OUTPUT echo skipped=false >> $GITHUB_OUTPUT fi
  • make build main=true
  • uv venv new-venv source new-venv/bin/activate uv pip install dist/*.whl
View raw YAML
name: Python tests

on:
  workflow_call:
    secrets:
      OPENAI_API_KEY:
        required: true
      ANTHROPIC_API_KEY:
        required: true
      CODECOV_TOKEN:
        required: false
    inputs:
      python-versions:
        description: "(Optional) Python versions to test"
        required: true
        type: string
        default: "['3.10', '3.11', '3.12', '3.13']"
      ref:
        description: "(Optional) ref to checkout"
        required: false
        type: string
      nightly:
        description: "Whether run is from the nightly build"
        required: false
        type: boolean
        default: false
      runs-on:
        description: "Runner to use for the tests"
        required: false
        type: string
        default: "ubuntu-latest"
  workflow_dispatch:
    inputs:
      python-versions:
        description: "(Optional) Python versions to test"
        required: true
        type: string
        default: "['3.10', '3.11', '3.12', '3.13']"
      runs-on:
        description: "Runner to use for the tests"
        required: false
        type: choice
        options:
          - ubuntu-latest
          - self-hosted
          - '["self-hosted", "linux", "ARM64", "langflow-ai-arm64-40gb-ephemeral"]'
        default: ubuntu-latest
env:
  POETRY_VERSION: "1.8.2"
  NODE_VERSION: "22"
  PYTEST_RUN_PATH: "src/backend/tests"
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

jobs:
  build:
    name: Unit Tests - Python ${{ matrix.python-version }} - Group ${{ matrix.group }}
    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' }}
    strategy:
      matrix:
        python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}
        splitCount: [5]
        group: [1, 2, 3, 4, 5]
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}

      - name: Setup Node.js
        uses: actions/setup-node@v6
        id: setup-node
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: "Setup Environment"
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false

      - name: Install the project
        run: uv sync

      - name: Generate dynamic coverage configuration
        run: |
          echo "Generating dynamic coverage configuration..."
          python3 scripts/generate_coverage_config.py
          echo "Generated .coveragerc with the following exclusions:"
          echo "Bundled components and legacy files excluded from coverage"

      - name: Run unit tests
        uses: nick-fields/retry@v3
        with:
          timeout_minutes: 12
          max_attempts: 2
          command: make unit_tests args="-x -vv --splits ${{ matrix.splitCount }} --group ${{ matrix.group }} --reruns 5 --cov --cov-config=src/backend/.coveragerc --cov-report=xml --cov-report=html"

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v5
        if: matrix.python-version == '3.10'
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage.xml
          flags: backend
          name: backend-coverage-group-${{ matrix.group }}
          fail_ci_if_error: false
          directory: ./

      - name: Upload coverage artifacts
        uses: actions/upload-artifact@v6
        if: matrix.python-version == '3.10'
        with:
          name: backend-coverage-report-group-${{ matrix.group }}
          path: |
            coverage.xml
            htmlcov/
          retention-days: 30

  integration-tests:
    name: Integration Tests - Python ${{ matrix.python-version }}
    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' }}
    strategy:
      matrix:
        python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}
      - name: "Setup Environment"
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false
      - name: Install the project
        run: uv sync
      - name: Run integration tests
        run: make integration_tests_no_api_keys
        env:
          PYLEAK_LOG_LEVEL: debug # enable pyleak logging
          DO_NOT_TRACK: true # disable telemetry reporting

  lfx-tests:
    name: LFX Tests - Python ${{ matrix.python-version }}
    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' }}
    strategy:
      matrix:
        python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}
      - name: "Setup Environment"
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false
      - name: Run lfx tests
        run: uv run make lfx_tests
        env:
          DO_NOT_TRACK: true # disable telemetry reporting

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v5
        if: matrix.python-version == '3.10'
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./src/lfx/coverage.xml
          flags: lfx
          name: lfx-coverage
          fail_ci_if_error: false
          directory: ./src/lfx/

      - name: Upload coverage artifacts
        uses: actions/upload-artifact@v6
        if: matrix.python-version == '3.10'
        with:
          name: lfx-coverage-report
          path: |
            src/lfx/coverage.xml
            src/lfx/htmlcov/
          retention-days: 30

  test-cli:
    name: Test CLI - Python ${{ matrix.python-version }}
    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' }}
    strategy:
      matrix:
        python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]') }}
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}
      - name: "Setup Environment"
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false

      - name: Check Version
        id: check-version
        # We need to print $3 because langflow-base is a dependency of langflow
        # For langlow we'd use print $2
        run: |
          version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1)
          url="https://pypi.org/pypi/langflow-base/json"
          if [ ${{ inputs.nightly }} == true ]; then
            url="https://pypi.org/pypi/langflow-base-nightly/json"
          fi

          last_released_version=$(curl -s $url | jq -r '.releases | keys | .[]' | sort -V | tail -n 1)
          if [ "$version" != "$last_released_version" ]; then
            echo "Version $version has not been released yet. Skipping the rest of the job."
            echo skipped=true >> $GITHUB_OUTPUT
            exit 0
          else
            echo version=$version >> $GITHUB_OUTPUT
            echo skipped=false >> $GITHUB_OUTPUT
          fi
      - name: Build wheel
        if: steps.check-version.outputs.skipped == 'false'
        run: |
          make build main=true
      - name: Install wheel and Test CLI
        if: steps.check-version.outputs.skipped == 'false'
        run: |
          uv venv new-venv
          source new-venv/bin/activate
          uv pip install dist/*.whl
      - name: Test CLI
        if: steps.check-version.outputs.skipped == 'false'
        run: |
          source new-venv/bin/activate
          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 5; 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
release .github/workflows/release.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
echo-inputs, validate-tag, validate-dependencies, determine-base-version, determine-main-version, determine-lfx-version, ci, build-lfx, build-base, build-main, test-cross-platform, publish-base, publish-main, publish-lfx, call_docker_build_base, call_docker_build_main, call_docker_build_main_backend, call_docker_build_main_frontend, call_docker_build_main_ep, call_docker_build_main_all, create_release
Actions
astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, ncipollo/release-action
Commands
  • 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 "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 }}"
  • # 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."
  • 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 echo "✅ Release dependencies validated successfully."
  • uv sync
  • 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 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" 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 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
  • uv sync
  • version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $2}' | sed 's/^v//') 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" 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 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
  • uv sync --dev --package lfx
View raw YAML
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
      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

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 "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-dependencies:
    name: Validate Release Dependencies
    runs-on: ubuntu-latest
    if: ${{ inputs.release_package_base || inputs.release_package_main || inputs.release_lfx || 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

          echo "✅ Release dependencies validated successfully."

  determine-base-version:
    name: Determine Base Version
    needs: [validate-tag, validate-dependencies]
    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
            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"
            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
            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]
    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 'langflow' | grep -v 'langflow-base' | awk '{print $2}' | sed 's/^v//')
          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"
            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
            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]
    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
            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"
            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

  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']"
      frontend-tests-folder: "tests"
      release: true
      run-all-tests: true
      runs-on: ubuntu-latest
    secrets: inherit

  build-lfx:
    name: Build LFX
    needs: [determine-lfx-version]
    if: ${{ 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: 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

          # Verify the change
          echo "Updated pyproject.toml version:"
          grep "^version" 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
          uv pip install dist/*.whl --force-reinstall
          uv run 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, determine-base-version, determine-lfx-version]
    if: ${{ inputs.release_package_base && needs.determine-base-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.12"
          prune-cache: false
      - name: Download LFX artifact
        uses: actions/download-artifact@v7
        with:
          name: dist-lfx
          path: ./lfx-dist
      - name: Install dependencies with local LFX wheel
        run: |
          # Create virtual environment
          uv venv
          # Install using pip with local wheel directory as find-links
          uv pip install --find-links ./lfx-dist --prerelease=allow -e src/backend/base
      - name: Check for dependency incompatibility
        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"
          cd src/backend/base

          # Extract current lfx constraint from pyproject.toml
          CURRENT_CONSTRAINT=$(grep -E '^\s*"lfx' pyproject.toml | head -1)
          echo "Current constraint: $CURRENT_CONSTRAINT"

          # Extract the major.minor version (e.g., "0.3" from "~=0.3.0")
          MAJOR_MINOR=$(echo "$CURRENT_CONSTRAINT" | sed -E 's/.*[~>=<]+([0-9]+\.[0-9]+).*/\1/')
          NEXT_MAJOR=$((${MAJOR_MINOR%.*} + 1))

          # Create new constraint: >=LFX_VERSION,<NEXT_MAJOR.dev0
          NEW_CONSTRAINT="\"lfx>=$LFX_VERSION,<$NEXT_MAJOR.dev0\""

          echo "New constraint: $NEW_CONSTRAINT"

          # Replace the constraint
          sed -i.bak "s|\"lfx[^\"]*\"|$NEW_CONSTRAINT|" pyproject.toml

          # Verify the change
          echo "Updated lfx dependency:"
          grep "lfx" 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,
        determine-base-version,
        determine-main-version,
        determine-lfx-version,
      ]
    if: ${{ inputs.release_package_main && needs.determine-main-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.12"
          prune-cache: false
      - name: Download LFX artifact
        uses: actions/download-artifact@v7
        with:
          name: dist-lfx
          path: ./lfx-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
          uv pip install --find-links ./lfx-dist --find-links ./base-dist --prerelease=allow -e .
      - name: Check for dependency incompatibility
        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

  test-cross-platform:
    name: Test Cross-Platform Installation
    needs: [build-base, build-main, build-lfx]
    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"
      pre_release: ${{ inputs.pre_release }}

  publish-base:
    name: Publish Langflow Base to PyPI
    if: ${{ inputs.release_package_base }}
    needs: [build-base, test-cross-platform, ci]
    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-main:
    name: Publish Langflow Main to PyPI
    if: ${{ inputs.release_package_main }}
    needs: [build-main, test-cross-platform, publish-base, 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-lfx:
    name: Publish LFX to PyPI
    if: ${{ inputs.release_lfx }}
    needs: [build-lfx, test-cross-platform, ci]
    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]
    uses: ./.github/workflows/docker-build-v2.yml
    with:
      ref: ${{ inputs.release_tag }}
      release_type: base
      pre_release: ${{ inputs.pre_release }}
      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]
    uses: ./.github/workflows/docker-build-v2.yml
    with:
      ref: ${{ inputs.release_tag }}
      release_type: main
      pre_release: ${{ inputs.pre_release }}
      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]
    uses: ./.github/workflows/docker-build-v2.yml
    with:
      ref: ${{ inputs.release_tag }}
      release_type: main-backend
      pre_release: ${{ inputs.pre_release }}
      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]
    uses: ./.github/workflows/docker-build-v2.yml
    with:
      ref: ${{ inputs.release_tag }}
      release_type: main-frontend
      pre_release: ${{ inputs.pre_release }}
      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]
    uses: ./.github/workflows/docker-build-v2.yml
    with:
      ref: ${{ inputs.release_tag }}
      release_type: main-ep
      pre_release: ${{ inputs.pre_release }}
      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]
    uses: ./.github/workflows/docker-build-v2.yml
    with:
      ref: ${{ inputs.release_tag }}
      release_type: main-all
      pre_release: ${{ inputs.pre_release }}
      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]
    if: |
      always() &&
      !cancelled() &&
      !inputs.dry_run &&
      inputs.create_release &&
      needs.build-main.result == 'success' &&
      needs.publish-main.result == 'success'
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist-main
          path: dist
      - name: Create Release
        uses: ncipollo/release-action@v1
        with:
          artifacts: dist/*
          token: ${{ secrets.GITHUB_TOKEN }}
          draft: false
          generateReleaseNotes: true
          prerelease: ${{ inputs.pre_release }}
          tag: ${{ needs.determine-main-version.outputs.version }}
          allowUpdates: true
          updateOnlyUnreleased: false
release-lfx matrix perms .github/workflows/release-lfx.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
validate-version, run-tests, release-lfx, build-docker, create-release, test-release, notify
Matrix
python-version, variant→ 3.10, 3.11, 3.12, 3.13, alpine, production
Actions
astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/metadata-action, docker/build-push-action, softprops/action-gh-release
Commands
  • cd src/lfx # Use uv tree to get package info, consistent with nightly workflow name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}') version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}') # Strip leading 'v' if present version=$(echo $version | sed 's/^v//') echo "current_version=$version" >> $GITHUB_OUTPUT if [ "$version" != "${{ github.event.inputs.version }}" ]; then echo "❌ Version mismatch: package has $version but input is ${{ github.event.inputs.version }}" echo "Please update the version in pyproject.toml first" echo "should_release=false" >> $GITHUB_OUTPUT exit 1 fi # Check if version already exists on PyPI if curl -s "https://pypi.org/pypi/lfx/json" | jq -r '.releases | keys[]' | grep -q "^${{ github.event.inputs.version }}$"; then echo "❌ Version ${{ github.event.inputs.version }} already exists on PyPI" echo "should_release=false" >> $GITHUB_OUTPUT exit 1 fi echo "✅ Version ${{ github.event.inputs.version }} is valid and not yet released" echo "should_release=true" >> $GITHUB_OUTPUT
  • cd src/lfx make test
  • cd src/lfx uv pip install . uv run lfx --help uv run lfx run --help uv run lfx serve --help
  • uv sync --dev --package lfx
  • cd src/lfx # Use uv tree to get package info, consistent with nightly workflow name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}') version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}') # Verify package name if [ "$name" != "lfx" ]; then echo "Package name $name does not match lfx. Exiting the workflow." exit 1 fi # Strip leading 'v' if present version=$(echo $version | sed 's/^v//') # Verify version matches input if [ "$version" != "${{ github.event.inputs.version }}" ]; then echo "Version $version does not match input ${{ github.event.inputs.version }}. Exiting the workflow." exit 1 fi echo "version=$version" >> $GITHUB_OUTPUT
  • cd src/lfx rm -rf dist/ uv build --wheel --out-dir dist
  • cd src/lfx ls -la dist/ # Verify wheel contents unzip -l dist/*.whl | grep -E "(lfx/__main__.py|lfx/cli/run.py|lfx/cli/commands.py)"
  • cd src/lfx uv pip install dist/*.whl --force-reinstall uv run lfx --help echo "LFX CLI test completed successfully"
View raw YAML
name: LFX Release
run-name: LFX Release ${{ github.event.inputs.version || 'dev' }} by @${{ github.actor }}

on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version to release (e.g., 0.1.0)"
        required: true
        type: string
      publish_pypi:
        description: "Publish to PyPI"
        required: true
        type: boolean
        default: true
      build_docker:
        description: "Build and publish Docker images"
        required: true
        type: boolean
        default: true
      pre_release:
        description: "Mark as pre-release"
        required: false
        type: boolean
        default: false
      create_github_release:
        description: "Create GitHub release"
        required: true
        type: boolean
        default: true

env:
  PYTHON_VERSION: "3.13"

permissions:
  contents: write
  packages: write

jobs:
  validate-version:
    name: Validate Version
    runs-on: ubuntu-latest
    outputs:
      should_release: ${{ steps.check.outputs.should_release }}
      current_version: ${{ steps.check.outputs.current_version }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - 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: Check version
        id: check
        run: |
          cd src/lfx
          # Use uv tree to get package info, consistent with nightly workflow
          name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}')
          version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}')

          # Strip leading 'v' if present
          version=$(echo $version | sed 's/^v//')
          echo "current_version=$version" >> $GITHUB_OUTPUT

          if [ "$version" != "${{ github.event.inputs.version }}" ]; then
            echo "❌ Version mismatch: package has $version but input is ${{ github.event.inputs.version }}"
            echo "Please update the version in pyproject.toml first"
            echo "should_release=false" >> $GITHUB_OUTPUT
            exit 1
          fi

          # Check if version already exists on PyPI
          if curl -s "https://pypi.org/pypi/lfx/json" | jq -r '.releases | keys[]' | grep -q "^${{ github.event.inputs.version }}$"; then
            echo "❌ Version ${{ github.event.inputs.version }} already exists on PyPI"
            echo "should_release=false" >> $GITHUB_OUTPUT
            exit 1
          fi

          echo "✅ Version ${{ github.event.inputs.version }} is valid and not yet released"
          echo "should_release=true" >> $GITHUB_OUTPUT

  run-tests:
    name: Run Tests
    needs: validate-version
    if: needs.validate-version.outputs.should_release == 'true'
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.10", "3.11", "3.12", "3.13"]
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Setup Environment
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false

      - name: Run LFX tests
        run: |
          cd src/lfx
          make test

      - name: Test CLI installation
        run: |
          cd src/lfx
          uv pip install .
          uv run lfx --help
          uv run lfx run --help
          uv run lfx serve --help

  release-lfx:
    name: Build and Release LFX
    needs: [validate-version, run-tests]
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.check-version.outputs.version }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - 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 LFX dependencies
        run: uv sync --dev --package lfx

      - name: Verify Version
        id: check-version
        run: |
          cd src/lfx
          # Use uv tree to get package info, consistent with nightly workflow
          name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}')
          version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}')

          # Verify package name
          if [ "$name" != "lfx" ]; then
            echo "Package name $name does not match lfx. Exiting the workflow."
            exit 1
          fi

          # Strip leading 'v' if present
          version=$(echo $version | sed 's/^v//')

          # Verify version matches input
          if [ "$version" != "${{ github.event.inputs.version }}" ]; then
            echo "Version $version does not match input ${{ github.event.inputs.version }}. Exiting the workflow."
            exit 1
          fi

          echo "version=$version" >> $GITHUB_OUTPUT

      - name: Build distribution
        run: |
          cd src/lfx
          rm -rf dist/
          uv build --wheel --out-dir dist

      - name: Check build artifacts
        run: |
          cd src/lfx
          ls -la dist/
          # Verify wheel contents
          unzip -l dist/*.whl | grep -E "(lfx/__main__.py|lfx/cli/run.py|lfx/cli/commands.py)"

      - name: Test installation from wheel
        run: |
          cd src/lfx
          uv pip install dist/*.whl --force-reinstall
          uv run lfx --help
          echo "LFX CLI test completed successfully"

      - name: Upload artifacts
        uses: actions/upload-artifact@v6
        with:
          name: lfx-dist
          path: src/lfx/dist/
          retention-days: 5

      - name: Publish to PyPI
        if: github.event.inputs.publish_pypi == 'true'
        env:
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          cd src/lfx
          uv publish dist/*.whl

  build-docker:
    name: Build Docker Images
    needs: [validate-version, run-tests]
    if: github.event.inputs.build_docker == 'true'
    runs-on: ubuntu-latest
    strategy:
      matrix:
        variant: [production, alpine]
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Docker System Info and Cleanup
        run: |
          echo "=== Docker System Usage Before Cleanup ==="
          docker system df || true
          docker buildx du || true

          echo "=== Cleaning up Docker System ==="
          docker system prune -af --volumes || true
          docker buildx prune -af || true

          echo "=== Docker System Usage After Cleanup ==="
          docker system df || true
          docker buildx du || true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Prepare Docker metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            langflowai/lfx
            ghcr.io/langflow-ai/lfx
          tags: |
            type=raw,value=${{ github.event.inputs.version }}${{ matrix.variant == 'alpine' && '-alpine' || '' }}
            type=raw,value=latest${{ matrix.variant == 'alpine' && '-alpine' || '' }},enable=${{ github.event.inputs.pre_release == 'false' }}
          labels: |
            org.opencontainers.image.title=LFX
            org.opencontainers.image.description=Langflow Executor - CLI tool for running Langflow AI workflows
            org.opencontainers.image.vendor=Langflow
            org.opencontainers.image.version=${{ github.event.inputs.version }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: src/lfx/docker/Dockerfile${{ matrix.variant == 'alpine' && '.alpine' || '' }}
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            LFX_VERSION=${{ github.event.inputs.version }}

  create-release:
    name: Create GitHub Release
    needs: [release-lfx, build-docker]
    if: always() && github.event.inputs.create_github_release == 'true' && needs.release-lfx.result == 'success'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Download artifacts
        uses: actions/download-artifact@v7
        with:
          name: lfx-dist
          path: dist/

      - name: Generate release notes
        id: notes
        run: |
          cat > release_notes.md << EOF
          # LFX ${{ github.event.inputs.version }}

          ## 🚀 Installation

          ### PyPI
          \`\`\`bash
          pip install lfx==${{ github.event.inputs.version }}
          # or
          uv pip install lfx==${{ github.event.inputs.version }}
          # or run without installing
          uvx lfx@${{ github.event.inputs.version }} --help
          \`\`\`

          ### Docker
          \`\`\`bash
          # Standard image
          docker pull langflowai/lfx:${{ github.event.inputs.version }}

          # Alpine image (smaller)
          docker pull langflowai/lfx:${{ github.event.inputs.version }}-alpine

          # Run a flow
          docker run --rm -v \$(pwd):/app/data langflowai/lfx:${{ github.event.inputs.version }} lfx run flow.json --input-value "Hello"
          \`\`\`

          ## 📦 What's New

          <!-- Add release notes here -->

          ## 📋 Checksums

          \`\`\`
          $(cd dist && sha256sum *)
          \`\`\`

          ---

          **Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ needs.validate-version.outputs.current_version }}...lfx-v${{ github.event.inputs.version }}
          EOF

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: lfx-v${{ github.event.inputs.version }}
          name: LFX ${{ github.event.inputs.version }}
          body_path: release_notes.md
          draft: false
          prerelease: ${{ github.event.inputs.pre_release }}
          files: |
            dist/*
          generate_release_notes: true

  test-release:
    name: Test Release
    needs: [release-lfx, build-docker]
    if: always() && (needs.release-lfx.result == 'success' || needs.build-docker.result == 'success')
    runs-on: ubuntu-latest
    steps:
      - name: Wait for PyPI propagation
        if: needs.release-lfx.result == 'success'
        run: sleep 60

      - name: Test PyPI installation
        if: needs.release-lfx.result == 'success'
        run: |
          # Test installation using uv
          uv pip install lfx==${{ github.event.inputs.version }}
          uv run lfx --help

      - name: Test Docker image
        if: needs.build-docker.result == 'success'
        run: |
          # Test standard image
          docker run --rm langflowai/lfx:${{ github.event.inputs.version }} lfx --help

          # Test alpine image
          docker run --rm langflowai/lfx:${{ github.event.inputs.version }}-alpine lfx --help

          # Test with a simple flow
          cat > test_flow.json << 'EOF'
          {
            "nodes": [],
            "edges": []
          }
          EOF

          docker run --rm -v $(pwd):/app/data langflowai/lfx:${{ github.event.inputs.version }} \
            lfx run /app/data/test_flow.json --input-value "test" || true

  notify:
    name: Notify Release Status
    needs: [create-release, test-release]
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Notify success
        if: needs.create-release.result == 'success'
        run: |
          echo "✅ LFX ${{ github.event.inputs.version }} released successfully!"
          echo "PyPI: https://pypi.org/project/lfx/${{ github.event.inputs.version }}/"
          echo "Docker Hub: https://hub.docker.com/r/langflowai/lfx/tags"
          echo "GitHub Release: https://github.com/${{ github.repository }}/releases/tag/lfx-v${{ github.event.inputs.version }}"

      - name: Notify failure
        if: needs.create-release.result != 'success'
        run: |
          echo "❌ LFX ${{ github.event.inputs.version }} release failed!"
          exit 1
release_nightly .github/workflows/release_nightly.yml
Triggers
workflow_call
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
build-nightly-lfx, build-nightly-base, build-nightly-main, test-cross-platform, publish-nightly-lfx, publish-nightly-base, publish-nightly-main, call_docker_build_base, call_docker_build_main
Actions
astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv
Commands
  • cd src/lfx && uv sync
  • cd src/lfx name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}') version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}') if [ "$name" != "lfx-nightly" ]; then echo "Name $name does not match lfx-nightly. Exiting the workflow." exit 1 fi if [ "$version" != "${{ inputs.nightly_tag_lfx }}" ]; then echo "Version $version does not match nightly tag ${{ inputs.nightly_tag_lfx }}. Exiting the workflow." exit 1 fi # Strip the leading `v` from the version version=$(echo $version | sed 's/^v//') echo "version=$version" >> $GITHUB_OUTPUT
  • cd src/lfx rm -rf dist/ uv build --wheel --out-dir dist
  • cd src/lfx uv pip install dist/*.whl --force-reinstall uv run lfx --help echo "LFX CLI test completed successfully"
  • uv sync
  • echo "Installing LFX from built wheel to test the actual distribution package..." # While workspace resolution installs from source, we want to test the exact # wheel that will be published to PyPI to catch any packaging issues uv pip install --force-reinstall --no-deps lfx-dist/*.whl
  • name=$(uv tree | grep 'langflow-base' | awk '{print $2}' | head -n 1) version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | head -n 1) # Strip extras from package name (e.g., "langflow-base-nightly[complete]" -> "langflow-base-nightly") name_without_extras=$(echo $name | sed 's/\[.*\]//') if [ "$name_without_extras" != "langflow-base-nightly" ]; then echo "Name $name_without_extras does not match langflow-base-nightly. Exiting the workflow." exit 1 fi if [ "$version" != "${{ inputs.nightly_tag_base }}" ]; then echo "Version $version does not match nightly tag ${{ inputs.nightly_tag_base }}. Exiting the workflow." exit 1 fi # Strip the leading `v` from the version version=$(echo $version | sed 's/^v//') echo "version=$version" >> $GITHUB_OUTPUT
  • rm -rf src/backend/base/dist rm -rf dist make build base=true args="--no-sources --wheel"
View raw YAML
name: Langflow Nightly Build
run-name: Langflow Nightly Release by @${{ github.actor }}

on:
  workflow_call:
    inputs:
      build_docker_base:
        description: "Build Docker Image for Langflow Nightly Base"
        required: true
        type: boolean
        default: false
      build_docker_main:
        description: "Build Docker Image for Langflow Nightly"
        required: true
        type: boolean
        default: false
      build_docker_ep:
        description: "Build Docker Image for Langflow Nightly with Entrypoint"
        required: false
        type: boolean
        default: false
      build_lfx:
        description: "Build and release LFX package"
        required: false
        type: boolean
        default: false
      nightly_tag_release:
        description: "Tag for the nightly main build"
        required: true
        type: string
      nightly_tag_base:
        description: "Tag for the nightly base build"
        required: true
        type: string
      nightly_tag_lfx:
        description: "Tag for the nightly LFX build"
        required: false
        type: string
      push_to_registry:
        description: "Whether to push images to registries. Set to false for testing builds without publishing."
        required: true
        type: boolean
        default: false

env:
  POETRY_VERSION: "1.8.3"
  PYTHON_VERSION: "3.13"

jobs:
  build-nightly-lfx:
    name: Build LFX Nightly
    if: ${{ inputs.build_lfx }}
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.verify.outputs.version }}
    defaults:
      run:
        shell: bash
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.nightly_tag_release }}
          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 LFX dependencies
        run: cd src/lfx && uv sync

      - name: Verify Nightly Name and Version
        id: verify
        run: |
          cd src/lfx
          name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}')
          version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}')
          if [ "$name" != "lfx-nightly" ]; then
            echo "Name $name does not match lfx-nightly. Exiting the workflow."
            exit 1
          fi
          if [ "$version" != "${{ inputs.nightly_tag_lfx }}" ]; then
            echo "Version $version does not match nightly tag ${{ inputs.nightly_tag_lfx }}. Exiting the workflow."
            exit 1
          fi
          # Strip the leading `v` from the version
          version=$(echo $version | sed 's/^v//')
          echo "version=$version" >> $GITHUB_OUTPUT

      - name: Build LFX for distribution
        run: |
          cd src/lfx
          rm -rf dist/
          uv build --wheel --out-dir dist

      - name: Test LFX CLI
        run: |
          cd src/lfx
          uv pip install dist/*.whl --force-reinstall
          uv run lfx --help
          echo "LFX CLI test completed successfully"

      # PyPI publishing moved to after cross-platform testing

      - name: Upload LFX Artifact
        uses: actions/upload-artifact@v6
        with:
          name: dist-nightly-lfx
          path: src/lfx/dist

  build-nightly-base:
    name: Build Langflow Nightly Base
    needs: [build-nightly-lfx]
    if: ${{ always() && (needs.build-nightly-lfx.result == 'success' || inputs.build_lfx == false) }}
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
    outputs:
      version: ${{ steps.verify.outputs.version }}
      skipped: ${{ steps.verify.outputs.skipped }}
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.nightly_tag_release }}
          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: Download LFX artifact
        if: inputs.build_lfx
        uses: actions/download-artifact@v7
        with:
          name: dist-nightly-lfx
          path: lfx-dist

      - name: Install the project
        run: uv sync

      - name: Force reinstall LFX from built wheel
        if: inputs.build_lfx
        run: |
          echo "Installing LFX from built wheel to test the actual distribution package..."
          # While workspace resolution installs from source, we want to test the exact
          # wheel that will be published to PyPI to catch any packaging issues
          uv pip install --force-reinstall --no-deps lfx-dist/*.whl

      - name: Verify Nightly Name and Version
        id: verify
        run: |
          name=$(uv tree | grep 'langflow-base' | awk '{print $2}' | head -n 1)
          version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | head -n 1)
          # Strip extras from package name (e.g., "langflow-base-nightly[complete]" -> "langflow-base-nightly")
          name_without_extras=$(echo $name | sed 's/\[.*\]//')
          if [ "$name_without_extras" != "langflow-base-nightly" ]; then
            echo "Name $name_without_extras does not match langflow-base-nightly. Exiting the workflow."
            exit 1
          fi
          if [ "$version" != "${{ inputs.nightly_tag_base }}" ]; then
            echo "Version $version does not match nightly tag ${{ inputs.nightly_tag_base }}. Exiting the workflow."
            exit 1
          fi
          # Strip the leading `v` from the version
          version=$(echo $version | sed 's/^v//')
          echo "version=$version" >> $GITHUB_OUTPUT

      - name: Build Langflow Base for distribution
        run: |
          rm -rf src/backend/base/dist
          rm -rf dist
          make build base=true args="--no-sources --wheel"

      - name: Test Langflow Base 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/
          # Install with [complete] extra to test all optional dependencies
          WHEEL_FILE=$(ls src/backend/base/dist/*.whl)
          uv pip install "${WHEEL_FILE}[complete]"
          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-nightly-base
          path: src/backend/base/dist

  build-nightly-main:
    name: Build Langflow Nightly Main
    needs: [build-nightly-base]
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.verify.outputs.version }}
    defaults:
      run:
        shell: bash
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.nightly_tag_release}}
          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: Download LFX artifact
        if: inputs.build_lfx
        uses: actions/download-artifact@v7
        with:
          name: dist-nightly-lfx
          path: lfx-dist

      - name: Download base artifact
        uses: actions/download-artifact@v7
        with:
          name: dist-nightly-base
          path: base-dist

      - name: Install the project
        run: uv sync

      - name: Force reinstall packages from built wheels
        run: |
          # While workspace resolution installs from source, we want to test the exact
          # wheels that will be published to PyPI to catch any packaging issues
          if [ "${{ inputs.build_lfx }}" == "true" ]; then
            echo "Installing LFX from built wheel..."
            uv pip install --force-reinstall --no-deps lfx-dist/*.whl
          fi
          echo "Installing langflow-base from built wheel..."
          uv pip install --force-reinstall --no-deps base-dist/*.whl

      - name: Verify Nightly Name and Version
        id: verify
        run: |
          name=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $1}')
          version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $2}')
          if [ "$name" != "langflow-nightly" ]; then
            echo "Name $name does not match langflow-nightly. Exiting the workflow."
            exit 1
          fi
          if [ "$version" != "${{ inputs.nightly_tag_release }}" ]; then
            echo "Version $version does not match nightly tag ${{ inputs.nightly_tag_release }}. Exiting the workflow."
            exit 1
          fi
          # Strip the leading `v` from the version
          version=$(echo $version | sed 's/^v//')
          echo "version=$version" >> $GITHUB_OUTPUT
      - name: Wait for PyPI Propagation
        if: needs.build-nightly-base.outputs.skipped == 'false'
        run: sleep 300 # wait for 5 minutes to ensure PyPI propagation of base

      - name: Build Langflow Main for distribution
        run: make build main=true args="--no-sources --wheel"
      - name: Test Langflow Main 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-nightly-main
          path: dist

  test-cross-platform:
    name: Test Cross-Platform Installation
    needs: [build-nightly-lfx, build-nightly-base, build-nightly-main]
    uses: ./.github/workflows/cross-platform-test.yml
    with:
      base-artifact-name: "dist-nightly-base"
      main-artifact-name: "dist-nightly-main"
      lfx-artifact-name: "dist-nightly-lfx"

  publish-nightly-lfx:
    name: Publish LFX Nightly to PyPI
    needs: [build-nightly-lfx, test-cross-platform]
    if: ${{ inputs.build_lfx }}
    runs-on: ubuntu-latest
    steps:
      - name: Check out the code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.nightly_tag_release }}
          persist-credentials: true
      - name: Download LFX artifact
        uses: actions/download-artifact@v7
        with:
          name: dist-nightly-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
        env:
          POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          make lfx_publish

  publish-nightly-base:
    name: Publish Langflow Base Nightly to PyPI
    needs: [build-nightly-base, test-cross-platform, publish-nightly-lfx]
    if: ${{ always() && needs.build-nightly-base.result == 'success' && needs.test-cross-platform.result == 'success' && (needs.publish-nightly-lfx.result == 'success' || inputs.build_lfx == false) }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.nightly_tag_release }}
          persist-credentials: true
      - name: Download base artifact
        uses: actions/download-artifact@v7
        with:
          name: dist-nightly-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: needs.build-nightly-base.outputs.skipped == 'false'
        env:
          POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          make publish base=true

  publish-nightly-main:
    name: Publish Langflow Main Nightly to PyPI
    needs: [build-nightly-main, test-cross-platform, publish-nightly-base]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.nightly_tag_release }}
          persist-credentials: true
      - name: Download main artifact
        uses: actions/download-artifact@v7
        with:
          name: dist-nightly-main
          path: dist
      - name: Setup Environment
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: false
          python-version: "3.13"
      - name: Publish to PyPI
        env:
          POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          make publish main=true

  call_docker_build_base:
    name: Call Docker Build Workflow for Langflow Base
    if: ${{ always() && inputs.build_docker_base }}
    needs: [build-nightly-base, build-nightly-main]
    uses: ./.github/workflows/docker-nightly-build.yml
    with:
      ref: ${{ inputs.nightly_tag_release }}
      release_type: nightly-base
      push_to_registry: ${{ inputs.push_to_registry }}
    secrets: inherit

  call_docker_build_main:
    name: Call Docker Build Workflow for Langflow
    if: ${{ always() && inputs.build_docker_main }}
    needs: [build-nightly-main, call_docker_build_base]
    uses: ./.github/workflows/docker-nightly-build.yml
    with:
      ref: ${{ inputs.nightly_tag_release }}
      release_type: nightly-main
      push_to_registry: ${{ inputs.push_to_registry }}
    secrets: inherit

  # TODO: Uncomment this when our runner can fit the builds that contain pytorch (and other large dependencies)
  # call_docker_build_main_all:
  #   name: Call Docker Build Workflow for langflow-all
  #   if: always() && ${{ inputs.build_docker_main == 'true' }}
  #   needs: [build-nightly-main]
  #   uses: ./.github/workflows/docker-nightly-build.yml
  #   with:
  #     ref: ${{ inputs.nightly_tag_release }}
  #     release_type: nightly-main-all
  #     main_version: ${{ inputs.nightly_tag_release }}
  #   secrets: inherit

  # call_docker_build_main_ep:
  #   name: Call Docker Build Workflow for Langflow with Entrypoint
  #   if: ${{ always() && inputs.build_docker_ep }}
  #   needs: [build-nightly-main, call_docker_build_main]
  #   uses: ./.github/workflows/docker-build-v2.yml
  #   with:
  #     ref: ${{ inputs.nightly_tag_release }}
  #     release_type: main-ep
  #     push_to_registry: ${{ inputs.push_to_registry }}
  #   secrets: inherit
request-docs-review .github/workflows/request-docs-review.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
request-docs-review
View raw YAML
name: Request Docs Review
on:
  pull_request:
    types: [labeled]

jobs:
  request-docs-review:
    if: github.event.label.name == 'needs-docs'
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/github-script@v8
        with:
          script: |
            const prAuthor = context.payload.pull_request.user.login;
            console.log(`PR author: ${prAuthor}`);

            const reviewers = '${{ vars.DOCS_TEAM_MEMBERS }}'
              .split(',')
              .map(u => u.trim())
              .filter(u => u.length > 0 && u !== prAuthor);

            if (reviewers.length > 0) {
              console.log(`Requesting review from: ${reviewers.join(', ')}`);

              await github.rest.pulls.requestReviewers({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.issue.number,
                reviewers: reviewers
              });

              console.log('Review request sent successfully');
            } else {
              console.log('No eligible reviewers found (all team members may be the PR author)');
            }
smoke-tests .github/workflows/smoke-tests.yml
Triggers
pull_request, release, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
backend-smoke-tests, lfx-smoke-tests, frontend-smoke-tests, comment-results
Actions
astral-sh/setup-uv, astral-sh/setup-uv
Commands
  • uv sync --dev
  • uv run pytest \ src/backend/tests/unit/test_database.py \ src/backend/tests/unit/test_login.py \ src/backend/tests/unit/api/v1/test_validate.py \ src/backend/tests/unit/test_endpoints.py \ src/backend/tests/unit/api/v1/test_flows.py \ src/backend/tests/unit/test_chat_endpoint.py \ src/backend/tests/unit/api/v1/test_api_key.py \ src/backend/tests/unit/api/v1/test_endpoints.py \ src/backend/tests/unit/components/languagemodels/test_openai_model.py \ src/backend/tests/unit/components/agents/test_agent_component.py \ src/backend/tests/unit/services/tracing/test_tracing_service.py \ -m 'not api_key_required' \ --tb=short \ -v
  • cd src/lfx uv sync --dev
  • cd src/lfx uv run pytest \ tests/unit/graph/test_graph.py \ tests/unit/custom/component/test_component_instance_attributes.py \ tests/unit/schema/test_schema_message.py \ -m 'not api_key_required' \ --tb=short \ --maxfail=5 \ -v
  • cd src/frontend npm ci
  • cd src/frontend CI=true npx jest --ci --watchAll=false --passWithNoTests
View raw YAML
name: Smoke Tests

on:
  pull_request:
    types: [opened, labeled, synchronize, reopened]
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      ref:
        description: 'Git ref to checkout (branch, tag, or commit SHA)'
        required: false
        default: ''
        type: string

jobs:
  backend-smoke-tests:
    if: contains(github.event.pull_request.labels.*.name, 'smoke-test') || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
    name: "Backend Smoke Tests"
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Checkout PR
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          ref: ${{ github.event.inputs.ref || github.ref }}

      - name: Set up Python 3.12
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "latest"

      - name: Install backend dependencies
        run: |
          uv sync --dev

      - name: Run backend smoke tests (critical tests only)
        run: |
          uv run pytest \
            src/backend/tests/unit/test_database.py \
            src/backend/tests/unit/test_login.py \
            src/backend/tests/unit/api/v1/test_validate.py \
            src/backend/tests/unit/test_endpoints.py \
            src/backend/tests/unit/api/v1/test_flows.py \
            src/backend/tests/unit/test_chat_endpoint.py \
            src/backend/tests/unit/api/v1/test_api_key.py \
            src/backend/tests/unit/api/v1/test_endpoints.py \
            src/backend/tests/unit/components/languagemodels/test_openai_model.py \
            src/backend/tests/unit/components/agents/test_agent_component.py \
            src/backend/tests/unit/services/tracing/test_tracing_service.py \
            -m 'not api_key_required' \
            --tb=short \
            -v
        env:
          LANGFLOW_SUPERUSER: admin
          LANGFLOW_SUPERUSER_PASSWORD: 123456

  lfx-smoke-tests:
    # created a separate label for this since it's a different test suite for LFX components
    if: contains(github.event.pull_request.labels.*.name, 'smoke-test') || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
    name: "LFX Smoke Tests"
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout PR
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          ref: ${{ github.event.inputs.ref || github.ref }}

      - name: Set up Python 3.12
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          version: "latest"

      - name: Install lfx dependencies (isolated)
        run: |
          cd src/lfx
          uv sync --dev

      - name: Run lfx smoke tests (critical tests only)
        run: |
          cd src/lfx
          uv run pytest \
            tests/unit/graph/test_graph.py \
            tests/unit/custom/component/test_component_instance_attributes.py \
            tests/unit/schema/test_schema_message.py \
            -m 'not api_key_required' \
            --tb=short \
            --maxfail=5 \
            -v
        env:
          LANGFLOW_SUPERUSER: admin
          LANGFLOW_SUPERUSER_PASSWORD: 123456

  frontend-smoke-tests:
    if: contains(github.event.pull_request.labels.*.name, 'smoke-test') || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
    name: "Frontend Smoke Tests"
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout PR
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          ref: ${{ github.event.inputs.ref || github.ref }}

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: "22"
          cache: "npm"
          cache-dependency-path: src/frontend/package-lock.json

      - name: Install frontend dependencies
        run: |
          cd src/frontend
          npm ci

      - name: Run frontend smoke tests (unit tests only)
        run: |
          cd src/frontend
          CI=true npx jest --ci --watchAll=false --passWithNoTests
        env:
          NODE_ENV: test

  comment-results:
    if: always() && (contains(github.event.pull_request.labels.*.name, 'smoke-test') || github.event_name == 'release' || github.event_name == 'workflow_dispatch')
    name: "Comment Results"
    needs: [backend-smoke-tests, lfx-smoke-tests, frontend-smoke-tests]
    runs-on: ubuntu-latest

    steps:
      - name: Comment on PR with results
        uses: actions/github-script@v8
        with:
          script: |
            const backendStatus = '${{ needs.backend-smoke-tests.result }}';
            const lfxStatus = '${{ needs.lfx-smoke-tests.result }}';
            const frontendStatus = '${{ needs.frontend-smoke-tests.result }}';
            
            const overallSuccess = backendStatus === 'success' && lfxStatus === 'success' && frontendStatus === 'success';
            const emoji = overallSuccess ? '✅' : '❌';
            const status = overallSuccess ? 'passed' : 'failed';
            
            const backendEmoji = backendStatus === 'success' ? '✅' : '❌';
            const lfxEmoji = lfxStatus === 'success' ? '✅' : '❌';
            const frontendEmoji = frontendStatus === 'success' ? '✅' : '❌';

            const comment = `${emoji} **Smoke tests ${status}**

            Critical functionality validated:

            - ${backendEmoji} **Backend**: 11 essential test files (database, auth, API endpoints, flows, components)
            - ${lfxEmoji} **LFX**: Graph tests (langflow execution) - runs in isolation
            - ${frontendEmoji} **Frontend**: Unit tests only (components, utilities)
            
            **Coverage**: Core functionality without external dependencies

            View details in the [Actions tab](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).`;

            // Only comment on PRs, not on releases or manual runs
            if (context.payload.pull_request) {
              await github.rest.issues.createComment({
                issue_number: context.payload.pull_request.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body: comment
              });
            } else {
              console.log('Smoke test results:', comment);
            }
store_pytest_durations .github/workflows/store_pytest_durations.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
build
Actions
astral-sh/setup-uv, peter-evans/create-pull-request
Commands
  • uv sync
  • uv run pytest src/backend/tests/unit --timeout=150 --durations-path src/backend/tests/.test_durations --splitting-algorithm least_duration --store-durations
View raw YAML
name: Store pytest durations

on:
  workflow_dispatch:
  schedule:
    # Run job at 6:30 UTC every Monday (10.30pm PST/11.30pm PDT Sunday night)
    - cron: "30 6 * * 1"

env:
  PYTEST_RUN_PATH: "src/backend/tests"

jobs:
  build:
    name: Run pytest and store durations
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    env:
      OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      ASTRA_DB_API_ENDPOINT: ${{ secrets.ASTRA_DB_API_ENDPOINT }}
      ASTRA_DB_APPLICATION_TOKEN: ${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}
    steps:
      - uses: actions/checkout@v6
      - 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: Run unit tests
        id: run_tests
        continue-on-error: true
        run: uv run pytest src/backend/tests/unit --timeout=150 --durations-path src/backend/tests/.test_durations --splitting-algorithm least_duration --store-durations


      - name: Close existing PRs
        uses: actions/github-script@v8
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const { data: pulls } = await github.rest.pulls.list({
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'open'
            });

            for (const pull of pulls) {
              if (pull.title === "chore: update test durations") {
                await github.rest.pulls.update({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  pull_number: pull.number,
                  state: 'closed'
                });
              }
            }

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v8
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          branch-token: ${{ secrets.GITHUB_TOKEN }}
          commit-message: "chore: update test durations"
          title: "chore: update test durations"
          body: |
            Automated PR to update test durations file.

            This PR was automatically created by the store_pytest_durations workflow.
          branch: update-test-durations
          branch-suffix: timestamp
          delete-branch: true
          maintainer-can-modify: true
style-check-py matrix .github/workflows/style-check-py.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
lint
Matrix
python-version→ 3.13
Actions
astral-sh/setup-uv
Commands
  • echo "::add-matcher::.github/workflows/matchers/ruff.json"
  • uv run --only-dev ruff check --output-format=github .
View raw YAML
name: Ruff Style Check

on:
  pull_request:
    types: [opened, synchronize, reopened, auto_merge_enabled]
    paths:
      - "**/*.py"

jobs:
  lint:
    name: Ruff Style Check
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version:
          - "3.13"
    steps:
      - name: Check out the code at a specific ref
        uses: actions/checkout@v6
      - name: "Setup Environment"
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"
          python-version: ${{ matrix.python-version }}
          prune-cache: false
      - name: Register problem matcher
        run: echo "::add-matcher::.github/workflows/matchers/ruff.json"
      - name: Run Ruff Check
        run: uv run --only-dev ruff check --output-format=github .

template-tests perms .github/workflows/template-tests.yml
Triggers
pull_request, workflow_dispatch
Runs on
ubuntu-latest
Jobs
test-starter-projects
Actions
astral-sh/setup-uv
Commands
  • uv sync --dev
  • uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v -n auto
View raw YAML
name: Template Tests

on:
  pull_request:
    paths:
      - 'src/backend/base/langflow/initial_setup/starter_projects/**'
      - 'src/backend/tests/unit/template/test_starter_projects.py'
      - 'src/backend/base/langflow/utils/template_validation.py'
      - '.github/workflows/template-tests.yml'
  workflow_dispatch:

permissions:
  contents: read
  pull-requests: read

jobs:
  test-starter-projects:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v6

    - name: Set up Python 3.12
      uses: actions/setup-python@v6
      with:
        python-version: 3.12

    - name: Install uv
      uses: astral-sh/setup-uv@v6
      with:
        version: "latest"

    - name: Install dependencies
      run: |
        uv sync --dev

    - name: Test all starter project templates
      run: |
        uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v -n auto
typescript_test matrix .github/workflows/typescript_test.yml
Triggers
workflow_call, workflow_dispatch
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' }}, ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}, ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
Jobs
determine-test-suite, setup-and-test, merge-reports
Matrix
Actions
dorny/paths-filter, astral-sh/setup-uv, nick-fields/retry
Commands
  • # Start with input suites if provided, otherwise empty array echo "Changes filter output: $(echo '${{ toJSON(steps.filter.outputs) }}')" SUITES='${{ inputs.suites }}' echo "Initial suites: $SUITES" TEST_GREP="" echo "Inputs Release: ${{ inputs.release }}" RELEASE="${{ inputs.release || 'false' }}" echo "Release build: $RELEASE" # Only set to release if it's explicitly a release build if [[ "$RELEASE" == "true" ]]; then SUITES='["release"]' echo "Release build detected - setting suites to: $SUITES" # grep pattern for release is the @release tag - run all tests TEST_GREP='--grep="@release"' else # If input suites were not provided, determine based on changes if [[ "$SUITES" == "[]" ]]; then echo "No input suites provided - determining from changes" SUITES='[]' # Ensure we start with a valid JSON array TAGS=() # Add suites and tags based on changed files if [[ "${{ steps.filter.outputs.components }}" == "true" ]]; then SUITES=$(echo $SUITES | jq -c '. += ["components"]') TAGS+=("@components") echo "Added components suite" fi if [[ "${{ steps.filter.outputs.starter-projects }}" == "true" ]]; then SUITES=$(echo $SUITES | jq -c '. += ["starter-projects"]') TAGS+=("@starter-projects") echo "Added starter-projects suite" fi if [[ "${{ steps.filter.outputs.workspace }}" == "true" ]]; then SUITES=$(echo $SUITES | jq -c '. += ["workspace"]') TAGS+=("@workspace") echo "Added workspace suite" fi if [[ "${{ steps.filter.outputs.api }}" == "true" ]]; then SUITES=$(echo $SUITES | jq -c '. += ["api"]') TAGS+=("@api") echo "Added api suite" fi if [[ "${{ steps.filter.outputs.database }}" == "true" ]]; then SUITES=$(echo $SUITES | jq -c '. += ["database"]') TAGS+=("@database") echo "Added database suite" fi if [[ "${{ steps.filter.outputs.mainpage }}" == "true" ]]; then SUITES=$(echo $SUITES | jq -c '. += ["mainpage"]') TAGS+=("@mainpage") echo "Added mainpage suite" fi if [[ "${{ steps.filter.outputs.development }}" == "true" ]]; then SUITES=$(echo $SUITES | jq -c '. += ["development"]') TAGS+=("@development") echo "Added development suite" fi # Create grep pattern if we have tags if [ ${#TAGS[@]} -gt 0 ]; then # Join tags with | for OR logic REGEX_PATTERN=$(IFS='|'; echo "${TAGS[*]}") TEST_GREP='--grep="${REGEX_PATTERN}"' else # No path-specific changes detected - default to running release tests # so that FE tests always run even for backend-only changes SUITES='["release"]' TEST_GREP='--grep="@release"' echo "No path-specific changes detected - defaulting to release suite" fi else # Process input suites to tags # First ensure SUITES is valid JSON if ! echo "$SUITES" | jq -e . > /dev/null 2>&1; then echo "Warning: Input suites is not valid JSON, attempting to fix" # Try to fix common issues like missing quotes if [[ "$SUITES" == "[development]" ]]; then SUITES='["development"]' elif [[ "$SUITES" =~ ^\[(.*)\]$ ]]; then # Extract items and add quotes ITEMS="${BASH_REMATCH[1]}" QUOTED_ITEMS=$(echo "$ITEMS" | sed 's/\([^,]*\)/"\1"/g') SUITES="[$QUOTED_ITEMS]" fi echo "Fixed suites: $SUITES" fi TAGS=() if echo "$SUITES" | jq -e 'contains(["components"])' > /dev/null; then TAGS+=("@components") fi if echo "$SUITES" | jq -e 'contains(["starter-projects"])' > /dev/null; then TAGS+=("@starter-projects") fi if echo "$SUITES" | jq -e 'contains(["workspace"])' > /dev/null; then TAGS+=("@workspace") fi if echo "$SUITES" | jq -e 'contains(["api"])' > /dev/null; then TAGS+=("@api") fi if echo "$SUITES" | jq -e 'contains(["database"])' > /dev/null; then TAGS+=("@database") fi if echo "$SUITES" | jq -e 'contains(["development"])' > /dev/null; then TAGS+=("@development") fi if [ ${#TAGS[@]} -gt 0 ]; then # Join tags with | for OR logic REGEX_PATTERN=$(IFS='|'; echo "${TAGS[*]}") TEST_GREP='--grep "${REGEX_PATTERN}"' fi fi fi # Ensure compact JSON output SUITES=$(echo "$SUITES" | jq -c '.') echo "Final test suites to run: $SUITES" echo "Test grep pattern: $TEST_GREP" # Ensure proper JSON formatting for matrix output echo "matrix=$(echo $SUITES | jq -c .)" >> $GITHUB_OUTPUT echo "test_grep=$TEST_GREP" >> $GITHUB_OUTPUT echo "suites=$SUITES" >> $GITHUB_OUTPUT
  • npm ci
  • cd src/frontend # Get the test count using playwright's built-in grep if [ -n "${{ steps.set-matrix.outputs.test_grep }}" ]; then TEST_COUNT=$(npx playwright test ${{ inputs.tests_folder }} ${{ steps.set-matrix.outputs.test_grep }} --list | wc -l) else TEST_COUNT=$(npx playwright test ${{ inputs.tests_folder }} --list | wc -l) fi echo "Total tests to run: $TEST_COUNT" # Calculate optimal shard count - 1 shard per 5 tests, min 1, max 70 SHARD_COUNT=$(( (TEST_COUNT + 4) / 5 )) if [ $SHARD_COUNT -lt 1 ]; then SHARD_COUNT=1 elif [ $SHARD_COUNT -gt 70 ]; then SHARD_COUNT=70 fi # Create the matrix combinations string MATRIX_COMBINATIONS="" for i in $(seq 1 $SHARD_COUNT); do if [ $i -gt 1 ]; then MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS," fi MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS{\"shardIndex\": $i, \"shardTotal\": $SHARD_COUNT}" done echo "matrix={\"include\":[$MATRIX_COMBINATIONS]}" >> "$GITHUB_OUTPUT"
  • npm ci
  • cd ./src/frontend npx playwright install --with-deps chromium
  • cd ./src/frontend npx playwright install chromium
  • uv sync --extra audio
  • touch .env echo "${{ secrets.ENV_VARS }}" > .env
View raw YAML
name: Run Frontend Tests

on:
  workflow_call:
    secrets:
      OPENAI_API_KEY:
        required: true
      STORE_API_KEY:
        required: true
      ANTHROPIC_API_KEY:
        required: true
      TAVILY_API_KEY:
        required: true
    inputs:
      suites:
        description: "Test suites to run (JSON array)"
        required: false
        type: string
        default: "[]"
      release:
        description: "Whether this is a release build"
        required: false
        type: boolean
        default: false
      tests_folder:
        description: "(Optional) Tests to run"
        required: false
        type: string
        default: "tests"
      ref:
        description: "(Optional) ref to checkout"
        required: false
        type: string
      runs-on:
        description: "Runner to use for the tests"
        required: false
        type: string
        default: "ubuntu-latest"
  workflow_dispatch:
    inputs:
      suites:
        description: "Test suites to run (JSON array)"
        required: false
        type: string
        default: "[]"
      release:
        description: "Whether this is a release build"
        required: false
        type: boolean
        default: false
      tests_folder:
        description: "(Optional) Tests to run"
        required: false
        type: string
        default: "tests"
      runs-on:
        description: "Runner to use for the tests"
        required: false
        type: choice
        options:
          - ubuntu-latest
          - windows-latest
          - self-hosted
          - '["self-hosted", "linux", "ARM64", "langflow-ai-arm64-40gb-ephemeral"]'
        default: ubuntu-latest

env:
  NODE_VERSION: "22"
  PYTHON_VERSION: "3.13"
  # Define the directory where Playwright browsers will be installed.
  # This path is used for caching across workflows
  PLAYWRIGHT_BROWSERS_PATH: "ms-playwright"
  PLAYWRIGHT_VERSION: "1.57.0"

jobs:
  determine-test-suite:
    name: Determine Test Suites and Shard Distribution
    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' }}
    outputs:
      matrix: ${{ steps.setup-matrix.outputs.matrix }}
      test_grep: ${{ steps.set-matrix.outputs.test_grep }}
      suites: ${{ steps.set-matrix.outputs.suites }}
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}
          fetch-depth: 0

      - name: Paths Filter
        id: filter
        uses: dorny/paths-filter@v3
        with:
          filters: .github/changes-filter.yaml

      - name: Determine Test Suites from Changes
        id: set-matrix
        shell: bash
        run: |
          # Start with input suites if provided, otherwise empty array
          echo "Changes filter output: $(echo '${{ toJSON(steps.filter.outputs) }}')"
          SUITES='${{ inputs.suites }}'
          echo "Initial suites: $SUITES"
          TEST_GREP=""
          echo "Inputs Release: ${{ inputs.release }}"
          RELEASE="${{ inputs.release || 'false' }}"
          echo "Release build: $RELEASE"

          # Only set to release if it's explicitly a release build
          if [[ "$RELEASE" == "true" ]]; then
            SUITES='["release"]'
            echo "Release build detected - setting suites to: $SUITES"
            # grep pattern for release is the @release tag - run all tests
            TEST_GREP='--grep="@release"'
          else
            # If input suites were not provided, determine based on changes
            if [[ "$SUITES" == "[]" ]]; then
              echo "No input suites provided - determining from changes"
              SUITES='[]'  # Ensure we start with a valid JSON array
              TAGS=()
              # Add suites and tags based on changed files
              if [[ "${{ steps.filter.outputs.components }}" == "true" ]]; then
                SUITES=$(echo $SUITES | jq -c '. += ["components"]')
                TAGS+=("@components")
                echo "Added components suite"
              fi
              if [[ "${{ steps.filter.outputs.starter-projects }}" == "true" ]]; then
                SUITES=$(echo $SUITES | jq -c '. += ["starter-projects"]')
                TAGS+=("@starter-projects")
                echo "Added starter-projects suite"
              fi
              if [[ "${{ steps.filter.outputs.workspace }}" == "true" ]]; then
                SUITES=$(echo $SUITES | jq -c '. += ["workspace"]')
                TAGS+=("@workspace")
                echo "Added workspace suite"
              fi
              if [[ "${{ steps.filter.outputs.api }}" == "true" ]]; then
                SUITES=$(echo $SUITES | jq -c '. += ["api"]')
                TAGS+=("@api")
                echo "Added api suite"
              fi
              if [[ "${{ steps.filter.outputs.database }}" == "true" ]]; then
                SUITES=$(echo $SUITES | jq -c '. += ["database"]')
                TAGS+=("@database")
                echo "Added database suite"
              fi
              if [[ "${{ steps.filter.outputs.mainpage }}" == "true" ]]; then
                SUITES=$(echo $SUITES | jq -c '. += ["mainpage"]')
                TAGS+=("@mainpage")
                echo "Added mainpage suite"
              fi
              if [[ "${{ steps.filter.outputs.development }}" == "true" ]]; then
                SUITES=$(echo $SUITES | jq -c '. += ["development"]')
                TAGS+=("@development")
                echo "Added development suite"
              fi

              # Create grep pattern if we have tags
              if [ ${#TAGS[@]} -gt 0 ]; then
                # Join tags with | for OR logic
                REGEX_PATTERN=$(IFS='|'; echo "${TAGS[*]}")
                TEST_GREP='--grep="${REGEX_PATTERN}"'
              else
                # No path-specific changes detected - default to running release tests
                # so that FE tests always run even for backend-only changes
                SUITES='["release"]'
                TEST_GREP='--grep="@release"'
                echo "No path-specific changes detected - defaulting to release suite"
              fi
            else
              # Process input suites to tags
              # First ensure SUITES is valid JSON
              if ! echo "$SUITES" | jq -e . > /dev/null 2>&1; then
                echo "Warning: Input suites is not valid JSON, attempting to fix"
                # Try to fix common issues like missing quotes
                if [[ "$SUITES" == "[development]" ]]; then
                  SUITES='["development"]'
                elif [[ "$SUITES" =~ ^\[(.*)\]$ ]]; then
                  # Extract items and add quotes
                  ITEMS="${BASH_REMATCH[1]}"
                  QUOTED_ITEMS=$(echo "$ITEMS" | sed 's/\([^,]*\)/"\1"/g')
                  SUITES="[$QUOTED_ITEMS]"
                fi
                echo "Fixed suites: $SUITES"
              fi

              TAGS=()
              if echo "$SUITES" | jq -e 'contains(["components"])' > /dev/null; then
                TAGS+=("@components")
              fi
              if echo "$SUITES" | jq -e 'contains(["starter-projects"])' > /dev/null; then
                TAGS+=("@starter-projects")
              fi
              if echo "$SUITES" | jq -e 'contains(["workspace"])' > /dev/null; then
                TAGS+=("@workspace")
              fi
              if echo "$SUITES" | jq -e 'contains(["api"])' > /dev/null; then
                TAGS+=("@api")
              fi
              if echo "$SUITES" | jq -e 'contains(["database"])' > /dev/null; then
                TAGS+=("@database")
              fi
              if echo "$SUITES" | jq -e 'contains(["development"])' > /dev/null; then
                TAGS+=("@development")
              fi

              if [ ${#TAGS[@]} -gt 0 ]; then
                # Join tags with | for OR logic
                REGEX_PATTERN=$(IFS='|'; echo "${TAGS[*]}")
                TEST_GREP='--grep "${REGEX_PATTERN}"'
              fi
            fi
          fi

          # Ensure compact JSON output
          SUITES=$(echo "$SUITES" | jq -c '.')

          echo "Final test suites to run: $SUITES"
          echo "Test grep pattern: $TEST_GREP"
          # Ensure proper JSON formatting for matrix output
          echo "matrix=$(echo $SUITES | jq -c .)" >> $GITHUB_OUTPUT
          echo "test_grep=$TEST_GREP" >> $GITHUB_OUTPUT
          echo "suites=$SUITES" >> $GITHUB_OUTPUT

      - name: Setup Node ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v6
        id: setup-node
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"
          cache-dependency-path: ./src/frontend/package-lock.json

      - name: Install Frontend Dependencies
        run: npm ci
        working-directory: ./src/frontend

      - name: Calculate Test Shards Distribution
        id: setup-matrix
        shell: bash
        run: |
          cd src/frontend

          # Get the test count using playwright's built-in grep
          if [ -n "${{ steps.set-matrix.outputs.test_grep }}" ]; then
            TEST_COUNT=$(npx playwright test ${{ inputs.tests_folder }} ${{ steps.set-matrix.outputs.test_grep }} --list | wc -l)
          else
            TEST_COUNT=$(npx playwright test ${{ inputs.tests_folder }} --list | wc -l)
          fi

          echo "Total tests to run: $TEST_COUNT"

          # Calculate optimal shard count - 1 shard per 5 tests, min 1, max 70
          SHARD_COUNT=$(( (TEST_COUNT + 4) / 5 ))
          if [ $SHARD_COUNT -lt 1 ]; then
            SHARD_COUNT=1
          elif [ $SHARD_COUNT -gt 70 ]; then
            SHARD_COUNT=70
          fi

          # Create the matrix combinations string
          MATRIX_COMBINATIONS=""
          for i in $(seq 1 $SHARD_COUNT); do
            if [ $i -gt 1 ]; then
              MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS,"
            fi
            MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS{\"shardIndex\": $i, \"shardTotal\": $SHARD_COUNT}"
          done

          echo "matrix={\"include\":[$MATRIX_COMBINATIONS]}" >> "$GITHUB_OUTPUT"

  setup-and-test:
    name: Playwright Tests - Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
    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' }}
    if: ${{ needs.determine-test-suite.outputs.test_grep != '' }}
    needs: determine-test-suite
    strategy:
      fail-fast: false
      matrix: ${{ fromJson(needs.determine-test-suite.outputs.matrix) }}
    env:
      OPENAI_API_KEY: ${{ inputs.openai_api_key || secrets.OPENAI_API_KEY }}
      STORE_API_KEY: ${{ inputs.store_api_key || secrets.STORE_API_KEY }}
      SEARCH_API_KEY: "${{ secrets.SEARCH_API_KEY }}"
      ASTRA_DB_APPLICATION_TOKEN: "${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}"
      ASTRA_DB_API_ENDPOINT: "${{ secrets.ASTRA_DB_API_ENDPOINT }}"
      ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}"
      TAVILY_API_KEY: "${{ secrets.TAVILY_API_KEY }}"
      LANGFLOW_DEACTIVE_TRACING: "true"
    outputs:
      failed: ${{ steps.check-failure.outputs.failed }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}

      - name: Setup Node.js Environment
        uses: actions/setup-node@v6
        id: setup-node
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"
          cache-dependency-path: ./src/frontend/package-lock.json
      - name: Install Frontend Dependencies
        run: npm ci
        working-directory: ./src/frontend

      # Cache Playwright browsers using a composite key
      - name: Cache Playwright Browsers
        id: cache-playwright
        uses: actions/cache@v5
        with:
          path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}
          key: playwright-${{ env.PLAYWRIGHT_VERSION }}-chromium-${{ runner.os }}
          restore-keys: |
            playwright-${{ env.PLAYWRIGHT_VERSION }}-chromium-${{ runner.os }}

      - name: Install Playwright Browser Dependencies (Linux)
        if: steps.cache-playwright.outputs.cache-hit != 'true' && runner.os == 'Linux' && !contains(inputs.runs-on, 'self-hosted')
        shell: bash
        run: |
          cd ./src/frontend
          npx playwright install --with-deps chromium

      - name: Install Playwright Browsers (Windows/macOS/Self-Hosted)
        if: steps.cache-playwright.outputs.cache-hit != 'true' && (runner.os != 'Linux' || contains(inputs.runs-on, 'self-hosted'))
        shell: bash
        run: |
          cd ./src/frontend
          npx playwright install chromium

      - 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 Python Dependencies
        run: uv sync --extra audio

      - name: Configure Environment Variables
        shell: bash
        run: |
          touch .env
          echo "${{ secrets.ENV_VARS }}" > .env

      - name: Execute Playwright Tests
        uses: nick-fields/retry@v3
        with:
          timeout_minutes: 12
          max_attempts: 2
          command: |
            cd src/frontend
            echo 'Running tests with pattern: ${{ needs.determine-test-suite.outputs.test_grep }}'
            npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --list --retries=3
            # echo command before running
            echo "npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2 --retries=3"

            npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2 --retries=3

      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v6
        with:
          name: blob-report-${{ runner.os }}-${{ matrix.shardIndex }}
          path: src/frontend/blob-report
          retention-days: 1

      - name: Cleanup UV Cache
        run: uv cache prune --ci

  merge-reports:
    # We need to repeat the condition at every step
    # https://github.com/actions/runner/issues/662
    needs: setup-and-test
    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' }}
    if: always()
    env:
      EXIT_CODE: ${{!contains(needs.setup-and-test.result, 'failure') && !contains(needs.setup-and-test.result, 'cancelled') && '0' || '1'}}
    steps:
      - name: "Should Merge Reports"
        # If the CI was successful, we don't need to merge the reports
        # so we can skip all the steps below
        id: should_merge_reports
        shell: bash
        run: |
          if [ "$EXIT_CODE" == "0" ]; then
            echo "should_merge_reports=false" >> $GITHUB_OUTPUT
          else
            echo "should_merge_reports=true" >> $GITHUB_OUTPUT
          fi
      - name: Checkout code
        if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}

      - name: Setup Node.js

        if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Download blob reports from GitHub Actions Artifacts

        if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
        uses: actions/download-artifact@v7
        with:
          path: all-blob-reports
          pattern: blob-report-*
          merge-multiple: true

      - name: Merge into HTML Report

        if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
        shell: bash
        run: |
          npx playwright merge-reports --reporter html ./all-blob-reports

      - name: Upload HTML report

        if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
        uses: actions/upload-artifact@v6
        with:
          name: html-report--attempt-${{ github.run_attempt }}
          path: playwright-report
          retention-days: 14