diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000..e5b6d8d6 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000..d88011f6 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index e81bc801..00000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,7 +0,0 @@ -"pkg:llama-index-workflows": - - changed-files: - - any-glob-to-any-file: 'packages/llama-index-workflows/**' - -"pkg:llama-index-utils-workflow": - - changed-files: - - any-glob-to-any-file: 'packages/llama-index-utils-workflow/**' diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml deleted file mode 100644 index 1eb1cf8e..00000000 --- a/.github/workflows/pr-labeler.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Label PRs by package - -on: - pull_request_target: - types: [opened, synchronize, reopened, ready_for_review] - -jobs: - label: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/labeler@v6 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish_changesets.yml b/.github/workflows/publish_changesets.yml new file mode 100644 index 00000000..15df1c12 --- /dev/null +++ b/.github/workflows/publish_changesets.yml @@ -0,0 +1,82 @@ +name: Version Bump and Release + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + # Only run on main branch pushes + if: github.ref == 'refs/heads/main' + steps: + - name: Checkout Repo + uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: "22" + cache: "pnpm" + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Install dependencies + run: pnpm install + + - name: Create Release Pull Request or Publish packages + id: changesets + uses: changesets/action@v1 + with: + commit: "chore: version packages" + title: "chore: version packages" + # Custom version script + version: pnpm -w run version + # Custom publish script + publish: pnpm -w run publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }} + + - name: Generate GitHub App token + if: steps.changesets.outputs.published == 'true' + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.CI_BOT_APP_ID }} + private-key: ${{ secrets.CI_BOT_PRIVATE_KEY }} + + - name: Extract published llama-index-workflows version + if: steps.changesets.outputs.published == 'true' + id: workflows-version + run: | + PACKAGES='${{ steps.changesets.outputs.publishedPackages }}' + VERSION=$(echo "$PACKAGES" | jq -r '.[] | select(.name == "llama-index-workflows") | .version') + + if [ -n "$VERSION" ]; then + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=llama-index-workflows@v${VERSION}" >> "$GITHUB_OUTPUT" + echo "Found llama-index-workflows version: $VERSION" + else + echo "llama-index-workflows was not published in this release" + fi + + - name: Trigger OpenAPI publish for llama-index-workflows + if: steps.changesets.outputs.published == 'true' && steps.workflows-version.outputs.version != '' + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ steps.app-token.outputs.token }} + event-type: publish-openapi + client-payload: '{"tag": "${{ steps.workflows-version.outputs.tag }}"}' diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml deleted file mode 100644 index 72d8e598..00000000 --- a/.github/workflows/publish_release.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Publish packages to PyPI and GitHub - -on: - workflow_dispatch: - push: - branches: - - main - -jobs: - publish: - if: github.repository == 'run-llama/workflows-py' - runs-on: ubuntu-latest - permissions: - contents: write - strategy: - fail-fast: false - matrix: - include: - - package: llama-index-workflows - display_name: LlamaIndex Workflows - artifact_glob: dist/llama_index_workflows-* - trigger_openapi: true - - package: llama-index-utils-workflow - display_name: LlamaIndex Utils Workflow - artifact_glob: dist/llama_index_utils_workflow-* - trigger_openapi: false - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install uv - uses: astral-sh/setup-uv@v6 - - - name: Determine if package needs a release - id: decision - run: > - uv run --package workflows-dev workflows-dev needs-release - --pyproject packages/${{ matrix.package }}/pyproject.toml - --tag-prefix ${{ matrix.package }}@ - --output "$GITHUB_OUTPUT" - - - name: Configure git identity - if: steps.decision.outputs.release == 'true' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Generate release notes - id: notes - if: steps.decision.outputs.release == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: > - uv run --package workflows-dev workflows-dev generate-release-notes - --repository ${{ github.repository }} - --package-label pkg:${{ matrix.package }} - --package-name "${{ matrix.display_name }}" - --current-tag ${{ matrix.package }}@v${{ steps.decision.outputs.version }} - --previous-tag "${{ steps.decision.outputs.previous_tag }}" - --semver ${{ steps.decision.outputs.version }} - --output "$GITHUB_OUTPUT" - - - name: Build package - if: steps.decision.outputs.release == 'true' - working-directory: packages/${{ matrix.package }} - run: uv build - - - name: Publish package to PyPI - if: steps.decision.outputs.release == 'true' - working-directory: packages/${{ matrix.package }} - env: - UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} - run: uv publish - - # push tag after publishing, in case build fails, can retry - - name: Create and push tag - id: tag - if: steps.decision.outputs.release == 'true' - env: - VERSION: ${{ steps.decision.outputs.version }} - run: | - TAG_NAME="${{ matrix.package }}@v${VERSION}" - echo "tag=${TAG_NAME}" >> "$GITHUB_OUTPUT" - if [ -n "$(git tag -l "${TAG_NAME}")" ]; then - echo "Deleting existing tag ${TAG_NAME}" - git tag -d "${TAG_NAME}" - git push origin :"${TAG_NAME}" - fi - git tag "${TAG_NAME}" - git push origin "${TAG_NAME}" - - - name: Create GitHub Release - if: steps.decision.outputs.release == 'true' - uses: ncipollo/release-action@v1 - with: - tag: ${{ steps.tag.outputs.tag }} - name: "${{ matrix.display_name }} v${{ steps.decision.outputs.version }}" - body: ${{ steps.notes.outputs.body }} - artifacts: ${{ matrix.artifact_glob }} - - - name: No release needed for package - if: steps.decision.outputs.release != 'true' - run: echo "No new release required for ${{ matrix.package }}." - - - name: Trigger OpenAPI publish workflow - if: steps.decision.outputs.release == 'true' && matrix.trigger_openapi == true - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - repository: ${{ github.repository }} - event-type: publish-openapi - client-payload: >- - {"tag": "${{ steps.tag.outputs.tag }}"} diff --git a/.github/workflows/update_debugger_assets.yml b/.github/workflows/update_debugger_assets.yml index 7c2d628a..dc1eec73 100644 --- a/.github/workflows/update_debugger_assets.yml +++ b/.github/workflows/update_debugger_assets.yml @@ -46,6 +46,22 @@ jobs: --js-url "${{ steps.payload.outputs.js_url }}" --css-url "${{ steps.payload.outputs.css_url }}" + - name: Create changeset for patch version + run: | + # Create a changeset file with a unique name + CHANGESET_FILE=".changeset/update-debugger-assets-$(date +%s).md" + cat > "$CHANGESET_FILE" << 'EOF' + --- + "llama-index-workflows": patch + --- + + Update debugger assets + + - JavaScript: ${{ steps.payload.outputs.js_url }} + - CSS: ${{ steps.payload.outputs.css_url }} + EOF + echo "Created changeset: $CHANGESET_FILE" + - name: Create Pull Request uses: peter-evans/create-pull-request@v6 with: @@ -57,7 +73,7 @@ jobs: body: | ## Update Debugger Assets - This PR updates the workflow debugger assets. + This PR updates the workflow debugger assets and creates a changeset for a patch version bump. ### Changes - Updated `packages/llama-index-workflows/src/workflows/server/static/index.html` diff --git a/.gitignore b/.gitignore index 796ab464..471d6214 100644 --- a/.gitignore +++ b/.gitignore @@ -148,3 +148,4 @@ cython_debug/ # Generated files openapi.json workflow_all_flows.mermaid +node_modules/ diff --git a/package.json b/package.json new file mode 100644 index 00000000..73531131 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "llama-agents-workspace", + "version": "1.0.0", + "private": "true", + "license": "MIT", + "scripts": { + "pre-commit-version": "pnpm changeset", + "version": "uv run workflows-dev changeset-version", + "release": "uv run workflows-dev changeset-publish --tag" + } +} diff --git a/packages/llama-index-utils-workflow/package.json b/packages/llama-index-utils-workflow/package.json new file mode 100644 index 00000000..db2d086b --- /dev/null +++ b/packages/llama-index-utils-workflow/package.json @@ -0,0 +1,8 @@ +{ + "name": "llama-index-utils-workflow", + "version": "0.5.0", + "private": "false", + "license": "MIT", + "scripts": { + } +} diff --git a/packages/llama-index-workflows/package.json b/packages/llama-index-workflows/package.json new file mode 100644 index 00000000..9fb36af6 --- /dev/null +++ b/packages/llama-index-workflows/package.json @@ -0,0 +1,8 @@ +{ + "name": "llama-index-workflows", + "version": "2.11.1", + "private": "false", + "license": "MIT", + "scripts": { + } +} diff --git a/packages/workflows-dev/pyproject.toml b/packages/workflows-dev/pyproject.toml index 9ac957d6..27d634c4 100644 --- a/packages/workflows-dev/pyproject.toml +++ b/packages/workflows-dev/pyproject.toml @@ -20,7 +20,9 @@ requires-python = ">=3.9" dependencies = [ "click>=8.1.7", "httpx>=0.28.1", - "packaging>=24.1" + "packaging>=24.1", + "pydantic>=2.12.3", + "tomlkit>=0.13.3" ] [project.scripts] diff --git a/packages/workflows-dev/src/workflows_dev/changesets.py b/packages/workflows-dev/src/workflows_dev/changesets.py new file mode 100644 index 00000000..336ba691 --- /dev/null +++ b/packages/workflows-dev/src/workflows_dev/changesets.py @@ -0,0 +1,198 @@ +""" +This is a script called by the changeset bot. Normally changeset can do the following things, but this is a mixed ts and python repo, so we need to do some extra things. + +There's 2 things this does: +- Versioning: Makes changes that may be committed with the newest version. +- Releasing/Tagging: After versions are changed, we check each package to see if its released, and if not, we release it and tag it. + +""" + +from __future__ import annotations + +from dataclasses import dataclass +import json +import os +import subprocess +from pathlib import Path +from typing import Any, Generator, List, cast +import urllib.request +import urllib.error +from pydantic import BaseModel + +import click +import tomlkit +from packaging.version import Version + + +def run_command( + cmd: List[str], cwd: Path | None = None, env: dict[str, str] | None = None +) -> None: + """Run a command, streaming output to the console, and raise on failure.""" + subprocess.run(cmd, check=True, text=True, cwd=cwd or Path.cwd(), env=env) + + +def run_and_capture( + cmd: List[str], cwd: Path | None = None, env: dict[str, str] | None = None +) -> str: + """Run a command and return stdout as text, raising on failure.""" + result = subprocess.run( + cmd, + check=True, + text=True, + cwd=cwd or Path.cwd(), + env=env, + capture_output=True, + ) + return result.stdout + + +@dataclass +class PackageJson: + name: str + version: str + path: Path + private: bool + + +def get_pnpm_workspace_packages() -> list[PackageJson]: + """Return directories for all workspace packages from pnpm list JSON output.""" + output = run_and_capture(["pnpm", "list", "-r", "--depth=-1", "--json"]) + + data = cast(list[dict[str, Any]], json.loads(output)) + packages: list[PackageJson] = [ + PackageJson( + name=data["name"], + version=data["version"], + path=Path(data["path"]), + private=data["private"], + ) + for data in data + ] + return packages + + +def sync_package_version_with_pyproject( + package_dir: Path, packages: dict[str, PackageJson], js_package_name: str +) -> None: + """Sync version from package.json to pyproject.toml. + + Returns True if pyproject was changed, else False. + """ + pyproject_path = package_dir / "pyproject.toml" + if not pyproject_path.exists(): + return + + package_version = packages[js_package_name].version + toml_doc, py_doc = PyProjectContainer.parse(pyproject_path.read_text()) + current_version = py_doc.project.version + + # update workspace dependency strings by replacing the first version after == or >= + # Perhaps sync dependency versions some day + changed = False + if current_version != package_version: + toml_doc["project"]["version"] = package_version + changed = True + + if changed: + pyproject_path.write_text(tomlkit.dumps(toml_doc)) + click.echo( + f"Updated {pyproject_path} version to {package_version} and synced dependency specs" + ) + + +def _publishable_packages() -> Generator[Path, None, None]: + """Finds all paths to pyproject.toml that also have a package.json with private: false.""" + packages = get_pnpm_workspace_packages() + for package in packages: + if not package.private: + pyproject = package.path.parent / "pyproject.toml" + if pyproject.exists(): + yield pyproject + + +def lock_python_dependencies() -> None: + """Lock Python dependencies.""" + try: + run_command(["uv", "lock"]) + click.echo("Locked Python dependencies") + except subprocess.CalledProcessError as e: + click.echo(f"Warning: Failed to lock Python dependencies: {e}", err=True) + + +@click.group() +def cli() -> None: + """Changeset-based version management for llama-cloud-services.""" + pass + + +def maybe_publish_pypi(dry_run: bool) -> None: + """Publish the py packages if they need to be published.""" + any = False + for package in _publishable_packages(): + name, version = current_version(package) + if is_published(name, version): + click.echo(f"PyPI package {name}@{version} already published, skipping") + continue + any = True + click.echo(f"Publishing PyPI package {name}@{version}") + + token = os.environ["UV_PUBLISH_TOKEN"] + if dry_run: + summary = (token[:3] + "***") if len(token) <= 6 else token[:6] + "****" + click.echo( + f"Dry run, skipping publish. Would run with publish token {summary}:" + ) + click.echo(" uv build") + click.echo(" uv publish") + else: + run_command(["uv", "build"], cwd=package.parent) + if any: + if dry_run: + click.echo("Dry run, skipping publish. Would run:") + click.echo(" uv publish") + else: + run_command(["uv", "publish"]) + + +def current_version(pyproject: Path) -> tuple[str, str]: + """Return (package_name, version_str) taken from the given pyproject.toml.""" + toml_doc, py_doc = PyProjectContainer.parse(pyproject.read_text()) + name = py_doc.project.name + version = str(Version(py_doc.project.version)) # normalise + return name, version + + +def is_published( + name: str, version: str, index_url: str = "https://pypi.org/pypi" +) -> bool: + """ + True → `==` exists on the given index + False → package missing *or* version missing + """ + url = f"{index_url.rstrip('/')}/{name}/json" + try: + data = json.load(urllib.request.urlopen(url)) + except urllib.error.HTTPError as e: # 404 → package not published at all + if e.code == 404: + return False + raise # any other error should surface + return version in data["releases"] # keys are version strings + + +if __name__ == "__main__": + cli() + + +class PyProjectContainer(BaseModel): + project: PyProject + + @classmethod + def parse(cls, text: str) -> tuple[Any, PyProjectContainer]: + doc = tomlkit.parse(text) + return doc, PyProjectContainer.model_validate(doc) + + +class PyProject(BaseModel): + name: str + version: str + dependencies: list[str] diff --git a/packages/workflows-dev/src/workflows_dev/cli.py b/packages/workflows-dev/src/workflows_dev/cli.py index 44814605..75d5707b 100644 --- a/packages/workflows-dev/src/workflows_dev/cli.py +++ b/packages/workflows-dev/src/workflows_dev/cli.py @@ -1,11 +1,12 @@ from __future__ import annotations +import os from pathlib import Path from typing import Optional import click -from . import git_utils, gha, index_html, release_notes, versioning +from . import git_utils, gha, index_html, versioning, changesets def _resolve_tag(explicit_tag: Optional[str], github_ref: Optional[str]) -> str: @@ -23,30 +24,6 @@ def cli() -> None: """Developer tooling for the workflows repository.""" -@cli.command("validate-version") -@click.option( - "--pyproject", - type=click.Path(exists=True, dir_okay=False), - default="pyproject.toml", - show_default=True, -) -@click.option("--tag-prefix", default="", show_default=True) -@click.option("--tag", envvar="GITHUB_REF_NAME") -@click.option("--github-ref", envvar="GITHUB_REF") -def validate_version( - pyproject: str, tag_prefix: str, tag: Optional[str], github_ref: Optional[str] -) -> None: - """Ensure the git tag matches the pyproject version.""" - target_tag = _resolve_tag(tag, github_ref) - pyproject_version = versioning.read_pyproject_version(pyproject) - tag_version = versioning.extract_semver(target_tag, tag_prefix) - try: - versioning.ensure_versions_match(pyproject_version, tag_version, target_tag) - except versioning.VersionMismatchError as exc: - raise click.ClickException(str(exc)) from exc - click.echo(f"✅ Version validated: {pyproject_version} (tag: {target_tag})") - - @cli.command("detect-change-type") @click.option("--tag-glob", default="v*", show_default=True) @click.option("--tag-prefix", default="", show_default=True) @@ -103,80 +80,6 @@ def find_previous_tag(tag_prefix: str, current_tag: str, output: Optional[str]) gha.write_outputs({"previous": previous}, output_path=output) -@cli.command("generate-release-notes") -@click.option("--repository", envvar="GITHUB_REPOSITORY", required=True) -@click.option("--github-token", envvar="GITHUB_TOKEN", required=True) -@click.option("--package-label", required=True) -@click.option("--package-name", required=True) -@click.option("--current-tag", required=True) -@click.option("--previous-tag", default="", show_default=True) -@click.option("--semver", required=True) -@click.option("--output", type=click.Path(), default=None) -def generate_release_notes( - repository: str, - github_token: str, - package_label: str, - package_name: str, - current_tag: str, - previous_tag: str, - semver: str, - output: Optional[str], -) -> None: - """Generate release notes by aggregating labeled pull requests.""" - body = release_notes.generate_release_notes( - token=github_token, - repository=repository, - package_label=package_label, - package_name=package_name, - current_tag=current_tag, - previous_tag=previous_tag, - semver=semver, - ) - gha.write_outputs({"body": body}, output_path=output) - - -@cli.command("needs-release") -@click.option( - "--pyproject", - type=click.Path(exists=True, dir_okay=False), - required=True, - help="Path to the package's pyproject.toml file.", -) -@click.option( - "--tag-prefix", - required=True, - help="Git tag prefix for this package (e.g. llama-index-workflows@).", -) -@click.option("--output", type=click.Path(), default=None) -def needs_release(pyproject: str, tag_prefix: str, output: Optional[Path]) -> None: - """Determine whether a package needs a new release tag.""" - current_version = versioning.read_pyproject_version(pyproject) - tags = git_utils.list_tags(Path.cwd(), f"{tag_prefix}v*") - previous_tag = tags[0] if tags else "" - previous_version = ( - versioning.extract_semver(previous_tag, tag_prefix) if previous_tag else None - ) - change_type = versioning.detect_change_type(current_version, previous_version) - release_needed = "true" if change_type != "none" else "false" - - click.echo(f"Detected version: {current_version}") - if previous_tag: - click.echo(f"Previous tag: {previous_tag} (version {previous_version})") - else: - click.echo("No previous tag found; treating as first release.") - click.echo(f"Release needed: {release_needed}") - - gha.write_outputs( - { - "version": current_version, - "previous_tag": previous_tag, - "change_type": change_type, - "release": release_needed, - }, - output_path=output, - ) - - @cli.command("update-index-html") @click.option("--js-url", required=True, help="URL for the JavaScript bundle.") @click.option("--css-url", required=True, help="URL for the CSS bundle.") @@ -195,3 +98,47 @@ def update_index_html_cmd(js_url: str, css_url: str, index_path: Optional[str]) click.echo("✅ Updated index.html") click.echo(f" JavaScript: {js_url}") click.echo(f" CSS: {css_url}") + + +@cli.command("changeset-version") +def changeset_version() -> None: + """Apply changeset versions, then sync versions for co-located Python packages. + + - Runs changesets to bump package.json versions. + - Discovers all workspace packages via pnpm. + - For any directory containing both package.json and pyproject.toml, and with + package.json private: false, set pyproject [project].version to match the JS version. + - If a pyproject is updated, run `uv sync` in that directory to update its lock file. + """ + # Ensure we're at the repo root + os.chdir(Path(__file__).parents[4]) + + # First, run changeset version to update all package.json files + changesets.run_command(["npx", "@changesets/cli", "version"]) + + # Enumerate workspace packages and perform syncs + packages = changesets.get_pnpm_workspace_packages() + version_map = {pkg.name: pkg for pkg in packages} + for pkg in packages: + changesets.sync_package_version_with_pyproject(pkg.path, version_map, pkg.name) + + +@cli.command("changeset-publish") +@click.option("--tag", is_flag=True, help="Tag the packages after publishing") +@click.option("--dry-run", is_flag=True, help="Dry run the publish") +def publish(tag: bool, dry_run: bool) -> None: + """Publish all packages.""" + # move to the root + os.chdir(Path(__file__).parents[4]) + + changesets.maybe_publish_pypi(dry_run) + + if tag: + if dry_run: + click.echo("Dry run, skipping tag. Would run:") + click.echo(" npx @changesets/cli tag") + click.echo(" git push --tags") + else: + # Let changesets create JS-related tags as usual + changesets.run_command(["npx", "@changesets/cli", "tag"]) + changesets.run_command(["git", "push", "--tags"]) diff --git a/packages/workflows-dev/src/workflows_dev/release_notes.py b/packages/workflows-dev/src/workflows_dev/release_notes.py deleted file mode 100644 index 47782393..00000000 --- a/packages/workflows-dev/src/workflows_dev/release_notes.py +++ /dev/null @@ -1,275 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import Iterable, Literal, Mapping, Optional - -import httpx - -API_ROOT = "https://api.github.com" -API_VERSION = "2022-11-28" - - -@dataclass(frozen=True) -class Repository: - owner: str - name: str - - -@dataclass -class PullRequest: - number: int - title: str - author: str - merge_commit_sha: Optional[str] - merged: bool - labels: tuple[str, ...] - - -CategoryName = Literal["breaking-change", "enhancement", "bug", "other"] - - -class GitHubClient: - """Thin wrapper around httpx for GitHub REST calls.""" - - def __init__(self, token: str) -> None: - self._client = httpx.Client( - headers={ - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "User-Agent": "workflows-dev-cli", - "X-GitHub-Api-Version": API_VERSION, - }, - timeout=30.0, - ) - - def get( - self, url: str, params: Optional[Mapping[str, str]] = None - ) -> httpx.Response: - response = self._client.get(url, params=params) - response.raise_for_status() - return response - - def close(self) -> None: - self._client.close() - - -def parse_repository(value: str) -> Repository: - if "/" not in value: - raise ValueError("Repository must be in the form OWNER/NAME") - owner, name = value.split("/", 1) - return Repository(owner=owner, name=name) - - -def parse_pull_request(raw: Mapping[str, object]) -> PullRequest: - user = raw.get("user") - author = "" - if isinstance(user, Mapping): - login = user.get("login") - author = str(login) if isinstance(login, str) else "" - - labels: list[str] = [] - raw_labels = raw.get("labels", []) - if isinstance(raw_labels, Iterable): - for label in raw_labels: - if isinstance(label, Mapping): - name = label.get("name") - if isinstance(name, str): - labels.append(name) - - # number - number_value = raw.get("number") - if isinstance(number_value, int): - number = number_value - elif isinstance(number_value, str): - try: - number = int(number_value) - except ValueError as exc: - raise RuntimeError("Invalid PR number value") from exc - else: - raise RuntimeError("Missing PR number value") - - # title - title_value = raw.get("title") - title = str(title_value) if title_value is not None else "" - - # merge sha - merge_sha_obj = raw.get("merge_commit_sha") - merge_commit_sha = str(merge_sha_obj) if isinstance(merge_sha_obj, str) else None - - # merged flag - merged = bool(raw.get("merged_at")) - - return PullRequest( - number=number, - title=title, - author=author or "unknown", - merge_commit_sha=merge_commit_sha, - merged=merged, - labels=tuple(labels), - ) - - -def parse_link_header(header_value: Optional[str]) -> dict[str, str]: - links: dict[str, str] = {} - if not header_value: - return links - parts = [chunk.strip() for chunk in header_value.split(",")] - for part in parts: - if not part: - continue - section, _, rel = part.partition(";") - url = section.strip().strip("<>") - rel = rel.strip() - if rel.startswith('rel="') and rel.endswith('"'): - links[rel[5:-1]] = url - return links - - -def fetch_pull_requests(client: GitHubClient, repo: Repository) -> list[PullRequest]: - prs: list[PullRequest] = [] - url: Optional[str] = f"{API_ROOT}/repos/{repo.owner}/{repo.name}/pulls" - params: Optional[Mapping[str, str]] = { - "state": "closed", - "per_page": "100", - "sort": "updated", - "direction": "desc", - } - - while url: - response = client.get(url, params=params) - data = response.json() - if not isinstance(data, list): - raise RuntimeError("Unexpected response while listing pull requests.") - for entry in data: - if isinstance(entry, Mapping): - prs.append(parse_pull_request(entry)) - params = None - links = parse_link_header(response.headers.get("Link")) - url = links.get("next") - - return prs - - -def collect_commit_shas( - client: GitHubClient, repo: Repository, previous_tag: str, current_tag: str -) -> Optional[set[str]]: - if not previous_tag: - return None - url = f"{API_ROOT}/repos/{repo.owner}/{repo.name}/compare/{previous_tag}...{current_tag}" - try: - response = client.get(url) - except httpx.HTTPStatusError: - return None - data = response.json() - commits = data.get("commits") - if not isinstance(commits, list): - return None - return { - str(commit.get("sha")) - for commit in commits - if isinstance(commit, Mapping) and isinstance(commit.get("sha"), str) - } - - -def filter_pull_requests( - pull_requests: Iterable[PullRequest], - package_label: str, - commit_shas: Optional[set[str]], -) -> list[PullRequest]: - relevant: list[PullRequest] = [] - for pr in pull_requests: - if not pr.merged or not pr.merge_commit_sha: - continue - if package_label not in pr.labels: - continue - if commit_shas is None or pr.merge_commit_sha in commit_shas: - relevant.append(pr) - return relevant - - -def _categorize_pull_request(pr: PullRequest) -> CategoryName: - if "breaking-change" in pr.labels: - return "breaking-change" - if "enhancement" in pr.labels: - return "enhancement" - if "bug" in pr.labels: - return "bug" - return "other" - - -def format_release_notes( - repo: Repository, - package_name: str, - semver: str, - current_tag: str, - previous_tag: str, - pull_requests: Iterable[PullRequest], -) -> str: - lines: list[str] = [f"## {package_name} {semver}", ""] - - sections: dict[CategoryName, list[PullRequest]] = { - "breaking-change": [], - "enhancement": [], - "bug": [], - "other": [], - } - for pr in pull_requests: - category = _categorize_pull_request(pr) - sections[category].append(pr) - - added = False - headings: dict[CategoryName, str] = { - "breaking-change": "Breaking changes", - "enhancement": "Enhancements", - "bug": "Bug fixes", - "other": "Other changes", - } - order: tuple[CategoryName, ...] = ( - "breaking-change", - "enhancement", - "bug", - "other", - ) - for category in order: - prs = sections[category] - if not prs: - continue - if added: - lines.append("") - lines.append(f"### {headings[category]}") - for pr in prs: - lines.append(f"- {pr.title} (#{pr.number}) by @{pr.author}") - added = True - - if not added: - lines.append("_No labeled pull requests for this package in this release._") - - if previous_tag: - lines.append("") - lines.append( - f"[View changes between {previous_tag} and {current_tag}]" - f"(https://github.com/{repo.owner}/{repo.name}/compare/{previous_tag}...{current_tag})." - ) - return "\n".join(lines).strip() - - -def generate_release_notes( - token: str, - repository: str, - package_label: str, - package_name: str, - current_tag: str, - previous_tag: str, - semver: str, -) -> str: - repo = parse_repository(repository) - client = GitHubClient(token) - try: - pull_requests = fetch_pull_requests(client, repo) - commit_shas = collect_commit_shas(client, repo, previous_tag, current_tag) - relevant = filter_pull_requests(pull_requests, package_label, commit_shas) - return format_release_notes( - repo, package_name, semver, current_tag, previous_tag, relevant - ) - finally: - client.close() diff --git a/packages/workflows-dev/tests/test_workflows_dev_cli.py b/packages/workflows-dev/tests/test_workflows_dev_cli.py index c72cd5f0..fc3aa841 100644 --- a/packages/workflows-dev/tests/test_workflows_dev_cli.py +++ b/packages/workflows-dev/tests/test_workflows_dev_cli.py @@ -1,11 +1,20 @@ from __future__ import annotations +import json import subprocess from pathlib import Path +from unittest.mock import Mock, patch +from urllib.error import HTTPError from click.testing import CliRunner -from workflows_dev import release_notes +from workflows_dev.changesets import ( + PackageJson, + PyProjectContainer, + current_version, + is_published, + sync_package_version_with_pyproject, +) from workflows_dev.cli import cli @@ -42,64 +51,6 @@ def _commit_and_tag(repo_path: Path, filename: str, content: str, tag: str) -> N subprocess.run(["git", "tag", tag], cwd=repo_path, check=True) -def test_validate_version_matching() -> None: - runner = CliRunner() - with runner.isolated_filesystem(): - pyproject = Path("pyproject.toml") - _write_pyproject(pyproject, "1.2.3") - result = runner.invoke( - cli, ["validate-version", "--pyproject", str(pyproject), "--tag", "v1.2.3"] - ) - assert result.exit_code == 0 - assert "Version validated: 1.2.3" in result.output - - -def test_validate_version_not_matching() -> None: - runner = CliRunner() - with runner.isolated_filesystem(): - pyproject = Path("pyproject.toml") - _write_pyproject(pyproject, "1.2.3") - result = runner.invoke( - cli, ["validate-version", "--pyproject", str(pyproject), "--tag", "v9.9.9"] - ) - assert result.exit_code != 0 - assert "doesn't match pyproject.toml version" in result.output - - -def test_validate_version_with_prefix() -> None: - runner = CliRunner() - with runner.isolated_filesystem(): - pyproject = Path("pyproject.toml") - _write_pyproject(pyproject, "0.5.0") - result = runner.invoke( - cli, - [ - "validate-version", - "--pyproject", - str(pyproject), - "--tag-prefix", - "pkg@", - "--tag", - "pkg@v0.5.0", - ], - ) - assert result.exit_code == 0 - assert "Version validated: 0.5.0" in result.output - - -def test_validate_version_not_a_tag() -> None: - runner = CliRunner() - with runner.isolated_filesystem(): - pyproject = Path("pyproject.toml") - _write_pyproject(pyproject, "1.0.0") - env = {"GITHUB_REF": "refs/heads/main", "GITHUB_REF_NAME": ""} - result = runner.invoke( - cli, ["validate-version", "--pyproject", str(pyproject)], env=env - ) - assert result.exit_code != 0 - assert "Unable to determine tag" in result.output - - def test_detect_change_type_patch() -> None: runner = CliRunner() with runner.isolated_filesystem(): @@ -253,94 +204,6 @@ def test_find_previous_tag_returns_match() -> None: assert out_file.read_text().strip() == "previous=pkg@v1.0.0" -def test_needs_release_first_release() -> None: - runner = CliRunner() - with runner.isolated_filesystem(): - repo = Path.cwd() - _init_git_repo(repo) - (repo / "pyproject.toml").write_text( - """ -[project] -name = "example" -version = "1.0.0" -""".strip() - ) - - output_file = Path("out.txt") - result = runner.invoke( - cli, - [ - "needs-release", - "--pyproject", - "pyproject.toml", - "--tag-prefix", - "pkg@", - "--output", - str(output_file), - ], - ) - assert result.exit_code == 0 - contents = output_file.read_text() - assert "version=1.0.0" in contents - assert "previous_tag=" in contents - assert "change_type=major" in contents - assert "release=true" in contents - - -def test_needs_release_when_version_has_not_advanced() -> None: - runner = CliRunner() - with runner.isolated_filesystem(): - repo = Path.cwd() - (repo / "pyproject.toml").write_text( - """ -[project] -name = "example" -version = "1.0.0" -""".strip() - ) - # initialise git and create matching tag - subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True) - subprocess.run( - ["git", "config", "user.email", "dev@example.com"], - cwd=repo, - check=True, - ) - subprocess.run( - ["git", "config", "user.name", "Dev User"], - cwd=repo, - check=True, - ) - (repo / "file.txt").write_text("content") - subprocess.run(["git", "add", "."], cwd=repo, check=True) - subprocess.run( - ["git", "commit", "-m", "init"], - cwd=repo, - check=True, - capture_output=True, - ) - subprocess.run(["git", "tag", "pkg@v1.0.0"], cwd=repo, check=True) - - output_file = Path("out.txt") - result = runner.invoke( - cli, - [ - "needs-release", - "--pyproject", - "pyproject.toml", - "--tag-prefix", - "pkg@", - "--output", - str(output_file), - ], - ) - assert result.exit_code == 0 - contents = output_file.read_text() - assert "version=1.0.0" in contents - assert "previous_tag=pkg@v1.0.0" in contents - assert "change_type=none" in contents - assert "release=false" in contents - - def test_update_index_html_success(tmp_path: Path) -> None: runner = CliRunner() index_path = tmp_path / "index.html" @@ -390,26 +253,162 @@ def test_update_index_html_missing_file(tmp_path: Path) -> None: assert "not found" in result.output -def test_release_notes_formatting() -> None: - repo = release_notes.Repository(owner="run-llama", name="workflows-py") - prs = [ - release_notes.PullRequest( - number=1, - title="Add feature", - author="alice", - merge_commit_sha="abc", - merged=True, - labels=("pkg:llama-index-workflows", "enhancement"), - ) - ] - body = release_notes.format_release_notes( - repo=repo, - package_name="LlamaIndex Workflows", - semver="1.2.3", - current_tag="v1.2.3", - previous_tag="v1.2.2", - pull_requests=prs, +# Tests for changesets.py functionality + + +def test_current_version(tmp_path: Path) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + """ +[project] +name = "test-package" +version = "1.2.3" +dependencies = [] +""".strip() + ) + name, version = current_version(pyproject) + assert name == "test-package" + assert version == "1.2.3" + + +def test_current_version_normalizes_version(tmp_path: Path) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + """ +[project] +name = "test-package" +version = "01.02.03" +dependencies = [] +""".strip() ) - assert "Add feature (#1) by @alice" in body - assert "### Enhancements" in body - assert "View changes between v1.2.2 and v1.2.3" in body + name, version = current_version(pyproject) + assert name == "test-package" + assert version == "1.2.3" + + +def test_pyproject_container_parse() -> None: + toml_text = """ +[project] +name = "my-package" +version = "0.1.0" +dependencies = ["requests>=2.0.0"] +""".strip() + toml_doc, py_doc = PyProjectContainer.parse(toml_text) + assert py_doc.project.name == "my-package" + assert py_doc.project.version == "0.1.0" + assert py_doc.project.dependencies == ["requests>=2.0.0"] + + +def test_is_published_returns_true_when_version_exists() -> None: + mock_response = Mock() + mock_response.read.return_value = json.dumps( + {"releases": {"1.0.0": [], "1.1.0": []}} + ).encode() + + with patch("urllib.request.urlopen", return_value=mock_response): + result = is_published("test-package", "1.0.0") + assert result is True + + +def test_is_published_returns_false_when_version_missing() -> None: + mock_response = Mock() + mock_response.read.return_value = json.dumps({"releases": {"1.0.0": []}}).encode() + + with patch("urllib.request.urlopen", return_value=mock_response): + result = is_published("test-package", "1.1.0") + assert result is False + + +def test_is_published_returns_false_when_package_not_found() -> None: + mock_error = HTTPError("url", 404, "Not Found", {}, None) # type: ignore + + with patch("urllib.request.urlopen", side_effect=mock_error): + result = is_published("nonexistent-package", "1.0.0") + assert result is False + + +def test_is_published_raises_on_other_http_errors() -> None: + mock_error = HTTPError("url", 500, "Internal Server Error", {}, None) # type: ignore + + with patch("urllib.request.urlopen", side_effect=mock_error): + try: + is_published("test-package", "1.0.0") + assert False, "Expected HTTPError to be raised" + except HTTPError as e: + assert e.code == 500 + + +def test_sync_package_version_with_pyproject_updates_version(tmp_path: Path) -> None: + package_dir = tmp_path / "package" + package_dir.mkdir() + pyproject = package_dir / "pyproject.toml" + pyproject.write_text( + """ +[project] +name = "test-package" +version = "1.0.0" +dependencies = [] +""".strip() + ) + + packages = { + "test-js-package": PackageJson( + name="test-js-package", + version="2.0.0", + path=package_dir, + private=False, + ) + } + + sync_package_version_with_pyproject(package_dir, packages, "test-js-package") + + toml_doc, py_doc = PyProjectContainer.parse(pyproject.read_text()) + assert py_doc.project.version == "2.0.0" + + +def test_sync_package_version_with_pyproject_skips_when_no_pyproject( + tmp_path: Path, +) -> None: + package_dir = tmp_path / "package" + package_dir.mkdir() + + packages = { + "test-js-package": PackageJson( + name="test-js-package", + version="2.0.0", + path=package_dir, + private=False, + ) + } + + # Should not raise an error + sync_package_version_with_pyproject(package_dir, packages, "test-js-package") + + +def test_sync_package_version_with_pyproject_skips_when_versions_match( + tmp_path: Path, +) -> None: + package_dir = tmp_path / "package" + package_dir.mkdir() + pyproject = package_dir / "pyproject.toml" + original_content = """ +[project] +name = "test-package" +version = "2.0.0" +dependencies = [] +""".strip() + pyproject.write_text(original_content) + + packages = { + "test-js-package": PackageJson( + name="test-js-package", + version="2.0.0", + path=package_dir, + private=False, + ) + } + + sync_package_version_with_pyproject(package_dir, packages, "test-js-package") + + # Content should be unchanged + assert pyproject.read_text() == original_content diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..922b4c14 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,834 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@changesets/cli': + specifier: ^2.29.5 + version: 2.29.7 + changesets: + specifier: ^1.0.2 + version: 1.0.2 + + packages/llama-index-utils-workflow: + devDependencies: + '@changesets/cli': + specifier: ^2.29.5 + version: 2.29.7 + changesets: + specifier: ^1.0.2 + version: 1.0.2 + + packages/llama-index-workflows: + devDependencies: + '@changesets/cli': + specifier: ^2.29.5 + version: 2.29.7 + changesets: + specifier: ^1.0.2 + version: 1.0.2 + + packages/workflows-dev: + devDependencies: + '@changesets/cli': + specifier: ^2.29.5 + version: 2.29.7 + changesets: + specifier: ^1.0.2 + version: 1.0.2 + +packages: + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@changesets/apply-release-plan@7.0.13': + resolution: {integrity: sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg==} + + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + + '@changesets/cli@2.29.7': + resolution: {integrity: sha512-R7RqWoaksyyKXbKXBTbT4REdy22yH81mcFK6sWtqSanxUCbUi9Uf+6aqxZtDQouIqPdem2W56CdxXgsxdq7FLQ==} + hasBin: true + + '@changesets/config@3.1.1': + resolution: {integrity: sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.3': + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + + '@changesets/get-release-plan@4.0.13': + resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.1': + resolution: {integrity: sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==} + + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + + '@changesets/read@0.6.5': + resolution: {integrity: sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==} + + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + changesets@1.0.2: + resolution: {integrity: sha512-lnXvvqJEcK0z/6RtwKNLbejazl+Hxd1bocMcNgfLHWb4rGxuqkO/LdeGNzwIx3jHj+fNWZ6AGgK5AqNBwva4Xg==} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + human-id@4.1.2: + resolution: {integrity: sha512-v/J+4Z/1eIJovEBdlV5TYj1IR+ZiohcYGRY+qN/oC9dAfKzVT023N/Bgw37hrKCoVRBvk3bqyzpr2PP5YeTMSg==} + hasBin: true + + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + +snapshots: + + '@babel/runtime@7.28.4': {} + + '@changesets/apply-release-plan@7.0.13': + dependencies: + '@changesets/config': 3.1.1 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.7.3 + + '@changesets/assemble-release-plan@6.0.9': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.7.3 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/cli@2.29.7': + dependencies: + '@changesets/apply-release-plan': 7.0.13 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.1 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.13 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.5 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3 + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + ci-info: 3.9.0 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + p-limit: 2.3.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.7.3 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.1': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.3': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.7.3 + + '@changesets/get-release-plan@4.0.13': + dependencies: + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.5 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.1': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 3.14.2 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.5': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.1 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.1.2 + prettier: 2.8.8 + + '@inquirer/external-editor@1.0.3': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.0 + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.28.4 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.28.4 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@types/node@12.20.55': {} + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + array-union@2.1.0: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + changesets@1.0.2: {} + + chardet@2.1.1: {} + + ci-info@3.9.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + detect-indent@6.1.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + esprima@4.0.1: {} + + extendable-error@0.1.7: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + human-id@4.1.2: {} + + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-windows@1.0.2: {} + + isexe@2.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + lodash.startcase@4.4.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mri@1.2.0: {} + + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pify@4.0.1: {} + + prettier@2.8.8: {} + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.2 + pify: 4.0.1 + strip-bom: 3.0.0 + + resolve-from@5.0.0: {} + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + sprintf-js@1.0.3: {} + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: {} + + term-size@2.2.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + universalify@0.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..dee51e92 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "packages/*" diff --git a/uv.lock b/uv.lock index dbaf347c..6804a9ac 100644 --- a/uv.lock +++ b/uv.lock @@ -3658,6 +3658,8 @@ dependencies = [ { name = "click", version = "8.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "httpx" }, { name = "packaging" }, + { name = "pydantic" }, + { name = "tomlkit" }, ] [package.dev-dependencies] @@ -3671,6 +3673,8 @@ requires-dist = [ { name = "click", specifier = ">=8.1.7" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "packaging", specifier = ">=24.1" }, + { name = "pydantic", specifier = ">=2.12.3" }, + { name = "tomlkit", specifier = ">=0.13.3" }, ] [package.metadata.requires-dev]