mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-20 12:55:21 +02:00
Merge branch 'main' into feat/add-start-scripts-everything-server
This commit is contained in:
212
.github/workflows/release.yml
vendored
Normal file
212
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
name: Automatic Release Creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 10 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-metadata:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
hash: ${{ steps.last-release.outputs.hash }}
|
||||||
|
version: ${{ steps.create-version.outputs.version}}
|
||||||
|
npm_packages: ${{ steps.create-npm-packages.outputs.npm_packages}}
|
||||||
|
pypi_packages: ${{ steps.create-pypi-packages.outputs.pypi_packages}}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get last release hash
|
||||||
|
id: last-release
|
||||||
|
run: |
|
||||||
|
HASH=$(git rev-list --tags --max-count=1 || echo "HEAD~1")
|
||||||
|
echo "hash=${HASH}" >> $GITHUB_OUTPUT
|
||||||
|
echo "Using last release hash: ${HASH}"
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
|
||||||
|
- name: Create version name
|
||||||
|
id: create-version
|
||||||
|
run: |
|
||||||
|
VERSION=$(uv run --script scripts/release.py generate-version)
|
||||||
|
echo "version $VERSION"
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create notes
|
||||||
|
run: |
|
||||||
|
HASH="${{ steps.last-release.outputs.hash }}"
|
||||||
|
uv run --script scripts/release.py generate-notes --directory src/ $HASH > RELEASE_NOTES.md
|
||||||
|
cat RELEASE_NOTES.md
|
||||||
|
|
||||||
|
- name: Release notes
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-notes
|
||||||
|
path: RELEASE_NOTES.md
|
||||||
|
|
||||||
|
- name: Create python matrix
|
||||||
|
id: create-pypi-packages
|
||||||
|
run: |
|
||||||
|
HASH="${{ steps.last-release.outputs.hash }}"
|
||||||
|
PYPI=$(uv run --script scripts/release.py generate-matrix --pypi --directory src $HASH)
|
||||||
|
echo "pypi_packages $PYPI"
|
||||||
|
echo "pypi_packages=$PYPI" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create npm matrix
|
||||||
|
id: create-npm-packages
|
||||||
|
run: |
|
||||||
|
HASH="${{ steps.last-release.outputs.hash }}"
|
||||||
|
NPM=$(uv run --script scripts/release.py generate-matrix --npm --directory src $HASH)
|
||||||
|
echo "npm_packages $NPM"
|
||||||
|
echo "npm_packages=$NPM" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
update-packages:
|
||||||
|
needs: [create-metadata]
|
||||||
|
if: ${{ needs.create-metadata.outputs.npm_packages != '[]' || needs.create-metadata.outputs.pypi_packages != '[]' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
changes_made: ${{ steps.commit.outputs.changes_made }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
|
||||||
|
- name: Update packages
|
||||||
|
run: |
|
||||||
|
HASH="${{ needs.create-metadata.outputs.hash }}"
|
||||||
|
uv run --script scripts/release.py update-packages --directory src/ $HASH
|
||||||
|
|
||||||
|
- name: Configure git
|
||||||
|
run: |
|
||||||
|
git config --global user.name "GitHub Actions"
|
||||||
|
git config --global user.email "actions@github.com"
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
id: commit
|
||||||
|
run: |
|
||||||
|
VERSION="${{ needs.create-metadata.outputs.version }}"
|
||||||
|
git add -u
|
||||||
|
if git diff-index --quiet HEAD; then
|
||||||
|
echo "changes_made=false" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
git commit -m 'Automatic update of packages'
|
||||||
|
git tag -a "$VERSION" -m "Release $VERSION"
|
||||||
|
git push origin "$VERSION"
|
||||||
|
echo "changes_made=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
publish-pypi:
|
||||||
|
needs: [update-packages, create-metadata]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
package: ${{ fromJson(needs.create-metadata.outputs.pypi_packages) }}
|
||||||
|
name: Build ${{ matrix.package }}
|
||||||
|
environment: release
|
||||||
|
permissions:
|
||||||
|
id-token: write # Required for trusted publishing
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.create-metadata.outputs.version }}
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version-file: "src/${{ matrix.package }}/.python-version"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: src/${{ matrix.package }}
|
||||||
|
run: uv sync --frozen --all-extras --dev
|
||||||
|
|
||||||
|
- name: Run pyright
|
||||||
|
working-directory: src/${{ matrix.package }}
|
||||||
|
run: uv run --frozen pyright
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
working-directory: src/${{ matrix.package }}
|
||||||
|
run: uv build
|
||||||
|
|
||||||
|
- name: Publish package to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
packages-dir: src/${{ matrix.package }}/dist
|
||||||
|
|
||||||
|
publish-npm:
|
||||||
|
needs: [update-packages, create-metadata]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
package: ${{ fromJson(needs.create-metadata.outputs.npm_packages) }}
|
||||||
|
name: Build ${{ matrix.package }}
|
||||||
|
environment: release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.create-metadata.outputs.version }}
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: src/${{ matrix.package }}
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Check if version exists on npm
|
||||||
|
working-directory: src/${{ matrix.package }}
|
||||||
|
run: |
|
||||||
|
VERSION=$(jq -r .version package.json)
|
||||||
|
if npm view --json | jq --arg version "$VERSION" '[.[]][0].versions | contains([$version])'; then
|
||||||
|
echo "Version $VERSION already exists on npm"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Version $VERSION is new, proceeding with publish"
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
working-directory: src/${{ matrix.package }}
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Publish package
|
||||||
|
working-directory: src/${{ matrix.package }}
|
||||||
|
run: |
|
||||||
|
npm publish --access public
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
create-release:
|
||||||
|
needs: [update-packages, create-metadata, publish-pypi, publish-npm]
|
||||||
|
if: needs.update-packages.outputs.changes_made == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: release
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download release notes
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-notes
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN}}
|
||||||
|
run: |
|
||||||
|
VERSION="${{ needs.create-metadata.outputs.version }}"
|
||||||
|
gh release create "$VERSION" \
|
||||||
|
--title "Release $VERSION" \
|
||||||
|
--notes-file RELEASE_NOTES.md
|
||||||
210
scripts/release.py
Executable file
210
scripts/release.py
Executable file
@@ -0,0 +1,210 @@
|
|||||||
|
#!/usr/bin/env uv run --script
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.12"
|
||||||
|
# dependencies = [
|
||||||
|
# "click>=8.1.8",
|
||||||
|
# "tomlkit>=0.13.2"
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import click
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import tomlkit
|
||||||
|
import datetime
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Iterator, NewType, Protocol
|
||||||
|
|
||||||
|
|
||||||
|
Version = NewType("Version", str)
|
||||||
|
GitHash = NewType("GitHash", str)
|
||||||
|
|
||||||
|
|
||||||
|
class GitHashParamType(click.ParamType):
|
||||||
|
name = "git_hash"
|
||||||
|
|
||||||
|
def convert(
|
||||||
|
self, value: Any, param: click.Parameter | None, ctx: click.Context | None
|
||||||
|
) -> GitHash | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not (8 <= len(value) <= 40):
|
||||||
|
self.fail(f"Git hash must be between 8 and 40 characters, got {len(value)}")
|
||||||
|
|
||||||
|
if not re.match(r"^[0-9a-fA-F]+$", value):
|
||||||
|
self.fail("Git hash must contain only hex digits (0-9, a-f)")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Verify hash exists in repo
|
||||||
|
subprocess.run(
|
||||||
|
["git", "rev-parse", "--verify", value], check=True, capture_output=True
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
self.fail(f"Git hash {value} not found in repository")
|
||||||
|
|
||||||
|
return GitHash(value.lower())
|
||||||
|
|
||||||
|
|
||||||
|
GIT_HASH = GitHashParamType()
|
||||||
|
|
||||||
|
|
||||||
|
class Package(Protocol):
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
def package_name(self) -> str: ...
|
||||||
|
|
||||||
|
def update_version(self, version: Version) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NpmPackage:
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
def package_name(self) -> str:
|
||||||
|
with open(self.path / "package.json", "r") as f:
|
||||||
|
return json.load(f)["name"]
|
||||||
|
|
||||||
|
def update_version(self, version: Version):
|
||||||
|
with open(self.path / "package.json", "r+") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
data["version"] = version
|
||||||
|
f.seek(0)
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
f.truncate()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PyPiPackage:
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
def package_name(self) -> str:
|
||||||
|
with open(self.path / "pyproject.toml") as f:
|
||||||
|
toml_data = tomlkit.parse(f.read())
|
||||||
|
name = toml_data.get("project", {}).get("name")
|
||||||
|
if not name:
|
||||||
|
raise Exception("No name in pyproject.toml project section")
|
||||||
|
return str(name)
|
||||||
|
|
||||||
|
def update_version(self, version: Version):
|
||||||
|
# Update version in pyproject.toml
|
||||||
|
with open(self.path / "pyproject.toml") as f:
|
||||||
|
data = tomlkit.parse(f.read())
|
||||||
|
data["project"]["version"] = version
|
||||||
|
|
||||||
|
with open(self.path / "pyproject.toml", "w") as f:
|
||||||
|
f.write(tomlkit.dumps(data))
|
||||||
|
|
||||||
|
|
||||||
|
def has_changes(path: Path, git_hash: GitHash) -> bool:
|
||||||
|
"""Check if any files changed between current state and git hash"""
|
||||||
|
try:
|
||||||
|
output = subprocess.run(
|
||||||
|
["git", "diff", "--name-only", git_hash, "--", "."],
|
||||||
|
cwd=path,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
changed_files = [Path(f) for f in output.stdout.splitlines()]
|
||||||
|
relevant_files = [f for f in changed_files if f.suffix in [".py", ".ts"]]
|
||||||
|
return len(relevant_files) >= 1
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def gen_version() -> Version:
|
||||||
|
"""Generate version based on current date"""
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
return Version(f"{now.year}.{now.month}.{now.day}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_changed_packages(directory: Path, git_hash: GitHash) -> Iterator[Package]:
|
||||||
|
for path in directory.glob("*/package.json"):
|
||||||
|
if has_changes(path.parent, git_hash):
|
||||||
|
yield NpmPackage(path.parent)
|
||||||
|
for path in directory.glob("*/pyproject.toml"):
|
||||||
|
if has_changes(path.parent, git_hash):
|
||||||
|
yield PyPiPackage(path.parent)
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def cli():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("update-packages")
|
||||||
|
@click.option(
|
||||||
|
"--directory", type=click.Path(exists=True, path_type=Path), default=Path.cwd()
|
||||||
|
)
|
||||||
|
@click.argument("git_hash", type=GIT_HASH)
|
||||||
|
def update_packages(directory: Path, git_hash: GitHash) -> int:
|
||||||
|
# Detect package type
|
||||||
|
path = directory.resolve(strict=True)
|
||||||
|
version = gen_version()
|
||||||
|
|
||||||
|
for package in find_changed_packages(path, git_hash):
|
||||||
|
name = package.package_name()
|
||||||
|
package.update_version(version)
|
||||||
|
|
||||||
|
click.echo(f"{name}@{version}")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("generate-notes")
|
||||||
|
@click.option(
|
||||||
|
"--directory", type=click.Path(exists=True, path_type=Path), default=Path.cwd()
|
||||||
|
)
|
||||||
|
@click.argument("git_hash", type=GIT_HASH)
|
||||||
|
def generate_notes(directory: Path, git_hash: GitHash) -> int:
|
||||||
|
# Detect package type
|
||||||
|
path = directory.resolve(strict=True)
|
||||||
|
version = gen_version()
|
||||||
|
|
||||||
|
click.echo(f"# Release : v{version}")
|
||||||
|
click.echo("")
|
||||||
|
click.echo("## Updated packages")
|
||||||
|
for package in find_changed_packages(path, git_hash):
|
||||||
|
name = package.package_name()
|
||||||
|
click.echo(f"- {name}@{version}")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("generate-version")
|
||||||
|
def generate_version() -> int:
|
||||||
|
# Detect package type
|
||||||
|
click.echo(gen_version())
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("generate-matrix")
|
||||||
|
@click.option(
|
||||||
|
"--directory", type=click.Path(exists=True, path_type=Path), default=Path.cwd()
|
||||||
|
)
|
||||||
|
@click.option("--npm", is_flag=True, default=False)
|
||||||
|
@click.option("--pypi", is_flag=True, default=False)
|
||||||
|
@click.argument("git_hash", type=GIT_HASH)
|
||||||
|
def generate_matrix(directory: Path, git_hash: GitHash, pypi: bool, npm: bool) -> int:
|
||||||
|
# Detect package type
|
||||||
|
path = directory.resolve(strict=True)
|
||||||
|
version = gen_version()
|
||||||
|
|
||||||
|
changes = []
|
||||||
|
for package in find_changed_packages(path, git_hash):
|
||||||
|
pkg = package.path.relative_to(path)
|
||||||
|
if npm and isinstance(package, NpmPackage):
|
||||||
|
changes.append(str(pkg))
|
||||||
|
if pypi and isinstance(package, PyPiPackage):
|
||||||
|
changes.append(str(pkg))
|
||||||
|
|
||||||
|
click.echo(json.dumps(changes))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(cli())
|
||||||
@@ -188,6 +188,95 @@ MCP Server for the GitHub API, enabling file operations, repository management,
|
|||||||
- `issue_number` (number): Issue number to retrieve
|
- `issue_number` (number): Issue number to retrieve
|
||||||
- Returns: Github Issue object & details
|
- Returns: Github Issue object & details
|
||||||
|
|
||||||
|
18. `get_pull_request`
|
||||||
|
- Get details of a specific pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- Returns: Pull request details including diff and review status
|
||||||
|
|
||||||
|
19. `list_pull_requests`
|
||||||
|
- List and filter repository pull requests
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `state` (optional string): Filter by state ('open', 'closed', 'all')
|
||||||
|
- `head` (optional string): Filter by head user/org and branch
|
||||||
|
- `base` (optional string): Filter by base branch
|
||||||
|
- `sort` (optional string): Sort by ('created', 'updated', 'popularity', 'long-running')
|
||||||
|
- `direction` (optional string): Sort direction ('asc', 'desc')
|
||||||
|
- `per_page` (optional number): Results per page (max 100)
|
||||||
|
- `page` (optional number): Page number
|
||||||
|
- Returns: Array of pull request details
|
||||||
|
|
||||||
|
20. `create_pull_request_review`
|
||||||
|
- Create a review on a pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- `body` (string): Review comment text
|
||||||
|
- `event` (string): Review action ('APPROVE', 'REQUEST_CHANGES', 'COMMENT')
|
||||||
|
- `commit_id` (optional string): SHA of commit to review
|
||||||
|
- `comments` (optional array): Line-specific comments, each with:
|
||||||
|
- `path` (string): File path
|
||||||
|
- `position` (number): Line position in diff
|
||||||
|
- `body` (string): Comment text
|
||||||
|
- Returns: Created review details
|
||||||
|
|
||||||
|
21. `merge_pull_request`
|
||||||
|
- Merge a pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- `commit_title` (optional string): Title for merge commit
|
||||||
|
- `commit_message` (optional string): Extra detail for merge commit
|
||||||
|
- `merge_method` (optional string): Merge method ('merge', 'squash', 'rebase')
|
||||||
|
- Returns: Merge result details
|
||||||
|
|
||||||
|
22. `get_pull_request_files`
|
||||||
|
- Get the list of files changed in a pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- Returns: Array of changed files with patch and status details
|
||||||
|
|
||||||
|
23. `get_pull_request_status`
|
||||||
|
- Get the combined status of all status checks for a pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- Returns: Combined status check results and individual check details
|
||||||
|
|
||||||
|
24. `update_pull_request_branch`
|
||||||
|
- Update a pull request branch with the latest changes from the base branch (equivalent to GitHub's "Update branch" button)
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- `expected_head_sha` (optional string): The expected SHA of the pull request's HEAD ref
|
||||||
|
- Returns: Success message when branch is updated
|
||||||
|
|
||||||
|
25. `get_pull_request_comments`
|
||||||
|
- Get the review comments on a pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- Returns: Array of pull request review comments with details like the comment text, author, and location in the diff
|
||||||
|
|
||||||
|
26. `get_pull_request_reviews`
|
||||||
|
- Get the reviews on a pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- Returns: Array of pull request reviews with details like the review state (APPROVED, CHANGES_REQUESTED, etc.), reviewer, and review body
|
||||||
|
|
||||||
## Search Query Syntax
|
## Search Query Syntax
|
||||||
|
|
||||||
### Code Search
|
### Code Search
|
||||||
|
|||||||
89
src/github/common/errors.ts
Normal file
89
src/github/common/errors.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
export class GitHubError extends Error {
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
public readonly status: number,
|
||||||
|
public readonly response: unknown
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = "GitHubError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitHubValidationError extends GitHubError {
|
||||||
|
constructor(message: string, status: number, response: unknown) {
|
||||||
|
super(message, status, response);
|
||||||
|
this.name = "GitHubValidationError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitHubResourceNotFoundError extends GitHubError {
|
||||||
|
constructor(resource: string) {
|
||||||
|
super(`Resource not found: ${resource}`, 404, { message: `${resource} not found` });
|
||||||
|
this.name = "GitHubResourceNotFoundError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitHubAuthenticationError extends GitHubError {
|
||||||
|
constructor(message = "Authentication failed") {
|
||||||
|
super(message, 401, { message });
|
||||||
|
this.name = "GitHubAuthenticationError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitHubPermissionError extends GitHubError {
|
||||||
|
constructor(message = "Insufficient permissions") {
|
||||||
|
super(message, 403, { message });
|
||||||
|
this.name = "GitHubPermissionError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitHubRateLimitError extends GitHubError {
|
||||||
|
constructor(
|
||||||
|
message = "Rate limit exceeded",
|
||||||
|
public readonly resetAt: Date
|
||||||
|
) {
|
||||||
|
super(message, 429, { message, reset_at: resetAt.toISOString() });
|
||||||
|
this.name = "GitHubRateLimitError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitHubConflictError extends GitHubError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message, 409, { message });
|
||||||
|
this.name = "GitHubConflictError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGitHubError(error: unknown): error is GitHubError {
|
||||||
|
return error instanceof GitHubError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createGitHubError(status: number, response: any): GitHubError {
|
||||||
|
switch (status) {
|
||||||
|
case 401:
|
||||||
|
return new GitHubAuthenticationError(response?.message);
|
||||||
|
case 403:
|
||||||
|
return new GitHubPermissionError(response?.message);
|
||||||
|
case 404:
|
||||||
|
return new GitHubResourceNotFoundError(response?.message || "Resource");
|
||||||
|
case 409:
|
||||||
|
return new GitHubConflictError(response?.message || "Conflict occurred");
|
||||||
|
case 422:
|
||||||
|
return new GitHubValidationError(
|
||||||
|
response?.message || "Validation failed",
|
||||||
|
status,
|
||||||
|
response
|
||||||
|
);
|
||||||
|
case 429:
|
||||||
|
return new GitHubRateLimitError(
|
||||||
|
response?.message,
|
||||||
|
new Date(response?.reset_at || Date.now() + 60000)
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return new GitHubError(
|
||||||
|
response?.message || "GitHub API error",
|
||||||
|
status,
|
||||||
|
response
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
259
src/github/common/types.ts
Normal file
259
src/github/common/types.ts
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// Base schemas for common types
|
||||||
|
export const GitHubAuthorSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
email: z.string(),
|
||||||
|
date: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubOwnerSchema = z.object({
|
||||||
|
login: z.string(),
|
||||||
|
id: z.number(),
|
||||||
|
node_id: z.string(),
|
||||||
|
avatar_url: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubRepositorySchema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
node_id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
full_name: z.string(),
|
||||||
|
private: z.boolean(),
|
||||||
|
owner: GitHubOwnerSchema,
|
||||||
|
html_url: z.string(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
fork: z.boolean(),
|
||||||
|
url: z.string(),
|
||||||
|
created_at: z.string(),
|
||||||
|
updated_at: z.string(),
|
||||||
|
pushed_at: z.string(),
|
||||||
|
git_url: z.string(),
|
||||||
|
ssh_url: z.string(),
|
||||||
|
clone_url: z.string(),
|
||||||
|
default_branch: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GithubFileContentLinks = z.object({
|
||||||
|
self: z.string(),
|
||||||
|
git: z.string().nullable(),
|
||||||
|
html: z.string().nullable()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubFileContentSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
path: z.string(),
|
||||||
|
sha: z.string(),
|
||||||
|
size: z.number(),
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
git_url: z.string(),
|
||||||
|
download_url: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
encoding: z.string().optional(),
|
||||||
|
_links: GithubFileContentLinks
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubDirectoryContentSchema = z.object({
|
||||||
|
type: z.string(),
|
||||||
|
size: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
path: z.string(),
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
git_url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
download_url: z.string().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubContentSchema = z.union([
|
||||||
|
GitHubFileContentSchema,
|
||||||
|
z.array(GitHubDirectoryContentSchema),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const GitHubTreeEntrySchema = z.object({
|
||||||
|
path: z.string(),
|
||||||
|
mode: z.enum(["100644", "100755", "040000", "160000", "120000"]),
|
||||||
|
type: z.enum(["blob", "tree", "commit"]),
|
||||||
|
size: z.number().optional(),
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubTreeSchema = z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
tree: z.array(GitHubTreeEntrySchema),
|
||||||
|
truncated: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubCommitSchema = z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
node_id: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
author: GitHubAuthorSchema,
|
||||||
|
committer: GitHubAuthorSchema,
|
||||||
|
message: z.string(),
|
||||||
|
tree: z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
}),
|
||||||
|
parents: z.array(
|
||||||
|
z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubListCommitsSchema = z.array(z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
node_id: z.string(),
|
||||||
|
commit: z.object({
|
||||||
|
author: GitHubAuthorSchema,
|
||||||
|
committer: GitHubAuthorSchema,
|
||||||
|
message: z.string(),
|
||||||
|
tree: z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string()
|
||||||
|
}),
|
||||||
|
url: z.string(),
|
||||||
|
comment_count: z.number(),
|
||||||
|
}),
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
comments_url: z.string()
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const GitHubReferenceSchema = z.object({
|
||||||
|
ref: z.string(),
|
||||||
|
node_id: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
object: z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// User and assignee schemas
|
||||||
|
export const GitHubIssueAssigneeSchema = z.object({
|
||||||
|
login: z.string(),
|
||||||
|
id: z.number(),
|
||||||
|
avatar_url: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Issue-related schemas
|
||||||
|
export const GitHubLabelSchema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
node_id: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
color: z.string(),
|
||||||
|
default: z.boolean(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubMilestoneSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
labels_url: z.string(),
|
||||||
|
id: z.number(),
|
||||||
|
node_id: z.string(),
|
||||||
|
number: z.number(),
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
state: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubIssueSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
repository_url: z.string(),
|
||||||
|
labels_url: z.string(),
|
||||||
|
comments_url: z.string(),
|
||||||
|
events_url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
id: z.number(),
|
||||||
|
node_id: z.string(),
|
||||||
|
number: z.number(),
|
||||||
|
title: z.string(),
|
||||||
|
user: GitHubIssueAssigneeSchema,
|
||||||
|
labels: z.array(GitHubLabelSchema),
|
||||||
|
state: z.string(),
|
||||||
|
locked: z.boolean(),
|
||||||
|
assignee: GitHubIssueAssigneeSchema.nullable(),
|
||||||
|
assignees: z.array(GitHubIssueAssigneeSchema),
|
||||||
|
milestone: GitHubMilestoneSchema.nullable(),
|
||||||
|
comments: z.number(),
|
||||||
|
created_at: z.string(),
|
||||||
|
updated_at: z.string(),
|
||||||
|
closed_at: z.string().nullable(),
|
||||||
|
body: z.string().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Search-related schemas
|
||||||
|
export const GitHubSearchResponseSchema = z.object({
|
||||||
|
total_count: z.number(),
|
||||||
|
incomplete_results: z.boolean(),
|
||||||
|
items: z.array(GitHubRepositorySchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pull request schemas
|
||||||
|
export const GitHubPullRequestRefSchema = z.object({
|
||||||
|
label: z.string(),
|
||||||
|
ref: z.string(),
|
||||||
|
sha: z.string(),
|
||||||
|
user: GitHubIssueAssigneeSchema,
|
||||||
|
repo: GitHubRepositorySchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubPullRequestSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
id: z.number(),
|
||||||
|
node_id: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
diff_url: z.string(),
|
||||||
|
patch_url: z.string(),
|
||||||
|
issue_url: z.string(),
|
||||||
|
number: z.number(),
|
||||||
|
state: z.string(),
|
||||||
|
locked: z.boolean(),
|
||||||
|
title: z.string(),
|
||||||
|
user: GitHubIssueAssigneeSchema,
|
||||||
|
body: z.string().nullable(),
|
||||||
|
created_at: z.string(),
|
||||||
|
updated_at: z.string(),
|
||||||
|
closed_at: z.string().nullable(),
|
||||||
|
merged_at: z.string().nullable(),
|
||||||
|
merge_commit_sha: z.string().nullable(),
|
||||||
|
assignee: GitHubIssueAssigneeSchema.nullable(),
|
||||||
|
assignees: z.array(GitHubIssueAssigneeSchema),
|
||||||
|
requested_reviewers: z.array(GitHubIssueAssigneeSchema),
|
||||||
|
labels: z.array(GitHubLabelSchema),
|
||||||
|
head: GitHubPullRequestRefSchema,
|
||||||
|
base: GitHubPullRequestRefSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export types
|
||||||
|
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
|
||||||
|
export type GitHubRepository = z.infer<typeof GitHubRepositorySchema>;
|
||||||
|
export type GitHubFileContent = z.infer<typeof GitHubFileContentSchema>;
|
||||||
|
export type GitHubDirectoryContent = z.infer<typeof GitHubDirectoryContentSchema>;
|
||||||
|
export type GitHubContent = z.infer<typeof GitHubContentSchema>;
|
||||||
|
export type GitHubTree = z.infer<typeof GitHubTreeSchema>;
|
||||||
|
export type GitHubCommit = z.infer<typeof GitHubCommitSchema>;
|
||||||
|
export type GitHubListCommits = z.infer<typeof GitHubListCommitsSchema>;
|
||||||
|
export type GitHubReference = z.infer<typeof GitHubReferenceSchema>;
|
||||||
|
export type GitHubIssueAssignee = z.infer<typeof GitHubIssueAssigneeSchema>;
|
||||||
|
export type GitHubLabel = z.infer<typeof GitHubLabelSchema>;
|
||||||
|
export type GitHubMilestone = z.infer<typeof GitHubMilestoneSchema>;
|
||||||
|
export type GitHubIssue = z.infer<typeof GitHubIssueSchema>;
|
||||||
|
export type GitHubSearchResponse = z.infer<typeof GitHubSearchResponseSchema>;
|
||||||
|
export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>;
|
||||||
|
export type GitHubPullRequestRef = z.infer<typeof GitHubPullRequestRefSchema>;
|
||||||
133
src/github/common/utils.ts
Normal file
133
src/github/common/utils.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { createGitHubError } from "./errors.js";
|
||||||
|
|
||||||
|
type RequestOptions = {
|
||||||
|
method?: string;
|
||||||
|
body?: unknown;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function parseResponseBody(response: Response): Promise<unknown> {
|
||||||
|
const contentType = response.headers.get("content-type");
|
||||||
|
if (contentType?.includes("application/json")) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildUrl(baseUrl: string, params: Record<string, string | number | undefined>): string {
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
url.searchParams.append(key, value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function githubRequest(
|
||||||
|
url: string,
|
||||||
|
options: RequestOptions = {}
|
||||||
|
): Promise<unknown> {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...options.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) {
|
||||||
|
headers["Authorization"] = `Bearer ${process.env.GITHUB_PERSONAL_ACCESS_TOKEN}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: options.method || "GET",
|
||||||
|
headers,
|
||||||
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseBody = await parseResponseBody(response);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw createGitHubError(response.status, responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateBranchName(branch: string): string {
|
||||||
|
const sanitized = branch.trim();
|
||||||
|
if (!sanitized) {
|
||||||
|
throw new Error("Branch name cannot be empty");
|
||||||
|
}
|
||||||
|
if (sanitized.includes("..")) {
|
||||||
|
throw new Error("Branch name cannot contain '..'");
|
||||||
|
}
|
||||||
|
if (/[\s~^:?*[\\\]]/.test(sanitized)) {
|
||||||
|
throw new Error("Branch name contains invalid characters");
|
||||||
|
}
|
||||||
|
if (sanitized.startsWith("/") || sanitized.endsWith("/")) {
|
||||||
|
throw new Error("Branch name cannot start or end with '/'");
|
||||||
|
}
|
||||||
|
if (sanitized.endsWith(".lock")) {
|
||||||
|
throw new Error("Branch name cannot end with '.lock'");
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateRepositoryName(name: string): string {
|
||||||
|
const sanitized = name.trim().toLowerCase();
|
||||||
|
if (!sanitized) {
|
||||||
|
throw new Error("Repository name cannot be empty");
|
||||||
|
}
|
||||||
|
if (!/^[a-z0-9_.-]+$/.test(sanitized)) {
|
||||||
|
throw new Error(
|
||||||
|
"Repository name can only contain lowercase letters, numbers, hyphens, periods, and underscores"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (sanitized.startsWith(".") || sanitized.endsWith(".")) {
|
||||||
|
throw new Error("Repository name cannot start or end with a period");
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateOwnerName(owner: string): string {
|
||||||
|
const sanitized = owner.trim().toLowerCase();
|
||||||
|
if (!sanitized) {
|
||||||
|
throw new Error("Owner name cannot be empty");
|
||||||
|
}
|
||||||
|
if (!/^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,38}$/.test(sanitized)) {
|
||||||
|
throw new Error(
|
||||||
|
"Owner name must start with a letter or number and can contain up to 39 characters"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkBranchExists(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
branch: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/branches/${branch}`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkUserExists(username: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await githubRequest(`https://api.github.com/users/${username}`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
112
src/github/operations/branches.ts
Normal file
112
src/github/operations/branches.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { githubRequest } from "../common/utils.js";
|
||||||
|
import { GitHubReferenceSchema } from "../common/types.js";
|
||||||
|
|
||||||
|
// Schema definitions
|
||||||
|
export const CreateBranchOptionsSchema = z.object({
|
||||||
|
ref: z.string(),
|
||||||
|
sha: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateBranchSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
branch: z.string().describe("Name for the new branch"),
|
||||||
|
from_branch: z.string().optional().describe("Optional: source branch to create from (defaults to the repository's default branch)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Type exports
|
||||||
|
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
|
||||||
|
|
||||||
|
// Function implementations
|
||||||
|
export async function getDefaultBranchSHA(owner: string, repo: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`
|
||||||
|
);
|
||||||
|
const data = GitHubReferenceSchema.parse(response);
|
||||||
|
return data.object.sha;
|
||||||
|
} catch (error) {
|
||||||
|
const masterResponse = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`
|
||||||
|
);
|
||||||
|
if (!masterResponse) {
|
||||||
|
throw new Error("Could not find default branch (tried 'main' and 'master')");
|
||||||
|
}
|
||||||
|
const data = GitHubReferenceSchema.parse(masterResponse);
|
||||||
|
return data.object.sha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createBranch(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
options: CreateBranchOptions
|
||||||
|
): Promise<z.infer<typeof GitHubReferenceSchema>> {
|
||||||
|
const fullRef = `refs/heads/${options.ref}`;
|
||||||
|
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/refs`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
ref: fullRef,
|
||||||
|
sha: options.sha,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return GitHubReferenceSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBranchSHA(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
branch: string
|
||||||
|
): Promise<string> {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = GitHubReferenceSchema.parse(response);
|
||||||
|
return data.object.sha;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createBranchFromRef(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
newBranch: string,
|
||||||
|
fromBranch?: string
|
||||||
|
): Promise<z.infer<typeof GitHubReferenceSchema>> {
|
||||||
|
let sha: string;
|
||||||
|
if (fromBranch) {
|
||||||
|
sha = await getBranchSHA(owner, repo, fromBranch);
|
||||||
|
} else {
|
||||||
|
sha = await getDefaultBranchSHA(owner, repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createBranch(owner, repo, {
|
||||||
|
ref: newBranch,
|
||||||
|
sha,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateBranch(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
branch: string,
|
||||||
|
sha: string
|
||||||
|
): Promise<z.infer<typeof GitHubReferenceSchema>> {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
body: {
|
||||||
|
sha,
|
||||||
|
force: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return GitHubReferenceSchema.parse(response);
|
||||||
|
}
|
||||||
26
src/github/operations/commits.ts
Normal file
26
src/github/operations/commits.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { githubRequest, buildUrl } from "../common/utils.js";
|
||||||
|
|
||||||
|
export const ListCommitsSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
sha: z.string().optional(),
|
||||||
|
page: z.number().optional(),
|
||||||
|
perPage: z.number().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function listCommits(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
page?: number,
|
||||||
|
perPage?: number,
|
||||||
|
sha?: string
|
||||||
|
) {
|
||||||
|
return githubRequest(
|
||||||
|
buildUrl(`https://api.github.com/repos/${owner}/${repo}/commits`, {
|
||||||
|
page: page?.toString(),
|
||||||
|
per_page: perPage?.toString(),
|
||||||
|
sha
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
219
src/github/operations/files.ts
Normal file
219
src/github/operations/files.ts
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { githubRequest } from "../common/utils.js";
|
||||||
|
import {
|
||||||
|
GitHubContentSchema,
|
||||||
|
GitHubAuthorSchema,
|
||||||
|
GitHubTreeSchema,
|
||||||
|
GitHubCommitSchema,
|
||||||
|
GitHubReferenceSchema,
|
||||||
|
GitHubFileContentSchema,
|
||||||
|
} from "../common/types.js";
|
||||||
|
|
||||||
|
// Schema definitions
|
||||||
|
export const FileOperationSchema = z.object({
|
||||||
|
path: z.string(),
|
||||||
|
content: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateOrUpdateFileSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
path: z.string().describe("Path where to create/update the file"),
|
||||||
|
content: z.string().describe("Content of the file"),
|
||||||
|
message: z.string().describe("Commit message"),
|
||||||
|
branch: z.string().describe("Branch to create/update the file in"),
|
||||||
|
sha: z.string().optional().describe("SHA of the file being replaced (required when updating existing files)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetFileContentsSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
path: z.string().describe("Path to the file or directory"),
|
||||||
|
branch: z.string().optional().describe("Branch to get contents from"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PushFilesSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"),
|
||||||
|
files: z.array(FileOperationSchema).describe("Array of files to push"),
|
||||||
|
message: z.string().describe("Commit message"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GitHubCreateUpdateFileResponseSchema = z.object({
|
||||||
|
content: GitHubFileContentSchema.nullable(),
|
||||||
|
commit: z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
node_id: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
author: GitHubAuthorSchema,
|
||||||
|
committer: GitHubAuthorSchema,
|
||||||
|
message: z.string(),
|
||||||
|
tree: z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
}),
|
||||||
|
parents: z.array(
|
||||||
|
z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Type exports
|
||||||
|
export type FileOperation = z.infer<typeof FileOperationSchema>;
|
||||||
|
export type GitHubCreateUpdateFileResponse = z.infer<typeof GitHubCreateUpdateFileResponseSchema>;
|
||||||
|
|
||||||
|
// Function implementations
|
||||||
|
export async function getFileContents(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
path: string,
|
||||||
|
branch?: string
|
||||||
|
) {
|
||||||
|
let url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
|
||||||
|
if (branch) {
|
||||||
|
url += `?ref=${branch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await githubRequest(url);
|
||||||
|
const data = GitHubContentSchema.parse(response);
|
||||||
|
|
||||||
|
// If it's a file, decode the content
|
||||||
|
if (!Array.isArray(data) && data.content) {
|
||||||
|
data.content = Buffer.from(data.content, "base64").toString("utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createOrUpdateFile(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
path: string,
|
||||||
|
content: string,
|
||||||
|
message: string,
|
||||||
|
branch: string,
|
||||||
|
sha?: string
|
||||||
|
) {
|
||||||
|
const encodedContent = Buffer.from(content).toString("base64");
|
||||||
|
|
||||||
|
let currentSha = sha;
|
||||||
|
if (!currentSha) {
|
||||||
|
try {
|
||||||
|
const existingFile = await getFileContents(owner, repo, path, branch);
|
||||||
|
if (!Array.isArray(existingFile)) {
|
||||||
|
currentSha = existingFile.sha;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Note: File does not exist in branch, will create new file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
|
||||||
|
const body = {
|
||||||
|
message,
|
||||||
|
content: encodedContent,
|
||||||
|
branch,
|
||||||
|
...(currentSha ? { sha: currentSha } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await githubRequest(url, {
|
||||||
|
method: "PUT",
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return GitHubCreateUpdateFileResponseSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTree(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
files: FileOperation[],
|
||||||
|
baseTree?: string
|
||||||
|
) {
|
||||||
|
const tree = files.map((file) => ({
|
||||||
|
path: file.path,
|
||||||
|
mode: "100644" as const,
|
||||||
|
type: "blob" as const,
|
||||||
|
content: file.content,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/trees`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
tree,
|
||||||
|
base_tree: baseTree,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return GitHubTreeSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createCommit(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
message: string,
|
||||||
|
tree: string,
|
||||||
|
parents: string[]
|
||||||
|
) {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/commits`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
message,
|
||||||
|
tree,
|
||||||
|
parents,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return GitHubCommitSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateReference(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
ref: string,
|
||||||
|
sha: string
|
||||||
|
) {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/${ref}`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
body: {
|
||||||
|
sha,
|
||||||
|
force: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return GitHubReferenceSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pushFiles(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
branch: string,
|
||||||
|
files: FileOperation[],
|
||||||
|
message: string
|
||||||
|
) {
|
||||||
|
const refResponse = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const ref = GitHubReferenceSchema.parse(refResponse);
|
||||||
|
const commitSha = ref.object.sha;
|
||||||
|
|
||||||
|
const tree = await createTree(owner, repo, files, commitSha);
|
||||||
|
const commit = await createCommit(owner, repo, message, tree.sha, [commitSha]);
|
||||||
|
return await updateReference(owner, repo, `heads/${branch}`, commit.sha);
|
||||||
|
}
|
||||||
118
src/github/operations/issues.ts
Normal file
118
src/github/operations/issues.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { githubRequest, buildUrl } from "../common/utils.js";
|
||||||
|
|
||||||
|
export const GetIssueSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
issue_number: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IssueCommentSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
issue_number: z.number(),
|
||||||
|
body: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateIssueOptionsSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
body: z.string().optional(),
|
||||||
|
assignees: z.array(z.string()).optional(),
|
||||||
|
milestone: z.number().optional(),
|
||||||
|
labels: z.array(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateIssueSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
...CreateIssueOptionsSchema.shape,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ListIssuesOptionsSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
direction: z.enum(["asc", "desc"]).optional(),
|
||||||
|
labels: z.array(z.string()).optional(),
|
||||||
|
page: z.number().optional(),
|
||||||
|
per_page: z.number().optional(),
|
||||||
|
since: z.string().optional(),
|
||||||
|
sort: z.enum(["created", "updated", "comments"]).optional(),
|
||||||
|
state: z.enum(["open", "closed", "all"]).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateIssueOptionsSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
issue_number: z.number(),
|
||||||
|
title: z.string().optional(),
|
||||||
|
body: z.string().optional(),
|
||||||
|
assignees: z.array(z.string()).optional(),
|
||||||
|
milestone: z.number().optional(),
|
||||||
|
labels: z.array(z.string()).optional(),
|
||||||
|
state: z.enum(["open", "closed"]).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function getIssue(owner: string, repo: string, issue_number: number) {
|
||||||
|
return githubRequest(`https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addIssueComment(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
issue_number: number,
|
||||||
|
body: string
|
||||||
|
) {
|
||||||
|
return githubRequest(`https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}/comments`, {
|
||||||
|
method: "POST",
|
||||||
|
body: { body },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createIssue(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
options: z.infer<typeof CreateIssueOptionsSchema>
|
||||||
|
) {
|
||||||
|
return githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/issues`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: options,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listIssues(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
options: Omit<z.infer<typeof ListIssuesOptionsSchema>, "owner" | "repo">
|
||||||
|
) {
|
||||||
|
const urlParams: Record<string, string | undefined> = {
|
||||||
|
direction: options.direction,
|
||||||
|
labels: options.labels?.join(","),
|
||||||
|
page: options.page?.toString(),
|
||||||
|
per_page: options.per_page?.toString(),
|
||||||
|
since: options.since,
|
||||||
|
sort: options.sort,
|
||||||
|
state: options.state
|
||||||
|
};
|
||||||
|
|
||||||
|
return githubRequest(
|
||||||
|
buildUrl(`https://api.github.com/repos/${owner}/${repo}/issues`, urlParams)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateIssue(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
issue_number: number,
|
||||||
|
options: Omit<z.infer<typeof UpdateIssueOptionsSchema>, "owner" | "repo" | "issue_number">
|
||||||
|
) {
|
||||||
|
return githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
body: options,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
302
src/github/operations/pulls.ts
Normal file
302
src/github/operations/pulls.ts
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { githubRequest } from "../common/utils.js";
|
||||||
|
import {
|
||||||
|
GitHubPullRequestSchema,
|
||||||
|
GitHubIssueAssigneeSchema,
|
||||||
|
GitHubRepositorySchema,
|
||||||
|
} from "../common/types.js";
|
||||||
|
|
||||||
|
// Schema definitions
|
||||||
|
export const PullRequestFileSchema = z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
filename: z.string(),
|
||||||
|
status: z.enum(['added', 'removed', 'modified', 'renamed', 'copied', 'changed', 'unchanged']),
|
||||||
|
additions: z.number(),
|
||||||
|
deletions: z.number(),
|
||||||
|
changes: z.number(),
|
||||||
|
blob_url: z.string(),
|
||||||
|
raw_url: z.string(),
|
||||||
|
contents_url: z.string(),
|
||||||
|
patch: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StatusCheckSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
state: z.enum(['error', 'failure', 'pending', 'success']),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
target_url: z.string().nullable(),
|
||||||
|
context: z.string(),
|
||||||
|
created_at: z.string(),
|
||||||
|
updated_at: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CombinedStatusSchema = z.object({
|
||||||
|
state: z.enum(['error', 'failure', 'pending', 'success']),
|
||||||
|
statuses: z.array(StatusCheckSchema),
|
||||||
|
sha: z.string(),
|
||||||
|
total_count: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PullRequestCommentSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
id: z.number(),
|
||||||
|
node_id: z.string(),
|
||||||
|
pull_request_review_id: z.number().nullable(),
|
||||||
|
diff_hunk: z.string(),
|
||||||
|
path: z.string().nullable(),
|
||||||
|
position: z.number().nullable(),
|
||||||
|
original_position: z.number().nullable(),
|
||||||
|
commit_id: z.string(),
|
||||||
|
original_commit_id: z.string(),
|
||||||
|
user: GitHubIssueAssigneeSchema,
|
||||||
|
body: z.string(),
|
||||||
|
created_at: z.string(),
|
||||||
|
updated_at: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
pull_request_url: z.string(),
|
||||||
|
author_association: z.string(),
|
||||||
|
_links: z.object({
|
||||||
|
self: z.object({ href: z.string() }),
|
||||||
|
html: z.object({ href: z.string() }),
|
||||||
|
pull_request: z.object({ href: z.string() })
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PullRequestReviewSchema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
node_id: z.string(),
|
||||||
|
user: GitHubIssueAssigneeSchema,
|
||||||
|
body: z.string().nullable(),
|
||||||
|
state: z.enum(['APPROVED', 'CHANGES_REQUESTED', 'COMMENTED', 'DISMISSED', 'PENDING']),
|
||||||
|
html_url: z.string(),
|
||||||
|
pull_request_url: z.string(),
|
||||||
|
commit_id: z.string(),
|
||||||
|
submitted_at: z.string().nullable(),
|
||||||
|
author_association: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Input schemas
|
||||||
|
export const CreatePullRequestSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
title: z.string().describe("Pull request title"),
|
||||||
|
body: z.string().optional().describe("Pull request body/description"),
|
||||||
|
head: z.string().describe("The name of the branch where your changes are implemented"),
|
||||||
|
base: z.string().describe("The name of the branch you want the changes pulled into"),
|
||||||
|
draft: z.boolean().optional().describe("Whether to create the pull request as a draft"),
|
||||||
|
maintainer_can_modify: z.boolean().optional().describe("Whether maintainers can modify the pull request")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetPullRequestSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
pull_number: z.number().describe("Pull request number")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ListPullRequestsSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
state: z.enum(['open', 'closed', 'all']).optional().describe("State of the pull requests to return"),
|
||||||
|
head: z.string().optional().describe("Filter by head user or head organization and branch name"),
|
||||||
|
base: z.string().optional().describe("Filter by base branch name"),
|
||||||
|
sort: z.enum(['created', 'updated', 'popularity', 'long-running']).optional().describe("What to sort results by"),
|
||||||
|
direction: z.enum(['asc', 'desc']).optional().describe("The direction of the sort"),
|
||||||
|
per_page: z.number().optional().describe("Results per page (max 100)"),
|
||||||
|
page: z.number().optional().describe("Page number of the results")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreatePullRequestReviewSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
pull_number: z.number().describe("Pull request number"),
|
||||||
|
commit_id: z.string().optional().describe("The SHA of the commit that needs a review"),
|
||||||
|
body: z.string().describe("The body text of the review"),
|
||||||
|
event: z.enum(['APPROVE', 'REQUEST_CHANGES', 'COMMENT']).describe("The review action to perform"),
|
||||||
|
comments: z.array(z.object({
|
||||||
|
path: z.string().describe("The relative path to the file being commented on"),
|
||||||
|
position: z.number().describe("The position in the diff where you want to add a review comment"),
|
||||||
|
body: z.string().describe("Text of the review comment")
|
||||||
|
})).optional().describe("Comments to post as part of the review")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MergePullRequestSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
pull_number: z.number().describe("Pull request number"),
|
||||||
|
commit_title: z.string().optional().describe("Title for the automatic commit message"),
|
||||||
|
commit_message: z.string().optional().describe("Extra detail to append to automatic commit message"),
|
||||||
|
merge_method: z.enum(['merge', 'squash', 'rebase']).optional().describe("Merge method to use")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetPullRequestFilesSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
pull_number: z.number().describe("Pull request number")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetPullRequestStatusSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
pull_number: z.number().describe("Pull request number")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdatePullRequestBranchSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
pull_number: z.number().describe("Pull request number"),
|
||||||
|
expected_head_sha: z.string().optional().describe("The expected SHA of the pull request's HEAD ref")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetPullRequestCommentsSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
pull_number: z.number().describe("Pull request number")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetPullRequestReviewsSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
pull_number: z.number().describe("Pull request number")
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function implementations
|
||||||
|
export async function createPullRequest(
|
||||||
|
params: z.infer<typeof CreatePullRequestSchema>
|
||||||
|
): Promise<z.infer<typeof GitHubPullRequestSchema>> {
|
||||||
|
const { owner, repo, ...options } = CreatePullRequestSchema.parse(params);
|
||||||
|
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: options,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return GitHubPullRequestSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPullRequest(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number
|
||||||
|
): Promise<z.infer<typeof GitHubPullRequestSchema>> {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`
|
||||||
|
);
|
||||||
|
return GitHubPullRequestSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listPullRequests(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
options: Omit<z.infer<typeof ListPullRequestsSchema>, 'owner' | 'repo'>
|
||||||
|
): Promise<z.infer<typeof GitHubPullRequestSchema>[]> {
|
||||||
|
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/pulls`);
|
||||||
|
|
||||||
|
if (options.state) url.searchParams.append('state', options.state);
|
||||||
|
if (options.head) url.searchParams.append('head', options.head);
|
||||||
|
if (options.base) url.searchParams.append('base', options.base);
|
||||||
|
if (options.sort) url.searchParams.append('sort', options.sort);
|
||||||
|
if (options.direction) url.searchParams.append('direction', options.direction);
|
||||||
|
if (options.per_page) url.searchParams.append('per_page', options.per_page.toString());
|
||||||
|
if (options.page) url.searchParams.append('page', options.page.toString());
|
||||||
|
|
||||||
|
const response = await githubRequest(url.toString());
|
||||||
|
return z.array(GitHubPullRequestSchema).parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createPullRequestReview(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number,
|
||||||
|
options: Omit<z.infer<typeof CreatePullRequestReviewSchema>, 'owner' | 'repo' | 'pull_number'>
|
||||||
|
): Promise<z.infer<typeof PullRequestReviewSchema>> {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: options,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return PullRequestReviewSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mergePullRequest(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number,
|
||||||
|
options: Omit<z.infer<typeof MergePullRequestSchema>, 'owner' | 'repo' | 'pull_number'>
|
||||||
|
): Promise<any> {
|
||||||
|
return githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/merge`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
body: options,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPullRequestFiles(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number
|
||||||
|
): Promise<z.infer<typeof PullRequestFileSchema>[]> {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/files`
|
||||||
|
);
|
||||||
|
return z.array(PullRequestFileSchema).parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updatePullRequestBranch(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number,
|
||||||
|
expectedHeadSha?: string
|
||||||
|
): Promise<void> {
|
||||||
|
await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/update-branch`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
body: expectedHeadSha ? { expected_head_sha: expectedHeadSha } : undefined,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPullRequestComments(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number
|
||||||
|
): Promise<z.infer<typeof PullRequestCommentSchema>[]> {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`
|
||||||
|
);
|
||||||
|
return z.array(PullRequestCommentSchema).parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPullRequestReviews(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number
|
||||||
|
): Promise<z.infer<typeof PullRequestReviewSchema>[]> {
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`
|
||||||
|
);
|
||||||
|
return z.array(PullRequestReviewSchema).parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPullRequestStatus(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number
|
||||||
|
): Promise<z.infer<typeof CombinedStatusSchema>> {
|
||||||
|
// First get the PR to get the head SHA
|
||||||
|
const pr = await getPullRequest(owner, repo, pullNumber);
|
||||||
|
const sha = pr.head.sha;
|
||||||
|
|
||||||
|
// Then get the combined status for that SHA
|
||||||
|
const response = await githubRequest(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/commits/${sha}/status`
|
||||||
|
);
|
||||||
|
return CombinedStatusSchema.parse(response);
|
||||||
|
}
|
||||||
65
src/github/operations/repository.ts
Normal file
65
src/github/operations/repository.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { githubRequest } from "../common/utils.js";
|
||||||
|
import { GitHubRepositorySchema, GitHubSearchResponseSchema } from "../common/types.js";
|
||||||
|
|
||||||
|
// Schema definitions
|
||||||
|
export const CreateRepositoryOptionsSchema = z.object({
|
||||||
|
name: z.string().describe("Repository name"),
|
||||||
|
description: z.string().optional().describe("Repository description"),
|
||||||
|
private: z.boolean().optional().describe("Whether the repository should be private"),
|
||||||
|
autoInit: z.boolean().optional().describe("Initialize with README.md"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchRepositoriesSchema = z.object({
|
||||||
|
query: z.string().describe("Search query (see GitHub search syntax)"),
|
||||||
|
page: z.number().optional().describe("Page number for pagination (default: 1)"),
|
||||||
|
perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ForkRepositorySchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
organization: z.string().optional().describe("Optional: organization to fork to (defaults to your personal account)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Type exports
|
||||||
|
export type CreateRepositoryOptions = z.infer<typeof CreateRepositoryOptionsSchema>;
|
||||||
|
|
||||||
|
// Function implementations
|
||||||
|
export async function createRepository(options: CreateRepositoryOptions) {
|
||||||
|
const response = await githubRequest("https://api.github.com/user/repos", {
|
||||||
|
method: "POST",
|
||||||
|
body: options,
|
||||||
|
});
|
||||||
|
return GitHubRepositorySchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchRepositories(
|
||||||
|
query: string,
|
||||||
|
page: number = 1,
|
||||||
|
perPage: number = 30
|
||||||
|
) {
|
||||||
|
const url = new URL("https://api.github.com/search/repositories");
|
||||||
|
url.searchParams.append("q", query);
|
||||||
|
url.searchParams.append("page", page.toString());
|
||||||
|
url.searchParams.append("per_page", perPage.toString());
|
||||||
|
|
||||||
|
const response = await githubRequest(url.toString());
|
||||||
|
return GitHubSearchResponseSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function forkRepository(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
organization?: string
|
||||||
|
) {
|
||||||
|
const url = organization
|
||||||
|
? `https://api.github.com/repos/${owner}/${repo}/forks?organization=${organization}`
|
||||||
|
: `https://api.github.com/repos/${owner}/${repo}/forks`;
|
||||||
|
|
||||||
|
const response = await githubRequest(url, { method: "POST" });
|
||||||
|
return GitHubRepositorySchema.extend({
|
||||||
|
parent: GitHubRepositorySchema,
|
||||||
|
source: GitHubRepositorySchema,
|
||||||
|
}).parse(response);
|
||||||
|
}
|
||||||
45
src/github/operations/search.ts
Normal file
45
src/github/operations/search.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { githubRequest, buildUrl } from "../common/utils.js";
|
||||||
|
|
||||||
|
export const SearchOptions = z.object({
|
||||||
|
q: z.string(),
|
||||||
|
order: z.enum(["asc", "desc"]).optional(),
|
||||||
|
page: z.number().min(1).optional(),
|
||||||
|
per_page: z.number().min(1).max(100).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchUsersOptions = SearchOptions.extend({
|
||||||
|
sort: z.enum(["followers", "repositories", "joined"]).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchIssuesOptions = SearchOptions.extend({
|
||||||
|
sort: z.enum([
|
||||||
|
"comments",
|
||||||
|
"reactions",
|
||||||
|
"reactions-+1",
|
||||||
|
"reactions--1",
|
||||||
|
"reactions-smile",
|
||||||
|
"reactions-thinking_face",
|
||||||
|
"reactions-heart",
|
||||||
|
"reactions-tada",
|
||||||
|
"interactions",
|
||||||
|
"created",
|
||||||
|
"updated",
|
||||||
|
]).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchCodeSchema = SearchOptions;
|
||||||
|
export const SearchUsersSchema = SearchUsersOptions;
|
||||||
|
export const SearchIssuesSchema = SearchIssuesOptions;
|
||||||
|
|
||||||
|
export async function searchCode(params: z.infer<typeof SearchCodeSchema>) {
|
||||||
|
return githubRequest(buildUrl("https://api.github.com/search/code", params));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchIssues(params: z.infer<typeof SearchIssuesSchema>) {
|
||||||
|
return githubRequest(buildUrl("https://api.github.com/search/issues", params));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchUsers(params: z.infer<typeof SearchUsersSchema>) {
|
||||||
|
return githubRequest(buildUrl("https://api.github.com/search/users", params));
|
||||||
|
}
|
||||||
@@ -1,719 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
// Base schemas for common types
|
|
||||||
export const GitHubAuthorSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
email: z.string(),
|
|
||||||
date: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Repository related schemas
|
|
||||||
export const GitHubOwnerSchema = z.object({
|
|
||||||
login: z.string(),
|
|
||||||
id: z.number(),
|
|
||||||
node_id: z.string(),
|
|
||||||
avatar_url: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubRepositorySchema = z.object({
|
|
||||||
id: z.number(),
|
|
||||||
node_id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
full_name: z.string(),
|
|
||||||
private: z.boolean(),
|
|
||||||
owner: GitHubOwnerSchema,
|
|
||||||
html_url: z.string(),
|
|
||||||
description: z.string().nullable(),
|
|
||||||
fork: z.boolean(),
|
|
||||||
url: z.string(),
|
|
||||||
created_at: z.string(),
|
|
||||||
updated_at: z.string(),
|
|
||||||
pushed_at: z.string(),
|
|
||||||
git_url: z.string(),
|
|
||||||
ssh_url: z.string(),
|
|
||||||
clone_url: z.string(),
|
|
||||||
default_branch: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// File content schemas
|
|
||||||
export const GitHubFileContentSchema = z.object({
|
|
||||||
type: z.string(),
|
|
||||||
encoding: z.string(),
|
|
||||||
size: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
path: z.string(),
|
|
||||||
content: z.string(),
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
git_url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
download_url: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubDirectoryContentSchema = z.object({
|
|
||||||
type: z.string(),
|
|
||||||
size: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
path: z.string(),
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
git_url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
download_url: z.string().nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubContentSchema = z.union([
|
|
||||||
GitHubFileContentSchema,
|
|
||||||
z.array(GitHubDirectoryContentSchema),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Operation schemas
|
|
||||||
export const FileOperationSchema = z.object({
|
|
||||||
path: z.string(),
|
|
||||||
content: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Tree and commit schemas
|
|
||||||
export const GitHubTreeEntrySchema = z.object({
|
|
||||||
path: z.string(),
|
|
||||||
mode: z.enum(["100644", "100755", "040000", "160000", "120000"]),
|
|
||||||
type: z.enum(["blob", "tree", "commit"]),
|
|
||||||
size: z.number().optional(),
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubTreeSchema = z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
tree: z.array(GitHubTreeEntrySchema),
|
|
||||||
truncated: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubListCommitsSchema = z.array(z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
node_id: z.string(),
|
|
||||||
commit: z.object({
|
|
||||||
author: GitHubAuthorSchema,
|
|
||||||
committer: GitHubAuthorSchema,
|
|
||||||
message: z.string(),
|
|
||||||
tree: z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string()
|
|
||||||
}),
|
|
||||||
url: z.string(),
|
|
||||||
comment_count: z.number(),
|
|
||||||
}),
|
|
||||||
url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
comments_url: z.string()
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const GitHubCommitSchema = z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
node_id: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
author: GitHubAuthorSchema,
|
|
||||||
committer: GitHubAuthorSchema,
|
|
||||||
message: z.string(),
|
|
||||||
tree: z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
}),
|
|
||||||
parents: z.array(
|
|
||||||
z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reference schema
|
|
||||||
export const GitHubReferenceSchema = z.object({
|
|
||||||
ref: z.string(),
|
|
||||||
node_id: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
object: z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Input schemas for operations
|
|
||||||
export const CreateRepositoryOptionsSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
private: z.boolean().optional(),
|
|
||||||
auto_init: z.boolean().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreateIssueOptionsSchema = z.object({
|
|
||||||
title: z.string(),
|
|
||||||
body: z.string().optional(),
|
|
||||||
assignees: z.array(z.string()).optional(),
|
|
||||||
milestone: z.number().optional(),
|
|
||||||
labels: z.array(z.string()).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreatePullRequestOptionsSchema = z.object({
|
|
||||||
title: z.string(),
|
|
||||||
body: z.string().optional(),
|
|
||||||
head: z.string(),
|
|
||||||
base: z.string(),
|
|
||||||
maintainer_can_modify: z.boolean().optional(),
|
|
||||||
draft: z.boolean().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreateBranchOptionsSchema = z.object({
|
|
||||||
ref: z.string(),
|
|
||||||
sha: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Response schemas for operations
|
|
||||||
export const GitHubCreateUpdateFileResponseSchema = z.object({
|
|
||||||
content: GitHubFileContentSchema.nullable(),
|
|
||||||
commit: z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
node_id: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
author: GitHubAuthorSchema,
|
|
||||||
committer: GitHubAuthorSchema,
|
|
||||||
message: z.string(),
|
|
||||||
tree: z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
}),
|
|
||||||
parents: z.array(
|
|
||||||
z.object({
|
|
||||||
sha: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubSearchResponseSchema = z.object({
|
|
||||||
total_count: z.number(),
|
|
||||||
incomplete_results: z.boolean(),
|
|
||||||
items: z.array(GitHubRepositorySchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fork related schemas
|
|
||||||
export const GitHubForkParentSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
full_name: z.string(),
|
|
||||||
owner: z.object({
|
|
||||||
login: z.string(),
|
|
||||||
id: z.number(),
|
|
||||||
avatar_url: z.string(),
|
|
||||||
}),
|
|
||||||
html_url: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubForkSchema = GitHubRepositorySchema.extend({
|
|
||||||
parent: GitHubForkParentSchema,
|
|
||||||
source: GitHubForkParentSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Issue related schemas
|
|
||||||
export const GitHubLabelSchema = z.object({
|
|
||||||
id: z.number(),
|
|
||||||
node_id: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
color: z.string(),
|
|
||||||
default: z.boolean(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubIssueAssigneeSchema = z.object({
|
|
||||||
login: z.string(),
|
|
||||||
id: z.number(),
|
|
||||||
avatar_url: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubMilestoneSchema = z.object({
|
|
||||||
url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
labels_url: z.string(),
|
|
||||||
id: z.number(),
|
|
||||||
node_id: z.string(),
|
|
||||||
number: z.number(),
|
|
||||||
title: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
state: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubIssueSchema = z.object({
|
|
||||||
url: z.string(),
|
|
||||||
repository_url: z.string(),
|
|
||||||
labels_url: z.string(),
|
|
||||||
comments_url: z.string(),
|
|
||||||
events_url: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
id: z.number(),
|
|
||||||
node_id: z.string(),
|
|
||||||
number: z.number(),
|
|
||||||
title: z.string(),
|
|
||||||
user: GitHubIssueAssigneeSchema,
|
|
||||||
labels: z.array(GitHubLabelSchema),
|
|
||||||
state: z.string(),
|
|
||||||
locked: z.boolean(),
|
|
||||||
assignee: GitHubIssueAssigneeSchema.nullable(),
|
|
||||||
assignees: z.array(GitHubIssueAssigneeSchema),
|
|
||||||
milestone: GitHubMilestoneSchema.nullable(),
|
|
||||||
comments: z.number(),
|
|
||||||
created_at: z.string(),
|
|
||||||
updated_at: z.string(),
|
|
||||||
closed_at: z.string().nullable(),
|
|
||||||
body: z.string().nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pull Request related schemas
|
|
||||||
export const GitHubPullRequestHeadSchema = z.object({
|
|
||||||
label: z.string(),
|
|
||||||
ref: z.string(),
|
|
||||||
sha: z.string(),
|
|
||||||
user: GitHubIssueAssigneeSchema,
|
|
||||||
repo: GitHubRepositorySchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubPullRequestSchema = z.object({
|
|
||||||
url: z.string(),
|
|
||||||
id: z.number(),
|
|
||||||
node_id: z.string(),
|
|
||||||
html_url: z.string(),
|
|
||||||
diff_url: z.string(),
|
|
||||||
patch_url: z.string(),
|
|
||||||
issue_url: z.string(),
|
|
||||||
number: z.number(),
|
|
||||||
state: z.string(),
|
|
||||||
locked: z.boolean(),
|
|
||||||
title: z.string(),
|
|
||||||
user: GitHubIssueAssigneeSchema,
|
|
||||||
body: z.string(),
|
|
||||||
created_at: z.string(),
|
|
||||||
updated_at: z.string(),
|
|
||||||
closed_at: z.string().nullable(),
|
|
||||||
merged_at: z.string().nullable(),
|
|
||||||
merge_commit_sha: z.string().nullable(),
|
|
||||||
assignee: GitHubIssueAssigneeSchema.nullable(),
|
|
||||||
assignees: z.array(GitHubIssueAssigneeSchema),
|
|
||||||
head: GitHubPullRequestHeadSchema,
|
|
||||||
base: GitHubPullRequestHeadSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
const RepoParamsSchema = z.object({
|
|
||||||
owner: z.string().describe("Repository owner (username or organization)"),
|
|
||||||
repo: z.string().describe("Repository name"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({
|
|
||||||
path: z.string().describe("Path where to create/update the file"),
|
|
||||||
content: z.string().describe("Content of the file"),
|
|
||||||
message: z.string().describe("Commit message"),
|
|
||||||
branch: z.string().describe("Branch to create/update the file in"),
|
|
||||||
sha: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
"SHA of the file being replaced (required when updating existing files)"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SearchRepositoriesSchema = z.object({
|
|
||||||
query: z.string().describe("Search query (see GitHub search syntax)"),
|
|
||||||
page: z
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Page number for pagination (default: 1)"),
|
|
||||||
perPage: z
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Number of results per page (default: 30, max: 100)"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ListCommitsSchema = z.object({
|
|
||||||
owner: z.string().describe("Repository owner (username or organization)"),
|
|
||||||
repo: z.string().describe("Repository name"),
|
|
||||||
page: z.number().optional().describe("Page number for pagination (default: 1)"),
|
|
||||||
perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)"),
|
|
||||||
sha: z.string().optional()
|
|
||||||
.describe("SHA of the file being replaced (required when updating existing files)")
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreateRepositorySchema = z.object({
|
|
||||||
name: z.string().describe("Repository name"),
|
|
||||||
description: z.string().optional().describe("Repository description"),
|
|
||||||
private: z
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Whether the repository should be private"),
|
|
||||||
autoInit: z.boolean().optional().describe("Initialize with README.md"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GetFileContentsSchema = RepoParamsSchema.extend({
|
|
||||||
path: z.string().describe("Path to the file or directory"),
|
|
||||||
branch: z.string().optional().describe("Branch to get contents from"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const PushFilesSchema = RepoParamsSchema.extend({
|
|
||||||
branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"),
|
|
||||||
files: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
path: z.string().describe("Path where to create the file"),
|
|
||||||
content: z.string().describe("Content of the file"),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.describe("Array of files to push"),
|
|
||||||
message: z.string().describe("Commit message"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreateIssueSchema = RepoParamsSchema.extend({
|
|
||||||
title: z.string().describe("Issue title"),
|
|
||||||
body: z.string().optional().describe("Issue body/description"),
|
|
||||||
assignees: z
|
|
||||||
.array(z.string())
|
|
||||||
.optional()
|
|
||||||
.describe("Array of usernames to assign"),
|
|
||||||
labels: z.array(z.string()).optional().describe("Array of label names"),
|
|
||||||
milestone: z.number().optional().describe("Milestone number to assign"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreatePullRequestSchema = RepoParamsSchema.extend({
|
|
||||||
title: z.string().describe("Pull request title"),
|
|
||||||
body: z.string().optional().describe("Pull request body/description"),
|
|
||||||
head: z
|
|
||||||
.string()
|
|
||||||
.describe("The name of the branch where your changes are implemented"),
|
|
||||||
base: z
|
|
||||||
.string()
|
|
||||||
.describe("The name of the branch you want the changes pulled into"),
|
|
||||||
draft: z
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Whether to create the pull request as a draft"),
|
|
||||||
maintainer_can_modify: z
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Whether maintainers can modify the pull request"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ForkRepositorySchema = RepoParamsSchema.extend({
|
|
||||||
organization: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
"Optional: organization to fork to (defaults to your personal account)"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreateBranchSchema = RepoParamsSchema.extend({
|
|
||||||
branch: z.string().describe("Name for the new branch"),
|
|
||||||
from_branch: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
"Optional: source branch to create from (defaults to the repository's default branch)"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response schema for a code search result item
|
|
||||||
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-code
|
|
||||||
*/
|
|
||||||
export const SearchCodeItemSchema = z.object({
|
|
||||||
name: z.string().describe("The name of the file"),
|
|
||||||
path: z.string().describe("The path to the file in the repository"),
|
|
||||||
sha: z.string().describe("The SHA hash of the file"),
|
|
||||||
url: z.string().describe("The API URL for this file"),
|
|
||||||
git_url: z.string().describe("The Git URL for this file"),
|
|
||||||
html_url: z.string().describe("The HTML URL to view this file on GitHub"),
|
|
||||||
repository: GitHubRepositorySchema.describe(
|
|
||||||
"The repository where this file was found"
|
|
||||||
),
|
|
||||||
score: z.number().describe("The search result score"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response schema for code search results
|
|
||||||
*/
|
|
||||||
export const SearchCodeResponseSchema = z.object({
|
|
||||||
total_count: z.number().describe("Total number of matching results"),
|
|
||||||
incomplete_results: z
|
|
||||||
.boolean()
|
|
||||||
.describe("Whether the results are incomplete"),
|
|
||||||
items: z.array(SearchCodeItemSchema).describe("The search results"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response schema for an issue search result item
|
|
||||||
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests
|
|
||||||
*/
|
|
||||||
export const SearchIssueItemSchema = z.object({
|
|
||||||
url: z.string().describe("The API URL for this issue"),
|
|
||||||
repository_url: z
|
|
||||||
.string()
|
|
||||||
.describe("The API URL for the repository where this issue was found"),
|
|
||||||
labels_url: z.string().describe("The API URL for the labels of this issue"),
|
|
||||||
comments_url: z.string().describe("The API URL for comments of this issue"),
|
|
||||||
events_url: z.string().describe("The API URL for events of this issue"),
|
|
||||||
html_url: z.string().describe("The HTML URL to view this issue on GitHub"),
|
|
||||||
id: z.number().describe("The ID of this issue"),
|
|
||||||
node_id: z.string().describe("The Node ID of this issue"),
|
|
||||||
number: z.number().describe("The number of this issue"),
|
|
||||||
title: z.string().describe("The title of this issue"),
|
|
||||||
user: GitHubIssueAssigneeSchema.describe("The user who created this issue"),
|
|
||||||
labels: z.array(GitHubLabelSchema).describe("The labels of this issue"),
|
|
||||||
state: z.string().describe("The state of this issue"),
|
|
||||||
locked: z.boolean().describe("Whether this issue is locked"),
|
|
||||||
assignee: GitHubIssueAssigneeSchema.nullable().describe(
|
|
||||||
"The assignee of this issue"
|
|
||||||
),
|
|
||||||
assignees: z
|
|
||||||
.array(GitHubIssueAssigneeSchema)
|
|
||||||
.describe("The assignees of this issue"),
|
|
||||||
comments: z.number().describe("The number of comments on this issue"),
|
|
||||||
created_at: z.string().describe("The creation time of this issue"),
|
|
||||||
updated_at: z.string().describe("The last update time of this issue"),
|
|
||||||
closed_at: z.string().nullable().describe("The closure time of this issue"),
|
|
||||||
body: z.string().describe("The body of this issue"),
|
|
||||||
score: z.number().describe("The search result score"),
|
|
||||||
pull_request: z
|
|
||||||
.object({
|
|
||||||
url: z.string().describe("The API URL for this pull request"),
|
|
||||||
html_url: z.string().describe("The HTML URL to view this pull request"),
|
|
||||||
diff_url: z.string().describe("The URL to view the diff"),
|
|
||||||
patch_url: z.string().describe("The URL to view the patch"),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe("Pull request details if this is a PR"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response schema for issue search results
|
|
||||||
*/
|
|
||||||
export const SearchIssuesResponseSchema = z.object({
|
|
||||||
total_count: z.number().describe("Total number of matching results"),
|
|
||||||
incomplete_results: z
|
|
||||||
.boolean()
|
|
||||||
.describe("Whether the results are incomplete"),
|
|
||||||
items: z.array(SearchIssueItemSchema).describe("The search results"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response schema for a user search result item
|
|
||||||
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-users
|
|
||||||
*/
|
|
||||||
export const SearchUserItemSchema = z.object({
|
|
||||||
login: z.string().describe("The username of the user"),
|
|
||||||
id: z.number().describe("The ID of the user"),
|
|
||||||
node_id: z.string().describe("The Node ID of the user"),
|
|
||||||
avatar_url: z.string().describe("The avatar URL of the user"),
|
|
||||||
gravatar_id: z.string().describe("The Gravatar ID of the user"),
|
|
||||||
url: z.string().describe("The API URL for this user"),
|
|
||||||
html_url: z.string().describe("The HTML URL to view this user on GitHub"),
|
|
||||||
followers_url: z.string().describe("The API URL for followers of this user"),
|
|
||||||
following_url: z.string().describe("The API URL for following of this user"),
|
|
||||||
gists_url: z.string().describe("The API URL for gists of this user"),
|
|
||||||
starred_url: z
|
|
||||||
.string()
|
|
||||||
.describe("The API URL for starred repositories of this user"),
|
|
||||||
subscriptions_url: z
|
|
||||||
.string()
|
|
||||||
.describe("The API URL for subscriptions of this user"),
|
|
||||||
organizations_url: z
|
|
||||||
.string()
|
|
||||||
.describe("The API URL for organizations of this user"),
|
|
||||||
repos_url: z.string().describe("The API URL for repositories of this user"),
|
|
||||||
events_url: z.string().describe("The API URL for events of this user"),
|
|
||||||
received_events_url: z
|
|
||||||
.string()
|
|
||||||
.describe("The API URL for received events of this user"),
|
|
||||||
type: z.string().describe("The type of this user"),
|
|
||||||
site_admin: z.boolean().describe("Whether this user is a site administrator"),
|
|
||||||
score: z.number().describe("The search result score"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response schema for user search results
|
|
||||||
*/
|
|
||||||
export const SearchUsersResponseSchema = z.object({
|
|
||||||
total_count: z.number().describe("Total number of matching results"),
|
|
||||||
incomplete_results: z
|
|
||||||
.boolean()
|
|
||||||
.describe("Whether the results are incomplete"),
|
|
||||||
items: z.array(SearchUserItemSchema).describe("The search results"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input schema for code search
|
|
||||||
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-code--parameters
|
|
||||||
*/
|
|
||||||
export const SearchCodeSchema = z.object({
|
|
||||||
q: z
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"Search query. See GitHub code search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-code"
|
|
||||||
),
|
|
||||||
order: z
|
|
||||||
.enum(["asc", "desc"])
|
|
||||||
.optional()
|
|
||||||
.describe("Sort order (asc or desc)"),
|
|
||||||
per_page: z
|
|
||||||
.number()
|
|
||||||
.min(1)
|
|
||||||
.max(100)
|
|
||||||
.optional()
|
|
||||||
.describe("Results per page (max 100)"),
|
|
||||||
page: z.number().min(1).optional().describe("Page number"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input schema for issues search
|
|
||||||
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests--parameters
|
|
||||||
*/
|
|
||||||
export const SearchIssuesSchema = z.object({
|
|
||||||
q: z
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"Search query. See GitHub issues search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests"
|
|
||||||
),
|
|
||||||
sort: z
|
|
||||||
.enum([
|
|
||||||
"comments",
|
|
||||||
"reactions",
|
|
||||||
"reactions-+1",
|
|
||||||
"reactions--1",
|
|
||||||
"reactions-smile",
|
|
||||||
"reactions-thinking_face",
|
|
||||||
"reactions-heart",
|
|
||||||
"reactions-tada",
|
|
||||||
"interactions",
|
|
||||||
"created",
|
|
||||||
"updated",
|
|
||||||
])
|
|
||||||
.optional()
|
|
||||||
.describe("Sort field"),
|
|
||||||
order: z
|
|
||||||
.enum(["asc", "desc"])
|
|
||||||
.optional()
|
|
||||||
.describe("Sort order (asc or desc)"),
|
|
||||||
per_page: z
|
|
||||||
.number()
|
|
||||||
.min(1)
|
|
||||||
.max(100)
|
|
||||||
.optional()
|
|
||||||
.describe("Results per page (max 100)"),
|
|
||||||
page: z.number().min(1).optional().describe("Page number"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input schema for users search
|
|
||||||
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-users--parameters
|
|
||||||
*/
|
|
||||||
export const SearchUsersSchema = z.object({
|
|
||||||
q: z
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"Search query. See GitHub users search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-users"
|
|
||||||
),
|
|
||||||
sort: z
|
|
||||||
.enum(["followers", "repositories", "joined"])
|
|
||||||
.optional()
|
|
||||||
.describe("Sort field"),
|
|
||||||
order: z
|
|
||||||
.enum(["asc", "desc"])
|
|
||||||
.optional()
|
|
||||||
.describe("Sort order (asc or desc)"),
|
|
||||||
per_page: z
|
|
||||||
.number()
|
|
||||||
.min(1)
|
|
||||||
.max(100)
|
|
||||||
.optional()
|
|
||||||
.describe("Results per page (max 100)"),
|
|
||||||
page: z.number().min(1).optional().describe("Page number"),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add these schema definitions for issue management
|
|
||||||
|
|
||||||
export const ListIssuesOptionsSchema = z.object({
|
|
||||||
owner: z.string(),
|
|
||||||
repo: z.string(),
|
|
||||||
state: z.enum(['open', 'closed', 'all']).optional(),
|
|
||||||
labels: z.array(z.string()).optional(),
|
|
||||||
sort: z.enum(['created', 'updated', 'comments']).optional(),
|
|
||||||
direction: z.enum(['asc', 'desc']).optional(),
|
|
||||||
since: z.string().optional(), // ISO 8601 timestamp
|
|
||||||
page: z.number().optional(),
|
|
||||||
per_page: z.number().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UpdateIssueOptionsSchema = z.object({
|
|
||||||
owner: z.string(),
|
|
||||||
repo: z.string(),
|
|
||||||
issue_number: z.number(),
|
|
||||||
title: z.string().optional(),
|
|
||||||
body: z.string().optional(),
|
|
||||||
state: z.enum(['open', 'closed']).optional(),
|
|
||||||
labels: z.array(z.string()).optional(),
|
|
||||||
assignees: z.array(z.string()).optional(),
|
|
||||||
milestone: z.number().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const IssueCommentSchema = z.object({
|
|
||||||
owner: z.string(),
|
|
||||||
repo: z.string(),
|
|
||||||
issue_number: z.number(),
|
|
||||||
body: z.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GetIssueSchema = z.object({
|
|
||||||
owner: z.string().describe("Repository owner (username or organization)"),
|
|
||||||
repo: z.string().describe("Repository name"),
|
|
||||||
issue_number: z.number().describe("Issue number")
|
|
||||||
});
|
|
||||||
|
|
||||||
// Export types
|
|
||||||
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
|
|
||||||
export type GitHubFork = z.infer<typeof GitHubForkSchema>;
|
|
||||||
export type GitHubIssue = z.infer<typeof GitHubIssueSchema>;
|
|
||||||
export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>;
|
|
||||||
export type GitHubRepository = z.infer<typeof GitHubRepositorySchema>;
|
|
||||||
export type GitHubFileContent = z.infer<typeof GitHubFileContentSchema>;
|
|
||||||
export type GitHubDirectoryContent = z.infer<
|
|
||||||
typeof GitHubDirectoryContentSchema
|
|
||||||
>;
|
|
||||||
export type GitHubContent = z.infer<typeof GitHubContentSchema>;
|
|
||||||
export type FileOperation = z.infer<typeof FileOperationSchema>;
|
|
||||||
export type GitHubTree = z.infer<typeof GitHubTreeSchema>;
|
|
||||||
export type GitHubCommit = z.infer<typeof GitHubCommitSchema>;
|
|
||||||
export type GitHubListCommits = z.infer<typeof GitHubListCommitsSchema>;
|
|
||||||
export type GitHubReference = z.infer<typeof GitHubReferenceSchema>;
|
|
||||||
export type CreateRepositoryOptions = z.infer<
|
|
||||||
typeof CreateRepositoryOptionsSchema
|
|
||||||
>;
|
|
||||||
export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>;
|
|
||||||
export type CreatePullRequestOptions = z.infer<
|
|
||||||
typeof CreatePullRequestOptionsSchema
|
|
||||||
>;
|
|
||||||
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
|
|
||||||
export type GitHubCreateUpdateFileResponse = z.infer<
|
|
||||||
typeof GitHubCreateUpdateFileResponseSchema
|
|
||||||
>;
|
|
||||||
export type GitHubSearchResponse = z.infer<typeof GitHubSearchResponseSchema>;
|
|
||||||
export type SearchCodeItem = z.infer<typeof SearchCodeItemSchema>;
|
|
||||||
export type SearchCodeResponse = z.infer<typeof SearchCodeResponseSchema>;
|
|
||||||
export type SearchIssueItem = z.infer<typeof SearchIssueItemSchema>;
|
|
||||||
export type SearchIssuesResponse = z.infer<typeof SearchIssuesResponseSchema>;
|
|
||||||
export type SearchUserItem = z.infer<typeof SearchUserItemSchema>;
|
|
||||||
export type SearchUsersResponse = z.infer<typeof SearchUsersResponseSchema>;
|
|
||||||
Reference in New Issue
Block a user