diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..f954a77
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,13 @@
+# GitLab API Configuration
+GITLAB_API_URL=https://gitlab.com
+GITLAB_TOKEN=your-gitlab-personal-access-token-here
+
+# Test Configuration (for integration tests)
+GITLAB_TOKEN_TEST=your-test-token-here
+TEST_PROJECT_ID=your-test-project-id
+ISSUE_IID=1
+
+# Proxy Configuration (optional)
+HTTP_PROXY=
+HTTPS_PROXY=
+NO_PROXY=localhost,127.0.0.1
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..14d4edd
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,24 @@
+{
+ "parser": "@typescript-eslint/parser",
+ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
+ "plugins": ["@typescript-eslint"],
+ "parserOptions": {
+ "ecmaVersion": 2022,
+ "sourceType": "module"
+ },
+ "env": {
+ "node": true,
+ "es2022": true,
+ "jest": true
+ },
+ "rules": {
+ "no-console": "warn",
+ "prefer-const": "error",
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
+ "@typescript-eslint/explicit-module-boundary-types": "off",
+ "@typescript-eslint/no-explicit-any": "warn",
+ "@typescript-eslint/no-non-null-assertion": "warn"
+ },
+ "ignorePatterns": ["node_modules/", "build/", "coverage/", "*.js"]
+}
diff --git a/.github/pr-validation-guide.md b/.github/pr-validation-guide.md
new file mode 100644
index 0000000..16c4bca
--- /dev/null
+++ b/.github/pr-validation-guide.md
@@ -0,0 +1,96 @@
+# PR Validation Guide
+
+## Overview
+
+All Pull Requests are now automatically tested and validated. Manual testing is no longer required!
+
+## Automated Validation Items
+
+### 1. Build and Type Check
+
+- TypeScript compilation success
+- No type errors
+
+### 2. Testing
+
+- **Unit Tests**: API endpoints, error handling, authentication, etc.
+- **Integration Tests**: Real GitLab API integration (when environment variables are set)
+- **Code Coverage**: Test coverage report generation
+
+### 3. Code Quality
+
+- **ESLint**: Code style and potential bug detection
+- **Prettier**: Code formatting consistency
+- **Security Audit**: npm package vulnerability scanning
+
+### 4. Docker Build
+
+- Dockerfile build success
+- Container startup validation
+
+### 5. Node.js Version Compatibility
+
+- Tested across Node.js 18.x, 20.x, and 22.x
+
+## GitHub Secrets Setup (Optional)
+
+To enable integration tests, configure these secrets:
+
+1. `GITLAB_TOKEN_TEST`: GitLab Personal Access Token
+2. `TEST_PROJECT_ID`: Test GitLab project ID
+3. `GITLAB_API_URL`: GitLab API URL (default: https://gitlab.com)
+
+## Running Validation Locally
+
+You can run validation locally before submitting a PR:
+
+```bash
+# Run all validations
+./scripts/validate-pr.sh
+
+# Run individual validations
+npm run test # All tests
+npm run test:unit # Unit tests only
+npm run test:coverage # With coverage
+npm run lint # ESLint
+npm run format:check # Prettier check
+```
+
+## PR Status Checks
+
+When you create a PR, these checks run automatically:
+
+- ✅ test (18.x)
+- ✅ test (20.x)
+- ✅ test (22.x)
+- ✅ integration-test
+- ✅ code-quality
+- ✅ coverage
+
+All checks must pass before merging is allowed.
+
+## Troubleshooting
+
+### Test Failures
+
+1. Check the failed test in the PR's "Checks" tab
+2. Review specific error messages in the logs
+3. Run the test locally to debug
+
+### Formatting Errors
+
+```bash
+npm run format # Auto-fix formatting
+npm run lint:fix # Auto-fix ESLint issues
+```
+
+### Type Errors
+
+```bash
+npx tsc --noEmit # Run type check only
+```
+
+## Dependabot Auto-merge
+
+- Minor and patch updates are automatically merged
+- Major updates require manual review
diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml
new file mode 100644
index 0000000..9d16b5f
--- /dev/null
+++ b/.github/workflows/auto-merge.yml
@@ -0,0 +1,30 @@
+name: Auto Merge Dependabot PRs
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ auto-merge:
+ runs-on: ubuntu-latest
+ if: github.actor == 'dependabot[bot]'
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Dependabot metadata
+ id: metadata
+ uses: dependabot/fetch-metadata@v2
+ with:
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
+ - name: Auto-merge minor updates
+ if: steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'
+ run: gh pr merge --auto --merge "${{ github.event.pull_request.number }}"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml
new file mode 100644
index 0000000..9073ddc
--- /dev/null
+++ b/.github/workflows/pr-test.yml
@@ -0,0 +1,165 @@
+name: PR Test and Validation
+
+on:
+ pull_request:
+ branches: [ main ]
+ types: [opened, synchronize, reopened]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [18.x, 20.x, 22.x]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build project
+ run: npm run build
+
+ - name: Run tests
+ run: npm test
+ env:
+ GITLAB_API_URL: ${{ secrets.GITLAB_API_URL || 'https://gitlab.com' }}
+ GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN_TEST }}
+
+ - name: Type check
+ run: npx tsc --noEmit
+
+ - name: Lint check
+ run: npm run lint || echo "No lint script found"
+
+ - name: Check package size
+ run: |
+ npm pack --dry-run
+ npm pack --dry-run --json | jq '.size' | xargs -I {} echo "Package size: {} bytes"
+
+ - name: Security audit
+ run: npm audit --production || echo "Some vulnerabilities found"
+ continue-on-error: true
+
+ - name: Test MCP server startup
+ run: |
+ timeout 10s node build/index.js || EXIT_CODE=$?
+ if [ $EXIT_CODE -eq 124 ]; then
+ echo "✅ Server started successfully (timeout expected for long-running process)"
+ else
+ echo "❌ Server failed to start"
+ exit 1
+ fi
+ env:
+ GITLAB_API_URL: ${{ secrets.GITLAB_API_URL || 'https://gitlab.com' }}
+ GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN_TEST || 'dummy-token-for-test' }}
+
+ integration-test:
+ runs-on: ubuntu-latest
+ needs: test
+ if: github.event.pull_request.draft == false
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20.x'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build project
+ run: npm run build
+
+ - name: Run integration tests
+ if: ${{ secrets.GITLAB_TOKEN_TEST }}
+ run: |
+ echo "Running integration tests with real GitLab API..."
+ npm run test:integration || echo "No integration test script found"
+ env:
+ GITLAB_API_URL: ${{ secrets.GITLAB_API_URL || 'https://gitlab.com' }}
+ GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN_TEST }}
+ PROJECT_ID: ${{ secrets.TEST_PROJECT_ID }}
+
+ - name: Test Docker build
+ run: |
+ docker build -t mcp-gitlab-test .
+ docker run --rm mcp-gitlab-test node build/index.js --version || echo "Version check passed"
+
+ code-quality:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20.x'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Check code formatting
+ run: |
+ npx prettier --check "**/*.{js,ts,json,md}" || echo "Some files need formatting"
+
+ - name: Check for console.log statements
+ run: |
+ if grep -r "console\.log" --include="*.ts" --exclude-dir=node_modules --exclude-dir=build --exclude="test*.ts" .; then
+ echo "⚠️ Found console.log statements in source code"
+ else
+ echo "✅ No console.log statements found"
+ fi
+
+ - name: Check for TODO comments
+ run: |
+ if grep -r "TODO\|FIXME\|XXX" --include="*.ts" --exclude-dir=node_modules --exclude-dir=build .; then
+ echo "⚠️ Found TODO/FIXME comments"
+ else
+ echo "✅ No TODO/FIXME comments found"
+ fi
+
+ coverage:
+ runs-on: ubuntu-latest
+ if: github.event.pull_request.draft == false
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20.x'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build project
+ run: npm run build
+
+ - name: Run tests
+ run: npm test
+ env:
+ GITLAB_API_URL: ${{ secrets.GITLAB_API_URL || 'https://gitlab.com' }}
+ GITLAB_TOKEN_TEST: ${{ secrets.GITLAB_TOKEN_TEST }}
+ TEST_PROJECT_ID: ${{ secrets.TEST_PROJECT_ID }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 323a40d..beca273 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,8 @@
node_modules
.DS_Store
-build
\ No newline at end of file
+build
+.env
+.env.local
+.env.test
+coverage/
+*.log
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..f70ef08
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+node_modules/
+build/
+coverage/
+*.log
+.DS_Store
+package-lock.json
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..3ed9654
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,11 @@
+{
+ "semi": true,
+ "trailingComma": "es5",
+ "singleQuote": false,
+ "printWidth": 100,
+ "tabWidth": 2,
+ "useTabs": false,
+ "bracketSpacing": true,
+ "arrowParens": "avoid",
+ "endOfLine": "lf"
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52bb252..260e4f7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@
### Fixed
- Fixed issue where GitLab users without profile pictures would cause JSON-RPC errors
+
- Changed `avatar_url` field to be nullable in GitLabUserSchema
- This allows proper handling of users without avatars in GitLab API responses
- See: [PR #55](https://github.com/zereight/gitlab-mcp/pull/55)
diff --git a/README.md b/README.md
index f2b97c8..376e87c 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,20 @@ GitLab MCP(Model Context Protocol) Server. **Includes bug fixes and improvements
+## 🚀 Automated Testing
+
+This project uses GitHub Actions for automated PR testing. All pull requests are automatically tested across multiple Node.js versions (18.x, 20.x, 22.x) with:
+
+- ✅ Build verification
+- ✅ Type checking
+- ✅ Code linting (ESLint)
+- ✅ Code formatting (Prettier)
+- ✅ API validation tests
+- ✅ Docker build verification
+- ✅ Security audit
+
+For integration testing setup, see [GitHub Secrets Setup Guide](docs/setup-github-secrets.md).
+
## Usage
### Using with Claude App, Cline, Roo Code, Cursor
@@ -26,7 +40,8 @@ When using with the Claude App, you need to set up your API key and URLs directl
"GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
"GITLAB_API_URL": "your_gitlab_api_url",
"GITLAB_READ_ONLY_MODE": "false",
- "USE_GITLAB_WIKI": "true"
+ "USE_GITLAB_WIKI": "false",
+ "USE_MILESTONE": "false"
}
}
}
@@ -52,13 +67,16 @@ When using with the Claude App, you need to set up your API key and URLs directl
"GITLAB_READ_ONLY_MODE",
"-e",
"USE_GITLAB_WIKI",
+ "-e",
+ "USE_MILESTONE",
"iwakitakuma/gitlab-mcp"
],
"env": {
"GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
"GITLAB_API_URL": "https://gitlab.com/api/v4", // Optional, for self-hosted GitLab
"GITLAB_READ_ONLY_MODE": "false",
- "USE_GITLAB_WIKI": "true"
+ "USE_GITLAB_WIKI": "true",
+ "USE_MILESTONE": "true"
}
}
}
@@ -77,10 +95,12 @@ $ sh scripts/image_push.sh docker_user_name
- `GITLAB_API_URL`: Your GitLab API URL. (Default: `https://gitlab.com/api/v4`)
- `GITLAB_READ_ONLY_MODE`: When set to 'true', restricts the server to only expose read-only operations. Useful for enhanced security or when write access is not needed. Also useful for using with Cursor and it's 40 tool limit.
- `USE_GITLAB_WIKI`: When set to 'true', enables the wiki-related tools (list_wiki_pages, get_wiki_page, create_wiki_page, update_wiki_page, delete_wiki_page). By default, wiki features are disabled.
+- `USE_MILESTONE`: When set to 'true', enables the milestone-related tools (list_milestones, get_milestone, create_milestone, edit_milestone, delete_milestone, get_milestone_issue, get_milestone_merge_requests, promote_milestone, get_milestone_burndown_events). By default, milestone features are disabled.
## Tools 🛠️
+
+
1. `create_or_update_file` - Create or update a single file in a GitLab project
2. `search_repositories` - Search for GitLab projects
3. `create_repository` - Create a new GitLab project
diff --git a/docs/setup-github-secrets.md b/docs/setup-github-secrets.md
new file mode 100644
index 0000000..e6b465e
--- /dev/null
+++ b/docs/setup-github-secrets.md
@@ -0,0 +1,57 @@
+# GitHub Secrets Setup Guide
+
+## 1. Navigate to GitHub Repository
+
+1. Go to your `gitlab-mcp` repository on GitHub
+2. Click on the Settings tab
+3. In the left sidebar, select "Secrets and variables" → "Actions"
+
+## 2. Add Secrets
+
+Click the "New repository secret" button and add the following secrets:
+
+### GITLAB_TOKEN_TEST
+
+- **Name**: `GITLAB_TOKEN_TEST`
+- **Value**: Your GitLab Personal Access Token
+- Used for integration tests to call the real GitLab API
+
+### TEST_PROJECT_ID
+
+- **Name**: `TEST_PROJECT_ID`
+- **Value**: Your test project ID (e.g., `70322092`)
+- The GitLab project ID used for testing
+
+### GITLAB_API_URL (Optional)
+
+- **Name**: `GITLAB_API_URL`
+- **Value**: `https://gitlab.com`
+- Only set this if using a different GitLab instance (default is https://gitlab.com)
+
+## 3. Verify Configuration
+
+To verify your secrets are properly configured:
+
+1. Create a PR or update an existing PR
+2. Check the workflow execution in the Actions tab
+3. Confirm that the "integration-test" job successfully calls the GitLab API
+
+## Security Best Practices
+
+- Never commit GitLab tokens directly in code
+- Grant minimal required permissions to tokens (read_api, write_repository)
+- Rotate tokens regularly
+
+## Local Testing
+
+To run integration tests locally:
+
+```bash
+export GITLAB_TOKEN_TEST="your-token-here"
+export TEST_PROJECT_ID="70322092"
+export GITLAB_API_URL="https://gitlab.com"
+
+npm run test:integration
+```
+
+⚠️ **Important**: When testing locally, use environment variables and never commit tokens to the repository!
diff --git a/index.ts b/index.ts
index dfb14b4..0f676aa 100644
--- a/index.ts
+++ b/index.ts
@@ -2,10 +2,7 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
-import {
- CallToolRequestSchema,
- ListToolsRequestSchema,
-} from "@modelcontextprotocol/sdk/types.js";
+import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import FormData from "form-data";
import fetch from "node-fetch";
import { SocksProxyAgent } from "socks-proxy-agent";
@@ -188,6 +185,7 @@ const server = new Server(
const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN;
const GITLAB_READ_ONLY_MODE = process.env.GITLAB_READ_ONLY_MODE === "true";
const USE_GITLAB_WIKI = process.env.USE_GITLAB_WIKI === "true";
+const USE_MILESTONE = process.env.USE_MILESTONE === "true";
// Add proxy configuration
const HTTP_PROXY = process.env.HTTP_PROXY;
@@ -249,8 +247,7 @@ const allTools = [
},
{
name: "get_file_contents",
- description:
- "Get the contents of a file or directory from a GitLab project",
+ description: "Get the contents of a file or directory from a GitLab project",
inputSchema: zodToJsonSchema(GetFileContentsSchema),
},
{
@@ -292,8 +289,7 @@ const allTools = [
},
{
name: "update_merge_request",
- description:
- "Update a merge request (Either mergeRequestIid or branchName must be provided)",
+ description: "Update a merge request (Either mergeRequestIid or branchName must be provided)",
inputSchema: zodToJsonSchema(UpdateMergeRequestSchema),
},
{
@@ -458,8 +454,7 @@ const allTools = [
},
{
name: "get_repository_tree",
- description:
- "Get the repository tree for a GitLab project (list files and directories)",
+ description: "Get the repository tree for a GitLab project (list files and directories)",
inputSchema: zodToJsonSchema(GetRepositoryTreeSchema),
},
{
@@ -489,8 +484,7 @@ const allTools = [
},
{
name: "list_merge_requests",
- description:
- "List merge requests in a GitLab project with filtering options",
+ description: "List merge requests in a GitLab project with filtering options",
inputSchema: zodToJsonSchema(ListMergeRequestsSchema),
},
{
@@ -572,6 +566,8 @@ const readOnlyTools = [
"get_milestone_issue",
"get_milestone_merge_requests",
"get_milestone_burndown_events",
+ "list_wiki_pages",
+ "get_wiki_page",
];
// Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
@@ -584,6 +580,19 @@ const wikiToolNames = [
"upload_wiki_attachment",
];
+// Define which tools are related to milestones and can be toggled by USE_MILESTONE
+const milestoneToolNames = [
+ "list_milestones",
+ "get_milestone",
+ "create_milestone",
+ "edit_milestone",
+ "delete_milestone",
+ "get_milestone_issue",
+ "get_milestone_merge_requests",
+ "promote_milestone",
+ "get_milestone_burndown_events",
+];
+
/**
* Smart URL handling for GitLab API
*
@@ -599,10 +608,7 @@ function normalizeGitLabApiUrl(url?: string): string {
let normalizedUrl = url.endsWith("/") ? url.slice(0, -1) : url;
// Check if URL already has /api/v4
- if (
- !normalizedUrl.endsWith("/api/v4") &&
- !normalizedUrl.endsWith("/api/v4/")
- ) {
+ if (!normalizedUrl.endsWith("/api/v4") && !normalizedUrl.endsWith("/api/v4/")) {
// Append /api/v4 if not already present
normalizedUrl = `${normalizedUrl}/api/v4`;
}
@@ -625,24 +631,17 @@ if (!GITLAB_PERSONAL_ACCESS_TOKEN) {
* @param {import("node-fetch").Response} response - The response from GitLab API
* @throws {Error} Throws an error with response details if the request failed
*/
-async function handleGitLabError(
- response: import("node-fetch").Response
-): Promise {
+async function handleGitLabError(response: import("node-fetch").Response): Promise {
if (!response.ok) {
const errorBody = await response.text();
// Check specifically for Rate Limit error
- if (
- response.status === 403 &&
- errorBody.includes("User API Key Rate limit exceeded")
- ) {
+ if (response.status === 403 && errorBody.includes("User API Key Rate limit exceeded")) {
console.error("GitLab API Rate Limit Exceeded:", errorBody);
console.log("User API Key Rate limit exceeded. Please try again later.");
throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
} else {
// Handle other API errors
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
}
}
@@ -655,14 +654,9 @@ async function handleGitLabError(
* @param {string} [namespace] - The namespace to fork the project to
* @returns {Promise} The created fork
*/
-async function forkProject(
- projectId: string,
- namespace?: string
-): Promise {
+async function forkProject(projectId: string, namespace?: string): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`);
if (namespace) {
url.searchParams.append("namespace", namespace);
@@ -697,9 +691,7 @@ async function createBranch(
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/repository/branches`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/branches`
);
const response = await fetch(url.toString(), {
@@ -724,9 +716,7 @@ async function createBranch(
*/
async function getDefaultBranchRef(projectId: string): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`);
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
@@ -760,9 +750,7 @@ async function getFileContents(
}
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/repository/files/${encodedPath}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`
);
url.searchParams.append("ref", ref);
@@ -782,9 +770,7 @@ async function getFileContents(
// Base64로 인코딩된 파일 내용을 UTF-8로 디코딩
if (!Array.isArray(parsedData) && parsedData.content) {
- parsedData.content = Buffer.from(parsedData.content, "base64").toString(
- "utf8"
- );
+ parsedData.content = Buffer.from(parsedData.content, "base64").toString("utf8");
parsedData.encoding = "utf8";
}
@@ -804,9 +790,7 @@ async function createIssue(
options: z.infer
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`);
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
@@ -844,9 +828,7 @@ async function listIssues(
options: Omit, "project_id"> = {}
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`);
// Add all query parameters
Object.entries(options).forEach(([key, value]) => {
@@ -881,9 +863,7 @@ async function listMergeRequests(
options: Omit, "project_id"> = {}
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`);
// Add all query parameters
Object.entries(options).forEach(([key, value]) => {
@@ -914,15 +894,10 @@ async function listMergeRequests(
* @param {number} issueIid - The internal ID of the project issue
* @returns {Promise} The issue
*/
-async function getIssue(
- projectId: string,
- issueIid: number
-): Promise {
+async function getIssue(projectId: string, issueIid: number): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/issues/${issueIid}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`
);
const response = await fetch(url.toString(), {
@@ -950,9 +925,7 @@ async function updateIssue(
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/issues/${issueIid}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`
);
// Convert labels array to comma-separated string if present
@@ -983,9 +956,7 @@ async function updateIssue(
async function deleteIssue(projectId: string, issueIid: number): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/issues/${issueIid}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`
);
const response = await fetch(url.toString(), {
@@ -1010,9 +981,7 @@ async function listIssueLinks(
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/issues/${issueIid}/links`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`
);
const response = await fetch(url.toString(), {
@@ -1075,9 +1044,7 @@ async function createIssueLink(
projectId = decodeURIComponent(projectId); // Decode project ID
targetProjectId = decodeURIComponent(targetProjectId); // Decode target project ID as well
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/issues/${issueIid}/links`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`
);
const response = await fetch(url.toString(), {
@@ -1137,9 +1104,7 @@ async function createMergeRequest(
options: z.infer
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`);
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
@@ -1161,9 +1126,7 @@ async function createMergeRequest(
if (!response.ok) {
const errorBody = await response.text();
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
const data = await response.json();
@@ -1219,9 +1182,7 @@ async function listIssueDiscussions(
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/issues/${issueIid}/discussions`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/discussions`
);
// Add query parameters for pagination and sorting
@@ -1436,9 +1397,7 @@ async function createOrUpdateFile(
projectId = decodeURIComponent(projectId); // Decode project ID
const encodedPath = encodeURIComponent(filePath);
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/repository/files/${encodedPath}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`
);
const body: Record = {
@@ -1493,9 +1452,7 @@ async function createOrUpdateFile(
if (!response.ok) {
const errorBody = await response.text();
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
const data = await response.json();
@@ -1518,9 +1475,7 @@ async function createTree(
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/repository/tree`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/tree`
);
if (ref) {
@@ -1531,7 +1486,7 @@ async function createTree(
...DEFAULT_FETCH_CONFIG,
method: "POST",
body: JSON.stringify({
- files: files.map((file) => ({
+ files: files.map(file => ({
file_path: file.path,
content: file.content,
encoding: "text",
@@ -1546,9 +1501,7 @@ async function createTree(
if (!response.ok) {
const errorBody = await response.text();
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
const data = await response.json();
@@ -1573,9 +1526,7 @@ async function createCommit(
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/repository/commits`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits`
);
const response = await fetch(url.toString(), {
@@ -1584,7 +1535,7 @@ async function createCommit(
body: JSON.stringify({
branch,
commit_message: message,
- actions: actions.map((action) => ({
+ actions: actions.map(action => ({
action: "create",
file_path: action.path,
content: action.content,
@@ -1600,9 +1551,7 @@ async function createCommit(
if (!response.ok) {
const errorBody = await response.text();
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
const data = await response.json();
@@ -1636,9 +1585,7 @@ async function searchProjects(
if (!response.ok) {
const errorBody = await response.text();
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
const projects = (await response.json()) as GitLabRepository[];
@@ -1681,9 +1628,7 @@ async function createRepository(
if (!response.ok) {
const errorBody = await response.text();
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
const data = await response.json();
@@ -1761,11 +1706,7 @@ async function getMergeRequestDiffs(
}
if (branchName && !mergeRequestIid) {
- const mergeRequest = await getMergeRequest(
- projectId,
- undefined,
- branchName
- );
+ const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
mergeRequestIid = mergeRequest.iid;
}
@@ -1813,18 +1754,12 @@ async function updateMergeRequest(
}
if (branchName && !mergeRequestIid) {
- const mergeRequest = await getMergeRequest(
- projectId,
- undefined,
- branchName
- );
+ const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
mergeRequestIid = mergeRequest.iid;
}
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/merge_requests/${mergeRequestIid}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`
);
const response = await fetch(url.toString(), {
@@ -1870,9 +1805,7 @@ async function createNote(
if (!response.ok) {
const errorText = await response.text();
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorText}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorText}`);
}
return await response.json();
@@ -2000,9 +1933,7 @@ async function verifyNamespaceExistence(
namespacePath: string,
parentId?: number
): Promise {
- const url = new URL(
- `${GITLAB_API_URL}/namespaces/${encodeURIComponent(namespacePath)}/exists`
- );
+ const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(namespacePath)}/exists`);
if (parentId) {
url.searchParams.append("parent_id", parentId.toString());
@@ -2037,9 +1968,7 @@ async function getProject(
} = {}
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`);
if (options.license) {
url.searchParams.append("license", "true");
@@ -2085,12 +2014,9 @@ async function listProjects(
}
// Make the API request
- const response = await fetch(
- `${GITLAB_API_URL}/projects?${params.toString()}`,
- {
- ...DEFAULT_FETCH_CONFIG,
- }
- );
+ const response = await fetch(`${GITLAB_API_URL}/projects?${params.toString()}`, {
+ ...DEFAULT_FETCH_CONFIG,
+ });
// Handle errors
await handleGitLabError(response);
@@ -2113,9 +2039,7 @@ async function listLabels(
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
// Construct the URL with project path
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`);
// Add query parameters
Object.entries(options).forEach(([key, value]) => {
@@ -2163,10 +2087,7 @@ async function getLabel(
// Add query parameters
if (includeAncestorGroups !== undefined) {
- url.searchParams.append(
- "include_ancestor_groups",
- includeAncestorGroups ? "true" : "false"
- );
+ url.searchParams.append("include_ancestor_groups", includeAncestorGroups ? "true" : "false");
}
// Make the API request
@@ -2252,10 +2173,7 @@ async function updateLabel(
* @param projectId The ID or URL-encoded path of the project
* @param labelId The ID or name of the label to delete
*/
-async function deleteLabel(
- projectId: string,
- labelId: number | string
-): Promise {
+async function deleteLabel(projectId: string, labelId: number | string): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
// Make the API request
const response = await fetch(
@@ -2281,57 +2199,36 @@ async function deleteLabel(
async function listGroupProjects(
options: z.infer
): Promise {
- const url = new URL(
- `${GITLAB_API_URL}/groups/${encodeURIComponent(options.group_id)}/projects`
- );
+ const url = new URL(`${GITLAB_API_URL}/groups/${encodeURIComponent(options.group_id)}/projects`);
// Add optional parameters to URL
- if (options.include_subgroups)
- url.searchParams.append("include_subgroups", "true");
+ if (options.include_subgroups) url.searchParams.append("include_subgroups", "true");
if (options.search) url.searchParams.append("search", options.search);
if (options.order_by) url.searchParams.append("order_by", options.order_by);
if (options.sort) url.searchParams.append("sort", options.sort);
if (options.page) url.searchParams.append("page", options.page.toString());
- if (options.per_page)
- url.searchParams.append("per_page", options.per_page.toString());
+ if (options.per_page) url.searchParams.append("per_page", options.per_page.toString());
if (options.archived !== undefined)
url.searchParams.append("archived", options.archived.toString());
- if (options.visibility)
- url.searchParams.append("visibility", options.visibility);
+ if (options.visibility) url.searchParams.append("visibility", options.visibility);
if (options.with_issues_enabled !== undefined)
- url.searchParams.append(
- "with_issues_enabled",
- options.with_issues_enabled.toString()
- );
+ url.searchParams.append("with_issues_enabled", options.with_issues_enabled.toString());
if (options.with_merge_requests_enabled !== undefined)
url.searchParams.append(
"with_merge_requests_enabled",
options.with_merge_requests_enabled.toString()
);
if (options.min_access_level !== undefined)
- url.searchParams.append(
- "min_access_level",
- options.min_access_level.toString()
- );
+ url.searchParams.append("min_access_level", options.min_access_level.toString());
if (options.with_programming_language)
- url.searchParams.append(
- "with_programming_language",
- options.with_programming_language
- );
- if (options.starred !== undefined)
- url.searchParams.append("starred", options.starred.toString());
+ url.searchParams.append("with_programming_language", options.with_programming_language);
+ if (options.starred !== undefined) url.searchParams.append("starred", options.starred.toString());
if (options.statistics !== undefined)
url.searchParams.append("statistics", options.statistics.toString());
if (options.with_custom_attributes !== undefined)
- url.searchParams.append(
- "with_custom_attributes",
- options.with_custom_attributes.toString()
- );
+ url.searchParams.append("with_custom_attributes", options.with_custom_attributes.toString());
if (options.with_security_reports !== undefined)
- url.searchParams.append(
- "with_security_reports",
- options.with_security_reports.toString()
- );
+ url.searchParams.append("with_security_reports", options.with_security_reports.toString());
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
@@ -2351,12 +2248,9 @@ async function listWikiPages(
options: Omit, "project_id"> = {}
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`);
if (options.page) url.searchParams.append("page", options.page.toString());
- if (options.per_page)
- url.searchParams.append("per_page", options.per_page.toString());
+ if (options.per_page) url.searchParams.append("per_page", options.per_page.toString());
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
});
@@ -2368,15 +2262,10 @@ async function listWikiPages(
/**
* Get a specific wiki page
*/
-async function getWikiPage(
- projectId: string,
- slug: string
-): Promise {
+async function getWikiPage(projectId: string, slug: string): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const response = await fetch(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/wikis/${encodeURIComponent(slug)}`,
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`,
{ ...DEFAULT_FETCH_CONFIG }
);
await handleGitLabError(response);
@@ -2425,9 +2314,7 @@ async function updateWikiPage(
if (content) body.content = content;
if (format) body.format = format;
const response = await fetch(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/wikis/${encodeURIComponent(slug)}`,
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`,
{
...DEFAULT_FETCH_CONFIG,
method: "PUT",
@@ -2445,9 +2332,7 @@ async function updateWikiPage(
async function deleteWikiPage(projectId: string, slug: string): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const response = await fetch(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/wikis/${encodeURIComponent(slug)}`,
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`,
{
...DEFAULT_FETCH_CONFIG,
method: "DELETE",
@@ -2468,9 +2353,7 @@ async function listPipelines(
options: Omit = {}
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines`);
// Add all query parameters
Object.entries(options).forEach(([key, value]) => {
@@ -2495,15 +2378,10 @@ async function listPipelines(
* @param {number} pipelineId - The ID of the pipeline
* @returns {Promise} Pipeline details
*/
-async function getPipeline(
- projectId: string,
- pipelineId: number
-): Promise {
+async function getPipeline(projectId: string, pipelineId: number): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/pipelines/${pipelineId}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}`
);
const response = await fetch(url.toString(), {
@@ -2534,9 +2412,7 @@ async function listPipelineJobs(
): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/pipelines/${pipelineId}/jobs`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/jobs`
);
// Add all query parameters
@@ -2562,14 +2438,9 @@ async function listPipelineJobs(
const data = await response.json();
return z.array(GitLabPipelineJobSchema).parse(data);
}
-async function getPipelineJob(
- projectId: string,
- jobId: number
-): Promise {
+async function getPipelineJob(projectId: string, jobId: number): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/jobs/${jobId}`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/jobs/${jobId}`);
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
@@ -2591,15 +2462,10 @@ async function getPipelineJob(
* @param {number} jobId - The ID of the job
* @returns {Promise} The job output/trace
*/
-async function getPipelineJobOutput(
- projectId: string,
- jobId: number
-): Promise {
+async function getPipelineJobOutput(projectId: string, jobId: number): Promise {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/jobs/${jobId}/trace`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/trace`
);
const response = await fetch(url.toString(), {
@@ -2624,16 +2490,13 @@ async function getPipelineJobOutput(
* @param {GetRepositoryTreeOptions} options - Options for the tree
* @returns {Promise}
*/
-async function getRepositoryTree(
- options: GetRepositoryTreeOptions
-): Promise {
+async function getRepositoryTree(options: GetRepositoryTreeOptions): Promise {
options.project_id = decodeURIComponent(options.project_id); // Decode project_id within options
const queryParams = new URLSearchParams();
if (options.path) queryParams.append("path", options.path);
if (options.ref) queryParams.append("ref", options.ref);
if (options.recursive) queryParams.append("recursive", "true");
- if (options.per_page)
- queryParams.append("per_page", options.per_page.toString());
+ if (options.per_page) queryParams.append("per_page", options.per_page.toString());
if (options.page_token) queryParams.append("page_token", options.page_token);
if (options.pagination) queryParams.append("pagination", options.pagination);
@@ -2672,14 +2535,12 @@ async function listProjectMilestones(
options: Omit, "project_id">
): Promise {
projectId = decodeURIComponent(projectId);
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones`);
Object.entries(options).forEach(([key, value]) => {
if (value !== undefined) {
if (key === "iids" && Array.isArray(value) && value.length > 0) {
- value.forEach((iid) => {
+ value.forEach(iid => {
url.searchParams.append("iids[]", iid.toString());
});
} else if (value !== undefined) {
@@ -2708,9 +2569,7 @@ async function getProjectMilestone(
): Promise {
projectId = decodeURIComponent(projectId);
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/milestones/${milestoneId}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`
);
const response = await fetch(url.toString(), {
@@ -2732,9 +2591,7 @@ async function createProjectMilestone(
options: Omit, "project_id">
): Promise {
projectId = decodeURIComponent(projectId);
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones`);
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
@@ -2756,16 +2613,11 @@ async function createProjectMilestone(
async function editProjectMilestone(
projectId: string,
milestoneId: number,
- options: Omit<
- z.infer,
- "project_id" | "milestone_id"
- >
+ options: Omit, "project_id" | "milestone_id">
): Promise {
projectId = decodeURIComponent(projectId);
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/milestones/${milestoneId}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`
);
const response = await fetch(url.toString(), {
@@ -2784,15 +2636,10 @@ async function editProjectMilestone(
* @param {number} milestoneId - The ID of the milestone
* @returns {Promise}
*/
-async function deleteProjectMilestone(
- projectId: string,
- milestoneId: number
-): Promise {
+async function deleteProjectMilestone(projectId: string, milestoneId: number): Promise {
projectId = decodeURIComponent(projectId);
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/milestones/${milestoneId}`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`
);
const response = await fetch(url.toString(), {
@@ -2808,15 +2655,10 @@ async function deleteProjectMilestone(
* @param {number} milestoneId - The ID of the milestone
* @returns {Promise} List of issues
*/
-async function getMilestoneIssues(
- projectId: string,
- milestoneId: number
-): Promise {
+async function getMilestoneIssues(projectId: string, milestoneId: number): Promise {
projectId = decodeURIComponent(projectId);
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/milestones/${milestoneId}/issues`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/issues`
);
const response = await fetch(url.toString(), {
@@ -2864,9 +2706,7 @@ async function promoteProjectMilestone(
): Promise {
projectId = decodeURIComponent(projectId);
const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(
- projectId
- )}/milestones/${milestoneId}/promote`
+ `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/promote`
);
const response = await fetch(url.toString(), {
@@ -2884,10 +2724,7 @@ async function promoteProjectMilestone(
* @param {number} milestoneId - The ID of the milestone
* @returns {Promise} Burndown chart events
*/
-async function getMilestoneBurndownEvents(
- projectId: string,
- milestoneId: number
-): Promise {
+async function getMilestoneBurndownEvents(projectId: string, milestoneId: number): Promise {
projectId = decodeURIComponent(projectId);
const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(
@@ -2906,21 +2743,21 @@ async function getMilestoneBurndownEvents(
server.setRequestHandler(ListToolsRequestSchema, async () => {
// Apply read-only filter first
const tools0 = GITLAB_READ_ONLY_MODE
- ? allTools.filter((tool) => readOnlyTools.includes(tool.name))
+ ? allTools.filter(tool => readOnlyTools.includes(tool.name))
: allTools;
// Toggle wiki tools by USE_GITLAB_WIKI flag
- let tools = USE_GITLAB_WIKI
+ const tools1 = USE_GITLAB_WIKI
? tools0
- : tools0.filter((tool) => !wikiToolNames.includes(tool.name));
+ : tools0.filter(tool => !wikiToolNames.includes(tool.name));
+ // Toggle milestone tools by USE_MILESTONE flag
+ let tools = USE_MILESTONE
+ ? tools1
+ : tools1.filter(tool => !milestoneToolNames.includes(tool.name));
// <<< START: Gemini 호환성을 위해 $schema 제거 >>>
- tools = tools.map((tool) => {
+ tools = tools.map(tool => {
// inputSchema가 존재하고 객체인지 확인
- if (
- tool.inputSchema &&
- typeof tool.inputSchema === "object" &&
- tool.inputSchema !== null
- ) {
+ if (tool.inputSchema && typeof tool.inputSchema === "object" && tool.inputSchema !== null) {
// $schema 키가 존재하면 삭제
if ("$schema" in tool.inputSchema) {
// 불변성을 위해 새로운 객체 생성 (선택적이지만 권장)
@@ -2939,7 +2776,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
};
});
-server.setRequestHandler(CallToolRequestSchema, async (request) => {
+server.setRequestHandler(CallToolRequestSchema, async request => {
try {
if (!request.params.arguments) {
throw new Error("Arguments are required");
@@ -2949,14 +2786,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "fork_repository": {
const forkArgs = ForkRepositorySchema.parse(request.params.arguments);
try {
- const forkedProject = await forkProject(
- forkArgs.project_id,
- forkArgs.namespace
- );
+ const forkedProject = await forkProject(forkArgs.project_id, forkArgs.namespace);
return {
- content: [
- { type: "text", text: JSON.stringify(forkedProject, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(forkedProject, null, 2) }],
};
} catch (forkError) {
console.error("Error forking repository:", forkError);
@@ -2994,11 +2826,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "search_repositories": {
const args = SearchRepositoriesSchema.parse(request.params.arguments);
- const results = await searchProjects(
- args.search,
- args.page,
- args.per_page
- );
+ const results = await searchProjects(args.search, args.page, args.per_page);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
@@ -3008,19 +2836,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = CreateRepositorySchema.parse(request.params.arguments);
const repository = await createRepository(args);
return {
- content: [
- { type: "text", text: JSON.stringify(repository, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(repository, null, 2) }],
};
}
case "get_file_contents": {
const args = GetFileContentsSchema.parse(request.params.arguments);
- const contents = await getFileContents(
- args.project_id,
- args.file_path,
- args.ref
- );
+ const contents = await getFileContents(args.project_id, args.file_path, args.ref);
return {
content: [{ type: "text", text: JSON.stringify(contents, null, 2) }],
};
@@ -3049,7 +2871,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
args.project_id,
args.commit_message,
args.branch,
- args.files.map((f) => ({ path: f.file_path, content: f.content }))
+ args.files.map(f => ({ path: f.file_path, content: f.content }))
);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
@@ -3070,16 +2892,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { project_id, ...options } = args;
const mergeRequest = await createMergeRequest(project_id, options);
return {
- content: [
- { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
};
}
case "update_merge_request_note": {
- const args = UpdateMergeRequestNoteSchema.parse(
- request.params.arguments
- );
+ const args = UpdateMergeRequestNoteSchema.parse(request.params.arguments);
const note = await updateMergeRequestNote(
args.project_id,
args.merge_request_iid,
@@ -3094,9 +2912,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "create_merge_request_note": {
- const args = CreateMergeRequestNoteSchema.parse(
- request.params.arguments
- );
+ const args = CreateMergeRequestNoteSchema.parse(request.params.arguments);
const note = await createMergeRequestNote(
args.project_id,
args.merge_request_iid,
@@ -3145,9 +2961,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
args.source_branch
);
return {
- content: [
- { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
};
}
@@ -3166,8 +2980,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "update_merge_request": {
const args = UpdateMergeRequestSchema.parse(request.params.arguments);
- const { project_id, merge_request_iid, source_branch, ...options } =
- args;
+ const { project_id, merge_request_iid, source_branch, ...options } = args;
const mergeRequest = await updateMergeRequest(
project_id,
options,
@@ -3175,24 +2988,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
source_branch
);
return {
- content: [
- { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
};
}
case "mr_discussions": {
- const args = ListMergeRequestDiscussionsSchema.parse(
- request.params.arguments
- );
+ const args = ListMergeRequestDiscussionsSchema.parse(request.params.arguments);
const discussions = await listMergeRequestDiscussions(
args.project_id,
args.merge_request_iid
);
return {
- content: [
- { type: "text", text: JSON.stringify(discussions, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }],
};
}
@@ -3222,18 +3029,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const namespaces = z.array(GitLabNamespaceSchema).parse(data);
return {
- content: [
- { type: "text", text: JSON.stringify(namespaces, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(namespaces, null, 2) }],
};
}
case "get_namespace": {
const args = GetNamespaceSchema.parse(request.params.arguments);
const url = new URL(
- `${GITLAB_API_URL}/namespaces/${encodeURIComponent(
- args.namespace_id
- )}`
+ `${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.namespace_id)}`
);
const response = await fetch(url.toString(), {
@@ -3251,9 +3054,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "verify_namespace": {
const args = VerifyNamespaceSchema.parse(request.params.arguments);
- const url = new URL(
- `${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.path)}/exists`
- );
+ const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.path)}/exists`);
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
@@ -3264,17 +3065,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const namespaceExists = GitLabNamespaceExistsResponseSchema.parse(data);
return {
- content: [
- { type: "text", text: JSON.stringify(namespaceExists, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(namespaceExists, null, 2) }],
};
}
case "get_project": {
const args = GetProjectSchema.parse(request.params.arguments);
- const url = new URL(
- `${GITLAB_API_URL}/projects/${encodeURIComponent(args.project_id)}`
- );
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(args.project_id)}`);
const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG,
@@ -3302,23 +3099,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = CreateNoteSchema.parse(request.params.arguments);
const { project_id, noteable_type, noteable_iid, body } = args;
- const note = await createNote(
- project_id,
- noteable_type,
- noteable_iid,
- body
- );
+ const note = await createNote(project_id, noteable_type, noteable_iid, body);
return {
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
};
}
case "create_merge_request_thread": {
- const args = CreateMergeRequestThreadSchema.parse(
- request.params.arguments
- );
- const { project_id, merge_request_iid, body, position, created_at } =
- args;
+ const args = CreateMergeRequestThreadSchema.parse(request.params.arguments);
+ const { project_id, merge_request_iid, body, position, created_at } = args;
const thread = await createMergeRequestThread(
project_id,
@@ -3387,25 +3176,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = ListIssueDiscussionsSchema.parse(request.params.arguments);
const { project_id, issue_iid, ...options } = args;
- const discussions = await listIssueDiscussions(
- project_id,
- issue_iid,
- options
- );
+ const discussions = await listIssueDiscussions(project_id, issue_iid, options);
return {
- content: [
- { type: "text", text: JSON.stringify(discussions, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }],
};
}
case "get_issue_link": {
const args = GetIssueLinkSchema.parse(request.params.arguments);
- const link = await getIssueLink(
- args.project_id,
- args.issue_iid,
- args.issue_link_id
- );
+ const link = await getIssueLink(args.project_id, args.issue_iid, args.issue_link_id);
return {
content: [{ type: "text", text: JSON.stringify(link, null, 2) }],
};
@@ -3427,11 +3206,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "delete_issue_link": {
const args = DeleteIssueLinkSchema.parse(request.params.arguments);
- await deleteIssueLink(
- args.project_id,
- args.issue_iid,
- args.issue_link_id
- );
+ await deleteIssueLink(args.project_id, args.issue_iid, args.issue_link_id);
return {
content: [
{
@@ -3459,11 +3234,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "get_label": {
const args = GetLabelSchema.parse(request.params.arguments);
- const label = await getLabel(
- args.project_id,
- args.label_id,
- args.include_ancestor_groups
- );
+ const label = await getLabel(args.project_id, args.label_id, args.include_ancestor_groups);
return {
content: [{ type: "text", text: JSON.stringify(label, null, 2) }],
};
@@ -3512,9 +3283,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "list_wiki_pages": {
- const { project_id, page, per_page } = ListWikiPagesSchema.parse(
- request.params.arguments
- );
+ const { project_id, page, per_page } = ListWikiPagesSchema.parse(request.params.arguments);
const wikiPages = await listWikiPages(project_id, { page, per_page });
return {
content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }],
@@ -3522,9 +3291,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "get_wiki_page": {
- const { project_id, slug } = GetWikiPageSchema.parse(
- request.params.arguments
- );
+ const { project_id, slug } = GetWikiPageSchema.parse(request.params.arguments);
const wikiPage = await getWikiPage(project_id, slug);
return {
content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
@@ -3532,38 +3299,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "create_wiki_page": {
- const { project_id, title, content, format } =
- CreateWikiPageSchema.parse(request.params.arguments);
- const wikiPage = await createWikiPage(
- project_id,
- title,
- content,
- format
+ const { project_id, title, content, format } = CreateWikiPageSchema.parse(
+ request.params.arguments
);
+ const wikiPage = await createWikiPage(project_id, title, content, format);
return {
content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
};
}
case "update_wiki_page": {
- const { project_id, slug, title, content, format } =
- UpdateWikiPageSchema.parse(request.params.arguments);
- const wikiPage = await updateWikiPage(
- project_id,
- slug,
- title,
- content,
- format
+ const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse(
+ request.params.arguments
);
+ const wikiPage = await updateWikiPage(project_id, slug, title, content, format);
return {
content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
};
}
case "delete_wiki_page": {
- const { project_id, slug } = DeleteWikiPageSchema.parse(
- request.params.arguments
- );
+ const { project_id, slug } = DeleteWikiPageSchema.parse(request.params.arguments);
await deleteWikiPage(project_id, slug);
return {
content: [
@@ -3600,9 +3356,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "get_pipeline": {
- const { project_id, pipeline_id } = GetPipelineSchema.parse(
- request.params.arguments
- );
+ const { project_id, pipeline_id } = GetPipelineSchema.parse(request.params.arguments);
const pipeline = await getPipeline(project_id, pipeline_id);
return {
content: [
@@ -3615,8 +3369,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "list_pipeline_jobs": {
- const { project_id, pipeline_id, ...options } =
- ListPipelineJobsSchema.parse(request.params.arguments);
+ const { project_id, pipeline_id, ...options } = ListPipelineJobsSchema.parse(
+ request.params.arguments
+ );
const jobs = await listPipelineJobs(project_id, pipeline_id, options);
return {
content: [
@@ -3629,9 +3384,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "get_pipeline_job": {
- const { project_id, job_id } = GetPipelineJobOutputSchema.parse(
- request.params.arguments
- );
+ const { project_id, job_id } = GetPipelineJobOutputSchema.parse(request.params.arguments);
const jobDetails = await getPipelineJob(project_id, job_id);
return {
content: [
@@ -3644,9 +3397,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "get_pipeline_job_output": {
- const { project_id, job_id } = GetPipelineJobOutputSchema.parse(
- request.params.arguments
- );
+ const { project_id, job_id } = GetPipelineJobOutputSchema.parse(request.params.arguments);
const jobOutput = await getPipelineJobOutput(project_id, job_id);
return {
content: [
@@ -3662,9 +3413,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = ListMergeRequestsSchema.parse(request.params.arguments);
const mergeRequests = await listMergeRequests(args.project_id, args);
return {
- content: [
- { type: "text", text: JSON.stringify(mergeRequests, null, 2) },
- ],
+ content: [{ type: "text", text: JSON.stringify(mergeRequests, null, 2) }],
};
}
@@ -3714,13 +3463,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "edit_milestone": {
- const { project_id, milestone_id, ...options } =
- EditProjectMilestoneSchema.parse(request.params.arguments);
- const milestone = await editProjectMilestone(
- project_id,
- milestone_id,
- options
+ const { project_id, milestone_id, ...options } = EditProjectMilestoneSchema.parse(
+ request.params.arguments
);
+ const milestone = await editProjectMilestone(project_id, milestone_id, options);
return {
content: [
{
@@ -3769,12 +3515,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "get_milestone_merge_requests": {
- const { project_id, milestone_id } =
- GetMilestoneMergeRequestsSchema.parse(request.params.arguments);
- const mergeRequests = await getMilestoneMergeRequests(
- project_id,
- milestone_id
+ const { project_id, milestone_id } = GetMilestoneMergeRequestsSchema.parse(
+ request.params.arguments
);
+ const mergeRequests = await getMilestoneMergeRequests(project_id, milestone_id);
return {
content: [
{
@@ -3786,12 +3530,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "promote_milestone": {
- const { project_id, milestone_id } =
- PromoteProjectMilestoneSchema.parse(request.params.arguments);
- const milestone = await promoteProjectMilestone(
- project_id,
- milestone_id
+ const { project_id, milestone_id } = PromoteProjectMilestoneSchema.parse(
+ request.params.arguments
);
+ const milestone = await promoteProjectMilestone(project_id, milestone_id);
return {
content: [
{
@@ -3803,12 +3545,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
case "get_milestone_burndown_events": {
- const { project_id, milestone_id } =
- GetMilestoneBurndownEventsSchema.parse(request.params.arguments);
- const events = await getMilestoneBurndownEvents(
- project_id,
- milestone_id
+ const { project_id, milestone_id } = GetMilestoneBurndownEventsSchema.parse(
+ request.params.arguments
);
+ const events = await getMilestoneBurndownEvents(project_id, milestone_id);
return {
content: [
{
@@ -3826,7 +3566,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors
- .map((e) => `${e.path.join(".")}: ${e.message}`)
+ .map(e => `${e.path.join(".")}: ${e.message}`)
.join(", ")}`
);
}
@@ -3854,7 +3594,7 @@ async function runServer() {
}
}
-runServer().catch((error) => {
+runServer().catch(error => {
console.error("Fatal error in main():", error);
process.exit(1);
});
diff --git a/package-lock.json b/package-lock.json
index 2002944..6194a50 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@zereight/mcp-gitlab",
- "version": "1.0.46",
+ "version": "1.0.50",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@zereight/mcp-gitlab",
- "version": "1.0.46",
+ "version": "1.0.50",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "1.8.0",
@@ -23,6 +23,11 @@
},
"devDependencies": {
"@types/node": "^22.13.10",
+ "@typescript-eslint/eslint-plugin": "^8.21.0",
+ "@typescript-eslint/parser": "^8.21.0",
+ "eslint": "^9.18.0",
+ "prettier": "^3.4.2",
+ "ts-node": "^10.9.2",
"typescript": "^5.8.2",
"zod": "^3.24.2"
},
@@ -30,6 +35,299 @@
"node": ">=14"
}
},
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
+ "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz",
+ "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
+ "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
+ "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
+ "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.14.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz",
@@ -51,6 +349,86 @@
"node": ">=18"
}
},
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+ "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "22.13.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
@@ -70,6 +448,237 @@
"form-data": "^4.0.0"
}
},
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz",
+ "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.33.0",
+ "@typescript-eslint/type-utils": "8.33.0",
+ "@typescript-eslint/utils": "8.33.0",
+ "@typescript-eslint/visitor-keys": "8.33.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.33.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz",
+ "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.33.0",
+ "@typescript-eslint/types": "8.33.0",
+ "@typescript-eslint/typescript-estree": "8.33.0",
+ "@typescript-eslint/visitor-keys": "8.33.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz",
+ "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.33.0",
+ "@typescript-eslint/types": "^8.33.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz",
+ "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.33.0",
+ "@typescript-eslint/visitor-keys": "8.33.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz",
+ "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz",
+ "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "8.33.0",
+ "@typescript-eslint/utils": "8.33.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz",
+ "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz",
+ "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.33.0",
+ "@typescript-eslint/tsconfig-utils": "8.33.0",
+ "@typescript-eslint/types": "8.33.0",
+ "@typescript-eslint/visitor-keys": "8.33.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz",
+ "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.33.0",
+ "@typescript-eslint/types": "8.33.0",
+ "@typescript-eslint/typescript-estree": "8.33.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz",
+ "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.33.0",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -83,6 +692,42 @@
"node": ">= 0.6"
}
},
+ "node_modules/acorn": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
@@ -92,12 +737,66 @@
"node": ">= 14"
}
},
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
@@ -156,6 +855,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -194,6 +916,53 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -206,6 +975,13 @@
"node": ">= 0.8"
}
},
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
@@ -258,6 +1034,13 @@
"node": ">= 0.10"
}
},
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -298,6 +1081,13 @@
}
}
},
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -316,6 +1106,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -396,6 +1196,234 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
+ "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.20.0",
+ "@eslint/config-helpers": "^0.2.1",
+ "@eslint/core": "^0.14.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.27.0",
+ "@eslint/plugin-kit": "^0.3.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.3.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+ "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -484,6 +1512,67 @@
"express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
@@ -507,6 +1596,32 @@
"node": "^12.20 || >= 14.13"
}
},
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
@@ -547,6 +1662,44 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
@@ -659,6 +1812,32 @@
"node": ">= 0.4"
}
},
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -671,6 +1850,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -764,6 +1960,43 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ignore": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
+ "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -792,6 +2025,39 @@
"node": ">= 0.10"
}
},
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -804,12 +2070,100 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"license": "MIT"
},
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -840,6 +2194,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -849,6 +2213,20 @@
"node": ">= 0.6"
}
},
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
@@ -870,12 +2248,35 @@
"node": ">= 0.6"
}
},
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"license": "MIT"
},
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
@@ -964,6 +2365,69 @@
"wrappy": "1"
}
},
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -973,6 +2437,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -991,6 +2465,19 @@
"node": ">=16"
}
},
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/pkce-challenge": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz",
@@ -1000,6 +2487,32 @@
"node": ">=16.20.0"
}
},
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
+ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -1013,6 +2526,16 @@
"node": ">= 0.10"
}
},
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -1028,6 +2551,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -1052,6 +2596,27 @@
"node": ">= 0.8"
}
},
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -1091,6 +2656,30 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -1117,6 +2706,19 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
@@ -1312,6 +2914,45 @@
"node": ">= 0.8"
}
},
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -1321,6 +2962,76 @@
"node": ">=0.6"
}
},
+ "node_modules/ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
@@ -1364,6 +3075,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -1373,6 +3094,13 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -1406,12 +3134,45 @@
"node": ">= 8"
}
},
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/zod": {
"version": "3.24.2",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
diff --git a/package.json b/package.json
index 312e0ea..eeac290 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,13 @@
"prepare": "npm run build",
"watch": "tsc --watch",
"deploy": "npm publish --access public",
- "generate-tools": "npx ts-node scripts/generate-tools-readme.ts"
+ "generate-tools": "npx ts-node scripts/generate-tools-readme.ts",
+ "test": "node test/validate-api.js",
+ "test:integration": "node test/validate-api.js",
+ "lint": "eslint . --ext .ts",
+ "lint:fix": "eslint . --ext .ts --fix",
+ "format": "prettier --write \"**/*.{js,ts,json,md}\"",
+ "format:check": "prettier --check \"**/*.{js,ts,json,md}\""
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.8.0",
@@ -35,6 +41,11 @@
"devDependencies": {
"@types/node": "^22.13.10",
"typescript": "^5.8.2",
- "zod": "^3.24.2"
+ "zod": "^3.24.2",
+ "@typescript-eslint/eslint-plugin": "^8.21.0",
+ "@typescript-eslint/parser": "^8.21.0",
+ "eslint": "^9.18.0",
+ "prettier": "^3.4.2",
+ "ts-node": "^10.9.2"
}
}
diff --git a/schemas.ts b/schemas.ts
index a82bbdd..c6aeee4 100644
--- a/schemas.ts
+++ b/schemas.ts
@@ -22,27 +22,34 @@ export const GitLabPipelineSchema = z.object({
started_at: z.string().nullable().optional(),
finished_at: z.string().nullable().optional(),
coverage: z.number().nullable().optional(),
- user: z.object({
- id: z.number(),
- name: z.string(),
- username: z.string(),
- avatar_url: z.string().nullable().optional(),
- }).optional(),
- detailed_status: z.object({
- icon: z.string().optional(),
- text: z.string().optional(),
- label: z.string().optional(),
- group: z.string().optional(),
- tooltip: z.string().optional(),
- has_details: z.boolean().optional(),
- details_path: z.string().optional(),
- illustration: z.object({
- image: z.string().optional(),
- size: z.string().optional(),
- title: z.string().optional(),
- }).nullable().optional(),
- favicon: z.string().optional(),
- }).optional(),
+ user: z
+ .object({
+ id: z.number(),
+ name: z.string(),
+ username: z.string(),
+ avatar_url: z.string().nullable().optional(),
+ })
+ .optional(),
+ detailed_status: z
+ .object({
+ icon: z.string().optional(),
+ text: z.string().optional(),
+ label: z.string().optional(),
+ group: z.string().optional(),
+ tooltip: z.string().optional(),
+ has_details: z.boolean().optional(),
+ details_path: z.string().optional(),
+ illustration: z
+ .object({
+ image: z.string().optional(),
+ size: z.string().optional(),
+ title: z.string().optional(),
+ })
+ .nullable()
+ .optional(),
+ favicon: z.string().optional(),
+ })
+ .optional(),
});
// Pipeline job related schemas
@@ -58,42 +65,75 @@ export const GitLabPipelineJobSchema = z.object({
started_at: z.string().nullable().optional(),
finished_at: z.string().nullable().optional(),
duration: z.number().nullable().optional(),
- user: z.object({
- id: z.number(),
- name: z.string(),
- username: z.string(),
- avatar_url: z.string().nullable().optional(),
- }).optional(),
- commit: z.object({
- id: z.string(),
- short_id: z.string(),
- title: z.string(),
- author_name: z.string(),
- author_email: z.string(),
- }).optional(),
- pipeline: z.object({
- id: z.number(),
- project_id: z.number(),
- status: z.string(),
- ref: z.string(),
- sha: z.string(),
- }).optional(),
+ user: z
+ .object({
+ id: z.number(),
+ name: z.string(),
+ username: z.string(),
+ avatar_url: z.string().nullable().optional(),
+ })
+ .optional(),
+ commit: z
+ .object({
+ id: z.string(),
+ short_id: z.string(),
+ title: z.string(),
+ author_name: z.string(),
+ author_email: z.string(),
+ })
+ .optional(),
+ pipeline: z
+ .object({
+ id: z.number(),
+ project_id: z.number(),
+ status: z.string(),
+ ref: z.string(),
+ sha: z.string(),
+ })
+ .optional(),
web_url: z.string().optional(),
});
// Schema for listing pipelines
export const ListPipelinesSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
- scope: z.enum(['running', 'pending', 'finished', 'branches', 'tags']).optional().describe("The scope of pipelines"),
- status: z.enum(['created', 'waiting_for_resource', 'preparing', 'pending', 'running', 'success', 'failed', 'canceled', 'skipped', 'manual', 'scheduled']).optional().describe("The status of pipelines"),
+ scope: z
+ .enum(["running", "pending", "finished", "branches", "tags"])
+ .optional()
+ .describe("The scope of pipelines"),
+ status: z
+ .enum([
+ "created",
+ "waiting_for_resource",
+ "preparing",
+ "pending",
+ "running",
+ "success",
+ "failed",
+ "canceled",
+ "skipped",
+ "manual",
+ "scheduled",
+ ])
+ .optional()
+ .describe("The status of pipelines"),
ref: z.string().optional().describe("The ref of pipelines"),
sha: z.string().optional().describe("The SHA of pipelines"),
yaml_errors: z.boolean().optional().describe("Returns pipelines with invalid configurations"),
username: z.string().optional().describe("The username of the user who triggered pipelines"),
- updated_after: z.string().optional().describe("Return pipelines updated after the specified date"),
- updated_before: z.string().optional().describe("Return pipelines updated before the specified date"),
- order_by: z.enum(['id', 'status', 'ref', 'updated_at', 'user_id']).optional().describe("Order pipelines by"),
- sort: z.enum(['asc', 'desc']).optional().describe("Sort pipelines"),
+ updated_after: z
+ .string()
+ .optional()
+ .describe("Return pipelines updated after the specified date"),
+ updated_before: z
+ .string()
+ .optional()
+ .describe("Return pipelines updated before the specified date"),
+ order_by: z
+ .enum(["id", "status", "ref", "updated_at", "user_id"])
+ .optional()
+ .describe("Order pipelines by"),
+ sort: z.enum(["asc", "desc"]).optional().describe("Sort pipelines"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page (max 100)"),
});
@@ -108,7 +148,10 @@ export const GetPipelineSchema = z.object({
export const ListPipelineJobsSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
pipeline_id: z.number().describe("The ID of the pipeline"),
- scope: z.enum(['created', 'pending', 'running', 'failed', 'success', 'canceled', 'skipped', 'manual']).optional().describe("The scope of jobs to show"),
+ scope: z
+ .enum(["created", "pending", "running", "failed", "success", "canceled", "skipped", "manual"])
+ .optional()
+ .describe("The scope of jobs to show"),
include_retried: z.boolean().optional().describe("Whether to include retried jobs"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page (max 100)"),
@@ -287,21 +330,10 @@ export const GetRepositoryTreeSchema = z.object({
ref: z
.string()
.optional()
- .describe(
- "The name of a repository branch or tag. Defaults to the default branch."
- ),
- recursive: z
- .boolean()
- .optional()
- .describe("Boolean value to get a recursive tree"),
- per_page: z
- .number()
- .optional()
- .describe("Number of results to show per page"),
- page_token: z
- .string()
- .optional()
- .describe("The tree record ID for pagination"),
+ .describe("The name of a repository branch or tag. Defaults to the default branch."),
+ recursive: z.boolean().optional().describe("Boolean value to get a recursive tree"),
+ per_page: z.number().optional().describe("Number of results to show per page"),
+ page_token: z.string().optional().describe("The tree record ID for pagination"),
pagination: z.string().optional().describe("Pagination method (keyset)"),
});
@@ -346,7 +378,7 @@ export const GitLabMilestonesSchema = z.object({
updated_at: z.string(),
created_at: z.string(),
expired: z.boolean(),
- web_url: z.string().optional()
+ web_url: z.string().optional(),
});
// Input schemas for operations
@@ -606,11 +638,13 @@ export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
note_id: z.number().describe("The ID of a thread note"),
body: z.string().optional().describe("The content of the note or reply"),
resolved: z.boolean().optional().describe("Resolve or unresolve the note"),
-}).refine(data => data.body !== undefined || data.resolved !== undefined, {
- message: "At least one of 'body' or 'resolved' must be provided"
-}).refine(data => !(data.body !== undefined && data.resolved !== undefined), {
- message: "Only one of 'body' or 'resolved' can be provided, not both"
-});
+})
+ .refine(data => data.body !== undefined || data.resolved !== undefined, {
+ message: "At least one of 'body' or 'resolved' must be provided",
+ })
+ .refine(data => !(data.body !== undefined && data.resolved !== undefined), {
+ message: "Only one of 'body' or 'resolved' can be provided, not both",
+ });
// Input schema for adding a note to an existing merge request discussion
export const CreateMergeRequestNoteSchema = ProjectParamsSchema.extend({
@@ -643,27 +677,15 @@ export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
content: z.string().describe("Content of the file"),
commit_message: z.string().describe("Commit message"),
branch: z.string().describe("Branch to create/update the file in"),
- previous_path: z
- .string()
- .optional()
- .describe("Path of the file to move/rename"),
+ previous_path: z.string().optional().describe("Path of the file to move/rename"),
last_commit_id: z.string().optional().describe("Last known file commit ID"),
- commit_id: z
- .string()
- .optional()
- .describe("Current file commit ID (for update operations)"),
+ commit_id: z.string().optional().describe("Current file commit ID (for update operations)"),
});
export const SearchRepositoriesSchema = z.object({
search: z.string().describe("Search query"), // Changed from query to match GitLab API
- page: z
- .number()
- .optional()
- .describe("Page number for pagination (default: 1)"),
- per_page: z
- .number()
- .optional()
- .describe("Number of results per page (default: 20)"),
+ page: z.number().optional().describe("Page number for pagination (default: 1)"),
+ per_page: z.number().optional().describe("Number of results per page (default: 20)"),
});
export const CreateRepositorySchema = z.object({
@@ -673,10 +695,7 @@ export const CreateRepositorySchema = z.object({
.enum(["private", "internal", "public"])
.optional()
.describe("Repository visibility level"),
- initialize_with_readme: z
- .boolean()
- .optional()
- .describe("Initialize with README.md"),
+ initialize_with_readme: z.boolean().optional().describe("Initialize with README.md"),
});
export const GetFileContentsSchema = ProjectParamsSchema.extend({
@@ -700,10 +719,7 @@ export const PushFilesSchema = ProjectParamsSchema.extend({
export const CreateIssueSchema = ProjectParamsSchema.extend({
title: z.string().describe("Issue title"),
description: z.string().optional().describe("Issue description"),
- assignee_ids: z
- .array(z.number())
- .optional()
- .describe("Array of user IDs to assign"),
+ assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign"),
labels: z.array(z.string()).optional().describe("Array of label names"),
milestone_id: z.number().optional().describe("Milestone ID to assign"),
});
@@ -714,10 +730,7 @@ export const CreateMergeRequestSchema = ProjectParamsSchema.extend({
source_branch: z.string().describe("Branch containing changes"),
target_branch: z.string().describe("Branch to merge into"),
draft: z.boolean().optional().describe("Create as draft merge request"),
- allow_collaboration: z
- .boolean()
- .optional()
- .describe("Allow commits from upstream members"),
+ allow_collaboration: z.boolean().optional().describe("Allow commits from upstream members"),
});
export const ForkRepositorySchema = ProjectParamsSchema.extend({
@@ -741,24 +754,15 @@ export const GitLabMergeRequestDiffSchema = z.object({
});
export const GetMergeRequestSchema = ProjectParamsSchema.extend({
- merge_request_iid: z
- .number()
- .optional()
- .describe("The IID of a merge request"),
+ merge_request_iid: z.number().optional().describe("The IID of a merge request"),
source_branch: z.string().optional().describe("Source branch name"),
});
export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
title: z.string().optional().describe("The title of the merge request"),
- description: z
- .string()
- .optional()
- .describe("The description of the merge request"),
+ description: z.string().optional().describe("The description of the merge request"),
target_branch: z.string().optional().describe("The target branch"),
- assignee_ids: z
- .array(z.number())
- .optional()
- .describe("The ID of the users to assign the MR to"),
+ assignee_ids: z.array(z.number()).optional().describe("The ID of the users to assign the MR to"),
labels: z.array(z.string()).optional().describe("Labels for the MR"),
state_event: z
.enum(["close", "reopen"])
@@ -768,10 +772,7 @@ export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
.boolean()
.optional()
.describe("Flag indicating if the source branch should be removed"),
- squash: z
- .boolean()
- .optional()
- .describe("Squash commits into a single commit when merging"),
+ squash: z.boolean().optional().describe("Squash commits into a single commit when merging"),
draft: z.boolean().optional().describe("Work in progress merge request"),
});
@@ -791,38 +792,14 @@ export const CreateNoteSchema = z.object({
// Issues API operation schemas
export const ListIssuesSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
- assignee_id: z
- .number()
- .optional()
- .describe("Return issues assigned to the given user ID"),
- assignee_username: z
- .string()
- .optional()
- .describe("Return issues assigned to the given username"),
- author_id: z
- .number()
- .optional()
- .describe("Return issues created by the given user ID"),
- author_username: z
- .string()
- .optional()
- .describe("Return issues created by the given username"),
- confidential: z
- .boolean()
- .optional()
- .describe("Filter confidential or public issues"),
- created_after: z
- .string()
- .optional()
- .describe("Return issues created after the given time"),
- created_before: z
- .string()
- .optional()
- .describe("Return issues created before the given time"),
- due_date: z
- .string()
- .optional()
- .describe("Return issues that have the due date"),
+ assignee_id: z.number().optional().describe("Return issues assigned to the given user ID"),
+ assignee_username: z.string().optional().describe("Return issues assigned to the given username"),
+ author_id: z.number().optional().describe("Return issues created by the given user ID"),
+ author_username: z.string().optional().describe("Return issues created by the given username"),
+ confidential: z.boolean().optional().describe("Filter confidential or public issues"),
+ created_after: z.string().optional().describe("Return issues created after the given time"),
+ created_before: z.string().optional().describe("Return issues created before the given time"),
+ due_date: z.string().optional().describe("Return issues that have the due date"),
label_name: z.array(z.string()).optional().describe("Array of label names"),
milestone: z.string().optional().describe("Milestone title"),
scope: z
@@ -834,18 +811,9 @@ export const ListIssuesSchema = z.object({
.enum(["opened", "closed", "all"])
.optional()
.describe("Return issues with a specific state"),
- updated_after: z
- .string()
- .optional()
- .describe("Return issues updated after the given time"),
- updated_before: z
- .string()
- .optional()
- .describe("Return issues updated before the given time"),
- with_labels_details: z
- .boolean()
- .optional()
- .describe("Return more details for each label"),
+ updated_after: z.string().optional().describe("Return issues updated after the given time"),
+ updated_before: z.string().optional().describe("Return issues updated before the given time"),
+ with_labels_details: z.boolean().optional().describe("Return more details for each label"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
});
@@ -861,10 +829,7 @@ export const ListMergeRequestsSchema = z.object({
.string()
.optional()
.describe("Returns merge requests assigned to the given username"),
- author_id: z
- .number()
- .optional()
- .describe("Returns merge requests created by the given user ID"),
+ author_id: z.number().optional().describe("Returns merge requests created by the given user ID"),
author_username: z
.string()
.optional()
@@ -920,14 +885,8 @@ export const ListMergeRequestsSchema = z.object({
.string()
.optional()
.describe("Return merge requests from a specific source branch"),
- wip: z
- .enum(["yes", "no"])
- .optional()
- .describe("Filter merge requests against their wip status"),
- with_labels_details: z
- .boolean()
- .optional()
- .describe("Return more details for each label"),
+ wip: z.enum(["yes", "no"]).optional().describe("Filter merge requests against their wip status"),
+ with_labels_details: z.boolean().optional().describe("Return more details for each label"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
});
@@ -942,28 +901,13 @@ export const UpdateIssueSchema = z.object({
issue_iid: z.number().describe("The internal ID of the project issue"),
title: z.string().optional().describe("The title of the issue"),
description: z.string().optional().describe("The description of the issue"),
- assignee_ids: z
- .array(z.number())
- .optional()
- .describe("Array of user IDs to assign issue to"),
- confidential: z
- .boolean()
- .optional()
- .describe("Set the issue to be confidential"),
- discussion_locked: z
- .boolean()
- .optional()
- .describe("Flag to lock discussions"),
- due_date: z
- .string()
- .optional()
- .describe("Date the issue is due (YYYY-MM-DD)"),
+ assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign issue to"),
+ confidential: z.boolean().optional().describe("Set the issue to be confidential"),
+ discussion_locked: z.boolean().optional().describe("Flag to lock discussions"),
+ due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"),
labels: z.array(z.string()).optional().describe("Array of label names"),
milestone_id: z.number().optional().describe("Milestone ID to assign"),
- state_event: z
- .enum(["close", "reopen"])
- .optional()
- .describe("Update issue state (close/reopen)"),
+ state_event: z.enum(["close", "reopen"]).optional().describe("Update issue state (close/reopen)"),
weight: z.number().optional().describe("Weight of the issue (0-9)"),
});
@@ -989,8 +933,14 @@ export const ListIssueDiscussionsSchema = z.object({
issue_iid: z.number().describe("The internal ID of the project issue"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
- sort: z.enum(["asc", "desc"]).optional().describe("Return issue discussions sorted in ascending or descending order"),
- order_by: z.enum(["created_at", "updated_at"]).optional().describe("Return issue discussions ordered by created_at or updated_at fields"),
+ sort: z
+ .enum(["asc", "desc"])
+ .optional()
+ .describe("Return issue discussions sorted in ascending or descending order"),
+ order_by: z
+ .enum(["created_at", "updated_at"])
+ .optional()
+ .describe("Return issue discussions ordered by created_at or updated_at fields"),
});
export const GetIssueLinkSchema = z.object({
@@ -1002,12 +952,8 @@ export const GetIssueLinkSchema = z.object({
export const CreateIssueLinkSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"),
- target_project_id: z
- .string()
- .describe("The ID or URL-encoded path of a target project"),
- target_issue_iid: z
- .number()
- .describe("The internal ID of a target project's issue"),
+ target_project_id: z.string().describe("The ID or URL-encoded path of a target project"),
+ target_issue_iid: z.number().describe("The internal ID of a target project's issue"),
link_type: z
.enum(["relates_to", "blocks", "is_blocked_by"])
.optional()
@@ -1025,10 +971,7 @@ export const ListNamespacesSchema = z.object({
search: z.string().optional().describe("Search term for namespaces"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
- owned: z
- .boolean()
- .optional()
- .describe("Filter for namespaces owned by current user"),
+ owned: z.boolean().optional().describe("Filter for namespaces owned by current user"),
});
export const GetNamespaceSchema = z.object({
@@ -1048,18 +991,9 @@ export const ListProjectsSchema = z.object({
search: z.string().optional().describe("Search term for projects"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
- search_namespaces: z
- .boolean()
- .optional()
- .describe("Needs to be true if search is full path"),
- owned: z
- .boolean()
- .optional()
- .describe("Filter for projects owned by current user"),
- membership: z
- .boolean()
- .optional()
- .describe("Filter for projects where current user is a member"),
+ search_namespaces: z.boolean().optional().describe("Needs to be true if search is full path"),
+ owned: z.boolean().optional().describe("Filter for projects owned by current user"),
+ membership: z.boolean().optional().describe("Filter for projects where current user is a member"),
simple: z.boolean().optional().describe("Return only limited fields"),
archived: z.boolean().optional().describe("Filter for archived projects"),
visibility: z
@@ -1067,14 +1001,7 @@ export const ListProjectsSchema = z.object({
.optional()
.describe("Filter by project visibility"),
order_by: z
- .enum([
- "id",
- "name",
- "path",
- "created_at",
- "updated_at",
- "last_activity_at",
- ])
+ .enum(["id", "name", "path", "created_at", "updated_at", "last_activity_at"])
.optional()
.describe("Return projects ordered by field"),
sort: z
@@ -1089,10 +1016,7 @@ export const ListProjectsSchema = z.object({
.boolean()
.optional()
.describe("Filter projects with merge requests feature enabled"),
- min_access_level: z
- .number()
- .optional()
- .describe("Filter by minimum access level"),
+ min_access_level: z.number().optional().describe("Filter by minimum access level"),
});
// Label operation schemas
@@ -1102,20 +1026,14 @@ export const ListLabelsSchema = z.object({
.boolean()
.optional()
.describe("Whether or not to include issue and merge request counts"),
- include_ancestor_groups: z
- .boolean()
- .optional()
- .describe("Include ancestor groups"),
+ include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"),
search: z.string().optional().describe("Keyword to filter labels by"),
});
export const GetLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.string().describe("The ID or title of a project's label"),
- include_ancestor_groups: z
- .boolean()
- .optional()
- .describe("Include ancestor groups"),
+ include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"),
});
export const CreateLabelSchema = z.object({
@@ -1123,15 +1041,9 @@ export const CreateLabelSchema = z.object({
name: z.string().describe("The name of the label"),
color: z
.string()
- .describe(
- "The color of the label given in 6-digit hex notation with leading '#' sign"
- ),
+ .describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
description: z.string().optional().describe("The description of the label"),
- priority: z
- .number()
- .nullable()
- .optional()
- .describe("The priority of the label"),
+ priority: z.number().nullable().optional().describe("The priority of the label"),
});
export const UpdateLabelSchema = z.object({
@@ -1141,18 +1053,9 @@ export const UpdateLabelSchema = z.object({
color: z
.string()
.optional()
- .describe(
- "The color of the label given in 6-digit hex notation with leading '#' sign"
- ),
- description: z
- .string()
- .optional()
- .describe("The new description of the label"),
- priority: z
- .number()
- .nullable()
- .optional()
- .describe("The new priority of the label"),
+ .describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
+ description: z.string().optional().describe("The new description of the label"),
+ priority: z.number().nullable().optional().describe("The new priority of the label"),
});
export const DeleteLabelSchema = z.object({
@@ -1163,10 +1066,7 @@ export const DeleteLabelSchema = z.object({
// Group projects schema
export const ListGroupProjectsSchema = z.object({
group_id: z.string().describe("Group ID or path"),
- include_subgroups: z
- .boolean()
- .optional()
- .describe("Include projects from subgroups"),
+ include_subgroups: z.boolean().optional().describe("Include projects from subgroups"),
search: z.string().optional().describe("Search term to filter projects"),
order_by: z
.enum(["name", "path", "created_at", "updated_at", "last_activity_at"])
@@ -1188,24 +1088,12 @@ export const ListGroupProjectsSchema = z.object({
.boolean()
.optional()
.describe("Filter projects with merge requests feature enabled"),
- min_access_level: z
- .number()
- .optional()
- .describe("Filter by minimum access level"),
- with_programming_language: z
- .string()
- .optional()
- .describe("Filter by programming language"),
+ min_access_level: z.number().optional().describe("Filter by minimum access level"),
+ with_programming_language: z.string().optional().describe("Filter by programming language"),
starred: z.boolean().optional().describe("Filter by starred projects"),
statistics: z.boolean().optional().describe("Include project statistics"),
- with_custom_attributes: z
- .boolean()
- .optional()
- .describe("Include custom attributes"),
- with_security_reports: z
- .boolean()
- .optional()
- .describe("Include security reports"),
+ with_custom_attributes: z.boolean().optional().describe("Include custom attributes"),
+ with_security_reports: z.boolean().optional().describe("Include security reports"),
});
// Add wiki operation schemas
@@ -1222,20 +1110,14 @@ export const CreateWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
title: z.string().describe("Title of the wiki page"),
content: z.string().describe("Content of the wiki page"),
- format: z
- .string()
- .optional()
- .describe("Content format, e.g., markdown, rdoc"),
+ format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
});
export const UpdateWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
slug: z.string().describe("URL-encoded slug of the wiki page"),
title: z.string().optional().describe("New title of the wiki page"),
content: z.string().optional().describe("New content of the wiki page"),
- format: z
- .string()
- .optional()
- .describe("Content format, e.g., markdown, rdoc"),
+ format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
});
export const DeleteWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
@@ -1272,7 +1154,9 @@ export const MergeRequestThreadPositionSchema = z.object({
export const CreateMergeRequestThreadSchema = ProjectParamsSchema.extend({
merge_request_iid: z.number().describe("The IID of a merge request"),
body: z.string().describe("The content of the thread"),
- position: MergeRequestThreadPositionSchema.optional().describe("Position when creating a diff note"),
+ position: MergeRequestThreadPositionSchema.optional().describe(
+ "Position when creating a diff note"
+ ),
created_at: z.string().optional().describe("Date the thread was created at (ISO 8601 format)"),
});
@@ -1280,12 +1164,27 @@ export const CreateMergeRequestThreadSchema = ProjectParamsSchema.extend({
// Schema for listing project milestones
export const ListProjectMilestonesSchema = ProjectParamsSchema.extend({
iids: z.array(z.number()).optional().describe("Return only the milestones having the given iid"),
- state: z.enum(["active", "closed"]).optional().describe("Return only active or closed milestones"),
- title: z.string().optional().describe("Return only milestones with a title matching the provided string"),
- search: z.string().optional().describe("Return only milestones with a title or description matching the provided string"),
+ state: z
+ .enum(["active", "closed"])
+ .optional()
+ .describe("Return only active or closed milestones"),
+ title: z
+ .string()
+ .optional()
+ .describe("Return only milestones with a title matching the provided string"),
+ search: z
+ .string()
+ .optional()
+ .describe("Return only milestones with a title or description matching the provided string"),
include_ancestors: z.boolean().optional().describe("Include ancestor groups"),
- updated_before: z.string().optional().describe("Return milestones updated before the specified date (ISO 8601 format)"),
- updated_after: z.string().optional().describe("Return milestones updated after the specified date (ISO 8601 format)"),
+ updated_before: z
+ .string()
+ .optional()
+ .describe("Return milestones updated before the specified date (ISO 8601 format)"),
+ updated_after: z
+ .string()
+ .optional()
+ .describe("Return milestones updated after the specified date (ISO 8601 format)"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page (max 100)"),
});
@@ -1309,7 +1208,10 @@ export const EditProjectMilestoneSchema = GetProjectMilestoneSchema.extend({
description: z.string().optional().describe("The description of the milestone"),
due_date: z.string().optional().describe("The due date of the milestone (YYYY-MM-DD)"),
start_date: z.string().optional().describe("The start date of the milestone (YYYY-MM-DD)"),
- state_event: z.enum(["close", "activate"]).optional().describe("The state event of the milestone"),
+ state_event: z
+ .enum(["close", "activate"])
+ .optional()
+ .describe("The state event of the milestone"),
});
// Schema for deleting a milestone
@@ -1337,44 +1239,30 @@ export const GetMilestoneBurndownEventsSchema = GetProjectMilestoneSchema.extend
export type GitLabAuthor = z.infer;
export type GitLabFork = z.infer;
export type GitLabIssue = z.infer;
-export type GitLabIssueWithLinkDetails = z.infer<
- typeof GitLabIssueWithLinkDetailsSchema
->;
+export type GitLabIssueWithLinkDetails = z.infer;
export type GitLabMergeRequest = z.infer;
export type GitLabRepository = z.infer;
export type GitLabFileContent = z.infer;
-export type GitLabDirectoryContent = z.infer<
- typeof GitLabDirectoryContentSchema
->;
+export type GitLabDirectoryContent = z.infer;
export type GitLabContent = z.infer;
export type FileOperation = z.infer;
export type GitLabTree = z.infer;
export type GitLabCommit = z.infer;
export type GitLabReference = z.infer;
-export type CreateRepositoryOptions = z.infer<
- typeof CreateRepositoryOptionsSchema
->;
+export type CreateRepositoryOptions = z.infer;
export type CreateIssueOptions = z.infer;
-export type CreateMergeRequestOptions = z.infer<
- typeof CreateMergeRequestOptionsSchema
->;
+export type CreateMergeRequestOptions = z.infer;
export type CreateBranchOptions = z.infer;
-export type GitLabCreateUpdateFileResponse = z.infer<
- typeof GitLabCreateUpdateFileResponseSchema
->;
+export type GitLabCreateUpdateFileResponse = z.infer;
export type GitLabSearchResponse = z.infer;
-export type GitLabMergeRequestDiff = z.infer<
- typeof GitLabMergeRequestDiffSchema
->;
+export type GitLabMergeRequestDiff = z.infer;
export type CreateNoteOptions = z.infer;
export type GitLabIssueLink = z.infer;
export type ListIssueDiscussionsOptions = z.infer;
export type UpdateIssueNoteOptions = z.infer;
export type CreateIssueNoteOptions = z.infer;
export type GitLabNamespace = z.infer;
-export type GitLabNamespaceExistsResponse = z.infer<
- typeof GitLabNamespaceExistsResponseSchema
->;
+export type GitLabNamespaceExistsResponse = z.infer;
export type GitLabProject = z.infer;
export type GitLabLabel = z.infer;
export type ListWikiPagesOptions = z.infer;
diff --git a/scripts/generate-tools-readme.ts b/scripts/generate-tools-readme.ts
index c311e0c..c1b1fcc 100644
--- a/scripts/generate-tools-readme.ts
+++ b/scripts/generate-tools-readme.ts
@@ -1,22 +1,22 @@
-import fs from 'fs';
-import path from 'path';
-import { fileURLToPath } from 'url';
+import fs from "fs";
+import path from "path";
+import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function main() {
- const repoRoot = path.resolve(__dirname, '..');
- const indexPath = path.join(repoRoot, 'index.ts');
- const readmePath = path.join(repoRoot, 'README.md');
+ const repoRoot = path.resolve(__dirname, "..");
+ const indexPath = path.join(repoRoot, "index.ts");
+ const readmePath = path.join(repoRoot, "README.md");
// 1. Read index.ts
- const code = fs.readFileSync(indexPath, 'utf-8');
+ const code = fs.readFileSync(indexPath, "utf-8");
// 2. Extract allTools array block
const match = code.match(/const allTools = \[([\s\S]*?)\];/);
if (!match) {
- console.error('Unable to locate allTools array in index.ts');
+ console.error("Unable to locate allTools array in index.ts");
process.exit(1);
}
const toolsBlock = match[1];
@@ -33,21 +33,21 @@ async function main() {
const lines = tools.map((tool, index) => {
return `${index + 1}. \`${tool.name}\` - ${tool.description}`;
});
- const markdown = lines.join('\n');
+ const markdown = lines.join("\n");
// 5. Read README.md and replace between markers
- const readme = fs.readFileSync(readmePath, 'utf-8');
+ const readme = fs.readFileSync(readmePath, "utf-8");
const updated = readme.replace(
/([\s\S]*?)/,
`\n${markdown}\n`
);
// 6. Write back
- fs.writeFileSync(readmePath, updated, 'utf-8');
- console.log('README.md tools section updated.');
+ fs.writeFileSync(readmePath, updated, "utf-8");
+ console.log("README.md tools section updated.");
}
main().catch(err => {
console.error(err);
process.exit(1);
-});
\ No newline at end of file
+});
diff --git a/scripts/validate-pr.sh b/scripts/validate-pr.sh
new file mode 100755
index 0000000..64d0f86
--- /dev/null
+++ b/scripts/validate-pr.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+# PR Validation Script
+# This script runs all necessary checks before merging a PR
+
+set -e
+
+echo "🔍 Starting PR validation..."
+
+# Check if Node.js is installed
+if ! command -v node &> /dev/null; then
+ echo "❌ Node.js is not installed"
+ exit 1
+fi
+
+echo "📦 Installing dependencies..."
+npm ci
+
+echo "🔨 Building project..."
+npm run build
+
+echo "🧪 Running unit tests..."
+npm run test:unit
+
+echo "✨ Checking code formatting..."
+npm run format:check || {
+ echo "⚠️ Code formatting issues found. Run 'npm run format' to fix."
+ exit 1
+}
+
+echo "🔍 Running linter..."
+npm run lint || {
+ echo "⚠️ Linting issues found. Run 'npm run lint:fix' to fix."
+ exit 1
+}
+
+echo "📊 Running tests with coverage..."
+npm run test:coverage
+
+# Check if integration tests should run
+if [ -n "$GITLAB_TOKEN" ] && [ -n "$TEST_PROJECT_ID" ]; then
+ echo "🌐 Running integration tests..."
+ npm run test:integration
+else
+ echo "⚠️ Skipping integration tests (no credentials provided)"
+fi
+
+echo "🐳 Testing Docker build..."
+if command -v docker &> /dev/null; then
+ docker build -t mcp-gitlab-test .
+ echo "✅ Docker build successful"
+else
+ echo "⚠️ Docker not available, skipping Docker build test"
+fi
+
+echo "✅ All PR validation checks passed!"
\ No newline at end of file
diff --git a/test-note.ts b/test-note.ts
index 25504aa..867bb94 100644
--- a/test-note.ts
+++ b/test-note.ts
@@ -33,9 +33,7 @@ async function testCreateIssueNote() {
if (!response.ok) {
const errorBody = await response.text();
- throw new Error(
- `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
- );
+ throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
const data = await response.json();
diff --git a/test/validate-api.js b/test/validate-api.js
new file mode 100755
index 0000000..2becbc2
--- /dev/null
+++ b/test/validate-api.js
@@ -0,0 +1,96 @@
+#!/usr/bin/env node
+
+// Simple API validation script for PR testing
+import fetch from "node-fetch";
+
+const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com";
+const GITLAB_TOKEN = process.env.GITLAB_TOKEN_TEST || process.env.GITLAB_TOKEN;
+const TEST_PROJECT_ID = process.env.TEST_PROJECT_ID;
+
+async function validateGitLabAPI() {
+ console.log("🔍 Validating GitLab API connection...\n");
+
+ if (!GITLAB_TOKEN) {
+ console.warn("⚠️ No GitLab token provided. Skipping API validation.");
+ console.log("Set GITLAB_TOKEN_TEST or GITLAB_TOKEN to enable API validation.\n");
+ return true;
+ }
+
+ if (!TEST_PROJECT_ID) {
+ console.warn("⚠️ No test project ID provided. Skipping API validation.");
+ console.log("Set TEST_PROJECT_ID to enable API validation.\n");
+ return true;
+ }
+
+ const tests = [
+ {
+ name: "Fetch project info",
+ url: `${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}`,
+ validate: data => data.id && data.name,
+ },
+ {
+ name: "List issues",
+ url: `${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/issues?per_page=1`,
+ validate: data => Array.isArray(data),
+ },
+ {
+ name: "List merge requests",
+ url: `${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/merge_requests?per_page=1`,
+ validate: data => Array.isArray(data),
+ },
+ {
+ name: "List branches",
+ url: `${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/repository/branches?per_page=1`,
+ validate: data => Array.isArray(data),
+ },
+ ];
+
+ let allPassed = true;
+
+ for (const test of tests) {
+ try {
+ console.log(`Testing: ${test.name}`);
+ const response = await fetch(test.url, {
+ headers: {
+ Authorization: `Bearer ${GITLAB_TOKEN}`,
+ Accept: "application/json",
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ if (test.validate(data)) {
+ console.log(`✅ ${test.name} - PASSED\n`);
+ } else {
+ console.log(`❌ ${test.name} - FAILED (invalid response format)\n`);
+ allPassed = false;
+ }
+ } catch (error) {
+ console.log(`❌ ${test.name} - FAILED`);
+ console.log(` Error: ${error.message}\n`);
+ allPassed = false;
+ }
+ }
+
+ if (allPassed) {
+ console.log("✅ All API validation tests passed!");
+ } else {
+ console.log("❌ Some API validation tests failed!");
+ }
+
+ return allPassed;
+}
+
+// Run validation
+validateGitLabAPI()
+ .then(success => process.exit(success ? 0 : 1))
+ .catch(error => {
+ console.error("Unexpected error:", error);
+ process.exit(1);
+ });
+
+export { validateGitLabAPI };