From ce21bf469d7493f4716bc30feb15a5b3f16ebe1e Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Mon, 6 Apr 2026 22:46:11 -0400 Subject: [PATCH] ci: convert working-directory to validated dropdown (#36575) Convert the `working-directory` input in the release workflow from a free-text string to a dropdown of known package paths. ## Changes - Change `working-directory` from `type: string` to `type: choice` in `_release.yml`, enumerating all 21 releasable packages under `libs/` and `libs/partners/` - Add `check-release-options` CI job in `check_diffs.yml` that runs a pytest script to assert the dropdown options match directories containing a `pyproject.toml` --- .github/scripts/test_release_options.py | 48 +++++++++++++++++++++++++ .github/workflows/_release.yml | 37 +++++++++++++------ .github/workflows/check_diffs.yml | 16 +++++++++ 3 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 .github/scripts/test_release_options.py diff --git a/.github/scripts/test_release_options.py b/.github/scripts/test_release_options.py new file mode 100644 index 00000000000..a233f1bc905 --- /dev/null +++ b/.github/scripts/test_release_options.py @@ -0,0 +1,48 @@ +"""Verify _release.yml dropdown options match actual package directories.""" + +from pathlib import Path + +import yaml + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _get_release_options() -> list[str]: + workflow = REPO_ROOT / ".github" / "workflows" / "_release.yml" + with open(workflow) as f: + data = yaml.safe_load(f) + try: + # PyYAML (YAML 1.1) parses the bare key `on` as boolean True + return data[True]["workflow_dispatch"]["inputs"]["working-directory"]["options"] + except (KeyError, TypeError) as e: + msg = f"Could not find workflow_dispatch options in {workflow}: {e}" + raise AssertionError(msg) from e + + +def _get_package_dirs() -> set[str]: + libs = REPO_ROOT / "libs" + dirs: set[str] = set() + # Top-level packages (libs/core, libs/langchain, etc.) + for p in libs.iterdir(): + if p.is_dir() and (p / "pyproject.toml").exists(): + dirs.add(f"libs/{p.name}") + # Partner packages (libs/partners/*) + partners = libs / "partners" + if partners.exists(): + for p in partners.iterdir(): + if p.is_dir() and (p / "pyproject.toml").exists(): + dirs.add(f"libs/partners/{p.name}") + return dirs + + +def test_release_options_match_packages() -> None: + options = set(_get_release_options()) + packages = _get_package_dirs() + missing_from_dropdown = packages - options + extra_in_dropdown = options - packages + assert not missing_from_dropdown, ( + f"Packages on disk missing from _release.yml dropdown: {missing_from_dropdown}" + ) + assert not extra_in_dropdown, ( + f"Dropdown options with no matching package directory: {extra_in_dropdown}" + ) diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index 6bad31d1b1d..547cd02057c 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -17,9 +17,31 @@ on: inputs: working-directory: required: true - type: string + type: choice description: "From which folder this pipeline executes" default: "libs/langchain_v1" + options: + - libs/core + - libs/langchain + - libs/langchain_v1 + - libs/text-splitters + - libs/standard-tests + - libs/model-profiles + - libs/partners/anthropic + - libs/partners/chroma + - libs/partners/deepseek + - libs/partners/exa + - libs/partners/fireworks + - libs/partners/groq + - libs/partners/huggingface + - libs/partners/mistralai + - libs/partners/nomic + - libs/partners/ollama + - libs/partners/openai + - libs/partners/openrouter + - libs/partners/perplexity + - libs/partners/qdrant + - libs/partners/xai release-version: required: true type: string @@ -64,6 +86,7 @@ jobs: # We want to keep this build stage *separate* from the release stage, # so that there's no sharing of permissions between them. # (Release stage has trusted publishing and GitHub repo contents write access, + # which the build stage must not have access to.) # # Otherwise, a malicious `build` step (e.g. via a compromised dependency) # could get access to our GitHub or PyPI credentials. @@ -273,15 +296,7 @@ jobs: env: PKG_NAME: ${{ needs.build.outputs.pkg-name }} VERSION: ${{ needs.build.outputs.version }} - # Here we use: - # - The default regular PyPI index as the *primary* index, meaning - # that it takes priority (https://pypi.org/simple) - # - The test PyPI index as an extra index, so that any dependencies that - # are not found on test PyPI can be resolved and installed anyway. - # (https://test.pypi.org/simple). This will include the PKG_NAME==VERSION - # package because VERSION will not have been uploaded to regular PyPI yet. - # - attempt install again after 5 seconds if it fails because there is - # sometimes a delay in availability on test pypi + # Install directly from the locally-built wheel (no index resolution needed) run: | uv venv VIRTUAL_ENV=.venv uv pip install dist/*.whl @@ -592,7 +607,7 @@ jobs: - test-pypi-publish - pre-release-checks - publish - # Run if all needed jobs succeeded or were skipped (test-dependents only runs for core/langchain_v1) + # Run if all needed jobs succeeded or were skipped if: ${{ !cancelled() && !failure() }} runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/check_diffs.yml b/.github/workflows/check_diffs.yml index 69a062c8fa3..c311575ef62 100644 --- a/.github/workflows/check_diffs.yml +++ b/.github/workflows/check_diffs.yml @@ -185,6 +185,21 @@ jobs: # and `set -e` above will cause the step to fail. echo "$STATUS" | grep 'nothing to commit, working tree clean' + # Verify _release.yml dropdown options stay in sync with package directories + check-release-options: + name: "Validate Release Options" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: "🐍 Setup Python 3.11" + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: "📦 Install Dependencies" + run: python -m pip install pyyaml pytest + - name: "🔍 Check release dropdown matches packages" + run: python -m pytest .github/scripts/test_release_options.py -v + # Final status check - ensures all required jobs passed before allowing merge ci_success: name: "✅ CI Success" @@ -197,6 +212,7 @@ jobs: vcr-tests, extended-tests, test-pydantic, + check-release-options, ] if: | always()